最近尝试学习使用了深度学习框架pytorch,记录如下内容。主要是分析例程CIFAR10数据分类网络。
1,网络的定义
网络部分的定义主要是两个部分。
(1)组成网络的各个模块定义。如卷积层,池化层,全连接层等。
a. 卷积层定义。
卷积层定义使用函数torch.nn.Conv2d(),主要参数包括in_channels,out_channels,kernel_size等。这里主要介绍这三个参数的含义和使用方法。in_channels是输入数据的通道数,如,若是第一层卷积,输入是一个RGB图像,则in_channels=3;若不是第一次卷积层,即前面已有卷积等操作,则in_channels等于上一层操作的输出的图像的数量值。out_channels代表卷积核的数量,也代表这层卷积输出的图像数量。kernel_size是卷积参数,可以是整数或者图元tuple类型。若是整数类型,则宽和高的参数是一样的。关于卷积中的channel参数的理解可以参照下面的链接:
https://blog.csdn.net/qq_34107425/article/details/104117670
下面是一张彩色图像经过一层卷积的过程:
如一张彩色图像的尺寸参数为32 * 32 * 3;卷积参数为torch.nn.Conv2d(3,6,5);则可以看到kernel_size = 5 * 5 * 3,in_channel = 3, out_channel = 6; 则经过这层卷积的输出为(32-5+1) * (32-5+1) * 6.
b. 池化层定义。
以2维池化为例。函数torch.nn.MaxPool2d()用于图像的池化。主要参数为kernel_size, stride_size。kernel_size参数是池化时的参数,包括宽和高,可以是整数或者图元tuple类型,是整数时,宽和高相等。stride_size是滑动尺寸参数,也包括宽和高,同样的,可以是整数或者图元tuple类型,是整数时,宽和高相等。池化操作通常在卷积层后面进行操作。
如下是一个池化操作的过程:
上一层卷积的输出为 28 * 28 * 6,再经过一个池化层torch.nn.MaxPool2d(2,2),可知池化kernel_size = 2 * 2,stride_size = 2 * 2。即对卷积层输出的6个输出分别进行池化操作得到14 * 14 * 6的尺寸。
c. 全连接层定义。
全连接层就是矩阵和向量相乘在加上一个向量的操作了。全连接层函数使用torch.nn.Linear()进行操作,主要参数就是矩阵的行数和列数,即输入行数尺寸的数据,输出列数尺寸的数据。即全连接权重参数的尺寸就是行数乘以列数。
如下是一个全连接层的使用过程:
上一层数据的输出尺寸为16 * 5 * 5的行,使用全连接函数torch.nn.Linear(16 * 5 * 5,120),则可知全连接层的输出尺寸是120。
(2)网络的前向计算过程,即整个网络的前向流程。
这部分就是将网络各模块层进行组织成一个完整的网络结构。如卷积层1->激活计算->池化层1->卷积层2->激活计算->池化层2->数据reshape操作->全连接层1->激活计算->全连接层2->激活计算->全连接层3->输出。
下面是例程的网络定义部分代码。
# 定义一个卷积神经网络
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) #相当于 reshape 成 16 * 5 * 5 列, 行数自适应
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
2,网络的训练过程
a, 在进行网络训练之前需要进行损失函数的选择和优化函数的选择。
损失函数用于计算训练输出和目标输出之间的差距,如torch.nn.CrossEntropyLoss(),这个是交叉熵损失函数,常用于分类问题中。详见下面两个链接:
https://blog.csdn.net/geter_CS/article/details/84857220
https://blog.csdn.net/Jeremy_lf/article/details/102725285
优化函数是用于反向计算时计算更新权重等参数等的优化算法。如torch.optim.SGD()函数等。基本使用需要搞懂参数的设置。
b, 网络的训练过程主要是两个嵌套循环过程。
外层循环是整个训练数据集的多次训练过程,常用epoch代表,这是为了充分利用训练数据,使得训练得到很好的结果。
内层训练就是训练数据集的一次训练过程,每次循环是一个前向和反向过程的结束,也是一个batch数据的训练过程。一次循环过程包括:一个batch数据及其标签的获取;一次正向过程的计算,一次损失函数的计算,一次损失反向计算,一次模型更新等。
下面为此例程的训练过程代码:
# 定义损失函数和优化器
import torch.optim as optim
criterion = nn.CrossEntropyLoss() # 交叉熵损失
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
#训练网络
for epoch in range(2):
running_loss = 0.0
for i, data in enumerate(trainloader, 0):
# 获取输入
inputs, labels = data
# print(inputs.size())
# print(labels.size())
# 梯度置0
optimizer.zero_grad()
# 正向传播,反向传播,优化
outputs = net(inputs)
# print(outputs.size())
loss = criterion(outputs, labels)
# print(loss.size())
loss.backward()
optimizer.step() # 更新模型
# 打印状态信息
running_loss += loss.item() # 每个batch训练得到的损失求和
if i % 2000 == 1999: # 每2000批次(batch)打印一次
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / 2000))
running_loss = 0.0
print('Finished Training')
3,网络的测试过程
例程中测试主要从三个方面:
1,随机从测试集中选择一个batch的数据进行分类预测,输出类别名称与实际的类别进行比较。代码如下:
# 测试
dataiter = iter(testloader)
images, labels = dataiter.next()
# 显示图片
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)))
2,对整个测试集数据进行分类测试,计算分类正确率。
# 整个测试集测试
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))
3,多整个测试集进行分类识别,计算每一类别的识别正确率。
# 识别
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]))
4,数据获取和预处理等补充代码
# pytorch实列 训练一个分类器
# step1: 读取和归一化CIFAR10
import torch
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
# if __name__ == '__main__':
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')
# 展示一些训练图像
def imshow(img):
img = img / 2 + 0.5
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
# 获取随机数据
dataiter = iter(trainloader)
images, labels = dataiter.next()
# # 展示图像
# imshow(torchvision.utils.make_grid(images))
# # 显示图像标签
# print(''.join('%5s' % classes[labels[j]] for j in range(4)))
5,遇到的两个问题及其解决方法:
a, 数据集下载过慢问题。
解决办法:按住control,鼠标点击代码中torchvision.datasets.CIFAR10的CIFAR10的,跳到cifar.py文件,将url = "https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz"改为url = “http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz”。速度明显提升。
b, torch.utils.data.DataLoader(trainset, batch_size=4,
shuffle=True, num_workers = 2) 函数中加上num_workers参数后出现错误。
解决办法:在代码中加入if name == ‘main’: (如代码所示)。
但是加上后,数据加载明显速度变慢。参考链接:
https://github.com/pytorch/pytorch/issues/12831