実験用 GPU 環境をどう準備したらいい?(非情報系が機械学習を使う研究をしたいとき)

深層学習が著しく発展し、今まで人間にしかできないと思われていたことができるようになってきました。そのおかげで、今まで機械学習と縁が薄かった分野でも、機械学習を使った研究がしたいという声が上がるようになっています。
前々回は、それを裏付けるように非情報系の学生さんが機械学習を使った研究をしたいという応募がサイボウズ・ラボユースに増えているという話、前回はいままで機械学習や深層学習に縁のなかった人が何から勉強したらいいかという話を書きました。
今回はその続き、研究に必要な実験用 PC 環境をどのように準備したらいいかというお話です。

深層学習の実験をするには、十分な性能の GPU を積んだ PC が必要です。
今どきの機械学習関連の研究室では、院生有志がメンテナンスしている GPUクラスタがあって、それを使わせてもらえることが期待できます。自分用の PC を手配する場合も、研究テーマに適したマシンの選択について教授や先輩が相談に乗ってくれるでしょうし、環境構築&運用のノウハウもバッチリです。
しかし非情報系だと、そんな環境も相談相手もおそらくないでしょう。予算だけ出すから自分でマシンを選定するように、ってところでしょうか。環境構築も自分でググって手探りでやってみるしか。
ということで、前回の記事と同じくあくまで私見ですが、あまり後悔しなくて済むだろう深層学習用の PC 選びと環境構築の第一歩を書いてみます。

まず結論を言うと(TL;DR ってやつ)、

  • GPUNVIDIA(CUDA) 一択。予算の範囲で一番 GPU のメモリが多いものを選ぶ
  • OS は Linux がベスト。好きなディストリビューションでいいが、困ったときのググりやすさなら Ubuntu がおすすめ
  • CUDA の環境構築は nvidia-docker2(NVIDIA Container Toolkit) の導入を強くおすすめ

GPU

深層学習では GPU が必須です。CPU オンリーでも動きはしますが、大きいモデルの学習が現実的な時間で終わりません。

今購入が可能な GPUNVIDIA(GeForce, Quadro 等) と AMD (Radeon) です。他には、まもなく Intelディスクリート GPU (CPU と分離している、GPU 専用チップ)である Arc が出ますし、Apple の M1/M2 も AI 演算性能を喧伝しています。
ただ、深層学習の研究で使うなら、現状は NVIDIA 一択です。
他の選択肢を選んでもいいのは、機械学習とコンピュータ全般にとてもとても詳しくて、困難に出くわしたときに嬉々として自力で解決したがる人か、人柱になってもいいから特定のメーカーを応援したい人だけです。

NVIDIA は、GPU 上での基本的な演算をサポートした CUDA Toolkit と、 cuDNN という深層学習に頻出する計算を最適化したライブラリを提供しており、tensorflow や pytorch などの深層学習ライブラリは CUDA/cuDNN をサポートしています。
特に cuDNN は深層学習の推論速度に大きな影響があります。cuDNN がなくても tensorflow などは一応動きますが、推論速度に大きな差が出ます。
一方の AMD は、CUDA に相当する機能は OpenCL などで代替できますし、Intel も oneDNN という自社製ライブラリで頑張ってますが、いずれも cuDNN に相当する部分が強くないです。そのため、スペック上では同等の AMDGPU を持ってきても、深層学習の推論性能では NVIDIA に負けてしまいます。
Apple M1/M2 はメモリを CPU と GPU で共用できるなど、将来のポテンシャルには期待できます*1。何年かしたら「深層学習を始めるなら、コスト対性能で Mac に決まり!」なんて時代がもしかしたら来ちゃったりするかもしれません。が、それは今ではありません。

さて、NVIDIA 一択と言っても、「NVIDIAGPU」はいっぱいあります。GPU のスペック比較で注目されがちな CUDA コア数が多い製品をつい選んでしまうかもしれませんが、こと機械学習で使うとなると、最優先するべきスペックは GPU のメモリ量です。
CUDA コア数は GPU の並列計算能力を表し、それが多いほど計算が速いというのは間違っていません。しかし速い遅い以前に、実は深層学習のモデルは GPU のメモリに乗らなかったら計算ができません。「遅いけど計算できる」と「速いけど計算できない」なら、どちらを選ぶべきかは言うまでもないですよね。

深層学習の訓練では、その推論(予測)の何倍ものメモリが必要になります。その倍率に直結するのがミニバッチサイズ(1回のモデルパラメータ更新に用いる訓練データ数)です。GPU のメモリが足りないと、推奨されているミニバッチサイズで訓練ができなくて、しかたなくミニバッチサイズを減らすという貧乏ハック(涙)がよくあります。*2
ミニバッチサイズは学習したモデルの性能に結構影響があり、最適なバッチサイズを試行錯誤するのが深層学習チューニングの第一歩なところがありますが、メモリが少ないとその手も使えません。*3

よって、(低予算での) GPU 選びはメモリが最重要! これが鉄則です。*4
2022年現在、一部の低ランク NVIDIA GPU は、上位製品より搭載メモリが多いという逆転現象が起きていて、12GB のメモリを積んだ GeForce RTX 3060 が狙い目です。RTX 2060 にも 12GB 版がありますが、価格差は小さいので、より高性能な 3060 がいいでしょう。
12GB の次にメモリが多いのは RTX 3090(24GB) ですが、暗号通貨ブームによる高騰が収まった今でも20万円以上しますので、予算に収めるのは難しいでしょう(苦笑)。*5

CPU とメモリ

CPU は、IntelAMD で動かせるプログラムに差が出ることはありませんので、どちらでも大丈夫です*6。予算が許す範囲で高速なものを選びましょう。
深層学習では CPU より GPU の速度のほうが重要なので、グレードは高くなくても大丈夫。とはいえ Intel で言えば、Core i5 以上を選んでおきたいところです。
ARM 系(Apple M1/M2 含む)はもう何年かしたら主流に上がってくる可能性は秘めてますが、今はまだ苦労の元なのでやめましょう。

メインメモリは 16GB が最低ラインです。32GB あると嬉しい。予算が厳しいなら、CPU のグレードを下げてでもメインメモリを積んでください。
深層学習を主に使った研究でも、従来の機械学習の手法や線形代数の演算(SVD など)が前処理などに組み合わせて使われることも多く、それらは大量のメモリを消費することがあります。また深層学習においても、訓練データをオンメモリで保持できたら訓練時間を短縮できる可能性があります。
また後述の docker においても、メモリが多ければ多いほどコンテナをたくさん立てることができて嬉しいです。

OS

OS は Linux を割と強めにおすすめします。
tensorflow や pytorch といった深層学習のライブラリ自体は WindowsMac にも対応していますが、配布されているモデルを動かすには、それ以外のプログラムをビルド(プログラムのソースファイルから実行ファイルを作成する作業)する必要があって、それが Linux にしか対応していない(あるいは Windows/Mac で動かすのが面倒)ということが少なくありません。
また深層学習を使った研究における第一級の必需品と断言したい nvidia-docker2(後で説明) を動かすのは Linux が一番楽です。
Linuxディストリビューションは、CUDA が対応しているものならどれでもいいです。困ったときにググると情報が多く見つかるのは UbuntuCentOS*7 でしょう。

どうしても Windows がいいという場合、nvidia-docker2 を Windows で動かすのは大変なので、docker を使わずに深層学習するほうが楽でしょう。逆に言えば、docker を使わないなら Windows でもなんとかなります。nvidia-docker2 を使わないのはまあまあ大きなハンデになりますが。
どうしても Mac がいいという場合、以前なら Intel Mac + NVIDIA GPU という選択肢もありました。でも NVIDIAMac OS での CUDA サポートを 2021年に終了してしまいました……。「Mac でする苦労は大歓迎」という人以外は、M1 や M2 で安心して深層学習ができると定評が得られるまで待ったほうがいいでしょう。

docker

例えば tensorflow を使って深層学習の研究をするなら、調達したマシンに以下のパッケージをインストールする必要があります。

  • CUDA Toolkit と cuDNN の適したバージョン
  • python の適したバージョン
  • tensorflow の適したバージョン

この「適したバージョン」というのが厄介です。うまく動くバージョンの組み合わせが限られていて、適していない組み合わせだと簡単に動かなくなります。
深層学習の速すぎる発展に合わせてなのか、tensorflow や pytorch といった深層学習ライブラリでは、通常のソフトウェアのようなバージョン間の互換性が期待できません。今まで動いていたモデルの実装が、tensorflow をバージョンアップしたら動かなくなった、ということが普通に頻繁によくあります。特に tensorflow v1(バージョン1系) と v2(バージョン2系) は、名前変えた方がいいくらい別物です。

さて話は変わりますが、これから深層学習を使った研究を始めますという人が、最近の複雑なモデルを自力でいきなり実装することはやっぱり難しいです。幸い、機械学習の研究では github などで実装を共有するのが一般的であり*8、公開されているモデル実装を自身の研究に応用するというアプローチを多くの人が取るでしょう。

ここで先ほどのバージョン問題が立ちはだかります。モデルの実装は tensorflow (あるいは pytorch)の特定のバージョンでしか動きません。それに対応する python や CUDA/cuDNN のバージョンも限られます。
うまくいくモデルを探すためには、また論文にモデル性能比較を書くためには、深層学習の研究では複数のモデルを実験する必要もあります。それらが要求する複数バージョンの CUDA/tensorflow/pytorch を自分の実験環境に混在させようとすると、環境は簡単に壊れて、今まで動いていたモデルも動かなくなります……。

そこで是非使ってほしいのが docker と nvidia-docker2(NVIDIA Container Toolkit) です。
docker はコンテナと呼ばれる仮想環境を親マシン(ホストと言います)の中に簡単に作って、動かして、消すことができます。コンテナはホストから独立しており、コンテナ内での変更がホストや他のコンテナに影響を及ぼすには特別に許可する必要があります。nvidia-docker2 は、docker コンテナが GPU を使えるようにするためのランタイム(コンテナの実行機能)です。docker と nvidia-docker2 があれば、CUDA や tensorflow などのバージョンごとにコンテナを立ててしまえば、安全に環境混在できます。
以前はバージョン不整合を起こした CUDA 環境の復旧を年に数回とかしなくちゃいけなくて、とてもとても面倒でしたが、nvidia-docker2 を導入してからは一切なくなりました。天国です。

とはいえ、docker を導入するということは docker を勉強するコストがかかってしまいます。Linux に慣れていなかったら、さらに困難でしょう。docker 勉強のコストくらい上記のメリットだけで簡単にペイしてお釣りが来ると思いますが、未経験でそれを実感しろって言うのも無理な話……。
最初は普通に CUDA 環境構築しておいて、複数の環境が必要になる頃には Linux や深層学習にちょっとは慣れてくるでしょうから、そのときに docker のことを思い出してあげてください。

(番外編) GPU クラウド

GPU サーバをスポットで借りて使う GPU クラウドサービスという選択肢もあります。Google Colab が最もポピュラーで手軽ですね。他に、AmazonAWSマイクロソフトの Azure、GoogleGCP なども GPU を使えるクラウドサービスです。これらの GPU クラウドは、費用対効果を考えればとても安価に GPU を使った深層学習を行うことができます。
上述した通り、深層学習で一番ネックになる GPU のメモリ量についても、Google Colab の無料版でも 11GB 以上、月1000円の Google Colab Pro だと 16GB もあり(2022年現在)、頑張って導入した自分用の GPU サーバと匹敵するか、それ以上の容量があります。
では、自分用の GPU マシンなんか用意せずに、最初から GPU クラウドを使えばいいのでは? という発想もあるかもしれません。しかし深層学習を使った研究に挑戦するなら、最悪 GPU のメモリが少なくてもいいので、手元に1台マシンがあったほうが良いと思います。

実は深層学習は、うまく学習させることが結構難しいのです。機械学習用に整備されたデータセットではなく、自前のデータを比較的新しめの(つまり複雑な)深層学習のモデルに食わせて学習させたら、十中八九失敗します。なんなら賭けてもいいくらいです。
学習失敗には、学習の目安となるloss(損失)が全然下がらない、loss が下がらないどころか inf(無限大) や nan(非数値エラー) に飛ぶ、性能がランダム分類より悪いなど、いろいろパターンがあり、これを解決するには手探りで試行錯誤を繰り返すことになります。最も有効な解決策は、この非情報系向けシリーズの初回の記事に書いたように、機械学習に詳しい人にアドバイスをもらうことですが、まあそれはおいときましょう(苦笑)。

そうした試行錯誤を GPU クラウドでできるかというと、正直厳しいでしょう。Google Colab の無料版はセッションがいつ切れても文句言えませんし、内部で割り当てられたリソース使用量の上限*9を超えたらしばらく使えなくなります。AWS などの GPU クラウドは従量制で、失敗を繰り返す可能性の高い学習に使うのは強い抵抗を感じてしまうのではないでしょうか。
深層学習の試行錯誤は何度も繰り返す必要があるので、まずはデータを減らしたりモデルを小さくしたりして、1回の学習サイクルは短くするのが効果的です。そのため、メモリがちょっと少なくてもちゃんと役に立ってくれます。
ある程度うまく動くことを確信できるようになって、もう少しメモリがあったら……というとき、GPU クラウドの利用を検討してみてください。

参考リンク

nvidia-docker2(NVIDIA Container Toolkit) について最も参考になった NVIDIA 公式ブログの記事です。
短い記事ではないですが、nvidia-docker2 とは何か? インストールと使い方は? が端的に書かれています。足りない分は後からリファレンスで補うので事足ります。
medium.com

CUDA や tensorflow などのバージョン非互換性の話題に触れています。
ほかのソフトウェアではあまり問題にならないような機械学習特有の諸問題についても簡単にまとまっていますので、一読して警戒レベルを上げておくのもいいかも(苦笑)。
euske.github.io

 

 

蛇足の追記

この記事の本来のターゲットである「深層学習を使った研究を始めたいけど、周りも含めて機械学習の経験が少ない人」はここから先を読む必要は特にありません。

実験用 GPU 環境をどう準備したらいい?(非情報系が機械学習を使う研究をしたいとき) - 木曜不足

個人的には非情報系ならなおさらColab等の環境が良い気が。より安定するPro+(月$50)にしても自前でPC持つコスト(PC代、電気代、メンテ時間)考えるとペイするかな。NotebookをGitHubにpushして共有すればアドバイスももらいやすい

2022/07/26 13:26

9割同意ですが、そう書かなかった理由がいくつかあります。

GPU クラウドは高い」と言われる

Google Colab Pro は破格の安さ、めっちゃお得、むしろしない理由がない、くらい猛プッシュしてるんです。
ただ、それでも「月 1000円は高いですよね」と言われてしまう。
クラウドの安さを実感・納得してもらうには、理屈と正論は無力で、知識と経験が必要なんですよね。

また、従量制のクラウド料金は天井知らずなので、予算を立てづらいという問題もあります(研究室だけじゃなく企業でも)。
それを説得するにはやっぱり実績が物を言うわけで、ここにもニワトリタマゴ問題が。

PC 購入の予算なら研究室から出してもらえる

見えている範囲からの勝手な推測なので間違っているかもしれませんが、非情報系の研究室の方々も深層学習は大きな鍵になる予感を持っていて、でもノウハウが無いから自発的に声を上げた学生さんに期待を寄せているという雰囲気を感じます。
その期待の表れでしょうか、深層学習研究用の PC を購入する予算が、論文執筆用のノート PC レベルではなく、それなりの GPU を積んだゲーミング PC レベルでちゃんと支給されている、という認識。
つまり、PC を買うか買わないかという問題はすでにクリアして、何を買うかという問題に移っているわけです。

ただそこで RTX 3070(8GB) を買いました~、みたいになることも多くて、もちろん全然ダメじゃないけど、事前に相談してくれたら RTX 3060(12GB) を勧められたのになあ、というのがまさにこの記事を書いた動機です。

Google Colab 用の運用ノウハウが必要

Linux や docker は身につけたことが他でも役立つ、いわゆる「つぶしがきく」ので、そこでの苦労は報われるチャンスが多いです。
一方 Google Colab をホームグラウンドとして真面目に深層学習研究しようと思ったら、セッション切れ対策など Google Colab 固有のノウハウが必要になり、これは他の場所では基本的に役立ちません。
Google Colab は共有には手軽で便利ですが、初手の選択肢としては難点も多いかなあ、というのが個人的な感想。


冒頭に書いたように「あくまで私見」なので、他の意見にも耳を傾けることは全然いいことだと思います。

*1:CPU と GPU のメモリが共有で十分な容量があると、後述の GPU メモリ量の問題が大きく軽減されますし、推論コストの無視できない割合を占める CPU から GPU へのデータ転送もなくなるので、環境さえ整えば一番手に躍り出る可能性は十分あります。それにはまず cuDNN 相当品を Apple が作ってくれないと……。

*2:モデルやデータによって適したミニバッチサイズは違いますが、だいたい 16~100 くらいの数になると考えてください。それを、メモリエラーが出なくなるまで減らしたら、ミニバッチサイズが 2 になった、なんて悲しい出来事も本当によくあるんです……。

*3:ミニバッチサイズが大きいほどいい/小さいほどいい、という単純な話ではなく、収束の速さと性能のバランスを見た適切な値を選ぶ必要があります。

*4:予算が潤沢なら、マルチ GPU のための NVLink を備えているか等、メモリ以外にも必須要件が出てきます。

*5:RTX 3070 に 16GB 版が出る計画があったらしいのですが、キャンセルになったそうな。残念。

*6:厳密には違いはありますが、深層学習の研究をする上でその違いが問題になることはまずありません。

*7:CentOS はサポート終了しているので、今だと RHEL 互換の RockyLinux とかでしょうか。使ったことがないからわからない……。

*8:近年の機械学習の学会では、実装を共有して検証可能にすることが論文をアクセプトする条件になっていることも珍しくありません。

*9:Google Colab のリソース上限の正確な値はわかりません。

何から勉強始めたらいい?(非情報系が機械学習を使う研究をしたいとき)

以前、「非情報系が機械学習を使う研究をしたいとき」という記事を書きましたが、内容の半分はサイボウズ・ラボユースの宣伝だったんで、今回はタイトル詐欺じゃあないことも書きます。

いままで機械学習や深層学習に縁のなかった人が、それを使った研究を始めたいとなったとき、共通して直面する大きな課題は「何を優先的に勉強したらいいか」と「実験用の環境(PC)をどのように整えたらいいか」でしょう。
今回は何から勉強する? という話。
機械学習そのもの(特に自分が使おうとしているモデル)を学ぶのは必須に決まっているので、機械学習を使う上で必要となる前提知識を学ぶ優先順位について考えてみます。

機械学習(深層学習を含む)を使う上でキーになる前提知識は、数学(特に解析・線形代数・統計)とプログラミングを含む情報科学であることは意見の一致するところだと思います。
情報系の人なら、情報科学はさすがにやってます。プログラミングもやってるでしょう(始めたばかりかもしれませんが)。解析と線形代数はカリキュラムに入っていたはず。最近はデータサイエンス流行りで、統計も必修だったりするかも。
「テストが終わったら忘れた。まさか数学使う日が来るとは夢にも思わなかった」という人には厳しい日々が待ってます……。

非情報系の人だと、それらを修めている可能性はやっぱり高くはないですよね。「いわゆる文系」だと、解析・線形代数すら怪しい。それらを学び終わるまで待っていたら研究が始まりませんから、走りながら学ぶしかありません。
しかし自身の専門分野の勉強もある中で、数学と情報科学を学ぶ時間を作るのは大変ですし、モチベーションの維持も難しい。となると優先順位をつけるしかありません。
あくまで個人的な見解ですが、それらに学ぶ優先順位をつけるとすれば、「プログラミング > 情報科学 > 統計 > 線形代数・解析」だろうと考えています(先に書いたものほど優先順位が高い)。
これは、機械学習を使った研究をする上で、それを学ばなかった場合に生じるかもしれない支障の大きさの順で並べました。

プログラミング

今は深層学習系のライブラリがとても充実していて、数学の知識が足りなくても、モデルとその学習を実装するくらいならそこそこなんとかなります。
もちろん、数学を勉強する必要が全くなくなった、というわけではありません。モデルを理解し、データに合わせて改良するときなどは、数学の知識は間違いなく役立ちますし、自分が最終的に選んだ手法を(後付でも)理屈をつけて説明したいときに数学ほど頼りになるものはありません。

ただ、深層学習を使った研究をするときにネックになりやすいのは、設定(ハイパーパラメータやモデルの構造)を変えながら試行錯誤を繰り返す部分です。深層学習では同じモデルですら、ハイパーパラメータだけではなく初期値や学習順序の乱数で性能が大きく変わるので、様々な設定で実験を繰り返し、計測した性能を比較する必要があります。
これを手作業でやっていては実験できる回数が限られてしまい、最終的に到達できる性能が(本来届いたかもしれない性能よりも)低くなってしまいます。したがって、放っておいても自動的に実験を繰り返してくれるようにシステムを作り上げることがとても有効です。
もちろんそれを助けてくれるライブラリもありますが、それを使いこなして、自動的にモデルを組み替えて性能を計測してくれる仕組みを作るにはやっぱりある程度以上のプログラミング能力がないとうまくいきません。つまり、プログラミング能力が結果(論文に書く性能値)に直結するわけです。

また、非情報系の人がわざわざ機械学習に手を出すということは、機械学習用に整備されたデータセットを使うのではなく、おそらく独自のデータを使って研究するのでしょう。そういうデータを機械学習のモデルで扱えるように変換したり、ラベルをつけたりするだけで結構な手間がかかります。経験のない人がなんとなく想像するのより、10倍以上大変だと思っておいてください。
さらに、機械学習では性能を出すためにオーグメンテーション(データにランダムなノイズを乗せたり変換したりして訓練データを増やすこと)などの工夫を行うことが望ましいです。独自データで量が少なかったら、そのあたりの工夫がとりわけ重要になります。
これらを解決するのにも、とにかくプログラミング能力が要求されます。ほら、プログラミングが最優先な気分になってきたでしょう?

情報科学

情報科学のうち、プログラミングに関わりが深い範囲は同じくらいの必要度があると考えてます。例えば、基本的なアルゴリズムと計算量の考え方、疑似乱数、そしてできれば浮動小数点の精度と演算誤差についても押さえておきたいです。
アルゴリズムはデータの前処理の部分で必要になりがちですし、計算量は処理時間の見積もり(データやパラメータを増やしたときの変化)や、先行研究との速度比較において重要です。
機械学習の実験では、初期値や学習データの順番などさまざまなランダム性が用いられますが、ランダムな値を使っていると毎回結果が異なってしまい、実験結果を再現できないという問題があります。コンピュータが使っている通常の乱数は、実際は疑似乱数と呼ばれるもので、乱数の初期化に用いられる値(シードといいます)を固定することで、毎回同じ乱数列を生成してくれるようになります。

コンピュータで実数を表現するのは浮動小数点数というデータ形式です。これは任意の実数を扱えるわけではありません。「浮動小数点数で扱えない実数」が出てきたときは、誤差が生じたり、オーバーフロー(扱える範囲を超えた)というエラーになったりします。
というと、滅多にない事のように聞こえるかもしれませんが、実は残念なことに、浮動小数点数で表現できる実数は、実数全体からしたらほんのわずかでしかなく、誤差は常に付きまとう問題なのです*1
例えば 0.1 という、人間にはごく普通に見える値ですら、コンピュータ上では「0.1 に一番近い浮動小数点数」として表現されているに過ぎません。

In [1]: format(0.1, ".18g") # 0.1 を小数点以下18桁まで表示
Out[1]: '0.100000000000000006'

数学では 1-(1-x)=x は常に成り立ちますが、浮動小数点数の計算誤差のためコンピュータ上では必ずしも成り立ちません*2

In [2]: 1-(1-0.0001) # 0.0001 が出力されることを期待
Out[2]: 9.999999999998899e-05

こうした誤差を考慮して、機械学習でもよく使われるライブラリの numpy や torch には、isclose という「同じ値とみなせるほど十分近い」を判定する関数があり、イコールの代わりにそちらを使わなければならないケースも多いです。
また、機械学習では指数関数 exp が大活躍しますが、この関数はとてもオーバーフローしやすいです。GPU で主に使われる float32(32ビットの浮動小数点数) では exp(89) がオーバーフローして inf(無限大) になります。無限大よりはるかにはるかに小さいんですけどね(笑)。

浮動小数点数の計算誤差は普段はあまり気にしなくていいですが、たまにそれに起因する問題が生じます(長く機械学習の研究をしている人なら、少なからず経験してるはず)。そのとき、それを知ってるか知らないかで対処できるかどうかが変わってくるでしょう。

数学

数学はプログラミングたちより低い優先順位にしちゃいましたが、機械学習にとって数学は引き続き重要です。ただ、学習コストと即効性がね……。
上にも書いた通り、深層学習の流行と環境の整備のおかげで、「数学を避けたら研究できない」という状況でもなくなったという事情もあります。

ただそれでもやっぱり数学をやるしか! となったとき、機械学習に直接関係してくる数学トリオ(解析・線形代数・統計)の中で優先するものを1つ選ぶなら、統計です。
特に、母集団、標本、分布、各種統計量などの基本と、検定の初歩(統計で何がわかって、何がわからないか)あたりが重要です。ついでに大数の法則中心極限定理も是非押さえておきたい*3
統計の本で結構なページ数で紹介されていることの多い「いろんな種類の検定(z検定とかカイ二乗検定とかとか)」はひとまず不要です(仮に必要になったらその時学べばいい)。

というのも、(統計的)機械学習がやっていることは「与えられた標本から母集団の分布を推定する」ことにほかならないからです。
深層学習の時代になっても、いやむしろ、現実のデータをそのまま食わせる深層学習だからこそ、その統計の基本を理解していることは大きなアドバンテージになります。
即効性のある話に限っても、標準化やオーグメンテーションなどのデータ前処理では統計の知識が役立ちます。統計がわかっていれば、トンチンカンな標準化をしてしまう可能性を小さくできるでしょう。


最初にも書いた通り、この優先順位はあくまで個人的な意見です。機械学習(深層学習)をどのように使いたいかという個々の事情によって優先度が変わってくる可能性もあるでしょうから、もし身近に聞ける人がいるなら自分が何を優先したらいいか聞いてみるといいでしょう。
そういう人がいるなら、こんなブログ記事を読む必要もないんですけどね(笑)。

*1:浮動小数点数の問題ではなく、そもそもコンピュータは有限通りの値しか扱えないので、任意の実数など当然扱えません

*2:float32 で 1-(1-0.0001) を計算すると、もっと無視できない大きさの誤差になります。

*3:チェビシェフ不等式とイェンセン不等式も重要でしょ……みたいなツッコミも出るでしょうが、キリがないので、大数の法則中心極限定理で勘弁してあげてください。

非情報系が機械学習を使う研究をしたいとき

機械学習(深層学習・人工知能を含む)が使われる領域は、自然言語処理や画像処理といった機械学習の近隣とみなされる分野が従来のメインストリートでしたが、最近はそれ以外の分野の人からも機械学習を使った研究をしたいという声がよく聞かれるようになってきたと感じています。
サイボウズ・ラボでは、学生の開発・研究をサポートする「サイボウズ・ラボユース」という制度を10年続けています。メンターごとに募集テーマは異なり、「機械学習/自然言語処理に関するソフトウェア開発」では、その名前通り機械学習に関する応募を受け付けています。

labs.cybozu.co.jp

初期の応募者の動機は「自然言語処理やりたい」「画像処理やりたい」ばかりでしたが、そのうち応募の傾向が「機械学習を使った○○○○をやりたいが、ネットワーク(など機械学習と隣接していない情報系)の研究室なので機械学習に詳しい人が周りにいない」といったものに変わっていき、さらには非情報系の自然科学についても「機械学習を使いたい!」という応募をもらえるようになってきました。
昨年度のサイボウズ・ラボユースでも、「赤外線/電波望遠鏡の画像データから天体を検出」という天文学の研究に機械学習を応用するテーマをメンタリングしました(継続中)。

blog.cybozu.io

機械学習研究が広がっている背景には、深層学習以降の機械学習(AI)ブームがあるだろう、とは無難でありきたりな考察ですが、実際、機械学習でできることが劇的に広がり、(良くも悪くも)機械学習への期待が大きくなっているのを感じます。幸い、機械学習を始めるハードルは年々下がっています。入門本が数多く出版され*1、各種ライブラリもどんどん使いやすくなっています。
一方で、学習に使うデータは巨大化して扱いづらくなり、またモデルの推論に GPU などの高価な計算リソースが必要になってきているという面もありますが、AWSGoogle Colab などのクラウド環境を使えばリソースを持っていない人でも機械学習できます。また学習済みモデルのリポジトリの整備も驚くほどの勢いで進んでおり、巨大なデータで学習された性能の高いモデルを誰でも簡単に利用できるようになっています。

機械学習がそうやって広く研究されるのはとても嬉しいですが、その人の属する分野が機械学習から遠ければ遠いほど大変だろうなあと、ここ数年のラボユースの学生さんを見ていて実感します。
モデルを引っ張ってきてとりあえずあり物のデータで動かして見るだけなら結構誰でもできますが、機械学習用に整備されたデータセットではなく自分の手元のデータを食わせてみようと考えただけでいきなり壁が立ちはだかることも珍しくありません。データに適切な(と期待できる)前処理を施すだけでも大変ですし、だいたい必ずモデルの実装にも手を入れる必要も出てきます。
それを機械学習の知識も、モデルの知識も、フレームワークの知識も全部乏しい状態で試みるのはそりゃあ茨の道に決まってますよね。これが非情報系になると、さらに情報科学の知識やプログラミング経験も乏しくなりがち。
それだけの困難が見えているのに*2果敢に機械学習研究にチャレンジする学生さんがいたら、応援したくなりますよね。

解決策はとても簡単。機械学習に多少なりとも詳しい人を引っ張ってくることです!!

……と書くと完全に身もふたもないですが、まあ考えてみてください。
例えば、自分のデータを機械学習のモデルに食わせた実験結果を見て、「training loss が中途半端に下げ止まっているから、実装が間違っているか、モデルの表現力が足りないか、途中の層で特徴が潰れているか、訓練データにゴミが入っているか」と機械学習の立場から分析した上で、それらの切り分け方法を一緒に考えてくれる人がいるときと、いないときを想像して、いなかったときにそれを補う方法が果たしてあるだろうか、と。

「いっしょに研究してくれたら嬉しいけど、機械学習の人が全然関係ない分野の研究に協力してくれるんだろうか……」と心配になるでしょう。
それに対して、機械学習の代表どころか中の人と言うのもおこがましい立場の人間が何を言っても保証にならないかもしれませんが、実は機械学習は、本質的に他の分野とくっつかなければ研究ができません。というのも、機械学習の研究にはデータが絶対に必要ですが、そのデータを機械学習自身が生むことは基本的にないからです。
機械学習の代表例的な自然言語処理や画像処理も、そのデータの発生に機械学習は関係ありません。単に、機械学習人工知能から分かれて生まれたときからくっついているから、一体化しているようにみえるだけです。
だから機械学習界隈の人は、他の分野の話を聞くことをあまり嫌がりません。むしろ、データがどんな集められ方をして、どれくらいの規模があって、どういう特性があるのか、ついつい聞きたがります。共同研究に発展できるかどうかはさすがに時の運ですが……。
機械学習はその存在自体が学際的なんですね。

ただ、たとえそうだとしても、機械学習に詳しい外部の人と話す機会を作るのは難しいでしょう。研究室の先生にツテがあれば一番簡単ですが、もしそうならこんな記事読んで悩む必要が最初からないですね(苦笑)。IBIS(情報論的学習理論と機械学習研究会のワークショップ)のポスターセッションに出かけていって声かけまくる、なんてできたら尊敬しますが、それはさすがに強心臓すぎる……。
あとは機械学習人工知能をテーマにしたインターンに応募するという手もありますが、おそらく情報系の学生が優先されますし、自分の研究ができる可能性は低めです。(その他に良い手をコメントでもらえれば追記します)
そんな中、ハードル低めでチャレンジし甲斐があるのがラボユース応募ですね!(手前味噌!!)

labs.cybozu.co.jp

今回ラボユースで、天文学の学生さんの研究をメンタリングして、domain specific なデータそのもののおもしろさ、そのデータを機械学習のモデルと突き合わせて、お互いを調整するおもしろさにとても刺激的でした。
天文学に限らず、機械学習から遠い分野の研究からの応募をお待ちしています。もちろん普通の自然言語処理や画像処理もね!

*1:拙著もその1つです(笑)。

*2:困難さに気づいて無くて、無謀に当たって砕けてるのかも?(苦笑)

TextCNN の pytorch 実装 (IMDb 感情分析)

いきなりタイトルと話が違うが、DistilBERT で Sentiment Analysis を実装してみた。

これは accuracy=0.9344, f1 score=0.9347 くらいの性能を叩き出す(初期値などの具合で実行するたびに変わる。気になる人はシード指定して)。
きっとすごいんだけど、 これだけ見てるとどのくらいすごいかわからない。
そこでロジスティック回帰・ランダムフォレスト・ナイーブベイズSVM などの深層学習以前の分類モデルに加えて、 TextCNN [Kim2014] でも実装してみた。

TextCNN は畳み込みと max-pooling を組み合わせたテキスト分類器。発表時点では state-of-the-art だったんじゃあないかな?
モデルの詳細については解説ブログが結構あるので略。

枯れたモデルは sklearn で誰でもサクッと書けるが、TextCNN の今ちゃんと動く実装は意外とあんまり無い(tensorflow の古いバージョン用とかならある)。
今どきは BERT 系のモデルが圧倒的な性能を叩き出しており、その実装は、pretrained model の読み込みまで含めても Hugging Face のおかげでアホみたいに簡単なので、今更 TextCNN の出番は求められてないという話もある。
でもまあ、BERT との比較目的のように baseline としての役割ならまだ十分ある気がするので(ホント?)、ちゃんと動く TextCNN の実装を転がしておくのは有意義、ということにして以下に公開しておく。

TextCNN [Kim2014] の提案手法について、感情分析(2値分類)を以下のように実装している。*1

  • IMDb の映画レビューを元にした Large Movie Review Dataset を torchtext 経由で用いている。訓練/テストともに 25000件。
    • DistilBERT による実装では、IMDb のテキストから '<br/>' を除外しているが(accuracyが1ポイント向上)、torchtext だとその前処理をうまく入れられなくて、条件を同じにできていない……
  • term の符号化に static と non-static(符号化を初期値に使い勾配で更新する) を併用する Multi Channel。
    • ただし [Kim2014] は符号化に Word2Vec を提案しているが、この実装では GloVe を用いる(torchtext がサポートしてて楽なため)
  • 畳み込みのフィルタは各ウィンドウサイズ(3,4,5) ごとに 100個と論文では提案されているが、128個でも実験している
    • 初期値による揺れはあるが、たいてい 128個のほうが性能が高い。256個まで増やすと逆に性能は落ちる。
  • optimizer は AdamW を使用。5エポック学習しているが、最初の 1,2 エポック目でテストのスコアは最大となり、後は下がるだけ。
  • tokenizer に spacy の en_core_web_sm を利用。
    • torchtext のデフォルト tokeninzer である string.split (ただの空白区切り)と比べて、語彙数が 1/4(27万→7万) に減り、accuracy が 2ポイント向上する。

前処理で性能を上げるのはちょっとずるいかもしれないが、モデルのポテンシャルを見るという意味で汎用的かつ常識的な範囲内での前処理ということで許してもらおう(ダメならコードちょちょいと直してね)。

ここまでで記事の本題は終わっているが、せっかくなのでモデルの性能を比較しておこう。
比較するモデルは DistilBERT, TextCNN, Logistic Regression, SVM, Random Forests, Naive Bayes の6個。SVM はそのまま食わせたら終わらないので、 先に SVD で 1000次元に縮約している。その他パラメータは適当に良さげなものを選んでいる。
BERT-large などではなく DistilBERT を使ったのは、誰でも使えるリソース(Google Colab の無料ランタイム)で動かせる範囲に収めたかったから。

DistilBERT TextCNN LR SVM RF NB
accuracy 0.9289 0.8965 0.8846 0.8822 0.8501 0.8319
f1 score 0.9284 0.8940 0.8843 0.8793 0.8495 0.8197
precision 0.9351 0.9156 0.8867 0.9016 0.8530 0.8836
recall 0.9218 0.8734 0.8819 0.8581 0.8460 0.7645

当然というか、DistilBERT が頭一つ抜けている。
Browse State-of-the-Art の Sentiment Analysis on IMDb によれば、現在の最高性能(accuracy)は 0.974 とのことで、当然そのレベルには及ばないものの、わずかなコードを書くだけでこのくらいの性能を叩き出すことができるなんて、本当いい時代になったもんだ。

TextCNN は LR たちをちょっと上回るスコアであり、DistilBERT とは3ポイント以上の差がついている。上のページを見ると BERT や LSTM が成す一軍の最後尾にギリギリ滑り込めてはいるというレベル。
baseline としては、LR たちと BERT 系の間を埋めるものが欲しいところだが、そのためにはもうちょっと頑張って欲しいところ。TextCNN に attention を入れる提案をしているっぽい論文もあるので、そういうのを取り込むといいのかな。

*1:tqdm で訓練のプログレスバーを表示しているのだが、torchtext の前処理が tqdm のインスタンスを close せずに break かなにかしているようで、訓練時に tqdm のゴミが表示されるという問題の対処が一番大変だった。しかもまだ完全には抑えられていない……。

「ベイズ統計の理論と方法」の補題4(2)の反例?

タイトルは釣り。
ベイズ統計の理論と方法」(渡辺澄夫)を読んでいて、2章でちょっと困っている。

ベイズ統計の理論と方法

ベイズ統計の理論と方法

他の本には書かれていないようなことが注意書きにたっぷり書かれていたりして、普通に通読するだけでもおもしろいが、やっぱり紙と鉛筆でじっくり楽しむのが本筋かな。

「本書を読むのに必要な予備知識は、大学初年度に習う線形代数微分積分だけで十分」「(それに含まれない基礎数学が必要な)場所においては重要概念について初等的に理解できるように導入部分を加えている」(まえがき)「本書ではルベーグ測度論を仮定しない」(p203)など、厳密さよりも前提知識のハードルを下げることを優先していることが伺える。
それはもちろん良いことなので全然構わないのだが、「これは書かれてない仮定がありそうだな~」ということがちょいちょいあるのが読んでいて困る。

一番明確なところを例に上げると、注意12(p36-37)には、分子分母にパラメータ集合 W 上で定義された関数を含む数式を受けて「本書では W としてコンパクト集合を考えていくので、その場合にはこの式は分母、分子ともに有限の値を取る」*1とある。この記述からその関数はおそらく連続なのだろうと推測できるが、この本では関数が連続かどうか全く触れられておらず、暗黙の仮定となっている。
まあこのくらいわかりやすかったら書いてなくても忖度できるのでなんとかなるんだが*2、2章の補題4(2)はそれがわからなくて困っている。

まずできるだけ簡単に補題4(2)に必要な記号を導入する。
\mathbb{R}^N 上で定義された真の確率分布 q(x) をパラメータ w\in W\subset\mathbb{R}^d を持つ確率モデル p(x|w) で表現する統計的推測を考える。
任意の x に対し q(x)=p(x|w_0) となる w_0\in W が存在するとき、q(x)p(x|w)実現可能という。
q(x)p(x|w) で実現可能なとき、対数尤度比関数 f(x,w) を以下のように定義する。*3

 \displaystyle f(x,w)=\log\frac{q(x)}{p(x|w)}

関数 g(x)q(x) で平均したものを \mathbb{E}_q[g(x)]=\int q(x)g(x)dx と書く。*4
ある定数 c_0>0 が存在して、任意の w\in W に対して、\mathbb{E}_q[f(x,w)]\geq c_0\mathbb{E}_q[f(x,w)^2] が成り立つとき、対数尤度比関数が相対的に有限な分散を持つという。
ここで補題4(2) は次のような命題である。

補題4(2)
q(x)p(x|w) で実現可能であれば、f(x,w) は相対的に有限な分散を持つ。

本では q(x) にも p(x|w) にも何の仮定もない。
W にもなにもないが、実は先の注意12はこの補題4(2)の証明を f(x,w)\approx0 の近傍で考えればよいということを言っていた。よって、命題には書かれていないものの W にはコンパクト性が仮定されていると思われる。
この状態で本に書かれている補題4(2) の証明のアウトラインを追いかけて、「ここ何の仮定もないと成り立たないよね」という部分を拾っていくと次のような反例が構成できてしまった。



真の分布: q(x)=1 \; (0\leq x\leq1)、パラメータ空間: W=\{w|0\leq w\leq1\}、確率モデル: p(x|w)=\frac1{z(w)}\exp(-x^{-w}), \;z(w)=\int_0^1\exp(-x^{-w})dx とする。0<\exp(-x^{-w})\leq \exp(-1)=1/e より z(w)有界である。
p(x|w=0)=q(x) かつ最適なパラメータの集合は W_0=\{0\} なので、q(x)p(x|w) によりユニークに実現可能である。

この確率モデルの対数尤度比関数は f(x,w)=\log \frac{q(x)}{p(x|w)}=\log z(w)+x^{-w}
その期待値は \mathbb{E}_q[f(x,w)]=\log z(w)+\int_0^1x^{-w}dx=\log z(w)+\frac1{1-w} となり、w<1 にて有限値を持つ。
ところが対数尤度比関数の2乗の期待値は
\displaystyle \begin{eqnarray}
\mathbb{E}_q[f(x,w)^2]&=&\int_0^1\{\log z(w)+x^{-w}\}^2dx\\
&=&\{\log z(w)\}^2+\log z(w)\int_0^1x^{-w}dx+\int_0^1x^{-2w}dx
\end{eqnarray}
となる。この第1項・第2項は有限値だが、第3項 \int_0^1x^{-2w}dxw\geq1/2 にて +∞ に発散することから、この対数尤度比関数は相対的に有限な分散を持たない。


この「反例」の q(x),p(x|w) はともに連続かつ W もコンパクトなので、即座に忖度できる範囲の仮定では足りないことがわかる。
\mathbb{E}_q[f(x,w)], \mathbb{E}_q[f(x,w)^2] が有限 or 有界とか? 要求される仮定としては使いにくすぎるし、それを満たす確率モデルというのも直感的にわかりにくい。

本に証明として書かれているのは、F(t)=t+e^t-1 という関数を天下りに考え、

 \displaystyle q(x)F\left(\log\frac{q(x)}{p(x|w)}\right)=q(x)\log\frac{q(x)}{p(x|w)}+p(x|w)-q(x)

から両辺積分して

 \displaystyle \int q(x)F\left(\log\frac{q(x)}{p(x|w)}\right)dx=\int q(x)\log\frac{q(x)}{p(x|w)}dx=\mathbb{E}_q[f(x,w)]

が得られる(p(x|w)-q(x)積分して消える)。
一方、平均値の定理から(と本には書いてあるが、F(t) をテイラー展開したときに0次と1次がともに消えるので、平均値の定理から派生したテイラーの定理を2次剰余項=F(t)に適用、といったほうが一般にはわかりやすいだろう)、 F(t)=\frac{t^2}2\exp(-t^*), |t^*|\leq|t| を満たす t^* が存在し、 f(x,w)\approx0 の近傍で

 \displaystyle \int q(x)F\left(\log\frac{q(x)}{p(x|w)}\right)dx\approx\int q(x)\cdot\frac12\left(\log\frac{q(x)}{p(x|w)}\right)^2dx=\frac12\mathbb{E}_q[f(x,w)^2]

より相対的に有限な分散を持つと言える、というもの。

自力ではとても思いつく自信がないトリッキーで見事な「証明」だが、最後の最後で突然えらく雑な評価に落とし込まれている。t^* とやらはこのままでは t=f(x,w) に依存してるので、そんな簡単に積分から消せない。
そこをちゃんと評価するには、 f(x,w)\approx0 の近傍という条件を使って |t^*|\leq|f(x,w)|<\epsilont^* を定数で押さえることで \exp(-t^*)>\exp(-\epsilon)積分の外に出し、近傍の外は有限開被覆有界性で処理かな。
でもそれをするには、上の積分が w を動かしたときにちゃんと収束しないといけないので、{}^\forall w_1\in W に対して \lim_{w\rightarrow w_1} p(x|w)p(x|w_1) に一様収束するって仮定とかあれば行けそう? この仮定があれば上の反例も弾けるはず(\lim_{w\rightarrow0}p(x|w) が p(x|w=0) に各点収束しない)。
そんなに変な仮定ではないとは思うけど、もっと弱い仮定でも通るんだろうか。統計の理論畑は全然見てきていないので、一般的な仮定がどれくらいなのかを全然知らないんだよなあ。

【追記】
その後つらつら考えてみたら、f(x,w) が発散せずに定義できるために、常に q(x)>0, p(x|w)>0 の仮定もあったほうがいい気がしてきた。
上に書いた一様収束の条件は、\mathbb{E}_q[f(x,w)] たちが W 上で連続を言うために使う。これでどうだ?
【/追記】

*1:この記述も問題があって、「分子・分母ともに有界」でないとわざわざコンパクト性を要求した意味がないし、その後に続く記述にも有界性が必要だったりする。

*2:数学的構造うんぬんを言うなら、当たり前の仮定だからこそちゃんと書いてほしいという気持ちもなくもない。

*3:本ではまず実現可能でない場合について定義するが、補題4(2) の説明には不要なので省略。

*4:本では確率変数 X を使って \mathbb{E}_X と表記しているが、notation を節約するために q を使った。こっちのほうが紛れも少ないし。

Randomized Response のベイズ推論(3): 変分ベイズ

Randomized Response はアンケートの回答をランダム化することで、個人の回答は伏せつつ平均などの統計量を得る手法の1つ。
前回までの記事で、ランダム化された回答から真の割合を最尤推定ベイズ推定(ギブスサンプリング)で推定する方法とそれらの実験を紹介した。今回は同じベイズ推定でも変分ベイズによる推論を行ってみる。
Randomized Response のギブスサンプリングによる推論までは普通に論文が見つかるが、変分ベイズ推論は探した範囲では見つけられなかったので、頑張って導出してみる。

Collapsed Variational Bayes 推論

記法は前回と同様。
D 通りの選択肢を持つ質問 Q に対する回答を確率変数 X\in\{1,\cdots,D\} で表す。
回答者は真の回答 X を知られたくないので、 X をランダム化した Y\in\{1,\cdots,D\} を集計者に返す。
このとき以下のモデルを考える。

 \begin{cases}
P(Y=j|X=i)=p_{ij} & i,j=1,\cdots,D, \; \sum_j p_{ij}=1 \\
P(X=i)=\pi_i & i=1,\cdots,D \\
P(\boldsymbol{\pi}) = \rm{Dir}( \boldsymbol{\pi}| \boldsymbol{\alpha} )
\end{cases}

ランダム化の確率 P(Y=j|X=i)=p_{ij} からなる計画行列を \boldsymbol{P}=\left(p_{ij}\right) と記す。
また \boldsymbol{\pi}=(\pi_i) にはパラメータ \boldsymbol{\alpha}=\left( \alpha_i \right) を持つディリクレ共役事前分布を入れる。

ユーザ n の真の回答を X_n、それをランダム化したものを Y_n で表す。
集計者は N 人の回答者のランダム化された回答の観測値 Y_1,\cdots,Y_N から \mathbb{E}[X] などを推定する。

f:id:n_shuyo:20210119180208p:plain:w320

変分ベイズ推論は事後分布に独立性の仮定(変分近似)を入れて、最終的に \log q(X_n)\log q(\boldsymbol{\pi}) などを計算して更新式を導出する

 q(X_1,\cdots,X_N,\boldsymbol{\pi}) \approx q(\boldsymbol{\pi})\prod_{n=1}^Nq(X_n)

しかしせっかくなので、より高い性能を期待して LDA(Latent Dirichlet Allocation) でおなじみの Collapsed Variational Bayes を使ってみよう(Teh+2007, Asuncion+2009)。
Collapsed Variational Bayes ではまず変分近似を以下のように弱める。

  q(\boldsymbol{X},\boldsymbol{\pi}) 
= q(\boldsymbol{\pi}|\boldsymbol{X})q(\boldsymbol{X})
\approx q(\boldsymbol{\pi}|\boldsymbol{X})\prod_{n=1}^Nq(X_n)

ただしこれ以降 X_1,\cdots,X_N\boldsymbol{X}Y_1,\cdots,Y_N\boldsymbol{Y}X_n 以外の X_* 全体を \boldsymbol{X}^{-n}=\{X_1,\cdots,X_{n-1},X_{n+1},\cdots,X_N\} で表す。
この仮定に基づき変分自由エネルギー \mathcal{F}\left(q(\boldsymbol{X},\boldsymbol{\pi})\right) を以下のように展開する。

 \begin{eqnarray}
\mathcal{F}\left(q(\boldsymbol{X},\boldsymbol{\pi})\right)
&=& \mathbb{E}_{q(\boldsymbol{X},\boldsymbol{\pi})} [ -\log P(\boldsymbol{Y},\boldsymbol{X},\boldsymbol{\pi}) ] - \mathcal{H}(q(\boldsymbol{X},\boldsymbol{\pi})) \\
&=& \mathbb{E}_{q(\boldsymbol{X})} [ \mathbb{E}_{q(\boldsymbol{\pi}|\boldsymbol{X})} [ -\log P(\boldsymbol{Y},\boldsymbol{X},\boldsymbol{\pi}) ] - \mathcal{H}(q(\boldsymbol{\pi}|\boldsymbol{X})) ]
 - \mathcal{H}(q(\boldsymbol{X}))
\end{eqnarray}

ここで自由エネルギー \mathcal{F}\left(q(\boldsymbol{X},\boldsymbol{\pi})\right)q(\boldsymbol{X},\boldsymbol{\pi}) で最小化する代わりに、まず q(\boldsymbol{\pi}|\boldsymbol{X}) で最小化して、次に q(\boldsymbol{X}) で最小化する。q(\boldsymbol{\pi}|\boldsymbol{X}) で最小化したものを \mathcal{F}\left(q(\boldsymbol{X})\right) と書く。

 \begin{eqnarray}
\mathcal{F}\left(q(\boldsymbol{X})\right) 
&:=& \min_{q(\boldsymbol{\pi}|\boldsymbol{X})} \mathcal{F}\left(q(\boldsymbol{X},\boldsymbol{\pi})\right) \\
&=& \mathbb{E}_{q(\boldsymbol{X})} [  -\log P(\boldsymbol{Y},\boldsymbol{X}) ]
 - \mathcal{H}(q(\boldsymbol{X}))
\end{eqnarray}

この \mathcal{F}\left(q(\boldsymbol{X})\right) を、q(\boldsymbol{X})=\prod_{n=1}^Nq(X_n) に基づいて、各 q(X_n) で反復的に最小化するのが Collapsed Variational Bayes となる。
多項分布となる q(X_n) のパラメータを q(X_n=i)=\gamma_{ni} とし、c_k^{-n}=\sum_{m\neq n}I(X_m=k), \;\gamma_k^{-n}=\sum_{m\neq n} \gamma_{mk} とおくと(ただし I(\cdot) は指示関数)、その更新式は以下のように求められる。

 \begin{eqnarray}
\gamma_{ni} &\propto& \exp\left( \mathbb{E}_{q(\boldsymbol{X}^{-n})} [ \log P(\boldsymbol{Y},\boldsymbol{X}^{-n},X_n=i) ]\right)  \\
&\propto& \exp\left(  \log P(Y_n|X_n=i) + \mathbb{E}_{q(\boldsymbol{X}^{-n})} \left[ \log \int \pi_i \prod_k \pi_k^{\alpha_k+c_k^{-n}-1}d\boldsymbol{\pi}  \right]\right)  \\
&\propto& \exp\left(  \log p_{iY_n} + \mathbb{E}_{q(\boldsymbol{X}^{-n})} \left[ \log (\alpha_i+c_i^{-n})  \right]\right)  \\
&\approx& p_{iY_n}(\alpha_i+\gamma_i^{-n})
\end{eqnarray}

式の最後の近似は、LDA の CVB0 と同じくテイラー展開の 0 次の項のみに近似している。

実験

実装はこちら。

github.com

同じ反復法だが、データ件数 N でループする必要があるため、Gibbs Sampling より処理が10倍以上重い。そのため、推論の試行とサマリーを分離した上、試行の並列化までしている(苦笑)。
ここまでやっても、(N,\alpha) の組み合わせ 9通り×10000試行の推論には手元の環境で10時間かかった。

前回と同様に Randomized Response の計画行列 P(Y|X) は1個のパラメータ p で表現し、実験では p=1/5 を用いる。
 \displaystyle P(Y=j|X=i)=p_{ij}=\begin{cases}p + \frac1D(1-p)& (i=j) \\ \frac1D(1-p) & (i\neq j) \end{cases}

D=4 個の選択肢を持つ質問に対し、N=100,1000,10000 人のユーザが 1:2:3:4 の割合で回答(\boldsymbol{\pi}=(0.1\;0.2\;0.3\;0.4)^\top)p=1/5 の Randomized Response でランダム化された回答から推定した \hat\pi_1,\cdots,\hat\pi_4 の分布を見る。
事前分布のパラメータによる挙動の違いも見たいので、\alpha=1.0,\;0.1,\;0.01 を対称なディリクレ事前分布のパラメータとして用いる。

まずは手法ごとの違いを見るため、最尤推定、ギブスサンプリング、そして変分ベイズ(Collapsed Variational Bayes)に対して N=100,1000,10000 ごとの推定値の分布を見比べてみる。事前分布のパラメータは \alpha=1 のものを選んでいる。
今回は密度推定した線も書き加えてみたので、分布の形がよりわかりやすい。
 

N=100 N=1000 N=10000
MLE f:id:n_shuyo:20210204133030p:plain:w200 f:id:n_shuyo:20210204133034p:plain:w200 f:id:n_shuyo:20210204133037p:plain:w200
Gibbs f:id:n_shuyo:20210204130853p:plain:w200 f:id:n_shuyo:20210204130856p:plain:w200 f:id:n_shuyo:20210204130900p:plain:w200
VB f:id:n_shuyo:20210204130903p:plain:w200 f:id:n_shuyo:20210204130907p:plain:w200 f:id:n_shuyo:20210204130911p:plain:w200

最尤推定解は \pi_i によらず均等な散らばり具合で [0,1] からはみ出すが、ベイズ化によって \pi_i を確率値としてモデリングすると解消できることは前回までの記事で見たとおり。変分ベイズももちろんベイズモデルなので、同様に [0,1] に収まるように推定される。
上の図ではギブスサンプリングと変分ベイズの結果はそっくり同じに見えるが、実は縦軸・横軸の数値をよく見ると分かるように、変分ベイズの方が分散が小さい(山も高い)。
それがもっと明確に読み取れるように、\hat\pi_1\hat\pi_4 について箱ひげ図を書いて、特にそれらの散らばり具合の違いを見比べてみる。

N=100 N=1000 N=10000
f:id:n_shuyo:20210204152557p:plain:w200 f:id:n_shuyo:20210204152600p:plain:w200 f:id:n_shuyo:20210204152603p:plain:w200

すべての場合で 変分ベイズ(左) のほうがギブスサンプリング(右) より分散が小さい。横軸の下に書かれた推定値の標準偏差からも、グラフの見た目だけではなく実際に分散が小さくなっていることがわかる。これは推定値の精度が良くなっていることを意味している。*1

次にディリクレ事前分布のハイパーパラメータによる挙動の違いを見る。
N=10000 ではハイパーパラメータによる差異がほとんどない(データが多いと事前分布の影響が小さくなる)のは前回ギブスサンプリングでも見たとおりなので、N=100,1000 の図を掲載する。

N=100 α=0.01 α=0.1 α=1.0
Gibbs f:id:n_shuyo:20210204161937p:plain:w200 f:id:n_shuyo:20210204161941p:plain:w200 f:id:n_shuyo:20210204161944p:plain:w200
VB f:id:n_shuyo:20210204161959p:plain:w200 f:id:n_shuyo:20210204162002p:plain:w200 f:id:n_shuyo:20210204162005p:plain:w200
N=1000 α=0.01 α=0.1 α=1.0
Gibbs f:id:n_shuyo:20210204162023p:plain:w200 f:id:n_shuyo:20210204162026p:plain:w200 f:id:n_shuyo:20210204162030p:plain:w200
VB f:id:n_shuyo:20210204162047p:plain:w200 f:id:n_shuyo:20210204162051p:plain:w200 f:id:n_shuyo:20210204162054p:plain:w200

ギブスサンプリングでは \alpha を 1 より小さくすると事前分布の影響が強く出た推定になっていた。特に N=100,\alpha=0.01 の両端が上がった分布(つまり \hat\pi_i のどれか1つが 1 で残りが 0)が顕著だろう。*2
それに対し、変分ベイズではそうした極端さはほぼなく、直感に即した真値の周りの山形の分布となっている。

\alpha を小さくすることによる平滑化の低減(トレードオフとして分散の増加)を箱ひげ図で確認しよう。平均が理屈通りに振る舞うのは当然なので、今回は中央値に着目してみる。

α=0.01 α=0.1 α=1.0
N=100 f:id:n_shuyo:20210204165425p:plain:w200 f:id:n_shuyo:20210204165428p:plain:w200 f:id:n_shuyo:20210204152557p:plain:w200
N=1000 f:id:n_shuyo:20210204165531p:plain:w200 f:id:n_shuyo:20210204165534p:plain:w200 f:id:n_shuyo:20210204152600p:plain:w200

まず前回の記事で指摘しそびれていたこととして、ギブスサンプリングの \alpha<1 での極端な分布では中央値も真値から大きく離れていることが箱ひげ図から読み取れる。推定値の分散も非常に大きい。N=100,\alpha=0.01\hat\pi_4 では台が [0,1] しかないのに標準偏差が 0.430 と、一様分布(標準偏差 0.289)のほうがマシなレベル。
ギブスサンプリングで \alpha<1.0 のハイパーパラメータを使うのは難しそうだ。

変分ベイズでは、真値の周りの山型分布から想定できるとおり、中央値がちゃんと真値に近い値として推定できている。そして \alpha を小さくすると中央値はより真値に近づく。

このように変分ベイズ(Collapsed Variational Bayes)による推論は、最尤推定やギブスサンプリングより良い推定値が得られる。\alpha<1 でも直感的に良い推定値が得られる可能性が十分高く、欲しい精度に合わせてハイパーパラメータを厳選する甲斐がありそう。
実際の観測値に対する推定を行う場合、平均より中央値が外れてないことのほうが嬉しいので(確率 1/2 で中央値以上/以下になる)、その点でも変分ベイズによる推論は筋が良いとわかる。

同じ(真の事後)分布の推定を目的としているギブスサンプリングとの意外なほどの大きな差異が何によって生じているのかは興味深いところ。思い当たる要因は変分近似と CVB0(テーラー展開の0次近似)しかなく、どちらも原理的には精度を下げる効果しかないはずなのに、変分ベイズのほうが自明に良い結果を叩き出すのは、LDA-CVB0 の不思議な性能の良さと同じ根っこにつながっていそう(そう言えば CVB0 の理論的解釈とか誰かやってたような……)。

References

  • Teh, Yee W., David Newman, and Max Welling. A collapsed variational Bayesian inference algorithm for latent Dirichlet allocation. In Advances in Neural Information Processing Systems 19, 2007.
  • Asuncion, A., Welling, M., Smyth, P., and Teh, Y. W. On smoothing and inference for topic models. In Proceedings of the International Conference on Uncertainty in Artificial Intelligence, 2009.

*1:ギブスサンプリングはサンプルの平均を推定値とするが、その長さが小さいために散らばりが大きくなっているという可能性を確認するため、元のソースでは 200サンプルを burn-out して、続く 200サンプルの平均をとっていたのを 500サンプル burn-out & 500サンプル平均 にしてみたが、傾向は全く変わらなかった。

*2:それでいて \mathbb{E}[\hat\pi_i] はちゃんと真値に近くなるのがよくできているというかなんというか

Randomized Response のベイズ推論(2): ギブスサンプリング

Randomized Response はアンケートの回答をランダム化することで、個人の回答は伏せつつ平均などの統計量を得る手法の1つ。
前回記事では、回答の割合の推定量最尤推定で得る手順を紹介したが、割合の推定値が負になる可能性があることを示した。
shuyo.hatenablog.com
その問題を解消する方法の1つに、統計モデルをベイズ化するアプローチがある。今回はその中でも一番シンプルなギブスサンプリングによる推論を紹介する。

2値アンケートだとベイズ化の効果が見えにくいので、ベイズ化の前にまずは多値化する。
なお、i 番目の要素が a_i であるベクトルを \left(a_i\right)(i,j)要素が a_{ij} である行列を \left(a_{ij}\right) のように簡易的に表記している。

質問 Q に対する回答者の答えを確率変数 X で表す。
回答者は真の回答 X を知られたくないので、 X をランダム化した Y を集計者に返す。
集計者は N 人の回答者のランダム化された回答 Y_1,\cdots,Y_N から \mathbb{E}[X] などを推定する。

QD 通りの回答を持つとし、X, Y ともに 1,\cdots,D の値を取るとする。
X をランダム化した Y の確率を P(Y=j|X=i)=p_{ij} とし\left(\sum_j p_{ij}=1\right)p_{ij} を要素とする計画行列 \boldsymbol{P}=\left(p_{ij}\right) を考える。
X のモデルを P(X=i)=\pi_i としたとき、\boldsymbol{\pi}=\left(\pi_i\right) の推定値が欲しい回答の割合となる。

Y の観測値 \mathcal{D}=\left\{y_1,\cdots,y_N\right\} に対し、過程は省略するが、最尤推定\hat{\boldsymbol{\pi}} は以下のように求められる。ただし c_j=\left|\left\{ n | y_n=j \right\}\right|, \boldsymbol{c}=\left(c_j\right) とする。

\displaystyle \hat{\boldsymbol{\pi}}=\boldsymbol{P}^{-1}\boldsymbol{c}

計画行列 \boldsymbol{P}=\left(p_{ij}\right)D(D-1) 個のパラメータを持つが、自由度が多すぎてそのままでは使いにくい。
そこで真値が X=i であるとき、確率 pY=i、確率 1-p\{1,\cdots,D\} から一様乱数で選んだ値を Y とするランダム化メカニズムがよく採用される。
この場合、1つのパラメータ p に対し、計画行列は以下のように定められる。*1

\displaystyle p_{ij}=\begin{cases}p + \frac1D(1-p)& (i=j) \\ \frac1D(1-p) & (i\neq j) \end{cases} (式1)

最尤推定\hat{\boldsymbol{\pi}} は 0 から 1 の範囲に収まらないことがある。前回と同様にシミュレーションで確認しておこう。
実験に使ったコードはこちら:
github.com

4個の選択肢を持つ質問に対し、N(=100,1000,10000) 人のユーザが 1:2:3:4 の割合で回答するとき、ランダム化された回答から \hat{\boldsymbol{\pi}} を推定する。
p=1/5 の Randomized Response でランダム化された回答から推定した \hat\pi_1,\cdots,\hat\pi_4 をプロットした。

f:id:n_shuyo:20210119133717p:plain:w320f:id:n_shuyo:20210119133720p:plain:w320
f:id:n_shuyo:20210119133723p:plain:w320

N=10000 ではいい感じに推定できているが、N=100 では混然一体となってしまっており、\pi_i 間の大小さえうまく推定できるかどうかわからないことが読み取れる。
Randomized Response のパラメータ p を大きくすれば精度は改善できるので、集めたい N の大きさや最低限望む安全性などと相談して p を決める必要がある。

N=100 における各 \pi_i の平均・標準偏差、および 95%信頼区間も見ておこう。

\pi_i 0.1 0.2 0.3 0.4
\mathbb{E}[\hat\pi_i] 0.1 0.2 0.3 0.4
\rm{std}[\hat\pi_i] 0.21 0.21 0.22 0.22
95%区間 -0.3~0.5 -0.2~0.6 -0.1~0.75 0.0~0.85

これほど重なっていても、\mathbb{E}[\hat\pi_i]=\pi_i はちゃんと成立している。
分散(標準偏差)は i によらず一定だ。ここはベイズ版との比較ポイントになるので覚えておこう。
95%信頼区間は思いっきり負の領域にはみだしている。ベイズ化によってこれの改善を図る。

ベイズ版 Randomized Response

\boldsymbol{\pi} に共役事前分布としてディリクレ分布を入れて、その事後分布を推定する。

\displaystyle P(\boldsymbol{\pi}) = \rm{Dir}( \boldsymbol{\pi}| \boldsymbol{\alpha} ) = \frac{\Gamma\left(\sum_{i=1}^D\alpha_i\right)}{\prod_{i=1}^D\Gamma(\alpha_i)}\prod_{i=1}^D\pi_i^{\alpha_i-1}

\boldsymbol{\alpha}=\left( \alpha_i \right) はディリクレ事前分布のパラメータである。

事後分布の推定には方法はいくつかあるが、ここではまずギブスサンプリングを使った推論を導出しよう。
ユーザ n の真の回答を X_n、それをランダム化したものを Y_n という確率変数で表す。
また X_n 以外の X_* 全体を \boldsymbol{X}_{-n}=\{X_1,\cdots,X_{n-1},X_{n+1},\cdots,X_N\} で表す。

f:id:n_shuyo:20210119180208p:plain:w490
Randomized Response のグラフィカルモデル

このとき観測値 \mathcal{D}=\left\{Y_1=y_1,\cdots,Y_N=y_N\right\} に対する事後分布 P(\boldsymbol{\pi}|\mathcal{D}) をギブスサンプリングで求めるのに必要な、潜在変数 X_1, \cdots, X_N および \boldsymbol{\pi} の全条件付き事後分布を計算する。

\displaystyle \begin{eqnarray}
&& P(X_n=i|\boldsymbol{X}_{-n},\boldsymbol{\pi},\mathcal{D}) \\
&\propto& P(X_n=i,Y_n=y_n|\boldsymbol{\pi}) \\
&=& P(X_n=i|\boldsymbol{\pi}) P(Y_n=y_n|X_n=i) = \pi_i p_{iy_n}
\end{eqnarray} (式2)

\displaystyle \begin{eqnarray}
&& P(\boldsymbol{\pi}|X_1,\cdots,X_N,\mathcal{D}) \\
&\propto& P(\boldsymbol{\pi},X_1,\cdots,X_N,\mathcal{D}) \\
&=& P(\boldsymbol{\pi})\prod_{n=1}^N \left\{ P(X_n|\boldsymbol{\pi})P(Y_n|X_n) \right\}
\propto \prod_{i=1}^D \pi_i^{\alpha_i-1+d_i}
\end{eqnarray} (式3)

ただし d_iX_n の値が i である個数 d_i = \left|\{n|X_n=i\}\right| とする。
P(\boldsymbol{\pi}|X_1,\cdots,X_N,\mathcal{D}) はその式の形から  \rm{Dir}\left( \boldsymbol{\pi}| (\alpha_i+d_i) \right) とわかる。

X_1, \cdots, X_N\boldsymbol{\pi} の初期値を適当に決めて、(式2) と (式3) からのサンプリングを繰り返せばギブスサンプリングとなる。
(式2) は、X_n のサンプリングが他の \boldsymbol{X}_{-n} に依存しないことを表している。実際グラフィカルモデルを見ると、\boldsymbol{\pi} を縛れば X_1,\cdots,X_N は条件付き独立になる。
つまり X_1,\cdots,X_N 全体を一度にサンプリングするようなコードを書くことができる*2

実験に使ったコードはこちら:
github.com

実装ではサンプリングを400回繰り返し、最初の200回を捨てて、残る 200回のサンプル \boldsymbol{\pi} の系列の平均をとって割合の推定値としている(回数は適当w)。
事前分布のパラメータは {}^\forall \alpha_i=\alpha と対称とし、まずは \alpha=1 に対してデータの件数 N=100,1000,10000 に対して、Randomized Response の試行とギブスサンプリングによる推論を繰り返して得られた分布が以下のようになった。

f:id:n_shuyo:20210120155525p:plain:w240f:id:n_shuyo:20210120155534p:plain:w240f:id:n_shuyo:20210120155546p:plain:w240

まず N=10000 ではきれいな結果になっている。これは問題ないだろう。

N が小さくなると分散が大きくなり、最尤推定解が負になることがある問題を解決するためにベイズ化したわけだが、N=100 や 1000 のヒストグラムの横軸を見れば分かる通り、\hat\pi_1 などの分布もすべて 0 以上の範囲に収まっている。
わかりやすい N=1000 で見ると、\hat\pi_2,\hat\pi_3,\hat\pi_4 は左右対称な釣鐘型であるのに対し、\hat\pi_1 の分布は左の裾が 0 のところにストンと落ちる非対称な分布となっている。
N=100 になると分布の重なりは著しくなってしまうが、それでも分布の台が 0 から 1 の範囲に収まることはちゃんと守られている。

この通りベイズ化は期待通りの効果を発揮してくれた様子だが、別の問題も生じている。
N=100, \alpha=1.0 における推定値の平均 \mathbb{E}[\hat\pi_i] を見てみよう。

\pi_i 0.1 0.2 0.3 0.4
\mathbb{E}[\hat\pi_i] 0.18 0.22 0.27 0.32
\rm{std}[\hat\pi_i] 0.09 0.11 0.12 0.13
95%区間 0.06~0.41 0.07~0.48 0.09~0.54 0.11~0.59

分散が \pi_i ごとに異なり、また最尤推定解のそれより小さいなど嬉しいところもあるが、今見てほしいのは最尤推定では成立していた \mathbb{E}[\hat\pi_i]=\pi_i| が成立しなくなっている点である。
これはディリクレ事前分布によって正則化の効果が生じて、解が平準化されているためだ(0.1,0.2 の推定解は大きく、0.3,0.4 のは小さくなっている)。
N=10000 ではまたちゃんと \mathbb{E}[\hat\pi_i]=\pi_i| が成立しており、データが少ないと事前分布に引っ張られるという正しくベイズらしい振る舞いになっている。

では事前分布による正則化の効果を下げるために \alpha を小さくしてみたらどうだろう。N=100,1000,10000 それぞれについて事前分布のパラメータを \alpha=0.1 にしてみたときの推定解の分布が以下の図だ。

f:id:n_shuyo:20210120155522p:plain:w240f:id:n_shuyo:20210120155532p:plain:w240f:id:n_shuyo:20210120155542p:plain:w240

N=10000 では事前分布の違いを感じさせないが、N=100,1000 では \pi_1 が高い確率で 0 に縮退することがわかる。
N=100, \alpha=0.1 における推定値の平均や分散も確認する。

\pi_i 0.1 0.2 0.3 0.4
\mathbb{E}[\hat\pi_i] 0.13 0.19 0.28 0.4
\rm{std}[\hat\pi_i] 0.18 0.23 0.27 0.3
95%区間 0~0.69 0~0.81 0~0.89 0~0.93

\alpha を小さくしたことで、たしかに平準化の効果は押さえられ、\mathbb{E}[\hat\pi_i] は真の割合に近づいている。しかし 95% 区間を見れば分かる通り、一番大きい \pi_4 でさえも 0 に縮退する可能性が十分ある( PRML に書いてあった「関連度自動決定(ARD)」と同じ効果)。
0 に潰れる可能性があるということは、広い範囲の値を取りうるということで分散も大きくなっている。

このようにベイズ推論を用いる場合は事前分布をうまく決める必要がある、という至極当たり前な結論がでたところで*3、Randomized Response のグラフィカルモデルをもう一度よく見てみると、LDA の簡易版(ドキュメントが1個で、単語の生起確率が given)であることがわかる。
となるとせっかくだから LDA ではポピュラーな Collapsed Variational Bayes 推論も試してみたいよね! ということで次回記事に続く。

shuyo.hatenablog.com

Reference

  • Oh, Man-Suk. "Bayesian analysis of randomized response models: a Gibbs sampling approach." Journal of the Korean Statistical Society 23.2 (1994): 463-482.

*1:本稿では触れないが、local differential privacy における privacy budget が一定の範囲で推定値の分散が一番小さくなるのは(式1)の形で表現できることが示せる。

*2:\boldsymbol{X}_{-n} に依存しないので、観測データのループを書かなくていい。今回の Python 実装では、numpy.random.multinomial を選択肢の種類数である M 回呼ぶだけで済んでおり、データ数の N を増やしても実行時間は変わらない。

*3:今回はまだそこまで手を伸ばしてないが、経験ベイズ的アプローチでエビデンスが最大となるようにハイパーパラメータを決定してもおもしろい結果が出るかも?