MXNet深度學(xué)習(xí)實(shí)戰(zhàn)》">《MXNet深度學(xué)習(xí)實(shí)戰(zhàn)》
948
2025-04-04
CHAPTER 3
第3章
MXNet基礎(chǔ)
相信很多程序員在學(xué)習(xí)一門新的編程語言或者框架時,都會先了解下該語言或者該框架涉及的數(shù)據(jù)結(jié)構(gòu),畢竟當(dāng)你清晰地了解了數(shù)據(jù)結(jié)構(gòu)之后才能更加優(yōu)雅地編寫代碼,MXNet同樣也是如此。在MXNet框架中你至少需要了解這三駕馬車:NDArray、Symbol和Module。這三者將會是你今后在使用MXNet框架時經(jīng)常用到的接口。那么在搭建或者訓(xùn)練一個深度學(xué)習(xí)算法時,這三者到底扮演了一個什么樣的角色呢?這里可以做一個簡單的比喻,假如將從搭建到訓(xùn)練一個算法的過程比作是一棟房子從建造到裝修的過程,那么NDArray就相當(dāng)于是鋼筋水泥這樣的零部件,Symbol就相當(dāng)于是房子每一層的設(shè)計,Module就相當(dāng)于是房子整體框架的搭建。
還記得我們在引入深度學(xué)習(xí)框架時提到的命令式編程(imperative programming)和符號式編程(symbolic programming)嗎?在本章中你將實(shí)際感受二者的區(qū)別,因?yàn)镹DArray接口采用的是命令式編程的方式,而Symbol接口采用的是符號式編程的方式。
3.1 NDArray
NDArray是MXNet框架中數(shù)據(jù)流的基礎(chǔ)結(jié)構(gòu),NDArray的官方文檔地址是:https://mxnet.apache.org/api/python/ndarray/ndarray.html,與NDArray相關(guān)的接口都可以在該文檔中查詢到。在了解NDArray之前,希望你先了解下Python中的NumPy庫(http://www.numpy.org/),因?yàn)橐环矫嬖诖蟛糠稚疃葘W(xué)習(xí)框架的Python接口中,NumPy庫的使用頻率都非常高;另一方面大部分深度學(xué)習(xí)框架的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)設(shè)計都借鑒了NumPy。在NumPy庫中,一個最基本的數(shù)據(jù)結(jié)構(gòu)是array,array表示多維數(shù)組,NDArray與NumPy庫中的array數(shù)據(jù)結(jié)構(gòu)的用法非常相似,可以簡單地認(rèn)為NDArray是可以運(yùn)行在GPU上的NumPy array。
接下來,我會介紹在NDArray中的一些常用操作,并提供其與NumPy array的對比,方便讀者了解二者之間的關(guān)系。
首先,導(dǎo)入MXNet和NumPy,然后通過NDArray初始化一個二維矩陣,代碼如下:
import mxnet as mx
import numpy as np
a = mx.nd.array([[1,2],[3,4]])
print(a)
輸出結(jié)果如下:
[[1. 2.]
[3. 4.]]
接著,通過NumPy array初始化一個相同的二維矩陣,代碼如下:
b = np.array([[1,2],[3,4]])
print(b)
輸出結(jié)果如下:
[[1 2]
[3 4]]
實(shí)際使用中常用縮寫mx代替mxnet,mx.nd代替mxnet.ndarray,np代替numpy,本書后續(xù)篇章所涉及的代碼默認(rèn)都采取這樣的縮寫。
再來看看NumPy array和NDArray常用的幾個方法對比,比如打印NDArray的維度信息:
print(a.shape)
輸出結(jié)果如下:
(2, 2)
打印NumPy array的維度信息:
print(b.shape)
輸出結(jié)果如下:
(2, 2)
打印NDArray的數(shù)值類型:
print(a.dtype)
輸出結(jié)果如下:
打印Numpy array的數(shù)值類型:
print(b.dtype)
輸出結(jié)果如下:
int64
在使用大部分深度學(xué)習(xí)框架訓(xùn)練模型時默認(rèn)采用的都是float32數(shù)值類型,因此初始化一個NDArray對象時默認(rèn)的數(shù)值類型是float32。
如果你想要初始化指定數(shù)值類型的NDArray,那么可以通過dtype參數(shù)來指定,代碼如下:
c=mx.nd.array([[1,2],[3,4]], dtype=np.int8)
print(c.dtype)
輸出結(jié)果如下:
如果你想要初始化指定數(shù)值類型的NumPy array,則可以像如下這樣輸入代碼:
d = np.array([[1,2],[3,4]], dtype=np.int8)
print(d.dtype)
輸出結(jié)果如下:
int8
在NumPy的array結(jié)構(gòu)中有一個非常常用的操作是切片(slice),這種操作在NDArray中同樣也可以實(shí)現(xiàn),具體代碼如下:
c = mx.nd.array([[1,2,3,4],[5,6,7,8]])
print(c[0,1:3])
輸出結(jié)果如下:
[2. 3.]
在NumPy array中可以這樣實(shí)現(xiàn):
d = np.array([[1,2,3,4],[5,6,7,8]])
print(d[0,1:3])
輸出結(jié)果如下:
[2 3]
在對已有的NumPy array或NDArray進(jìn)行復(fù)制并修改時,為了避免影響到原有的數(shù)組,可以采用copy()方法進(jìn)行數(shù)組復(fù)制,而不是直接復(fù)制,這一點(diǎn)非常重要。下面以NDArray為例來看看采用copy()方法進(jìn)行數(shù)組復(fù)制的情況,首先打印出c的內(nèi)容:
print(c)
輸出結(jié)果如下:
[[1. 2. 3. 4.]
[5. 6. 7. 8.]]
然后調(diào)用c的copy()方法將c的內(nèi)容復(fù)制到f,并打印f的內(nèi)容:
f = c.copy()
print(f)
輸出結(jié)果如下:
[[1. 2. 3. 4.]
[5. 6. 7. 8.]]
修改f中的一個值,并打印f的內(nèi)容:
f[0,0] = -1
print(f)
輸出結(jié)果如下,可以看到此時對應(yīng)位置的值已經(jīng)被修改了:
[[-1. 2. 3. 4.]
[ 5. 6. 7. 8.]]
那么c中對應(yīng)位置的值有沒有被修改呢?可以打印此時c的內(nèi)容:
print(c)
輸出結(jié)果如下,可以看到此時c中對應(yīng)位置的值并沒有被修改:
[[1. 2. 3. 4.]
[5. 6. 7. 8.]]
接下來看看如果直接將c復(fù)制給e,會有什么樣的情況發(fā)生:
e = c
print(e)
輸出結(jié)果如下:
[[1. 2. 3. 4.]
[5. 6. 7. 8.]]
修改e中的一個值,并打印e的內(nèi)容:
e[0,0] = -1
print(e)
輸出內(nèi)容如下:
[[-1. 2. 3. 4.]
[ 5. 6. 7. 8.]]
此時再打印c的內(nèi)容:
print(c)
輸出結(jié)果如下,可以看到對應(yīng)位置的值也發(fā)生了改變:
[[-1. 2. 3. 4.]
[ 5. 6. 7. 8.]]
實(shí)際上,NumPy array和NDArray之間的轉(zhuǎn)換也非常方便,NDArray轉(zhuǎn)NumPy array可以通過調(diào)用NDArray對象的asnumpy()方法來實(shí)現(xiàn):
g=e.asnumpy()
print(g)
輸出結(jié)果如下:
[[-1. 2. 3. 4.]
[ 5. 6. 7. 8.]]
NumPy array轉(zhuǎn)NDArray可以通過mxnet.ndarray.array()接口來實(shí)現(xiàn):
print(mx.nd.array(g))
輸出結(jié)果如下:
[[-1. 2. 3. 4.]
[ 5. 6. 7. 8.]]
前面曾提到過NDArray和NumPy array最大的區(qū)別在于NDArray可以運(yùn)行在GPU上,從前面打印出來的NDArray對象的內(nèi)容可以看到,最后都有一個@cpu,這說明該NDArray對象是初始化在CPU上的,那么如何才能將NDArray對象初始化在GPU上呢?首先,調(diào)用NDArray對象的context屬性可以得到變量所在的環(huán)境:
print(e.context)
輸出結(jié)果如下:
cpu(0)
然后,調(diào)用NDArray對象的as_in_context()方法指定變量的環(huán)境,例如這里將環(huán)境指定為第0塊GPU:
e = e.as_in_context(mx.gpu(0))
print(e.context)
輸出結(jié)果如下:
gpu(0)
環(huán)境(context)是深度學(xué)習(xí)算法中比較重要的內(nèi)容,目前常用的環(huán)境是CPU或GPU,在深度學(xué)習(xí)算法中,數(shù)據(jù)和模型都要在同一個環(huán)境中才能正常進(jìn)行訓(xùn)練和測試。MXNet框架中NDArray對象的默認(rèn)初始化環(huán)境是CPU,在不同的環(huán)境中,變量初始化其實(shí)就是變量的存儲位置不同,而且存儲在不同環(huán)境中的變量是不能進(jìn)行計算的,比如一個初始化在CPU中的NDArray對象和一個初始化在GPU中的NDArray對象在執(zhí)行計算時會報錯:
f = mx.nd.array([[2,3,4,5],[6,7,8,9]])
print(e+f)
顯示結(jié)果如下,從報錯信息可以看出是2個對象的初始化環(huán)境不一致導(dǎo)致的:
mxnet.base.MXNetError: [11:14:13] src/imperative/./imperative_utils.h:56: Check failed: inputs[i]->ctx().dev_mask() == ctx.dev_mask() (1 vs. 2) Operator broadcast_add require all inputs live on the same context. But the first argument is on gpu(0) while the 2-th argument is on cpu(0)
下面將f的環(huán)境也修改成GPU,再執(zhí)行相加計算:
f = f.as_in_context(mx.gpu(0))
print(e+f)
輸出結(jié)果如下:
[[? 1.?? 5.?? 7.?? 9.]
[ 11.? 13.? 15.? 17.]]
NDArray是MXNet框架中使用最頻繁也是最基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),是可以在CPU或GPU上執(zhí)行命令式操作(imperative operation)的多維矩陣,這種命令式操作直觀且靈活,是MXNet框架的特色之一。因?yàn)樵谑褂肕XNet框架訓(xùn)練模型時,幾乎所有的數(shù)據(jù)流都是通過NDArray數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的,因此熟悉該數(shù)據(jù)結(jié)構(gòu)非常重要。
TensorFlow 深度學(xué)習(xí)
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。