CNN卷积神经网络 - LeNet具体实现及代码讲解
本次笔记来源bilibili 霹雳吧啦Wz pytorch官方demo(Lenet)
个人github仓库:heisenbergCh/way-to-deep-learning: 个人零基础自学深度学习时产生的笔记与示例代码
参照官网demo定义LeNet网络结构
完整代码:
import torch.nn as nn
import torch.nn.functional as F
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv1 = nn.Conv2d(3, 16, 5) # in_channels, out_channels, kernel_size
self.pool1 = nn.MaxPool2d(2, 2) # kernel_size, stride
self.conv2 = nn.Conv2d(16, 32, 5)
self.pool2 = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(32 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = F.relu(self.conv1(x)) # input(3, 32, 32) output(16, 28, 28)
x = self.pool1(x) # output(16, 14, 14)
x = F.relu(self.conv2(x)) # output(32, 10, 10)
x = self.pool2(x) # output(32, 5, 5)
x = x.view(-1, 32 * 5 * 5) # output(32*5*5)
x = F.relu(self.fc1(x)) # output(120)
x = F.relu(self.fc2(x)) # output(84)
x = self.fc3(x) # output(10)
return x
拆分讲解:
初始化
def __init__(self): # 定义网络结构
super(LeNet, self).__init__()
self.conv1 = nn.Conv2d(3, 16, 5) # in_channels, out_channels(几个卷积核,生成深度为几维的特征矩阵、输出维度), kernel_size(卷积核大小)
self.pool1 = nn.MaxPool2d(2, 2) # kernel_size(2*2), stride(未指定时,选取与池化核大小相同的步幅)
self.conv2 = nn.Conv2d(16, 32, 5) # 深度为16(通道数)
self.pool2 = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(32 * 5 * 5, 120) # 全连接层的输入是一个一维的向量,需要将前面输出的特征矩阵展平。全连接层输入的节点个数为32*5*5
self.fc2 = nn.Linear(120, 84) # 但节点个数不知道如何设置
self.fc3 = nn.Linear(84, 10) # 最后一层的输出需要注意,需要根据训练集进行修改(此处用的CIFAR 10的类别数为10)
定义前向传播方式
def forward(self, x): # 定义前向传播过程 x:输入数据,按[batch, channel, height, width]的顺序排列
x = F.relu(self.conv1(x)) # input(3, 32, 32) output(16, 28, 28),输出的大小为batch*16*28*28 都是通过计算得到,其中batch隐含不写
x = self.pool1(x) # output(16, 14, 14) ,高宽直接缩短为一半(池化层只改变高宽,不改变深度)
x = F.relu(self.conv2(x)) # output(32, 10, 10) 都是通过计算得到。经过conv2之后经过一层relu激活。
x = self.pool2(x) # output(32, 5, 5),高宽变为原始的一半
x = x.view(-1, 32 * 5 * 5) # output(32*5*5)。此处为展平操作,展平为一维向量。-1,表示自动推理得到的第一个维度,即为batch的值。
x = F.relu(self.fc1(x)) # output(120)
x = F.relu(self.fc2(x)) # output(84)
x = self.fc3(x) # output(10)
# 一般会在全连接层的最后接上softmax层,将输出转化为概率。但此处没有。因为在后面计算卷积交叉熵的时候实现了更高效的softmax计算。
return x
train / 使用CIFAR 10对模型进行训练
完整代码
import torch
import torchvision
import torch.nn as nn
from model import LeNet
import torch.optim as optim
import torchvision.transforms as transforms
import numpy as np
import matplotlib.pyplot as plt
def main():
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# 50000张训练图片
# 第一次使用时要将download设置为True才会自动去下载数据集
train_set = torchvision.datasets.CIFAR10(root='./data', train=True,
download=False, transform=transform)
train_loader = torch.utils.data.DataLoader(train_set, batch_size=36,
shuffle=True, num_workers=0)
# 10000张验证图片
# 第一次使用时要将download设置为True才会自动去下载数据集
val_set = torchvision.datasets.CIFAR10(root='./data', train=False,
download=False, transform=transform)
val_loader = torch.utils.data.DataLoader(val_set, batch_size=5000,
shuffle=False, num_workers=0)
val_data_iter = iter(val_loader)
val_image, val_label = val_data_iter.next()
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
net = LeNet()
loss_function = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)
for epoch in range(5): # loop over the dataset multiple times
running_loss = 0.0
for step, data in enumerate(train_loader, start=0):
# get the inputs; data is a list of [inputs, labels]
inputs, labels = data
# zero the parameter gradients
optimizer.zero_grad()
# forward + backward + optimize
outputs = net(inputs)
loss = loss_function(outputs, labels)
loss.backward()
optimizer.step()
# print statistics
running_loss += loss.item()
if step % 500 == 499: # print every 500 mini-batches
with torch.no_grad():
outputs = net(val_image) # [batch, 10]
predict_y = torch.max(outputs, dim=1)[1]
accuracy = torch.eq(predict_y, val_label).sum().item() / val_label.size(0)
print('[%d, %5d] train_loss: %.3f test_accuracy: %.3f' %
(epoch + 1, step + 1, running_loss / 500, accuracy))
running_loss = 0.0
print('Finished Training')
save_path = './Lenet.pth'
torch.save(net.state_dict(), save_path)
# # 简单展示数据集中的图片,随机展示4张图片并输出标签
# def imshow(img):
# # denormalize 反标准化,将图片转化为正常格式
# img = img / 2 + 0.5 # 前面transform部分进行标准化 output = (input=0.5)/0.2;此处为反标准化input=output*o.5+0.5=output/2+0.5
# nping = img.numpy()
# plt.imshow(np.transpose(nping, (1, 2, 0))) # H*W*C,图片原始shape格式
# plt.show()
#
# # print labels
# print(''.join('%5s' % classes[test_label[j]] for j in range(4)))
# # show images
# imshow(torchvision.utils.make_grid(val_image)) # 需要首先将测试数据集的数量改为4,简单读取查看
if __name__ == '__main__':
main()
拆分讲解
数据集下载与导入
# 图像预处理,compose打包,ToTensor(),将numpy数据转化为Tensor
transform = transforms.Compose(
[transforms.ToTensor(), # ToTensor:将PIL图片或numpy数组转化为Tensor格式
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) # normalize标准化,使用均值和标准差
# 50000张训练图片
# 第一次使用时要将download设置为True才会自动去下载数据集,之后改为False
train_set = torchvision.datasets.CIFAR10(root='./data', train=True, # root:下载目录
download=False, transform=transform)
train_loader = torch.utils.data.DataLoader(train_set, batch_size=36, # batch_size:一个批次的大小为36
shuffle=True, num_workers=0) # shuffle:随机抽取batch时是否打乱数据
# 10000张验证图片
# 第一次使用时要将download设置为True才会自动去下载数据集,之后改为False
val_set = torchvision.datasets.CIFAR10(root='./data', train=False,
download=False, transform=transform)
val_loader = torch.utils.data.DataLoader(val_set, batch_size=10000, # 直接将验证集中的图片全部取出
shuffle=False, num_workers=0)
val_data_iter = iter(val_loader) # 可迭代获取数据的迭代器
val_image, val_label = val_data_iter.next() # 迭代器通过next获取数据
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck') # 标签是元组类型,其值不能改变。classes[0]恒表示‘plane’
实例化模型,并定义Loss和优化器
net = LeNet() # 实例化模型
loss_function = nn.CrossEntropyLoss() # 交叉熵损失函数,包含LogSoftmax和NLLLoss(所以在前面不需要缀写一个softmax)
optimizer = optim.Adam(net.parameters(), lr=0.001) # 优化器,将所有参数放入进行训练。学习率定为0.001
训练
for epoch in range(5): # loop over the dataset multiple times ,将训练集迭代五次
running_loss = 0.0 # 临时变量,用于累加训练过程中的损失
for step, data in enumerate(train_loader, start=0): # step从0开始,作为Index
# get the inputs; data is a list of [inputs, labels]
inputs, labels = data
# zero the parameter gradients
optimizer.zero_grad() # 每计算一个batch,就会调用一次。(不清除历史梯度,就会对计算的历史梯度进行累加。实现一个很大batch数值的训练)
# forward + backward + optimize
outputs = net(inputs)
loss = loss_function(outputs, labels)
loss.backward() # 误差,反向传播
optimizer.step() # 优化器,参数更新
# print statistics
running_loss += loss.item()
if step % 500 == 499: # print every 500 mini-batches,每隔500张图片打印并计算loss
with torch.no_grad(): # with是一个上下文管理器。这句表示接下来的过程不计算梯度。避免梯度计算占用更多计算和内存资源,避免崩溃
outputs = net(val_image) # [batch, 10] # 将验证集中的图片放入模型作为inputs
predict_y = torch.max(outputs, dim=1)[1] # 对预测结果排序,并取最大值;在维度1上进行取最大值操作,(维度0为index)。[1]表示只需要获取其index,不需要知道其最大值是多少。
accuracy = torch.eq(predict_y, val_label).sum().item() / val_label.size(0) # 预测正确的样本个数 / 测试集样本的总个数
print('[%d, %5d] train_loss: %.3f test_accuracy: %.3f' %
(epoch + 1, step + 1, running_loss / 500, accuracy)) # 每500次进行打印,所以loss求和之后除以500,求该轮平均Loss
running_loss = 0.0 # 重置,进入下一轮的500
print('Finished Training') # 训练完全结束后打印
保存模型
save_path = './Lenet.pth'
torch.save(net.state_dict(), save_path) # torch.save() 保存模型训练权重
predict
完整代码
import torch
import torchvision.transforms as transforms
from PIL import Image
from model import LeNet
def main():
transform = transforms.Compose(
[transforms.Resize((32, 32)), # 将图片进行尺寸缩放
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) # 训练与测试过程中的标准化应该保持一致
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
net = LeNet()
net.load_state_dict(torch.load('Lenet.pth'))
im = Image.open('R.jpg')
im = transform(im) # [C, H, W]
im = torch.unsqueeze(im, dim=0) # [N, C, H, W] # 增加新的维度N,表示batch数,dim=0,表示加在最前面
with torch.no_grad():
outputs = net(im)
predict = torch.max(outputs, dim=1)[1].numpy() # 此处的max不是softmax,是简单的求取最大值的操作
# predict = torch.softmax(outputs, dim=1) # 得到对于十个类别的不同预测值。
print(classes[int(predict)])
if __name__ == '__main__':
main()
注意事项
transforms.Resize((32, 32)), # 将图片进行尺寸缩放
im = torch.unsqueeze(im, dim=0) # [N, C, H, W] # 增加新的维度N,表示batch数,dim=0,表示加在最前面
预测:将图片放入同路径下,并修改im = Image.open('R.jpg')
中的文件名。