ニューラル常微分方程式 / Neural Ordinary Differential Equations
昨年末にとても面白い論文を見つけましたので、合間の時間を見つけて読み進めています。
この論文は、機械学習の分野でもっとも権威のある国際学会、NeurIPSにて2018年度に最優秀論文として評価されている論文です。
ミーハー心と興味本位で読み進めていましたが、読めば読むほど面白く読み応えのある論文です。
従来までのニューラルネットワークは、脳をモデルとして作られています。一見アナログ的ですが、数学的にはとてもデジタルな存在で、自然数個のノードを繋げてネットワークを構成しています。
近年のネットワークの層は年々ディープになり、2016年にMicrosoftから発表されたResNet(残差ネットワーク)は実に152層のノードから構成されています。
ResNetは勾配消失問題を解決し、より深い学習を可能にしましたが、一方で多くのメモリと多くの時間を要する存在になっていきました。
なにより、この152という数字には特別な意味はなく経験則的なアプローチから生み出された結果に過ぎません。
この曼荼羅のようなネットワークの状態をみて、どうにかならないものかと思った人も少なくないのではないかと思います。
ニューラル常微分方程式(Neural ODE)は、ここに対して非常に素晴らしいアプローチを提供しています。単純に数を増やすのではなく、デジタル的なネットワークの状態をアナログ化しているのです。イメージとしては今まで自然数で数えていたものを小数を含めて考えるアプローチに近いです。より正確にいうと離散化状態だったネットワークを連続的にします。
新しいネットワークの構造から比較すると、従来までのニューロンをベースに作られた概念の方が、むしろデジタルといいますか、カクカクとしたもののように見えてきます。そのぐらいネットワークの構造が滑らかに感じます。私の主観かもしれませんが、きっとそのように理解された方もいらっしゃると思います。
これによりニューラルネットワークの計算に常微分方程式(以下、ODE)ソルバーを導入することが可能になりました。最適化の手法に従来までのバックプロパゲーション(誤差逆伝搬法)ではなくアドジョイント法を用いているということも素晴らしいのですが、結果として解法ロジックを分離し、すでに研究しつくされた優秀なODEソルバーを採用すればよいという発想も素晴らしいと思います。
この構造変化の適応先はバラエティ豊かで、近年の画像処理の雄であるResNet、時系列処理の常識であるRNN(リカレントニューラルネットワーク)に加え、Glowで有名になった情報補完を可能にするNormalizing Flowへも適応できます。
メリットは以下のようなものがあります。
1) メモリが効率的
2) 最適な解法を選択可能
3) パラメータが効率的
4) スケーラブルで可逆的なNormalizing Flowを構築可能
5) 連続的な時系列モデルを実現可能
1のメモリ効率的はビジネス観点からみて有益です。ネットワークが複雑になる程、必要なメモリサイズが増えていかねばならず、高コストでした。また、現存するコンピュータの性能限界で計算できる範囲が限定されることもなくなるという意味でのメリットもあります。
2,3は最適な解法を選択可能,パラメータが効率的は機械学習システムを構築する上でのメリットです。特に2は目的に応じて最適なODEを選択できるようになるので、目的に応じて適切な機械学習手法が選択できるようになるはずです。
4,5は具体的な応用です。時系列モデルについては非常にわかりやすく、今までは自然数単位でしか解析できなかった情報が、連続化されることで任意の時間で取ることができるようになります。
以上がこの論文の概要になりますが、数学的なアプローチについても興味深いものが盛りだくさんですので、以後少しずつ書き進めていこうと思います。
TensorFlow まとめ 続き
前回の続きです。
tf.argmax
数学的なArgmaxは「その中で最大の値をとる集合」です。例えば、[1,3,2,1,3] という数列があった場合、[2番目の3,5番目の3]がArgmaxになります。tfのargmaxは少し違っていて、「最大の数字を取る値のインデックス番号」を返します。この例でいうと、2番目なので、1を返します。(インデックスなので0から始まります)
import tensorflow as tf sess = tf.Session()
単純な数列の場合
x = tf.constant([1,2,3,2,3]) # 最大値を持つ値(ここでは3)の最初のインデックスを返すので、最初の3のインデックスである2を返す xd = sess.run(tf.argmax(x)) print(xd)
2
行列の場合は、評価する方向を指定できます。
x = tf.constant([[1,2,3],[2,1,4]]) xd = sess.run(tf.argmax(x,axis=0)) # 行で評価する print(xd) xd = sess.run(tf.argmax(x,axis=1)) # 列で評価する print(xd)
[1 0 1]
[2 2]
tf.cast
型変換をします。tf.cast(x, "float") は xをfloat型に変換します。
参考:
tf.reduce_mean
テンソルの次元間の要素の平均を計算します。
x = tf.constant([[1., 1.], [2., 2.]]) tf.reduce_mean(x) # 1.5 tf.reduce_mean(x, 0) # [1.5, 1.5] tf.reduce_mean(x, 1) # [1., 2.]
TensorFlow まとめ
TensorFlowは、計算をあらかじめ記述し、それを後ほど明示的に実行するという流れをとります。「計算の記述」をOpノード、「明示的な実行」をセッションという概念で行います。
こちらの記事がよくまとまっています。
qiita.com
少し昔の記事なので、initialize_all_variablesはobsoletedなことに注意してください。(今はglobal_variables_initializerです)
以下、tfのライブラリの補足です。MNIST Basicに出てくる関数を取り扱います。
前提として、行列の概念を思い出す必要があります。
mathwords.net
をところどころで参考にしてください。
tf.constant
定数です。実際の定数を定義します。
xは1という数字です。 (数学的にはスカラーです)
import tensorflow as tf x = tf.constant(1) print(sess.run(x))
1
yは[3,4]という2次元配列です。(数学的にはベクトルです。つまり1階のテンソル)
y = tf.constant([3,4]) # 2次元の配列 print(sess.run(y))
[3 4]
zは[[3,4],[5,6]]]という2次元配列です(数学的には以下の行列です。つまり2階のテンソル)
z = tf.constant([[3,4],[5,6]]) # 2x2行列 print(sess.run(z))
[[3 4]
[5 6]]
tf.placeholder
セッションの外側からデータを入れるために使います。プログラミング的には、関数の入力パラメーターが、セッションに対するプレースホルダーとなります。constantは実体を指定していますが、placeholderと次に出てくるvariableは、構造を指定しているのがポイントです。
1次元(=スカラー)
import tensorflow as tf sess = tf.Session() x = tf.placeholder("float") # 特に指定しない場合は、1次元の数字、つまりスカラー double = tf.add(x,x) r = sess.run(double, feed_dict={x:2}) print(r)
4.0
2次元(=ベクトル、1階のテンソル)
x = tf.placeholder(tf.int32,[2]) # 2次元を指定 double = tf.add(x,x) r = sess.run(double, feed_dict={x:[1,2]}) # 実体を入力 print(r)
[2 4]
2x2行列(=は、nxm行列、つまり2階のテンソル)
x = tf.placeholder("float",[2,2]) # 2x2行列を指定 double = tf.add(x,x) r = sess.run(double, feed_dict={x:[[1,2],[3,4]]}) # 実体を入力 print(r)
[[2. 4.]
[6. 8.]]
tf.Variable
セッションの中で変数として扱われます。
import tensorflow as tf x = tf.Variable([2,3], name="counter") # 2x3行列 c = tf.constant([[1,2],[3,4],[5,6]]) x = c init_op = tf.global_variables_initializer() sess = tf.Session() sess.run(init_op) r = sess.run(x) print(r)
[[1 2]
[3 4]
[5 6]]
tf.matmul
数学の「内積」です。
x = tf.placeholder("float", [1, 2]) # 1x2行列 y = tf.placeholder("float", [2, 1]) # 2x1行列 z = tf.matmul(x,y) init = tf.initialize_all_variables() sess = tf.Session() sess.run(init) sess.run(z,feed_dict={x: [[3, 4]], y: [[5],[6]]}) # 内積だから 3x5+4x6 = 15+24 = 39
array([[39.]], dtype=float32)
tf.nn
Nerual Network系のライブラリです。
tf.nn.softmax
Softmax(シグモイド)関数です。確率分布、つまり総和が1になっています。
z = tf.placeholder("float", [1, 3]) # 1x3行列 s = tf.nn.softmax(z) init = tf.initialize_all_variables() sess = tf.Session() sess.run(init) pd1 = sess.run(s,feed_dict={z: [[1,2,3]]}) print("[1,2,3] -> " , pd1) # 和が1 pd2 = sess.run(s,feed_dict={z: [[1,2,2]]}) print("[1,2,2] -> " , pd2) # 和が1
[1,2,3] -> [[0.09003057 0.24472848 0.66524094]] [1,2,2] -> [[0.15536241 0.42231882 0.42231882]]
MNISTの tf.nn.softmax(tf.matmul(x, W) + b) の理解のためにこちらを記載します。
# 2次元ベクトルへのSoftmax回帰 x = tf.placeholder("float", [1, 2]) # 1x2行列 y = tf.placeholder("float", [2, 2]) # 2x2行列 z = tf.matmul(x,y) s = tf.nn.softmax(z) init = tf.initialize_all_variables() sess = tf.Session() sess.run(init) pz = sess.run(z,feed_dict={x: [[3, 4]], y: [[5,6],[7,8]]}) ps = sess.run(s,feed_dict={x: [[3, 4]], y: [[5,6],[7,8]]}) print("pz -> " , pz) print("ps -> " , ps)
pz -> [[43. 50.]] ps -> [[9.1105123e-04 9.9908900e-01]]
LSTM / Long Short Term Memory / 長期短期記憶
LSTMについて。
LSTMは、ニューラルネットワークのなかでもRNN(リカレントニューラルネットワーク)に分類されるものです。RNNは時系列データを学習するためのニューラルネットワークで、LSTMはRNNの中でも繰り返し改善が施されたもので、RNNの主流と言っても過言ではないでしょう。画像処理系のCNNとあわせて、昨今のニューラルネットワークの双璧をなしていると個人的には思っています。
主たる改善は、LSTM、Long Short Term Memory(長期短期記憶)という言葉が表すように、時系列データのなかから短期的な情報だけでなく、長期的な情報も取り込めるような設計が施されているところにあります。従来のRNNでは勾配消失問題が発生するため、長期的な情報をうまく取り込むことができませんでしたが、LSTMではこの問題が解決されています。
LSTMの歴史は(人口知能においては比較的)古く、1997年に論文が発表されています。
http://deeplearning.cs.cmu.edu/pdfs/Hochreiter97_lstm.pdf
その後、LSTMは複数回の改善(Forgate Gate、Peeple Connectioin、Full Gradient)がなされました。
このあたりは、非常に素晴らしい記事であるわかるLSTM ~ 最近の動向と共に - Qiita に詳しく記載されています。
LSTMにはLSTM Blockという概念が出てきます。前述までの改善は主にこのLSTM Blockに対してなされているものです。LSTM Blockは太陽系における惑星のようなもので、LSTMを理解するうえで非常に重要な概念ではありますが、これだけではLSTMを理解したというわけにはいきません。LSTMに対する理解の解像度を上げるのであれば、Blockで表示されるチャートで表現される意味を数式で理解することをお勧めします。また、実際にコードを書きたいのであれば、LSTMそのものの理解を実装ベースで深める必要があります。
前者についてはイメージはあるのですが、、少し書き出すのに時間がかかりそうです。後者については、TensorflowのLSTMのサンプルが有効だと思います。説明自体は
にあり、コードはGithubにあります。
特に以下のコードです。
tensorflow/ptb_word_lm.py at master · tensorflow/tensorflow · GitHub
2つの特徴的な関数がヒントになります。
BasicLSTMCell
https://www.tensorflow.org/versions/r0.9/api_docs/python/rnn_cell.html#BasicLSTMCell
MultiRNNCell
https://www.tensorflow.org/versions/r0.9/api_docs/python/rnn_cell.html#MultiRNNCell
関数を深掘りする前に、このチュートリアルが行っていることを説明しますと、特定の言語サンプルに対する、ある優秀な解析の再現を行っています。
特定の言語サンプルとは、PTB(Penn Tree Bank)のことです。以下のサイトで「Basic Examples」として提供されています。
該当する、simple-examples.tgzをダウンロードし、tar vxfzで展開すると、data/ ディレクトリの中に、ptb.test.txt (テストデータ) , ptb.train.txt (訓練データ) , ptb.valid.txt (開発データ、ハイパーパラメータの調整用) があります。ptb.test.txtの冒頭を見てみましょう。
no it was n't black monday
but while the new york stock exchange did n't fall apart friday as the dow jones industrial average plunged N points most of it in the final hour it barely managed to stay this side of chaos ..
ブラックマンデー、ニューヨーク証券取引所と証券関連の言葉が綴られていますね。
PTBはコーパスというテキストを大量に集めてデータベース化した資料の1つで、データに構造を持たせたものになります。コンパクトにまとめられており、ベンチマークとしてよく利用されるそうです。やや話が逸れますが、日本語のコーパスとしては、小納言というサイトがあります。
次に、ある優秀な解析の部分ですが、2014年に発表されたRNNの正則化に関するZarembaさんの論文に基づいています。
[1409.2329] Recurrent Neural Network Regularization
PDFはこちらです。
http://arxiv.org/pdf/1409.2329v5.pdf
LSTMでは、過学習を防止するために通常のドロップアウトがうまく機能しないことに対し、ドロップアウトの適応方法を紹介するものです。言語モデリングの応用事例として、上記のPTB(simple-examples)に適応した際の結果が紹介されている他、スピーチの認識、機械翻訳、画像キャプションの生成などへの適応が紹介されています。
BasicLSTMCellについて
前置きが長くなりましたが、TensorflowのLSTMに関する関数の1つであるBasicLSTMCellで用いられているLSTMはこの論文に基づいて実装されています。Memory Cellの構造は以下のとおりです。
(転載については、Zarembaさんに許可を頂いております)
他、97年の初版LSTMに基づいたLSTMCellという関数もあります。
(続く)
LSTM/RNNとCNNの組み合わせ
複数言語の同時解釈への応用の観点から、以前からLSTM(もしくは単にRNN)とCNNの組み合わせについて興味がありましたので、調べました。3つほどそれらしい論文があったのでメモを取ります。
1. 画像認識と画像抽出のためのLong-term Reccurent Convolution Networks
[1411.4389] Long-term Recurrent Convolutional Networks for Visual Recognition and Description
PDFはこちら: https://arxiv.org/pdf/1411.4389.pdf
動画が対象になっています。動画は、基本的には画像なので、CNNによる処理が有効です。また、連続的なものですから、LSTMが関わってきます。基本的なアプローチはCNNをかけた後、LSTMで処理するモデルです。以下の3つのモデルが紹介されていました。
1)連続的な入力に対して、単一の意味を出力するモデル(動画から「走高跳び」と出力する)
2)単一の入力に対して、連続的な出力をするモデル(静止画から「人」「が」「走っている」と出力する)
3)連続的な入力に対して、連続的な出力をするモデル(動画から「人」「が」「高く」「飛んだ」と出力する)
3はCNNではなくCRFを使っていました。
2. 複数ラベルによる画像分類のための統合フレームワーク
[1604.04573] CNN-RNN: A Unified Framework for Multi-label Image Classification
PDF: https://arxiv.org/pdf/1604.04573.pdf
単一の画像に複数のラベル付けをするためのフレームワークとして、CNNにLSTMの概念を追加しているようですが、すみません、なぜLSTMを追加することで複数のラベルを扱えるようになっているかはあまりわかっていないです。
3. CONVOLUTIONAL, LONG SHORT-TERM MEMORY, FULLY CONNECTED DEEP NEURAL NETWORKS
CONVOLUTIONAL, LONG SHORT-TERM MEMORY, FULLY CONNECTED DEEP NEURAL NETWORKS - Patent application
PDF:
http://static.googleusercontent.com/media/research.google.com/ja//pubs/archive/43455.pdf
タイトルの方は特許です。PDFは論文です。
複数のバラエティーがあるスピーチに対して認識をさせるためのものとして、CNN、LSTM、DNNの組み合わせを紹介しています。CLDNNという呼称を付けており、基本的な構造はConvolutional Layers => LSTM Layers => 全接続 Layersとなっています。英語のスピーチを学習データとして、CNN+LSTMのケース、LSTM+DNNのケースといった風に分析をかけています。
多言語、例えば、英語、ドイツ語、フランス語、日本語、中国語、韓国語などを同時に読み込ませ、そこから同時翻訳や文章予測に向けた解釈をするアルゴリズムができれば面白いなというのが、今回の調査のモチベーションでした。残念ながら該当する方法論に対する研究は見つけられませでしたが、近しいアプローチをしている方はそれなりにいるようです。いずれも「2016年」に何らかの動きがあるあたり、まさに今動いているテクノロジーという感じで興味深かったです。
自分自身でもロジックを組んでみようと思います。
Google Cloud Shellについて
Google Cloudには、Google Cloud Shellという、Shell環境がデフォルトで用意されています。
Google Cloudアカウントを開設すると、メニューバーからアクセスできるシェル環境で、Google Cloud SDK/gcloudがビルトインされていて便利です。
画像はgoogleより引用
以下、2016年07月26日の情報です。
基本的な特徴
* ブラウザベースでアクセスできるCLI環境
* 5GBの永続的ストレージが用意されている。
* Java,Go,Pythonなどの言語をサポートしている。
* Webプレビュー機能
* gcloudを利用する際の認証が済んでいる
* tmuxサポート
一時的に利用できる仮想マシンのインスタンス
読んで字のごとくですが、一時的に利用できる仮想マシンです。コマンドラインボタンを押すと、プロビジョニングされ、新たなインスタンスを立ち上げています。自分のディレクトリ(/home/username/)の情報は、5GBの永続的ストレージから読み込まれているようで情報が保持されますが、cronは引き継がないようですので、別の仕組みを用意する必要がありそうです。
ブラウザベースでアクセスできるCLI環境
Compute Engineと同様にブラウザからアクセスできます。macのシェルからアクセスする必要がなくなりました。
5GBの永続的ストレージ
Google Cloud Shell用に5GBのストレージが用意されています。 APIを叩く等の小さな管理作業をするためのスペースとしては十分だと思います。それ以上の作業をする場合は、別のストレージに展開することになります。
Java,Go,Pythonなどの言語をサポート
対応している言語はこちらにあります
https://cloud.google.com/shell/docs/features#language_support
Java,Go,Python(2系のみ),Node.js,Ruby,PHPです。同じページにサポートしているシェルやエディタの情報があり、シェルはsh, bash、エディタは、emacs,vim,nanoに対応しています。他、make, pip , git , docker , mysql clientに対応しています。管理系で必要なものはだいたい入っているという印象です。
https://cloud.google.com/shell/docs/features
Webプレビュー
Webアプリケーションのプレビューができるようです。Ruby on Railsなどではテストサーバーを立ち上げてローカルで動作を確認しますが、そういった機能だと思います。
gcloudを利用する際の認証が済んでいる
gcloudの認証ができているので、立ち上げたCompute Engineに直接アクセスできるようです。まだ試していません。
tmuxサポート
tmuxをサポートしています。tmuxは作業中のセッションを保存したり、画面を複数端末で見れて便利なシェルのユーティリティですが、インスタンス自体が一時的なので、セッション保存がどこまで活用できるかはやや不明です。まだ試していません。
Google Cloud Plathome上にTensorflowを展開する
Tensorflowを使いはじめて、はや4ヶ月。そろそろ手元のMacbookではマシンパワーがおぼつかないケースが出てきました。
既存のAWSのDebianに入れようかとも考えましたが、せっかくなので、Google Cloud Plathomeを使うことにしました。
申し込みからコードの実行まで1.5時間ぐらいでした。以下、その記録です。
1. 申し込む
cloud.google.comから申し込みます。
既存のGoogleアカウントでログインしましたが、再度住所等の情報を入力しました。60日、$300分の無料クレジットがついてきます。クレジットカードの入力が必要です。
2. 利用する
チュートリアルが表示されます。ゲーミフィケーションのような流れです。pythonによるHello, Worldチュートリアルがあったので、それに従ってみたところ、Webアプリケーションができてしまいました。App Engineというアプリケーション用のクラウドプラットホームを使っていたようです。Herokuに似ています。Webアプリケーションを構築するのであれば魅力的ですが、今回は違うようです。
少し調べるとGoogle Prediction APIというものがありましたが、Tensorflowではないようです。また、Cloud Machine LearningというJupyter Notebookが利用できるものもありましたが、Limited Previewでした。GoogleのサイトでJupyter Notebookを用いたデモを見たことがあり、こちらも興味深いのですが、今回は対話形式は特に必要としておらず、cronなどによるスケジュールなどをしたかったので、こちらの利用も見送りました。
結局、AWSのEC2のような、VMを作るタイプのCompute Engineというものを使うことにしました。 割とスタンダードなクラウド環境です。CPUとメモリのサイズをカスタマイズできるので、CPU=8コア , メモリ=7.2GBを選択しました。メモリはこんなに必要ないのですが、どうやらこれが最小サイズのようです。時代ですね。
3. ログイン
Compute Engine上で作成したインスタンスへのアクセスはブラウザベースで行えます。SSHのアイコンをクリックすると、ブラウザ上にエミュレートされたターミナルが立ち上がります。AWSのkey pairと比べると簡単にアクセスできる一方で、益々googleアカウントの重要性が増すなと思いました。
Chromeを使っている場合(私がそうだったのですが)、ターミナル・プラグインのインストールを勧められます。これを利用することで、ほぼターミナルと遜色のない環境になります。
4.Tensorflowをインストールする
ここから先は、おなじみの作業です。私はanacondaを使っているので、
Anacondaをダウンロードし、インストールします。
user$ wget http://repo.continuum.io/archive/Anaconda3-4.1.1-Linux-x86_64.sh user$ chmod u+x Anaconda3-4.1.1-Linux-x86_64.sh user$ ./Anaconda3-4.1.1-Linux-x86_64.sh
Cloud Plathomeで用意されたdebianはコンパクトなものでしたので、インストール途中でbzip2がないと警告を受けました。apt-get update、apt-get install bzip2したのち、再度anacondaを入れました。
root# apt-get update root# apt-get install bzip2
その後、tensorflowを以下を参考に入れます。
といっても今回は環境切り分けを特にしないので、pip install --upgrade でおしまいです。
user$ touch~/anaconda3/lib/python3.5/site-packages/easy-install.pth
user$ export TF_BINARY_URL=https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.9.0-cp35-cp35m-linux_x86_64.whl
user$ pip install --upgrade $TF_BINARY_URL
5. 実行
まずは単純にpythonのコードを実行するだけをしました。実行中にCPUの使用率を見ると、該当するプロセスの利用率がおおよそ230%と3コアしか利用できていませんでした。この辺りの指定の仕方は、もう少し調べようと思います。今回は、学習パラメータを複数持たせた結果を知りたかったので、4つのプロセスを同時に走らせました。
6. その他
ずうっとマシンリソースを使い続けるわけではないので、動的に仮想マシンを制御することになります。少し調べたところ、gcloudというツールで制御できるようです。
更に、プリエンプティブル VMという仮想マシンを利用することで、よりリーズナブルに利用できそうです。