何番煎じか分からないけど集合知プログラミングをPHPでやってみた その2「ピアソン相関によるスコアの算出・評者のランキング」

前回に引き続き、「今日も元気に集合知!」って事で、やっていきたいと思います。

今回は、ピアソン相関によるスコア算出のメソッドと、それを利用して評者のランキングを得るメソッドを見ていきます。

ピアソン相関によるスコア算出のメソッド

前回作成したRecommendationsクラスにメソッドを追加します。

<?php
class Recommendations
{
    /**
     * sim_peason
     *
     * #test 同じ人の相関値は1
     * <code>
     *  require('./setcritics.php');
     *  #eq(1.0, #f($critics, 'Lisa Rose', 'Lisa Rose'));
     * </code>
     *
     * #test 正常動作確認
     * <code>
     *  require('./setcritics.php');
     *  $d =0.000000000001;
     *  #eq(0.396059017191, #f($critics, 'Lisa Rose', 'Gene Seymour'), '', $d);
     * </code>
     *
     * @param mixed $prefs
     * @param mixed $person1
     * @param mixed $person2
     * @access public
     * @return float 相関値
     */
    function sim_peason ( $prefs, $person1, $person2 )
    {
        $si = array();
        foreach ( $prefs[$person1] as $item => $value ) {
            if ( array_key_exists($item, $prefs[$person2]) ) {
                $si[$item] = 1;
            }
        }

        if ( ( $n = count($si) ) === 0 ) {
            return 0.0;
        }

        $sum1 = 0.0;
        $sum2 = 0.0;
        $sum1Sq = 0.0;
        $sum2Sq = 0.0;
        $pSum = 0.0;

        foreach ( $si as $item => $value ) {
            $sum1 += $prefs[$person1][$item];
            $sum2 += $prefs[$person2][$item];
            $sum1Sq += pow($prefs[$person1][$item], 2);
            $sum2Sq += pow($prefs[$person2][$item], 2);
            $pSum += $prefs[$person1][$item] * $prefs[$person2][$item];
        }

        $num = $pSum - ($sum1 * $sum2 / $n);
        $den = sqrt(($sum1Sq - pow($sum1, 2) / $n)
                    * ($sum2Sq - pow($sum2, 2) / $n));
        if ( $den === 0.0 ) {
            return 0.0;
        }

        return $num / $den;
    }
}
?>

本の方のソースコードだと、メソッドの引数が"p1"と"p2"になっていたり、後半のループのカウンタが、"item"の意味だと思われるものが"it"に略されていたりしたんですが、「それは違わね?」って事で、それぞれ"$person1"・"$person2"・"$item"にしました。

本の方は、前回の分のソースコードではちゃんと"person1"とかになっていたのに、何で統一しなかったんですかねぇ…? まぁ、そこはあまり突っ込んでも仕方ないので、先に進みましょう。

テストも無事に通ったので、問題なく機能しているようですね。という事で続いて、評者のランキングを取得するメソッド作成に行きましょう。

評者のランキング取得メソッド

こちらもRecommendationsクラスにメソッドを追加しましょう。

<?php
class Recommendations
{
    /**
     * topMatches
     *
     * #test 正常動作確認
     * <code>
     *  require('./setcritics.php');
     *  $expected = array(
     *      array(0.99124070716192991, 'Lisa Rose'),
     *      array(0.92447345164190486, 'Mick LaSalle'),
     *      array(0.89340514744156474, 'Claudia Puig'),
     *      );
     *  #eq($expected,#f($critics, 'Toby', 3));
     * </code>
     *
     * @param mixed $prefs
     * @param mixed $person
     * @param int $n
     * @param string $similarity
     * @access public
     * @return array 相関値上位n人分のスコアと人名の組
     */
    function topMatches ( $prefs, $person, $n = 5, $similarity = 'sim_peason' )
    {
        $result = array();
        $scorelist = array();
        $personlist = array();
        foreach ( $prefs as $other => $value ) {
            if ( $other !== $person ) {
                $s = $this->$similarity($prefs, $person, $other);
                $p = $other;
                $scorelist[] = $s;
                $personlist[] = $p;
                $result[] = array($s, $p);
            }
        }
        array_multisort($scorelist, SORT_DESC, $personlist, SORT_ASC, $result);
        return array_slice($result, 0, $n);;
    }
}
?>

このメソッドは、4つ目の引数でランキングに使う関数を切り替えられるようになっていますね。こういう作りは汎用性があって良いですな。

こちらもテストが無事に通ったので、とりあえずよしとしましょう。

若干、説明

今まで何の説明も無く淡々とソースコードを載せているだけだったので、集合知プログラミングの本を読んでない人には、何のプログラムを作っているのかさっぱりだと思うので、ここらで若干説明なんぞを…。

元々、「推薦を行う」というタイトルの章で、オススメの物を返すメソッドを作るのが最終的な目標になっています。考え方としては、"自分と嗜好が似ている人が高い評価をしている物は、自分にとっても有益に違いない"という感じですね。

という事で、まずは評者同士がお互いにどのくらい嗜好が似ているのかを数値化する為のメソッドが、前回作成した"sim_distance"メソッドと、今回作成した"sim_peason"ですね。

それを使って、人物名を引数にして他の人との嗜好の類似度ランキングを返すのが、今回作成した"topMatches"メソッドです。

推薦システムって結構複雑そうなイメージがありますが、こうした簡単なアルゴリズムの組み合わせで、的確な推薦結果を出力するシステムが組みあがっているんだろうな、というのが想像できてなかなか興味深いですね。

という訳で、ざっくりとした説明を書いてみましたが、このシリーズ記事は集合知プログラミングの解説し直しをするつもりはないので、今後はあまり説明的なものは書かないと思います。

アルゴリズムの詳細などを知りたい方は、集合知プログラミングの本をお買い求め頂く事をオススメします。*1

次回は?

次回はいよいよ実際に推薦を行うメソッドに挑戦したいと思います。

*1:別にO'REILLYの回し者じゃないですよ。(笑)