更新流程↓
Task01:賽題理解
Task02:數據讀取與數據擴增
Task03:字符識別模型
Task04:模型訓練與驗證
Task05:模型集成
???
淦,飛碟
飛…飛車
比賽鏈接
文章目錄
1. 卷積神經網絡CNN
卷積神經網絡(Convolutional Neural Networks, CNN)是一類包含卷積計算且具有深度結構的前饋神經網絡(Feedforward Neural Networks),是深度學習(deep learning)的代表算法之一。卷積神經網絡具有表徵學習(representation learning)能力,能夠按其階層結構對輸入信息進行平移不變分類(shift-invariant classification),因此也被稱爲“平移不變人工神經網絡(Shift-Invariant Artificial Neural Networks, SIANN)”
卷積神經網絡的主要結構如下圖所示,由輸入層、卷積層、池化層、全連接層、輸出層構成。
1.1. 輸入層
本次賽題CNN輸入層代表了一張圖片的像素矩陣。上圖中最左側三維矩陣代表一張圖片,三維矩陣的維度代表了圖像的 (高度h,寬度w,色彩通道數c)。三維矩陣的長、寬代表了圖像的大小,深度代表了圖像的色彩通道,RGB模式下圖片深度爲3,灰度模式圖片深度爲1。
1.2. 卷積層(Convolutional layer)
CNN 中最爲重要的部分。與全連接層不同,卷積層中每一個節點的輸入只是上一層神經網絡中的一小塊,這個小塊常用的大小有 3×3 或者 5×5。一般來說,通過卷積層處理過的節點矩陣會變的更深。
1.2.1. 卷積(Convolutions)
卷積中含有以下幾個參數
- 內核大小(filter):卷積內核大小定義了卷積的視野。二維的常見選擇是3——即3x3像素。
- 卷積步長(stride):卷積步長定義了遍歷圖像時內核的步長。雖然它的默認值通常爲1,但我們可以使用2的步長,類似於最大池化對圖像進行下采樣。
- padding:padding定義樣本的邊框如何處理。一(半)個padding卷積將保持空間輸出尺寸等於輸入尺寸,而如果內核大於1,則不加捲積將消除一些邊界。
- 輸入輸出通道:卷積層需要一定數量的輸入通道(I),並計算出特定數量的輸出通道(O)。可以通過I * O * K來計算這樣一層所需的參數,其中K等於內核中的值的數量。
還有擴張卷積、轉置卷積等等。更多卷積類型可以閱讀參考資料[1]進行了解。
1.2.2. 過濾器(filter)
過濾器(filter),即卷積內核。若使用內核大小爲3的卷積內核對一張5x5的圖片進行卷積,會得到一個3x3的矩陣(Feature Map)。
內核可以將當前層神經網絡上的一個子節點矩陣轉化爲下一層神經網絡上的一個單位節點矩陣。單位節點矩陣制的是長和寬都是1,但深度不限的節點矩陣。
常用的內核尺寸有 3×3 或 5×5,即上圖黃色和橙色矩陣中的前兩維,這個是人爲設定的;內核的節點矩陣深度,即上圖黃色和橙色矩陣中的最後一維(內核尺寸的最後一維),是由當前層神經網絡節點矩陣的深度(RGB 圖像節點矩陣深度爲 3)決定的;卷積層輸出矩陣的深度(也稱爲內核深度)是由該卷積層中內核的個數決定,該參數也是人爲設定的,一般隨着卷積操作的進行越來越大。上圖中內核尺寸爲 3×3×3,內核深度爲 2。
卷積操作中,一個 3×3×3 的子節點矩陣和一個 3×3×3 的 卷積內核對應元素相乘,得到的是一個 3×3×3 的矩陣,此時將該矩陣所有元素求和,得到一個 1×1×1 的矩陣,將其再加上卷積內核的偏斜量bias,經過激活函數得到最後的結果,將最後的結果填入到對應的輸出矩陣中。輸出矩陣中第一個元素的計算如下所示:
上式中, 表示當前層的一個子節點矩陣,即 6×6×3 矩陣中左上角 3×3×3 部分; 表示第一個卷積內核的權重,即第一個卷積內核每個位置的值; 表示第一個卷積內核的偏置 bias,是一個實數; 表示激活函數,如 ReLU 激活函數。
“卷積層結構的前向傳播過程就是通過將一個卷積內核從神經網絡當前層的左上角移動到右下角,並且在移動中計算每一個對應的單位矩陣得到的。”
5×5×3矩陣,卷積步長爲2,padding爲1
1.2.3. padding
padding,顧名思義,就是在圖像周圍進行填充,一般常用zero padding,即用 0 來填充。當padding=1時,在圖像周圍填充一圈;當padding=2時,填充兩圈。
爲什麼要padding?兩個原因,第一個是隨着卷積操作的進行,圖像會越來越小; 第二個是對圖片邊緣信息和內部信息的重視程度不一樣,邊緣信息卷積內核只經過一次,而內部信息會被經過多次。
有效卷積和相同卷積的區別在於有效卷積: 無padding;相同卷積: padding使輸出尺寸與輸入尺寸相同。
1.2.4. 卷積步長
卷積步長(stride) 就是像在上圖中卷積內核一次移動的格數,上圖中 卷積步長S爲2。
卷積層輸出矩陣的大小與輸入圖片大小 、卷積內核的尺寸 、padding 的大小 、卷積步長 都有關。(假設輸入的圖片是方形的)
當 時,可能存在 不是整數的情況,這個時候對 取下整或者使用整除。
依據《TensorFlow實戰Google深度學習框架》, 也可以寫成如下形勢:
上面兩個公式最後的結果會是一樣。
1.3. 池化層(Pooling layer)
池化層可以非常有效地縮小矩陣的尺寸(主要減少矩陣的長和寬,一般不會去減少矩陣深度),從而減少最後全連接層中的參數。“使用池化層既可以加快計算速度也有防止過擬合問題的作用。”
與卷積層類似,池化層的前向傳播過程也是通過一個類似卷積內核的結構完成的。不過池化層卷積內核中的計算不是節點的加權和,而是採用更加簡單的最大值或者平均值運算。使用最大值操作的池化層被稱爲最大池化層(max pooling),這是使用最多的池化層結構。使用平均值操作的池化層被稱爲平均池化層(average pooling)。
與卷積層的卷積內核類似,池化層的卷積內核也需要人工設定卷積內核的尺寸、是否使用全 0 填充 以及卷積內核移動的步長等設置,而且這些設置的意義也是一樣的。
卷積層和池化層中卷積內核的移動方式是相似的,唯一的區別在於卷積層使用的卷積內核是橫跨整個深度的,而池化層使用的卷積內核隻影響一個深度上的節點。所以池化層的卷積內核除了在長和寬兩個維度移動之外,它還需要在深度這個維度移動。也就是說,在進行 max 或者 average 操作時,只會在同一個矩陣深度上進行,而不會跨矩陣深度進行。
上圖中,池化層卷積內核尺寸爲 2×2,即,padding 大小 ,卷積步長 。
池化層一般不改變矩陣的深度,只改變矩陣的長和寬。
池化層沒有trainable參數,只有一些需要人工設定的超參數。
1.4. 全連接層( Fully Connected layer)
全連接層( Fully Connected layer,FC) 在整個卷積神經網絡中起到分類器的作用。如果說卷積層、池化層和激活函數層等操作是將原始數據映射到隱層特徵空間的話,全連接層則起到將學到的“分佈式特徵表示”映射到樣本標記空間的作用。在實際使用中,全連接層可由卷積操作實現:
1)對前層是全連接的全連接層可以轉化爲卷積核爲1x1的卷積。
2)對前層是卷積層的全連接層可以轉化爲卷積核爲的全局卷積,和分別爲前層卷積結果的高度和寬度。
全連接的核心操作就是矩陣向量乘積 。本質就是由一個特徵空間線性變換到另一個特徵空間。目標空間的任一維——也就是隱層的一個cell——都認爲會受到源空間的每一維的影響。不考慮嚴謹,可以說,目標向量是源向量的加權和。在本文的第一張圖中,最後一層全連接層激活函數使用 softmax。
在 CNN 中,全連接常出現在最後幾層,用於對前面設計的特徵做加權和。比如mnist,前面的卷積和池化相當於做特徵工程,後面的全連接相當於做特徵加權。(卷積相當於全連接的有意弱化,按照局部視野的啓發,把局部之外的弱影響直接抹爲零影響;還做了一點強制,不同的局部所使用的參數居然一致。弱化使參數變少,節省計算量,又專攻局部不貪多求全;強制進一步減少參數。少即是多) 在 RNN 中,全連接用來把 embedding 空間拉到隱層空間,把隱層空間轉回 label 空間等。
2. Pytorch構建CNN模型
在Pytorch中構建CNN模型非常簡單,只需要定義好模型的參數和正向傳播即可,Pytorch會根據正向傳播自動計算反向傳播。
在本章我們會構建一個非常簡單的CNN,然後進行訓練。這個CNN模型包括兩個卷積層,最後並聯六個全連接層進行分類。
import torch
torch.manual_seed(0)
torch.backends.cudnn.deterministic = False
torch.backends.cudnn.benchmark = True
import torchvision.models as models
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
from torch.utils.data.dataset import Dataset
# 定義模型
class SVHN_Model1(nn.Module):
def __init__(self):
super(SVHN_Model1, self).__init__()
# CNN提取特徵模塊
self.cnn = nn.Sequential(
nn.Conv2d(3, 16, kernel_size=(3, 3), stride=(2, 2)),nn.ReLU(),nn.MaxPool2d(2),
nn.Conv2d(16, 32, kernel_size=(3, 3), stride=(2, 2)),nn.ReLU(),nn.MaxPool2d(2),)
self.fc1 = nn.Linear(32*3*7, 11)
self.fc2 = nn.Linear(32*3*7, 11)
self.fc3 = nn.Linear(32*3*7, 11)
self.fc4 = nn.Linear(32*3*7, 11)
self.fc5 = nn.Linear(32*3*7, 11)
self.fc6 = nn.Linear(32*3*7, 11)
def forward(self, img):
feat = self.cnn(img)
feat = feat.view(feat.shape[0], -1)
for i range(1:7)
c1 = self.fc1(feat)
c2 = self.fc2(feat)
c3 = self.fc3(feat)
c4 = self.fc4(feat)
c5 = self.fc5(feat)
c6 = self.fc6(feat)
return c1, c2, c3, c4, c5, c6
model = SVHN_Model1()
接下來是訓練代碼:
# 損失函數
criterion = nn.CrossEntropyLoss()
# 優化器
optimizer = torch.optim.Adam(model.parameters(), 0.005)
loss_plot, c0_plot = [], []
# 迭代10個Epoch
for epoch in range(10):
for data in train_loader:
c0, c1, c2, c3, c4, c5 = model(data[0])
loss = criterion(c0, data[1][:, 0]) + \
criterion(c1, data[1][:, 1]) + \
criterion(c2, data[1][:, 2]) + \
criterion(c3, data[1][:, 3]) + \
criterion(c4, data[1][:, 4]) + \
criterion(c5, data[1][:, 5])
loss /= 6
optimizer.zero_grad()
loss.backward()
optimizer.step()
loss_plot.append(loss.item())
c0_plot.append((c0.argmax(1) == data[1][:, 0]).sum().item()*1.0 / c0.shape[0])
print(epoch)
在訓練完成後我們可以將訓練過程中的損失和準確率進行繪製,如下圖所示。從圖中可以看出模型的損失在迭代過程中逐漸減小,字符預測的準確率逐漸升高。
.
當然爲了追求精度,也可以使用在ImageNet數據集上的預訓練模型,具體方法如下:
class SVHN_Model2(nn.Module):
def __init__(self):
super(SVHN_Model1, self).__init__()
model_conv = models.resnet18(pretrained=True)
model_conv.avgpool = nn.AdaptiveAvgPool2d(1)
model_conv = nn.Sequential(*list(model_conv.children())[:-1])
self.cnn = model_conv
self.fc1 = nn.Linear(512, 11)
self.fc2 = nn.Linear(512, 11)
self.fc3 = nn.Linear(512, 11)
self.fc4 = nn.Linear(512, 11)
self.fc5 = nn.Linear(512, 11)
def forward(self, img):
feat = self.cnn(img)
# print(feat.shape)
feat = feat.view(feat.shape[0], -1)
c1 = self.fc1(feat)
c2 = self.fc2(feat)
c3 = self.fc3(feat)
c4 = self.fc4(feat)
c5 = self.fc5(feat)
return c1, c2, c3, c4, c5
參考資料