pytorch实战动⼿设计CNN+MNIST⼿写体数字识别
⽂章⽬录
前⾔
相信对于每⼀个刚刚上⼿深度学习的孩⼦来说,利⽤mnist数据集来训练⼀个CNN是再好不过的学习demo了。
本⽂使⽤pytorch来动⼿搭建⼀个卷积神经⽹络来训练和预测⼿写数字。通过本⽂,你将了解到pytorch的⼀些功能:
1.⾼效加载数据集;
2.简单灵活地设计神经⽹络;
3.了解对训练和泛化有帮助的⽹络结构tricks(如batchnorm,dropout)
4.学习优化器(⼀般⽤adam);
5.神经⽹络的损失函数(⼀般⽤交叉熵);
6.学习率的动态调节(学习率的动态变化);
h训练过程(尤其是批量进⾏的训练⽅式mini-batch)
h预测的过程
接下来就开始啦,每⼀部分的代码我尽量搭配详细的注释,让你快速理解,轻松上⼿pytorch!
引⼊库函数
importtorch#pytorch最基本模块
#pytorch中最重要的模块,封装了神经⽹络相关的函数
onalasF#提供了⼀些常⽤的函数,如softmax
soptim#优化模块,封装了求解模型的⼀些优化器,如AdamSGD
mportlr_scheduler#学习率调整器,在训练过程中合理变动学习率
fromtorchvisionimporttransforms#pytorch视觉库中提供了⼀些数据变换的接⼝
fromtorchvisionimportdatats#pytorch视觉库提供了加载数据集的接⼝
预设超参数
#预设⽹络超参数(所谓超参数就是可以⼈为设定的参数
BATCH_SIZE=64#由于使⽤批量训练的⽅法,需要定义每批的训练的样本数⽬
EPOCHS=2#总共训练迭代的次数
#让torch判断是否使⽤GPU,建议使⽤GPU环境,因为会快很多
DEVICE=("cuda"_available()el"cpu")
learning_rate=0.1#设定初始的学习率
加载数据集
像MNIST这么知名的数据集,pytorch居然内置了对应的加载接⼝,真的优秀!不过第⼀次使⽤我们会下载数据集到⼀个⽂件夹中,以后就
可以直接读取该⽂件夹内部的数据了。这⾥我们使⽤dataloader迭代器来加载数据集,题外话:迭代器的作⽤可以减少内存的占⽤。
#加载训练集
train_loader=ader(
('data',train=True,download=True,
transform=e([
or(),
ize(mean=(0.5,),std=(0.5,))#数据规范化到正态分布
])),
batch_size=BATCH_SIZE,shuffle=True)#指明批量⼤⼩,打乱,这是处于后续训练的需要。
test_loader=ader(
('data',train=Fal,transform=e([
or(),
ize((0.5,),(0.5,))
])),
batch_size=BATCH_SIZE,shuffle=True)
上述代码的作⽤是:
1.将mnist训练集和测试集下载到⽂件夹data中
2.对数据进⾏变化,包括转为tensor数据类型,以及为了保证训练集测试集的独⽴同分布,数据规范化到正态分布
3.数据分批量存储,顺序打乱,⽅便后续训练。
设计CNN
⼀个简单的卷积神经⽹络的结构,⼀般包括:
卷积层Conv:通过卷积核提取图像特征,得到featuremap
池化层Pool:利⽤卷积核对featuremap降采样,减少尺⼨。最⼤池化层就是取滑动窗⼝内最⼤的像素,⽽平均池化层就是取滑动窗
⼝内平均像素结果。
全连接层:多个linearmodel+激活函数eg:ReLU。
这样就构成了⼀个基本的CNN了,但是我们出于爱学习的⼼态呢,我们最好还要了解⼀些深度学习训练过程中的⼀些tricks,以提⾼模型的
泛化能⼒。如:
batchnormalization:简单来说就是将上⼀层的加权求和的所有输出结果再批量归⼀化(标准正态分布),然后输⼊⼀个线性模型,
然后再连接到激活函数。
DropOut:在全连接层中,我们通过设定概率随机的让这⼀层的某些权重为0,相当于神经元⽆效。
emmmm,经过⼤量时间,这两个tricks表现挺好,不知如何解释。
且看代码吧,我给每⼀个接⼝的参数进⾏了详细的解释。
#设计模型
classConvNet():
def__init__(lf):
super(ConvNet,lf).__init__()
#提取特征层
es=tial(
#卷积层
#输⼊图像通道为1,因为我们使⽤的是⿊⽩图,单通道的
#输出通道为32(代表使⽤32个卷积核),⼀个卷积核产⽣⼀个单通道的特征图
#卷积核kernel_size的尺⼨为3*3,stride代表每次卷积核的移动像素个数为1
#padding填充,为1代表在图像长宽都多了两个像素
2d(in_channels=1,out_channels=32,kernel_size=3,stride=1,padding=1),
#批量归⼀化,跟上⼀层的out_channels⼤⼩相等,以下的通道规律也是必须要对应好的
orm2d(num_features=32),
#激活函数,inplace=true代表直接进⾏运算
(inplace=True),
2d(32,32,kernel_size=3,stride=1,padding=1),
orm2d(32),
(inplace=True),
#最⼤池化层
#kernel_size为2*2的滑动窗⼝
#stride为2,表⽰每次滑动距离为2个像素
#经过这⼀步,图像的⼤⼩变为1/4,即28*28-》14*14
l2d(kernel_size=2,stride=2),
2d(32,64,kernel_size=3,padding=1),
orm2d(64),
(inplace=True),
2d(64,64,kernel_size=3,padding=1),
orm2d(64),
(inplace=True),
l2d(kernel_size=2,stride=2)#14*14-》7*7
)
#分类全连接层
fier=tial(
#Dropout层
#p=0.5代表该层的每个权重有0.5的可能性为0
t(p=0.5),
#这⾥是通道数64*图像⼤⼩7*7,然后输⼊到512个神经元中
(64*7*7,512),
orm1d(512),
(inplace=True),
t(p=0.5),
(512,512),
orm1d(512),
(inplace=True),
t(p=0.5),
(512,10),
)
defforward(lf,x):
#经过特征提取层
x=es(x)
#输出结果必须展平成⼀维向量,然后连接到全连接层
x=((0),-1)
x=fier(x)
x=_softmax(out,dim=1)#经过softmax输出,可以交叉熵损失函数搭配使⽤,便于计算
returnx
⽹络结构的设计都是⾃定义的哦!你可以有⾃⼰更奇特的想法,或者复现知名⽹络结构如LeNet等等。
训练前准备
#初始化模型,将⽹络操作移动到GPU或者CPU
ConvModel=ConvNet().to(DEVICE)
#定义交叉熵损失函数
criterion=ntropyLoss().to(DEVICE)
#定义模型优化器:输⼊模型参数,定义初始学习率
optimizer=(ters(),lr=learning_rate)
#定义学习率调度器,输⼊包装的模型,定义学习率衰减周期step_size,gamma为衰减的乘法因⼦
exp_lr_scheduler=lr_(optimizer,step_size=3,gamma=0.1)
#在官⽹上的解释。如果初始学习率lr=0.05,衰减周期step_size为30,衰减乘法因⼦gamma=0.01
#Assumingoptimizeruslr=0.05forallgroups
#>>>#lr=0.05ifepoch<30
#>>>#lr=0.005if30<=epoch<60
#>>>#lr=0.0005if60<=epoch<90
训练模块
我们把训练模块封装起来,可以保证多次调⽤。
deftrain(num_epochs,_model,_device,_train_loader,_optimizer,_lr_scheduler):
_()#设置模型为训练模式
_lr_()#设置学习率调度器开始准备更新
forepochinrange(num_epochs):
#从迭代器抽取图⽚和标签
fori,(images,labels)inenumerate(_train_loader):
samples=(_device)
labels=(_device)
#此时样本是⼀批图⽚,在CNN的输⼊中,我们需要将其变为四维,
#reshape第⼀个-1代表⾃动计算批量图⽚的数⽬n
#最后reshape得到的结果就是n张图⽚,每⼀张图⽚都是单通道的28*28,得到四维张量
output=_model(e(-1,1,28,28))
#计算损失函数值
loss=criterion(output,labels)
#优化器内部参数梯度必须变为0
_grad()
#损失值后向传播
rd()
#更新模型参数
()
if(i+1)%100==0:
print("Epoch:{}/{},step:{},loss:{:.4f}".format(epoch+1,num_epochs,i+1,()))
预测模块
deftest(_test_loader,_model,_device):
_()#设置模型进⼊预测模式evaluation
loss=0
correct=0
_grad():#如果不需要backward更新梯度,那么就要禁⽤梯度计算,减少内存和计算资源浪费。
fordata,targetin_test_loader:
data,target=(_device),(_device)
output=ConvModel(e(-1,1,28,28))
loss+=criterion(output,target).item()#添加损失值
pred=(1,keepdim=True)[1]#找到概率最⼤的下标,为输出值
correct+=(_as(pred)).cpu().sum()#.cpu()是将参数迁移到cpu上来。
loss/=len(_test_t)
print('nAverageloss:{:.4f},Accuracy:{}/{}({:.3f}%)n'.format(
loss,correct,len(_test_t),
100.*correct/len(_test_t)))
运⾏
这时封装的好处就显⽽易见啦。
forepochinrange(1,EPOCHS+1):
train(epoch,ConvModel,DEVICE,train_loader,optimizer,exp_lr_scheduler)
test(test_loader,ConvModel,DEVICE)
test(train_loader,ConvModel,DEVICE)
结果
由于训练时间有点长(我没有N卡,只能⽤CPU。。),我只训练了两个epoch,训练达到⼀定的次数,泛化能⼒会增强,但是训练过度之
后,泛化能⼒会显著下降(过拟合),合理的制定epoch,利⽤测试集(验证集)来测试最佳的epoch。
准确度还是可以的,多训练⼏次,准确度就会提升滴,加油!
总结
本⽂还是有许多可扩展的地⽅,如要提⾼准确度,可以引⼊数据增⼴的⽅法,对原始图像进⾏⼀定程度的旋转,漂移,处理后的数据来训
练,可提⾼泛化能⼒。
个⼈觉得pytorch的学习并不复杂,多接触项⽬,遇到不懂的,及时查官⽅⽂档,看接⼝,还得多⽤,⽤着⽤着就熟悉掌握啦。
更⾼阶的就是去接触⼀些前沿的论⽂,看它们开源的代码,会有更深的体会。
所以下⼀步,我的计划就是研读论⽂代码并理解,多跑跑⼀些经典模型,如ResNet;
再下⼀步⾃⼰⼿动复现代码!
届时我会把我的感想和收获分享出来!如果觉得本⽂对你有所帮助,请为我点个赞好么^^
完整代码
见我的github:。
本文发布于:2022-12-27 20:44:42,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/42537.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |