情報推薦システムの基本

第5回 協調フィルタリング

この記事を読むのに必要な時間:およそ 4 分

こんにちは,株式会社Gunosyの福島です。今回は協調フィルタリングという推薦手法について解説したいと思います。

協調フィルタリングとは

協調フィルタリングとは,口コミによる興味喚起を自動化したものといわれています。おそらく最も有名であるAmazonの「この商品を買った人はこんなものも買っています」という推薦システムもこの協調フィルタリングの応用で,アイテムベース協調フィルタリングという技術を利用しています。

協調フィルタリングはユーザのコミュニティや過去の振る舞いを利用して,ユーザの好みや興味を持つ情報を予測します。具体的には,過去の各々のユーザの購入情報やクリック情報などを利用します。

ユーザベース協調フィルタリング

もっと具体的に見てみましょう。協調フィルタリングの最も直感的な説明は,ユーザコミュニティの中で自分と購入履歴が似ているユーザを探し出し,その似ているユーザが購入しており,かつ推薦対象ユーザがまだ購入していない商品は興味を持たれたり購入されたりする可能性が高いというものです。これをユーザベース協調フィルタリングと呼びます。

では実際にこれをどう実現しているのでしょうか。今回は簡単な説明のため,ユーザの過去の購入情報のみを推薦に利用するとします(図1)⁠

図1 ユーザの購入履歴

商品1 商品2 商品3 商品4
ユーザーA 購入 × 購入 ×
ユーザーB 購入 × × 購入
ユーザーC 購入 購入 購入 ×
ユーザーD × 購入 × ×

このような購入履歴のデータを持っており,ユーザAに商品の推薦をしたいと考えます。このとき数式的に扱いやすくするために行列で表したのが,図2です。

図2 ユーザの購入履歴の行列図

購入した履歴を1,購入しなかった履歴を0としています。各行はユーザの特徴を表し,各列はアイテムの特徴を表しています。この行列はユーザ-アイテム行列と呼ばれ,協調フィルタリングではたびたび登場します。ユーザベース協調フィルタリングではこの行列を利用しユーザ間の類似度(=過去の購入行動がどれくらい似ているか)を計算し,それにもとづき提示する商品に対して優先度をつけます。ユーザAに推薦するタスクを考えます。ユーザAの場合まだ購入していない商品は2と4です。ここでの推薦タスクは「ユーザAに商品2か4どちらを提示すべきか(または提示しないか)」を確定することになります。

ユーザ間の類似度を求めるには,一般的にピアソン相関係数やコサイン類似度が用いられます。またユーザベースの協調フィルタリングには一般的にピアソン相関係数が用いられます。しかし今回は0/1のみの値ですので,jaccard係数を利用してみることにします(図3)⁠

図3 Jaccard係数

◆相関に関連することと思いますが,Jaccard係数とは,だいたいこういうものです。という追記があれば欲しいです◆

jaccard係数を使ってユーザの類似度を求めた結果が図4です。

図4 Jaccardによる類似度の結果

jaccard係数
ユーザーB 0.333
ユーザーC 0.667
ユーザーD 0.0

この jaccard相関係数は,ユーザAとユーザB,C,Dの類似度を表します。値が大きいほど類似しています。図4では,ユーザCがこの中では,一番類似していることをしめしています。

それでは購入していない商品2と4を推薦するかどうかはどう決定するのでしょうか。商品2のスコアはユーザAの平均評価値に各ユーザの類似度を考慮した重みつきの評価を加えることで計算します。簡易ではありますが今回は次のように実装してみました。

#! /usr/local/bin/python
# -*- coding:utf-8 -*-

import sys


def jaccard(v1, v2):
    """
    jacard係数を求める
    """
    v1_and_v2 = 0.
    v1_or_v2 = 0.
    for i in xrange(len(v1)):
        if v1[i] == 1 or v2[i] == 1:
            v1_or_v2 += 1
            if v1[i] == 1 and v2[i] == 1:
                v1_and_v2 += 1
    try:
        return v1_and_v2 / v1_or_v2
    except ZeroDivisionError:
        return 0.0


def calc_users_similarity(user_id, user_data, sim=jaccard):
    """
    userとuserの類似度を求める
    """
    users_similarity = {}
    for target_id in xrange(len(user_data)):
        if target_id == user_id:
            continue
        users_similarity[target_id] = sim(
            user_data[user_id], user_data[target_id])
    return users_similarity


def calc_user_average_score(user_id, user_data):
    """
    ユーザーの平均スコア
    """
    return sum(user_data[user_id]) / len(user_data[user_id])


def userbase_scoring(user_id, item_id, user_data, users_similarity):
    """
    userとitemのスコアを求める
    """
    if user_data[user_id][item_id] == 1.:
        return -1. * sys.maxint
    ave_score = calc_user_average_score(user_id, user_data)
    bunshi = 0.
    bunbo = 0.
    for target_id in xrange(len(user_data)):
        if target_id == user_id:
            continue
        bunshi += users_similarity[target_id] * (
            user_data[target_id][item_id]
            - calc_user_average_score(target_id, user_data)
        )
        bunbo += users_similarity[target_id]

    return ave_score + (bunshi/bunbo)


if __name__ == "__main__":
    user_data = [[1., 0., 1., 0.],
                 [1., 0., 0., 1.],
                 [1., 1., 1., 0.],
                 [0., 1., 0., 0.]]
    user_id = int(sys.argv[1])
    users_similarity = calc_users_similarity(user_id, user_data, sim=jaccard)
    for item_id in xrange(len(user_data[0])):
        print "item{}:".format(item_id),
        print userbase_scoring(user_id, item_id, user_data, users_similarity)

結果は図5のようになりました。(実装上ではすでに購入した商品はスコアを-sys,maxintにしています)

図5

スコア
商品1 -
商品2 0.50
商品3 -
商品4 0.167

この結果から,ユーザAには商品2を推薦すべきという結果になりました。

コメント

コメントの記入