上次我们简单讲了一个神经网络的构成,这次我们小试牛刀,来试着基于CIFAR-10来训练一个分类器,CIFAR-10共有60000张彩色图像,这些图像是32*32,分为10个类,每类6000张图。这里面有50000张用于训练,构成了5个训练批,每一批10000张图;另外10000用于测试,单独构成一批。测试批的数据里,取自10类中的每一类,每一类随机取1000张。抽剩下的就随机排列组成了训练批。注意一个训练批中的各类图像并不一定数量相同,总的来看训练批,每一类都有5000张图。
我们将按照如下步骤训练一个图像分类器:
先看代码:
import torch
import torchvision
import torchvision.transforms as transforms
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')
就部分函数进行说明
这就相当于一个函数的嵌套,例如把f和g组合起来就是f(g(x)).x是g的参数,执行的结果作为f的参数再执行,最后的结果就是组合函数的结果.这里就是把规则化的数据集传给ToTensor函数
我们本来的数据集是RGB在[0,1]范围内的图像数据集,我们通过标准化将其转化为[-1,1]的数据集,其中两个参数分别为输入的均值和标准差,1*3对应了RGB三个channels,公式如下:
我们原本的数据集是PILImage格式,我们要将其转换成我们需要的张量tensor格式
简单解释一下这行代码的意思,读取训练数据集,一次读四个样本,每次读取都会重洗数据集随机读取,有两个子程序用于数据读取
输出:
Downloading http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./data/cifar-10-python.tar.gz
Files already downloaded and verified
我们来简单看看一些训练图像:
import matplotlib.pyplot as plt
import numpy as np
#functions to show an image
def imshow(img):
img = img/2+0.5#unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg,(1,2,0)))
#get some random training images
dataiter = iter(trainloader)
images,labels = dataiter.__next__()
#show images
imshow(torchvision.utils.make_grid(images))
#print labels
print(''.join('%5s'%classes[labels[j]] for j in range(4)))
输出
其中有一点,教程中代码给的dataiter.next(),但我这样得不到图像,而且根据编辑器的暗示用了加下划线的就显示了图像,这点我觉得不是很重要,就没有细究
入门时,我们就用我们之前写的很简单的一个网络,不过在第一层卷积输入时要把1 channel输入换成3 channel
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
#3 input image channel,6 output channels,5x5 square convolution kernel
self.conv1=nn.Conv2d(3,6,5)#生成一个1输入6输出的5x5的卷基层
self.pool = nn.MaxPool2d(2,2)
self.conv2=nn.Conv2d(6,16,5)#生成一个6输入16输出的5x5的卷基层
#an affine operation:y=wx+b
self.fc1=nn.Linear(16*5*5,120)#生成一个16*5*5输入120输出的线性变换层
self.fc2=nn.Linear(120,84)#生成一个120输入84输出的线性变换层
self.fc3=nn.Linear(84,10)#生成一个84输入10输出的线性变换层
def forward(self,x):
#Max pooling over a (2,2) window
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()
你会感觉和我们之前写的代码有所不同,其实这里除了一些小的参数的改变,并没有什么变化,整个步骤还是按照我学习笔记(二)里面讲的来的,不同的其一是把池化函数放进了init里面,在这里顺便讲讲torch.nn和torch.nn.functional的 区别,在建图过程中,往往有两种层,一种如全连接层,卷积层等,当中有Variable
,另一种如Pooling层,Relu层等,当中没有Variable
。如果所有的层都用nn.functional
来定义,那么所有的Variable
,如weights
,bias
等,都需要用户来手动定义,非常不方便。而如果所有的层都换成nn
来定义,那么即便是简单的计算都需要建类来做,而这些可以用更为简单的函数来代替的。所以在定义网络的时候,如果层内有Variable
,那么用nn
定义,反之,则用nn.functional
定义。(来源)
其二是少了一个num_flat_features函数,而是直接x.view(-1,16* 5* 5)代替,因为我们当时只是为了看看这个网络的输出,没实际意义,而这里的输入是有他的意义的,我们不要随意改变,所以直接把这些展成一维数组。
其实本质上和我们前面的没什么区别,很好理解的
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(),lr = 0.001,momentum = 0.9)
这里我们用到的损失函数是Cross-Entropy loss和带动量的随机梯度下降方法更新参数
交叉熵损失,在机器学习领域,多分类问题很常见,在很多深度学习模型当中,网络的输出层就是一个softmax层,对于NN分类问题,输出是一个NN维的向量,向量元素介于[0,1]之间,且元素累加和为1(这是softmax性质所决定的); 将softmax层输出向量视为预测类别的概率分布q(x),用真实类别标签构造真实的类别概率分布p(x)(例如,令真实类别概率为1,其余类别概率为0),那么相对熵DKL(P||Q就可以评价预测结果q(x)的好坏了,我们只需要最小化它就好了。 交叉熵等于基于预测概率分布q(x)对符合p(x)分布的字符集进行编码之后的平均字符编码长度,通过最小化交叉熵可以使q(x)逼近真实分布p(x),也就使得预测模型更优。来源
关于SGD我们不再多说,什么叫动量的呢,其实在这里我们更好地可以把它理解成一个摩擦系数,用来阻碍梯度的下降速度,使得梯度在下降到最低的时候不要再继续下降,更确切地说,不要再下降的过于迅猛
v = momentum * v - learning_rate * dx
这个代码就很直观的反映了momentum的作用
for epoch in range(2):#loop over the dataset multiple times
running_loss = 0.0
for i,data in enumerate(trainloader,0):
#get the input
inputs,labels = data
#zero the parameter gradients
optimizer.zero_grad()
#forward+backward+optimize
outputs = net(inputs)
loss = criterion(outputs,labels)
loss.backward()
optimizer.step()
#print statistics
running_loss += loss.item()
if i % 2000 == 1999:#print every 2000 mini-batches
print('[%d %5d] loss:%.3f'%(epoch+1,i+1,running_loss/2000))
running_loss = 0.0
print('Finished Training')
这里面的代码都比较容易理解,整个思路就是在训练集中做两次大循环训练,每2000张训练完成后输出一次当前的平均损失值,直到循环结束。
其中enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。使用起来非常便捷。
输出:
[1 2000] loss:1.919
[1 4000] loss:1.886
[1 6000] loss:1.862
[1 8000] loss:1.859
[1 10000] loss:1.846
[1 12000] loss:1.859
[2 2000] loss:1.838
[2 4000] loss:1.822
[2 6000] loss:1.812
[2 8000] loss:1.815
[2 10000] loss:1.825
[2 12000] loss:1.827
Finished Training
结果因电脑而异,本人电脑可能比较水,就没怎么把损失降下来。
首先我们来随机展示几张测试集的图片:
dataier = iter(testloader)
images,labels = dataiter.__next__()
#print images
imshow(torchvision.utils.make_grid(images))
print('GroundTruth:',' '.join('%5s'%classes[labels[j]]for j in range(4)))
接下来把这些图片放入我们的网络中看会给它们什么标签:
outputs = net(images)
_,predicted = torch.max(outputs,1)
print('Predicted: ',' '.join('%5s'%classes[predicted[j]]for j in range(4)))
torch.max就是输出最可能的那个预测
Predicted: Deer Deer Plane 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: 30 %
感觉不是特别棒,但还是那句话,因电脑而异
再来看看各个标签的正确率
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('Accurary of %5s : %2d %%'%(classes[i],100*class_correct[i]/class_total[i]))
Accurary of Plane : 62 %
Accurary of Car : 28 %
Accurary of Bird : 4 %
Accurary of Cat : 34 %
Accurary of Deer : 37 %
Accurary of Dog : 27 %
Accurary of Frog : 20 %
Accurary of Horse : 50 %
Accurary of Ship : 2 %
Accurary of Truck : 32 %
可以发现,在某些上预测还不错,但比如鸟和船的预测就没那么棒了,在我的直觉中,我的电脑可能把所有的船都当做飞船了吧
这次我们用了一个比较简单的卷积网络训练了一个分类器,效果不是很好,但算是入门了吧,更多的是明白一些函数的用处和其中的一些原理,因为我用的是cpu版的pytorch,所以就不涉及cuda以及gpu的操作了,大家有兴趣的可以自己看看,我也比较建议学一学,这次内容不少,但不难,是把之前的整个串了起来