機械学習によるレコメンデーション入門

レコメンドシステムの3要素: https://arxiv.org/pdf/2401.17878 より引用

レコメンドシステムは、ユーザーの過去の行動履歴や、似たような趣味を持つ他のユーザーの行動データなどをもとに、そのユーザーが興味を持ちそうな商品やコンテンツを予測し、提案する情報フィルタリングシステムの一種です。

協調フィルタリングコンテンツベースフィルタリングといった様々なアルゴリズムが用いられ、ユーザーの満足度向上や、新たなアイテムの発見を支援します。

今回は、レコメンドシステムの基礎的な概念から、具体的な実装方法、そしてその効果測定手法までを網羅的に解説します。特に、コールドスタート問題や評価指標など、実システム構築における重要な課題についても言及します。

§1. レコメンドの基礎

レコメンドシステムは、ユーザーが求めている商品や情報を的確に提示することで、利便性を向上させる仕組みです。ECサイト、動画配信サービス、SNSなど、多くのプラットフォームで利用されており、ユーザーエクスペリエンスの向上やビジネスの売上拡大に寄与しています。

まずは、レコメンドシステムの基本的な構成や方法、またビジネス上の課題ついて解説します。

1-1. レコメンドの基本的な構成

  1. ユーザーの行動: ユーザーがシステムを利用し、アイテムを閲覧したり、購入したりするなどの行動を行います。これらの行動履歴がデータとして蓄積されます。
  2. 候補生成: 蓄積されたデータに基づいて、ユーザーに推薦する候補アイテムを生成します。この段階では、膨大な数のアイテムの中から、ある程度絞り込まれた候補が生成されます。
  3. ランキング: 生成された候補アイテムに対して、ユーザーへの関連度が高い順にランキング付けを行います。このランキングにより、ユーザーにとって最も魅力的なアイテムが上位に表示されます。
  4. 推薦: ランキングされたアイテムをユーザーに提示します。

1-2. レコメンド手法のカテゴリ

以下、代表的なレコメンド手法のカテゴリをまとめたものになります。

カテゴリ概要説明メリットデメリット
ルールベース事前に定めたルールに基づいてアイテムを推薦。セグメント、新商品、過去の購入履歴など、様々なルールを設定可能。ルールが明確で、運用が容易。特定のアイテムをプッシュしたい場合に有効。柔軟性が低い。新しいトレンドやユーザーの嗜好の変化に追従しにくい。
アソシエーション分析過去の購買データから、アイテム間の関連性を分析し、一緒に購入されることが多いアイテムを推薦。計算がシンプルで実装が容易。SQLなどでも実行可能。ユーザーの個別的な好みを考慮しにくい。
コンテンツベースフィルタリングアイテムの属性情報(ジャンル、キーワードなど)と、ユーザーの過去の行動履歴に基づいて、類似したアイテムを推薦。ユーザーの興味関心に基づいた推薦が可能。新しいアイテムも推薦しやすい。アイテムの属性情報が不足している場合、精度の低い推薦となる可能性がある。
協調フィルタリング他のユーザーの行動履歴から、類似したユーザーが好むアイテムを推薦。メモリベースとモデルベースに分けられる。ユーザーの個別的な好みを反映しやすい。高い精度が期待できる。コールドスタート問題(新しいユーザーやアイテムに対する推薦が難しい)が発生しやすい。スパースなデータ(ユーザーとアイテムの組み合わせが少ない)では精度が低下する可能性がある。
ディープラーニングニューラルネットワークを用いて、ユーザーとアイテムの複雑な関係性を学習し、より精度の高い推薦を実現。大量のデータから複雑な特徴量を自動抽出できる。高い表現力を持つ。モデルが複雑で、学習に時間がかかる。大量のデータが必要。
知識グラフアイテム間の関係性(例えば、商品とブランド、著者と書籍など)をグラフ構造で表現し、より深いレベルでの推薦を実現。知識に基づいた多様な推薦が可能。ユーザーの興味関心を深く理解できる。知識グラフの構築に手間がかかる。
強化学習推薦行動をエージェントの行動とみなし、報酬を最大化するように学習を進める。長期的なユーザーの満足度を最大化できる。ダイナミックな環境に適応できる。実装が複雑で、チューニングが難しい。
レコメンド手法のトレンドの推移: https://arxiv.org/pdf/2409.05033 より引用

1-3. ビジネス上の課題

レコメンドシステムの構築は、技術的な側面だけでなく、ビジネス戦略や倫理的問題、ユーザー体験など、多岐にわたる課題を内包しています。

1. ビジネス要件の明確化 レコメンドシステムは、単なる技術ではなく、企業のビジネス目標達成のための強力なツールです。売上向上、顧客LTV向上、顧客満足度向上など、長期的な目標との整合性を図り、マーケティング、営業、エンジニアリングなど、関係部門との連携を密に行う必要があります。また、フェアなレコメンド、プライバシー保護など、倫理的な側面も考慮することが不可欠です。

2. コールドスタート問題とバイアス 新規ユーザーやアイテムに対するレコメンドは、従来の方法では困難です。コンテキスト依存性や選択バイアス、確認バイアス、アルゴリズムバイアスといった問題も、レコメンドの精度を低下させる要因となります。これらの問題を解決するためには、コンテンツベースフィルタリングや知識グラフなどの手法を組み合わせ、ユーザーの初期設定情報やアイテム属性を効果的に活用することが重要です。

3. データの質とスケーラビリティ 高品質なデータは、レコメンドシステムの命脈です。データ収集、クリーニング、統合には、多大な労力とコストがかかります。また、ユーザー数やアイテム数の急増に対応できる、スケーラブルなシステム設計も求められます。

4. 解釈可能性とユーザーフィードバック レコメンド結果がどのように導き出されたのかを、人間が理解できるように説明できることが重要です。また、ユーザーからのフィードバックを積極的に収集し、モデルを継続的に改善していく仕組みが必要です。A/Bテストを導入することで、より効果的なレコメンドアルゴリズムやユーザーインターフェースを選択することができます。

5. 多様性、説明責任、透明性 ユーザーの多様性を考慮し、多様なレコメンドを提供することは、ユーザーエンゲージメントを高める上で重要です。同時に、レコメンド結果に対して企業が責任を持つこと、そしてレコメンドシステムの仕組みをユーザーに開示し、透明性を確保することも求められます。

6. 継続的な改善 技術の進化やユーザーの行動変化は急速であり、レコメンドシステムもそれに合わせて継続的に改善していく必要があります。機械学習技術の進歩を捉え、深層学習や強化学習などの最新手法を積極的に導入することが重要です。

§2. 様々なレコメンデーションアルゴリズム

本章では、レコメンド手法の具体的な方法について解説します。また、コンテンツベースフィルタリングと協調フィルタリングについては、サンプルコードも合わせて掲載します。なお、サンプルコードで使用しているデータは、オープンデータである MovieLens(映画のレコメンド用データ)となります。

2-1. コンテンツベースフィルタリング

コンテンツベースフィルタリングは、ユーザーの過去の行動履歴や属性情報から、そのユーザーが好むであろうアイテムを推薦する手法です。この手法の大きな特徴は、アイテムそのものの内容に基づいて推薦を行う点にあります。

長所として、まず、新規ユーザーに対しても、その人が興味を持つ情報を直接入力することで、パーソナライズされた推薦をすぐに開始できる点が挙げられます。また、推薦結果について、アイテムの具体的な特徴を根拠として説明できるため、ユーザーはなぜそのアイテムが推薦されたのかを直感的に理解しやすいというメリットもあります。さらに、テキストや数値、画像など、様々な種類のデータに対応できるため、幅広い分野のアイテムに対して推薦を行うことが可能です。

一方で、短所も存在します。アイテムの特徴が十分に表現できていない場合、正確な推薦を行うことが難しく、精度が低下する可能性があります。また、ユーザーの過去の行動履歴に基づいて推薦を行うため、似たようなアイテムばかりが推薦されてしまい、ユーザーの多様な興味を捉えきれないという問題も起こりえます。さらに、ユーザーの好みは時間とともに変化するため、長期間にわたって一貫した精度で推薦を行うことは容易ではありません。

では、以下に、コンテンツベースフィルタリングの仕組をまとめます。

  • 特徴抽出:
    • テキストデータ(説明文、レビューなど)に対しては、TF-IDFWord2Vecなどの自然言語処理技術を用いて特徴ベクトルに変換
    • 数値データ(価格、評価など)はそのまま特徴として利用可能
    • 画像データに対しては、CNNを用いて特徴抽出することも可能
  • 類似度の計算:
    • コサイン類似度ユークリッド距離マンハッタン距離などが一般的に用いられる
    • 特徴ベクトル間の類似度が高いほど、アイテム間の類似性が高いと判断
  • 推薦:
    • 類似度が高いアイテムを、ユーザーに推薦
    • 類似度の閾値を設定したり、上位N件を推薦したりといった方法がある
コンテンツベースフィルタリングと協調フィルタリング: https://arxiv.org/pdf/2206.02631 より引用

手法①: カテゴリデータ(または、タグデータ)を使用し、アイテム間の類似度を計算して推薦

アイテムに複数割り当てられたカテゴリ(またはタグ)に基づき、類似したアイテムを推薦する手法です。大まかなアルゴリズムと、Pythonによる具体的な実装例を以下にまとめます。なお、コードの実装例では、

  1. データの前処理:
    • 各アイテムを1行とし、カテゴリ(またはタグ)を列とする行列を作成します。
    • カテゴリを数値化するOne-Hotエンコーディングを施し、各アイテムをベクトルに変換します。
  2. ユーザープロファイルの作成:
    • ユーザーが最近高評価したアイテムのベクトルを平均化し、そのユーザーの興味関心を表すベクトル(ユーザープロファイル)を作成します。
  3. 類似アイテムの特定:
    • ユーザープロファイルと、他のアイテムのベクトルの類似度を計算します。
    • 類似度が高いアイテムほど、ユーザーの興味関心に合致する可能性が高いため、推薦対象となります。

コサイン類似度: \( \displaystyle \hspace{3mm} \cos(\mathbf{x}, \mathbf{y}) = \frac{\mathbf{x} \cdot \mathbf{y}}{|\mathbf{x}| |\mathbf{y}|} \)

※ 高次元データについては、類似度が \(0\) に漸近することがあるので注意

アイテム間の類似度によるコンテンツベースフィルタリングのサンプルコード
# -*- coding: utf-8 -*-

import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity


def load_datasets(data_dir: str) -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
    rate_df = pd.read_csv(f"{data_dir}/ratings.csv")
    movies_df = pd.read_csv(f"{data_dir}/movies.csv")
    tags_df = pd.read_csv(f"{data_dir}/tags.csv")

    return rate_df, movies_df, tags_df


def genre_pivot_table(movies_df: pd.DataFrame) -> pd.DataFrame:
    # ジャンルをリストに分割し、各ジャンルを1行とする
    movies_df["genre_list"] = movies_df["genres"].str.split("|")
    movies_df = movies_df.explode("genre_list")

    # ジャンル情報であることを示すフラグを追加
    movies_df["is_genre"] = 1

    # ピボットテーブルを作成
    movies_df_pivot = movies_df.pivot_table(
        index=["movieId", "title"],
        columns="genre_list",
        values="is_genre",
        fill_value=0
    )

    # インデックスをリセットし、不要な列を削除
    movies_df_pivot = movies_df_pivot.reset_index()
    movies_df_pivot.columns.name = None

    # 列の順序を変更し、不要なカラムを削除
    columns = movies_df_pivot.columns.tolist()
    columns[:2] = ["movieId", "title"]
    movies_df_pivot = movies_df_pivot[columns]
    movies_df_pivot = movies_df_pivot.drop("(no genres listed)", axis=1)

    return movies_df_pivot


def create_recommendations(rate_df: pd.DataFrame, movies_df: pd.DataFrame, target_user_id: int, n_target_items: int = 5) -> pd.DataFrame:
    # 映画情報からジャンル情報を抽出しやすいようにデータフレームを整形
    movies_df_pivot = genre_pivot_table(movies_df)

    # 各映画情報からジャンル情報をOne-Hotエンコーディングしたベクトルを準備
    x = movies_df_pivot.drop(["movieId", "title"], axis=1).to_numpy()

    # レコメンデーション対象のユーザーが高評価した映画情報を抽出
    high_review_movie_df = rate_df.query(f"userId == {target_user_id} and rating == 5").sort_values(by="timestamp", ascending=False)

    # 高評価した映画情報のタイトルと紐づくようにデータフレームを作成する
    high_review_movie_df = pd.merge(high_review_movie_df[:n_target_items], movies_df_pivot, how="left", on="movieId")

    # ユーザーが好む映画と、すべての映画の類似度を計算
    avg_high_review_movie_df_x = high_review_movie_df.loc[:, "Action":"Western"].mean().to_numpy()
    similarities = cosine_similarity(avg_high_review_movie_df_x.reshape(1, -1), x)
    movies_df_pivot["similarity"] = similarities[0]

    return movies_df_pivot


if __name__ == "__main__":
    # データセット (MovieLens) の読み込み
    rate_df, movies_df, _ = load_datasets("./ml-latest-small")

    # 指定したユーザーに対する推薦情報を作成
    # 戻り値のデータフレームにあるカラムの「similarity」が大きいものほど推薦対象となる
    target_user_id = 1      # 推薦対象のユーザーIDを指定
    recommendation = create_recommendations(
        rate_df,
        movies_df,
        target_user_id=target_user_id,
    )

    # 結果の保存
    recommendation.to_csv(f"recommendation-for-user{target_user_id}.csv")

手法②: カテゴリデータとタグデータをWord2Vecでベクトル化しアイテム間の類似度を計算して推薦

各アイテムに付与された複数のカテゴリとタグを、自然言語処理のWord2Vecを用いてベクトル化し、アイテム間の類似度を計算することでレコメンドを行います。アルゴリズムの概要は、次の通りです。

  1. ベクトル化: 各アイテムのカテゴリとタグをWord2Vecによって数値のベクトルに変換します。これにより、単語の意味や文脈が数値として表現され、アイテム間の類似度を定量的に測ることができるようになります。
  2. 平均ベクトルの算出: ユーザーが最近購入し、高評価を付けたアイテムのベクトルの平均を計算します。この平均ベクトルは、ユーザーの興味関心を表すものとみなすことができます。
  3. 類似アイテムの探索: 2で算出した平均ベクトルと、他のアイテムのベクトルとの間のコサイン類似度を計算し、類似度が高いアイテムをレコメンドします。コサイン類似度は、ベクトルの間の角度を基に計算される類似度の指標であり、値が1に近づくほど類似度が高いことを示します。
Word2Vecによるコンテンツベースフィルタリングのサンプルコード
# -*- coding: utf-8 -*-

import gensim
import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity


def load_datasets(data_dir: str) -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
    rate_df = pd.read_csv(f"{data_dir}/ratings.csv")
    movies_df = pd.read_csv(f"{data_dir}/movies.csv")
    tags_df = pd.read_csv(f"{data_dir}/tags.csv")

    return rate_df, movies_df, tags_df


def extract_genres_and_tags(movies_df: pd.DataFrame, tags_df: pd.DataFrame) -> pd.DataFrame:
    # タグ情報を抽出してリスト化
    tags_df["tag"] = tags_df["tag"].str.lower()
    user_tags_df = tags_df.groupby("movieId")["tag"].apply(list).reset_index()

    # ジャンルとタグ情報を一つのデータフレームにまとめる
    genres_tags_df = pd.merge(movies_df, user_tags_df, how="left", on="movieId")

    # ジャンル情報をリストに変換
    genres_tags_df["genre_list"] = genres_tags_df["genres"].map(lambda x: x.split("|")).to_list()

    # タグとジャンルを一つのリストにまとめる
    genres_tags_df["tag"] = genres_tags_df["tag"].fillna("").apply(list)
    genres_tags_df["tag_genre"] = genres_tags_df["tag"] + genres_tags_df["genre_list"]

    return genres_tags_df


def create_word2vec(words: list[str]) -> gensim.models.Word2Vec:
    return gensim.models.word2vec.Word2Vec(
        words,
        vector_size=100,    # 埋め込みベクトルの次元
        window=100,         # 上下文の最大距離
        sg=1,               # 0=CBoW, 1=Skip-Gram
        hs=0,               # 0=NS, 1=HS
        epochs=30           # 学習回数
    )


def embedding_movie_info(word2vec: gensim.models.word2vec.Word2Vec, tags_and_genres: list[str]) -> list:
    movie_vec = []

    tag_genre_in_model = set(word2vec.wv.key_to_index.keys())

    for tag_genre in tags_and_genres:
        input_tag_genre = set(tag_genre) & tag_genre_in_model

        if len(input_tag_genre) == 0:
            vec = np.random.randn(word2vec.vector_size)
        else:
            vec = word2vec.wv[input_tag_genre].mean(axis=0)

        movie_vec.append(vec)

    return movie_vec


def create_recommendations(rate_df: pd.DataFrame, genres_tags_df: pd.DataFrame, movie_vec: list, target_user_id: int, top_n: int = 10) -> pd.DataFrame:
    # ユーザーの高評価映画を取得
    high_review_movie_df = rate_df.query(f"userId == {target_user_id} and rating == 5").sort_values(by="timestamp", ascending=False)[:top_n]
    high_review_movie_df_list = high_review_movie_df["movieId"].tolist()

    # 高評価映画の平均ベクトルを計算
    movie_vec_arr = np.array(movie_vec)
    high_review_movie_vec_arr = movie_vec_arr[high_review_movie_df_list]
    avg_high_review_movie_vec_arr = high_review_movie_vec_arr.mean(axis=0)

    # すべての映画との類似度を計算
    similarities = cosine_similarity(avg_high_review_movie_vec_arr.reshape(1, -1), movie_vec_arr)
    genres_tags_df["similarity"] = similarities[0]

    return genres_tags_df


if __name__ == "__main__":
    # データセット (MovieLens) の読み込み
    rate_df, movies_df, tags_df = load_datasets("./ml-latest-small")

    # タグとジャンルの組み合わせを抽出
    genres_tags_df = extract_genres_and_tags(movies_df, tags_df)

    # Word2Vec モデルの作成
    tags_and_genres = genres_tags_df["tag_genre"].tolist()
    word2vec = create_word2vec(tags_and_genres)

    # 動画情報の埋め込み
    movie_vec = embedding_movie_info(word2vec, tags_and_genres)

    # 推奨映画の作成
    # 戻り値のデータフレームにあるカラムの「similarity」が大きいものほど推薦対象となる
    target_user_id = 11     # 推薦対象のユーザーIDを指定
    recommendation = create_recommendations(rate_df, genres_tags_df, movie_vec, target_user_id)

    # 結果の保存
    recommendation.to_csv(f"recommendation-for-user{target_user_id}-with-word2vec.csv")

2-2. 協調フィルタリング

協調フィルタリングは、多くのユーザーの嗜好情報を集め、あるユーザーと似た好みを持つ他のユーザーが好むものを推薦する、という仕組みです。例えば、あるユーザーがAという映画を評価した場合、Aと似た映画を評価している他のユーザーが好むBという映画を、最初のユーザーにも推薦する、といった具合です。

協調フィルタリングの手法

協調フィルタリングには、大きく分けて以下の3つの手法があります。

  • ユーザーベース協調フィルタリング: ユーザー同士の類似度を計算し、似たユーザーが好むアイテムを推薦します。例えば、AさんとBさんが多くの映画で同じような評価をしている場合、Aさんが高評価した映画をBさんにも推薦します。
  • アイテムベース協調フィルタリング: アイテム同士の類似度を計算し、ユーザーが既に評価したアイテムと似たアイテムを推薦します。例えば、Aさんが映画AとBを高く評価した場合、AとBが似たジャンルの映画Cを推薦します。
  • 行列分解: ユーザーとアイテムの評価を、潜在的な特徴(例えば、ユーザーの好みやアイテムのジャンル)に分解し、その特徴に基づいて推薦を行います。これにより、データのスパース性(多くの評価が欠けている状態)の問題を緩和できます。

データの種類

協調フィルタリングで利用するデータには、以下の2種類があります。

  • 明示的データ: ユーザーが直接的に自分の好みを表現したデータです。例えば、映画の評価(星の数など)やアンケートの回答などが挙げられます。
  • 暗黙的データ: ユーザーの行動から間接的に推測されるデータです。例えば、商品の購入履歴、Webサイトの閲覧履歴、クリック履歴などが挙げられます。

評価値行列と課題

ユーザーとアイテムの評価をまとめた表を評価値行列と言います。しかし、実際の評価値行列は、多くの要素が空欄になっているスパースな行列であることが一般的です。また、評価には様々なバイアス(例えば、人気のあるアイテムは高評価されやすい、など)が含まれているため、そのままでは正確な推薦を行うことが難しい場合があります。

手法①: ユーザー間の類似度による手法

過去の行動履歴に基づいて、似たユーザーの評価を参考に、新しいアイテムを推薦する手法です。

  1. 類似ユーザーの特定:
    • すべてのユーザーの評価データから、ピアソンの相関係数などの指標を用いて、推薦対象となるユーザーと最も類似性の高いユーザーを特定
    • 類似度は、二人のユーザーが共通して評価したアイテムに対する評価の傾向がどの程度一致しているかを数値化したものとなる
  2. 予測評価値の算出:
    • 特定された類似ユーザーが評価しているが、推薦対象ユーザーがまだ評価していないアイテムに対して、類似ユーザーの評価値を参考に、推薦対象ユーザーがそのアイテムに対してどの程度の評価を与えるかを予測
    • この予測評価値は、類似ユーザーの評価値を重み付けして算出することで、より精度を高めることが可能
  3. 推薦アイテムの選定:
    • 予測評価値の高いアイテムから順に、推薦対象ユーザーに提示
    • 予測評価値が最も高いアイテムが必ずしも最適な推薦とは限らないため、多様なアイテムを組み合わせたり、ユーザーの過去の行動履歴や属性情報を考慮したりすることで、よりパーソナライズされた推薦を行うことが可能
ユーザー間の類似度を使用した協調フィルタリングのサンプルコード
# -*- coding: utf-8 -*-

import pandas as pd
from surprise import Dataset, KNNWithMeans, Reader, accuracy
from surprise.model_selection import train_test_split
from surprise.trainset import Trainset


def load_datasets(data_dir: str) -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
    rate_df = pd.read_csv(f"{data_dir}/ratings.csv")
    movies_df = pd.read_csv(f"{data_dir}/movies.csv")
    tags_df = pd.read_csv(f"{data_dir}/tags.csv")

    return rate_df, movies_df, tags_df


def to_train_test_data(rate_df: pd.DataFrame) -> tuple[Trainset, list]:
    reader = Reader(rating_scale=(0.5, 5.0))
    data_rate = Dataset.load_from_df(rate_df[["userId", "movieId", "rating"]], reader)

    return train_test_split(data_rate, test_size=0.2)


def train_recommender(train_set: Trainset, test_set: list) -> KNNWithMeans:
    # モデルの学習
    sim_options = {"name": "pearson", "user_based": True}
    knn = KNNWithMeans(k=20, sim_options=sim_options)
    knn.fit(train_set)

    # テストデータの評価
    test_pred = knn.test(test_set)
    print(f"RMSE: {accuracy.rmse(test_pred)}")
    print(f"MAE: {accuracy.mae(test_pred)}")

    return knn


def create_recommendations(recommender: KNNWithMeans, movies_df: pd.DataFrame, train_set: Trainset, top_n: int = 10) -> pd.DataFrame:
    # 予測結果の作成
    data_test = train_set.build_anti_testset()
    predictions = recommender.test(data_test)

    # 予測結果の上位 N 個を取得
    result_df = pd.DataFrame([[uid, iid, est] for uid, iid, _, est, _ in predictions], columns=["userId", "movieId", "pred"])
    result_top_n = result_df.sort_values("pred", ascending=False).groupby("userId").head(top_n).sort_index()
    result_top_n_merge = pd.merge(result_top_n, movies_df[["movieId", "title"]], how="left", on="movieId")

    return result_top_n_merge


if __name__ == "__main__":
    # データセット (MovieLens) の読み込み
    rate_df, movies_df, _ = load_datasets("./ml-latest-small")

    # 訓練データとテストデータに分割
    train_set, test_set = to_train_test_data(rate_df)

    # モデルの学習と評価
    recommender = train_recommender(train_set, test_set)

    # 推薦結果の作成
    recommendation = create_recommendations(recommender, movies_df, train_set)

    # 結果の保存
    recommendation.to_csv("recommendation_with_user_similarity.csv")

手法②: 行列分解にもとづく手法

行列分解に基づく手法は、ユーザーとアイテムの評価値行列を、より低次元の潜在因子行列に分解することで、ユーザーとアイテムの潜在的な特徴を抽出し、未知の評価値を予測する手法です。

  • 特異値分解 (SVD)Matrix Factorization (MF) などの手法が代表的です。
  • 潜在因子: ユーザーやアイテムの潜在的な特徴を数値ベクトルで表現したもの。
  • 目的:
    • 次元削減: 高次元な評価値行列を低次元に圧縮し、計算量を削減。
    • ノイズ除去: 観測データに含まれるノイズを軽減し、より本質的な特徴を抽出。
    • 予測: 未観測の評価値を予測し、レコメンドに利用。
MF (Matrix Factorization) による手法

MFは、観測された評価値のみを用いて行列分解を行い、潜在因子を学習する手法です。

  • 損失関数: \(\displaystyle \hspace{3mm} \min_{U,V} \sum_{(u,i) \in \Omega} (r_{ui} – u_i^T v_i)^2 + \lambda (|U|^2 + |V|^2) \)
    • \(U\): ユーザーの潜在因子行列
    • \(V\): アイテムの潜在因子行列
    • \(\Omega\): 観測された評価値のインデックス集合
    • \(\lambda\): 正則化パラメータ(過学習を防ぐ)
  • 特徴:
    • 欠損値への対応: 観測値のみを用いるため、欠損値を直接扱う必要がない。
    • 並列化: 大規模データに対しても並列処理が可能。
    • 拡張性: 様々な正則化項や非線形関数を取り入れることで、表現力を高めることができる。
行列分解にもとづく協調フィルタリングのサンプルコード
# -*- coding: utf-8 -*-

import pandas as pd
from surprise import SVD  # Matrix Factorization
from surprise import Dataset, Reader, accuracy
from surprise.model_selection import train_test_split
from surprise.trainset import Trainset


def load_datasets(data_dir: str) -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
    rate_df = pd.read_csv(f"{data_dir}/ratings.csv")
    movies_df = pd.read_csv(f"{data_dir}/movies.csv")
    tags_df = pd.read_csv(f"{data_dir}/tags.csv")

    return rate_df, movies_df, tags_df


def to_train_test_data(rate_df: pd.DataFrame) -> tuple[Trainset, list]:
    reader = Reader(rating_scale=(0.5, 5.0))
    data_rate = Dataset.load_from_df(rate_df[["userId", "movieId", "rating"]], reader)

    return train_test_split(data_rate, test_size=0.2)


def train_recommender(train_set: Trainset, test_set: list) -> SVD:
    # モデルの学習
    mf = SVD(
        n_factors=10,
        n_epochs=50,
        lr_all=0.01,
        biased=False
    )
    mf.fit(train_set)

    # テストデータの評価
    test_pred = mf.test(test_set)
    print(f"RMSE: {accuracy.rmse(test_pred)}")
    print(f"MAE: {accuracy.mae(test_pred)}")

    return mf


def create_recommendations(recommender: SVD, movies_df: pd.DataFrame, train_set: Trainset, top_n: int = 10) -> pd.DataFrame:
    # 予測結果の作成
    data_test = train_set.build_anti_testset()
    predictions = recommender.test(data_test)

    # 予測結果の上位 N 個を取得
    result_df = pd.DataFrame([[uid, iid, est] for uid, iid, _, est, _ in predictions], columns=["userId", "movieId", "pred"])
    result_top_n = result_df.sort_values("pred", ascending=False).groupby("userId").head(top_n).sort_index()
    result_top_n_merge = pd.merge(result_top_n, movies_df[["movieId", "title"]], how="left", on="movieId")

    return result_top_n_merge


if __name__ == "__main__":
    # データセット (MovieLens) の読み込み
    rate_df, movies_df, _ = load_datasets("./ml-latest-small")

    # 訓練データとテストデータに分割
    train_set, test_set = to_train_test_data(rate_df)

    # モデルの学習と評価
    recommender = train_recommender(train_set, test_set)

    # 推薦結果の作成
    recommendation = create_recommendations(recommender, movies_df, train_set)

    # 結果の保存
    recommendation.to_csv("recommendation_with_matrix_factorization.csv")

2-3. ディープラーニング

ディープラーニングは、レコメンドシステムにおいて従来の線形手法(行列分解など)よりも複雑なユーザ・アイテム間の相互作用を捉えることで、予測精度とレコメンド品質を向上させることができます。

カテゴリ説明課題代表的手法
多層パーセプトロン (MLP)フィードフォワードニューラルネットワークの一種であり、多層構造を用いて非線形な相互作用をモデル化モデルの複雑さ、過学習のリスク、空間不変性の欠如、勾配消失・爆発問題、説明性Neural Collaborative Filtering (NCF)Deep Factorization Machine (DFM)Wide & DeepxDeepFMDeep & Cross Network (DCN)FMLP-RecFinalMLP
オートエンコーダ入力データを圧縮した低次元表現にエンコードし、その後デコードして元のデータの再構築を行う教師なし学習モデルノイズに敏感であり、ノイズの多い入力データに対しては再構築の精度が低下する可能性がある。また、再構築プロセスにおいてレコメンドに重要な意味的なパターンが保持されない可能性があるAutoRecCollaborative Filtering Autoencoder、Multi-Variational Auto-Encoder (Multi-VAE)Deep Recommender (DeepRec)Recommender Variational Auto-Encoder (RecVAE)Item-based variational auto-encoder for fair recommendationVariational Bandwidth Auto-Encoder (VBAE)
畳み込みニューラルネットワーク (CNN)画像、時系列データ、マルチモーダルデータから学習可能であり、レコメンドの精度とパーソナライゼーションを向上させるデータスパースネス、スケーラビリティ、プライバシー、ドメイン固有の問題DeepCoNNグラフ構造との統合DKN (ニュースレコメンド)MusicCNN (音楽レコメンド)CoCNNCAGCN
再帰型ニューラルネットワーク (RNN)時系列データにおける複雑なユーザ・アイテム間の相互作用を捉えるのに適している勾配消失・爆発問題、逐次的な学習処理GRU4RecNARMSASRecDANLSTMR-CNNCNN-RNNハイブリッドモデル

2-4. 知識グラフ

知識グラフは、エンティティとその間の関係をグラフ構造で表現したものであり、レコメンドにおいてユーザーの嗜好やアイテム間の関係性を理解する上で有効です。

知識グラフを用いたレコメンド手法は主に以下の3つに分類されます。

  • エンベディングベース手法: 知識グラフのエンティティと関係をベクトル空間にエンベディング、ユーザーやアイテムの表現を強化
  • パスベース手法: 知識グラフ内のエンティティ間のパス情報を活用
  • 伝播ベース手法: 知識グラフ内の多段階の隣接ノード間の相互作用を通じてエンベディングを改善

また、エンベディングとレコメンドの両方を同時に最適化する同時学習手法や、知識グラフ補完とレコメンドを同時に扱う多目的学習手法も存在します。

手法分類手法名説明
エンベディングベースTransEエンティティと関係のエンベディング学習
 Node2Vecグラフ上のノードのエンベディング学習
 KSRアテンション機構を用いたレコメンド
 BEM生成モデルを用いたレコメンド
同時学習CKEオートエンコーダを用いたアイテム表現学習
 SHINE異種グラフからのユーザーエンベディング取得
多目的学習KTUP知識グラフ補完とレコメンドの同時学習
 MKR知識グラフ補完とレコメンドの同時学習
パスベースKGCNグラフ畳み込みを用いたエンティティ近接性の保持
 MCRecパスエンベディングを用いたレコメンド
 RKGERNNを用いたパスの意味の取り込み
伝播ベースRipplenetアイテム関連のエンベディングの集約
 KGATユーザー-アイテムグラフ上のエンベディング伝播
 IntentGCユーザー-アイテムグラフ上のエンベディング伝播

2-5. 強化学習

強化学習は、エージェントが経験を通して学習し、最適な行動を選択する手法です。レコメンドシステムでは、ユーザーの好みを学習し、より良い商品やサービスを提案するために使われています。

手法分類手法名特徴
従来の強化学習Q-learningモデルフリー、マルコフ決定過程内で方策を最適化
SARSAモデルフリー、マルコフ決定過程内で方策を最適化
深層強化学習DQN (Deep Q-Network)ニューラルネットワークを用いた行動・報酬推定
Actor-Critic価値ベースと方策ベースの手法を組み合わせる
DDPG (Deep Deterministic Policy Gradient)Actor-Criticの一種、連続行動空間に対応
SAC (Soft Actor-Critic)探索と活用を効果的にバランスさせる
モデルベース強化学習GAN (Generative Adversarial Network)ユーザー行動のシミュレーション
マルチエージェントシステム複数のエージェントが相互作用しながらレコメンドを行う

§3. レコメンドシステムの評価

レコメンドシステムの性能を測るためには、適切な評価手法が不可欠です。この章では、レコメンドシステムの評価方法を、オフライン評価、オンライン評価、そしてユーザー調査の3つの側面から解説します。

3-1. オフライン評価:過去のデータで精度を検証

オフライン評価は、過去のデータを用いて、モデルの精度を測る方法です。

評価方法と指標

  • データ分割: 学習用とテスト用にデータを分割し、学習用データでモデルを作成、テストデータで精度を評価します。
  • 予測誤差評価: レビュー評価など、数値で評価できる場合に用いられます。RMSE(二乗平均平方根誤差)、MSE(平均二乗誤差)、MAE(平均絶対誤差)などの指標が利用されます。
  • 集合評価: 購入したか否かのような2値の評価の場合に用いられます。Precision@N(適合率)、Recall@N(再現率)、ROC曲線、PR曲線などの指標が利用されます。
  • カバレッジ: 全アイテムまたは全ユーザーの中で、どれだけ多くのアイテムやユーザーに推薦できたかを評価します。

評価の難しさ

  • 精度ばかりを追求すると、似たようなアイテムばかりが推薦されてしまい、ユーザーの満足度が低下する可能性があります。
  • 新しいアイテムを推薦することで、ユーザーの満足度が向上することもあります。
  • 人気アイテムばかりを推薦すると、新たなヒットアイテムの発掘が難しくなる可能性があります。
  • クリックだけでなく、購買行動など、より詳細な行動ログを評価に含めるべきという意見もあります。

データの分割と交差検証

  • 訓練データ、検証データ、テストデータ: モデル作成、パラメータチューニング、精度評価にそれぞれ利用します。
  • 交差検証: データが少ない場合や偏りがある場合に有効な手法です。データを複数に分割し、それぞれを検証データとして評価することで、より信頼性の高い評価結果を得られます。
  • 時系列データの交差検証: 時系列データの場合、過去のデータで学習し、未来のデータで評価を行う必要があります。リークと呼ばれる現象を防ぐため、注意が必要です。

3-2. オンライン評価:実環境で評価

オフライン評価では、実際の環境での性能を測ることが難しいという課題があります。オンライン評価は、実際のユーザーを対象に、システムの性能を評価する方法です。

  • A/Bテスト: 異なるアルゴリズムを、異なるユーザーグループに適用し、その結果を比較する手法です。
  • インターリービング: 複数のアルゴリズムの推薦結果を混ぜて、同一のユーザーに提示し、どのアルゴリズムの推薦結果が選択されたかを評価する手法です。A/Bテストよりも、より公平な比較が可能です。

インターリービング用のPythonライブラリ: interleaving

3-3. ユーザー調査:ユーザーの声を聴く

オフライン評価やオンライン評価で高い精度が出たとしても、必ずしもユーザーが満足しているとは限りません。ユーザー調査は、ユーザーの生の声を聞き、システムの改善に役立てるための重要なステップです。

調査項目例

  • 推薦の質
  • UIの使いやすさ
  • システム全体の満足度
  • 再利用意欲

まとめ

今回は、レコメンドシステムの概要について、その手法や評価方法などをまとめました。コンテンツベースフィルタリングや協調フィルタリングが広く用いられていますが、近年はディープラーニングや知識グラフを使用した方法なども提案されています。また、今回は紹介できませんでしたが、大規模言語モデル(LLM)による手法なども提案されています。

協調フィルタリングについては、PythonパッケージのSurpriseを使用した実装例を紹介しました。さらに詳しい使い方については、公式ドキュメントを参照してください。

More Information:

  • arXiv:2206.02631, Ziyuan Xia et al., 「Contemporary Recommendation Systems on Big Data and Their Applications: A Survey」, https://arxiv.org/abs/2206.02631
  • arXiv:2210.05662, Zhengbang Zhu et al., 「Understanding or Manipulation: Rethinking Online Performance Gains of Modern Recommender Systems」, https://arxiv.org/abs/2210.05662
  • arXiv:2404.16177, Aditya Chichani et al., 「Advancing Recommender Systems by mitigating Shilling attacks」, https://arxiv.org/abs/2404.16177
  • arXiv:2407.13699, Shaina Raza et al., 「A Comprehensive Review of Recommender Systems: Transitioning from Theory to Practice」, https://arxiv.org/abs/2407.13699