studylog/北の雲

chainer/python/nlp

KerasがTensorFlow本体に統合される?

TensorFlowをバックエンドとして使用しており、Python製DeepLearningライブラリとしては頭5つぐらい抜け出している感じのあったKerasですが、TensorFlow本体に取り込まれる?動きがあるようです。TensorFlow/Keras人気はさらに加速して他のライブラリを寄せ付けないでしょう。

自分が使いやすいライブラリを使えばいいと思いますし各ライブラリごとにいい所はあるので多様性万歳ではあるんですが、TensorFlow本体にKeras由来の使いやすいAPIが統合されちゃうと、DeepLearningライブラリのシェア争いという観点ではほぼ勝負あったかなという感じがします。


以下ソースなど。fcholletさんはKerasの開発者でGoogleにお勤めの人です。

(訳)KerasをTensorFlowに統合しようとしている。


redditでの発言

Keras is gaining official Google support, and is moving into contrib, then core TF. If you want a high-level object-oriented TF API to use for the long term, Keras is the way to go.

(redditにリンクするとなぜか投稿できないためリンク除去してます)
www.reddit.com/r/MachineLearning/comments/5jg7b8/p_deep_learning_for_coders18_hours_of_lessons_for/dbhaizx/

(訳)Kerasは公式にGoogleのサポートを受けていて、TensorFlowのCoreに移行します。
TensorFlowの高レベルAPIを長期的に使用したい場合は、Kerasを使用してください。



数学的バックグラウンドが無い人は理論を勉強しようと思っても厳しい

という事を痛切に悟りました。無理・無茶です。2015に出たLSTMとかCNNの教科書的の段階ならば、自分みたいな人間でも頑張って青本読んでも何とか理解できました。でもそのレベルでは特に自然言語処理関係であまり実用的なモノは作れません。LSTMで言語モデル作って文章出力して「知性!(実際はワードサラダ)」とか言ってた牧歌的な時代はもうとうの昔に過ぎ去りました。数学的バックグラウンドが無いと最新論文見ても何がなんだかわかりません。論文を簡単に説明してくれているブログ記事を読んでも理解できなくなってきました。片手間では無理ですね。

理論を理解するのは諦めて、他の人の成果物(論文)を誰かがコード実装してくれてそれを使ってなんかやるっていう方向性に特化しないと全部中途半端になっちゃうでしょう。最低限CNNの畳み込み・フィルタとかDropoutとかそのレベルぐらいまでは理解しないと誰かが書いたコードすらも弄れないのでゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装レベルだけでも最低限理解して、最新の理論についていくのは諦めて誰かの実装コードを使わせてもらい自分のやりたいタスクに合わせて弄る、という方向性なのかなと。

書きながらちょっと酔っぱらってきました。与太話が始まります。

「そんなレベルで何が作れんのよ?」って大半の人は思うでしょうし、自分もそう思います。でも、すごい企業やすごい人が作るような素晴らしいモノだけに価値があるわけでは無いとも思う。凄くて汎用的なモノだけでは解決できないニッチな場所や、あるいは凄くて汎用的なモノが出てくるまでのタイムラグを埋めるだけのしょぼいモノにも幾ばくかの価値があると思うんですよね。価値0ではない。

なんかweb2.0なんて古くさい話を出しますが、あれだって2006だが2007ぐらいに作られたweb2.0的なモノってもうほぼ淘汰されたじゃないですか。一部の凄いモノだけが生き残ってる。スマホのアプリ界隈もそう。でも、じゃあ淘汰されたモノに意味が無かったとは思わない。あの淘汰されちゃったモノ達は立派に隙間を埋めたし、制作したプログラマ個人の人生という視点で言えばちょっとした収入にはなって人生を豊かにしたじゃないですか。年収*5ぐらいでサイトを売った人、2~3年ぐらい月収1.5倍になった人。世間的に見れば「で?」っていうレベルの小さい話かもしれないけど、プログラマ個人の人生からすれば「おお!」っていうレベルを在野の名無しのプログラマは目指してもいいと思うんですよね。自分も過去のそういう体験が人生を少しだけ豊かにしてくれたという実感があります。在野でも個人でも無名でも、プログラミングができるってことはこんなに素晴らしい事なんだと。

まあweb2.0時代やスマホアプリブームと決定的に違うところは求められる能力が格段に上がってるって事です。あの時代は基礎的なプログラミング・webフレームワーク・DBの知識があって、後はアイディアや行動力や分野ごとの専門知識があればそれなりのモノを個人でも作れてマネタイズできたと思うんですが、現代のDNN時代は厳しいですね。非常に厳しい。数学的バックグラウンドが無いと最新論文がさっぱり読めない。そしてGPUみたいな計算資源の問題。そんでデータ・コーパスの量。こういったところがその辺の在野の一匹狼プログラマではどうする事もできないレベルになってます。自腹で10万近くでGPUを1枚買っても1年もすれば性能2倍お値段ちょい安、みたいなGPUが出てきてそういうのを4枚刺しして学習するのが当たり前になる。そういうのをたくさん集めてクラスタ化してぶん回す企業がぞろぞろいる。こういうのが相手じゃ隙間を埋めるのすらも厳しいなという虚無感を覚えてます。

来年の今頃はどういう世界になっているんでしょうか。

それでは皆さんよいお年を。

2016振り返り

今年も終わってしまいますね。今年の札幌の冬は強烈に寒そうで豪雪っぽいです。去年一昨年と生温い冬だったので久々に冬将軍炸裂って感じです。

去年(2015)の6月頃からChainerを通してDeepLearningを触りだし、このブログもわりと更新してきましたが、今年中盤以降は更新頻度がかなり落ちました。元々仕事でやってるわけではないですし、仕事とは無縁の世界ですし、何より数学的素養が無い人間が学ぶには敷居が高すぎます。まあ興味が薄れつつあります。そんな自分なりの観測範囲で今年を振り返ってみようと思います。

DNN系プログラミング和書が激増

増えましたねえ。2015年はいわゆる青本(深層学習 (機械学習プロフェッショナルシリーズ))など何冊かDNN本が出ましたが、いずれも数学的な見地から書かれた書籍なので数式多めでコードが出てこない。でも今年はゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装などコードを交えてDNNを解説する本が増えました。Chainerによる実践深層学習みたいにChainerやTensorFlowなどのフレームワーク解説本も増えましたね。

一方で、これを書籍として出しちゃっていいの?って思う本もありました。「本人はDNNのことあんまりわかってないのに、その辺の他の人が書いた事を切り貼りして背伸びしてなんかそれっぽく語ってる」系の著者さんが本を出していたので読んでみると、見事にその辺ブログとかQiitaの記事そのまんまじゃないのって感想。読みながら「これはQiitaのあの記事だ」「これは自分が書いたあの記事じゃないの?」とモヤモヤします。そういう人が書いた本は避けた方がいいです。DNNコード系は質的に玉石混合になりつつあります。選択肢が全然無いよりは遥かにマシなのですが、どうせなら玉の方を選びたいですね。

AlphaGo

こちらも忘れてはならないエポックメイキング。強化学習NNの現時点での最も成功した例なのではないでしょうか。っていうかこれ今年の出来事だったんですね。随分昔の事のようです。

Kerasの躍進

PythonのDNNライブラリの最右翼Kerasがさらに強いです。
chainer python, keras python - Google トレンド(なんか不安定で表示されない時があります。リロードしてください。)
去年の年末にKerasがTensorFlowに対応した頃から一気に勢いがついて、Chainerとは差が開いてます。ちなみに国別トレンドも見られるのでアメリカ、日本でも見てみてください。
使いやすさは人それぞれなんでしょうけど、英語圏コミュニティでの強さ・バックエンドにTensorFlowが使えるという点でChainerをリードしているのかな。「論文をコード実装してみました」みたいなのはKerasが圧倒的に強いです。

ちなみにKerasの作者さんはGoogleの人なんですが日本人っぽい(少なくともネイティブな日本語を話す方)です。なのでChainerについて色々と思うところもあるみたいで。あまりここでは書きませんが。


Googleの翻訳が歴史的な進歩を見せる

CNNを持ち上げた直後でなんなんですがGoogle翻訳ニューラルネットワーク版に置き換わり精度の高さが話題になりました。このシステムはEncoder-Decoderやattentionを使ったRNN系です。
www.yasuhisay.info

CNNが強い

2015の時点では画像物体認識はCNN、自然言語に代表される時系列系はRNN(LSTM)みたいな住み分けができてました。まあCNNは去年の時点でもう実アプリケーションとしてばりばり実用的なレベルに達してましたが、2016は特に年末にかけていよいよCNNが時系列にも浸食してRNNを食ってる感じがします。LSTMを超える期待の新星、QRNN - Qiitaとかですね。2017はCNN的アプローチで文章や音声分野で劇的な進歩を見せるんじゃないでしょうか。RNN系の運命やいかに。



来年はCNN系で自然言語の劇的なブレークスルーがあるんじゃないかということ、そして日本国内でKerasが主役に躍り出るんじゃないかということに注目したいですね。次の記事でも書く予定ですがもう自分ではDNNには全くついていけないのでwatch専門になるんじゃないかなと思います。

今年(2016)参考にさせてもらったChainerの論文実装、サンプルコード集

去年書いたサンプルコード集の2016年版です。
個人的な興味範囲のみ集めているので網羅的では無いとは思います。
基本的に上の方が新しいコードです。

QRNN(Quasi-Recurrent Neural Networks)

論文ではchainerを使って実験しており、普通のLSTMはもちろんcuDNNを使ったLSTMよりも高速らしい。
一番下にchainer実装コードが埋め込まれている。
New neural network building block allows faster and more accurate text understanding

[1611.01576] Quasi-Recurrent Neural Networks
f:id:kitanokumo:20161201140155p:plain
Softmaxなどに要している時間を除き純粋にRNN部だけで比較すると相当速くなっているのがわかる。

このように実験でchainerが使われている研究論文が徐々に増えているらしい。

VAE(Variational AutoEncoder)

VAEはchainer公式サンプルもありますが、解説付きのこちらの方がわかりやすいです。
Semi-Supervised Learning with Deep Generative Models [arXiv:1406.5298] – ご注文は機械学習ですか?

[1406.5298] Semi-Supervised Learning with Deep Generative Models

Combining Markov Random Fields and Convolutional Neural Networks for Image Synthesis

去年流行ったゴッホっぽくスタイルを変換できるやつの別手法らしいです
Convolutional Neural Networkを使ったもう1つのスタイル変換手法 - Qiita

ChainerのcuDNN-RNN(NStepLSTM)のとっかかり

16.0の新機能NstepLSTMはcuDNN5.0以降で最適化されたcuDNN-RNNを利用できます。速くなるらしいです。
Optimizing Recurrent Neural Networks in cuDNN 5 | Parallel Forall

これの良い所は次元数が合わないデータでもミニバッチ処理が簡単にできる点です。
再掲しますが以前はこんな風にやっていました。
可変長データのミニバッチをchainerのwhereでやる - studylog/北の雲

以前のやり方

手順1(次元が合ってない)
データA 1 2
データB 1 2 3

手順2(0で末尾を埋めて次元を合わせる)
データA 1 2 0 0
データB 1 2 3 0

手順3(転置)
A B
1 1
2 2
0 3
0 0

手順4-1(入力する)
1 1 ←最初はここが入力(x)
2 2 ←最初はここが正解データ(t)
0 3
0 0

手順4-2(一つずらして入力)
1 1
2 2 ← x
0 3 ← t
0 0

以下繰り返す

次元を合わせるために末尾に-1や0などを追加して、転置して…というめんどくさい方法。これNStepLSTMでは全部Chainerがやってくれます。

NStepLSTMのやり方

手順1(次元が合ってないけどそのまま入力する)
データA 1 2
データB 1 2 3

おわり。
めちゃくちゃ楽だ!

15.0で導入された可変長対応LSTMでは次元数を小さい順に並び替える処理が必要でしたがNStepLSTMでは必要無いです。
こんなのもそのまま放り込むだけ。
データA 1 2
データB 1 2 3 4
データC 1 2 3

lossの計算

以前のLSTMは入力する時に時刻tを一つづつずらしてその都度lossを計算していたと思うんですが、NStepLSTMは入力時に時刻ずらしは考えなくて済むようになってます。全ての時刻での出力結果がまとまって返ってくるのでそれを時刻ごとにずらしてloss計算するイメージ。入力時じゃなくて出力時に時刻ずらし。文章で伝わりますかね…。そのうちなるべくシンプルなサンプルコードをあげたいと思います。

速度

以前のLSTMとの比較はまだやれていません。
cuDNNの有無では明確に有る方が速いです。条件によるんで具体的な数字は何とも言えないですがEmbedサイズが大きくなればなるほど差がつくようです。ここは後で追記するかもしれません。

その他

dropoutを有効にするとエラーが出ちゃいます。何故だ。cuDNN5.1,Ubuntu14.04です。
(追記:17.0で治った模様)

File "/usr/local/lib/python3.4/dist-packages/chainer/links/connection/n_step_lstm.py", line 96, in __call__
train=train, use_cudnn=self.use_cudnn)
File "/usr/local/lib/python3.4/dist-packages/chainer/functions/connection/n_step_lstm.py", line 468, in n_step_lstm
ret = rnn(*inputs)
File "/usr/local/lib/python3.4/dist-packages/chainer/function.py", line 198, in __call__
outputs = self.forward(in_data)
File "/usr/local/lib/python3.4/dist-packages/chainer/functions/connection/n_step_lstm.py", line 267, in forward
self.reserve_space.data.ptr, reserve_size)
File "cupy/cuda/cudnn.pyx", line 960, in cupy.cuda.cudnn.RNNForwardTraining (cupy/cuda/cudnn.cpp:12578)
File "cupy/cuda/cudnn.pyx", line 978, in cupy.cuda.cudnn.RNNForwardTraining (cupy/cuda/cudnn.cpp:12318)
File "cupy/cuda/cudnn.pyx", line 321, in cupy.cuda.cudnn.check_status (cupy/cuda/cudnn.cpp:1721)
cupy.cuda.cudnn.CuDNNError: CUDNN_STATUS_BAD_PARAM: b'CUDNN_STATUS_BAD_PARAM'

追記

実サンプルを上げてくれている方がいました。DropoutのバグもGithubに報告してくれたおかげで治ったみたいです。感謝。
www.monthly-hack.com

NN言語モデルの低頻度単語とメモリ問題

低火力ディープラーニングにつきまとう問題として第一にGPUのメモリ問題がある。
個人で用意できるレベルのハードではとにかく足りない。

以下言語モデル構築の話。
wikipediaの1/50コーパスをneologdで分かち書きしたときに語彙は25万ぐらい。
これを全部語彙として採用してChainerのEmbedに投げると6G~8GクラスのGPUだとメモリ不足で学習できない。

なので低頻度語として一部を切り捨てる必要がある。
仮に10万を語彙として採用し、それ以外の15万は低頻度として切り捨ててそれらを「低頻度単語(<低頻度>)」としてEmbedに専用のIDを一つ付与して学習すると、文出力の時にこうなる。

これによって<低頻度>の<低頻度>な<低頻度>を<低頻度>、<低頻度>の<低頻度>に<低頻度>をつけた。

おぉ…。
一つ一つは低頻度でも、ちりも積もればエベレストに。
普通の名詞や動詞が全く出力されず、助詞・助動詞・<低頻度>の3つばかりが出力されてしまう。

こういう場合はどうすればいいんだろうと前から思ってて、品詞を付与したりしてた。<低頻度-名詞><低頻度-動詞>のようにわける。
これでも粒度が荒すぎるので<低頻度-名詞-固有名詞>と細かくしたりあるいは後ろの接続品詞情報を付与したりしてた。

書きながら思いついたのだけどfastTextやWord2VecのWord embeddingをクラスタリングして番号振ったのを付与したらどうだろうか。
意味的にも近いし品詞もほぼ同じなので出力するときに楽そう。

品詞と違ってクラスタリングの分割数を変える事で粒度をコントロールできるし便利そう。
おわり。

Chainerのcleargradsと旧zerograds

1.15.0よりzerogradsが非推奨になりcleargradsというものが導入されたらしい。

github.com

0埋めはメモリいっぱい使うし意味が無いからNone埋めにする!みたいな感じ。
変更されたコードはこちら。上がclearで下がzero。

    def cleargrad(self):
        """Clears the gradient array."""
        self._grad = None

    def zerograd(self):
        """Initializes the gradient array by zeros.

        .. deprecated:: v1.15
           Use :meth:`cleargrad` instead.

        """
        warnings.warn(
            'Variable.zerograd is deprecated. Use Variable.cleargard instead.',
            DeprecationWarning)
        with cuda.get_device(self.data) as dev:
            if self._grad is None:
                xp = numpy if int(dev) == -1 else cuda.cupy
                self._grad = xp.zeros_like(self.data)
            else:
                self._grad.fill(0)

確かに0ではなくNoneを入れてる。

早速使ってみるとエラーが出る。
エラー箇所を見てみると、どうも0(あるいは数値)が入ってる事を想定されたコードなのに、cleargradsのせいでNoneが入っててエラーになってるよう。

https://github.com/pfnet/chainer/blob/master/chainer/optimizer.py#L487

    def __call__(self, opt):
        if cuda.available:
            kernel = cuda.elementwise(
                'T p, T decay', 'T g', 'g += decay * p', 'weight_decay')

        rate = self.rate
        for param in opt.target.params():
            p, g = param.data, param.grad #このparam.gradがNoneになってる
            with cuda.get_device(p) as dev:
                if int(dev) == -1:
                    g += rate * p #だからここで落ちる(CPUの場合)
                else:
                    kernel(p, rate, g) #GPUだとこっちで落ちる

https://github.com/pfnet/chainer/blob/master/chainer/optimizers/adam.py#L44

    def update_one_gpu(self, param, state):
        cuda.elementwise(
            'T grad, T lr, T one_minus_beta1, T one_minus_beta2, T eps',
            'T param, T m, T v',
            '''m += one_minus_beta1 * (grad - m);
               v += one_minus_beta2 * (grad * grad - v);
               param -= lr * m / (sqrt(v) + eps);''',
            'adam')(param.grad, self.lr, 1 - self.beta1, 1 - self.beta2, #このparam.gradがNone
                    self.eps, param.data, state['m'], state['v'])

他にもいっぱいある。
1.8.0から一気に1.16.0に上げたので、その間に書き方が色々と変わってて自分の書いたコードで何か必要なのが抜け落ちているのかもしれない。trainerは使ってないです。NstepLSTM(cuDNN RNN)を使っています。

よくわからないので今はとりあえずzerogradsのまま動かしています。

この方も関連する感じ。
hiho-developer.hatenablog.com

1.15.0リリースからはだいぶ経ってるのにエラー報告が全く無いという事はやはりこっち側が原因なのかな。
わからない。