《动手学深度学习》第十六天---卷积神经网络(LeNet)

卷积层的优势:一方面,卷积层保留输入形状,使图像的像素在高和宽两个方向上的相关性均可能被有效识别;另一方面,卷积层通过滑动窗口将同一卷积核与不同位置的输入重复计算,从而避免参数尺寸过大。

(一)LeNet模型

LeNet分为卷积层和全连接层两部分。
卷积层块里的基本单位是卷积层后接最大池化层:卷积层用来识别图像里的空间模式,如线条和物体局部,之后的最大池化层则用来降低卷积层对位置的敏感性。
卷积层块的输出形状为(批量大小, 通道, 高, 宽)。当卷积层块的输出传入全连接层块时,全连接层块会将小批量中每个样本变平(flatten)。
在这里插入图片描述 LeNet架构由两个系列的CONV => TANH => POOL层集组成,后跟一个完全连接的层和softmax输出。

这个网络架构已经成为标准的“模板”:堆栈式卷积和池化层,以一个或多个全连接层作为网络的末端。

input:批量大小为1,一个通道,大小为28×28的灰度图像
Conv2D:第一层是一个卷积层,输出通道数为6,卷积核大小为(5,5)
MaxPoolD:第二层是最大池化层,池化层的大小为(2,2),步幅为(2,2)
Conv2D:第三层也是一个卷积层,输出通道数为16(这是因为输入尺寸变小了,所以需要增通道数来使得两个卷积层的参数尺寸类似),卷积核大小为(5,5)
MaxPoolD:第四层是最大池化层,池化层的大小为(2,2),步幅为(2,2)
Dense:全连接层把四维变成二维,第一维是批量大小,第二维是每个样本变平后的向量表示,具体转化过程可见多维输入输出 (三)1×1卷积层
采用的是激活函数是过去比较常用的sigmoid。

import d2lzh as d2l
import mxnet as mx
from mxnet import autograd, gluon, init, nd
from mxnet.gluon import loss as gloss, nn
import time

net = nn.Sequential()
net.add(nn.Conv2D(channels=6, kernel_size=5, activation='sigmoid'),
        nn.MaxPool2D(pool_size=2, strides=2),
        nn.Conv2D(channels=16, kernel_size=5, activation='sigmoid'),
        nn.MaxPool2D(pool_size=2, strides=2),
        # Dense会默认将(批量大小, 通道, 高, 宽)形状的输入转换成
        # (批量大小, 通道 * 高 * 宽)形状的输入
        nn.Dense(120, activation='sigmoid'),
        nn.Dense(84, activation='sigmoid'),
        nn.Dense(10))
        
X = nd.random.uniform(shape=(1, 1, 28, 28))
net.initialize()
for layer in net:   #  逐层前向计算,并且输出每一层的输出形状
    X = layer(X)
    print(layer.name, 'output shape:\t', X.shape)

在这里插入图片描述
我们可以分析验证一下模型是否是正确的:参考‘’填充和步幅‘’一节的计算方法
input:1×28×28
kernel_size=:6×1×5×5
conv1_output:6×(28-5+1)×(28-5+1)
pool1_output:6×((24-2+2)/2)×((24-2+2)/2)
kernel_size=:16×6×5×5
conv1_output:16×(12-5+1)×(12-5+1)
pool1_output:16×((8-2+2)/2)×((8-2+2)/2)
dense0:(1,120)
dense1:(1,84)
dense2:(1,10)
计算结果和输出结果一致。
可以看到,在卷积层块中输入的高和宽在逐层减小。卷积层由于使用高和宽均为5的卷积核,从而将高和宽分别减小4,而池化层则将高和宽减半,但通道数则从1增加到16。全连接层则逐层减少输出个数,直到变成图像的类别数10。

(二)获取数据和训练模型

因为卷积神经网络计算比多层感知机要复杂,建议使用GPU来加速计算。
首先在d2lzh中定义了函数try_gpu()来使用GPU加速。

def try_gpu():  # 本函数已保存在d2lzh包中方便以后使用
    try:
        ctx = mx.gpu()   #  尝试在gpu(0)中创建NDArray,成功就使用gpu(0)
        _ = nd.zeros((1,), ctx=ctx)
    except mx.base.MXNetError:
        ctx = mx.cpu()   #  不成功仍然使用cpu(0)
    return ctx

ctx = try_gpu()
ctx

然后定义评价准确率的函数evaluate_accuracy():
部分参数的意义可见softmax回归的从零开始

def evaluate_accuracy(data_iter, net, ctx): 
    acc_sum, n = nd.array([0], ctx=ctx), 0
    for X, y in data_iter:
        # 如果ctx代表GPU及相应的显存,则利用函数as_in_context()将数据复制到显存上
        X, y = X.as_in_context(ctx), y.as_in_context(ctx).astype('float32')
        acc_sum += (net(X).argmax(axis=1) == y).sum()  #  所有计算准确的数据
        n += y.size   #  参与运算的总数据
    return acc_sum.asscalar() / n  #  算出比例

定义函数train_ch5(),但是本次实现用的是简洁实现,利用了Gluon。

  def train_ch5(net, train_iter, test_iter, batch_size, trainer, ctx,num_epochs):
    print('training on', ctx)   #  在ctx上进行训练
    loss = gloss.SoftmaxCrossEntropyLoss()  
    #利用Gluon提供的包括softmax运算和交叉熵损失计算的函数,它的数值稳定性更好。  
    for epoch in range(num_epochs):  #  在每一次运算周期内实现下列操作:
        train_l_sum, train_acc_sum, n, start = 0.0, 0.0, 0, time.time()  #  初始化
        for X, y in train_iter:
            X, y = X.as_in_context(ctx), y.as_in_context(ctx)  #  利用Gpu运算,将数据复制到显卡中
            with autograd.record():
                y_hat = net(X)  #  利用net对X进行softmax回归运算得到y_hat
                l = loss(y_hat, y).sum()   #  计算损失函数,求和成标量  
            l.backward()  #  对损失求梯度,以训练参数
            trainer.step(batch_size)  
            #  step()  进行一步参数更新。应该在autograd.backward()之后调用,并且在record()范围之外。与sgd功能类似。
            y = y.astype('float32')
            train_l_sum += l.asscalar()  #  计算损失总数
            train_acc_sum += (y_hat.argmax(axis=1) == y).sum().asscalar()  # 计算训练中准确的数据
            n += y.size  #总数
        test_acc = evaluate_accuracy(test_iter, net, ctx)  #测试
        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))

lr, num_epochs = 0.9, 5  #  手动设定学习率和学习周期
net.initialize(force_reinit=True, ctx=ctx, init=init.Xavier())
#Xavier()返回一个初始化器,执行权重的“xavier”初始化。这个初始值设定项被设计成在所有层中保持梯度的比例大致相同。
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': lr})
train_ch5(net, train_iter, test_iter, batch_size, trainer, ctx, num_epochs)

对比一下CPU学习和GPU学习的速度:
CPU:
在这里插入图片描述

GPU:
在这里插入图片描述
GPU确实提速很多的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值