# 5.5 卷積神經網絡(LeNet)
在3.9節(多層感知機的從零開始實現)里我們構造了一個含單隱藏層的多層感知機模型來對Fashion-MNIST數據集中的圖像進行分類。每張圖像高和寬均是28像素。我們將圖像中的像素逐行展開,得到長度為784的向量,并輸入進全連接層中。然而,這種分類方法有一定的局限性。
1. 圖像在同一列鄰近的像素在這個向量中可能相距較遠。它們構成的模式可能難以被模型識別。
2. 對于大尺寸的輸入圖像,使用全連接層容易造成模型過大。假設輸入是高和寬均為1000像素的彩色照片(含3個通道)。即使全連接層輸出個數仍是256,該層權重參數的形狀是`$ 3,000,000\times 256 $`:它占用了大約3 GB的內存或顯存。這帶來過復雜的模型和過高的存儲開銷。
卷積層嘗試解決這兩個問題。一方面,卷積層保留輸入形狀,使圖像的像素在高和寬兩個方向上的相關性均可能被有效識別;另一方面,卷積層通過滑動窗口將同一卷積核與不同位置的輸入重復計算,從而避免參數尺寸過大。
卷積神經網絡就是含卷積層的網絡。本節里我們將介紹一個早期用來識別手寫數字圖像的卷積神經網絡:LeNet [1]。這個名字來源于LeNet論文的第一作者Yann LeCun。LeNet展示了通過梯度下降訓練卷積神經網絡可以達到手寫數字識別在當時最先進的結果。這個奠基性的工作第一次將卷積神經網絡推上舞臺,為世人所知。LeNet的網絡結構如下圖所示。
<div align=center>
<img width="600" src="images/5.5_lenet.png"/>
</div>
<div align=center>LeNet網絡結構</div>
## 5.5.1 LeNet模型
LeNet分為卷積層塊和全連接層塊兩個部分。下面我們分別介紹這兩個模塊。
卷積層塊里的基本單位是卷積層后接最大池化層:卷積層用來識別圖像里的空間模式,如線條和物體局部,之后的最大池化層則用來降低卷積層對位置的敏感性。卷積層塊由兩個這樣的基本單位重復堆疊構成。在卷積層塊中,每個卷積層都使用`$ 5\times 5 $`的窗口,并在輸出上使用sigmoid激活函數。第一個卷積層輸出通道數為6,第二個卷積層輸出通道數則增加到16。這是因為第二個卷積層比第一個卷積層的輸入的高和寬要小,所以增加輸出通道使兩個卷積層的參數尺寸類似。卷積層塊的兩個最大池化層的窗口形狀均為`$ 2\times 2 $`,且步幅為2。由于池化窗口與步幅形狀相同,池化窗口在輸入上每次滑動所覆蓋的區域互不重疊。
卷積層塊的輸出形狀為(批量大小, 通道, 高, 寬)。當卷積層塊的輸出傳入全連接層塊時,全連接層塊會將小批量中每個樣本變平(flatten)。也就是說,全連接層的輸入形狀將變成二維,其中第一維是小批量中的樣本,第二維是每個樣本變平后的向量表示,且向量長度為通道、高和寬的乘積。全連接層塊含3個全連接層。它們的輸出個數分別是120、84和10,其中10為輸出的類別個數。
下面我們通過`Sequential`類來實現LeNet模型。
``` python
import time
import torch
from torch import nn, optim
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
nn.Sigmoid(),
nn.MaxPool2d(2, 2), # kernel_size, stride
nn.Conv2d(6, 16, 5),
nn.Sigmoid(),
nn.MaxPool2d(2, 2)
)
self.fc = nn.Sequential(
nn.Linear(16*4*4, 120),
nn.Sigmoid(),
nn.Linear(120, 84),
nn.Sigmoid(),
nn.Linear(84, 10)
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1))
return output
```
接下來查看每個層的形狀。
```python
net = LeNet()
print(net)
```
輸出:
```
LeNet(
(conv): Sequential(
(0): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
(1): Sigmoid()
(2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(3): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
(4): Sigmoid()
(5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(fc): Sequential(
(0): Linear(in_features=256, out_features=120, bias=True)
(1): Sigmoid()
(2): Linear(in_features=120, out_features=84, bias=True)
(3): Sigmoid()
(4): Linear(in_features=84, out_features=10, bias=True)
)
)
```
可以看到,在卷積層塊中輸入的高和寬在逐層減小。卷積層由于使用高和寬均為5的卷積核,從而將高和寬分別減小4,而池化層則將高和寬減半,但通道數則從1增加到16。全連接層則逐層減少輸出個數,直到變成圖像的類別數10。
## 5.5.2 獲取數據和訓練模型
下面我們來實驗LeNet模型。實驗中,我們仍然使用Fashion-MNIST作為訓練數據集。
``` python
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
```
因為卷積神經網絡計算比多層感知機要復雜,建議使用GPU來加速計算。因此,我們對3.6節(softmax回歸的從零開始實現)中描述的`evaluate_accuracy`函數略作修改,使其支持GPU計算。
``` python
# 本函數已保存在d2lzh_pytorch包中方便以后使用。該函數將被逐步改進。
def evaluate_accuracy(data_iter, net,
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')):
acc_sum, n = 0.0, 0
with torch.no_grad():
for X, y in data_iter:
if isinstance(net, torch.nn.Module):
net.eval() # 評估模式, 這會關閉dropout
acc_sum += (net(X.to(device)).argmax(dim=1) == y.to(device)).float().sum().cpu().item()
net.train() # 改回訓練模式
else: # 自定義的模型, 3.13節之后不會用到, 不考慮GPU
if('is_training' in net.__code__.co_varnames): # 如果有is_training這個參數
# 將is_training設置成False
acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item()
else:
acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
return acc_sum / n
```
我們同樣對3.6節中定義的`train_ch3`函數略作修改,確保計算使用的數據和模型同在內存或顯存上。
``` python
# 本函數已保存在d2lzh_pytorch包中方便以后使用
def train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs):
net = net.to(device)
print("training on ", device)
loss = torch.nn.CrossEntropyLoss()
batch_count = 0
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 = X.to(device)
y = y.to(device)
y_hat = net(X)
l = loss(y_hat, y)
optimizer.zero_grad()
l.backward()
optimizer.step()
train_l_sum += l.cpu().item()
train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()
n += y.shape[0]
batch_count += 1
test_acc = evaluate_accuracy(test_iter, net)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, time %.1f sec'
% (epoch + 1, train_l_sum / batch_count, train_acc_sum / n, test_acc, time.time() - start))
```
學習率采用0.001,訓練算法使用Adam算法,損失函數使用交叉熵損失函數。
``` python
lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)
```
輸出:
```
training on cuda
epoch 1, loss 0.0072, train acc 0.322, test acc 0.584, time 3.7 sec
epoch 2, loss 0.0037, train acc 0.649, test acc 0.699, time 1.8 sec
epoch 3, loss 0.0030, train acc 0.718, test acc 0.724, time 1.7 sec
epoch 4, loss 0.0027, train acc 0.741, test acc 0.746, time 1.6 sec
epoch 5, loss 0.0024, train acc 0.759, test acc 0.759, time 1.7 sec
```
> 注: 本節代碼在GPU和CPU上都已測試過。
## 小結
* 卷積神經網絡就是含卷積層的網絡。
* LeNet交替使用卷積層和最大池化層后接全連接層來進行圖像分類。
## 參考文獻
[1] LeCun, Y., Bottou, L., Bengio, Y., & Haffner, P. (1998). Gradient-based learning applied to document recognition. Proceedings of the IEEE, 86(11), 2278-2324.
-----------
> 注:除代碼外本節與原書此節基本相同,[原書傳送門](https://zh.d2l.ai/chapter_convolutional-neural-networks/lenet.html)
- Home
- Introduce
- 1.深度學習簡介
- 深度學習簡介
- 2.預備知識
- 2.1環境配置
- 2.2數據操作
- 2.3自動求梯度
- 3.深度學習基礎
- 3.1 線性回歸
- 3.2 線性回歸的從零開始實現
- 3.3 線性回歸的簡潔實現
- 3.4 softmax回歸
- 3.5 圖像分類數據集(Fashion-MINST)
- 3.6 softmax回歸的從零開始實現
- 3.7 softmax回歸的簡潔實現
- 3.8 多層感知機
- 3.9 多層感知機的從零開始實現
- 3.10 多層感知機的簡潔實現
- 3.11 模型選擇、反向傳播和計算圖
- 3.12 權重衰減
- 3.13 丟棄法
- 3.14 正向傳播、反向傳播和計算圖
- 3.15 數值穩定性和模型初始化
- 3.16 實戰kaggle比賽:房價預測
- 4 深度學習計算
- 4.1 模型構造
- 4.2 模型參數的訪問、初始化和共享
- 4.3 模型參數的延后初始化
- 4.4 自定義層
- 4.5 讀取和存儲
- 4.6 GPU計算
- 5 卷積神經網絡
- 5.1 二維卷積層
- 5.2 填充和步幅
- 5.3 多輸入通道和多輸出通道
- 5.4 池化層
- 5.5 卷積神經網絡(LeNet)
- 5.6 深度卷積神經網絡(AlexNet)
- 5.7 使用重復元素的網絡(VGG)
- 5.8 網絡中的網絡(NiN)
- 5.9 含并行連結的網絡(GoogLeNet)
- 5.10 批量歸一化
- 5.11 殘差網絡(ResNet)
- 5.12 稠密連接網絡(DenseNet)
- 6 循環神經網絡
- 6.1 語言模型
- 6.2 循環神經網絡
- 6.3 語言模型數據集(周杰倫專輯歌詞)
- 6.4 循環神經網絡的從零開始實現
- 6.5 循環神經網絡的簡單實現
- 6.6 通過時間反向傳播
- 6.7 門控循環單元(GRU)
- 6.8 長短期記憶(LSTM)
- 6.9 深度循環神經網絡
- 6.10 雙向循環神經網絡
- 7 優化算法
- 7.1 優化與深度學習
- 7.2 梯度下降和隨機梯度下降
- 7.3 小批量隨機梯度下降
- 7.4 動量法
- 7.5 AdaGrad算法
- 7.6 RMSProp算法
- 7.7 AdaDelta
- 7.8 Adam算法
- 8 計算性能
- 8.1 命令式和符號式混合編程
- 8.2 異步計算
- 8.3 自動并行計算
- 8.4 多GPU計算
- 9 計算機視覺
- 9.1 圖像增廣
- 9.2 微調
- 9.3 目標檢測和邊界框
- 9.4 錨框
- 10 自然語言處理
- 10.1 詞嵌入(word2vec)
- 10.2 近似訓練
- 10.3 word2vec實現
- 10.4 子詞嵌入(fastText)
- 10.5 全局向量的詞嵌入(Glove)
- 10.6 求近義詞和類比詞
- 10.7 文本情感分類:使用循環神經網絡
- 10.8 文本情感分類:使用卷積網絡
- 10.9 編碼器--解碼器(seq2seq)
- 10.10 束搜索
- 10.11 注意力機制
- 10.12 機器翻譯