gihyo.jp の連載「機械学習 はじめよう」の第11回「線形回帰を実装してみよう」が公開されました

gihyo.jp の連載「機械学習 はじめよう」の第11回「線形回帰を実装してみよう」が公開されました。

機械学習 はじめよう 第11回「線形回帰を実装してみよう」
https://gihyo.jp/dev/serial/01/machine-learning/0011


今回は、第8回と9回で紹介した線形回帰をおなじみの Python / numpy / matplotlib で実装する内容となっています。
実践編を担当するのは今回が初めてなので、どういう書き方にしようかいろいろ迷ってしまいました。
ノリが以前と変わっているから調子が狂っちゃうかもしれませんが、ご容赦ください(笑)。


見せ方は変えてありますが、今回の連載記事の内容は、先日 Tokyo.SciPy #2 で発表した内容と一部被っています。

Tokyo.SciPy #2 でフルボッコされたコード*1はさらにパワーアップして、心ある numpy 使いなら目を背けるようなコードになっています(苦笑)。ほんとすいません。
恐らく「numpy を使う以上はループを極限まで使わないコードを書くべきだ。こんな numpy の流儀に従っていないコードをサンプルとして、しかも gihyo.jp のような影響力の大きいサイトに掲載するなど言語道断、けしからん!」と怒られるのでしょう(目に浮かびます)。

「見通しの良い、書き換えやすいコード」であることを優先したため、と書くとこの前ブログで書いたことの繰り返しでしかないので、もう少し具体的に。
例えばサンプルコードの phi() を(numpy で言うところの)「ユニバーサル」に書けば、numpy らしい、より高速なコードに近づきます。
しかし記事の中では、応用問題的に特徴関数 phi() を書き換えてみるススメなどもしています。そのとき phi() がユニバーサルであることを仮定するコードだったら、どうでしょう。正しく書き換えるには、numpy についての十分な知識が必要になってしまいます。
想定している読者は「機械学習を学んでみたい人」なので(言わずもがな?)、numpy をよく知っていることを前提にはできません。せめて間違った場合に「 phi をユニバーサルにしてください」のようなエラーメッセージが出てくれれば良かったのですが、実際に出るのは間接的で謎めいたエラー。numpy 初心者にはなにがなにやらさっぱり。

というわけで、今後も numpy コミュニティの方々の気に触るコードを生産し続けるかもしれませんが、あしからず……。
そうだ! gihyo.jp に numpy 講座の連載があればいいんだ! お客様の中にどなたかが numpy に詳しい方〜 (笑)

ここからおまけ。
記事の冒頭の線形回帰の復習*2の中で、連立方程式を整理すると行列の式になる、というくだりがありました。ちなみに「パターン認識機械学習」(PRML) では、式 (3.13) から (3.15) あたりに相当します(表記などは少し違っています)。

  • \frac{\partial E(\bf{w})}{\partial w_m}=\sum_{n=1}^N \phi_m(x_n)\left(\sum_{j=1}^M w_j\phi_j(x_n)-t_n\right)=0\hspace{2ex} (m=1,\;\cdots\;,M)   (式 A)
  • {\boldsymbol w}=({\boldsymbol\Phi}^T{\boldsymbol\Phi})^{-1}{\boldsymbol\Phi}^T{\boldsymbol t}   (式 B)

この「整理」の部分は記事の本筋ではないので省略しましたが、こういった計算を苦手にしている人もきっと多いでしょう。
教科書を読んでいる時なら、逆に (式B) から始めて、(式A) をひねくり出すという裏技(?)が使えるかもしれませんが、現実の問題では答えがあらかじめわかっているなあんてことはありません。

しかしこのような変形も、上の「数式」の中で紹介したテクニックを使えばかんたんに(つまり、ひらめきや職人技がなくても)できちゃいます。



要は (式A) をこの形に合うように変形していくだけです。
といっても、この中で (式A) に合うのは2番目の行列積しかありません。
Φ=(φ_nm), φ_nm=φ_m(x_n) に書き換えつつ、Σの中の引き算をバラして、それぞれ「真ん中の添え字」が組みになるように並び替えていきます。添え字の順番を変えるのは転置を使います。

  • \sum_{n=1}^N \phi_{nm}\left(\sum_{j=1}^M w_j\phi_{nj}\right)-\sum_{n=1}^N \phi_{nm}t_n=0
  • \sum_{n=1}^N \phi_{nm}\left(\sum_{j=1}^M \phi_{nj}w_j\right)-\sum_{n=1}^N (\Phi^T)_{mn}t_n=0

ここまででまず上の行列積の対応から \sum_{j=1}^M \phi_{nj}w_j=(\Phi{\boldsymbol w})_n, (\Phi^T)_{mn}t_n=(\Phi^T{\boldsymbol t})_m とわかります。

  • \sum_{n=1}^N \phi_{nm}(\Phi{\boldsymbol w})_n-(\Phi^T{\boldsymbol t})_m=0
  • \sum_{n=1}^N (\Phi^T)_{mn}(\Phi{\boldsymbol w})_n-(\Phi^T{\boldsymbol t})_m=0

おなじく \sum_{n=1}^N (\Phi^T)_{mn}(\Phi{\boldsymbol w})_n=(\Phi^T\Phi{\boldsymbol w})_m となりました。というわけで、

  • (\Phi^T\Phi{\boldsymbol w})_m-(\Phi^T{\boldsymbol t})_m=0\hspace{3ex} (m=1,\;\cdots\;,M)
  • \Phi^T\Phi{\boldsymbol w}-\Phi^T{\boldsymbol t}={\boldsymbol 0}

出ましたね。慣れるともっとさくさく出来るようになりますよ。

*1:でも負けませんでしたけどね!(えっへん)

*2:といいつつ、初出の式がちらほらw