FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

機械学習初心者がTensorFlowのホワイトペーパを読んで勉強したことをまとめる(1)

TensorFlowには、Whitepaperがあり、www.tensorflow.orgにて公開されている。

http://www.tensorflow.org/resources

機械学習について勉強するのも面白いが、こういうインフラストラクチャがどのような考え方に基いて実装されているのかを知るたために、ホワイトペーパーを読んでまとめてみよう。 基本的な概要とアルゴリズムについては1~4章までに書いてあるので、まずはそこを読んでみよう。

以下、機械学習初心者&英語苦手、が読んでまとめたものですので、間違いがあるかもしれません。内容についての保証はし切れませんのであしからず。

なお、文章中に挿入する図については、全てホワイトペーパ内の図を利用させて頂いている。

概要

TensorFlowの目的は、機械学習アルゴリズムを、モバイルデバイスから大規模コンピュータデバイスまで、一環したインタフェースで提供することである。 TensorFlowの提供するディープニューラルネットワークにより、音声認識、コンピュータビジョン、ロボティクス、情報探索、自然言語処理、地理情報抽出、コンピュータによる医療薬の探索などが行えるようになる。 このTensorFlowは2015年の11月にオープンソースとしてGoogleより公開され、www.tensorflow.orgから入手可能になった。

1. Introduction

Googleは既に2011年からディープニューラルネットワークの研究を始めており、DistBeliefというシステムを構築した。 このシステムはGoogleの内部で様々なチームとコラボレーションし、音声認識から画像認識、ビデオの分類やGo言語のためのmove selection、歩行者の認識からGoogleの検索機能からGoogle Photoの 画像認識、Google Street View、Google Translate、Youtubeなど、様々なチームで成果を出してきた。

DistBeliefの経験から第2世代の機械学習システムとして開発されたのがTensorFlowだ。 TensorFlowは、データフローを記述する形でモデルを作り、小さいものではAndroidiOSのようなモバイルデバイスから、通常のCPUやGPUカードでの実行はもちろん、 大規模な計算環境でも適用dきるように設計されている。 データフローの記述を用いて、TensorFlowは簡単にコアモデルのデータフローを並列に実行することができ、第一世代のDistBelielfよりも、より柔軟で高性能なシステムを実現することができた。

既にGoogle内部のクライアントはDistBeliefからTensorFlowに切り替えており、クライアントはTensorFlowを用いてニューラルネットワークを1兆を越えるパラメータを 1兆を越えるサンプルの記録を、数百のマシンを使って学習させている。 このTensorFlowによる抽象化は多の機械学習アルゴリズムや、別の数値計算の分野にも有用であると考え、2015年の11月に、Apache 2.0のライセンスとしてオープンソース化し、 TensorFlowのAPIといくつかのチュートリアルとともに公開した。

本論文では、TensorFlowのより詳細について伸べる。

  • 第2章: TensorFlowインタフェースにおける基本的な概念やプログラミングモデルについて
  • 第3章: シングルコアのマシンと、分散システムにおける実装について
  • 第4章: 基本プログラミングモデルのいくつかの拡張について
  • 第5章: 基本の実装からのいくつかの最適化について
  • 第6章: TensorFlowのいくつかの使用例について
  • 第7章: TensorFlowを用いたときに便利ないくつかのイディオムについて
  • 第9章: TensorFlowのコアシステムの周辺で利用するための、いくつかの高機能なツールについて

2. プログラミングモデルと基本的な概念

TensorFlowによる計算は、「方向グラフ」によって表現される。この方向グラフにより、制御を行う。これはNaiadシステムに似ている。 クライアントのフロントエンドの言語としてはC++Pythonとなる。これらを利用してTensorFlowのグラフを構築し、Pythonのフロントエンドを利用してTensorFlowのグラフを実行する。 TensorFlowのグラフをPythonで構築し実行する様子を図1、TensorFlowで構築したグラフを図2に示す。

f:id:msyksphinz:20151115234602p:plain

f:id:msyksphinz:20151115234616p:plain

TensorFlowのグラフでは、各ノードには0個以上の入力と0個以上の出力が付き、各ノードは「演算」を表現する。グラフ中の辺を流れる値のことを「テンソル」と呼ぶ。 また、制御フローを管理するために、データフローだけでなく、制御フローも挿入されていることがある。これは、メモリの最大利用可能値を制御するときなどに、制御の順番を 決める必要があるときなどに利用する。

演算とカーネル

「演算」は演算の名前と抽象的な演算内容を表現する(例えば、行列積や、加算など)、演算には「属性」が付いているが、これは方の変換などの属性を付加するときに利用する。 カーネルは「演算」の特定のデバイスでの実装になっており、例えばCPUの実装やGPUの実装などがある。 TensorFlowのライブラリでは、特定の演算に対して特定のデバイスでのカーネルが定義されており、これを登録することで、特定のデバイスでカーネルを実行できるようになる。 表1に、TensorFlowに付属しているそれらの演算の一覧を示す。

カテゴリ
要素毎の数値演算 加算、減算、乗算、除算、指数関数、対数関数、大小比較、等号...
配列演算 結合、スライシング、分割、定数、ランク、SHape,シャフル...
行列演算 行列積、逆行列固有値計算...
状態演算 変数、アサイン、変数への加算...
ニューラルネットのビルディングブロック SoftMax, Sigmoid, ReLU, Convolution2D, MaxPool, ...
キューと同期操作 エンキュー、デキュー、ミューテックスの取得、ミューテックスの解放
制御フローの演算 マージ、スイッチ、入力、解放、NextIteration

セッション

TensorFlowでシステムを汲み上げるときには、「セッション」を作成する。 計算グラフを作成するためには、セッションを作成して、そこにノードとノード間を接続する辺を挿入していくことになる。 セッションの別の重要なメソッドとして「Run」というものがある。Runは計算に必要な出力名と、ノードの特定の出力に置かれているグラフに食わせるためのテンソルの集合を持たせることができる。 Runの引数を利用することによって、TensorFlowの実装は全てのノードについて推移閉包を計算することができ、要求された出力を計算することができるようになる。 まずはセッションを作成してグラフを作成し、Runを実行して、グラフ内の非常に多くの回数の計算を実行していくことになる。

変数

計算グラフは殆どの場合複数回実行されるが、殆どのテンソルは、一回の実行で内容を消失してしまう。 しかし、「変数」を利用し、AssignやAssignAdd(+=)を利用することで、グラフ内の計算が終了してもその計算結果を保持し、次の計算に値を再利用することができる。 TensorFlowの機械学習アルゴリズムでは、モデルのパラメータは変数置かれてテンソルに格納されており、モデルのトレーニング中のRunにより値を更新していくことになる。

3. 実装

TesorFlowの実装には、主要なコンポーネントとして「クライアント」、つまりセッションと「マスター」を接続するためのコンポーネント、 そしてマスタとデバイスを接続するための「ワーカープロセス」が存在する。 実装には、ローカル版と分散版が存在し、ローカル版は上記のコンポーネントが全て一つのローカル環境に存在する場合である。 一方で、分散環境では殆どのコードはローカル実装と共用であるが、クライアント、マスター、ワーカプロセスが異なるマシンの異なるプロセスで動作するようになっている。 これらのプロセスはクラスタのスケジューリングシステムによって管理されている。

f:id:msyksphinz:20151115234719p:plain

デバイス

TensorFlwoは必ず一つ以上の「デバイス」を持っており、そのデバイスのタイプ、インデックスなどを管理している。 例: /job:localhost/device:cpu:0 or /job:worker/task:17/device:gpu:3 など これらのデバイスをTensorFlowのシステムに登録することで使えるようになる。 そして、カーネルをこれらのデバイスに割り当てることによってTesorFlowの高位の実装が、それぞれのデバイスの実行に落とされていく。

テンソル

テンソルは様々な型を取ることができる多次元配列として表現される。整数から浮動小数点などを格納する。 適当なサイズのバックアップ領域がデバイスにより割り当てられ、テンソルが格納される。 テンソルがそれ以上参照されなくなると、自動的に開放処理がなされる。

3.1 シングルデバイスでの実行

まずは最も簡単な、シングルデバイス、シングルワーカーでの実行環境を考える。 グラフが存在し、依存関係に従って順にノードが実行されていく。 各ノードには、まだ未実行な依存関係のあるノードの数が付加されており、この数が0になると、その対称のノードは取り外され、実行レディーのキューに挿入される。 ノードの実行が完了すると、そのノードに依存する全てのノードのカウントがデクリメントされる。

3.2 複数デバイスでの実行

実行するデバイスが複数になると、2つの問題が生じる。一つは、どのノードをどのデバイスで動作させるか、という点、そしてもう一つは、デバイス間の通信をどのようにして管理するかという点である。 これらの問題について解決していく。

3.2.1 ノードの配置

TensorFlowの一つの役割として、どのノードをどのデバイスにマップするかという問題が上げられる。ここでは基本的なアルゴリズムについて述べるが、 4.3節ではより拡張したモデルについても述べる。

配置アルゴリズムの入力として、コストモデルを利用することが上げられる。これはノードに対する入力のサイズなどの情報をj利用して、計算時間を予測して静的に ノードの割り当てを決定するというものである。これはグラフの実行前に割り当てのノードが決定される。

配置アルゴリズムはまず始めにグラフの近似実行を行う。 シミュレーションでは、グラフ中の各ノードに応じてデバイスをピックアップしていく。シミュレーションによって 得られるノードからデバイスへの配置の生成結果は、実際の実行の際にも利用される。

配置アルゴリズムは、グラフの入力側から探索を開始する。ノードにぶつかると、まずそのノードの演算が実現可能なデバイスをピックアップする。 例えば、演算によっては特定のデバイスによっては実行できないケースがあるので、これを考慮する。 実行可能なデバイスの中で、グリーディーヒュrーリスティックなアルゴリズムを使って、計算時間を見積るか、過去の計算時間の計測から予測して、 コストを計算する。基本的には、最も速く計算が可能なデバイスを選択して、計算時間を割り当てて、次のノードへ進んでいく。 4.3節ではこのフローを拡張して、配置アルゴリズムにヒントを与えたり、特定の制約を与える方法について述べる。 この配置アルゴリズムはこれが最適な訳ではななく、プロジェクトの中でまだ研究段階の領域である。

3.2.2 デバイス間の通信

ノードの割り当てが完了すると、デバイス毎にノードが分割される。そして、各ノードの通信で、まとめられるところはまとめて、ノードの送信側にはsendノード、 ノードの受信側にはrecvノードが付加される。図4にその例を示す。

実行時には、SendとReceiveノードがデバイス間の通信を調停する。 これにより、SendとRecvの通信内容を、計算から独立させることができるようになる。

SendとRecvを挿入すると、ノード毎に一つのRecvノードが接続されるように、Recvノードをまとめる作業が入る。これにより、到着ノードとRecvの対応が一対一になる。

このような方法で通信を管理することで、グラフ内の異なるデバイスの個々のノードのスケジューリングを、ワーカープロセスに分散化できるようになる: SendとRecvノードは異なるワーカーとデバイスの同期が必要なことを知らせ、マスターは単純にRun時にグラフの実行を各ワーカーに伝えるだけでよく、 スケジューリングまで考慮する必要がなくなる。

f:id:msyksphinz:20151115234749p:plain

3.3 分散実行

グラフの分散実行は、複数デバイス実行と非常に似ている。 デバイスの配置が決定すると、デバイス毎にサブグラフが作成される。Send/Recvノードペアがワーカープロセス間で通信し、TCPやRDMAなどのメカニズムを使ってデバイスを越えてデータの授受を行う。

フォールトトレランス

分散実行では、様々な場所で実行の失敗を検出し得る。例えばSendからRecvへのデータの授受であったり、マスタープロセスから全てのワーカープロセスに対するヘルスチェックであったりである。 失敗を検出すると、全てのグラフの実行は停止され、最初かっr再実行される。 しかし、変数は保持されることを思いだして欲しい。従って、チェックポイントおよびリカバリのシステムを用意している。 特に、このような変数ノードは、保存ノードというものに接続されており、この保存ノードが定期的に変数を保持することで、システムが落ちても、チェックポイントからリスタートできるようになっている。 4.2節では、この手法がどのようなグラフのノードの実行においてのみ有効であるかについて述べる。