update.DyNetの技術論文が出ました。あって共著に入っています。12月5日の記事です。本記事ではニューラルネットワークのフレームワークの一つであるDyNetを簡単に紹介します。このフレームワークは米カーネギーメロン大学の言語技術研究所が中心となって開発しているライブラリで、少し縁があって自分も裏で開発に参加しています。自然言語処理の国際会議であるEMNLPのチュートリアルでも使用されて、こちらでも割と人気があったようです。DyNetの特徴としては:C++とPythonで使用可能インクリメンタルなネットワーク計算が可能ミニバッチの隠蔽が挙げられます。network系の実装が必要となる自然言語処理等の分野で力を発揮します。3つ目の特徴はかなり強力で、ユーザが投げたデータに応じてミニバッチ処理かそうでないか、ミニバッチ処理ならバッチの大きさ等を自動的に判別するので、ネットワークを記述するコード上ではそれを区別する必要がありません。論文に書いてある式をそのまま書けば、基本的には自動的にミニバッチ処理に対応したコードになるわけです。追記:現状は入力部分だけミニバッチサイズを指定する必要がある場合があります。(下記コード参照)メインの対応言語がC++なので、低レベルな領域で他のライブラリと連携したい場合には良い選択肢になるでしょう。DyNet自体はインターフェイスが高度に抽象化されているため、ネットワーク構築部分の見通しは他のフレームワークと比較しても遜色ないと思います。インストールは済んでいるものとして、以降ではC++上でのDyNetの基本的な使い方を解説します。初期化・後片付けDyNetは最初に内部で指定した量のメモリとデバイスを確保し、それを最後まで使い続けます。このため最初にdynet::initialize()を呼び出してライブラリを初期化してやる必要があります。これには2種類の方法があります。一つはDynetParams構造体に必要な値をセットして渡す方法、もう一つはargcとargvを渡してコマンドライン引数をDyNetに処理してもらう方法です。後者は前者のラッパーとして実装されているので、以下では構造体で明示的に初期化する例を載せています。処理が終わったらdynet::cleanup()を呼び出して後片付けをすることになりますが、他の処理にメモリが必要等の理由がなければ、この関数は呼び出す必要はありません。その場合、プログラム終了時に自動的に後片付けが行われます。確保するメモリ量を3つ指定する(MB単位)。パラメータで使用する量。下記のように1つだけ指定することも可能。その場合は内部的に3等分したものがそれぞれに使用される。以下はCUDAに関連付けてインストールした場合の設定。下記のように設定するとメモリに余裕があるGPUを1個勝手に選んで使う。複数GPUには今のところ未対応。挿さっているGPUの枚数以上にすればよい。なんか色々書きましたが、基本的にデフォルト値で大丈夫だと思います。dynet/init.h参照。gpus_maskは上記のような初期化が必要なので注意。ここにやりたい処理を書く。この時点でDyNet関係のオブジェクトが全て消滅している必要がある。それ以外の場合に呼び出すとエラーの原因になる。必要がなければ呼び出さなくてよい。0;}コンパイルには-std=c++11が必要です。実行ファイルはCPUで計算させる場合はlibdynet.so、CUDAで計算させる場合はlibgdynet.soとlibdynetcuda.soにリンクする必要があります。場合によってはCUDA関係のライブラリにも自分でリンクする必要があるかもしれません。簡単な例まず、学習の必要がない(パラメータが存在しない)ネットワークを作って計算させてみます。以下の例は入力値を2倍し、3を足した結果をDyNetで計算しています。dynet::realは今のところfloatと等価。第2引数で次元を指定する。1次元の場合は縦ベクトルになる。行列の場合はベクトルにpackした値をやりとりする。後片付け}Expression型は計算情報だけを保持した仮想的な型で、この変数に対して通常のC++と同じ方法で計算式を記述します。Chainerとは異なり、Expressionを生成した段階では実際の計算はまだ行われていません。最終的に必要な値を記述するExpressionに対してComputationGraph::forward()を呼び出すことで実際の値を計算させます。11ベクトルや行列同士の演算も可能です。値の初期化には、適切な順序で要素の値を格納したvectorを渡します。vectorを初期化できれば何でもよいので、initializer_listを直接投げてもOKです。160600ここでforward()ではなくincremental_forward()を使っています。forward()は今までに登録された全ての式を毎回計算対象としますが、incremental_forward()では既に計算された値については内部に保持しており、新たに追加された部分のみの計算を行います。これは逐次的な出力を必要とする言語モデルや翻訳モデルの実装に役立ちます。パラメータの学習ここまでで示したのは定数同士の演算例ですが、実際のネットワークには変更可能なパラメータが存在して、特定の学習器でそれらを調整することになります。DyNetではModelクラスがパラメータと学習器の対応付けの面倒を見ることになっています。コード全体の流れとしては、ModelにTrainerを登録Modelに各種Parameterを登録ComputationGraph上にネットワークを構築ComputationGraph::forward()、ComputationGraph::backward()、Trainer::update()3.〜4.を必要なだけ繰り返しのようになります。以下は2変数XOR問題を多層パーセプトロンで解く例です。こちらは間違い(仕様で通ってしまう。loss=0.0876631パラメータはランダムに初期化される(ように設定している)ので、実行結果は毎回変わります。注目したいのはネットワークを構築している部分で、input()がデータを自動的に整形してミニバッチ化しています。生成されるExpressionオブジェクトはミニバッチを完全に隠蔽しているので、後の計算では実際の数式を記述することに専念できます。追記:現状の実装ではDimオブジェクトに明示的にミニバッチサイズを渡してやる必要があります。渡すvectorのサイズが一致しなくても受理されてしまうようなので注意。ミニバッチサイズを自動的に判別する実装はそこまで難しくないし、サイズ不一致で受理されてしまうのはちょっと怖いので、可能なら修正しておこうと思います。このコード例では入出力が常に同じなので、ネットワークを1度だけ生成して学習時に使い回しています。入出力が変化する場合はその都度新たなComputationGraph上にネットワークを構築する必要があります。この辺りの挙動はChainerと基本的に同じで、ユーザが自分の好きなタイミングで明示的にforwardする点に差異があります。追記:ミニバッチサイズを指定する必要があるかどうかは、入力した値を共有したいかどうかに依存します。下記の例では、ミニバッチごとに異なる行列、または共通の行列を乗じています。他にもRNNを使用する例などが公式のexamplesに転がっているので、興味のある方は読んでみましょう。現時点では大分後発のフレームワークなので、実行速度やメモリ使用量の点で他のフレームワークより大雑把な動き方をしたり、細かいバグが残っていたりと若干の難点はあります。開発陣の対応はかなり速い部類だと思うので、使ってみて文句があればissuesに投げてみるといいかもしれません。いうか日本人でメインに使っているのが自分しかいない気がします(泣)C++使える人にはマジ便利なので使ってね!おまけ何番煎じか分かりませんが、ニューラル翻訳するツールを書きました。バックエンドが全部DyNetになっています。こちらについてはまた今度書く予定です。upLoginユーザーは見つかりませんでした。