pytorch实现MNIST识别——全流程

主要实现

  1. 掌握pytorch自带数据集的导入
  2. 初步编写DataLoader
  3. 定义模型、损失和优化器
  4. 训练简单神经网络
  5. 将模型结果保存至本地

参考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_channelint输入图像通道数
out_channelint输出图像通道数(同时也是卷积核个数)
kernel_sizeint or (tuple)卷积核大小
strideint or (tuple)卷积步长,默认为1
paddingint or (tuple)填充操作个数
padding_modestring填充模式
dilationint or (tuple)扩张操作(空洞卷积)
groupsint控制分组卷积
biasbool是否添加可学习的偏置

卷积参考: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 =inputkernel+1+2padding/stride

  • 第一个卷积层使用了64个3*3的卷积核,步长为1,填充数为1。计算得到的输出大小为 ( 28 − 3 + 1 + 1 ∗ 2 ) / 1 = 28 (28 - 3 + 1 + 1 * 2) / 1 = 28 (283+1+12)/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)=ipilogqi

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时,要依次进行

  1. 清零上一个batch计算得到的梯度,以免梯度累加
  2. 计算损失loss
  3. 利用backward()对损失loss进行反向传播
  4. 利用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 >

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值