Disentangled な表現の教師なし学習手法の検証

Keisuke Nakata

2019-10-08 11:20:57

本記事は、2019年インターンシップに参加された蕭喬仁さんによる寄稿です。


はじめまして。PFN の2019夏季インターンシップに参加した東京大学の蕭喬仁です。 大学では自然言語処理について研究しており、SNS からのマイニングに興味があります。
今回のインターンでは「Disentangled な表現の教師なし学習手法の検証 (Unsupervised Disentangled Representation Learning)」というテーマで研究を行いましたので、その紹介をいたします。

実験に使用したコードはこちら https://github.com/pfnet-research/chainer-disentanglement-lib で公開しています。

Disentangledな表現

映画 Star Wars がお好きな方は ”imperial entanglements” という表現でおなじみかもしれませんが、entangle とはもつれるという意味の英単語です。したがって Disentangled Representation とは直訳するともつれを解いた表現ということを指します。
では、もつれを解いた表現とは何なのでしょうか?実は disentangled な表現の定義は研究者の間でもまだ定まったものが無いのですが、多くの研究では潜在空間中の各次元が観測データ中の因子や性状ごとに分かれているような状態を disentangled な表現としています。たとえば、画像認識における disentangled な表現の各次元は被写体の「色」「形」「大きさ」などをそれぞれ表すことが期待されます。このような性質を持つ表現はある1つの次元を変動させても観測データ中の複数の要素が同時に変わる事が無いため、観測データの情報が解釈可能で低次元な潜在空間に圧縮された表現とも言えます。そのため教師有り・半教師有り学習などの機械学習タスクや few-shot learning, domain adaptation などに有用であるとされており、様々な手法が近年提案されてきました。

先行研究

教師なしで disentangled な表現を得る手法として state-of-the-art を主張している論文の多くは変分オートエンコーダ (Variational AutoEncoder; VAE) [1] をベースにした手法を採用しています。

図1. VAE の概念図

VAE では潜在変数の確率分布を標準正規分布に近づけながら学習を行いますが、disentangled な表現を得るためには潜在変数の事後分布がより標準正規分布に近い必要があります。データ \(X\) を生成している真の因子は各々独立であることを仮定しているからです。このような仮定のもと近年提案されてきた手法は VAE にどのような正則化項を加えるかで以下のように分類することができます。

  • 近似事後分布 \(q(z \mid x) \) をより事前分布である標準正規分布に近づける正則化項を加えた β-VAE [2]
  • aggregated variational posterior \(q(z) \) が各成分独立になるような正則化項を加えた FactorVAE [3] や β-TCVAE [4]
  • aggregated variational posterior \(q(z) \) が事前分布と近くなるような正則化項を加えた DIPVAE-1/DIPVAE-2 [5]

また、潜在変数に離散変数を使えるようにした、JointVAE [6] や CascadeVAE [7] などの手法もあります。

様々なモデルの提案と同時に disentangled な表現を定量的に評価する指標も数多く提案されています。評価指標には以下のような2系統が存在しますが、いずれの指標でも値の計算のためにデータの真の生成因子が必要となります。こちらでは紹介していませんが、インターンに参加していた8月中にも新しい評価指標 [8] が提案されており、どの指標を用いるべきかは研究者の間でも定まっていません。

  • データ \(X \) のある生成因子を固定した状態でその他の因子を変化させた時の潜在変数の変化をみる BetaVAE metric [2], FactorVAE metric [3], IRS [9]
  • エンコードした潜在変数から真の生成因子をどれだけ予測可能かをみる MIG [4], SAP-SCORE [5], DCI [10]

一方で、disentangled な表現を完全に教師無しで学習することは困難なのではないかという主張が最近なされるようになりました。Locattello らの研究 [11] では近年 state-of-the-art を主張してきた手法に対して大規模な検証実験を行い以下のようなことを示しています。

  1. モデルや正則化項等のハイパーパラメータよりも乱数による影響が大きいということ。
  2. モデルが学習した disentangled な表現が人間の直感に合う保証が無いこと。
  3. 再構成誤差や ELBO などの教師無しで得られるモデルの評価指標と教師有りの disentangled な表現の評価指標には相関が見られないこと。
  4. データ \(X \) を生成する確率 \(P(X) \) しか持っていない状況では、disentangled な潜在変数 \(p(z) \) と entangled な潜在変数 \(p(z^\prime) \) の識別が数学的に不可能であること。

ただし、論文では適切な帰納バイアスを取り入れることで教師ラベルを用いる事なく disentangled な表現を得ることは可能かもしれないとされており、別の論文 [12] でも損失関数に潜在変数の事前分布を適切に調整することでより良い disentangled な表現を得ることが可能であったという報告もされています。disentangled な表現を得る試みはまだまだ始まったばかりと言えるでしょう。

実験設定

今回のインターンでは、先行研究では検証されていなかった潜在変数の次元数や種類がパフォーマンスにどのような影響を与えるかを検証するために state-of-the-art を主張してきた BetaVAE, FactorVAE, DIPVAE-1/-2 に加えて離散変数を潜在変数として扱える JointVAE の Chainer 実装を行い、2つのデータセットを用いて実験をしました。モデル間で公平な比較を行うために、encoder/decoder の構造や optimizer・バッチサイズ・iteration の数などは共通にした上で、モデルごとに30個のシードで学習を実施しました。

本記事では代表的な実験結果として、データセットのうちの dSprites データに関する結果を紹介したいと思います。このデータセットにはハートや楕円などの物体がサイズや位置を変えて配置された2次元画像が納められており、データを生成する真の因子には物体の種類・物体の大きさ・物体の回転・x軸座標・y軸座標の5種類があります。

図2. データセットの例

実験設定1

上で紹介したモデルを使用する時にまず気になるのが潜在因子の次元数をどのように設定するべきかだと思います。そこで、今回は連続変数を潜在変数とする BetaVAE, FactorVAE, DIPVAE-1/-2 に関して、潜在変数の次元数を真の生成因子の数の半分・同じ・倍の3パターンのモデルを用意して学習結果を比較しました。

実験設定2

実務で使用する際には、dSprites データのように因子の全組み合わせに対応する網羅的なデータを用意することは現実的ではないことが考えられます。そこで、x座標と物体の大きさに相関ができるように因子の組み合わせを半分削除したデータと、比較対象として因子の組み合わせをランダムに半分削除したデータを dSprites データセットから人工的に作成し、パフォーマンスがどの程度落ちるのかを検証しました。

結果

実験1

以下に、潜在変数のある次元だけを動かして、その他の次元を固定した時にデータがどのように変化するかを見た latent traversal という図を掲載します。一番左の列は VAE に与えた元のデータを示しており、その右隣の画像は VAE による再構成画像です。その他の列は潜在変数中の各次元を \(-1.5 \) から \(1.5 \) まで動かした時の画像の変化を順番に描画しています。

 

潜在変数を3次元とした場合
BetaVAE
FactorVAE
DIPVAE-1
DIPVAE-2

 

潜在変数を5次元とした場合
BetaVAE
FactorVAE
DIPVAE-1
DIPVAE-2

 

潜在変数を10次元とした場合
BetaVAE
FactorVAE
DIPVAE-1
DIPVAE-2

 

潜在変数の次元数が小さい場合は、各画像の左から2番目、つまり再構成画像を見ると、どのモデルを使っても画像の再構成が上手くいかず、物体の形がぼやけてしまっているのが見て取れます。どうやら次元数を少なくしすぎるのは問題のようです。しかし、BetaVAE は潜在変数を大きくしても再構成があまり上手くいっておらず物体の形がぼやけてしまっています。実はこの問題は FactorVAE を提唱した論文でも述べられており、BetaVAE に観測データ \(X \) と潜在変数 \(Z \) の相互情報量を小さくするような効果があるから起きるとされています。次元数を真の生成因子と同じ5次元にした場合は、FactorVAE はx軸座標とy軸座標、サイズの変化を disentangle できていますが、DIPVAE-1/-2 では entangled された特徴が学習されてしまっています。次元数が10次元の場合も FactorVAE が5つの因子を disentangle できているのに対して、DIPVAE-1/-2 は直感的ではない特徴を学んでしまっているように見えます。次元数を真の生成因子の数よりも多くした場合は全てのモデルで latent traversal 上で動かない次元が出てきていますが、FactorVAE では真の生成因子の学習が上手くいっていることから、実務で使用する際には潜在変数の次元数は出来るだけ余分にとっておいたほうがいいことが推察されます。

実験2

以下では実験1で定性的に性能がよかった FactorVAE による結果を掲載しています。x座標と物体の大きさに相関があるデータで学習したものは、latent traversal 上でもx座標の移動とともに物体の大きさが変化していることがわかります。また、y軸方向の移動とともに物体の形状が変化しているのをみると、その他の因子の学習にも悪影響を及ぼしていてそうです。ランダムな欠損を与えたデータでは、完全なデータと同じレベルとまではいかないものの、ある程度は5つの因子を獲得できています。今回は全データの半分を削除したので、データを削除しすぎた可能性もあります。しかし、実務で使用する際に生成因子の全ての組み合わせのデータの準備が難しい場合は、せめて因子に相関がないようなデータに整えた上で学習を行うべきである事がこの結果から推察できます。

完全なデータ

x座標と物体の大きさに相関があるデータ
ランダムな欠損を与えたデータ

 

メンターからのコメント

メンターを担当した PFN の仲田と吉川です。

本文中でも触れられている通り、disentangled な表現の教師なし学習は few-shot learning や domain adaptation といった分野への応用や、機械学習モデルの解釈性の向上が期待できます。これらは現実世界への機械学習の適用をする際にいつも直面する課題です。
深層学習などの先端技術による現実世界の課題解決をミッションとする PFN としても以前から取り組んではいるのですが、タスク自体の難しさはもちろん、問題設定をおこなうことも難しい分野です。インターン期間中は手法の再現性や各種文献の実装の読解などにお互い苦労しましたが、最終的には代表的な手法を一通り Chainer で実装し、様々なデータやパラメータ、評価指標を広く調査して頂いた蕭さんの今回の成果は、今後の PFN における研究開発にあたっての足がかりとなることと思います。

参考文献

Chainer Chemistryの大規模グラフのタスクへの拡張

Kosuke Nakago

2019-10-01 13:09:08

本記事は、2019年インターンシップで勤務した 阿部健信 さんによる寄稿です。

こんにちは。2019年夏季インターンに参加した東京大学の阿部健信です。「Chainer Chemistryの大規模グラフのタスクへの拡張」というテーマで取り組んだ内容を説明させていただきます。インターン内容のスライドはこちらにアップロードされています。

PFN Summer Internship 2019 / Kenshin Abe: Extension of Chainer-Chemistry for Large and Sparse Graph from Preferred Networks

 

TLDR;

  • Chainer Chemistryで大規模グラフのデータを扱えるようにしました。
  • convolution演算を\( O(V^2) \)から\( O(E) \)にしました。
  • メモリ使用量も抑えて、PyTorch Geometricでは動かないRedditデータセット(23万頂点, 1100万辺)を16GBのsingle GPU上で学習できるようにしました。

 

はじめに

 

graph convolution [1]

画像に対する2D ConvolutionとGraph Convolutionの比較 [1]

入力としてグラフを受け取ることのできる、Graph Neural Network(GNN)という分野が近年注目を集めています。
その注目の高まりから、PyTorch Geometric [2]やDeep Graph Library (DGL) [3]といった高機能で最適化されたGNNライブラリの開発が盛んに進められています。

Chainer Chemistryは、PFNが開発しているGNNのオープンソースのライブラリです。
名前からも分かるとおり、もともと分子など化学データへの適用を目的として作られたもので、qm9などの化学データセットが手厚くサポートされています。一方、他にGNNの研究でよく用いられるSNSなどのネットワークデータのサポートはなされていませんでした。

 

課題内容

今回のインターンのタスクは、Chainer Chemistryでネットワークデータのサポートを行うことです。そのために、大きく以下の2つの内容を行いました。

1. node classificationのサポート
化学分子データなどたくさんの小さなグラフのデータセットに対してGNNは、graph classification/regressionといった、グラフ全体の性質を学習するのに用いられます。
一方、巨大なネットワークデータに対しては、1つのグラフを入力として各頂点ラベルの分類を行うといった異なるタスクに用いられる事が多いです。
[4]で提案されているようなsemi-supervised node classificationへの対応を行いました。
具体的なフレームワークの違いはスライドをご参照ください。

2. 巨大でsparseなグラフのためのGNNの効率的な実装
こちらが今回のインターン内容のメインで、巨大なグラフを動かすためには必要不可欠な内容でした。
以下、\( V \) 個の頂点、\( E \)個の辺からなるグラフを考えます。
Message passingにもとづくGNNでは、各頂点に対して近傍の頂点の特徴量のaggregationの操作を行います。このaggregationの関数はpermutation invariantな様々な関数が用いられ、例えばよく使われるsumの場合は以下の式になります。
\( H’ = AH \)
(\( H \): 頂点の特徴量行列, \( A \): 隣接行列, \( H’ \): aggregateされた特徴量)

既存の実装は全てこの行列演算に基づくものでしたが、これは2つ問題点があります。
1つめは、グラフが疎な際にメモリ的にも実行時間的にも無駄が生じてしまうことです。
2つめは、batch化の際のゼロパディングのオーバーヘッドです。

これらの問題を解決するために、辺の情報を密な隣接行列ではなく、疎なデータ形式で持たせるという事が考えられます。今回のインターンでは、こちらのレポジトリでsparse patternとして紹介されているデータの持ち方を新たに実装しました。
これは辺の情報を\( [2, E] \)のサイズの行列で持つ手法で、PyTorch Geometricでも採用されています。

Sparse patternでは、scatter演算と呼ばれる命令を用いることでaggregation部分の計算量を\( O(E) \)で行うことができます。
またbatch化の際に、複数のグラフを全体として大きな1つのグラフとしてみなすことによってゼロパディングのオーバーヘッド完全になくすことができます。
こちらも、より詳細な手法が知りたい方はスライドをご覧ください。

 

結果

行列演算による既存実装と、sparse patternによる実装の速度比較は以下のようになりました。
まず、3312頂点、4660辺の疎なネットワークグラフに対しては、CPUでは50倍以上、行列演算との相性が良いGPU上でも2倍以上の速度改善が見られました。
また、1つ予想外だったのは、最大でも38頂点という比較的小さなグラフからなる化学データセットに対してもGPU上でも1.5倍程度の速度改善が見られたことです。
これには、バッチ化のオーバーヘッドをなくす工夫が効いていると考えられます。

sparse patternはグラフのconvolution演算に特化して実装されているため速いもののメモリ使用量にまだ無駄があり、Redditデータセット(23万頂点, 1100万辺)を動かすことはできませんでした。
これについては、ChainerのサポートしているCooMatrix演算によるモデルを用いたところsingle GPU (16GB)で動かすことができました。

これまで触れた、既存の隣接行列・sparse pattern・CooMatrixの3パターンについてまとめると、グラフが疎であったりバッチ化のオーバーヘッドが大きかったりすれば基本的にsparse patternが早く、それではメモリが足りない場合はCooMatrixを使うとよい、という結果になりました。
この結果を踏まえて、3つのパターンを場合に応じて使い分けられるように実装しています。
特に、現状のPyTorch Geometricでは動かすことができないredditなどの超巨大なグラフを動かせるという点は、Chainer Chemistryを使うモチベーションの1つになると思います。
新しいモデルを自分で実装したいときに、ChainerでサポートされているCooMatrix演算を普通の行列演算と同じようなインターフェースで直感的に使えるのも魅力です。

 

 

まとめ

今回の成果はChainer Chemistryにマージされています。新しい実装方針に対応しているモデルはまだ多くはありませんが、これからどんどん対応していく予定です。
exampleのコードを動かすことで簡単に巨大グラフ上での学習ができるようになっているので、ぜひ試してみてください。

 

参考資料

[1] https://arxiv.org/pdf/1901.00596.pdf
[2] https://rlgm.github.io/papers/2.pdf
[3] https://rlgm.github.io/papers/49.pdf
[4] https://arxiv.org/pdf/1609.02907.pdf

 

小学生向けディープラーニング体験教室で「ものまね算」のワークショップをしました。

Yuki Nishizawa
エンジニア

2019-06-26 13:33:18

Preferred Networks エンジニアの西澤です。

Preferred Networksでは2019年の6/8(土)に小学校高学年向けのディープラーニング体験教室を行いました。

この体験教室は3部に分かれており、第2部のロボットカーの授業については弊社の丸山史郎よりブログ記事として公開しました。今回は第1部の「ものまね算」の講義について書こうと思います。

 

「ものまね算」

今回、子どもたちに機械学習の概念を説明する時に使用した言葉が「ものまね算」です。機械学習ではたくさんの訓練データを用意し、入力と出力の関係性を学習していきますが、これを機械が「ものまね」していると表現することによって、子どもたちにとってよりイメージの沸きやすい講義ができたのではないかと思います。

 

 

火星語の数字を認識してみよう

ものまね算の講義の後には機械学習の例として「数字認識」を挙げ、実際に機械学習がどのように実社会に応用されるのかを考えてもらった後、「火星語の数字を認識する」というテーマでワークショップを実施しました。

これは、その場で子どもたちに考えてもらった架空の「火星語の数字」についてデータセットを作ってもらい、実際にその場で認識器を訓練し、手描き数字を認識させてみるというものです。みんなで一文字ずつ考えた10個の数字をワークシートに書き、タブレット端末で撮影することによりデータセットとして取り込んで学習します。

 

子どもたちが一体どのような文字を書いてくるのか少し心配していましたが、無事に認識され歓声を上げる子どもたちを見てほっとしました。中にはデータセットにない文字を書いてみる子や複数の字を並べて書いてみる子、点線で書いてみる子などもいて、子どもの柔軟な発想にはこちらも驚かされました。休み時間の間もずっと遊んでくれていたりと、機械学習の技術に興味を持ってくれた子がたくさんいたようでとても嬉しく思います。

 

またPreferred Networksは、2019年9月に文部科学省により実施される「未来の学びプログラミング推進月間」へプログラミング教材「自動化の進展とそれに伴う自分たちの生活の変化を考えよう」を提供予定です。ご興味のある方はぜひこちらも合わせてご覧ください。

https://mirapro.miraino-manabi.jp/lp_pfn.html

小学生向けディープラーニング体験教室でロボットカーの授業をしました。

maruyama
リサーチャー

2019-06-11 13:05:34

こんにちは。丸山史郎です。

Preferred Networksでは2019年の6/8(土)に小学校高学年向けのディープラーニング体験教室を行いました。
体験教室の内容は大きく3つに分かれており、その中の30分くらいの時間でレゴ® マインドストーム®を使ったロボットカーの体験教室を行いました。この直前に「ものまね」算という形で小学生向けのディープラーニング(機械学習)の解説があり、ここではディープラーニングをロボットに使うとどのようなことができるのかということを実際に体験してもらうことを目的としました。

また、弊社としては小学生向けの体験教室は初めてのことでしたので、ロボットの準備やロボット体験の内容へのアドバイス、当日の運営などを株式会社アフレル様にご協力いただきました。

続きを読む »

深層強化学習エージェント可視化ライブラリChainerRL Visualizer公開

ofk
エンジニア

2019-03-19 12:40:11

本記事は、2018年インターンシップを経て、PEとして勤務した石川さんによる寄稿です。

ChainerRLで学習したエージェントの挙動などを, ブラウザ上に可視化するライブラリ「ChainerRL Visualizer」を公開しました.

PFN2018年夏季インターンシップに引き続き, 現在アルバイトをしている東京大学の石川貴大です.

このライブラリは「強化学習のデバッグを容易にする」「強化学習のエージェントの仕組みの理解に貢献する」という思想の元で作られ,
学習済み, 及び学習途中の強化学習のエージェントの挙動をインタラクティブにブラウザ上から観察できるような機能が実装されています.

使い方は非常に簡単で, このライブラリで提供されている launch_visualizer という関数に,
ChainerRLで実装した agent オブジェクトと, 特定のインターフェースを満たした env オブジェクトを, オプションと共に渡すだけです.

from chainerrl_visualizer import launch_visualizer

# Prepare agent and env object here
#

# Prepare dictionary which explains meanings of each action
ACTION_MEANINGS = {
  0: 'hoge',
  1: 'fuga',
  ...
}

launch_visualizer(
    agent,                           # required
    env,                             # required
    ACTION_MEANINGS,                 # required
    port=5002,                       # optional (default: 5002)
    log_dir='log_space',             # optional (default: 'log_space')
    raw_image_input=False,           # optional (default: False)
    contains_rnn=False,              # optional (default: False)
)

実行するとローカルにサーバーが立ち上がり, ブラウザ上から以下のような操作ができます.

1. 指定したステップ分, エージェントをサーバー上で動作させる
サーバー上でエージェントが指定されたステップ分の動作をし, エージェントのモデルの出力が時系列で可視化されます.
以下の動画では, A3Cで学習されたエージェントの Probability of next action と State Value が時系列で出力されています.

2. エージェントを1ステップずつ動作させ, その時の挙動をモデルの出力と共に可視化する
以下の動画では, A3Cで学習されたエージェントを1ステップずつ(前後に)動作させ, その出力をenvの様子と共に観察しています.
右下の円グラフは, 各ステップにおける Probability of next action を表しています.

3. Saliency mapの可視化 (モデルの入力が画像のピクセルデータの場合のみ)
エージェントが画像のどの部分に特に注目して特徴量を抽出しているのかを可視化する機能を, モデルの入力が画像のピクセルデータの場合に特化して実装しました.
この機能は, Visualizing and Understanding Atari Agents を参考にして実装しました.
以下の動画では, CategoricalDQNで学習されたエージェントのSaliency mapを可視化しています.
現時点でSaliency mapを可視化する計算コストがかなり大きいため, Saliency mapを可視化するステップの範囲を指定できるようにしています.

4. その他様々な可視化
エージェントの種類に応じて様々な可視化をサポートしています.
例えば以下の動画では, CategoricalDQNで学習したエージェントの, ステップごとの価値の分布を可視化しています.

とりあえず動かしてみるためのクイックスタートガイドを用意しています.

これまでの多くの可視化ツールは, 「学習の進行に合わせたスコアの表示, およびその他指標の遷移」を可視化することに主眼が置かれていましたが,
ChainerRL Visualizerは, 学習済み, 及び学習途中のエージェントの挙動を動的かつインタラクティブに可視化したという点に, これまでにない可視化ツールとしての特徴があります.

似た思想の元作られたライブラリとしては, Uber Researchによる Atari Zoo が挙げられます.
このライブラリは, 強化学習エージェントを理解するための研究を加速することを設計理念として掲げ, 学習済みモデルと, その学習済みモデルを分析するツールを提供することによって, 計算リソースを十分に持たない研究者に対して, 強化学習エージェントを理解するための研究を促進することを狙っています.
Atari Zooでは, 特定のアーキテクチャ(RawImagePIxel => Conv => Conv .. => FC => FC..)とALEという環境に限定した学習済みモデルと分析ツールを提供しているのに対し, ChainerRL Visualizerでは, ChainerRLで学習したエージェントならばユーザーが学習したどんなエージェントも動的に可視化できるという点で差別化ができています.

また, DQNViz のようにDQNに特化して各種メトリクスを可視化し, DQNのエージェントがどのようなStrategyを学習過程で獲得しているのかを研究しやすいようにサポートするツールの提案も出てきています.

これまで数多くの強化学習の性能向上に関する研究がなされてきましたが, 強化学習のエージェントがどのように環境を解釈し, 何を学習したのかについての研究は比較的少ないものでした.
しかし今後, 以上に見た各種ツールの提案に見られるような強化学習エージェントを理解するための研究が徐々に増えていくことが予想されます.

現在ChainerRL Visuailzerはベータ版であり, エージェントを分析するための機能もまだまだ少ない状態です.
今後発展していくであろう強化学習エージェントを理解するための研究開発に少しでも貢献できるようなライブラリとして進化していけるように, さらなる継続的な開発が必要です.

機能の追加やUX向上に関する調整に関して, OSSなどを通したChainerRL Visualizerへの貢献を歓迎しています.

分散深層学習とモデル並列性

Kota Uenishi

2018-12-21 15:29:18

(本記事は、2016年インターンシップを経て現在はアルバイトとして勤務されている包さんによる寄稿です)

はじめまして。Preferred Networksの分散深層学習チームでアルバイトをしている包です。私は分散深層学習の中でも主にモデル並列に関する機能実装を行っています。今回はモデル並列性の概要と、ChainerMNにおいてどのようにモデル並列性を実現しているのかについて紹介します。

分散深層学習: データ並列性とモデル並列性

深層学習における各種フレームワークは目覚ましい発展を遂げ続けており、最近では一般ユーザーでも簡単に複数GPUを用いたニューラルネットの訓練ができるようになってきました。たとえば、ChainerMNではoptimizerの定義にほんの数行加えるだけでニューラルネットを複数GPUで訓練できます[1]。これにより1024GPU上でImageNetによるResNet-50の学習を15分で行うなどの実績を上げています[2]。このような複数プロセス、複数ノードを用いた分散深層学習によってニューラルネットの訓練は高速に行えるようになっており、分散深層学習は現在の深層学習の基盤を支えているといえます。

ところで、「分散深層学習」にはデータ並列とモデル並列という2通りのアプローチがあることが知られています[3]。データ並列では、全プロセスに同じモデルのコピーして訓練することでバッチサイズをプロセス数倍し、学習を高速化させる手法です。先程お話したImageNetの並列訓練もデータ並列による高速化の一例です。一方でモデル並列とは、1つのモデルを分割して複数のプロセスに配置し、全プロセスで協調して1つのモデルを訓練する手法です。主なユースケースとしては超解像度を入力とするCNNやMixture of Experts[4]など、1プロセス上に載りきらないサイズのモデルを訓練したい場合に用いられます。最近ではMesh-Tensorflow[5]というTensorflow用のモデル並列ライブラリが公開されましたが、現状ではモデル並列をサポートしているフレームワークは非常に少ないです。

この記事では、ChainerMNに実装されているモデル並列APIを、実例を交えて紹介します。特に、Define-by-Runとともにモデル並列を実現する際に発生する問題と、その解決方法について重点的にお話をします。

ChainerMNにおけるモデル並列性の実現

ChainerMNでは、通信をChainerの関数呼び出しによって定義 します。これにより非常に柔軟な通信パターンを実現することができます。

図1: 関数呼び出しによる通信の定義の例

ChainerMNにおける通信はMPIを用いて実現されており、モデル並列でも基本的にMPIの通信スタイルを踏襲しています。MPIでは大きく分けて MPI_Send を始めとした1対1通信と、 MPI_Bcast のような集団通信向けのAPIが提供されています。ChainerMNでは、これらの通信APIと対応するように chainermn.functions.sendchainermn.functions.bcast のように、Chainerの関数を提供しています。通信用の関数は、それぞれbackwardにおいて「勾配を逆向きに通信」するように設計されています。例えば bcast の場合、forward計算ではmasterからslaveに対して入力変数がbroadcast通信されます。一方で、backward計算ではslaveからmasterに対して勾配をallreduceします。

ChainerMNに実装されているforward通信に対応するbackwardの通信パターンは以下のようになります。

表: forward と backward における通信パターンの対応

forward backward
allgather allgather
alltoall alltoall
bcast allgather
gather scatter
scatter gather
send recv
recv send

 

次に、モデル並列APIの具体的な使い方について見ていきます。まず、データ並列の際と同様に、通信を行うためのコミュニケータを作成します。

comm = chainermn.create_communicator()

例えば、図1のようなモデルの実装イメージは次のようになります(図1のモデルに特に意味はありません)。

class ExampleModel(chainer.Chain):
    def __init__(self):
        self.comm = chainermn.create_communicator()
        self.conv = L.Convolution2D(...)

    def forward(self, x):
        x = chainermn.functions.bcast(self.comm, x)
        h = self.conv(x)
        y = F.relu(h)
        ys = chainermn.functions.gather(self.comm, y)
        ...

この例では、masterからブロードキャストされた変数が Convolution2D の入力になります。一方で、backward計算の際には、 Convolution2D の勾配が自動的に Bcast のbackwardによってmasterへ集約されます。

ChainerMNに用意されているAPIの詳細については、ドキュメントを参照してください[6]。なお、モデル並列関連のAPIに関しては現状では実験段階なので、将来的に後方互換でないAPIの変更が起こる可能性があります。

Define-by-Runにおける注意点(その1)

ChainerをはじめとしたDefine-by-Runによる計算グラフの定義はモデルを直感的に記述することができる点で優れているといえます。backward計算時には、出力変数からグラフのバックトラックを行うことによってパラメータの更新を行うことができます。しかし、モデル並列を実現するために上述のように通信を関数として定義すると、計算グラフが正しくバックトラックできない状況が発生します。

例えば、下記のような2つのプロセス間におけるシンプルな1対1通信の例を考えます。

図2: 1対1通信の例

「Process #0」に注目してみると、出力変数 y からバックトラックを行ったときに、 recv から send へ戻ることができません。その結果、「Process #1」は recv のbackward(すなわち勾配のsend)を呼んでいるにもかかわらず、「Process #0」は send のbackward(すなわち勾配のrecv)を呼ぶことができず、デッドロックが発生します。このような状況は、1つのプロセス上における計算グラフが非連結になっているときに生じます。そのため、 send 関数が戻り値として返す特別な変数を recv に渡すことによって、 計算グラフが連結になるようにモデルの定義を行います

図3: delegate variableによる計算グラフの連結化

このような sendrecv を繋ぐような send 関数の戻り値を、便宜的に「delegate variable」と呼ぶことにします。Delegate variableは「Process #0」においてグラフを連結にする役割を果たす他に、「Process #1」でもバックトラックの起点となるダミーの出力変数として振る舞います。図3をコードで記述すると以下のようになります。

class ExampleModel_0(chainer.Chain):
    def forward(self, x):
        # first component
        z = f(x)
        phi = chainermn.functions.send(z, comm, rank=1)

        # second component
        z = chainermn.functions.recv(comm, rank=1, delegate_variable=phi)
        y = h(z)

        return y

class ExampleModel_1(chainer.Chain):
    def forward(self, _):
        z = chainermn.functions.recv(comm, rank=0)
        z = g(z)
        phi = chainermn.functions.send(z, comm, rank=0)
        return phi

Define-by-Runにおける注意点 (その2)

先程の節ではグラフが非連結になると計算グラフのバックトラックができない例を1つ挙げました。このような例は他にも存在します。

 

図4: 1対1通信を2回呼ぶ例

 

図4では、1対1通信が2回発生しています。この場合、「Process #0」における send が返す2つのdelegate variableを適切に処理する必要があります。そこで、以下のように2つの変数を1つにまとめる処理を行います。

 

図5: pseudo_connectを用いた例

 

chainermn.functions.pseudo_connect という関数は、「delegate variableがあたかも別の変数であるかのように振る舞うような変数」を返す関数です。図5の例では、 \( \phi_1 \) というdelegate variableが実際には \( \phi_2 \) という別の変数として振る舞うような変数 \( \psi \) を返します。 \( \psi \) をバックトラックする際には、まず \( \phi_1 \) のバックトラックを行い、次に \( \phi_2 \) のバックトラックを行います。このようにして、backward計算の際に2つのdelegate variableを正しくトラックバックすることができます。図5をコードで記述すると次のようになります。

class ExampleModel_0(chainer.Chain):
    def forward(self, x):
        z1, z2 = f(x)
        phi1 = chainermn.functions.send(z1, comm, rank=1)
        phi2 = chainermn.functions.send(z2, comm, rank=1)
        psi = chainermn.functions.pseudo_connect(phi1, phi2)
        return psi

class ExampleModel_1(chainer.Chain):
    def forward(self, _):
        z1 = chainermn.functions.recv(comm, rank=0)
        z2 = chainermn.functions.recv(comm, rank=0)
        y = g(z1, z2)
        return y

図5では pseudo_connect で2つのdelegate variableをまとめましたが、次の図6のように通常の変数にdelegate variableを結合することも可能です。

図6: delegate variableと通常の変数を結合する例

 

y_ = chainermn.functions.pseudo_connect(phi, y)

以上がChainerMNにおけるモデル並列の概要になります。次に、実際のモデルの例を見てみます。

1対1通信を用いた例: encoder-decoderモデル

Encoder-decoderモデル[7]は可変長の入力を可変長の出力に変換することを目的としたモデルで、自然言語処理をはじめとした応用分野で広く用いられています。
Chainerのexampleにも機械翻訳の例があります[8]。Encoder-decoderモデルの入力や出力に画像を用いるようなモデルの場合、CNNをencoderやdecoderに用いることになりますが、層数やパラメータ数が膨大なencoderやdecoderになると、全体のモデルが1GPUに載らないケースが発生します。その場合、モデルをいくつかに分割して複数プロセスでモデル並列学習を行うことによって学習できます。例えば、下図のようにencoderとdecoderにそれぞれ1プロセスずつ割り当てるような分割が考えられます。

図7: encoder-decoderのモデル並列化

 

ここでは、はじめのプロセスでencodeしたcontext vectorをdecoderへ送信して、decoder側のプロセスでdecodeするように分割を行っています。例えばLSTMの場合はcontext vectorが2つあるので、2回の1対1通信を行うことで実現できます。ただし、図5の例と同様に、encoder側では pseudo_connect を用いてdelegate variableを1つにまとめる必要があることに注意してください。基本的には sendrecvpseudo_connect を用いれば実装することができますが、encoder-decoderモデルの分割は実装が煩雑になるので、専用のLinkを用意しています。

rnn = chainermn.links.create_multi_node_n_step_rnn(
        L.NStepLSTM(n_layers, n_units, n_units, 0.1),
        comm, rank_in=None, rank_out=1)

create_multi_node_n_step_rnn は、Chainerで提供されている NStepRNN [9](可変長系列をまとめて入出力するAPI)をラップして、内部で別のプロセスと自動的にcontext vectorを送受信します。rank_in に指定したプロセスからcontext vectorを受信し、 rank_out に指定したプロセスに対してcontext vectorを送信します。これを用いると、次のようにモデル並列なencoder-decoderモデルを簡単に実装することができます。

class Encoder(chainer.Chain):
    def __init__(self, comm, n_layers, n_units):
        super(Encoder, self).__init__(
            # Corresponding decoder LSTM will be invoked on process 1.
            mn_encoder=chainermn.links.create_multi_node_n_step_rnn(
                L.NStepLSTM(n_layers, n_units, n_units, 0.1),
            comm, rank_in=None, rank_out=1
            ),
        )
        self.comm = comm
        self.n_layers = n_layers
        self.n_units = n_units

    def forward(self, *xs):
        exs = f(xs)
        c, h, _, phi = self.mn_encoder(exs)
        return phi

class Decoder(chainer.Chain):
    def __init__(self, comm, n_layers, n_units):
        super(Decoder, self).__init__(
            # Corresponding encoder LSTM will be invoked on process 0.
            mn_decoder=chainermn.links.create_multi_node_n_step_rnn(
                L.NStepLSTM(n_layers, n_units, n_units, 0.1),
            comm, rank_in=0, rank_out=None),
        )
        self.comm = comm
        self.n_layers = n_layers
        self.n_units = n_units

    def forward(self, *ys):
        c, h, os, _ = self.mn_decoder(ys)
        ...

この例はChainerMNのexampleに公開されています[10]。

集団通信を用いた例: チャネル方向の並列化

集団通信を用いると、下図のようにCNNのチャネル方向の並列化が実現できます。この並列化は高解像度画像を扱う際や、バッチサイズを大きくする際に有用です。

図8: チャネル方向の並列化

 

各プロセスはCNNのチャネルのうち一部だけを入力としてとって畳み込みを行うので、各々のプロセス上のCNNのパラメータ数を減らすことができます。CNNの出力に対して “allgather“ を用いることで、全チャネルを集約することができます。実装のイメージは以下のようになります。

class ParallelConvolution(chainer.Chain):
    def __init__(self, comm, in_channels, out_channels):
        self.comm = comm
        self.in_channels = in_channels
        self.out_channels = out_channels
        self.conv = L.Convolution2D(...)

    @property
    def _indices(self):
        # index % comm.size == comm.rankとなるインデックスのチャネルを担当
        # 例 (size=4, rank=1の場合): _indices = [1, 5, 9, ...]
        idx = numpy.arange(self.in_channels)
        return idx[idx % self.comm.size == self.comm.rank]

    def forward(self, x):
        # 当該プロセスの担当チャネルをスライス
        x = x[:, self._indices, :, :]

        y = self.conv(x)

        # 全チャネルを集約
        ys = chainermn.functions.allgather(self.comm, y)
        return F.concat(ys, axis=1)

この例はchainerMNのexampleに公開されています[11]。

まとめ

本記事では、ChainerMNにおけるモデル並列の実現と、実際の例をいくつか紹介しました。特に、Defined-by-Runの下では計算グラフが連結でなければならないため、delegate variableやpseudo_connectなどのテクニックが必要になります。今回はスペースの都合で紹介がかないませんでしたが、特定のタイプのモデル向けによりシンプルにモデルを定義できるようなAPI( MultiNodeChainList, MultiNodeNStepRNN)も用意されているので、お手軽に試してみたい方はぜひドキュメント[6]をご覧ください。

参考文献

DNN推論用ライブラリ「Menoh」リリースについて

Shintarou Okada
エンジニア

2018-06-21 11:41:46

Python以外も使いたくないですか?  特にDeepLearning界隈で.

Menoh開発者の岡田です.この記事ではMenohの紹介と開発に至った動機について説明します.

Menohのレポジトリ: https://github.com/pfnet-research/menoh

Menoh(メノウ)は学習済みのDNNモデルをONNX形式から読み込んで動作させる推論専用のライブラリです.実装はC++で書きましたが,C言語のインターフェースを持たせて,他の言語用からもその機能を呼び出しやすくしてあります.リリース時点でC++版ラッパーとC#版ラッパー,Haskell版ラッパーがあり,Ruby版ラッパーとNodeJS版ラッパー,Java(JVM)版ラッパーが開発中です.バックエンドにはIntelの開発しているMKL-DNNを採用し,GPUが無くてもIntel CPUが使える環境で高速にモデルの推論が可能になっています.Menohを使えばChainerで学習したモデルをPython以外の言語で実装したアプリケーションに瞬時にデプロイすることが可能です.

ところでなぜDeepLearning界隈で覇権を握ったのがPythonであって,Rubyじゃなかったんでしょう? Rは? Perlは? C++は? プログラミング言語は数多くありますが,どの言語も今のPythonのように広くDL用学習フレームワークを記述するために利用される可能性はありました(もちろん言語ごとに用途の向き不向きがあって,可能性の大小はあったにせよ).我々の宇宙ではPythonが覇権を握りましたが,どこか別の宇宙ではLispが覇権を握ることもきっとあったでしょう.とは言え,我々は我々の宇宙に生きるしかなく,今日そのディープなんとかを実装するには甘美な()や{}やbeginendから離れて空虚なインデントでブロックを記述する必要があります.このことについて,なんと悲しいことかと手放しに言い切れれば良かったんですが,皆さんご存知の通り,Pythonは良い言語です.

そう,Pythonは良い言語です.豊富なライブラリ,特にNumpyが使えること,動的型付けであること,GC機能が搭載されていること――どれもがDNNを実装したり学習させたりするコードを試行錯誤しながら書くという作業をやりやすくしてくれます.もちろんChainerはPythonで記述されており,改造・拡張しやすいDNN学習フレームワークとなっています.ChainerはそのDefine-by-Runという魔法によってすばらしく使いやすい.このDefine-by-Runをもっと別の言語で実装することも出来たと思いますが,コードはより複雑に,その作業はもっと苦痛の伴うものになっていたでしょう.明らかにChainerの使い勝手の良さの一端はPythonという言語そのものが担っています.

我々にとって,DNNについて研究する作業というのは地獄ではありません.Pythonの使い勝手の良さに裏打ちされたChainerがあるからです.手軽にDNNモデルを記述して学習を回せる.素晴らしいことです.地獄なのは学習したDNNモデルをデプロイする作業です.

地獄というのは言いすぎかもしれません.Pythonがデプロイ先の環境で使えるならChainerをそのまま使えばよく,首尾貫徹して苦痛はどこにも(少なくともデプロイ作業には)ありません.でもPythonが使えない環境はどうでしょうか.研究室の外に出ると,セキュリティや計算資源的な問題などでPythonが使えない環境や,分野によっては別の言語が覇権を握っていてPythonではそこにある資産を利用できない状況というのは山ほどあります(例えば,Web界隈では今でもRubyに根強い人気があります).現在でもDLフレームワークの中には設計がデプロイまで意識されたものや,Pythonを使わずにCやC++などでDNNを記述できるものもありますが,大掛かりだったり,あまりに実装が剥き身すぎて使いづらかったりします.現状はDNNの学習については広く知見が行き渡っているのと比べて,まだまだDNNのデプロイについては発展途上であると言えます.

ただ学習したモデルを自分のアプリケーションに組み込みたいだけなのにそれがなかなか難しい.

以上が私がMenohの開発を始めた動機です.

MenohはPFNが社内で定められた20%ルールの下でのプロジェクトの成果です.20%ルールとは「PFNメンバーは公式にアサインされたタスクとは別に20%の時間を各自の好きなタスクやプロジェクトに充てても良い」というもので,Menohプロジェクト以外にも様々な個人やチームのプロジェクトや勉強会が進行しています.

MenohはChainer Advent Calendar 2017で開発した「Instant」というライブラリが元になっています.20%の時間を使って,Instantの機能を拡充していく中で,設計の助言をしてくれたり,他の言語のラッパーを書いてくれたりするメンバーが現れて,そうした自発的に協力してくれたメンバー達のお陰でInstantはMenohに名前を変えて実験的なプロダクトとしてpfn-researchにてリリースするに至りました.これからも20%の時間を使って開発は継続していく予定なので,ぜひ利用してもらって,バグや要望等あればどんどんIssueに投げていただければと思います.

オープンソースの深層学習フレームワーク Chainer アマゾン ウェブ サービスが公式にサポート

Shingo Omura

2018-06-01 12:02:14

深層学習フレームワークの Chainer は、アマゾン ウェブ サービス(AWS) の協力により、多数の AWS アプリケーションで利用できるようになりました。Chainerは、ニューラルネットワークを簡単に扱える Pythonのフレームワークですが、AWSと組み合わせる事で、マルチ GPU やマルチサーバーにおける Chainer の並外れたスケーリング能力を最大限活用できます。Chainer の非常に優れたスケーリング能力については、ImageNet-1K を利用した ResNet50 の学習を、それまで最速とされた Facebook の記録より4倍速い15分で完了した事により実証済みです。

Chainer のマルチ GPU とマルチサーバーのスケーリングにより、必要時に必要量の計算資源を提供するというクラウドの利点を有効活用できます。Chainer の比類なき並列計算能力と AWS のオンデマンド型クラウド資源を併用すれば、費用を最小限に抑えながら、ハードウェアの制約がある環境下と比べて、非常に短時間で複雑な深層学習モデルの学習が可能になります。

Chainer は、AWS 深層学習 AMI(AMI)ですでに利用可能となっていますが、Chainerが最新の CloudFormation スクリプトをリリースした事により、一度に複数のChainer AMIを容易にデプロイできるようになりました。また、ChainerはAWS上で32 GPUまでのスケーリング効率95%を達成する事を確認済みで、これはニューラルネットワークの学習を最大30倍高速化できる事を意味します。

データの前処理やハイパーパラメータの調整、ならびにニューラルネットワークのデプロイといった作業の簡素化を目的として、Chainer は Amazon SageMaker でもサポートされるようになりました。Amazon SageMaker は、開発者やデータサイエンティストが、機械学習モデルをあらゆる規模で、迅速かつ簡単に構築、トレーニング、デプロイできるようにする完全マネージド型プラットフォームです。SageMaker で Chainer を使用すれば、SageMaker が持つデプロイ上の利点に加え、並列化により速度が向上します。

上記に加えて、Chainer は AWS Greengrass でもサポートされるようになりました。AWS Greengrass は、接続されたデバイスでローカルのコンピューティング、メッセージング、データキャッシュ、同期、ML 推論機能を安全な方法で実行できるようにするソフトウェアです。Amazon SageMaker と組み合わせる事で、SageMaker でのモデル学習時や、AWS Greengrass でIoTデバイスへ直接デプロイする際に、Chainer の利便性とスピードを活用できます。

Chainer チームは AWS による今回のリリースを大変うれしく思うと同時に、進化し続ける深層学習技術のさらなる発展に貢献する事を目指します。

AWS CloudFormationを使ったChainerMNの実行

Shingo Omura

2018-06-01 11:58:13

※本記事はChainer Blogの日本語訳です。

AWS CloudFormationInfrastructure As Code の実践を助けてくれるAWSサービスで、幅広いAWSリソースを、宣言的に設定ファイルに記述し、その設定ファイルから、AWS上のインフラストラクチャを自動的に生成したり、再生成できます。それによってAWS上のインフラストラクチャを手作業を自動化できます。

分散深層学習向けのインフラストラクチャの構築の作業負荷も大幅に軽減できます。EC2インスタンスを起動したり、必要なライブラリをインストール・設定したり、計算/通信性能の最適化設定を行ったりする作業を軽減できます。特に、ChainerMNにおいては、MPIクラスタを構築することが必要です。AWS CloudFormationはこの構築作業を自動化することができます。

本日、Chainer/ChainermNがプリインストールされたAMIChainerMN向けのクラスタを自動的に構築するためのCloudFormationテンプレートを公開しました。

この記事では、これらの利用方法とともに、自動構築されたクラスタ上でのChainerMNの実行方法について説明します。

 

Chainer AMI

Chainer AMI には Chainer/CuPy/ChainerMN, ChianerCV, ChainerRLといったChainerライブラリ群と CUDA-aware OpenMPI ライブラリがプリインストールされていいます。そのため、このイメージを利用すればGPUを搭載したAWS EC2 インスタンス上で簡単に高速に深層学習が実行できます。このイメージはAWS Deep Learning Base AMIを基にビルドされています。

Chainer AMIの最新バージョンは0.1.0で、同梱されているライブラリ群のバージョンは次のとおりです:

  • OpenMPI 2.1.3
    • CUDA 9向けにのみビルドされています
  • Chainerライブラリ群 (python, python3 両方の環境にインストールされています)
    • CuPy 4.1.0
    • Chainer 4.1.0
    • ChainerMN 1.3.0
    • ChainerCV 0.9.0
    • ChainerRL 0.3.0

 

CloudFormation Template For ChainerMN

このテンプレートは Chainer AMI を使って ChainerMN クラスタを自動的にAWS上に構築します。構築されるインフラストラクチャの概要は下記のとおりです。

  • クラスタが配置される VPC と Subnet (既存のVPC/Subnetを設定可能)
  • MPIプロセス起動時に利用するephemeralなssh鍵をクラスタ内で共有するための S3 バケット
  • クラスタが配置されるプレイスメントグループ(ネットワークスループット/レイテンシを最適化するため)
  • ChainerMNクラスタ
    • 1 台の マスター EC2 インスタンス
    • N (>=0) 台のワーカーインスタンス(AutoScalingGroup経由で起動されます)
    • MPIジョブ実行用の chainer ユーザ
    • MPIジョブ実行用の hostfile
  • (オプション) Amazon Elastic Filesystem (既存のFilesystemを設定可能)
    • このファイルシステムは各インスタンスに自動的にマウントされれます
  • 各種Security Group および IAM Role

Chaainer CFNの最新バージョンは0.1.0です。詳細なリソース定義についてはリリースされているテンプレートを参照してください。

先日公開した ChainerMN 1.3.0のブログで述べられている通り(英語)ChainerMN 1.3.0 の新機能である、ダブルバッファリング、 半精度浮動小数点数による All-Reduce を使えば、Infinibandが利用できないAWSであっても、ほぼ線形のスケーラビリティを期待できます。

 

CloudFromationテンプレート を使った ChainerMNクラスタ構築

ここでは、CloudFromationテンプレートを使ってChainerMNクラスタを構築する方法をstep-by-stepで説明します。

まず、下記のリンクから CloudFormationのページを開いて「次へ」をクリックしてください。

launch stack

「詳細の指定」画面では、スタック名、VPC/Subnet、クラスタ、EFSといった各種設定を入力できます。下記は 4 台の p3.16xlargeインスタンス(8 NVIDIA Tesla V100 GPUs/インスタンス) によって構成される ChainerMNクラスタを構築する設定例です(VPC, Subnet, EFS等はすべて新規作成)。

最後の確認画面では、Capabilities セクションにある、IAM Roleに関する同意をチェックする必要があります。この CloudFormation テンプレートではクラスタ内インスタンスに割り当てるためのIAM Roleを作成するためです。

しばらして、CloudFormationスタックの状態が CREATE_COMPLETE に遷移したら、クラスタは構築完了です。 スタックの出力セクションの ClusterMasterPublicDNS にマスターインスタンスの Public DNS が表示されます。

 

構築済みクラスタでのChainerMN ジョブ実行

クラスタ内インスタンスには、CloudFormationテンプレートのパラメータに設定したキーペアでログインが可能です。

ssh -i keypair.pem ubuntu@ec2-ww-xxx-yy-zzz.compute-1.amazonaws.com

クラスタ内のインスタンスは前述の [Chainer AMI][ChainerAMI] をつかって起動しているので、必要なライブラリ群はすべてインストール済みです。あとは、自身の学習用のコードとデータをダウンロードするだけです。

# switch user to chainer
ubuntu@ip-ww-xxx-yy-zzz$ sudo su chainer

# download ChainerMN's train_mnist.py into EFS
chainer@ip-ww-xxx-yy-zzz$ wget \
  https://raw.githubusercontent.com/chainer/chainermn/v1.3.0/examples/mnist/train_mnist.py \
  -O /efs/train_mnist.py

これで実行準備完了です。mpiexecコマンドを使ってChainerMNをつかったMNISTの例を実行できます。

# 4インスタンス上で合計32プロセス(-nオプション)を起動(8 プロセス/インスタンス(-N オプション))
chainer@ip-ww-xxx-yy-zzz$ mpiexec -n 32 -N 8 python /efs/train_mnist.py -g
...(you will see ssh warning here)
==========================================
Num process (COMM_WORLD): 32
Using GPUs
Using hierarchical communicator
Num unit: 1000
Num Minibatch-size: 100
Num epoch: 20
==========================================
epoch main/loss validation/main/loss main/accuracy validation/main/accuracy elapsed_time
1 0.795527 0.316611 0.765263 0.907536 4.47915
...
19 0.00540187 0.0658256 0.999474 0.979351 14.7716
20 0.00463723 0.0668939 0.998889 0.978882 15.2248

# 注意: 上記実行例は2回めの実行例です。(初回実行時はサンプルデータのダウンロードが行われます)

 

 

ChainerMNのクラウド環境向け新機能とAWSにおける性能評価

Shuji Suzuki

2018-05-25 17:00:23

※この記事はChainer Blogの抄訳です

Chainer にマルチノードでの分散学習機能を追加するパッケージであるChainerMN に、ネットワークスループットが低いシステム向けの以下の2つの機能をv1.2.0とv1.3.0で追加しました。

  • Double bufferingによる通信時間の隠ぺい機能
  • 半精度浮動小数点数(FP16)によるAll-Reduce機能

ChainerMNは高速なネットワークを持つスーパーコンピュータやMicrosoft Azureのようなシステムを想定して開発してきたため、高速なネットワークのない環境では高い並列性能を達成するのが難しいという問題がありました。しかし、これらの機能を使うことで、GTC2018で発表したようにAmazon Web Services (AWS)のような一般的なシステムでもChainerMNによって高い並列性能を達成することができるようになりました。

 

背景

データ並列による分散深層学習においては、学習時間のうちノード毎に計算したgradientの和を計算するAll-Reduceにかかる時間が支配的になります。以前、我々が実施した1024 GPUを利用した大規模な学習では、スーパーコンピュータでも利用される高速なインターコネクトであるInfiniBandと高速なAll-Reduceを実現可能なNVIDIA Collective Communications Library (NCCL)を利用することで解決しました [1]。一方、AWSのような一般的なシステムはInfiniBandのような高速なインターコネクトがないため、通信に時間がかかってしまいます。この結果、ノード数を増やしても学習が速くならない場合がありました。これらを解決するためにChainerMN v1.2.0, v1.3.0でdouble bufferingによる通信時間の隠ぺい機能とFP16によるAll-Reduce機能の2つを追加しました。

 

Double bufferingによる通信時間の隠ぺい機能

この機能は、計算(foward, backward, optimize)と通信(All-Reduce)をオーバーラップすることで通信にかかる時間を隠ぺいし、全体の計算時間を短縮する機能です。通常のChainerMNの場合、1イテレーションは下図のように forward, backward, All-Reduce, optimize の 4 つのステップからなります。

一方、double bufferingによる通信時間の隠ぺい機能を使うと以下のように計算の部分と 通信の部分をオーバーラップすることができます。

この際、optimizeには一つ前のイテレーションのgradientを利用して行います。これにより、精度に影響がでる可能性が考えられます。しかし、後ほど示す実験の通り、ImageNetの学習の場合、実際はほとんど精度を低下させずに学習を行うことができます。

この機能は以下のようにマルチノード用のoptimizerを作成していた部分で、double_buffering=Trueとするだけで使用できます。

optimizer = chainermn.create_multi_node_optimizer(optimizer, comm, double_buffering=True)

現在、この機能はpure_ncclのcommunicatorにのみ対応しています。

FP16によるAll-Reduce機能

v1.2.0のChainerMNはFP32のAll-Reduceにのみ対応していましたが、v1.3.0ではFP16にも対応しました。これにより、FP16のモデルでもChainerMNを利用して分散学習を実行することができるようになりました。All-ReduceにFP16を用いた場合、FP32のときと比較して、通信量が半分になるため、All-Reduceの大幅な時間短縮を期待できます。

また、計算においてFP32を使っていても、All-ReduceだけはFP16を利用し、All-Reduceにかかる時間を短縮するということも同時にできるようになりました。これは我々が1024 GPUを利用したImageNetの学習で利用したテクニックです[1]。

FP16のモデルの場合は特に変更を加えなくても、FP16のAll-Reduceが利用されます。また、計算とAll-Reduceで違うデータタイプを使用したい場合は、以下のようにcommunicatorを作成する際に、allreduce_grad_dtype='float16'とすることで利用することができます。

comm = chainermn.create_communicator('pure_nccl', allreduce_grad_dtype='float16')

この機能も現在はpure_ncclのcommunicatorにのみ対応しています。

実験結果

2つの新機能により、高い並列性能を得られることを示すために、ImageNet の画像分類データセットを使って性能を測定しました。CNN のモデルとしては ResNet-50 を使いました。実験には、低速なネットワークとしてPFNのスーパーコンピュータであるMN-1の10 Gb Ethernetと、AWSを利用しています。詳しい実験の設定については記事末尾の付録をご覧ください。

10Gb Ethernetによる評価

下記の図では、MN-1を利用し、InfiniBand FDRと10 Gb Ethernetを使った場合、さらに10 Gb Ethernet上で2つの新機能を使った場合の3通りで、GPU 数を増やしたときのスループットを示しています。

この図に示す通り、10 Gb Ethernetを使った場合、GPU数が増えても性能が上がっていないことがわかります。一方、新機能を使った場合は、線形に性能向上が得られており、理想的な高速化が達成できています。また、下記の表に32GPUを使って90 epochまでの学習を5回実行した際の平均validation accuracyと平均学習時間を示します。

Validation Accuracy (%) 学習時間 (hour)
InfiniBand FDR 76.4 6.96
10 Gb Ethernet 76.4 21.3
10 Gb Ethernet + Double Buffering
+ FP16 Allreduce
75.8 7.71

精度に関しては、2つの新機能を使ってもほとんど影響が出ていないことがわかります。また、学習時間については10 Gb Ethernetと2つの新機能を使った場合には、InfiniBand FDRを使った場合に比べて11%しか計算時間が増加しませんでした。このことから、2つの新機能を使うことで、InfiniBand のような高速なネットワークを利用しなくても、精度を維持したまま高い並列性能を得られることがわかりました。

AWSによる評価

AWSの検証ではp3.16xlargeを利用しました。このインスタンスは最新のGPUであるV100を8個搭載しています。このインスタンスを利用してGPU 数を増やしたときのスループットを以下の図に示します。

どれだけ並列性能がでているかの指標として並列化効率が良く用いられます。今回の実験の場合、基準となるスループットを\(t_0\)、基準から\(n\)倍のGPUを使用したときのスループットを\(t\)とすると、並列化効率\(e\)は以下のように計算できます。
$$e = t/(t_0*n)$$
この並列化効率が1(100%)に近いほど高い並列性能を達成していることを示しています。

この実験においては、8GPUを基準とした32GPUを利用した場合の並列化効率は96%となり、新機能を使うことにより高い並列化性能を達成できることがわかります。

今後の展望

今後、ChainerMNは多くの学習モデルにおいてデータ並列では実現できない多様なモデルの並列化に対応するために、モデル並列をはじめとした機能や、耐障害性を向上するための機能を追加していく予定です。また、我々のチームではChainerMNの開発だけでなく, ChainerとCuPyを含めた高速化、P100を1024機搭載したMN-1やV100を512機搭載した次のクラスタを全台使うような大規模実験に関する研究開発を行っています。このような分野に興味のある方のご応募お待ちしております。

付録

性能測定の詳細について

実験設定

データセット:ImageNet-1k
モデル:ResNet-50 (入力画像サイズ 224×224)

スループット測定時の設定

バッチサイズ:64
学習率:固定
Data augmentation:Goyal et al. [2]と同じ手法を利用
最適化:Momentum SGD (momentum=0.9)
Weight decay: 0.0001
測定回数:400イテレーション

90エポックまで学習させたときの設定

バッチサイズ:30エポックまで各 GPU が 64、その後、128
学習率:5エポックまでGradual warmup、30 エポックのとき0.2 倍、60エポック、80エポックで0.1倍
Data augmentation:Goyal et al. [2]と同じ手法を利用
最適化:Momentum SGD (momentum=0.9)
Weight decay: 0.0001
エポック数:90 エポック
この設定は基本的にGoyal et al. [2]をベースに、Smith et al. [3]のテクニックを利用した設定になっています。

10Gb Ethernetによる検証に使用した実験環境

実験環境
最大4ノード、計 32 GPUs
ノード
GPU: 8 * NVIDIA Tesla P100 GPUs
CPU: 2 * Intel Xeon E5-2667 processors (3.20 GHz, 8 cores)
ネットワーク: InfiniBand FDR
学習データの保存先:ローカルディスク

AWSによる検証に使用した実験環境

実験環境
最大4ノード、計 32 GPUs
ノード(p3.16xlarge)
GPU: 8 * NVIDIA Tesla V100 GPUs
CPU: 64 vCPUs
ネットワーク: 25 Gbps のネットワーク
学習データの保存先:RAM disk

References

[1] Akiba, T., et al. Extremely Large Minibatch SGD: Training ResNet-50 on ImageNet in 15 Minutes. CoRR, abs/1711.04325, 2017.
[2] Goyal, P., et al. Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour. CoRR, abs/1706.02677, 2017.
[3] Smith, S. L., et al. Don’t Decay the Learning Rate, Increase the Batch Size. CoRR, abs/1711.00489, 2017.