基于pytorch实现的手写数字识别

这两天一直在学pytorch和CNN,用LeNet-5和MNIST练练手,我魔改了很多也有比LeNet-5结果更好一点的,不过还是觉得经典永流传~

闲话不多说,LeNet-5结构如下:

 

 

网络结构图里都给的很清晰了,注意S4卷积之后到C5这里,其实是一个卷积操作,用的是120个5*5的卷积核来得到120层,之后才能拉伸再接全连接层,我在这里卡了很久想不通为什么直接拉伸会报错,只能说还是太嫩了。

简单说一下为什么要用卷积而不是直接全连接层:主要还是为了减少参数的个数,卷积有一个好处是参数共享,也就是说,无论上一层有多复杂,到这一层都是共用这5*5个参数,有效减少参数量,加快训练速度。池化在我的理解中一是为了进一步减少参数,二是提取2*2大小里一个主要的特征,增强了所谓的平移不变性(就是希望图像稍微移动也不影响)。

代码如下:

1.导入所需要的库/包,成为一个合格的调参侠的第一步

1 #1.导入库
2 import numpy as np
3 import torch 
4 import torch.nn as nn
5 import torch.nn.functional as F
6 import torch.optim as optim
7 from torchvision import datasets,transforms

2.定义一些超参和图像预处理的方法

#2.定义超参和pipeline
Batch_size=4096     #根据自己情况来,我这里是6GB显存
Epoch=50        #迭代次数

#选择是用GPU还是CPU,我这里的cuda已经配置好了所以用了GPU
Device=torch.device("cuda" if torch.cuda.is_available() else "cpu")        

#pipeline用来预处理图像
pipeline=transforms.Compose([
    transforms.ToTensor(),      #转化成人见人爱的tensor
    transforms.Normalize((0.1307,),(0.3081,))       #归一化处理加快训练速度,两个数字分别是均值和标准差,由官方文档给出
])    

3.下载数据集并分别加载训练集和测试集

这里用的是torch的MNIST,大概有60000张图片用来训练,10000张图片用来测试,值得一提的是每张灰度图图片大小都是28*28*1

from torch.utils.data import DataLoader
train_set=datasets.MNIST("data",train=True,download=True,transform=pipeline)
test_set=datasets.MNIST("data",train=False,download=True,transform=pipeline)

train_loader=DataLoader(train_set,batch_size=Batch_size,shuffle=True)    #shuffle是选择是否随机打乱
test_loader=DataLoader(test_set,batch_size=Batch_size,shuffle=True)

运行下面这几行可以查看一下其中的一张图片

#可以先看一下图像
with open("./data/MNIST/raw/t10k-images-idx3-ubyte","rb") as f:
    file=f.read()
image1=[int(str(item).encode("ascii"),16)for item in file[16:16+784]]       #这是官方文档给的格式,16开始到16+28*28*1是第一张图像
#print(image1)      #全是一堆灰度值,没什么意思
#import cv2
image1_np=np.array(image1,dtype=np.uint8).reshape(28,28,1)
#cv2.imwrite("1.jpg",image1_np)       #会在文件夹里生成一个图片,没什么影响

import matplotlib.pyplot as plt    #不得不说还是plt好用,我先用了PIL难写死了,cv2的话会跳出来一个新的窗口,不够帅我不喜欢
#import matplotlib.image as mping
plt.imshow(image1_np)

4.定义模型。

这里有一个问题,MNIST的图片是28*28*1,而LeNet-5一开始的输入为32*32*1,所以我干脆加了两层padding让他第一次卷积之后大小和LeNet-5一致,也算是偷个懒吧

#4.定义模型
class Digit(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1=nn.Conv2d(1,6,5,padding=2)    #灰度图片的通道1,输出通道10,卷积核大小5
        self.conv2=nn.Conv2d(6,16,5)             #出现奇怪的问题哈哈哈,因为MNIST图像大小为28*28*1,原本的LeNet-5是32*32*1
        self.conv3=nn.Conv2d(16,120,5)           #所以在conv1这里加了padding模拟一下
        self.fc1=nn.Linear(120,84)    #全连接层,输入通道120,输出通道84
        self.fc2=nn.Linear(84,10)
        
    def forward(self,x):
        input_size=x.size(0)    #x的格式是batch_size*1*28*28
        x=self.conv1(x)         #卷积层
        x=F.relu(x)             #激活函数relu,不要用sigmoid很慢
        x=F.max_pool2d(x,2,2)   #最大池化2*2
              
        x=self.conv2(x)
        x=F.relu(x)         
        x=F.max_pool2d(x,2,2)

        x=self.conv3(x)
        x=F.relu(x)

        x=x.view(input_size,-1)     #拉伸成一个长条~
        x=self.fc1(x)       #全连接层输入b_s*120,输出b_s*84
        x=F.relu(x)
        x=self.fc2(x)       #输入b_x*84,输出b_s*10
        output=F.softmax(x,dim=1)   #计算分类概率

        return output

5.定义优化方法

我这边是直接用了别人建议的Adam

#5.定义优化方法,就用Adam
model=Digit().to(Device)
optimizer=optim.Adam(model.parameters())

6.定义训练方法

#6.定义训练方法
def train_model(model,Device,train_loader,optimizer,epoch):
    #模型训练
    model.train()
    for batch_index,(data,label) in enumerate(train_loader):
        data,label=data.to(Device),label.to(Device)
        #梯度初始化为0,每次迭代之前一定要初始化,否则会一直累计之前的梯度,这里卡了我很久吃了大亏
        optimizer.zero_grad()
        output=model(data)
        
        loss=F.cross_entropy(output,label)
        #pred=output.max(1,keepdim=True)[1]     #找到最大概率对应的下标,这个在测试环节再加上就行了

        loss.backward()
        optimizer.step()
        if batch_index %3000 ==0:
            print("Train Epoch:{}\t Loss : {:6f}".format(epoch,loss.item()))

7。定义测试方法

#7.定义测试方法
def test_model(model,device,test_loader):
    model.eval()
    acc=0.0
    test_loss=0.0
    with torch.no_grad():
        for data,label in test_loader:
            data,label=data.to(device),label.to(device)
            output=model(data)
            test_loss += F.cross_entropy(output,label).item()
            pred= output.max(1,keepdim=True)[1]
            acc+=pred.eq(label.view_as(pred)).sum().item()      #学艺不精了,这行抄网上的,我试了几次都和预期的有很大差别
        test_loss/=len(test_loader.dataset)
        print("Test Loss :{:4f},Acc : {:3f}\n".format(test_loss,100.0*acc/len(test_loader.dataset)) )

8.调用,跑模型,测试

#8.调用开始跑
for epoch in range(1,1+Epoch):  
    train_model(model,Device,train_loader,optimizer,epoch)
    test_model(model,Device,test_loader)

9.这里其实应该再加一个画图环节,但我画图用的不太熟练,下次一定hhh。

posted @ 2022-01-15 22:29  Guoyf1696  阅读(353)  评论(0编辑  收藏  举报