EasyHPC - PyTorch入门教程【笔记】

内容来源:超算习堂 (easyhpc.net)

文章目录

  • 01 Tensors
    • 环境要求
    • 1.1 Tensors
      • 1.1.1 直接创建tensor
      • 1.1.2 在现有tensor中创建tensor
      • 1.1.3 从NumPy中创建tensor
    • 1.2 基本运算
      • 1.2.1 使用运算符
      • 1.2.2 调用方法
    • 1.3 CUDA Tensors
  • 02 Autograd
    • 2.1 Tensor
    • 2.2 Gradient
  • 03 Neural Network
    • 3.1 网络的定义
    • 3.2 损失函数
    • 3.3 反向传播
    • 3.4 更新权重
  • 04 Training a Classifier
    • 4.1 使用torchvision下载和标准化CIFAR10数据集
    • 4.2 定义一个卷积神经网络
    • 4.3 定义损失函数以及相应的优化器
    • 4.4 在训练集上训练神经网络
    • 4.5 在测试集上测试模型性能
  • 05 Data Parallelism
    • 5.1 初始化参数
    • 5.2 创建一个随机的数据集
    • 5.3 DataParallel对象
    • 5.4 运行模型

01 Tensors

PyTorch是一个基于Python语言的科学计算库,主要的目标是:

  • 作为NumPy的替代者,可以有效使用GPU资源
  • 为深度学习研究者提供一个灵活、快速的开发平台。

环境要求

安装PyTorch根据不同的操作系统、语言等有多种安装方式,下面简单介绍Python3语言,基于pip安装PyTorch的流程:

1、安装python,pip:

sudo apt-get update
sudo apt-get install -y python3 python3-pip

2、安装不含CUDA的版本(0.4):

sudo pip3 install http://download.pytorch.org/whl/cpu/torch-0.4.0-cp36-cp36m-linux_x86_64.whl
sudo pip3 install torchvision

另外的安装命令可以查阅 Installing PyTorch

1.1 Tensors

Tensor是PyTorch类似于NumPy中ndarray的数据结构,特别之处在于tensor可以利用GPU进行加速计算。引入依赖包:

from __future__ import print_function
import torch
import numpy

1.1.1 直接创建tensor

创建5×3未初始化矩阵

x = torch.empty(5, 3)
print(x)

创建5×3随机初始化矩阵

x = torch.rand(5, 3)
print(x)

创建全零矩阵,且指定元素类型

x = torch.zeros(5, 3, dtype=torch.long)
print(x)

从列表开始创建tensor

x = torch.tensor([5.2, 1])
print(x)

1.1.2 在现有tensor中创建tensor

从现有的tensor中创建tensor(属性会被复用)

x = x.new_ones(5, 3, dtype=torch.double)
print(x)

x = torch.randn_like(x, dtype=torch.float)
print(x)

tensor的形状

print(x.size())

1.1.3 从NumPy中创建tensor

NumPy -> PyTorch

a = np.ones(5)
b = torch.from_numpy(a)
print (b)

PyTorch -> NumPy

a = torch.ones(5)
b = a.numpy()
print (b)

1.2 基本运算

Tensor在运算上支持多种语法,以下例子以加法进行展开

1.2.1 使用运算符

x = torch.ones(5, 3)
y = torch.ones(5, 3)
print (x + y)

1.2.2 调用方法

print (torch.add(x, y))

提供一个输出的tensor

result = torch.empty(5, 3)
torch.add(x, y, out=result)
print (result)

x加到y

y.add_(x)
print (y)

方法中带_后缀的,都是in-place的操作

Pytorch中inplace操作_二十米的博客-CSDN博客

1.3 CUDA Tensors

当设备允许时,tensor对象可以调用.to方法将数据移动到其它设备上。

# let us run this cell only if CUDA is available
# We will use ``torch.device`` objects to move tensors in and out of GPU
if torch.cuda.is_available():
    device = torch.device("cuda")          # a CUDA device object
    y = torch.ones_like(x, device=device)  # directly create a tensor on GPU
    x = x.to(device)                       # or just use strings ``.to("cuda")``
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))       # ``.to`` can also change dtype together!

02 Autograd

autograd包是PyTorch中构建神经网络的核心。

autograd包提供了在tensor上自动求导的操作,并且它是一个“define-by-run”的框架,也就是说,代码的执行顺序决定了反向传播(backprop)的过程

2.1 Tensor

torch.Tensor对象是操作的核心

  • 当你把tensor的.requires_grad属性设为True,那框架就会开始追踪你在tensor上的所有操作;
  • 当你需要完成计算时,你可以调用.backward()方法,然后框架将自动完成梯度的计算;
  • 在这个tensor上的梯度会累积到.grad属性中。

为了停止tensor追踪操作的行为(对内存的消耗),

  • 你可以手动调用.detach()使得计算历史从tensor中分离出来,同时tensor以后的计算过程也不会遭到记录。
  • 除此之外,还可以用with torch.no_grad():来包裹代码块,这在验证模型的过程中特别有用,因为有些模型可能会含有可训练的参数(requires_grad=True),但是显然我们不需要计算它的梯度。

另外一个重要的概念是Function

  • TensorFunction是相互联系的,它们共同建立了一个非循环的计算图;
  • 其中的每一个变量都有一个.grad_fn属性,该属性引用了一个已经创建了TensorFunction

Tensor是一个标量时(只有一个元素),你不需要指定tensor的形状,但是当tensor含有多个元素时则需要显式指定匹配的形状

创建一个记录计算历史的tensor

import torch
x = torch.ones(2, 2, requires_grad=True)

进行一次运算,查看与之相关的Function对象

y = x + 1
print (y.grad_fn)

2.2 Gradient

进行更复杂的计算

z = y * y * 3
out = z.mean()

现在来进行计算梯度的实验,计算out的梯度,即: d ( o u t ) d ( x ) \frac{d(out)}{d(x)} d(x)d(out)

out.backward()
print(x.grad)

计算过程是

o u t = 1 2 × 2 ∑ i z i = 3 4 ∑ i ( x i + 1 ) 2 out = \frac{1}{2 \times 2} \sum_{i} z_i = \frac{3}{4} \sum_{i} (x_i + 1)^2 out=2×21izi=43i(xi+1)2

∂ o u t ∂ x i = 3 2 ( x i + 1 ) \frac{\partial_{out}}{\partial_{x_i}} = \frac{3}{2}(x_i + 1) xiout=23(xi+1)

∂ o u t ∂ x i ∣ x i = 1 = 3 \left. \frac{\partial_{out}}{\partial_{x_i}}\right|_{x_i=1} = 3 xiout xi=1=3

使用with torch.no_grad():可以停止自动求导

print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
    print((x ** 2).requires_grad)

03 Neural Network

PyTorch中神经网络可以通过torch.nn包进行构建,nn依赖于autograd对模型进行定义及其对神经网络的求导nn.Module包括了神经网络的层级设计,以及名为一个forward(input)的调用方法,该方法会返回一个output对象。

以图像分类网络为例:

这是一个前馈神经网络,网络接受一个输入input,然后计算结果一层一层往后传递,最终得到输出结果output

一个神经网络典型的训练过程应该包括:

  • 定义神经网络(应包含可学习的模型参数/权值)
  • 将数据集迭代地输入神经网络
  • 在神经网络中,逐层地计算输入的数据
  • 计算最后的损失(真实值与预测值之间的差距)
  • 梯度的反向传播
  • 更新网络的参数,常见的更新规则: w e i g h t = w e i g h t − l e a r n i n g _ r a t e ∗ g r a d i e n t weight = weight - learning\_rate * gradient weight=weightlearning_rategradient

3.1 网络的定义

一个网络定义的例子:

import torch
import torch.nn as nn
import torch.nn.functional as F

# 这段代码定义了一个神经网络模型类 `Net`,继承自 `nn.Module`。
# 该模型包含两个卷积层和三个全连接层,用于对图像进行分类。
class Net(nn.Module):

		# 初始化函数
    def __init__(self):
        super(Net, self).__init__()

				# 定义了两个卷积层 `self.conv1` 和 `self.conv2`,
				# 分别将输入的单通道图像转换为6通道的特征图和16通道的特征图。
				# 这两个卷积层的卷积核大小均为5x5。
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)

        # an affine operation: y = Wx + b
				# 定义了三个全连接层 `self.fc1`、`self.fc2` 和 `self.fc3`,
				# 分别将输入的特征图转换为120维、84维和10维的向量。
				# 这三个全连接层的作用是将卷积层提取的特征进行分类。
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

		# 在前向传播函数 `forward` 中
    def forward(self, x):

				# 首先对输入的图像进行卷积操作,并使用 ReLU 激活函数进行非线性变换。
				# 然后使用 2x2 的最大池化操作对特征图进行降维,以减小模型的参数量。

        # Max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # If the size is a square you can only specify a single number
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)

				# 在实际使用中,这两种方式的效果是一样的
				# 第一个代码中使用了元组的形式,更加明确了池化窗口的大小,也更加灵活,可以使用不同大小的池化窗口。
				# 第二个代码中使用了整数的形式,更加简洁,但是不够灵活,只能使用固定大小的池化窗口。

				# 接着将特征图展开成一维向量,传入三个全连接层中,最后输出分类结果。
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

		# 定义了一个辅助函数 `num_flat_features`,用于计算特征图展开后的向量维度。
		# 这个函数的作用是帮助自动计算全连接层的输入维度。
    def num_flat_features(self, x):
        size = x.size()[1:]  # all dimensions except the batch dimension
        num_features = 1
        for s in size:
            num_features *= s
        return num_features

net = Net() # 创建了一个 `Net` 的实例 `net`
print(net) # 输出该模型的结构。

输出结果:

Net(
  (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear(in_features=400, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)

仅需要定义好forward函数,求导过程将会自动开始执行。想要获得模型中可学习的参数,可调用net.parameters()函数:

# 通过 `net.parameters()` 获取神经网络中的所有可学习参数,返回一个包含所有参数的迭代器。然后,将迭代器转换成列表,便于后续操作。
params = list(net.parameters())

# 打印出参数的数量,即神经网络中可学习参数的个数。
print(len(params))

# 打印出第一个参数的形状,即第一个可学习参数的大小,通常是一个张量。
print(params[0].size())  # conv1's .weight

输出结果:

10
torch.Size([6, 1, 5, 5])

将32×32大小的随机数矩阵作为网络的输入:

# nSamples x nChannels x Height x Width
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)

输出结果:

tensor([[-0.0089, -0.0514,  0.0059,  0.1412, -0.1543,  0.0494, -0.0966,
         -0.1150, -0.0986, -0.1103]])

将所有参数的梯度buffer置零,然后将随机梯度反向传播:

# `net.zero_grad()` 用于清空网络中所有参数的梯度,以便进行新的反向传播计算。
net.zero_grad()

# `out.backward(torch.randn(1, 10))` 是对 `out` 进行反向传播计算,并且传入了一个随机张量作为 `out` 关于某些标量的梯度。
# 这里的随机张量相当于是一个虚拟的损失值,用于计算梯度。在实际的训练中,这个随机张量应该被替换成真实的损失值的梯度。
out.backward(torch.randn(1, 10))

3.2 损失函数

损失函数将(预测值,真实值)作为输入,然后计算评估模型与真实情况之间的差距。loss functions列出了nn包中可用的损失函数。

output = net(input) # torch.Size([1, 10])
target = torch.arange(1, 11)  # a dummy target, for example
target = target.view(1, -1)  # make it the same shape as output
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)

这段代码用于计算模型预测结果与目标值之间的均方误差(MSE)损失。具体来说:

  • output = net(input):使用神经网络模型net对输入input进行预测,得到输出output

  • target = torch.arange(1, 11):生成一个1到10的序列作为目标值,这里只是为了演示,实际应用中通常是根据具体任务定义的。

  • target = target.view(1, -1):将目标值的形状调整为与输出output相同,即(1, 10)

    在这里,target张量原本是一个形状为(10,)的一维张量,表示模型需要预测的目标值。调用view(1, -1)方法将其形状调整为(1, 10)的二维张量,其中第一维大小为1,表示这是一个批次大小为1的输入数据。第二维大小为10,与output张量的第二维大小相同,表示模型需要预测的10个目标值。

    需要注意的是,view()方法只是改变了张量的形状,而不改变张量中的元素。因此,target张量中的元素顺序不变,只是被重新排列成了一个二维张量。

  • criterion = nn.MSELoss():定义一个均方误差(MSE)损失函数

  • loss = criterion(output, target):计算预测输出output与目标值target之间的均方误差损失。

输出结果:

tensor(39.2273)

然后当你调用loss.grad_fn属性时,你可以看到loss上的计算图:

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
         -> view -> linear -> relu -> linear -> relu -> linear
         -> MSELoss
         -> loss

更具体一点可以一步步往回输出:

print(loss.grad_fn)  # MSELoss
print(loss.grad_fn.next_functions[0][0])  # Linear
print(loss.grad_fn.next_functions[0][0].next_functions[0][0])  # ReLU

在PyTorch中,每个张量都有一个.grad_fn属性,代表它的梯度计算函数。对于计算图中的每个节点(操作),都有一个对应的梯度计算函数,这些函数被存储在.grad_fn属性中。这些梯度计算函数构成了计算图,用于自动求导

对于上面的代码,loss是由nn.MSELoss()计算得到的,因此loss.grad_fn是一个MSELoss对象,代表均方误差损失函数

loss.grad_fn.next_functions是一个元组,包含了计算图中与该节点相连的下一层节点的梯度计算函数。在这个例子中,loss的下一层节点是一个Linear操作,因此loss.grad_fn.next_functions[0][0]是一个Linear对象,代表线性变换操作。

同理,loss.grad_fn.next_functions[0][0].next_functions[0][0]代表的是Linear操作的下一层节点,即ReLU操作。

3.3 反向传播

为了实现反向传播,我们需要调用函数loss.backward()在调用之前需要清空梯度的buffer,否则梯度会被累加

net.zero_grad()     # zeroes the gradient buffers of all parameters

print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)

loss.backward()

print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)

这段代码主要是用于梯度计算和反向传播的过程。具体来说:

  • net.zero_grad():将模型net的所有参数的梯度缓冲区清零,以避免梯度累加的影响。

    梯度累加指在一次迭代中多次计算梯度,然后将这些梯度相加,最后再更新模型参数。这种方法可以在内存有限的情况下,使用更大的batch size,从而加快训练速度。

    但是,梯度累加也有一些缺点。首先,它会占用更多的内存,因为需要存储多个梯度。其次,它会导致训练过程中的噪声增加,因为每个梯度都是在不同的batch上计算得到的,这些batch可能存在差异。最重要的是,梯度累加会导致模型更新不稳定,因为多个梯度相加可能会产生较大的梯度值,从而导致模型参数更新过大或不稳定。

    因此,为了避免这些问题,我们通常不建议使用梯度累加,而是使用更大的GPU内存或者分布式训练等方法来加速训练。

  • print('conv1.bias.grad before backward'):输出卷积层conv1的偏置项bias的梯度值,此时梯度值为0。

  • loss.backward():调用损失函数lossbackward()方法进行反向传播,计算模型各参数相对于损失函数的梯度。

  • print('conv1.bias.grad after backward'):输出卷积层conv1的偏置项bias的梯度值,此时梯度值已经被计算出来。

  • print(net.conv1.bias.grad):输出卷积层conv1的偏置项bias的梯度值,即反向传播计算得到的梯度值。

通过这段代码,我们可以了解到反向传播的过程:在前向传播计算出模型的输出后,根据输出和目标值计算出损失函数的值,然后通过反向传播计算出模型各参数相对于损失函数的梯度,最终利用梯度下降等优化算法来更新模型参数,使得损失函数的值逐渐减小,从而提高模型的预测准确率。

报错解决:RuntimeError: Found dtype Long but expected Float_唐僧爱吃唐僧肉的博客-CSDN博客

输出结果:

conv1.bias.grad before backward
tensor([ 0.,  0.,  0.,  0.,  0.,  0.])
conv1.bias.grad after backward
tensor([ 0.0501,  0.1040, -0.1200,  0.0833,  0.0081,  0.0120])

3.4 更新权重

在实际场景中,最简单实用的更新规则是随机梯度下降 w e i g h t = w e i g h t − l e a r n i n g r a t e ∗ g r a d i e n t weight = weight - learning_rate * gradient weight=weightlearningrategradient

实现的代码如下:

learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate) # sub_表示in-place操作,即直接对f.data进行修改

这段代码用于手动更新神经网络的参数,具体来说:

  • learning_rate = 0.01:设置学习率为0.01,即每次更新的步长。
  • for f in net.parameters()::遍历神经网络net的所有参数。
  • f.data.sub_(f.grad.data * learning_rate):使用梯度下降法(Gradient Descent)更新参数,即将参数的值减去学习率乘以其对应的梯度值。这里使用了sub_()函数实现就地减法操作,即直接在原有的参数值上进行更新。

通过手动更新参数,可以实现自定义的优化算法,而不是使用预定义的优化器(如SGD、Adam等)。但是需要注意的是,手动更新参数需要对参数的梯度进行手动计算,这对于复杂的神经网络来说是非常困难的。因此,通常情况下我们会使用PyTorch提供的优化器来自动更新参数,从而简化优化过程。

sub_()Tensor类中的一个函数,用于将Tensor对象中的所有元素减去一个给定的标量或另一个Tensor对象中的元素。

在这里,f是一个Parameter对象,它是Tensor的子类,表示神经网络模型中的可训练参数。f.dataf的值,是一个Tensor对象,它存储了f的值。f.grad也是一个Tensor对象,它存储了f关于损失函数的梯度值。

需要注意的是,在每次更新参数之后,我们需要手动将梯度缓存清零,以避免梯度累加的影响。这可以通过调用optimizer.zero_grad()来实现。

在实际中,你可能还会用到其它的更新方式,一些常用的更新算法(Nesterov-SGD,Adam,RMSProp等)都已经被预先实现在torch.optim包中,使用方法如下:

import torch.optim as optim

# create your optimizer
optimizer = optim.SGD(net.parameters(), lr=0.01)

# in your training loop:
optimizer.zero_grad()   # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # Does the update

这段代码演示了使用torch.optim中的SGD优化器来更新神经网络模型的参数。具体来说:

  • optimizer = optim.SGD(net.parameters(), lr=0.01):创建一个SGD优化器,将神经网络模型net的参数作为待优化参数传递给优化器,并设置学习率为0.01
  • optimizer.zero_grad():将优化器中所有参数的梯度缓存清零。
  • output = net(input):使用神经网络模型net对输入input进行预测,得到输出output
  • loss = criterion(output, target):计算预测输出output与目标值target之间的损失。
  • loss.backward():计算损失函数对模型参数的导数。
  • optimizer.step():根据计算得到的梯度更新模型参数,即执行参数更新操作。

SGD(Stochastic Gradient Descent)是深度学习中常用的优化算法之一,也是最基础的优化算法之一。它的基本思想是在每个训练步骤中,随机选择一小部分样本计算梯度,通过梯度下降的方式更新模型权重,以最小化损失函数。

在PyTorch中,可以使用optim.SGD类来实现SGD优化器。optim.SGD类的构造函数接受两个参数:

  • params:需要优化的参数列表,可以通过net.parameters()方法获取。
  • lr:学习率,即每次梯度下降时的步长大小。

在训练过程中,可以通过调用optimizer.zero_grad()方法来清空所有参数的梯度,然后计算模型输出和损失,调用loss.backward()方法计算梯度,最后调用optimizer.step()方法执行一步梯度下降更新模型参数。

04 Training a Classifier

本实验将沿以下步骤展开:

4.1 使用torchvision下载和标准化CIFAR10数据集

Torchvision是PyTorch的一个包,它包含了一些用于计算机视觉任务的实用函数和数据集。它提供了一些预处理图像的工具,可以用于数据增强和训练数据的准备。此外,它还提供了一些经典的计算机视觉模型,如AlexNet、VGG、ResNet和Inception等,可以用于图像分类、目标检测、分割和生成等任务。

可以使用以下代码查看torch和torchvision的版本:

import torch
import torchvision

print("Torch version:", torch.__version__)
print("Torchvision version:", torchvision.__version__)

# 输出结果
# Torch version: 2.0.0
# Torchvision version: 0.15.0

使用torchvision载入CIFAR10

CIFAR-10是一个常用的图像分类数据集,由10个类别的60000张32x32彩色图像组成,每个类别有6000张图像。这些类别包括飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船和卡车。数据集被分为训练集和测试集,其中训练集包含50000张图像,测试集包含10000张图像。CIFAR-10数据集是计算机视觉领域中最常用的数据集之一,被广泛用于图像分类、目标识别、图像分割等任务的研究和算法评估。CIFAR10数据集的大小约为163 MB。

import torch
import torchvision
import torchvision.transforms as transforms

这段代码是用来导入PyTorch深度学习框架及其相关的库和模块,其中

  • torch是PyTorch的核心库,提供了各种张量操作自动求导功能;
  • torchvision是PyTorch的视觉库,提供了图像数据处理、数据加载和预处理等功能;
  • transforms是torchvision库中的一个模块,提供了各种图像数据预处理的方法,如缩放、裁剪、旋转、翻转、标准化等。

通过导入这些库和模块,可以方便地进行深度学习任务,尤其是图像分类任务。

torchvision的数据集输出是PILImage图像(范围[0,1]),我们需要将其转换为Tensor对象接受的范围([-1,1])

transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
                                          shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
                                         shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

这段代码是在使用PyTorch进行图像分类任务时的数据预处理部分。具体来说,这段代码使用了PyTorch中的torchvision模块来加载CIFAR-10数据集,并对数据集进行了一些预处理操作。

其中,transforms.Compose()函数用于将多个数据预处理操作组合在一起,这里使用了两个操作:

  • ToTensor()将数据转换为张量格式,
  • Normalize()对数据进行标准化处理,使得数据的均值为0.5,标准差为0.5。

接下来,使用torchvision.datasets.CIFAR10()函数来加载CIFAR-10数据集,其中train=True表示加载训练集train=False表示加载测试集。同时,通过transform参数传入上一步定义的数据预处理操作

最后,使用torch.utils.data.DataLoader()函数将数据集转化为可迭代的数据加载器,用于训练和测试模型。其中batch_size参数表示每次迭代加载的数据量shuffle参数表示是否打乱数据顺序num_workers参数表示使用多少个进程来加载数据

最后,定义了一个classes列表,其中包含了CIFAR-10数据集中的10个类别。

结果输出:

Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./data\cifar-10-python.tar.gz
100.0%
Extracting ./data\cifar-10-python.tar.gz to ./data
Files already downloaded and verified

第一次运行时,程序会从指定的链接下载CIFAR-10数据集的压缩文件,并将其保存到本地目录./data下。下载完成后,程序会自动将压缩文件解压./data目录下,然后就可以使用PyTorch提供的数据集类torchvision.datasets.CIFAR10加载数据集了。

第二次运行时,因为数据集已经下载并解压到了本地,所以程序会直接跳过下载和解压的步骤,而是使用已经下载好的数据集进行训练和测试。因此,程序会输出"Files already downloaded and verified"。

4.2 定义一个卷积神经网络

# 将上一节实验中神经网络的定义稍作修改(单通道改成3通道)
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5) # 输入通道数为3,输出通道数为6,卷积核大小为5x5
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5) # 输入通道数为6,输出通道数为16,卷积核大小为5x5
        
				# 最后一层卷积层的输出是一个大小为16x5x5的张量,其中16表示卷积核的数量,5x5表示每个卷积核的输出大小
				self.fc1 = nn.Linear(16 * 5 * 5, 120) 
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5) # 将16x5x5的特征图展开成一维向量
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()

4.3 定义损失函数以及相应的优化器

简单起见,我们使用最简单的交叉熵损失函数(Cross-Entropy)和随机梯度下降(SGD)优化模型:

import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9) 
# 学习率控制了每一次参数更新的幅度
# 动量则是一种加速算法,可以加快模型收敛的速度。

4.4 在训练集上训练神经网络

我们迭代地从trainloader对象中取数据,然后将数据输入到神经网络中,得到输出,计算损失,不断地优化参数:

# 对数据集进行多次循环训练(每个循环称为一个 epoch)
for epoch in range(2):  

    running_loss = 0.0

		# 遍历 trainloader 中的每个 batch,对 batch 中的数据进行训练并更新参数。
    for i, data in enumerate(trainloader, 0):
        
				# get the inputs
				# 遍历训练数据集中的每个 batch,
				# 每个 batch 包含一个 inputs 和一个 labels。
        inputs, labels = data

        # zero the parameter gradients
				# 将 optimizer 的梯度清零,以避免梯度累加
        optimizer.zero_grad()

        # forward + backward + optimize
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward() # 反向传播误差,计算每个参数的梯度
        optimizer.step() # 根据梯度下降算法更新参数值

        # print statistics
				# 在训练神经网络时用来监控训练过程的损失函数值的变化情况
        running_loss += loss.item()
        if i % 2000 == 1999:    # print every 2000 mini-batches
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0

print('Finished Training')

在训练神经网络时用来监控训练过程的损失函数值的变化情况

  1. running_loss += loss.item():将当前 batch 的损失函数值加到 running_loss 变量中,loss.item() 是获取当前 batch 的损失函数值的方法。
  2. if i % 2000 == 1999::当当前 batch 的索引值 i 是 2000 的倍数减1时,执行下面的代码,即每训练完 2000 个 mini-batches 就输出一次损失函数值
  3. print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000)):输出当前训练 epoch 和 mini-batch 的索引值以及当前的平均损失函数值,其中 %d 表示整数占位符,%5d 表示占 5 个字符的整数占位符,%.3f 表示保留 3 位小数的浮点数占位符。
  4. running_loss = 0.0:将 running_loss 变量清零,以便下一次计算平均损失函数值

结果输出:

[1,  2000] loss: 2.199
[1,  4000] loss: 1.856
[1,  6000] loss: 1.688
[1,  8000] loss: 1.606
[1, 10000] loss: 1.534
[1, 12000] loss: 1.488
[2,  2000] loss: 1.420
[2,  4000] loss: 1.384
[2,  6000] loss: 1.336
[2,  8000] loss: 1.351
[2, 10000] loss: 1.309
[2, 12000] loss: 1.277
Finished Training

报错解决:解决RuntimeError: An attempt has been made to start a new process before…办法 - 知乎 (zhihu.com)

4.5 在测试集上测试模型性能

我们在训练数据集上训练了两轮,现在来验证模型的实际效果。

correct = 0 # 记录正确预测的数量
total = 0 # 记录总共预测的数量
with torch.no_grad(): # 使用`torch.no_grad()`上下文管理器,关闭梯度计算,以减少内存消耗和加速计算
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs.data, 1) # 获取每个样本在每个类别上的分数,然后选取分数最高的类别作为预测结果
        total += labels.size(0) # 将当前批次中的样本数量累加到总样本数中
        correct += (predicted == labels).sum().item()

# 在 `(predicted == labels)` 中,`predicted == labels` 的结果是一个由 True 和 False 组成的张量,表示模型预测的结果是否正确。`.sum()` 会将所有 True 的数量加起来,即表示正确的样本数。`.item()` 则将这个数量转化为 Python 中的标量值,方便打印输出。

print('Accuracy of the network on the 10000 test images: %d %%' % (
    100 * correct / total)) # %d %%用于将正确率格式化为整数百分比形式

# `%%` 表示输出一个百分号。这是因为在 Python 中,如果要输出一个百分号,需要使用两个百分号来转义。

这段代码是用来计算神经网络在测试集上的准确率的。具体来说,代码中的for循环遍历了测试集中的所有数据,然后将数据输入到神经网络中进行预测。对于每个预测结果,代码通过torch.max函数取出最大值及其对应的索引,即预测的标签。然后将预测的标签与真实标签进行比较,如果相同,则认为预测正确,将正确预测的数量累加到correct变量中。最后,代码计算正确率并输出。

torch.no_grad()是一个上下文管理器(context manager),用于在执行代码时禁用梯度计算。在这个上下文管理器内部,PyTorch将不会跟踪计算图中的梯度信息,这样可以提高代码的执行效率,特别是在测试模型时,不需要计算梯度,可以加快模型的推理速度。在训练模型时,需要计算梯度,所以不需要使用torch.no_grad()

torch.max() 函数返回输入张量中所有元素的最大值和相应的索引。在这里,outputs.data网络输出的结果,第二个参数 1 指定了在第一个维度上进行最大值计算,即在每个样本的输出结果中找到最大的那个值及其对应的索引。

_ 是一个占位符,表示我们不需要使用这个值,只需要获取最大值对应的索引,即 predicted。在 Python 中,通常使用 _ 表示一个变量的值不需要使用

举个例子,如果 outputs.data 的形状是 (batch_size, num_classes),那么 torch.max(outputs.data, 1) 的返回值是一个元组包含两个张量:第一个张量是每个样本的最大值,形状是 (batch_size,);第二个张量是每个样本最大值对应的索引,形状也是 (batch_size,)predicted 就是这个索引张量。

结果输出:

Accuracy of the network on the 10000 test images: 53 %

53%的准确率对比随机猜一个结果(1/10)还是要好一些的;接下来再看看具体到某一类上,准确率到底表现如何:

class_correct = list(0. for i in range(10)) # 定义一个长度为10的列表,初始化为0,用于存储每个类别的正确分类数
class_total = list(0. for i in range(10)) # 定义一个长度为10的列表,初始化为0,用于存储每个类别的样本总数
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs, 1)
        c = (predicted == labels).squeeze() # 由于张量c的维度可能为1,使用squeeze()函数将数组中维度为1的维度去掉
        for i in range(4): # 遍历每个batch中的前4个样本
				# `DataLoader`在每次遍历数据集时都会对数据进行随机打乱,以增加模型的泛化能力
				# 所以每次获取到的`data`中的数据是随机的,因此range(4)获取的数据也是随机的
            label = labels[i] # 获取当前样本的标签
            class_correct[label] += c[i].item() # 如果当前样本被正确分类,则将对应类别的正确分类数加1
            class_total[label] += 1 # 将对应类别的样本总数加1
 
for i in range(10):
    print('Accuracy of %5s : %2d %%' % (
        classes[i], 100 * class_correct[i] / class_total[i]))

在这个代码段中,(predicted == labels)是一个维度为1的张量,其中每个元素对应一个测试样本的预测是否正确。然而,在后面的代码中,我们需要将c与标签label对应起来,以便在class_correctclass_total中更新对应类别的计数器。因此,我们需要将c从维度为1的张量转换为一个标量,以便在后面的代码中进行索引。这里使用了.squeeze()函数将维度为1的张量压缩为标量。

data是由DataLoader迭代器返回的一个batch数据,而batch是指一次性输入到神经网络中的一组数据,通常是多个数据样本一起组成的一个张量。在深度学习中,通常我们为了提高训练效率,会将数据集分成若干个batch,每个batch包含多个数据样本,然后将每个batch依次输入到神经网络中进行训练。

每个data对应一个batch,而range(4)是指每个batch中的前4个数据。在这个代码片段中,这个循环只迭代了每个batch中的前4个数据,因为我们只想查看每个类别的前4个预测结果。如果需要查看整个batch的结果,可以将循环范围改为range(batch_size),其中batch_size是batch大小。

输出结果:

Accuracy of plane : 60 %
Accuracy of   car : 75 %
Accuracy of  bird : 33 %
Accuracy of   cat : 50 %
Accuracy of  deer : 26 %
Accuracy of   dog : 47 %
Accuracy of  frog : 54 %
Accuracy of horse : 66 %
Accuracy of  ship : 48 %
Accuracy of truck : 70 %

05 Data Parallelism

在PyTorch利用GPU资源其实很便捷,首先指定GPU:

device = torch.device("cuda:0")

然后将model迁移到GPU上:

model.to(device)

tensor也迁移到GPU上:

tensor = tensor.to(device)

最后将模型放到并行环境(DataParallel)中,

model = nn.DataParallel(model)

以上就是PyTorch中数据并行的核心思路。

5.1 初始化参数

引入依赖包、定义初始化参数:

import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader

# Parameters and DataLoaders
input_size = 5
output_size = 2

batch_size = 30
data_size = 100

# Device
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

5.2 创建一个随机的数据集

简单实现__getitem__方法即可:

class RandomDataset(Dataset): # RandomDataset类继承了PyTorch中的Dataset类

    def __init__(self, size, length):
        self.len = length
        self.data = torch.randn(length, size)

    def __getitem__(self, index):
        return self.data[index]

    def __len__(self):
        return self.len

rand_loader = DataLoader(dataset=RandomDataset(input_size, 100),
                         batch_size=batch_size, shuffle=True)
  • init()方法初始化RandomDataset类的实例。它接受两个参数:size和length。其中,size表示每个数据样本的大小,length表示数据集的大小。在这个方法中,我们使用torch.randn()方法生成一个大小为(length, size)的随机数据集,作为RandomDataset的数据。
  • getitem()方法用于获取数据集中的一个数据样本。它接受一个index参数,表示要获取的数据样本的索引。在这个方法中,我们直接返回数据集中的第index个数据样本。
  • len()方法返回数据集的大小。
  • 最后,我们使用DataLoader类来创建一个名为rand_loader的数据加载器。它接受一个RandomDataset类的实例作为数据集,batch_size参数表示每个mini-batch的大小,shuffle参数表示是否对数据进行洗牌。在这个例子中,我们将数据集洗牌,并将每个mini-batch的大小设置为batch_size=30。

5.3 DataParallel对象

对于这个简单demo,我们的模型中只有一层线性变换,但是DataParallel是可以应用于任一模型上的(包括CNN、RNN、Capsule Net等)。

建立一个简单线性模型:

class Model(nn.Module):
    # Our model

    def __init__(self, input_size, output_size):
        super(Model, self).__init__()
        self.fc = nn.Linear(input_size, output_size)

    def forward(self, input):
        output = self.fc(input)
        print("\tIn Model: input size", input.size(),
              "output size", output.size())

        return output

(如果存在GPU)将模型迁移到GPU上:

model = Model(input_size, output_size)
if torch.cuda.device_count() > 1:
  print("Let's use", torch.cuda.device_count(), "GPUs!")
  # dim = 0 [30, xxx] -> [10, ...], [10, ...], [10, ...] on 3 GPUs
  model = nn.DataParallel(model)

model.to(device)

5.4 运行模型

正式运行模型:

for data in rand_loader:
    input = data.to(device)
    output = model(input)
    print("Outside: input size", input.size(),
          "output_size", output.size())

运行在CPU上时,可以看到:

In Model: input size torch.Size([30, 5]) output size torch.Size([30, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
        In Model: input size torch.Size([30, 5]) output size torch.Size([30, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
        In Model: input size torch.Size([30, 5]) output size torch.Size([30, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
        In Model: input size torch.Size([10, 5]) output size torch.Size([10, 2])
Outside: input size torch.Size([10, 5]) output_size torch.Size([10, 2])

运行在2个GPU上时,可以看到:

Let's use 2 GPUs!
    In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
    In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
    In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
    In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
    In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
    In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
    In Model: input size torch.Size([5, 5]) output size torch.Size([5, 2])
    In Model: input size torch.Size([5, 5]) output size torch.Size([5, 2])
Outside: input size torch.Size([10, 5]) output_size torch.Size([10, 2])

因为tensor的维度从[30, xx]变成了2个GPU上的[15, xx]和[15, xx]。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/621180.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

shell脚本中条件语句

一.test测试 在Shell脚本中,test命令用于进行条件测试。它也可以通过方括号[]来表示,因为test实际上是[命令的一个别名。 格式1:test 条件表达式 格式2:[ 条件表达式 ] 注意[ ]空格,否则会失败 测试 是否成功使用…

永嘉原厂8×16点阵数码管驱动抗干扰数码管驱动IC防干扰数显芯片VK1640 SOP28

产品型号:VK1640 产品品牌:永嘉微电/VINKA 封装形式:SOP28 原厂,工程服务,技术支持! 概述 VK1640是一种数码管或点阵LED驱动控制专用芯片,内部集成有数据锁存器、LED 驱动等电路。SEG脚接LE…

netstat命令详解

netstat网络连接分析工具 工具说明: netstat 是一款命令行工具,主要是用于列出系统上所有的网络套接字连接情况,包括 tcp, udp 以及 unix 套接字,另外它还能列出处于监听状态(即等待接入请求)的套接字。除…

SpringBoot集成Curator实现Watch事件监听

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 Zookeeper是一个Ap…

【MySQL】常见的数据类型

欢迎来到Cefler的博客😁 🕌博客主页:折纸花满衣 🏠个人专栏:MySQL 目录 👉🏻常见的数据类型bit类型enumset集合查询函数find_ in_ set 👉🏻浮点类型float类型decimal &am…

用友U8与旺店通的对接案例分析

在现代企业管理中,财务管理和电商运营管理是企业数字化转型的两个重要组成部分。用友U8作为企业的财务管理系统,与旺店通这一电商ERP系统的结合,可以为企业带来全面的数据整合和流程自动化。本文将通过轻易云集成平台的视角,分析用…

weblogic [WeakPassword]

一、漏洞描述 开放了wblogic端口,进去发现有任意读取文件漏洞,配合解密工具读出密码,登录后台传入webshell 二、影响版本 当前版本 三、影响组件 weblogic 四、漏洞判断 hello/file.jsp?path/etc/passwd 发现有任意文件下载 五、漏洞…

47.全排列

1.题目 47. 全排列 II - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/permutations-ii/description/ 2.思路 注意剪枝的条件 3.代码 class Solution {vector<int> path;vector<vector<int>> ret;bool check[9]; public:vector<…

防伪溯源一体化管理系统基于FastAdmin+ThinkPHP和Uniapp(源码搭建/上线/运营/售后/维护更新)

一款基于FastAdminThinkPHP和Uniapp进行开发的多平台&#xff08;微信小程序、H5网页&#xff09;溯源、防伪、管理一体化独立系统&#xff0c;拥有强大的防伪码和溯源码双码生成功能&#xff08;内置多种生成规则&#xff09;、批量大量导出防伪和溯源码码数据、支持代理商管理…

JavaScript数组(Array)方法 - toReversed、toSorted、toSpliced

最近发现几个数组方法&#xff0c;是一些常规方法的升级版&#xff0c;比较有意思&#xff0c;分享给大家 文章目录 一、温故二、知新toReversedtoSortedtoSpliced 一、温故 我们先来回顾几个比较常用的方法&#xff1a;reverse&#xff0c;sort&#xff0c;splice众所周知&a…

信号和槽基本概念

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;QT❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、概述 二、信号的本质 三、槽的本质 一、概述 在 Qt 中&#xff0c;用户和控件的每次交互过程称…

【会议征稿】2024年软件自动化与程序分析国际学术会议(SAPA 2024)

目录 1. 会议官方2. 支持单位3. 大会简介4. 大会组委5. 征稿主题6. 会议出版7. 会议议程8. 参会方式9. 更多会议 1. 会议官方 重要信息&#xff1a; 大会官网&#xff1a;www.icsapa.org大会时间&#xff1a;2024.6.14-16日大会地点&#xff1a;中国-大理接受/拒稿通知&#…

品牌舆情都包含什么内容?建议收藏

一个品牌的声誉、形象、产品质量、服务质量等&#xff0c;无时无刻不在接受着大众的检验。互联网传播迅速&#xff0c;一个不好的舆论直接导致整个品牌的声誉受到严重影响。品牌舆情都包含什么内容&#xff1f;接下来伯乐网络传媒就来给大家讲一讲。 一、品牌舆情的基本构成 1…

数据可视化(十):Pandas数据分析师职位信息表分析——箱线图、水平柱状图、学历城市双维分析等高级操作

Tips&#xff1a;"分享是快乐的源泉&#x1f4a7;&#xff0c;在我的博客里&#xff0c;不仅有知识的海洋&#x1f30a;&#xff0c;还有满满的正能量加持&#x1f4aa;&#xff0c;快来和我一起分享这份快乐吧&#x1f60a;&#xff01; 喜欢我的博客的话&#xff0c;记得…

STM32+CubeMX移植HAL库实现SYN6288语音合成模块模块

这里写目录标题 SYN6288语音模块简介引脚定义注意要点CubeMX配置HAL库移植运行结果 SYN6288语音模块简介 SYN6288中文语音合成芯片是北京宇音天下科技有限公司于2010年初推出的一款性/价比更高&#xff0c;效果 更自然的一款中高端语音合成芯片。SYN6288通过异步串口(UART)通讯…

picoCTF-Web Exploitation-Java Code Analysis!?!

Description BookShelf Pico, my premium online book-reading service.I believe that my website is super secure. I challenge you to prove me wrong by reading the ‘Flag’ book!Here are the credentials to get you started: Username: “user”Password: “user” S…

计算机发展史故事【11】

爆发超新星 IBM 号称巨人&#xff0c;竟在巨型机领域败在小小的控制数据公司CDC 手下。在小型机领域&#xff0c;“霸主”DEC 公司的“后院”也曾“起火”&#xff0c;绝非一直风平浪静。 本世纪60 年代末&#xff0c;DEC 公司因开发小型电脑迅速崛起&#xff0c;赢得“小型机…

五子棋对战(网页版)

目录 一、项目背景 用户模块 匹配模块 对战模块 二、核心技术 三、相关知识 WebSocket 原理 报文格式 代码 服务器代码 客户端代码 四、项目创建 4.1、实现用户模块 编写数据库代码 数据库设计 配置MyBatis 创建实体类 创建UserMapper 创建UserMapper接口 实现UserMapper.xml 前…

Android Studio Please select Android SDK

解决方案&#xff1a; 1、打开 SDK Manager 2、选择编辑&#xff08;Edit&#xff09; 3、 一直Next&#xff0c;直到完成&#xff0c;解决&#xff01;

科技查新中的工法查新点如何确立与提炼?案例讲解!

按《工程建设工法管理办法》( 建 质&#xff3b;2014&#xff3d;103 号) &#xff0c;工法&#xff0c;是指以工程为对象&#xff0c;以工艺为核心&#xff0c;运用系 统工程原理&#xff0c;把先进技术和科学管理结合起来&#xff0c;经过一定工程实践形成的综合配套的施工方…