# 三個YOLO重要的影像處理步驟 1. Resize輸入的圖到448*448 2. 執行一個卷積神經網路 3. 基於模型輸出的信心程度(Confidence)依據閾值和Non-max suppression得到偵測結果 ![](https://i.imgur.com/lvJu4kr.png) # YOLO用的卷積神經網路 YOLO的卷積網路架構是來自GoogleNet的模型,YOLO的網路有24卷積層(convolutional layer)和2層全連結層(fully connected layer),和GoogleNet不同的地方在於作者在某些3×3的卷積層前面用1×1的卷積層來減少filter數量,(1×1的卷積層通常拿來做降維度的作用,可以減低計算量,且不影響太多mAP)。整體架構如下圖: ![](https://i.imgur.com/gBdPVjB.png) # 卷積神經網路(Convolutional neural network, CNN): 1×1卷積計算 一般API在跑張量(tensor),通常是一個批次量的資料在執行 假設有100筆資料,每個資料都是10*10的彩色圖片(所以每個資料都是3張圖片,分別為R、G、B三張),此時的輸入資料的大小就是100 ×10 ×10 ×3,解讀方式batch ×height × width × channel,這個解讀很重要,因為除了影像的長跟寬之外,又多了兩個分別是batch和channel名詞。 Channel這個名詞非常重要,可以解讀為維度(dimension),但又不完全一樣,因為這個東西在卷積計算後的數量是跟你kernel map設定數量有關係,所以很重要,因為等等會講到1×1的捲積。 本例子假設輸入一張影像(R, G, B)所以有3個channel的4×4影像輸入,在1st卷積層(Conv. 1)設定2個kernel map (3×3×3(上一層的channel數)),所以1st卷積層(Conv. 1)輸出的影像就會有2張,在2nd卷積層(Conv. 2)設定3個kernel map (3×3×2(上一層的channel數)),所以2nd卷積層(Conv. 2)輸出的影像就會有3張 1st卷積層(Conv. 1) 設定2個3×3的kernel map,但卷積層Kernel的深度是跟輸入channel有關,所以Kernel Map的深度為3,每個kernel map實際大小為3×3×3(上一層的channel數),因此每個channel會各自對Kernel Map的不同深度各自做卷積,最後三個卷積結果再作Pixel-wise sum,所以1st卷積層(Conv. 1)輸出的影像就會有2張(4×4)。 此例: 1st卷積結果=Image( R )*kernel map(:,:,1)+ Image(G)*kernel map(:,:,2)+ Image(B)*kernel map(:,:,3) 2nd卷積層(Conv. 2) 設定3個3×3的kernel map,但卷積層Kernel的深度是跟輸入channel有關,所以Kernel Map的深度為2,每個kernel map實際大小為3×3×2(上一層的channel數),所以2nd卷積層(Conv. 2)輸出的影像就會有3張(4×4)。 ***所以可以知道卷積後的channel數是依據你設定的kernel map數量決定的。*** ![](https://i.imgur.com/83ZEVtY.png) 此圖例的主要是要視覺化說明卷積在設定不同filter數量時,kernel map和輸入輸出的feature map變化,所以我是假設輸入和輸出feature map是一樣大,實質上此例的卷積運算pad=1 (因為kernel size=3),這樣卷積後輸出的feature map大小才會跟輸入一樣。 ## 1×1捲積最大的好處是在作降維或是提高維度用 這邊的維度就是前面提到的channel 1×1卷積實際作法(我自認為比較好理解)如下圖: ![](https://i.imgur.com/nB6PFiy.png) 此圖例的主要是要視覺化說明卷積在設定不同filter數量時,kernel map和輸入輸出的feature map變化,所以我是假設輸入和輸出feature map是一樣大,實質上此例的卷積運算pad=1 (因為kernel size=3),這樣卷積後輸出的feature map大小才會跟輸入一樣。 ***所以1×1卷積其實就是作channel之間的合成*** 然後根據你設定的1×1 kernel map數量(也就是輸出的channel數量),然後輸出一個大小一樣但channel數不同的輸出。 從上例子得知 第一個1×1捲積是提升維度數/提升channel數 (channel從3→6) 原始圖片(4×4×3) → Conv 1 (6個1×1 ×3kernel ) → Conv1輸出圖片(4×4×6) : 第二個1×1捲積是降低維度數/降低channel數 (channel從6→2) Conv1輸出圖片(4×4×6) → Conv 2 (2個1×1×6 kernel ) → Conv2輸出圖片(4×4×2) 這邊例子可能沒有什麼感覺,但實際應用可能channel數會到128,此時如果不希望模型繼續大下去就可以用32個1×1捲積做降到32個channel的動作,之後可以在接著用3×3捲積進行特徵萃取的學習 # 卷積神經網路(Convolutional neural network, CNN) — 卷積運算、池化運算 卷積神經網路(Convolutional neural network, CNN)一開始就是提在影像上的模型,裡面用到的卷積部份(Convolution)和池化部份(Pooling)會和像素有相關 將影像進行特徵萃取的方法就是將影像進行濾波(filter),得到更有用的資訊,比如用在邊緣(Edge Detection)偵測的derivative mask(遮罩)有Prewitt operator,其遮罩設計出來後,利用卷積運算達到濾波影像的功能,得到只有邊的圖片,下圖就是用Prewitt operator水平和垂直邊緣偵測用卷積運算得到的結果 ![](https://i.imgur.com/NsmFgv8.png) ## CNN基礎介紹-卷積運算(Convolution) 假設大家知道什麼是灰階影像(8位元) 下圖是一個數字0(像素值為10*10=100),人的眼睛看到的是左圖,電腦看到的是右圖 ![](https://i.imgur.com/nuQbqkn.png) 濾波影像 = 影像*Operator mask (note: * 不是乘號是卷積運算) 這邊亂設計一個Operator mask(大小3x3) ![](https://i.imgur.com/3LnDdOn.png) 這邊開始對每個像素做卷積運算,圖片中紅色框起來的部份會和Operator mask進行點跟點相乘,最後在全部相加得到結果,這個步驟就是卷積運算。 ![](https://i.imgur.com/Aka2N2f.png) 整張圖的濾波就是每個位置都會運算到,運算方式一般都是從左上角開始計算,然後橫向向右邊移動運算,到最右邊後在往下移一格,繼續向右邊移動運算,直到整張圖都完成,如下圖。 ![](https://i.imgur.com/FgRmme9.png) ![](https://i.imgur.com/QAplYBq.png) 最左邊是原始影像,經由中間的mask去做卷積後,可以得到最右邊那張圖。 ![](https://i.imgur.com/zHwJAry.png) Note: 1. 大家有沒有意識到整張圖掃過一遍後,圖的大小會變小,從原本的10x10變成8x8。如果你不想濾波完,圖變小,當然還有很多技巧(例如:zero padding),比如先將圖擴大到12x12其他部份先塞0進去,在進行一次卷積運算得到10x10的圖。 2. 從來沒有人說mask在移動的時候只能移動一格,所以在用開源模組時,Convolution部份會有一個參數叫strides可以設定,看你一次想移動幾格。 ## CNN基礎介紹-池化(Pooling) 池化目的只是在將圖片資料量減少並保留重要資訊的方法,把原本的資料做一個最大化或是平均化的降維計算。取一個2x2的最大池化法(max pooling)當作例子 ![](https://i.imgur.com/ANYX9qI.png) 所以整個圖片做池化的方式如下圖,原本8x8的圖片因為我取2x2的池化,所以會變成4x4 ![](https://i.imgur.com/07FGufM.png) 當然池化法除了最大化池化法外,也可以做平均池化法(取最大部份改成取平均)、最小化池化法(取最大部份改成取最小化)等。 ![](https://i.imgur.com/VWgIENa.png) 從上圖可知max pooling後整張圖等於白的,所以此例就比較不適合用max pooling的方式。 Note: 1. 圖的大小很容易因為pooling變得很小,2x2的Pooling會讓圖小一半,3x3的pooling小3分之1。所以跟卷積運算一樣,Pooling也可以用zero padding和strides的方式,讓圖不要一次變太小。 # 卷積神經網路(Convolutional neural network, CNN) — CNN運算流程 CNN運作時的細節部分:先舉一個8x8的圖要分成兩類的範例來介紹。此CNN範例只做一次卷積(2個kernel maps)、一次池化(Pooling)、和一個全聯節層(Fully connection,hidden layer 1層,output為兩類,所以output nodes為兩個),結構如下: ![](https://i.imgur.com/em61FZq.png) ![](https://i.imgur.com/pdCOQ8S.png) 範例CNN的示意圖,第二層出現的Map 1和Map 2為Kernel Maps用來做卷積運算,激活函數我先不放到圖片去講,因為他只是在feature map部份做個非線性轉換。 ## 卷積部份(Convolution): 卷積運算的mask一般稱為kernel map,其數量是可以調整的,在此舉兩個3x3的kernel maps。 一般在影像上,捲積運算後會再加上激活函數(activation function),進行非線性轉換,之後得到的圖片會稱為特徵圖(feature map)。 Feature map{i} = original Data * Kernel Map{i} (*為卷積運算) ## 池化部份(Pooling): 池化法會根據feature map的結果去做pooling,然後得到的就是降維的特徵圖。此例為2x2的Pooling。 ## Flatten部份: 因為做完卷積運算和池化法後得到的特徵圖還是一個2-D的圖片,到全連接層前要先轉成1-D的陣列。(如果做完卷積或是池化後結構是1x1的feature map,此步驟可以省略) ![](https://i.imgur.com/NupbnO0.png) ## 全連接層部份(Fully connection): 這邊等於一般神經網路,請參照: https://hackmd.io/@wayne0509/ryTVXpmMw Input node: Flatten後的結果,此例為18個nodes。 Hidden layer: 1層 5個nodes Output node: 2個輸出結果。 # 解析CNN 卷積神經網路實線的重點之一就是權重共享(Shared Weight)這件事情 ***權重共享(Shared Weight)就是假設每個輸出的結果權重都是一樣的,這樣就大幅簡少訓練模型時的參數。*** 在卷積部份參數量是根據設計的kernel map數量和大小決定的,此範例為(3*3)*2=18 (此篇先不考慮bias存在) 從全連接層可以得知一般的神經網路(MLP/DNN),在node之間是有完全連線起來的,每一條線上都有ㄧ個參數(weight)需要靠訓練得到。所以全連接層所有weight數為Input node*hidden node+hidden node*output node=18*5+5*2=100 ![](https://i.imgur.com/xjx7O6d.png) 神經網路的學習網路架構框好後 ***所有參數/weight都是靠資料學習推導來的,不是try出來的。*** 早期影像方面的算法(機器學習)都是 影像→特徵擷取(方法有SIFT、HOG等)→分類。 ***特徵擷取(方法有SIFT、HOG等)是try出來的***。問題是特徵擷取這部份需要有非常專業的專家知識,必須知道什麼是對此分類問題重要的特徵,從影像中擷取出來後在做分類。但事實上專家的知識也是有盲點的,從大量資料(big data)中或許可以突破這個盲點,進而早到更合適的特徵。 因此深度學習就是可以克服這個問題,其結構為 影像→深度學習(特徵擷取+分類)。 ***卷積神經網路中的「卷積」就是特徵擷取的方法***,此部份必須靠大量資料的特性不斷的反覆學習,在學習中盡可能滿足設立的條件,達到目標。所以在用深度學習時通常希望訓練樣本數可以非常大,這樣電腦在學習Kernel map時才能靠著的optimizer(通常用Stochastic Gradient Descent,SGD)利用倒傳遞(backpropagation)學習更有效得到適合的答案。 note: 如果樣本數小就必須依靠前人的貢獻來達到相同的目的(關鍵字:遷移學習,transfer learning) # 卷積計算的倒傳遞推導方式 卷積計算的forward pass和backward pass計算就如同一般 https://hackmd.io/@wayne0509/ryTVXpmMw 的倒傳遞計算方式一樣都是如下圖: ![](https://i.imgur.com/tuXBlrI.png) 差別在於x部份與w部份,MLP在x部份通常是一個1D array的input,w則是全連結權重(fully connection)。 而在卷積計算(一般影像的卷積)部份: x: 影像(2D array) w: kernel map h: output 卷積計算通常是實現局部連結(local connection) 這篇文張會舉一個影像大小是3*3,kernel map為2*2的例子來說明。 影像: ![](https://i.imgur.com/vu6C8ST.png) Kernel map: ![](https://i.imgur.com/v4iLing.png) **Forward pass:** 卷積計算,stride=1如下: *: convolution ![](https://i.imgur.com/k5mG3sh.png) ![](https://i.imgur.com/XGBSKE1.png) ![](https://i.imgur.com/h6Q5R68.png) **Backward pass:** ![](https://i.imgur.com/n52tNuY.png) 因為這邊需要學習的參數是kernel map(w)內的元素(wij),所以需要對卷積後的輸出和下一層的loss function(L)作偏微分(denote: ∂L / ∂w),可以參考 https://hackmd.io/@wayne0509/ryTVXpmMw 那篇文章,然後因為∂L / ∂w無法直接計算,利用chain rule將其拆成兩項 ![](https://i.imgur.com/LxcUw3Q.png) 此時,∂L / ∂h和 ∂h / ∂w比較容易計算,如下: ![](https://i.imgur.com/79wiQNh.png) ![](https://i.imgur.com/KkVEL8Q.png) ![](https://i.imgur.com/KrirJUc.png) # 從稀疏矩陣觀點來看卷積計算 **Forward pass:** 上述forward pass卷積的式子其實等價於兩個矩陣相成如下: ![](https://i.imgur.com/EICkNOG.png) ![](https://i.imgur.com/nTgum1z.png) 所以從這個式子來看其實卷積計算其實等價於一個MLP的一層,只是這個權重矩陣(W)為一個稀疏矩陣,而輸出層連結用到的概念是Local connected neural nets和權重共享(Shared Weight)的概念,權重在此範例只有4個(kernel map的元素)。 ***Note: 稀疏矩陣(sparse matrix)簡單說就是一個矩陣,裡面有很多元素都是0。*** 下圖可以完整介紹為什麼是Local connection,左圖為「全連結」的連結方式,右圖為「區域連結」的連結方式 「全連結」的每個weight都是一個參數,此例共有9*4=36個參數 「區域連結」有權重共享的好處,此例則會只有4個參數。 ![](https://i.imgur.com/8PhFHC9.png) **Backward pass:** 雖然local connection部份看起來很像full connection,但倒傳遞部份找解不是完全跟MLP一樣 Recall: 前面的backward pass結果 ![](https://i.imgur.com/Qq4pmie.png) 從上圖看到黃色的線(權重是w11),在backward pass部份則是只看黃色線有連結到的部份 ![](https://i.imgur.com/yFWPjNH.png) 詳細推導: https://pdfs.semanticscholar.org/5d79/11c93ddcb34cac088d99bd0cae9124e5dcd1.pdf # 卷積神經網路(Convolutional neural network, CNN):卷積計算中的步伐(stride)和填充(padding) ![](https://i.imgur.com/I5rRTe2.png) 一般看到的卷積介紹,大概就像上圖,圖會因為你的kernel map大小做完卷積後變的更小 **Note: 一般卷積網路過程中,除了Input image不稱為Feature map外,中間產生的圖我們都稱之為Feature map,原因很簡單就是這些中間產生的圖都是為了「描繪出該任務所應該產生對應的特徵資料」,這也呼應Yann LeCun, Yoshua Bengio & Geoffrey Hinton寫的Deep Learning第一句話寫的「Deep learning allows computational models that are composed of multiple processing layers to learn representations of data with multiple levels of abstraction」,深度學習過程就是在學資料的特性,所以中間出來的結果都是特徵資料,在影像因為是2D,所以用Feature map來稱呼。** 所以一個卷積計算基本上有幾個部份: 1. 輸入的圖: 假設大小是W × W。 2. Filter (kernel map)大小是 ks × ks 3. Stride: kernel map在移動時的步伐長度 S 4. 輸出的圖大小為 new_height × new_width 上圖的例子 1. 輸入的圖: W × W =10 × 10。 2. Filter (kernel map): ks × ks=3 × 3 3. Stride: S=1 4. 輸出的圖大小為 new_height × new_width = 8 × 8 ## Zero padding 這個手法就是看你會消失多少的大小,在輸入的圖部份就給你加上0元素進去,這個手法稱為zero padding,實際作法如下圖。 ![](https://i.imgur.com/PooSAb7.png) 此刻的卷積計算如下,這樣卷積後的圖就不會變小了。 ![](https://i.imgur.com/cw23wKI.png) 上圖舉的例子是kernel map是3x3,假設kernel map為5x5,此刻在輸入的圖上下左右行和列都各加2行和2列的0,讓圖變成14x14,就可以了。 ## 卷積計算的移動 ![](https://i.imgur.com/78n6n3E.png) 在tensorflow,padding那邊給了兩個選項 1. 「padding = ‘VALID’」 **padding = ‘VALID’ 等於最一開始敘述的卷積計算,圖根據filter大小和stride大小而變小** 公式如下: new_height = new_width = (W - F + 1) / S (结果向上取整數,假設算出來結果是4.5,取5) 以剛剛的例子 filter 3x3, stride=1, 卷積後的大小: (10–3+1)/1=8 filter 3x3, stride=2, 卷積後的大小: (10–3+1)/2=4 2. 「padding = ‘SAME’」 **會用zero-padding的手法,讓輸入的圖不會受到kernel map的大小影響** new_height = new_width = W / S (结果向上取整數) ***剛剛的例子,filter 3x3, stride=2, 卷積後的大小: 10/2=5 (這邊我沒有做這張圖,可以自己想像一下,做法如下所述) 這邊的作法會先補zero-padding的0元素,然後在作stride=2的卷積,所以實際上是最(10+2)(10+2)的圖做padding = ‘VALID’的事情,(12–3+1)/2=5。*** ## Padding補充說明 上面的公式是針對tensorflow內建的function功能(「padding = ‘VALID’」和「padding = ‘SAME’」),並不是一般算卷積後算新的feature map長寬的公式。 **一般卷積後算新的feature map長寬的公式如下:** ![](https://i.imgur.com/NPKXBdm.png) 這邊跟前面差別在這邊多了一個pad參數 對應到caffe prototxt卷積的參數會這麼寫,如下: convolution_param { num_output: 32 pad: 1 kernel_size: 3 stride: 2 } 這代表卷積層filter數設定為32,filter的kernel size是3,步伐stride是2,pad是1。pad=1,表示圖外圈額外加1圈0,假設pad=2,圖外圈額外加2圈0,以此類推。 所以kernel size是3的時候,你希望卷積後圖的寬高不要變,pad就要設定為1 假設kernel size是5的時候,你希望卷積後圖的寬高不要變,pad就要設定為2 假設kernel size是7的時候,你希望卷積後圖的寬高不要變,pad就要設定為3 **Note**: 因為一般大多只會用到卷積後,Feature map寬高會依據kernel size縮小一點(「padding = ‘VALID’」)或Feature map寬高不變(「padding = ‘SAME’」),鮮少搞一些特殊的功能,比如1×1卷積還要加pad=1,這樣出來的圖會比原本大一圈,而且這一圈還全為0。 而且一般卷積網路都是希望卷積後圖越來越小(整體MACC計算量才會小),除了segmentation和一些multi-scale object detection等才會用到deconv.或是一些upsample的方法把feature map放大。 Tensorflow是提供簡單的api,你就不需要自己去算pad要設多少,直接下strin即可(如下說明) padding: A string from: "SAME", "VALID".