【PyTorch官方教程中文版学习笔记04】PyTorch 图像分类器
前言:
通常来说,当你处理图像,文本,语音或者视频数据时,你可以使用标准 python 包将数据加载成
numpy 数组格式,然后将这个数组转换成 torch.*Tensor
- 对于图像,可以用 Pillow,OpenCV
- 对于语音,可以用 scipy,librosa
- 对于文本,可以直接用 Python 或 Cython 基础数据加载模块,或者用 NLTK 和 SpaCy
对于视觉,我们已经创建了一个叫做 totchvision 的包,该包含有支持加载类似Imagenet,CIFAR10,MNIST 等公共数据集的数据加载模块torchvision.datasets 和支持加载图像数据数据转换模块 torch.utils.data.DataLoader。
torchvision.models库提供了众多经典的网络结构与预训练模型,例如VGG、ResNet和Inception等,利用这些模型可以快速搭建物体检测网络,不需要逐层手动实现。
详见torchvision — Torchvision 0.12 documentation (pytorch.org)
torchvision包与PyTorch相独立,需要通过pip指令进行安装。
pip install torchvision -i https://pypi.tuna.tsinghua.edu.cn/simple #对比发现,使用清华源下载torchvision包相对来说是最快的
对于本教程,我们将使用CIFAR10数据集,它包含十个类别:‘airplane’, ‘automobile’, ‘bird’, ‘cat’,
‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。CIFAR-10 中的图像尺寸为3*32*32,也就是RGB的3层颜色
通道,每层通道内的尺寸为32*32。
Step1: 加载数据集CIFAR10
- 引用到的工具包
1 import torch 2 import torchvision 3 import torchvision.transforms as transforms 4 import matplotlib.pyplot as plt 5 import numpy as np
- 加载并归一化 CIFAR10
1 transform = transforms.Compose( 2 [transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) 3 #transforms.Compose():即组合几个变换方法,按顺序变换相应数据,对应于问题描述中代码,即先应用ToTensor()使[0-255]变换为[0-1],再应用Normalize自定义标准化 4 #transforms.ToTensor():转换一个PIL库的图片或者numpy的数组为tensor张量类型;转换从[0,255]->[0,1];原理即各值除以255 5 #transforms.Normalize():通过平均值和标准差来标准化一个tensor图像,公式为:output[channel] = (input[channel] - mean[channel]) / std[channel] 6 #由于ToTensor()已经将图像变为[0,1],我们使其变为[-1,1] 7 #第一个(0.5,0.5,0.5) 即三个通道的平均值 8 #第二个(0.5,0.5,0.5) 即三个通道的标准差值 9 #ps:三个通道是因为CIFAR-10 中的图像尺寸为3*32*32 10 #以第一个通道为例,将最大与最小值代入公式 11 #(0-0.5)/0.5=-1 12 #(1-0.5)/0.5=1 13 14 #训练集 15 trainset = torchvision.datasets.CIFAR10(root='./data', train=True, 16 download=True, transform=transform) 17 #torchvision.datasets.CIFAR10 就指定了 CIFAR-10 这个数据集,这个模块定义了它如何去下载数据集,及如何从本地加载现成的数据。 18 #root 指定了数据集存放的位置,train 指定是否是训练数据集。 19 trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, 20 shuffle=True, num_workers=2) 21 #数据集需要配合 DataLoader 使用,DataLoader 从数据集中不断提取数据然后送往 Model 进行训练和预测。 22 #在这里,指定了 batch size 为 4,也就是 mini-batch 单批次图片的数量为 4. 23 #shuffle = True 表明提取数据时,随机打乱顺序,因为我们都是基于随机梯度下降的方式进行训练优化,但测试的时候因为不需要更新参数,所以就无须打乱顺序了。 24 #num_workers = 2 指定了工作线程的数量。 25 26 #测试集 27 testset = torchvision.datasets.CIFAR10(root='./data', train=False, 28 download=True, transform=transform) 29 #同上,但train=False 30 testloader = torch.utils.data.DataLoader(testset, batch_size=4, 31 shuffle=False, num_workers=2) 32 #同上 33 34 classes = ('plane', 'car', 'bird', 'cat', 35 'deer', 'dog', 'frog', 'horse', 'ship', 'truck') 36 #标签数组
- 显示数据集(使用到matplotlib,安装详情移步主页)
def imshow(img): img = img / 2 + 0.5 # unnormalize npimg = img.numpy() #img转换成numpy plt.imshow(np.transpose(npimg, (1, 2, 0))) #因为在plt.imshow在现实的时候输入的是(imagesize,imagesize,channels) # 但是现在imshow中的参数img的格式为(channels,imagesize,imagesize), # 这两者的格式不一致,我们需要调用一次np.transpose函数,即np.transpose(npimg,(1,2,0)), # 将npimg的数据格式由(channels,imagesize,imagesize)转化为(imagesize,imagesize,channels),进行格式的转换后方可进行显示。 plt.show() dataiter = iter(trainloader) # get some random training images images, labels = dataiter.next() # show all images as one image grid imshow(torchvision.utils.make_grid(images)) # Show the real labels on the screen print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
Step2: 搭建神经网络
这里用的是上一篇搭建好的神经网络,修改它为3通道的图片(在此之前它被定义为1通道)
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()
Step3: 定义一个损失函数和优化器
1 criterion = nn.CrossEntropyLoss() 2 #分类交叉熵Cross-Entropy 作损失函数 3 optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)#lr learning rate 学习速率,学习速率0.01比较小的时候比较稳定 4 5 for epoch in range(2): # loop over the dataset multiple times,进行2轮的学习训练 6 running_loss = 0.0 7 for i, data in enumerate(trainloader, 0):#enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。 8 # get the inputs 9 inputs, labels = data 10 # zero the parameter gradients 11 optimizer.zero_grad() 12 # forward + backward + optimize 13 outputs = net(inputs) 14 loss = criterion(outputs, labels) 15 loss.backward() 16 optimizer.step() 17 # print statistics 18 running_loss += loss.item() 19 if i % 2000 == 1999: # print every 2000 mini-batches 20 print('[%d, %5d] loss: %.3f' %(epoch + 1, i + 1, running_loss / 2000)) 21 running_loss = 0.0 22 print('Finished Training')
Step4: 训练网络
#得到结果(训练时间可能会比较长) [1, 2000] loss: 2.226 [1, 4000] loss: 1.925 [1, 6000] loss: 1.711 [1, 8000] loss: 1.567 [1, 10000] loss: 1.510 [1, 12000] loss: 1.457 [2, 2000] loss: 1.400 [2, 4000] loss: 1.370 [2, 6000] loss: 1.350 [2, 8000] loss: 1.330 [2, 10000] loss: 1.303 [2, 12000] loss: 1.274 Finished Training
Step5: 在测试网络上测试数据集
outputs = net(images) _, predicted = torch.max(outputs.data, 1) #其中这个 1代表行,0的话代表列。 # 不加_,返回的是一行中最大的数。 # 加_,则返回一行中最大数的位置。 print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))
这里只训练了一轮,精确度很低。
Step6: 检验精确度
#总体精确度 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))
#10个种类各自的精确度 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]))
Step7: Trainning on GPU
将神经网络传到GPU上
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # Assume that we are on a CUDA machine, then this should print a CUDA device: print(device)
#输出 cuda:0
接下来我们都是假设设备是有CUDA的设备。
然后这些方法将会递归的遍历所有Modules并且将他们的参数和缓存传入到CUDA tensors中:
net.to(device)
记住,你还必须将每一步的inputs和targets传入GPU中:
inputs, labels = input.to(device), labels.to(device)