從AlexNet看CNN
文章目錄
AlexNet的成功之處
AlexNet將LeNet的思想發揚光大,把CNN的基本原理應用到了很深很寬的網絡中。AlexNet主要使用到的新技術點如下。
(1)成功使用ReLU作爲CNN的激活函數,並驗證其效果在較深的網絡超過了Sigmoid,成功解決了Sigmoid在網絡較深時的梯度消失問題。
(2)訓練時使用Dropout隨機忽略一部分神經元,以避免模型過擬合。在AlexNet中主要是最後幾個全連接層使用了Dropout。
(3)在CNN中使用重疊的最大池化。此前CNN中普遍使用平均池化,AlexNet全部使用最大池化,避免平均池化的模糊化效果。並且AlexNet中提出讓步長比池化核的尺寸小,這樣池化層的輸出之間會有重疊和覆蓋,提升了特徵的豐富性。
(4)提出了LRN層,對局部神經元的活動創建競爭機制,使得其中響應比較大的值變得相對更大,並抑制其他反饋較小的神經元,增強了模型的泛化能力。
處理方法 | 作用 |
---|---|
ReLU、多GPU訓練 | 提高訓練速度 |
重疊池化 | 提高精度、不易過擬合 |
局部響應歸一化 | 提高精度 |
Dropout | 減少過擬合 |
激活函數ReLU
一般激活函數有如下一些性質:
- 非線性: 當激活函數是線性的,一個兩層的神經網絡就可以基本上逼近所有的函數。但如果激活函數是恆等激活函數的時候,即f(x)=x,就不滿足這個性質,而且如果MLP使用的是恆等激活函數,那麼其實整個網絡跟單層神經網絡是等價的;
- 可微性: 當優化方法是基於梯度的時候,就體現了該性質;
- 單調性: 當激活函數是單調的時候,單層網絡能夠保證是凸函數;
- f(x)≈x: 當激活函數滿足這個性質的時候,如果參數的初始化是隨機的較小值,那麼神經網絡的訓練將會很高效;如果不滿足這個性質,那麼就需要詳細地去設置初始值;
- 輸出值的範圍: 當激活函數輸出值是有限的時候,基於梯度的優化方法會更加穩定,因爲特徵的表示受有限權值的影響更顯著;當激活函數的輸出是無限的時候,模型的訓練會更加高效,不過在這種情況小,一般需要更小的Learning Rate。
在深度神經網絡中,通常使用一種叫**修正線性單元(Rectified linear unit,ReLU)**作爲神經元的激活函數。ReLU起源於神經科學的研究:2001年,Dayan、Abott從生物學角度模擬出了腦神經元接受信號更精確的激活模型。
sigmoid
sigmoid是通過把它輸入實數值並將其“擠壓”到0到1範圍內,適合輸出爲概率的情況。
對sigmoid求導
但是sigmoid函數的導數在0的時候取到最大值爲0.25。易知利用梯度下降算法的時候容易造成梯度消失。
relu
對relu求導
它與sigmoid相比有幾大優點:
- 在正區間內解決了梯度消失的問題
- 少了次方計算,計算速度加快
Dropout
在機器學習的模型中,如果模型的參數太多,而訓練樣本又太少,訓練出來的模型很容易產生過擬合的現象。在訓練神經網絡的時候經常會遇到過擬合的問題,過擬合具體表現在:模型在訓練數據上損失函數較小,預測準確率較高;但是在測試數據上損失函數比較大,預測準確率較低。
Dropout說的簡單一點就是:我們在前向傳播的時候,讓某個神經元的激活值以一定的概率p停止工作,這樣可以使模型泛化性更強,因爲它不會太依賴某些局部的特徵。
正常的神經網絡先通過前向傳播然後把誤差通過反向傳播更新參數,利用神經網絡來學習
而使用了dropout的神經網絡就按一定概率刪除一部分神經元。
把dropout後的網絡通過前向傳播,然後把誤差通過反向傳播,神經網絡學習,然後按梯度下降來更新參數;在恢復刪除掉的神經元,到隱藏層隨機選擇一個子集臨時刪除掉,然後通過前向傳播反向傳播在用梯度下降算法更新參數不斷重複這一過程。
在訓練時增加一個一次概率刪除
沒有dropout的神經網絡
有dropout的神經網絡
數據擴充
可以通過圖像增廣實現數據擴充
torchvision.transforms
對於多種變換可以使用torchvision.transforms.Compose
來合併。
相關介紹與圖像增廣實現代碼
LRN局部響應歸一化
在神經生物學有一個概念叫做“側抑制”(lateral inhibitio),指的是被激活的神經元抑制相鄰神經元。歸一化(normalization)的目的是“抑制”,局部歸一化就是借鑑了“側抑制”的思想來實現局部抑制,尤其當使用ReLU時這種“側抑制”很管用,因爲ReLU的響應結果是無界的(可以非常大),所以需要歸一化。使用局部歸一化的方案有助於增加泛化能力。
AlexNet的結構
第一層(卷積層)
該層的處理流程爲:卷積–>ReLU–>池化–>歸一化
卷積
在本層使用96個步長爲4的11×11×3的卷積核進行卷積計算,其大小爲:
其中floor表示向下取整,img_size爲圖像大小,filter_size爲核大小,stride爲步長,new_feature_size爲卷積後的特徵圖大小,pading爲填充數目,這個公式表示圖像尺寸減去卷積核尺寸除以步長,再加上被減去的核大小像素對應生成的一個像素,結果就是卷積後特徵圖的大小。
得到的特徵圖大小爲55x55,由於採用了兩個GPU並行運算,因此,網絡結構圖中上下兩部分分別承擔了48個卷積核的運算。所以尺寸爲2組55×55×48的像素層數據。
激活
卷積後的55×55像素層經過ReLU單元的激活,生成激活層,尺寸仍爲2組55×55×48的像素層數據。
池化
激活再經過池化運算,池化運算的尺寸爲3×3,步長爲2,則池化後圖像的尺寸爲 (55-3)/2+1=27,即池化後特徵圖的規模爲27×27×96
歸一化
池化後再進行歸一化處理,歸一化運算的尺寸爲5×5,歸一化後的像素規模不變,仍爲27×27×96,這96層像素層被分爲兩組,每組48層,分別在一個獨立的GPU上進行運算。
第二層(卷積層)
該層與第一層類似,處理流程爲:卷積–>ReLU–>池化–>歸一化
卷積
每一組經過128個5x5x3的卷積核其中padding=2,stride=1,所以得到的特徵圖爲27x27x128,其中每個GPU爲27x27x128
激活
然後經過relu激活
池化
每一組經過128個3x3,stride=2的池化,得到2組13x13x128的像素層
歸一化
歸一化運算的尺度爲5×5
第三層(卷積層)
第三層的處理流程爲:卷積–>ReLU
卷積
每一組經過192個大小爲3x3x256,padding=1,stride=1的卷積核得到2組13×13×192的像素層
激活
通過relu激活
第四層(卷積層)
第四層的處理流程爲:卷積–>ReLU
卷積
每一組經過過192個大小爲3×3×192,stride=1,padding=1(與第三層不同,第四層的GPU之間沒有虛線連接,也即GPU之間沒有通信)得到大小爲13×13×192的特徵圖
激活
通過relu激活
第五層(卷積層)
第五層的處理流程爲:卷積–>ReLU–>池化
卷積
每一組經過128個3x3,padding=1,stride=1的卷積,得到13×13×128像素層
激活
通過relu激活
池化
2組13×13×128像素層分別在2個不同GPU中進行池化運算處理,池化運算的尺寸爲3×3,步長爲2,池化後圖像的尺寸爲 (13-3)/2+1=6,即池化後像素的規模爲兩組6×6×128的像素層數據,共有6×6×256的像素層數據。
第六層(全連接層)
第六層的處理流程爲:卷積(全連接)–>ReLU–>Dropout
卷積(全連接)
第六層輸入數據是第五層的輸出,尺寸爲6×6×256。本層共有4096個卷積核,每個卷積核的尺寸爲6×6×256,由於卷積核的尺寸剛好與待處理特徵圖(輸入)的尺寸相同,即卷積核中的每個係數只與特徵圖(輸入)尺寸的一個像素值相乘,一一對應,因此,該層被稱爲全連接層。由於卷積核與特徵圖的尺寸相同,卷積運算後只有一個值,因此,卷積後的像素層尺寸爲4096×1×1,即有4096個神經元。
激活
通過relu激活
Dropout
然後再通過Dropout運算,輸出4096個結果值。
第七層(全連接層)
第七層的處理流程爲:全連接–>ReLU–>Dropout
第六層輸出的4096個數據與第七層的4096個神經元進行全連接,然後經ReLU進行處理後生成4096個數據,再經過Dropout處理後輸出4096個數據。
第八層(全連接層)
第八層的處理流程爲:全連接
第七層輸出的4096個數據與第八層的1000個神經元進行全連接,經過訓練後輸出1000個float型的值,這就是預測結果。
實現代碼
#!/usr/bin/env python
# coding: utf-8
# In[ ]:
import time
import torch
from torch import nn, optim
import torchvision
# In[ ]:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# In[4]:
device
# In[ ]:
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet,self).__init__()
self.conv=nn.Sequential(
nn.Conv2d(1,96,11,4), # in_channels, out_channels, kernel_size, stride, padding
nn.ReLU(),
nn.MaxPool2d(3,2), # kernel_size,stride
nn.Conv2d(96,256,5,1,2),
nn.ReLU(),
nn.MaxPool2d(3,2),
nn.Conv2d(256,384,3,1,1),
nn.ReLU(),
nn.Conv2d(384,384,3,1,1),
nn.ReLU(),
nn.Conv2d(384,256,3,1,1),
nn.ReLU(),
nn.MaxPool2d(3,2),
)
self.fc=nn.Sequential(
nn.Linear(256*5*5,4096),
nn.ReLU(),
nn.Dropout(0.5), # 使用dropout防止過擬合
nn.Linear(4096,4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096,10)
)
def forward(self,img):
feature=self.conv(img)
output=self.fc(feature.view(img.shape[0],-1))
return output
# In[6]:
net=AlexNet()
print(net) # 打印神經網絡的結構
# In[ ]:
def load_data_fashion_mnist(batch_size, resize=None, root='~/Datasets/FashionMNIST'):
"""Download the fashion mnist dataset and then load into memory."""
trans = []
if resize:
trans.append(torchvision.transforms.Resize(size=resize)) # 更改PIL圖像的大小
trans.append(torchvision.transforms.ToTensor()) # 將形狀爲(HxWxC)PIL的圖像轉爲形狀爲(CxHxW)的FloatTensor
transform = torchvision.transforms.Compose(trans) # 一起組成一個變換
mnist_train = torchvision.datasets.FashionMNIST(root=root, train=True, download=True, transform=transform)
mnist_test = torchvision.datasets.FashionMNIST(root=root, train=False, download=True, transform=transform)
train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=4)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=4)
return train_iter, test_iter
# In[ ]:
def evaluate_accuracy(data_iter,net,device=None):
if device is None and isinstance(net, nn.Module):
device=list(net.parameters())[0].device # 運行設備爲net所運行的設備
acc_sum,n=0.0,0
with torch.no_grad(): # 禁用梯度計算
for X,y in data_iter:
if isinstance(net, nn.Module):
net.eval() # 不啓用 BatchNormalization 和 Dropout
acc_sum+=(net(X.to(device)).argmax(dim=1)==y.to(device)).float().sum().cpu().item()
net.train() # 啓用 BatchNormalization 和 Dropout
else:
if('is training' in net.__code__.co_varnames):
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
# In[ ]:
def train(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs):
net = net.to(device)
print("training on ", device)
loss = torch.nn.CrossEntropyLoss()
for epoch in range(num_epochs):
train_l_sum, train_acc_sum, n, batch_count, start = 0.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() # softMax:y_hat.argmax(dim=1)
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))
# In[ ]:
batch_size=128
# 如出現“out of memory”的報錯信息,可減小batch_size或resize
train_iter,test_iter=load_data_fashion_mnist(batch_size=batch_size,resize=224)
# In[14]:
lr,num_epochs=0.001,5
optimizer=optim.Adam(net.parameters(),lr=lr)
train(net,train_iter,test_iter,batch_size=batch_size,optimizer=optimizer,device=device,num_epochs=num_epochs)