1.简介
要说当今社会最火的行业,当属互联网行业。互联网行业的营收主要由广告收入和用户付费组成。这两项都离不开推荐系统,广告需要给不同用户推荐感兴趣的内容,实现精准营销,而用户付费如视频网站等则需要推荐用户喜欢的内容,增加客户粘性。
作为大数据的典型应用,今天我们来谈谈推荐系统,首先我们简述一下推荐系统的典型算法,并使用业界闻名的Netflix竞赛数据集来实现算法。
问题定义:给定用户行为矩阵X,X为m*n的矩阵,其中m为用户数,n 为内容数。已知X中的一部分值,如何猜测未知值?
2.常用算法
常用算法主要有以下三种
(1)基于内容的算法
给用户推荐与之前该用户评分高的项目相似的项目
构建项目向量:选择一系列的属性,比如电影可分类为恐怖片、爱情片等,并在这些属性上为项目评分,如恐怖属性5分代表惊悚属性最高,从而得知用户喜欢那种类型的项目
优点:不需考虑其他用户、可以推荐符合用户独特品味的项目、易于解释
缺点:很难找到合适的特征、冷启动问题、只能推荐用户喜欢的项目
(2)协同过滤算法
为新用户做推荐时,找到相似的用户喜欢的内容,称为用户-用户协同过滤
确定新内容推荐给哪些用户时,找到相似的内容喜欢的用户,称为内容-内容协同过滤
相似度度量:
(1)Jaccard similarity: 忽视用户评分,只考虑是否看过
(2)Cosine similarity: 使用cos函数度量两个用户的距离,未评分的项目有误差
(3)Pearson correlation: 去均值后度量cosine距离
优点:无需选择特征
缺点:冷启动问题、用户矩阵是稀疏的、一些小众项目无法被推荐
(3)矩阵分解算法
原始的用户-项目m*k矩阵可以分解为两个新矩阵,维度分别为m*k和n*k,原始的矩阵是个稀疏矩阵,通过原有数据进行学习,使用梯度下降法,最终可求出分解后的矩阵,而原始矩阵中的缺失项可以通过训练出的两个矩阵乘积计算
3.代码实现
数据集采用Netflix推荐竞赛的一个子集,包含10000个用户和10000个电影,具体的文件格式如下
(1) 用户列表 users.txt
文件有 10000 行,每行一个整数,表示用户的 id,文件对应本次 Project 的所有用户。
(2) 训练集 netflix_train.txt
文件包含 689 万条用户打分,每行为一次打分,对应的格式为: 用户 id 电影 id 分数 打分日期 其中用户 id 均出现在 users.txt 中,电影 id 为 1 到 10000 的整数。各项之间用空格分开
(3) 测试集 netflix_test.txt
文件包含约 172 万条用户打分,格式与训练集相同。
3.1 数据预处理
将输入文件整理成维度为用户*电影的矩阵 ,其中 对应用户 对电影 的打分
# 导入包
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 导入数据
user = pd.read_csv("users.txt", names = ['userid'])
netflix_train = pd.read_csv("netflix_train.txt", sep = ' ', names = ['user_id', 'film_id', 'rating', 'date'])
netflix_test = pd.read_csv("netflix_test.txt", sep = ' ', names = ['user_id', 'film_id', 'rating', 'date'])
# 给用户从零开始进行编号
user['id'] = range(len(user))
netflix_train = netflix_train.merge(user, left_on='user_id', right_on='userid')
netflix_test = netflix_test.merge(user, left_on='user_id', right_on='userid')
# 通过数据透视函数构建用户*电影矩阵
X_train = netflix_train.pivot(index='id', columns='film_id', values='rating')
X_test = netflix_test.pivot(index='id', columns='film_id', values='rating')
# 测试集缺失部分电影,补齐为10000*10000矩阵
for i in range(1, 10001):
if i not in X_test.columns:
X_test[i] = np.nan
X_test = X_test.sort_index(axis=1)
# 查看输出的用户*电影矩阵
print(X_train.head())
print(X_test.head())
3.2 基于用户-用户协同过滤算法的实现
cosine相似度公式:
评分计算:
注意,此处对于未知值的计算,选择与该用户最相近的k个对此项目已评分的用户进行加权平均
(1)首先用argsort()函数求出与用户i最相似的用户,按照相似度倒序排列成列表indexs
(2)其次按照列表indexs进行遍历,找出看过此电影的相似度排名前三的用户并计算对电影评分的加权平均值作为该用户的评分
考虑到一些局部效应的存在,这里对原始算法进行了一些改进,如下图所示
# Collaborate Filtering
# Compute the overall mean and mean by row and column
mu = np.mean(np.mean(X_train))
bx = np.array(np.mean(X_train, axis=1) - mu)
by = np.array(np.mean(X_train, axis=0) - mu)
# Compute the similarity matrix
X = X_train.sub(bx+mu, axis=0) # Demean
X = X.div(np.sqrt(np.sum(np.square(X), axis=1)), axis=0)
X.fillna(0, inplace=True)
similarity_matrix = np.dot(X, X.T)
# Compute the point matrix using CF
X_train = np.array(X_train.fillna(0))
for i in range(X_train.shape[0]):
indexs = np.argsort(similarity_matrix[i, :])[::-1]
for j in range(X_train.shape[1]):
if X_train[i, j] == 0:
sum = 0
num = 0
simi = 0
k = 0
while num < 3 & k < X_train.shape[1]: # top 3
if X_train[indexs[k], j] > 0:
sum = sum + similarity_matrix[i, indexs[k]] * (X_train[indexs[k], j] - mu - bx[indexs[k]] - by[j])
simi = simi + similarity_matrix[i, indexs[k]]
k = k+1
num = num + 1
else:
k = k+1
if simi != 0:
X_train[i, j] = mu + bx[i] + by[j] + sum/simi
else:
X_train[i, j] = mu + bx[i] + by[j]
else:
continue
# Compute RMSE for the algorithm
RMSE = np.sqrt(np.sum(np.sum(np.square(X_train - X_test)))/netflix_test.shape[0])
print(RMSE)
最终计算得到的RMSE为1.013,基线误差(预测得分全部取3的情况)为
,RMSE降低了28.3%
3.3 基于矩阵分解的算法
矩阵分解:
目标函数:
通过梯度下降算法迭代更新目标函数,获取最优分解矩阵U和V
# Matrix Decomposition
A = X_train > 0
X_train = np.array(X_train.fillna(0))
U = np.random.randn(10000, 100)*0.1
V = np.random.randn(10000, 100)*0.1
alpha = 0.0001
lamda = 1
# Gradient Descent
J = np.zeros((1000))
RMSE = np.zeros((1000))
for i in range(200):
dU = np.dot(np.multiply(A, (np.dot(U, V.T) - X_train)), V) + 2 * lamda * U
dV = np.dot(np.multiply(A, (np.dot(U, V.T) - X_train)), U) + 2 * lamda * V
old_U = U
old_V = V
U = U - alpha/(1+0.1*i) * dU # Learning rate decay
V = V - alpha/(1+0.1*i) * dV
J[i, 0] = 1/2*np.sum(np.sum(np.square(np.multiply(A, (X_train - np.dot(U, V.T)))))) + lamda * np.sum(np.sum(np.square(U)))\
+ lamda * np.sum(np.sum(np.square(V)))
RMSE[i, 0] = np.sqrt(np.sum(np.sum(np.square(np.dot(U, V.T) - X_test)))/netflix_test.shape[0])
print(i)
# Visualization
X = np.dot(U, V.T)
plt.plot(range(1000), RMSE[:, 0])
plt.show()
plt.plot(range(1000), J[:, 0])
plt.show()
print(RMSE[999])
如下图所示为按照上述参数迭代1000次后的结果,随着迭代次数的增多,RMSE显著下降,最后RMSE为1.123,相比于基线误差降低了20.6%
矩阵分解的算法收敛效果与模型中的正则项系数
与矩阵维度k是有关,可以尝试不同的参数组合,通过RMSE与目标函数值来确定最优参数组合,目标函数值随着迭代次数的变化如下图所示,尝试的参数组合分别为
=1, 0.1 以及 矩阵维数k=100, 50, 10 共2*3=6种,每种参数组合迭代200次,迭代结果如下图所示,可以选择收敛最快的参数组合进行训练
4.总结
从上文我们可以看出推荐系统的核心问题是确定用户-内容矩阵(Utility Matrix)
(1)收集已知矩阵信息
通过让用户打分或者收集用户的行为数据
(2)从已知矩阵推测未知矩阵信息
通过基于内容的方法、协同过滤方法或者矩阵分解方法推测未知矩阵信息
(3)评价推测方法
常用的标准是RMSE均方根误差
本文的所有代码以及数据集见下面Github链接,大家可以自行下载取用