datacastle上的一道推荐算法竞赛(这里是地址和数据),由于最近想整理和汇总最常用的推荐算法。因此干脆就把这个竞赛拿出来实战一下。
1. 赛题 & 数据解析
本次比赛是一个名副其实的推荐算法大赛,在本次比赛中,我们提供了一个商品网站中大约16万名用户在四年内对商品的评分数据,每条评分记录都有时间戳(隐匿了具体时间,只保证顺序不变)。评分分为5级,1分最低,5分最高。
这是训练集,其中四列分别代表用户编号,物品编号,评分,时间。
这是测试集,只有两列用户编号,物品编号,需要预测分数:
2. 基于人工规则模型
基于人工规则的模型可以设计出很多,其特点是简单,鲁棒:
# -*- coding: utf-8 -*-
""" 基于各种指标对用户, 物品分类, 然后取均值进行预测
1. 基于用户对物品评分的均值来分类, 然后预测;
1.1 根据用户对train物品的评分来分类train, test中的用户;
1.2 由于对用户进行了分类, 那么对于同一种物品, 在不同的簇下得分就不同;
1.3 从而就可以确定[物品, 簇]的得分;
"""
import numpy as np
import pandas as pd
import data
train = data.load_data("train")
test = data.load_data("test")
# 求平均分
score_mean = train.groupby(["uid"], as_index=False)["score"].agg({"score_mean": "mean"})
score_mean["cluster"] = np.array(score_mean["score_mean"] * 2, np.int)
score_mean = score_mean[["uid", "cluster"]]
# 基于用户, 对train, test添加cluster列
train = pd.merge(train, score_mean, on="uid", how="left")
test = pd.merge(test, score_mean, on="uid", how="left")
# 基于cluster对物品打分
item_mean_score = train.groupby(["iid", "cluster"], as_index=False).mean()
result = pd.merge(test, item_mean_score[["iid", "cluster", "score"]], on=["iid", "cluster"], how="left")
result = result.fillna(3)
# 保存结果
data.save_data(result, "result_rule.csv")
3. 基于协同过滤模型
主要是基于物品的协同过滤算法,这个算法复杂度比较高,需要计算的东西很多。因此一般实际应用都是离线计算。主要注意两点:
1. 多使用字典,利于后期查询;
2. 对于两件不同的物品,可能对某个人而言,只评价过一种物品,另一种物品为空。此时计算距离,认为他们之间的距离为0,而不是两者之差。
这里给出一个之前实现的协同过滤推荐算法:http://blog.csdn.net/zk_j1994/article/details/77062091
# -*- coding: utf-8 -*-
""" 基于物品的协同过滤算法, 使用SVD分解对人进行降维
1. 有一个细节, 计算相似度时, 一个位置为空, 另一个位置有评价分数, 此时相似度视为0;
2. 算法复杂度很高, 需要离线计算;
3. 这里使用一个小数据集来进行示例;
4. 之所以使用SVD来对样本降维, 是由于推荐系统矩阵的稀疏性导致的, 事实上使用SVD不但可以减少
计算量, 还能提高推荐性能;
"""
import numpy as np
def load_data():
data = [[0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5],
[0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 3],
[0, 0, 0, 0, 4, 0, 0, 1, 0, 4, 0],
[3, 3, 4, 0, 0, 0, 0, 2, 2, 0, 0],
[5, 4, 5, 0, 0, 0, 0, 5, 5, 0, 0],
[0, 0, 0, 0, 5, 0, 1, 0, 0, 5, 0],
[4, 3, 4, 0, 0, 0, 0, 5, 5, 0, 1],
[0, 0, 0, 4, 0, 4, 0, 0, 0, 0, 4],
[0, 0, 0, 2, 0, 2, 5, 0, 0, 1, 2],
[0, 0, 0, 0, 5, 0, 0, 0, 0, 4, 0],
[1, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0]]
return np.array(data)
if __name__ == "__main__":
data = load_data()
u, sigma, v = np.linalg.svd(data)
sigma_4 = np.mat(np.eye(6) * sigma[0:6])
# 约简后的矩阵
simpler_data = np.dot(u[0:6, :], data)
"""
由于行被压缩, 因此计算物品相似度的时候, 计算复杂度大大降低了;
同时, 矩阵越稀疏, 这种先SVD降维, 再计算, 效果就越好
"""
4. 基于矩阵分解的模型
使用矩阵分解来对原矩阵进行恢复,但是这个数据量稍微大了点(用户 - 物品矩阵就有9G),我的本本跑不动。这里是一个例子:
# -*- coding: utf-8 -*-
"""
基于矩阵分解的推荐算法
1. 使用梯度下降进行迭代更新;
"""
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1)
def load_data():
data = [[0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5],
[0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 3],
[0, 0, 0, 0, 4, 0, 0, 1, 0, 4, 0],
[3, 3, 4, 0, 0, 0, 0, 2, 2, 0, 0],
[5, 4, 5, 0, 0, 0, 0, 5, 5, 0, 0],
[0, 0, 0, 0, 5, 0, 1, 0, 0, 5, 0],
[4, 3, 4, 0, 0, 0, 0, 5, 5, 0, 1],
[0, 0, 0, 4, 0, 4, 0, 0, 0, 0, 4],
[0, 0, 0, 2, 0, 2, 5, 0, 0, 1, 2],
[0, 0, 0, 0, 5, 0, 0, 0, 0, 4, 0],
[1, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0]]
return np.array(data)
def gradAscent(data, K, max_iter, alpha, beta):
""" 梯度下降更新P, Q矩阵的元素, 使均方误差最小 """
if not isinstance(data, np.matrix):
data = np.mat(data)
# 初始化P, Q矩阵
n, m = data.shape
P = np.mat(np.random.random((n, K)))
Q = np.mat(np.random.random((K, m)))
print("\nP = \n{0}".format(P))
print("\nQ = \n{0}".format(Q))
loss_list = []
_iter = 0
while _iter < max_iter:
# 更新P, Q中的每一个元素
for i in range(n):
for j in range(m):
if data[i, j] > 0:
error = (data[i, j] - P[i, :] * Q[:, j])[0, 0] # (i, j)处的误差
for k in range(K):
P[i, k] = P[i, k] + alpha * (2 * error * Q[k, j] - beta * P[i, k])
Q[k, j] = Q[k, j] + alpha * (2 * error * P[i, k] - beta * Q[k, j])
# 计算原矩阵和恢复矩阵之间的误差
loss = 0
for i in range(n):
for j in range(m):
if data[i, j]> 0:
for k in range(K):
loss += P[i, k] * Q[k, j]
loss = np.sum(abs(data[i, j] - loss))
loss_list.append(loss)
if loss <= 1e-3:
break
_iter += 1
return P, Q, loss_list
def draw_loss(loss):
plt.plot(range(len(loss)), loss)
plt.show()
if __name__ == "__main__":
data = load_data()
P, Q, loss = gradAscent(data, 5, 20000, 0.0002, 0.02)
print("\n恢复矩阵 = \n{0}".format(P * Q))
draw_loss(loss)
参考文献
http://blog.csdn.net/nihaoxiaocui/article/details/51974194
http://blog.csdn.net/datacastle/article/details/52190423