天天看點

Keras+數字識别DemoKeras2.0

Keras2.0

Why Keras

你可能會問,為什麼不學TensorFlow呢?明明tensorflow才是目前最流行的machine learning庫之一啊。其實,它并沒有那麼好用,tensorflow和另外一個功能相近的toolkit theano,它們是非常flexible的,你甚至可以把它想成是一個微分器,它完全可以做deep learning以外的事情,因為它的作用就是幫你算微分,拿到微分之後呢,你就可以去算gradient descent之類,而這麼flexible的toolkit學起來是有一定的難度的,你沒有辦法在半個小時之内精通這個toolkit

但是另一個toolkit——Keras,你是可以在數十分鐘内就熟悉并精通它的,然後用它來implement一個自己的deep learning,Keras其實是tensorflow和theano的interface,是以用Keras就等于在用tensorflow,隻是有人幫你把操縱tensorflow這件事情先幫你寫好

是以Keras是比較容易去學習和使用的,并且它也有足夠的彈性,除非你自己想要做deep learning的研究,去設計一個自己的network,否則多數你可以想到的network,在Keras裡都有現成的function可以拿來使用;因為它背後就是tensorflow or theano,是以如果你想要精進自己的能力的話,你永遠可以去改Keras背後的tensorflow的code,然後做更厲害的事情

而且,現在Keras已經成為了Tensorflow官方的API,它像搭積木一樣簡單

Keras+數字識别DemoKeras2.0

接下來我們用手寫數字識别的demo來介紹一下"Hello world" of deep learning

prepare data

使用的data是MNIST的Data:http://yann.lecun.com/exdb/mnist/

Keras提供了自動下載下傳MNIST data的function:http://keras.io/datasets/

process

首先要先導入keras包:

from keras.models import Sequential

step 1:define a set of function——neural network

先用

Sequential()

宣告建立一個model

然後開始疊一個neural network:它有兩個hidden layer,每個hidden layer都有500個neuron

  • 加一個Fully connected的layer——用Dense來表示,當然你也可以加别的layer,比如convolution的layer

    之前我們說過,input layer比較特殊,它并不是真正意義上的layer,因為它沒有所謂的"neuron",于是Keras在model裡面加的第一層layer會有一些特殊,要求同時輸入

    input_dim

    units

    ,分别代表第一層hidden layer輸入維數(也就是input layer的dimension)和第一層hidden layer的neuron個數

    input_dim=28*28

    表示一個28*28=784長度的vector,代表image;

    units=500

    表示該層hidden layer要有500個neuron;

    activation=‘sigmoid’

    表示激活函數使用sigmoid function

    加完layer之後,還需要設定該層hidden layer所使用的activation function,這裡直接就用sigmoid function

    在Keras裡還可以選别的activation function,比如softplus、softsign、relu、tanh、hard_sigmoid、linear等等,如果你要加上自己的activation function,其實也蠻容易的,隻要在Keras裡面找到寫activation function的地方,自己再加一個進去就好了

  • 從第二層hidden layer開始,如果要在model裡再加一個layer,就用model.add增加一個Dense全連接配接層,包括

    units

    activation

    參數

    這邊就不需要再redefine

    input_dim

    是多少了,因為新增layer的input就等于前一個layer的output,Keras自己是知道這件事情的,是以你就直接告訴它說,新加的layer有500個neuron就好了

    這裡同樣把activation function設定為sigmoid function

  • 最後,由于是分10個數字,是以output是10維,如果把output layer當做一個Multi-class classifier的話,那activation function就用softmax(這樣可以讓output每一維的幾率之和為1,表現得更像一個機率分布),當然你也可以選擇别的
Keras+數字識别DemoKeras2.0

注:上圖中寫的是Keras1.0的文法,在筆記中給出的則是Keras2.0的文法,應當使用後者

Step 2:goodness of function——cross entropy

evaluate一個function的好壞,你要做的事情是用model.compile去定義你的loss function是什麼

比如說你要用cross entropy的話,那你的loss參數就是categorical_crossentropy(Keras裡的寫法)

model.compile(loss='categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])
           
Keras+數字識别DemoKeras2.0
Step 3:pick the best function

Configuration

在training之前,你要先下一些configuration告訴它training的時候,你打算要怎麼做

你要定義的第一個東西是optimizer,也就是說,你要用什麼樣的方式來找最好的function,雖然optimizer後面可以接不同的方式,但是這些不同的方式,其實都是gradient descent類似的方法

有一些方法machine會自動地,empirically(根據經驗地)決定learning rate的值應該是多少,是以這些方法是不需要給它learning rate的,Keras裡面有諸如:SGD(gradient descent)、RMSprop、Adagrad、Adadelta、Adam、Adamax、Nadam之類的尋找最優參數的方法,它們都是gradient descent的方式

model.compile(loss='categorical crossentropy',
              optimizer='adam',
              metrics=['accuracy'])
           

Training

決定好怎麼做gradient descent之後,就是實際去做訓練了,去跑gradient descent找最優參數了

這裡使用的是

model.fit

方法,要給它4給input(假設我們給了10000張image作Training data)

  • 第一個input是Training data——

    x_train

    在這個case裡,Training data就是一張一張的image,需要把它存放到numpy array裡面,這個numpy array是two-dimension的matrix,每張image存為numpy array的一個行向量(它把image中28*28個像素值拉成一個行向量),總共有10000行,它的列數就是每張image的像素點個數,即28*28=784列
  • 第二個input是每一個Training data對應的label——

    y_train

    在這個case裡,就是标志着這張image對應的是0~9的那一個數字,同樣也是two-dimension的numpy array,每張image的label存為numpy array的一個行向量,用來表示0~9這10個數字中的某一個數,是以是10列,用的是one-hot編碼,10個數字中對了對應image的那個數字為1之外其餘都是0
  • 第三個input是

    batch_size

    ,告訴Keras我們的batch要有多大

    在這個case裡,batch_size=100,表示我們要把100張随機選擇的image放到一個batch裡面,然後把所有的image分成一個個不同的batch,Keras會自動幫你完成随機選擇image的過程,不需要自己去code

  • 第四個input是

    nb_epoch

    ,表示對所有batch的訓練要做多少次

    在這個case裡,nb_epoch=20,表示要對所有的batch進行20遍gradient descent的訓練,每看到一個batch就update一次參賽,假設現在每一個epoch裡面有100個batch,就對應着update 100次參數,20個epoch就是update 2000次參數

注:如果batch_size設為1,就是Stochastic Gradient Descent(随機梯度下降法),這個我們之前在讨論gradient descent的時候有提到,就是每次拿到一個樣本點就update一次參數,而不是每次拿到一批樣本點的error之後才去update參數,是以stochastic gradient descent的好處是它的速度比較快,雖然每次update參數的方向是不穩定的,但是天下武功,唯快不破,在别人出一拳的時候,它就已經出了100拳了,是以它是比較強的

Keras+數字識别DemoKeras2.0

Mini-batch

這裡有一個秘密,就是我們在做deep learning的gradient descent的時候,并不會真的去minimize total loss,那我們做的是什麼呢?我們會把Training data分成一個一個的batch,比如說你的Training data一共有1w張image,每次random選100張image作為一個batch(我的了解是,先将原來的image分布随機打亂,然後再按順序每次挑出batch_size張image組成一個batch,這樣才能保證所有的data都有被用到,且不同的batch裡不會出現重複的data)

  • 像gradient descent一樣,先随機initialize network的參數
  • 選第一個batch出來,然後計算這個batch裡面的所有element的total loss, L ′ = l 1 + l 31 + . . . L'=l^1+l^{31}+... L′=l1+l31+...,接下來根據 L ′ L' L′去update參數,也就是計算 L ′ L' L′對所有參數的偏微分,然後update參數

    注意:不是全部data的total loss

  • 再選擇第二個batch,現在這個batch的total loss是 L ′ ′ = l 2 + l 16 + . . . L''=l^2+l^{16}+... L′′=l2+l16+...,接下來計算 L ′ ′ L'' L′′對所有參數的偏微分,然後update參數
  • 反複做這個process,直到把所有的batch通通選過一次,是以假設你有100個batch的話,你就把這個參數update 100次,把所有batch看過一次,就叫做一個epoch
  • 重複epoch的過程,是以你在train network的時候,你會需要好幾十個epoch,而不是隻有一個epoch

整個訓練的過程類似于stochastic gradient descent,不是将所有資料讀完才開始做gradient descent的,而是拿到一部分資料就做一次gradient descent

Keras+數字識别DemoKeras2.0

Batch size and Training Speed

batch size太小會導緻不穩定,速度上也沒有優勢

前面已經提到了,stochastic gradient descent速度快,表現好,既然如此,為什麼我們還要用Mini-batch呢?這就涉及到了一些實際操作上的問題,讓我們必須去用Mini-batch

舉例來說,我們現在有50000個examples,如果我們把batch size設定為1,就是stochastic gradient descent,那在一個epoch裡面,就會update 50000次參數;如果我們把batch size設定為10,在一個epoch裡面,就會update 5000次參數

看上去stochastic gradient descent的速度貌似是比較快的,它一個epoch更新參數的次數比batch size等于10的情況下要快了10倍,但是!我們好像忽略了一個問題,我們之前一直都是下意識地認為不同batch size的情況下運作一個epoch的時間應該是相等的,然後我們才去比較每個epoch所能夠update參數的次數,可是它們又怎麼可能會是相等的呢?

實際上,當你batch size設定不一樣的時候,一個epoch需要的時間是不一樣的,以GTX 980為例,下圖是對總數為50000筆的Training data設定不同的batch size時,每一個epoch所需要花費的時間

  • case1:如果batch size設為1,也就是stochastic gradient descent,一個epoch要花費166秒,接近3分鐘
  • case2:如果batch size設為10,那一個epoch是17秒

也就是說,當stochastic gradient descent算了一個epoch的時候,batch size為10的情況已經算了近10個epoch了;是以case1跑一個epoch,做了50000次update參數的同時,case2跑了十個epoch,做了近5000*10=50000次update參數;你會發現batch size設1和設10,update參數的次數幾乎是一樣的

如果不同batch size的情況,update參數的次數幾乎是一樣的,你其實會想要選batch size更大的情況,就像在本例中,相較于batch size=1,你會更傾向于選batch size=10,因為batch size=10的時候,是會比較穩定的,因為由更大的資料集計算的梯度能夠更好的代表樣本總體,進而更準确的朝向極值所在的方向

Keras+數字識别DemoKeras2.0

我們之前把gradient descent換成stochastic gradient descent,是因為後者速度比較快,update次數比較多,可是現在如果你用stochastic gradient descent并沒有見得有多快,那你為什麼不選一個update次數差不多,又比較穩定的方法呢?

batch size會受到GPU平行加速的限制,太大可能導緻在train的時候卡住

上面例子的現象産生的原因是我們用了GPU,用了平行運算,是以batch size=10的時候,這10個example其實是同時運算的,是以你在一個batch裡算10個example的時間跟算1個example的時間幾乎可以是一樣的

那你可能會問,既然batch size越大,它會越穩定,而且還可以平行運算,那為什麼不把batch size變得超級大呢?這裡有兩個claim(聲明):

  • 第一個claim就是,如果你把batch size開到很大,最終GPU會沒有辦法進行平行運算,它終究是有自己的極限的,也就是說它同時考慮10個example和1個example的時間是一樣的,但當它考慮10000個example的時候,時間就不可能還是跟一個example一樣,因為batch size考慮到硬體限制,是沒有辦法無窮盡地增長的
  • 第二個claim是說,如果把batch size設的很大,在train gradient descent的時候,可能跑兩下你的network就卡住了,就陷到saddle point或者local minima裡面去了

    因為在neural network的error surface上面,如果你把loss的圖像可視化出來的話,它并不是一個convex的optimization problem,不會像理想中那麼平滑,實際上它會有很多的坑坑洞洞

    如果你用的batch size很大,甚至是Full batch,那你走過的路徑會是比較平滑連續的,可能這一條平滑的曲線在走向最低點的過程中就會在坑洞或是緩坡上卡住了;但是,如果你的batch size沒有那麼大,意味着你走的路線沒有那麼的平滑,有些步伐走的是随機性的,路徑是會有一些曲折和波動的

    可能在你走的過程中,它的曲折和波動剛好使得你“繞過”了那些saddle point或是local minima的地方;或者當你陷入不是很深的local minima或者沒有遇到特别麻煩的saddle point的時候,它步伐的随機性就可以幫你跳出這個gradient接近于0的區域,于是你更有可能真的走向global minima的地方

    而對于Full batch的情況,它的路徑是沒有随機性的,是穩定朝着目标下降的,是以在這個時候去train neural network其實是有問題的,可能update兩三次參數就會卡住,是以mini batch是有必要的

    下面是我手畫的圖例和注釋:

Keras+數字識别DemoKeras2.0
不同batch size在梯度下降上的表現

如下圖,左邊是full batch(拿全部的Training data做一個batch)的梯度下降效果,可以看到每一次疊代成本函數都呈現下降趨勢,這是好的現象,說明我們w和b的設定一直再減少誤差, 這樣一直疊代下去我們就可以找到最優解;右邊是mini batch的梯度下降效果,可以看到它是上下波動的,成本函數的值有時高有時低,但總體還是呈現下降的趨勢, 這個也是正常的,因為我們每一次梯度下降都是在min batch上跑的而不是在整個資料集上, 資料的差異可能會導緻這樣的波動(可能某段資料效果特别好,某段資料效果不好),但沒關系,因為它整體是呈下降趨勢的

Keras+數字識别DemoKeras2.0

把下面的圖看做是梯度下降空間:藍色部分是full batch而紫色部分是mini batch,就像上面所說的mini batch不是每次疊代損失函數都會減少,是以看上去好像走了很多彎路,不過整體還是朝着最優解疊代的,而且由于mini batch一個epoch就走了5000步(5000次梯度下降),而full batch一個epoch隻有一步,是以雖然mini batch走了彎路但還是會快很多

而且,就像之前提到的那樣,mini batch在update的過程中,步伐具有随機性,是以紫色的路徑可以在一定程度上繞過或跳出saddle point、local minima這些gradient趨近于0的地方;而藍色的路徑因為缺乏随機性,隻能按照既定的方式朝着目标前進,很有可能就在中途被卡住,永遠也跳不出來了

Keras+數字識别DemoKeras2.0

當然,就像之前讨論的一樣,如果batch size太小,會造成速度不僅沒有加快反而會導緻下降的曲線更加不穩定的情況産生

是以batch size既不能太大,因為它會受到硬體GPU平行加速的限制,導緻update次數過于緩慢,并且由于缺少随機性而很容易在梯度下降的過程中卡在saddle point或是local minima的地方(極端情況是Full batch);而且batch size也不能太小,因為它會導緻速度優勢不明顯的情況下,梯度下降曲線過于不穩定,算法可能永遠也不會收斂(極端情況是Stochastic gradient descent)

GPU是如何平行加速的

整個network,不管是Forward pass還是Backward pass,都可以看做是一連串的矩陣運算的結果

那今天我們就可以比較batch size等于1(stochastic gradient descent)和10(mini batch)的差别

如下圖所示,stochastic gradient descent就是對每一個input x進行單獨運算;而mini batch,則是把同一個batch裡面的input全部集合起來,假設現在我們的batch size是2,那mini batch每一次運算的input就是把黃色的vector和綠色的vector拼接起來變成一個matrix,再把這個matrix乘上 w 1 w_1 w1​,你就可以直接得到 z 1 z^1 z1和 z 2 z^2 z2

這兩件事在理論上運算量是一樣多的,但是在實際操作上,對GPU來說,在矩陣裡面相乘的每一個element都是可以平行運算的,是以圖中stochastic gradient descent運算的時間反而會變成下面mini batch使用GPU運算速度的兩倍,這就是為什麼我們要使用mini batch的原因

Keras+數字識别DemoKeras2.0

是以,如果你買了GPU,但是沒有使用mini batch的話,其實就不會有多少加速的效果

Save and Load Models

Keras是可以幫你save和load model的,你可以把train好的model存起來,以後再用另外一個程式讀出來,它也可以幫你做testing

那怎麼用neural network去testing呢?有兩種case:

  • case 1是evaluation,比如今天我有一組testing set,testing set的答案也是已知的,那Keras就可以幫你算現在的正确率有多少,這個

    model.evaluate

    函數有兩個input,就是testing的image和testing的label
    score = model.evaluate(x_test,y_test)
    print('Total loss on Testing Set:',score[0])
    print('Accuracy of Testing Set:',score[1])
               
  • case 2是prediction,這個時候

    model.predict

    函數的input隻有image data而沒有任何的label data,output就直接是分類的結果
Keras+數字識别DemoKeras2.0

Appendix:手寫數字識别完整代碼(Keras2.0)

code
推薦看,還有CNN版本 👉 Convolutional Neural network part1,在文章最後面
import numpy as np
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation
from keras.layers import Conv2D, MaxPooling2D, Flatten
from keras.optimizers import SGD, Adam
from keras.utils import np_utils
from keras.datasets import mnist

# categorical_crossentropy
def load_data():  
    (x_train, y_train), (x_test, y_test) = mnist.load_data()
    number = 10000
    x_train = x_train[0:number]
    y_train = y_train[0:number]
    x_train = x_train.reshape(number, 28 * 28)
    x_test = x_test.reshape(x_test.shape[0], 28 * 28)
    x_train = x_train.astype('float32')
    x_test = x_test.astype('float32')
    # convert class vectors to binary class matrices
    y_train = np_utils.to_categorical(y_train, 10)
    y_test = np_utils.to_categorical(y_test, 10)
    x_train = x_train
    x_test = x_test
    # x_test=np.random.normal(x_test)
    x_train = x_train / 255
    x_test = x_test / 255

    return (x_train, y_train), (x_test, y_test)


if __name__ == '__main__':
    # load training data and testing data
    (x_train, y_train), (x_test, y_test) = load_data()

    # define network structure
    model = Sequential()

    model.add(Dense(input_dim=28*28, units=500, activation='sigmoid'))
    model.add(Dense(units=500, activation='sigmoid'))
    model.add(Dense(units=10, activation='softmax'))

    # set configurations
    model.compile(loss='categorical_crossentropy',
                  optimizer='adam', metrics=['accuracy'])

    # train model
    model.fit(x_train, y_train, batch_size=100, epochs=20)

    # evaluate the model and output the accuracy
    result = model.evaluate(x_test, y_test)
    print('Test Acc:', result[1])

           
result
Epoch 1/20
10000/10000 [==============================] - 2s 214us/step - loss: 1.1724 - acc: 0.6558
Epoch 2/20
10000/10000 [==============================] - 1s 146us/step - loss: 0.3847 - acc: 0.8964
Epoch 3/20
10000/10000 [==============================] - 1s 132us/step - loss: 0.2968 - acc: 0.9119
Epoch 4/20
10000/10000 [==============================] - 1s 146us/step - loss: 0.2535 - acc: 0.9268
Epoch 5/20
10000/10000 [==============================] - 2s 185us/step - loss: 0.2284 - acc: 0.9332
Epoch 6/20
10000/10000 [==============================] - 1s 141us/step - loss: 0.2080 - acc: 0.9369
Epoch 7/20
10000/10000 [==============================] - 1s 135us/step - loss: 0.1829 - acc: 0.9455
Epoch 8/20
10000/10000 [==============================] - 1s 135us/step - loss: 0.1617 - acc: 0.9520
Epoch 9/20
10000/10000 [==============================] - 1s 136us/step - loss: 0.1470 - acc: 0.9563
Epoch 10/20
10000/10000 [==============================] - 1s 133us/step - loss: 0.1340 - acc: 0.9607
Epoch 11/20
10000/10000 [==============================] - 1s 141us/step - loss: 0.1189 - acc: 0.9651
Epoch 12/20
10000/10000 [==============================] - 1s 143us/step - loss: 0.1056 - acc: 0.9696
Epoch 13/20
10000/10000 [==============================] - 1s 140us/step - loss: 0.0944 - acc: 0.9728
Epoch 14/20
10000/10000 [==============================] - 2s 172us/step - loss: 0.0808 - acc: 0.9773
Epoch 15/20
10000/10000 [==============================] - 1s 145us/step - loss: 0.0750 - acc: 0.9800
Epoch 16/20
10000/10000 [==============================] - 1s 134us/step - loss: 0.0643 - acc: 0.9826
Epoch 17/20
10000/10000 [==============================] - 1s 132us/step - loss: 0.0568 - acc: 0.9850
Epoch 18/20
10000/10000 [==============================] - 1s 135us/step - loss: 0.0510 - acc: 0.9873
Epoch 19/20
10000/10000 [==============================] - 1s 134us/step - loss: 0.0434 - acc: 0.9898
Epoch 20/20
10000/10000 [==============================] - 1s 134us/step - loss: 0.0398 - acc: 0.9906
10000/10000 [==============================] - 1s 79us/step
Test Acc: 0.9439
           

可以發現每次做完一個epoch的update後,手寫數字識别的準确率都有上升,最終訓練好的model識别準确率等于94.39%

注:把activation function從sigmoid換成relu可以使識别準确率更高,這裡不再重複試驗

繼續閱讀