2. Pytorch初步应用
2.1 使用Pytorch构建一个神经网络
学习目标
掌握用Pytorch构建神经网络的基本流程. 掌握用Pytorch构建神经网络的实现过程.
关于torch.nn:
使用Pytorch来构建神经网络, 主要的工具都在torch.nn包中. nn依赖于autograd来定义模型, 并对其自动求导.
定义一个拥有可学习参数的神经网络 遍历训练数据集 处理输入数据使其流经神经网络 计算损失值 将网络参数的梯度进行反向传播 以一定的规则更新网络的权重
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 , 3 )
self. conv2 = nn. Conv2d( 6 , 16 , 3 )
self. fc1 = nn. Linear( 16 * 6 * 6 , 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 )
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= ( 3 , 3 ) , stride= ( 1 , 1 ) )
( conv2) : Conv2d( 6 , 16 , kernel_size= ( 3 , 3 ) , stride= ( 1 , 1 ) )
( fc1) : Linear( in_features= 576 , 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 )
)
注意:
模型中所有的可训练参数, 可以通过net.parameters()来获得.
params = list ( net. parameters( ) )
print ( len ( params) )
print ( params[ 0 ] . size( ) )
10
torch. Size( [ 6 , 1 , 3 , 3 ] )
input = torch. randn( 1 , 1 , 32 , 32 )
out = net( input )
print ( out)
tensor( [ [ 0.1242 , 0.1194 , - 0.0584 , - 0.1140 , 0.0661 , 0.0191 , - 0.0966 , 0.0480 ,
0.0775 , - 0.0451 ] ] , grad_fn= < AddmmBackward> )
有了输出张量后, 就可以执行梯度归零和反向传播的操作了.
net. zero_grad( )
out. backward( torch. randn( 1 , 10 ) )
注意:
torch.nn构建的神经网络只支持mini-batches的输入, 不支持单一样本的输入. 比如: nn.Conv2d 需要一个4D Tensor, 形状为(nSamples, nChannels, Height, Width). 如果你的输入只有单一样本形式, 则需要执行input.unsqueeze(0), 主动将3D Tensor扩充成4D Tensor.
损失函数
损失函数的输入是一个输入的pair: (output, target), 然后计算出一个数值来评估output和target之间的差距大小. 在torch.nn中有若干不同的损失函数可供使用, 比如nn.MSELoss就是通过计算均方差损失来评估输入和目标值之间的差距.
output = net( input )
target = torch. randn( 10 )
target = target. view( 1 , - 1 )
criterion = nn. MSELoss( )
loss = criterion( output, target)
print ( loss)
tensor( 1.1562 , grad_fn= < MseLossBackward> )
关于方向传播的链条: 如果我们跟踪loss反向传播的方向, 使用.grad_fn属性打印, 将可以看到一张完整的计算图如下:
input - > conv2d - > relu - > maxpool2d - > conv2d - > relu - > maxpool2d
- > view - > linear - > relu - > linear - > relu - > linear
- > MSELoss
- > loss
当调用loss.backward()时, 整张计算图将对loss进行自动求导, 所有属性requires_grad=True的Tensors都将参与梯度求导的运算, 并将梯度累加到Tensors中的.grad属性中.
print ( loss. grad_fn)
print ( loss. grad_fn. next_functions[ 0 ] [ 0 ] )
print ( loss. grad_fn. next_functions[ 0 ] [ 0 ] . next_functions[ 0 ] [ 0 ] )
< MseLossBackward object at 0x7fdba3216da0 >
< AddmmBackward object at 0x7fdba3216f28 >
< AccumulateGrad object at 0x7fdba3216f28 >
反向传播(backpropagation)
在Pytorch中执行反向传播非常简便, 全部的操作就是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
tensor( [ 0. , 0. , 0. , 0. , 0. , 0. ] )
conv1. bias. grad after backward
tensor( [ - 0.0002 , 0.0045 , 0.0017 , - 0.0099 , 0.0092 , - 0.0044 ] )
更新网络参数
更新参数最简单的算法就是SGD(随机梯度下降). 具体的算法公式表达式为: weight = weight - learning_rate * gradient
learning_rate = 0.01
for f in net. parameters( ) :
f. data. sub_( f. grad. data * learning_rate)
import torch. optim as optim
optimizer = optim. SGD( net. parameters( ) , lr= 0.01 )
optimizer. zero_grad( )
output = net( input )
loss = criterion( output, target)
loss. backward( )
optimizer. step( )
小节总结
学习了构建一个神经网络的典型流程:
定义一个拥有可学习参数的神经网络 遍历训练数据集 处理输入数据使其流经神经网络 计算损失值 将网络参数的梯度进行反向传播 以一定的规则更新网络的权重
学习了损失函数的定义:
采用torch.nn.MSELoss()计算均方误差. 通过loss.backward()进行反向传播计算时, 整张计算图将对loss进行自动求导, 所有属性requires_grad=True的Tensors都将参与梯度求导的运算, 并将梯度累加到Tensors中的.grad属性中.
学习了反向传播的计算方法:
在Pytorch中执行反向传播非常简便, 全部的操作就是loss.backward(). 在执行反向传播之前, 要先将梯度清零, 否则梯度会在不同的批次数据之间被累加.
net.zero_grad() loss.backward()
学习了参数的更新方法:
定义优化器来执行参数的优化与更新.
optimizer = optim.SGD(net.parameters(), lr=0.01)
通过优化器来执行具体的参数更新.
2.2 使用Pytorch构建一个分类器
学习目标
了解分类器的任务和数据样式 掌握如何用Pytorch实现一个分类器
分类器任务和数据介绍
构造一个将不同图像进行分类的神经网络分类器, 对输入的图片进行判别并完成分类. 本案例采用CIFAR10数据集作为原始图片数据.
CIFAR10数据集介绍: 数据集中每张图片的尺寸是3 * 32 * 32, 代表彩色3通道 CIFAR10数据集总共有10种不同的分类, 分别是"airplane", “automobile”, “bird”, “cat”, “deer”, “dog”, “frog”, “horse”, “ship”, “truck”.
训练分类器的步骤
1: 使用torchvision下载CIFAR10数据集 2: 定义卷积神经网络 3: 定义损失函数 4: 在训练集上训练模型 5: 在测试集上测试模型
1: 使用torchvision下载CIFAR10数据集
import torch
import torchvision
import torchvision. transforms as transforms
下载数据集并对图片进行调整, 因为torchvision数据集的输出是PILImage格式, 数据域在[0, 1]. 我们将其转换为标准数据域[-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' )
Downloading https: // www. cs. toronto. edu/ ~ kriz/ cifar- 10 - python. tar. gz to . / data/ cifar- 10 - python. tar. gz
Extracting . / data/ cifar- 10 - python. tar. gz to . / data
Files already downloaded and verified
注意:
如果你是在Windows系统下运行上述代码, 并且出现报错信息 “BrokenPipeError”, 可以尝试将torch.utils.data.DataLoader()中的num_workers设置为0.
import matplotlib. pyplot as plt
import numpy as np
def imshow ( img) :
img = img / 2 + 0.5
npimg = img. numpy( )
plt. imshow( np. transpose( npimg, ( 1 , 2 , 0 ) ) )
plt. show( )
dataiter = iter ( trainloader)
images, labels = dataiter. next ( )
imshow( torchvision. utils. make_grid( images) )
print ( ' ' . join( '%5s' % classes[ labels[ j] ] for j in range ( 4 ) ) )
bird truck cat cat
仿照2.1节中的类来构造此处的类, 唯一的区别是此处采用3通道3-channel
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 )
self. pool = nn. MaxPool2d( 2 , 2 )
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 = self. pool( F. relu( self. conv1( x) ) )
x = self. pool( F. relu( self. conv2( x) ) )
x = x. view( - 1 , 16 * 5 * 5 )
x = F. relu( self. fc1( x) )
x = F. relu( self. fc2( x) )
x = self. fc3( x)
return x
net = Net( )
import torch. optim as optim
criterion = nn. CrossEntropyLoss( )
optimizer = optim. SGD( net. parameters( ) , lr= 0.001 , momentum= 0.9 )
采用基于梯度下降的优化算法, 都需要很多个轮次的迭代训练.
for epoch in range ( 2 ) :
running_loss = 0.0
for i, data in enumerate ( trainloader, 0 ) :
inputs, labels = data
optimizer. zero_grad( )
outputs = net( inputs)
loss = criterion( outputs, labels)
loss. backward( )
optimizer. step( )
running_loss += loss. item( )
if ( i + 1 ) % 2000 == 0 :
print ( '[%d, %5d] loss: %.3f' %
( epoch + 1 , i + 1 , running_loss / 2000 ) )
running_loss = 0.0
print ( 'Finished Training' )
[ 1 , 2000 ] loss: 2.227
[ 1 , 4000 ] loss: 1.884
[ 1 , 6000 ] loss: 1.672
[ 1 , 8000 ] loss: 1.582
[ 1 , 10000 ] loss: 1.526
[ 1 , 12000 ] loss: 1.474
[ 2 , 2000 ] loss: 1.407
[ 2 , 4000 ] loss: 1.384
[ 2 , 6000 ] loss: 1.362
[ 2 , 8000 ] loss: 1.341
[ 2 , 10000 ] loss: 1.331
[ 2 , 12000 ] loss: 1.291
Finished Training
PATH = './cifar_net.pth'
torch. save( net. state_dict( ) , PATH)
dataiter = iter ( testloader)
images, labels = dataiter. next ( )
imshow( torchvision. utils. make_grid( images) )
print ( 'GroundTruth: ' , ' ' . join( '%5s' % classes[ labels[ j] ] for j in range ( 4 ) ) )
GroundTruth: cat ship ship plane
net = Net( )
net. load_state_dict( torch. load( PATH) )
outputs = net( images)
_, predicted = torch. max ( outputs, 1 )
print ( 'Predicted: ' , ' ' . join( '%5s' % classes[ predicted[ j] ] for j in range ( 4 ) ) )
Predicted: cat ship ship plane
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 ( 'Accuracy of the network on the 10000 test images: %d %%' % (
100 * correct / total) )
Accuracy of the network on the 10000 test images: 53 %
分析结果: 对于拥有10个类别的数据集, 随机猜测的准确率是10%, 模型达到了53%, 说明模型学到了真实的东西.
为了更加细致的看一下模型在哪些类别上表现更好, 在哪些类别上表现更差, 我们分类别的进行准确率计算.
class_correct = list ( 0. for i in range ( 10 ) )
class_total = list ( 0. for i in range ( 10 ) )
with torch. no_grad( ) :
for data in testloader:
images, labels = data
outputs = net( images)
_, predicted = torch. max ( outputs, 1 )
c = ( predicted == labels) . squeeze( )
for i in range ( 4 ) :
label = labels[ i]
class_correct[ label] += c[ i] . item( )
class_total[ label] += 1
for i in range ( 10 ) :
print ( 'Accuracy of %5s : %2d %%' % (
classes[ i] , 100 * class_correct[ i] / class_total[ i] ) )
Accuracy of plane : 62 %
Accuracy of car : 62 %
Accuracy of bird : 45 %
Accuracy of cat : 36 %
Accuracy of deer : 52 %
Accuracy of dog : 25 %
Accuracy of frog : 69 %
Accuracy of horse : 60 %
Accuracy of ship : 70 %
Accuracy of truck : 48 %
在GPU上训练模型
为了真正利用Pytorch中Tensor的优秀属性, 加速模型的训练, 我们可以将训练过程转移到GPU上进行.
首先要定义设备, 如果CUDA是可用的则被定义成GPU, 否则被定义成CPU.
device = torch. device( "cuda:0" if torch. cuda. is_available( ) else "cpu" )
print ( device)
cuda: 0
当训练模型的时候, 只需要将模型转移到GPU上, 同时将输入的图片和标签页转移到GPU上即可.
net. to( device)
inputs, labels = data[ 0 ] . to( device) , data[ 1 ] . to( device)
小节总结
学习了分类器的任务和数据样式.
将不同图像进行分类的神经网络分类器, 对输入的图片进行判别并完成分类. 采用CIFAR10数据集作为原始图片数据, CIFAR10数据集拥有10个类别的3 * 32 * 32彩色图片.
学习了训练分类器的步骤:
使用torchvision下载CIFAR10数据集. 定义卷积神经网络. 定义损失函数. 在训练集上训练模型. 在测试集上测试模型.
学习了在GPU上训练模型.
首先需要定义设备, CPU和GPU二选一:
device = torch.device(“cuda:0” if torch.cuda.is_available() else “cpu”)
然后将模型转移到GPU上去:
最后在迭代训练的过程中, 每一步都将图片和标签张量转移到GPU上去:
inputs, labels = data[0].to(device), data[1].to(device)