人工智能深度学习之卷积神经网络CNN LeNet-5网络 Pytorch 详细代码

2020年2月19日 169 次阅读 0 条评论 0 人点赞

卷积神经网络是真正的可以算得上是深度学习网络的神经网络了。毕竟传统的BP算法,不能算是真的深度学习算法。对于卷积神经网络来说,最具有代表性的网络就是Lenet5网络了。所以学习卷积神经网络,一般从Lenet5学起。对于这种复杂的网络我们完全没有必要,使用python一点一点写。有很多现成的框架我们可以使用。比如Tensorflow和Pytorch。但是Pytorch更好学习一点,于是我就是用Pytorch尝试着写了这个代码,来尝试学习卷积神经网络。

对于卷积神经网络,网上有很多资料,我完全没有必要重复造轮子。我只放一个链接吧。

https://blog.csdn.net/weixin_42398658/article/details/84392845

这儿介绍的很清楚。

一、Lenet5网络模型图

二、Lenet5详细分析

1. C1层-卷积层

输入:1*32*32(输入特征图数和大小)

卷积核:6*5*5(卷积核个数和卷积核大小)

输出:6*28*28(输出特征图数和大小)

2.S2层-池化层(下采样层)

输入:6*28*28(输入特征图数和大小)

采样区域:2*2

输出:6*14*14(输出特征图数和大小)

采样方式:4个输入相加,乘以一个可训练参数,再加上一个可训练偏置。结果套上sigmoid 等函数

3.C3-卷积层

输入:6*14*14 不同方式组合

卷积核:16*5*5(卷积核个数和卷积核大小)

输出:16*10*10

4.S4层-池化层(下采样层)

输入:16*10*10(输入特征图数和大小)

采样区域:2*2

输出:16*5*5(输出特征图数和大小)

采样方式:4个输入相加,乘以一个可训练参数,再加上一个可训练偏置。结果套上sigmoid 等函数

5.C5-卷积层(相当于与S4全连接)

输入:16*5*5

卷积核:120*5*5(卷积核个数和卷积核大小)

输出:120*1*1

6.F6层-全连接层

输入:120*1

激活函数:sigmod

输出:84*1

7.Output层-全连接层

输入:84*1

输出:10*1

输出层由欧式径向基函数(Euclidean Radial Basis Function)单元组成,每类一个单元,每个有84个输入。换句话说,每个输出RBF单元计算输入向量和参数向量之间的欧式距离。输入离参数向量越远,RBF输出的越大。

三、模型实现

学习神经网络主要是学习神经网络的原理,所以没有必要完全复现这个完整的Lenet5。网上很多代码都是低级api,就是自己写模型类。但是我感觉 torch.nn.Sequential 这个类很强大,于是就想写了如下的模型,使用pytorch高级api搭建Lenet5。C5卷积层被视为与S4层全连接。所以S4层输出之后把结果变成400*1的向量。然后与400个输入84个输出的C5线性层全连接。另外未实现池化层S2、S4的sigmod池化,使用平均值池化代替。

激活函数使用性能更加优秀的ReLu激活函数。网络最后一层为高斯连接层。而我们为了简单起见还是用了全连接层。模型如下:

model = nn.Sequential(
    # C1
    nn.Conv2d(1, 6, (5, 5), 1, 2),
    nn.ReLU(),
    # S2
    nn.AvgPool2d((2, 2)),
    # C3
    nn.Conv2d(6, 16, (5, 5), 1),
    nn.ReLU(),
    # S4
    nn.AvgPool2d((2, 2)),
    # C5
    Flatten(),
    nn.Linear(5*5*16, 120),
    nn.ReLU(),
    # F6
    nn.Linear(120, 84),
    nn.ReLU(),
    # OUTPUT
    nn.Linear(84, 10),
)

四、数据集描述

MNIST 数据集来自美国国家标准与技术研究所, National Institute of Standards and Technology (NIST). 训练集 (training set) 由来自 250 个不同人手写的数字构成, 其中 50% 是高中学生, 50% 来自人口普查局 (the Census Bureau) 的工作人员. 测试集(test set) 也是同样比例的手写数字数据.

MNIST数据集分为训练集和测试集。训练数据集包含 60,000 个样本, 测试数据集包含 10,000 样本. 在 MNIST 数据集中的每张图片由 28 x 28 个像素点构成, 每个像素点用一个灰度值表示. 

五、详细代码

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

# 定义超参数
batch_size = 128        # 批的大小
learning_rate = 0.001     # 学习率
num_epoches = 20000        # 遍历训练集的次数


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


# 多维向量归一化400*1一维向量
class Flatten(nn.Module):
    def forward(self, input):
        return input.reshape(input.size(0), -1)


# 模型定义
model = nn.Sequential(
    # C1
    nn.Conv2d(1, 6, (5, 5), 1, 2),
    nn.ReLU(),
    # S2
    nn.AvgPool2d((2, 2)),
    # C3
    nn.Conv2d(6, 16, (5, 5), 1),
    nn.ReLU(),
    # S4
    nn.AvgPool2d((2, 2)),
    # C5 # 多维向量归一化400*1一维向量
    Flatten(),
    nn.Linear(5*5*16, 120),
    # nn.Conv2d(16, 120, (5, 5), 1),
    nn.ReLU(),
    # nn.BatchNorm1d(120),# 批量准化
    # nn.Dropout(),
    # F6

    nn.Linear(120, 84),
    nn.ReLU(),
    # nn.BatchNorm1d(84),
    # nn.ReLU(),
    # OUTPUT
    # nn.Dropout(),
    nn.Linear(84, 10),
    # nn.Softmax(dim=1),

)


# 定义loss和optimizer
# 交叉熵损失函数
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate) # 优化器代替手动优化使用Adam 优化方法

# 开始训练
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
        img = Variable(img)
        label = Variable(label)
        # print(img.data)
        # 向前传播
        out = model(img)
        # print(out.data.shape, img.shape, label.shape)
        # 计算loss
        loss = criterion(out, label)
        # 计算总的loss
        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()
    # 输出训练接的准确率和误差
    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
        # 测试集计算不再计算梯度
        with torch.no_grad():
            img = Variable(img)
            label = Variable(label)
        out = model(img)
        # 计算loss
        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()
    # loss小于特定值则退出训练
    if running_loss / (len(train_dataset)) <= 0.005:
        break
# 保存模型
# torch.save(model.state_dict(), './cnn.pth')
# torch.save(model, "cnn1.pkl")
# model = torch.load("cnn1.pkl")

# 测试保存的模型
criterion = nn.CrossEntropyLoss()
eval_loss = 0
eval_acc = 0
count = 0
for data in test_loader:
    img, label = data
    with torch.no_grad():
        img = Variable(img)
        label = Variable(label)
    out = model(img)

    count += 1
    # print(out.shape, count)
    loss = criterion(out, label)
    # print(loss)
    #print(label.size(0))
    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()

代码比较乱,可能看明白需要一点时间。基本注释已经写明,不清楚请在下方留言。

完整源代码: https://github.com/mengxiangke/Lenet5

六、结果图

对于手写数字识别,效果还是蛮好的。

代码可能需要好好看一会才能看懂,若是不明白请在下方留言。

菜鸟

文章评论(0)