「論理処理が必要と思ったことが確率処理でできるとわかった」のは AI だけだろうか

2004年ごろに Google の猫で深層学習が一躍脚光を浴びたとき、画像認識は特徴抽出が難しいので深層学習で良い結果が出るが、自然言語処理は特徴量*1がリッチなので、深層学習を適用するのは難しいだろうと思っていた。
特徴量がリッチとは、例えば「ホームラン」のたった1語でその文はスポーツ、特に野球の話題である可能性が高くなる、みたいな話である。一方、ピクセルの1つが緑であることから何の画像か当てるのは不可能だ。
その後、自然言語処理でも深層学習が当たり前になったのは誰もがご存知のとおりであり、自身の不明を恥じるばかりだ。ただ言い訳をさせてもらえるなら、自然言語処理のえらい先生方も同じように言っていたのだ。

2014年ごろ、LSTM などの深層学習モデルが発展し、自然言語処理でも目覚ましい結果が出始めた。機械翻訳など多くのタスクで、人間の平均といい勝負になったのもこの頃だったと思う。すると、人工知能はもうすぐ人間に追いつき追い越すのでは、という話になってくる。いわゆるシンギュラリティというやつである。
しかしこの頃の論調としては、仮にシンギュラリティが来るとしても、どんなに早くても10年でなんとかなる話ではなく、いいとこ30年くらい先の話じゃあないか、という人が多かったんじゃあないかと思う(一部の気の早い人を除く)。他ならぬ自分もそうだったし。

大脳に含まれるニューロン神経細胞)のネットワークを数理モデル化したものがニューラルネットワークであり、規模こそとても大きくなったものの、今ある深層学習も基本的に全てニューラルネットワークである。
ニューラルネットワークで記述できるのは確率モデルであり、人間がするような論理的な思考を記述することはできないだろう。そうである以上、まだ見ぬブレイクスルーを1つ以上経なければ、真の汎用人工知能を実現することはできないだろう。そう思っていた。
実際、神経細胞網はあくまで脳の一部でしかなく、脳の他の部分をエミュレートした何かが人工知能を実現するのに重要な要素になる、と考えてしまうのはむしろ自然だったわけだ。

そして 2022年末、ChatGPT が登場した。
自然言語処理の数あるタスクの中でも最高難度である対話を、多くの人を感心させるレベルでこなすだけでもすごいのに、同じモデルで文書分類、質問応答、翻訳、言い換え、文生成、要約なども全部まるごと、1つのモデルで実現していることに言葉を失う。

どれほど難しいと言われていたタスクでも、一度でも解けることを誰かに示されれば、次からは他の人も同じレベル以上でそのタスクを解き始めるものである(場合によっては他の方法を使って)。つい最近も、テキストからの画像生成でそれを見たばかりだ。
つまり ChatGPT レベルの AI が今後続々と登場することが予想できる。今は汎用の ChatGPT(とその眷属)しかないが、いろんな特徴を持った AI や、専門分野に特化した AI が登場するわけだ。今は遅かったり不安定だったりする AI のレスポンスも、きっと日常使いに耐えるレベルに向上するだろう。

これはもうシンギュラリティ来ちゃったって言ってもいいんじゃない? いや待て落ち着こう。現時点でシンギュラリティ来てる来てない議論をしても、どちらも決定的な決め手はなく不毛な展開しか見えないので、ひとまず置いておく。

nowokay.hatenablog.com

ChatGPTのヤバいところは、論理処理が必要だと思っていたことが、じつは多数のデータを学習させた確率処理で解決可能だと示したことだと思います。

これは本当にそのとおり。「脳の他の部分をエミュレートした何か」なんかいらんかったんや……というところから、もう一歩考えを進めてみる。

  • そもそも人間の「知能」と呼ばれているナニカは、脳で実現されていることはわかっているものの、どうやって実現しているかはわかっていない。
  • 脳の一部を模したニューラルネットワークという確率的なモデルで、かなり高い精度の「知能」のエミュレーションが可能であることが示された。

ここから素直に考えれば、今まで人間の脳が行ってきたとされる論理処理も、実は「論理的に行われているように見えていただけの確率処理」だったりしないだろうか。実際、「人間の論理処理」は穴があったり飛躍があったりとしばしば間違っており、確率処理だったとしてもなんの不思議もない。むしろ納得感さえある。

Large Language Model は世界の知識を獲得できることが示され共通認識となりつつあるが、同様に「知能のエミュレーション」も獲得できることが ChatGPT によって示された。そしてサールの中国語の部屋は、知能と「知能のエミュレーション」を区別できないことを表している(サールの言いたかったことは逆だが*2 )。

ja.wikipedia.org

このように、「知能は高度な確率モデルである」という仮説は俄然信憑性を増しつつあると思うのだが、哲学はこの問いにどう答えるんだろう。
今までに哲学を浅く狭く読んだ範囲だと、「知能は高度な確率モデル」仮説は今までの哲学を土台から揺らしてくるように思えるのだが、今のところ目立った反応は見えない。スルーなのか、気づいてないのか、単に自分の観測範囲が狭いのか……。
というわけで哲学が ChatGPT 的 AI にちゃんと向き合った言説があればぜひ読みたいので教えて欲しい。緩募。

*1:その頃の自然言語処理界隈では特徴量のことを「素性(そせい)」と言っていた。最近は深層学習でマルチモーダルが当たり前になったからか聞かなくなったなあ。

*2:サールの主張を認めるには「意識」の存在を仮定しないといけないが、それは全然トリビアルじゃないよね? という解釈。

CLIP を使った画像検索(VRC-LT #15)

VRC-LT という VRChat 上で LT 大会を行うイベントに、のこのこ参加させてもらって、CLIP で画像検索の簡単なサービスを書いてみた話をしてきました。
主催の @haru2036 さん、発表者&参加者の皆さん、ありがとうございました&お疲れ様でした~。

vrc-lt.org

このブログ、VR や AR の記事がちらほらありますが、VRChat はなんか怖そうと手を出してませんでした。*1
@haru2036 さんは実はすごーく昔にサイボウズ・ラボユースでメンターさせてもらったことがあって、その縁で VRC-LT に誘ってもらい、ようやく VRChat を始める踏ん切りが付きました(笑)。

発表資料はこちら。

www.slideshare.net

ただ、しゃべり有り前提の資料なので、VRC-LT の録画を見るほうがいいかもしれません(↓のリンク先の1時間23分ごろから)。


さて、CLIP を一言で説明すると、画像とテキストを同じ潜在空間(512次元)に埋め込むモデルです。
画像とテキストが似ていたら、CLIP によって似ているベクトルにエンコードされることが期待できます。
ググラビリティの低さから OpenAI CLIP と呼ばれることも多いですね。

CLIP の応用範囲は広く、最近話題の Stable Diffusion をはじめとしたテキストから画像を生成するモデルの多くにも CLIP が組み込まれていますし、画像にキャプションを付けるモデルにも CLIP が使われているものがあります。
中でも最もシンプルな CLIP の応用が、「似ている画像とテキストが似ているベクトルにエンコードされる」という特徴をそのまま使った画像検索になります。
あらかじめ画像を全部長さ1のベクトルにしておくことで、入力されたクエリーテキストをベクトルにエンコードし長さ1に正規化、画像ベクトルと内積をとると、長さ1同士なのでコサイン類似度になります。
あとは topk をとるだけで画像検索の完成です。

CLIP の学習は、大量の GPU と大量の画像・テキストペアが必要なので自分でやるのは正直しんどいですが、いろんな会社や研究所が学習済みモデルを公開しています。
特に AI bot 「りんな」で有名な rinna さんが日本語 CLIP モデルを商用利用可能なライセンスで公開してくれているので、ありがたく使わせてもらうことにしましょう。*2

rinna.co.jp
huggingface.co

CLIP による画像検索の実力を見てみましょう。実装についてはあとで紹介します。
「バス」で検索すると、バスが大きく写った画像がヒットします。

「バス」の検索結果

これくらいなら Google Photos の画像検索でもできそうですが、実際やってみると「確かにバスも写ってはいるけど……」みたいな写真がヒットすることも多いです。

物体検出ベースの検索の困るところ(VRC-LT の発表資料より)

Google Photos のような物体検出による画像検索は、基本的にその物体が画像に入っているかいないかだけを情報として取得して、それをもとにしたインデックスを検索しているので、こうした問題が生じます。つまり「バスの写真」ではなく「バスが写っている写真」を検索しているわけですね。
他にも、物体検出があらかじめ想定しているキーワード以外や、「青いバス」「寝ている猫」では検索できません。

さて、CLIP を使った潜在ベクトル検索はどうでしょう。

「青いバス」の検索結果
「おもちゃのバス」の検索結果
「渋滞に巻き込まれたバス」の検索結果

期待以上にいい結果が得られてホクホクしてしまいます。
他の検索結果例については、冒頭の資料 or 録画をごらんください。

この手の深層学習の「いい感じの結果」は厳選前提だったりすることがよくあります。この CLIP 検索は、まあもちろんダメな結果もあるはあるんですが、どちらかというと「おもしろい結果が簡単にたくさん得られてしまって、どれをスライドに採用するか悩む」というくらい、安定して良い検索結果が得られました。

VRC-LT の歓談中に「検索結果のスコアが 0.3 くらいと低い」という良い指摘がありました。これは 512次元のような高次元空間では低次元の常識が成り立たず、ランダムなベクトル同士のコサイン類似度は 0 の周辺に集中する分布を持つ(つまり、ほぼ直交する)という事情があります。つまりコサイン類似度 = 0.3 というのはその分布においては実はかなり高い値で、ベクトル同士もかなりよく似てくるんですね。

shuyo.hatenablog.com


さて最後に実装を載せて終わりましょう。普通なら github とかに載せるところでしょうが、コードの短さを見てもらうために記事にそのまま貼り付けることにします。
VRC-LT のプレゼンでは、スライド一枚にプログラムコードを収めて短さを強調していましたが、実際、web サーバ部分込みで 70行程度に収まっています。

画像は COCO と ImageNette というデータセットから適当に借りてきた約5万枚を使っています。
好きな画像を用意して、「# 画像を置いてあるパス」のところを適宜書き換えてください。
バリエーションの幅広い、十分な枚数の画像を用意しないと結果がおもしろくならないので、画像の用意が今回の話で一番難しいところでしょう(笑)。

import os, io, base64, glob, tqdm
from PIL import Image

import tornado.ioloop, tornado.web
import torch
import japanese_clip as ja_clip

device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = ja_clip.load("rinna/japanese-clip-vit-b-16", cache_dir="/tmp/japanese_clip", device=device)
tokenizer = ja_clip.load_tokenizer()

DATASETS = [ # 画像を置いてあるパス
    "/media/hdd/dataset/imagenette2-320/train/**/*.JPEG",
    "/media/hdd/dataset/imagenette2-320/test/**/*.JPEG",
    "/media/hdd/dataset/coco/val2017/*.jpg",
    "/media/hdd/dataset/coco/test2017/*.jpg",
]
imglist = []
for path in DATASETS:
    imglist.extend(glob.glob(path))

if os.path.exists("image_features.pt"):
    norm = torch.load("image_features.pt")
else:
    features = []
    for path in tqdm.tqdm(imglist):
        img = Image.open(path)
        image = preprocess(img).unsqueeze(0).to(device)
        with torch.no_grad():
            features.append(model.get_image_features(image))
    features = torch.cat(features)
    norm = features / torch.sqrt((features**2).sum(axis=1)).unsqueeze(1)
    torch.save(norm, "image_features.pt")

def read(path):
    with open(path, "rb") as f: return base64.b64encode(f.read()).decode("utf-8")

def search(query):
    encodings = ja_clip.tokenize(query, tokenizer=tokenizer)
    with torch.no_grad():
        text_features = model.get_text_features(**encodings)
    textnorm = text_features / torch.sqrt((text_features**2).sum())
    sim = norm.matmul(textnorm.squeeze(0))
    topk = torch.topk(sim, 5)
    return [{"image_base64":read(imglist[topk.indices[i]]), "score":topk.values[i].item()} for i in range(5)]

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        query = self.get_argument("query", "").strip()
        if query!="":
            topk = search(query)
        else:
            topk = []
        self.render("main.html", query=query, topk=topk)

if __name__ == "__main__":
    dir = os.path.dirname(__file__)
    app = tornado.web.Application([
        ("/", MainHandler),
        ],
        template_path=os.path.join(dir, "template"),
        static_path=os.path.join(dir, "static"),
        compiled_template_cache=False,
    )
    app.listen(8000)
    print("Listening...")
    tornado.ioloop.IOLoop.current().start()

あと2つのファイルを置くだけで動きます。
まず以下のような tornado の HTML テンプレートを template/main.html に置きます。
渡された画像リストを展開するだけの簡単なものです。

次に スタイルシートを static/bootstrap.css に置きます。bootstrap に対応させてますので、好きなスタイルを選んでください。
LT で見せた実装では bootswatch の slate を使いました。

<!DOCTYPE html>
<html lang="ja" style="height:100%">
<head>
<meta charset="utf-8">
<title>CLIP Search</title>
<link href="{{ static_url("bootstrap.css") }}" rel="stylesheet"/>
<style>
.card {
 --bs-card-spacer-y: .3rem; --bs-card-spacer-x: .3rem;
 float: left; width: 240px; height: 240px;
}
.card-text { max-width: 99%; max-height: 99%; }
</style>
</head>
<body style="height:100%">
<div id="main" style="height:100%; box-sizing:border-box; padding: 30px 0 70px">
<h1 style="position:absolute; top:0">CLIP で画像をテキスト検索!</h1>

<div id="chat-log" style="height:100%; overflow-y:auto">
{% for x in topk %}
  <div id="card-template">
    <div class="card text-white bg-primary mb-3">
      <div class="card-header">score: {{ f"{x['score']:.04f}" }}</div>
      <div class="card-body">
        <img class="card-text" src="data:image/jpeg;base64,{{ x['image_base64'] }}" />
      </div>
    </div>
  </div>
{% end %}
</div>

<div id="chat-input" class="form-group" style="position:absolute; bottom:0; width:100%; padding-bottom: 15px">
  <form id="prompt_form" class="d-flex" method="get">
    <input id="query" name="query" class="form-control me-sm-2" type="text" placeholder="Query" value="{{ query }}">
    <button id="send_btn" class="btn btn-secondary my-2 my-sm-0" type="submit">Send</button>
  </form>
</div>
</div>
</body>
</html>

*1:友達がいないから……という真の理由はヒミツです。

*2:CLIP の改良版に当たる CLOOB の学習済みモデルも同時に公開されていますが、今回は CLIP の実力を見たかったのでそちらを採用しました。

実験用 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

【追記】
GeForce RTX 4060 Ti 16GB が 2023年7月に ¥ 88,800(NVIDIA 公式ページより。税別?) で出るそうです。
実売 5万円前後の RTX3060 12GB と悩ましい価格差ですが、この 4GB の差で動かせる LLM が結構増えそうなので、予算が許すなら 4060Ti を狙いたいところですね。
【/追記】

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 を使った。こっちのほうが紛れも少ないし。