导语
PyTorch是目前最受欢迎的深度学习框架之一,本文介绍PyTorch中的一些基本概念及操作,包括张量、自动微分等,最后通过训练一个用于图像分类的神经网络简单介绍用PyTorch框架训练神经网络的基本流程,希望对新入门PyTorch的读者有所帮助。
本文主要参考PyTorch官方文档及网络上的一些教程,如有侵权,请联系删除。
Tensor(张量)
Tensor是一种专用的数据结构,类似于数组和矩阵。在 PyTorch 中,我们使用张量对模型的输入和输出以及模型的参数进行编码。
Tensor与NumPy的ndarray类似,不同的是PyTorch中的 Tensor 可以在 GPU 或其他专用硬件上运行以加速计算。
Tensor有几种初始化方式:
          
import torch
          
import numpy as np
          
          
#用其他数据进行初始化
          
data = [[1, 2],[3, 4]]
          
x_data = torch.tensor(data)
          
#用NumPy数组进行初始化
          
np_array = np.array(data)
          
x_np = torch.from_numpy(np_array)
          
          
#用其他Tensors进行初始化
          
x_ones = torch.ones_like(x_data)     #与x_data的属性保持特性一致
          
x_rand = torch.rand_like(x_data, dtype=torch.float) #另外指定数据类型
          
Tensor的属性包括:shape, datatype, device等
tensor = torch.rand(3,4)
print("Shape of tensor: ", tensor.shape)
print("Datatype of tensor: ", tensor.dtype)
print("tensor is stored on: ", tensor.device)
结果:  
Shape of tensor: torch.Size([3, 4])
Datatype of tensor: torch.float32
tensor is stored on: cpu
  
我们可以把Tensor放到GPU上计算:
if torch.cuda.is_available():
tensor = tensor.to('cuda')
print("tensor is stored on: ", tensor.device)
结果:  
      tensor is stored on: cuda:0
    
  
  
PyTorch中支持超过100个Tensor操作,包括转置、索引、切片、数学运算、线性代数、随机采样等等。
print(tensor, "\n")
print(tensor[:,1], '\n')
print("tensor.mul(tensor):\n {} \n".format(tensor.mul(tensor)))
print("tensor * tensor: \n {} \n".format(tensor * tensor))
print("tensor.matmul(tensor.T): \n {} \n".format({tensor.matmul(tensor.T)}))
print("tensor @ tensor.T: \n {} \n".format({tensor @ tensor.T}))
tensor.add_(5)
print(tensor)
结果:
tensor([[0.7988, 0.5616, 0.4597, 0.0974],
[0.9885, 0.7940, 0.4081, 0.4274],
[0.5799, 0.3978, 0.6393, 0.6365]], device='cuda:0')
tensor([0.5616, 0.7940, 0.3978], device='cuda:0')
tensor.mul(tensor):
tensor([[0.6381, 0.3154, 0.2114, 0.0095],
[0.9771, 0.6305, 0.1665, 0.1826],
[0.3363, 0.1583, 0.4087, 0.4051]], device='cuda:0')
tensor * tensor:
tensor([[0.6381, 0.3154, 0.2114, 0.0095],
[0.9771, 0.6305, 0.1665, 0.1826],
[0.3363, 0.1583, 0.4087, 0.4051]], device='cuda:0')
tensor.matmul(tensor.T):
{tensor([[1.1744, 1.4648, 1.0426],
[1.4648, 1.9568, 1.4220],
[1.0426, 1.4220, 1.3084]], device='cuda:0')}
tensor @ tensor.T:
{tensor([[1.1744, 1.4648, 1.0426],
[1.4648, 1.9568, 1.4220],
[1.0426, 1.4220, 1.3084]], device='cuda:0')}
tensor([[5.7988, 5.5616, 5.4597, 5.0974],
[5.9885, 5.7940, 5.4081, 5.4274],
[5.5799, 5.3978, 5.6393, 5.6365]], device='cuda:0')
  
PyTorch中的Tensor与NumPy中的数组可以互相转换,并且会共享内存位置,更改其中一个的内容同样会影响另一个的值。
t = torch.ones(3)
n = t.numpy()
print("t: ", t)
print("n: ", n)
#更改Tensor会影响NumPy数组
t.add_(1)
print("\nt: ", t)
print("n: ", n)
#Tensor由NumPy数组初始化
n = np.ones(6)
t = torch.from_numpy(n)
print("\nn: ", n)
print("t: ", t)
#更改NumPy数组会影响Tensor
np.add(n, 1, out=n)
print("\nn: ", n)
print("t: ", t)
结果:  
t: tensor([1., 1., 1.])
n: [1. 1. 1.]
t: tensor([2., 2., 2.])
n: [2. 2. 2.]
n: [1. 1. 1. 1. 1. 1.]
t: tensor([1., 1., 1., 1., 1., 1.], dtype=torch.float64)
n: [2. 2. 2. 2. 2. 2.]
t: tensor([2., 2., 2., 2., 2., 2.], dtype=torch.float64)
  
Autograd(自动微分)机制
  
Autograd包是PyTorch中所有神经网络的核心,它为Tensors上的所有操作提供自动微分,为神经网络训练过程中的反向传播提供驱动力。对于每一个Tensor,如果设置它的属性 .requires\_grad 为 True,那么Autograd将会追踪对于该张量的所有操作。当完成计算后可以通过调用.backward(),来自动计算所有的梯度。这个张量的所有梯度将会自动累加到.grad属性中。
x = torch.ones(3, 3)
if not x.requires_grad:
x.requires_grad_(True)
print(x)
结果:  
tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]], requires_grad=True)
  
对Tensor做一些操作:
y = x * 2
print(y, '\n')
z = y * y * 2
print(z)
结果:
tensor([[2., 2., 2.],
[2., 2., 2.],
[2., 2., 2.]], grad_fn=)
tensor([[8., 8., 8.],
[8., 8., 8.],
[8., 8., 8.]], grad_fn=)
  
进行反正传播,求x的梯度x.grad:
z.backward(x)
print('x.grad: {} \n'.format(x.grad))
结果:
x.grad: tensor([[16., 16., 16.],
[16., 16., 16.],
[16., 16., 16.]])
  
可以通过将代码块包装在 with torch.no\_grad(): 中,来阻止Autograd去跟踪设置了.requires\_grad=True 的Tensor的历史记录:
with torch.no_grad():
print((x * 2).requires_grad)
      
  
结果:
False
  
神经网络
  
在PyTorch中我们可以通过torch.nn包来构建神经网络。一个典型的神经网络训练过程如下:
  
1. 构建一个神经网络;
2. 通过神经网络处理输入数据(Forward);
3. 根据神经网络输出的结果和真实的值,由损失函数计算loss;
4. 将梯度反向传播给网络的参数(Backward);
5. 更新网络的权重;
6. 在数据集上迭代2-5步的过程直至训练完成。
  
 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__()
      
self.conv1 = nn.Conv2d(1, 6, 5) #输入1通道,输出6通道,5x5卷积核
      
      
self.conv2 = nn.Conv2d(6, 16, 5)
      
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 = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
      
x = F.max_pool2d(F.relu(self.conv2(x)), (2, 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
      
      
def num_flat_features(self, x):
size = x.size()[1:]  
      
num_features = 1   
      
for s in size:
      
    num_features *= s
      
return num_features
      
      
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 函数,可以在中使用任何针对张量的操作和计算。backward函数用来计算导数,会通过autograd自动定义。
- 
输入数据,前向传播
 
给网络输入32x32的数据:
          
input = torch.randn(1, 1, 32, 32)
          
output = net(input)
          
print(output)
      
结果:
          
tensor([[-0.1158, -0.0385,  0.1082,  0.0346, -0.0512,  0.0358,  0.1280,  0.1219,
          
-0.0250,  0.0036]], grad_fn=<AddmmBackward>)
      
- 
计算loss
 
          
target = torch.randn(10)  # 使用模拟数据
          
target = target.view(1, -1)  # 使目标值与数据值尺寸一致
          
criterion = nn.MSELoss()
          
loss = criterion(output, target)  # 使用均方误差损失函数
          
print(loss)
      
结果:
        
            
          tensor(1.6162, grad\_fn=<MseLossBackward>)
        
      
- 
反向传播
 
首先需要清零现有的梯度,否则当前梯度会与已有的梯度累加,然后再调用loss.backward()来反向传播误差。
          
net.zero_grad()     # 清零所有参数的梯度缓存
          
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:
          
None
          
conv1.bias.grad after backward:
          
tensor([ 0.0106, -0.0016,  0.0181,  0.0205,  0.0186, -0.0276])
      
- 
更新权重
 
假设采用随机梯度下降(SGD)法来更新神经网络的权重:
          
import torch.optim as optim
          
# 创建SGD优化器
          
optimizer = optim.SGD(net.parameters(), lr=0.01)
          
 #更新权重
          
optimizer.step() 
      
训练图像分类器
torchvision包中包含了计算机视觉中常用的Imagenet、CIFAR10、MNIST等数据集,可以通过torchvision.datasets来引用。同时PyTorch中提供数据加载器torch.utils.data.DataLoader用于加载数据集。
在本例程中,我们使用CIFAR10数据集来训练一个神经网络,用于对图像进行分类。CIFAR10数据集有10个类别,每张图片都是32x32像素的3通道彩色图片。
- 
加载CIFAR10数据集并进行标准化
 
首先导入需要的包:
          
import torch
          
import torchvision
          
import torchvision.transforms as transforms
      
用torchvision加载数据集后输出的是范围在[0, 1]之间的PILImage,我们需要将其标准化为范围在[-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 = ('airplane', 'automobile', 'bird', 'cat',
          
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
      
- 
定义神经网络
 
          
class Net(nn.Module):
          
          
def __init__(self):
          
    super(Net, self).__init__()
          
    self.conv = nn.Sequential(
          
        nn.Conv2d(3, 6, 5),
          
        nn.ReLU(),
          
        nn.MaxPool2d(2, 2),
          
        nn.Conv2d(6, 16, 5),
          
        nn.ReLU(),
          
        nn.MaxPool2d(2, 2)
          
        )
          
    self.fc = nn.Sequential(
          
        nn.Linear(16*5*5, 120),
          
        nn.ReLU(),
          
        nn.Linear(120, 84),
          
        nn.ReLU(),
          
        nn.Linear(84, 10)
          
        )
          
          
def forward(self, x):
          
    feature = self.conv(x)
          
    output = self.fc(feature.view(x.shape[0], -1))
          
    return output
          
          
net = Net()
      
- 
定义损失函数和优化器
 
          
import torch.optim as optim
          
#使用交叉熵损失函数
          
criterion = nn.CrossEntropyLoss()
          
#使用SGD优化器
          
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
      
- 
训练网络
 
通过数据加载器,将训练集数据输入给网络和优化器进行训练:
          
total_epoches = 5
          
for epoch in range(total_epoches):
          
    totoal_loss = 0.0
          
    i = 0
          
    for data in trainloader:
          
        # 读取数据
          
        inputs, labels = data             
          
        # 对网络所有参数的梯度进行清零
          
        optimizer.zero_grad()           
          
        # Forward
          
        outputs = net(inputs)
          
        # 计算损失
          
        loss = criterion(outputs, labels)
          
        # Backward
          
        loss.backward()
          
        # 更新参数
          
        optimizer.step()    
          
        totoal_loss += loss.item()
          
        # 每5000个batch打印一次
          
        if i % 5000 == 4999:
          
        print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, totoal_loss / 5000))
          
        totoal_loss = 0.0
          
        i += 1
          
print('训练完成')
          
结果:  
[1, 5000] loss: 2.036
[1, 10000] loss: 1.607
[2, 5000] loss: 1.384
[2, 10000] loss: 1.326
[3, 5000] loss: 1.216
[3, 10000] loss: 1.177
[4, 5000] loss: 1.103
[4, 10000] loss: 1.094
[5, 5000] loss: 1.012
[5, 10000] loss: 1.034
训练完成
保存训练好的模型:
SAVE_PATH = './cifar10_net.pth'
torch.save(net.state_dict(), SAVE_PATH)
在测试集上测试模型的效果:
net = Net()
net.load_state_dict(torch.load(SAVE_PATH))
correct = 0
total = 0
with 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()
      
print('The accuracy of the network on the %d test images: %d %%' % (total, 100 * correct / total))
结果:  
      The accuracy of the network on the 10000 test images: 62 %
    
  
  
