深度学习高能干货:手把手教你搭建MXNet框架
導讀:相信很多程序員在學習一門新的編程語言或者框架時,都會先了解下該語言或者該框架涉及的數據結構,畢竟當你清晰地了解了數據結構之后才能更加優雅地編寫代碼,MXNet同樣也是如此。
在MXNet框架中你至少需要了解這三駕馬車:NDArray、Symbol和Module。這三者將會是你今后在使用MXNet框架時經常用到的接口。那么在搭建或者訓練一個深度學習算法時,這三者到底扮演了一個什么樣的角色呢?
這里可以做一個簡單的比喻,假如將從搭建到訓練一個算法的過程比作是一棟房子從建造到裝修的過程,那么NDArray就相當于是鋼筋水泥這樣的零部件,Symbol就相當于是房子每一層的設計,Module就相當于是房子整體框架的搭建。
在本文中你將實際感受命令式編程(imperative programming)和符號式編程(symbolic programming)的區別,因為NDArray接口采用的是命令式編程的方式,而Symbol接口采用的是符號式編程的方式。
作者:邁克爾·貝耶勒(Michael Beyeler)
如需轉載請聯系大數據(ID:hzdashuju)
01 NDArray
NDArray是MXNet框架中數據流的基礎結構,NDArray的官方文檔地址是:
https://mxnet.apache.org/api/python/ndarray/ndarray.html
與NDArray相關的接口都可以在該文檔中查詢到。在了解NDArray之前,希望你先了解下Python中的NumPy庫:
http://www.numpy.org/
因為一方面在大部分深度學習框架的Python接口中,NumPy庫的使用頻率都非常高;另一方面大部分深度學習框架的基礎數據結構設計都借鑒了NumPy。
在NumPy庫中,一個最基本的數據結構是array,array表示多維數組,NDArray與NumPy庫中的array數據結構的用法非常相似,可以簡單地認為NDArray是可以運行在GPU上的NumPy array。
接下來,我會介紹在NDArray中的一些常用操作,并提供其與NumPy array的對比,方便讀者了解二者之間的關系。
首先,導入MXNet和NumPy,然后通過NDArray初始化一個二維矩陣,代碼如下:
import?numpy?as?np
a?=?mx.nd.array([[1,2],[3,4]])
print(a)
輸出結果如下:
?[3.?4.]]
<NDArray?2x2?@cpu(0)>
接著,通過NumPy array初始化一個相同的二維矩陣,代碼如下:
print(b)
輸出結果如下:
?[3?4]]
實際使用中常用縮寫mx代替mxnet,mx.nd代替mxnet.ndarray,np代替numpy,本書后續篇章所涉及的代碼默認都采取這樣的縮寫。
再來看看NumPy array和NDArray常用的幾個方法對比,比如打印NDArray的維度信息:
輸出結果如下:
打印NumPy array的維度信息:
輸出結果如下:
打印NDArray的數值類型:
輸出結果如下:
打印Numpy array的數值類型:
輸出結果如下:
在使用大部分深度學習框架訓練模型時默認采用的都是float32數值類型,因此初始化一個NDArray對象時默認的數值類型是float32。
如果你想要初始化指定數值類型的NDArray,那么可以通過dtype參數來指定,代碼如下:
print(c.dtype)
輸出結果如下:
如果你想要初始化指定數值類型的NumPy array,則可以像如下這樣輸入代碼:
print(d.dtype)
輸出結果如下:
在NumPy的array結構中有一個非常常用的操作是切片(slice),這種操作在NDArray中同樣也可以實現,具體代碼如下:
print(c[0,1:3])
輸出結果如下:
<NDArray?2?@cpu(0)>
在NumPy array中可以這樣實現:
print(d[0,1:3])
輸出結果如下:
在對已有的NumPy array或NDArray進行復制并修改時,為了避免影響到原有的數組,可以采用copy()方法進行數組復制,而不是直接復制,這一點非常重要。下面以NDArray為例來看看采用copy()方法進行數組復制的情況,首先打印出c的內容:
輸出結果如下:
?[5.?6.?7.?8.]]
<NDArray?2x4?@cpu(0)>
然后調用c的copy()方法將c的內容復制到f,并打印f的內容:
輸出結果如下:
?[5.?6.?7.?8.]]
<NDArray?2x4?@cpu(0)>
修改f中的一個值,并打印f的內容:
print(f)
輸出結果如下,可以看到此時對應位置的值已經被修改了:
?[?5.?6.?7.?8.]]
<NDArray?2x4?@cpu(0)>
那么c中對應位置的值有沒有被修改呢?可以打印此時c的內容:
輸出結果如下,可以看到此時c中對應位置的值并沒有被修改:
?[5.?6.?7.?8.]]
<NDArray?2x4?@cpu(0)>
接下來看看如果直接將c復制給e,會有什么樣的情況發生:
輸出結果如下:
?[5.?6.?7.?8.]]
<NDArray?2x4?@cpu(0)>
修改e中的一個值,并打印e的內容:
print(e)
輸出內容如下:
?[?5.?6.?7.?8.]]
<NDArray?2x4?@cpu(0)>
此時再打印c的內容:
輸出結果如下,可以看到對應位置的值也發生了改變:
?[?5.?6.?7.?8.]]
<NDArray?2x4?@cpu(0)>
實際上,NumPy array和NDArray之間的轉換也非常方便,NDArray轉NumPy array可以通過調用NDArray對象的asnumpy()方法來實現:
輸出結果如下:
?[?5.?6.?7.?8.]]
NumPy array轉NDArray可以通過mxnet.ndarray.array()接口來實現:
輸出結果如下:
?[?5.?6.?7.?8.]]
<NDArray?2x4?@cpu(0)>?
前面曾提到過NDArray和NumPy array最大的區別在于NDArray可以運行在GPU上,從前面打印出來的NDArray對象的內容可以看到,最后都有一個@cpu,這說明該NDArray對象是初始化在CPU上的,那么如何才能將NDArray對象初始化在GPU上呢?首先,調用NDArray對象的context屬性可以得到變量所在的環境:
輸出結果如下:
然后,調用NDArray對象的as_in_context()方法指定變量的環境,例如這里將環境指定為第0塊GPU:
print(e.context)
輸出結果如下:
環境(context)是深度學習算法中比較重要的內容,目前常用的環境是CPU或GPU,在深度學習算法中,數據和模型都要在同一個環境中才能正常進行訓練和測試。
MXNet框架中NDArray對象的默認初始化環境是CPU,在不同的環境中,變量初始化其實就是變量的存儲位置不同,而且存儲在不同環境中的變量是不能進行計算的,比如一個初始化在CPU中的NDArray對象和一個初始化在GPU中的NDArray對象在執行計算時會報錯:
print(e+f)
顯示結果如下,從報錯信息可以看出是2個對象的初始化環境不一致導致的:
下面將f的環境也修改成GPU,再執行相加計算:
print(e+f)
輸出結果如下:
?[?11.??13.??15.??17.]]
<NDArray?2x4?@gpu(0)>
NDArray是MXNet框架中使用最頻繁也是最基礎的數據結構,是可以在CPU或GPU上執行命令式操作(imperative operation)的多維矩陣,這種命令式操作直觀且靈活,是MXNet框架的特色之一。因為在使用MXNet框架訓練模型時,幾乎所有的數據流都是通過NDArray數據結構實現的,因此熟悉該數據結構非常重要。
02 Symbol
Symbol是MXNet框架中用于構建網絡層的模塊,Symbol的官方文檔地址是:
https://mxnet.apache.org/api/python/symbol/symbol.html
與Symbol相關的接口都可以在該文檔中查詢。與NDArray不同的是,Symbol采用的是符號式編程(symbolic programming),其是MXNet框架實現快速訓練和節省顯存的關鍵模塊。
符號式編程的含義,簡單來說就是,符號式編程需要先用Symbol接口定義好計算圖,這個計算圖同時包含定義好的輸入和輸出格式,然后將準備好的數據輸入該計算圖完成計算。
而NDArray采用的是命令式編程(imperative programming),計算過程可以逐步來步實現。其實在你了解了NDArray之后,你完全可以僅僅通過NDArray來定義和使用網絡,那么為什么還要提供Symbol呢?主要是為了提高效率。在定義好計算圖之后,就可以對整個計算圖的顯存占用做優化處理,這樣就能大大降低訓練模型時候的顯存占用。
在MXNet中,Symbol接口主要用來構建網絡結構層,其次是用來定義輸入數據。接下來我們再來列舉一個例子,首先定義一個網絡結構,具體如下。
用mxnet.symbol.Variable()接口定義輸入數據,用該接口定義的輸入數據類似于一個占位符。
用mxnet.symbol.Convolution()接口定義一個卷積核尺寸為3*3,卷積核數量為128的卷積層,卷積層是深度學習算法提取特征的主要網絡層,該層將是你在深度學習算法(尤其是圖像領域)中使用最為頻繁的網絡層。
用 mxnet.symbol.BatchNorm()接口定義一個批標準化(batch normalization,常用縮寫BN表示)層,該層有助于訓練算法收斂。
用mxnet.symbol.Activation()接口定義一個ReLU激活層,激活層主要用來增加網絡層之間的非線性,激活層包含多種類型,其中以ReLU激活層最為常用。
用mxnet.symbol.Pooling()接口定義一個最大池化層(pooling),池化層的主要作用在于通過縮減維度去除特征圖噪聲和減少后續計算量,池化層包含多種形式,常用形式有均值池化和最大池化。
用mxnet.symbol.FullyConnected()接口定義一個全連接層,全連接層是深度學習算法中經常用到的層,一般是位于網絡的最后幾層。需要注意的是,該接口的num_hidden參數表示分類的類別數。
用mxnet.symbol.SoftmaxOutput()接口定義一個損失函數層,該接口定義的損失函數是圖像分類算法中常用的交叉熵損失函數(cross entropy loss),該損失函數的輸入是通過softmax函數得到的,softmax函數是一個變換函數,表示將一個向量變換成另一個維度相同,但是每個元素范圍在[0,1]之間的向量,因此該層用mxnet.symbol.SoftmaxOutput()來命名。這樣就得到了一個完整的網絡結構了。
網絡結構定義代碼如下:
data?=?mx.sym.Variable('data')
conv?=?mx.sym.Convolution(data=data,?num_filter=128,?kernel=(3,3),?pad=(1,1),?name='conv1')
bn?=?mx.sym.BatchNorm(data=conv,?name='bn1')
relu?=?mx.sym.Activation(data=bn,?act_type='relu',?name='relu1')
pool?=?mx.sym.Pooling(data=relu,?kernel=(2,2),?stride=(2,2),?pool_type='max',?name='pool1')
fc?=?mx.sym.FullyConnected?(data=pool,?num_hidden=2,?name='fc1')
sym?=?mx.sym.SoftmaxOutput?(data=fc,?name='softmax')
mx.sym是mxnet.symbol常用的縮寫形式,后續篇章默認采用這種縮寫形式。另外在定義每一個網絡層的時候最好都能指定名稱(name)參數,這樣代碼看起來會更加清晰。
定義好網絡結構之后,你肯定還想看看這個網絡結構到底包含哪些參數,畢竟訓練模型的過程就是模型參數更新的過程,在MXNet中,list_arguments()方法可用于查看一個Symbol對象的參數,命令如下:
由下面的輸出結果可以看出,第一個和最后一個分別是'data'和'softmax_label',這二者分別代表輸入數據和標簽;'conv1_weight'和'conv1_bias'是卷積層的參數,具體而言前者是卷積核的權重參數,后者是偏置參數;'bn1_gamma'和'bn1_beta'是BN層的參數;'fc1_weight'和'fc1_bias'是全連接層的參數。
除了查看網絡的參數層名稱之外,有時候我們還需要查看網絡層參數的維度、網絡輸出維度等信息,這一點對于代碼調試而言尤其有幫助。
在MXNet中,可以用infer_shape()方法查看一個Symbol對象的層參數維度、輸出維度、輔助層參數維度信息,在調用該方法時需要指定輸入數據的維度,這樣網絡結構就會基于指定的輸入維度計算層參數、網絡輸出等維度信息:?
print(arg_shape)
print(out_shape)
print(aux_shape)
由下面的輸出結果可知,第一行表示網絡層參數的維度,與前面list_arguments()方法列出來的層參數名一一對應,例如:
第二行表示網絡輸出的維度,因為網絡的最后一層是輸出節點為2的全連接層,且輸入數據的批次維度是1,所以輸出維度是[(1, 2)]。
第三行是輔助參數的維度,目前常見的主要是BN層的參數維度。
[(1,?2)]
[(128,),?(128,)]
如果要截取通過Symbol模塊定義的網絡結構中的某一部分也非常方便,在MXNet中可以通過get_internals()方法得到Symbol對象的所有層信息,然后選擇要截取的層即可,比如將sym截取成從輸入到池化層為止:
print(sym_mini.list_arguments())
輸出結果如下,可以看到層參數中沒有sym原有的全連接層和標簽層信息了:
截取之后還可以在截取得到的Symbol對象后繼續添加網絡層,比如增加一個輸出節點為5的全連接層和一個softmax層:
sym_new?=?mx.sym.SoftmaxOutput?(data=fc_new,?name='softmax')
print(sym_new.list_arguments())
輸出結果如下,可以看到全連接層已經被替換了:
除了定義神經網絡層之外,Symbol模塊還可以實現NDArray的大部分操作,接下來以數組相加和相乘為例介紹通過Symbol模塊實現上述操作的方法。首先通過 mxnet.symbol.Variable()接口定義兩個輸入data_a和data_b;然后定義data_a和data_b相加并與data_c相乘的操作以得到結果s,通過打印s的類型可以看出s的類型是Symbol,代碼如下:
data_a?=?mx.sym.Variable?('data_a')?????
data_b?=?mx.sym.Variable?('data_b')
data_c?=?mx.sym.Variable?('data_c')
s?=?data_c*(data_a+data_b)
print(type(s))
輸出結果如下:
接下來,調用s的bind()方法將具體輸入和定義的操作綁定到執行器,同時還需要為bind()方法指定計算是在CPU還是GPU上進行,執行bind操作后就得到了執行器e,最后打印e的類型進行查看,代碼如下:
????'data_c':mx.nd.array([2,3,4])})
print(type(e))
輸出結果如下:
這個執行器就是一個完整的計算圖了,因此可以調用執行器的forward()方法進行計算以得到結果:
輸出結果如下:
<NDArray?3?@cpu(0)>
相比之下,通過NDArray模塊實現這些操作則要簡潔和直觀得多,代碼如下:
data_a?=?mx.nd.array([1,2,3])
data_b?=?mx.nd.array([4,5,6])
data_c?=?mx.nd.array([2,3,4])
result?=?data_c*(data_a+data_b)
print(result)
輸出結果如下:
<NDArray?3?@cpu(0)>
雖然使用Symbol接口的實現看起來有些復雜,但是當你定義好計算圖之后,很多顯存是可以重復利用或共享的,比如在Symbol模塊實現版本中,底層計算得到的data_a+data_b的結果會存儲在data_a或data_b所在的空間,因為在該計算圖中,data_a和data_b在執行完相加計算后就不會再用到了。
前面介紹的是Symbol模塊中Variable接口定義的操作和NDArray模塊中對應實現的相似性,除此之外,Symbol模塊中關于網絡層的操作在NDArray模塊中基本上也有對應的操作,這對于靜態圖的調試來說非常有幫助。
之前提到過,Symbol模塊采用的是符號式編程(或者稱為靜態圖),即首先需要定義一個計算圖,定義好計算圖之后再執行計算,這種方式雖然高效,但是對代碼調試其實是不大友好的,因為你很難獲取中間變量的值。
現在因為采用命令式編程的NDArray模塊中基本上包含了Symbol模塊中同名的操作,因此可以在一定程度上幫助調試代碼。接下來以卷積層為例看看如何用NDArray模塊實現一個卷積層操作,首先用mxnet.ndarray.arange()接口初始化輸入數據,這里定義了一個4維數據data,之所以定義為4維是因為模型中的數據流基本上都是4維的。具體代碼如下:
print(data)
輸出結果如下:
???[?7.??8.??9.?10.?11.?12.?13.]
???[14.?15.?16.?17.?18.?19.?20.]
???[21.?22.?23.?24.?25.?26.?27.]]]]
<NDArray?1x1x4x7?@cpu(0)>
然后,通過mxnet.ndarray.Convolution()接口定義卷積層操作,該接口的輸入除了與mxnet.symbol.Convolution()接口相同的data、num_filter、kernel和name之外,還需要直接指定weight和bias。
weight和bias就是卷積層的參數值,為了簡單起見,這里將weight初始化成值全為1的4維變量,bias初始化成值全為0的1維變量,這樣就能得到最后的卷積結果。具體代碼如下:
??????????????????????????bias=mx.nd.zeros((10)),?num_filter=10,?kernel=(3,3),
??????????????????????????name='conv1')
print(conv1)
輸出結果如下:
???[135.?144.?153.?162.?171.]]
??[[?72.??81.??90.??99.?108.]
???[135.?144.?153.?162.?171.]
??[[?72.??81.??90.??99.?108.]
???[135.?144.?153.?162.?171.]]
??[[?72.??81.??90.??99.?108.]
???[135.?144.?153.?162.?171.]]
??[[?72.??81.??90.??99.?108.]
???[135.?144.?153.?162.?171.]]
??[[?72.??81.??90.??99.?108.]
???[135.?144.?153.?162.?171.]]
??[[?72.??81.??90.??99.?108.]
???[135.?144.?153.?162.?171.]]
??[[?72.??81.??90.??99.?108.]
???[135.?144.?153.?162.?171.]]
??[[?72.??81.??90.??99.?108.]
???[135.?144.?153.?162.?171.]]
??[[?72.??81.??90.??99.?108.]
???[135.?144.?153.?162.?171.]]]]
<NDArray?1x10x2x5?@cpu(0)>
總體來看,Symbol和NDArray有很多相似的地方,同時,二者在MXNet中都扮演著重要的角色。采用命令式編程的NDArray其特點是直觀,常用來實現底層的計算;采用符號式編程的Symbol其特點是高效,主要用來定義計算圖。
03 Module
在MXNet框架中,Module是一個高級的封裝模塊,可用來執行通過Symbol模塊定義的網絡模型的訓練,與Module相關的接口介紹都可以參考Module的官方文檔地址:
https://mxnet.apache.org/api/python/module/module.html
Module接口提供了許多非常方便的方法用于模型訓練,只需要將準備好的數據、超參數等傳給對應的方法就能啟動訓練。
上午中,我們用Symbol接口定義了一個網絡結構sym,接下來我們將基于這個網絡結構介紹Module模塊,首先來看看如何通過Module模塊執行模型的預測操作。
通過mxnet.module.Module()接口初始化一個Module對象,在初始化時需要傳入定義好的網絡結構sym并指定運行環境,這里設置為GPU環境。
然后執行Module對象的bind操作,這個bind操作與Symbol模塊中的bind操作類似,目的也是將網絡結構添加到執行器,使得定義的靜態圖能夠真正運行起來,因為這個過程涉及顯存分配,因此需要提供輸入數據和標簽的維度信息才能執行bind操作,讀者可以在命令行通過“$ watch nvidia-smi”命令查看執行bind前后,顯存的變化情況。
bind操作中還存在一個重要的參數是for_training,這個參數默認是True,表示接下來要進行的是訓練過程,因為我們這里只需要進行網絡的前向計算操作,因此將該參數設置為False。
最后調用Module對象的init_params()方法初始化網絡結構的參數,初始化的方式是可以選擇的,這里采用默認方式,至此,一個可用的網絡結構執行器就初始化完成了。初始化網絡結構執行器的代碼具體如下:
mod.bind(data_shapes=[('data',(8,3,28,28))],
?????????label_shapes=[('softmax_label',(8,))],
?????????for_training=False)
mod.init_params()
接下來隨機初始化一個4維的輸入數據,該數據的維度需要與初始化Module對象時設定的數據維度相同,然后通過mxnet.io.DataBatch()接口封裝成一個批次數據,之后就可以作為Module對象的forward()方法的輸入了,執行完前向計算后,調用Module對象的get_outputs()方法就能得到模型的輸出結果,具體代碼如下:
mod.forward(mx.io.DataBatch([data]))
print(mod.get_outputs()[0])
輸出結果如下,因為輸入數據的批次大小是8,網絡的全連接層輸出節點數是2,因此輸出的維度是8*2:
?[?0.50148612??0.49851385]
?[?0.50103837??0.4989616?]
?[?0.50171131??0.49828872]
?[?0.50254387??0.4974561?]
?[?0.50104254??0.49895743]
?[?0.50223148??0.49776852]
?[?0.49780959??0.50219035]]
<NDArray?8x2?@gpu(0)>
接下來介紹如何通過Module模塊執行模型的訓練操作,代碼部分與預測操作有較多地方是相似的,具體代碼見下文代碼清單3-1,接下來詳細介紹代碼內容。
本文中的代碼清單都可以在本書的項目代碼地址中找到:
https://github.com/miraclewkf/MXNet-Deep-Learning-in-Action
使用mxnet.io.NDArrayIter()接口初始化得到訓練和驗證數據迭代器,這里為了演示采用隨機初始化的數據,實際應用中要讀取有效的數據,不論讀取的是什么樣的數據,最后都需要封裝成數據迭代器才能提供給模型訓練。
用mxnet.module.Module()接口初始化得到一個Module對象,這一步至少要輸入一個Symbol對象,另外這一步還可以指定訓練環境是CPU還是GPU,這里采用GPU。
調用Module對象的bind()方法將準備好的數據和網絡結構連接到執行器構成一個完整的計算圖。
調用Module對象的init_params()方法初始化網絡的參數,因為前面定義的網絡結構只是一個架子,里面沒有參數,因此需要執行參數初始化。
調用Module對象的init_optimizer()方法初始化優化器,默認采用隨機梯度下降法(stochastic gradient descent,SGD)進行優化。
調用mxnet.metric.create()接口創建評價函數,這里采用的是準確率(accuracy)。
執行5次循環訓練,每次循環都會將所有數據過一遍模型,因此在循環開始處需要執行評價函數的重置操作、數據的初始讀取等操作。
此處的while循環只有在讀取完訓練數據之后才會退出,該循環首先會調用Module對象的forward()方法執行模型的前向計算,這一步就是輸入數據通過每一個網絡層的參數進行計算并得到最后結果。
調用Module對象的backward()方法執行模型的反向傳播計算,這一步將涉及損失函數的計算和梯度的回傳。
調用Module對象的update()方法執行參數更新操作,參數更新的依據就是第9步計算得到的梯度,這樣就完成了一個批次(batch)數據對網絡參數的更新。
調用Module對象的update_metric()方法更新評價函數的計算結果。
讀取下一個批次的數據,這里采用了Python中的try和except語句,表示如果try包含的語句執行出錯,則執行except包含的語句,這里用來標識是否讀取到了數據集的最后一個批次。
調用評價對象的get_name_value()方法并打印此次計算的結果。
調用Module對象的get_params()方法讀取網絡參數,并利用這些參數初始化Module對象了。
調用數據對象的reset()方法進行重置,這樣在下一次循環中就可以從數據的最初始位置開始讀取了。
import?logging
data?=?mx.sym.Variable('data')
conv?=?mx.sym.Convolution(data=data,?num_filter=128,?kernel=(3,3),?pad=(1,1),
??????????????????????????name='conv1')
bn?=?mx.sym.BatchNorm(data=conv,?name='bn1')
relu?=?mx.sym.Activation(data=bn,?act_type='relu',?name='relu1')
pool?=?mx.sym.Pooling(data=relu,?kernel=(2,2),?stride=(2,2),?pool_type='max',
??????????????????????name='pool1')
fc?=?mx.sym.FullyConnected(data=pool,?num_hidden=2,?name='fc1')
sym?=?mx.sym.SoftmaxOutput(data=fc,?name='softmax')
data?=?mx.nd.random.uniform(0,1,shape=(1000,3,224,224))
label?=?mx.nd.round(mx.nd.random.uniform(0,1,shape=(1000)))
train_data?=?mx.io.NDArrayIter(data={'data':data},
???????????????????????????????label={'softmax_label':label},
???????????????????????????????batch_size=8,
???????????????????????????????shuffle=True)
print(train_data.provide_data)
print(train_data.provide_label)
mod?=?mx.mod.Module(symbol=sym,context=mx.gpu(0))
mod.bind(data_shapes=train_data.provide_data,
?????????label_shapes=train_data.provide_label)
mod.init_params()
mod.init_optimizer()
eval_metric?=?mx.metric.create('acc')
for?epoch?in?range(5):
????end_of_batch?=?False
????eval_metric.reset()
????data_iter?=?iter(train_data)
????next_data_batch?=?next(data_iter)
????while?not?end_of_batch:
????????data_batch?=?next_data_batch
????????mod.forward(data_batch)
????????mod.backward()
????????mod.update()
????????mod.update_metric(eval_metric,?labels=data_batch.label)
????????try:
????????????next_data_batch?=?next(data_iter)
????????????mod.prepare(next_data_batch)
????????except?StopIteration:
????????????end_of_batch?=?True
????eval_name_vals?=?eval_metric.get_name_value()
????print("Epoch:{}?Train_Acc:{:.4f}".format(epoch,?eval_name_vals[0][1]))
????arg_params,?aux_params?=?mod.get_params()
????mod.set_params(arg_params,?aux_params)
????train_data.reset()
代碼清單3-1中的代碼其實從mod.bind()方法這一行到最后都可以用Module模塊中的fit()方法來實現。fit()方法不僅封裝了上述的bind操作、參數初始化、優化器初始化、模型的前向計算、反向傳播、參數更新和計算評價指標等操作,還提供了保存訓練結果等其他操作,因此fit()方法將是今后使用MXNet訓練模型時經常調用的方法。
下面這段代碼就演示了fit()方法的調用,前面兩行設置命令行打印訓練信息,這三行代碼可以直接替換代碼清單3-1中從mod.bind()那一行到最后的所有代碼。
在fit()方法的輸入參數中,train_data參數是訓練數據,num_epoch參數是訓練時整個訓練集的迭代次數(也稱epoch數量)。需要注意的是,將所有train_data過一遍模型才算完成一個epoch,因此這里設定為將這個訓練集數據過5次模型才完成訓練。
mod.fit(train_data=train_data,?num_epoch=5)
簡化版的代碼如代碼清單3-2所示。
import?logging
data?=?mx.sym.Variable('data')
conv?=?mx.sym.Convolution(data=data,?num_filter=128,?kernel=(3,3),?pad=(1,1),
??????????????????????????name='conv1')
bn?=?mx.sym.BatchNorm(data=conv,?name='bn1')
relu?=?mx.sym.Activation(data=bn,?act_type='relu',?name='relu1')
pool?=?mx.sym.Pooling(data=relu,?kernel=(2,2),?stride=(2,2),?pool_type='max',
??????????????????????name='pool1')
fc?=?mx.sym.FullyConnected(data=pool,?num_hidden=2,?name='fc1')
sym?=?mx.sym.SoftmaxOutput(data=fc,?name='softmax')
data?=?mx.nd.random.uniform(0,1,shape=(1000,3,224,224))
label?=?mx.nd.round(mx.nd.random.uniform(0,1,shape=(1000)))
train_data?=?mx.io.NDArrayIter(data={'data':data},
???????????????????????????????label={'softmax_label':label},
???????????????????????????????batch_size=8,
???????????????????????????????shuffle=True)
print(train_data.provide_data)
print(train_data.provide_label)
mod?=?mx.mod.Module(symbol=sym,context=mx.gpu(0))
logger?=?logging.getLogger()
logger.setLevel(logging.INFO)
mod.fit(train_data=train_data,?num_epoch=5)
從下面打印出來的訓練結果可以看到,輸出結果與代碼清單3-1的輸出結果基本吻合:
INFO:root:Epoch[0]?Time?cost=4.618
INFO:root:Epoch[1]?Train-accuracy=0.700000
INFO:root:Epoch[1]?Time?cost=4.425
INFO:root:Epoch[2]?Train-accuracy=0.969000
INFO:root:Epoch[2]?Time?cost=4.428
INFO:root:Epoch[3]?Train-accuracy=0.988000
INFO:root:Epoch[3]?Time?cost=4.410
INFO:root:Epoch[4]?Train-accuracy=0.999000
INFO:root:Epoch[4]?Time?cost=4.425
上面的演示代碼中只設定了fit()方法的幾個輸入,其實fit()方法的輸入還有很多,實際使用中可根據具體要求設定不同的輸入參數,本書后面的章節還會進行詳細介紹。
得益于MXNet的靜態圖設計和對計算過程的優化,你會發現MXNet的訓練速度相較于大部分深度學習框架要快,而且顯存占用非常少!這使得你能夠在單卡或單機多卡上使用更大的batch size訓練相同的模型,這對于復雜模型的訓練非常有利,有時候甚至還會影響訓練結果。
04 小結
本文主要介紹了MXNet框架中最常用到的三個模塊:NDArray、Symbol和Module,對比了三者之間的聯系并通過簡單的代碼對這三個模塊的使用有了大致的認識。
NDArray是MXNet框架中最基礎的數據結構,借鑒了NumPy中array的思想且能在GPU上運行,同時采取命令式編程的NDArray在代碼調試上非常靈活。NDArray提供了與NumPy array相似的方法及屬性,因此熟悉NumPy array的用戶應該能夠很快上手NDArray的操作,而且二者之間的轉換也非常方便。
Symbol是MXNet框架中定義網絡結構層的接口,采取符號式編程的Symbol通過構建靜態計算圖可以大大提高模型訓練的效率。Symbol中提供了多種方法用于查看Symbol對象的信息,包括參數層、參數維度等,同時也便于用戶在設計網絡結構的過程中查漏補缺。
此外,Symbol中的大部分網絡層接口在NDArray中都有對應的實現,因此可以通過NDArray中對應名稱的網絡層查看具體的計算過程。
Module是MXNet框架中封裝了訓練模型所需的大部分操作的高級接口,用戶可以通過Module模塊執行bind操作、參數初始化、優化器初始化、模型的前向計算、損失函數的反向傳播、網絡參數更新、評價指標計算等,同時,Module模塊還將常用的訓練操作封裝在了fit()方法中,通過該方法,用戶可以更加方便地訓練模型,可以說是既靈活又簡便。
關于作者:魏凱峰,資深AI算法工程師和計算機視覺工程師,在MXNet、Pytorch、深度學習相關算法等方面有深入的研究和豐富的實踐經驗,從事計算機視覺算法相關的工作,主要研究方向包括目標檢測、圖像分類、圖像對抗算法、模型加速和壓縮。
本文摘編自《MXNet深度學習實戰:計算機視覺算法實現》,經出版方授權發布。
延伸閱讀《MXNet深度學習實戰》
點擊上圖了解及購買
轉載請聯系微信:DoctorData
推薦語:網易資深計算機視覺算法工程師撰寫,從算法實現和框架原理2個維度詳細講解計算機視覺算法的實現和MXNet框架的使用與原理。
據統計,99%的大咖都完成了這個神操作
▼
更多精彩
在公眾號后臺對話框輸入以下關鍵詞
查看更多優質內容!
PPT?|?報告?|?讀書?|?書單?|?干貨?
大數據?|?揭秘?|?Python?|?可視化
AI?|?人工智能?|?5G?|?區塊鏈
機器學習?|?深度學習?|?神經網絡
1024?|?段子?|?數學?|?高考
猜你想看
手把手教你用OpenCV實現機器學習最簡單的k-NN算法(附代碼)
41款實用工具,數據獲取、清洗、建模、可視化都有了
你是怎樣“被平均”的?細數統計數據中的那些坑
高能!8段代碼演示Numpy數據運算的神操作
Q:?你在用哪些深度學習框架?
歡迎留言與大家分享
覺得不錯,請把這篇文章分享給你的朋友
轉載 / 投稿請聯系:baiyu@hzbook.com
更多精彩,請在后臺點擊“歷史文章”查看
點擊閱讀原文,了解更多
總結
以上是生活随笔為你收集整理的深度学习高能干货:手把手教你搭建MXNet框架的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微软著名程序员、歌手、NBA球队老板保罗
- 下一篇: 手把手教你使用Pandas读取结构化数据