用pytorch实现在MNIST手写数字数据集上的分类任务

本文是 pytorch 在 MNIST 数据集上实现手写数字识别的一个实例。

已经在代码中给出了较为详细的注释,所以直接看代码吧。

# 本代码的作用是将MNIST手写数据集中手写体的数字0~9给正确的识别出来。

'''
pytorch进行神经网络训练的流程:

一.首先定义神经网络模型 model=Net()

二.定义优化器  optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)

三.训练阶段,循环每个epoch,对于一个epoch:
    1.将模型设置为训练模式  model.train()
    2.将训练数据分为batch_size大小的组,对每一组中所有的样本:
        1).将梯度初始化为0  optimizer.zero_grad()
        2).对这组样本进行训练,并得到输出  output=model(data)
        3).根据预测输出和真实类别计算损失函数  loss=F.nll_loss(output,target)
        4).反向传播,计算梯度  loss.backward()
        5).更新模型参数  optimizer.step()
    3.保存模型(可省略)  torch.save(model,"mnist_torch.pkl")

四.加载已训练好的模型(可省略)

五.测试阶段,对于所有的测试样本:
    1.将模型设置为评价模式  model.eval()
    2.根据建好的模型对测试数据进行预测,得到预测结果  output=model(data)
    3.计算损失函数  test_loss+=F.nll_loss(output,target).data
    4.将概率最大的类别当作预测类别,并计算准确率  correct+=pred.eq(target.data).cpu().sum()

上述第四大步的第4小步是当问题是分类问题时才有的

上述中 model.train()和model.eval()的不同在于,model.eval()去除神经网络中的随机性,而model.train()
则保留了神经网络中的随机性。这是因为神经网络中的某些操作(如dropout等),会存在一定的随机性(dropout
会随机让神经网络中的节点失活),所以为了保证预测结果是可复现的,所以在测试阶段要设置为model.eval()。

'''
import os
import torch
import cv2 as cv
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from keras.datasets import mnist
from torch.autograd import Variable

epochs = 5
batch_size = 200


# 以下函数是为了实现数据可视化的,可不必理解
def save_as_jpg(train_x, train_y, test_x, test_y):
    # 将MNIST数据集中的图片保存为jpg格式,将标签保存到对应的txt文档中

    flag=0
    path = "./MNIST_JPG/train/images"
    if not os.path.exists(path):
        os.makedirs(path)
        flag=1
    path = "./MNIST_JPG/train/labels"
    if not os.path.exists(path):
        os.makedirs(path)
        flag=1

    if flag==1:
        for i in range(train_x.shape[0]):
            cv.imwrite("./MNIST_JPG/train/images/{}.jpg".format(i), train_x[i, :, :])
            file_name = "./MNIST_JPG/train/labels/{}.txt".format(i)
            f = open(file_name, 'w')
            f.write(str(train_y[i]))
            f.close()


    flag=0
    path = "./MNIST_JPG/test/images"
    if not os.path.exists(path):
        os.makedirs(path)
        flag=1
    path = "./MNIST_JPG/test/labels"
    if not os.path.exists(path):
        os.makedirs(path)
        flag=1

    if flag==1:
        for i in range(test_x.shape[0]):
            cv.imwrite("./MNIST_JPG/test/images/{}.jpg".format(i), test_x[i, :, :])
            file_name = "./MNIST_JPG/test/labels/{}.txt".format(i)
            f = open(file_name, 'w')
            f.write(str(test_y[i]))
            f.close()
    print("\nSave as jpg has finished.")


class Net(nn.Module):
    '''
    Net是nn.Module的子类,继承了它的所有方法,包括__call__()方法和forward()方法,
    __call__()方法中会使用到前向转播函数forward()。
    '''

    def __init__(self):
        super(Net, self).__init__()  # 对继承自父类的属性进行初始化
        '''
        torch.nn和torch.nn.functional的不同在于nn只是定义了各种操作而并没有运行,
        而functional是运行各个操作。
        '''
        # 定义卷积操作
        self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
        self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
        self.conv2_drop = nn.Dropout2d()  # 定义随机失活操作
        # 定义全连接操作
        self.fc1 = nn.Linear(320, 60)
        self.fc2 = nn.Linear(60, 10)
		# 下面的写法和上面的是等价的
        '''
        self.layer1 = nn.Sequential(
            nn.Conv2d(1, 10, kernel_size=5),
            nn.MaxPool2d(2),
            nn.ReLU()
        )
        self.layer2 = nn.Sequential(
            nn.Conv2d(10, 20, kernel_size=5),
            nn.MaxPool2d(2),
            nn.ReLU()
        )
        self.layer3=nn.Sequential(
            nn.Linear(320, 60),
            nn.ReLU()
        )
        '''

    # 前向传播
    def forward(self, x):
        layer1 = F.relu(F.max_pool2d(self.conv1(x), 2))  # 卷积+最大池化+relu激活函数
        #layer1=F.relu(F.max_pool2d(F.conv2d(x,5),2))
        layer2 = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(layer1)), 2))
        layer2 = layer2.view(-1, 320)
        layer3 = F.relu(self.fc1(layer2))  # 全连接+relu激活函数
        # 可将以上四行代码换成封装好的以下四行代码,效果是相同的
        #layer1=self.layer1(x)
        #layer2 = self.layer2(x)
        #layer2 = x.view(-1, 320)
        #layer3 = self.layer3(x)
        layer4 = self.fc2(F.dropout(layer3, training=self.training))
        return F.log_softmax(layer4)  # 用softmax进行分类


# 模型的训练过程
def train(model, epoch, train_x, train_y, num_samples, optimizer):
    num_batchs = int(num_samples / batch_size)
    model.train()  # 将模型设置为训练模式
    for batch in range(num_batchs):
        start, end = batch * batch_size, (batch + 1) * batch_size
        # 参数requires_grad=False的意思是不用求梯度
        data, target = Variable(train_x[start:end], requires_grad=False), Variable(train_y[start:end],
                                                                                   requires_grad=False)
        target = target.long()  # 转换为长整形
        optimizer.zero_grad()  # 将梯度初始化为0,否则每个batch的梯度会累积
        output = model(data)  # 会调用__call__()函数,进一步调用forward()函数进行前向传播
        print("target: ",target.shape)
        loss = F.nll_loss(output, target)  # 计算损失函数
        loss.backward()  # 反向传播
        optimizer.step()  # 更新模型参数
        if batch % 10 == 0:
            print("Train Epoch:{} [{}/{} ({:.2f}%)]\nLoss:{:.6f}".format(epoch, batch * len(data), num_samples,
                                                                         100.0 * batch_size * batch / num_samples,
                                                                         loss.data))
    torch.save(model, "mnist_torch.pkl")  # 保存训练好的模型


# 模型的测试过程
def test(model):
    model.eval()  # 将模型设置为评价模式
    data, target = Variable(test_x), Variable(test_y)
    output = model(data)  # 前向传播,得到每个图片的预测分类
    target = target.long()
    test_loss = F.nll_loss(output, target).data  # 计算损失函数
    pred=torch.argmax(output.data,dim=1) # 求每个样本的最大值,即样本的预测类别
    correct = pred.eq(target.data).cpu().sum()  # 计算所有样本的正确率(正确分类个数/总个数),.cpu()表示用cpu进行计算
    # test_loss/=len(test_x) #平均损失
    print("\nTest set Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n".format(test_loss, correct, len(test_x),
                                                                                 100.0 * correct / len(test_x)))


if __name__ == "__main__":
    # train_x是图像,共6w张28*28大小的图像;train_y是图像的真实分类
    (train_x, train_y), (test_x, test_y) = mnist.load_data()
    save_as_jpg(train_x, train_y, test_x, test_y)  # 把MNIST数据集保存为图片和文档
    # 将训练集和测试集转换为torch的张量
    train_x = torch.Tensor(train_x)
    train_y = torch.Tensor(train_y)
    test_x = torch.Tensor(test_x)
    test_y = torch.Tensor(test_y)
    # 增加一个多余的维度
    train_x.resize_(train_x.shape[0], 1, 28, 28)
    test_x.resize_(test_x.shape[0], 1, 28, 28)
    num_samples = train_x.shape[0]  # 数据个数

    model = Net()
    optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)  # 优化器
    for epoch in range(1,epochs+1):
        train(model,epoch,train_x,train_y,num_samples,optimizer) #训练
    model = torch.load("mnist_torch.pkl")  # 加载已经保存的模型
    test(model)  # 在测试集上测试效果
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值