張量 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. 張量的屬性
張量擁有幾個關鍵屬性,用於描述其結構與內容:
- 軸 (Axes) 或 ndim
張量的軸數量,也就是維度的數量。- 純量的軸為 0,向量為 1,矩陣為 2,依此類推。
- 形狀 (Shape)
每個軸的元素數量,形狀用 tuple 表示。- 範例:矩陣 [[1, 2, 3], [4, 5, 6]] 的 shape 為 (2, 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
- 在 NumPy 中會使用
張量重塑
- 重塑就是調整張量各軸內的元素數,而張量元素總數不變的一種手法。
- 如在前一回使用過的資料前處理
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 $$
- 這個結論非常重要,代表:如果我們建構了多個密集層的神經網路,卻沒有搭配任何的激活函數,其效果等同於一個密集層,換言之,這個「深層」的神經網路模型不過是一個線性模型。