In [ ]:
import numpy as np

In [ ]:
%run magic.ipynb

Supervised learning for classification

給一堆 $x$, 和他的分類,我們找出計算 x 的分類的方式

One hot encoding

如果我們有三類種類別, 我們可以來編碼這三個類別

  • $(1,0,0)$
  • $(0,1,0)$
  • $(0,0,1)$

問題

  • 為什麼不直接用 1,2,3 這樣的編碼呢?

Softmax Regression 的模型是這樣的

我們的輸入 $x=\begin{pmatrix} x_0 \\ x_1 \\ x_2 \\ x_3 \end{pmatrix} $ 是一個向量,我們看成 column vector 好了

而 Weight: $W = \begin{pmatrix} W_0 \\ W_1 \\ W_2 \end{pmatrix} = \begin{pmatrix} W_{0,0} & W_{0,1} & W_{0,2} & W_{0,3}\\ W_{1,0} & W_{1,1} & W_{1,2} & W_{1,3} \\ W_{2,0} & W_{2,1} & W_{2,2} & W_{2,3} \end{pmatrix} $

Bias: $b=\begin{pmatrix} b_0 \\ b_1 \\ b_2 \end{pmatrix} $

我們先計算"線性輸出" $ c = \begin{pmatrix} c_0 \\ c_1 \\ c_2 \end{pmatrix} = Wx+b = \begin{pmatrix} W_0 x + b_0 \\ W_1 x + b_1 \\ W_2 x + b_2 \end{pmatrix} $, 然後再取 $exp$ (逐項取)。 最後得到一個向量。

$d = \begin{pmatrix} d_0 \\ d_1 \\ d_2 \end{pmatrix} = e^{W x + b} = \begin{pmatrix} e^{c_0} \\ e^{c_1} \\ e^{c_2} \end{pmatrix}$

將這些數值除以他們的總和。 給定輸入 x, 我們希望算出來的數字 q_i 會符合 x 的類別是 i 的機率。

$q_i = Predict_{W,b}(Y=i|x) = \frac {e^{W_i x + b_i}} {\sum_j e^{W_j x + b_j}} = \frac {d_i} {\sum_j d_j}$

合起來看,就是 $q = \frac {d} {\sum_j d_j} $

問題

  • 為什麼要用 $exp$?

先隨便算一個 $\mathbb{R}^2 \rightarrow \mathbb{R}^3$ 的網路


In [ ]:
# Weight
W = Matrix([1,2],[3,4], [5,6])
W

In [ ]:
# Bias
b = Vector(1,0,-1)
b

In [ ]:
# 輸入
x = Vector(2,-1)
x

任務:計算最後的猜測機率 $q$

Hint: np.exp 可以算 $exp$


In [ ]:
# 請在這裡計算

In [ ]:
# 參考答案
#%load solutions/softmax_compute_q.py

In [ ]:
%run -i solutions/softmax_compute_q.py
# 顯示 q
q

練習

設計一個網路:

  • 輸入是二進位 0 ~ 15
  • 輸出依照對於 4 的餘數分成四類

Hint: 可以參考上面 W, b 的設定方式


In [ ]:
# Hint 下面產生數字 i 的 2 進位向量
i = 13
x = Vector(i%2, (i>>1)%2, (i>>2)%2, (i>>3)%2)
x

In [ ]:
# 請在這裡計算

In [ ]:
# 參考答案
#%load solutions/softmax_mod4.py

練習

設計一個網路:

  • 輸入是二進位 0 ~ 15
  • 輸出依照對於 3 的餘數分成三類

Hint: 不用全部正確,用猜的,但正確率要比亂猜高。可以利用統計的結果猜猜看。


In [ ]:
# 請在這裡計算

In [ ]:
# 參考答案
#%load solutions/softmax_mod3.py

Gradient descent

誤差函數

為了要評斷我們的預測的品質,要設計一個評斷誤差的方式

假設輸入值 $x$ 對應到的真實類別是 $y$, 那我們定義誤差函數

$ loss = -\log(q_y)=- \log(Predict_{W,b}(Y=y|x)) $

這個方法叫做 Cross entropy

其實比較一般但比較複雜一點的寫法是

$ loss = - \sum_i p_i\log(q_i) = - p \cdot \log q$

其中 $i$ 是所有類別, 而 $ p_i = \Pr(Y=i|x) $ 是真實發生的機率

但我們目前 $x$ 對應到的真實類別是 $y$, 所以直接 $p_i = 1$

想辦法改進。

我們用一種被稱作是 gradient descent 的方式來改善我們的誤差。

因為我們知道 gradient 是讓函數上升最快的方向。所以我們如果朝 gradient 的反方向走一點點(也就是下降最快的方向),那麼得到的函數值應該會小一點。

記得我們的變數是 $W$ 和 $b$ (裡面有一堆 W_i,j b_i 這些變數),所以我們要把 $loss$ 對 $W$ 和 $b$ 裡面的每一個參數來偏微分。

還好這個偏微分是可以用手算出他的形式,而最後偏微分的式子也不會很複雜。

$loss$ 展開後可以寫成

$loss = -\log(q_y) = \log(\sum_j d_j) - d_i \

= \log(\sum_j e^{W_j x + b_j}) - W_i x - b_i$

注意 $d_j = e^{W_j x + b_j}$ 只有變數 $b_j, W_j$

對 $k \neq i$ 時, $loss$ 對 $b_k$ 的偏微分是 $$ \frac{e^{W_k x + b_k}}{\sum_j e^{W_j x + b_j}} = q_k$$ 對 $k = i$ 時, $loss$ 對 $b_k$ 的偏微分是 $$ q_k - 1$$

對 $W$ 的偏微分也不難

對 $k \neq i$ 時, $loss$ 對 $W_{k,t}$ 的偏微分是 $$ \frac{e^{W_k x + b_k} x_t}{\sum_j e^{W_j x + b_j}} = q_k x_t$$ 對 $k = i$ 時, $loss$ 對 $W_{k,t}$ 的偏微分是 $$ q_k x_t - x_t$$

實做部份


In [ ]:
# 先產生隨機的 W 和 b
W = Matrix(np.random.normal(size=(3,4)))
b = Vector(np.random.normal(size=(3,)))

In [ ]:
W

In [ ]:
b

問題

W, b 的 size 為什麼要這樣設定?

任務: 隨便設定一組 x, y, 我們來跑跑看 gradient descent


In [ ]:
i = 14
x = Vector(i%2, (i>>1)%2, (i>>2)%2, (i>>3)%2)
y = i%3

步驟:計算 q


In [ ]:
# 請在這裡計算

In [ ]:
# 參考答案(跟前面一樣)¶ 
#%load solutions/softmax_compute_q.py
%run -i solutions/softmax_compute_q.py
#顯示 q
q

步驟: 計算 loss


In [ ]:
# 請在這裡計算

In [ ]:
# 參考答案(跟前面一樣)
%run -i solutions/softmax_compute_loss1.py
#顯示 loss
loss

步驟:計算對 b 的 gradient


In [ ]:
# 請在這裡計算 grad_b

In [ ]:
#參考答案
%run -i  solutions/softmax_compute_grad_b.py
grad_b

步驟:計算對 W 的 gradient


In [ ]:
# 請在這裡計算

In [ ]:
#參考答案
%run -i  solutions/softmax_compute_grad_W.py
grad_W

步驟:更新 W, b 各減掉 0.5 * gradient, 然後看看新的 loss 是否有進步了?


In [ ]:
# 請在這裡計算

In [ ]:
# 參考答案
%run -i solutions/softmax_update_Wb.py

In [ ]:
# 原先的 q
q

In [ ]:
# 原先的 loss
loss

In [ ]:
# 現在的 loss
%run -i solutions/softmax_compute_q.py
%run -i solutions/softmax_compute_loss1.py
loss

In [ ]:
q

一次訓練多組資料

上面只針對一組 x (i=14) 來訓練,如果一次對所有 x 訓練呢?

通常我們會把組別放在 axis-0


In [ ]:
X = np.array([Vector(i%2, (i>>1)%2, (i>>2)%2, (i>>3)%2) for i in range(16)])
for i in range(4):
    print("i=", i)
    display(X[i])

In [ ]:
X

In [ ]:
# 對應的組別 
y = np.array([i%3 for i in range(16)])
y

任務: 將訓練向量化


In [ ]:
# 請在這裡計算




# 參考解答如後

對照

d = np.exp(W @ x + b)
q = d/d.sum()
q

In [ ]:
d = np.exp(W @ X + b)
q = d/d.sum(axis=(1,2), keepdims=True)
q

對照

loss = -np.log(q[y])
loss

In [ ]:
loss = -np.log(q[range(len(y)), y])
loss

In [ ]:
# 用平均當成我們真正的 loss
loss.mean()

對照

grad_b = q - np.eye(3)[y][:, None]

In [ ]:
# fancy indexing :p
one_y = np.eye(3)[y][..., None]
grad_b_all = q - one_y
grad_b = grad_b_all.mean(axis=0)
grad_b

對照

grad_W = grad_b @ x.T

In [ ]:
grad_W_all = grad_b_all @ X.swapaxes(1,2)
grad_W = grad_W_all.mean(axis=0)
grad_W

In [ ]:
W -=  0.5 * grad_W
b -=  0.5 * grad_b

In [ ]:
# 之前的 loss
loss.mean()

In [ ]:
d = np.exp(W @ X + b)
q = d/d.sum(axis=(1,2), keepdims=True)
loss = -np.log(q[range(len(y)), y])
loss.mean()

任務:全部合在一起

  • 設定 W,b
  • 設定 X
  • 訓練三十次
    • 計算 q 和 loss
    • 計算 grad_b 和 grad_W
    • 更新 W, b
  • 看看準確度

In [ ]:
# 在這裡計算

In [ ]:
# 參考答案
%run -i solutions/softmax_train.py

In [ ]:
# 畫出 loss 的曲線
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(loss_history);

In [ ]:
# 對答案
display((W @ X + b).argmax(axis=1).ravel())
display(y)

In [ ]: