張量 Tensor 是神經網路的資料表示法。 在 Python,我們常用 NumPy 陣列來作為機器學習的基礎資料結構,說 NumPy 陣列也稱為張量。

張量的維、階、軸

  • 階(rank): 又稱為軸(axis),代表陣列的軸數。
  • 維(dimension): 某一階的元素個數。

1. 純量 (0D 張量)

純量是單一個數值,也稱為 0 維張量 (0D Tensor)

x = 5

2. 向量 (1D 張量)

向量是包含單一軸的數列,也稱為 1 維張量 (1D Tensor)

x = [1, 2, 3]

3. 矩陣 (2D 張量)

矩陣是二維的數據結構,也稱為 2 維張量 (2D Tensor)

x = [[1, 2, 3],
     [4, 5, 6]]

4. 3D 張量與高階張量

  • 3D 張量:在二維矩陣基礎上增加一個深度維度,常用於處理圖片數據 (例如 RGB 通道)。
  • 高階張量 (nD 張量):當張量的維度超過 3D 時,用於更高維度的資料表示,例如影片、文字數據等。
x = [[[1, 2, 3], 
      [4, 5, 6], 
      [7, 8, 9]],

     [[10, 11, 12], 
      [13, 14, 15], 
      [16, 17, 18]]]

5. 張量的屬性

張量擁有幾個關鍵屬性,用於描述其結構與內容:

  1. 軸 (Axes) 或 ndim
    張量的軸數量,也就是維度的數量。
    • 純量的軸為 0,向量為 1,矩陣為 2,依此類推。
  2. 形狀 (Shape)
    每個軸的元素數量,形狀用 tuple 表示。
    • 範例:矩陣 [[1, 2, 3], [4, 5, 6]] 的 shape 為 (2, 3)。
  3. 數據類型 (dtype) 張量中元素的資料類型,例如 float32、int32。
    • 範例:x = np.array([1, 2, 3], dtype=np.float32)。

6. batch 的概念

在深度學習中,為了加速訓練過程,通常將多筆資料合併為一個批次 (Batch),作為張量的第一維度。例如:

一個批次包含 32 張圖片 (每張圖片為 28x28 像素): shape = (32, 28, 28) 批次的概念允許高效處理資料,特別是在 GPU 上。

7. 張量運算

keras.layers.Dense(512, activation='relu) 的概念可以比擬成:

  • 它是一個計算公式: output = relu(w * input + b)

    • w 是權重(Weights),用包學習資料間的關係。
    • b 是偏差(Bias),用來調整輸出值。
    • relu 是一種激活函數,它讓輸出值變得非線性。
      • 具體而言就是 (double x): double => max(0.0, x);
  • 逐元素運算: NumPy 的運算中,很重要的就是逐元素(element-wise) 運算,意思是對張量中每個數值進行各自獨立的運算,如上述的加法與 relu 運算都是逐元素運算,非常適合平行處理,也就是向量化執行(vectorized implementations)

    • 在 GPU 上執行 Tensorflow 程式碼時,會透過全面向量化的 CUDA 架構來執行逐元素運算,加快運算的效率。
  • 張量擴張(Boardcasting): 在不考慮特例的情形,將兩個不同軸數的張量相加, NumPy 會對較小的張量進行擴張以匹配形狀較大的張量,包含:

    • 較小的張量會加入新的軸(擴張軸)以匹配較大的張量。
    • 較小的張量會在這些新的軸上重複寫入元素,以匹配較大張量的形狀。
    import numpy as np
    x = np.array((1,2,3,4,5))
    y = np.array((1))
    z = x + y
    z
    >>> array([2,3,4,5,6])
    
    • 實際的流程如:
    y = np.array((1,2,3,4,5,6,7,8,9,10))
    >>> array([1,2,3,4,5,6,7,8,9,10])
    
    y1 = np.expand_dims(y, axis=0)
    >>> array([[1,2,3,4,5,6,7,8,9,10]])
    
    y2 = np.concatenate([y1]*2, axios=0)
    >>> array([[1,2,3,4,5,6,7,8,9,10],
               [1,2,3,4,5,6,7,8,9,10]])
    
  • 張量的點積運算

    • 在 NumPy 中會使用 np.dot 函式來進行點積運算
    z = np.dot(x,y)
    
    • 向量 x 與向量 y 做點積,在一般邏輯上可表示成:
    def naive_vector_dot(x, y):
        assert len(x.shape) == 1
        assert len(y.shape) == 1
        assert x.shape[0] == y.shape[0]
        z = 0.
        for i in range(x.shape[0]):
            z += x[i] * y[i]
        return z
    
    • 矩陣 x 與向量 y 做點積,在一般邏輯上可表示成:
    def naive_matrix_vector_dot(x, y):
        assert len(x.shape) == 2
        assert len(y.shape) == 1
        assert x.shape[1] == y.shape[0]
        z = np.zeros(x.shape[0])
        for i in range(x.shape[0]):
            for j in range(x.shape[1]):
                z[i] += x[i, j] * y[j]
        return z
    
  • 張量重塑

    • 重塑就是調整張量各軸內的元素數,而張量元素總數不變的一種手法。
    • 如在前一回使用過的資料前處理 train_images = train_images.reshap((60000, 28*28)) 就是一種重塑
    • 常見的重塑還有矩陣轉置(transposition),其實就是 x[i, :] => x[:, i]
    x = np.transpose(x)
    
  • 張量運算的幾何解釋

1. 平移(Translation)

平移是一種將空間中的點沿特定方向移動的操作。
在幾何上,平移不是線性運算,需要用齊次座標表示。
假設平移向量為 \(\mathbf{t} = \begin{bmatrix} t_x \\ t_y \end{bmatrix}\),則點 \(\mathbf{p} = \begin{bmatrix} x \\ y \end{bmatrix}\) 被平移後的位置為:

$$ \mathbf{p}’ = \mathbf{p} + \mathbf{t} = \begin{bmatrix} x\\ y \end{bmatrix} + \begin{bmatrix} t_x\\ t_y \end{bmatrix} = \begin{bmatrix} x + t_x \\ y + t_y \end{bmatrix} $$

在齊次座標中,可以用矩陣形式表示為:

$$ \begin{bmatrix} x’ \\ y’ \\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y \\ 0 & 0 & 1 \end{bmatrix} . \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} $$

2. 旋轉 (Rotation)

旋轉是將點繞某個固定點(通常是原點)旋轉一個角度的操作。
旋轉矩陣(以逆時針旋轉角度 \(\theta\) ) 為: $$ \mathbf{R} = \begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix} $$ 對於 對於點 \(\mathbf{p} = \begin{bmatrix} x \\ y \end{bmatrix}\),旋轉後的位置為: $$ \mathbf{p}’ = \mathbf{R} \cdot \mathbf{p} = \begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix} . \begin{bmatrix} x \\ y \end{bmatrix} $$ 在齊次座標中表示為:

$$ \begin{bmatrix} x’ \\ y’ \\ 1 \end{bmatrix} = \begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix} . \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} $$

3. 縮放 (Scaling)

縮放改變點的大小,可以分別對 \(x\) 和 \(y\) 軸進行不同比例的縮放。

縮放矩陣為: $$ \mathbf{S} = \begin{bmatrix} s_x & 0 \\ 0 & s_y \end{bmatrix} $$

對於點 \(\mathbf{p} = \begin{bmatrix} x \\ y \end{bmatrix}\),縮放後的位置為:
$$ \mathbf{p}’ = \mathbf{S} \cdot \mathbf{p} = \begin{bmatrix} s_x & 0 \\ 0 & s_y \end{bmatrix} . \begin{bmatrix} x \\ y \end{bmatrix} $$

在齊次座標中表示為: $$ \begin{bmatrix} x’ \\ y’ \\ 1 \end{bmatrix} = \begin{bmatrix} s_x & 0 & 0 \\ 0 & s_y & 0 \\ 0 & 0 & 1 \end{bmatrix} . \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} $$

4. 線性變換 (Linear Transformation)

線性變換是縮放、旋轉或剪切等操作的統稱,可以用矩陣表示。
一般的線性變換矩陣為:
$$ \mathbf{A} = \begin{bmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{bmatrix} $$

對於點 \(\mathbf{p} = \begin{bmatrix} x \\ y \end{bmatrix}\),線性變換後的位置為:
$$ \mathbf{p}’ = \mathbf{A} \cdot \mathbf{p} = \begin{bmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{bmatrix} . \begin{bmatrix} x \\ y \end{bmatrix} $$

在齊次座標中: $$ \begin{bmatrix} x’ \\ y’ \\ 1 \end{bmatrix} = \begin{bmatrix} a_{11} & a_{12} & 0 \\ a_{21} & a_{22} & 0 \\ 0 & 0 & 1 \end{bmatrix} . \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} $$

5. 仿射變換 (Affine Transformation)

仿射變換是線性變換加上平移的組合。
在齊次座標中,仿射變換可表示為:
$$ \mathbf{T} = \begin{bmatrix} a_{11} & a_{12} & t_x \\ a_{21} & a_{22} & t_y \\ 0 & 0 & 1 \end{bmatrix} $$

對於點 \(\mathbf{p} = \begin{bmatrix} x \\ y \end{bmatrix}\),仿射變換後的位置為: $$ \begin{bmatrix} x’ \\ y’ \\ 1 \end{bmatrix} = \begin{bmatrix} a_{11} & a_{12} & t_x \\ a_{21} & a_{22} & t_y \\ 0 & 0 & 1 \end{bmatrix} . \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} $$

  • 由於仿射是線性變換(矩陣的點積)與平移運算(向量加法)的結合,其實就是密集層會用到的 \(\mathbf{y}=\mathbf{W}\cdot\mathbf{x}+\mathbf{b}\)。一個沒有激活函數的密集層就是仿射層。
  • 換句話說,在沒有激活函數的狀況下進行了無數次的仿射變換,可以等同於一次仿射變換 $$ \begin{align*} y_1 &= W_1 \cdot x + b_1 \\ y_2 &= W_2 \cdot y_1 + b_2 \end{align*} $$ 將 \(y_1\) 代入 \(y_2\) 的公式: $$ \begin{align*} y_2 &= W_2 \cdot (W_1 \cdot x + b_1) + b_2 \\ y_2 &= W_2 \cdot W_1 \cdot x + W_2 \cdot b_1 + b_2 \end{align*} $$ 設 \(W = W_2 \cdot W_1\) 和 \(b = W_2 \cdot b_1 + b_2\),則: $$ y_2 = W \cdot x + b $$
  • 這個結論非常重要,代表:如果我們建構了多個密集層的神經網路,卻沒有搭配任何的激活函數,其效果等同於一個密集層,換言之,這個「深層」的神經網路模型不過是一個線性模型。