深度學習領域中,卷積神經網路(CNN)已成為影像處理和電腦視覺的基本。理解 CNN 的核心概念,如卷積層、池化層和全連線層,對於構建有效的深度學習模型至關重要。本文將深入探討這些核心概念,並使用 Python 和 Keras 框架展示 CNN 的實際應用。此外,我們還將深入研究多維陣列的卷積運算,並提供相關的數學公式和程式碼實作。更進一步,我們將探討一維卷積網路的前向和反向傳播機制,並詳細說明梯度計算方法,以及步長在卷積運算中的影響。
卷積層(Convolutional Layer)
卷積層是CNNs的核心組成部分。它使用一組可學習的濾波器(Filters)或核(Kernels)對輸入影像進行卷積運算,以提取影像中的區域性特徵。每個濾波器都會掃描整個影像,並計算與影像每個位置的內積,以產生一張特徵對映(Feature Map)。
特徵對映(Feature Map)
特徵對映是卷積層輸出的結果,它代表了影像中特定特徵的存在和位置。特徵對映的大小取決於濾波器的大小和步長(Stride)。
池化層(Pooling Layer)
池化層用於降低特徵對映的空間解析度,從而減少引數數量和計算量。常見的池化方法包括最大池化(Max Pooling)和平均池化(Average Pooling)。
最大池化(Max Pooling)
最大池化是將特徵對映中每個區域的最大值作為該區域的代表值。
平均池化(Average Pooling)
平均池化是將特徵對映中每個區域的平均值作為該區域的代表值。
Flatten 層
Flatten 層用於將多維度的特徵對映轉換為一維度的向量,以便輸入全連線層(Fully Connected Layer)。
全連線層(Fully Connected Layer)
全連線層是傳統神經網路中的層,所有神經元之間都有連線。它用於對提取出的特徵進行分類別或迴歸。
實作卷積神經網路
要實作卷積神經網路,需要定義卷積層、池化層、Flatten 層和全連線層等。以下是使用 Python 和 Keras 實作的一個簡單例子:
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense
# 定義模型
model = Sequential()
# 增加捲積層
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
# 新增池化層
model.add(MaxPooling2D((2, 2)))
# 新增Flatten 層
model.add(Flatten())
# 新增全連線層
model.add(Dense(128, activation='relu'))
model.add(Dense(10, activation='softmax'))
# 編譯模型
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
這個例子定義了一個簡單的卷積神經網路,包含一個卷積層、一個池化層、一個Flatten 層和兩個全連線層。模型使用 Adam 最佳化器和 categorical crossentropy 損失函式進行編譯。
影像處理中的池化層
池化層(Pooling Layer)是一種常見的影像處理技術,主要用於降低影像的空間解析度,從而減少影像中的噪聲和冗餘資訊。池化層可以將影像中的每個區域(通常為2x2或3x3)對映到一個單一的值,例如最大值或平均值。
池化層的優點
- 降低空間解析度:池化層可以將影像的空間解析度降低,從而減少影像中的噪聲和冗餘資訊。
- 提高計算效率:池化層可以減少影像中的畫素數量,從而提高計算效率。
- 改善模型的泛化能力:池化層可以幫助模型學習到更抽象的特徵,從而改善模型的泛化能力。
池化層的缺點
- 資訊損失:池化層可能會導致影像中的某些資訊丟失,尤其是當池化層的視窗大小太大時。
- 敏感度:池化層對於影像中的小物體或細節可能會產生敏感度問題。
池化層的型別
- 最大池化層(Max Pooling Layer):將每個區域中的最大值作為輸出。
- 平均池化層(Average Pooling Layer):將每個區域中的平均值作為輸出。
AlphaGo 的應用
AlphaGo 是一種根據深度學習的圍棋 AI,使用了池化層來處理圍棋棋盤上的資訊。AlphaGo 的輸入是 19x19x17 的三維陣列,每個元素代表圍棋棋盤上的某個位置的狀態。池化層被用來降低圍棋棋盤上的空間解析度,從而提高計算效率和改善模型的泛化能力。
多維度陣列的卷積運算
卷積運算是一種廣泛用於影像和訊號處理的技術,尤其是在深度學習中扮演著重要角色。下面,我們將探討如何實作多維度陣列的卷積運算,並提供相關的數學表示和Python程式碼。
卷積運算的基本概念
卷積運算的基本思想是將一個小的陣列(稱為核或濾波器)滑動於一個較大的陣列上,對每個位置進行元素-wise乘法並累加,得到一個新的陣列。這個過程可以用於提取資料中的特徵或模式。
一維陣列的卷積運算
首先,讓我們考慮一維陣列的卷積運算。假設我們有一個長度為5的一維陣列input_1d
和一個長度為3的一維陣列param_1d
,我們想要進行卷積運算。
import numpy as np
input_1d = np.array([1, 2, 3, 4, 5])
param_1d = np.array([1, 1, 1])
為了避免輸出陣列的大小小於輸入陣列,我們需要在輸入陣列的兩端新增零,這個過程稱為填充(padding)。
def _pad_1d(inp: np.ndarray, num: int) -> np.ndarray:
z = np.array([0])
z = np.repeat(z, num)
return np.concatenate((z, inp, z))
padded_input = _pad_1d(input_1d, 1)
現在,我們可以進行卷積運算了。卷積運算的結果可以透過以下公式計算:
def conv_1d(inp: np.ndarray, param: np.ndarray) -> np.ndarray:
output = []
for i in range(len(inp) - len(param) + 1):
output.append(np.sum(inp[i:i+len(param)] * param))
return np.array(output)
output = conv_1d(padded_input, param_1d)
多維度陣列的卷積運算
多維度陣列的卷積運算與一維陣列的卷積運算類別似,但需要考慮多個維度。假設我們有一個三維陣列input_3d
和一個三維陣列param_3d
,我們想要進行卷積運算。
input_3d = np.random.rand(5, 5, 3)
param_3d = np.random.rand(3, 3, 3)
為了簡化問題,我們假設填充大小為1。
def _pad_3d(inp: np.ndarray, num: int) -> np.ndarray:
return np.pad(inp, ((num, num), (num, num), (0, 0)), mode='constant')
padded_input = _pad_3d(input_3d, 1)
現在,我們可以進行卷積運算了。
def conv_3d(inp: np.ndarray, param: np.ndarray) -> np.ndarray:
output = []
for i in range(inp.shape[0] - param.shape[0] + 1):
for j in range(inp.shape[1] - param.shape[1] + 1):
output.append(np.sum(inp[i:i+param.shape[0], j:j+param.shape[1], :] * param))
return np.array(output).reshape((inp.shape[0] - param.shape[0] + 1, inp.shape[1] - param.shape[1] + 1, inp.shape[2]))
output = conv_3d(padded_input, param_3d)
一維卷積神經網路
在深度學習中,卷積神經網路(Convolutional Neural Network, CNN)是一種常用的神經網路結構,尤其是在影像和語音處理任務中。下面,我們將實作一維卷積神經網路的前向傳播和反向傳播。
前向傳播
首先,我們需要定義卷積運算的前向傳播函式。假設輸入資料為 inp
,引數為 param
,則前向傳播函式可以定義如下:
def conv_1d(inp, param):
assert_dim(inp, 1)
assert_dim(param, 1)
param_len = param.shape[0]
param_mid = param_len // 2
input_pad = _pad_1d(inp, param_mid)
out = np.zeros(inp.shape)
for o in range(out.shape[0]):
for p in range(param_len):
out[o] += param[p] * input_pad[o+p]
assert_same_shape(inp, out)
return out
這個函式首先對輸入資料進行填充,以確保輸出資料的大小與輸入資料相同。然後,它對輸入資料和引數進行卷積運算,得到輸出資料。
反向傳播
接下來,我們需要定義卷積運算的反向傳播函式。假設輸出梯度為 output_grad
,則反向傳播函式可以定義如下:
def conv_1d_backward(output_grad, inp, param):
assert_dim(output_grad, 1)
assert_dim(inp, 1)
assert_dim(param, 1)
param_len = param.shape[0]
param_mid = param_len // 2
input_pad = _pad_1d(inp, param_mid)
input_grad = np.zeros(inp.shape)
param_grad = np.zeros(param.shape)
for o in range(output_grad.shape[0]):
for p in range(param_len):
input_grad[o+p] += output_grad[o] * param[p]
param_grad[p] += output_grad[o] * input_pad[o+p]
return input_grad, param_grad
這個函式首先對輸入資料進行填充,然後對輸出梯度和引數進行卷積運算,得到輸入梯度和引數梯度。
步長
在卷積運算中,步長(stride)是指卷積核在輸入資料上移動的步長。步長可以用來控制輸出資料的大小。下面,我們將實作步長為 2 的卷積運算:
def conv_1d_stride2(inp, param):
assert_dim(inp, 1)
assert_dim(param, 1)
param_len = param.shape[0]
param_mid = param_len // 2
input_pad = _pad_1d(inp, param_mid)
out = np.zeros((inp.shape[0]//2,))
for o in range(out.shape[0]):
for p in range(param_len):
out[o] += param[p] * input_pad[o*2+p]
return out
這個函式首先對輸入資料進行填充,然後對輸入資料和引數進行卷積運算,得到輸出資料。注意,步長為 2,所以輸出資料的大小是輸入資料大小的一半。
測試
最後,我們可以測試我們實作的卷積運算函式:
inp = np.array([1, 2, 3, 4, 5])
param = np.array([0.1, 0.2, 0.3])
output = conv_1d(inp, param)
print(output)
output_grad = np.array([0.5, 0.6, 0.7, 0.8, 0.9])
input_grad, param_grad = conv_1d_backward(output_grad, inp, param)
print(input_grad)
print(param_grad)
這個程式碼首先對輸入資料和引數進行卷積運算,得到輸出資料。然後,它對輸出梯度和輸入資料進行反向傳播,得到輸入梯度和引數梯度。最後,它印出輸出資料、輸入梯度和引數梯度。
5. 卷積神經網路
什麼是梯度?
根據梯度的原理,我們可以計算出向量梯度的元素應該是什麼:
import numpy as np
def conv_1d_sum(inp: np.ndarray, param: np.ndarray) -> np.ndarray:
out = conv_1d(inp, param)
return np.sum(out)
# 隨機增加第五個元素的值
input_1d_2 = np.array([1, 2, 3, 4, 6])
param_1d = np.array([1, 1, 1])
print(conv_1d_sum(input_1d, param_1d))
print(conv_1d_sum(input_1d_2, param_1d))
輸出結果為:
39.0
41.0
因此,第五個元素的梯度應該是 41 - 39 = 2。
如何計算梯度?
現在,我們來分析如何計算這個梯度,而不是簡單地計算兩個總和之間的差異。這裡的關鍵是要了解輸入元素如何影響輸出結果。
卷積的反向傳播
讓我們仔細觀察輸入元素 t_5
如何影響輸出結果。這個元素出現在輸出結果的兩個地方:y_o_4
和 y_o_5
。在 y_o_4
中,t_5
乘以 w_3
,而在 y_o_5
中,t_5
乘以 w_2
。
梯度計算
要了解輸入元素如何影響輸出結果的總和,我們需要計算梯度。假設損失函式為 L
,則梯度為:
∂L/∂t_5 = ∂L/∂y_o_4 \* ∂y_o_4/∂t_5 + ∂L/∂y_o_5 \* ∂y_o_5/∂t_5
在這個例子中,損失函式為簡單的總和,因此 ∂L/∂y_o_4 = ∂L/∂y_o_5 = 1
。
梯度計算(續)
現在,我們可以計算梯度:
∂L/∂t_5 = w_3 + w_2
在這個例子中,w_2 = w_3 = 1
,因此梯度為 2
。
一般模式
現在,我們來分析一般模式。假設我們有輸入元素 t_i
,則梯度為:
∂L/∂t_i = ∑(∂L/∂y_o_j \* ∂y_o_j/∂t_i)
其中 j
是輸出索引,y_o_j
是輸出元素。
梯度計算(一般模式)
現在,我們可以計算梯度:
def conv_1d_grad(inp: np.ndarray, param: np.ndarray, output_grad: np.ndarray) -> np.ndarray:
grad = np.zeros_like(inp)
for i in range(len(inp)):
for j in range(len(output_grad)):
grad[i] += output_grad[j] \* param[j - i]
return grad
這個函式計算輸入梯度,給定輸入、引數和輸出梯度。
逆向傳播:卷積神經網路的核心
在卷積神經網路中,逆向傳播(Backpropagation)是一個至關重要的過程,用於計算梯度和更新模型引數。在這個過程中,我們需要計算輸入梯度和引數梯度。
輸入梯度的計算
輸入梯度代表了輸入資料對輸出結果的影響程度。給定輸入資料 inp
和引數 param
,我們可以計算輸入梯度 input_grad
。以下是計算輸入梯度的步驟:
- 初始化輸入梯度
input_grad
為零。 - 對於每個輸入資料點
o
和每個引數點p
,計算輸入梯度input_grad[o]
加上輸出資料output_pad[o+param_len-p-1]
乘以引數param[p]
。
for o in range(inp.shape[0]):
for p in range(param.shape[0]):
input_grad[o] += output_pad[o+param_len-p-1] * param[p]
引數梯度的計算
引數梯度代表了模型引數對輸出結果的影響程度。給定輸入資料 inp
和引數 param
,我們可以計算引數梯度 param_grad
。以下是計算引數梯度的步驟:
- 初始化引數梯度
param_grad
為零。 - 對於每個引數點
p
,計算引數梯度param_grad[p]
加上輸入資料inp
乘以輸出資料output_pad
。
for p in range(param.shape[0]):
param_grad[p] += np.sum(inp * output_pad[:, p:])
卷積運算的實作
卷積運算可以使用 NumPy 的卷積函式實作。以下是卷積運算的實作:
def conv_1d(input, param):
output = np.zeros((input.shape[0] + param.shape[0] - 1))
for i in range(output.shape[0]):
output[i] = np.sum(input[max(0, i-param.shape[0]+1):min(i+1, input.shape[0])] * param[max(0, param.shape[0]-i-1):min(param.shape[0], input.shape[0]-i)])
return output
逆向傳播的實作
逆向傳播可以使用以下步驟實作:
- 初始化輸入梯度和引數梯度為零。
- 對於每個輸入資料點和每個引數點,計算輸入梯度和引數梯度。
- 更新模型引數使用計算出的梯度。
def backprop(input, param, output):
input_grad = np.zeros_like(input)
param_grad = np.zeros_like(param)
# 計算輸入梯度和引數梯度
for o in range(input.shape[0]):
for p in range(param.shape[0]):
input_grad[o] += output[o+param.shape[0]-p-1] * param[p]
param_grad[p] += input[o] * output[o+param.shape[0]-p-1]
# 更新模型引數
param -= 0.01 * param_grad
return input_grad, param_grad
一維卷積神經網路的實作
在本文中,我們將實作一維卷積神經網路的前向傳播和反向傳播。首先,我們需要定義卷積運算的函式,包括前向傳播和反向傳播。
前向傳播
在前向傳播中,我們需要計算輸出的梯度和過濾器的梯度。給定輸入 inp
、過濾器 param
和輸出梯度 output_grad
,我們可以計算輸入梯度 input_grad
和過濾器梯度 param_grad
。
def conv_1d(inp, param):
#...
return output
def conv_1d_batch(inp, param):
outs = [conv_1d(obs, param) for obs in inp]
return np.stack(outs)
反向傳播
在反向傳播中,我們需要計算輸入梯度和過濾器梯度。給定輸入 inp
、過濾器 param
、輸出梯度 output_grad
,我們可以計算輸入梯度 input_grad
和過濾器梯度 param_grad
。
def conv_1d_backward(inp, param, output_grad):
#...
return input_grad, param_grad
def conv_1d_batch_backward(inp, param, output_grad):
input_grads = []
param_grads = []
for obs, output_grad_obs in zip(inp, output_grad):
input_grad_obs, param_grad_obs = conv_1d_backward(obs, param, output_grad_obs)
input_grads.append(input_grad_obs)
param_grads.append(param_grad_obs)
return np.stack(input_grads), np.sum(param_grads, axis=0)
從技術架構視角來看,本文深入淺出地講解了卷積神經網路(CNN)的核心概念,包括卷積層、池化層、Flatten層和全連線層,並以一維和多維陣列的卷積運算為例,闡述了CNN的前向傳播和反向傳播機制,同時也探討了步長和梯度計算等關鍵細節。文章提供了清晰的Python程式碼示例,方便讀者理解和實踐。然而,文章未深入探討不同卷積核、啟用函式、最佳化器等對模型效能的影響,也缺乏對過擬合、梯度消失等常見問題的分析。對於CNN的應用場景,除了提及AlphaGo的案例外,可以進一步拓展到其他領域,例如影像分類別、目標檢測等,以提升文章的實用價值。展望未來,CNN與其他深度學習技術的融合,例如注意力機制、遷移學習等,將持續推動其在更多領域的應用和發展,值得密切關注其發展趨勢並探索更多創新性的應用。對於想要深入學習CNN的開發者,建議進一步研究不同網路架構的設計 principles,並關注模型壓縮、輕量化等前沿技術,以提升模型的效率和效能。