【TensorFlow】01 TensorFlow簡介與Python基礎
898
2025-04-01
3.2 Symbol
Symbol是MXNet框架中用于構建網絡層的模塊,Symbol的官方文檔地址是:https://mxnet.apache.org/api/python/symbol/symbol.html,與Symbol相關的接口都可以在該文檔中查詢。與NDArray不同的是,Symbol采用的是符號式編程(symbolic programming),其是MXNet框架實現快速訓練和節省顯存的關鍵模塊。之前我們介紹過符號式編程的含義,簡單來說就是,符號式編程需要先用Symbol接口定義好計算圖,這個計算圖同時包含定義好的輸入和輸出格式,然后將準備好的數據輸入該計算圖完成計算。而3.1節介紹的NDArray采用的是命令式編程(imperative programming),計算過程可以逐步來步實現。其實在你了解了NDArray之后,你完全可以僅僅通過NDArray來定義和使用網絡,那么為什么還要提供Symbol呢?主要是為了提高效率。在定義好計算圖之后,就可以對整個計算圖的顯存占用做優化處理,這樣就能大大降低訓練模型時候的顯存占用。
在MXNet中,Symbol接口主要用來構建網絡結構層,其次是用來定義輸入數據。接下來我們再來列舉一個例子,首先定義一個網絡結構,具體如下。
1)用mxnet.symbol.Variable()接口定義輸入數據,用該接口定義的輸入數據類似于一個占位符。
2)用mxnet.symbol.Convolution()接口定義一個卷積核尺寸為3*3,卷積核數量為128的卷積層,卷積層是深度學習算法提取特征的主要網絡層,該層將是你在深度學習算法(尤其是圖像領域)中使用最為頻繁的網絡層。
3)用 mxnet.symbol.BatchNorm()接口定義一個批標準化(batch normalization,常用縮寫BN表示)層,該層有助于訓練算法收斂。
4)用mxnet.symbol.Activation()接口定義一個ReLU激活層,激活層主要用來增加網絡層之間的非線性,激活層包含多種類型,其中以ReLU激活層最為常用。
5)用mxnet.symbol.Pooling()接口定義一個最大池化層(pooling),池化層的主要作用在于通過縮減維度去除特征圖噪聲和減少后續計算量,池化層包含多種形式,常用形式有均值池化和最大池化。
6)用mxnet.symbol.FullyConnected()接口定義一個全連接層,全連接層是深度學習算法中經常用到的層,一般是位于網絡的最后幾層。需要注意的是,該接口的num_hidden參數表示分類的類別數。
7)用mxnet.symbol.SoftmaxOutput()接口定義一個損失函數層,該接口定義的損失函數是圖像分類算法中常用的交叉熵損失函數(cross entropy loss),該損失函數的輸入是通過softmax函數得到的,softmax函數是一個變換函數,表示將一個向量變換成另一個維度相同,但是每個元素范圍在[0,1]之間的向量,因此該層用mxnet.symbol.SoftmaxOutput()來命名。這樣就得到了一個完整的網絡結構了。
網絡結構定義代碼如下:
import mxnet as mx
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對象的參數,命令如下:
print(sym.list_arguments())
由下面的輸出結果可以看出,第一個和最后一個分別是'data'和'softmax_label',這二者分別代表輸入數據和標簽;'conv1_weight'和'conv1_bias'是卷積層的參數,具體而言前者是卷積核的權重參數,后者是偏置參數;'bn1_gamma'和'bn1_beta'是BN層的參數;'fc1_weight'和'fc1_bias'是全連接層的參數。
['data', 'conv1_weight', 'conv1_bias', 'bn1_gamma', 'bn1_beta', 'fc1_weight', 'fc1_bias', 'softmax_label']
除了查看網絡的參數層名稱之外,有時候我們還需要查看網絡層參數的維度、網絡輸出維度等信息,這一點對于代碼調試而言尤其有幫助。在MXNet中,可以用infer_shape()方法查看一個Symbol對象的層參數維度、輸出維度、輔助層參數維度信息,在調用該方法時需要指定輸入數據的維度,這樣網絡結構就會基于指定的輸入維度計算層參數、網絡輸出等維度信息:
arg_shape,out_shape,aux_shape = sym.infer_shape(data=(1,3,10,10))
print(arg_shape)
print(out_shape)
print(aux_shape)
由下面的輸出結果可知,第一行表示網絡層參數的維度,與前面list_arguments()方法列出來的層參數名一一對應,例如輸入數據'data'的維度是(1, 3, 10, 10);卷積層的權重參數'conv1_weight'的維度是(128, 3, 3, 3);卷積層的偏置參數'conv1_bias'的維度是(128,),因為每個卷積核對應于一個偏置參數;全連接層的權重參數'fc1_weight'的維度是(2, 3200),這里的3000是通過計算5*5*128得到的,其中5*5表示全連接層的輸入特征圖的寬和高。第二行表示網絡輸出的維度,因為網絡的最后一層是輸出節點為2的全連接層,且輸入數據的批次維度是1,所以輸出維度是[(1, 2)]。第三行是輔助參數的維度,目前常見的主要是BN層的參數維度。
[(1, 3, 10, 10), (128, 3, 3, 3), (128,), (128,), (128,), (2, 3200), (2,), (1,)]
[(1, 2)]
[(128,), (128,)]
如果要截取通過Symbol模塊定義的網絡結構中的某一部分也非常方便,在MXNet中可以通過get_internals()方法得到Symbol對象的所有層信息,然后選擇要截取的層即可,比如將sym截取成從輸入到池化層為止:
sym_mini = sym.get_internals()['pool1_output']
print(sym_mini.list_arguments())
輸出結果如下,可以看到層參數中沒有sym原有的全連接層和標簽層信息了:
['data', 'conv1_weight', 'conv1_bias', 'bn1_gamma', 'bn1_beta']
截取之后還可以在截取得到的Symbol對象后繼續添加網絡層,比如增加一個輸出節點為5的全連接層和一個softmax層:
fc_new = mx.sym.FullyConnected (data=sym_mini, num_hidden=5, name='fc_new')
sym_new = mx.sym.SoftmaxOutput (data=fc_new, name='softmax')
print(sym_new.list_arguments())
輸出結果如下,可以看到全連接層已經被替換了:
['data', 'conv1_weight', 'conv1_bias', 'bn1_gamma', 'bn1_beta', 'fc_new_weight', 'fc_new_bias', 'softmax_label']
除了定義神經網絡層之外,Symbol模塊還可以實現NDArray的大部分操作,接下來以數組相加和相乘為例介紹通過Symbol模塊實現上述操作的方法。首先通過 mxnet.symbol.Variable()接口定義兩個輸入data_a和data_b;然后定義data_a和data_b相加并與data_c相乘的操作以得到結果s,通過打印s的類型可以看出s的類型是Symbol,代碼如下:
import mxnet as mx
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的類型進行查看,代碼如下:
e = s.bind(mx.cpu(), {'data_a':mx.nd.array([1,2,3]), 'data_b':mx.nd.array([4,5,6]),
'data_c':mx.nd.array([2,3,4])})
print(type(e))
輸出結果如下:
這個執行器就是一個完整的計算圖了,因此可以調用執行器的forward()方法進行計算以得到結果:
output=e.forward()
print(output[0])
輸出結果如下:
[ 10. 21. 36.]
相比之下,通過NDArray模塊實現這些操作則要簡潔和直觀得多,代碼如下:
import mxnet as mx
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)
輸出結果如下:
[ 10. 21. 36.]
雖然使用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維的。具體代碼如下:
data = mx.nd.arange(0,28).reshape((1,1,4,7))
print(data)
輸出結果如下:
[[[[ 0.? 1.? 2.? 3.? 4.? 5.? 6.]
[ 7.? 8.? 9. 10. 11. 12. 13.]
[14. 15. 16. 17. 18. 19. 20.]
[21. 22. 23. 24. 25. 26. 27.]]]]
然后,通過mxnet.ndarray.Convolution()接口定義卷積層操作,該接口的輸入除了與mxnet.symbol.Convolution()接口相同的data、num_filter、kernel和name之外,還需要直接指定weight和bias。weight和bias就是卷積層的參數值,為了簡單起見,這里將weight初始化成值全為1的4維變量,bias初始化成值全為0的1維變量,這樣就能得到最后的卷積結果。具體代碼如下:
conv1 = mx.nd.Convolution(data=data, weight=mx.nd.ones((10,1,3,3)),
bias=mx.nd.zeros((10)), num_filter=10, kernel=(3,3),
name='conv1')
print(conv1)
輸出結果如下:
[[[[ 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.]]
[[ 72.? 81.? 90.? 99. 108.]
[135. 144. 153. 162. 171.]]]]
總體來看,Symbol和NDArray有很多相似的地方,同時,二者在MXNet中都扮演著重要的角色。采用命令式編程的NDArray其特點是直觀,常用來實現底層的計算;采用符號式編程的Symbol其特點是高效,主要用來定義計算圖。
深度學習 神經網絡
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。