《动手学——LeNet》笔记

Convolutional Neural Networks

ps 之前图像分类识别的方法:构造一个单隐藏层的多层感知机模型,将28x28的图像展开,形成一个长为724的一维向量,并输入到全连接层。

使用全连接层的局限性:

  • 图像在同一列邻近的像素在这个向量中可能相距较远。它们构成的模式可能难以被模型识别。
  • 对于大尺寸的输入图像,使用全连接层容易导致模型过大。

使用卷积层的优势:

  • 卷积层保留输入形状。
  • 卷积层通过滑动窗口将同一卷积核与不同位置的输入重复计算,从而避免参数尺寸过大。

LeNet 模型

LeNet分为卷积层块和全连接层块两个部分。下面我们分别介绍这两个模块。

ps 输入为一通道,长和宽为28像素的图像

Image Name

卷积层块里的基本单位是卷积层后接平均池化层:卷积层用来识别图像里的空间模式,如线条和物体局部,之后的平均池化层则用来降低卷积层对位置的敏感性。

卷积层块由两个这样的基本单位重复堆叠构成。在卷积层块中,每个卷积层都使用 5 × 5 5 \times 5 5×5的窗口,并在输出上使用sigmoid激活函数。第一个卷积层输出通道数为6,第二个卷积层输出通道数则增加到16。

全连接层块含3个全连接层。它们的输出个数分别是120、84和10,其中10为输出的类别个数。

下面我们通过Sequential类来实现LeNet模型。

#import
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
import torch
import torch.nn as nn
import torch.optim as optim
import time
#net
class Flatten(torch.nn.Module):  #展平操作
    def forward(self, x):
        return x.view(x.shape[0], -1)

class Reshape(torch.nn.Module): #将图像大小重定型
    def forward(self, x):
        return x.view(-1,1,28,28)      #(B批量 x C通道数 x H x W)  #-1表示由系统定
    
net = torch.nn.Sequential(     #Lelet                                   #(nh-kh+ph+sh)/sh下取整                                           
    Reshape(),
    nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2), #b*1*28*28  =>b*6*28*28
    nn.Sigmoid(),                                                       
    nn.AvgPool2d(kernel_size=2, stride=2),                              #b*6*28*28  =>b*6*14*14
    nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5),           #b*6*14*14  =>b*16*10*10
    nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),                              #b*16*10*10  => b*16*5*5
    Flatten(),                                                          #b*16*5*5   => b*400
    nn.Linear(in_features=16*5*5, out_features=120),  #全连接层
    nn.Sigmoid(),
    nn.Linear(120, 84),
    nn.Sigmoid(),
    nn.Linear(84, 10)
)

接下来我们构造一个高和宽均为28的单通道数据样本,并逐层进行前向计算来查看每个层的输出形状。

#print
X = torch.randn(size=(1,1,28,28), dtype = torch.float32)
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__,'output shape: \t',X.shape)

Reshape output shape: torch.Size([1, 1, 28, 28])
Conv2d output shape: torch.Size([1, 6, 28, 28])
Sigmoid output shape: torch.Size([1, 6, 28, 28])
AvgPool2d output shape: torch.Size([1, 6, 14, 14])
Conv2d output shape: torch.Size([1, 16, 10, 10])
Sigmoid output shape: torch.Size([1, 16, 10, 10])
AvgPool2d output shape: torch.Size([1, 16, 5, 5])
Flatten output shape: torch.Size([1, 400])
Linear output shape: torch.Size([1, 120])
Sigmoid output shape: torch.Size([1, 120])
Linear output shape: torch.Size([1, 84])
Sigmoid output shape: torch.Size([1, 84])
Linear output shape: torch.Size([1, 10])

可以看到,在卷积层块中输入的高和宽在逐层减小。卷积层由于使用高和宽均为5的卷积核,从而将高和宽分别减小4,而池化层则将高和宽减半,但通道数则从1增加到16。全连接层则逐层减少输出个数,直到变成图像的类别数10。

Image Name

获取数据和训练模型

下面我们来实现LeNet模型。我们仍然使用Fashion-MNIST作为训练数据集。

# 数据
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(
    batch_size=batch_size, root='/home/kesci/input/FashionMNIST2065')
print(len(train_iter))  #训练集235个批次,每个批次batch_size张图片

因为卷积神经网络计算比多层感知机要复杂,建议使用GPU来加速计算。我们查看看是否可以用GPU,如果成功则使用cuda:0,否则仍然使用cpu

# This function has been saved in the d2l package for future use
#use GPU
def try_gpu():
    """If GPU is available, return torch.device as cuda:0; else return torch.device as cpu."""
    if torch.cuda.is_available():
        device = torch.device('cuda:0')
    else:
        device = torch.device('cpu')
    return device

device = try_gpu()
device

我们实现evaluate_accuracy函数,该函数用于计算模型net在数据集data_iter上的准确率。

#计算准确率
'''
(1). net.train()
  启用 BatchNormalization 和 Dropout,将BatchNormalization和Dropout置为True
(2). net.eval()
不启用 BatchNormalization 和 Dropout,将BatchNormalization和Dropout置为False
'''

def evaluate_accuracy(data_iter, net,device=torch.device('cpu')):  #data_iter测试集

"""Evaluate accuracy of a model on the given data set."""
acc_sum,n = torch.tensor([0],dtype=torch.float32,device=device),0   #acc_sum模型预测正确的总数目,n预测的总数目
for X,y in data_iter:   #将x,y一个批次一个批次地从测试集取出(下面的X都是一个批次)
    # If device is the GPU, copy the data to the GPU.
    X,y = X.to(device),y.to(device)
    net.eval()  #表示该网络正在进行预测,与net.train()意义相反
    with torch.no_grad():  #表示该区域数据不需要计算梯度也不会进行反向传播
        y = y.long()
        acc_sum += torch.sum((torch.argmax(net(X), dim=1) == y))  #[[0.2 ,0.4 ,0.5 ,0.6 ,0.8] ,[ 0.1,0.2 ,0.4 ,0.3 ,0.1]] => [ 4 , 2 ]
        #argmax返回特定维度上最大值对应的索引
        n += y.shape[0]
return acc_sum.item()/n

我们定义函数train_ch5,用于训练模型。

#训练函数
def train_ch5(net, train_iter, test_iter,criterion, num_epochs, batch_size, device,lr=None):
    """Train and evaluate a model with CPU or GPU."""
    print('training on', device)
    net.to(device)
    optimizer = optim.SGD(net.parameters(), lr=lr)
    for epoch in range(num_epochs):
        train_l_sum = torch.tensor([0.0],dtype=torch.float32,device=device)
        train_acc_sum = torch.tensor([0.0],dtype=torch.float32,device=device)
        n, start = 0, time.time()
        for X, y in train_iter:  #按批次训练,遍历取出每批次数据的训练数据和训练标签
            net.train()
            
            optimizer.zero_grad()  #梯度清零
            X,y = X.to(device),y.to(device) 
            y_hat = net(X)
            loss = criterion(y_hat, y)
            loss.backward()
            optimizer.step()
            
            with torch.no_grad():
                y = y.long()
                train_l_sum += loss.float()
                train_acc_sum += (torch.sum((torch.argmax(y_hat, dim=1) == y))).float()  #计算训练集预测正确的总数
                n += y.shape[0]
        test_acc = evaluate_accuracy(test_iter, net,device)
        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, '
              'time %.1f sec'
              % (epoch + 1, train_l_sum/n, train_acc_sum/n, test_acc,
                 time.time() - start))

我们重新将模型参数初始化到对应的设备device(cpu or cuda:0)之上,并使用Xavier随机初始化。损失函数和训练算法则依然使用交叉熵损失函数和小批量随机梯度下降。

# 训练
lr, num_epochs = 0.9, 10   #epochs指的就是针对所有样本的一次迭代,即训练过程中数据将被“轮”多少次

def init_weights(m):
    if type(m) == nn.Linear or type(m) == nn.Conv2d:  #初始化全连接层和卷积层
        torch.nn.init.xavier_uniform_(m.weight)

net.apply(init_weights)
net = net.to(device)

criterion = nn.CrossEntropyLoss()   #交叉熵描述了两个概率分布之间的距离,交叉熵越小说明两者之间越接近
train_ch5(net, train_iter, test_iter, criterion,num_epochs, batch_size,device, lr)

training on cpu
epoch 1, loss 0.0091, train acc 0.102, test acc 0.100, time 21.5 sec
epoch 2, loss 0.0089, train acc 0.128, test acc 0.357, time 21.2 sec
epoch 3, loss 0.0043, train acc 0.556, test acc 0.651, time 21.5 sec
epoch 4, loss 0.0032, train acc 0.684, test acc 0.725, time 21.3 sec
epoch 5, loss 0.0027, train acc 0.732, test acc 0.732, time 21.2 sec
epoch 6, loss 0.0024, train acc 0.761, test acc 0.774, time 21.2 sec
epoch 7, loss 0.0022, train acc 0.782, test acc 0.785, time 21.4 sec
epoch 8, loss 0.0020, train acc 0.800, test acc 0.762, time 21.4 sec
epoch 9, loss 0.0019, train acc 0.814, test acc 0.794, time 21.3 sec
epoch 10, loss 0.0018, train acc 0.824, test acc 0.800, time 21.4 sec

# test
for testdata,testlabe in test_iter:  #从测试集取出一个批次的data和label
    testdata,testlabe = testdata.to(device),testlabe.to(device)
    break
print(testdata.shape,testlabe.shape)
net.eval()
y_pre = net(testdata)
print(torch.argmax(y_pre,dim=1)[:10])
print(testlabe[:10])

torch.Size([256, 1, 28, 28]) torch.Size([256])
tensor([9, 2, 1, 1, 6, 1, 2, 6, 5, 7])
tensor([9, 2, 1, 1, 6, 1, 4, 6, 5, 7])

总结:

卷积神经网络就是含卷积层的网络。
LeNet交替使用卷积层和最大池化层后接全连接层来进行图像分类。

习题

  1. 关于LeNet,以下说法中错误的是:b

    LeNet主要分为两个部分:卷积层块和全连接层块

    LeNet的绝大多数参数集中在卷积层块部分

    LeNet在连接卷积层块和全连接层块时,需要做一次展平操作

    LeNet的卷积层块交替使用卷积层和池化层。

    答案解释

    选项2:错误,LeNet模型中,90%以上的参数集中在全连接层块

  2. 关于卷积神经网络,以下说法中错误的是:a

    因为全连接层的参数数量比卷积层多,所以全连接层可以更好地提取空间信息

    使用形状为2 \times 22×2,步幅为2的池化层,会将高和宽都减半

    卷积神经网络通过使用滑动窗口在输入的不同位置处重复计算,减小参数数量

    在通过卷积层或池化层后,输出的高和宽可能减小,为了尽可能保留输入的特征,我们可以在减小高宽的同时增加通道数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值