23 經(jīng)典卷積神經(jīng)網(wǎng)絡(luò) LeNet【動(dòng)手學(xué)深度學(xué)習(xí)v2】

MNIST:經(jīng)典手寫數(shù)字?jǐn)?shù)據(jù)集

LeNet結(jié)構(gòu):
32*32圖片——6通道5*5卷積層——2*2池化層——16通道5*5卷積層——2*2池化層——全連接層*2——輸出


代碼實(shí)現(xiàn)
通過(guò)下面的LeNet代碼,可以看出用深度學(xué)習(xí)框架實(shí)現(xiàn)此類模型非常簡(jiǎn)單。我們只需要實(shí)例化一個(gè)Sequential
塊并將需要的層連接在一起。
此處激活函數(shù)使用的sigmoid
import torch from torch import nn from d2l import torch as d2l net = nn.Sequential( nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(), #用Flatten函數(shù)將矩陣數(shù)據(jù)拉直 nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(), nn.Linear(120, 84), nn.Sigmoid(), nn.Linear(84, 10))
我們對(duì)原始模型做了一點(diǎn)小改動(dòng),去掉了最后一層的高斯激活。除此之外,這個(gè)網(wǎng)絡(luò)與最初的LeNet-5一致。
下面,我們將一個(gè)大小為28×28的單通道(黑白)圖像通過(guò)LeNet。通過(guò)在每一層打印輸出的形狀,我們可以檢查模型,以確保其操作與我們期望的?圖6.6.2一致。

X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32) for layer in net: X = layer(X) print(layer.__class__.__name__,'output shape: \t',X.shape)
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])
請(qǐng)注意,在整個(gè)卷積塊中,與上一層相比,每一層特征的高度和寬度都減小了。 第一個(gè)卷積層使用2個(gè)像素的填充,來(lái)補(bǔ)償5×5
卷積核導(dǎo)致的特征減少。 相反,第二個(gè)卷積層沒(méi)有填充,因此高度和寬度都減少了4個(gè)像素。 隨著層疊的上升,通道的數(shù)量從輸入時(shí)的1個(gè),增加到第一個(gè)卷積層之后的6個(gè),再到第二個(gè)卷積層之后的16個(gè)。 同時(shí),每個(gè)匯聚層的高度和寬度都減半。最后,每個(gè)全連接層減少維數(shù),最終輸出一個(gè)維數(shù)與結(jié)果分類數(shù)相匹配的輸出。
現(xiàn)在我們已經(jīng)實(shí)現(xiàn)了LeNet,讓我們看看LeNet在Fashion-MNIST數(shù)據(jù)集上的表現(xiàn)。
batch_size = 256 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
雖然卷積神經(jīng)網(wǎng)絡(luò)的參數(shù)較少,但與深度的多層感知機(jī)相比,它們的計(jì)算成本仍然很高,因?yàn)槊總€(gè)參數(shù)都參與更多的乘法。 通過(guò)使用GPU,可以用它加快訓(xùn)練。
為了進(jìn)行評(píng)估,我們需要對(duì)?3.6節(jié)中描述的evaluate_accuracy
函數(shù)進(jìn)行輕微的修改。 由于完整的數(shù)據(jù)集位于內(nèi)存中,因此在模型使用GPU計(jì)算數(shù)據(jù)集之前,我們需要將其復(fù)制到顯存中。
def evaluate_accuracy_gpu(net, data_iter, device=None): #@save """使用GPU計(jì)算模型在數(shù)據(jù)集上的精度""" if isinstance(net, nn.Module): net.eval() # 設(shè)置為評(píng)估模式 if not device: device = next(iter(net.parameters())).device # 正確預(yù)測(cè)的數(shù)量,總預(yù)測(cè)的數(shù)量 metric = d2l.Accumulator(2) with torch.no_grad(): for X, y in data_iter: if isinstance(X, list): # BERT微調(diào)所需的(之后將介紹) X = [x.to(device) for x in X] else: X = X.to(device) y = y.to(device) metric.add(d2l.accuracy(net(X), y), y.numel()) return metric[0] / metric[1]
為了使用GPU,我們還需要一點(diǎn)小改動(dòng)。 與?3.6節(jié)中定義的train_epoch_ch3
不同,在進(jìn)行正向和反向傳播之前,我們需要將每一小批量數(shù)據(jù)移動(dòng)到我們指定的設(shè)備(例如GPU)上。
如下所示,訓(xùn)練函數(shù)train_ch6
也類似于?3.6節(jié)中定義的train_ch3
。 由于我們將實(shí)現(xiàn)多層神經(jīng)網(wǎng)絡(luò),因此我們將主要使用高級(jí)API。 以下訓(xùn)練函數(shù)假定從高級(jí)API創(chuàng)建的模型作為輸入,并進(jìn)行相應(yīng)的優(yōu)化。 我們使用在?4.8.2.2節(jié)中介紹的Xavier隨機(jī)初始化模型參數(shù)。 與全連接層一樣,我們使用交叉熵?fù)p失函數(shù)和小批量隨機(jī)梯度下降。
#@save def train_ch6(net, train_iter, test_iter, num_epochs, lr, device): """用GPU訓(xùn)練模型(在第六章定義)""" def init_weights(m): if type(m) == nn.Linear or type(m) == nn.Conv2d: nn.init.xavier_uniform_(m.weight) net.apply(init_weights) print('training on', device) net.to(device) optimizer = torch.optim.SGD(net.parameters(), lr=lr) loss = nn.CrossEntropyLoss() animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs], legend=['train loss', 'train acc', 'test acc']) timer, num_batches = d2l.Timer(), len(train_iter) for epoch in range(num_epochs): # 訓(xùn)練損失之和,訓(xùn)練準(zhǔn)確率之和,樣本數(shù) metric = d2l.Accumulator(3) net.train() for i, (X, y) in enumerate(train_iter): timer.start() optimizer.zero_grad() X, y = X.to(device), y.to(device) y_hat = net(X) l = loss(y_hat, y) l.backward() optimizer.step() with torch.no_grad(): metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0]) timer.stop() train_l = metric[0] / metric[2] train_acc = metric[1] / metric[2] if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1: animator.add(epoch + (i + 1) / num_batches, (train_l, train_acc, None)) test_acc = evaluate_accuracy_gpu(net, test_iter) animator.add(epoch + 1, (None, None, test_acc)) print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, ' f'test acc {test_acc:.3f}') print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec ' f'on {str(device)}')
現(xiàn)在,我們訓(xùn)練和評(píng)估LeNet-5模型。
lr, num_epochs = 0.9, 10 train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
loss 0.467, train acc 0.825, test acc 0.821 88556.9 examples/sec on cuda:0
知識(shí)補(bǔ)充:
卷積法也可應(yīng)用于時(shí)序性數(shù)據(jù)(如文本)
數(shù)據(jù)集的好壞很大程度上決定了神經(jīng)網(wǎng)絡(luò)的準(zhǔn)確率
將此講網(wǎng)絡(luò)中的激活函數(shù)換成ReLU后,需要將學(xué)習(xí)率也調(diào)低,0.9的學(xué)習(xí)率對(duì)于ReLU來(lái)說(shuō)太大了,訓(xùn)練難以收斂