MXNet中含有init
包,它包含了多種模型初始化方法。
from mxnet import init, nd from mxnet.gluon import nn net = nn.Sequential() net.add(nn.Dense(256, activation='relu')) net.add(nn.Dense(10)) net.initialize() x = nd.random.uniform(shape=(2,20)) y = net(x)
一、訪問模型參數
我們知道可以通過[]
來訪問Sequential類構造出來的網絡的特定層。對於帶有模型參數的層,我們可以通過Block類的params
屬性來得到它包含的所有參數。例如我們查看隱藏層的參數:
print(net[0].params) print(net[0].collect_params())
dense0_ ( Parameter dense0_weight (shape=(256, 20), dtype=float32) Parameter dense0_bias (shape=(256,), dtype=float32) ) dense0_ ( Parameter dense0_weight (shape=(256, 20), dtype=float32) Parameter dense0_bias (shape=(256,), dtype=float32) )
有意思的是,
print(net.params) print(net.collect_params())
sequential0_ ( ) sequential0_ ( Parameter dense0_weight (shape=(256, 20), dtype=float32) Parameter dense0_bias (shape=(256,), dtype=float32) Parameter dense1_weight (shape=(10, 256), dtype=float32) Parameter dense1_bias (shape=(10,), dtype=float32) )
為了訪問特定參數,我們既可以通過名字來訪問字典里的元素,也可以直接使用它的變量名。下面兩種方法是等價的,但通常后者的代碼可讀性更好,
(net[0].params['dense0_weight'], net[0].weight)
(Parameter dense0_weight (shape=(256, 20), dtype=float32),
Parameter dense0_weight (shape=(256, 20), dtype=float32))
Parameter類
data
和
grad
函數來訪問:
net[0].weight.data() net[0].weight.grad()
聲明方法一
至於Parameter類,是指mxnet.gluon.Parameter,聲明需要名字和尺寸:
my_param = gluon.Parameter('exciting_parameter_yay', shape=(3, 3)) my_param.initialize()
聲明方法二
我們還可以使用Block自帶的ParameterDict類的成員變量params
。顧名思義,這是一個由字符串類型的參數名字映射到Parameter類型的模型參數的字典。
我們可以通過get
函數從ParameterDict
創建Parameter,
同樣的聲明需要名字和尺寸:
params = gluon.ParameterDict() params.get("param2", shape=(2, 3)) params
( Parameter param2 (shape=(2, 3), dtype=<class 'numpy.float32'>) )
形如net.params返回的就是一個ParameterDict類,自己的層class書寫時,就是用這個方式創建參數並被層class感知(收錄進層隸屬的ParameterDict類中)。
二、初始化模型參數
[-0.07, 0.07]
之間均勻分布的隨機數,偏差參數則全為0. 但經常我們需要使用其他的方法來初始話權重,MXNet的
init
模塊里提供了多種預設的初始化方法。例如下面例子我們將權重參數初始化成均值為0,標准差為0.01的正態分布隨機數。
# 非首次對模型初始化需要指定 force_reinit net.initialize(init=init.Normal(sigma=0.01), force_reinit=True)
- mxnet.init模塊
- force_reinit為了防止用戶失誤將參數全部取消
自定義初始化函數
有時候我們需要的初始化方法並沒有在init
模塊中提供,這時我們有兩種方法來自定義參數初始化。
一種是實現一個Initializer類的子類
使得我們可以跟前面使用init.Normal
那樣使用它。在這個方法里,我們只需要實現_init_weight
這個函數,將其傳入的NDArray修改成需要的內容。下面例子里我們把權重初始化成[-10,-5]
和[5,10]
兩個區間里均勻分布的隨機數,教程只講了weight初始化,后面查看源碼還有bias的,以及一些其他的子方法,理論上用時再研究,不過方法二明顯更簡單易用……
class MyInit(init.Initializer): def _init_weight(self, name, data): print('Init', name, data.shape) data[:] = nd.random.uniform(low=-10, high=10, shape=data.shape) data *= data.abs() >= 5 net.initialize(MyInit(), force_reinit=True) net[0].weight.data()[0]
第二種方法是我們通過Parameter類的set_data
函數
可以直接改寫模型參數。例如下例中我們將隱藏層參數在現有的基礎上加1.
net[0].weight.set_data(net[0].weight.data()+1) net[0].weight.data()[0]
[ -4.36596727 8.57739449 9.98637581 1. 9.8275547 1. 6.98405075 1. 1. 1. 8.48575974 1. 1. 7.89100075 7.97887039 -5.11315536 1. 6.46652031 -8.73526287 10.48517227] <NDArray 20 @cpu(0)>
三、共享模型參數
在有些情況下,我們希望在多個層之間共享模型參數。我們在“模型構造”這一節看到了如何在Block類里forward
函數里多次調用同一個類來完成。這里將介紹另外一個方法,它在構造層的時候指定使用特定的參數。如果不同層使用同一份參數,那么它們不管是在前向計算還是反向傳播時都會共享共同的參數。
原理:層函數params API接收其他層函數的params屬性即可
在下面例子里,我們讓模型的第二隱藏層和第三隱藏層共享模型參數:
from mxnet import nd from mxnet.gluon import nn net = nn.Sequential() shared = nn.Dense(8, activation='relu') net.add(nn.Dense(8, activation='relu'), shared, nn.Dense(8, activation='relu', params=shared.params), nn.Dense(10)) net.initialize() x = nd.random.uniform(shape=(2,20)) net(x) net[1].weight.data()[0] == net[2].weight.data()[0]
[ 1. 1. 1. 1. 1. 1. 1. 1.]
<NDArray 8 @cpu(0)>
我們在構造第三隱藏層時通過params
來指定它使用第二隱藏層的參數。由於模型參數里包含了梯度,所以在反向傳播計算時,第二隱藏層和第三隱藏層的梯度都會被累加在shared.params.grad()
里。
四、模型延后初始化
從下面jupyter可以看到模型參數實際初始化的時機:既不是調用initialize時,也不是沒次運行時,僅僅第一次送入數據運行時會調用函數進行參數初始化,所以MXNet不需要定義指定輸入數據尺寸的關鍵也在這里。
立即初始化參數
基於以上,當模型獲悉數據尺寸時不會發生延后,
- 已經運行過,指定重新初始化時不會延后
- 定義過程中,指定in_units的模型不會延后
代碼如下,
最后一格代碼塊中,第二個dense層去掉in_uints的話僅僅第一個dense會立即初始化。
數據形狀改變時網絡行為
如果在下一次net(x)
前改變x
形狀,包括批量大小和特征大小,會發生什么?
批量大小改變不影響,特征大小改變會報錯。