導讀
本文主要是對CNN和RNN的理解,通過對比總結(jié)各自的優(yōu)勢,同時加深自己對這方面知識的理解,其中代碼引用采用的是VQA模型中對圖像和文本的處理。
CNN是一種利用卷積計算的神經(jīng)網(wǎng)絡。它可以通過卷積計算將原像素很大的圖片保留主要特征變成很小的像素圖片。本文以李宏毅老師ppt內(nèi)容展開具體介紹。
①為什么引入CNN ?
圖片示意:給定一個圖片放入全連接神經(jīng)網(wǎng)絡,第一個hidden layer識別這張圖片有沒有綠色出現(xiàn)?有沒有黃色出現(xiàn)?有沒有斜的條紋?第二個hidden layer結(jié)合第一個hidden layer的輸出實現(xiàn)更復雜的功能,例如:如圖如果看到直線+橫線就是框框一部分,如果看到棕色+條紋就是木紋,如果看到斜條紋+綠色就是灰色類條紋一部分。再根據(jù)第二個hidden layer輸出結(jié)果,如果某個神經(jīng)元看到蜂巢就會activate,某個神經(jīng)元如果看到人就會activate。
但是我們?nèi)绻话愕赜胒ully network(全連接)神經(jīng)網(wǎng)絡處理的話,我們會需要很多的參數(shù),例如如果input的vector是個30000維,第一個hidden layer假設是1000個神經(jīng)元,那么第一個hidden layer就會30000*1000個,數(shù)據(jù)量非常大,導致計算效率和準確率效果低,而引入CNN,主要就是解決我們這些問題,簡化我們神經(jīng)網(wǎng)絡架構(gòu)。因為 某些weight我們是用不到的,CNN會使用過濾手段(filter)將一些不需要的參數(shù)過濾掉,保留一些重要的參數(shù)做圖像處理。
②為什么使用比較少的參數(shù)就足夠進行圖像處理 ?
三個特性:
不同位置的鳥嘴只需要訓練一個識別鳥嘴的參數(shù)就Ok了,不需要分別訓練。
我們可以采用子樣品來使圖片變小,子樣不會改變目標圖像。
1.1節(jié)的前兩個property需要卷積計算,后一個池化層處理,具體下節(jié)介紹。
矩陣卷積計算如下:
計算如下:圖像傳入的是553的像素值,經(jīng)過padding=1的填充構(gòu)成773的像素值,卷積核是333的大小,2個卷積核,步數(shù)stride=2。注意這里的卷積核深度要核傳入過來的像素值深度相同,當每次掃描到的藍色位置數(shù)對應卷積核紅色位置數(shù)位置相乘后相加得到綠色位置的數(shù)據(jù)。
像素數(shù)變化為:(n+2p-f)\s = (5+2-3)\2 + 1= 3得到332的數(shù)據(jù),經(jīng)過卷積輸出的像素深度等于上一層輸入的卷積核數(shù)目。將得的結(jié)果作為池化層的輸入值。
卷積核大小選取往往是奇數(shù)。深度要和它的上一層輸出像素值深度相同,如動圖卷積核的選取是3*3*3,而輸出像素值深度=本次卷積的卷積核數(shù)。這點不要混淆。
其實卷積就是將全連接層的一些weight拿掉,卷積層計算輸出的結(jié)果,其實就是全連接層的hidden layer輸出的結(jié)果。如下圖所示:
卷積沒有考慮輸入的全部特征,只和filter中經(jīng)過的特征有關(guān),如下:6*6的圖像展開成36pixel,然后下一層只和輸入層的9個pixel有關(guān),沒有連接全部,這樣就會使用很少的參數(shù),具體圖解如下:
由上圖也可發(fā)現(xiàn),計算結(jié)果3和-1不像全連接網(wǎng)絡那樣全部都需要不同的weight,而這個3和-1的部分計算的weight相同,這就是所謂的參數(shù)共享(權(quán)值共享)
根據(jù)前一個卷積計算后的矩陣,再進行池化處理(將一個框框內(nèi)的四個值合并一個值,可取最大值或者平均值),如下圖:
主要使用pytorch框架來介紹卷積神經(jīng)網(wǎng)絡。
源代碼:
torch.nn.Conv2d(
in_channels: int, #輸入圖像的通道數(shù)
out_channels: int, #卷積產(chǎn)生的輸出通道數(shù)
kernel_size: Union[T, Tuple[T, T]], #卷積核大小
stride: Union[T, Tuple[T, T]] = 1, #卷積的步數(shù) 默認:1
padding: Union[T, Tuple[T, T]] = 0, #0填充添加到輸入的兩邊 默認:0
dilation: Union[T, Tuple[T, T]] = 1, #核元素之間的間距 默認:1
groups: int = 1, #從輸入通道到輸出通道的阻塞鏈接數(shù),默認:1
#groups:控制輸入和輸出之間的連接,輸入和輸出通道數(shù)必須被組整除,
#當groups=1:所有輸入移交給所有輸出
#當groups=2:相當于兩個卷積層,一個看到一半的輸入通道并產(chǎn)生一半的輸出通道,將兩個合并
#當groups=in_channels:每個通道都有自己的一組過濾器,其大小為out_channel/in_channel
bias: bool = True, #將可學習的偏差添加到輸出中 默認:true
padding_mode: str = 'zeros')
#注:kenerl_size,stride,padding,dilation參數(shù)類型可以是int,可以是tuple,當是tuple時,第一個int是高度維度,第二個是寬度維度。當是單個int時,高度寬度值相同
# 方形核和等步幅
m = nn.Conv2d(16, 33, 3, stride=2)
# 非方形核和不等步幅和填充
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2))
# 非方形核和不等步幅和填充和擴展
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1))
input = torch.randn(20, 16, 50, 100)
output = m(input)
應用:此處采用的是VGG16,順便介紹以下pytorch的torchsummary庫,可以把網(wǎng)絡模型打印出來,例如:
import torchvision.models as models
import torch.nn as nn
import torch
from torchsummary import summary
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = models.vgg16(pretrained=True).to(device)
print(model)
輸出:
VGG(
(features): Sequential(
(0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU(inplace)
(2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(3): ReLU(inplace)
(4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(6): ReLU(inplace)
(7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(8): ReLU(inplace)
(9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(11): ReLU(inplace)
(12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(13): ReLU(inplace)
(14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(15): ReLU(inplace)
(16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(18): ReLU(inplace)
(19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(20): ReLU(inplace)
(21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(22): ReLU(inplace)
(23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(25): ReLU(inplace)
(26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(27): ReLU(inplace)
(28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(29): ReLU(inplace)
(30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(classifier): Sequential(
(0): Linear(in_features=25088, out_features=4096, bias=True)
(1): ReLU(inplace)
(2): Dropout(p=0.5)
(3): Linear(in_features=4096, out_features=4096, bias=True)
(4): ReLU(inplace)
(5): Dropout(p=0.5)
(6): Linear(in_features=4096, out_features=1000, bias=True)
)
)
model.classifier = nn.Sequential(
*list(model.classifier.children())[:-1]) # remove last fc layer
print(model)
summary(model,(3,224,224))
輸出:
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 64, 224, 224] 1,792
ReLU-2 [-1, 64, 224, 224] 0
Conv2d-3 [-1, 64, 224, 224] 36,928
ReLU-4 [-1, 64, 224, 224] 0
MaxPool2d-5 [-1, 64, 112, 112] 0
Conv2d-6 [-1, 128, 112, 112] 73,856
ReLU-7 [-1, 128, 112, 112] 0
Conv2d-8 [-1, 128, 112, 112] 147,584
ReLU-9 [-1, 128, 112, 112] 0
MaxPool2d-10 [-1, 128, 56, 56] 0
Conv2d-11 [-1, 256, 56, 56] 295,168
ReLU-12 [-1, 256, 56, 56] 0
Conv2d-13 [-1, 256, 56, 56] 590,080
ReLU-14 [-1, 256, 56, 56] 0
Conv2d-15 [-1, 256, 56, 56] 590,080
ReLU-16 [-1, 256, 56, 56] 0
MaxPool2d-17 [-1, 256, 28, 28] 0
Conv2d-18 [-1, 512, 28, 28] 1,180,160
ReLU-19 [-1, 512, 28, 28] 0
Conv2d-20 [-1, 512, 28, 28] 2,359,808
ReLU-21 [-1, 512, 28, 28] 0
Conv2d-22 [-1, 512, 28, 28] 2,359,808
ReLU-23 [-1, 512, 28, 28] 0
MaxPool2d-24 [-1, 512, 14, 14] 0
Conv2d-25 [-1, 512, 14, 14] 2,359,808
ReLU-26 [-1, 512, 14, 14] 0
Conv2d-27 [-1, 512, 14, 14] 2,359,808
ReLU-28 [-1, 512, 14, 14] 0
Conv2d-29 [-1, 512, 14, 14] 2,359,808
ReLU-30 [-1, 512, 14, 14] 0
MaxPool2d-31 [-1, 512, 7, 7] 0
Linear-32 [-1, 4096] 102,764,544
ReLU-33 [-1, 4096] 0
Dropout-34 [-1, 4096] 0
Linear-35 [-1, 4096] 16,781,312
ReLU-36 [-1, 4096] 0
Dropout-37 [-1, 4096] 0
================================================================
Total params: 134,260,544
Trainable params: 134,260,544
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.57
Forward/backward pass size (MB): 218.58
Params size (MB): 512.16
Estimated Total Size (MB): 731.32
----------------------------------------------------------------
每個訂票系統(tǒng)都會有一個空位填充(slot filling),有些slot是Destination,有些slot是time of arrival,系統(tǒng)要知道哪些詞屬于哪個slot;例如:
I would like to arrive Taipei on November 2nd;
這里的Taipei就是Destination,November 2nd就是time of arrival;
采用普通的神經(jīng)網(wǎng)絡,將Taipei這個詞丟入到網(wǎng)絡中,當然在丟入之前要將其轉(zhuǎn)化成向量表示,如何表示成向量?方法很多此處采用:1-of-N Encoding,表示方式如下:
其他詞向量方式如下:
但是,如果有如下情況,系統(tǒng)就會出錯。
問: 怎么辦?使有時輸入Taipei時,輸出dest概率高,有時輸入Taipei時輸出出發(fā)地概率高?
答: 此時就要使我們的網(wǎng)絡有“記憶”,能記住前面輸入的數(shù)據(jù)。例如:Taipei是目的地時看到了arrive,Taipei是出發(fā)地時看到了leave。那么這種有記憶的網(wǎng)絡就叫做:Recurrent Neural Network(RNN)
RNN的隱藏層的輸出會存儲在內(nèi)存中,當下次輸入數(shù)據(jù)時會使用到內(nèi)存中存儲的上次的輸出。圖解如下:
圖中,同樣的weight用同樣的顏色表示。當然hidden layer可以有很多層;以上介紹的RNN是最簡單的,接下來介紹加強版的LSTM;
現(xiàn)在常用的內(nèi)存(memory)是Long short-term內(nèi)存。
當外部信息需要輸入到memory時需要一個“閘門”——input gate,而input gate什么時候打開和關(guān)閉是被神經(jīng)網(wǎng)絡學到的,同理output gate也是神經(jīng)網(wǎng)絡學習的,forget gate也是如此。
所以LSTM有四個input,1個output。簡圖如下:
公式如下:
圖中x2 = 1將x1送入memory中,如果x2=-1就清空memory的數(shù)值,如果x3=1,就將memory中的數(shù)據(jù)輸出。如上圖,第一列,x2=0不送入memory,第二列x2=1,將此刻x1=3送入memory中(注意memory中的數(shù)據(jù)是x1的累加,例如第四列,x2=1,此時有x1=4,memory中=3,所以一起就是7)第五列發(fā)現(xiàn)x3=1,可以輸出,所以輸出memory中的值7.
結(jié)合LSTM簡圖如下:
假設進來的是第一列:x1=3,x2=1,x3=0步驟:g—Tanh: x1w1+x2w2+x3w3 = 3f—sigmod: x1w1+x2w2+x3w3=90 sigmod后=1算好f和g后傳入input gate=3*1=3,forget gate = 1,代表不需要清0,x3=0,代表output gate鎖上,輸出的還是0。
pytorch中封裝好了LSTM網(wǎng)絡,直接采用nn.lstm即可使用,例如
class QstEncoder(nn.Module):
def __init__(self, qst_vocab_size, word_embed_size, embed_size, num_layers, hidden_size):
super(QstEncoder, self).__init__()
self.word2vec = nn.Embedding(qst_vocab_size, word_embed_size)
self.tanh = nn.Tanh()
self.lstm = nn.LSTM(word_embed_size, hidden_size, num_layers)
self.fc = nn.Linear(2*num_layers*hidden_size, embed_size) # 2 for hidden and cell states
def forward(self, question):
qst_vec = self.word2vec(question) # [batch_size, max_qst_length=30, word_embed_size=300]
qst_vec = self.tanh(qst_vec)
qst_vec = qst_vec.transpose(0, 1) # [max_qst_length=30, batch_size, word_embed_size=300]
_, (hidden, cell) = self.lstm(qst_vec) # [num_layers=2, batch_size, hidden_size=512]
qst_feature = torch.cat((hidden, cell), 2) # [num_layers=2, batch_size, 2*hidden_size=1024]
qst_feature = qst_feature.transpose(0, 1) # [batch_size, num_layers=2, 2*hidden_size=1024]
qst_feature = qst_feature.reshape(qst_feature.size()[0], -1) # [batch_size, 2*num_layers*hidden_size=2048]
qst_feature = self.tanh(qst_feature)
qst_feature = self.fc(qst_feature) # [batch_size, embed_size]
return qst_feature
CNN與RNN區(qū)別鏈接如下,引用了這個博客作者總結(jié),(https://blog.csdn.net/lff1208/article/details/77717149)具體如下。
為了克服梯度消失,ReLU、maxout等傳輸函數(shù)代替了sigmoid,形成了如今DNN的基本形式。結(jié)構(gòu)跟多層感知機一樣,如下圖所示:
我們看到全連接DNN的結(jié)構(gòu)里下層神經(jīng)元和所有上層神經(jīng)元都能夠形成連接,從而導致參數(shù)數(shù)量膨脹。假設輸入的是一幅像素為1K*1K的圖像,隱含層有1M個節(jié)點,光這一層就有10^12個權(quán)重需要訓練,這不僅容易過擬合,而且極容易陷入局部最優(yōu)。
由于圖像中存在固有的局部模式(如人臉中的眼睛、鼻子、嘴巴等),所以將圖像處理和神將網(wǎng)絡結(jié)合引出卷積神經(jīng)網(wǎng)絡CNN。CNN是通過卷積核將上下層進行鏈接,同一個卷積核在所有圖像中是共享的,圖像通過卷積操作后仍然保留原先的位置關(guān)系。
通過一個例子簡單說明卷積神經(jīng)網(wǎng)絡的結(jié)構(gòu)。假設我們需要識別一幅彩色圖像,這幅圖像具有四個通道ARGB(透明度和紅綠藍,對應了四幅相同大小的圖像),假設卷積核大小為100*100,共使用100個卷積核w1到w100(從直覺來看,每個卷積核應該學習到不同的結(jié)構(gòu)特征)。
用w1在ARGB圖像上進行卷積操作,可以得到隱含層的第一幅圖像;這幅隱含層圖像左上角第一個像素是四幅輸入圖像左上角100*/100區(qū)域內(nèi)像素的加權(quán)求和,以此類推。
同理,算上其他卷積核,隱含層對應100幅“圖像”。每幅圖像對是對原始圖像中不同特征的響應。按照這樣的結(jié)構(gòu)繼續(xù)傳遞下去。CNN中還有max-pooling等操作進一步提高魯棒性。
注意到最后一層實際上是一個全連接層,在這個例子里,我們注意到輸入層到隱藏層的參數(shù)瞬間降低到了100100100=10^6個!這使得我們能夠用已有的訓練數(shù)據(jù)得到良好的模型。題主所說的適用于圖像識別,正是由于CNN模型限制參數(shù)了個數(shù)并挖掘了局部結(jié)構(gòu)的這個特點。順著同樣的思路,利用語音語譜結(jié)構(gòu)中的局部信息,CNN照樣能應用在語音識別中。
DNN無法對時間序列上的變化進行建模。然而,樣本出現(xiàn)的時間順序?qū)τ谧匀徽Z言處理、語音識別、手寫體識別等應用非常重要。為了適應這種需求,就出現(xiàn)了大家所說的另一種神經(jīng)網(wǎng)絡結(jié)構(gòu)——循環(huán)神經(jīng)網(wǎng)絡RNN。
在普通的全連接網(wǎng)絡或CNN中,每層神經(jīng)元的信號只能向上一層傳播,樣本的處理在各個時刻獨立,因此又被成為前向神經(jīng)網(wǎng)絡(Feed-forward Neural Networks)。而在RNN中,神經(jīng)元的輸出可以在下一個時間段直接作用到自身,即第i層神經(jīng)元在m時刻的輸入,除了(i-1)層神經(jīng)元在該時刻的輸出外,還包括其自身在(m-1)時刻的輸出!表示成圖就是這樣的:
為方便分析,按照時間段展開如下圖所示:
(t+1)時刻網(wǎng)絡的最終結(jié)果O(t+1)是該時刻輸入和所有歷史共同作用的結(jié)果!這就達到了對時間序列建模的目的。RNN可以看成一個在時間上傳遞的神經(jīng)網(wǎng)絡,它的深度是時間的長度!正如我們上面所說,“梯度消失”現(xiàn)象又要出現(xiàn)了,只不過這次發(fā)生在時間軸上。
所以RNN存在無法解決長時依賴的問題。為解決上述問題,提出了LSTM(長短時記憶單元),通過cell門開關(guān)實現(xiàn)時間上的記憶功能,并防止梯度消失,LSTM單元結(jié)構(gòu)如下圖所示:
除了DNN、CNN、RNN、ResNet(深度殘差)、LSTM之外,還有很多其他結(jié)構(gòu)的神經(jīng)網(wǎng)絡。如因為在序列信號分析中,如果我能預知未來,對識別一定也是有所幫助的。因此就有了雙向RNN、雙向LSTM,同時利用歷史和未來的信息。
事實上,不論是哪種網(wǎng)絡,他們在實際應用中常常都混合著使用,比如CNN和RNN在上層輸出之前往往會接上全連接層,很難說某個網(wǎng)絡到底屬于哪個類別。不難想象隨著深度學習熱度的延續(xù),更靈活的組合方式、更多的網(wǎng)絡結(jié)構(gòu)將被發(fā)展出來。
簡單總結(jié)如下:
聯(lián)系客服