本文基於python-pytorch框架,編寫CNN網路,並採用CNN手寫數字資料集訓練、測試網路。
網路的構建
以LeNet-5 網路為例
類定義
首先先了解一下網路的最基本框架
- 一般而言,首先建立一個類
class
,建立時,繼承nn.Module
父類,注意,在該類的建構函式中__init__
中,顯示的呼叫其父類的建構函式super(...).__init__()
- 網路的結構,例如卷積層、線性層等,一般在其建構函式中定義。
- 對於一些不帶引數的網路結構,也可以在forward方法中直接呼叫,而不定義,但不推薦。
- 每一個網路類必須顯示的定義
forward
方法,編寫程式時需要在該函式中編寫運算,實現對輸入張量(tensor)的運算,並最後給予返回值;通常forward
函式的返回值也為張量(tensor)。
import torch.nn as nn
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
''' Definition of Network Structure '''
def forward(self, x):
# x = f(x)
return x
卷積模組/特徵提取器
self.features = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5, stride=1, padding=2), # 28x28x1 -> 28x28x6
nn.Tanh(),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5, stride=1), # 14x14x6 -> 10x10x16
nn.Tanh(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
卷積層
nn.Conv2d
是 PyTorch 中用於定義二維卷積層的一個模組。其引數配置的含義如下:
-
in_channels (1): 輸入影像的通道數。對於單通道的灰度影像,值為 1;對於 RGB 影像,則為 3。
-
out_channels (6): 卷積層輸出的通道數,即卷積核的數量。每個卷積核會產生一個輸出通道。這裡設定為 6,意味著該卷積層會生成 6 個特徵圖(feature maps)。
-
kernel_size (5): 卷積核的尺寸。這裡的
5
表示使用 5x5 的卷積核。這是一個正方形的卷積核大小,但也可以設定成不相等的高度和寬度,比如(5, 3)
。 -
stride (1): 卷積操作的步幅。步幅決定了卷積核在輸入影像上滑動的速度。步幅為 1 表示卷積核每次滑動一個畫素。
-
padding (2): 輸入影像的邊界填充。填充用於控制輸出特徵圖的空間尺寸,通常用於保持特徵圖的尺寸不變或減少尺寸。這裡的
2
表示在每一邊填充 2 個畫素。
綜上,nn.Conv2d(1, 6, kernel_size=5, stride=1, padding=2)
的設定表示從單通道的輸入影像中提取 6 個特徵圖,每個特徵圖透過一個 5x5 大小的卷積核生成,卷積操作的步幅為 1,並且在輸入影像的每一邊填充 2 個畫素以控制輸出尺寸。
啟用函式
nn.Tanh()
是 PyTorch 中的一個啟用函式層,它實現了雙曲正切函式(tanh)。啟用函式在神經網路中用於引入非線性特性,從而使網路能夠學習更復雜的模式和特徵。
雙曲正切函式的數學形式是:
該函式的輸出範圍是 ([-1, 1]),具有以下特點:
-
非線性:tanh 是一個非線性函式,使得神經網路能夠處理更復雜的任務。
-
輸出範圍:輸出值範圍在 -1 到 1 之間,零中心化,使得均值接近零,這可以幫助加快訓練過程和提高模型的收斂速度。
-
梯度:tanh 函式的導數在輸入接近 0 時最大(為 1),而在輸入較大或較小時梯度逐漸變小,這意味著它在大輸入值時可能會遇到梯度消失的問題。
在 PyTorch 中,nn.Tanh()
可以作為模型的一個層來應用於網路的前向傳播中:
nn.MaxPool2d
是 PyTorch 中的一個池化層,用於對二維輸入資料進行最大池化操作。池化操作常用於卷積神經網路(CNN)中,以減少特徵圖的空間尺寸,減少計算量和過擬合的風險,並提高模型的魯棒性。
池化層
nn.MaxPool2d(kernel_size=2, stride=2)
的引數含義如下:
-
kernel_size (2): 池化視窗的尺寸。這裡的
2
表示池化視窗是 2x2 的正方形區域。池化操作將在每個 2x2 的區域內選取最大值。 -
stride (2): 池化視窗在輸入特徵圖上滑動的步幅。步幅為 2 表示池化視窗每次滑動 2 個畫素。這意味著池化操作會將特徵圖的空間尺寸縮小為原來的一半。
具體功能:
- 最大池化:在池化視窗(2x2 區域)內選擇最大值,並用該最大值替代整個視窗區域中的所有值。這樣,池化層能夠保留特徵圖中的重要資訊,同時減少空間尺寸。
作用:
-
降維:透過池化操作減少特徵圖的空間尺寸(寬度和高度),從而減少計算量和記憶體消耗。
-
提高魯棒性:池化操作可以使網路對位置的微小變化更具魯棒性,因為它只保留區域性區域的最大值。
-
防止過擬合:透過減少特徵圖的尺寸,可以降低模型的複雜性,從而減少過擬合的風險。
解釋:
對於一個 4x4 的輸入特徵圖,使用 2x2 的池化視窗和步幅為 2,池化操作會將特徵圖縮小為 2x2 的尺寸,每個池化視窗選擇區域內的最大值。例如,在 2x2 的池化視窗內,[[1, 2], [5, 6]]
會變成 6
,依此類推。
總之,nn.MaxPool2d
是卷積神經網路中常用的池化層,用於減少特徵圖的尺寸和計算複雜度,同時保留重要的特徵資訊。
線性層/分類器
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(16 * 5 * 5, 120), # 全連線層1
nn.Tanh(),
nn.Linear(120, 84), # 全連線層2
nn.Tanh(),
nn.Linear(84, 10) # 輸出層
)
展平函式
nn.Flatten()
是 PyTorch 中用於將多維張量展平成一維張量的模組。這個操作通常在卷積層(Convolutional Layers)和線性層(Linear Layers)之間使用,以便將卷積層輸出的多維特徵圖轉換成適合於線性層處理的一維特徵向量。
nn.Flatten()
的主要作用是將輸入張量從多維轉換為一維。例如,對於形狀為 (N, C, H, W)
的輸入張量,使用 nn.Flatten()
後,輸出的張量將變為形狀為 (N, C * H * W)
的一維張量。
為什麼在卷積層和線性層之間使用 nn.Flatten()
-
卷積層的輸出是多維的:卷積層生成的輸出通常是一個四維張量,表示批次的特徵圖,其中包含多個通道的二維特徵圖。為了將這些特徵圖傳遞到線性層,必須將其展平成一維張量,因為線性層要求輸入為一維特徵向量。
-
線性層的輸入是一維的:線性層(也稱全連線層)只能處理一維的輸入資料。透過
nn.Flatten()
,可以將卷積層的多維輸出展平為一維,從而可以將其作為線性層的輸入。 -
連線卷積和線性層:卷積層通常用於提取特徵,而線性層則用於對這些特徵進行分類或迴歸等任務。在這些任務中,線性層處理的是扁平化的特徵向量,因此需要將卷積層的輸出展平。
總結
nn.Flatten()
在卷積神經網路(CNN)的前向傳播中充當了重要的角色,它將卷積層的多維特徵圖展平為線性層所需的一維特徵向量。這使得卷積層提取的複雜特徵可以被線性層進一步處理,從而完成分類、迴歸等任務。
nn.Linear 的基本概念
nn.Linear
是 PyTorch 中的一個模組,用於實現線性變換,也稱為全連線層(Fully Connected Layer,FC Layer)。它將輸入的特徵透過一個線性變換對映到輸出特徵。
引數解釋
-
輸入特徵的數量 (
in_features
):- 在
nn.Linear(16 * 5 * 5, 120)
中,第一個引數16 * 5 * 5
表示輸入特徵的數量。 16
通常表示通道數,5 * 5
是特徵圖的高度和寬度。這裡的計算表示輸入的特徵總數為16 * 5 * 5 = 400
。- 這個輸入形狀很可能是卷積層的輸出,經過
Flatten
層處理後,變為一維向量。
- 在
-
輸出特徵的數量 (
out_features
):- 第二個引數
120
表示輸出特徵的數量。在這個例子中,模型將輸出一個長度為 120 的一維向量。 - 這通常用於模型的分類任務,其中 120 可能代表要預測的類別數量,或者是後續網路層的維度。
- 第二個引數
線性層的功能
線性層的功能可以用數學公式表示為:
[
y = Ax + b
]
- 其中 (y) 是輸出,(A) 是權重矩陣,(x) 是輸入,(b) 是偏差項。
在使用 nn.Linear(16 * 5 * 5, 120)
時,PyTorch 會自動建立一個形狀為 (120, 400)
的權重矩陣和一個形狀為 (120,)
的偏差向量。權重和偏差會在訓練過程中進行學習和最佳化。
總結
nn.Linear(16 * 5 * 5, 120)
用於定義一個線性層,它將輸入的 400 維特徵向量對映到 120 維輸出向量。- 此層通常用於將卷積層提取的特徵連線到分類器或其他層,以形成完整的神經網路架構。
程式碼彙總
import torch.nn as nn
# LeNet-5
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5, stride=1, padding=2), # 28x28x1 -> 28x28x6
nn.Tanh(),
nn.MaxPool2d(kernel_size=2, stride=2), # 池化層
nn.Conv2d(6, 16, kernel_size=5, stride=1), # 14x14x6 -> 10x10x16
nn.Tanh(),
nn.MaxPool2d(kernel_size=2, stride=2) # 池化層
)
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(16 * 5 * 5, 120), # 全連線層1
nn.Tanh(),
nn.Linear(120, 84), # 全連線層2
nn.Tanh(),
nn.Linear(84, 10) # 輸出層
)
def forward(self, x):
x = self.features(x)
x = self.classifier(x)
return x