植物の病気をDeep Learningで判別  概要+準備編(1/3)

植物の写真から病気を判別するサイト(http://www.plant-check.jp/)を作ってみた。
サイトは2017年8月くらいに作ったのだが、そのメモがてら、やったことをまとめてみる。
使用した技術は最近の流行をふまえ、わりと本格的。バリバリのDeep Learningなので、人工知能(AI)を利用し、植物の病気判定を行った!と言っていいはず。

  • 病気判定に使用してたのは(1)植物の葉かそれ以外かを判別する2値分類モデル+(2)植物の葉が病気か否かを判別する多値分類モデル。
  • 2つのモデルともにDeep Learningを活用。具体的にはInception V3(ImageNet)を転移学習させる形で作成。
  • ソフトウェアはPythonで作成。Deep Learning部分はkeras + tensorflow、WEB部分はnginx + uwsgi + bottleで構成。
  • インフラ部分にnvidia-docker(keras + tensorflowでの学習部分)とdocker(WEB部分と判別モデル利用)を使用。サーバはsakuraのVPS(ubuntu 16.04 LTS)。
  • 学習データにはa) 自分で集めたバラの葉っぱ画像(健康な葉と病気の葉をハンドラベリング)とb)SUN Database(http://groups.csail.mit.edu/vision/SUN/)を使用。
    • (1)植物の葉かそれ以外かを判別するモデルにはa) + b)を利用
    • (2)植物の葉が病気か否かを判別するモデルにはa)のハンドラベリング結果を利用

開発環境の準備

開発環境としてDockerを利用したコンテナを2つ+リリース用のコンテナを1つ作成した。

  1. GPUを用いた学習用としてtensorflow + kerasが動作するコンテナ
    • ホストとなるUbuntu 16.04にnVidia公式ドライバをインストールした後、nvidia-dockerを導入して構築。
    • 安定版のgcr.io/tensorflow/tensorflow:latest-gpuをベースにしてkerasを追加で入れた。
    • (困ったときは公式サイトを見るのが一番だった。)
  2. 学習したモデルを用いたWEBアプリを開発するためのコンテナ
    • ホストは1.と同じ。pipからkeras, tensorflow, bottle, uwsgiを入れた。
    • 開発用なのでjupyterも導入。
  3. WEBアプリをリリースする環境のためのコンテナ
    • 2.との違いはjupyterなど余計なソフトウェアを省いている点だけ。

データの準備

本件で作成したモデルの概要とモデル構築に利用するデータは次のとおり。

  1. 植物の葉かそれ以外かを判別する2値分類モデル
    • バラの葉の画像データを正例、SUN Database(http://groups.csail.mit.edu/vision/SUN/)の植物以外のカテゴリを負例とする2値分類モデル。
    • Imagenetで学習したInception V3(keras付属)を利用。
    • 前半250層を固定。Inception V3の後に判別層を追加する形でネットワークを構成(詳細は次回記載)、学習させた。
    • いわゆる転移学習とかfine tuningとか呼ばれる手法。
    • 使ったデータは約16000枚。(data augmentation前)
  2. 植物の葉が病気か否かを判別する多値分類モデル
    • バラの葉の画像データを「健康」「黒星病」「うどん粉病」「その他カビ系の病気」の4カテゴリに分類する多値分類モデル。カテゴリ分けは自分でとったバラの写真・動画(画像に一定フレームごとに画像として切り出し)をハンドラベリングした。
    • 利用した学習手法は1.と同様。
    • 使ったデータは約1800枚。(data augmentation前)

バラの葉の画像データは夏休みに都内の公園をめぐって自分で撮影した。モデルに投入していないデータ(バラ以外、対象病気以外)も含めて3000枚以上撮影。機械学習のコード書くより大変だった(が良い経験になった(詳しくは次回))
Read more »

ドキュメントとソースコードの対応2(word2vecの利用)

先週に引き続きドキュメントとソースコードの対応付けについて実験してみた。
手順は下記の通り。前回からの改良(?)としてword2vecで日本語→英語の変換を行ってみた。

  1. 英語と日本語のマニュアルからword2vecで使用できる形式で単語を抜き出す。
    • 形態素解析のライブラリにはMecabを利用。
    • 2.のword2vecの処理で使うためのデータ。
  2. PostgreSQLのソース内にあるhtml、c、hファイルを対象にファイル内に存在する単語を抜き出す。
    1. 単語が日本語であった場合はword2vec的に類似度の高い単語TOP10から距離に応じて英単語をランダムに選ぶ。
      • ライブラリにはgensim(http://radimrehurek.com/gensim/index.html)を利用。
  3. 2.で抜き出したデータに対し、TF-IDF用の辞書(単語と出現頻度)を作る。
  4. 2.で抜き出したデータと3.を使って、それぞれのファイルに含まれる単語に対してTF-IDFで重み付けを行う。
  5. データ内(PostgreSQLのソース内)にあるhtmlファイルに対してコサイン距離の近いc、hファイルを一覧化する。

結果は↓の通り。前回に比べてコサイン類似度の値自体は上がっているものの適切なソースファイルを引けているかというとやや疑問。とはいえ何もやらないよりはマシかも(?)
詳細な結果とソースは以下のURLをご参照。

--../data/postgresql-9.3.4/doc/src/sgml/html/app-createuser.html--
[   (   '../data/postgresql-9.3.4/src/bin/scripts/createuser.c',
        0.5312760078128166),
    (   '../data/postgresql-9.3.4/src/backend/commands/user.c',
        0.49654701095797743),
    (   '../data/postgresql-9.3.4/src/bin/pg_dump/pg_dumpall.c',
        0.4521924409520057),
    (   '../data/postgresql-9.3.4/src/include/commands/user.h',
        0.4458741678013434),
    (   '../data/postgresql-9.3.4/src/include/catalog/pg_authid.h',
        0.4366612559172716)]
--../data/postgresql-9.3.4/html-ja/app-createuser.html--
[   (   '../data/postgresql-9.3.4/src/bin/scripts/createuser.c',
        0.27458665377082037),
    (   '../data/postgresql-9.3.4/src/bin/psql/tab-complete.c',
        0.2411847340696424),
    (   '../data/postgresql-9.3.4/src/backend/commands/user.c',
        0.20269999574142247),
    ('../data/postgresql-9.3.4/src/include/utils/acl.h', 0.16197452745649446),
    (   '../data/postgresql-9.3.4/src/bin/pg_dump/pg_backup.h',
        0.16130232695577076)]
--../data/postgresql-9.3.4/doc/src/sgml/html/indexes-intro.html--
[   (   '../data/postgresql-9.3.4/src/backend/commands/indexcmds.c',
        0.5887593627491802),
    ('../data/postgresql-9.3.4/src/include/utils/rel.h', 0.5847988052787634),
    (   '../data/postgresql-9.3.4/src/backend/optimizer/path/indxpath.c',
        0.5736868945937168),
    (   '../data/postgresql-9.3.4/src/backend/catalog/index.c',
        0.5695396670614502),
    (   '../data/postgresql-9.3.4/src/backend/optimizer/util/plancat.c',
        0.5669650556082131)]
--../data/postgresql-9.3.4/html-ja/indexes-intro.html--
[   (   '../data/postgresql-9.3.4/src/backend/access/rmgrdesc/spgdesc.c',
        0.6065874266691939),
    (   '../data/postgresql-9.3.4/src/backend/access/transam/rmgr.c',
        0.6055142588080052),
    (   '../data/postgresql-9.3.4/contrib/pg_xlogdump/rmgrdesc.c',
        0.6055142588080052),
    (   '../data/postgresql-9.3.4/src/backend/access/spgist/spgquadtreeproc.c',
        0.6054292723362433),
    (   '../data/postgresql-9.3.4/src/include/access/rmgrlist.h',
        0.6054292723362433)]

ドキュメントとソースコードの対応

ソフトウェア開発においてドキュメントとソースコードの対応を取り続けることは想像以上に難しい。開発当初は綺麗に対応していたドキュメントとソースが年を追うごとに乖離していき、それぞれ別物として成長していくことはよくある光景である。そのような中、ドキュメントに対応するソースが何であるか一覧化できるそりゅ~しょんが強く求められている・・・ような気がする。
ざっくり言うとドキュメントとソースコードの紐付けが自動でできるかどうか気になったのでやってみた。(ここにはひさしぶりの投稿)
条件は下記の通り。

  • Postgresql9.3.4のマニュアルに対応するソースを表示する。
  • マニュアル、ソースそれぞれについて出てくる単語を抽出し、TF-IDFで重み付けを行い、コサイン類似度の大きなものを関連しているとみなす。

結果、英語版のマニュアルとソースの対応はそこそこの精度で取れるが、日本語版のマニュアルに対する対応はイマイチだった。ソース中に日本語が入っているとは思えない(=キーとなる情報が少ない)ので納得のいく結果ではある。
下記はcreateuserとindex-introの例。[]内には–・・・.html(マニュアル)–に対して類似度が大きなソース上位5つが入っている。()内の”の中がソースコードの名称でその後の数字がコサイン類似度。なお、htmlディレクトリ以下にあるのが英語版で、html-ja以下にあるのが日本語版となっている。

--../data/postgresql-9.3.4/doc/src/sgml/html/app-createuser.html--
[   (   '../data/postgresql-9.3.4/src/bin/scripts/createuser.c',
        0.5315036131957114),
    (   '../data/postgresql-9.3.4/src/backend/commands/user.c',
        0.49634873213501424),
    (   '../data/postgresql-9.3.4/src/bin/pg_dump/pg_dumpall.c',
        0.45199746892518794),
    (   '../data/postgresql-9.3.4/src/include/commands/user.h',
        0.4456889634461926),
    (   '../data/postgresql-9.3.4/src/include/catalog/pg_authid.h',
        0.436479878362433)]
--../data/postgresql-9.3.4/html-ja/app-createuser.html--
[   (   '../data/postgresql-9.3.4/src/bin/scripts/createuser.c',
        0.24660267524744783),
    (   '../data/postgresql-9.3.4/src/bin/psql/tab-complete.c',
        0.21638658446826328),
    (   '../data/postgresql-9.3.4/src/backend/commands/user.c',
        0.18185439320172517),
    (   '../data/postgresql-9.3.4/src/backend/catalog/objectaddress.c',
        0.17654887868713476),
    (   '../data/postgresql-9.3.4/src/backend/parser/gram.c',
        0.15892817112702087)]
--../data/postgresql-9.3.4/doc/src/sgml/html/indexes-intro.html--
[   (   '../data/postgresql-9.3.4/src/backend/commands/indexcmds.c',
        0.5887593627491802),
    ('../data/postgresql-9.3.4/src/include/utils/rel.h', 0.5847988052787634),
    (   '../data/postgresql-9.3.4/src/backend/optimizer/path/indxpath.c',
        0.5736868945937168),
    (   '../data/postgresql-9.3.4/src/backend/catalog/index.c',
        0.5695396670614502),
    (   '../data/postgresql-9.3.4/src/backend/optimizer/util/plancat.c',
        0.5669650556082131)]
--../data/postgresql-9.3.4/html-ja/indexes-intro.html--
[   (   '../data/postgresql-9.3.4/src/backend/lib/stringinfo.c',
        0.09184457364491616),
    (   '../data/postgresql-9.3.4/src/interfaces/ecpg/test/expected/sql-insupd.c',
        0.0886416204237675),
    (   '../data/postgresql-9.3.4/src/test/examples/testlibpq3.c',
        0.08837346944289427),
    (   '../data/postgresql-9.3.4/src/backend/access/heap/visibilitymap.c',
        0.08815864541352804),
    (   '../data/postgresql-9.3.4/src/interfaces/ecpg/test/expected/sql-indicators.c',
        0.08800994862677734)]

詳細な結果はhttp://staka.jp/doc-src/result.txtの通り。なんとなくだが、英語版マニュアルとソースコードはそれなりに紐付いているように見える。日本語のコメントが豊富にあるソースとであれば日本語版もうまく行くような気がしないでもない。
ソースコードはhttps://github.com/s-taka/doc-srcにおいておいた。基本的にPythonで書いていて、日本語マニュアルの形態素解析はMeCabで行っている。最近流行のword2vecの利用は今後の課題。

補記

前回同様、前段の補記。ドキュメントとソースが乖離していくのがよくある光景なのは事実だが、ドキュメントとソースが(ざっくりと)紐付いてうれしい人がどれだけいるかは謎。ドキュメント→ソース→コミットログ→作成者とたどっていけるのはある程度便利かもしれないが。本当は、ドキュメントとソースの乖離が分かるとうれしい人は多そうだけど、自然言語処理のアプローチでそれを見つけるのは至難の技だろーなーと思う。
 

GPGPUで精度保証付き計算

金融の計算において、計算精度は超重要である。精度が低ければ価格付けに失敗し大損するかもしれない。
CUDAには、__fadd_rn(x, y)、__fadd_rz(x, y)、__dadd_rd(x, y)、__dadd_ru(x, y)、のように四則演算で丸めの方向を指定可能な関数が用意されている。数学関数の誤差についての情報も与えられておりGPGPUで精度保証計算を行うことが可能である。
前回同様、ブラックショールズモデルでのオプションプライシングを例に精度保証付き計算の実験を行った。条件は下記の通り。

  • 計測対象のオプション明細は1024万件。パラメータは乱数により発生させる。
  • プレミアムを算出。(デルタ、ガンマはめんどーなので省略した。)
  • 計算はすべてdouble型で行う。(単精度版は後半)
  • Amazon EC2のクラスタ GPU クアドラプル エクストララージを使用。GPUはFermi M2050を1つ使用。CPUは1コアのみ使用。コンパイルは全部CUDAでやっている。(CPUには不利な条件だが、CPUとの計算時間は比較しない。)
  • 四則演算については丸めの方向を指定しつつ区間演算で精度保証を行う。
  • 数学関数については誤算情報を加味しつつ区間を設定し精度保証を行う。(わりとてきーにやっている。)

結果は下記の通り。数字の順はGPUで精度保証計算をしたプレミアムの下界、CPUでの計算結果、GPUでの計算結果、GPUで精度保証計算をしたプレミアムの上界となっている。最後の時間は、CPUでの計算時間、GPUへのデータ転送と初期化にかかった時間、GPUで精度保証付き計算をしたときにかかった時間(結果の転送時間込み)、GPUでふつーに計算したときにかかった時間(結果の転送時間込み)。

GPU lower < CPU | GPU < GPU upper
PREMIUM[0]: 0.405499973123749 < 0.405499973123820 | 0.405499973123821 < 0.405499973123893
PREMIUM[1024000]: 4.029060162124779 < 4.029060162124900 | 4.029060162124893 < 4.029060162125027
PREMIUM[2048000]: 0.733045301526717 < 0.733045301526774 | 0.733045301526773 < 0.733045301526825
PREMIUM[3072000]: 1.977617064175732 < 1.977617064175853 | 1.977617064175839 < 1.977617064175931
PREMIUM[4096000]: 0.162532300560706 < 0.162532300560737 | 0.162532300560734 < 0.162532300560762
PREMIUM[5120000]: 0.767333091428844 < 0.767333091428927 | 0.767333091428925 < 0.767333091428991
PREMIUM[6144000]: 8.282037131422769 < 8.282037131422967 | 8.282037131422962 < 8.282037131423138
PREMIUM[7168000]: 0.020357967912149 < 0.020357967912157 | 0.020357967912157 < 0.020357967912164
PREMIUM[8192000]: 0.442812778153888 < 0.442812778153947 | 0.442812778153947 < 0.442812778154016
PREMIUM[9216000]: 0.727449524196905 < 0.727449524196972 | 0.727449524196975 < 0.727449524197043
CPU time: 2779.697021 (ms)
GPU Initial time: 2082.956055 (ms)
GPU Processing time 1: 442.318848 (ms)
GPU Processing time 2: 89.383057 (ms)

GPUで精度保証付きの計算を行うために普通の5倍くらいの時間がかかる。単精度で計算すると下記のようになる。

GPU lower < CPU | GPU < GPU upper
PREMIUM[0]: 0.39829 < 0.39859 | 0.39859 < 0.39882
PREMIUM[1024000]: 4.04288 < 4.04349 | 4.04350 < 4.04403
PREMIUM[2048000]: 0.72358 < 0.72385 | 0.72385 < 0.72404
PREMIUM[3072000]: 1.98723 < 1.98767 | 1.98767 < 1.98803
PREMIUM[4096000]: 0.16034 < 0.16050 | 0.16050 < 0.16059
PREMIUM[5120000]: 0.77226 < 0.77262 | 0.77262 < 0.77288
PREMIUM[6144000]: 8.28720 < 8.28772 | 8.28772 < 8.28814
PREMIUM[7168000]: 0.02022 < 0.02033 | 0.02033 < 0.02036
PREMIUM[8192000]: 0.44575 < 0.44603 | 0.44603 < 0.44622
PREMIUM[9216000]: 0.72577 < 0.72608 | 0.72608 < 0.72630
CPU time: 8085.477051 (ms)
GPU Initial time: 2080.006104 (ms)
GPU Processing time 1: 200.956787 (ms)
GPU Processing time 2: 45.681152 (ms)

この例についてはCPUについては単精度の方が倍精度より遅い。理由としては最適化(特にSSEなのかFPUなのか)やらコンパイラやらCPUの特性やらメモリやらの話題があるがここでは省略する。
単精度の場合も普通の計算とくらべて5倍くらいの時間がかかっている。計算量もそうだが分岐命令が追加されたペナルティが大きいものと思われる。
単精度の方は精度保証有り無しの時間の比率が変わるかを調べる事が主目的であるため、四則演算以外の関数に関わる精度保証を非常に荒く行っている。上界、下界の差が広いのはそのためである。
以上の結果から、ブラックショールズモデルでのオプションプライシングで精度保証付き計算を行うためには通常の5倍程度の計算時間が必要であることがわかる。

補記

最初に計算精度が超重要とか書いたけれども、そもそもブラックショールズモデルではボラティリティスマイルや金利なんかの期間構造を無視してしまっているので、そのモデルに起因する差異のほうが数値計算上の誤差より大きい。なので、精度保証をやる意味はあんまり無(ry・・・いやすこしはあるかm・・・まぁちょっとは・・・
現実問題での上界下界を求めたければ正のバイアスがかかる式と負のバイアスがかかる式で挟み撃ちーという感じでやる必要がある。(アメリカンオプションのプライシングで見かける。とはいえ、そこまでやっても実際問題モデル誤差は残る。。。)
それはおいといて計算時間が5倍というのはそんなもんかなという印象。上界、下界のために最低2倍の計算とメモリが必要で、かつ、割り算の場合は正負の場合分けが必要なため分岐が入ってしまう。
精度保証付き数値計算は面白い考え方だと思うが、イマイチ流行っていない気がしなくも無い。コンパイラオプションなりVMの設定なりで気軽に使える時代が来たら利用する機会はありそうだが。。。
 
 

Amazon EC2 GPUインスタンス(Fermi)で金融の計算

先日(といってもかなり昔)にセットアップしたAmazonEC2のGPUインスタンスでFermiの実験してみた。(正確には下調べ。)
比較条件は以下の通り。お題は金融の世界でよく使われるブラックショールズモデルでのオプションプライシング。

  • 計測対象のオプション明細は512万件。パラメータは乱数により発生させる。
  • プレミアム、デルタ、ガンマを算出。
  • 計測はすべてdouble型。標準正規分布の累積分布関数についてもdouble型の精度のある近似式を用いる。
  • Amazon EC2のクラスタ GPU クアドラプル エクストララージを使用。GPUはFermi M2050を1つ使用。CPUは33.5ECUをフルで使用。(全力ならGPUは2つ使える。が、今回はGPU間の分散はやっていない。)
  • GPUのコンパイルなどはCUDA 3.2、CPUの方はGCC(4.1)+openMP。

結果は以下の通り。

  • CPUから見える場所に明細がある状態から計算完了(CPUから見える場所への転送完了)だとGPUが2000msecに対して、CPUは300msecくらい。(1コアのみCPUを使うと3300msecくらい)
  • GPU側に明細を転送した後からだとGPUは350msecくらいで計算を終え、計算結果をCPUから見える場所に転送できる。

ブラックショールズは計算負荷がかなり軽く、メモリ転送やデバイスの初期化に時間がかかるGPUには不利な条件。256万件だとGPU側の計算時間が「明細転送後計算開始~計算結果をCPU側にコピー完了」で230msecなので、GPUはまだ本気が出せていないよーな感じ(それを言うと、CPUも拡張命令使ってないので本気は出せていない)。
結論としてはGPU化するにはあんまり向いていない倍精度なブラックショールズ式の計算において、てきとーにコードを書いた場合でも計算部分(片道の転送時間は含む)ならば1 GPUでCPU 8コア(2つのクアッドコアNehalem)に相当する計算能力が出せるっぽい。素晴らしい。
ちなみに計算誤差は512万件分、「delta_diff += fabs( delta_cpu[i] –  delta_gpu[i]);」という感じで足しても

PREMIUM: DIFF = 0.000000026168368
DELTA: DIFF = 0.000000000065476
GAMMA: DIFF = 0.000000000001220

程度。なお、512万件の合算値はPREMIUM:52703033, DELTA:2487382, GAMMA:69438くらい。
個別でみるとほぼdoubleの限界のくらいまであっている。誤差部分はGPUの積和演算利用とか、CPUは実は微妙に拡張倍精度演算をすることがあるとか(最適化やSSEなど細かい話題次第ですが)、非正規化数の存在とかあるんだけど、どこが影響しているのかは調べていない。
むかーし試してみていたときよりもdouble型の能力・性能がかなり上がっている印象。相当向いていない処理にもかかわらずまずまずの結果だった。もうちょっと遊んでみる予定。
# NVIDIA的にはGPGPUに力を入れているようだが、新コアのKeplerが出たらAmazonも入れてくれるんだろうか?