Deep Karmaning

技術系の話から日常のことまで色々と書きます

PythonでBias付きのMatrix Factorizationを実装してみる

概要

今回はMatrix FactorizationをPythonで実装してみたいと思います。

Bias付きのものとそうでないものを実装して精度を簡単に比較します。

通常のMatrix Factorizationとバイアス付きのMatrix Factorization

それではMatrix Factorizationに関して少し具体的に説明します。

ちなみに今回参考にした記事は以下です。

PyTorchでもMatrix Factorizationがしたい! | takuti.me

qiita.com

hightensan.hateblo.jp

Matrix Factorizationとは

Matrix Factorizationは主にレコメンドでよく使われる機械学習アルゴリズムの一つです。

使い方としてはユーザーのアイテムに対する評価等の情報を元に、まだ評価してないアイテムから、評価するとしたら評価が高くなるであろうものをレコメンドするといったような使い方が想像出来ると思います。

実際のイメージとしては、以下の図の左側の用に縦軸にユーザー、横軸にアイテム、セルの値がユーザーのアイテムに対する評価値というデータがあったとします。

f:id:rf00:20180520173827p:plain

それをユーザー側の潜在変数(P)、アイテム側の潜在変数(Q)に分解するのがMatrix Factorizationです。

ここで各々の潜在変数は内積をとったときの値が実際の評価値に近づくようにパラメータを求めます

数式で表すと以下です。


{min_{P, Q} \sum_{(u, i) \in R} (r_{u,i} - \vec{P^{T}_{u}}\vec{Q_{i}})^2 + \lambda ( ||\vec{P_u}||^{2} +  ||\vec{Q_i}||^{2} )}

このようにパラメータを求めることで、内積をとったときに実際にユーザーとアイテムの組み合わせで、評価値が入っているところは実際の評価値に、そうでないところは仮にユーザーが評価した場合の予測値が求まる入まるのでこのスコアを使ってレコメンドが出来ることになります。

数式で表現すると以下になり、

{\hat{r}_{u, i} = \vec{P^{T}_{u}}\vec{Q_{i}} }

で求めることが出来ます。

パラメータは確率的勾配法で更新することができ、


{e_{u, i} = r_{u, i} - \vec{P^{T}_{u}}\vec{Q_{i}} \\
P^{’}_{u,k} = P_{u,k} + \alpha * (e_{u,i} * Q_{i,k} - \lambda * P_{u,k}) \\
Q^{‘}_{i,k} = Q_{i,k} + \alpha * (e_{u,i} * P_{u,k} - \lambda * Q_{i,k})
}

のように行うと良いようです。 ここでのαは学習率、λは正則化パラメータになります。

Bias付きのMatrix Factorizationに関して

Bias付きのMatrix Factorizationでは、これまで見てきたMatrix Factorizationに、複数のbias項を追加した物になります。こちらの方がユーザーやアイテムの偏りを考慮できるので精度が上がるようです。

具体的にユーザー×アイテムの評価行列を例に説明すると、全ユーザーの評価のbias項(β)、ユーザー毎のbias項(β_u)、アイテム毎のbias項(β_i)を追加します。

最小化する数式を表現すると、


{min_{P, Q} \sum_{(u, i) \in R} (r_{u,i} - \hat{r}_{u, i} )^2 + \lambda (  ||\vec{b_u}||^{2} + ||\vec{b_i}||^{2} + ||\vec{P_u}||^{2} +  ||\vec{Q_i}||^{2} )}

となります。

各種bias項を追加した際の予測値は、

{\hat{r}_{u, i} = \beta + \beta_{u} + \beta_{i} +\vec{P^{T}_{u}}\vec{Q_{i}} }

で求めます。

確率的勾配法での各パラメータの更新は、Matrix Factorizationの式に加えユーザーとアイテムのbaisだけ更新します。


{e_{u, i} = r_{u, i} - \vec{P^{T}_{u}}\vec{Q_{i}} \\
b^{’}_{u} = b_{u} + \alpha * (e_{u,i} - \lambda * b_{u}) \\
b^{‘}_{i} = b_{i} + \alpha * (e_{u,i} - \lambda * b_{i}) \\
P^{’}_{u,k} = P_{u,k} + \alpha * (e_{u,i} * Q_{i,k} - \lambda * P_{u,k}) \\
Q^{‘}_{i,k} = Q_{i,k} + \alpha * (e_{u,i} * P_{u,k} - \lambda * Q_{i,k}) \\
}

実装

それでは実際にPythonで実装していきます。

精度検証用のデータはMovie Lensの100kデータセットを使うので別途ダウンロードしましょう。

grouplens.org

今回使った全体のコードは以下です。

python_mf.ipynb · GitHub

Matrix Factorizationの実装

全体のコードを詳しくは見ずに、ポイントとなりそうなところを説明します。

まずは学習を走らせる部分は以下のようにします、

    def fit(self, X, n_user, n_item, n_iter = 100):
        self.R = X.copy()
        self.samples = X.copy()

        self.user_factors = np.random.rand(n_user, self.K)
        self.item_factors = np.random.rand(n_item, self.K)
                
        #stochastic gradient descent 
        self.loss = []
        for i in range(n_iter):
            self.sgd()
            mse = self.mse()
            self.loss.append((i, mse))  

このfitさせる際の引数Xはデータになります。

そしてX[user, item, rate]の形式で評価がついている組み合わせのデータのみをnumpy arrayでいれる形式にしています(詳細は全体のコードを確認してください)。

パラメータ更新のためのsgd関数は、

    def sgd(self):
        np.random.shuffle(self.samples)
        for user, item, rating in self.samples:
            err = rating - self.predict_pair(user, item)  
            
            # update parameter
            self.user_factors[user] += self.alpha * (err * self.item_factors[item] - self.beta * self.user_factors[user])
            self.item_factors[item] += self.alpha * (err * self.user_factors[user] - self.beta * self.item_factors[item])     

となり、予測値と実績値の乖離が少なくなるようにパラメータを動かしていきます。

Bias付きMatrix Factorizationの実装

次にBias付きMatrix Factorizationですが、通常のMatrix Factorizationとの差異は以下です。

  • bias項の用意

8~10行目のようにbias項を用意します。

    def fit(self, X, n_user, n_item, n_iter = 100):
        self.R = X.copy()
        self.samples = X.copy()

        self.user_factors = np.random.rand(n_user, self.K)
        self.item_factors = np.random.rand(n_item, self.K)
        
        self.bias_u = np.zeros(n_user)
        self.bias_i = np.zeros(n_item)
        self.bias = np.mean(X[:, 2])
        
        #stochastic gradient descent         
        self.loss = []
        for i in range(n_iter):
            self.sgd()
            mse = self.mse()
            self.loss.append((i, mse))
  • bias項を更新

sgd関数は以下のようにして、7行目、8行目のようにbias項を更新します。全体のバイアスは更新しません。

    def sgd(self):
        np.random.shuffle(self.samples)
        for user, item, rating in self.samples:
            err = rating - self.predict_pair(user, item)
            
            # update parameter
            self.bias_u[user] += self.alpha * (err - self.beta * self.bias_u[user])
            self.bias_i[item] += self.alpha * (err - self.beta * self.bias_i[item])
            
            self.user_factors[user] += self.alpha * (err * self.item_factors[item] - self.beta * self.user_factors[user])
            self.item_factors[item] += self.alpha * (err * self.user_factors[user] - self.beta * self.item_factors[item])            

ここまでで主な実装部分になります。

精度比較

それでは最後に精度比較をしてみましょう。

先ほど書いたようにMovie Lensの100kデータを用います。

では最初にデータを読み込みます。関数は以下。

import pandas as pd

def load_ml100k():
    samples = pd.read_csv('data/ml-100k/u.data', sep = '\t', header=None)
    
    samples = samples.iloc[:, :3]
    samples.columns = ['user', 'item', 'rate']
    
    samples['user'] = samples['user'] - 1
    samples['item'] = samples['item'] - 1
    
    return samples

実際の読み込みは以下のようにします。8割を学習用、2割を精度確認用としています。

df = np.array(load_ml100k())

n_user = np.unique(df[:, 0]).max() + 1
n_item = np.unique(df[:, 1]).max() + 1
n_rate = np.unique(df[:, 2]).max()

random.shuffle(df)
train_size = int(df.shape[0] * 0.8)
train_df = df[:train_size]
test_df = df[train_size:]

それでは、潜在変数の次元のKを20、パラメータ更新回数を20として学習させてみます。

まずは通常のMatrix Factorizationを実施、

#Matrix Factorization
MF = MatrixFactorization(K = 20, alpha = 0.01, beta = 0.5)
MF.fit(train_df, n_user, n_item, n_iter = 10)

pre = MF.predict(test_df)
ret1 = np.hstack((test_df, np.array(pre).reshape(-1, 1)))
np.sqrt(pow((ret1[:, 2] - ret1[:, 3]), 2).mean())

精度は、

1.0650956199556463

でした。

一方でBias付きのMatrix Factorizationというと、

#Bias Matrix Factorization
BMF = BiasMatrixFactorization(K=20, alpha = 0.01, beta = 0.5)
BMF.fit(train_df, n_user, n_item, n_iter = 10)

pre2 = BMF.predict(test_df[:, :2])
ret2 =np.hstack((test_df, np.array(pre2).reshape(-1, 1)))
np.sqrt(pow((ret2[:, 2] - ret2[:, 3]), 2).mean())

精度は、

0.962453605676165

bias付きのMatrix Factorizationのほうが高く出ているのでうまくいっていそうです。

まとめ

PythonでMatrix Factorizationは結構簡単に実装することが出来ました。

一見数式を見ると難しく見えるのですが、数式でひるまずに確率的勾配法でのパラメータの更新式が理解できさえすれば、実装はそこまで難しくないです。

またbiasをつけることもなかなか簡単に行うことができました。

これでMatrix Factorizationを少し理解できたので、今後様々な拡張も見ていけたらとおもいます。

間違い等ありましたらご指摘お願いいたします。