行列分解を用いた推薦のコード例
前回は、行列分解アルゴリズムとして特異値分解(Singular Value Decomposition、以下SVD)と重み付き行列分解(Weighted Matrix Factorization、以下WMF)を紹介しました。
SVDは行列分解で最も基本的な手法であり、推薦システムに限らず、さまざまな統計パッケージで実装されている一般的なアルゴリズムです。WMFは、暗黙的フィードバックで接触があった組み合わせを特に重視して学習する行列分解手法であり、SVDよりも暗黙的フィードバックのデータに適した改良が加わった手法です。
ここからは、具体的なデータに対してSVDとWMFを適用し推薦を実施する流れを、コード例を交えつつ見ていきましょう(下記コードは scikit-learn 0.24.0, scipy 1.6.0, matplotlib 3.3.4で作成しました)。
まずは前回で紹介したレストラン訪問履歴を表す行列のSVDを考えます。この履歴では、対象となる行列の行がユーザー(レストランの客)であり、列がアイテム(レストラン)に対応していました。
from sklearn.utils.extmath import randomized_svd from matplotlib import pyplot as plt import scipy.sparse as sps R = sps.csr_matrix([[1, 0, 1], [0, 1, 1], [1, 1, 1]]) # SVD 実行 O, Sigma, QT = randomized_svd(R, n_components=2) P = O * Sigma Q = QT.transpose() print(P.dot(QT))
SVDにはさまざまな実装がありますが、ここではSVDを計算するための手法の一つ、scikit-learnのrandomized_svdを用いています。randomized_svdは、大規模な行列\(R\)が与えられていて、計算の結果得られる特徴ベクトルの次元\(n\)が小さい場合に、高速で結果を得ることができます。また一般に接触履歴の行列のサイズが大きい場合でも、特徴ベクトルの次元\(n\)は数十から高々数百程度あれば十分な精度が得られる傾向にあります。このレストラン訪問履歴のような3x3程度の行列では計算コストに大きな違いは生じませんが、接触履歴の行列のサイズが大きい場合には大きな差が生まれますので、特徴ベクトルの次元が小さければ計算速度が速くなるrandomized_svdのようなアルゴリズムを利用することが適切であることが多いです。
それでは、得られた\(P\),\(Q\)をプロットしてみましょう:
figure, ax = plt.subplots() ax.set_aspect("equal") for user_index, user_vector in enumerate(P): ax.annotate( f"User {user_index+1}", xy=(0, 0), xytext=user_vector, arrowprops=dict(arrowstyle="<-", color="blue"), ) for item_index, item_vector in enumerate(Q): ax.annotate( f"Item {item_index + 1}", xy=(0, 0), xytext=item_vector, arrowprops=dict(arrowstyle="<-", color="orange"), )
上記コードで得られるのは、前回示した次の図を45°時計回りに回転させた結果になります(実は前回も著者が回転を施していました)。行列分解では\(P\),\(Q\)を同時に回転させても結果は変わらないので、このようなことが可能になります。