TIM Labs

fujiによるエントリー一覧

Deep Learning していると、学習にとても時間がかかるのがネックになる。
時間だけでなく、メモリもいっぱい必要になる。
今はGPUなるものがあり、これを使うと計算処理が一気に早くなると言われている。
GPUの計算性能は、CPUの50倍以上とか言われているのだが、実際にはそこまで高速にDeep Learningできるようにはならない。
GPUを使う場合には、GPUにデータを送ったり、GPUからデータを受け取ったりなど、CPUとGPUの間でのデータのやり取りが発生し、この量が半端ではない。このため、実際の速度向上は10倍程度にしかならない。

でも、もっと高速にしたいと思ったらどうすればよいだろうか。
多数のGPUを同時に使えば、どんどん高速になるのではないだろうか。
分散して学習する場合に問題になるのは、別々のGPUで学習した結果をまとめて、それをまた全GPUに分配し直すという作業が必要である。これをいかに上手にするかで性能が殆んど決まってしまう。

これは人間にたとえると、多人数で勉強をするにの、それぞれ別の参考書を読んで学習し、読み終わったら全員で学習結果を寄せ集めることでより賢くし、学習結果をまた全員に分配することで、全員が全参考書を読んで賢くなったようにしてしまう。そして、またそれぞれが別の参考書で学習すれば、また賢くなっていく。これを繰り返せば、相当な速度で賢くなるはず。
一人でやるより、二人でやれば2倍近く賢くなり、4人、8人、16人、、、、と増やしていくと、賢くなる速度が倍々で速くなるはずだ。

でも、そんなにうまくいくだろうか。

ということで調べていたら、こんな記事にぶち当たった。
ChainerMN による分散深層学習の性能について

そして、この中には、以下のグラフが含まれていた。

dlsummit_01_throughput.png
GPUを128個使うと、100倍高速になったとある。
つまり、ほぼGPUの数に比例して高速になるという夢のような効率である。
本当にそこまで高速になるのか、どうしても疑いたくなってしまう。

さらに、他のフレームワークとのスケーラビリティの比較があった。

dlsummit_05_framework-throughput.png
ChainerはPythonなので、単純比較してしまうと高速ではないはずだ。
しかし、GPUをたくさん使った場合には、他のDeep Learning よりも遥かに高速になるらしい。
以上は、Chainerを作っているPrefered NetworksのPreferred Researchにあったものの引用である。
もっと詳しく知りたい場合は、ぜひオリジナルを読んでみよう。

このGPUのマルチノード対応パッケージは、既に公開されているので、GPUがゴロゴロしている人、GPUをふんだんに使える環境にある場合は、ぜひ実験してみよう。
詳細は、分散深層学習パッケージ ChainerMN 公開 で確認のこと。
Chainerはどういう環境で動くであろうか。
Installation Guideを見ると、

  •     Ubuntu 14.04/16.04 LTS 64bit
  •     CentOS 7 64bit
とあるのだが、日本で特に多いWindowsが載っていない。
Windowsをサポートしていなくても、クラウド上のChainerを使って、手元のPCはWindowsというのはできるようだが、私はやったことがない。

それが、ついに、ChainerがMicrosoft Azure および Windows にも対応するようになるようだ。
詳細は、ASCII.jp の 「Chainer」をMicrosoft Azureに最適化、Windowsもサポート を参照のこと。

Microsoftサイトでの紹介:Preferred Networks とマイクロソフト、ディープラーニングソリューション分野で戦略的協業

ところで、Microsoft自体は、CNTK(Microsoft Cognitive Toolkit)を持っている。
CNTK(Microsoft Cognitive Toolkit)のホームページ(英語)があり、GitHubにてMITライセンスにてオープンソースとして公開されている。

マイクロソフトというと、まだ頭の固い連中は、オープンソースと対極にある会社と思っている者だらけだと思うが、ずいぶん前からOSSの活動を行っている。まだ、ビル・ゲイツが現役の最後の頃に日本にやってきて、「マイクロソフトはこんなにOSSをやっていて....」と説明したものの、そもそも相手がOSSなるものが何かも分からない連中で、全然マイクロソフトのOSS活動を伝えられなくて、ガックリとして帰国したことがあった。

マイクロソフトは、クラウドビジネスの拡大を目指しており、自社のソフトという縛りをつけていたらビジネスにならないので、積極的にOSS活動をしている。たとえば、 マイクロソフトによるオープン ソースへの取り組みが、ビジネス チャンスを拡大を見れば、その一端が分かるだろう。


2017年6月現在、Deep Learningのツール、フレームワークはいったいいくつあるのだろうか?
まだ、これからも増えそうな気がする。
また、各フレームワークは、機能、性能、開発環境などをどんどん向上させてきているので、どれが良いといっても、それは判断した瞬間だけしか有効でない。
GPUの性能向上も著しく、うまくGPUを活かせるのはどれかも重要な判断材料になる。
数年後に、Deep Learningのフレームワークの競争はどのようになっているか、今使われているもののうちどれが優勢になるかの判断は困難だ。
2020年頃には、消えてなくなる、弱体化しているのが多いだろうし、それはITの世界の運命だ。
でも、主流になるものを今から使っておきたい.....と誰もが思うだろう。

だからといって、Deep Learning を避けて通るというのは難しいというか、取り残されてしまう可能性もある。

でも、使う側にとって、フレームワークなので、別のに乗り換えるのに、スクラッチで書き上げたソフトを別の言語に移植するような困難は通常は無いので安心である。
とりあえず、自分の都合の良いのを使い込むべきだろう。
そして、いつ状況が変わってもよいように準備しておくべく、あまりにも使用フレームワークに依存したのを書かないのが良いだろう。

人工知能、機械学習、Deep Learning などは、大学でも普通に教えている情報科学、情報工学の一般教養になりつつある(かな?)


自然言語処理と深層学習,C言語による (424x600).jpg

題名:自然言語処理と深層学習

《C言語によるシミュレーション》

小高 知宏 著

A5、224項、本体2500円

平成29年3月25日 発行

オーム社

ISBN 978-4-274-22033-3



深層学習(Deep Learning)のブームが続いているが、多くの場合は画像系が話題の中心になっている。
深層学習は、何も画像処理だけでなく、自然言語処理にも使える、他にも、たぶん色々なことに使えるであろう。
ということで、少々画像関連ばかりをやるのに飽きてきたというのもあって、自然言語処理の本を取り上げることにした。


本書の最大の特徴は、C言語を使って実装されているということだ。

深層学習というと、今は圧倒的にPythonが多い。それも、多くはフレームワークを使っていて、深層学習がどのように実装されているのとかは全く分からないけれど、とりあえず動かすことができ、何かAI的な動きになっているのを感じられるという本が多いように思う。

この本は、そういう流れとはまったく違う。
C言語で、何もライブラリを利用せず、もろにゼロから作り上げている。

最初の方は準備として基本的な説明がされており、その後、畳み込みニューラルネットワーク(CNN)とリカーレントニューラルネットワーク(RNN)が、それぞれ1つの章として書かれている。
RNNについては、説明している書籍はかなり少ないようなので、参考になるかも知れない。

Cでゼロから作っているし、200ページ余のかなり薄い本なので、扱っている内容は基本的なことだけであるが、実行結果やその説明はかなり丁寧に書かれている。

Cのソースコードおよびデータは、オーム社のダウロードサービスのページから取得可能である。
書籍の実行結果を見ると、Windows上での実行結果が載っていたが、C言語の標準的な部分しか使用していないので、C言語が動くコンピュータなら何でも動作するのではないかと思う。
私はUbuntu/LinuxのGNUのCでコンパイル・実行してみたが、特に問題は無かった。

難点があるのは、Cのインデントである。
インデントの単位が1カラムになっており、読むのにちょっと困る。
印刷する場合、8tabだと都合が悪いかもしれないので、4tabで字下げをして欲しかった。
今では、C Beautifier(Cプログラム整形ソフト) という便利なツールが色々あるので、読みやすいように自動整形してから利用するのをお薦めする。

出力結果は非常に単純なものである。
座標値(位置)に対する変位電流など電磁気学的な量を計算するだけである。
要するに、横軸が位置xで、縦軸が f(x) というだけの簡単なグラフを描きたいのだが、1980年初期にはグラフを描くのはなかなか大変なので、単に数値だけがだらだらと出力ファイルになって出てくるものである。
もちろん、今は、計算結果をExcelに読み込んで、グラフを描いてシミュレーション結果を評価しているようだった。

このプログラム、1980年代のコンピュータを想定しているので、配列などのサイズが非常に小さく制限されている。
当時は、大型コンピュータといっても、せいぜい1MB程度しかメモリが使えないのが普通であった。
そういう制限の下で、ごちゃごちゃと計算していたのだ。
せっかくWindowsになるのだから、グラフをもっと細かく精密に出したいという当然の要望があった。

実際のプログラムでは、サイズの上限の数値がプログラム中に散在していたのだが、それを1つの定数変数として宣言することで、1箇所直せばサイズを変更できるようにした。
グラフの細かさは100倍くらい楽勝と思われたが、何とか現状の2倍にして欲しいというので、10倍に設定した。

さて、この機能強化により、より細かい精密な評価ができるようになる..........はずだった。
x軸上の測定間隔を半分にしたら、出発点が同じなら、1つおきに元の値と完全一致する筈だ。
でも、一致しなかったのだ。
まあ、計算誤差というのもあるから、ある程度誤差が出るのは仕方がないのであるが、明らかにかなり違う。

想定外の問題が出てしまった。どうしよう。
ということで、デバッガを2つ立ち上げて、計算間隔を丁度倍違うようにして、同じx値に対する計算が始まる箇所で止めを、変数の値を全部調べ比較したのだ。
どこで何が違うのかを調べた。
違っても問題ないのかもしれないし、プログラムの意味を知らずに調べているので苦しい。
それでも、デバッガでステップ動作で調べていると、徐々に分かってくるものである。

やっと分かったのは、やはり初期化の問題だった。
内部ループの最初で行うべき初期化が、なぜか省略されていた。
電磁波の反射などの影響を延々と足しこんでいるらしい配列だったのだが、1回前の測定点の値にそのまま足しこんでいるようだった。
恐る恐るループの最初で疑わしい複素配列を初期化すると、結果がまるで変わった。

数字の羅列では良く分からないし、でもExecelに結果を読み込むのも面倒なので、Pythonで比較グラフを表示するツールを作った。
青線が元の計算結果、赤が内部ループにゼロクリアを入れた場合の計算結果である。
計算は、右から左に向かって進んでいく。
振動が消えてしまったが、これで良いのだろうか、悪化したのか、情報がないので分からないので、お客さんに聞いてみた。
compare.png 昔は、赤い線のようなグラフになっていたそうだ。
このグラフが出るようになってから、急に資料の発掘が進み、30年以上前の資料でグラフが載っているものがあり、振動がないことがわかり、昔の状態に復元できたようで、魔法使いのように扱われ、とても感謝された。

ということで、一件落着になったのだが、こんなに上手くいくことは珍しい。
非常に古いプログラムで、資料が無い、作った人が居ない、その他問題が多いプログラムを扱うのは拒絶するのが正しい。
それでも諸般の事情、大人の事情などで作業しないといけないこともあるが、その場合は細心の注意で望まれたい。

このような作業をおこなうと、非常に力が身に付くことも確かである。
ハッカー養成ギブスとしては良いかも知れない。

(完)
プログラムの内容の詳細は書く訳にいかないのだが、技術計算で、電磁気学に関するシミュレーションである。
数学的には、複素関数、複素級数、偏微分(マックスウェルの方程式)などが使われている。
たぶん今だったら、MatLab(Octave)でちょっと書けば済みそうな内容であるが、困ったことに内容が分からない。

日々科学技術計算をやっている訳ではないので、どうなるか分からないが、まあ調べれば何とかなるのではと思った次第である。
科学技術計算とかいってずいぶん難しいことをやっていると予想して勉強してから行くと、ほとんど科学技術とは無縁のプログラムであることが多いので、たぶんこれも大丈夫と予想したのであった。
それでも、一応、いざというときのために、関連情報などをネットで軽くは調べておいた。

このくらい古くなると、作ったときの資料も無ければ、作った人も居ない。既に彼方の世界の人になっている。
でも、ソースプログラムがあるから恵まれているほうである。
ソースプログラムが無い移植などの仕事もあるのだが、そういうのに比較すれば易しい。(比較がオカシイかも)
かなり面倒な計算をしているのだが、詳細な計算内容はとても追いかけるのは無理で、核心部分はブラックボックスのままであるが、それでも移植は可能であり、全く同じ動作をするかを確認する手段を考えて、引き受けた。

以上は、元のプログラムに何も問題がなければ、全く同じ動作をすれば移植のテスト完了となるのであるが、世の中そんなに甘くないのである。

プログラムは9本に分かれていて、技術計算をいっぱいしているのは2本だけである。
ある特殊な装置の電磁波の影響を計算するプログラムで、水平方向と垂直方向で別々のプログラムに分かれている。
垂直方向が5本、水平方向が4本の構成である。
計算以外のプログラムは、入力データを作るためなのだが、昔々のプログラムなので全てCUIになっている。

まずはコンパイルエラーを取るのだが、ワーニングがかなり出てうっとうしい。
コンパイル時にワーニングを出なくすることは可能だが、問題があったときに大事なワーニング情報を見失う可能性がある。
それで、ワーニングが出なくなるようにするFORTRANのソースコード・コンバーターをちょっとPythonで書いた。

実際のコンパイルオプションはこんな感じである。
gfortran -Wall -pedantic -std=f95 -fbounds-check -fall-intrinsics -Wuninitialized 
	-ffptrap=invalid,zero,overflow -o HOGE.exe HOGE.for
FORTRANのコンパイラオプションを決めるのに、東京大学のFortranデバッグオプションのページを参考にした。
まだFORTRANをどんどん使っている研究室があるんだ。

コンパイルが済んだら、とりあえず走らせてみるのだが、特殊な技術計算用のプログラムなので、データを用意するのが困難なので、データをファイルでもらって、それを読み込んで走らせることにした。

最初は、実行時エラーが出てしまった。
変数に値をセットする前に参照していると叱られた。
つまり、配列の初期化がされていない。
これがやたらに多かったのだが、昔のFORTRANの一部には、配列宣言をしたら初期化も自動で行われるもの(コンパイラのオプション指定)もあったためだろう。
ということで、初期化されていない配列の初期化をかなり行った。

一応、初期化を済ませたら、計算処理プログラムが何とか走り出した。
コンパイル時のオプションもどんどん減らして、最終的にはソースとオブジェクトの指定だけまで減らした。
やれやれである。

入力データを、まだ旧ソフトが動くコンピュータ上で実行してもらい、入力データと出力データをもらって、移植したプログラムでも実行し、2つの出力ファイルを比較した。
完全一致したら、移植が完璧ということになる。
出力ファイルはテキストファイルなので、ファイル比較を行い、一文字も差がないことを確認すればよい。
もし、旧プログラムにバグがあれば、バグも完全に再現できているという素晴らしい移植ということになる。

技術計算なので、どうしても数値計算誤差がでてくる。
計算結果は固定小数点数で、有効数字も3,4桁程度であるので、少々の計算誤差があっても、結果は同じになる。
それでも、2つ差異が出てしまった。

1つは、0.00と-0.00の差である。
0に非常に近い値のとき、計算誤差により正になったり負になったりする。
これは値としては同じと考えられるので、OKとした。

計算結果の最後の1行が出たり出なかったりした。
これは、リミットの計算を計算誤差を無視して、単に不等号で判断していたのが原因で、ちょっと余裕をもたせるようにして対処した。

これで、同じ入力データを新旧のプログラムで走らせた結果の2つの出力ファイルが一致したことになる。
とりあえず移植完了。

移植以外に、いくつかの機能UPの要望があったのだ。
どれも比較的簡単なものと思って気楽に引き受けたのだが、思わぬ展開になるのであった。

次回につづく。
今回は、ちょっと別の昔話、ではなくて今年の話。

色々なことをやっていると、突然変な仕事が降ってくることがある。
FORTRANという、たぶん最近の人は見たことも聞いたこともないプログラムの話だ。

昔々、COBOLとFORTRANというプログラミング言語が主に使われていた。
COBOLが事務処理用で、FORTRANが技術計算用である。
当時は、もちろんネットもないし、WEBなど全然ない時代である。
以下、コンピュータ考古学の話である。

さて、今回、ずいぶん昔にFORTRANで書かれたプログラムを、Windows10で動くようにして欲しいというので持ち込まれた。
いや、泣きつかれたというほうが正しい。
平成ではなく、昭和の時代に書かれたもので、80年代に開発が行われ、当時、大企業のコンピュータセンターの大型コンピュータで動いていたものだ。
といっても、当時のコンピュータは呆れるほど非力で、今のパソコンはもちろん、Raspberry Pi にも劣る能力である。
でも、そこで計算していたものは、今でも高価な機械に関するシミュレーションプログラムで、30年以上使い続けられていて、今後も使う予定のものだ。

まあ、諸般の事情で逃げられないので引き受けたのだが、FORTRANを最後に使ったのは確か1982年だと思う。
その後は、C, C++, Java, Python などなど言語放浪を続けている。
なので、覚えているか全然自信がなかったのだが.....

それで、まずはWindows上で動くFORTRANを探した。
持ち込まれたFORTRANのプログラムが、非常に古い書き方をされたもので、まだパンチカード時代を偲ばせるものだ。
要するに古文書である。
FORTRANも長い間に仕様が色々変遷してきたのだが、1970年代の FORTRAN JIS 7000 という仕様で書き始められ、手を入れた部分だけがやや最近の書き方になっていた。
こういうプログラムでもちゃんとコンパイルできるものを探さないと修正作業が面倒になる。
とくに古いプログラムを扱うとき、まるで国宝を扱うように、可能な限り原型のままで動くようにしないといけない。
ちょっとでも手を入れると、何が起きるか怖いのである。
そのため、古い書き方でも対応してくれ、Windows10も対応しているFORTRANを探した。


その結果見つけたのが、GNUのFORTRANであり、gfortran(gfortran -- the GNU Fortran compiler, part of GCC)と呼ばれるものだ。
実際にWindows環境にインストールする方法はいくつかあるようだが、MinGWを使った。
これだと、古い書き方から最新の書き方まで混在しても大丈夫なようだった。
とりあえずコンパイルできることは分かったが、それだけでは動かないようだった。

また、デバッグにgdbを使うので、Cのデバッグと同じ感覚でデバッグでき、便利であった。
このデバッガが1980年頃に使えたらどんなに便利だったかと思う。

さて、実際の移植作業がどうだったかについては、次回紹介しよう。
プーリングのサイズを大きくするとすぐにダメになるのではと思ったが、そんなことはないらしいのが前回わかった。
それで、プーリングサイズをどんどん大きくし、テスト精度が最も良くなるのはどのあたりかを調べてみた。

グラフの右には、プーリングのサイズ、そのサイズでテスト成功率が最大になったepochとテストの最大精度を示している。

accuracy-CNN2s.pngプーリングなし  3 0.6667

accuracy-CNN5x5s.png プーリング 5x5  6 0.7354

accuracy-CNNp6x6s.png プーリング  6x6  7 0.7412

accuracy-CNNp7x7s.png プーリング 7x7  8 0.7436

accuracy-CNNp8x8s.png プーリング 8x8  6 0.7402

accuracy-CNNp10x10s.png プーリング 10x10  6 0.7307


この実験では、7x7のプーリングで、プーリングなしと比較して7.69%(66.67→74.36)テスト精度が向上した。
プーリングだけでこれだけ向上するということは、プーリングは何らかの形で使うのが良さそうだ。

上のグラフを眺めると、もう1つの変化が見えてくる。
緑がテスト精度であるが、青の学習精度の方に注目してみよう。
プーリングなしだと、10epochあたりで98%に達しているのだが、プーリングサイズを上げると、学習段階での精度の上昇ペースがかなり鈍るようである。
前回、コマンドラインからプーリングのサイズを指定できるようになったので、3x3, 4x4,....のプーリングを試してみよう。
サイズを大きくするということは、データが集約されて、肝心の情報が無視されてしまうような気もするのだが。

まず、3x3の場合。プーリングサイズ以外は、以前と同じ状態だ。


CIFAR-10$ python train_cifar5.py -g 0 -u 500 500 -p 3
GPU: 0
# number: 50000
# units: [500, 500]
# pooling: 3
# Minibatch-size: 100
# epoch: 20

epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           1.36881     1.05861               0.50406        0.6211                    9.16741       
2           0.941233    0.887284              0.66934        0.6877                    18.1766       
3           0.765903    0.862215              0.73098        0.6993                    27.2039       
4           0.628062    0.834987              0.7782         0.715                     36.3343       
5           0.491332    0.892278              0.82718        0.7122                    45.5346       
6           0.369885    0.987892              0.86914        0.7141                    54.6321       
7           0.258368    1.06508               0.909741       0.7145                    63.7151       
8           0.185255    1.28405               0.935921       0.7027                    72.7915       
9           0.134805    1.41083               0.953181       0.7086                    81.8067       
10          0.102211    1.61051               0.964881       0.7063                    90.8323       
11          0.0954875   1.59982               0.966341       0.7073                    99.89         
12          0.0744035   1.87134               0.974921       0.6999                    108.908       
13          0.0799358   1.87029               0.972621       0.7059                    117.916       
14          0.064477    1.79121               0.977761       0.706                     126.942       
15          0.0539574   1.99034               0.98168        0.6957                    135.992       
16          0.0564298   2.12325               0.981061       0.7012                    145.031       
17          0.0636167   1.98566               0.978781       0.6953                    154.082       
18          0.0543167   2.06661               0.98182        0.6956                    163.078       
19          0.0525986   2.02431               0.982701       0.7004                    172.108       
20          0.0473705   2.20115               0.98364        0.6995                    181.123       
CIFAR-10$ 

accuracy-CNNp3x3.png
3x3にしたら、テスト精度が70%を超え、71.5%まで達した。
プーリングのサイズを2や3で解説している例が多い。
たぶん、サイズを大きくしてしまうと、目が荒くなりすぎて、情報が漏れてしまう気がする。
でも、サイズが大きくなると、どのようにダメになっていくのもきちんと確認すべきではないだろうか。
ということで、コマンドラインからプーリングのサイズを指定できるようにした。

つまり、コマンド行で、 -p でプーリングサイズを指定できるようにしようという訳。
CIFAR-10$ python train_cifar5.py -g 0 -u 500 500 -p 4
GPU: 0
# number: 50000
# units: [500, 500]
# pooling: 4
# Minibatch-size: 100
# epoch: 20

プログラムは、最後にまとめて示す。
CNNオブジェクト(model)を生成するときに、プーリングサイズを渡すようにした。
渡されたプーリングのサイズはCNNオブジェクトの変数に記憶させておき、実際にプーリング処理をするときにサイズを参照するようにした。

プーリングサイズを大きくしていくな何が起きるのか、予想をしよう。
 サイズは、2か3あたりが良い。 それとも、もっと大きくして、7,8,9あたりの方が実は良いとか。
さすがに画像サイズと同程度になってしまったら、ダメだろうな。

実験の報告は、次回からになる。

以下が、プログラム全体である。
前回は、プーリングの説明をした。
今回からは、プーリングの実装について書く。
といっても、僅かな変更を加えるだけである。

とりあえす、プーリングのサイズが2の場合を試す。
# Network definition
class CNN(chainer.Chain):

    def __init__(self, n_units, n_out):
        super(CNN, self).__init__(
            conv1=L.Convolution2D(None, 36, 3),     # 畳み込み
            conv2=L.Convolution2D(None, 36, 4),     # 畳み込み
            l1=L.Linear(None, n_units[0]),  #            -> n_units[0]
            l2=L.Linear(None, n_units[1]),  # n_units[0] -> n_units[1]
            l3=L.Linear(None, n_out),  	    # n_units[1] -> n_out
        )
        
    def __call__(self, x):
        c1 = F.relu(self.conv1(x))                  # 畳み込み
        c2 = F.relu(self.conv2(c1))                 # 畳み込み
        p1 = F.max_pooling_2d(c2,2)                 # プーリング
        h1 = F.relu(self.l1(p1))
        h2 = F.relu(self.l2(h1))
        return self.l3(h2)

畳み込みでは、1層増やす毎に、__init__() と __call__() が1行ずつ増えたのだが、プーリングでは、__call__()の中に1行書き加えただけである。 畳み込みの2回目が終わって、全結合層に入る直前にプーリング層を入れている。 何も説明することがないくらい簡単で困る。

このアーカイブについて

このページには、fujiが最近書いたブログ記事が含まれています。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。