欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

根据LeNet-5理解CNN训练过程

程序员文章站 2024-03-19 18:31:22
...

       LeNet-5是一个较简单的卷积神经网络。下图显示了其结构:输入的二维图像,先经过两次卷积层到池化层,再经过全连接层,最后使用softmax分类作为输出层。关于CNN参见:https://blog.csdn.net/qq_42570457/article/details/81458077

根据LeNet-5理解CNN训练过程

       LeNet-5 这个网络虽然很小,但是它包含了深度学习的基本模块:卷积层,池化层,全连接层。是其他深度学习模型的基础, 这里我们对LeNet-5进行深入分析。同时,通过实例分析,加深对与卷积层和池化层的理解。

根据LeNet-5理解CNN训练过程

       LeNet-5共有7层,不包含输入,每层都包含可训练参数;每个层有多个Feature Map,每个FeatureMap通过一种卷积滤波器提取输入的一种特征,然后每个FeatureMap有多个神经元

各层参数详解:

1、INPUT层-输入层

       首先是数据 INPUT 层,输入图像的尺寸统一归一化为32*32。

       注意:本层不算LeNet-5的网络结构,传统上,不将输入层视为网络层次结构之一。

2、C1层-卷积层

      输入图片:32*32

      卷积核大小:5*5

      卷积核种类:6

      输出featuremap大小:28*28 (32-5+1)=28

      神经元数量:28*28*6

      可训练参数:(5*5+1) * 6(每个滤波器5*5=25个unit参数和一个bias参数,一共6个滤波器)

      连接数:(5*5+1)*6*28*28=122304

     详细说明:对输入图像进行第一次卷积运算(使用 6 个大小为 5*5 的卷积核),得到6个C1特征图(6个大小为28*28的 feature maps, 32-5+1=28)。我们再来看看需要多少个参数,卷积核的大小为5*5,总共就有6*(5*5+1)=156个参数,其中+1是表示一个核有一个bias。对于卷积层C1,C1内的每个像素都与输入图像中的5*5个像素和1个bias有连接,所以总共有156*28*28=122304个连接(connection)。有122304个连接,但是我们只需要学习156个参数,主要是通过权值共享实现的。

3、S2层-池化层(下采样层)

      输入:28*28

      采样区域:2*2

      采样方式:4个输入相加,乘以一个可训练参数,再加上一个可训练偏置。结果通过sigmoid

      采样种类:6

      输出featureMap大小:14*14(28/2)

      神经元数量:14*14*6

      可训练参数:2*6(和的权+偏置)

      连接数:(2*2+1)*6*14*14

      S2中每个特征图的大小是C1中特征图大小的1/4。

       详细说明:第一次卷积之后紧接着就是池化运算,使用 2*2核 进行池化,于是得到了S2,6个14*14的 特征图(28/2=14)。S2这个pooling层是对C1中的2*2区域内的像素求和乘以一个权值系数再加上一个偏置,然后将这个结果再做一次映射。于是每个池化核有两个训练参数,所以共有2x6=12个训练参数,但是有5x14x14x6=5880个连接。

4、C3层-卷积层

       输入:S2中所有6个或者几个特征map组合

      卷积核大小:5*5

      卷积核种类:16

      输出featureMap大小:10*10 (14-5+1)=10

      C3中的每个特征map是连接到S2中的所有6个或者几个特征map的,表示本层的特征map是上一层提取到的特征map的不同组合。

       存在的一个方式是:C3的前6个特征图以S2中3个相邻的特征图子集为输入。接下来6个特征图以S2中4个相邻特征图子集为输入。然后的3个以不相邻的4个特征图子集为输入。最后一个将S2中所有特征图为输入。则:可训练参数:6*(3*5*5+1)+6*(4*5*5+1)+3*(4*5*5+1)+1*(6*5*5+1)=1516

       连接数:10*10*1516=151600

       详细说明:第一次池化之后是第二次卷积,第二次卷积的输出是C3,16个10x10的特征图,卷积核大小是 5*5. 我们知道S2 有6个 14*14 的特征图,怎么从6 个特征图得到 16个特征图了? 这里是通过对S2 的特征图特殊组合计算得到的16个特征图。具体如下:

根据LeNet-5理解CNN训练过程

       C3的前6个feature map(对应上图第一个红框的6列)与S2层相连的3个feature map相连接(上图第一个红框),后面6个feature map与S2层相连的4个feature map相连接(上图第二个红框),后面3个feature map与S2层部分不相连的4个feature map相连接,最后一个与S2层的所有feature map相连。卷积核大小依然为5*5,所以总共有6*(3*5*5+1)+6*(4*5*5+1)+3*(4*5*5+1)+1*(6*5*5+1)=1516个参数。而图像大小为10*10,所以共有151600个连接。

根据LeNet-5理解CNN训练过程

        C3与S2中前3个图相连的卷积结构如下图所示:

根据LeNet-5理解CNN训练过程

       上图对应的参数为 3*5*5+1,一共进行6次卷积得到6个特征图,所以有6*(3*5*5+1)参数。 为什么采用上述这样的组合了?论文中说有两个原因:1)减少参数,2)这种不对称的组合连接的方式有利于提取多种组合特征。

5、S4层-池化层(下采样层)

       输入:10*10

       采样区域:2*2

      采样方式:4个输入相加,乘以一个可训练参数,再加上一个可训练偏置。结果通过sigmoid

      采样种类:16

      输出featureMap大小:5*5(10/2)

      神经元数量:5*5*16=400

      可训练参数:2*16=32(和的权+偏置)

      连接数:16*(2*2+1)*5*5=2000

      S4中每个特征图的大小是C3中特征图大小的1/4

      详细说明:S4是pooling层,窗口大小仍然是2*2,共计16个feature map,C3层的16个10x10的图分别进行以2x2为单位的池化得到16个5x5的特征图。这一层有2x16共32个训练参数,5x5x5x16=2000个连接。连接的方式与S2层类似。

6、C5层-卷积层

     输入:S4层的全部16个单元特征map(与s4全相连)

     卷积核大小:5*5

     卷积核种类:120

     输出featureMap大小:1*1(5-5+1)

     可训练参数/连接:120*(16*5*5+1)=48120

     详细说明:C5层是一个卷积层。由于S4层的16个图的大小为5x5,与卷积核的大小相同,所以卷积后形成的图的大小为1x1。这里形成120个卷积结果。每个都与上一层的16个图相连。所以共有(5x5x16+1)x120 = 48120个参数,同样有48120个连接。C5层的网络结构如下:

根据LeNet-5理解CNN训练过程

7、F6层-全连接层

      输入:c5 120维向量

      计算方式:计算输入向量和权重向量之间的点积,再加上一个偏置,结果通过sigmoid函数输出。

      可训练参数:84*(120+1)=10164

      详细说明:6层是全连接层。F6层有84个节点,对应于一个7x12的比特图,-1表示白色,1表示黑色,这样每个符号的比特图的黑白色就对应于一个编码。该层的训练参数和连接数是(120 + 1)x84=10164。ASCII编码图如下:

根据LeNet-5理解CNN训练过程

     F6层的连接方式如下:

根据LeNet-5理解CNN训练过程

8、Output层-全连接层

       Output层也是全连接层,共有10个节点,分别代表数字0到9,且如果节点i的值为0,则网络识别的结果是数字i。采用的是径向基函数(RBF)的网络连接方式。假设x是上一层的输入,y是RBF的输出,则RBF输出的计算方式是:

根据LeNet-5理解CNN训练过程

     上式w_ij 的值由i的比特图编码确定,i从0到9,j取值从0到7*12-1。RBF输出的值越接近于0,则越接近于i,即越接近于i的ASCII编码图,表示当前网络输入的识别结果是字符i。该层有84x10=840个参数和连接。

根据LeNet-5理解CNN训练过程

上图是LeNet-5识别数字3的过程。

总结

  • LeNet-5是一种用于手写体字符识别的非常高效的卷积神经网络。
  • 卷积神经网络能够很好的利用图像的结构信息。
  • 卷积层的参数较少,这也是由卷积层的主要特性即局部连接和共享权重所决定。

 参考:http://cuijiahua.com/blog/2018/01/dl_3.html


import torch
import torchvision
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
from torchvision import transforms
from torch.autograd import Variable
from torch import optim
import torch.nn as nn
import torch.nn.functional as F
import time

learning_rate = 1e-3
batch_size = 64
epoches = 50

trans_img = transforms.ToTensor()

trainset = MNIST('./data', train=True, transform=trans_img)
testset = MNIST('./data', train=False, transform=trans_img)

trainloader = DataLoader(trainset, batch_size=batch_size, shuffle=True, num_workers=4)
testloader = DataLoader(testset, batch_size=batch_size, shuffle=False, num_workers=4)

# build network
class Lenet(nn.Module):
    def __init__(self):
        super(Lenet, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(1, 6, 3, stride=1, padding=1),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(6, 16, 5, stride=1, padding=0),
            nn.MaxPool2d(2, 2)
        )

        self.fc = nn.Sequential(
            nn.Linear(400, 120),
            nn.Linear(120, 84),
            nn.Linear(84, 10)
        )


    def forward(self, x):
        out = self.conv(x)
        out = out.view(out.size(0), -1)
        out = self.fc(out)
        return out

lenet = Lenet()
lenet.cuda()

criterian = nn.CrossEntropyLoss(size_average=False)
optimizer = optim.SGD(lenet.parameters(), lr=learning_rate)

# train
for i in range(epoches):
    since = time.time()
    running_loss = 0.
    running_acc = 0.
    for (img, label) in trainloader:
        img = Variable(img).cuda()
        label = Variable(label).cuda()

        optimizer.zero_grad()
        output = lenet(img)
        loss = criterian(output, label)
        # backward
        loss.backward()
        optimizer.step()

        running_loss += loss.data[0]
        _, predict = torch.max(output, 1)
        correct_num = (predict == label).sum()
        running_acc += correct_num.data[0]

    running_loss /= len(trainset)
    running_acc /= len(trainset)
    print("[%d/%d] Loss: %.5f, Acc: %.2f, Time: %.1f s" %(i+1, epoches, running_loss, 100*running_acc, time.time()-since))
[1/50] Loss: 0.42389, Acc: 86.35, Time: 3.4 s
[2/50] Loss: 0.10881, Acc: 96.67, Time: 3.7 s
[3/50] Loss: 0.08289, Acc: 97.48, Time: 3.5 s
[4/50] Loss: 0.06887, Acc: 97.89, Time: 3.7 s
[5/50] Loss: 0.06077, Acc: 98.09, Time: 3.5 s
[6/50] Loss: 0.05484, Acc: 98.31, Time: 3.8 s
[7/50] Loss: 0.04954, Acc: 98.45, Time: 3.6 s
[8/50] Loss: 0.04534, Acc: 98.57, Time: 3.7 s
[9/50] Loss: 0.04253, Acc: 98.67, Time: 3.6 s
[10/50] Loss: 0.04110, Acc: 98.71, Time: 3.7 s
[11/50] Loss: 0.03782, Acc: 98.77, Time: 3.6 s
[12/50] Loss: 0.03587, Acc: 98.87, Time: 3.6 s
[13/50] Loss: 0.03356, Acc: 98.88, Time: 3.6 s
[14/50] Loss: 0.03267, Acc: 98.94, Time: 3.6 s
[15/50] Loss: 0.03037, Acc: 99.04, Time: 3.6 s
[16/50] Loss: 0.02808, Acc: 99.11, Time: 3.5 s
[17/50] Loss: 0.02749, Acc: 99.14, Time: 3.4 s
[18/50] Loss: 0.02691, Acc: 99.10, Time: 3.6 s
[19/50] Loss: 0.02525, Acc: 99.16, Time: 3.5 s
[20/50] Loss: 0.02455, Acc: 99.20, Time: 3.5 s
[21/50] Loss: 0.02329, Acc: 99.23, Time: 3.5 s
[22/50] Loss: 0.02152, Acc: 99.29, Time: 3.6 s
[23/50] Loss: 0.02143, Acc: 99.32, Time: 3.5 s
[24/50] Loss: 0.02045, Acc: 99.33, Time: 3.4 s
[25/50] Loss: 0.01931, Acc: 99.39, Time: 3.4 s
[26/50] Loss: 0.01956, Acc: 99.36, Time: 3.5 s
[27/50] Loss: 0.01907, Acc: 99.36, Time: 3.5 s
[28/50] Loss: 0.01744, Acc: 99.40, Time: 3.5 s
[29/50] Loss: 0.01684, Acc: 99.43, Time: 3.7 s
[30/50] Loss: 0.01576, Acc: 99.49, Time: 4.0 s
[31/50] Loss: 0.01711, Acc: 99.39, Time: 4.1 s
[32/50] Loss: 0.01573, Acc: 99.46, Time: 3.5 s
[33/50] Loss: 0.01597, Acc: 99.44, Time: 3.5 s
[34/50] Loss: 0.01551, Acc: 99.45, Time: 4.4 s
[35/50] Loss: 0.01424, Acc: 99.54, Time: 3.7 s
[36/50] Loss: 0.01306, Acc: 99.56, Time: 3.5 s
[37/50] Loss: 0.01376, Acc: 99.51, Time: 3.4 s
[38/50] Loss: 0.01328, Acc: 99.52, Time: 4.0 s
[39/50] Loss: 0.01250, Acc: 99.54, Time: 4.5 s
[40/50] Loss: 0.01212, Acc: 99.60, Time: 3.5 s
[41/50] Loss: 0.01381, Acc: 99.50, Time: 4.2 s
[42/50] Loss: 0.01245, Acc: 99.59, Time: 4.0 s
[43/50] Loss: 0.01067, Acc: 99.61, Time: 3.8 s
[44/50] Loss: 0.01067, Acc: 99.66, Time: 3.6 s
[45/50] Loss: 0.00985, Acc: 99.66, Time: 3.3 s
[46/50] Loss: 0.01216, Acc: 99.58, Time: 3.5 s
[47/50] Loss: 0.00961, Acc: 99.66, Time: 3.8 s
[48/50] Loss: 0.01073, Acc: 99.64, Time: 3.8 s
[49/50] Loss: 0.01136, Acc: 99.56, Time: 3.6 s
[50/50] Loss: 0.00965, Acc: 99.65, Time: 3.7 s

#########################################################
In [6]:测试代码
# evaluate
lenet.eval()

testloss = 0.
testacc = 0.
for (img, label) in testloader:
    img = Variable(img).cuda()
    label = Variable(label).cuda()

    output = lenet(img)
    loss = criterian(output, label)
    testloss += loss.data[0]
    _, predict = torch.max(output, 1)
    num_correct = (predict == label).sum()
    testacc += num_correct.data[0]

testloss /= len(testset)
testacc /= len(testset)
print("Test: Loss: %.5f, Acc: %.2f %%" %(testloss, 100*testacc))
Test: Loss: 0.07444, Acc: 98.46 %