2017年2月20日月曜日

Google機械翻訳に注目!

科学でも技術でも、その発展には、飛躍がある。もちろん、飛躍にも小さいものから大きいものまでいろいろあるのだが。
面白いことに、何かが共鳴するように、いろんな飛躍が、ある時期に集中して起きることがある。
人工知能の分野では、2012年がそうした時期であった。ImageNetの画像認識の国際的コンテストで、CNNを使ったAlexNetが圧勝し、Googleの巨大なシステム DistBeliefが、教師なしでも猫の認識に成功する。それが、2012年だ。
この時から、人工知能ブームが起き、技術的には、ニューラル・ネットワークを用いたDeep Learning技術が花形となる。そして、そうした技術的な達成は、ただちに、ビジネスの世界で応用を見つけ出していく。
現在、各社が取り組んでいる自動運転技術と、Alexa, Google Now, Siri 等のボイス・アシスタント・システムが、コンシューマー向けのAIプロダクトとしては、最も大きなマーケットになるのだが、そこでは、Deep Learning技術のCNNが、中心的な役割を演じている。
ただ、技術の変化は早い。
今また、2012年のブレークスルーに匹敵する目覚ましい飛躍が、AIの世界で起きようとしている。昨年11月にサービスが始まった、Googleの「ニューラル機械翻訳」がそのさきがけである。
そこで使われている技術は、RNN/LSTMと呼ばれるものである。
Googleの新しい機械翻訳のシステムは、LSTMのお化けのような巨大なシステムなのだが、その基本原理を理解することは、誰にでもできる。(今度のマルレクにいらしてください)
2012年に続く、この第二の飛躍が、どのようにビジネスに生かされるかは、まだ未知数だが、必ず、現実の世界で利用されていくようになるだろう。
今の眼で、2012年の「Googleの猫」のDistBeliefを振り返れば、かつては驚嘆して見上げたそのシステムが、ハードワイアーでプログラミングをしていた、かつての「巨大システム」ENIACのようなものに、見える。
Googleの機械翻訳のシステムも、いずれ、スマートフォンに収まるようになるだろう。僕の好きな「翻訳こんにゃく」は、早晩、実現されるだろう。
さらに、変化は加速している。
第三の飛躍を可能にしそうな技術の姿も、見え始めている。それは、量子コンピューターとは、別の切り口で、従来のノイマン型コンピューターのアーキテクチャーを変える可能性がある。
技術者にとって、こうした大きな変化の波に遭遇しているというのは、ある意味、幸せなことだと思う。

2017年2月19日日曜日

ダメだこりゃ。LSTMの図を書き直すの巻。

マルレクの準備で、図を書いているのだが、文章よりも図の方を先に書いていると、どうも、スライドがパラパラ漫画風になってしまう。ま、それは、楽しいし、そんなに悪いことではないと思っているのだが。

ただ、問題は、書いているうちに、前の図が気に食わなくなることがあること。文章の訂正は簡単だが、図の訂正は手間がかかる。その上、パラパラ漫画になっていると、同じような図が連続してたくさんあって、修正箇所も多くなる。うーん。

今回、でも、どうしても気に食わなくて、というか自分の認識不足があって、図を描き直そうと決心した。

実は、もっと深刻な問題もあって、講演の発表予定順に、スライドを準備しているわけではないので、後からの図の訂正は、実は、すでに完成している大量のスライドの図に影響が出る。自業自得なのだが、さあ、どうするか。

昨日、LSTMの形として、次のものを提示した。


今日になって、この図が、どうしても、気に入らなくなった。真ん中の丸の、「\(C_t\)を生成」の部分が気に入らないのだ。

この丸では、二つの演算が行われている。下から「tanh」の部分と、その上の「+」の部分だ。実際に、「\(C_t\)を生成」しているのは、「+」の部分だけだ。この丸は大きすぎる。

気に入らない、もう一つの理由は、LSTMの内部の演算は、全て、LSTM内部のGateと関連していることがある。図の一番左にあるGateは、この図では、ちょっと仲間外れにされている気がする。そうではなく、このGateは、演算「+」の仲間なのだ、

他にも問題がある。僕が、不都合に気づいたのは、実は、この図はLSTMの展開形なのだが、それをループを含む再帰形で表そうとして、うまくいかなかったからである。

修正版は、こちら。各ユニットが、一つの計算を行い、それぞれがGateに関連づけられているのが、見やすいのがわかると思う。


この展開形を、ループを含む再帰形で表したのが、こちらになる。図中の黒い四角は、データを変えることはないが、変数の添字の時間の部分だけを、一つ進めることを表している。




このように、LSTMの再帰形は、二つのループを含む。それは、LSTMの展開形が、横串二本で貫かれていたことから、想像できることだ。

本当は、この図のスタイルで、最後まで行くのがいいのだが、最初の泣き言で書いたように、この続きの図はもうできている。ということで、後半、LSTMの図のスタイル、元のままで行こうと思う。

以下に、二つの図を示す。この二つが、同じものであることを、各自確かめられたい。



これらの図には、「状態を生成」「記憶を生成」「入力を生成」といった新しい、ラベルがついている。それについては、後で詳しく述べる。特に「記憶を生成」に注目してほしい。LSTMは、「記憶」を持つRNNなのである。







2017年2月18日土曜日

LSTMの中の三つのGate

ここでは、LSTMの中で、Gateがどのようなところに配置されているかを見てみよう。

あらためて、RNNとLSTMの比較をしてみよう。

まずは、RNNから。


隠れ層に、横串が一本通っているが、RNNは単純な三層構造のニューラル・ネットワークが単位であることは、わかると思う。

では、LSTMは、どうであろうか?


横串が、一本増えた。

ただ、この二つの図を見ているだけではわからないことがある。もう少しだけ詳しく、内部の様子を見てみよう。

まずは、RNNから。

基本となるのは、RNNの次のような構成である。この図では、矢印に添えられた文字は、情報の名前ではなく、重みであることに注意されたい。

これまでも、また、これからも繰り返し登場する重要な式は、Full Connectなニューラル・ネットワーク一層の出力は、入力をX、重みをW、バイアスをb とした時に、\( \phi ( W \cdot X + b) \) で表されるということである。

RNNでは、隣り合う隠れ層もFull Connectである。ここでは、RNNの隠れ層が受け取る二つの入力 \(h_{t-1}\) と \(x_t\) から、次の式で、出力が計算されている。
$$ h_t = \phi _h ( U \cdot x_t + W \cdot h_{t-1} + b_h $$
ここで、基本式に新たに付け加えられた \(W \cdot h_{t-1}\)が、隣の隠れ層からの入力に対応している。


この関係式を、先のRNNのグラフに書き込んでみよう。次の図が、それである。
出力層では、活性化関数とバイアスが省略されている。



この図に対応するLSTMのグラフは、次のようになる。


似ているようでもあり、似ていないような気もするが。

ただ、先の図には、重要な情報が抜けている。LSTMが、もっともRNNと異なるのは、それが内部にGateを持つということだ。それを図示したのが、次の図である。


LSTMは、内部に三つのGateを持っている。それが、LSTMの振る舞いを、RNNとは大きく変えることになる。


LSTMをRNNと比較する

LSTMは、RNNの一種である。ただ、LSTMは、旧来の単純なRNNとは異なっている。ここでは、LSTMとかつてのRNNを比較してみようと思う。

かつてのRNN(以下、単にRNNと呼ぶことにする)は、次のような形をしていた。


両者の比較を図示すれば、次のようになる。

RNNでは、横の繋がり(展開形で)は、h層の一本だけだったのだが、LSTMでは、もう一本多く横のつながりを持つ。このc層は、LSTMで新たに付け加えられたものだ。あとで見るように、LSTMでは、この層が独自のとても大事な働きをすることになる。

この図では、入力は水色、出力は緑色で表されているのだが、RNNは、二つの入力 \(h_{t-1}\) 、\( x_t \) を持ち、二つの出力 \( h_t\) 、\( o_t \) を持つ。それに対して、LSTMは、三つの入力 \(h_{t-1}\) 、\( c_{t-1} \)、\( x_t \) を持ち、三つの出力 \( h_t\) 、\(c_t \)、\( o_t \) を持つ。


LSTMの内部を、簡単に見てみよう。(詳細な説明は、後にする。)基本的には、左の図に対応して、LSTMは、\( h_t \) を生成する層と、\( c_t \) を生成する層の二つからできている。

ここでは、次の二つのことに注意しよう。

第一に、LSTMの出力 \( o_t \)は、\( h_t \) と同じものである。先ほど、三つの出力があると書いたが、三つのうち二つは、同じ \( h_t \)である(右の図で、上方向と横方向の出力の \( h_t \) )。右と左の図を比べて、c層の位置とh層の位置が、上下反対のように見えるのは、h層が出力を行う層だからである。また、右図の大きな回り道は、\( h_t \) が、二箇所で利用されているからである。

第二に、それでは、情報の流れは、右の図の大きなノード間の、c層からh層への流れであって、左の図にあるような h層からc層の流れは、存在しないのであろうか? そうではない。h層への入力である \(h_{t-1}\)  は、入力 \( x_t \) と合流して、それがc層に流れている。ここでは、二つの入力を合流させるのに、二つのベクトルを横に繋げて連結するという方法を取っている。(右図の下の方)



連結する(concatanate)とは、次のような処理である。

\( h_{t-1} = (h_1, h_2, h_3. \ldots , h_m) \) で、\(x_t = ( x_1, x_2, x_3. \ldots , x_n) \) なら、\( [ h_{t-1} , x_t ] = ( h_1, h_2, h_3. \ldots , h_m, x_1, x_2, x_3. \ldots , x_n) \) になるということである。 


2017年2月17日金曜日

TensorFlow 1.0リリースと TF Dev Summit 2017 でのJeff Deanのキーノート

2月15日、TensorFlow 1.0をリリースした、TensorFlow Developers Summit 2017 のキーノートの様子です。

冒頭のスピーチは、もちろん、Jeff Dean。タイトルは、「TensorFlow : ML for Everyone」 みんなのための機械学習ですね。


TeasorFlow登場以前の、「第一世代」の"DistBelief" の話から始まります。2012年の「Googleの猫」で有名なシステムですね。スケーラブルで実際に製品版として動いていたんだけど、GPUもCPUもハードは固定されていて柔軟ではなかったと。RNNや強化学習にも使えなかったと。


それと比べると、「第二世代」のTensorFlowは、多くのプラットフォームをサポートして(Rasberry Piの上だって動く)、その上、Googleのクラウド上でも動く(それにもたくさんの方法がある)。



そして、TensorFlowは、Pythonを始め、C++, Java, Go, Haskell, R と、多くの言語をサポートしている。(Haskellでも動くんだ、知らなかった。もっとも最近出た Fold は、関数型プログラミングそのものだからな。)


TensorFlowの大きな特徴は、TensorBoardのような、Visualizationの優れたツールを、一緒に提供していること。


TensorFlowは、また、Google自身の内部でも、検索、Gmail、翻訳、地図、Android、写真、スピーチ、YouTube ... 様々な分野で、急速に利用が進んでいる。


オープンソースのTensorFlowの開発は、GitHubの機械学習の分野では、ナンバーワンのレポジトリーの数を占めている。


機械学習の様々なフレームワークの中でも、TensorFlowが、圧倒的に利用されている。

GitHubでのStarの数は、

  1. TensorFlow   44508
  2. scikit-lean     16191
  3. Caffe            15690
  4. CNTK             9383
  5. MXNet           7896
  6. Torch             6285
  7. Theano          5568 

である。


TensorFlowは、非常に活発なオープンソースのコミュニティを形成している。
今回発表したTensorFlow1.0には、Google 以外から、475名以上がコントリビュートしをし、この14ヶ月で、14,000回以上のコミットがなされた。多くのコミュニティが、チュートリアルを作成し、モデルを提供し、翻訳や新しいプロジェクトを立ち上げた。GitHubには、TensorFlowをタイトルに持つ、5,500以上のレポジトリーがある。


プロジェクトがローンチしてからの、短い間にも、大きな進歩があった。


これらすべてが、この一年と少しの間に起きたのだ。


2017年2月16日木曜日

Cloud Spannerについて

Googleが、これまで自社内部で利用してきたSpannerをクラウド・サービスとして公開した。

"Introducing Cloud Spanner: a global database service for mission-critical applications"

2012年のOSDIで論文が公開されたSpannerは、key-valueをベースにした拡張性と、RDBのACIDなトランザクション処理能力を併せ持つ強力な分散データベースである。タイムスタンプ・ベースのトランザクション処理に必要な正確な時間を得る為に、GPSや原子時計を利用。その上に、時間の不確実性を組み込んでいる。

当時、次のSpanner論文を日本語に翻訳した。(当時、残念ながら、Google翻訳はなかった。)


丸山による翻訳は、次のリンクからダウンロードできる。利用されたい。

その頃のGoogleの大規模分散システムの動向については、Spannerを含めて、2013年のマルレクで取り上げている。「大規模分散システムの現在 --- GFS, MapReduce, BigTableは、どう進化したか」合わせて参照されたい。



LSTM -- Gateを持つRNN

RNNは、基本的には、単純な三層構造を持つネットワークをユニットとして、その隣り合う隠れ層同士をフルコネクトで横につなげたものだ。ただし、Feed ForwardのDNNのように、実際に、ユニットを積み重ねるのではなく、隠れ層を結合する重みのパラメーターを共有し、再帰的にシステムを記述する。

RNNの発展系LSTMも、こうしたRNNの基本的なアイデアを踏襲している。ただ、組み合わせの基本となるユニットが少し複雑な構成をしている。LSTMのユニット(Cellと呼ばれる)に導入された新しいアイデアの中心にあるのが、今回、取り上げるGateである。

LSTMの働きを理解するのに、Gateの働きの理解は必須であるのだが、同時に、それは、LSTMの働きを理解する、最も早い近道でもある。

今回は、RNNやLSTMの文脈を離れて、その意味では、天下り的であるが、ニューラル・ネットワーク上のGate回路について見てみようと思う。

次の図の左側が、Gateの構成をグラフで示したものである。
Gateは、入力( In )を出力( Out )に渡すのだが、その流れをGateに与えられる第三の情報( X )でコントロールするのだ。


Gateを構成しているのは、二つの回路だ。(図の右)

一つの回路(図右下)は、Gateに与えられる情報 Xを、Gateを直接コントロールする情報 Controlに変える。もう一つの回路は、Control 情報の元で、入力 In を出力 Out に変える。

Gate内部で、XからControlを生成する回路(図右下)は、Sigmoid関数を活性化関数とする一層のフルコネクトのニューラル・ネットワークである。図中の四角の中に書かれたσは、この層の活性化関数がSigmoidであることを表している。ただし、この層の重み W やバイアス b は、この図では省略されている。

ContorolとInから、Outを生成する回路(図右上)が行なっているのは、簡単な演算である。二つの量を掛け合わせるだけ。ただし、掛け合わせは、ベクトルの要素ごとに行われる。これは、Hadamard積と呼ばれるものだ。Hadamard積については、次の図を参考にされたい。


こうして、Gateが行なっている働きは、次の二つの式で表すことができる。

$$Out = Control \cdot In$$
$$Control = \sigma ( W \cdot X + b)$$

ただし、前者の積(・)は要素ごとのHadamard積、後者の積(・)は、行列とベクトルの積である。


Control信号を生成するのに、Sigmoid関数σが使われているのには理由がある。σ(x)は、0と1の間の値をとるのだが、xが正の時には、ほとんどのxについてσ(x)は、ほぼ 1となり、xが負の時には、ほとんどのxについてσ(x)は、ほぼ 0となる。


こうして、σから出力された Controlが 0の時、Gateの出力は、\( Out = Control \cdot In = 0 \cdot In = 0 \) となり、情報は流れず、Controlが 1の時、Gateの出力は、\( Out = Control \cdot In = 1 \cdot In = In \)となり、情報はそのまま流れることになる。






2017年2月15日水曜日

Google様にAdSense落とされた!

1月からblogを始めた。

Google様謹製のBloggerを使ったので、どうせならAdSenseも使ってみようと申し込んだ。

ところが、先日、こんな通知が来て、Google様にAdSense落とされた!

-----------------------------------------------------
お客様のアカウントで次の違反が確認されましたので、アカウントは不承認となりました。
承認されなかった理由は次のとおりです。

不十分なコンテンツ(ここ太字): AdSense の承認を得てサイトに関連性の高い広告を掲載していただくには、AdSense の担当者による審査とクローラによる解析でページの内容を判断できるだけのテキストがページに含まれている必要があります。

この問題を解決するには、次のヒントをお試しください。

・ページで十分なテキストを使用してください。画像や動画、Flash アニメーションがほとんどを占めるウェブサイトは承認されません。

・コンテンツには、タイトルだけでなく、意味のある文章や段落が含まれている必要があります。

.....(以下略)
-----------------------------------------------------

僕のblog は、「ページの内容を判断できるだけのテキストがページに含まれて」いないと判断されたらしい。コンテンツでWelqにも負け、「担当者による審査」Welqチェックにも敗れたということなのかしら?

「コンテンツには、タイトルだけでなく、意味のある文章や段落が含まれている必要があります。」と言われるのは、なんかおかしいと思うけど、まあ、いいか。いろいろあらーな。

明日から、SEO対策しよ。

それよりも、Google様にいぢめられた丸山が、かわいそうだと思ったら、丸山のblogにアクセスしてください。

2017年2月14日火曜日

TensorFlow Fold Blocks チュートリアル (Google翻訳バージョン)

TensorFlow FoldのBlocks Tutorial を翻訳しました。
嘘です。全部、Google翻訳です。

Blocks Tutorial
  • Introduction 
    • Motivating example
  • Basic concepts
  • Primitive blocks 
    • Converting Python objects into tensors.
    • Using TensorFlow tensors.
    • Functions and Layers
    • Python operations
  • Block composition
    • Wiring blocks together
    • Dealing with sequences
    • Dealing with records
    • Wiring things together, in more complicated ways.
    • Recursion and forward declaration

Introduction

読者がTensorFlowの基本的な概念と深い理解を理解していると仮定します。そうでない場合は、TensorFlowチュートリアルを開始するのがよいでしょう。

Foldモデルへの入力は、Pythonオブジェクトのミニバッチです。これらのオブジェクトは、プロトコルバッファ、JSON、XML、またはある種のカスタムパーサをデシリアライズして生成することができます。入力オブジェクトは木構造であると仮定される。 Foldモデルの出力は、TensorFlowテンソルの集合であり、通常の方法で損失関数とオプティマイザに接続できます。

入力としてデータ構造のミニバッチが与えられた場合、Foldは入力データを走査し、TensorFlowによって効率的に実行できる方法で操作を結合およびスケジューリングします。たとえば、ツリー内の各ノードが、共有ウェイトを持つ完全に接続されたレイヤーを使用してベクトルを出力する場合、Foldはツリーを単にトラバースするだけでなく、ベクトル行列の乗算演算を行います。代わりに、ツリー内の同じ深さのノードをマージします。これは、より大きい、より効率的な行列 - 行列乗算演算に並行して実行され、その後、出力行列を再びベクトルに分割します。

Motivating Example

次のコード例では、階層型のLSTMを実装しています。これはFoldでは簡単ですが、TensorFlowでは実行しにくいものです。

# Create RNN cells using the TensorFlow RNN library
char_cell = td.ScopedLayer(tf.contrib.rnn.BasicLSTMCell(num_units=16), 'char_cell')
word_cell = td.ScopedLayer(tf.contrib.rnn.BasicLSTMCell(num_units=32), 'word_cell')

# character LSTM converts a string to a word vector
char_lstm = (td.InputTransform(lambda s: [ord(c) for c in s]) >>
             td.Map(td.Scalar('int32') >>
                    td.Function(td.Embedding(128, 8))) >>
             td.RNN(char_cell))
# word LSTM converts a sequence of word vectors to a sentence vector.
word_lstm = td.Map(char_lstm >> td.GetItem(1)) >> td.RNN(word_cell)


階層的LSTMは文字列のリストを入力として受け取り、各文字列は単語であり、出力として文脈ベクトルを生成する。 2つの入れ子になったLSTMを使用してこれを行います。文字LSTMは文字列を入力とし、出力として単語ベクトルを生成します。文字列を整数のリスト(td.InputTransform)に変換し、埋め込みテーブル(td.Embedding)の各整数を検索し、埋め込みシーケンスをLSTMで処理して単語ベクトルを生成します。単語LSTMは単語LSTMを単語のシーケンスにマッピングして(td.Map)単語ベクトルのシーケンスを取得し、単語ベクトルを第2の大きなLSTMで処理して文章ベクトルを生成します。


以降のセクションでは、これらの操作について詳しく説明します。

Basic concepts

Foldモデルの基本コンポーネントはtd.Blockです。ブロックは本質的に関数であり、オブジェクトを入力として受け取り、出力として別のオブジェクトを生成します。問題のオブジェクトはテンソルであってもよいが、タプル、リスト、Python辞書、またはそれらの組み合わせであってもよい。 typesページでは、Foldタイプのシステムについて詳しく説明しています。

ブロックは、プログラミング言語の表現のように、ツリーに階層的に編成されています。ここでは、大きくて複雑なブロックが小さくて単純なブロックで構成されています。ブロック構造はDAGではなくツリーでなければならないことに注意してください。言い換えれば、各ブロック(すなわち、以下のブロッククラスの1つの各インスタンス)は、ツリー内で固有の位置を持たなければならない。型チェックとコンパイルの手順は、ツリーのプロパティに依存します。

Primitive blocks

プリミティブブロックは、ブロック階層の葉を形成し、テンソルについての基本的な計算を担う。

Pythonオブジェクトをテンソルに変換する。

td.Scalar()ブロックは、Pythonスカラーを0ランクのテンソルに変換します。
td.Vector(shape)ブロックは、Pythonリストを与えられた形状のテンソルに変換します。

TensorFlowテンソルを使用する。

td.FromTensorブロックは、TensorFlowテンソルをブロックにラップします。引数のない関数と同様に、FromTensorブロックは入力を受け付けません。出力として対応するテンソルを生成するだけです。 FromTensorはまた、numpyのテンソルで使用することもできます。

  td.FromTensor(np.zeros([])) # a constant with dtype=float64
  td.FromTensor(tf.zeros([]))  # a constant with dtype=float32
  td.FromTensor(tf.Variable(tf.zeros([])))  # a trainable variable

Functions and Layers

td.Functionブロックは、TensorFlow操作をブロックにラップします。テンソルを入力として受け取り、出力としてテンソルを生成します。複数の引数を取る、または複数の結果を生成する関数は、入出力としてテンソルのタプルを渡します。たとえば、td.Function(tf.add)は、2つのテンソルのタプルを入力とし、1つのテンソル(合計)を出力として生成するブロックです。

ファンクションブロックは、完全に接続されたレイヤーや埋め込みなど、一般的なニューラルネットワークの計算を実行するために、レイヤーと組み合わせて使用​​できます。 td.Layerは呼び出し可能なPythonオブジェクトで、レイヤーの異なるインスタンス間でウェイトを共有します。

以下の例では、ffnetは3層フィードフォワードネットワークで、3つのレイヤーのそれぞれが重みを共有しています。

# fclayer defines the weights for a fully-connected layer with 1024 hidden units.
fclayer = td.FC(1024)
# Each call to Function(fclayer) creates a fully-connected layer,
# all of which share the weights provided by the fclayer object.
ffnet = td.Function(fclayer) >>  td.Function(fclayer) >> td.Function(fclayer)

>>演算子は、関数の構成を示します。

Python operations

td.InputTransformブロックは、任意のPython関数をブロックにラップします。 Pythonオブジェクトを入力として受け取り、Pythonオブジェクトを出力として返します。たとえば、次のブロックはPython文字列を浮動小数点数のリストに変換します。

td.InputTransform(lambda s: [ord(c)/255.0 for c in s])

その名前が示すように、InputTransformは、主にPythonで入力データを前処理してTensorFlowに渡すために使用されます。データがパイプラインのTensorFlow部分に到達すると、データ上で任意のPythonコードを実行することはできなくなります.Foldはそのような試みに対して型エラーを生成します。

Block composition

ブロックは、より複雑な振る舞いを持つブロックを作成するために、さまざまな方法で他のブロックと合成することができます。

Wiring blocks together

最も簡単な構成は、>>演算子を使用して、あるブロックの出力を別のブロックの入力に配線することです。構文f >> gは関数の構成を示します。入力をfに送り、fの出力をgに渡し、gの出力を返す新しいブロックを作成します。

例えば、次のブロックはMNISTモデルの一部であり、MNIST画像は長さ784の文字列として直列化されて格納される。各文字列を浮動体のリストに変換し、リストをテンソルに変換し、接続された層。

mnist_model = (td.InputTransform(lambda s: [ord(c) / 255.0 for c in s]) >>
               td.Vector(784) >>             # convert python list to tensor
               td.Function(td.FC(100)) >>    # layer 1, 100 hidden units
               td.Function(td.FC(100)))      # layer 2, 100 hidden units

Dealing with sequences

フォールドは、マップやフォールドなどの高次関数に類似するブロックを使用してデータのシーケンスを処理します。配列は任意の長さとすることができ、長さが例から実施例に変わることがある。シーケンスをあらかじめ定義された長さに切り捨てるか埋め込む必要はありません。
  • td.Map(f):シーケンスを入力として受け取り、ブロックfをシーケンス内のすべての要素に適用し、シーケンスを出力として生成します。
  • td.Fold(f、z):シーケンスを入力として受け取り、ブロックzの出力を初期要素として使用して左折を実行します。
  • td.RNN(c):リカレントニューラルネットワーク。マップとフォールドの組み合わせです。初期状態と入力シーケンスをとり、rnn-cell cを使用して新しい状態と前の状態と入力からの出力を生成し、最終状態と出力シーケンスを返します。
  • td.Reduce(f):シーケンスを入力として1つの値に減らします.fを要素に対して適用し、本質的にfでバイナリ式のツリーを実行します。
  • td.Zip():シーケンスのタプルを入力として受け取り、一連のタプルを出力として生成します。
  • td.Broadcast(a):ブロックaの出力を取り出し、無限の繰り返しシーケンスに変換します。通常、ZipとMapと一緒に使用され、aを使用する関数でシーケンスの各要素を処理します。
サンプル
# Convert a python list of scalars to a sequence of tensors, and take the
# absolute value of each one.
abs = td.Map(td.Scalar() >> td.Function(tf.abs))

# Compute the sum of a sequence, processing elements in order.
sum = td.Fold(td.Function(tf.add), td.FromTensor(tf.zeros(shape)))

# Compute the sum of a sequence, in parallel.
sum = td.Reduce(td.Function(tf.add))

# Convert a string to a vector with a character RNN, using Map and Fold
char_rnn = (td.InputTransform(lambda s: [ord(c) for c in s]) >>
            # Embed each character using an embedding table of size 128 x 16
            td.Map(td.Scalar('int32') >>
                   td.Function(td.Embedding(128, 16))) >>
            # Fold over the sequence of embedded characters,
            # producing an output vector of length 64.
            td.Fold(td.Concat() >> td.Function(td.FC(64)),
                    td.FromTensor(tf.zeros(64))))

FoldブロックとRNNブロックは、LSTMのようなシーケンスモデルをデータのリストに適用するために使用できます。 1つの注意点として、非常に長いシーケンスに対する逆伝播には依然として勾配の問題が残っているため、Reduceを使用すると、シーケンスの長さの対数であるReduceを使用することができます。

さらに、長いシーケンスを処理している間にTensorFlow自体がメモリ不足になる可能性があります。すべての中間結果をメモリに保存してグラデーションを計算する必要があるためです。したがって、Foldはシーケンス長に制限を設けませんが、td.InputTransformを使用して長いシーケンスを管理可能な長さに切り捨てることが依然として望ましい場合があります。

Dealing with records

レコードは、それぞれがPython辞書やprotobufメッセージなどの異なる型を持つ名前付きフィールドのセットです。 td.Recordブロックはレコードを入力として受け取り、子ブロックを各フィールドに適用し、その結果をタプルに結合して出力として生成します。出力タプルは、出力ベクトルを得るためにtd.Concat()に渡すことができます。

たとえば、次のブロックは3つのフィールドのレコードを128次元のベクトルに変換します。これは、埋め込みテーブルを使用してidフィールドをベクトルに変換し、上で定義した文字RNNを使用して名前フィールドを実行し、その位置フィールドをそのまま扱い、3つの結果を連結して完全連結レイヤーに渡します。

# Takes as input records of the form:
# {'id':       some_id_number,
#  'name':     some_string,
#  'location': (x,y)
# }
rec = (td.Record([('id', td.Scalar('int32') >>
                         td.Function(td.Embedding(num_ids, embed_len))),
                  ('name', char_rnn),
                  ('location', td.Vector(2))]) >>
       td.Concat() >> td.Function(td.FC(128)))

完全に接続されたレイヤーは128個の隠れた単位を持ち、長さ128のベクトルを出力します。これは、recがコンパイルされたときの入力(embed_len + 64 + 2)のサイズを推測します。

Wiring things together, in more complicated ways.

>>演算子を使用した単純な関数の構成は、標準のUnixパイプに似ています。通常は、大抵の場合、特に入力データ構造を横断するRecordやFoldのようなブロックと組み合わせると十分です。

しかし、いくつかのモデルでは、より複雑な方法で物事を結ぶ必要がある場合があります。 td.Compositionブロックでは、子の入力と出力を任意のDAGにまとめることができます。たとえば、次のコードでは、LSTMセルをブロックとして定義しています。これは、前述のRNNブロックでの使用に適しています。 lstm_cell.scope()内で定義されたすべてのブロックは、lstm_cellの子になります。 b.reads(...)メソッドは、別のブロックまたはブロックのタプルの出力をbの入力に結び付け、関数適用にほぼ対応します。 bの出力がタプルの場合、b [i]構文を使用してタプルの個々の要素を選択することができます。

# The input to lstm_cell is (input_vec, (previous_cell_state, previous_output_vec))
# The output of lstm_cell is (output_vec, (next_cell_state, output_vec))
lstm_cell = td.Composition()
with lstm_cell.scope():
  in_state = td.Identity().reads(lstm_cell.input[1])
  bx = td.Concat().reads(lstm_cell.input[0], in_state[1])     # inputs to gates
  bi = td.Function(td.FC(num_hidden, tf.nn.sigmoid)).reads(bx)  # input gate
  bf = td.Function(td.FC(num_hidden, tf.nn.sigmoid)).reads(bx)  # forget gate
  bo = td.Function(td.FC(num_hidden, tf.nn.sigmoid)).reads(bx)  # output gate
  bg = td.Function(td.FC(num_hidden, tf.nn.tanh)).reads(bx)     # modulation
  bc = td.Function(lambda c,i,f,g: c*f + i*g).reads(in_state[0], bi, bf, bg)
  by = td.Function(lambda c,o: tf.tanh(c) * o).reads(bc, bo)    # final output
  out_state = td.Identity().reads(bc, by)   # make a tuple of (bc, by)
  lstm_cell.output.reads(by, out_state)

この定義は、説明目的でのみ提供されています。セルの本体は対応するTensorFlow操作をラップするファンクションブロックのみで構成されているため、LSTMセルをコンポジションではなく単純なレイヤーとして直接実装する方がクリーンで効率的です。

Recursion and forward declarations

ツリー再帰的ニューラルネットワークを実装するには、再帰的ブロック定義が必要です。ブロックの型は、最初にtd.ForwardDeclarationで宣言されます。その後、ブロック自体は、順方向宣言を使用して再帰的参照を作成することで通常どおりに定義されます。最後に、td.ForwardDeclaration.resolve_toを呼び出すと、再帰的定義が前方宣言に結び付けられます。

たとえば、TensorFlowを使用して算術式を評価するブロックを次に示します。

# the expr block processes objects of the form:
# expr_type ::=  {'op': 'lit', 'val': <float>}
#             |  {'op': 'add', 'left': <expr_type>, 'right': <expr_type>}
expr_fwd = td.ForwardDeclaration(pvt.PyObjectType(), pvt.Scalar())
lit_case = td.GetItem('val') >> td.Scalar()
add_case = (td.Record({'left': expr_fwd(), 'right': expr_fwd()}) >>
            td.Function(tf.add))
expr = td.OneOf(lambda x: x['op'], {'lit': lit_case, 'add': add_case}) 

expr_fwd.resolve_to(expr)expr_fwdは宣言であり、ブロックではありません。 expr_fwd()を呼び出すたびに、宣言を参照するブロックが作成されます。

2017年2月13日月曜日

2/28 マルレク「RNNの基礎」参考資料まとめ

2/28開催のマルレク「RNNの基礎」では、第一部「RNN」、第二部「LSTM」という構成を考えています。

この間、blogに書いた情報のインデックスをまとめておきました。

「2/28 マルレク「RNNの基礎」参考資料まとめ」http://maruyama097.blogspot.com/2017/02/228-rnn.html
ご利用ください。

2/28 マルレク「RNNの基礎」

第一部 「RNN」



第二部 「LSTM」

 これからです。乞うご期待。


TensorFlow Foldについて

Googleが、TensorFlow Foldというライブラリーを発表しました。
"Announcing TensorFlow Fold: Deep Learning With Dynamic Computation Graphs"
(論文は、こちら)

この図の意味について簡単に説明したいと思います、



"computer scientists love "という文は、文字列あるいはwordのSepquence として見ることができます。普通のRNNやLSTMでは、こうしたSequenceとして入力を受け付けます。

ただ、先のSequenceは、文法的な構造を持っています。"computer scientists"が主語(節)で、"love"が動詞、"putting data in trees"が目的語(節)です。

構文の構造は、((Cpmputer scientists) (love ((putting data) (in tree)))) こんな感じですね。
ただ、先の文字列をSequenceとして入力すると、そうした文法構造の情報は、皆、失われます。それでは、文の持つ情報を正しく引き出すことはできません。

と言って、一つの文ごとに、その文法構造に対応したニューラルネットを構築していたのでは、沢山の文を解析するには、沢山のニューラルネットを構築しないといけなくなります。

一つのモデルで、SGDのバッチを回すというやり方だと、個々にモデルが違うような場合には、うまく、バッチの処理ができません。

この図では、次のようなプログラムが中心的な役割を果たしています。キャプチャーのタイミングが悪くて、図が書けていますが、本当は、単純なループです。


先の図と、この図を足したものが、本当のプログラムに近いかな。


先のデータ((Cpmputer scientists) (love ((putting data) (in tree)))) ですが、データの結合がわかりやすいように、色をつけます。(オレンジ (青 Cpmputer scientists) (紫 love ((赤 putting data) (青 in tree)))) 

この構文を解析すると、(青 in tree)が一番深く、次が(赤 putting data)、次が (紫 love ...)、次が (青 Cpmputer scientists)、最上位がオレンジであることが簡単にわかります。

そこで、先のループするプログラムに、一番深いところから順番にデータを置いて、青->赤->紫->オレンジ とループを回すたびに、フルコネクトのネットワークを積み上げていきます。

そうしてできたフルコネクトのネットワークがこれです。



このフルコネクトネットワークは、先の文の文法構造を正確に反映したネットワークになります。多様な構造を持つ文に対して、同じプログラムで、内部にその文に対応するネットワークを動的に生成するということができるということです。

ただ、この例は、単純なループでしたが、このライブラリーの本当の力は、Map, Fold, Reduce といった関数型の手法の導入ですね。これは、とてもスマートです。

ニューラル・ネットワークにFunctional Programmingの手法を導入しようというアイデアは、Colahが出していましね。"Neural Networks, Types, and Functional Programming" "Data.List Recursion Illustrated"

Deep Learningでの自然言語処理のモデルについては、やはり、Colahの次のblogがとても参考になります。"Deep Learning, NLP, and Representations" 是非、ご覧ください。

----------------------------------------------------------------------------------
この間、blogに書いた情報のインデックスをまとめておきました。
----------------------------------------------------------------------------------

「2/28 マルレク「RNNの基礎」参考資料まとめ」http://maruyama097.blogspot.com/2017/02/228-rnn.html
ご利用ください。

2/28 マルレク「RNNの基礎」

第一部 「RNN」

第二部 「LSTM」

 これからです。乞うご期待。

2017年2月12日日曜日

Back to the Future -- RNNの復活

RNNは、その"Sequence to Sequence"の能力を生かして、現在は、Deep Learningの主役の一人に返り咲いている。では、前回のポストで見たRNNの抱えていた困難は、どのように克服されたのであろうか? ここでは、そのことを詳しく述べることはできない。

ただ、RNNの復活に大きな役割を果たしたのは、皮肉なことに、RNNの抱えている困難を原理的に明らかにし、いわば、かつてRNNに「引導」を渡した二人、Hochreiter とBengioであることに気づく。

それは、本当は、皮肉なことではないのかもしれない。問題の難しさを一番よく理解している人が、問題の解決に一番近いところにいるのは、ある意味自然なことであるのだから。

1997年に、Hochreiter と Schmidhuberが論文  "Long Short Term Memory "で導入したLSTMは、現在のRNN技術の中核となった。Googleの「ニューラル機械翻訳」もLSTMベースである。LSTMについては、次回以降のポストで、詳しく見てみたいと思う。

去年 2016年の6月のICML (International Conference on Machine Learning)で、Bengioが、面白いタイトルの講演をしている。タイトルは、"Learning Long-Term Dependencies with Gradient Descent is Difficult" 。そう、1994年に、彼がRNNの原理的な困難を明らかにした論文と同じタイトルである。

ポイントは、この講演が、このカンファレンスの "Neural Nets Back to the Future" というワークショップで行われていること。

このワークショップの目的は、次のようなものだ。(LSTMを使ったGoogle翻訳を使って見た。十分、下訳として使える!)
As research in deep learning is extremely active today, we could take a step back and examine its foundations. We propose to have a critical look at previous work on neural networks, and try to have a better understanding of the differences with today's work. Previous work can point at promising directions to follow, pitfalls to avoid, ideas and assumptions to revisit. Similarly, today's progress can allow a critical examination of what should  still be investigated, what has been answered...
ディープ・ラーニングの研究が今日非常に活発なので、私たちは一歩踏み込んで基礎を調べることができました。私たちは、ニューラルネットワークに関する以前の研究を批判的に見て、今日の研究との違いをよりよく理解しようと提案します。これまでの作業は、有望な指示に従うこと、回避すべき落とし穴、アイディアや再考の前提を指すことができます。同様に、今日の進歩は、何がまだ調査されるべきか、何が答えられたのかの批判的検討を可能にすることができる... (Google 翻訳)
ディープ・ラーニングの研究が今日非常に活発なので、私たちは一歩踏み込んでその基礎を調べることができるでしょう。私たちは、ニューラルネットワークに関する過去の研究を批判的に見て、今日の研究との違いをよりよく理解しようと提案します。過去の取り組みは、従うべき有望な方向、回避すべき落とし穴、再考すべきアイディアや前提を、私たちに示すことができます。同様に、今日の進歩は、何が今後も研究されるべきか、何が解決されたのかを、批判的検討することを可能にします... (Google 翻訳を下訳に使った、丸山訳)
こうした趣旨からいえば、1994年のBengioの論文は、取り上げられてしかるべきものの一つだろう。

この講演のスライドはこちら 。ビデオはこちらである。

2017年2月11日土曜日

20数年前、いったん放棄されたRNN

テキストの文字列や音素のつらなりである人間の発話、あるいは音楽の音やビデオのフレームのようなSequentialなデータは、我々の周りに多数存在している。こうしたSequentialなデータを、単純なニューラル・ネットを横に連ねたニューラル・ネットで解析しようとするRNNは、当初、大きな関心を集めたようだ。今から、20数年前のことだ。


隠れ層をつなぐ接続を同じ重みのパラメーター W を共有し、同一のネットワークを繰り返し利用するというアイデアで、実装の負担も抑えられそうに見えた。

ところが、この試みはうまくいかなかった。RNNでは、Back Propargation / Gradient Descent を使った学習アルゴリズムがうまく働かないのだ。横に並ぶネットワークの数が増えるにつれて、パラメーターを修正する勾配が、どんどんゼロに近づいてゆくのだ(時には、爆発的に増大することもあった)。「勾配の消失(あるいは、爆発)の問題」という。

当時のRNNは、次のような形をしていたらしい。 (この繰り返し)


この式に問題があったわけではない。この構成のRNNは、繰り返しの少ない短い系列なら、ちゃんと動くのだ。問題は、他のところにあった。

Full ConnectのDNNとRNNとでは、単純なニューラルネットを、縦に並べる、横に並べるだけではない違いがある。


Full ConnectのDNNでは、各層ごとの重みのパラメーター Wi は、一般には、異なっている。RNNでは、隠れ層を結ぶ重みのパラメーター W は、常に同じである。DNNの場合には、勾配の消失が起きそうになったら、一つ一つの層の「学習率」を個別に修正して、問題に対応できる。これは、いささか姑息な手段だが、DNNでは有効である。しかし、この方法は、常に全く同じ形でパラメーターを適用するRNNには、適用できない。

さらに、一般的には、DNNでの縦への積み重ねより、RNNでの横の繰り返しは長くなる傾向がある。50段重ねのDNNは、あまり見たことないが、50文字以上の文字列を扱うRNNを作ろうとするのは自然である。

20数年前、皆さんいろいろやって見たらしいが、実験結果は、思わしくなかった。

そこにある論文が登場する。1994年、Bengioらが、"Learning Long-Term Dependencies with Gradient Descent is Difficult" という論文を発表する。この論文は、長いRNNでの学習の難しさを、理論的に証明したものだった。実は、それより早く、1991年に、Hochreiter がドイツ語で書いた修士論文で、同じ証明を与えていたことが、現在では知られている。

これらの論文で、RNNの学習の困難の理論的根拠が明らかにされたことを受けて、当時のニューラルネットの世界では、RNNの探求は、事実上、放棄されることになったらしい。

次のポストで、DNNの復活の動きを紹介しようと思う。




Convolution パラパラ漫画



以前にもConvolutionのパラパラ漫画作ったことあるのですが、例が少し単純で、「3Dのかたまりから3Dのかたまりの変換」としてのConvolutionをうまく伝えられなかったような気がします。

その時のは、厚さ3のデータに、フィルター2つでコンボリューションをかけて、厚さ2のデータを作るというものでしたので、薄いんです。

もう一度、Convolutionのパラパラ漫画作ってみました。お気軽にご利用ください。 https://goo.gl/IeZccS