主要实现
- 掌握pytorch自带数据集的导入
- 初步编写DataLoader
- 定义模型、损失和优化器
- 训练简单神经网络
- 将模型结果保存至本地
参考https://zhuanlan.zhihu.com/p/128137225
0. 库函数
import torch
import torchvision
from torch.autograd import Variable
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import numpy as np
import cv2
1. 数据部分
1.1 Dataset
从torchvision.datasets
自带的数据集中下载、导入MNIST数据集
train_dataset = datasets.MNIST(
root = 'data/', # 文件夹名称
train = True, # 是否为训练集
transform = transforms.ToTensor(), # 变换为张量
download = True # 如果数据集不存在则下载
)
test_dataset = datasets.MNIST(
root = 'data/',
train = False,
transform = transforms.ToTensor(),
download = True
)
1.2 DataLoader
使用torch.utils.data.DataLoader()
声明数据发生器
train_loader = DataLoader(
dataset=train_dataset,
batch_size=100,
shuffle=True
)
test_loader = DataLoader(
dataset=test_dataset,
batch_size=100,
shuffle=True
)
1.3 数据可视化
# 显示第一个batch的第一张图片
plt.figure("Image") # 图像窗口名称
plt.figure(figsize=(4,4))
plt.imshow(np.array(train_dataset[0][0])[0], cmap=plt.cm.binary)
plt.axis('on') # 关掉坐标轴为 off
plt.title('image') # 图像题目
plt.show()
2. 定义模型
class Model(torch.nn.Module):
'''
自定义模型继承自torch.nn.Module
将各种层定义到self中
要复写forward()
'''
def __init__(self):
super(Model, self).__init__()
# 卷积层
self.conv1 = torch.nn.Sequential(
torch.nn.Conv2d(1, 64, 3, 1, 1), # 卷积,从1通道卷积成64通道,需要64个卷积核
torch.nn.ReLU(), #激活层
torch.nn.Conv2d(64, 128, 3, 1, 1),
torch.nn.ReLU(),
torch.nn.MaxPool2d(2, 2) # 池化层
)
# 全连接层
self.dense = torch.nn.Sequential(
torch.nn.Linear(14*14*128, 1024),
torch.nn.ReLU(),
torch.nn.Dropout(p = 0.5),
torch.nn.Linear(1024, 10) # 最后一层无需激活
)
def forward(self, x):
x = self.conv1(x)
x = x.view(-1, 14*14*128) # 改变数据形状
x = self.dense(x)
return x
2.1 顺序容器——Sequential()
torch.nn.Sequential()
是一个有序的容器。自定义的神经网络模块按照Sequential()的参数顺序依次执行。
因为自定义的Model继承自nn.Module
下,所以在forward函数中通过for循环依次执行Sequential()中的各模块。
Sequential参考:https://blog.csdn.net/dss_dssssd/article/details/82980222
2.2 卷积层——Conv2d
nn.Conv2d
:对由多个输入平面组成的输入信号做二维卷积。(Applies a 2D convolution over an input signal composed of several input planes.)
函数定义为
torch.nn.Conv2d(
in_channels,
out_channels,
kernel_size,
stride=1,
padding=0,
dilation=1,
groups=1,
bias=True
)
参数
参数 | 参数类型 | info |
---|---|---|
in_channel | int | 输入图像通道数 |
out_channel | int | 输出图像通道数(同时也是卷积核个数) |
kernel_size | int or (tuple) | 卷积核大小 |
stride | int or (tuple) | 卷积步长,默认为1 |
padding | int or (tuple) | 填充操作个数 |
padding_mode | string | 填充模式 |
dilation | int or (tuple) | 扩张操作(空洞卷积) |
groups | int | 控制分组卷积 |
bias | bool | 是否添加可学习的偏置 |
卷积参考:https://blog.csdn.net/qq_34243930/article/details/107231539
2.3 全连接层——dense
将第一个卷积网络层(self.conv1
)的输出压缩至一维后输入dense层。
nn.Linear()
是类似于Ax+b的函数,可以设置bias=True or False表示是否有偏置值b。nn.Dropout(x, p)
确保没有过拟合。参数x是输入(上述代码中因为Dropout在Sequential中,所以省去了x);p是需要删除的神经元的比例(p=0时保留全部神经元;p=1时神经元输出值为0)
dense层输入值计算
输 出 = ( i n p u t − k e r n e l + 1 + 2 ∗ p a d d i n g ) / s t r i d e 输出 = (input - kernel + 1 + 2 * padding)/ stride 输出=(input−kernel+1+2∗padding)/stride
- 第一个卷积层使用了64个3*3的卷积核,步长为1,填充数为1。计算得到的输出大小为 ( 28 − 3 + 1 + 1 ∗ 2 ) / 1 = 28 (28 - 3 + 1 + 1 * 2) / 1 = 28 (28−3+1+1∗2)/1=28。 即64 * 28 * 28
- 第二个卷积层输出大小同理为28。即128 * 28 *28
- 第三个池化层,kernel为2, 步长为2。输出大小为14 = 28 / 2。
由此,卷积网络最后的得到的是128 * 14 * 14的输出
2.4 前馈——forward
将从卷积网络得到的输出x经过变形后送给全连接层
x = x.view(-1, 14*14*128)
3. 损失函数与优化方法
cuda_available = torch.cuda.is_available()
# 如果cuda可行,使用cuda
if cuda_available:
print("cuda loaded")
device = torch.device('cuda')
model = Model().to(device)
else:
model = Model()
# 定义损失函数
cost = torch.nn.CrossEntropyLoss() # 交叉熵损失
optimizer = torch.optim.Adam(model.parameters()) # Adam优化器
cuda loaded
3.1 实例化模型
model = Model().to(torch.device('cuda'))
使用gpu算力。
3.2 交叉熵损失
公式为 H ( p , q ) = − ∑ i p i ∗ l o g q i H(p, q) = - \sum_{i}{p_{i} * log q_{i}} H(p,q)=−∑ipi∗logqi
3.3 优化器
使用Adam优化器,结合RMSProp(自适应调整学习率)和Momentum(动量)。
- RMSProp方法不断更新xuexil
- Momentum通过上一次的更新增强或削弱梯度。如果梯度方向与上一次相同,则增强;不同,则削减。
4. 训练与测试
if __name__ == '__main__':
# train
model.train()
epochs = 5
for epoch in range(epochs):
# 训练5次
sum_loss = 0.0
train_correct = 0
for data in train_loader:
# data是一个batch,而不是一条数据
inputs, labels = data
inputs, labels = Variable(inputs).cuda(), Variable(labels).cuda() # 包装Tensor
optimizer.zero_grad()
outputs = model(inputs)
loss = cost(outputs, labels)
loss.backward()
optimizer.step() # 梯度下降——更新参数
_, id = torch.max(outputs.data, 1)
sum_loss += loss.data
train_correct += torch.sum(id == labels.data)
print('[%d,%d] loss:%.03f' % (epoch + 1, epochs, sum_loss / len(train_loader)))
print(' correct:%.03f%%' % (100 * train_correct / len(train_dataset)))
# test
model.eval() 测试需要用到model的eval()模式,以免将测试数据也用于训练。
with torch.no_grad():
test_correct = 0
for data in test_loader:
inputs, lables = data
inputs, lables = Variable(inputs).cuda(), Variable(lables).cuda()
outputs = model(inputs)
_, id = torch.max(outputs.data, 1)
test_correct += torch.sum(id == lables.data)
print("\ntest correct:%.3f%%" % (100 * test_correct / len(test_dataset)))
输出结果
[1,5] loss:0.006
correct:99.803%
[2,5] loss:0.006
correct:99.800%
[3,5] loss:0.006
correct:99.798%
[4,5] loss:0.005
correct:99.835%
[5,5] loss:0.005
correct:99.865%
test correct:99.160%
4.1 训练
本轮训练进行了5次,即5个epoch。
在训练每个batch时,要依次进行
- 清零上一个batch计算得到的梯度,以免梯度累加
- 计算损失loss
- 利用backward()对损失loss进行反向传播
- 利用step()对优化器进行梯度下降(参数数据都保存在optimizer中)
打印精度
torch.max(a,b)
对a中的固定第b维的情况下,计算最大值,返回最大值及其索引。这里是固定outputs的列,对行求最大值。outputs返回的值可以看作是归属每个类的概率,取最大概率作为最终结果。
Variable
autograd.Variable()
包装一个Tensor,使其包括.data
和.grad
两种属性。用Variable()
封装好的张量可以进行.backward()
反向传播运算。
Variable():https://blog.csdn.net/scutjy2015/article/details/71214928/
4.2 测试
在模型中,我们通常会加上Dropout层和batch normalization层,在模型预测阶段,我们需要将这些层设置到预测模式。否则会导致不一致的预测结果。
通常也使用torch.no_grad()
关闭梯度的计算,如
model.eval()
with torch.no_grad():
...
out_data = model(data)
...
详细model.eval():https://zhuanlan.zhihu.com/p/356500543
5. 保存模型
将训练结果保存至本地,下次训练直接load结果
#save
torch.save(model.state_dict(), "parameter.pkl")
#load
model.load_state_dict(torch.load('parameter.pkl'))
输出结果
< All keys matched successfully >