【深度學習】深入淺出卷積神經網絡及實現!
CNN,又稱卷積神經網絡,是深度學習中重要的一個分支。CNN在很多領域都表現優(yōu)異,精度和速度比傳統計算學習算法高很多。特別是在計算機視覺領域,CNN是解決圖像分類、圖像檢索、物體檢測和語義分割的主流模型。
1. 卷積
如圖1所示,圖中的X和O無論怎么旋轉或者縮放,人眼其實還是很容易識別出X和O。

圖1
但是計算機不同,它看到的其實是一個個的像素陣列,如圖2。如何對像素的陣列進行特征的提取其實就是卷積神經網絡要干的事情。
圖2
再看圖3,我們發(fā)現X即使進行了旋轉,但是綠、橙、紫框標記的區(qū)域在兩張圖中還是一致的,某種程度上,這其實就是X的特征。

圖3
因此可以將這三個特征的區(qū)間提取出來,就形成了三個卷積核,如圖4所示。

圖4
既然有了卷積核,那么卷積核是如何進行卷積操作的呢?其實很簡單,可以看一下圖5,卷積核其實就是拿著這個矩陣在圖片的矩陣上一點點的平移,就像掃地一樣。每掃到一處地方就可以進行卷積的運算,計算方法很簡單,如圖5所示,左上角的卷積核掃到綠色框的位置,則卷積核矩陣的數字就和掃到的位置的矩陣的數字一一對應相乘然后相加,最后取一個均值,該值就是卷積核提取的特征。

圖5
卷積核提取的所有的特征組成了一個長和寬變小的矩陣,這個矩陣又稱為feature map,如圖6。使用不同的卷積核也就能提取出不同的feature map。所以可以想象的是,如果不斷的進行卷積操作,那么圖片的矩陣會逐步地長寬減少,厚度增加。

圖6
2. 池化(pooling)
池化其實就是對每個feature map進一步提煉的過程。如圖7所示,原來4X4的feature map經過池化操作之后就變成了更小的2*2的矩陣。池化的方法包括max pooling,即取最大值,以及average pooling,即取平均值。

圖7
3. Normalization
這里的Normalization就是將矩陣中負數的值轉成0,也就是使用一個稱之為ReLu的激活函數進行負數變?yōu)?的操作。ReLu函數本質上就是max(0,x)。這一步其實也是為了方便運算。
4. 卷積神經網絡理解
因此卷積、ReLu、pooling,不斷重復其實也就基本上構成了卷積神經網絡的框架,如圖8。然后將最終得到的feaure map 排成一列(圖8),接到全連接層,這樣就形成了我們的卷積神經網絡。值得注意的是,排成一列的數值,是有權重,而這些權重是通過訓練、反向傳播得到的,通過權重的計算,可以知道不同分類的概率是怎么樣的。
圖8


卷積神經網絡
如下圖所示為LeNet網絡結構,總共有7層網絡(不含輸入層),2個卷積層、2個池化層、3個全連接層。

在卷積層塊中輸入的高和寬在逐層減小。卷積層由于使用高和寬均為5的卷積核,從而將高和寬分別減小4,而池化層則將高和寬減半,但通道數則從1增加到16。全連接層則逐層減少輸出個數,直到變成圖像的類別數10。

一個數字識別的效果如圖所示:

卷積神經網絡進階
隨著網絡結構的發(fā)展,研究人員最初發(fā)現網絡模型結構越深、網絡參數越多模型的精度更優(yōu)。比較典型的是AlexNet、VGG、InceptionV3和ResNet的發(fā)展脈絡。 
1. AlexNet(2012)
2012年,AlexNet橫空出世。這個模型的名字來源于論文第一作者的姓名Alex Krizhevsky。AlexNet使用了8層卷積神經網絡,并以很大的優(yōu)勢贏得了ImageNet 2012圖像識別挑戰(zhàn)賽。它首次證明了學習到的特征可以超越手工設計的特征,從而一舉打破計算機視覺研究的前狀。
AlexNet與LeNet的設計理念非常相似,但也有顯著的區(qū)別。
小結:
AlexNet跟LeNet結構類似,但使用了更多的卷積層和更大的參數空間來擬合大規(guī)模數據集ImageNet。它是淺層神經網絡和深度神經網絡的分界線。
雖然看上去AlexNet的實現比LeNet的實現也就多了幾行代碼而已,但這個觀念上的轉變和真正優(yōu)秀實驗結果的產生令學術界付出了很多年。
2. VGG-16(2014)
AlexNet在LeNet的基礎上增加了3個卷積層。但AlexNet作者對它們的卷積窗口、輸出通道數和構造順序均做了大量的調整。雖然AlexNet指明了深度卷積神經網絡可以取得出色的結果,但并沒有提供簡單的規(guī)則以指導后來的研究者如何設計新的網絡。VGG,它的名字來源于論文作者所在的實驗室Visual Geometry Group。VGG提出了可以通過重復使用簡單的基礎塊來構建深度模型的思路。VGG的結構圖如下:
VGG塊的組成規(guī)律是:連續(xù)使用數個相同的填充為1、窗口形狀為3*3的卷積層后接上一個步幅為2、窗口形狀為2*2的最大池化層。卷積層保持輸入的高和寬不變,而池化層則對其減半。我們使用vgg_block函數來實現這個基礎的VGG塊,它可以指定卷積層的數量和輸入輸出通道數。
對于給定的感受野(與輸出有關的輸入圖片的局部大?。捎枚逊e的小卷積核優(yōu)于采用大的卷積核,因為可以增加網絡深度來保證學習更復雜的模式,而且代價還比較?。▍蹈伲?。例如,在VGG中,使用了3個3x3卷積核來代替7x7卷積核,使用了2個3x3卷積核來代替5*5卷積核,這樣做的主要目的是在保證具有相同感知野的條件下,提升了網絡的深度,在一定程度上提升了神經網絡的效果。
VGG:通過重復使?簡單的基礎塊來構建深度模型。 Block: 數個相同的填充為1、窗口形狀為3×3的卷積層,接上一個步幅為2、窗口形狀為2×2的最大池化層。卷積層保持輸入的高和寬不變,而池化層則對其減半。VGG和AlexNet的網絡圖對比如下:

小結:VGG-11通過5個可以重復使用的卷積塊來構造網絡。根據每塊里卷積層個數和輸出通道數的不同可以定義出不同的VGG模型。
3. 網絡中的網絡(NiN)
LeNet、AlexNet和VGG:先以由卷積層構成的模塊充分抽取空間特征,再以由全連接層構成的模塊來輸出分類結果。NiN:串聯多個由卷積層和“全連接”層構成的小?絡來構建?個深層?絡。 ?了輸出通道數等于標簽類別數的NiN塊,然后使?全局平均池化層對每個通道中所有元素求平均并直接用于分類。
1×1卷積核作用
放縮通道數:通過控制卷積核的數量達到通道數的放縮;
增加非線性。1×1卷積核的卷積過程相當于全連接層的計算過程,并且還加入了非線性激活函數,從而可以增加網絡的非線性;
計算參數少。
NiN塊我們知道,卷積層的輸入和輸出通常是四維數組(樣本,通道,高,寬),而全連接層的輸入和輸出則通常是二維數組(樣本,特征)。如果想在全連接層后再接上卷積層,則需要將全連接層的輸出變換為四維?;貞浽诙噍斎胪ǖ篮投噍敵鐾ǖ览锝榻B的1*1卷積層。它可以看成全連接層,其中空間維度(高和寬)上的每個元素相當于樣本,通道相當于特征。因此,NiN使用1*1卷積層來替代全連接層,從而使空間信息能夠自然傳遞到后面的層中去。
小結:
NiN重復使用由卷積層和代替全連接層的1*1卷積層構成的NiN塊來構建深層網絡。
NiN去除了容易造成過擬合的全連接輸出層,而是將其替換成輸出通道數等于標簽類別數的NiN塊和全局平均池化層。
NiN的以上設計思想影響了后面一系列卷積神經網絡的設計。
4. 含并行連結的網絡(GoogLeNet)
在2014年的ImageNet圖像識別挑戰(zhàn)賽中,一個名叫GoogLeNet的網絡結構大放異彩。它雖然在名字上向LeNet致敬,但在網絡結構上已經很難看到LeNet的影子。GoogLeNet吸收了NiN中網絡串聯網絡的思想,并在此基礎上做了很大改進。在隨后的幾年里,研究人員對GoogLeNet進行了數次改進,本節(jié)將介紹這個模型系列的第一個版本。
由Inception基礎塊組成。
Inception塊相當于?個有4條線路的??絡。它通過不同窗口形狀的卷積層和最?池化層來并?抽取信息,并使?1×1卷積層減少通道數從而降低模型復雜度。
可以?定義的超參數是每個層的輸出通道數,我們以此來控制模型復雜度。
Inception塊里有4條并行的線路。前3條線路使用窗口大小分別是1*1、3*3和5*5的卷積層來抽取不同空間尺寸下的信息,其中中間2個線路會對輸入先做1*1卷積來減少輸入通道數,以降低模型復雜度。第四條線路則使用3*3最大池化層,后接1*1卷積層來改變通道數。4條線路都使用了合適的填充來使輸入與輸出的高和寬一致。最后我們將每條線路的輸出在通道維上連結,并輸入接下來的層中去。Inception塊中可以自定義的超參數是每個層的輸出通道數,我們以此來控制模型復雜度。GoogLeNet跟VGG一樣,在主體卷積部分中使用5個模塊(block),每個模塊之間使用步幅為2的3*3最大池化層來減小輸出高寬。
小結:
Inception塊相當于一個有4條線路的子網絡。它通過不同窗口形狀的卷積層和最大池化層來并行抽取信息,并使用1*1卷積層減少通道數從而降低模型復雜度。
GoogLeNet將多個設計精細的Inception塊和其他層串聯起來。其中Inception塊的通道數分配之比是在ImageNet數據集上通過大量的實驗得來的。
GoogLeNet和它的后繼者們一度是ImageNet上最高效的模型之一:在類似的測試精度下,它們的計算復雜度往往更低。
5、殘差網絡(ResNet-50)
深度學習的問題:深度CNN網絡達到一定深度后再一味地增加層數并不能帶來進一步地分類性能提高,反而會招致網絡收斂變得更慢,準確率也變得更差。- - -殘差塊(Residual Block)恒等映射:
左邊:f(x)=x;
右邊:f(x)-x=0 (易于捕捉恒等映射的細微波動)。

ResNet的前兩層跟之前介紹的GoogLeNet中的一樣:在輸出通道數為64、步幅為2的7*7卷積層后接步幅為2的3*3的最大池化層。不同之處在于ResNet每個卷積層后增加的批量歸一化層。ResNet-50網絡結構如下:

小結:
殘差塊通過跨層的數據通道從而能夠訓練出有效的深度神經網絡。
ResNet深刻影響了后來的深度神經網絡的設計。
import torchtorch.manual_seed(0)torch.backends.cudnn.deterministic= Falsetorch.backends.cudnn.benchmark = Trueimport torchvision.models as modelsimport torchvision.transforms as transformsimport torchvision.datasets as datasetsimport torch.nn as nnimport torch.nn.functional as Fimport torch.optim as optimfrom torch.autograd import Variablefrom torch.utils.data.dataset import Dataset
1. pytorch常用網絡
1.1 Linear介紹 [全連接層]
nn.Linear(input_feature,out_feature,bias=True)1.2 卷積介紹 [2D卷積層]
nn.Conv2d(in_channels,out_channels,kernel_size,stride=1,padding=0,dilation=1,groups,bias=True,padding_mode='zeros')##kernel_size,stride,padding 都可以是元組## dilation 為在卷積核中插入的數量
1.3 轉置卷積介紹 [2D反卷積層]
nn.ConvTranspose2d(in_channels,out_channels,kernel_size,stride=1,padding=0,out_padding=0,groups=1,bias=True,dilation=1,padding_mode='zeros')##padding是輸入填充,out_padding填充到輸出
1.4 最大值池化層 [2D池化層]
nn.MaxPool2d(kernel_size,stride=None,padding=0,dilation=1)1.5 批量歸一化層 [2D歸一化層]
nn.BatchNorm2d(num_features,eps,momentum,affine=True,track_running_stats=True)affine=True 表示批量歸一化的α,β是被學到的track_running_stats=True 表示對數據的統計特征進行關注
2. pytorch 創(chuàng)建模型的四種方法
假設創(chuàng)建卷積層–》Relu層–》池化層–》全連接層–》Relu層–》全連接層
# 導入包import torchimport torch.nn.functional as Ffrom collections import OrderedDict
2.1.自定義型[定義在init,前向過程在forward]
class Net1(torch.nn.Module):def __init__(self):super(Net1, self).__init__()self.conv1 = torch.nn.Conv2d(3, 32, 3, 1, 1)self.dense1 = torch.nn.Linear(32 * 3 * 3, 128)self.dense2 = torch.nn.Linear(128, 10)def forward(self, x):x = F.max_pool2d(F.relu(self.conv(x)), 2)x = x.view(x.size(0), -1)x = F.relu(self.dense1(x))x = self.dense2(x)return x
2.2 序列集成型[利用nn.Squential(順序執(zhí)行的層函數)]
訪問各層只能通過數字索引
class Net2(torch.nn.Module):def __init__(self):super(Net2, self).__init__()self.conv = torch.nn.Sequential(torch.nn.Conv2d(3, 32, 3, 1, 1),torch.nn.ReLU(),torch.nn.MaxPool2d(2))self.dense = torch.nn.Sequential(torch.nn.Linear(32 * 3 * 3, 128),torch.nn.ReLU(),torch.nn.Linear(128, 10))def forward(self, x):conv_out = self.conv(x)res = conv_out.view(conv_out.size(0), -1)out = self.dense(res)return out

2.3 序列添加型[利用Squential類add_module順序逐層添加]
給予各層的name屬性
class Net3(torch.nn.Module):def __init__(self):super(Net3, self).__init__()self.conv=torch.nn.Sequential()self.conv.add_module("conv1",torch.nn.Conv2d(3, 32, 3, 1, 1))self.conv.add_module("relu1",torch.nn.ReLU())self.conv.add_module("pool1",torch.nn.MaxPool2d(2))self.dense = torch.nn.Sequential()self.dense.add_module("dense1",torch.nn.Linear(32 * 3 * 3, 128))self.dense.add_module("relu2",torch.nn.ReLU())self.dense.add_module("dense2",torch.nn.Linear(128, 10))def forward(self, x):conv_out = self.conv(x)res = conv_out.view(conv_out.size(0), -1)out = self.dense(res)return out

2.4 序列集成字典型[OrderDict集成模型字典【‘name’:層函數】]
name為key
lass Net4(torch.nn.Module):def __init__(self):super(Net4, self).__init__()self.conv = torch.nn.Sequential(OrderedDict([("conv1", torch.nn.Conv2d(3, 32, 3, 1, 1)),("relu1", torch.nn.ReLU()),("pool", torch.nn.MaxPool2d(2))]))self.dense = torch.nn.Sequential(OrderedDict([("dense1", torch.nn.Linear(32 * 3 * 3, 128)),("relu2", torch.nn.ReLU()),("dense2", torch.nn.Linear(128, 10))]))def forward(self, x):conv_out = self.conv1(x)res = conv_out.view(conv_out.size(0), -1)out = self.dense(res)return out

3. pytorch 對模型參數的訪問,初始化,共享
3.1 訪問參數
訪問層
如果采用序列集成型,序列添加型或者字典集成性,都只能使用id索引訪問層。eg:net[1];
如果想以網絡的name訪問,eg:net.layer_name。
訪問參數【權重參數名:層名_weight/bias】
layer.params----訪問該層參數字典;
layer.weight , layer.bias-----訪問該層權重和偏置;
layer.weight.data()/grad() ------訪問該層權重的具體數值/梯度【bias也使用】;
net.collect_params() ----返回該網絡的所有參數,返回一個由參數名稱到實例的字典。
3.2 初始化[若非首次初始化,force_reinit=True]
常規(guī)初始化【網絡初始化】
init 利用各種分布初始化
net.initialize(init=init.Normal(sigma=0.1),force_reinit=True)init 對網絡參數進行常數初始化
net.initialize(init=init.Constant(1))特定參數初始化
(某參數).initialize(init=init.Xavier(),force_reinit=True)自定義初始化
繼承init的Initialize類,并實現函數_init_weight(self,name,data)
def _init_weight(self, name, data):print('Init', name, data.shape)data[:] = nd.random.uniform(low=-10, high=10, shape=data.shape)# 表示一半幾率為0,一半幾率為[-10,-5]U[5,10]的均勻分布data *= data.abs() >= 5# 調用自定義初始化函數1net.initialize(MyInit(), force_reinit=True)
3.3 參數共享
參數共享,梯度共享,但是梯度計算的是所有共享層的和
梯度共享,且梯度只更新一次
net = nn.Sequential()shared = nn.Dense(8, activation='relu')net.add(nn.Dense(8, activation='relu'),shared,nn.Dense(8, activation='relu', params=shared.params),nn.Dense(10))net.initialize()X = nd.random.uniform(shape=(2, 20))net(X)net[1].weight.data()[0] == net[2].weight.data()[0]
構建網絡模型:繼承nn.Module函數的__init__ 函數,重定義前向傳播函數forward
構造優(yōu)化器
構造損失函數
訓練 確定幾個epoch【若運用數據增廣,隨機增廣epoch次達到多樣性】
對每個batch損失函數后向傳播,優(yōu)化器更新參數
optimizer.zero_grad() 清空梯度
loss.backward()
optimizer.step()
4.1 普通自建網絡
class SVHN_model(nn.Module):def __init__(self):super(SVHN_model,self).__init__()self.cnn = nn.Squential(nn.Conv2d(3,16,kernel_size=(3,3),stride=(2,2)), #3X64X128--> 16X31X63nn.Relu(),nn.MaxPool2d(2), #16X31X63--> 16X15X31nn.Conv2d(16,32,kernel_size=(3,3),stride=(2,2)),#16X15X31--> 32X7X15nn.Relu(),nn.MaxPool2d(2) #32X7X15--> 32X3X7)# 并行五次字符預測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)def forward(self,x):cnn_result = self.cnn(x)cnn_result = cnn_result.view(cnn_result.shape[0],-1)f1 = fc1(cnn_result)f2 = fc2(cnn_result)f3 = fc3(cnn_result)f4 = fc4(cnn_result)f5 = fc5(cnn_result)return f1,f2,f3,f4,f5
4.2 利用resnet預訓練模型
class SVHN_resnet_Model(nn.Module):def __init__(self):super(SVHN_resnet_Model,self).__init__()resnet_conv = models.resnet18(pretrain=True)resnet_conv.avgpool = nn.AdaptiveAvgPool2d(1)resnet_conv = nn.Sequential(*list(resnet_conv.children()[:-1]))self.cnn = model_convself.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):cnn_result = cnn(x)cnn_result.view(cnn_result.shape[0],-1)f1 = fc1(cnn_result)f2 = fc2(cnn_result)f3 = fc3(cnn_result)f4 = fc4(cnn_result)f5 = fc5(cnn_result)return f1,f2,f3,f4,f5
—END— 聲明:部分內容來源于網絡,僅供讀者學術交流之目的。文章版權歸原作者所有。如有不妥,請聯系刪除。
往期精彩回顧
適合初學者入門人工智能的路線及資料下載 (圖文+視頻)機器學習入門系列下載 機器學習及深度學習筆記等資料打印 《統計學習方法》的代碼復現專輯
機器學習交流qq群955171419,加入微信群請掃碼



