数式をnumpyに落としこむコツ

47
数式を numpy に落としこむコツ ~機械学習を題材に~ 2011/10/15 中谷 秀洋@サイボウズ・ラボ @shuyo / id:n_shuyo

Upload: shuyo-nakatani

Post on 12-Nov-2014

20.189 views

Category:

Documents


3 download

DESCRIPTION

Tokyo.SciPy #2 にて発表した、数式(あるいは数式入りのアルゴリズム)から実装に落とす場合、何に気をつけるのか、どう考えればいいのか、というお話。 対象は、どうやって数式をプログラムすればいいかよくわからない人、ちょっとややこしい数式になると四苦八苦してしまい、コードに落とすのにすごく時間がかかってしまう人、など。 ここでは実行速度についてはひとまずおいといて、簡潔で間違いにくい、ちゃんと動くコードを書くことを目標にしています。

TRANSCRIPT

Page 1: 数式をnumpyに落としこむコツ

数式を numpy に落としこむコツ ~機械学習を題材に~

2011/10/15

中谷 秀洋@サイボウズ・ラボ

@shuyo / id:n_shuyo

Page 2: 数式をnumpyに落としこむコツ

「機械学習の手法を実装」って どうするの?

機械学習の手法いろいろ

数式! 数式! 数式!!!

numpy で実装

Page 3: 数式をnumpyに落としこむコツ

今回のターゲット

機械学習の手法いろいろ

数式! 数式! 数式!!!

numpy で実装

ここは対象外

ここをやっつけます

!!

Page 4: 数式をnumpyに落としこむコツ

「数式→実装」は共通

機械学習

数式! 数式! 数式!!!

numpy で実装

ここは共通

数値解析 統計処理

Page 5: 数式をnumpyに落としこむコツ

数式から実装まで

数式! 数式! 数式!!!

numpy で実装

数式見てすぐ実装? ムリムリ!

Page 6: 数式をnumpyに落としこむコツ

小さいステップに分解

数式! 数式! 数式!!!

numpy で実装

数式から 行間の情報を読み解く

「逐語訳」できる形に 数式を書き換える

今日のポイント

Page 7: 数式をnumpyに落としこむコツ

この後の流れ

1. 数式が降ってきた!

– 「式はどうやって出てきたか」は無視!

2. (必要なら)数式を読み解こう

3. (必要なら)数式を書き換えよう

4. 数式を「逐語訳」で実装しよう

Page 8: 数式をnumpyに落としこむコツ

「数式」と言っても いろいろある

Page 9: 数式をnumpyに落としこむコツ

対象とする「数式」

• 数式の例は「パターン認識と機械学習」

(以降 PRML)から引く

• 主に行列やその要素の掛け算が出てくる数式

– 掛け算は基本中の基本!

• コンピュータで実装したい数式は、行列を

使って表されているものも多い

– 機械学習は典型例の1つ、かな?

– 他の分野は……あまり知りません(苦笑

Page 10: 数式をnumpyに落としこむコツ

おことわり

• Python/numpyの基本機能は説明しません

– Python の文法とか

– 行列やベクトルの四則演算とか

• ラムダ式とリスト内包はちょろっと紹介

• 線形代数の基本的な知識も説明しません

– 四則演算とか、転置とか、逆行列とかとか

• 行列式や固有値なんかは出てこないので安心して

Page 11: 数式をnumpyに落としこむコツ

記法

• 数式

– ベクトルは太字の小文字

– 行列は太字の大文字

• コード

– import numpy は省略

– import numpy as np はしない

– numpy.matrix は使わず ndarray で

• 行列積と要素積が紛らわしくなるとかいろいろ嫌いw

ネームスペースを 省略するの嫌い~

C++ の using namespace も 使ったことないしw

Page 12: 数式をnumpyに落としこむコツ

書き換え不要なパターン

Page 13: 数式をnumpyに落としこむコツ

まずは一番簡単なパターンから

𝒘 = 𝚽𝑇𝚽 −1𝚽𝑇𝒕 (PRML 3.15 改)

• 線形回帰のパラメータ推定の式

– この式がどこから降ってきたかは気にしな

い!

Page 14: 数式をnumpyに落としこむコツ

ちなみに「線形回帰」って?

• 回帰:与えられた点を(だいたい)通る曲線

(関数)を見つけること

– 「回帰」って何が戻ってくるの? というの

は突っ込んではいけないお約束

• 線形回帰:∑𝒘𝑇𝝓(𝒙)という線形結合の形

の中で点を通るものを探す

– 線形の関数(つまり直線)を求めているわけで

はありません

一応紹介してみたけど、気にしなくていいですw

Page 15: 数式をnumpyに落としこむコツ

数式の「読み解き」

𝒘 = 𝚽𝑇𝚽 −1𝚽𝑇𝒕 (PRML 3.15 改)

• 𝚽:N×M次元の特徴行列

– 中身は気にしない

– N×M次元の行列が与えられているだけ!

• t:N次のベクトル(正解データ)

– 中身は気にしない(以下同様)

• w はベクトル? 行列? 何次の?

※特徴行列の作り方は後の「おまけ」で出てきます

Page 16: 数式をnumpyに落としこむコツ

掛け算した行列のサイズの求め方

𝒘 = 𝚽𝑇𝚽 −1𝚽𝑇𝒕

M×1 ← (M×N N×M) M×N N×1

隣接する行列の列数と行数は一致。 そうでなければ必ずどこか間違ってる

各行列のサイズ。 ベクトルは

1列の行列として

「数式がわからない」というとき この段階で間違っていることも少なくない

両端の行数・列数が 行列(ベクトル)のサイズ。 列数が1ならベクトル

Page 17: 数式をnumpyに落としこむコツ

numpy に「逐語訳」

𝒘 = 𝚽𝑇𝚽 −1𝚽𝑇𝒕 (PRML 3.15 改)

# PHI = N×M次元の特徴行列 # t = N次のベクトル(正解データ) w = numpy.linalg.solve(numpy.dot(PHI.T, PHI), numpy.dot(PHI.T, t))

※ 逆行列のところで inv() を使ってもいいですが、

solve() の方がコードが短いし、速度もかなり速いです

numpy.dot(PHI.T, PHI) numpy.dot(PHI.T, t)

𝑨−1𝒃 = numpy.linalg.solve(𝑨, 𝒃)

Page 18: 数式をnumpyに落としこむコツ

いつもこんなにかんたんとは 限りませんよね

Page 19: 数式をnumpyに落としこむコツ

書き換えが必要になるパターン

Page 20: 数式をnumpyに落としこむコツ

多クラスロジスティック回帰の 誤差関数の勾配

𝛻𝒘𝑘𝐸 𝑾 = 𝑦𝑛𝑘 − 𝑡𝑛𝑘 𝝓𝑛

𝑁

𝑛=1

(k = 1,⋯ , 𝐾)

(PRML 4.109 改)

• 𝒀 = 𝑦𝑛𝑘 : N×K 次行列(予測値)

• 𝑻 = 𝑡𝑛𝑘 : N×K 次行列(1-of-K 表現)

• 𝑾 = 𝒘1, … ,𝒘𝐾 = (𝑤𝑚𝑘) : M×K 次行列

• 𝚽 = 𝜙𝑛𝑚 = 𝝓1, ⋯ ,𝝓𝑁𝑇 : N×M 次行列

– 𝝓𝑛 = 𝝓 𝒙𝑛 = 𝜙𝑚 𝒙𝑛𝑇: M 次ベクトル

与えられている情報

Page 21: 数式をnumpyに落としこむコツ

「ロジスティック回帰」って?

Page 22: 数式をnumpyに落としこむコツ

「誤差関数」って?

Page 23: 数式をnumpyに落としこむコツ

「勾配」って?

Page 24: 数式をnumpyに落としこむコツ

式がどこから降ってきたかは 気にしない!

Page 25: 数式をnumpyに落としこむコツ

さすがに「勾配」は 必要なんじゃあないの?

𝛻𝒘𝑘𝐸 𝑾 = 𝑦𝑛𝑘 − 𝑡𝑛𝑘 𝝓𝑛

𝑁

𝑛=1

• 右辺は M 次ベクトル

– 𝑦𝑛𝑘 − 𝑡𝑛𝑘 はただのスカラー

– 一般には先ほどの方法で次元を読み解けばいい

• それが k=1,……,K 個あるだけ

– つまり求めるのは「M×K次元の行列」と読み解く

• ∴「勾配」は実装になんの関係もない!

これ

Page 26: 数式をnumpyに落としこむコツ

求めるものは 読み解けたが

Page 27: 数式をnumpyに落としこむコツ

どうすれば実装できるか まだよくわからない

Page 28: 数式をnumpyに落としこむコツ

「逐語訳」できる形に書き換える

• 掛けて行列になるパターンは大きく3通り

– 上から要素積、行列積、直積

𝑐𝑖𝑗 = 𝑎𝑖𝑗𝑏𝑖𝑗 ⇔ C = A * B

𝑐𝑖𝑗 = ∑ 𝑎𝑖𝑘𝑏𝑘𝑗𝑘 ⇔ C=numpy.dot(A, B)

𝑐𝑖𝑗 = 𝑎𝑖𝑏𝑗 ⇔ C=numpy.outer(a, b)

数式を左の形に書き換えれば、 右の numpy コードに「逐語訳」できる

※「外積」もあるが、使う人やシーンが限られるので略

Page 29: 数式をnumpyに落としこむコツ

式を書き換える (1)

𝛻𝒘𝑘𝐸 𝑾 = 𝑦𝑛𝑘 − 𝑡𝑛𝑘 𝝓𝑛

𝑁

𝑛=1

• 行列の要素の式になおす

𝛻𝐸 𝑾𝑚𝑘= 𝑦𝑛𝑘 − 𝑡𝑛𝑘 𝜙𝑛𝑚

𝑁

𝑛=1

(𝑚 = 1,⋯ ,𝑀; 𝑘 = 1,⋯ ,𝐾)

– 𝛻𝐸 𝑾 は「求める行列」としてひとかたまりで扱う

Page 30: 数式をnumpyに落としこむコツ

式を書き換える (2)

𝛻𝐸 𝑾𝑚𝑘= 𝑦𝑛𝑘 − 𝑡𝑛𝑘 𝜙𝑛𝑚

𝑁

𝑛=1

• 注:右辺の添え字に未解決のものは残らない

– 左辺に現れる : m, k

– 右辺で解決 : n (総和で消える)

• 3種類の積のどれかに帰着するよう変形

– この場合、総和があるので 𝑐𝑖𝑗 = ∑ 𝑎𝑖𝑘𝑏𝑘𝑗𝑘 に

Page 31: 数式をnumpyに落としこむコツ

式を書き換える (3)

𝑨 = 𝑎𝑛𝑘 = 𝑦𝑛𝑘 − 𝑡𝑛𝑘 とおくと(𝑁 × 𝐾 行列)

𝛻𝐸 𝑾𝑚𝑘= 𝑎𝑛𝑘𝜙𝑛𝑚

𝑁

𝑛=1

= Φ𝑇 𝑚𝑛 𝐴 𝑛𝑘

𝑁

𝑛=1

• 右辺を Σn○mn○nk の形に調整

– 左辺が○mk & 右辺は n で和を取っている

– 添え字の順序を逆にしたければ転置でOK

• 𝛻𝐸 𝑾 = 𝚽𝑇𝑨 であることがわかる

– 難しくて実装できなさそうだった式が かんたんに!

内側は 同じ添え字同士

Page 32: 数式をnumpyに落としこむコツ

numpyに「逐語訳」

• 𝑨 = 𝒀 − 𝑻, 𝛻𝐸 𝑾 = 𝚽𝑇𝑨 を実装

– うわあ、かんたんすぎ

• 元の数式と見比べてみよう

𝛻𝒘𝑘𝐸 𝑾 = 𝑦𝑛𝑘 − 𝑡𝑛𝑘 𝝓𝑛 (k = 1,⋯ , 𝐾)

𝑁

𝑛=1

# PHI = N×M 次元の特徴行列 # Y, T = N×K 次元の行列 gradient_E = numpy.dot(PHI.T, Y - T)

Page 33: 数式をnumpyに落としこむコツ

まとめ

• 数式から条件を読み解こう

– この段階で間違っていると、絶対うまく行かない

– さぼらず紙と鉛筆で確認するのが一番賢い

• 「逐語訳」できる数式なら実装かんたん

– 基本機能の呼び出しで完成!

– 難しい数式は「逐語訳」できる形に書き換え

– さぼらず紙と鉛筆(ry

Page 34: 数式をnumpyに落としこむコツ

(おまけ) 「リスト内包」を使いこなして楽しよう

Page 35: 数式をnumpyに落としこむコツ

特徴行列(先ほどの 𝚽)

𝚽 =

𝜙1 𝒙1 𝜙1 𝒙2 ⋯ 𝜙1 𝒙𝑁𝜙2 𝒙1 𝜙2 𝒙2 ⋯ 𝜙2 𝒙𝑁⋮ ⋮ ⋱ ⋮

𝜙𝑀 𝒙1 𝜙𝑀 𝒙2 ⋯ 𝜙𝑀 𝒙𝑁

• 関数 𝝓 𝒙 = 𝜙1 𝒙 ,⋯ , 𝜙𝑀 𝒙 と、

• データ 𝑿 = (𝒙1, ⋯ , 𝒙𝑁) から作る行列

– カーネル法のグラム行列も似たような作り

Page 36: 数式をnumpyに落としこむコツ

特徴行列の作り方 (1) # X = N×D 次元の行列(今回は D=1) phi = [ lambda x: 1, lambda x: x, # φ:特徴関数の列 lambda x: x ** 2, # lambda ってなに? lambda x: x ** 3 ] N = len(X) M = len(phi) PHI = numpy.zeros((N, M)) # Φ:N×M行列の入れ物を用意 for n in xrange(N): for m in xrange(M): PHI[n, m] = phi[m](X[n]) # φ_m(x_n)

Page 37: 数式をnumpyに落としこむコツ

‘lambda’ ってなに?

Page 38: 数式をnumpyに落としこむコツ

ぷちPython講座:ラムダ式

• lambda : その場で関数を作る

– def を書かなくていい

f = lambda x: x ** 3

def f(x): return x ** 3

だいたい同じ

※厳密には def と lambda はいろいろ違うわけだけど、

ここでは細かいことは気にしない

Page 39: 数式をnumpyに落としこむコツ

つまりラムダ式のところは

• 実はこの数式の実装でした

𝜙𝑚 𝑥 = 𝑥𝑚 (𝑚 = 0,⋯ ,𝑀 − 1)

• 繰り返しなんだから、もっとかんたんに

できそう

phi = [ lambda x: 1, # φ_0(x) = 1 lambda x: x, # φ_1(x) = x lambda x: x ** 2, # φ_2(x) = x^2 lambda x: x ** 3 # φ_3(x) = x^3 ]

Page 40: 数式をnumpyに落としこむコツ

ぷちPython講座:リスト内包

• リスト内包 : ルールから配列を作る

– for ループを書かなくていい

– R の apply() 系の関数に相当

a = [] for x in xrange(10): a.append(x * x)

a = [x * x for x in xrange(10)]

リスト内包なら簡潔!

※厳密にはいろいろ(ry

Page 41: 数式をnumpyに落としこむコツ

「リスト内包」を使えば……

• かんたんになったね!

phi = [ lambda x: 1, lambda x: x, lambda x: x ** 2, lambda x: x ** 3 ]

phi = [lambda x: x ** m for m in xrange(M)]

𝜙𝑚 𝑥 = 𝑥𝑚 (𝑚 = 0,⋯ ,𝑀 − 1)

こう書ける気がする

Page 42: 数式をnumpyに落としこむコツ

だめでした……

• 𝜙0 2 , 𝜙1 2 , 𝜙2 2 , 𝜙3 2 を表示してみる

– “1 2 4 8” と出力されることを期待

• ところがこれの実行結果は “8 8 8 8”

– って、全部同じ!? なんで???

M = 4 phi = [lambda x: x ** m for m in xrange(M)] print phi[0](2), phi[1](2), phi[2](2), phi[3](2)

Page 43: 数式をnumpyに落としこむコツ

うまくいかない理由は……

• 「レキシカルスコープ」がどうとか

– ちょっとややこしい

• 回避する裏技もあるけど……

– もっとややこしい

M = 4 phi = [lambda x, c=m: x ** c for m in xrange(M)] print phi[0](2), phi[1](2), phi[2](2), phi[3](2) # => “1 2 4 8” と表示される(ドヤ

Page 44: 数式をnumpyに落としこむコツ

結論

• リスト内包の中では lambda を使わない

ようにしよう!(ぇ

– これで同種の問題はだいたい避けられる

• かんたんに書く他の方法を考えてみる

Page 45: 数式をnumpyに落としこむコツ

特徴行列の作り方 (2)

• phi を「ベクトルを返す関数」として定義

– 𝜙𝑚のリストではなく,𝝓 = (𝜙𝑚)を扱う

– lambda を書かなくていい

– 関数の呼び出し回数も減って高速化

• 行列の生成にもリスト内包を使う

– numpy.array(リスト内包) は頻出!

def phi(x): return [x ** m for m in xrange(4)] PHI = numpy.array([phi(x) for x in X])

numpy の機能の一部と言っても いいくらい

Page 46: 数式をnumpyに落としこむコツ

まとめ

• リスト内包は超便利

– 憶えましょう

– 憶えてなかったら Python 使ってる意味ない

と言い切ってしまっていいくらい

• ラムダ式も便利

– でもリスト内包の中で使うとハマることがあ

るので避けましょう

Page 47: 数式をnumpyに落としこむコツ

よだん

• numpy.fromfunction() を使って特徴行

列を作る方法もあるよ。あるけど……

– なんかいろいろひどい

• take とか dtype=int とか

– ダメな numpy の見本

PHI = numpy.fromfunction( lambda n, m: X.take(n) ** m, (N, M), dtype=int)