機器學習筆記7:矩陣分解Recommender.Matrix.Factorization

參考地址:
貪心學院:https://github.com/GreedyAIAcademy/Machine-Learninggit

1矩陣分解概述

1.1用在什麼地方

推薦系統:最著名的就那個爛大街的啤酒和尿布的故事,還有如今頭條的投喂用戶使用的也是推薦系統。就很少說了。github

1.2推薦的原理

設,矩陣R表明3個用戶對4部影片的評分,矩陣U和P是經過算法分解出來的矩陣,R是預測出來的矩陣。
此時咱們能夠看出, 矩陣R
中的值很接近原始矩陣R中的值,這樣填補以後的值就是咱們要的數字。算法

2矩陣分解的原理

2.1目標函數

如1.2所示,咱們但願的結果就是R*中的結果與R中的結果差值最小。
所以咱們能夠獲得目標函數:
\[ arg \min_{U,P} \sum_{(i,j) \in Z}(R_{ij}-U_{i}P_{j}^{T})^2 \\ \\ Z = \{(i,j):r_{ij} 已知\} \]dom

\(U_i P_j\)爲行向量,分別來自於矩陣U和矩陣P的第i行和第j行;分別表明了第i個用戶的畫像向量,和第j個物品的畫像向量。函數

2.2 損失函數

爲了方便求導,咱們乘個1/2,結果以下:
\[ arg \min_{U,P} \sum_{(i,j) \in Z}\frac{1}{2}(R_{ij}-U_{i}\cdot P_{j})^2 \\ \\ Z = \{(i,j):r_{ij} 已知\} \]
繼續計算結果以下:
\[ L_{ij} = \frac{1}{2}(R_{ij}-U_{i}\cdot P_{j})^2 \\ \]學習

獲得損失梯度以下:測試

\[ \frac{\partial L_{ij}}{\partial U_{i}}= \frac{\partial }{\partial U_{i}} [\frac{1}{2}(R_{ij}-U_{i}\cdot P_{j})^2] = -P_j(R_{ij}-U_{i}\cdot P_{j}) \\ \\ \frac{\partial L_{ij}}{\partial P_{j}}= \frac{\partial }{\partial P_{j}} [\frac{1}{2}(R_{ij}-U_{i}\cdot P_{j})^2] = -U_i(R_{ij}-U_{i}\cdot P_{j}) \]spa

爲了防止過擬合和訓練過程當中的偏差,加入正則項code

\[ arg \min_{U,P} \sum_{(i,j) \in Z}\frac{1}{2}(R_{ij}-U_{i}\cdot P_{j})^2 + \lambda [ \sum_{i=1}^{m}\left \| U_i \right \|^2 + \sum_{i=1}^{n}\left \| P_j \right \|^2] \]

再求偏導可得:
\[ \frac{\partial L_{ij}}{\partial U_{i}}=-P_{j}(R_{ij}-U_{i}\cdot P_{j}) + \lambda U_{i} \\ \\ \frac{\partial L_{ij}}{\partial P_{j}}=-U_{i}(R_{ij}-U_{i}\cdot P_{j}) + \lambda P_{j} \\ \]

2.3 經過梯度降低的方法求得結果

設定k的值,設定學習步長\(\gamma\)(learning rate),初始化U和P,重複如下步驟直到均方差滿意爲止:
遍歷Z中的(i,j),Z={(i,j):\(r_{ij}\)已知}
\[ U_{i}\leftarrow U_{i} - \gamma \frac{\partial L_{ij}}{\partial U_{i}} \\ P_{j}\leftarrow P_{j} - \gamma \frac{\partial L_{ij}}{\partial P_{j}} \\ \]

3 代碼實現

看了上面的公式確定是只知其一;不知其二的,但看了矩陣分解函數,就會對梯度降低問題的解決方法豁然開朗
代碼:

# 導入 nunpy 和 surprise 輔助庫
import numpy as np
import surprise  

# 計算模型
class MatrixFactorization(surprise.AlgoBase):
    '''基於矩陣分解的推薦.'''
    
    def __init__(self, learning_rate, n_epochs, n_factors, lmd):
        
        self.lr = learning_rate  # 梯度降低法的學習率
        self.n_epochs = n_epochs  # 梯度降低法的迭代次數
        self.n_factors = n_factors  # 分解的矩陣的秩(rank)
        self.lmd = lmd # 防止過擬合的正則化的強度
        
    def fit(self, trainset):
        '''經過梯度降低法訓練, 獲得全部 u_i 和 p_j 的值'''
        
        print('Fitting data with SGD...')
        
        # 隨機初始化 user 和 item 矩陣.
        u = np.random.normal(0, .1, (trainset.n_users, self.n_factors))
        p = np.random.normal(0, .1, (trainset.n_items, self.n_factors))
        
        # 梯度降低法
        for _ in range(self.n_epochs):
            for i, j, r_ij in trainset.all_ratings():
                err = r_ij - np.dot(u[i], p[j])
                # 利用梯度調整 u_i 和 p_j
                u[i] -= -self.lr * err * p[j] + self.lr * self.lmd * u[i]
                p[j] -= -self.lr * err * u[i] + self.lr * self.lmd * p[j]
                # 注意: 修正 p_j 時, 按照嚴格定義, 咱們應該使用 u_i 修正以前的值, 可是實際上差異微乎其微
        
        self.u, self.p = u, p
        self.trainset = trainset

    def estimate(self, i, j):
        '''預測 user i 對 item j 的評分.'''
        
        # 若是用戶 i 和物品 j 是已知的值, 返回 u_i 和 p_j 的點積
        # 不然使用全局平均評分rating值(cold start 冷啓動問題)
        if self.trainset.knows_user(i) and self.trainset.knows_item(j):
            return np.dot(self.u[i], self.p[j])
        else:
            return self.trainset.global_mean
            
# 應用
from surprise import BaselineOnly
from surprise import Dataset
from surprise import Reader
from surprise import accuracy
from surprise.model_selection import cross_validate
from surprise.model_selection import train_test_split
import os

# 數據文件
file_path = os.path.expanduser('./ml-100k/u.data')

# 數據文件的格式以下:
# 'user item rating timestamp', 使用製表符 '\t' 分割, rating值在1-5之間.
reader = Reader(line_format='user item rating timestamp', sep='\t', rating_scale=(1, 5))
data = Dataset.load_from_file(file_path, reader=reader)

# 將數據隨機分爲訓練和測試數據集
trainset, testset = train_test_split(data, test_size=.25)

# 初始化以上定義的矩陣分解類.
algo = MatrixFactorization(learning_rate=.005, n_epochs=60, n_factors=2, lmd = 0.2)

# 訓練
algo.fit(trainset)

# 預測
predictions = algo.test(testset)

# 計算平均絕對偏差
accuracy.mae(predictions)

#結果:0.7871327139440717

# 使用 surpise 內建的基於最近鄰的方法作比較
algo = surprise.KNNBasic()
algo.fit(trainset)
predictions = algo.test(testset)
accuracy.mae(predictions)

#結果:0.7827160139309475

# 使用 surpise 內建的基於 SVD 的方法作比較
algo = surprise.SVD()
algo.fit(trainset)
predictions = algo.test(testset)
accuracy.mae(predictions)

#結果:0.7450633876817936
相關文章
相關標籤/搜索