Tweetレトリバ製品企画部の田村(@masatam81)です。Elixir」というタイトルでお話しました。LearningフレームワークでConvolution2D/MaxPooling2Dまで使えるようになったので、関数型言語ならではの苦労や工夫・学びの共有といった感じでした。本記事は、そのフォローアップ記事です。LearningやElixirに詳しくない人もいたため、それらの簡単な説明も含めて、なるべく伝わるように話しました。本記事ではDNNやCNN、Elixirの簡単な説明やConvolution2D、MaxPooling2D以外のレイヤーの話は省きます。その辺りの話は下記のYouTubeや関連書籍を見て下さい。動画ではNIFやMatrexに関しても少しだけ話しています。Elixirスライドはこちらになります。MaxPooling2DのElixirでの実装で工夫した点にフォーカスして書きたいと思います。利用して各レイヤーをLayerモジュールの継承関係のように扱う例を説明してから、それを関数型らしく修正した話をしましたが、今回の引用コードは修正後のコードを示します。Learning」です。元の処理に関する詳細な説明は、そちらをご覧下さい。畳み込みの準備まず、Convolutionなど畳み込み処理によく使われるim2colという手法の説明と実装です。mが多い)のカーネルを順に掛けていって、カーネル内の各要素、もしくはカーネル全体に対して処理を行います。このカーネルに対する処理を行いやすくするために、カーネル内の各要素をフラットに並べて積む処理がim2colです。Convolutionの重みとの行列積やバイアスの足し合わせを容易にするため、チャンネル(4次元のリスト=テンソルのバッチの次の2次元目をチャンネルと呼びます)を後ろにつなぎ合わせたりします。Elixirでの実装これをElixirで、単方向リストで処理しやすい形で実装したのですが、通常のやり方で多次元リストを任意の形に変換するのはなかなか面倒です。ようなもの)で作成される要素分のリストを生成します。それを用いて、チャンネルの位置でim2colの行列を結合、もしくはそれぞれのチャンネルに吐いています。チャンネル数)になっています。下記は行列に部分に対してim2colをかけるコードです。ストライド幅とカーネルの行数、元の行列の行数を元に、その行列にカーネルが何回かけられるかを算出しています。本記事内では被カーネル行と表現させて頂きます。同様に列側に何回かけられるかで、被カーネル列と便宜的に呼びます。x=1の場合で説明します。Enum.take(kernel_width))))という処理を行い、最後にflattenで1列に並び替えています。呼んでいるのは、Poolingの場合、この時点で処理をしてしまえばカーネルで切り出しながら処理を終わらせられるためです。簡単にさせたいため、私の書いたim2colではチャンネルは結合するかそのままか選択できるように書いています。推論のforwardとパラメーターを誤差逆伝播するbackwardでは、forward同士・backward同士で実装が似ているため、処理ごとに分けて説明します。推論に用いるforward処理です。forward処理は関数型、と言ってもあまり違いがない気もします。MaxPooling2DMaxPooling2Dには下記の特徴があります。カーネル内の最大の値のみを残すチャンネル数に変更はないチャンネルに変更はないので、チャンネル内の行列から、行列に適応されるカーネル数のデータが出力されます。リストの最大値のみを取り出しています。カーネルのそれぞれの位置に1つの数値が残るので、これだけでOKです。以外はそのままになっています。セミナーでは一般的なim2colを使ったMaxPooling2Dの動きを説明しましたが、厳密には私の書いたMaxPooling2Dの動きは下のようにreshapeの要らないシンプルな動きになっています。maskに関してはbackwardで使用します。MaxPoolingでは最大値のデータ以外は以降の推論に影響しないため、パラメータの修正もそのデータの部分のみで良いため、"カーネルを平らに並べた何番目のデータを使ったか"を残しています。Convolution2DConvolution2Dは2つのパラメータがあります。カーネル列)チャンネルに対するバイアス(1つのチャンネルに1つの数値)バイアスは対象のチャンネルの行列、全てのデータに同じものが足されます。im2colの説明で"カーネルに対する処理を行いやすくするため"と書きましたが、Convolution2Dの処理はかなり楽になります(ただし、後処理が必要になりますが)。im2colでチャンネルが結合したカーネルで行列の特定位置を抜いたリストが作られます。チャンネルをわかりやすく言うと、RGBの画像データのカーネルに対応する同じ位置のRのデータ、Gのデータ、Bのデータで、これらが並んだ状態になっています。カーネル列)のデータになります。処理の都合上(行列積は行列同士にすると一気に計算できる)、一旦前の3つのサイズをまとめた形でreshapeします。出力チャンネル)になるので、それぞれのカーネルの位置の全チャンネル情報から新しいチャンネル数分のカーネル処理後が出力されるようなイメージになります。バイアスの形は出力チャンネル数分の値のリストなので、そのまま足せば、カーネルで取得した値に重みを掛けてバイアスを足す、という処理が完了します。チャンネル数)という形になってるためです。チャンネル数)という形に解釈を変えます(基本的にデータの並びは変わらない)。2)で並び替えればOKです。endbackward処理backward処理は関数型ではなかなか大変です。Elixirの基本データ構造は単方向リストで不変性(一度メモリ上で定義されたら破棄されるまで変更されない)という特性があります。ElixirでCNNを書こうとしてbackwardで行き詰まった人もいると思います。Convolution2DもMaxPooling2Dもカーネルの特定の位置に該当する元のデータでの位置に逆伝播しなくてはならない上、Convolutionでは同じ位置が複数回使われることが多いので集計が必要になります。この問題を解決するため、次の(モデル上入力に近い)レイヤーに渡すための処理はマップを使って、戻るべき位置のindexへの値を集計した上で、集計を元にテンソルを構築する形を取りました。MaxPooling2DまずはよりシンプルなMaxPooling2Dに関して説明します。MaxPooling2Dにはパラメータがないので、基本的に次のレイヤーに戻す処理のみです。カーネルの対象の数値の中で最も大きい値のみが採用されるため、最大値のあったデータの位置のみに逆伝播され、他は0になります。カーネルがオーバーラップしている場合は加算された値が伝播します。Elixirでの実装これを下記のようなMapを使ったコードで実現しました。endポイントとしては、下記のコードでbackwardの入力の位置から、入力のどこの位置に値を戻すかを決定しています。endそれを、元のshapeの多次元リストをforで構築しながら値を設定しています。endConvolution2DConvolution2Dはパラメータが2つあるので、それぞれの更新のための処理もあります。パラメータ部分は一旦backwardへの入力をim2colの結果の形に戻してやる必要がある以外はAffine(全結合層)と大差ないので簡単です。Elixirでの実装コードが長いので全体は省略して、要点だけ抜き出して説明します。パラメータの更新のための処理は結構スッキリまとまりました。NN.transpose()問題の次の層へ戻す処理ですが、基本的にはMaxPooling2Dと同じで、Mapで集計しています。違いとしては、重みを掛けてあげる必要があるのと、カーネルの全てのカラムに値を戻しつつ集計する必要がある(MaxPooling2Dでも最大値が複数カーネルで同じ位置なら必要ですが)ことでしょうか。MaxPooling2Dではpadding部分が最大値になることは稀なのであまり影響ありませんが、Convolution2Dでは下記の最後のforの処理が結構上手く動いてると思います。-1)のようなindexで集計されて入るものの、結果を構築する際には無視されるため、集計時点では戻る位置のindexがはみ出ないか確認せず一律全てのカーネルが当たったとされる位置を集計すれば良い、と処理が容易になっています。速度の問題Mapを使った集計処理はElixirの単方向リストでの処理で実現するという意味では目的を達成しているものの、処理速度はかなり厳しいものがあり、せっかく実装したものの、対象のbackward処理はNIF等で最優先で改善する必要があると思います。同様に下記の構成でCIFAR10の50000の訓練データに対し、1エポック100〜150時間かかります。フレームワークの今後NIFでC++/OpenCLでの高速化(CUDAでなくても速くしたい気持ちがあり)を考えてはいますが、自分の勉強のためとエコシステムが整った時に高速化できそう、という目論見で書き始めましたが、趣味のフレームワークで終わりそうな気がします。いえ、実際に書いてみての学びは非常に大きかったので、ゆっくりメンテナンスしつつ、他の層や手法も実装できればと思います。Runに関数型で挑戦するというのも面白いかも知れません。違う言語で書く場合、足りないものが多く、大変ではありますが、その分本質的な理解が求められるので非常に勉強になりました。»ABOUT株式会社レトリバの技術ブログです。レトリバのリサーチャー及びエンジニアが分担して技術的な記事を載せていこうと思います。Inc.スターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。