Pytorch学习(三)构建训练并测试神经网络
训练图像分类器
官方教程
可参考博客
我们将按顺序执行以下步骤:
1.使用使用 torchvision
2.定义卷积神经网络
3.定义损耗函数
4.根据培训数据对网络进行训练
5.在测试数据上测试网络
这篇博文为第二三四五步,详细代码即注释可见Pytorch学习(一)一些基础认识
构建一个简单的神经网络
我们这次训练的是彩色图片,所以第一层卷积层的输入应为3个channel。
常见简单的神经元:
具体代码及注释见下
###########定义网络开始#########################
class Net(nn.Module):
#定义net的初始化函数,这个函数定义了该神经网络的基本结构
def __init__(self):
#复制并使用Net的父类的初始化方法,即先运行nn.Module的初始化函数
super(Net, self).__init__()
# 定义conv1函数的是图像卷积函数:输入为图像(3个频道,即彩色图),输出为6张特征图, 卷积核为5x5正方形
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2,2) # 定义2d的MaxPooling层
# 定义conv2函数的是图像卷积函数:输入为6张特征图,输出为16张特征图, 卷积核为5x5正方形
self.conv2 = nn.Conv2d(6, 16, 5)
# 定义fc1(fullconnect)全连接函数1为线性函数:y = Wx + b,并将16*5*5个节点连接到120个节点上。
self.fc1 = nn.Linear(16*5*5, 120)
#定义fc2(fullconnect)全连接函数2为线性函数:y = Wx + b,并将120个节点连接到84个节点上。
self.fc2 = nn.Linear(120, 84)
#定义fc3(fullconnect)全连接函数3为线性函数:y = Wx + b,并将84个节点连接到10个节点上。
self.fc3 = nn.Linear(84, 10)
#定义该神经网络的向前传播函数,该函数必须定义,一旦定义成功,向后传播函数也会自动生成(autograd)
def forward(self, x):
#输入x经过卷积conv1之后,经过激活函数ReLU,使用2x2的窗口进行最大池化Max pooling,然后更新到x。
x = self.pool(F.relu(self.conv1(x)))
#输入x经过卷积conv2之后,经过激活函数ReLU,使用2x2的窗口进行最大池化Max pooling,然后更新到x。
x = self.pool(F.relu(self.conv2(x)))
#view函数将张量x变形成一维的向量形式,总特征数并不改变,为接下来的全连接作准备。
x = x.view(-1, 16*5*5)
x = F.relu(self.fc1(x))#输入x经过全连接1,再经过ReLU激活函数,然后更新x
x = F.relu(self.fc2(x))#输入x经过全连接2,再经过ReLU激活函数,然后更新x
x = self.fc3(x)#输入x经过全连接3,然后更新x
return x
###########定义网络结束#########################
#新建一个之前定义的网路
net = Net()
训练网络的步骤,
第一步:将输入input向前传播,进行运算后得到输出output
第二步:将output再输入loss函数,计算loss值(是个标量)
第三步:将梯度反向传播到每个参数
第四步:利用下面公式进行权重更新
新权重w = 旧权重w + 学习速率𝜂 x 梯度向量g
定义损失函数和优化函数
pytorch中有计算loss的函数和优化的函数。
先初始化loss和优化函数:
import torch.optim as optim
#叉熵损失函数
criterion = nn.CrossEntropyLoss()
#使用SGD(随机梯度下降)优化,学习率为0.001,动量为0.9
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
这两个函数会在之会调用
训练网络
我们把之前读取处理的训练集数据trainloader
当作input和labelsinputs, labels = data
,输入到上面定义的神经网络net = Net()
中,向前传播,得到outputoutputs = net(inputs)
。即训练网络的第一步。
将得到的outputs
及之前的labels
输入到叉熵损失函数criterion
中,得到loss值,loss = criterion(outputs, labels)
即第二步。
得到loss
后就可以将梯度反向传播到每个参数。这之前要将参数的grad值初始化为0optimizer.zero_grad()
,使用loss.backward()
反向传播,然后用SGD更新参数optimizer.step()
。即第三步。
训练网络时,需要多次输入训练集。这里把所有的训练集输入到网络中两次,每次输入时,数据的顺序是不一样的。因为设置了shffule=True
,表示不同批次的数据遍历时,打乱顺序。
for epoch in range(2): # 遍历数据集两次
running_loss = 0.0
#enumerate(sequence, [start=0]),i序号,data是数据
for i, data in enumerate(trainloader, 0):
# get the inputs
inputs, labels = data #data的结构是:[4x3x32x32的张量,长度4的张量]
# wrap them in Variable
#把input数据从tensor转为variable
inputs, labels = Variable(inputs), Variable(labels)
# zero the parameter gradients
optimizer.zero_grad() #将参数的grad值初始化为0
# forward + backward + optimize
outputs = net(inputs)
loss = criterion(outputs, labels) #将output和labels使用叉熵计算损失
loss.backward() #反向传播
optimizer.step() #用SGD更新参数
代码中running_loss
是我们待会用来统计loss的平均值的;data是从trainloader
中枚举出来的,结构为[4x3x32x32的张量,长度4的张量]
。
训练前,会将网络中每个参数的grad值清空为0,这样做是因为grad值是累加的。设置为0后,每次bp后的grad更新后的值才是正确的。详情见pytorch学习笔记(二):gradient。
计算loss值的方法除了叉熵损失函数,还有其他计算loss的方法。
loss得到后,就可以使用backward向后传播了。传播应该让每一个网络参数的grad值进行更新,我们网络中的每一个参数都是Variable类型,并且均是叶子节点,grad值必然会进行更新。
所以我们希望每个参数能利用自身的grad值进行梯度下降法的更新,我们利用先前定义好的optimizer使用step()函数进行这样的更新。其他优化方法
下面我们通过打印loss值来体验训练的过程,在上面代码下加上如下语句
optimizer.step() #用SGD更新参数
# 每2000批数据打印一次平均loss值
# loss本身为Variable类型,所以要使用data获取其Tensor,因为其为标量,所以取0
#running_loss += loss.data[0]版本升级这个不能使用
running_loss += loss.item()
if i % 2000 == 1999: # 每2000批打印一次
print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000))
running_loss = 0.0
print('Finished Training')
打印loss值训练结果
注意loss值在不断地减小,说明网络正在不断地优化。
测试网络
测试的时候需要用到之前的测试集数据,开始的和上面一样,一开始先遍历testloader
,将数据存入images, labels
中,再把images
数据从tensor转为variable,并输入到神经网络中outputs = net(Variable(images))
,计算正确率需要有正确数量和全部数量我们可以定义两个变量分别代表这两个correct = 0 total = 0
。
代码如下:
#############测试过程开始##################
correct = 0
total = 0
for data in testloader:
images, labels = data
outputs = net(Variable(images))
#print outputs.data
#outputs.data是一个4x10张量,将每一行的最大的那一列的值和序号各自组成一个一维张量返回,第一个是值的张量,第二个是序号的张量。
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
#两个一维张量逐行对比,相同的行记为1,不同的行记为0,再利用sum(),求总和,得到相同的个数。
correct += (predicted == labels).sum()
#print('Accuracy of the network on the 10000 test images: %d %%' % (100 * correct / total))版本升级,/不能用
print('Accuracy of the network on the 10000 test images: %d %%' % (100 * torch.true_divide(correct, total)))
#############测试过程结束##################
outputs是一个4x10的张量,我们输入的是一张图片,那么出来的是一个10维的特征向量(最后的激活函数self.fc3 = nn.Linear(84, 10)),因为我们同时输入了4张,所以就是4x10。
关于_, predicted = torch.max(outputs.data, 1)
,outputs.data是一个4x10张量,max函数会将每一行的最大的那一列的值和序号各自组成一个一维张量返回,第一个是值的张量,第二个是序号的张量。max的第二个参数1是指最大值。举例如下:
import torch
data = torch.rand(4,10)
data
Out[4]:
tensor([[0.3018, 0.2843, 0.3567, 0.3983, 0.4448, 0.8321, 0.3542, 0.8185, 0.5456,
0.9051],
[0.8774, 0.4345, 0.7412, 0.3280, 0.6261, 0.3423, 0.0736, 0.4716, 0.8004,
0.1158],
[0.3879, 0.7969, 0.6929, 0.2749, 0.9799, 0.3719, 0.6480, 0.6029, 0.9449,
0.4101],
[0.8299, 0.3545, 0.1206, 0.9223, 0.7659, 0.8966, 0.7494, 0.0130, 0.8517,
0.2757]])
torch.max(data,1)
Out[5]:
torch.return_types.max(
values=tensor([0.9051, 0.8774, 0.9799, 0.9223]),
indices=tensor([9, 0, 4, 3]))
关于total += labels.size(0)
,我们的labels是4维的向量,size(0)就是4,即每次total都加4。
关于correct += (predicted == labels).sum()
,两个4维向量逐行对比,相同的即正确的行记为1,不同的即不正确的行记为0,再利用sum(),求各元素总和,得到正确的个数。
接下来我们运行代码:
D:\Python\python.exe C:/Users/22222/Desktop/try/通信/cnn.py
[1, 2000] loss: 2.184
[1, 4000] loss: 1.819
[1, 6000] loss: 1.688
[1, 8000] loss: 1.606
[1, 10000] loss: 1.536
[1, 12000] loss: 1.510
[2, 2000] loss: 1.442
[2, 4000] loss: 1.387
[2, 6000] loss: 1.350
[2, 8000] loss: 1.344
[2, 10000] loss: 1.316
[2, 12000] loss: 1.289
Finished Training
Accuracy of the network on the 10000 test images: 54 %
正确率是54%,原因是训练次数太少。