簡易的深度學習框架Keras代碼解析與應用

邀請關注微信公眾號:人工智慧LeadAI(ID:atleadai)

歡迎訪問我們的官方主頁:www.leadai.org

正文約12690個字,22張圖,預計閱讀時間:32分鐘。

總體來講keras這個深度學習框架真的很「簡易」,它體現在可參考的文檔寫的比較詳細,不像caffe,裝完以後都得靠技術博客,keras有它自己的官方文檔(不過是英文的),這給初學者提供了很大的學習空間。

這個文檔必須要強推!英文nice的可以直接看文檔,我這篇文章就是用中文來講這個事兒。

keras.io/

Keras官方文檔

首先要明確一點:我沒學過Python,寫代碼都是需要什麼百度什麼的,所以有時候代碼會比較冗餘,可能一句話就能搞定的能寫很多~

論文引用—3.2 測試平台

項目代碼是在Windows 7上運行的,主要用到的Matlab R2013a和Python,其中Matlab用於patch的分割和預處理,卷積神經網路搭建用到了根植於Python和Theano的深度學習框架Keras。Keras是基於Theano的一個深度學習框架,它的設計參考了Torch,用Python語言編寫,是一個高度模塊化的神經網路庫,支持GPU和CPU,用起來特別簡單,適合快速開發。

卷積神經網路構建的主函數ndef create_model(data):n model = Sequential()n model.add(Convolution2D(64, 5, 5, border_mode=valid, input_shape=data.shape[-3:])) n model.add(Activation(relu))n model.add(Dropout(0.5))n model.add(Convolution2D(64, 5, 5, border_mode=valid))n model.add(Activation(relu))n model.add(MaxPooling2D(pool_size=(2, 2)))n model.add(Dropout(0.5))n model.add(Convolution2D(32, 3, 3, border_mode=valid)) n model.add(Activation(relu))n model.add(MaxPooling2D(pool_size=(2, 2)))n model.add(Convolution2D(32, 3, 3, border_mode=valid)) n model.add(Activation(relu))n model.add(Dropout(0.5))n model.add(Flatten())n model.add(Dense(512, init=normal))n model.add(Activation(relu))n model.add(Dropout(0.5))n model.add(Dense(LABELTYPE, init=normal))n model.add(Activation(softmax))n sgd = SGD(l2=0.0, lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)n model.compile(loss=categorical_crossentropy, optimizer=sgd, class_mode="categorical") nreturn modeln

這個函數相當的簡潔清楚了,輸入訓練集,輸出一個空的神經網路,其實就是卷積神經網路的初始化。model = Sequential()是給神經網路起了頭,後面的model.add()是一直加層,像搭積木一樣,要什麼加什麼,卷積神經網路有兩種類型的層:1)卷積,2)降採樣,對應到代碼上是:

model.add(Convolution2D(64, 5, 5, border_mode=valid))n# 加一個卷積層,卷積個數64,卷積尺寸5*5nmodel.add(MaxPooling2D(pool_size=(2, 2)))n# 加一個降採樣層,採樣窗口尺寸2*2n

激活函數

注意:每個卷積層後面要加一個激活函數,就是在教科書上說的這個部分

它可以將卷積後的結果控制在某一個數值範圍內,如0~1,-1~1等等,不會讓每次卷積完的數值相差懸殊

對應到代碼上是這句:

model.add(Activation(relu))n

這個激活函數(Activation)keras提供了很多備選的,我這兒用的是ReLU這個,其他還有:

  • tanh
  • sigmoid
  • hard_sigmoid
  • linear

    等等,keras庫是不斷更新的,新出來的論文裡面用到的更優化的激活函數裡面也會有收錄,比如:
  • LeakyReLU
  • PReLU
  • ELU

都是可以替換的,美其名曰「優化網路」,其實就只不過是改一下名字罷了哈哈,內部函數已經都幫你寫好了呢。注意一下:卷積神經網路的最後一層的激活函數一般就是選擇「softmax」。我這兒多說一嘴這些激活函數應該怎麼去選擇吧,一句話

原因分別是

導致梯度消失,不是零中心

導致梯度消失

x<0時梯度沒了

啊喲,不錯哦。

我知道Leaky ReLU已經有現成的了,但是暫時還沒有用,現在還用的是ReLU這個,別問我為什麼。

Dropout層

棄權(Dropout):針對「過度擬合」問題。

不讓某些神經元興奮

人腦在處理信號的時候並不是所有的神經元都處於興奮狀態的,原因是1) 大腦的能量供給跟不上,2)神經元的特異性,特定的神經元處理特定信號,3) 全部的神經元都激活的話增加了反應時間。所以我們用神經網路模擬的也要有所取捨,比如把信號強度低於某個值的神經元都抑制下來,這樣能提高了網路的速度和魯棒性,降低「過擬合」的可能性。額,廢話不說了,反正就是好!體現在代碼上是這個:

model.add(Dropout(0.5))n

這個0.5可以改,意思是信號強度排在後50%的神經元都被抑制,就是把他們都扔掉~

還有點細節

到現在為止對這個網路初始化的函數應該只有一些小東西不清楚了吧:

model.add(Convolution2D(64, 5, 5, border_mode=valid, input_shape=data.shape[-3:]))n

你會發現第一個卷積層代碼比其他的長,原因是它還需要加上訓練集的一些參數,也就是input_shape = data.shape[-3:]這個,它的意思是說明一下訓練集的樣本有幾個通道和每個輸入圖像的尺寸,我這兒是

data.shape[-3:],表示我用了六通道,每個patch的尺寸是24*24像素

通道的概念就是比如一幅黑白圖,就是一通道,即灰度值;一幅彩色圖就是三通道,即RGB;當然也可以不用顏色作為通道,比如我用的六通道。但是通道內部的機制我並不是很清楚,可能它就是為RGB設置的也說不定。這兒打一個問號?

model.add(Flatten())nmodel.add(Dense(512, init=normal))n

這兒加個一個全連接層,就是這兩句代碼,相當於卷積神經網路中的這個

512意思就是這個層有512個神經元

沒什麼可說的,就是模型里的一部分,可以有好幾層,但一般放在網路靠後的地方。

sgd = SGD(l2=0.0, lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)nmodel.compile(loss=categorical_crossentropy, optimizer=sgd, class_mode="categorical")n

這部分就是傳說中的「梯度下降法」,它用在神經網路的反饋階段,不斷地學習,調整每一層卷積的參數,即所謂「學習」的過程。我這兒用的是最常見的sgd,參數包括學習速度(lr),,雖然吧其他的參數理論上也能改,但是我沒有去改它們,呵呵。

小建議:學習參數一般比較小,我用的是0.01,這個是根據不同的訓練集數據決定的,太小的話訓練的速度很慢,太大的話容易訓練自爆掉,像這樣。

圓圈是當前位置,五角星是目標位置,若學習速度過快容易直接跳過目標位置,導致訓練失敗

對於keras提供的其他反饋的方法(Optimizer)(keras.io/optimizers/),我並沒有試過,也不清楚它們各自的優缺點,這兒列舉幾個其他的可選方法:

  • RMSprop
  • Adagrad
  • Adadelta
  • Adam
  • Adamax

我猜每一個方法都能對應一篇深度學習的論文吧,代碼keras已經都提供了,想了解詳情就去追溯論文吧。這兒我提一嘴代價函數的事兒,針對「學習緩慢」和「過渡擬合」問題,有提出對代價函數進行修改的方法。道理都懂,具體在keras的哪兒做修改我還在摸索中,先來講一波道理:

講道理

由此可見,比較好的代價函數是

找機會把keras內部這一部分的代碼改了

主代碼部分,The End。

訓練前期代碼

在開始訓練以前需要做幾個步驟

  • 導入需要的python包
  • 導入數據
  • 瓜分訓練集和測試集

相關的python包導入

#coding:utf-8nnGPU run command:nTHEANO_FLAGS=mode=FAST_RUN,device=gpu,floatX=float32 python cnn.pynCPU run command:npython cnn.pynn####################################### n導入各種用到的模塊組件n####################################### nConvNets的模塊nfrom __future__ import absolute_importnfrom __future__ import print_functionnfrom keras.models import Sequentialnfrom keras.layers.core import Dense, Dropout, Activation, Flattenfrom keras.layers.advanced_activationsnimport PReLU, LeakyReLUimport keras.layers.advanced_activations as adactnfrom keras.layers.convolutional import Convolution2D, MaxPooling2Dnfrom keras.optimizers import SGD, Adadelta, Adagrad, Adam, Adamaxnfrom keras.utils import np_utils, generic_utilsfrom six.movesnimport rangefrom keras.callbacks import EarlyStoppingn# 統計的模塊nfrom collections import Counterimport random, cPicklenfrom cutslice3d import load_datanfrom cutslice3d import ROW, COL, LABELTYPE, CHANNEn# 內存調整的模塊nimport sysn

這兒就沒的說了,相當於C語言裡面的#include,後面要用到什麼就導入什麼。對了具體導入哪些包就是去keras安裝的位置看看,我的安裝路徑是:

C:UsersAdministratorAnaconda2Libsite-packageskeras

你會看到一個個的.py文件

keras目錄下就這樣子的

比如你需要導入Sequential()這個函數的話首先得知道它在keras的models.py中定義的,然後就很自然的出來這個代碼。

from keras.models import Sequentialn# 從keras的models.py中導入Sequentialn

你看,代碼簡單的都能直譯了。難點是你根本不知道Sequential()函數在哪兒定義的,這個就需要好好地去系統得看一下keras的文檔(keras.io/)了,這麼多函數我這兒也不可能逐一舉例。

這部分我個人感覺挺需要python的知識,因為除去keras,很多包都蠻有用的,有了這些函數能省不少事兒。舉例:

from collections import Countern

作用是統計一個矩陣裡面的不同元素分別出現的次數,落實到後面的代碼就是

cnt = Counter(A)for k,v in cnt.iteritems(): nprint (t, k, -->, v)n# 實現了統計A矩陣的元素各自出現的次數n

數據的簡單處理模塊

######################################n# 對於本次試驗的描述n######################################nprint("nnnHey you, this is a trial on malignance and benign tumors detection via ConvNets. Im Zongwei Zhou. :)")nprint("Each input patch is 51*51, cutted from 1383 3d CT & PT images. The MINIMUM is above 30 segment pixels.")n######################################n# 載入數據n######################################nprint(">> Loading Data ...")nTrData, TrLabel, VaData, VaLabel = load_data()n######################################n# 打亂數據n######################################nindex = [i for i in range(len(TrLabel))]nrandom.shuffle(index)nTrData = TrData[index]nTrLabel = TrLabel[index]nnprint(tTherefore, read in, TrData.shape[0], samples from the dataset totally.)n# label為0~1共2個類別,keras要求格式為binary class matrices,轉化一下,直接調用keras提供的這個函數nTrLabel = np_utils.to_categorical(TrLabel, LABELTYPE)n

這兒我用到了一個load_data()函數,是自己寫的,就是一個數據導入,從.mat文件中分別讀入訓練集和測試集。也就是對於輸入patch的平移,旋轉變換以及訓練集測試集劃分都是在MATLAB中完成的,得到的數據量爆大,截止到4月7日,我的訓練集以及達到了31.4GB的規模,而python端的函數就比較直觀了,是這樣的:

def load_data(): nn ######################################n # 從.mat文件中讀入數據n ######################################nn mat_training = h5py.File(DATAPATH_Training);n mat_training.keys()n Training_CT_x = mat_training[Training_CT_1];n Training_CT_y = mat_training[Training_CT_2];n Training_CT_z = mat_training[Training_CT_3];n Training_PT_x = mat_training[Training_PT_1];n Training_PT_y = mat_training[Training_PT_2];n Training_PT_z = mat_training[Training_PT_3];n TrLabel = mat_training[Training_label];n TrLabel = np.transpose(TrLabel);n Training_Dataset = len(TrLabel);nn mat_validation = h5py.File(DATAPATH_Validation);n mat_validation.keys()n Validation_CT_x = mat_validation[Validation_CT_1];n Validation_CT_y = mat_validation[Validation_CT_2];n Validation_CT_z = mat_validation[Validation_CT_3];n Validation_PT_x = mat_validation[Validation_PT_1];n Validation_PT_y = mat_validation[Validation_PT_2];n Validation_PT_z = mat_validation[Validation_PT_3];n VaLabel = mat_validation[Validation_label];n VaLabel = np.transpose(VaLabel);n Validation_Dataset = len(VaLabel); n######################################n # 初始化n######################################n TrData = np.empty((Training_Dataset, CHANNEL, ROW, COL), dtype = "float32");n VaData = np.empty((Validation_Dataset, CHANNEL, ROW, COL), dtype = "float32"); n######################################n # 裁剪圖片,通道輸入n######################################n for i in range(Training_Dataset):n TrData[i,0,:,:]=Training_CT_x[:,:,i];n TrData[i,1,:,:]=Training_CT_y[:,:,i];n TrData[i,2,:,:]=Training_CT_z[:,:,i];n TrData[i,3,:,:]=Training_PT_x[:,:,i]; n TrData[i,4,:,:]=Training_PT_y[:,:,i]; n TrData[i,5,:,:]=Training_PT_z[:,:,i]; for i in range(Validation_Dataset):n VaData[i,0,:,:]=Validation_CT_x[:,:,i];n VaData[i,1,:,:]=Validation_CT_y[:,:,i];n VaData[i,2,:,:]=Validation_CT_z[:,:,i];n VaData[i,3,:,:]=Validation_PT_x[:,:,i]; n VaData[i,4,:,:]=Validation_PT_y[:,:,i]; n VaData[i,5,:,:]=Validation_PT_z[:,:,i]; print tThe dimension of each data and label, listed as folllowing:n print tTrData : , TrData.shape print tTrLabel : , TrLabel.shape nprint tRange : , np.amin(TrData[:,0,:,:]), ~, np.amax(TrData[:,0,:,:]) nprint tt, np.amin(TrData[:,1,:,:]), ~, np.amax(TrData[:,1,:,:]) print tt, np.amin(TrData[:,2,:,:]), ~, np.amax(TrData[:,2,:,:]) print tt, np.amin(TrData[:,3,:,:]), ~, np.amax(TrData[:,3,:,:]) print tt, np.amin(TrData[:,4,:,:]), ~, np.amax(TrData[:,4,:,:]) print tt, np.amin(TrData[:,5,:,:]), ~, np.amax(TrData[:,5,:,:]) print tVaData : , VaData.shape nprint tVaLabel : , VaLabel.shape nprint tRange : , np.amin(VaData[:,0,:,:]), ~, np.amax(VaData[:,0,:,:]) nprint tt, np.amin(VaData[:,1,:,:]), ~, np.amax(VaData[:,1,:,:])nprint tt, np.amin(VaData[:,2,:,:]), ~, np.amax(VaData[:,2,:,:])nprint tt, np.amin(VaData[:,3,:,:]), ~, np.amax(VaData[:,3,:,:]) nprint tt, np.amin(VaData[:,4,:,:]), ~, np.amax(VaData[:,4,:,:]) nprint tt, np.amin(VaData[:,5,:,:]), ~, np.amax(VaData[:,5,:,:]) return TrData, TrLabel, VaData, VaLabeln

讀入.mat中儲存的數據,輸出的就直接是劃分好的訓練集(TrData, TrLabel)和測試集(VaData, VaLabel)啦,比較簡單,不展開說了。關於MATLAB端的數據拓展(Data Augmentation),我將在後續再介紹。說明一下數據拓展的作用也是針對「過度擬合」問題的。

注意一點:我的label為0~1共2個類別,keras要求格式為binary class matrices,所以要轉化一下,直接調用keras提供的這個函數np_utils.to_categorical()即可。

訓練中後期代碼

前面的硬骨頭啃完了,這兒就是向開玩笑一樣,短短几句代碼解決問題。

print(">> Build Model ...")nmodel = create_model(TrDatan)######################################n# 訓練ConvNets模型n######################################nprint(">> Training ConvNets Model ...")nprint("tHere, batch_size =", BATCH_SIZE, ", epoch =", EPOCH, ", lr =", LR, ", momentum =", MOMENTUM)nearly_stopping = EarlyStopping(monitor=val_loss, patience=2)nhist = model.fit(TrData, TrLabel, n batch_size=BATCH_SIZE, n nb_epoch=EPOCH, n shuffle=True, n verbose=1, n show_accuracy=True, n validation_split=VALIDATION_SPLIT, n callbacks=[early_stopping])n######################################n# 測試ConvNets模型n######################################nprint(">> Test the model ...")npre_temp=model.predict_classes(VaData)n

訓練模型

先調用1. 直接上卷積神經網路構建的主函數中的函數create_model()建立一個初始化的模型。然後的訓練主代碼就是一句話

hist = model.fit(TrData, TrLabel, n batch_size=100, n nb_epoch=10, n shuffle=True, n verbose=1, n show_accuracy=True, n validation_split=0.2, n callbacks=[early_stopping])n

沒錯,就一句話,不過這句話裡面的事兒稍微比較多一點。。。我這兒就簡單列舉一下我關注的項:

  • TrData:訓練數據
  • TrLabel:訓練數據標籤
  • batch_size:每次梯度下降調整參數是用的訓練樣本
  • nb_epoch:訓練迭代的次數
  • shuffle:當suffle=True時,會隨機打算每一次epoch的數據(默認打亂),但是驗證數據默認不會打亂。
  • validation_split:測試集的比例,我這兒選了0.2。注意,這和2.2 數據的簡單處理模塊中的測試集不是一個東西,這個測試集是一次訓練的測試集,也就是下次訓練他有可能變成訓練集了。而2.2 數據的簡單處理模塊中的是全局的測試集,對於訓練好的網路做的最終測試。
  • early_stopping:是否提前結束訓練,網路自己判斷,當本次訓練和上次訓練的結果差不多了自動回停止訓練迭代,也就是不一定訓練完nb_epoch(10)次哦

early_stopping的調用在這兒

early_stopping = EarlyStopping(monitor=val_loss, patience=2)n

其他的都是和訓練的時候的界面有關,按照我的或者默認的來就可以了。

提一嘴,如果你想要看每一次的訓練的結果是可以做到的!hist = model.fit()的hist中存放的是每一次訓練完的結果和測試精確度等信息。

再來一嘴,如果你想要看每一層的輸出的啥,也是可以做到的!

這個可以用到卷積神經網路和其他傳統分類器結合來優化softmax方法的實驗,涉及到比較高級的演算法了,我以後再說。這兒先只放上看每一層輸出的代碼:

get_feature = theano.function([origin_model.layers[0].input],origin_model.layers[12].get_output(train=False),allow_input_downcast=False)nfeature = get_feature(data)n

好吧,再提供一下SVM和Random Forests的Python函數代碼吧,如果大家想做這個實驗可以用哈:

######################################n# SVMn######################################ndef svc(traindata,trainlabel,testdata,testlabel):n print("Start training SVM...")n svcClf = SVC(C=1.0,kernel="rbf",cache_size=3000)n svcClf.fit(traindata,trainlabel)nn pred_testlabel = svcClf.predict(testdata)n num = len(pred_testlabel)n accuracy = len([1 for i in range(num) if testlabel[i]==pred_testlabel[i]])/float(num)n print("n>> cnn-svm Accuracy")n prt(testlabel, pred_testlabel)n######################################n# Random Forestsn######################################ndef rf(traindata,trainlabel,testdata,testlabel):n print("Start training Random Forest...")n rfClf = RandomForestClassifier(n_estimators=100,criterion=gini)n rfClf.fit(traindata,trainlabel) n pred_testlabel = rfClf.predict(testdata)n print("n>> cnn-rf Accuracy")n prt(testlabel, pred_testlabel)n

收~打住。

測試模型

呼呼,這個最水,也是一句話

pre_temp=model.predict_classes(VaData)n

套用一個現有函數predict_classes()輸入測試集VaData,返回訓練完的網路的預測結果pre_temp。好了,最後把pre_temp和正確的測試集標籤VaLabel對比一下,就知道這個網路訓練的咋樣了,實驗階段性勝利!發個截圖:

Everybody Happy

保存模型

訓練一個模型不容易,不但需要調整參數,調整網路結構,訓練的時間還特別長,所以要學會保存訓練完的網路,代碼是這樣的:

######################################n# 保存ConvNets模型n######################################nmodel.save_weights(MyConvNets.h5)ncPickle.dump(model, open(./MyConvNets.pkl,"wb"))njson_string = model.to_json()nopen(W_MODEL, w).write(json_string)n

就保存好啦,是這三個文件

保存的模型文件

當你回頭要調用這個網路時,用這個代碼就可以了

model = cPickle.load(open(』MyConvNets.pkl,"rb"))n

model中就讀入了pkl文件內存儲的模型啦。

本文是keras的初階分享,如果其中涉及到有些知識點我略過了,可以參考。

基於Theano的深度學習(Deep Learning)框架Keras學習隨筆-01-FAQ

回頭我會在整理一篇中階的分享,詳細地去講這些東西。

參考資料

1、基於Theano的深度學習(Deep Learning)框架Keras學習隨筆-12-核心層

2、基於Theano的深度學習(Deep Learning)框架Keras學習隨筆-13-卷積層

原文鏈接:http://www.jianshu.com/p/8c36a5e42d6c

查閱更為簡潔方便的分類文章以及最新的課程、產品信息,請移步至全新呈現的

www.leadai.org

關注人工智慧LeadAI公眾號,查看更多專業文章

weixin.qq.com/r/ZDnC2j- (二維碼自動識別)

大家都在看

LSTM模型在問答系統中的應用

基於TensorFlow的神經網路解決用戶流失概覽問題

最全常見演算法工程師面試題目整理(一)

最全常見演算法工程師面試題目整理(二)

TensorFlow從1到2 | 第三章 深度學習革命的開端:卷積神經網路

裝飾器 | Python高級編程

今天不如來複習下Python基礎


推薦閱讀:

【消息】Keras 2參上!這次也請大家多多關照哦
FancyKeras-數據的輸入(花式)
時間序列與回歸預測(一)
自己實現黑白圖片自動上色AI(一)

TAG:深度学习DeepLearning | Keras |