卷積神經網絡CNN
全連接神經網絡存在的問題:
- 輸入的形式應該是列向量,但是卷積神經網絡中的輸入是圖像(2D矩陣),那么就需要對圖片進行展平處理,原本圖像中蘊含的空間等信息就被打亂了
- 輸入的特征多了,那么神經元的參數就會很多,層的結構就很復雜,容易過擬合
通道數
通道數 是指圖像或特征圖在深度方向上的維度數量(可以類比全連接神經網絡中的特征矩陣的x_i的特征數)
例如:
- 灰度圖只有一個通道(特征):亮度
- RGB圖像:3個通道(紅,綠,藍)
- RGBA圖像:4個通道(紅,綠,藍,透明度)
圖像在計算機中的本質
一個大的數值矩陣,每個元素代表像素的亮度或顏色
對于灰度圖(只有黑白兩色):255表示亮白,0表示黑,只有一個通道
對于彩色圖像
三通道圖
整體結構
卷積層
還是與手寫數字為例;在密集層中,每個神經元都可以獲取到整個的像素矩陣;而在卷積層中,我們讓神經元只能獲取到一部分像素而不是全部,這個特性被稱為局部感知
局部感知的優點
- 計算更快(輸入的矩陣更小,那么對于神經元來說,參數的數量大大減小)
- 需要更少的訓練數據,更不容易過擬合(參數減少,模型復雜度降低,就不容易過擬合)
卷積層會改變通道的數目,在前一層的輸入提取出不同的特征,同時會改變圖片的長和寬
卷積核/濾波器
定義:一個在圖像上滑動的小窗口,它與圖像的局部區域進行點積運算,從而提取出該區域的特征。其實可以類比全連接神經網絡的參數w,只不過它是共享的
卷積核是一個4D張量,形狀為(C_out,C_in,K,K)
- C_out:輸出通道數/卷積核數量
- C_in:輸入通道數
- K:卷積核大小(K×K)
案例:心電圖信號分類
池化層
池化層可以改變圖像的大小,但不改變通道數
卷積運算
對應位置相乘再相加
帶偏置的計算過程
權重共享
對于全連接神經網絡,輸入中的每個特征都對應著一個神經元中的一個參數
而對于卷積神經網絡并非這樣
卷積神經網絡中,是同一個卷積核在整個輸入圖像上滑動,對所有局部區域使用相同權重參數
- 卷積核的參數在整個圖像上是共享的
- 不同位置的局部區域都使用這個相同的卷積核做卷積操作
- 每個位置的輸出是該卷積核與對應局部區域的點積結果
填充
為了控制卷積后輸出的特征圖尺寸;在輸入特征圖周圍填充0
步幅
指卷積核一次移動幾格;能夠控制輸出特征圖的大小
eg:步幅為1
步幅為2
卷積運算后特征圖大小
輸出特征圖的高OH=H+2P?FHS+1 輸出特征圖的高OH=\frac{H+2P-FH}{S}+1 輸出特征圖的高OH=SH+2P?FH?+1
- H:輸入特征圖的高
- P:填充的寬度(多了幾圈)
- FH:卷積核的高
- S:步幅
OW=W+2P?FWS+1 OW = \frac{W+2P-FW}{S}+1 OW=SW+2P?FW?+1
- W:輸入特征圖的寬
- P:填充的寬度(多了幾圈)
- FW:卷積核的寬
- S:步幅
多通道卷積運算
特征圖有n通道,卷積核就有n通道
將每個通道的卷積結構相加;有n個卷積核,輸出特征圖通道數就為n
對于多通道卷積運算,我們可以用立體圖表示
- C:輸入通道數
- W:輸入特征圖的寬度
- H:輸入特征圖的高度
- FW:卷積核寬度
- FH:卷積核高度
- FN:輸出通道數/輸出的特征圖數/卷積核數量
池化運算
池化運算是按通道獨立運算的,即池化層運算不會對輸入特征圖的通道進行改變
池化核
類比卷積運算,我們也可以引入池化核這一概念
與卷積核相同和區別:
- 相同點:都是在特征圖上滑動的窗口,并提取信息
- 不同:池化核僅僅是告訴算法要在特征圖什么區域提取一塊多大的特征,對這塊特征取最大值或者平均,而不是和自身進行點積
最大池化運算
每次在區域中找到最大值
平均池化運算
區域內的值相加求平均
優點
-
對微小的位置變化具有魯棒性,使模型更加健壯
輸出尺寸
OH=H+2P?FHS+1OW=W+2P?FWS+1 OH=\frac{H+2P-FH}{S}+1 \\ OW = \frac{W+2P-FW}{S}+1 OH=SH+2P?FH?+1OW=SW+2P?FW?+1
- H,W:輸入特征圖的高、寬
- P:填充的寬度(多了幾圈)
- FH,FW:池化核的高、寬
- S:步幅
代碼實現
模擬mnist手寫數字識別
全部代碼
import numpy as np
import tensorflow as tf
from keras.optimizers import Adam
from tensorflow.keras import models, layers, activations# 獲取mnist手寫數字數據集
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()# 維度改成4維,適配卷積層的輸入格式 (60000, 28, 28)->(60000, 28, 28, 1)
x_train = x_train[:, :, :, np.newaxis].astype(float)
x_test = x_test[:, :, :, np.newaxis].astype(float)model = models.Sequential([layers.Conv2D(32, # 卷積核數量(4, 4), # 卷積核尺寸activation=activations.relu, # 經過該層后使用的激活函數,為了引入非線性特征input_shape=(28, 28, 1) # 與mnist特征的shape相同),layers.MaxPool2D((3, 3) # 池化核大小),layers.Conv2D(64, # 卷積核數量(4, 4), # 卷積核尺寸activation=activations.relu, # 經過該層后使用的激活函數,為了引入非線性特征),layers.MaxPool2D((3, 3) # 池化核大小),# 將2D展平為1D才能傳入全連接層layers.Flatten(),# 全連接層layers.Dense(units=64,activation=activations.relu),# 輸出層layers.Dense(units=10,activation=activations.linear)
])model.compile(loss=tf.losses.SparseCategoricalCrossentropy(from_logits=True), # 稀疏交叉熵損失函數optimizer=Adam() # adam優化
)model.fit(x_train,y_train,epochs=5,validation_split=0.1 # 每次從訓練集中劃分10%作為驗證集
)# 前向傳播值
logits = model(x_train)
y_train_pred = tf.nn.softmax(logits)
print(y_train_pred)# 評估模型
loss = model.evaluate(x_test, y_test)
print(loss)
詳解:
用到的庫
import numpy as np
import tensorflow as tf
from keras.optimizers import Adam
from tensorflow.keras import models, layers, activations
獲取數據集并進行初步處理
# 獲取mnist手寫數字數據集
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()# 維度改成4維,適配卷積層的輸入格式 (60000, 28, 28)->(60000, 28, 28, 1)
x_train = x_train[:, :, :, np.newaxis].astype(float)
x_test = x_test[:, :, :, np.newaxis].astype(float)
卷積層,使用tensorflow.keras.layers.Conv2D()
layers.Conv2D(32, # 卷積核數量(4, 4), # 卷積核尺寸activation=activations.relu, # 經過該層后使用的激活函數,為了引入非線性特征input_shape=(28, 28, 1) # 與mnist特征的shape相同),
池化層,使用tensorflow.keras.layers.MaxPool2D() (最大池化運算)
layers.MaxPool2D((3, 3) # 池化核大小),
整體結構
model = models.Sequential([layers.Conv2D(32, # 卷積核數量(4, 4), # 卷積核尺寸activation=activations.relu, # 經過該層后使用的激活函數,為了引入非線性特征input_shape=(28, 28, 1) # 與mnist特征的shape相同),layers.MaxPool2D((3, 3) # 池化核大小),layers.Conv2D(64, # 卷積核數量(4, 4), # 卷積核尺寸activation=activations.relu, # 經過該層后使用的激活函數,為了引入非線性特征),layers.MaxPool2D((3, 3) # 池化核大小),# 將2D展平為1D才能傳入全連接層layers.Flatten(),# 全連接層layers.Dense(units=64,activation=activations.relu),# 輸出層layers.Dense(units=10,activation=activations.linear)
])
編譯模型
model.compile(loss=tf.losses.SparseCategoricalCrossentropy(from_logits=True), # 稀疏交叉熵損失函數optimizer=Adam() # adam優化
)
訓練模型
model.fit(x_train,y_train,epochs=5,validation_split=0.1 # 每次從訓練集中劃分10%作為驗證集
)
獲取最終損失函數值