SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

現場のAIエンジニアに学ぶ推薦システム入門

レコメンドアルゴリズム「BPR」による推薦システムの実装と、SVD・WARP手法との比較【推薦システム入門】


  • X ポスト
  • このエントリーをはてなブックマークに追加

BPR vs. SVD vs. WARPの比較

 ここでは、BPRやWARPを手軽に試すことができるライブラリlightFMを用いて、前回紹介したSVDと結果を定性的に比較したいと思います。

 まずはlightfmをインストールします。また、前回に引き続き、データセット読み込みのためにirspackを用います。

    pip install irspack==0.1.18
    pip install lightfm==1.16
    

 前回と同じく、Movielens1Mのデータを疎行列に変換します。

    from irspack import df_to_sparse
    from irspack.dataset import MovieLens1MDataManager
    from lightfm import LightFM
    import numpy as np
    from sklearn.utils.extmath import randomized_svd

    data_manager = MovieLens1MDataManager()

    rating_df = data_manager.read_interaction()
    R, user_ids, item_ids = df_to_sparse(rating_df, "userId", "movieId")

    movie_df = data_manager.read_item_info()
    

 接触履歴の行列Rに対して、まずはBPRによる行列分解を実行します。

 以下、パラメータの値は後に紹介する検証手続きで決定されています。

    bpr = LightFM(
        no_components=152,
        item_alpha=8.173e-08,
        user_alpha=6.137e-05,
        loss='bpr',
        random_state=0
    )
    bpr.fit(R, epochs=20)
    

 続いてWARPによる行列分解も実行します。

    warp = LightFM(
        no_components=227,
        item_alpha=2.314e-4,
        user_alpha=1.970e-4,
        loss='warp',
        random_state=0
    )
    warp.fit(R, epochs=30)
    

 最後に、前回と同じくrandomized_svdによる行列分解も実行します。

    svd_item_vectors = randomized_svd(R, n_components=33, random_state=0)[2]
    

 3通りの行列分解はそれぞれ異なった考え方・誤差関数に基づく行列分解の手法であり、ベクトルの次元数も大きく異なりますが、大まかには同じ結果を与えます。このことを、ある映画に似たベクトルを持つ映画はどれか、ということを計算して定性的に確認してみましょう。

    vectors = {
        'bpr': bpr.get_item_representations()[1],
        'warp':  warp.get_item_representations()[1],
        # SVDの出力は転置が必要
        'svd': svd_item_vectors.T
    }

    def show_similar_items(item_id=None, n_items=5):
        if item_id is None:
            item_id = np.random.choice(item_ids)

        print(f'Query item: {movie_df.title.loc[item_id]}')

        for algorithm_type, vector in vectors.items():
            # それぞれの映画ベクトルのノルムを計算
            vector_norms = (vector ** 2).sum(axis=1) ** .5

            # クエリとなる映画のベクトルを取得
            movie_index = item_ids.get_loc(item_id)
            query_vector = vector[movie_index]

            # コサイン類似度を計算
            query_vector /= (query_vector ** 2).sum() ** .5
            cosine_similarity = vector.dot(query_vector) / vector_norms

            # 自分自身は表示されないようにする
            cosine_similarity[movie_index] = -np.inf

            # Top `n_items` 類似アイテムを表示
            print(f'{algorithm_type}:')
            recommended_item_ids = item_ids[cosine_similarity.argsort()[::-1][:n_items]]
            print(list(movie_df.reindex(recommended_item_ids).title))
    

 上記コードでは、ベクトル\(\vec{v}_1, \vec{v}_2\)の類似度をコサイン類似度 \(\mathrm{sim}(\vec{v_1}, \vec{v_2}) : = \vec{v}_1\cdot \vec{v}_2 / (||\vec{v_1}|| \ ||\vec{v}_2||)\)で定義しています。

 試しに、item_id=1に対する結果を見てみましょう。

    show_similar_items(item_id=1)
    # Query item: Toy Story (1995)
    # bpr:
    # ['Toy Story 2 (1999)', 'Aladdin (1992)', 'Babe (1995)', "Bug's Life, A (1998)", 'Antz (1998)']
    # warp:
    # ['Toy Story 2 (1999)', 'Babe (1995)', "Bug's Life, A (1998)", 'Groundhog Day (1993)', 'Aladdin (1992)']
    # svd:
    # ['Toy Story 2 (1999)', 'Babe (1995)', 'Aladdin (1992)', "Bug's Life, A (1998)", 'Babe: Pig in the City (1998)']
    

 対象の映画の続編や、90年代の子供向け映画が類似したベクトルを持っていると判断されており、妥当な結果と言えるでしょう。item_id=1270などでは、BPR,WARPとSVDで若干異なった結果が出てきます。

    show_similar_items(item_id=1270)
    # Query item: Back to the Future (1985)
    # bpr:
    # ['Back to the Future Part II (1989)', 'Big (1988)', "Ferris Bueller's Day Off (1986)", 'Cocoon (1985)', 'Spaceballs (1987)']
    # warp:
    # ['Back to the Future Part II (1989)', "Ferris Bueller's Day Off (1986)", 'Cocoon (1985)', 'Star Wars: Episode V - The Empire Strikes Back (1980)', 'Big (1988)']
    # svd:
    # ["Ferris Bueller's Day Off (1986)", 'Breakfast Club, The (1985)', 'E.T. the Extra-Terrestrial (1982)', 'Bull Durham (1988)', 'Star Wars: Episode VI - Return of the Jedi (1983)']
    

 この場合は「バック・トゥー・ザ・フューチャーPart I」に対して「Part II」が近いと判断しているBPR,WARPの方がSVDより良い結果を出しているように思えます。

 ただし、前回からの繰り返しになりますが、定性的にどのアルゴリズムが秀でているのかを人間が判断するのはドメイン知識と労力を要する作業であり、連載後半で述べるオフライン検証手続きに沿って定量的にパフォーマンスを評価する必要があるでしょう。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
現場のAIエンジニアに学ぶ推薦システム入門連載記事一覧

もっと読む

この記事の著者

大槻 知貴(株式会社ビズリーチ)(オオツキ トモキ)

 株式会社ビズリーチ(Visionalグループ) CTO室 AIグループ所属 NTTデータ数理システム、AIベンチャーを経て、2018年にビズリーチ入社。データサイエンス業務に従事し、Visionalグループにおける機械学習関連機能のR&Dを担当。理学博士(物理学)。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

中江 俊博(株式会社ビズリーチ)(ナカエ トシヒロ)

 株式会社ビズリーチ(Visionalグループ) CTO室 AIグループ所属 NTTデータ数理システムにてデータ分析の受託案件を多数担当。その後、IoTスタートアップを経て、2019年にビズリーチ入社。レコメンドシステムなど機械学習関連の実装作業に従事。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/15361 2022/04/14 11:02

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング