深度學習領域中,卷積神經網路(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)對映到一個單一的值,例如最大值或平均值。

池化層的優點

  1. 降低空間解析度:池化層可以將影像的空間解析度降低,從而減少影像中的噪聲和冗餘資訊。
  2. 提高計算效率:池化層可以減少影像中的畫素數量,從而提高計算效率。
  3. 改善模型的泛化能力:池化層可以幫助模型學習到更抽象的特徵,從而改善模型的泛化能力。

池化層的缺點

  1. 資訊損失:池化層可能會導致影像中的某些資訊丟失,尤其是當池化層的視窗大小太大時。
  2. 敏感度:池化層對於影像中的小物體或細節可能會產生敏感度問題。

池化層的型別

  1. 最大池化層(Max Pooling Layer):將每個區域中的最大值作為輸出。
  2. 平均池化層(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_4y_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。以下是計算輸入梯度的步驟:

  1. 初始化輸入梯度 input_grad 為零。
  2. 對於每個輸入資料點 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。以下是計算引數梯度的步驟:

  1. 初始化引數梯度 param_grad 為零。
  2. 對於每個引數點 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

逆向傳播的實作

逆向傳播可以使用以下步驟實作:

  1. 初始化輸入梯度和引數梯度為零。
  2. 對於每個輸入資料點和每個引數點,計算輸入梯度和引數梯度。
  3. 更新模型引數使用計算出的梯度。
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,並關注模型壓縮、輕量化等前沿技術,以提升模型的效率和效能。