MXNet公式ドキュメント中国語版チュートリアル(3):ニューラルネットワーク図(Symbol)

26152 ワード

ドキュメントの英語版はSymbol-Neural network graphs and auto-differentiationを参照
前のチュートリアルでは、NDArray、MXNetでデータを操作する基本的なデータ構造について説明しました.NDArray自体を使うだけで、多くの数学操作を実行することができます.実際には、NDArrayを使用してニューラルネットワーク全体を定義し、更新することもできます.NDArrayはコマンドプログラミング(科学計算用)をサポートし、フロントエンド言語のネイティブ制御を十分に利用しています.しかし、私たちはなぜNDArrayをすべての計算に使用しなかったのでしょうか.
MXNetは、シンボルプログラミングのインタラクションのためのシンボルインタフェースを提供します.一歩一歩説明するコマンドプログラミングとは異なり、まず計算図を定義します.この図には、入力されたプレースホルダと設計された出力が含まれています.その後、この図をコンパイルし、NDArraysにバインドして実行できる関数を生成します.Symbol APIは、caffeのネットワーク構成またはTheanoのシンボルプログラミングに類似しています.
もう1つの利点は、使用する前に関数を最適化できることです.たとえば、コマンド式の数学計算を実行しますが、この値は後で計算されるので、各操作を実行する時間がわかりません.しかし、シンボル化プログラミングを使用して、必要な出力を事前に宣言します.これは、中間ステップで割り当てられたメモリを操作によって回収できることを意味します.Symbol APIは、同じネットワークに対してより少ないメモリを使用します.
我々の設計ドキュメントでは,コマンド式とシンボル式のプログラミングの利点についてより深く検討した.しかし、このドキュメントでは、MXNetのSymbol APIの使い方を教えています.単純なマトリクスオペレータ(「+」)やニューラルネットワーク層全体を用いて,他の適合から新しいシンボルを組み合わせることができる.オペレータは、複数の変数入力、複数の出力をサポートし、内部ステータスシンボルを維持します.
前提条件
次のチュートリアルを完了するには、次の手順に従います.
  • MXNet:インストールチュートリアル
  • Jupyter
  • pip install jupyter
  • GPUs:チュートリアルの一部の実装にはGPUが必要です.GPUがなければ変数gpu_だけをデバイスをmxに設定.cpu()で
  • きほんきごう
    基本オペレータ
    次の例では、簡単な式「a+b」を複合します.まずmxを使います.sym.Variableは、プレースホルダaおよびbとその名前を作成し、オペレータ「+」で所望のシンボルを構築します.新しい名前文字列が指定されていない場合、MXNetはcの例に示すように、記号にユニークな名前を自動的に生成します.
    import mxnet as mx
    a = mx.sym.Variable('a')
    b = mx.sym.Variable('b')
    c = a + b
    (a, b, c)

    ほとんどのNDArrayオペレータはSymbolに適用できます.たとえば、次のようにします.
    # elemental wise multiplication
    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))
    # plot
    mx.viz.plot_network(symbol=g)

    上記の例で宣言した計算はbindメソッドを使用して入力データにバインドして評価することができる.シンボルレイアウトのセクションで説明します.
    きほんニューラルネットワーク
    基本的なオペレータに加えて、Symbolは豊富なニューラルネットワーク層セットを持っています.次のコードは、2つのレイヤのフル接続レイヤを構築し、指定された入力データサイズで構造をインスタンス化します.
    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)})

    各シンボルには、一意の文字列名があります.NDArrayとSymbolはいずれも単一のテンソルを表す.オペレータはテンソル間の計算を表します.オペレータはsymbol(またはNDArray)を入力として受け入れるか、隠しニューロン個数(num_hidden)、アクティブタイプ(act_type)のようなスーパーパラメータを増やして出力を生成することもできる.
    実際にはsymbolを複数のパラメータを持つ関数と見なすことができ、以下の方法でこれらのパラメータを遍歴することができます.
    net.list_arguments()

    これらのパラメータは、symbolごとに必要なパラメータと入力です.
  • data:変数dataに必要な入力データ
  • fc1_weight & fc1_bias:第1の全接続層fc 1の重みとバイアス
  • fc2_weight &fc2_bias:第2の全接続層fc 2の重みとバイアス
  • out_Label:損失関数に必要なラベル
  • また、名前を明確に指定することもできます.
    net = mx.symbol.Variable('data')
    w = mx.symbol.Variable('myweight')
    net = mx.symbol.FullyConnected(data=net, weight=w, name='fc1', num_hidden=128)
    net.list_arguments()

    上記の例では、FullyConnectedレイヤには、データ、重み、バイアスの3つの入力があります.指定されていない入力は、自動的に変数を生成します.
    より複雑な構成
    MXNetは、深度学習でよく使用されるレイヤに対してより最適化されたシンボルを提供する.Pythonで新しいオペレータを定義することもできます.次の例では、まず2つのsymbolを加算し、フル接続オペレータに送ります.
    lhs = mx.symbol.Variable('data1')
    rhs = mx.symbol.Variable('data2')
    net = mx.symbol.FullyConnected(data=lhs + rhs, name='fc1', num_hidden=128)
    net.list_arguments()

    また、上記の例で説明した単一の順方向の組み合わせよりも柔軟な方法でシンボルを構築することもできる.
    data = mx.symbol.Variable('data')
    net1 = mx.symbol.FullyConnected(data=data, name='fc1', num_hidden=10)
    net1.list_arguments()
    net2 = mx.symbol.Variable('data2')
    net2 = mx.symbol.FullyConnected(data=net2, name='fc2', num_hidden=10)
    composed = net2(data2=net1, name='composed')
    composed.list_arguments()

    この例では、net 2は、既存のシンボルnet 1に関数として適用され、得られた組合せシンボルは、net 1およびnet 2のすべての属性を有する.
    より大きなネットワークの構築が開始されると、ネットワークの構造を統合するために、共通の接頭辞を使用していくつかのシンボルに名前を付ける必要がある場合があります.接頭辞名管理は、次の例のように使用できます.
    data = mx.sym.Variable("data")
    net = data
    n_layer = 2
    for i in range(n_layer):
        with mx.name.Prefix("layer%d_" % (i + 1)):
            net = mx.sym.FullyConnected(data=net, name="fc", num_hidden=100)
    net.list_arguments()

    深さネットワークのモジュール化構築
    Google Inceptionのような深いネットワークでは、大量のレイヤがある場合、レイヤを1つずつ構築するのは苦痛です.これらのネットワークでは、通常、その構築をモジュール化します.Google Inceptionを例にとると、まず、ボリューム層、バッチ標準化層、およびReluアクティブ化層を結合する製造関数を定義します.
    def ConvFactory(data, num_filter, kernel, stride=(1,1), pad=(0, 0),name=None, suffix=''):
        conv = mx.sym.Convolution(data=data, num_filter=num_filter, kernel=kernel,
                      stride=stride, pad=pad, name='conv_%s%s' %(name, suffix))
        bn = mx.sym.BatchNorm(data=conv, name='bn_%s%s' %(name, suffix))
        act = mx.sym.Activation(data=bn, act_type='relu', name='relu_%s%s'
                      %(name, suffix))
        return act
    prev = mx.sym.Variable(name="Previous Output")
    conv_comp = ConvFactory(data=prev, num_filter=64, kernel=(7,7), stride=(2, 2))
    shape = {"Previous Output" : (128, 3, 28, 28)}
    mx.viz.plot_network(symbol=conv_comp, shape=shape)

    次に、ConvFactoryベースのInceptionモデルを構築する関数を定義します.
    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.sym.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.sym.Concat(*[c1x1, c3x3, cd3x3, cproj], name='ch_concat_%s_chconcat' % name)
        return concat
    prev = mx.sym.Variable(name="Previous Output")
    in3a = InceptionFactoryA(prev, 64, 64, 64, 64, 96, "avg", 32, name="in3a")
    mx.viz.plot_network(symbol=in3a, shape=shape)

    最終的には,マルチinceptionモデルを変えることでネットワーク全体を得ることができる.
    マルチシンボルの組み合わせ
    マルチロス層を用いてネットワークを構築するためにmxnet.を用いることができる.sym.グループは複数の記号を組み合わせます.次の例では、2つの出力レイヤを組み合わせます.
    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()

    NDArrayとの関係
    現在見ているように、MXNetでは、SymbolとNDArrayは、c=a+bなどの多次元配列オペレータを提供しています.ここでは両者の違いを簡潔に説明する.NDArrayは、計算が文ごとに実行されるインタラクティブなコマンドプログラミングを提供します.Symbolは宣言プログラミングに近いので、計算方法を宣言し、データで評価します.この例には、正規表現とSQLが含まれます.
    NDArrayのメリット:
  • 直截当
  • ローカル言語機能(forループ、if-else条件、.)とライブラリ(numpy,.)
  • を使用しやすい
  • 簡単なステップコードデバッグ
  • Symbolのメリット:
  • は、+、*、sin、reshapeなどのNDArrayのほとんどの機能を提供します.
  • 保存、ロード、可視化が容易
  • バックグラウンド最適化コンピューティングとメモリ使用
  • シンボルアクション
    SymbolとNDArrayの大きな違いは、まず計算を宣言し、データをバインドして実行することです.
    このセクションでは、シンボルを直接操作する関数について説明します.しかし、それらの大部分はmoduleに完璧に包装されていることに注意してください.だから、本節を飛び越えても大雅を傷つけることはない.
    シェイプとタイプインタフェース
    各シンボルについて、入力(またはパラメータ)と出力を尋ねることができます.入力サイズを指定することで出力サイズを得ることもできます.これにより、記憶領域申請が容易になります.
    arg_name = c.list_arguments()  # get the names of the inputs
    out_name = c.list_outputs()    # get the names of the outputs
    # infers output shape given the shape of input arguments
    arg_shape, out_shape, _ = c.infer_shape(a=(2,3), b=(2,3))
    # infers output type given the type of input arguments
    arg_type, out_type, _ = c.infer_type(a='float32', b='float32')
    {'input' : dict(zip(arg_name, arg_shape)),
     'output' : dict(zip(out_name, out_shape))}
    {'input' : dict(zip(arg_name, arg_type)),
     'output' : dict(zip(out_name, out_type))}

    データのバインドと評価
    我々が構築したシンボル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 of outputs = %d
    the first output =
    %s'
    % ( len(ex.outputs), ex.outputs[0].asnumpy()))

    GPU上で同じ符号を異なるデータで計算した.
    gpu_device=mx.gpu() # Change this to mx.cpu() in absence of GPUs.
    
    ex_gpu = c.bind(ctx=gpu_device, args={'a' : mx.nd.ones([3,4], gpu_device)*2,
                                          'b' : mx.nd.ones([3,4], gpu_device)*3})
    ex_gpu.forward()
    ex_gpu.outputs[0].asnumpy()

    シンボルをeval法で評価することもでき,bindとforwardの方法を組み合わせた.
    ex = c.eval(ctx = mx.cpu(), a = mx.nd.ones([2,3]), b = mx.nd.ones([2,3]))
    print('number of outputs = %d
    the first output =
    %s' % ( len(ex), ex[0].asnumpy()))

    ニューラルネットワークではsimple_が一般的ですbindは、すべてのパラメータ配列を提供します.その後、forwardとbackwardを呼び出して勾配を得ることができます.
    保存と読み込み
    NDArrayと同様に、オペレータの入出力であるテンソルを表します.pickleモジュールのシリアル番号Symbolを使用するか、saveとloadを直接使用することができます.NDArrayをシーケンス化すると、テンソルデータをシーケンス化し、ディスクに直接バイナリ形式でダンプします.しかし、記号は図の概念を使用します.図はリンクオペレータで組み合わせたものです.出力シンボルによって暗黙的に表されます.したがって、シンボルをシーケンス化する場合、シーケンス化出力はシンボルの図である.シーケンス化されるが、Symbolはより読みやすいjson形式でシーケンス化される.シンボルをjson文字列に変換する場合はtojsonメソッドを使用します.
    print(c.tojson())
    c.save('symbol-c.json')
    c2 = mx.sym.load('symbol-c.json')
    c.tojson() == c2.tojson()

    カスタムシンボル
    ほとんどのオペレータは、例えばmxである.sym.Convolutionとmx.SymReshapeはC++を用いてより良い性能を実現できる.MXNetはまた、Pythonなどの任意のフロントエンド言語で新しいオペレータを作成することをサポートします.これにより、開発とデバッグがより簡単になります.Pythonで実装するには、新しいオペレータの作成方法を参照してください.
    高度な使い方
    タイプ変換
    MXNetのデフォルトでは32ビットfloatタイプが使用されます.より正確なレートであるパフォーマンスのバランスをとるために、低精度のデータ型を使用したい場合があります.例えば、P 100のような英偉達Tesla Pascal GPUsは16ビットの浮動小数点の性能向上を使用し、GTX Pascal GPUs(GTX 1080のような)は8ビットの整数型の速度を使用するのがより速い.
    mxを使用できます.sym.castオペレータはデータ型を変換します.
    a = mx.sym.Variable('data')
    b = mx.sym.cast(data=a, dtype='float16')
    arg, out, _ = b.infer_type(data='float32')
    print({'input':arg, 'output':out})
    
    c = mx.sym.cast(data=a, dtype='uint8')
    arg, out, _ = c.infer_type(data='int32')
    print({'input':arg, 'output':out})

    変数の共有
    複数の異なる記号の内容を共有したい場合があります.これは、これらのシンボルを同じ配列でバインドすることによって直接実現することができる.
    a = mx.sym.Variable('a')
    b = mx.sym.Variable('b')
    b = a + a * a
    
    data = mx.nd.ones((2,3))*2
    ex = b.bind(ctx=mx.cpu(), args={'a':data, 'b':data})
    ex.forward()
    ex.outputs[0].asnumpy()