CNN的Pytorch实现(LeNet)

  上次写了一篇CNN的详解,可是累坏了老僧我。写完后拿给朋友看,朋友说你这Pytorch的实现方式对于新人来讲会很不友好,然后反问我说里面所有的细节你都明白了吗。我想想,的确如此。那个源码是我当时《动手学pytorch》的时候整理的,里面有很多包装过的函数,对于新入门的人来讲,的确是个大问题。于是,痛定思痛的我决定重新写Pytorch实现这一部分,理论部分我就不多讲了,咱们直接分析代码,此代码是来自Pytorch官方给出的LeNet Model。你可以使用Jupyter Notebook一行一行的学习,也可以使用Pycharm进行断点训练和Debug来学习。

没有看过理论部分的同学可以看我上篇博客:CNN卷积神经网络详解

  为了这篇文章易懂,我把分成以下几个模块:

  • 任务目标

  • 库导入

  • 模型定义

  • 数据加载、处理

  • 模型训练

  • 代码汇总

  在整个讲解的过程中,其中的一些比较重要的代码我会引入一些例子来进行解释它的功能,如果你想先直接跑通代码,可以直接跳到代码汇总部分,Here we go~

1. 任务目标

  这是一个对于彩色图的10分类的问题,具体种类有:'plane', 'car', 'bird', 'cat','deer', 'dog', 'frog', 'horse', 'ship', 'truck',训练一个能够对其进行分类的分类器。

2. 库的导入

这一部分咱们就不说太多了吧,直接上code:

import torch # 张量的有关运算,如创建、索引、连接、转置....和numpy的操作很像
import torch.nn as nn # 八廓搭建神经网络层的模块、loss等等
import torch.nn.functional as F # 常用的激活函数都在这里面
import torchvision # 专门处理图像的库
import torch.optim as optim # 各种参数优化方法,SGD、Adam...
import torchvision.transforms as transforms # 提供了一般的图像转换操作的类,也可以用于图像增强
import matplotlib.pyplot as plt
import numpy as np

3. 模型定义

  我们在定义自己网络的时候,需要继承nn.Module类,并重新实现构造函数__init__和forward两个方法。forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。如果你是用我下面的这个方法来定义的模型,在forward中要去连接它们之间的关系;如果你是用Sequential的方法来定义的模型,一般来讲可以直接在构造函数定义好后,在foward函数中return就行了(如果模型比较复杂就另当别论)。

class LeNet(nn.Module):
"""
下面这个模型定义没有用Sequential来定义,Sequential的定义方法能够在init中就给出各个层
之间的关系,我这里是根据是否有可学习的参数。我将可学习参数的层(如全连接、卷积)放在构造函数
中(其实你想把不具有参数的层放在里面也可以),把不具有学习参数的层(如dropout,
ReLU等激活函数、BN层)放在forward。 """
def __init__(self):
super(LeNet,self).__init__() # 第一个卷积块,这里输入的是3通道,彩色图。
self.conv1 = nn.Conv2d(3,16,5)
self.pool1 = nn.MaxPool2d(2,2) # 第二个卷积块
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)
pass def forward(self,x):
# x是输入数据,是一个tensor
# 正向传播
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)
# 数据通过view展成一维向量,第一个参数-1是batch,自动推理;32x5x5是展平后的个数
x = F.relu(self.fc1(x)) # output(120)
x = F.relu(self.fc2(x)) # output(84)
x = self.fc3(x) # output(10) # 为什么没有用softmax函数 --- 在网络模型中已经计算交叉熵以及概率
return x

我们还可以随便看一下可训练参数:

model = LeNet()
for name,parameters in model.named_parameters():
if param.requires_grad:
print(name,':',parameters.size())

看一下实例化的模型:

import torch
input1 = torch.rand([32,3,32,32])
model = LeNet() # 模式实例化
print(model) # 看一下模型结构
output = model(input1)

这里就不再拓展了,我发4我发4,我会专门再写一篇使用pytorch查看特征矩阵 和卷积核参数的文章。

4. 数据加载、处理

# 调用设备内的GPU并打印出来
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print("using {} device.".format(device)) # 定义图像数据的数据预处理方式
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) # 如果是第一次运行代码,没有下载数据集,则将download调制为True进行下载,并加载训练集
# transform是选择数据预处理的方式,我们已经提前定义
train_set = torchvision.datasets.CIFAR10(root='./data', train=True,
download=False,transform=transform) # 如果你是windows系统,一定要记得把num_workers设置为0,不然会报错。
# 这个是将数据集划为为n个批次,每个批次的数据集有batchSize张图片,shuffle是打乱数据集
train_loader = torch.utils.data.DataLoader(train_set, batch_size=36,
shuffle=True, num_workers=0) # 上面已经下载过的话,download设置为False
val_set = torchvision.datasets.CIFAR10(root='./data', train=False,
download=False, transform=transform) # 验证集不用打乱,把batchsize设置为1,每次拿出1张来验证
val_loader = torch.utils.data.DataLoader(val_set, batch_size=1
shuffle=False, num_workers=0) # 定义classes类别
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck') val_data_iter = iter(val_loader) # 转换成可迭代的迭代器
val_image, val_label = val_data_iter.next() # 定义imshow函数显示图像
def imshow(img):
img = img / 2 + 0.5 # unnormalize -> 反标准化处理
npimg = img.numpy() # numpy和tensor的通道顺序不同 tensor是通道度、宽度,numpy是高、宽、通 # 使用transpose调整维度
plt.imshow(np.transpose(npimg, (1, 2, 0))) #(1,2,0)-> 代表高度、宽度 通道
plt.show() imshow(torchvision.utils.make_grid(val_image))
# 显示图像结果:

在这个图像加载部分,我做了些其它的尝试,想要去发现train_set和train_loader之间的不同。这里你可以逐行取消我注释的代码,然后去观察,去对比,你就知道有哪些不一样了。


"""
train_set: 总结:经过多次尝试,发现train_set是用一个Dataset包装起来,用索引来提取第n个数据,提出的数据是一个元组。
元组的第一个索引是Tensor的图像数据,(channel,height,width),索引的第二个数据是标签 int类型。
可以选择用enumerate迭代器,也可以直接进行索引,这里因为没有batchsize的维度,所以可以直接调用自己写的
imshow函数来显示图片
""" for i,data in enumerate(train_set):
if i == 7:
# imshow(data[0])
# print(data[0])
# print(train_set[i][0]) # 查看train-set第七张图元组 的 索引0
print(train_set[i][0].shape)
print(train_set[i][1]) # 查看train-set第七张图元组 的 索引1
# imshow(train_set[i][0])
print(type(train_set[i][1]))
# print(train_set[i].shape)
print(data[0])
print(data[0].shape)
# print(type(data[i]))
"""
train_loader 总结:和Dataset类型不一样,DataLoader不能够直接用索引获取数据。需要用enumerate迭代器来获取 或者 iter.
经过enumerate索引后,得到的data类型是拥有两个变量的列表类型。第一个变量是Tensor类型,用[batchSize,channel,height,width]表示
批图像数据,里面是有batchsize张图的。第二个变量也是Tensor类型,是代表每张图像的标签,是个一维torch """ for i,data in enumerate(train_loader):
if i == 7:
print(type(data))
print(len(data))
print(type(data[0]))
print(type(data[1]))
print(data[0].shape)
print(data[1].shape)
print(type(data[1]))
# print(data[0])
print(data[1])
# print(type(data[2]))

5. 模型训练

# 用GPU训练
import time
torch.cuda.synchronize()
start = time.time() net = LeNet()
net.to(device) #使用GPU时把网络分配到指定的device中
loss_function = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(),lr=0.001) # Adam优化器 Loss = []
for epoch in range(5):
# 这里就只训练5个epoch,你可以试试多个
running_loss = 0.0
for step,data in enumerate(train_loader,start=0):
inputs,labels = data # data是一个列表,[数据,标签] # 清除历史梯度,加快训练
optimizer.zero_grad() outputs = net(inputs.to(device)) # 将输入的数据分配到指定的GPU中 loss = loss_function(outputs,labels.to(device)) # 将labels分配到指定的device loss.backward() # loss进行反向传播
optimizer.step() # step进行参数更新 # 打印数据
running_loss += loss.item() # 每次计算完loss后加入到running_loss中
if step % 500 == 499: # 每500个mini-batches 就打印一次
with torch.no_grad():
outputs = net(val_image.to(device))
# outputs的shape = [32,10]
# dim是max函数索引的维度,0是每列最大值,1是每行最大值
predict_y = torch.max(outputs,dim=1)[1] # max函数返回的每个batchSize的最大值 + 索引。获取索引[1] # == 来比较每个batchSize中的训练结果标签和原标签是否相同,如果预测正确就返回1,否则返回0,并累计正确的数量。
# 得到的是tensor,用item转成数字,CPU时使用 accuracy = (predict_y == val_label.to(device)).sum().item()/val_label.size(0)
# val_label.size是验证集中batchSize的大小
print('[%d %5d] train_loss: %.3f test_accuracy:%.3f' % (epoch+1,step+1,
running_loss/500,accuracy))
Loss.append(running_loss)
running_loss = 0.0
print('Finished Training') torch.cuda.synchronize()
end = time.time() print("训练用时:",end-start,'s')

五个epoch在我的GPU上训练了68s。

整个代码

model.py

import torch.nn as nn
import torch.nn.functional as F class LeNet(nn.Module):
# 要继承于nn.Moudule父类
def __init__(self):
# 初始化函数 super(LeNet, self).__init__()
# 使用super函数,解决多继承可能遇到的一些问题;调用基类的构造函数 self.conv1 = nn.Conv2d(3, 16, 5) # 调用卷积层 (in_channels,out_channels(也是卷积核个数。输出的通道数),kernel_size(卷积核大小),stride)
self.pool1 = nn.MaxPool2d(2, 2) # 最大池化层,进行下采样
self.conv2 = nn.Conv2d(16, 32, 5) # 输出的通道数为32
self.pool2 = nn.MaxPool2d(2, 2) self.fc1 = nn.Linear(32*5*5, 120) # 全连接层输入是一维向量,这里是32x5x5,我们要展平,120是节点的个数
# 32是通道数
# Linear(input_features,output_features) self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10) def forward(self, x):
# x是输入数据,是一个tensor
# 正向传播
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)
# 数据通过view展成一维向量,第一个参数-1是batch,自动推理;32x5x5是展平后的个数
x = F.relu(self.fc1(x)) # output(120)
x = F.relu(self.fc2(x)) # output(84)
x = self.fc3(x) # output(10)
# 为什么没有用softmax函数 --- 在网络模型中已经计算交叉熵以及概率
return x import torch
input1 = torch.rand([32,3,32,32])
model = LeNet() # 模式实例化
print(model) # 看一下模型结构
output = model(input1)

train.py

import torch
import torchvision
import torch.nn as nn
from model import LeNet
import torch.optim as optim
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np def main():
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print("using {} device.".format(device))
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)
# 把训练集读取,别分成一个一个批次的,shuffle可用于随机打乱;batch_size是一次处理36张图像
# num_worker在windows下只能设置成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)
# 验证集 一次拿出5000张1出来验证,不用打乱 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') # 这一部分用来看数据集
# def imshow(img):
# img = img / 2 + 0.5 # unnormalize -> 反标准化处理
# npimg = img.numpy()
# plt.imshow(np.transpose(npimg, (1, 2, 0))) #(1,2,0)-> 代表高度、宽度 通道
# plt.show()
#
# # print labels
# print(' '.join('%5s' % classes[val_label[j]] for j in range(4)))
# imshow(torchvision.utils.make_grid(val_image)) net = LeNet()
net.to(device) # 使用GPU时将网络分配到指定的device中,不使用GPU注释
loss_function = nn.CrossEntropyLoss() # 已经包含了softmax函数
optimizer = optim.Adam(net.parameters(), lr=0.001) #Adam优化器 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()
# 一般batch_size根据硬件设备来设置的,这个清楚历史梯度,不让梯度累计,可以让配置低的用户加快训练 # forward + backward + optimize 、、、、、CPU
# outputs = net(inputs)
# loss = loss_function(outputs, labels) # GPU使用时添加,不使用时注释
outputs = net(inputs.to(device)) # 将inputs分配到指定的device中
loss = loss_function(outputs, labels.to(device)) # 将labels分配到指定的device中 loss.backward() # loss进行反向传播
optimizer.step() # step进行参数更新 # print statistics
running_loss += loss.item() # m每次计算完后就加入到running_loss中
if step % 500 == 499: # print every 500 mini-batches
with torch.no_grad(): # 在测试、预测过程中,这个函数可以优化内存,防止爆内存
# outputs = net(val_image) # [batch, 10]
outputs = net(val_image.to(device)) # 使用GPU时用这行将test_image分配到指定的device中
predict_y = torch.max(outputs, dim=1)[1] #dim=1,因为dim=0是batch;[1]是索引,最大值在哪个位置
# accuracy = torch.eq(predict_y, val_label).sum().item() / val_label.size(0)
# eq用来比较,如果预测正确返回1,错误返回0 -> 得到的是tensor,要用item转成数值 CPU时使用 accuracy = (predict_y==val_label.to(device)).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) if __name__ == '__main__':
main()

Tips:数据集在当前目录下创建一个data文件夹,然后在train_set导入数据那里的download设置为True就可以下载了。如果你没有GPU的话,你可以使用CPU训练,只需要把代码中标记的GPU部分注释,注释掉的CPU部分取消注释就ok了。有条件还是GPU吧,CPU太慢了。

引用:

pytorch官方model

CNN的Pytorch实现(LeNet)的更多相关文章

  1. 深度学习识别CIFAR10:pytorch训练LeNet、AlexNet、VGG19实现及比较(三)

    版权声明:本文为博主原创文章,欢迎转载,并请注明出处.联系方式:460356155@qq.com VGGNet在2014年ImageNet图像分类任务竞赛中有出色的表现.网络结构如下图所示: 同样的, ...

  2. 深度学习识别CIFAR10:pytorch训练LeNet、AlexNet、VGG19实现及比较(二)

    版权声明:本文为博主原创文章,欢迎转载,并请注明出处.联系方式:460356155@qq.com AlexNet在2012年ImageNet图像分类任务竞赛中获得冠军.网络结构如下图所示: 对CIFA ...

  3. 深度学习识别CIFAR10:pytorch训练LeNet、AlexNet、VGG19实现及比较(一)

    版权声明:本文为博主原创文章,欢迎转载,并请注明出处.联系方式:460356155@qq.com 前面几篇文章介绍了MINIST,对这种简单图片的识别,LeNet-5可以达到99%的识别率. CIFA ...

  4. 深度学习方法(五):卷积神经网络CNN经典模型整理Lenet,Alexnet,Googlenet,VGG,Deep Residual Learning

    欢迎转载,转载请注明:本文出自Bin的专栏blog.csdn.net/xbinworld. 技术交流QQ群:433250724,欢迎对算法.技术感兴趣的同学加入. 关于卷积神经网络CNN,网络和文献中 ...

  5. 大话CNN经典模型:LeNet

        近几年来,卷积神经网络(Convolutional Neural Networks,简称CNN)在图像识别中取得了非常成功的应用,成为深度学习的一大亮点.CNN发展至今,已经有很多变种,其中有 ...

  6. 动手学习Pytorch(7)--LeNet

    Convolutional Neural Networks 使用全连接层的局限性: 图像在同一列邻近的像素在这个向量中可能相距较远.它们构成的模式可能难以被模型识别. 对于大尺寸的输入图像,使用全连接 ...

  7. 卷积神经网络CNN经典模型整理Lenet,Alexnet,Googlenet,VGG,Deep Residual Learning(转)

    参考:http://blog.csdn.net/xbinworld/article/details/45619685

  8. 超简单!pytorch入门教程(五):训练和测试CNN

    我们按照超简单!pytorch入门教程(四):准备图片数据集准备好了图片数据以后,就来训练一下识别这10类图片的cnn神经网络吧. 按照超简单!pytorch入门教程(三):构造一个小型CNN构建好一 ...

  9. CNN网络架构演进:从LeNet到DenseNet

    卷积神经网络可谓是现在深度学习领域中大红大紫的网络框架,尤其在计算机视觉领域更是一枝独秀.CNN从90年代的LeNet开始,21世纪初沉寂了10年,直到12年AlexNet开始又再焕发第二春,从ZF ...

随机推荐

  1. sshd_config详解

    # $OpenBSD: sshd_config,v 1.101 2017/03/14 07:19:07 djm Exp $ # This is the sshd server system-wide ...

  2. javascript学习--(四)面向对象:

    一.生成器generator: javascript里的generator函数是用function*定义的, 案例:yield 也会返回 function* foo(x){ yield x+1; yi ...

  3. C++第三十四篇 -- 安装Windows Driver后,编译以前项目出现打不开lib文件

    VS2017默认是没有安装WDK的,但是我们写驱动文件的话需要用到WDK.不过安装了WDK后,发现以前一些正常的项目在Release模式下编译会报LINK1104,无法打开.lib的错误 针对这个错误 ...

  4. windows下python -m pip install --upgrade pip升级后报错的解决方法

    前言: 笔者装某库的时候提示需要升级pip版本,就python -m pip install --upgrade pip默认升级了,结果升级之后只要输入pip就有报错(如下图),网上百度了很多解决方法 ...

  5. Python - 基础数据类型 set 集合

    集合的简介 集合是一个无序.不重复的序列 它的基本用法包括成员检测和消除重复元素 集合对象也支持像 联合,交集,差集,对称差分等数学运算 集合中所有的元素放在 {} 中间,并用逗号分开 集合的栗子 这 ...

  6. maven 标签 关于<import>标签

      标签用途:在dependecyManagement元素下用,合并此import标签上级dependency的groupId和artid中指向依赖的dependecyManagement内容   标 ...

  7. synchronized 优化手段之锁膨胀机制!

    synchronized 在 JDK 1.5 之前性能是比较低的,在那时我们通常会选择使用 Lock 来替代 synchronized.然而这个情况在 JDK 1.6 时就发生了改变,JDK 1.6 ...

  8. anyRTC Web SDK 实现音视频呼叫功能

    前言 大家好,今天小编带给大家一个基于 anyRTC Web SDK 实现音视频呼叫的功能(本项目采用vue开发). 前提条件 在开始写代码之前还需要做一些准备工作,如果你之前没有使用过 anyRTC ...

  9. 在Windows中安装PySpark环境

    在Windows中安装PySpark环境 安装Python 可以选择安装官方版本的Python,或是Anaconda,对应的地址如下. 下载地址 Python:https://www.python.o ...

  10. RSA算法之学习

    一.RSA算法 RSA是非对称加密算法中的代表,它的重要性不言而喻,为了弄清楚RSA算法,我们一起来完成一项任务: 背景:现在是疫情时代,假如小明和女朋友被迫在两个城市,小明为了表达感情,想发给对方一 ...