總體來講keras這個深度學(xué)習(xí)框架真的很“簡易”,它體現(xiàn)在可參考的文檔寫的比較詳細,不像caffe,裝完以后都得靠技術(shù)博客,keras有它自己的官方文檔(不過是英文的),這給初學(xué)者提供了很大的學(xué)習(xí)空間。
這個文檔必須要強推!英文nice的可以直接看文檔,我這篇文章就是用中文來講這個事兒。
Keras官方文檔
首先要明確一點:我沒學(xué)過Python,寫代碼都是需要什么百度什么的,所以有時候代碼會比較冗余,可能一句話就能搞定的能寫很多~
論文引用——3.2 測試平臺
項目代碼是在Windows 7上運行的,主要用到的Matlab R2013a和Python,其中Matlab用于patch的分割和預(yù)處理,卷積神經(jīng)網(wǎng)絡(luò)搭建用到了根植于Python和Theano的深度學(xué)習(xí)框架Keras。Keras是基于Theano的一個深度學(xué)習(xí)框架,它的設(shè)計參考了Torch,用Python語言編寫,是一個高度模塊化的神經(jīng)網(wǎng)絡(luò)庫,支持GPU和CPU,用起來特別簡單,適合快速開發(fā)。
基于Theano的深度學(xué)習(xí)(Deep Learning)框架Keras學(xué)習(xí)隨筆-12-核心層
基于Theano的深度學(xué)習(xí)(Deep Learning)框架Keras學(xué)習(xí)隨筆-13-卷積層
1. 直接上卷積神經(jīng)網(wǎng)絡(luò)構(gòu)建的主函數(shù)
def create_model(data):
model = Sequential()
model.add(Convolution2D(64, 5, 5, border_mode=‘valid’, input_shape=data.shape[-3:]))
model.add(Activation(‘relu’))
model.add(Dropout(0.5))
model.add(Convolution2D(64, 5, 5, border_mode=‘valid’))
model.add(Activation(‘relu’))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.5))
model.add(Convolution2D(32, 3, 3, border_mode=‘valid’))
model.add(Activation(‘relu’))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Convolution2D(32, 3, 3, border_mode=‘valid’))
model.add(Activation(‘relu’))
model.add(Dropout(0.5))
model.add(Flatten())
model.add(Dense(512, init=‘normal’))
model.add(Activation(‘relu’))
model.add(Dropout(0.5))
model.add(Dense(LABELTYPE, init=‘normal’))
model.add(Activation(‘softmax’))
sgd = SGD(l2=0.0, lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss=‘categorical_crossentropy’, optimizer=sgd, class_mode=“categorical”)
return model
這個函數(shù)相當(dāng)?shù)暮啙嵡宄耍斎胗?xùn)練集,輸出一個空的神經(jīng)網(wǎng)絡(luò),其實就是卷積神經(jīng)網(wǎng)絡(luò)的初始化。model = Sequential()是給神經(jīng)網(wǎng)絡(luò)起了頭,后面的model.add()是一直加層,像搭積木一樣,要什么加什么,卷積神經(jīng)網(wǎng)絡(luò)有兩種類型的層:1)卷積,2)降采樣,對應(yīng)到代碼上是:
model.add(Convolution2D(64, 5, 5, border_mode=‘valid’))
# 加一個卷積層,卷積個數(shù)64,卷積尺寸5*5
model.add(MaxPooling2D(pool_size=(2, 2)))
# 加一個降采樣層,采樣窗口尺寸2*2
1.1 激活函數(shù)
注意:每個卷積層后面要加一個激活函數(shù),就是在教科書上說的這個部分
它可以將卷積后的結(jié)果控制在某一個數(shù)值范圍內(nèi),如0~1,-1~1等等,不會讓每次卷積完的數(shù)值相差懸殊
對應(yīng)到代碼上是這句:
model.add(Activation(‘relu’))
這個激活函數(shù)(Activation)keras提供了很多備選的,我這兒用的是ReLU這個,其他還有
tanh
sigmoid
hard_sigmoid
等等,keras庫是不斷更新的,新出來的論文里面用到的更優(yōu)化的激活函數(shù)里面也會有收錄,比如:
LeakyReLU
PReLU
ELU
等等,都是可以替換的,美其名曰“優(yōu)化網(wǎng)絡(luò)”,其實就只不過是改一下名字罷了哈哈,內(nèi)部函數(shù)已經(jīng)都幫你寫好了呢。注意一下:卷積神經(jīng)網(wǎng)絡(luò)的最后一層的激活函數(shù)一般就是選擇“softmax”。我這兒多說一嘴這些激活函數(shù)應(yīng)該怎么去選擇吧,一句話
參考的是這篇文章
原因分別是
導(dǎo)致梯度消失,不是零中心
導(dǎo)致梯度消失
x《0時梯度沒了
我知道Leaky ReLU已經(jīng)有現(xiàn)成的了,但是暫時還沒有用,現(xiàn)在還用的是ReLU這個,別問我為什么:)
1.2 Dropout層
棄權(quán)(Dropout):針對“過度擬合”問題
不讓某些神經(jīng)元興奮
人腦在處理信號的時候并不是所有的神經(jīng)元都處于興奮狀態(tài)的,原因是1) 大腦的能量供給跟不上,2)神經(jīng)元的特異性,特定的神經(jīng)元處理特定信號,3) 全部的神經(jīng)元都激活的話增加了反應(yīng)時間。所以我們用神經(jīng)網(wǎng)絡(luò)模擬的也要有所取舍,比如把信號強度低于某個值的神經(jīng)元都抑制下來,這樣能提高了網(wǎng)絡(luò)的速度和魯棒性,降低“過擬合”的可能性。額,廢話不說了,反正就是好!體現(xiàn)在代碼上是這個:
model.add(Dropout(0.5))
這個0.5可以改,意思是信號強度排在后50%的神經(jīng)元都被抑制,就是把他們都扔掉~
1.3 還有點細節(jié)
到現(xiàn)在為止對這個網(wǎng)絡(luò)初始化的函數(shù)應(yīng)該只有一些小東西不清楚了吧:
model.add(Convolution2D(64, 5, 5, border_mode=‘valid’, input_shape=data.shape[-3:]))
你會發(fā)現(xiàn)第一個卷積層代碼比其他的長,原因是它還需要加上訓(xùn)練集的一些參數(shù),也就是input_shape = data.shape[-3:]這個,它的意思是說明一下訓(xùn)練集的樣本有幾個通道和每個輸入圖像的尺寸,我這兒是
data.shape[-3:],表示我用了六通道,每個patch的尺寸是24*24像素。
通道的概念就是比如一幅黑白圖,就是一通道,即灰度值;一幅彩色圖就是三通道,即RGB;當(dāng)然也可以不用顏色作為通道,比如我用的六通道。但是通道內(nèi)部的機制我并不是很清楚,可能它就是為RGB設(shè)置的也說不定。這兒打一個問號?
model.add(Flatten())
model.add(Dense(512, init=‘normal’))
這兒加個一個全連接層,就是這兩句代碼,相當(dāng)于卷積神經(jīng)網(wǎng)絡(luò)中的這個
512意思就是這個層有512個神經(jīng)元
沒什么可說的,就是模型里的一部分,可以有好幾層,但一般放在網(wǎng)絡(luò)靠后的地方。
sgd = SGD(l2=0.0, lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss=‘categorical_crossentropy’, optimizer=sgd, class_mode=“categorical”)
這部分就是傳說中的“梯度下降法”,它用在神經(jīng)網(wǎng)絡(luò)的反饋階段,不斷地學(xué)習(xí),調(diào)整每一層卷積的參數(shù),即所謂“學(xué)習(xí)”的過程。我這兒用的是最常見的sgd,參數(shù)包括學(xué)習(xí)速度(lr),,雖然吧其他的參數(shù)理論上也能改,但是我沒有去改它們,呵呵。
小建議:學(xué)習(xí)參數(shù)一般比較小,我用的是0.01,這個是根據(jù)不同的訓(xùn)練集數(shù)據(jù)決定的,太小的話訓(xùn)練的速度很慢,太大的話容易訓(xùn)練自爆掉,像這樣
圓圈是當(dāng)前位置,五角星是目標(biāo)位置,若學(xué)習(xí)速度過快容易直接跳過目標(biāo)位置,導(dǎo)致訓(xùn)練失敗
對于keras提供的其他反饋的方法(Optimizer),我并沒有試過,也不清楚它們各自的優(yōu)缺點,這兒列舉幾個其他的可選方法:
RMSprop
Adagrad
Adadelta
Adam
Adamax
等等,我猜每一個方法都能對應(yīng)一篇深度學(xué)習(xí)的論文吧,代碼keras已經(jīng)都提供了,想了解詳情就去追溯論文吧。這兒我提一嘴代價函數(shù)的事兒,針對“學(xué)習(xí)緩慢”和“過渡擬合”問題,有提出對代價函數(shù)進行修改的方法。道理都懂,具體在keras的哪兒做修改我還在摸索中,先來講一波道理:
由此可見,比較好的代價函數(shù)是
找機會把keras內(nèi)部這一部分的代碼改了
主代碼部分,The End
2. 訓(xùn)練前期代碼
在開始訓(xùn)練以前需要做幾個步驟
導(dǎo)入需要的python包
導(dǎo)入數(shù)據(jù)
瓜分訓(xùn)練集和測試集
2.1 相關(guān)的python包導(dǎo)入
#coding:utf-8
‘’‘
GPU run command:
THEANO_FLAGS=mode=FAST_RUN,device=gpu,floatX=float32 python cnn.py
CPU run command:
python cnn.py
’‘’
######################################
# 導(dǎo)入各種用到的模塊組件
######################################
# ConvNets的模塊
from __future__ import absolute_import
from __future__ import print_function
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation, Flatten
from keras.layers.advanced_activations import PReLU, LeakyReLU
import keras.layers.advanced_activations as adact
from keras.layers.convolutional import Convolution2D, MaxPooling2D
from keras.optimizers import SGD, Adadelta, Adagrad, Adam, Adamax
from keras.utils import np_utils, generic_utils
from six.moves import range
from keras.callbacks import EarlyStopping
# 統(tǒng)計的模塊
from collections import Counter
import random, cPickle
from cutslice3d import load_data
from cutslice3d import ROW, COL, LABELTYPE, CHANNEL
# 內(nèi)存調(diào)整的模塊
import sys
這兒就沒的說了,相當(dāng)于C語言里面的#include,后面要用到什么就導(dǎo)入什么。對了具體導(dǎo)入哪些包就是去keras安裝的位置看看,我的安裝路徑是
C:UsersAdministratorAnaconda2Libsite-packageskeras
你會看到一個個的.py文件
keras目錄下就這樣子的
比如你需要導(dǎo)入Sequential()這個函數(shù)的話首先得知道它在keras的models.py中定義的,然后就很自然的出來這個代碼
from keras.models import Sequential
# 從keras的models.py中導(dǎo)入Sequential。
你看,代碼簡單的都能直譯了。難點是你根本不知道Sequential()函數(shù)在哪兒定義的,這個就需要好好地去系統(tǒng)得看一下keras的文檔了,這么多函數(shù)我這兒也不可能逐一舉例。
這部分我個人感覺挺需要python的知識,因為除去keras,很多包都蠻有用的,有了這些函數(shù)能省不少事兒。舉例:
from collections import Counter
作用是統(tǒng)計一個矩陣里面的不同元素分別出現(xiàn)的次數(shù),落實到后面的代碼就是
cnt = Counter(A)
for k,v in cnt.iteritems():
print (‘\t’, k, ‘--》’, v)
# 實現(xiàn)了統(tǒng)計A矩陣的元素各自出現(xiàn)的次數(shù)
2.2 數(shù)據(jù)的簡單處理模塊
######################################
# 對于本次試驗的描述
######################################
print(“\n\n\nHey you, this is a trial on malignance and benign tumors detection via ConvNets. I‘m Zongwei Zhou. :)”)
print(“Each input patch is 51*51, cutted from 1383 3d CT & PT images. The MINIMUM is above 30 segment pixels.”)
######################################
# 加載數(shù)據(jù)
######################################
print(“》》 Loading Data 。。.”)
TrData, TrLabel, VaData, VaLabel = load_data()
######################################
# 打亂數(shù)據(jù)
######################################
index = [i for i in range(len(TrLabel))]
random.shuffle(index)
TrData = TrData[index]
TrLabel = TrLabel[index]
print(’\tTherefore, read in‘, TrData.shape[0], ’samples from the dataset totally.‘)
# label為0~1共2個類別,keras要求格式為binary class matrices,轉(zhuǎn)化一下,直接調(diào)用keras提供的這個函數(shù)
TrLabel = np_utils.to_categorical(TrLabel, LABELTYPE)
這兒我用到了一個load_data()函數(shù),是自己寫的,就是一個數(shù)據(jù)導(dǎo)入,從.mat文件中分別讀入訓(xùn)練集和測試集。也就是對于輸入patch的平移,旋轉(zhuǎn)變換以及訓(xùn)練集測試集劃分都是在MATLAB中完成的,得到的數(shù)據(jù)量爆大,截止到4月7日,我的訓(xùn)練集以及達到了31.4GB的規(guī)模,而python端的函數(shù)就比較直觀了,是這樣的
def load_data():
######################################
# 從.mat文件中讀入數(shù)據(jù)
######################################
mat_training = h5py.File(DATAPATH_Training);
mat_training.keys()
Training_CT_x = mat_training[Training_CT_1];
Training_CT_y = mat_training[Training_CT_2];
Training_CT_z = mat_training[Training_CT_3];
Training_PT_x = mat_training[Training_PT_1];
Training_PT_y = mat_training[Training_PT_2];
Training_PT_z = mat_training[Training_PT_3];
TrLabel = mat_training[Training_label];
TrLabel = np.transpose(TrLabel);
Training_Dataset = len(TrLabel);
mat_validation = h5py.File(DATAPATH_Validation);
mat_validation.keys()
Validation_CT_x = mat_validation[Validation_CT_1];
Validation_CT_y = mat_validation[Validation_CT_2];
Validation_CT_z = mat_validation[Validation_CT_3];
Validation_PT_x = mat_validation[Validation_PT_1];
Validation_PT_y = mat_validation[Validation_PT_2];
Validation_PT_z = mat_validation[Validation_PT_3];
VaLabel = mat_validation[Validation_label];
VaLabel = np.transpose(VaLabel);
Validation_Dataset = len(VaLabel);
######################################
# 初始化
######################################
TrData = np.empty((Training_Dataset, CHANNEL, ROW, COL), dtype = “float32”);
VaData = np.empty((Validation_Dataset, CHANNEL, ROW, COL), dtype = “float32”);
######################################
# 裁剪圖片,通道輸入
######################################
for i in range(Training_Dataset):
TrData[i,0,:,:]=Training_CT_x[:,:,i];
TrData[i,1,:,:]=Training_CT_y[:,:,i];
TrData[i,2,:,:]=Training_CT_z[:,:,i];
TrData[i,3,:,:]=Training_PT_x[:,:,i];
TrData[i,4,:,:]=Training_PT_y[:,:,i];
TrData[i,5,:,:]=Training_PT_z[:,:,i];
for i in range(Validation_Dataset):
VaData[i,0,:,:]=Validation_CT_x[:,:,i];
VaData[i,1,:,:]=Validation_CT_y[:,:,i];
VaData[i,2,:,:]=Validation_CT_z[:,:,i];
VaData[i,3,:,:]=Validation_PT_x[:,:,i];
VaData[i,4,:,:]=Validation_PT_y[:,:,i];
VaData[i,5,:,:]=Validation_PT_z[:,:,i];
print ’\tThe dimension of each data and label, listed as folllowing:‘
print ’\tTrData : ‘, TrData.shape
print ’\tTrLabel : ‘, TrLabel.shape
print ’\tRange : ‘, np.amin(TrData[:,0,:,:]), ’~‘, np.amax(TrData[:,0,:,:])
print ’\t\t‘, np.amin(TrData[:,1,:,:]), ’~‘, np.amax(TrData[:,1,:,:])
print ’\t\t‘, np.amin(TrData[:,2,:,:]), ’~‘, np.amax(TrData[:,2,:,:])
print ’\t\t‘, np.amin(TrData[:,3,:,:]), ’~‘, np.amax(TrData[:,3,:,:])
print ’\t\t‘, np.amin(TrData[:,4,:,:]), ’~‘, np.amax(TrData[:,4,:,:])
print ’\t\t‘, np.amin(TrData[:,5,:,:]), ’~‘, np.amax(TrData[:,5,:,:])
print ’\tVaData : ‘, VaData.shape
print ’\tVaLabel : ‘, VaLabel.shape
print ’\tRange : ‘, np.amin(VaData[:,0,:,:]), ’~‘, np.amax(VaData[:,0,:,:])
print ’\t\t‘, np.amin(VaData[:,1,:,:]), ’~‘, np.amax(VaData[:,1,:,:])
print ’\t\t‘, np.amin(VaData[:,2,:,:]), ’~‘, np.amax(VaData[:,2,:,:])
print ’\t\t‘, np.amin(VaData[:,3,:,:]), ’~‘, np.amax(VaData[:,3,:,:])
print ’\t\t‘, np.amin(VaData[:,4,:,:]), ’~‘, np.amax(VaData[:,4,:,:])
print ’\t\t‘, np.amin(VaData[:,5,:,:]), ’~‘, np.amax(VaData[:,5,:,:])
return TrData, TrLabel, VaData, VaLabel
讀入.mat中儲存的數(shù)據(jù),輸出的就直接是劃分好的訓(xùn)練集(TrData, TrLabel)和測試集(VaData, VaLabel)啦,比較簡單,不展開說了。關(guān)于MATLAB端的數(shù)據(jù)拓展(Data Augmentation),我將在后續(xù)再介紹。說明一下數(shù)據(jù)拓展的作用也是針對“過度擬合”問題的。
注意一點:我的label為0~1共2個類別,keras要求格式為binary class matrices,所以要轉(zhuǎn)化一下,直接調(diào)用keras提供的這個函數(shù)np_utils.to_categorical()即可。
3. 訓(xùn)練中后期代碼
前面的硬骨頭啃完了,這兒就是向開玩笑一樣,短短幾句代碼解決問題。
print(“》》 Build Model 。。.”)
model = create_model(TrData)
######################################
# 訓(xùn)練ConvNets模型
######################################
print(“》》 Training ConvNets Model 。。.”)
print(“\tHere, batch_size =”, BATCH_SIZE, “, epoch =”, EPOCH, “, lr =”, LR, “, momentum =”, MOMENTUM)
early_stopping = EarlyStopping(monitor=’val_loss‘, patience=2)
hist = model.fit(TrData, TrLabel, \
batch_size=BATCH_SIZE, \
nb_epoch=EPOCH, \
shuffle=True, \
verbose=1, \
show_accuracy=True, \
validation_split=VALIDATION_SPLIT, \
callbacks=[early_stopping])
######################################
# 測試ConvNets模型
######################################
print(“》》 Test the model 。。.”)
pre_temp=model.predict_classes(VaData)
3.1 訓(xùn)練模型
先調(diào)用1. 直接上卷積神經(jīng)網(wǎng)絡(luò)構(gòu)建的主函數(shù)中的函數(shù)create_model()建立一個初始化的模型。然后的訓(xùn)練主代碼就是一句話
hist = model.fit(TrData, TrLabel, \
batch_size=100, \
nb_epoch=10, \
shuffle=True, \
verbose=1, \
show_accuracy=True, \
validation_split=0.2, \
callbacks=[early_stopping])
:)沒錯,就一句話,不過這句話里面的事兒稍微比較多一點。。。我這兒就簡單列舉一下我關(guān)注的項:
TrData:訓(xùn)練數(shù)據(jù)
TrLabel:訓(xùn)練數(shù)據(jù)標(biāo)簽
batch_size:每次梯度下降調(diào)整參數(shù)是用的訓(xùn)練樣本
nb_epoch:訓(xùn)練迭代的次數(shù)
shuffle:當(dāng)suffle=True時,會隨機打算每一次epoch的數(shù)據(jù)(默認打亂),但是驗證數(shù)據(jù)默認不會打亂。
validation_split:測試集的比例,我這兒選了0.2。注意,這和2.2 數(shù)據(jù)的簡單處理模塊中的測試集不是一個東西,這個測試集是一次訓(xùn)練的測試集,也就是下次訓(xùn)練他有可能變成訓(xùn)練集了。而2.2 數(shù)據(jù)的簡單處理模塊中的是全局的測試集,對于訓(xùn)練好的網(wǎng)絡(luò)做的最終測試。
early_stopping:是否提前結(jié)束訓(xùn)練,網(wǎng)絡(luò)自己判斷,當(dāng)本次訓(xùn)練和上次訓(xùn)練的結(jié)果差不多了自動回停止訓(xùn)練迭代,也就是不一定訓(xùn)練完nb_epoch(10)次哦
early_stopping的調(diào)用在這兒
early_stopping = EarlyStopping(monitor=’val_loss‘, patience=2)
其他的都是和訓(xùn)練的時候的界面有關(guān),按照我的或者默認的來就可以了:)
提一嘴,如果你想要看每一次的訓(xùn)練的結(jié)果是可以做到的!hist = model.fit()的hist中存放的是每一次訓(xùn)練完的結(jié)果和測試精確度等信息。
再來一嘴,如果你想要看每一層的輸出的啥,也是可以做到的!
這個可以用到卷積神經(jīng)網(wǎng)絡(luò)和其他傳統(tǒng)分類器結(jié)合來優(yōu)化softmax方法的實驗,涉及到比較高級的算法了,我以后再說。這兒先只放上看每一層輸出的代碼:
get_feature = theano.function([origin_model.layers[0].input],origin_model.layers[12].get_output(train=False),allow_input_downcast=False)
feature = get_feature(data)
好吧,再提供一下SVM和Random Forests的Python函數(shù)代碼吧,如果大家想做這個實驗可以用哈:
######################################
# SVM
######################################
def svc(traindata,trainlabel,testdata,testlabel):
print(“Start training SVM.。。”)
svcClf = SVC(C=1.0,kernel=“rbf”,cache_size=3000)
svcClf.fit(traindata,trainlabel)
pred_testlabel = svcClf.predict(testdata)
num = len(pred_testlabel)
accuracy = len([1 for i in range(num) if testlabel[i]==pred_testlabel[i]])/float(num)
print(“\n》》 cnn-svm Accuracy”)
prt(testlabel, pred_testlabel)
######################################
# Random Forests
######################################
def rf(traindata,trainlabel,testdata,testlabel):
print(“Start training Random Forest.。。”)
rfClf = RandomForestClassifier(n_estimators=100,criterion=’gini‘)
rfClf.fit(traindata,trainlabel)
pred_testlabel = rfClf.predict(testdata)
print(“\n》》 cnn-rf Accuracy”)
prt(testlabel, pred_testlabel)
收~打住。
3.2 測試模型
呼呼,這個最水,也是一句話
pre_temp=model.predict_classes(VaData)
套用一個現(xiàn)有函數(shù)predict_classes()輸入測試集VaData,返回訓(xùn)練完的網(wǎng)絡(luò)的預(yù)測結(jié)果pre_temp。好了,最后把pre_temp和正確的測試集標(biāo)簽VaLabel對比一下,就知道這個網(wǎng)絡(luò)訓(xùn)練的咋樣了,實驗階段性勝利!發(fā)個截圖:
Everybody Happy
3.3 保存模型
訓(xùn)練一個模型不容易,不但需要調(diào)整參數(shù),調(diào)整網(wǎng)絡(luò)結(jié)構(gòu),訓(xùn)練的時間還特別長,所以要學(xué)會保存訓(xùn)練完的網(wǎng)絡(luò),代碼是這樣的:
######################################
# 保存ConvNets模型
######################################
model.save_weights(’MyConvNets.h5‘)
cPickle.dump(model, open(’。/MyConvNets.pkl‘,“wb”))
json_string = model.to_json()
open(W_MODEL, ’w‘).write(json_string)
就保存好啦,是這三個文件
保存的模型文件
當(dāng)你回頭要調(diào)用這個網(wǎng)絡(luò)時,用這個代碼就可以了
model = cPickle.load(open(’MyConvNets.pkl’,“rb”))
model中就讀入了pkl文件內(nèi)存儲的模型啦。
評論
查看更多