返回列表 发布新帖
查看: 1176|回复: 0

PyTorch入门

12

主题

1

回帖

179

积分

管理员

积分
179
发表于 2024-4-28 16:42:48 | 查看全部 |阅读模式
PyTorch入门
Pytorch是torch的python版本,是由Facebook开源的神经网络框架,专门针对 GPU 加速的深度神经网络(DNN)编程。Torch 是一个经典的对多维矩阵数据进行操作的张量(tensor )库,在机器学习和其他数学密集型应用有广泛应用。
PyTorch 是基于以下两个目的而打造的python科学计算框架:
  • 无缝替换NumPy,并且通过利用GPU的算力来实现神经网络的加速。
  • 通过自动微分机制,来让神经网络的实现变得更加容易。

一、什么是张量
张量如同数组和矩阵一样, 是一种特殊的数据结构。在PyTorch中, 神经网络的输入、输出以及网络的参数等数据, 都是使用张量来进行描述。
张量的使用和Numpy中的ndarrays很类似, 区别在于张量可以在GPU或其它专用硬件上运行, 这样可以得到更快的加速效果。如果你对ndarrays很熟悉的话, 张量的使用对你来说就很容易了。
import torch
import numpy as np1、张量初始化
  • 直接生成张量

由原始数据直接生成张量, 张量类型由原始数据类型决定。
data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)
  • 通过Numpy数组生成

由已有的Numpy数组来生成张量
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
  • 通过已有张量来生成新张量

新的张量将继承已有张量的数据属性(结构、类型), 也可以重新指定新的数据类型。
x_ones = torch.ones_like(x_data)   # 保留 x_data 的属性
print(f"Ones Tensor: \n {x_ones} \n")

x_rand = torch.rand_like(x_data, dtype=torch.float)   # 重写 x_data 的数据类型:int -> float
print(f"Random Tensor: \n {x_rand} \n")
输出:
Ones Tensor:
tensor([[1, 1],
         [1, 1]])

Random Tensor:
tensor([[0.0381, 0.5780],
         [0.3963, 0.0840]])
  • 通过指定数据维度来生成张量

shape是元组类型, 用来描述张量的维数, 下面3个函数通过传入shape来指定生成张量的维数。
shape = (2,3,)
rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)

print(f"Random Tensor: \n {rand_tensor} \n")
print(f"Ones Tensor: \n {ones_tensor} \n")
print(f"Zeros Tensor: \n {zeros_tensor}")
输出:
Random Tensor:
tensor([[0.0266, 0.0553, 0.9843],
         [0.0398, 0.8964, 0.3457]])

Ones Tensor:
tensor([[1., 1., 1.],
         [1., 1., 1.]])

Zeros Tensor:
tensor([[0., 0., 0.],
         [0., 0., 0.]])2、张量属性
从张量属性我们可以得到张量的维数、数据类型以及它们所存储的设备(CPU或GPU)。
来看一个简单的例子:
tensor = torch.rand(3,4)

print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")
显示:
Shape of tensor: torch.Size([3, 4])   # 维数
Datatype of tensor: torch.float32     # 数据类型
Device tensor is stored on: cpu       # 存储设备3、张量运算
有超过100种张量相关的运算操作, 例如转置、索引、切片、数学运算、线性代数、随机采样等。
所有这些运算都可以在GPU上运行(相对于CPU来说可以达到更高的运算速度)。
# 判断当前环境GPU是否可用, 然后将tensor导入GPU内运行
if torch.cuda.is_available():
  tensor = tensor.to('cuda')
  • 张量的索引和切片

tensor = torch.ones(4, 4)
tensor[:,1] = 0            # 将第1列(从0开始)的数据全部赋值为0
print(tensor)
显示:
tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]])
  • 张量的拼接

你可以通过torch.cat方法将一组张量按照指定的维度进行拼接, 也可以参考torch.stack方法。这个方法也可以实现拼接操作, 但和torch.cat稍微有点不同。
t1 = torch.cat([tensor, tensor, tensor], dim=1)
print(t1)
显示:
tensor([[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
        [1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
        [1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
        [1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.]])
  • 张量的乘积和矩阵乘法

# 逐个元素相乘结果
print(f"tensor.mul(tensor): \n {tensor.mul(tensor)} \n")
# 等价写法:
print(f"tensor * tensor: \n {tensor * tensor}")
显示:
tensor.mul(tensor):
tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]])

tensor * tensor:
tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]])
下面写法表示张量与张量的矩阵乘法:
print(f"tensor.matmul(tensor.T): \n {tensor.matmul(tensor.T)} \n")
# 等价写法:
print(f"tensor @ tensor.T: \n {tensor @ tensor.T}")
显示:
tensor.matmul(tensor.T):
tensor([[3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.]])

tensor @ tensor.T:
tensor([[3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.]])
  • 自动赋值运算

自动赋值运算通常在方法后有 _ 作为后缀, 例如: x.copy_(y), x.t_()操作会改变 x 的取值。
print(tensor, "\n")
tensor.add_(5)
print(tensor)
显示:
tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]])

tensor([[6., 5., 6., 6.],
        [6., 5., 6., 6.],
        [6., 5., 6., 6.],
        [6., 5., 6., 6.]])
注意:
自动赋值运算虽然可以节省内存, 但在求导时会因为丢失了中间过程而导致一些问题, 所以我们并不鼓励使用它。
4、Tensor与Numpy的转化
张量和Numpy array数组在CPU上可以共用一块内存区域, 改变其中一个另一个也会随之改变。
  • 由张量变换为Numpy array数组

t = torch.ones(5)
print(f"t: {t}")
n = t.numpy()
print(f"n: {n}")
显示:
t: tensor([1., 1., 1., 1., 1.])
n: [1. 1. 1. 1. 1.]
修改张量的值,则Numpy array数组值也会随之改变。
t.add_(1)
print(f"t: {t}")
print(f"n: {n}")
显示:
t: tensor([2., 2., 2., 2., 2.])
n: [2. 2. 2. 2. 2.]
  • 由Numpy array数组转为张量

n = np.ones(5)
t = torch.from_numpy(n)
修改Numpy array数组的值,则张量值也会随之改变。
np.add(n, 1, out=n)
print(f"t: {t}")
print(f"n: {n}")
显示:
t: tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
n: [2. 2. 2. 2. 2.]二、自动梯度
自动梯度torch.autograd是 PyTorch 的自动差分引擎,可为神经网络训练提供支持。 在这里,我们将了解 Autograd 如何帮助神经网络训练。
1、背景
神经网络(NN)是在某些输入数据上执行的嵌套函数的集合。 这些函数由参数(由权重和偏差组成)定义,这些参数在 PyTorch 中存储在张量中。
训练 NN 分为两个步骤:
正向传播:在正向传播中,NN 对正确的输出进行最佳猜测。 它通过其每个函数运行输入数据以进行猜测。
反向传播:在反向传播中,NN 根据其猜测中的误差调整其参数。 它通过从输出向后遍历,收集有关函数参数(梯度)的误差导数并使用梯度下降来优化参数来实现。
2、使用方法
让我们来看一个训练步骤:从torchvision加载了经过预训练的 resnet18 模型。 我们创建一个随机数据张量来表示具有 3 个通道的单个图像,高度&宽度为 64,其对应的label初始化为一些随机值。
import torch, torchvision
model = torchvision.models.resnet18(pretrained=True)
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 1000)
接下来,我们通过模型的每一层运行输入数据以进行预测。 这是正向传播
prediction = model(data) # forward pass
我们使用模型的预测和相应的标签来计算误差(loss)。 下一步是通过网络反向传播此误差。 当我们在误差张量上调用.backward()时,开始反向传播。 然后,Autograd 会为每个模型参数计算梯度并将其存储在参数的.grad属性中。
loss = (prediction - labels).sum()
loss.backward() # backward pass
接下来,我们加载一个优化器,在本例中为 SGD,学习率为 0.01,动量为 0.9。 我们在优化器中注册模型的所有参数。
optim = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)
最后,我们调用.step()启动梯度下降。 优化器通过.grad中存储的梯度来调整每个参数。
optim.step() #gradient descent
至此,您已经具备了训练神经网络所需的一切。
三、神经网络
我们可以使用torch.nn包构建神经网络。现在已经了解了autograd,nn依赖于autograd来定义模型并对其进行微分。 nn.Module包含层,以及返回output的方法forward(input)。
比如有这样一个对数字图像进行分类的网络:

这是一个简单的前馈网络。 它获取输入,将其一层又一层地馈入,然后最终给出输出。
神经网络的典型训练过程如下:
  • 定义具有一些可学习参数(或权重)的神经网络
  • 遍历输入数据集
  • 通过网络处理输入
  • 计算损失(输出正确的距离有多远)
  • 将梯度传播回网络参数
  • 通常使用简单的更新规则来更新网络的权重:weight = weight - learning_rate * gradient

1、定义网络
让我们定义这个网络:
import torch
import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        # 1 input image channel, 6 output channels, 5x5 square convolution
        # kernel
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        # an affine operation: y = Wx + b
        self.fc1 = nn.Linear(16 * 5 * 5, 120)  # 5*5 from image dimension
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # 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 specify with a single number
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = torch.flatten(x, 1) # flatten all dimensions except the batch dimension
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x


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函数,就可以使用autograd为您自动定义backward函数(计算梯度)。 也可以在forward函数中使用任何张量操作。
模型的可学习参数由net.parameters()返回
params = list(net.parameters())
print(len(params))
print(params[0].size())  # conv1's .weight
输出:
10
torch.Size([6, 1, 5, 5])
让我们尝试一个32x32的随机输入。 注意:该网络的预期输入大小(LeNet)为32x32。 要在 MNIST 数据集上使用此网络,请将图像从数据集中调整为32x32。
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
输出:
tensor([[ 0.0818, -0.0857,  0.0695,  0.1430,  0.0191, -0.1402,  0.0499, -0.0737,
         -0.0857,  0.1395]], grad_fn=<AddmmBackward0>)
使用随机梯度将所有参数和反向传播的梯度缓冲区归零:
net.zero_grad()
out.backward(torch.randn(1, 10))
注意
torch.nn仅支持小批量。 整个torch.nn包仅支持作为微型样本而不是单个样本的输入。
例如,nn.Conv2d将采用nSamples x nChannels x Height x Width的 4D 张量。
如果您只有一个样本,只需使用input.unsqueeze(0)添加一个假批量尺寸。
2、损失函数
损失函数采用一对(输出,目标)输入,并计算一个值,该值估计输出与目标之间的距离。
nn包下有几种不同的损失函数。 一个简单的损失是:nn.MSELoss,它计算输入和目标之间的均方误差。
例如:
output = net(input)
target = torch.randn(10)  # 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)
输出:
tensor(1.1649, grad_fn=<MseLossBackward0>)
现在,如果使用.grad_fn属性向后跟随loss,我们将看到一个计算图,如下所示:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
      -> view -> linear -> relu -> linear -> relu -> linear
      -> MSELoss
      -> loss
因此,当我们调用loss.backward()时,整个图将被微分。 并且图中具有requires_grad=True的所有张量将随梯度累积其.grad张量。
为了说明,让我们向后走几步:
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
输出:
<MseLossBackward0 object at 0x7f71283dd048>
<AddmmBackward0 object at 0x7f71283dd7f0>
<AccumulateGrad object at 0x7f71283dd7f0>3、反向传播
要反向传播误差,我们只需要调用loss.backward()。 不过,需要清除现有的梯度,否则梯度将累积到现有的梯度中。
现在,我们调用loss.backward(),然后看一下反向传播后的前后conv1的偏差梯度。
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)
输出:
conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([ 0.0188,  0.0172, -0.0044, -0.0141, -0.0058, -0.0013])4、更新权重
实践中使用的最简单的更新规则是随机梯度下降(SGD):
weight = weight - learning_rate * gradient
我们可以使用简单的 Python 代码实现此目标:
learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate)
但是,在使用神经网络时,我们希望使用各种不同的更新规则,例如 SGD,Nesterov-SGD,Adam,RMSProp 等。 使用它们非常简单:
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
注意
观察如何使用optimizer.zero_grad()将梯度缓冲区手动设置为零。 这是因为如反向传播部分中所述累积了梯度。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关灯 在本版发帖
扫一扫添加微信客服
返回顶部
快速回复 返回顶部 返回列表