在了解了如何定义神经网络、计算损失和更新神经网络的权重后,你可能会想:
What about data?
一般来说,当你要处理图像、文字、音频或视频数据时,你可以使用标准的python包来把数据载入numpy数组。然后就可以把这个数组转化为torch.*Tensor。
- 对图像来说,Pillow和OpenCV包很有用。
- 对音频来说,scipy和librosa包很有用。
- 对文字来说,无论是基于载入的原生Python(raw Python)和Cython,还是NLTK和SpaCy都很有用。
而对视觉来说,我们创建了一个torchvision包,它有着对常规数据集——如imagenet,CIFAR10,MINST等的载入器,以及图像的数据转换器,即torchvision.datasets和torch.utils.data.DataLoader。
这提供了巨大的方便,且避免了书写样板数据。
在本教程中,我们会使用CIFAR10数据集,它有“飞机”、“汽车”、“鸟”、“猫”、“鹿”、“狗”、“青蛙”、“马”、“船”、“卡车”十类数据。CIFAR10数据集中的图片大小为3×32×32,即为三个颜色通道,大小为32×32的图像。
Training an image classifier
我们将按顺序做以下步骤:
- 使用torchvision加载并规范化CIFAR10的训练和测试数据。
- 定义卷积神经网络。
- 定义损失函数。
- 使用训练数据训练神经网络。
- 使用测试数据测试神经网络。
1.加载和规范化CIFAR10
import torch
import torchvision
import torchvision.transforms as transforms
torchvision数据集的输出是范围为[0,1]的PILImage。我们把它规范到[-1,1]的张量。
## Compose()效果为遍历所有transforms操作,更详细的信息见
## https://blog.csdn.net/lwplwf/article/details/85776309
## ToTensor()将PILImage转换为张量,便于下一步操作
## 规范化,其两个参数分别为均值和方差。由于原数据的范围为[0,1]
## 这样就将其转化到了[-1,1]。而之所以每个参数由三个数值组成,
## 是因为数据有三个通道
transform = transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5,0.5,0.5), (0.5,0.5,0.5))])
## root : cifar-10-batches-py 的根目录
## train : True = 训练集, False = 测试集
## download : True = 从互联上下载数据,并将其放在root目录下。
## 如果数据集已经下载,什么都不干。
## 更详细的见
## https://pytorch-cn.readthedocs.io/zh/latest/torchvision/torchvision-datasets/
trainset = torchvision.datasets.CIFAR10(root='./data',
train=True,download=True,transform=transform)
## trainset为输入的datasets类数据
## batch_size:每个batch加载的样本数
## shuffle若为True则在每个epoch打乱顺序,默认为None,为了能随机取样,在训练时通常打开
## num_workers设置加载数据时使用的子进程数,默认为0时使用主进程
## epoch即指一轮迭代
## 更详细的见
## https://blog.csdn.net/HowardWood/article/details/79508690
## https://blog.csdn.net/u014380165/article/details/79058479
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'}
运行后就会开始下载:
如果已经下载了该数据则会提示:
我们可以查看一下训练图片:
import matplotlib.pyplot as plt
import numpy as np
def imshow(img):
## 反规范化
img = img/2+0.5
## 转换为numpy
npimg = img.numpy()
## 绘图,其中transpose的效果是,将原来的第二维作为现在的第一维
## 原来的第三维作为现在的第二维,原来的第一维作为现在的第三维
plt.imshow(np.transpose(npimg,(1,2,0)))
plt.show()
## 关于iter和next,见https://blog.csdn.net/xun527/article/details/76652189
dataiter = iter(trainloader)
image,labels = dataiter.next()
## torchvision.utils.make_grid()的效果是将第一个输入数据拼接为一幅图片
## 更详细的见 https://blog.csdn.net/u012343179/article/details/83007296
imshow(torchvision.utils.make_grid(images))
## join()使用调用它的字符串连接它的输入值
## 更详细的见 https://www.runoob.com/python/att-string-join.html
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
其结果为:
2.定义卷积神经网络
从之前的神经网络复制部分代码,并对其进行修改以接受三通道图像(而不是像它之前定义的接受单通道图像)。
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
## 此处super指代nn.module
super(Net,self).__init__()
## 三个参数分别为输入图像的通道数,输出通道数以及正方形卷积的边长
self.conv1 = nn.Conv2d(3,6,5)
self.pool = nn.MaxPool2d(2,2)
self.conv2 = nn.Conv2d(6,16,5)
## 建立仿射函数 y = Wx + b
## 两个参数分别为该层神经网络的输入参数数和输出参数数
self.fc1 = nn.Linear(16 * 6 * 6,120)
self.fc2 = nn.Linear(120,84)
self.fc3 = nn.Linear(84,10)
def forward(self,x):
## 通过(2,2)的窗口进行最大池化
x = self.pool(F.relu(self.conv1(x))
## 当窗口为正方形时,参数可以为一个数
x = self.pool(F.relu(self.conv2(x)))
## 将x拍扁为一维向量,以传入fc1,num_flat_features为元素数量
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()
3.定义损失函数和优化器
我们使用分类交叉熵损失(Classfication Cross-Entropy loss)和SGD动量。
import torch.optim as optim
criterion = nn.crossEntropyLoss
optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
4.训练网络
这是事情开始变得有趣的部分。我们只需要循环遍历数据迭代器,并将输入提供给网络并进行优化。
## 迭代的次数
for epoch in range(2):
running_loss = 0.0
## enumerate将一个可遍历的数据对象变为一个索引序列
for i,data in enumerate(trainloader,0):
inputs,labels = data
optimizer.zero_grad()
outputs = net(inputs)
loss = criterion(outputs,labels)
loss.backward()
optimizer.step()
## 这一步通过损失来得到所有的损失值。loss.item()为损失的数值
## 这一步不使用张量相加是为了避免张量累加,形成一个庞大的计算图
runing_lose += loss.item()
## 每2000个样本输出一次
if i%2000 == 1999:
print('[%d,%5d] loss: %.3f' % (epoch + 1,i+1,running_loss/2000))
running_loss = 0.0
print('Finished Training')
输出结果为:
5.使用测试数据测试神经网络
我们已经在数据集上训练了两次神经网络。现在我们需要知道神经网络是否学到了什么东西。
我们通过对比神经网络输出的结果与实际情况来确定神经网络是否学到了东西。如果输出结果正确,我们就把该样本加入到正确预测的列表内。
第一步,我们先看一看我们的测试数据。
dataiter = iter(testloader)
image,labels = dataiter.next()
imshow(torchvision.utils.make_grid(image))
print('GroundTrueth:',''.join('%5s' % classes[labels[j]] for j in range(4)))
输出结果为:
我们现在来看看神经网络是如何看待上述例子的:
outputs = net(image)
输出为该图像属于十类的概率,输入某个类的概率越高,神经网络就越认为该图像属于该类。所以,让我们看看最高概率的索引吧:
_,predicted = torch.max(outputs,1)
print('Predict:',' '.join('%5s' % classes[predicted[j]] for j in range(4)))
结果为:
结果看上去很不错。
让我们看看神经网络在整个数据集上的表现如何:
correct = 0
total = 0
## with函数首先执行它右侧的__enter__(),在运行完其下的代码后,在执行它右侧的__exit__()
## 更详细的见 https://www.cnblogs.com/wanglei-xiaoshitou1/p/9238275.html
with torch.no_grad():
for data in testloader:
images,labels = data
outputs = net(images)
_,predicted = torch.max(outputs,1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('10000幅测试数据的准确率为:%d %%' % correct/total*100)
结果为:
这看上去比随便乱选——只有10%的准确率,要好很多了。看起来神经网络真的有学到东西。
那么那些类别分类较好,而哪些较差呢:
## 构建一个由10个0.0元素构成的列表
class_correct = list(0. for i in range(10))
## 不直接复制class_correct是因为这样会共享存储
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)
## 经过squeeze后并没有发生变化, 不知道这里为什么要这样做
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('%5s的准确率为:%2d %%' % (classes[i],
100 * class_correct[i] / class_total[i]))
结果为:
在GPU上训练
就像把张量转移到GPU上一样,也可以把神经网络转移至GPU。
如果我们有可用的CUDA设备的话,我们把我们的设备定义为第一个可用的cuda设备。
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
结果为:
然后这些方法将递归地遍历所有地模块并将其参数和缓冲区转换为CUDA张量:
net.to(device)
但这里出现了一个问题:
奇怪的是,什么都没有做,早上起来再运行时却正常了。也许是服务器的问题,也可能是重启了ipynb文件/Jupyter/电脑,不得而知。
除了将神经网络送入GPU外,还要在每一步都将输入和目标发到GPU上:
inputs,labels = data[0].to(device),data[1].to(device)