Fork me on GitHub

CNN01:Pytorch实现LeNet的Mnist手写数字识别

CNN01:Pytorch实现LeNet的Mnist手写数字识别

1、LeNet的网络结构和原理

LeNet的具体网络结构和原理参考博客:
https://www.cnblogs.com/guoyaohua/p/8534077.html
该博客不只讲了LeNet还讲了其他的网络结构,比较详细,容易理解。

2、基于Pytorch的LeNet的MNIST手写数字识别Python代码实现

代码来源于:
https://github.com/L1aoXingyu/pytorch-beginner/blob/master/04-Convolutional Neural Network/convolution_network.py

(1) 整体代码:

#__author__ = 'SherlockLiao'

import torch
from torch import nn, optim
#import torch.nn.functional as F
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision import datasets
#from logger import Logger

# 定义超参数
batch_size = 128        # 批的大小
learning_rate = 1e-2    # 学习率
num_epoches = 20        # 遍历训练集的次数

# 数据类型转换,转换成numpy类型
#def to_np(x):
#    return x.cpu().data.numpy()


# 下载训练集 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())

train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)


# 定义 Convolution Network 模型
class Cnn(nn.Module):
    def __init__(self, in_dim, n_class):
        super(Cnn, self).__init__()    # super用法:Cnn继承父类nn.Model的属性,并用父类的方法初始化这些属性
        self.conv = nn.Sequential(     #padding=2保证输入输出尺寸相同(参数依次是:输入深度,输出深度,ksize,步长,填充)
            nn.Conv2d(in_dim, 6, 5, stride=1, padding=2),
            nn.ReLU(True),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(6, 16, 5, stride=1, padding=0),
            nn.ReLU(True), 
            nn.MaxPool2d(2, 2))

        self.fc = nn.Sequential(
            nn.Linear(400, 120), 
            nn.Linear(120, 84), 
            nn.Linear(84, n_class))

    def forward(self, x):
        out = self.conv(x)
        out = out.view(out.size(0), -1)
        out = self.fc(out)
        return out


model = Cnn(1, 10)  # 图片大小是28x28,输入深度是1,最终输出的10类
use_gpu = torch.cuda.is_available()  # 判断是否有GPU加速
if use_gpu:
    model = model.cuda()

# 定义loss和optimizer
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)

#logger = Logger('./logs')
# 开始训练
for epoch in range(num_epoches):
    print('epoch {}'.format(epoch + 1))      # .format为输出格式,formet括号里的即为左边花括号的输出
    print('*' * 10)
    running_loss = 0.0
    running_acc = 0.0
    for i, data in enumerate(train_loader, 1):
        img, label = data
        # cuda
        if use_gpu:
            img = img.cuda()
            label = label.cuda()
        img = Variable(img)
        label = Variable(label)
        # 向前传播
        out = model(img)
        loss = criterion(out, label)
        running_loss += loss.item() * label.size(0)
        _, pred = torch.max(out, 1)
        num_correct = (pred == label).sum()
        accuracy = (pred == label).float().mean()
        running_acc += num_correct.item()
        # 向后传播
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        """
        # ========================= Log ======================
        step = epoch * len(train_loader) + i
        # (1) Log the scalar values
        info = {'loss': loss.data[0], 'accuracy': accuracy.data[0]}

        for tag, value in info.items():
            logger.scalar_summary(tag, value, step)

        # (2) Log values and gradients of the parameters (histogram)
        for tag, value in model.named_parameters():
            tag = tag.replace('.', '/')
            logger.histo_summary(tag, to_np(value), step)
            logger.histo_summary(tag + '/grad', to_np(value.grad), step)

        # (3) Log the images
        info = {'images': to_np(img.view(-1, 28, 28)[:10])}

        for tag, images in info.items():
            logger.image_summary(tag, images, step)
        if i % 300 == 0:
            print('[{}/{}] Loss: {:.6f}, Acc: {:.6f}'.format(
                epoch + 1, num_epoches, running_loss / (batch_size * i),
                running_acc / (batch_size * i)))
        """
    print('Finish {} epoch, Loss: {:.6f}, Acc: {:.6f}'.format(
        epoch + 1, running_loss / (len(train_dataset)), running_acc / (len(train_dataset))))
    model.eval()
    eval_loss = 0
    eval_acc = 0
    for data in test_loader:
        img, label = data
        if use_gpu:
            img = Variable(img, volatile=True).cuda()
            label = Variable(label, volatile=True).cuda()
        else:
            img = Variable(img, volatile=True)
            label = Variable(label, volatile=True)
        out = model(img)
        loss = criterion(out, label)
        eval_loss += loss.item() * label.size(0)
        _, pred = torch.max(out, 1)
        num_correct = (pred == label).sum()
        eval_acc += num_correct.item()
    print('Test Loss: {:.6f}, Acc: {:.6f}'.format(eval_loss / (len(
        test_dataset)), eval_acc / (len(test_dataset))))
    print()

# 保存模型
torch.save(model.state_dict(), './cnn.pth')

(2) 代码详解:

首先,该代码可以分为以下几个部分:

  1. 导入各种包
  2. 定义超参数
  3. 下载MNIST数据集
  4. 定义LeNet网络模型
  5. 定义损失函数loss和优化方式SGD
  6. 训练模型
    1). 初始化loss和accuracy
    2). 前向传播
    3). 反向传播
    4). 测试模型
    5). 打印每个epoch的loss和acc
  7. 保存模型
1、导包
import torch
from torch import nn, optim
#import torch.nn.functional as F
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision import datasets
#from logger import Logger

其中logger包是记录日志用的,并不需要

2、定义超参数
batch_size = 128        # 批的大小
learning_rate = 1e-2    # 学习率
num_epoches = 20        # 遍历训练集的次数

超参数有3个,分别是batch_sizelearning_ratemun_epoches
batch_size(批大小):指将数据集分成nbatch(批),每一个batch的大小
learning_rate(学习率):训练时,每次更新的步长,不宜设置过大
mun_epoches(遍历数据集训练的次数):指遍历训练整个数据集的次数。

3、下载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())

train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

Pytorchtorchvision.datasets包含MNISTCOCOLSUNImageFolderImagenet-12CIFARSTL10等数据集。可以通过datasets.MNIST这种方式来下载调用这些数据集。

datasets.MNIST(root, train=True, transform=None, target_transform=None, download=False)参数说明:
rootprocessed/training.ptprocessed/test.pt的主目录。数据集下载和保存的地址。
traintrain=True表示训练集,train=False表示测试集。
downloaddownload=True表示从网上下载数据集,download=False表示数据已经下载过。

torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, num_workers=0, collate_fn=<function default_collate>, pin_memory=False, drop_last=False)
数据加载器。组合数据集和采样器,并在数据集上提供单进程或多进程迭代器。
参数说明:
dataset (Dataset):加载数据的数据集。
batch_size (int, optional):每个batch加载多少个样本(默认: 1)。
shuffle (bool, optional):设置为True时会在每个epoch重新打乱数据(默认: False).。
sampler (Sampler, optional):定义从数据集中提取样本的策略。如果指定,则忽略shuffle参数。
num_workers (int, optional):用多少个子进程加载数据。0表示数据将在主进程中加载(默认: 0)。
collate_fn (callable, optional)
pin_memory (bool, optional)
drop_last (bool, optional):如果数据集大小不能被batch size整除,则设置为True后可删除最后一个不完整的batch。如果设为False并且数据集的大小不能被batch size整除,则最后一个batch将更小。(默认: False)。

4、定义LeNet网络模型
# 定义 Convolution Network 模型
class Cnn(nn.Module):
    def __init__(self, in_dim, n_class):
        super(Cnn, self).__init__()    # super用法:Cnn继承父类nn.Model的属性,并用父类的方法初始化这些属性
        self.conv = nn.Sequential(     #padding=2保证输入输出尺寸相同(参数依次是:输入深度,输出深度,ksize,步长,填充)
            nn.Conv2d(in_dim, 6, 5, stride=1, padding=2),
            nn.ReLU(True),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(6, 16, 5, stride=1, padding=0),
            nn.ReLU(True), 
            nn.MaxPool2d(2, 2))

        self.fc = nn.Sequential(
            nn.Linear(400, 120), 
            nn.Linear(120, 84), 
            nn.Linear(84, n_class))

    def forward(self, x):
        out = self.conv(x)
        out = out.view(out.size(0), -1)
        out = self.fc(out)
        return out

(1) superd的用法:Cnn继承父类nn.Model的属性,并用父类的方法初始化这些属性
(2) nn.Sequential():一个时序容器。Modules 会以他们传入的顺序被添加到容器中。这个容器里可以初始化卷积层、激活层和池化层。
nn.Conv2d(in_dim, 6, 5, stride=1, padding=2):初始化卷积层1

  • in_dim:输入图像的通道深度
  • out_dim:输出图像的通道深度
  • Ksize:卷积核的尺寸大小
  • std:步长
  • padding:填充

nn.ReLU(True):激活函数

nn.MaxPool2d(2, 2):池化层,KsizePadding均为2
值得注意的是,由于LeNet的输入为32x32,而MNIST的图像大小为28x28,要使数据大小和网络结构大小一致,一般是改网络大小而不改数据的大小。将padding置为2就可以使输出为28x28。
全连接层nn.Linear(in_features, out_features, bias=True)
参数

  • in_features:每个输入样本的大小
  • out_features:每个输出样本的大小
  • bias:若设置为False,这层不会学习偏置。默认值:True

形状

  • 输入: (N,in_features)
  • 输出: (N,out_features)

变量

  • weight:形状为(out_features x in_features)的模块中可学习的权值
  • bias:形状为(out_features)的模块中可学习的偏置

各层的输入和输出尺寸大小:
在这里插入图片描述
(3) 前向传播

    def forward(self, x):
        out = self.conv(x)
        out = out.view(out.size(0), -1)
        out = self.fc(out)
        return out
5、定义损失函数loss和优化方式SGD
# 定义loss和optimizer
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)

nn.CrossEntropyLoss():交叉熵损失
optim.SGD():SGD方式优化

6、训练模型
# 开始训练
for epoch in range(num_epoches):
    print('epoch {}'.format(epoch + 1))      # .format为输出格式,formet括号里的即为左边花括号的输出
    print('*' * 10)
    running_loss = 0.0
    running_acc = 0.0
    for i, data in enumerate(train_loader, 1):
        img, label = data
        # cuda
        if use_gpu:
            img = img.cuda()
            label = label.cuda()
        img = Variable(img)
        label = Variable(label)
        # 向前传播
        out = model(img)
        loss = criterion(out, label)
        running_loss += loss.item() * label.size(0)
        _, pred = torch.max(out, 1)
        num_correct = (pred == label).sum()
        accuracy = (pred == label).float().mean()
        running_acc += num_correct.item()
        # 向后传播
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        """
        # ========================= Log ======================
        step = epoch * len(train_loader) + i
        # (1) Log the scalar values
        info = {'loss': loss.data[0], 'accuracy': accuracy.data[0]}

        for tag, value in info.items():
            logger.scalar_summary(tag, value, step)

        # (2) Log values and gradients of the parameters (histogram)
        for tag, value in model.named_parameters():
            tag = tag.replace('.', '/')
            logger.histo_summary(tag, to_np(value), step)
            logger.histo_summary(tag + '/grad', to_np(value.grad), step)

        # (3) Log the images
        info = {'images': to_np(img.view(-1, 28, 28)[:10])}

        for tag, images in info.items():
            logger.image_summary(tag, images, step)
        if i % 300 == 0:
            print('[{}/{}] Loss: {:.6f}, Acc: {:.6f}'.format(
                epoch + 1, num_epoches, running_loss / (batch_size * i),
                running_acc / (batch_size * i)))
        """
    print('Finish {} epoch, Loss: {:.6f}, Acc: {:.6f}'.format(
        epoch + 1, running_loss / (len(train_dataset)), running_acc / (len(train_dataset))))
    model.eval()
    eval_loss = 0
    eval_acc = 0
    for data in test_loader:
        img, label = data
        if use_gpu:
            img = Variable(img, volatile=True).cuda()
            label = Variable(label, volatile=True).cuda()
        else:
            img = Variable(img, volatile=True)
            label = Variable(label, volatile=True)
        out = model(img)
        loss = criterion(out, label)
        eval_loss += loss.item() * label.size(0)
        _, pred = torch.max(out, 1)
        num_correct = (pred == label).sum()
        eval_acc += num_correct.item()
    print('Test Loss: {:.6f}, Acc: {:.6f}'.format(eval_loss / (len(
        test_dataset)), eval_acc / (len(test_dataset))))
    print()
1). 初始化loss和accuracy

print('epoch {}'.format(epoch + 1)): .format为输出格式,formet括号里的即为左边花括号的输出
在这里插入图片描述
data是一个以两个张量为元素的列表:
在这里插入图片描述
两个元素分别是imglabel,元素类型为tensor类型。

Variable():由于Variable API几乎和Tensor API一致 (除了一些in-place方法,这些in-place方法会修改 required_grad=True的 input 的值)。多数情况下,将Tensor替换为Variable,代码一样会正常的工作。可以通过torch.Tensor的文档来获取相关知识。
torch.tensor(data, dtype=None, device=None, requires_grad=False)
参数

  • data (array_like):张量的初始数据。可以是列表,元组,NumPy ndarray,标量和其他类型。
  • dtype (torch.dtype, optional):返回张量的所需数据类型。默认值:如果为None,则从data中推断数据类型。
  • device (torch.device, optional):返回张量的理想设备。默认值:如果为None,则使用当前设备作为默认张量类型(请参阅torch.set_default_tensor_type())。device将是CPU张量类型的CPU和CUDA张量类型的当前CUDA设备。
  • requires_grad (bool, optional):如果autograd应该在返回的张量上记录操作。默认值:False
img = Variable(img)
label = Variable(label)

是将tensor类型的imglabel转换为variable类型。

2). 前向传播forward()
# 向前传播
        out = model(img)    # 输出
        loss = criterion(out, label)    # 计算交叉熵损失
        running_loss += loss.item() * label.size(0)     # 
        _, pred = torch.max(out, 1)    # 预测最大值所在的位置标签,即预测的数字
        num_correct = (pred == label).sum()   # 预测正确的数目
        accuracy = (pred == label).float().mean()
        running_acc += num_correct.item()
criterion = LossCriterion()   #构造函数有自己的参数
loss = criterion(x, y)     #调用标准时也有参数

计算出来的结果已经对mini-batch取了平均。

torch.max():返回输入张量所有元素的最大值。
torch.max(input, dim, max=None, max_indices=None)
返回输入张量给定维度上每行的最大值,并同时返回每个最大值的位置索引。输出形状中,将dim维设定为1,其它与输入形状保持一致。
参数

  • input (Tensor):输入张量
  • dim (int):指定的维度
  • max (Tensor, optional):结果张量,包含给定维度上的最大值
  • max_indices (LongTensor, optional):结果张量,包含给定维度上每个最大值的位置索引
    预测最大值所在的位置标签,即预测的数字。
    accuracy-求平均准确率
3). 反向传播backward()
        # 向后传播
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

zero_grad():清空所有被优化过的Variable的梯度.
step():进行单次优化

4). 测试模型
model.eval()    # 模型评估
    eval_loss = 0
    eval_acc = 0
    for data in test_loader:      # 测试模型
        img, label = data
        if use_gpu:
            img = Variable(img, volatile=True).cuda()
            label = Variable(label, volatile=True).cuda()
        else:
            img = Variable(img, volatile=True)
            label = Variable(label, volatile=True)
        out = model(img)
        loss = criterion(out, label)
        eval_loss += loss.item() * label.size(0)
        _, pred = torch.max(out, 1)
        num_correct = (pred == label).sum()
        eval_acc += num_correct.item()
    print('Test Loss: {:.6f}, Acc: {:.6f}'.format(eval_loss / (len(
        test_dataset)), eval_acc / (len(test_dataset))))
    print()
5). 打印每个epoch的loss和acc
print('Finish {} epoch, Loss: {:.6f}, Acc: {:.6f}'.format(
        epoch + 1, running_loss / (len(train_dataset)), running_acc / (len(train_dataset))))

这里又有.format()的用法。
在这里插入图片描述

7、保存模型
# 保存模型
torch.save(model.state_dict(), './cnn.pth')

torch.save(model.state_dict(), './cnn.pth')
model.state_dict():需要保存的模型
'./cnn.pth':模型的名称


参考
1、https://www.cnblogs.com/guoyaohua/p/8534077.html
2、https://blog.csdn.net/hustchenze/article/details/79154139
3、https://github.com/L1aoXingyu/pytorch-beginner/blob/master/04-Convolutional Neural Network/convolution_network.py
4、https://www.pytorchtutorial.com/10-minute-pytorch-4/
5、https://blog.csdn.net/u013066730/article/details/82498229

posted @ 2019-03-19 11:10  小黑子杜  阅读(1164)  评论(0编辑  收藏  举报