張量 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
張量重塑
重塑就是調整張量各軸內的元素數,而張量元素總數不變的一種手法。
如在前一回使用過的資料前處理 train_images = train_images.reshap((60000, 28*28)) 就是一種重塑
常見的重塑還有矩陣轉置(transposition) ,其實就是 x[i, :] => x[:, i]
x = np . transpose ( x )
張量運算的幾何解釋
1. 平移(Translation)
平移是一種將空間中的點沿特定方向移動的操作。
在幾何上,平移不是線性運算,需要用齊次座標 表示。
假設平移向量為 t = [ t x t y ] \mathbf{t} = \begin{bmatrix} t_x \\\\ t_y \end{bmatrix} t = t x t y ,則點 p = [ x y ] \mathbf{p} = \begin{bmatrix} x \\\\ y \end{bmatrix} p = x y 被平移後的位置為:
p ′ = p + t = [ x y ] + [ t x t y ] = [ x + t x y + t y ] \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} p ′ = p + t = x y + t x t y = x + t x y + t y
在齊次座標中,可以用矩陣形式表示為:
[ x ′ y ′ 1 ] = [ 1 0 t x 0 1 t y 0 0 1 ] . [ x y 1 ] \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} x ′ y ′ 1 = 1 0 0 0 1 0 t x t y 1 . x y 1
2. 旋轉 (Rotation)
旋轉是將點繞某個固定點(通常是原點)旋轉一個角度的操作。
旋轉矩陣(以逆時針旋轉角度 θ \theta θ ) 為:
R = [ cos θ − sin θ sin θ cos θ ] \mathbf{R} =
\begin{bmatrix}
\cos\theta & -\sin\theta \\\\
\sin\theta & \cos\theta
\end{bmatrix} R = cos θ sin θ − sin θ cos θ
對於
對於點 p = [ x y ] \mathbf{p} = \begin{bmatrix} x \\\\ y \end{bmatrix} p = x y ,旋轉後的位置為:
p ′ = R ⋅ p = [ cos θ − sin θ sin θ cos θ ] . [ x y ] \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} p ′ = R ⋅ p = cos θ sin θ − sin θ cos θ . x y
在齊次座標中表示為:
[ x ′ y ′ 1 ] = [ cos θ − sin θ 0 sin θ cos θ 0 0 0 1 ] . [ x y 1 ] \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} x ′ y ′ 1 = cos θ sin θ 0 − sin θ cos θ 0 0 0 1 . x y 1
3. 縮放 (Scaling)
縮放改變點的大小,可以分別對 x x x 和 y y y 軸進行不同比例的縮放。
縮放矩陣為:
S = [ s x 0 0 s y ] \mathbf{S} =
\begin{bmatrix}
s_x & 0 \\\\
0 & s_y
\end{bmatrix} S = s x 0 0 s y
對於點 p = [ x y ] \mathbf{p} = \begin{bmatrix} x \\\\ y \end{bmatrix} p = x y ,縮放後的位置為:
p ′ = S ⋅ p = [ s x 0 0 s y ] . [ x y ] \mathbf{p}' = \mathbf{S} \cdot \mathbf{p} =
\begin{bmatrix}
s_x & 0 \\\\
0 & s_y
\end{bmatrix}
.
\begin{bmatrix}
x \\\\
y
\end{bmatrix} p ′ = S ⋅ p = s x 0 0 s y . x y
在齊次座標中表示為:
[ x ′ y ′ 1 ] = [ s x 0 0 0 s y 0 0 0 1 ] . [ x y 1 ] \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} x ′ y ′ 1 = s x 0 0 0 s y 0 0 0 1 . x y 1
線性變換是縮放、旋轉或剪切等操作的統稱,可以用矩陣表示。
一般的線性變換矩陣為:
A = [ a 11 a 12 a 21 a 22 ] \mathbf{A} =
\begin{bmatrix}
a_{11} & a_{12} \\\\
a_{21} & a_{22}
\end{bmatrix} A = a 11 a 21 a 12 a 22
對於點 p = [ x y ] \mathbf{p} = \begin{bmatrix} x \\\ y \end{bmatrix} p = [ x y ] ,線性變換後的位置為:
p ′ = A ⋅ p = [ a 11 a 12 a 21 a 22 ] . [ x y ] \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} p ′ = A ⋅ p = a 11 a 21 a 12 a 22 . x y
在齊次座標中:
[ x ′ y ′ 1 ] = [ a 11 a 12 0 a 21 a 22 0 0 0 1 ] . [ x y 1 ] \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} x ′ y ′ 1 = a 11 a 21 0 a 12 a 22 0 0 0 1 . x y 1
仿射變換是線性變換加上平移的組合。
在齊次座標中,仿射變換可表示為:
T = [ a 11 a 12 t x a 21 a 22 t y 0 0 1 ] \mathbf{T} =
\begin{bmatrix}
a_{11} & a_{12} & t_x \\\\
a_{21} & a_{22} & t_y \\\\
0 & 0 & 1
\end{bmatrix} T = a 11 a 21 0 a 12 a 22 0 t x t y 1
對於點 p = [ x y ] \mathbf{p} = \begin{bmatrix} x \\\\ y \end{bmatrix} p = x y ,仿射變換後的位置為:
[ x ′ y ′ 1 ] = [ a 11 a 12 t x a 21 a 22 t y 0 0 1 ] . [ x y 1 ] \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} x ′ y ′ 1 = a 11 a 21 0 a 12 a 22 0 t x t y 1 . x y 1
由於仿射是線性變換(矩陣的點積)與平移運算(向量加法)的結合,其實就是密集層會用到的 y = W ⋅ x + b \mathbf{y}=\mathbf{W}\cdot\mathbf{x}+\mathbf{b} y = W ⋅ x + b 。一個沒有激活函數的密集層就是仿射層。
換句話說,在沒有激活函數的狀況下進行了無數次的仿射變換,可以等同於一次仿射變換
y 1 = W 1 ⋅ x + b 1 y 2 = W 2 ⋅ y 1 + b 2 \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 = W 1 ⋅ x + b 1 = W 2 ⋅ y 1 + b 2
將 y 1 y_1 y 1 代入 y 2 y_2 y 2 的公式:
y 2 = W 2 ⋅ ( W 1 ⋅ x + b 1 ) + b 2 y 2 = W 2 ⋅ W 1 ⋅ x + W 2 ⋅ b 1 + b 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*} y 2 y 2 = W 2 ⋅ ( W 1 ⋅ x + b 1 ) + b 2 = W 2 ⋅ W 1 ⋅ x + W 2 ⋅ b 1 + b 2
設 W = W 2 ⋅ W 1 W = W_2 \cdot W_1 W = W 2 ⋅ W 1 和 b = W 2 ⋅ b 1 + b 2 b = W_2 \cdot b_1 + b_2 b = W 2 ⋅ b 1 + b 2 ,則:
y 2 = W ⋅ x + b y_2 = W \cdot x + b y 2 = W ⋅ x + b
這個結論非常重要,代表:如果我們建構了多個密集層的神經網路,卻沒有搭配任何的激活函數,其效果等同於一個密集層,換言之,這個「深層」的神經網路模型不過是一個線性模型。