プログラミング教育推進月間の教材について

Hiroshi Maruyama

2019-02-18 18:00:37

PFNフェローの丸山です。2月18日に、PFNは文部科学省、総務省及び経済産業省の「未来の学び プログラミング教育推進月間」に協力して、小学校向けのプログラミング教材を作成することを発表しました。この教材は、今年の9月に一部の小学校で、総合学習の一環として利用されることを目指しています(指導案のページはこちらです)。この記事では、私達PFNがなぜこのような活動をしているかをお話ししたいと思います。

PFNは若い会社です。多くの若い社員が次々に家庭を持ち、子どもを育て始めています。社長の西川をはじめ皆、次の世代が活躍し、よりよい社会を作っていくことを強く願っています。これからの社会を考えたとき、マーク・アンドリーセンが「ソフトウェアが世界を侵食する」(Software is eating the world) と言ったことに象徴されるように、社会の価値の多くが情報技術、特にソフトウェアから得られることは明らかです。ですから、私達の次の世代の誰もが、小さい頃から情報技術に親しみ、プログラミングの楽しさを知る機会を持てることは、私達にとっても大変喜ばしいことです。そんな若い人たちが、ソフトウェアに興味を持ち、将来社会のあらゆる場で活躍している、そんな社会の実現をPFNは願っています。

新しいスタイルのプログラミング

同時に私達は、技術の進歩によって、今までとは違う新しいスタイルのプログラミングが現れつつあることも強く認識しています。簡単にするため、プログラムとはある入力Xに対して出力Yを計算する箱であると考えます。

このようなプログラムを作る1つの方法は、計算のルールを書き下すことです。たとえば、商品の価格を入力としてその消費税を出力するプログラムを考えましょう。このようなプログラムは、入力Xに対して「X の0.08倍を計算してYとせよ」 というルールで表現することができます。これが普通のプログラムの作り方で、ここではルールによるプログラミングと呼ぶことにしましょう。

しかし、深層学習という新しい技術によって、別の形のプログラミングが現れてきました。こんな例を考えてみましょう。人とじゃんけんをする機械を作るために、カメラで撮った手の画像を入力として、それがグー・チョキ・パーのいずれであるかを判定して出力するプログラムを作りたいとします。

このようなプログラムを、ルールによるプログラミングで作るのは容易ではありません。カメラで撮影した画像は画素が格子状に並んだものですが、ある画素が肌色だからこれはチョキだ、と判断するわけにはいきません。同じチョキでも位置や角度や光源が違ったりして毎回異なる画像になるからです。

そこで、深層学習では、様々に異なるチョキの画像をプログラムに見せて「これはチョキだよ」と教えていくことによってプログラミングを行います。これを例示によるプログラミングと呼ぶことにします。例示によるプログラミングでは、ルールを書くのが難しいようなプログラムも作ることができます。

一方で、例示によるプログラミングでは、例示に使うデータによっては、必ずしも毎回正解が出ないかもしれません。あるいは例示に偏りがあった場合には、偏りのある結果が出るかもしれません。これは、ルールによるプログラミングが(プログラムに誤りが無い限り)常に正しい答えを出すこととは対照的です。

将来のソフトウェア

例示によるプログラミングは、ここ10年ほどの、深層学習の急速な発展によって可能になってきていて、今までのルールによるプログラミングでは難しかった画像認識、音声認識、機械翻訳などに応用されつつあります。これは、1950年代にデジタル計算機が発明されて以来の最大のパラダイムシフトかもしれません。おそらく、将来のソフトウェアの多くは、ルールによるプログラミングと例示によるプログラミングを組み合わせたハイブリッドなものになるでしょう。

私達が今回の「未来の学び プログラミング教育推進月間」にご協力している真の理由はここにあります。今の子供達が大人になって、社会におけるソフトウェア開発の一翼を担うころには、例示によるプログラミングが広く使われていることは間違いありません。ルールによるプログラミングが苦手でも、例示によるプログラミングは得意だ、という子どももいるでしょう。

「プログラミング」を狭く捉えないで、いろいろな考え方があるのだ、ということを知ってほしい、それが私達の願いです。

 

インターン参加報告:Concolic Testing による SystemVerilog 向けテストパターン生成

Masahiro Sakai

2018-12-25 12:00:56

本記事は、2018年インターンシップに参加された押川さんによる寄稿です。


こんにちは。2018年夏季インターンシップに参加していた東京大学の押川広樹です。大学では定理証明支援系やモデル検査を用いたソフトウェアの検証について研究しています。

インターンでは、PFN で開発中のハードウェアに対して「良い」テストを実行することが目標でした。「良い」テストの基準は色々ありますが、今回は効率的で効果的なテストケースを生成することを目指しました。ここで言う効率的なテストとは、短い時間で実行できるように出来るだけサイズの小さいテストセットのことです。また、効果的なテストとはプログラムのより多くの部分を検査できるカバレッジの高いもののことです。

今回はそのようなテストケースを Concolic Testing と呼ばれる手法に基づいて生成することを試みました。

Concolic Testing

まず、テストというとランダムテストが考えられます。ランダムテストは手軽ですが効果的だとは言えません。例えば以下のようなコードを考えてみます。

int x, y;
if (x == y)
  if (x == 10)
    fail;
  else
    ...
else
  ...

上の例は xy が共に 10 の時だけバグがあるプログラムを表します。ランダムテストによってバグを見つけられる確率は単純計算で (int が 32 ビットの時) 232 × 232 回に一回です。

このためランダムテストはコーナーケースになるような微妙な部分のテストには向いておらず、高いカバレッジをとるためにはもう少し賢くテストケースを作る必要があります。その一つの例が Concolic Testing と呼ばれる手法です。Concolic は Concrete と Symbolic を組み合わせた言葉で、Concolic Testing とはプログラムの実際の実行とシンボリックな実行を交互に行うテスト手法です。

Concolic Testing では次の手順でテストを進めて行きます。

  1. ランダムな入力を生成する
  2. 入力に対して プログラム を実行する
  3. 実行時にプログラムのどの部分が実行されたかを記録しておく
  4. 3. の情報を元にその部分が使われるための条件を計算する
  5. 4. の条件の一部を変更し、プログラムの別の部分が実行されるための制約を得る
  6. 5. の制約を論理ソルバーで解くことで、実際にその部分を使うような入力を得る
  7. 2.-6. を繰り返す

各イテレーションごとにプログラムの新しい部分を実行するような入力が得られるので、理想的にはラインカバレッジ100%のテストケースを生成することが出来ます。

ランダムテストと同じ例でどのように動作するかをみてみます。
プログラムは if 文などで分岐する木だと思えるので、上の例は次のように表せます。

まず、xy の値をランダムに生成します。例えば x = 1163551168y = 1363922006 だとします。この下でプログラムを実行すると以下のようなパスを通ります。

このパスを通ったのは、x == y が成り立たなかったからです。したがって制約としてこの条件の否定、つまり x != y を考えます。これを満たすような xy をソルバーを使って求めます。
例えば x = 0y = 0 はこの制約を満たします。次はこれを入力としてプログラムを実行します。次のようになります。

確かに1度目の実行とは異なる部分が実行されています。先ほどと同様、このパスを通ったことから、「x == y かつ x != 10」が成り立つとわかります。x != 10 を否定した制約、「x == y かつ x == 10」をソルバーを用いて解くことで次の入力 x = 10y = 10 を得ます。そして、x = 10y = 10で実行することでちゃんと fail を見つけることができます。

Concolic Testing は Godefroid らによる DART (Directed Automated Random Testing) [1] によって導入されました。[1] では Concolic Testing の手続きの導入と、Cで書かれたプログラムに対しての実装の実験がなされています。

上で説明した Concolilc Testing の手続きを素朴に行うと、対象のプログラムが大きくなるにつれてたどる必要のあるパスの数が爆発するためスケールしません。また、生成される制約が大きくなりすぎてソルバーで制約が解消できなくなる可能性もあります。

そのため実用上は、できるだけ早くカバレッジを増やすようにパスをたどったり、生成される制約を小さくする最適化を加えることで、実際のアプリケーションにも適応できるように改良されています。実際、DART にこのような最適化を加えたものに SAGE [2] があります。SAGE は X86 アセンブリを対象にしており、Microsoft で Office のバグを見つけるのに使われたようです。

また、対象をハードウェア設計言語にしたものに HYBRO [3] があります。HYBRO は Verilog を対象とします。

やったこと

今回は SystemVerilog で記述されたプログラムに対して Concolic Testing を行うフレームワークを作成しました。実装時間の都合で、受け入れられるプログラムの種類は限られていています。具体的には、ある程度制限された SystemVerilog の機能を用いて書かれた組み合わせ回路になります。例えば、interface が使われていないことなどを仮定しています。

テストケース生成の手続きの全体像は以下の図のようになります。

大まかには上で説明した手順と同じです。

  1. まず、SystemVerilog のソースコードをパース・解析して always 文ごとに Control Flow Graph (CFG) を作ります。この際にプログラムが期待する入力の情報(名前や型)を得ます。
  2. 次に、得た情報を使って最初の入力をランダムに生成し、シミュレータを用いて実行します。シミュレータは実行時の各変数の値の情報を Value Change Dump (VCD) というフォーマットで出力します。
  3. VCD には各タイミングでの変数の値の情報が入っているのでこれを元に CFG のどのパスが実行されたかを計算します。
  4. そこからその実行が行われるための条件を求めて、まだ実行されていない部分を次に実行するような入力を生成するのための制約を求めます。
  5. この制約をSMT ソルバーで解くことで次の入力を得ます。
  6. このプロセスをラインカバレッジが100%になるまで繰り返します。

SMTソルバーは Z3 [4] を用い、その他は OCaml によって実装しました。Z3 の API は OCaml から利用することができ便利です。

実装は https://github.com/pfnet-research/ATPG4SV で公開しています。

結果

300行程度の比較的小さめの回路に対して実行したところ10イテレーションほどで100%のカバレッジになるテストケースが得られました。

まだ実装が不完全なので対象に制限がありますが、実際の回路に対して動作させることができました。

まとめ

効率的で効果的なテストケースを生成する手法として Concolic Testing を紹介しました。また、インターンで取り組んだ、SystemVerilog で記述されたハードウェアへの適用例を紹介しました。まだ実用的に役に立つレベルのものではないですが、プロトタイプとして面白いことができたと思います。

最後になりましたが、メンターの酒井さんと Kühn さんには、テーマ決め、自分の知識が少なかったハードウェアに関する質問、発表練習、そして休憩中の雑談、とインターン期間を通して大変お世話になりました。ありがとうございました。

参考文献

  • [1] P. Godefroid, N. Klarlund, and K. Sen. DART: Directed Automated Random Testing
  • [2] P. Godefroid, M. Levin, and D. Molnar. Automated Whitebox Fuzz Testing
  • [3] L. Liu and S. Vasudevan. Efficient validation input generation in RTL using hybridized source code analysis
  • [4] The Z3 Theorem Prover https://github.com/Z3Prover/z3

おまけ:メンターより

押川さんのメンターを担当した、PFNの酒井とKühnです。

PFN ではディープラーニングを高速化する専用プロセッサー MN-Core™(エムエヌ・コア)の開発も行っていますが、チップ設計の検証は大きな課題の一つでした。 今回の取り組みは、この課題への取り組みとして始まった実験的なプロジェクトであり、押川さんには短い期間で、Coqによる部分的な証明などのアプローチも含めた複数のアプローチを検討のうえ、Concolic Testing に基づいたアプローチを選択し、プロトタイプを実装して実際の回路を対象にテストパターン生成のループを回せるところまで開発していただきました。(注: 既存の回路を対象に実験したものであり、現時点でのMN-Coreの実際の開発に適用しているわけではありません。)

PFNは上から下まで様々なことに取り組んでいていますが、まだまだ手が回っていないことも沢山あり、そこに興味のあるインターンの学生さんが来てくれたことをきっかけに新たな取組みが始まることがしばしばあります。今回のプロジェクトはそのような取り組みの一例でもあります。我こそはという学生の皆さんは、ぜひ来年のPFNインターンシップへの応募をご検討ください。 また、もちろん中途・新卒の人材募集も通年で行っていますので、こちらも興味のある方はぜひご検討ください!PFNの人材募集のページはこちら https://www.preferred-networks.jp/ja/jobs です。(FPGA/ASIC開発の人材も募集しています)

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

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]をご覧ください。

参考文献

インターン参加報告:確率的なプログラムの型による静的検証

Masahiro Sakai

2018-12-19 12:00:32

本記事は、2018年インターンシップに参加された南條陽史さんによる寄稿です。


はじめまして。PFNの2018夏季インターンシップに参加した筑波大学の南條陽史です。 大学ではプログラム言語について研究しており, 型理論やプログラム検証に興味があります。

今回のインターンで“確率的なプログラムの型による静的検証”というテーマで研究開発を行いましたので、その紹介をいたします。

テーマの説明

確率的なプログラムの検証

確率的プログラム、というと色々な意味がありますがここでは確率分布からのサンプリングといった確率的な挙動を含むプログラムを指します。 研究の目的はこの確率的プログラムに対して“悪いことが起きる確率はいくら以下である”とか“よいことが起きる確率はいくら以上である”といった確率についての仕様検証をすることです。

確率的なプログラムの仕様検証の応用例として以下が挙げられます。

  • 確率的に生じる観測誤差によってシステムが誤った判断をしてしまう確率がしきい値以下であることの検証
  • クライアントに仕事を割り振るシステムについて、あるクライアントに捌き切れないほど多くの仕事を流してしまう確率がしきい値以下であることの検証

このように確率的なプログラムの検証は現実的な問題に直接結びついています。

型による検証

僕のテーマはこの確率的なプログラムの検証を“型”主導で行うというものです。 型というのはintとかcharとかstringとかのあの型です。 プログラムの仕様としての型という考えを推し進めると、例えば以下のように詳細な仕様を型として記述できます。

rand(0, 5) : {x:int|Prob(x%2=0) = 1/2}

これは0から5の整数を一様分布からサンプリングするプログラム (rand(0, 5)) に対して, その値が偶数である確率は1/2だという仕様 (Prob(x%2=0) = 1/2) を与えています。

型主導で検証することの利点として一般に以下が挙げられます

  • 再利用性 : 検証が合成的なのであるプログラムの検証の結果をより大きなプログラムの検証に再利用することができます
  • 網羅性 : 型による検証は数学的な証明に対応するので単体テストなどと違ってカバレッジ100%です
  • 静的 : 検証がプログラムのコンパイル時に行われるので実際にプログラムを実行する必要はありません

既存研究

既存の確率的なプログラム検証のツールに確率的モデル検査器PRISM[1][2] があります。 これは確率的な状態遷移系に対して、悪い状態に到達する確率が十分低いことや良い状態に到達する確率が十分高いことを検証できます。

しかしPRISMを使うためには確率的状態遷移系を書き下す必要があり、人間が読み書きするには不向きでした。 一方で今回のテーマでは検証の対象をプログラムという形で与えることで、関数などのプログラム言語が持つ抽象化の手段を使うことにより検証の対象を人間が読みやすく記述することを可能にしています。

実装

インターンでは確率的なプログラムを記述できる小さなプログラミング言語のインタプリタとその型検査器を実装しました。ソースコードは[3]で公開しています。 この型検査器では型付可能性問題を既存ツールPRISMが扱える妥当性判定問題に帰着させて解いています。

実装したプログラミング言語では例えば次のプログラムの型検査が可能です。

(let a = rand(-10, 10) in
let b = a + rand(0, 10) in
 not (b - a >= 10) /\ ((b + rand(0, 5)) - (a + rand(0, 5)) >= 10))
 : {x:bool | Prob(x) <= 1/10}

このプログラムは“GPSに基づいてスピード違反を検出するシステムが観測誤差によって誤った判断をする確率がしきい値以下”であることの検証を簡単にモデル化したものです。

詳細に説明すると、 a はある時点での自動車の真の座標で b は微小時間後の自動車の真の座標として、 実際にはスピード違反していない (not (b - a >= 10)) のに観測誤差によってスピード違反と判定される ((a + rand(0, 5)) - (b + rand(0, 5)) >= 10) 確率が1/10以下である (Prob(x) <= 1/10) と言っています。

このプログラムと同等な検証をPRISMを使って行うと確率的状態遷移系は 1460 bytes ほどの非直感的なコードになり、確かに実装した言語上の方が簡潔にわかりやすく記述できることがわかります。

実装したプログラミング言語は一様な離散分布からのサンプリングのみを考えており言語機能も大きく制限されているので、現実的な問題を考えるためにはいくつかの機能拡張をする必要があります。 つまり

  • 連続分布からのサンプリング
  • 高階関数
  • 条件付き確率

などです。 考えるべきことはまだまだ残っているので今後も開発を進めていきたいと考えています。

謝辞

インターンが始まった頃は機械学習について全く詳しくなかったのですが、メンターの方が僕にレベルを合わせてかなり初歩的なことから丁寧に説明をしてくださったのでとても助かりました。 メンターの方々以外にも多くの社員さんと交流する機会が用意されており、どの方のお話もとても楽しかったです。 社員の方々だけでなくインターン生にも優秀な方が多くとても刺激的で心地よい夏休みを過ごせました。 皆様に心から感謝いたします。貴重な経験をありがとうございました。

参考文献


おまけ:メンターより

南條さんのメンターを担当したPFNの吉川と酒井です。

PFNは機械学習/深層学習そのものの研究開発のイメージが強いとは思いますが、フレームワークの開発などにおいてはプログラミング言語理論も含め幅広い分野の知識が必要となります。 今回南條さんに取り組んでもらったのも、現時点では非常に簡単なものではありますが、プログラミング言語理論的な知見を確率モデリング等に活かしていけないかと考えての試みです。お互いのバックグラウンドとなる知識の違いから苦労した部分もありましたが、最終的には簡単な言語の設計と実装までこぎつけることができました。

これに限らず、機械学習・深層学習にはプログラミング言語理論などで解決できる課題がまだまだあるのではないかと考えています。それらに興味をお持ちの皆さんも、ぜひ来年のPFNインターンシップへの応募をご検討ください。 また、もちろん中途・新卒の人材募集も通年で行っています。興味のある方はぜひご検討ください!PFNの人材募集のページはこちら https://www.preferred-networks.jp/ja/jobs です。

[BoF] How to choose programming language for product/in-house software development

kashihara
エンジニア

2018-08-24 15:35:26

Preferred Networksでエンジニアをしている柏原です。PFN Dayでは “How to choose programming language for product/in-house software development” という題でBoFのセッションを開きました。PFN Dayとはトビアスのブログエントリ「[PFN Day] BoF session: How to Improve Sharing of Software Components and Best Practices」にもあるように、社内向けの技術カンファレンスです。

ソフトウェア開発において、プログラミング言語は開発環境をはじめとして、開発チームやサポート体制などに大きな影響を与えます。 PFNの中でもたくさんのプログラミング言語が使われていると思います。 今回は社内で何が使われているかという現状については言及せず、社外にリリースする製品/社内製品を開発することを想定して、どうやってプログラミング言語を選択するか、どのような要素がプログラミング言語の選択に影響を与えるのか議論したいと考えました。

まず、参加メンバーのバックグラウンドを共有するため、どういったソフトウェア開発・プログラミング言語の経験があるか自己紹介をしました。 その後、過去にどのような点を重視してプログラミング言語を選んだのか、プログラミング言語を選ぶときの重要な点についての項目を議論の中であげていきました。

結論としては必要としているものを正しく選ぶ、ということになりますが、以下の優先順位がプログラミング言語の決定に大きく依存しているということになりました。

  • Priority 1: Real world restrictions (E.g. frameworks, platforms)
  • Priority 2: Real world needs (E.g. stability, production readiness, concurrency, distributed computed)
  • Priority 3: Real world benefits (E.g. productivity factors)

1番目のrestrictionsでは、実行環境(OS、モバイル端末、組込)や、目的を実現するためのフレームワークが優先されます。 近年ではたくさんのプログラミング言語が増えてきたとはいえ、その言語が利用できるかは環境に大きく依存します。

2番目は、ソフトウェアで求められている機能・非機能要件を満たすことが、当然ながらソフトウェアの開発で求められます。 プログラミング言語やランタイム環境は、適材適所であるべきといえるでしょう。 ソフトウェアの安定性が求められるのはもちろんのこと、近年ではCPUのマルチコア環境を活かすことも必要とされてきています。 プログラミング言語の機能や特性によって、ソフトウェアの要求を実現できるというのはとても心強いです。

3番前は、プログラマーがプログラミングするにあたって、あると嬉しい部分です。 例えば、テキストエディタやIDEによる、プログラミング言語を書くことをサポートする機能(プラグイン)があげられます。

BoFを開催する前は極端な意見に偏るかもしれないと少し不安でしたが、最終的には現実的な結論に落ち着いたと思います。 その他、興味深いトピックとして、ソフトウェアの正しさを検証するものとして、モデル検査やHDLのSystemVerilog(言語)といったものも話題にあがりました。 80分と長いような短い時間の議論でしたが、興味深い会話ができたと思います。BoFのメモがもし公開されたら、そちらも是非ご覧ください。

2018年 PFN夏季インターンシップのコーディング課題公開

楠本充
エンジニア

2018-07-18 11:32:38

PFN 2018夏季インターンシップの選考で用いたコーディング課題を github 上で公開しました。

https://github.com/pfnet/intern-coding-tasks

PFN の楠本です。PFN では毎年8,9月前後に2ヶ月間の長期インターンシップを行っています。コーディング課題はその選考で応募者のプログラミング能力や問題解決能力を見るために出題させて頂いているものです。PFN のインターンシップでは機械学習をはじめとする幅広い分野で応募を行っているため、今年は「機械学習・数理」「バックエンド」「フロントエンド」「プロセッサ/コンパイラ」「Chainer」の5種類のコーディング課題を用意し、応募者の希望するテーマに応じてこのうちのいずれかを解いていただく形にしていました。

今年は去年を大きく上回る数の応募を国内外双方からいただくことができました。それに伴い、インターン生の受け入れ人数も去年よりもさらに拡充する形になりました。

今年の問題は以下のような構成になっています。

  • 機械学習・数理課題: ニューラルネットワークの敵対的入力(Adversarial Example)のアルゴリズムを実装し、性能を報告するためのレポートを記す課題。
  • バックエンド課題: 与えられたログファイルを分析するツールを作る課題。
  • フロントエンド課題: セミナー発表のような動画に対して、発表内容のアノテーションを行うウェブサービスのプロトタイプを作る課題。
  • プロセッサ/コンパイラ課題: 行列積コードの最適化と、行列積回路の設計を行う課題。
  • Chainer 課題: モデルの学習を行うコードを Chainer で実装する課題。

コーディング課題では毎年、出題者が趣向を凝らした問題を作成しています。これらの課題が、興味のある分野を実践的に学ぶための練習問題になれば幸いです。

私は今年の機械学習・数理課題の出題に携わりました。少し余談になりますが、課題を作る際に意識していたことについて書きたいと思います。他の課題ではまた話が違ってくるかもしれませんが、共通しているところもありそうです。

  • 前提知識があまり無くても解けるようにする: PFN では幅広い分野の方々を募集しています。そのため、機械学習そのものの経験や知識が無くても課題を一通り解けるように問題を設定したり、問題文を記述するようにしています。また、特定の知識を持っている人が有利になりすぎるということがあまりないようにも配慮しているつもりです。
  • 実際の研究に近いような設定にする: 深層学習のような分野の研究では「何か良いテーマを見つけて手法を考える → 実装する → 出てきた結果をまとめ、考察を与える」という過程を繰り返しますが、このうち「実装して考察する」という流れを短期間で一通り辿れるような設定にしています。大学の授業の課題のような感じに近いかもしれません。
  • できるだけ興味深いテーマを問う: 機械学習・深層学習の分野では日々研究が進んで面白い結果が次々に出ているので、それに少しでも触れられるような課題を設定しているつもりです。今回の課題である Fast Gradient Signed Method という手法は、シンプルな手法でありながらランダムよりも遥かに強い攻撃手法であるという点で興味深いものだったと思います。
  • 時間が掛かりすぎないようにする: 学業に支障が出ると良くないので、実力が十分あれば1~2日程度で終わるような分量にすることを目標にしています。

提出されたコードは様々な観点から評価するようにしています。単に実装されたコードが正しいのかどうかだけではなく、コードが読みやすいものになっているか、単体テストなどの検証のためのコードが適切に書かれているか、他人がコードの追試をしやすいようになっているか、といった要素も考慮するようにしています。
実験ではコードを書いて動かしたら終わりではなく、手法がどの程度うまくいったのかを評価し、なぜそのような結果になったのかを考察するのが重要になります。特に、複数人で一つの課題に取り組む際にはそれら(評価・考察)を他のチームメンバーに共有することも大事になるでしょう。レポートでは結果の評価と考察ができているかを評価するようにしています。

これらの課題を見て PFN に興味を持っていただけた方は、ぜひ来年のインターンシップへ応募することを検討していただければ幸いです。また、PFN ではフルタイムの採用も通年で行っておりますので、こちらもご検討をよろしくお願いします。

Preferred Networks における研究活動

秋葉 拓哉
リサーチャー

2018-06-08 14:36:39

こんにちは、新しく執行役員兼 Chief Research Strategist に就任した秋葉です。就任の挨拶を兼ねて、PFN における研究活動に関する考えを共有したいと思います。

PFN における研究とは何か?

何が研究であり何が研究でないかという境界を引くのは非常に難しく、またそれを積極的に行う意味もありません。研究とは「研ぎ澄まし究めること」を語義とし、一般に、物事について深く調査・考察を行い事実を解明したり発明を行ったりすることを指します。

PFN では挑戦的であり不確実性の高いプロジェクトが大部分を占めており、ほぼ全てのプロジェクトが少なからず研究的側面を伴います。深層学習関連のコア技術の研究開発は勿論、その応用に関してもデータやタスクに応じた適切な手法の選択や非自明な工夫がなければ上手くいかないことが殆どです。また、ロボティクス、コンピュータビジョン、自然言語処理等のような多分野の技術を組み合わせることにより新たに出てくる課題もあります。それに加えて、クラスタの設計やそのリソース管理、及びディープラーニングフレームワークに関しても、深層学習特有の要求を満たし、便利かつ高性能にするために、多くのことを考え試行錯誤をしています。

そのような中でも、特に研究的側面を強く持つプロジェクトには、以下のようなものがあります。

  • 論文となるような学術的研究
  • デモンストレーションの制作と展示
  • コンペティションへの参加
  • 社会での未解決問題の解決

このような分野でも、既に素晴らしい成果が出ています。論文に関しては、ICML, CVPR, ACL, CHI など、幅広い分野のトップ会議に論文が継続的に採択されるようになりました。また、数が増えているだけでなく、ICRA’18 にて論文が Best Paper Award on Human-Robot Interaction を受賞したり、ICLR’18 にて論文が Oral に選ばれたりと、世界的に極めて高い注目を集める論文を出すことに成功しています。デモンストレーションとしては、CEATEC 2016 や ICRA 2017 等で制作したものを展示しました。コンペティションとしても、Amazon Picking Challenge 2016 や IPAB 創薬コンテスト等で優れた成果を残しています。

PFN はなぜ研究をするのか?

PFN のような企業で、今すぐ直接お金に結びつかないような研究をする意味はあるのでしょうか?例えば、論文を書こうと思えば貴重な業務の時間をごっそりと使ってしまうことになるし、それを出版すれば社外の人たちに技術を教えてしまうことになります。こう考えると、学術的研究や論文執筆は、会社にとってマイナスの活動のようにすら見えます。

実際には、PFN においてそのような研究活動は極めて重要視されており、今後もなお重点的に強化を行っていく予定です。コンピュータや AI 分野のビジネスでは、しばしば「Winner takes all」といったことが言われます。このような領域では、ビジネスに国境がなく、中途半端では生き残ることはできません。世界でトップクラスの技術を持ちリードを保ち続ける必要があります。従って、我々は、研究活動を通じ技術力を中心とした競争力を持ち続けることがビジネス上で極めて重要だと考えています。また、現実的には、優れた特許ポートフォリオを構築するといったことも重要です。

また、「よそから出てくる論文の実用化に注力する方が効率的ではないのか?」という疑問もよく聞きます。しかし、論文が出てきて我々の目に止まるタイミングでは、世界のトップは必ずもっと進んでしまっています。そして、論文を読んで得られる情報はかなり限られており、試行錯誤したり著者に問い合わせながら再現に成功したり、他のデータセットへの適用を通じて論文に書かれていない手法のネガティブな性質について把握したりするのには、さらにかなりの時間がかかります。パーソナルコンピュータの父として知られるアラン・ケイの「未来を予測する最善の方法は、それを発明することだ」という言葉は、実際にいくつかの分野で世界をリードしたりトップに迫ったりといった成果を出すことができている我々にとって、大きな実感があります。

更に、単に社内で研究を行うことだけでなく、成果をコミュニティに発表し還元することも重要視しています。一つには国内外でのプレゼンスを得るという目的もあります。それに加えて、我々の発表した技術に基づいた研究や我々の発表に触発された研究が社外でも行われることにより、トータルで考えて我々に必要な技術の発展が加速されると考えています。そのため、OSS としてソフトウェアを公開したり、研究に使ったコードやデータなども積極的に公開しています。また、アカデミックなコミュニティへ貢献するため、学会や論文誌の査読も業務で行えるようにしています。

どのような研究を推進していくのか?

深層学習を中心として、コンピュータビジョン、自然言語処理、音声認識、ロボティクス、コンパイラ、分散処理、専用ハードウェア、バイオインフォマティクス、ケモインフォマティクスといった、幅広い分野での研究を行っており、これを以下のような理念に基づき強化していきます。

正しくクレイジーに

全ての研究は現在だけでなく未来を見据えて行われるべきです。研究の価値も、今の常識だけで判断するべきではありません。「そんな計算が重い方法は実用的じゃないよ」といったことや「今はそんな処理したい人いないよ」といったことは、必ずしもネガティブではありません。例えば、我々は昨年、1024 台の GPU を用いた分散処理により画像認識モデルを高速に学習するというプロジェクトを成功させ、世界的に大きな注目を集めました。達成した速度が常識外れだっただけでなく、1024 台の GPU を一度に使うと言った実験の規模自体も常識外れでした。1024 台の GPU を使って日常的な学習を行うといったことは現実的ではないかもしれません。それでは、このような研究の価値は無いのでしょうか?

計算機は未だに速くなり続けています。特に、深層学習に関しては、専用チップの開発も盛んです。OpenAI の調査によれば、深層学習の大規模なトレーニングで使われる計算力は、3.5 ヶ月で倍という急速なペースで上がっています。今は馬鹿げた計算力に見えるそのような設定も、数年のうちに当たり前のように使える状況が来る可能性は高いでしょう。未来を見据え、そのような状況では何が起こるのかといったことを知り、そこでの課題を解決したり新たにできることを模索したりといったことに早く乗り出すことは、非常に重要だと考えています。1024 台の GPU を用いた上述の実験はその第一歩であり、プライベートスーパーコンピュータと並列分散計算チームを持つ強みを活かしながら、大規模な実験を促進し、このような規模での実験を当たり前のように感じられるぐらいの環境を作りたいと考えています。

世界とグラウンディングする

全ての研究は何らかの意味で世界の最先端を目指すべきです。技術力は、世界的にリードを持つことにより大きな価値に繋がります。社内だけでなく、積極的に外を向き、論文が世界的に高く評価されたり、世界的なコンペティションで高い順位を取ったり、注目を集め講演に呼ばれたり、といったことを目指すべきだと考えています。実際には、全ての研究プロジェクトで世界をリードするようなことは難しいかもしれません。しかし、世界トップをしっかり意識し目指すことで、自分たちの相対的な位置を知ることができます。

また、世界的なコミュニティに食い込むことも非常に重要です。社外の世界トップを走る人たちと知り合いになり、無視できない存在だと認識してもらうことで、有益な情報の交換ができます。そのためにも、外部発表を推奨しており、貢献をしたメンバーの顔がしっかり外に出るようにしています。

積極的に展開する

全ての研究は小さく閉じこもることなく積極的な展開を目指すべきです。例えば、研究を論文にすることは非常に重要なマイルストーンですが、それは完成ではありませんし、それだけを目標にするべきではありません。深層学習では共通の技術が異なる応用分野を跨がり力を発揮することがあります。PFN には幅広い分野に取り組む人がいるという利点を活かし、研究のスコープを狭く捉えず、人を巻き込み、幅広い展開を目指してほしいです。また、新たなソフトウェアを開発したり社内のソフトウェアにフィードバックしたりして人が利用できる形にすることも可能であれば検討するべきです。社内での実務に成果を還元できれば素晴らしいでしょう。トップ会議への論文採択数は重要視していますが、一方で、論文の本数や論文が採択された会議のランクのみから研究開発活動を評価することはしないつもりです。

もちろん、全てを自分でやる必要はありません。世界のトップレベルに食い込んでいくためには、自分の能力的な強みとモチベーションを存分に発揮することが必要です。従って、自分が持っていない能力は積極的に人に頼ることも検討するべきです。これは技術領域のみでなく、研究のまとめ方に関してもです。せっかく面白い研究開発をやっていても、論文執筆の経験を持たないためどうやって論文にしていいか分からなかったり、誤解が原因で学会投稿で過小評価され採択に繋がらないこともあります。論文の執筆方法や徹底したサーベイ、正しい比較実験の仕方などについて、基礎研究で活躍してきた研究のベテランが社内に多く存在することを活かしていけるようにしたいと考えています。

PFN で研究開発をする魅力は?

リサーチャー・エンジニアとして PFN における研究開発に携わる良さとは何でしょう?

最も魅力的な点の 1 つは、PFN の対象とする深層学習を中心とした技術領域の特徴として、個人及び組織的な卓越した技術力が、本当に必要とされており、非常に重要であるということです。個人としても組織としても技術力の差が成果に反映されやすいという意味で、高い技術力を持つことが高い価値に直接的につながります。個人として高い技術力を持つこと、そしてチームとしてさらなる力を発揮することが非常に高く評価されます。これは、技術力に自信を持つ人や、技術力の向上にモチベーションを持つ人に、とても良いことであると感じます。

取り組み方が非常にフレキシブルな点も魅力だと考えています。100% の時間をピュアな基礎研究に費やすメンバーも今では複数人いてチームも構成しており、増強していく予定です。一方で、実務的な課題にも触れながら研究活動を行っているメンバーも多数います。また、アカデミアとの共同研究も積極的に行われていますし、社会人博士としてパートタイムで大学院に通い専門性を磨くメンバーもいます。

研究開発活動を促進するための社内制度にも気を使っています。会社がメンバーを信頼して大きな裁量を与え、足りない社内制度や資産があればフレキシブルに対応するなど、新しいチャレンジを積極的に支援しています。例えば、20% ルールにより、全てのメンバーは 20% までの時間を自分の裁量で使うことができます。これにより、誰でも思いついたアイディアをすぐに試すことができます。強いモチベーションやユニークなアイディアを持つ取り組みがボトムアップに出てくることを期待しています。

PFN が取り組む深層学習を中心とした技術領域では、アルゴリズムからソフトウェアフレームワーク、研究支援ミドルウェア、そしてハードウェアまで、その全てが重要になってきます。深層学習、強化学習、コンピュータビジョン、自然言語処理、バイオインフォマティクス、高性能計算、分散システム、ネットワーク、ロボティクス、シミュレーション、データ解析、最適化、異常検知といったような幅広い専門を持つ人が社内の近い位置にいて、気軽に情報交換ができる点もとても魅力的だと思います。分からない事柄について教えてもらったり、実務上出てくる問題を交換したり、一緒に研究に取り組んだりすることができます。

終わりに

最後に、少し個人的な抱負を書かせてください。今回、執行役員兼 Chief Research Strategist という身に余る大役を頂戴しました。能力面でもそれ以外でも心から尊敬できるメンバー達が素晴らしいチームとなり活躍しているこの会社で、私なんかにこのような大役が務まるのかという不安もあり、引き受けていいものか迷いました。

私は前職ではアカデミアでの研究者でしたが、企業での研究にも学生時代から興味を持ち、海外の企業研究所でのインターンにも複数回参加していました。その中で一度、インターン期間中にレイオフが起こり、自分のメンターも含めてその研究所に所属していた全研究者が解雇になるという様子を目の当たりにしたことがあります。企業での研究を意義あるものに保つ難しさを実感しました。

そのような経験を踏まえて考えても、私は PFN は企業として研究活動をするべきだと思います。それを健全な状態に保ち価値に繋げるのは決して簡単なことではないと思いますが、そのような部分にもし私の色々な場所での経験や考えを活かして貢献できるのであれば、それは非常に刺激的かつ意義のあることだと感じ、新たなポジションで頑張ってみることにしました。

また、研究とエンジニアリング、深層学習と分散計算など、複数面の得意分野を融合させることのできる自分の強みや、勝ちにこだわり戦略を練り遂行できる自分の強みを、今後はより広範囲で活かしていければと考えています。

PFN では、このような研究開発活動に興味を持ち一緒に取り組んでくれるメンバーをリサーチャー・エンジニアとして募集しています

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.

NIPS’17 Adversarial Learning Competition に参戦しました

秋葉 拓哉
リサーチャー

2018-04-11 19:01:39

機械学習の国際学会 NIPS’17 に併設され Kaggle で開催されたコンテスト「NIPS’17 Adversarial Learning Competition」に PFN のメンバーで参加し、4 位入賞を果たすことができました。その結果、NIPS’17 に招待され発表を行い、手法についての論文も執筆し公開しました。ソースコードも公開しています。本記事では、具体的にどういった内容のコンテストだったかや、我々がどのようなアプローチで取り組み 4 位入賞を達成したのかについて紹介します。

 

Adversarial Example とは?

Adversarial example [1, 2, 3] は深層学習を実用化していく上での最も大きな課題の 1 つとして考えられている非常にホットな研究分野です。画像認識を例に取ると、元画像に対し人が気づかない程度の僅かな変更を加えるだけで、CNN による画像認識を大きく誤動作させることができることが知られています。

上の画像は adversarial example の例です(出典 [2])。左の元画像にはパンダが写っており、CNN もパンダであると正しく分類できています。真ん中に描かれているのは悪意を持って作られたノイズです。右の画像も、我々人間には一見して左のものと同じパンダの画像に見えます。しかし、実際にはノイズがわずかに重ねられており、右の画像を CNN に分類させると、パンダではなくテナガザルの画像であると非常に高い確度をもって誤判定してしまいます。

 

NIPS’17 Adversarial Learning Competition

今回開催された NIPS’17 Adversarial Learning Competition は、その名の通り、adversarial example に関するコンテストです。攻撃トラックと防御トラックの 2 種目を順番に説明します。

 

攻撃トラック

画像を入力として受け取り、悪意を持ってノイズを加え画像を adversarial example に変換し出力するプログラムを提出します。防御トラックに提出された画像分類器をどれだけ騙せるかが得点になります。具体的には、防御トラックに提出された全画像分類器に対するエラー率の平均がスコアになります。できるだけ強い adversarial example を作る手法を開発することが目的になります。

 

防御トラック

画像を入力として受け取り、分類結果を出力するプログラムを提出します。攻撃トラックに提出された全攻撃プログラムが生成した全画像に対する精度の平均がスコアになります。できるだけ adversarial example に騙されにくい画像分類器を作ることが目的になります。

 

ルール詳細

画像は複数用意されています。また、攻撃トラックのプログラムには実行時にパラメータ ε が渡され、ノイズの大きさは ε までにしなければなりません。具体的には、画像の各ピクセルの各 R, G, B の値を ε まで変更することが許されます。別の言い方をすると、ノイズの L∞ ノルムを ε 以下にする必要があります。攻撃トラックは non-targeted と targeted の 2 部門に分かれていますが、本記事では我々が参加した non-targeted 部門についてのみ紹介しています。詳しくは以下のコンテストの公式ページ [4, 5, 6] をご覧ください。

 

標準的な Adversarial Example の作り方

我々は攻撃トラックに参加しました。まずは、標準的な adversarial example の作り方について説明します。最も有名な手法である FGSM  (fast gradient sign method) [2] をはじめとする既存のほとんど全てのアプローチは、大まかに以下を行います。

  1. 画像分類器に対象画像を分類させる
  2. 誤差逆伝播を画像まで行い、画像に対する勾配を計算する
  3. 得られた勾配を用いて画像にノイズを加える

このステップを 1 度だけ行うか繰り返すか。誤差逆伝播に用いるロス関数をどのように定義するか。勾配をどのように用いて画像を更新するか。こういった部分が工夫され、強い adversarial example を生成する手法が開発されてきました。今回のコンテストでも、攻撃トラックに参加したほとんどのチームはこういったアプローチを用いました。

 

我々の手法について

概要

我々は現在主流となっている上述のアプローチと大きく異なる、「adversarial example を直接生成するニューラルネットワークを作る」というアプローチを取りました。

攻撃を行う際の処理は、ニューラルネットワークに画像を入れるだけです。出力された画像がそのまま adversarial example となります。

 

攻撃ネットワークの学習方法

このアプローチでのキモは、当然、このニューラルネットワークをどのように作るかという点になります。以下、我々が作る adversarial example を出力するニューラルネットワークを「攻撃ネットワーク」と呼びます。以下を 1 イテレーションとして、これを繰り返すことによって攻撃ネットワークを学習しました。

  1. 攻撃ネットワークによって adversarial example を生成する
  2. 既存の学習済み画像分類 CNN に生成した adversarial example を分類させる
  3. CNN 上で誤差逆伝播を行い adversarial example に対する勾配を計算する
  4. そこからさらに攻撃ネットワークで誤差逆伝播を行い、攻撃ネットワークを勾配を用いて更新する

攻撃ネットワークのアーキテクチャとしては fully convolutional なものを設計し用いました。なお、これと類似したアプローチは以下の論文 [7] で提案されています。

 

攻撃を強化するテクニック

より強い adversarial example を生成するために、攻撃ネットワークのアーキテクチャや学習方法の工夫を試み、multi-target training, multi-task training, gradient hint などのテクニックを開発しました。詳しくは論文をご覧ください。

 

データ並列とモデル並列を組み合わせた 128 GPU での分散学習

学習に非常に時間がかかってしまう点を解決し、攻撃ネットワークのアーキテクチャをより大規模にするため、ChainerMN [8] を用いて 128 GPUでの分散学習を行いました。特に、攻撃ネットワークの方が分類ネットワークよりも大規模なため GPU メモリの都合上バッチサイズを小さくしなければいけない点、上述の multi-target training により分類ネットワークはワーカー毎に異なるものを用いている点などを考慮し、標準的なデータ並列に加えて、ChainerMN の最新機能であるモデル並列機能を組み合わせて用い、効果的な並列化を実現しました。

 

生成された画像

我々のアプローチでは、手法だけでなく、生成された adversarial example も非常に個性的なものになりました。

左列が元画像、真ん中の列が生成された adversarial example、右の列が生成されたノイズ(元画像と adversarial example の差)です。以下の 2 つの特徴が見て取れます。

  • パンダの毛の質感のような細かい模様をキャンセルするノイズが生成されており、画像がのっぺりとしている。
  • ジグゾーパズルのような模様が付加されている。しかも、一様にではなく、元画像を上手く活用して効果的に模様を付加している。

この 2 つの特徴により、多くの画像分類器はこの adversarial example をジグゾーパズルとして分類してしまうようです。興味深いのが、我々は特にこのようなジグゾーパズル風の画像を生成するように学習を行ったわけではないという点です。画像分類器を騙す画像を作るという目的関数のもと攻撃ネットワークの学習を行ったところ、攻撃ネットワークが自動的にこのようなジグゾーパズル風の画像を生成することが効果的であると学習したことになります。

 

結果について

我々の最終順位は約 100 チーム中 4 位となりました。優勝を目指し取り組んでいただけあり非常に残念ではありますが、4 位までが入賞とされ、NIPS’17 のワークショップに招待され発表を行いました。

また、コンテスト主催者の方々のお誘いにより、共著でコンテストに関する論文を執筆しました。Ian Goodfellow や Samy Bengio など、業界で知らない人の居ないビッグネームと一緒に論文を出す経験ができました [9]。また、ソースコードも GitHub にて公開しました [10]。

  • [9] Alexey Kurakin, Ian Goodfellow, Samy Bengio, Yinpeng Dong, Fangzhou Liao, Ming Liang, Tianyu Pang, Jun Zhu, Xiaolin Hu, Cihang Xie, Jianyu Wang, Zhishuai Zhang, Zhou Ren, Alan Yuille, Sangxia Huang, Yao Zhao, Yuzhe Zhao, Zhonglin Han, Junjiajia Long, Yerkebulan Berdibekov, Takuya Akiba, Seiya Tokui, Motoki Abe. Adversarial Attacks and Defences Competition. CoRR, abs/1804.00097, 2018.
  • [10] pfnet-research/nips17-adversarial-attack: Submission to Kaggle NIPS’17 competition on adversarial examples (non-targeted adversarial attack track) : https://github.com/pfnet-research/nips17-adversarial-attack

我々は結果こそ 4 位だったものの、アプローチが大きく異ることに起因して実行時間の性質が他のチームと全く違うため、コンテスト終了前から他の参加者の注目を集めていました。下図が上位チームのスコアと実行時間の表になります。我々のチームだけ、実行時間が桁違いに短いことがわかります。これは、我々の手法は攻撃時に前向き計算のみを行うので計算時間が短いのに対し、他のチームはほぼ全て画像に対する勾配を用いる方法を使っており前向き計算と後ろ向き計算を繰り返していたためです。

また、ある参加者によって公開された PageRank 風の解析によると、我々のチームがトップのスコアとなりました。これは、我々の攻撃が、特に上位の防御チームに対して効果的だったことを示唆しています。他と傾向が異なる攻撃であったため防御が難しかったのではないかと思います。なお、優勝チームが手法を解説した論文 [11] はコンピュータビジョンの国際学会 CVPR’18 に採択され spotlight セッションにて発表予定とのことです。

 

終わりに

このコンテストへの参加は、会社の 20% プロジェクトとしてスタートしました。その後、軌道に乗ってきたため、優勝を目指して本腰を据えて取り組みたいと考え調整を行い、終盤は業務時間のほぼ 100% をこのコンテストに使いました。PFN では、他にも Amazon Picking Challenge や IT 創薬コンテストなどのコンテストに会社として参加してきており、こういったコンテストへの挑戦を推奨する雰囲気が有ります。私はこういったコンテストへの参加が大好きなので、今後も社内の課題と関連を持つコンテストを上手く選んで定期的にこういった活動を続けていきたいと考えています。また、社外のコンテストに限らず、社内でも精度や速度をチューニングするタスクが重要な場所で出て来ることが少なくなく、こういったコンテストで養われる能力が活用できる場があります。

PFN では、こういった領域や活動に興味を持ち一緒に取り組んでくれるメンバーをエンジニア・リサーチャー及び夏季インターンとして募集しています。夏季インターンの募集は 4/30 締切ですので、お忘れなきようお願いします。

「コンピューターサイエンスのすべての分野に精通していること」という応募資格に込めた想い

Toru Nishikawa

2018-02-27 16:00:55

※PFNの募集要項は、本ブログの内容をふまえ、適切に意図が伝わるよう一部更新しました


PFN代表の西川です。

今回は、SNS上でもたびたび話題(炎上?)になっているPFNの応募資格について、改めてご紹介したいと思います。
PFNの採用募集ページに書かれたリサーチャーの条件には、「コンピュータサイエンスのすべての分野に精通していること」という一文があります。この条件は、PFIの時から、リサーチャーの応募資格として常に掲げてきました。

その背景にある想いは、コンピュータサイエンスの研究をする上では、一つの分野だけでなく、幅広い分野について深い知見を有することが極めて重要である、ということです。たとえば、データベースの研究をする上では、トランザクション処理の理論や関係代数について詳しく知っているだけではなく、データベースを動かすコンピュータアーキテクチャ、ストレージ、また、今では分散データベースが一般的になってきているので、コンピュータネットワークについての知見も持っていることが必要不可欠です。深層学習を研究する上では、いまや、一台のコンピュータを使って実現するだけでは競争力が持てず、効率の良い並列処理が必須です。フレームワークを作る上では、コンピュータアーキテクチャや言語処理系の仕組みを理解していることが不可欠です。ドメイン特化した言語を作るような場面では、プログラミング言語理論についての理解がなければ、建増し建築のような言語が簡単にできてしまいます。強化学習においては、シミュレーションや、レンダリングの技術を洗練させていくことも重要です。

このように、一つの分野を知っているだけではもはや強みを出すことはできない時代になってきています。どのような分野と分野を融合したら新しい技術が生み出されるのか、最初から予見するのは難しいことです。私たちには、最先端の技術を切り拓いていくミッションがあります。そのために、すべての分野へ精通するべく、技術を追求していくことが極めて重要だと考えています。

精通という日本語は、その分野について広い知識や深い理解を持つことを示しています。必ずしも、その分野のトップカンファレンスに論文を出せるということを示しているわけではありません(そのためには、知識や理解だけでなく、新しい研究をするという重要な能力が必要になります。そして、そのような能力を身につけていくことも重要だと思います)。また、すべてを知り尽くしているということを表しているわけでもありません。むしろ、すべてのことを知り尽くしていると言い切れる人は、サイエンティストではないと思います。コンピュータサイエンスは今まさに急速に発展している分野であり、その進化を常に追い求めていくことは、極めて重要な姿勢だと考えています。

時にはSNSなどで「コンピュータサイエンスのすべての分野に」という部分が、誤解されたり、面白おかしく茶化されたりするようなことも散見されますが、この応募資格に込めたメッセージは、依然としてPFNの文化を形成する上では重要なことだと思っており、変えるつもりはありません。しかし、この応募資格について、誤解を生まないような表現を新たに考えていくことも必要だと考えています。

この10年間、コンピュータサイエンスの領域も急速に広がってきています。この流れはさらに加速するでしょう。また、コンピュータサイエンスの枠にとどまらず、様々な分野を融合することによって、新しい研究分野もできてくるでしょう。ですので、次のような要素を加味して考える必要があると思います。

  • 現時点でのコンピュータサイエンスのすべての分野に詳しいということよりも(もちろん広い分野を知っていることは必要ですが)、これから生まれてくる新しい分野、コンピュータサイエンスの発展の動きを吸収することのほうが、今後は重要になることを考え、現時点での知識よりも、学ぶ意欲を重要視します
  • コンピュータサイエンスだけでなく、ソフトウェア工学、ライフサイエンス、機械工学など、他の分野についても理解を深めていくという姿勢を評価します
  • 新しい技術の組み合わせによるイノベーションをおこすため、人工知能分野の専門家だけでなく、多様な専門性を持つメンバーを歓迎します


これまでは、この条件は、リサーチャーのみに適用していました。しかしPFNでは、リサーチャーもエンジニアも、分け隔てなく、一丸となって、新しい技術を切り拓いていくことが重要だと考えています。リサーチャーもエンジニアリング的な素養が必要ですし、エンジニアも研究について理解をしていく必要があります。ですので、リサーチャー・エンジニア関係なく、これを応募資格にしようと考えています。

 

また私は、大切な、すべてのPFNのメンバーが、新しい技術を切り拓くために全力を尽くせる環境を作っていくことも重要だと考えており、それは今後も継続的かつ積極的に取り組んでいきます。

 

PFNでは、多様な専門分野を持った人材を募集しています。興味を持ってくださった方は、ぜひご応募ください。
https://www.preferred-networks.jp/ja/job