這是一篇關於CNN(卷積神經網絡)的簡單指南,本文將介紹CNN如何工作,以及如何在Python中從頭開始構建一個CNN。
作者 | victorzhou
譯者 | 虎說,責編 | 郭芮
出品 | CSDN(ID:CSDNnews)
以下為譯文:
在過去的幾年中,有很多關於卷積神經網絡(CNN)的討論,尤其是因為它們已經徹底改變了計算機視覺領域。在這篇文章中,我們將基於神經網絡的基本背景知識,探索CNN是什麼,理解它們是如何工作的,並使用Python中的numpy從頭開始構建一個真正的卷積神經網絡。
本文假設讀者有一定的神經網絡的基本知識。如果你想要了解一些關於神經網絡的知識,你可以讀一下我的關於對神經網絡的介紹(https://victorzhou.com/blog/intro-to-neural-networks/)。
現在,讓我們開始今天的話題。
動機
CNN的經典用例是執行圖像分類,例如查看寵物的圖像並確定它是貓還是狗。這是一項看似非常簡單的任務,你可能會有這樣的疑惑:為什麼不使用普通的神經網絡呢?不得不說這是一個好問題。
原因1:圖像很大
目前用於計算機視覺問題的圖像通常為224x224甚至更大。想像一下,構建一個神經網絡來處理224x224彩色圖像:包括圖像中的3個顏色通道(RGB),即224x224x3=150528個輸入權重!這種網絡中的典型隱藏層可能有1024個節點,因此我們必須僅為第一層訓練150528x1024=15億個權重。想像一下擁有15億個權重的神經網絡,是不是太大了?這幾乎是不可能完成訓練的。
最重要的是其實我們並不需要那麼多的權重,相反我們僅知道像素點其鄰居的點才是最有用的。因為圖像中的物體是由小的局部特徵組成的,如圓形虹膜或一張紙的方角。對於第一個隱藏層中的每個節點來說,查看每個像素似乎是很浪費的!
原因2:位置可變
如果你訓練了一個網絡來檢測狗,那麼無論圖像出現在哪張照片中,你都希望它能夠檢測到狗。想像一下,訓練一個在某個狗圖像上運行良好的網絡,然後為它提供相同圖像的略微移位版本,此時的網絡會有完全不同的反應!
那麼CNN是如何幫助我們解決這些問題的呢?不要著急我們很快就會看到CNN如何幫助我們緩解這些問題!
數據集
在這篇文章中,我們將解決計算機視覺的「Hello,World!」:MNIST手寫數字分類問題。很簡單:給定圖像,將其分類為數字。
來自MNIST數據集的樣本圖像
MNIST數據集中的每個圖像都是28x28,其中包含了一個居中的灰度數字。說實話,一個正常的神經網絡實際上可以很好地解決這個問題。你可以將每個圖像視為28x28=784維矢量,將其輸入到784-dim圖層,堆疊一些隱藏圖層,最後輸出10個節點的輸出圖層,每個數字1個。
這樣做可以完成任務,因為MNIST數據集包含的都是些居中的小圖像,因此我們不會遇到上述的大小或移位問題。但是,請記住,大多數現實世界的圖像分類問題並不容易。
那麼就讓我們進入CNN吧!
卷積
什麼是卷積神經網絡?
它們基本上是使用卷積層的神經網絡,即Conv層,它們基於卷積的數學運算。Conv圖層由一組過濾器組成,你可以將其視為2d數字矩陣。這是一個示例3x3過濾器:
一個3x3過濾器
我們可以通過將濾波器與輸入圖像進行卷積來產生輸出圖像。這包括:
注釋:實際上我們(以及許多CNN實現)在技術上使用互相關(Cross-correlation)而不是卷積,但它們幾乎完全相同。
這4步描述有點抽象,所以讓我們舉個例子吧,考慮這個微小的4x4灰度圖像和這個3x3過濾器:
4x4圖像(左)和3x3濾鏡(右)
圖像中的數字表示像素強度,其中0是黑色,255是白色。我們將對輸入圖像和過濾器進行卷積以生成2x2輸出圖像:
2x2輸出圖像
首先,讓我們將過濾器疊加在圖片的左上角:
第1步:將過濾器(右)疊加在圖像上方(左)
接下來,我們在重疊圖像值和過濾器值之間執行逐元素乘法。以下是結果,從左上角開始向右,然後向下:
第2步:執行逐元素乘法。
接下來,我們總結所有結果。這很容易:62-33=29。
最後,我們將結果放在輸出圖像的目標像素中。由於我們的過濾器覆蓋在輸入圖像的左上角,因此我們的目標像素是輸出圖像的左上角像素:
我們做同樣的步驟來生成輸出圖像的其餘部分:
3.1這有用嗎?
用過濾器卷積圖像有什麼作用?我們可以先使用我們一直使用的示例3x3濾波器,這通常被稱為垂直索貝爾濾波器:
垂直索貝爾濾波器
以下是垂直Sobel濾波器的示例:
垂直Sobel濾波器卷積的圖像
同樣,還有一個水平Sobel濾波器:
水平Sobel濾波器
水平Sobel濾波器卷積的圖像
到底發生了什麼?Sobel濾波器是邊緣檢測器。垂直Sobel濾波器檢測的是垂直邊緣,水平Sobel濾波器的是檢測水平邊緣。現在可以輕鬆解釋輸出圖像:輸出圖像中的亮像素(具有高值的像素)表示原始圖像中存在強邊緣。
你能看出為什麼邊緣檢測圖像可能比原始圖像更有用嗎?回想一下我們的MNIST手寫數字分類問題。在MNIST上訓練的CNN可以尋找數字1,例如,通過使用邊緣檢測濾波器並檢查圖像中心附近的兩個突出的垂直邊緣。通常,卷積有助於我們查找特定的本地化圖像特徵。
3.2填充(Padding)
還記得先用3x3過濾器對4x4的輸入圖像進行卷積,以產生2x2輸出圖像嗎?通常,我們希望輸出圖像的大小與輸入圖像的大小相同。為此,我們在圖像周圍添加零,以便我們可以在更多位置疊加過濾器。3x3濾鏡需要1個像素的填充:
4x4輸入與3x3濾波器卷積,使用填充以產生4x4輸出
這稱為「相同」填充,因為輸入和輸出具有相同的尺寸。不使用任何填充,這是我們一直在做的,有時也被稱為「有效」填充。
3.3 Conv圖層
既然我們知道圖像卷積是如何工作的以及為什麼用它,那讓我們看看它是如何在CNN中實際使用的。如前所述,CNN包括使用一組過濾器將輸入圖像轉換為輸出圖像的conv layer。conv層的主要參數是它具有的過濾器數量。
對於我們的MNIST CNN,我們將使用一個帶有8個過濾器的小conv layer作為我們網絡中的初始層。這意味著它會將28x28輸入圖像轉換為26x26x8輸出向量:
提醒:輸出為26x26x8而不是28x28x8,因為我們使用有效填充,這會將輸入的寬度和高度減少2。
conv layer中的4個過濾器中的每一個都會產生26x26輸出,因此堆疊在一起它們構成26x26x8的向量。
3.4執行卷積
是時候將我們學到的東西放到代碼中了!我們將實現一個conv layer的前饋部分,該部分負責處理帶有輸入圖像的過濾器,以產生輸出向量。為簡單起見,我們假設過濾器是3x3(其實5x5和7x7過濾器也很常見)。
讓我們開始實現一個conv layer類:
import numpy as np
class Conv3x3:
def __init__(self, num_filters):
self.num_filters = num_filters
self.filters = np.random.randn(num_filters, 3, 3) / 9
該類只有一個參數:過濾器的數量。在構造函數中,我們存儲過濾器的數量並使用NumPy的randn()方法初始化隨機過濾器數組。
注意:在初始化期間初始值很重要,如果初始值太大或太小,則訓練網絡將無效。要了解更多信息,請閱讀Xavier Initialization(https://www.quora.com/What-is-an-intuitive-explanation-of-the-Xavier-Initialization-for-Deep-Neural-Networks)。
接下來,實際卷積:
class Conv3x3:
def iterate_regions(self, image):
'''
Generates all possible 3x3 image regions using valid padding.
- image is a 2d numpy array
'''
h, w = image.shape
for i in range(h - 2):
for j in range(w - 2):
im_region = image[i:(i + 3), j:(j + 3)]
yield im_region, i, j
def forward(self, input):
'''
Performs a forward pass of the conv layer using the given input.
Returns a 3d numpy array with dimensions (h, w, num_filters).
- input is a 2d numpy array
'''
h, w = input.shape
output = np.zeros((h - 2, w - 2, self.num_filters))
for im_region, i, j in self.iterate_regions(input):
output[i, j] = np.sum(im_region * self.filters, axis=(1, 2))
return output
iterate_regions()是一個輔助生成器方法,為我們生成所有有效的3x3圖像區域,這對於稍後實現此類的後向部分非常有用。
上面是實際執行卷積的代碼行。讓我們分解一下:
im_region:一個包含相關圖像區域的3x3陣列。
self.filters:一個3d數組。
im_region*self.filtersself.filters:我們使用numpy的廣播(broadcasting)功能以元素方式乘以兩個數組,結果是具有相同尺寸的3d數組。
axis=(1,2)num_filters:我們使用np.sum()上一步的結果,產生一個長度為1d的數組,其中每個元素包含相應過濾器的卷積結果。
我們將結果分配給output[i,j],其中包含輸出中像素的卷積結果(i,j)。
對輸出中的每個像素執行上面的序列,直到我們獲得我們想要的結果!讓我們的代碼進行測試運行:
import mnist
from conv import Conv3x3
train_images = mnist.train_images()
train_labels = mnist.train_labels()
conv = Conv3x3(8)
output = conv.forward(train_images[0])
print(output.shape)
注意:為簡單起見,在我們的Conv3x3實現中,我們假設輸入是一個2d numpy數組,因為這是我們的MNIST圖像的存儲方式。這對我們有用,我們將它用作網絡中的第一層,但大多數CNN都有更多的Conv層。如果我們要構建一個需要Conv3x3多次使用的更大的網絡,我們必須使輸入成為3d numpy數組。
池化(Pooling)
圖像中的相鄰像素傾向於具有相似的值,因此conv layer通常也會為輸出中的相鄰像素產生類似的值。但是conv layer中輸出的大部分信息都是冗餘的,例如,如果我們使用邊緣檢測過濾器並在某個位置找到強邊緣,那麼我們也可能會在距離原始像素1個像素偏移的位置找到相對較強的邊緣。但是,我們可能並沒有找到任何新的東西。
池化層解決了這個問題。他們所做的就是減少通過猜測在輸入中產生的匯總值。該池通常是通過簡單的操作完成max,min或average等這些操作。以下是池化大小為2的Max Pooling圖層的示例:
4x4圖像上的最大池(池大小為2)以產生2x2輸出
為了執行最大池化,我們以2x2塊(因為池大小=2)遍歷輸入圖像,並將最大值放入相應像素的輸出圖像中。
對於我們的MNIST CNN,我們將在初始conv layer之後放置一個池大小為2的Max Pooling層,這樣池化層就會將26x26x8輸入轉換為13x13x8輸出:
4.1執行池化
我們將使用MaxPool2與上一節中的conv類相同的方法實現一個類:
import numpy as np
class MaxPool2:
def iterate_regions(self, image):
'''
Generates non-overlapping 2x2 image regions to pool over.
- image is a 2d numpy array
'''
h, w, _ = image.shape
new_h = h // 2
new_w = w // 2
for i in range(new_h):
for j in range(new_w):
im_region = image[(i * 2):(i * 2 + 2), (j * 2):(j * 2 + 2)]
yield im_region, i, j
def forward(self, input):
'''
Performs a forward pass of the maxpool layer using the given input.
Returns a 3d numpy array with dimensions (h / 2, w / 2, num_filters).
- input is a 3d numpy array with dimensions (h, w, num_filters)
'''
h, w, num_filters = input.shape
output = np.zeros((h // 2, w // 2, num_filters))
for im_region, i, j in self.iterate_regions(input):
output[i, j] = np.amax(im_region, axis=(0, 1))
return output
此類與我們之前實現的Conv3x3類工作方式類似。特別注意的是為了找到給定圖像區域的最大值,我們使用np.amax()。我們設置是axis=(0,1),因為我們只希望在前兩個維度(高度和寬度)上進行最大化,而不是第三個維度,num_filters。
我們來試試吧!
import mnist
from conv import Conv3x3
from maxpool import MaxPool2
train_images = mnist.train_images()
train_labels = mnist.train_labels()
conv = Conv3x3(8)
pool = MaxPool2()
output = conv.forward(train_images[0])
output = pool.forward(output)
print(output.shape)
MNIST CNN馬上要完成了!
Softmax
為了完成我們的CNN,我們需要讓它能夠實際進行預測。我們將通過使用標準最終層來實現多類分類問題:Softmax層,一個使用softmax激活函數的標準全連接(密集)層。
提示:完全連接層將每個節點連接到前一層的每個輸出。我們在之前的神經網絡的介紹中使用了完全連接的圖層。
Softmax將任意實際值轉換為概率。如果你對其背後的數學有興趣,自己可以簡單了解下,因為它很簡單。
5.1用法
我們將使用一個帶有10個節點的softmax層,每個節點都代表一個數字,softmax層是我們CNN的最後一層。圖層中的每個節點都將連接到輸入,在使用softmax變換之後,概率最高的節點表示的數字將成為CNN的輸出!
5.2交叉熵損失函數
你可能會有這樣的疑問,為什麼還要將輸出轉換為概率呢?最高輸出值是否總是具有最高概率?如果你有這樣的疑問,那說明你的感覺很對。我們實際上不需要使用softmax來預測數字,因為我們可以選擇網絡輸出最高的數字!
softmax真正做的是幫助我們量化我們對預測的確定程度,這在訓練和評估我們的CNN時非常有用。更具體地說,它可以幫我們確定每個預測的正確程度。
5.3實施Softmax
讓我們實現一個Softmax圖層類:
import numpy as np
class Softmax:
def __init__(self, input_len, nodes):
self.weights = np.random.randn(input_len, nodes) / input_len
self.biases = np.zeros(nodes)
def forward(self, input):
'''
Performs a forward pass of the softmax layer using the given input.
Returns a 1d numpy array containing the respective probability values.
- input can be any array with any dimensions.
'''
input = input.flatten()
input_len, nodes = self.weights.shape
totals = np.dot(input, self.weights) + self.biases
exp = np.exp(totals)
return exp / np.sum(exp, axis=0)
我們現在已經完成了CNN的整個編碼工作!把它放在一起:
import mnist
import numpy as np
from conv import Conv3x3
from maxpool import MaxPool2
from softmax import Softmax
test_images = mnist.test_images()[:1000]
test_labels = mnist.test_labels()[:1000]
conv = Conv3x3(8)
pool = MaxPool2()
softmax = Softmax(13 * 13 * 8, 10)
def forward(image, label):
'''
Completes a forward pass of the CNN and calculates the accuracy and
cross-entropy loss.
- image is a 2d numpy array
- label is a digit
'''
out = conv.forward((image / 255) - 0.5)
out = pool.forward(out)
out = softmax.forward(out)
loss = -np.log(out[label])
acc = 1 if np.argmax(out) == label else 0
return out, loss, acc
print('MNIST CNN initialized!')
loss = 0
num_correct = 0
for i, (im, label) in enumerate(zip(test_images, test_labels)):
_, l, acc = forward(im, label)
loss += l
num_correct += acc
if i % 100 == 99:
print(
'[Step %d] Past 100 steps: Average Loss %.3f | Accuracy: %d%%' %
(i + 1, loss / 100, num_correct)
)
loss = 0
num_correct = 0
執行cnn.py,我們可以得到:
MNIST CNN initialized!
[Step 100] Past 100 steps: Average Loss 2.302 | Accuracy: 11%
[Step 200] Past 100 steps: Average Loss 2.302 | Accuracy: 8%
[Step 300] Past 100 steps: Average Loss 2.302 | Accuracy: 3%
[Step 400] Past 100 steps: Average Loss 2.302 | Accuracy: 12%
想親自嘗試或修改這些代碼?在瀏覽器中運行此CNN,你也可以在Github上找到它。
結論
關於CNN的介紹就到此結束了!在這篇文章中,我們:
介紹了為什麼CNN可能對某些問題更有用,例如圖像分類;
介紹了MNIST手寫數字數據集;
了解了Conv圖層,它將過濾器與圖像進行卷積,以產生更有用的輸出;
了解了Pooling圖層,它可以幫助修剪除最有用特徵之外的內容;
實現了Softmax層,因此我們可以使用交叉熵損失函數。
原文:https://victorzhou.com/blog/intro-to-cnns-part-1/
本文為 CSDN 翻譯,轉載請註明來源出處。
【End】
CSDN 5G免費沙龍來啦!
6月29日,微軟(中國)首席技術官韋青、北京郵電大學信息與通信工程學院多媒體技術教研中心主任/博士生導師孫松林、愛立信中國研發部多天線高級專家朱懷松、愛立信中國研發部主任系統工程師劉陽等行業內頂尖的領軍者、資深的技術專家們共聚一堂,共同探討5G在物聯網中的巨大潛能。
熱 文 推 薦
☞華為準備替代安卓?小米停止MIUI全球Beta計劃;首臺類腦超級計算機2022有望誕生 | 極客頭條
☞@程式設計師,不容錯過的 Vim 實用技巧請查收!
☞微軟發布 VS Code Java 安裝程序,一鍵安裝所有 Java 開發環境
☞那些去德國的程式設計師後來怎麼樣了?
☞獨家對話V神! 質疑之下的以太坊路在何方?
☞蘋果宣布加入CNCF;華為要求美國運營商支付專利費;微軟刪除最大的公開人臉識別數據集
☞阿里巴巴楊群:高並發場景下Python的性能挑戰
☞新技術「紅」不過十年,半監督學習為什麼是個例外?
☞老碼農冒死揭開編程黑幕:這些Bug讓我認輸,誰踩誰服!
點擊閱讀原文,輸入關鍵詞,即可搜索您想要的 CSDN 文章。