1. 程式人生 > >MXNet官方文件教程(6):神經網路圖

MXNet官方文件教程(6):神經網路圖

符號教程

除了張量計算介面NDArrayMXNet中另一個主要的物件就是Symbol,位於mxnet.symbol(縮寫mxnet.sym)中。一個符號代表一個多輸出的符號表達式。他們由運算子複合而成,例如簡單的矩陣運算(如“+”),或者一個神經網路層(如卷積層)。一個操作符可以獲得多個輸入變數,並提供至少一個輸出變數,並擁有內部變數。一個變數可以是空的(我們可以在之後為其賦值),也可以是一個其他符號的輸出。

符號組成

基本操作符

以下的例子複合了一個簡單的表示式a+b”。我們首先使用mx.sym.Variable建立佔位符ab及其名稱,然後用操作符“+”構造期望的符號。在新建時如果名字字串沒有給定,

MXNet會自動為符號生產一個獨一無二的名字,如c的例子所示。

import mxnet as mx

a = mx.sym.Variable('a')

b = mx.sym.Variable('b')

c = a+ b

(a, b, c)

輸出

(<Symbol a>, <Symbol b>, <Symbol _plus0>)

大多數NDArray操作符可以被應用於Symbol,例如:

# elemental wise times
d= a * b  
# matrix multiplication
e= mx.sym.dot(a, b)   
# reshape
f= mx.sym.Reshape(d+e, shape=(1,4))
# broadcast
g= mx.sym.broadcast_to(f, shape=(2,4))
mx.viz.plot_network(symbol=g)

基本神經網路

除了基本的操作符,Symbol擁有豐富的神經網路層集。以下程式碼構造了一個兩層的全連線層,然後通過給定輸入資料大小例項化該結構。

# Output may vary
net= mx.sym.Variable('data')
net= mx.sym.FullyConnected(data=net, name='fc1', num_hidden=
128)
net= mx.sym.Activation(data=net, name='relu1', act_type="relu")
net= mx.sym.FullyConnected(data=net, name='fc2', num_hidden=10)
net= mx.sym.SoftmaxOutput(data=net, name='out')
mx.viz.plot_network(net, shape={'data':(100,200)})

 

深度網路的模組化構建

對於深度網路,例如Google Inception,當有大量的層時,一個一個地構建層會十分痛苦。對於這些網路,我們通常模組化其構建。以Google Inception為例,我們首先定義一個製造函式來將卷積層,批標準化層和Relu啟用層捆綁在一起:

# Output may vary
def ConvFactory(data, num_filter, kernel, stride=(1,1), pad=(0,0), name=None, suffix=''):
    conv = mx.symbol.Convolution(data=data, num_filter=num_filter, kernel=kernel, stride=stride, pad=pad, name='conv_%s%s'%(name, suffix))
    bn = mx.symbol.BatchNorm(data=conv, name='bn_%s%s'%(name, suffix))
    act = mx.symbol.Activation(data=bn, act_type='relu', name='relu_%s%s'%(name, suffix))
return act
prev= mx.symbol.Variable(name="Previos Output")
conv_comp= ConvFactory(data=prev, num_filter=64, kernel=(7,7), stride=(2,2))
shape= {"Previos Output" : (128,3,28,28)}
mx.viz.plot_network(symbol=conv_comp, shape=shape)

然後我們定義一個構建基於ConvFactoryInception模型的函式:

# @@@ AUTOTEST_OUTPUT_IGNORED_CELL
def InceptionFactoryA(data, num_1x1, num_3x3red, num_3x3, num_d3x3red, num_d3x3, pool, proj, name):
# 1x1
    c1x1 = ConvFactory(data=data, num_filter=num_1x1, kernel=(1,1), name=('%s_1x1'% name))
# 3x3 reduce + 3x3
    c3x3r = ConvFactory(data=data, num_filter=num_3x3red, kernel=(1,1), name=('%s_3x3'% name), suffix='_reduce')
    c3x3 = ConvFactory(data=c3x3r, num_filter=num_3x3, kernel=(3,3), pad=(1,1), name=('%s_3x3'% name))
# double 3x3 reduce + double 3x3
    cd3x3r = ConvFactory(data=data, num_filter=num_d3x3red, kernel=(1,1), name=('%s_double_3x3'% name), suffix='_reduce')
    cd3x3 = ConvFactory(data=cd3x3r, num_filter=num_d3x3, kernel=(3,3), pad=(1,1), name=('%s_double_3x3_0'% name))
    cd3x3 = ConvFactory(data=cd3x3, num_filter=num_d3x3, kernel=(3,3), pad=(1,1), name=('%s_double_3x3_1'% name))
# pool + proj
    pooling = mx.symbol.Pooling(data=data, kernel=(3,3), stride=(1,1), pad=(1,1), pool_type=pool, name=('%s_pool_%s_pool'% (pool, name)))
    cproj = ConvFactory(data=pooling, num_filter=proj, kernel=(1,1), name=('%s_proj'%  name))
# concat
    concat = mx.symbol.Concat(*[c1x1, c3x3, cd3x3, cproj], name='ch_concat_%s_chconcat'% name)
return concat
prev= mx.symbol.Variable(name="Previos Output")
in3a= InceptionFactoryA(prev, 64,64,64,64,96,"avg",32, name="in3a")
mx.viz.plot_network(symbol=in3a, shape=shape)

最終我們可以通過改變多inception模型獲得整個網路。

 

多符號組合

為了使用多損失層構建網路,我們可以使用mxnet.sym.Group來將多個符號組合在一起。如下示例將組合了兩個輸出層:

net = mx.sym.Variable('data')

fc1 = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=128)

net = mx.sym.Activation(data=fc1, name='relu1', act_type="relu")

out1 = mx.sym.SoftmaxOutput(data=net, name='softmax')

out2 = mx.sym.LinearRegressionOutput(data=net, name='regression')

group = mx.sym.Group([out1, out2])

group.list_outputs()

輸出

['softmax_output','regression_output']

NDArray關係

如我們目前所見,在MXNet中,SymbolNDArray都提供多維陣列操作符,例如c=a+b

NDArray提供了類指令式程式設計的介面:其中,計算被一條語句一條語句地定值。而相比之下,Symbol更加接近於宣告式程式設計:其中,我們首先宣告計算,然後再用資料定值。

同樣的例子包括正則表示式和SQL

NDArray優點:

  • 簡便。
  • 方便與其他語言特性(迴圈、分支)和庫(numpy,…)配合。
  • 易於逐步debug

Symbol優點:

  • 提供幾乎NDArray所有的功能,例如+*reshape
  • 提供了大量的神經網路相關操作符,例如卷積、啟用和BatchNorm
  • 提供了自動微分。
  • 易於構建和操作複雜的計算,例如深度神經網路。
  • 易於儲存、讀取和視覺化。
  • 後端易於優化計算和儲存的使用。

符號操作

SymbolNDArray的一大不同就是,我們首先宣告計算,然後繫結資料來執行。

在此部分,我們介紹直接操作符號的函式。但注意,它們大部分都被mx.module完美地包裝了起來。所以,即便跳過本節也無傷大雅。

形狀介面

對於每個符號,我們可以詢問其輸入(或者引數)和輸出。我們也可以通過給定輸入大小來獲得輸出大小,這有易於儲存空間申請

arg_name = c.list_arguments() # get the names of the inputs

out_name = c.list_outputs()   # get the names ofthe outputs

arg_shape, out_shape,_ = c.infer_shape(a=(2,3), b=(2,3)) 

{'input' :dict(zip(arg_name, arg_shape)),

'output' :dict(zip(out_name, out_shape))}

輸出

{'input': {'a':(2L, 3L), 'b': (2L, 3L)},

 'output': {'_plus0_output': (2L, 3L)}}

繫結資料與定值

我們構建的符號c聲明瞭應該執行的計算。為了為其定值,我們需要首先用資料確定引數,也就是自由變數。我們可以使用bind方法來完成。該方法接受裝置上下文和一個將自由變數名稱對映到NDArray的字典作為引數,然後返回一個執行器。執行器為提供forward方法來定值和歸屬outputs以獲取所有結果。

ex = c.bind(ctx=mx.cpu(), args={'a' : mx.nd.ones([2,3]),

'b' : mx.nd.ones([2,3])})

ex.forward()

print 'number ofoutputs =%d\nthe first output =\n%s'% (

len(ex.outputs), ex.outputs[0].asnumpy())

輸出

number of outputs =1

the first output =

[[ 2.  2.  2.]

 [ 2. 2.  2.]]

我們在GPU上用不同的的資料計算同一個符號:

ex_gpu = c.bind(ctx=mx.gpu(), args={'a' : mx.nd.ones([3,4], mx.gpu())*2,

'b' : mx.nd.ones([3,4], mx.gpu())*3})

ex_gpu.forward()

ex_gpu.outputs[0].asnumpy()

輸出

array([[ 5.,  5.,  5.,  5.],
       [ 5.,  5.,  5.,  5.],
       [ 5.,  5.,  5.,  5.]], dtype=float32)

儲存和載入

類似於NDArray,我們可以使用pickle模組序列號Symbol或者直接使用saveload。與NDArray選擇的二進位制格式不同,Symbol使用可讀性更強的json格式來序列化。tojson方法返回json字串。

print(c.tojson())
c.save('symbol-c.json')
c2= mx.symbol.load('symbol-c.json')
c.tojson()== c2.tojson()

輸出

{
  "nodes": [
    {
      "op": "null", 
      "name": "a", 
      "inputs": []
    }, 
    {
      "op": "null", 
      "name": "b", 
      "inputs": []
    }, 
    {
      "op": "elemwise_add", 
      "name": "_plus0", 
      "inputs": [[0, 0, 0], [1, 0, 0]]
    }
  ], 
  "arg_nodes": [0, 1], 
  "node_row_ptr": [0, 1, 2, 3], 
  "heads": [[2, 0, 0]], 
  "attrs": {"mxnet_version": ["int", 901]}
}
True

自定義符號

大部分操作符例如mx.sym.Convolutionmx.symReshape為了更好的效能使用C++實現。MXNet也支援使用者用任何前端語言例如Python撰寫新的操作符。這經常使開發和除錯更加簡便。

為了用Python實現一個操作符,我們只需要定義兩個計算方法forwardbackward和一些查詢屬性的方法,例如list_argumentsinfer_shape

NDArrayforwardbackward方法預設的引數型別。因此我們通常用NDArray操作符來實現計算。當然為例展示MXNet的靈活性,我們將演示使用NumPy來實現一個softmax層的過程。即便基於NumPy的操作符只能運行於CPU且失去一些可應用於NDArray的優化,但是可以享受NumPy提供的豐富的方法功能。

我們首先建立一個mx.operator.CustomOp的子類,然後定義forwardbackward

class Softmax(mx.operator.CustomOp):
def forward(self, is_train, req, in_data, out_data, aux):
        x = in_data[0].asnumpy()
        y = np.exp(x- x.max(axis=1).reshape((x.shape[0],1)))
        y /= y.sum(axis=1).reshape((x.shape[0],1))
self.assign(out_data[0], req[0], mx.nd.array(y))
def backward(self, req, out_grad, in_data, out_data, in_grad, aux):
        l = in_data[1].asnumpy().ravel().astype(np.int)
        y = out_data[0].asnumpy()
        y[np.arange(l.shape[0]), l] -=1.0
self.assign(in_grad[0], req[0], mx.nd.array(y))

此處我們使用asnumpyNDArray輸入轉換為numpy.ndarray。然後根據req的取值(“重寫”或“加上”)使用CustomOp.assign來將結果賦回mxnet.NDArray

之後我們建立一個mx.operator.CustomOpProp的子類來查詢屬性。

# register this operator into MXNet by name "softmax"
@mx.operator.register("softmax")
class SoftmaxProp(mx.operator.CustomOpProp):
def __init__(self):
# softmax is a loss layer so we don’t need gradient input
# from layers above. 
super(SoftmaxProp,