こんにちは。Anieca です。
先日、Google が開発する Deep Learning のフレームワーク「Tensorflow」がメジャーアップデートされ、2.0 系になりました。
これから何回かに分けて、Tensorflow 2.0 の記事を書いていきたいと思います。
最初は -背景 編- です。(のであまり Tensorflow 関係ないです。)
Tensorflow 2.0
Google社の開発しているディープラーニングのフレームワークです。
兼ねてより予定していたメジャーアップデートを経て 2.0系 になりました。
細かい変更点はたくさんありますが、やはり一番大きな変更点は「 Eager Excecutoin がデフォルトになる」事だと思います。
Eager Execution とは
直訳すると<積極的実行>で、「計算グラフの作成と評価を同時におこなう実行方法」です。
これは「Define-by-Run」と呼ばれる方式です。
Eager Execution がデフォルトになる前の Tensorflow は「グラフの作成を全て終えてから評価を行う実行方法」がデフォルトになっていました。
これは「Define-and-Run」と呼ばれる方式です。
Eager Execution がデフォルトになったと言うことは、実行方法のデフォルトが「Define-and-Run」から「Define-by-Run」になったことを表しています。
この二つは何が違うのでしょうか。次節でこれらの方式について説明します。
Define-and-Run
最初に計算グラフ(モデルが実施する計算の連続)を全て宣言し終えてからデータを与えて学習を行う方式が「Define-and-Run」です。
1系の Tensorflow や Caffe2 といったフレームワークがこれを採用しています。
Define-and-Runのメリットは、学習が早いことです。
計算グラフを事前に定義してコンパイルするので高速に動作します。
ディープラーニングの学習は数日単位かかることも多いので、早いことのメリットは非常に大きいです。
一方、Define-and-Run のデメリットは「計算グラフの途中変更が不可能なこと」、「記述が難解になること」です。
特に、自然言語を扱うディープラーニングにおいては与えるデータや出力する結果の長さが変わることは良くあるので、そういった場合に事前にモデルを定義するのは難しいです。
事前に定義することが難しい場合は、考えうる最大の大きさでネットワークを定義しておき、空きがある場合は無効な値で Padding するのが一般的です。
しかし、これは計算量の増加につながるので、計算そのものは高速に動作するのに計算量が増えてしまうことになります。
なお、二つ目の「記述が難解になる」ことについては、曖昧ですが非常に良く聞く表現です。
これについては、そもそも「Define-and-Run」流の書き方はディープラーニングを行うにあたってメジャーなプログラミング言語である「Python」との相性があまり良くないことが原因のように思います。
と言うより、「Pythonを主に扱うエンジニアにとって理解しづらい構文になっている」と言うのが正しいかもしれません。
その理由は、Pythonの構文は基本的に「Define-by-Run」ライクだからです。
Python は 「Define-by-Run」?
以下のコードを見てみましょう。
a = 3
b = 5
c = a + b
c はどんな値が入っているでしょうか?
Python に慣れ親しんだ人はInt 型の整数 8 が入っていると判断すると思います。そしてそれは正しいです。
では、なぜ c = 8 とわかるのでしょうか。少し深掘りして考えてみます。
この3行目で行われている処理を詳しく分解すると、以下の処理が行われています。
- 式 a + b を定義
- 式 a + b を評価(計算)し、c に代入
式を定義し、評価して、値を代入しています。
これは、「Define-by-Run」です。そして、これは Python において一般的な書き方といってよいでしょう。
このように、Python は「Define-by-Run」ライクに書かれることが多いです。
では「Define-and-Run」の方式で先ほどのコードを実行するとどうなるでしょうか。c はどんな値が入っているでしょうか。
上述の説明を思い出すと、「Define-and-Run」はまず計算グラフを全て宣言します。計算はしません。
そのため、c はあくまで「 a + b 」を表しています。この段階では式が定義されたにすぎず、実行されていないのです。
式を実行するためには、明示的にそれを示す必要があります。
したがって、「Define-and-Run」流にコードを書き直すと、以下のようになります。
a = 3
b = 5
# この段階では計算されないため、文字列のように式を保持している
c = "a + b"
# 実行コマンドで実行して、初めて計算される
ans = exec(c)
どうでしょうか。これは随分と不自然な感じがしますよね。
この書き方は高速に動作する一方でPythonのプログラミングをするにあたってあまり頻繁に使われる方式ではありません。
Define-and-Run 方式では常にこの書き方を強いられるため、難解な記述と考えられていると思われます。
Define-by-Run
Define-by-Runのメリットは動的に計算グラフが変更可能な点と、直感的に理解しやすい書き方ができる点です。
この方式を提案したのは日本発のベンチャー企業である Prefferd Networks 社です。同社のフレームワーク「Chainer」でその恩恵を預かることができます。
最近では FacebookAI が Chainer からフォークした 「Pytorch」 が一躍有名になっており、多くのエンジニアがこの方式を支持しています。(方式を支持しているというより、信頼できて便利な「Pytorch」を使っているだけかもしれませんが)
特に、「Define-and-Run」の唯一に近いメリットである速度においても、これらのフレームワーク開発者の尽力により「Define-and-Run」以上の速度が出るケースもあるとのレポートが提出されており、ほとんど「Define-and-Run」の上位互換のような状態になってきています。
終わりに
これらの背景もあり、Tensorflow もメジャーアップデートのタイミングで方式を切り替えてきたのだと思われます。
おそらく「Define-and-Run」だけのフレームワークは廃れていくでしょう。
「Define-by-Run」で高速にプロトタイプを作成し、高速化のために「Define-and-Run」化するような区分けになるかと思います。
色々背景はありますが、とってもシンプルにいうと
「豊富なライブラリをもつ Tensorflow が書きやすくなった!」
ということに尽きます。
次回から Tensorflow の使い方を解説していきたいと思います。
引き続きよろしくお願いします。
コメント