在研究深度学习技术的过程中,选择了PyTorch框架作为技能树的基础。在经过几个实际项目的实践后,发现不仅需要继续巩固数学基础,还需要研究更复杂模型在框架中对应的高级实现。于是决定精研PyTorch框架,计划出一个框架实战系列,以官方教程中的实例为基础,以工程化方法实现。
本系列作为个人研究学习记录,不考虑让初学者看懂。
官方教程:https://pytorch.org/tutorials/
中文翻译教程:http://pytorch123.com/
中文教程有些翻译比较水,而且相比官网不太全,更新也不及时。建议英文基础比较好的,直接看官方原版教程。
本例教程:https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html#
中文教程:http://pytorch123.com/SecondSection/training_a_classifier/
# -*- coding: utf-8 -*-
from torchvision import datasets, transforms
data_path = './data' # 数据集
# 数据集转化为张量,并标准化
# input[channel] = (input[channel] - mean[channel]) / std[channel]
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
# transform = None
# 下载数据集
trainset = datasets.CIFAR10(root=data_path, train=True, download=True, transform=transform)
testset = datasets.CIFAR10(root=data_path, train=False, download=True, transform=transform)
# 查看数据集大小
print('trainset', len(trainset))
print('testset', len(testset))
CIFAR10数据集保存的是PIL.Image格式数据,图片尺寸32*32,RGB三通道,可以通过注释transform查看数据集的数据类型。
使用transforms先转化成PyTorch模型使用的张量[3, 32, 32],其数值范围为[0, 1],模型处理需要将其归一化为[-1, 1]的数值范围。Normalize第一个参数为每个通道的平均值列表,第二个参数为每个通道的标准差列表,transforms按下列公式做归一化处理:
input[channel] = (input[channel] - mean[channel]) / std[channel]
from torch.utils.data import DataLoader
batch_size = 10 # mini-batch
# 构造迭代器
trainloader = DataLoader(dataset=trainset, batch_size=batch_size, shuffle=True)
testloader = DataLoader(dataset=testset, batch_size=batch_size, shuffle=True)
# 迭代器输出的张量
for train, label in trainloader:
print(train.size(), label.size())
break
DataLoader可以很方便的通过数据集构造训练用的数据迭代器,batch_size指每次输入的样本个数,shuffle可以打乱样本顺序。查看一下迭代器每次输出的数据集和标签的张量:
torch.Size([10, 3, 32, 32]) torch.Size([10])
可以看到数据集张量的第四维度变成了样本的个数。
新建模型定义模块:CNN.py
使用教程定义的模型:
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
这个模型效果一般,主要是超参数的设置不太合理,后面再优化修改,本例主要感受模型训练损失的变化。
import torch.optim as optim
import torch.nn as nn
from CNN import Net
net = Net()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)
损失函数选择交叉熵损失函数,优化器使用Adam,相比教程中的SGD收敛效果要好一些。
# 在训练集上循环训练
for epoch in range(2):
# 损失值
running_loss = 0.0
for i, data in enumerate(trainloader):
# 获得输入张量和标签
inputs, labels = data
# 模型梯度归零,否则会不断累积。optimizer.zero_grad()也可以
net.zero_grad()
# 数据经过模型计算
outputs = net(inputs)
# 计算模型预测与实际标签的损失值
loss = criterion(outputs, labels)
# 梯度自动反向传播
loss.backward()
# 优化器执行优化,按梯度下降原则调整参数
optimizer.step()
running_loss += loss.item()
# 每1000轮输出一次损失值
if (i+1) % 1000 == 0:
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / 1000))
running_loss = 0.0
print('Finished Training')
输出:
[1, 1000] loss: 1.854
[1, 2000] loss: 1.553
[1, 3000] loss: 1.464
[1, 4000] loss: 1.411
[1, 5000] loss: 1.361
[2, 1000] loss: 1.299
[2, 2000] loss: 1.276
[2, 3000] loss: 1.234
[2, 4000] loss: 1.226
[2, 5000] loss: 1.192
Finished Training
可以看到loss在收敛,说明训练是有效果的。但因为模型的原因,还有训练次数不够,没有达到最佳效果。可以通过改进模型、增加训练次数和增加验证集的方式改进训练。
import torch
import numpy as np
from sklearn import metrics
predict_all = np.array([], dtype=int)
labels_all = np.array([], dtype=int)
# 测试预测时取消自动梯度,即向前计算时不自动求导
with torch.no_grad():
for images, labels in testloader:
outputs = net(images)
labels = labels.data.numpy()
predicted = torch.max(outputs.data, 1)[1].numpy()
labels_all = np.append(labels_all, labels)
predict_all = np.append(predict_all, predicted)
confusion = metrics.confusion_matrix(labels_all, predict_all)
print(u'混淆矩阵:')
print(confusion)
# 验证集准确度
acc = metrics.accuracy_score(labels_all, predict_all)
print(u'10000测试集图片的准确率: {0:.2%}'.format(acc))
可以先注释训练过程,看一下未训练的初始化模型的测试结果:
混淆矩阵:
[[ 0 995 0 0 0 0 5 0 0 0]
[ 0 991 0 0 0 0 9 0 0 0]
[ 0 995 0 0 0 0 5 0 0 0]
[ 0 999 0 0 0 0 1 0 0 0]
[ 0 1000 0 0 0 0 0 0 0 0]
[ 0 996 0 0 0 0 4 0 0 0]
[ 0 1000 0 0 0 0 0 0 0 0]
[ 0 996 0 0 0 0 4 0 0 0]
[ 0 990 0 0 0 0 10 0 0 0]
[ 0 992 0 0 0 0 8 0 0 0]]
10000测试集图片的准确率: 9.91%
结果训练后的测试结果:
混淆矩阵:
[[629 46 84 20 24 17 15 44 75 46]
[ 37 701 10 13 16 10 20 34 13 146]
[ 58 9 381 47 159 157 97 74 10 8]
[ 16 13 77 244 68 327 160 62 15 18]
[ 20 3 103 35 457 94 136 135 12 5]
[ 11 2 72 91 68 598 76 65 6 11]
[ 8 9 41 47 102 47 709 27 4 6]
[ 11 3 42 36 62 134 24 670 4 14]
[211 88 47 21 8 18 9 13 526 59]
[ 49 132 13 20 10 12 47 65 23 629]]
10000测试集图片的准确率: 55.44%
可以看到准确率从10%到55%,得到了提升。说明模型经过训练的确是学到了东西。
完整代码如下:train.py
# -*- coding: utf-8 -*-
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import torch.optim as optim
import torch.nn as nn
from CNN import Net
import torch
import numpy as np
from sklearn import metrics
data_path = './data' # 数据集
# 数据集转化为张量,并标准化
# input[channel] = (input[channel] - mean[channel]) / std[channel]
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
# transform = None
# 下载数据集
trainset = datasets.CIFAR10(root=data_path, train=True, download=True, transform=transform)
testset = datasets.CIFAR10(root=data_path, train=False, download=True, transform=transform)
# 查看数据集大小
print('trainset', len(trainset))
print('testset', len(testset))
batch_size = 10 # mini-batch
# 构造迭代器
trainloader = DataLoader(dataset=trainset, batch_size=batch_size, shuffle=True)
testloader = DataLoader(dataset=testset, batch_size=batch_size, shuffle=True)
# 迭代器输出的张量
for train, label in trainloader:
print(train.size(), label.size())
break
# 模型实例化,指定损失函数和优化器,学习率0.001
net = Net()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)
# 未经训练的模型测试效果
predict_all = np.array([], dtype=int)
labels_all = np.array([], dtype=int)
# 测试预测时取消自动梯度,即向前计算时不自动求导
with torch.no_grad():
for images, labels in testloader:
outputs = net(images)
labels = labels.data.numpy()
predicted = torch.max(outputs.data, 1)[1].numpy()
labels_all = np.append(labels_all, labels)
predict_all = np.append(predict_all, predicted)
confusion = metrics.confusion_matrix(labels_all, predict_all)
print(u'混淆矩阵:')
print(confusion)
# 验证集准确度
acc = metrics.accuracy_score(labels_all, predict_all)
print(u'10000测试集图片的准确率: {0:.2%}'.format(acc))
# 在训练集上循环训练
for epoch in range(2):
# 损失值
running_loss = 0.0
for i, data in enumerate(trainloader):
# 获得输入张量和标签
inputs, labels = data
# 模型梯度归零,否则会不断累积。optimizer.zero_grad()也可以
net.zero_grad()
# 数据经过模型计算
outputs = net(inputs)
# 计算模型预测与实际标签的损失值
loss = criterion(outputs, labels)
# 梯度自动反向传播
loss.backward()
# 优化器执行优化,按梯度下降原则调整参数
optimizer.step()
running_loss += loss.item()
# 每1000轮输出一次损失值
if (i+1) % 1000 == 0:
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / 1000))
running_loss = 0.0
print('Finished Training')
# 训练后的模型测试效果
predict_all = np.array([], dtype=int)
labels_all = np.array([], dtype=int)
# 测试预测时取消自动梯度,即向前计算时不自动求导
with torch.no_grad():
for images, labels in testloader:
outputs = net(images)
labels = labels.data.numpy()
predicted = torch.max(outputs.data, 1)[1].numpy()
labels_all = np.append(labels_all, labels)
predict_all = np.append(predict_all, predicted)
confusion = metrics.confusion_matrix(labels_all, predict_all)
print(u'混淆矩阵:')
print(confusion)
# 验证集准确度
acc = metrics.accuracy_score(labels_all, predict_all)
print(u'10000测试集图片的准确率: {0:.2%}'.format(acc))
后期个人对此模型进行了优化,详见:PyTorch框架实战系列(2)——图像分类器优化
问题:
PyTorch当完成训练后,程序并不能自动停止。只要使用backward()函数反向传播计算梯度,进程就不会自动释放,而且不管是点stop按钮,还是加quit(0)或sys.exit(0)都不管用,每次必须用资源管理强制杀掉python的进程。
一直没有解决这个问题,网上也没找到实际能够解决的方法。简直逼死强迫症!
如果有哪位解决了这个问题,请留言分享一下,万分感谢。