参考地址:
贪心学院: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