Logistic Regression 之手寫數字識別


 

0. 前言

  本文是應用 logsitic regression 模型對手寫數字識別的實現,整個程序是基於 MNIST 手寫數字數據庫進行 train, cross validate 和 test 的,如需下載 python 實現的源代碼,請點擊這里,你還可以在這里下載數據集。 MNIST 數據庫由NYU 的 Yann LeCun 等人維護, Yann LeCun 自 1998 年以來就一直從事這方面的研究,實現的方法包括 linear classifier, K-NN, SVM, CNN 等,他提出的卷積神經網絡是第一個真正多層結構學習算法,利用空間相對關系減少參數數目以提高訓練性能,咱手寫數字識別的第三種方法就是基於這個算法滴,Yan LeCun 同志曾經在深度機器學習大神 Geoffrey E. Hinton 底下做過博士后,現在也是 deep learning 的一個領軍人物了。

  言歸正轉,要用 python 實現這個項目還得用到 python 里面一個比較特殊的 deep learning 的庫—— Theano, 初次接觸這個庫,理解起來還需要一點時間,比如說 GPU 加速處理時,你需要將向量塊結構的變量轉換為 shared variables, 比如說類似於函數作用的 Graph structure, 學習曲線稍顯陡峭,如果你現在編程暫時用不到這些,直接跳過也行。

1. MNIST 相關

  動手之前,我們要先了解一下 MNIST 的相關情況。MNIST 包含了 7w 張 28×28 pixels 大小、數字 size 已經歸一化 (標准是什么?是每類數字外面輪廓框框的大小嗎?)、中心化( maybe 框框的中心)的圖片。 deep learning tutorial 已經把它分為 train set, validation set and test set 3 個部分,分別包含了 5w、 1w 和 1w 張圖片。你如果一定要自己親眼瞅一瞅,跑一跑下面的代碼,看一看 train set 上第 0 張圖片長啥樣:

 1 import cPickle
 2 import gzip
 3 
 4 import numpy
 5 import Image
 6 
 7 f = gzip.open('../data/mnist.pkl.gz', 'rb')
 8 train_set, valid_set, test_set = cPickle.load(f)
 9 f.close()
10 
11 print "The 0'th label:", train_set[1][0]
12 lst = numpy.asarray(train_set[0][0], dtype=numpy.float32)
13 img = Image.fromstring(mode='F', size=(28, 28), data=lst*255)
14 img.show()

  不出意外的話,第 0 張訓練圖片應該是 5, label 也是 5:

The 0'th label: 5

2. 梯度下降

  首先,本次實驗的 logistic regression 框架可以用下面這張圖來表示( logistic regression 基礎知識請參考上篇文章):

x: 28*28 個 pixels作為 784 個輸入節點

w: 輸入節點的權重

b: bias 項的權重

0~9: 輸出節點

      此次實驗的優化方法采用的是梯度下降法。梯度下降法中我們熟知的是標准的梯度下降算法 (ordinary gradient descent), 即每次迭代需要計算基於所有 datapoint 的 loss function 的 gradient, 計算量很大,速度較慢,而且因為標准誤差曲面只有一個,如果存在多個極小值點,此算法容易陷於局部極小值。同時還有另外一種比較高效的方法是 SGD (stochastic gradient descent), 這種方法 estimating the gradient from just a few examples at a time instead of the entire training set. 由於每次計算 gradient 的時候每個誤差曲面是不同的,它們不一定具有相同的極小值,所以有時可以避免局部極小值 (這是我的直觀理解)。A stationary point with respect to the error funciton for the whole data set will generally not be a stationary point for each data point individually (Bishop PRML). 在這個程序中,考慮到 shared variable 的特點,我們采用的是類似 SGD 的 Minibatch SGD ,每次迭代是基於好幾百個 examples 的梯度計算。

  同時,由於我們采用的是梯度下降策略,並不要求 Hessian 矩陣是可逆的,所以損失函數無需 weight decay 項,直接最小化最大似然函數的負對數即可,當然,這種方法可能導致的后果是最優解不是唯一的,並且容易產生 overfitting (這里采用了 early-stopping 方法解決這個問題)。對於某一次 batch of train set 來說, Loss Function 可以寫成:

  Logistic Regression 模型建立的代碼如下:

 1 ####################
 2 #Build Actual Model#
 3 ####################
 4 
 5 # allocate symbolic variables for the data
 6 index = T.lscalar()  # index to a [mini]batch
 7 x = T.matrix('x')  # the data is presented as rasterized images
 8 y = T.ivector('y')  # the labels are presented as 1D vector of [int] labels
 9 
10 # 創建之前定義的 LR 類,這里沒有寫出來。
11 # 類里面初始化了 w, b 為全零向量
12 # 按照 LR 的結構建立了從 inputs 到 outputs 的 graph structure。
13 classifier = LogisticRegression(input=x, n_in=28 * 28, n_out=10)
14 
15 # 輸入 batch 的 index, 函數按照 index 將數據塊傳遞給 x, 
16 # x 作為 classfier 的默認參數進入 LogisticRegression 類處理流程
17 # 最后輸出錯誤分類的點的數目
18 # 整個過程就像 pipes
19 test_model = theano.function(inputs=[index],
20         outputs=classifier.errors(y),
21         givens={
22             x: test_set_x[index * batch_size: (index + 1) * batch_size],
23             y: test_set_y[index * batch_size: (index + 1) * batch_size]})
24 
25 validate_model = theano.function(inputs=[index],
26         outputs=classifier.errors(y),
27         givens={
28             x: valid_set_x[index * batch_size:(index + 1) * batch_size],
29             y: valid_set_y[index * batch_size:(index + 1) * batch_size]})
30 
31 # 求梯度,直接調用函數
32 g_W = T.grad(cost=cost, wrt=classifier.W)
33 g_b = T.grad(cost=cost, wrt=classifier.b)
34 
35 updates = [(classifier.W, classifier.W - learning_rate * g_W),
36            (classifier.b, classifier.b - learning_rate * g_b)]
37 
38 cost = classifier.negative_log_likelihood(y)
39 
40 # 數據先利用 givens 得到數據塊
41 # 然后進入類求得 cost 函數
42 # 最后利用 update 將權重更新
43 train_model = theano.function(inputs=[index],
44         outputs=cost,
45         updates=updates,
46         givens={
47             x: train_set_x[index * batch_size:(index + 1) * batch_size],
48             y: train_set_y[index * batch_size:(index + 1) * batch_size]})

3. Early Stopping

  Early stopping是應對 overfitting 的方法之一。主要思路如下:先用 train set 將 model 進行訓練,然后用得到的 model 來預測 validation set, 預測效果可以用 error 來表示。如果 error 在減小,說明我們的模型還可以繼續訓練,當 error 增大的時候,很有可能我們的 model 就 overfitting 了,這時候優化算法就應該 halts 了。但是有一個問題是 “increasing validation error” is ambiguous, 很有可能是整體先下降再上升,但是在局部上表現為 up and down, 就像股票走勢一樣。要解決這個問題,可以一開始我們就把 model 訓練到全局極小值,然后對這個過程中的每一次迭代進行 validation error 的計算,這樣做雖然很安全,但是損失了 early stopping 快速的優點。這里,我們采用的方法是:計算每一次迭代中 validation error, 如果比上次至少降低了 0.05%, 說明效果可以,就可以將迭代的限制次數增加到本次迭代次數的兩倍。顯然,這是一個基於經驗的辦法。

  訓練模型的重要參數:

  1. epoch: 默認在整個 train set 層次上訓練輪數低於 1000
  2. practice: 默認在 batch 層次上最少的迭代次數是 5000, 如果 model 在 validation set 上表現效果好,可以增加到目前迭代次數的 2 倍
  3. validation_frequency: 每隔多少個 batch 評價一下 model, model 效果提升了,就可以增加 batch 的迭代限制次數。

  最后,由於 model 是 基於 validation set 上的最小錯誤率選出來的,因此,validation set 對於這個 model 來說是有偏的。換句話說, 選出來的模型會比較契合你現在的 validation set,所以不能用它來表示你模型的准確度,於是乎,我們又划分出一個 model 從來沒有見過的 test set,用它的 error 來表示最終的預測能力。根據 Andrew Ng 的課程, train set : validation set : test set = 6 : 2 : 2, 這里,我們用的是 5 : 1 : 1.

  模型訓練的代碼如下:

1 #############
2 #Train Model#
3 #############
4 epoch = 0
5 while (epoch < n_epochs) and (not done_looping):
6         epoch = epoch + 1
7         for minibatch_index in xrange(n_train_batches):
8                 minibatch_avg_cost = train_model(minibatch_index)
9 ....

 

 由於代碼比較簡單,這里不做詳細解析了,詳細步驟請參考這里的最后。

  用 logistic regression 的方法,可以將 test set 上的錯誤率降低到 7.489583 %

4. Load and Save Models

  辛辛苦苦把 model 訓練好了,千萬不能忘記保存啊,這樣,下次一個新數據過來的時候,我們就可以直接進行判斷啦。

 

 


參考資料:

[1]: http://deeplearning.net/tutorial/logreg.html

[2]: Early-stopping


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM