LetNet-5 神经网络模型搭建

模型介绍

img

1.1 LeNet-5 结构:

  • 输入层

图片大小为 32×32×1,其中 1 表示为黑白图像,只有一个 channel。

  • 卷积层

filter 大小 5×5,filter 深度(个数)为 6,padding 为 0, 卷积步长 s=1s=1,输出矩阵大小为 28×28×6,其中 6 表示 filter 的个数。

  • 池化层

average pooling,filter 大小 2×2(即 f=2f=2),步长 s=2s=2,no padding,输出矩阵大小为 14×14×6。

  • 卷积层

filter 大小 5×5,filter 个数为 16,padding 为 0, 卷积步长 s=1s=1,输出矩阵大小为 10×10×16,其中 16 表示 filter 的个数。

  • 池化层

average pooling,filter 大小 2×2(即 f=2f=2),步长 s=2s=2,no padding,输出矩阵大小为 5×5×16。注意,在该层结束,需要将 5×5×16 的矩阵flatten 成一个 400 维的向量。

  • 全连接层(Fully Connected layer,FC)

neuron 数量为 120。

  • 全连接层(Fully Connected layer,FC)

neuron 数量为 84。

  • 全连接层,输出层

现在版本的 LeNet-5 输出层一般会采用 softmax 激活函数,在 LeNet-5 提出的论文中使用的激活函数不是 softmax,但其现在不常用。该层神经元数量为 10,代表 0~9 十个数字类别。(图 1 其实少画了一个表示全连接层的方框,而直接用 y^y^ 表示输出层。)

1.2 LeNet-5 一些性质:

  • 输入层不算神经网络的层数,LeNet-5 是一个 5 层的网络。(把 卷积和池化 当作一个 layer)(LeNet-5 名字中的“5”也可以理解为整个网络中含可训练参数的层数为 5。)
  • LeNet-5 大约有 60,000 个参数。
  • 随着网络越来越深,图像的高度和宽度在缩小,与此同时,图像的 channel 数量一直在增加。
  • 现在常用的 LeNet-5 结构和 Yann LeCun 教授在 1998 年论文中提出的结构在某些地方有区别,比如激活函数的使用,现在一般使用 ReLU 作为激活函数,输出层一般选择 softmax。

参考文章:https://www.cnblogs.com/wuliytTaotao/p/9544625.html

代码:

Model代码:

from torch.nn import Module
from torch import nn


class Model(Module):
def __init__(self):
super(Model, self).__init__()
self.conv1 = nn.Conv2d(1, 6, 5)
self.relu1 = nn.ReLU()
self.pool1 = nn.MaxPool2d(2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.relu2 = nn.ReLU()
self.pool2 = nn.MaxPool2d(2)
self.fc1 = nn.Linear(256, 120)
self.relu3 = nn.ReLU()
self.fc2 = nn.Linear(120, 84)
self.relu4 = nn.ReLU()
self.fc3 = nn.Linear(84, 10)
self.relu5 = nn.ReLU()

def forward(self, x):
y = self.conv1(x)
y = self.relu1(y)
y = self.pool1(y)
y = self.conv2(y)
y = self.relu2(y)
y = self.pool2(y)
y = y.view(y.shape[0], -1)
y = self.fc1(y)
y = self.relu3(y)
y = self.fc2(y)
y = self.relu4(y)
y = self.fc3(y)
y = self.relu5(y)
return y

Dateset Loading代码:

import torch
from torch.autograd import Variable
from torchvision import transforms
from torch.utils.data import Dataset, DataLoader
from PIL import Image

root = "E:\\"

# -----------------ready the dataset--------------------------
def default_loader(path):
return Image.open(path).convert('L')

class MyDataset(Dataset):
# 构造函数带有默认参数
def __init__(self, txt, transform=None, target_transform=None, loader=default_loader):
fh = open(txt, 'r')
imgs = []
for line in fh:
# 移除字符串首尾的换行符
# 删除末尾空
# 以空格为分隔符 将字符串分成
line = line.strip('\n')
line = line.rstrip()
words = line.split()
imgs.append((words[0], int(words[1]))) # imgs中包含有图像路径和标签
self.imgs = imgs
self.transform = transform
self.target_transform = target_transform
self.loader = loader

def __getitem__(self, index):
fn, label = self.imgs[index]
# 调用定义的loader方法
img = self.loader(fn)
if self.transform is not None:
img = self.transform(img)
return img, label

def __len__(self):
return len(self.imgs)

train_dataset = MyDataset(txt=root + 'train\\train.txt', transform=transforms.Compose([transforms.Resize(32),transforms.ToTensor()]))
test_dataset = MyDataset(txt=root + 'test\\test.txt', transform=transforms.Compose([transforms.Resize(32),transforms.ToTensor()]))

# print(train_dataset)
# print(test_dataset)

# train_data 和test_data包含多有的训练与测试数据,调用DataLoader批量加载
train_loader = DataLoader(dataset=train_dataset, batch_size=50, shuffle=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=50, shuffle=True)

Resize

9x9 ->transform = Compose([Resize(32),ToTensor()])) ->32x32 通过resize将小图片转换成大图片

Train代码:

from model import Model
import numpy as np
import torch
from torchvision.datasets import mnist
from torch.nn import CrossEntropyLoss
from torch.optim import SGD
from torch.utils.data import DataLoader
from torchvision.transforms import ToTensor
from my_dataset import train_dataset, test_dataset

if __name__ == '__main__':
batch_size = 50

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

print(torch.cuda.is_available())
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = Model()
sgd = SGD(model.parameters(), lr=1e-1)
cost = CrossEntropyLoss()
epoch = 100
min_acc = 0

for _epoch in range(epoch):
print("epoch: " + str(_epoch + 1) + " ---------------")
model.train()
for idx, (train_x, train_label) in enumerate(train_loader):
#print(train_x[0].shape)
label_np = np.zeros((train_label.shape[0], 10))
sgd.zero_grad()
predict_y = model(train_x.float())
loss = cost(predict_y, train_label.long())
if idx % 10 == 0:
print('idx: {}, loss: {}'.format(idx, loss.sum().item()))
loss.backward()
sgd.step()

correct = 0
_sum = 0
model.eval()
for idx, (test_x, test_label) in enumerate(test_loader):
predict_y = model(test_x.float()).detach()
predict_ys = np.argmax(predict_y, axis=-1)
label_np = test_label.numpy()
_ = predict_ys == test_label
correct += np.sum(_.numpy(), axis=-1)
_sum += _.shape[0]

accuracy = correct / _sum
print('accuracy: {:.3f}'.format(accuracy))
if accuracy > min_acc:
min_acc = accuracy
print("save_best_model!\n")
torch.save(model, 'mnist_best_model.pkl')