參考地址:
貪心學院:https://github.com/GreedyAIAcademy/Machine-Learninggit
推薦系統:最著名的就那個爛大街的啤酒和尿布的故事,還有如今頭條的投喂用戶使用的也是推薦系統。就很少說了。github
設,矩陣R表明3個用戶對4部影片的評分,矩陣U和P是經過算法分解出來的矩陣,R是預測出來的矩陣。
此時咱們能夠看出, 矩陣R中的值很接近原始矩陣R中的值,這樣填補以後的值就是咱們要的數字。算法
如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個物品的畫像向量。函數
爲了方便求導,咱們乘個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} \\ \]
設定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}} \\ \]
看了上面的公式確定是只知其一;不知其二的,但看了矩陣分解函數,就會對梯度降低問題的解決方法豁然開朗
代碼:
# 導入 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