本人只是出於興趣研究這個話題,本人並不是學者,或者從業者,因此可能在寫做中出現技術上的硬傷,請各位指正。
本文可能根據業界最新進展進行更新, 歡迎來評論中進行交流和研究。
本文所引用的資料將嚴格標明出處和版權。本文首發於簡書平臺,博客園和本人公衆號,請勿轉載html
卷積神經網絡在圖像識別領域無限風光,經過一張圖片,算法能夠知道圖片上的物體是什麼,着實使人震驚,可是不少人和我同樣,對於其背後的原理,都很是好奇,卷積神經網絡是如何進行圖像識別的呢?python
若是你的英文主夠好的話,能夠閱讀這篇論文:
Visualizing and Understanding Convolutional Networksgit
看過女神李飛飛的ImageNet演講的人,都對於下面兩張圖片印象深入。
原文請閱讀:
ImageNet締造者:讓冰冷的機器讀懂照片背後的故事github
(從薛定諤開始,貓就一直被各類科學家拿出來講事情,固然汪星人也時不時出鏡)算法
人類是如何識別貓咪的?借用知乎的一個回答:網絡
如今假設要作一個圖像的分類問題,好比辨別一個圖像裏是否有一隻貓,咱們能夠先判斷是否有貓的頭,貓的尾巴,貓的身子等等,若是這些特徵都具有,那麼我就斷定這應該是一隻貓。固然,若是圖像是下面這樣一隻老實本分的貓咪,則一切都好辦了。架構
可是喵星人不但品種不一樣,顏色繁多,各類銷魂的動做也層出不窮,因此,機器識別貓仍是很困難的。app
這樣,咱們必需要讓機器知道,貓,到底應該長成什麼樣子。iphone
第一次考慮怎麼處理這個問題,一個很天然的想法浮想在腦海裏面:
將全部貓咪的圖片放在一塊兒,提取出貓咪的共同特徵,作成一個識別貓的模型。而後對於每張圖片,使用模型,看一下是貓的機率爲多少。可是若是真的這樣作的話,可能每種物體都必需要有一個專門的模型了,這樣多是不行的,計算量可能也是一個問題。特別對於扭曲的貓,這樣子的例子很是難處理,咱們不太可能窮舉出全部貓的正常和非正常形態。(毛色,眼神,是否有物體和貓進行交互)機器學習
固然,能夠考慮,將貓進行分解,就如知乎網友所說,貓頭,貓尾巴,貓爪子,獨立進行識別。這樣無論貓怎麼扭曲,都無所謂了。固然,若是你是資深貓奴,你能夠很高興的說出貓的組成特徵,可是,這樣本質上仍是加入了太多的領域專家的干涉。若是要識別大型粒子加速器,這個是否是要請物理學家參與呢?因此,機器應該徹底屏蔽領域知識才能夠作到泛用。
雖然不是科班出身,可是之前或多或少看過一些圖像處理的書籍。
通常的圖像處理都是經過矩陣操做完成的。
具體的顏色矩陣文章:
C++圖像處理 -- 顏色矩陣變換
其實我認爲卷積核這個概念,應該是從圖像處理矩陣這個概念來的。經過不一樣的圖像處理矩陣,能夠突出圖像的某些特徵,屏蔽掉某些細節。
處理後的圖片,屏蔽了顏色,突出了輪廓特徵。(貓的輪廓特徵保留下來了,顏色特徵暫時消失了)
固然,實際處理的時候,可能使用的卷積核可能更加複雜。不過,若是真的看一下卷積核的工做方式,通常來講,卷積用來進行特徵的提取,而不是進行圖像的預處理的(或者說,是將圖像針對特徵進行壓縮的一個過程)。
上面所說的都大半是猜想,不管如何也應該看一下真實的算法究竟是怎麼樣的。圖像識別上最有名氣的算法大概就是Inception模型。整個算法的架構大概是這樣的,深度也是歎爲觀止。(當前ResNet神經網絡已經152層了,計算量至關至關至關可怕)
原始圖像通過了深深的流水線以後,最後在Softmax層進行分類。這個過程當中到底發生了什麼事情,圖像在Softmax層變成了什麼,這個多是全部人都關心的問題。本文也想經過長期的研究,能或多或少搞清楚裏面的奧祕。這個過程應該是極其艱苦的,很是困難的。可是對於機器學習的思考卻很是有幫助。
Google的Tensorflow已經在Github上開源了,找到了這樣的一個源代碼,因爲非科班出身,因此也沒法判定是否這個就是inception的源代碼了。暫時就以這個做爲對象進行研究了
https://github.com/tensorflow/models/tree/master/inception
而後按照ReadMe的指示看到如下的工程
https://github.com/tensorflow/models/tree/master/slim
最新的V3代碼在如下連接裏面
https://github.com/tensorflow/models/blob/master/slim/nets/inception_v3.py
分析源代碼的時候,能夠將上節的圖和代碼一塊兒觀看。(暫時沒有找到V4的圖片,因此,只能研究V3了。若是你們有興趣也能夠研究最牛逼的ResNet深度殘差網絡)
從代碼上看,整個深度網絡的結構體系多是這樣子的。從輸入端開始,先有3個卷積層,而後是1個pool層。而後又是2個卷積層,一個pool層。這個和上面那張神經網絡構造圖是徹底一致的。前3個是卷積層(黃色),而後是1個MaxPool(綠色),而後是2個卷積層,1個Maxpool。
後面的11個混合層(Mixed)具體的代碼還須要進一步檢查。
Here is a mapping from the old_names to the new names: Old name | New name ======================================= conv0 | Conv2d_1a_3x3 conv1 | Conv2d_2a_3x3 conv2 | Conv2d_2b_3x3 pool1 | MaxPool_3a_3x3 conv3 | Conv2d_3b_1x1 conv4 | Conv2d_4a_3x3 pool2 | MaxPool_5a_3x3 mixed_35x35x256a | Mixed_5b mixed_35x35x288a | Mixed_5c mixed_35x35x288b | Mixed_5d mixed_17x17x768a | Mixed_6a mixed_17x17x768b | Mixed_6b mixed_17x17x768c | Mixed_6c mixed_17x17x768d | Mixed_6d mixed_17x17x768e | Mixed_6e mixed_8x8x1280a | Mixed_7a mixed_8x8x2048a | Mixed_7b mixed_8x8x2048b | Mixed_7c
先看一下最前面的第1個卷積層,在繼續閱讀代碼以前,想去網絡上找一下關於slim的API資料,惋惜暫時沒有太多的資料。
TensorFlow-Slim@github
slim操做的源代碼
TF-Slimを使ってTensorFlowを簡潔に書く
從下面這個例子能夠看到,slim的conv2d構造的是一個激活函數爲Relu的卷積神經網絡。(其實slim估計和keras同樣,是一套高級的API函數,語法糖)
//使用TensorFlow的代碼 W_conv1 = weight_variable([5, 5, 1, 32]) b_conv1 = bias_variable([32]) h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) //使用slim的代碼 h_conv1 = slim.conv2d(x_image, 32, [5, 5])
第一個卷積層的輸入參數 299 x 299 x 3 :
# 299 x 299 x 3 end_point = 'Conv2d_1a_3x3' net = slim.conv2d(inputs, depth(32), [3, 3], stride=2, scope=end_point) end_points[end_point] = net if end_point == final_endpoint: return net, end_points
前面的299 x 299 表明的含義,在源代碼中能夠看到,是圖片的默認尺寸。(The default image size used to train this network is 299x299.)
後面一個3 表示深度Depth,原始的JPEG圖片的每一個像素具備RGB 3個不一樣的數值,在卷積層中則設置了3個通道。(這裏只是個人主觀推測而已)
而後看一下第一個卷基層自身的參數:
表示有32個不一樣的Filter(32套不一樣的參數,最終造成32個FeatureMap)。卷積核是 3 * 3 ,步長爲2。
(每一個Filter的深度也應該是3,若是要表示這個Filter的張量,應該是 3 x 3 x 3,高度,寬度,深度都是3)
在上面兩個公式中,W2是卷積後Feature Map的寬度;W1是卷積前圖像的寬度;F是filter的寬度;P是Zero Padding數量,Zero Padding是指在原始圖像周圍補幾圈0,若是的值是1,那麼就補1圈0;S是步幅;H2是卷積後Feature Map的高度;H1是卷積前圖像的高度。
按照公式能夠推導出卷積以後的Feature Map 爲 149 x 149
W2 = (299 - 3 + 2 * 0)/ 2 + 1 = 149
第一層的卷積輸出就是第二層的卷積輸入,因此第二層的第一行表示輸入的註釋是這樣的:
# 149 x 149 x 32 end_point = 'Conv2d_2a_3x3' net = slim.conv2d(net, depth(32), [3, 3], scope=end_point) end_points[end_point] = net if end_point == final_endpoint: return net, end_points
149 x 149 x 32 :卷積前的特徵圖(FeatureMap)的大小是149 x 149 ,一共有32個特徵圖。
若是再往下看代碼,會看到一個padding的參數設定
# 147 x 147 x 32 end_point = 'Conv2d_2b_3x3' net = slim.conv2d(net, depth(64), [3, 3], padding='SAME', scope=end_point) end_points[end_point] = net if end_point == final_endpoint: return net, end_points
padding有兩種參數能夠設定,分別是SAME和VALID:
What is the difference between 'SAME' and 'VALID' padding in tf.nn.max_pool of tensorflow?
If you like ascii art:
In this example:
Input width = 13
Filter width = 6
Stride = 5
Notes:
"VALID" only ever drops the right-most columns (or bottom-most rows).
"SAME" tries to pad evenly left and right, but if the amount of columns to be added is odd, it will add the extra column to the right, as is the case in this example (the same logic applies vertically: there may be an extra row of zeros at the bottom).
這個例子很清楚的解釋了兩個參數的含義。若是Input的寬度是13,卷積核寬度是6,步長是5的狀況下,VALID將只作2次卷積(1-6,6-11),第三次因爲寬度不夠(11-16,可是14,15,16缺失),就被捨棄了。SAME的狀況下,則自動在外層補零(Zero Padding),保證全部的元素都可以被卷積使用到。
注意:若是conv2d方法沒有特別設定padding,則須要看一下arg_scope是否標明瞭padding。
with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d], stride=1, padding='VALID'): # 299 x 299 x 3 end_point = 'Conv2d_1a_3x3' net = slim.conv2d(inputs, depth(32), [3, 3], stride=2, scope=end_point) end_points[end_point] = net if end_point == final_endpoint: return net, end_points # 149 x 149 x 32 end_point = 'Conv2d_2a_3x3' net = slim.conv2d(net, depth(32), [3, 3], scope=end_point) end_points[end_point] = net if end_point == final_endpoint: return net, end_points # 147 x 147 x 32 end_point = 'Conv2d_2b_3x3' net = slim.conv2d(net, depth(64), [3, 3], padding='SAME', scope=end_point) end_points[end_point] = net if end_point == final_endpoint: return net, end_points
注意:前三層默認是步長爲1,padding爲VALID。
如下文字,須要業內人士幫忙看一下是否正確:
輸入的時候,原始圖像大小是 299 x 299 的。
在圖像預處理的時候,根據 R G B 三個通道,將圖像分爲了3個深度。
這樣的話,輸入層是 高度299 寬度 299 深度3
第一個卷積層,因爲Depth是32,則認爲一共有32個深度爲3,高度和寬度爲3的Filter。步長爲2
卷積以後,結果爲32個特徵圖,高度和寬度爲149.
前面咱們已經講了深度爲1的卷積層的計算方法,若是深度大於1怎麼計算呢?其實也是相似的。若是卷積前的圖像深度爲D,那麼相應的filter的深度也必須爲D。咱們擴展一下式1,獲得了深度大於1的卷積計算公式:
無論深度爲多少,通過一個Filter,最後都經過上面的公式變成一個深度爲1的特徵圖。
下面的例子中,輸入層是高度和寬度是 7 x 7 ,深度是3.
兩個Filter的,每一個Filter的高度和寬度是 3 x 3 ,深度由於要和輸入層保持一致,因此也必須是 3
最左邊的輸入層(Input Volume)和Filter W0 進行計算(輸入的第一層和Filter的第一層進行運算,第二層和第二層進行運算,第三層和第三層進行運算,最後三層結果累加起來),得到了 Output Volume 的第一個結果(綠色的上面一個矩陣);和Filter W1 進行計算,得到了 Output Volume 的第二個結果(綠色的下面一個矩陣)。
訪問 //upload-images.jianshu.io/upload_images/2256672-958f31b01695b085.gif 觀看動態圖片
Pool是一個將卷積參數進行減小的過程,這裏是將 3 x 3 的區域進行步長爲2的Max的下采樣。
這裏一樣可使用步長和寬度的計算公式,得到輸出層的高度和寬度。
W2 = (147 - 3 + 2 * 0)/ 2 + 1 = 73
和卷積層相比,這裏就沒有什麼深度計算了。這裏只是單純的進行特徵圖的壓縮而已。
對於深度爲D的Feature Map,各層獨立作Pooling,所以Pooling後的深度仍然爲D。
# 147 x 147 x 64 end_point = 'MaxPool_3a_3x3' net = slim.max_pool2d(net, [3, 3], stride=2, scope=end_point) end_points[end_point] = net if end_point == final_endpoint: return net, end_points # 73 x 73 x 64 end_point = 'Conv2d_3b_1x1' net = slim.conv2d(net, depth(80), [1, 1], scope=end_point) end_points[end_point] = net if end_point == final_endpoint: return net, end_points
按照這個思路整理Inception V3的Mixed Layer以前的代碼,應該沒有什麼問題了。
# 299 x 299 x 3 end_point = 'Conv2d_1a_3x3' net = slim.conv2d(inputs, depth(32), [3, 3], stride=2, scope=end_point) end_points[end_point] = net if end_point == final_endpoint: return net, end_points # 149 x 149 x 32 end_point = 'Conv2d_2a_3x3' net = slim.conv2d(net, depth(32), [3, 3], scope=end_point) end_points[end_point] = net if end_point == final_endpoint: return net, end_points # 147 x 147 x 32 end_point = 'Conv2d_2b_3x3' net = slim.conv2d(net, depth(64), [3, 3], padding='SAME', scope=end_point) end_points[end_point] = net if end_point == final_endpoint: return net, end_points # 147 x 147 x 64 end_point = 'MaxPool_3a_3x3' net = slim.max_pool2d(net, [3, 3], stride=2, scope=end_point) end_points[end_point] = net if end_point == final_endpoint: return net, end_points # 73 x 73 x 64 end_point = 'Conv2d_3b_1x1' net = slim.conv2d(net, depth(80), [1, 1], scope=end_point) end_points[end_point] = net if end_point == final_endpoint: return net, end_points # 73 x 73 x 80. end_point = 'Conv2d_4a_3x3' net = slim.conv2d(net, depth(192), [3, 3], scope=end_point) end_points[end_point] = net if end_point == final_endpoint: return net, end_points # 71 x 71 x 192. end_point = 'MaxPool_5a_3x3' net = slim.max_pool2d(net, [3, 3], stride=2, scope=end_point) end_points[end_point] = net if end_point == final_endpoint: return net, end_points # 35 x 35 x 192.
原始的圖片大小是299 x 299 ,因爲有三元色,則深度爲 3.
通過一系列處理以後,尺寸變成了 35 * 35 ,深度則上升爲 192.
卷積使用的激活函數是Relu。Pooling使用的是 Max Pooling。
# mixed: 35 x 35 x 256. end_point = 'Mixed_5b' with tf.variable_scope(end_point): with tf.variable_scope('Branch_0'): branch_0 = slim.conv2d(net, depth(64), [1, 1], scope='Conv2d_0a_1x1') with tf.variable_scope('Branch_1'): branch_1 = slim.conv2d(net, depth(48), [1, 1], scope='Conv2d_0a_1x1') branch_1 = slim.conv2d(branch_1, depth(64), [5, 5], scope='Conv2d_0b_5x5') with tf.variable_scope('Branch_2'): branch_2 = slim.conv2d(net, depth(64), [1, 1], scope='Conv2d_0a_1x1') branch_2 = slim.conv2d(branch_2, depth(96), [3, 3], scope='Conv2d_0b_3x3') branch_2 = slim.conv2d(branch_2, depth(96), [3, 3], scope='Conv2d_0c_3x3') with tf.variable_scope('Branch_3'): branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3') branch_3 = slim.conv2d(branch_3, depth(32), [1, 1], scope='Conv2d_0b_1x1') net = tf.concat(axis=3, values=[branch_0, branch_1, branch_2, branch_3]) end_points[end_point] = net if end_point == final_endpoint: return net, end_points
通常作Pooling的時候,使用的是maxPooling,在Mixed層出現了avgPooling。
除了Max Pooing以外,經常使用的還有Mean Pooling——取各樣本的平均值。
可能avgpooling就是mean pooling吧。
tf.concat函數也是一個重點知識:這裏使用concat將張量進行鏈接。可是具體的鏈接形狀還須要進一步考證。
v3的架構圖,對於mixed的細節並非很清晰,因此這裏找了一張v4的架構圖來看一下。
看一下右下角,除了參數以外和咱們的Mixed模型很像了。(V4比V3更加的深)
inception結構具備3種不一樣的形式,(Mixed_5x,Mixed_6x,Mixed_7x),下面就是這3種形式的示例圖。
仔細觀察,這裏有兩個特色:
這裏使用了不少 1 x 1的卷積核。
一種簡單的解釋是用來降維。
For example, an image of 200200 with 50 features on convolution with 20 filters of 11 would result in size of 20020020.
可是,1*1卷積核的做用不只僅於此。
或者將 n x n 的卷積核改寫爲 n x 1 和 1 x n 。
v3一個最重要的改進是分解(Factorization),將7x7分解成兩個一維的卷積(1x7,7x1),3x3也是同樣(1x3,3x1),這樣的好處,既能夠加速計算(多餘的計算能力能夠用來加深網絡),又能夠將1個conv拆成2個conv,使得網絡深度進一步增長,增長了網絡的非線性,還有值得注意的地方是網絡輸入從224x224變爲了299x299,更加精細設計了35x35/17x17/8x8的模塊;
做者:無話可說
連接:https://www.zhihu.com/question/50370954/answer/138938524
來源:知乎
著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
在整個Mixed層的中間,能夠看到有一個分支塊。這個分支包含一個AvgPool層,兩個Conv層,和一個Fully Connect層,一個Softmax層。
這個層是用來幹什麼的呢?從代碼的註釋看:
Auxiliary Head logits 若是直譯的話:輔助用頭部洛基特概率。
這個東西的用法,在模型裏面沒法找到答案,那麼咱們看一下測試用代碼裏面是否是有答案。
https://github.com/tensorflow/models/blob/master/slim/nets/inception_v3_test.py
def testBuildEndPoints(self): batch_size = 5 height, width = 299, 299 num_classes = 1000 ... ... self.assertTrue('AuxLogits' in end_points) aux_logits = end_points['AuxLogits'] self.assertListEqual(aux_logits.get_shape().as_list(), [batch_size, num_classes])
這個看上去應該是用來作檢證的,看一下張量的形狀是否是和咱們預期的同樣。並無什麼特別的意義。
最後3層的理解應該是比較容易的。
def inception_v3(inputs, num_classes=1000, is_training=True, dropout_keep_prob=0.8, min_depth=16, depth_multiplier=1.0, prediction_fn=slim.softmax, spatial_squeeze=True, reuse=None, scope='InceptionV3'): # Final pooling and prediction with tf.variable_scope('Logits'): kernel_size = _reduced_kernel_size_for_small_input(net, [8, 8]) net = slim.avg_pool2d(net, kernel_size, padding='VALID', scope='AvgPool_1a_{}x{}'.format(*kernel_size)) # 1 x 1 x 2048 net = slim.dropout(net, keep_prob=dropout_keep_prob, scope='Dropout_1b') end_points['PreLogits'] = net # 2048 logits = slim.conv2d(net, num_classes, [1, 1], activation_fn=None, normalizer_fn=None, scope='Conv2d_1c_1x1') if spatial_squeeze: logits = tf.squeeze(logits, [1, 2], name='SpatialSqueeze') # 1000 end_points['Logits'] = logits end_points['Predictions'] = prediction_fn(logits, scope='Predictions')
這個層的做用是隨機除去一些神經元,使得整個模型不至於過擬合。
至於爲何這樣作可以防止過擬合,網絡上有不少說明文檔,這裏就再也不囉嗦了。
這裏通常選擇keep_prob = 0.8 (這個參數值在代碼中定義,能夠修改),保留80%的神經元。至於爲何是0.8,這個應該是不少實驗得出的結果。
理解dropout
全鏈接層,在整個過程的最後,才使用全鏈接,訓練出權重。
(僅僅這裏進行訓練權重?仍是filter也須要訓練?)
這個神經網絡的最後是softmax層。softmax層也就是分類專用的層,使用一個機率來表示待分類對象有多大機率屬於某個類。
最後的機率矩陣看上去應該是這個樣子的。
本章節參照了zhihu.com的內容。因此我完整引用,不進行任何修改。版權歸原做者全部
你眼睛真實看到的圖像實際上是上圖的下半部分。然後通過大腦的層層映射後纔出現了你腦中所「看見」的圖像。CNN的卷積層部分能夠理解成是學習你的「眼球結構」。
同一個filter內部的權重是相同的,由於它用一個「抓取方式」去偵測特徵。好比說「邊緣偵測」。 你也注意到了,咱們的眼睛不僅觀看一次,等到掃描完該特徵後,另外一個filter能夠改變「抓取方式」去偵測另外一個特徵。所權重在同一個filter內是共享的理解是該filter對整個圖片進行了某個特徵的掃描。
提取若干個特徵後,就能夠靠這些特徵來判斷圖片是什麼了。
1.filter的選擇問題,各個filter是怎麼肯定內部的值的?
2.訓練到底只是訓練最後的全鏈接層,仍是整個神經網絡?
未完待續
卷積神經網絡工做原理直觀的解釋?
[透析] 卷積神經網絡CNN到底是怎樣一步一步工做的?
TF-Slimを使ってTensorFlowを簡潔に書く
深刻淺出——網絡模型中Inception的做用與結構全解析
零基礎入門深度學習(4) - 卷積神經網絡
cs231n學習筆記-CNN-目標檢測、定位、分割
A Note to Techniques in Convolutional Neural Networks and Their Influences III (paper summary)
理解dropout
CNN卷積神經網絡架構綜述