這個系列文章主要記錄使用keras框架來搭建深度學習模型的學習過程,其中有一些自己的想法和體會,主要學習的書籍是:Deep Learning with Python,使用的IDE是pycharm,需要安裝keras和tensorflow庫。
本文第一部分編寫一個簡單的深度學習網絡來識別手寫數字。難度不是很大,主要是對keras框架中語句的調用,以及參數的改寫(keras已經把深度學習中的一系列操作打包成了函數或者編程文件,所以我們只需要調用即可)。該深度學習模型的主要步驟是:首先對數據進行預處理,變成歸一化的浮點型數值矩陣輸入神經網絡的輸入層,然后第一層的權重與輸入的數據做運算后加上權重b,通過一個非線性函數輸入到下一層,其他層類似;最后在輸出層設有10個神經元,表示最后的輸出是一個一行十列的數組,用來存放估計的數字的概率。之后由損失函數(categorical_crossentropy)進行后向傳播來更新網絡中的權重,其中所用到的優化器是rmsprop。
第二部分是編寫一個簡單梯度下降算法,類似於自己編寫簡單的梯度下降算法,只要清楚的了解深度學習的每個步驟,就可以自己嘗試編寫代碼完成,從簡單的開始,一步步去優化。這樣會更加有利於去理解keras中的每個步驟是怎么來的,對深度學習的每個步驟理解更深。在之后的文章中,可能會慢慢的把深度學習中的步驟一一的嘗試去實現。
在深度學習中的深度指的是數據模型中包含着的多個層次,而深度學習是對一堆數值做數學運算,但是這種數學運算是高緯度的,是大量的;在這些數學運算中,深度學習中的層通過反饋(比如后向傳播)來對參數進行調整,然后再進行計算。如此反復數次,從而越來越接近我們所給出的正確結果。而在這個過程中,深度學習中的每個層所學習到的就是參數的具體數值。模型訓練好以后,只需要一個輸入,就能得到相應的輸出。
對於深度學習的編程,以下是具體步驟(針對於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中的一個函數,表示以下的網絡結構都是線性排列下去。
然后我們添加了兩個全連接層(Dense),該網絡一共就只有兩個層次。第一層,神經元是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中的每個步驟是怎么來的,對深度學習的每個步驟理解更深。在之后的文章中,可能會慢慢的把深度學習中的步驟一一的嘗試去實現。