少女祈祷中...

数据集

数据集介绍

MNIST是一个手写数字的数据集,专门用于进行深度学习训练,属于是深度学习中的”Hello World”,已经算是经典中的经典。其中有60000个训练集和10000个测试集,每个数据包含了28×28的灰度图像和对应的标签0~9。

获取数据集

1
2
3
4
5
6
7
8
import torchvision
from torchvision import transforms

trans = transforms.ToTensor() #将图像数据直接转为张量。

#root指定数据路径,train指定训练集或测试集,transform指定转换格式,download表示是否需要下载,下载一次就可以了。
mnist_train = torchvision.datasets.MNIST(root='./',train=True,transform=trans,download=False)
mnist_test = torchvision.datasets.MNIST(root='./',train=True,transform=trans,download=False)

观察数据集

要建立神经网络,必须要了解数据集的格式或者说形状。

1
2
3
4
5
6
import matplotlib.pyplot as plt
print('type:',type(mnist_train[1]))
mnist_train[1][0].shape
plt.imshow(mnist_train[1][0].reshape(28,-1,1),cmap='gray') #不写cmap的话会默认将灰度图像映射成彩色图像。
plt.show()
mnist_train[1][1]
1
2
3
4
type: <class 'tuple'>
torch.Size([1, 28, 28])
<matplotlib.image.AxesImage object at 0x000002A41C7D7B80>
0

206f522ece9c5884a7d1e11c4a6ca5b7.png

根据以上的输出结构,可以确定每个数据都是一个元组,由28×28的图像和整数标签组成。

加载数据

通常,我们会选择创建一个数据加载器,进行小批量数据训练。下面代码中batch_size就是批量大小,一般会设定为2的整数次方,这是为了充分利用GPU的并行计算能力,如果没有用GPU进行计算,那倒是无所谓。不过批量过小和学习率过高可能会导致最后的损失不收敛。

1
2
3
from torch.utils import data
train_load = data.DataLoader(mnist_train,batch_size=64,shuffle=True) #shuffle表示是否随机打乱。
test_load = data.DataLoader(mnist_test,batch_size=64,shuffle=True)

建立模型

神经网络类和实例化

一般会选择建立神经网络的类并实例化。这里选用卷积神经网络,因为图像识别领域一般用CNN比较快而且准确。输入内容是28×28的灰度图像,输出结果是一个十维向量(专业术语叫独热编码),每一维对应了图像是0~9的概率。 至于为什么要设定这么多层网络,网络中的参数为什么这么设定,只能说这是一种素养。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import torch
#利用GPU进行训练,前期可以忽略。GPU只有在数据量较大时才能有明显的优势。
def try_gpu(i=0):
if torch.cuda.device_count() >= i + 1:
return torch.device(f'cuda:{i}')
return torch.device('cpu')

device = try_gpu()

import torch.nn as nn
class Net(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=1,out_channels=6,kernel_size=5,padding=2)
self.act1 = nn.ReLU()
self.pool1 = nn.MaxPool2d(2)
self.conv2 = nn.Conv2d(in_channels=6,out_channels=16,kernel_size=5,padding=0)
self.act2 = nn.ReLU()
self.pool2 = nn.MaxPool2d(2)
self.conv3 = nn.Conv2d(in_channels=16,out_channels=120,kernel_size=5,padding=0)
self.act3 = nn.ReLU()
self.fc1 = nn.Linear(in_features=120,out_features=84)
self.act4 = nn.ReLU()
self.fc2 = nn.Linear(in_features=84,out_features=10)


def forward(self,x):
out = self.pool1(self.act1(self.conv1(x)))
out = self.pool2(self.act2(self.conv2(out)))
out = self.act3(self.conv3(out))
out = out.view(-1,120)
out = self.act4(self.fc1(out))
out = self.fc2(out)
return out

model = Net().to(device=device)

损失函数

对于多分类问题一般选择交叉熵损失函数。需要注意的是,在Pytorch中,神经网络的最后输出层不管有没有再进行一次softmax运算,最后用交叉熵损失函数算出来的结果都是一样的,好像是内部自带了。

1
loss_fn = nn.CrossEntropyLoss()

优化器

SGD是随机梯度下降法优化器,这种简单的问题用简单的优化器就可以。

1
2
import torch.optim as optim
optimizer = optim.SGD(model.parameters(),lr=0.1) #同时初始化模型参数,并设定学习率。

模型的训练和评价

训练方法

训练过程一般是设定训练轮数,计算模型预测结果与真实标签的损失,用优化器对参数进行调整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
num_epochs = 10
for epoch in range(1,1+num_epochs):
loss_train = 0.0 #记录损失值
for X,y in train_load:
X = X.to(device=device)
y = y.to(device=device)
y_hat = model(X) #计算预测值。
loss = loss_fn(y_hat,y) #计算损失。

#下面这三行可以说是整个Pytorch的精髓,
#本来很复杂的训练过程只用这三行就完成了。
optimizer.zero_grad() #梯度清零。
loss.backward() #反向传播。
optimizer.step() #优化参数。

loss_train += loss.item() #将损失累加。

print('epochs: ',epoch,' ',loss_train/len(train_load)) #输出损失,相当于训练日志的作用。
1
2
3
4
5
6
7
8
9
10
epochs:  1     0.08579787918389328
epochs: 2 0.059785575573163816
epochs: 3 0.04760235151487476
epochs: 4 0.03793407763548837
epochs: 5 0.03081712542800295
epochs: 6 0.026653120353041508
epochs: 7 0.022646719979969705
epochs: 8 0.019911968960397704
epochs: 9 0.017030523287513772
epochs: 10 0.015213624578597575

模型评价和保存模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def accuracy(data_set,model):
correct = 0
total = 0

with torch.no_grad():
for imgs,labels in data_set:
imgs = imgs.to(device=device)
labels = labels.to(device=device)
outputs = model(imgs)
_,labels_pre = torch.max(outputs,dim=1)
total += labels.shape[0]
correct += int((labels_pre == labels).sum())

return correct/total

print('Train_Accuracy: ',accuracy(train_load,model))
print('Test_Accuracy: ',accuracy(test_load,model))

torch.save(model.state_dict(),'./mnist.pth')
#用这种方法保存的模型只保留模型参数,
#下次在加载模型的时候还是需要先定义网络。
#但据说这种保存方式比保留整个模型更稳定。
1
2
Train_Accuracy:  0.9960666666666667
Test_Accuracy: 0.9960666666666667

可以看到最终不管是在训练集还是测试集上,准确率都是很高的。