keras框架下的深度學習(一)手寫體識別
這個系列文章主要記錄使用keras框架來搭建深度學習模型的學習過程,其中有一些自己的想法和體會,主要學習的書籍是:Deep Learning with Python,使用的IDE是pycharm。
在深度學習中的深度指的是資料模型中包含著的多個層次,而深度學習是對一堆數值做數學運算,但是這種數學運算是高緯度的,是大量的;在這些數學運算中,深度學習中的層通過反饋(比如後向傳播)來對引數進行調整,然後再進行計算。如此反覆數次,從而越來越接近我們所給出的正確結果。而在這個過程中,深度學習中的每個層所學習到的就是引數的具體數值。模型訓練好以後,只需要一個輸入,就能得到相應的輸出。
對於深度學習的程式設計,以下是具體步驟(針對於keras框架):
1.對資料的預處理,如把圖片資料變成數值矩陣,把離散的資料變成向量,把資料歸一化等等,其目的是為了之後的學習;2.搭建網路框架,即輸入層,隱藏層和輸出層,深度學習所需要學習的引數在這些層裡面;3.編譯,即定義所需要用的優化器、損失函式等;4.迴圈訓練,讓網路一次又一次的執行,最後得到學習後的引數;5.測試訓練的網路模型,在模型訓練結束後,進行測試來驗證其泛化能力。
手寫體識別:
我們使用mnist資料集,這個資料集包含60000張訓練影象和10000張測試影象,全部都是尺寸28*28畫素的手寫數字圖片,如下圖所示:
以下我們使用keras框架進行手寫體識別的程式編寫:
1.對資料的預處理
from keras.datasets import mnist (train_image,train_labels),(test_images,test_labels)=mnist.load_data()
第一句是從keras.datasets中匯入mnist,具體來說,我們在pychram中下載了keras這個庫。它是一個資料夾,裡面由很多的檔案,而其中一個檔案是datasets,裡面主要用來呼叫各種資料集;比如mnist是keras檔案下datasets檔案中的一個程式設計檔案(.py),和我們自己寫的程式設計檔案沒有區別,而這個檔案的具體功能是取爬取到mnist網站中的訓練集和測試集。在mnist中有一個load_data的函式,在該函式中把訓練集和測試集都分類好了,所以我們用(train_image,train_labels),(test_images,test_labels)來呼叫該函式中的訓練集和測試集。訓練集是為了訓練模型識別手寫體,而測試集是為了檢測模型訓練好之後對其他非訓練集圖片識別的能力,也就是泛化能力,而模型的優秀泛化能力是我們所需要的。
其中train_image的型別是一個三維張量:(60000,28,28),即是擁有60000張28*28的照片。而train_labels是一個一維的張量:(60000,),表示有60000個標籤。測試集test也和訓練集合類似,只是照片總數只有10000張,所以標籤也只有10000個。
在匯入資料集後,我們首先對資料進行處理,首先我們得確定深度學習網路是用什麼層來搭建,我們假設使用全連線層(後面會講到用卷積網路),則需要把圖片資料處理成一個矩陣輸入,該矩陣的多少行表示是有多少張照片,列是影象28*28個畫素值(把圖片拉成一維的)因為矩陣內是灰色圖片的畫素點,又需要把矩陣裡面的數值壓縮到0到1之間變成float32型別,所以我們需要把矩陣內的數值變成浮點型變數後除以255,最後還需要對標籤(是什麼數字)處理成為一個一維的向量(one-hot),比如,如果標籤是3(對應的圖片是手寫體數字3),則生成一個只在索引3寫入1,其他地方寫入0的一維向量:[0,0,0,1,0,0,0,0,0,0,]。程式碼如下:
train_image=train_image.reshape((60000,28*28)) #定義訓練集的資料型別,是一個(60000,784)的矩陣 train_image=train_image.astype('float32')/255 #把以上定義的矩陣內數值變成一個在(0,1)之間的數 test_images=test_images.reshape((10000,28*28)) #同上 test_images=test_images.astype('float32')/255 train_labels = to_categorical(train_labels) test_labels = to_categorical(test_labels)
2.搭建網路框架
from keras import models from keras import layers network=models.Sequential() network.add(layers.Dense(512,activation='relu',input_shape=(28*28,))) network.add(layers.Dense(10,activation='softmax'))
首先需要匯入models和layers,即匯入keras中編寫好了的層和模型,第一行和第二行是要放在最前面的(所有的匯入都需要放在最前面,這裡為了方便講解,所以和相應的程式碼放在一起)
我們可以想象該程式在keras這個檔案中,匯入models程式設計檔案和layers檔案,然後使得network為models.Sequential(),sequential是models中的一個函式,表示以下的網路結構都是線性排列下去。
然後我們添加了兩個全連線層(Dence),該網路一共就只有兩個層次。第一層,神經元是512個,啟用函式是relu函式,而且定義了輸入的形狀,其引數一共有:28*28*512+512=401920(影象畫素尺寸*神經元個數+權值b)。第二層是最後一層也是輸出層,因為是判斷手寫數字,所以是10個神經元,表示輸出10個概率值(總和為1)的列表,其中啟用函式是softmax,其引數一共有:512*10+10=5120(上一層神經元個數*該層神經元個數+權值b)。
3.編譯
network.compile( optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'] )
這一步是為第二步定義的網路結構做訓練的準備,其中有三個引數:1.損失函式(loss):告訴網路學習的情況,如何讓網路朝著爭取的放向進行。(告訴網路,你和正確值差了多少,然後用優化器來反饋訊號(後向傳播)進行引數更新)2.優化器(optimizer):用於訓練資料和損失函式來更新網路的機制。3訓練過程中需要監視的指標(metrics):這裡只監視訓練的正確率如何(把每一次執行的accuracy的數值列印在頁面)
4.迴圈訓練
network.fit(train_image,train_labels,epochs=100,batch_size=34)
用之前定義的network呼叫fit函式執行迴圈訓練,在fit中,train_image和train_labels是訓練要使用的圖片資料和標籤,epochs是指訓練多少次,batch_size是指一次訓練多少個數據(比如每一次epoch中,一共要訓練60000組資料,但是一次性只訓練34次)。在這裡的程式碼,我們可以令其等於一個變數,然後在最後畫出準確率和損失值的圖形(詳細方法在下篇文章討論)
5.測試訓練的網路模型
test=network.evaluate(test_images,test_labels) print(test)
在network中呼叫evaluate函式對最後的測試集進行評估,然後把數賦值給test,最後把test打印出來,得到最後的acc(準確度)和loss(損失值)。一般來說,最後的測試是在訓練好了模型之後,去測試模型的泛化能力。
總程式碼如下:
from keras.datasets import mnist from keras import models from keras import layers from keras.utils import to_categorical #匯入資料集 (train_image,train_labels),(test_images,test_labels) = mnist.load_data() #資料預處理 train_image=train_image.reshape((60000,28*28)) #定義訓練集的資料型別,是一個(60000,784)的矩陣 train_image=train_image.astype('float32')/255 #把以上定義的矩陣內數值,歸一化 test_images=test_images.reshape((10000,28*28)) #同上 test_images=test_images.astype('float32')/255 train_labels = to_categorical(train_labels) test_labels = to_categorical(test_labels) #定義網路架構 network=models.Sequential() network.add(layers.Dense(512,activation='relu',input_shape=(28*28,))) network.add(layers.Dense(10,activation='softmax')) #編譯 network.compile( optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'] ) #開始迴圈訓練網路 network.fit(train_image,train_labels,epochs=100,batch_size=34) #對網路進行評估 test=network.evaluate(test_images,test_labels) print(test)
以上是編寫一個簡單的深度學習網路來識別手寫數字。難度不是很大,主要是對keras框架中語句的呼叫,以及引數的改寫(keras已經把深度學習中的一系列操作打包成了函式或者程式設計檔案,所以我們只需要呼叫即可)。該深度學習模型的主要步驟是:首先對資料進行預處理,變成歸一化的浮點型數值矩陣輸入神經網路的輸入層,然後第一層的權重與輸入的資料做運算後加上權重b,通過一個非線性函式輸入到下一層,其他層類似;最後在輸出層設有10個神經元,表示最後的輸出是一個一行十列的陣列,用來存放估計的數字的概率。之後由損失函式(categorical_crossentropy)進行後向傳播來更新網路中的權重,其中所用到的優化器是rmsprop。
附:梯度下降演算法
梯度下降演算法其內容不是很難,主要是在某個點找到其梯度的方向,然後選擇梯度的反方向移動,最後慢慢的接近函式的極值點。
舉個簡單的例子,令f(x)=x**2(函式是x的平方),然後取一個點:x=2,下面我們編寫其梯度下降的演算法。
在編寫之前,我們首先要明白,梯度下降演算法是怎麼慢慢的接近極值點的。在上面例子中,我們選定點x=2後,我們在x=2這一點求其導數,然後用x減去該導數乘以學習率,得到一個新的x座標,計算出函式的值,再重複以上步驟,讓其慢慢接近極值點(導數為零,或者每一次移動都足夠小,小到滿足需求)。
其python程式如下:
首先我們定義f(x)函式,以及其一階導函式fn(x):
def f(x): f=x**2 return f def fn(x): h=0.0001 fn=(f(x+h)-f(x-h))/(2*h) return fn
注:fn=(f(x+h)-f(x-h))/(2*h)是取了x兩邊的變化(中心差分),這會使得導數誤差更小。
然後定義學習率,迴圈的次數以及x的初始值,程式碼如下:
eta=0.01 #學習率 mun_times=1000 #迴圈次數 x_value=[] #儲存x的值 f_value=[] #儲存f(x)的值 x=2 #讓x從2開始取值
之後就是通過迴圈,不斷的接近函式的極值:
for i in range(mun_times): x-=eta*fn(x) #更新x的取值 x_value.append(x) f_value.append(f(x))
最後可以把x的每次取值和f(x)的值畫出來,看看梯度下降的軌跡圖:
import matplotlib.pyplot as plt plt.plot(x_value,f_value) plt.show()
圖形如下圖所示:
總的程式碼為:
import matplotlib.pyplot as plt def f(x): f=x**2 return f def fn(x): #輸入的是x輸出的是derivate h=0.0001 fn=(f(x+h)-f(x-h))/(2*h) return fn eta=0.01 #學習率 mun_times=1000 #迴圈次數 x_value=[] #儲存x的值 f_value=[] #儲存f(x)的值 x=2 #讓x從2開始取值 for i in range(mun_times): x-=eta*fn(x) #更新x的取值 x_value.append(x) f_value.append(f(x)) plt.plot(x_value,f_value) plt.show()
下面討論一下偏導數的梯度下降,核心思想是求出x(x在這裡是一個列表,有多個數值)的偏導數,然後把梯度儲存在一個列表中,最後總的進行數值更新:
首先我們定義其求偏導的過程,即分別求x列表中的每個x的偏導數:
def gradient(f,x): h=0.0001 grad = np.zeros_like(x) #生成一個與x大小一致的全零矩陣 for i in range(x.size): tem = x[i] x[i] = tem + h #求第i個x的前向差分 fh1 = f(x) x[i] = tem - h #求第i個x的後向差分 fh2 = f(x) grad[i] = (fh1-fh2)/(2*h) #求第i個x的中心差分 x[i] = tem #把第i個x的值還給它自己 return grad
定義函式,這裡為了方便,我們只定義一個包含兩個變數的函式:
def function(x): return x[0]**2+x[1]**2
定義初始引數:
x_list1=[] #儲存x[0]的值 x_list2=[] #儲存x[1]的值 function_list=[] #儲存函式的值 x = np.array([-2.0,4.0]) #選定初始的點 lr=0.01 #學習率是0.01 ste_num=500 #迴圈500次
梯度下降:
for i in range(ste_num): x_list1.append(x[0]) x_list2.append(x[1]) function_list.append(function(x)) grad=gradient(function,x) #計算x的梯度 x -= lr * grad #更新x的值
最後畫圖檢視:
import matplotlib.pyplot as plt plt.plot(x_list1,function_list,'r',label='x[0]') plt.plot(x_list2,function_list,'b',label='x[1]') plt.show()
總程式碼:
import numpy as np import matplotlib.pyplot as plt def gradient(f,x): h=0.0001 grad = np.zeros_like(x) for i in range(x.size): tem = x[i] x[i] = tem + h fh1 = f(x) x[i] = tem - h fh2 = f(x) grad[i] = (fh1-fh2)/(2*h) x[i] = tem return grad def function(x): return x[0]**2+x[1]**2 x_list1=[] #儲存x[0]的值 x_list2=[] #儲存x[1]的值 function_list=[] #儲存函式的值 x = np.array([-2.0,4.0]) #選定初始的點 lr=0.01 #學習率是0.01 ste_num=500 #迴圈500次 for i in range(ste_num): x_list1.append(x[0]) x_list2.append(x[1]) function_list.append(function(x)) grad=gradient(function,x) #計算x的梯度 x -= lr * grad #更新x的值 plt.plot(x_list1,function_list,'r',label='x[0]') plt.plot(x_list2,function_list,'b',label='x[1]') plt.show()
深度學習的梯度下降類似於以上的過程,不過要比上面的例子複雜很多。
類似於自己編寫的簡單梯度下降演算法,只要清楚的瞭解深度學習的每個步驟,就可以自己嘗試編寫程式碼完成,從簡單的開始,一步步去優化。這樣會更加有利於去理解keras中的每個步驟是怎麼來的,對深度學習的每個步驟理解更深。在之後的文章中,可能會慢慢的把深度學習中的步驟一一的嘗試去實現。