PyTorch入门
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 = [, ]
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([,
])
Random Tensor:
tensor([,
])
[*]通过指定数据维度来生成张量
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([,
])
Ones Tensor:
tensor([,
])
Zeros Tensor:
tensor([,
])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() # 维数
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([,
,
,
])
[*]张量的拼接
你可以通过torch.cat方法将一组张量按照指定的维度进行拼接, 也可以参考torch.stack方法。这个方法也可以实现拼接操作, 但和torch.cat稍微有点不同。t1 = torch.cat(, dim=1)
print(t1)显示:tensor([,
,
,
])
[*]张量的乘积和矩阵乘法
# 逐个元素相乘结果
print(f"tensor.mul(tensor): \n {tensor.mul(tensor)} \n")
# 等价写法:
print(f"tensor * tensor: \n {tensor * tensor}")显示:tensor.mul(tensor):
tensor([,
,
,
])
tensor * tensor:
tensor([,
,
,
])下面写法表示张量与张量的矩阵乘法: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([,
,
,
])
tensor @ tensor.T:
tensor([,
,
,
])
[*]自动赋值运算
自动赋值运算通常在方法后有 _ 作为后缀, 例如: x.copy_(y), x.t_()操作会改变 x 的取值。print(tensor, "\n")
tensor.add_(5)
print(tensor)显示:tensor([,
,
,
])
tensor([,
,
,
])注意:自动赋值运算虽然可以节省内存, 但在求导时会因为丢失了中间过程而导致一些问题, 所以我们并不鼓励使用它。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()
n: 修改张量的值,则Numpy array数组值也会随之改变。t.add_(1)
print(f"t: {t}")
print(f"n: {n}")显示:t: tensor()
n:
[*]由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(, dtype=torch.float64)
n: 二、自动梯度自动梯度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.size())# conv1's .weight输出:10
torch.Size()让我们尝试一个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)# Linear
print(loss.grad_fn.next_functions.next_functions)# 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()
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()将梯度缓冲区手动设置为零。 这是因为如反向传播部分中所述累积了梯度。
页:
[1]