深度學習模型的訓練仰賴有效率的反向傳播演算法,本文將深入探討一維和二維卷積神經網路(CNN)中,前向傳播和反向傳播的具體實作方式,並提供 Python 程式碼範例。首先,我們會闡述一維卷積的計算過程,包含輸入、引數和輸出張量的維度定義,以及如何透過 NumPy 進行向量化運算。接著,延伸至二維卷積,說明如何處理多通道輸入和輸出,以及如何應用於影像處理。最後,我們將詳細說明反向傳播的計算過程,包含輸入梯度和權重梯度的推導,並提供程式碼示例,展示如何使用 Python 和 NumPy 實作這些計算。程式碼中包含了詳細的註解,以幫助讀者理解每個步驟的含義。

實作

現在,我們可以實作一維卷積神經網路的前向傳播和反向傳播。

import numpy as np

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)

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)

測試

現在,我們可以測試一維卷積神經網路的前向傳播和反向傳播。

inp = np.array([[0, 1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6, 7]])
param = np.array([1, 2, 3])
output = conv_1d_batch(inp, param)
output_grad = np.array([[1, 2, 3], [4, 5, 6]])
input_grad, param_grad = conv_1d_batch_backward(inp, param, output_grad)
print(output)
print(input_grad)
print(param_grad)

這樣,我們就完成了一維卷積神經網路的實作。

2D 卷積神經網路

在討論 2D 卷積神經網路之前,我們先來瞭解一下 1D 卷積的基本概念。1D 卷積是一種將輸入訊號與濾波器進行卷積運算的過程,目的是提取輸入訊號中的特徵。

1D 卷積的基本概念

1D 卷積的輸入通常是一維陣列,例如時間序列資料或一維訊號。濾波器也是一維陣列,與輸入訊號進行卷積運算,以提取特徵。

2D 卷積的基本概念

2D 卷積是 1D 卷積的延伸,適用於二維陣列的輸入,例如影像資料。2D 卷積的目的是提取影像中的特徵,例如邊緣、線條或形狀等。

2D 卷積的實作

2D 卷積的實作與 1D 卷積類別似,區別在於需要考慮二維陣列的寬度和高度。以下是 2D 卷積的基本步驟:

  1. 填充:在影像周圍新增零值或其他填充值,以便於卷積運算。
  2. 卷積:將濾波器與填充後的影像進行卷積運算,提取特徵。
  3. 步長:設定步長以控制卷積運算的速度。
  4. 偏移:設定偏移以控制卷積運算的起始位置。

2D 卷積的反向傳播

2D 卷積的反向傳播與 1D 卷積類別似,需要計算輸入梯度和濾波器梯度。以下是 2D 卷積反向傳播的基本步驟:

  1. 計算輸入梯度:計算輸入梯度以更新輸入值。
  2. 計算濾波器梯度:計算濾波器梯度以更新濾波器值。

2D 卷積的應用

2D 卷積廣泛應用於影像處理、物體偵測、分割等領域。以下是 2D 卷積的一些應使用案例子:

  • 影像分割:使用 2D 卷積提取影像中的特徵,以進行影像分割。
  • 物體偵測:使用 2D 卷積提取影像中的特徵,以進行物體偵測。
  • 影像去噪:使用 2D 卷積去除影像中的噪點。

雙向卷積運算:前向與反向傳遞

在深度學習中,卷積運算是一種基本的操作,用於提取影像或序列中的區域性特徵。卷積運算可以分為前向傳遞(forward pass)和反向傳遞(backward pass)。在本文中,我們將探討雙向卷積運算的實作,包括前向傳遞和反向傳遞。

前向傳遞

前向傳遞是指將輸入資料經過卷積運算,產生輸出結果的過程。給定輸入資料 inp 和引數 param,我們可以計算輸出結果 out

import numpy as np

def conv_forward(inp, param):
    # 輸入資料的形狀
    img_size = inp.shape[0]
    
    # 引數的形狀
    param_size = param.shape[0]
    
    # 輸出結果的形狀
    out = np.zeros_like(inp)
    
    # 前向傳遞
    for o_w in range(img_size):
        for o_h in range(img_size):
            for p_w in range(param_size):
                for p_h in range(param_size):
                    out[o_w][o_h] += param[p_w][p_h] * inp[o_w+p_w][o_h+p_h]
                    
    return out

反向傳遞

反向傳遞是指計算輸入資料的梯度,根據輸出結果的梯度和引數的梯度。給定輸出結果 out、輸入資料 inp 和引數 param,我們可以計算輸入資料的梯度 input_grad

def conv_backward(out, inp, param):
    # 輸入資料的形狀
    img_width = inp.shape[0]
    
    # 引數的形狀
    param_len = param.shape[0]
    
    # 輸入資料的梯度
    input_grad = np.zeros_like(inp)
    
    # 反向傳遞
    for i_w in range(img_width):
        for p in range(param_len):
            input_grad[i_w] += out[i_w+param_len-p-1] * param[p]
            
    return input_grad

雙向卷積運算

雙向卷積運算是指同時進行前向傳遞和反向傳遞。給定輸入資料 inp 和引數 param,我們可以計算輸出結果 out 和輸入資料的梯度 input_grad

def conv_double_forward_backward(inp, param):
    # 前向傳遞
    out = conv_forward(inp, param)
    
    # 反向傳遞
    input_grad = conv_backward(out, inp, param)
    
    return out, input_grad
圖表翻譯:
  graph LR
    A[輸入資料] -->|前向傳遞|> B[輸出結果]
    B -->|反向傳遞|> C[輸入資料梯度]
    C -->|最佳化|> D[模型引數]

在上述圖表中,我們可以看到雙向卷積運算的流程。輸入資料經過前向傳遞,產生輸出結果。然後,輸出結果經過反向傳遞,產生輸入資料梯度。最後,輸入資料梯度用於最佳化模型引數。

卷積神經網路的實作

在本文中,我們將實作一個基本的卷積神經網路(Convolutional Neural Network, CNN)。CNN是一種特殊的神經網路結構,非常適合於處理影像和其他空間資料。

卷積運算

首先,我們需要實作卷積運算。卷積運算是CNN中的核心運算,它將一個小的視窗(稱為核)滑動於整個影像上,並在每個位置計算視窗內的元素與影像的元素之間的點積。

import numpy as np

def conv2d(input_pad, param):
    """
    2D卷積運算
    
    Parameters:
    input_pad (ndarray): 輸入影像資料,已經填充邊界
    param (ndarray): 核資料
    
    Returns:
    ndarray: 輸出影像資料
    """
    img_height, img_width = input_pad.shape
    param_size = param.shape[0]
    output = np.zeros((img_height - param_size + 1, img_width - param_size + 1))
    
    for i_h in range(img_height - param_size + 1):
        for i_w in range(img_width - param_size + 1):
            for p_h in range(param_size):
                for p_w in range(param_size):
                    output[i_h, i_w] += input_pad[i_h + p_h, i_w + p_w] * param[p_h, p_w]
    
    return output

多通道卷積運算

接下來,我們需要實作多通道卷積運算。多通道卷積運算是指輸入影像和輸出影像都有多個通道(例如,RGB影像有3個通道)。

def conv2d_multichannel(input_pad, param):
    """
    多通道2D卷積運算
    
    Parameters:
    input_pad (ndarray): 輸入影像資料,已經填充邊界
    param (ndarray): 核資料
    
    Returns:
    ndarray: 輸出影像資料
    """
    batch_size, img_height, img_width, input_channels = input_pad.shape
    param_size = param.shape[0]
    output_channels = param.shape[-1]
    output = np.zeros((batch_size, img_height - param_size + 1, img_width - param_size + 1, output_channels))
    
    for i in range(batch_size):
        for o_h in range(img_height - param_size + 1):
            for o_w in range(img_width - param_size + 1):
                for p_w in range(param_size):
                    for p_h in range(param_size):
                        for input_channel in range(input_channels):
                            for output_channel in range(output_channels):
                                output[i, o_h, o_w, output_channel] += input_pad[i, o_h + p_h, o_w + p_w, input_channel] * param[p_h, p_w, input_channel, output_channel]
    
    return output

反向傳播

最後,我們需要實作反向傳播演算法,以計算輸出誤差對於輸入和核的梯度。

def conv2d_backward(input_pad, output_grad, param):
    """
    2D卷積運算的反向傳播
    
    Parameters:
    input_pad (ndarray): 輸入影像資料,已經填充邊界
    output_grad (ndarray): 輸出誤差梯度
    param (ndarray): 核資料
    
    Returns:
    ndarray: 輸入誤差梯度
    """
    img_height, img_width = input_pad.shape
    param_size = param.shape[0]
    input_grad = np.zeros_like(input_pad)
    
    for i_h in range(img_height):
        for i_w in range(img_width):
            for p_h in range(param_size):
                for p_w in range(param_size):
                    input_grad[i_h, i_w] += output_grad[i_h + p_h - param_size + 1, i_w + p_w - param_size + 1] * param[p_h, p_w]
    
    return input_grad

這些實作提供了基本的CNN結構,可以用於影像分類別、物體偵測等任務。然而,在實際應用中,可能需要考慮更多因素,例如邊界填充、步長、啟用函式等。

影像卷積運算的核心實作

在進行影像處理任務時,卷積運算是一種基本且重要的操作。它能夠提取影像中的特徵,並根據不同的卷積核(或濾波器)對影像進行不同的變換。在本文中,我們將探討如何實作影像卷積運算的核心部分。

資料結構與維度

首先,讓我們瞭解一下輸入資料的結構。假設 obs 是輸入的影像資料,其維度為 (channels, img_width, img_height),代表影像的通道數、寬度和高度。另一方面,param 代表卷積核的引數,其維度為 (in_channels, out_channels, param_width, param_height),分別代表輸入通道數、輸出通道數、卷積核的寬度和高度。

# 定義輸入資料的維度
obs: [channels, img_width, img_height]
param: [in_channels, out_channels, param_width, param_height]

資料維度檢查

在進行卷積運算之前,我們需要檢查輸入資料的維度是否正確。這可以透過 assert_dim 函式來實作,該函式檢查給定的陣列是否具有指定的維度數量。

assert_dim(obs, 3)  # 檢查 obs 是否為 3 維
assert_dim(param, 4)  # 檢查 param 是否為 4 維

引數計算

接下來,我們需要計算一些重要的引數。首先,計算卷積核的大小 (param_size) 和其中間位置 (param_mid)。這些引數對於後續的卷積運算非常重要。

param_size = param.shape[2]  # 取得卷積核的大小
param_mid = param_size // 2  # 計算卷積核中間位置

輸入資料填充

為了避免邊界問題,通常需要對輸入影像進行填充。這裡,我們使用 _pad_2d_channel 函式對 obs 進行填充,填充大小為 param_mid

obs_pad = _pad_2d_channel(obs, param_mid)  # 對 obs 進行填充

卷積運算

現在,讓我們開始實作卷積運算的核心部分。首先,取得輸入和輸出的通道數 (in_channelsout_channels),以及影像的大小 (img_size)。

in_channels = fil.shape[0]  # 取得輸入通道數
out_channels = fil.shape[1]  # 取得輸出通道數
img_size = obs.shape[1]  # 取得影像大小

然後,初始化輸出的陣列 out,其大小為 (out_channels,) + obs.shape[1:]

out = np.zeros((out_channels,) + obs.shape[1:])  # 初始化輸出陣列

最後,進行卷積運算。這涉及到三個巢狀迴圈:對於每個輸入通道 (c_in)、每個輸出通道 (c_out)和每個影像位置 (o_w)。

for c_in in range(in_channels):
    for c_out in range(out_channels):
        for o_w in range(img_size):
            # 進行卷積運算
            pass

內容解密:

在上述迴圈中,我們需要實際進行卷積運算。這涉及到將卷積核與對應的影像區域進行元素-wise 乘法,並將結果累加起來。具體實作細節取決於所使用的卷積運算型別(例如標準卷積、跨步卷積等)。

圖表翻譯:

下面是一個簡單的Mermaid圖表,用於展示卷積運算的流程:

  flowchart TD
    A[輸入影像] --> B[填充]
    B --> C[卷積運算]
    C --> D[輸出結果]

圖表翻譯:

此圖表展示了從輸入影像到最終輸出結果的流程。首先,對輸入影像進行填充,以避免邊界問題。然後,進行卷積運算,這是本文的核心部分。最後,得到輸出結果,即經過卷積運算後的特徵對映。

卷積神經網路的實作

前向傳遞

在卷積神經網路中,前向傳遞的過程涉及將輸入資料經過卷積運算,以產生特徵圖。給定輸入資料 obs 和引數 param,我們可以計算輸出 out

def _compute_output_obs(obs, param):
    """
    obs: [channels, img_width, img_height]
    param: [in_channels, out_channels, param_width, param_height]
    """
    out = np.zeros((param.shape[1], obs.shape[1], obs.shape[2]))
    for c_out in range(param.shape[1]):
        for c_in in range(param.shape[0]):
            for o_w in range(obs.shape[1]):
                for o_h in range(obs.shape[2]):
                    for p_w in range(param.shape[2]):
                        for p_h in range(param.shape[3]):
                            if 0 <= o_w + p_w - param.shape[2] // 2 < obs.shape[1] and 0 <= o_h + p_h - param.shape[3] // 2 < obs.shape[2]:
                                out[c_out][o_w][o_h] += param[c_in][c_out][p_w][p_h] * obs[c_in][o_w + p_w - param.shape[2] // 2][o_h + p_h - param.shape[3] // 2]
    return out

def _output(inp, param):
    """
    inp: [batch_size, channels, img_width, img_height]
    param: [in_channels, out_channels, param_width, param_height]
    """
    outs = [_compute_output_obs(obs, param) for obs in inp]
    return np.stack(outs)

反向傳遞

在反向傳遞中,我們需要計算輸入的梯度和引數的梯度。給定輸入梯度 input_grad_obs 和輸出梯度 output_grad_obs,我們可以計算輸入的梯度和引數的梯度。

def _compute_grads_obs(input_obs, output_grad_obs, param):
    """
    input_obs: [in_channels, img_width, img_height]
    output_grad_obs: [out_channels, img_width, img_height]
    param: [in_channels, out_channels, img_width, img_height]
    """
    input_grad = np.zeros_like(input_obs)
    for c_in in range(input_obs.shape[0]):
        for o_w in range(input_obs.shape[1]):
            for o_h in range(input_obs.shape[2]):
                for c_out in range(output_grad_obs.shape[0]):
                    for p_w in range(param.shape[2]):
                        for p_h in range(param.shape[3]):
                            if 0 <= o_w + p_w - param.shape[2] // 2 < input_obs.shape[1] and 0 <= o_h + p_h - param.shape[3] // 2 < input_obs.shape[2]:
                                input_grad[c_in][o_w][o_h] += output_grad_obs[c_out][o_w + p_w - param.shape[2] // 2][o_h + p_h - param.shape[3] // 2] * param[c_in][c_out][p_w][p_h]
    return input_grad

圖表翻譯

下圖示意了卷積神經網路的前向傳遞和反向傳遞過程。

  graph LR
    A[輸入資料] -->|前向傳遞|> B[卷積運算]
    B -->|產生特徵圖|> C[輸出]
    C -->|反向傳遞|> D[計算梯度]
    D -->|更新引數|> E[最佳化模型]

圖表翻譯:

此圖表描述了卷積神經網路的前向傳遞和反向傳遞過程。在前向傳遞中,輸入資料經過卷積運算產生特徵圖。然後,在反向傳遞中,計算梯度並更新引數以最佳化模型。

卷積神經網路中的反向傳播:計算輸入梯度

在深度學習中,反向傳播(Backpropagation)是一種用於訓練神經網路的演算法。它涉及計算損失函式相對於模型引數的梯度,以便更新這些引數並最小化損失。當處理卷積神經網路(Convolutional Neural Networks, CNNs)時,計算輸入梯度是反向傳播過程中的關鍵步驟之一。

輸入梯度計算

給定輸入觀測值 input_obs、輸出梯度 output_grad_obs 和模型引數 param,我們可以計算輸入梯度 input_grad。以下是計算過程的步驟:

  1. 取得引數尺寸:首先,我們需要取得引數的尺寸,包括通道數、寬度和高度。

    • param_size = param.shape[2] 取得引數的寬度(或高度,因為通常是方形)。
    • param_mid = param_size // 2 計算引數中心點的索引,這對於後面的填充和索引計算很重要。
  2. 取得輸入觀測值尺寸:我們需要知道輸入觀測值的尺寸,包括寬度和高度。

    • img_size = input_obs.shape[1] 取得輸入觀測值的寬度(假設 input_obs 的 shape 為 (通道數, 寬度, 高度))。
  3. 取得通道數:瞭解輸入和輸出的通道數對於正確計算梯度至關重要。

    • in_channels = input_obs.shape[0] 取得輸入觀測值的通道數。
    • out_channels = param.shape[1] 取得引數的輸出通道數。
  4. 填充輸出梯度:為了方便計算,我們需要對輸出梯度進行填充,使其尺寸與輸入觀測值相匹配。

    • output_obs_pad = _pad_2d_channel(output_grad_obs, param_mid) 進行填充,確保在計算梯度時能夠正確地與引數進行卷積運算。
  5. 計算輸入梯度:現在,我們可以根據填充後的輸出梯度、引數和輸入觀測值的尺寸計算輸入梯度了。

    • 透過六個巢狀迴圈(分別對應輸入通道、輸出通道、輸入寬度、輸入高度、引數寬度和引數高度),計算每個位置的梯度,並累加到 input_grad 中。

從技術架構視角來看,本文深入探討了卷積神經網路(CNN)中一維、二維和雙向卷積運算的正向和反向傳播的實作細節,並清晰地闡述了影像卷積運算的核心概念及多通道卷積的處理方式。文章提供的程式碼範例涵蓋了從基本的卷積運算到多通道卷積以及反向傳播中輸入梯度的計算,展現了構建CNN的基礎模組。然而,程式碼示例主要關注核心計算邏輯,缺乏對邊界條件、步長和填充等關鍵引數的詳細說明,這在實際應用中需要開發者額外考量。此外,文章未涉及效能最佳化策略,例如使用向量化運算替代迴圈操作。展望未來,隨著硬體加速技術的發展,CNN的運算效率將進一步提升,同時,更複雜的卷積變體和網路架構也將不斷湧現。對於追求高效能的應用場景,建議開發者深入研究GPU加速和程式碼最佳化技巧,並關注新興的輕量級CNN架構,以在有限資源下最大化模型效能。