数式を綺麗にプログラミングするコツ #spro2013

26
数式を綺麗に プログラミングするコツ 夏のプログラミングシンポジウム 2013 2013/8/25 中谷 秀洋@サイボウズ・ラボ / @shuyo

Upload: shuyo-nakatani

Post on 12-Nov-2014

28.492 views

Category:

Technology


2 download

DESCRIPTION

 

TRANSCRIPT

Page 1: 数式を綺麗にプログラミングするコツ #spro2013

数式を綺麗に プログラミングするコツ

夏のプログラミングシンポジウム 2013

2013/8/25

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

Page 2: 数式を綺麗にプログラミングするコツ #spro2013

今回のおはなし

Page 3: 数式を綺麗にプログラミングするコツ #spro2013

「ビッグデータの手法を実装」って どうするの?

手法いろいろ

数式! 数式!! 数式!!!

実装

Page 4: 数式を綺麗にプログラミングするコツ #spro2013

「数式→実装」は共通

機械学習

数式! 数式!! 数式!!!

実装

ここは共通

数値解析 統計処理

Page 5: 数式を綺麗にプログラミングするコツ #spro2013

数式から実装まで

数式! 数式!! 数式!!!

実装

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

Page 6: 数式を綺麗にプログラミングするコツ #spro2013

小さいステップに分解

数式! 数式!! 数式!!!

実装

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

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

今日のポイント

Page 7: 数式を綺麗にプログラミングするコツ #spro2013

この後の流れ

1. 数式がそこにあった

– 「式はどうやって出てきたか」は考慮しない

2. 数式を読み解く

3. 数式を書き換える

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

Page 8: 数式を綺麗にプログラミングするコツ #spro2013

対象とする「数式」

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

– 機械学習などの手法には、行列を使って表さ

れているものが多い

– 強力な線形代数ライブラリをうまく使えば楽

に実装できる

• 数式の例はC.M.ビショップ「パターン認

識と機械学習」(以降 PRML)から採用

– ただし機械学習の知識は一切要求しない

Page 9: 数式を綺麗にプログラミングするコツ #spro2013

方針

• 「楽に」「確実に」実装しよう

– 間違いにくく、可読性が高い

– 最速は必ずしも目指していない

• 動くものを確かに作れるようになってから

• Python/numpy と R での実装例を紹介

– 基本的な行列計算しか使わないので、その他

の環境(Eigen など)にも参考になる(はず)

Page 10: 数式を綺麗にプログラミングするコツ #spro2013

書き換え不要なパターン

Page 11: 数式を綺麗にプログラミングするコツ #spro2013

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

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

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

– 「線形回帰とは何か」などは一切気にせず、

この式を実装することのみ考える

Page 12: 数式を綺麗にプログラミングするコツ #spro2013

数式の「読み解き」

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

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

– 「特徴行列とは?」は気にしない

– 「N×M次元の行列」ということだけ

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

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

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

Page 13: 数式を綺麗にプログラミングするコツ #spro2013

掛け算した行列(ベクトル)のサイズ

𝒘 = 𝚽𝑇𝚽 −1𝚽𝑇𝒕

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

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

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

1列の行列として

この段階で勘違いしていると実装できないので 丁寧に確認しておく

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

Page 14: 数式を綺麗にプログラミングするコツ #spro2013

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 15: 数式を綺麗にプログラミングするコツ #spro2013

R に「逐語訳」

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

# PHI = N×M次元の特徴行列 # T = N次のベクトル(正解データ) w <- solve(t(PHI) %*% PHI, t(PHI) %*% T) # crossprod(X) = t(X) %*% X 関数を使っても良い w <- solve(crossprod(PHI), crossprod(PHI, T))

t(PHI) %*% PHI t(PHI) %*% T

𝑨−1𝒃 = solve(𝑨, 𝒃)

Page 16: 数式を綺麗にプログラミングするコツ #spro2013

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

Page 17: 数式を綺麗にプログラミングするコツ #spro2013

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

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

𝑁

𝑛=1

(k = 1,⋯ , 𝐾)

(PRML 4.109 改)

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

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

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

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

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

与えられている情報

実装に関係するのはサイズだけ

Page 18: 数式を綺麗にプログラミングするコツ #spro2013

「勾配」の扱い

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

𝑁

𝑛=1

• 右辺は M 次ベクトル

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

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

• だから左辺 𝛻𝒘𝑘𝐸 𝑾 も M 次ベクトル

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

• 「M×K次元の行列 𝛻𝐸 𝑾 」を求めると読み解く

– 「勾配」そのものは実装には関係ない

これ

Page 19: 数式を綺麗にプログラミングするコツ #spro2013

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

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

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

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

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

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

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

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

Page 20: 数式を綺麗にプログラミングするコツ #spro2013

R 版「逐語訳」

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

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

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

𝑐𝑖𝑗 = 𝑎𝑖𝑘𝑏𝑘𝑗𝑘 ⇔ C <- A %*% B

𝑐𝑖𝑗 = 𝑎𝑖𝑏𝑗 ⇔ C <- outer(a, b)

※直積は outer(a, b) の他に a %o% b という書き方もある

Page 21: 数式を綺麗にプログラミングするコツ #spro2013

式を書き換える (1)

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

𝑁

𝑛=1

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

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

𝑁

𝑛=1

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

– 「求める行列𝛻𝐸 𝑾 」の (m, k) 成分の式にする

M次ベクトルの式

スカラー

Page 22: 数式を綺麗にプログラミングするコツ #spro2013

式を書き換える (2)

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

𝑁

𝑛=1

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

– 左辺に現れる : m, k

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

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

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

Page 23: 数式を綺麗にプログラミングするコツ #spro2013

式を書き換える (3)

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

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

𝑁

𝑛=1

= Φ𝑇 𝑚𝑛 𝐴 𝑛𝑘

𝑁

𝑛=1

• ○mk=Σn○mn○nk の形に調整

– 右辺の内側の添え字とΣは同じ n

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

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

Page 24: 数式を綺麗にプログラミングするコツ #spro2013

numpyに「逐語訳」

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

𝑁

𝑛=1

𝑨 = 𝒀 − 𝑻, 𝛻𝐸 𝑾 = 𝚽𝑇𝑨

• ここまで簡単になれば、実装は一瞬

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

式の書き換え

Page 25: 数式を綺麗にプログラミングするコツ #spro2013

R に「逐語訳」

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

𝑁

𝑛=1

𝑨 = 𝒀 − 𝑻, 𝛻𝐸 𝑾 = 𝚽𝑇𝑨

• 同様に R での実装例:

# PHI = N×M 次元の特徴行列 # Y, T = N×K 次元の行列 gradient_E <- t(PHI) %*% (Y - T)

Page 26: 数式を綺麗にプログラミングするコツ #spro2013

まとめ

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

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

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

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

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

– さぼらず紙と鉛筆