起手式,導入 numpy, matplotlib
In [1]:
from PIL import Image
import numpy as np
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
matplotlib.style.use('bmh')
matplotlib.rcParams['figure.figsize']=(8,5)
使用之前下載的 mnist 資料,載入訓練資料 train_set
和測試資料 test_set
In [2]:
import gzip
import pickle
with gzip.open('../Week02/mnist.pkl.gz', 'rb') as f:
train_set, validation_set, test_set = pickle.load(f, encoding='latin1')
train_X, train_y = train_set
validation_X, validation_y = validation_set
test_X, test_y = test_set
之前的看圖片函數
In [3]:
from IPython.display import display
def showX(X):
int_X = (X*255).clip(0,255).astype('uint8')
# N*784 -> N*28*28 -> 28*N*28 -> 28 * 28N
int_X_reshape = int_X.reshape(-1,28,28).swapaxes(0,1).reshape(28,-1)
display(Image.fromarray(int_X_reshape))
# 訓練資料, X 的前 20 筆
showX(train_X[:20])
train_set 是用來訓練我們的模型用的
我們的模型是很簡單的 logistic regression 模型,用到的參數只有一個 784x10 的矩陣 W 和一個長度 10 的向量 b。
我們先用均勻隨機亂數來設定 W 和 b 。
In [4]:
W = np.random.uniform(low=-1, high=1, size=(28*28,10))
b = np.random.uniform(low=-1, high=1, size=10)
先拿第一筆資料試試看, x 是輸入。 y 是這張圖片對應到的數字(以這個例子來說 y=5)。
In [5]:
x = train_X[0]
y = train_y[0]
showX(x)
y
Out[5]:
先計算 $e^{Wx+b} $
In [6]:
Pr = np.exp(x @ W + b)
Pr.shape
Out[6]:
然後 normalize,讓總和變成 1 (符合機率的意義)
In [7]:
Pr = Pr/Pr.sum()
Pr
Out[7]:
由於 $W$ 和 $b$ 都是隨機設定的,所以上面我們算出的機率也是隨機的。
正確解是 $y=5$, 運氣好有可能猜中
為了要評斷我們的預測的品質,要設計一個評斷誤差的方式,我們用的方法如下(不是常見的方差,而是用熵的方式來算,好處是容易微分,效果好)
上述的誤差評分方式,常常稱作 error 或者 loss,數學式可能有點費解。實際計算其實很簡單,就是下面的式子
In [8]:
loss = -np.log(Pr[y])
loss
Out[8]:
$loss$ 展開後可以寫成 $loss = \log(\sum_j e^{W_j x + b_j}) - W_i x - b_i$
對 $k \neq i$ 時, $loss$ 對 $b_k$ 的偏微分是 $$ \frac{e^{W_k x + b_k}}{\sum_j e^{W_j x + b_j}} = \Pr(Y=k | x, W, b)$$ 對 $k = i$ 時, $loss$ 對 $b_k$ 的偏微分是 $$ \Pr(Y=k | x, W, b) - 1$$
In [9]:
gradb = Pr.copy()
gradb[y] -= 1
print(gradb)
對 $W$ 的偏微分也不難
對 $k \neq i$ 時, $loss$ 對 $W_{k,t}$ 的偏微分是 $$ \frac{e^{W_k x + b_k} W_{k,t} x_t}{\sum_j e^{W_j x + b_j}} = \Pr(Y=k | x, W, b) x_t$$ 對 $k = i$ 時, $loss$ 對 $W_{k,t}$ 的偏微分是 $$ \Pr(Y=k | x, W, b) x_t - x_t$$
In [10]:
print(Pr.shape, x.shape, W.shape)
gradW = x.reshape(784,1) @ Pr.reshape(1,10)
gradW[:, y] -= x
算好 gradient 後,讓 W 和 b 分別往 gradient 反方向走一點點,得到新的 W 和 b
In [11]:
W -= 0.1 * gradW
b -= 0.1 * gradb
再一次計算 $\Pr$ 以及 $loss$
In [12]:
Pr = np.exp(x @ W + b)
Pr = Pr/Pr.sum()
loss = -np.log(Pr[y])
loss
Out[12]:
我們將同樣的方式輪流對五萬筆訓練資料來做,看看情形會如何
In [13]:
W = np.random.uniform(low=-1, high=1, size=(28*28,10))
b = np.random.uniform(low=-1, high=1, size=10)
score = 0
N=50000*20
d = 0.001
learning_rate = 1e-2
for i in range(N):
if i%50000==0:
print(i, "%5.3f%%"%(score*100))
x = train_X[i%50000]
y = train_y[i%50000]
Pr = np.exp( x @ W +b)
Pr = Pr/Pr.sum()
loss = -np.log(Pr[y])
score *=(1-d)
if Pr.argmax() == y:
score += d
gradb = Pr.copy()
gradb[y] -= 1
gradW = x.reshape(784,1) @ Pr.reshape(1,10)
gradW[:, y] -= x
W -= learning_rate * gradW
b -= learning_rate * gradb
結果發現正確率大約是 92%, 但這是對訓練資料而不是對測試資料
而且,一筆一筆的訓練資也有點慢,線性代數的特點就是能夠向量運算。如果把很多筆 $x$ 當成列向量組合成一個矩陣(然後叫做 $X$),由於矩陣乘法的原理,我們還是一樣計算 $WX+b$ , 就可以同時得到多筆結果。
下面的函數,可以一次輸入多筆 $x$, 同時一次計算多筆 $x$ 的結果和準確率。
In [14]:
def compute_Pr(X):
Pr = np.exp(X @ W + b)
return Pr/Pr.sum(axis=1, keepdims=True)
def compute_accuracy(Pr, y):
return (Pr.argmax(axis=1)==y).mean()
下面是更新過得訓練過程, 當 i%100000 時,順便計算一下 test accuracy 和 valid accuracy。
In [15]:
%%timeit -r 1 -n 1
def compute_Pr(X):
Pr = np.exp(X @ W + b)
return Pr/Pr.sum(axis=1, keepdims=True)
def compute_accuracy(Pr, y):
return (Pr.argmax(axis=1)==y).mean()
W = np.random.uniform(low=-1, high=1, size=(28*28,10))
b = np.random.uniform(low=-1, high=1, size=10)
score = 0
N=20000
batch_size = 128
learning_rate = 0.5
for i in range(0, N):
if (i+1)%2000==0:
test_score = compute_accuracy(compute_Pr(test_X), test_y)*100
train_score = compute_accuracy(compute_Pr(train_X), train_y)*100
print(i+1, "%5.2f%%"%test_score, "%5.2f%%"%train_score)
# 隨機選出一些訓練資料出來
rndidx = np.random.choice(train_X.shape[0], batch_size, replace=False)
X, y = train_X[rndidx], train_y[rndidx]
# 一次計算所有的 Pr
Pr = compute_Pr(X)
# 計算平均 gradient
Pr_one_y = Pr-np.eye(10)[y]
gradb = Pr_one_y.mean(axis=0)
gradW = X.T @ (Pr_one_y) / batch_size
# 更新 W 和 ba
W -= learning_rate * gradW
b -= learning_rate * gradb
最後得到的準確率是 92%-93%
不算完美,不過畢竟這只有一個矩陣而已。
光看數據沒感覺,我們來看看前十筆測試資料跑起來的情形
可以看到前十筆只有錯一個
In [16]:
Pr = compute_Pr(test_X[:10])
pred_y =Pr.argmax(axis=1)
for i in range(10):
print(pred_y[i], test_y[i])
showX(test_X[i])
看看前一百筆資料中,是哪些情況算錯
In [17]:
Pr = compute_Pr(test_X[:100])
pred_y = Pr.argmax(axis=1)
for i in range(100):
if pred_y[i] != test_y[i]:
print(pred_y[i], test_y[i])
showX(test_X[i])