読者です 読者をやめる 読者になる 読者になる

レコメンドエンジンを作りたい part2

というわけで、計算ロジックをPHPにて実装してみた。

/**
 * 座標格納クラス
 */
class Coordinate
{
    /** X座標 */
    public $x;
    /** Y座標 */
    public $y;

    function __construct()
    {
        $this->x = 0.0;
        $this->y = 0.0;
    }
}

/**
 * 座標を計算する
 */
class CalcCoordinate
{
    /** カテゴリマスタ */
    private $category;

    /** 重みづけテーブル */
    private $weight_table;

    /**
     * 座標計算クラスを初期化する。
     * 引数は以下の様な配列を渡すこと。
     * $category = array("book", "food", "suply", "adults", "kids");
     */
    function __construct($category)
    {
        $this->category = $category;
        $this->weight_table = array();

        $degress = 360 / count($this->category);
        $weight = 0;

        foreach($this->category as $name)
        {
            $this->weight_table[$name] = $weight;
            $weight = $weight + $degress;
        }
    }

    /**
     * カテゴリ別のデータを渡すことで、ユーザー座標を計算して返す。
     * 座標の範囲は -100 ~ 100。
     * 引数は以下の様な配列を渡すこと。なお、コンストラクタに渡したカテゴリと項目名が違う値は無視される。
     * $data = array (
     *       "book"=>92,
     *       "food"=>38,
     *       "suply"=>67,
     *       "adults"=>55,
     *       "kids"=>44,
     * );
     */
    public function get_coordinate($data)
    {
        $total = 0.0;
        foreach($data as $ammount)
        {
            $total += $ammount;
        }

        $coordinate = new Coordinate();
        foreach($data as $key=>$value)
        {
            if (array_key_exists($key, $this->weight_table) === TRUE)
            {
                $distance = ($value / $total) * 100.0;
                $coordinate = $this->advance($coordinate, $this->weight_table[$key], $distance);
            }
        }
        return $coordinate;
    }

    /**
     * 任意の方向に前進させる
     */
    private function advance($coordinate, $degress, $value)
    {
        $coordinate->x = $coordinate->x + cos($degress * M_PI / 180) * $value;
        $coordinate->y = $coordinate->y + sin($degress * M_PI / 180) * $value;
        return $coordinate;
    }
}

前回の記事では、カテゴリを4つに固定していたが、それでは何ともいやはや。ということで、実装では複数カテゴリに対応している。

その為に、任意の方向(角度)に座標を進める必要があった。それが上記ソースコードのadvance関数である。

これがなかなか良い感じに動くので、デモプログラムを作って動作を確認してみた。

デモプログラムはSkyDriveの共有フォルダに置いておきました。

座標が分かると、近くにいるユーザーが分かります。そして近くにいるユーザーほど、関連度が高いと判断するわけです。

f:id:omiya6048:20130613221158p:plain

上表は、「井上 運吉」に近いユーザーの検索結果です。この結果の上位三名をレーダーチャートにして、視覚的に本当に似たユーザーが取れているのか確認したいと思います。

(あんまり人数が多いとレーダーチャートが見づらいので、上位三名のみにします。)

f:id:omiya6048:20130613222329p:plain

結果はこのような感じ。黒い線が井上 ウンキチ(サダヨシ?)さんです。

井上さんは、小説と絵本が好きな空想好きと言ったところでしょうか。ノンフィクションや実用書はあまり読まないタイプのようです。

一方、関連度一位の藤井 彩さんは、哲学と論文が好きな学者肌の人のようですね。井上さんとは全然違うタイプのようです。

二位の谷さんはどうでしょうか。エッセイ、生活、絵本などをよく読んでいるようなので、恐らく幼いお子さんがいるお母さんではないでしょうか。

三位の松崎さんはカルチャー、参考書、思想と読む本が大分偏っています。おそらく女子大学生(サブカル系女子)ですね。

といったように、レーダーチャートで比較すると、あんまり井上さんに似ていない気がします。

まぁダミーデータなので、他に似た人がいないという可能性もありますが、1000人分のダミーデータなので、そんなことは無いと思うんですよね。

あとはお気づきかもしれませんが、カテゴリによって進む方向が決まるので、CalcCoordinateクラスにカテゴリを渡す時に、カテゴリ順が重要になってきます。

似たようなカテゴリを隣り合わせにしておけば、座標はそっちの方向に進むので、より関連の高いユーザーを抽出できるというわけです。

二次元座標を用いた関連度はこんなところですね。

次は、先ほどレーダーチャートで分析したように購買カテゴリのジャンルの比率から関連度を計算したいと思います。