写在前面

  • 使用了LeNet、AlexNet两个经典CNN结构,来进行对比分析
  • 但是效果并不好,如图所示
结构 准确度(%)
AlexNet(LR=0.001,epoch=2) 32.7
LeNet(LR=0.001,epoch=2) 42.1
AlexNet(LR=0.01,epoch=2) 9.4
LeNet(LR=0.01,epoch=2) 12.6
AlexNet(LR=0.001,epoch=3) 84.2
LeNet(LR=0.001,epoch=3) 48.1
  • 分析与猜想:
    • 1.由于层数较多,所以终稿的训练数据和测试数据较初稿都小了很多,初稿的训练数据有60000,但终稿的训练数据只有1000。所以在训练模型时,拟合度不高
    • 2.训练epoch次数小,实验中只设了2和3。
    • 3.在mnist数据集中lr-0.01显得有些大了

导入需要的包

In [1]:
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
import time


from torchvision import transforms,models
from torch import  optim
from torch.utils.data import DataLoader
from torchvision.datasets import mnist
from torch.autograd import Variable
In [2]:
# models.AlexNet

超参数的设定

  • LR:学习率
  • EPOCH:完整传播次数
  • BS:批处理数据量
  • __C:HyperParams类的一个对象
In [3]:
class HyperParams:
    def __init__(self):
        self.LR=0.001
        self.BS=64
#         self.RUN='train'
        self.EPOCH=3
        
__C=HyperParams()

获取数据

  • 从训练集、测试集中随机取1000个数据,不然要跑很久
  • train_data:训练集
  • test_data:测试集
In [4]:
#train_set = mnist.MNIST('./data',train=True)
#test_set = mnist.MNIST('./data',train=False)

train_set = mnist.MNIST('./data',train=True,transform=transforms.ToTensor(),download=False)
test_set = mnist.MNIST('./data',train=False,transform=transforms.ToTensor(),download=False)

# train_data=torch.unsqueeze(test_set.test_data, dim=1).type(torch.FloatTensor)/255
#从训练集、测试集中随机取1000个数据,不然要跑很久
train_data = DataLoader(train_set,batch_size=__C.BS,sampler = torch.utils.data.sampler.SubsetRandomSampler(range(1000)))
test_data = DataLoader(test_set,sampler = torch.utils.data.sampler.SubsetRandomSampler(range(1000)))
# test_data=(train_set,shuffle=True,sampler = DataLoader.sampler.SubsetRandomSampler(range(1000))
# test_x = torch.unsqueeze(test_set.test_data, dim=1).type(torch.FloatTensor)/255
# tt=type(train_data)
# print(tt)
# print(tt[:10000])
# test_y = test_set.test_labels
# print(torch.unsqueeze(train_set.train_data, dim=1).size())
# print(test_y.size())

定义LeNet

  • 每个卷积之后加了padding(这点跟原LeNet不一样)
  • 共5层,前2层是卷积层,后3层是全连接层
  • 没有使用Overlapping Pooling
In [5]:
class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv1 = nn.Conv2d(1, 16, 3, 1, 1) #(1,28,28)->(16,28,28)
        self.pool1 = nn.MaxPool2d(kernel_size=2,stride=2)#(16,28,28)->(16,14,14)
        self.conv2 = nn.Conv2d(16, 32, 5, 1, 2) #(16,14,14)->(32,14,14)
        self.pool2 = nn.MaxPool2d(kernel_size=2,stride=2)#(32,14,14)->(32,7,7)
        self.out1   = nn.Linear(32*7*7, 64)  
        self.out2   = nn.Linear(64, 16)
        self.out3   = nn.Linear(16, 10)

    def forward(self, x): 
        x = self.pool1(F.relu(self.conv1(x))) 
        x = self.pool1(F.relu(self.conv2(x))) 
        x = x.view(-1,32*7*7) 
        x = F.relu(self.out1(x))
        x = F.relu(self.out2(x))
        x = self.out3(x)        
        return x

定义AlexNet

  • 每个卷积之后加了padding(这点跟原AlexNet不一样)
  • 共8层,前5层是卷积层,后3层是全连接层
  • dropout设置的参数是0.2
  • 采用AlexNet中的Overlapping Pooling
In [6]:
class AlexNet(nn.Module):
    def __init__(self):
        super(AlexNet,self).__init__()
        self.conv1 = nn.Conv2d(in_channels=1,out_channels=16,kernel_size=3,stride=1,padding=1)#(1,28,28)->(16,28,28)
        self.pool1 = nn.MaxPool2d(kernel_size=3,stride=1)#Overlapping Pooling,(16,28,28)->(16,26,26)
        self.conv2 = nn.Conv2d(in_channels=16,out_channels=64,kernel_size=3,stride=1,padding=1)#(16,26,26)->(64,26,26)
        self.pool2 = nn.MaxPool2d(kernel_size=3, stride=1)#(64,26,26)->(64,24,24)
        self.conv3 = nn.Conv2d(in_channels=64,out_channels=128,kernel_size=3,stride=1,padding=1)#(64,24,24)->(128,24,24)
        self.conv4 = nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1,padding=1)#(128,24,24)->(128,24,24)
        self.conv5 = nn.Conv2d(in_channels=256, out_channels=32, kernel_size=3, stride=1,padding=1)#(256,24,24)->(32,24,24)
        self.pool3 = nn.MaxPool2d(kernel_size=3, stride=1)#(32,24,24)->(32,22,22)
        self.out1 = nn.Linear(32*22*22,2048)
        self.drop1 = nn.Dropout(0.2)
        self.out2 = nn.Linear(2048,2048)
        self.drop2 = nn.Dropout(0.2)
        self.out3 = nn.Linear(2048,10)

    def forward(self,x):
        x=self.pool1(F.relu(self.conv1(x)))
        x=self.pool2(F.relu(self.conv2(x)))
        x=self.pool3(F.relu(self.conv5(F.relu(self.conv4(F.relu(self.conv3(x)))))))
        x=x.view(-1,32*22*22)
        x=self.out3(self.drop2(F.relu(self.out2(self.drop1(F.relu(self.out1(x)))))))
        return x

定义Calculation

  • 该函数是用来训练模型并计算测试结果
  • 因为有两个模型要重复训练、测试,所以写了个函数,稍微简洁一些
In [19]:
def Calculation(models):
        model=models()
        optimizer = optim.Adam(model.parameters(),lr  =__C.LR)
        loss_func = nn.CrossEntropyLoss()
        
        #训练
        if models=='AlexNet':
            model.train()
        for epoch in range(__C.EPOCH):
            for step,(train_x,train_y) in enumerate(train_data):
            
                #AlexNet训练
                output = model(train_x)
                loss = loss_func(output,train_y)
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()
                
        #测试
        correct_num = 0
        model.eval()
        for data in test_data:
            test_x,test_y = data
            test_output = model(test_x)
            pred_y = torch.max(test_output,1)[1]
            if pred_y.item() == test_y.item():
                correct_num += 1
        acc=correct_num/ 1000
        print( models, 'test accuracy: %.4f' % acc)

主函数

  • 用Adam算法优化
  • 分类问题使用CrossEntropyLoss损失函数
  • 因为准确率比较低,所以训练两个epoch后再进行测试
In [20]:
if __name__ =='__main__':
#     model1='AlexNet'
    Calculation(AlexNet)
    Calculation(LeNet)
<class '__main__.AlexNet'> test accuracy: 0.8420
<class '__main__.LeNet'> test accuracy: 0.4810