每天,全世界的用户都会收到大量不同的邮件-仅通过MailChimp服务每天发送十亿封信 。 其中,发现了20.81% 。
每个月,我们网站的用户都会收到新闻通讯以及编辑选择的材料。 大约21%的读者打开这些信件。
为了增加此数量,可以使它们个性化。 一种方法是添加推荐系统,该系统将提示特定读者感兴趣的材料。
在本文中,我将讨论如何基于协作过滤从头实现推荐系统。
本文的第一部分包含了实施推荐系统的理论基础。 学校数学足以理解材料。
第二部分介绍了我们的站点数据的Python实现。
一点协同过滤理论
协作过滤可能是推荐系统中最简单的方法。 它基于这样的想法,即相似的用户喜欢相似的对象,例如文章。
“相似用户”是什么意思?

如何确定Vasily像Ivan或SQL Server到PostgreSQL的多少?
让我们来看一个例子。 假设我们有四个用户:Vasily,Ivan,Inna和Anna。 该站点有五篇文章:第1条,第2条,第3条,第4条和第5条。在下表中,用户与文章相交处的数字是用户对文章的五分制评分。 表中的零表示用户未对文章进行评分。 例如,瓦西里(Vasily)喜欢第1、3和4条。
表1
凭直觉,我们可以假设,如果用户喜欢相同的文章,那么他们的口味是一致的。 您怎么看,其利益与瓦西里的利益相似?
瓦西里(Vasily)的利益与伊万(Ivan)和因纳(Inna)的利益更相似,而与安娜(Anna)的利益相似。 为什么-会进一步告诉您。
为了进行进一步的工作,有必要形式化和衡量Vasily和Ivan或Inna和Anna的“相似性”。
最简单的方法是将用户评分视为对其个人资料的描述。 在示例中,表中的每一行都是一个用户的描述。 第一行-Basil的描述-是五个数字的向量:[4,0,5,5,0]; 第二个-伊万-[0,0,4,5,0]; 第三个是Inna-[4,2,4,0,0]; 第四位是安娜-[5,5,0,0,5]。
现在,您可以介绍“相似性度量”用户描述的概念。
衡量用户“相似度”的一种方法是计算描述用户的向量之间的余弦距离 。

余弦距离通过以下公式计算:
在哪里 和 -用户描述向量; -描述向量的标量积; , -描述向量的长度。
余弦距离的含义如下:如果两个向量 和 (用户描述向量)“相似”,则它们之间的角度将趋于零,并且该角度的余弦将趋于统一。 在理想情况下,当两个用户的“兴趣”一致时,他们的余弦距离将为零。
Vasily和Ivan之间的余弦距离:
同样,瓦西里(Vasily)和安娜(Anna)之间的余弦距离为0.715。 也就是说,瓦西里(Vasily)的利益更像伊万(Ivan)的利益,而不是安娜(Anna)的利益。
如何预测用户评分?
这部分是最有趣的。 有很多不同的选择。 下面我们考虑两个简单的选项。
预测评分-“相似”用户的平均评分
计算预测评分的最简单方法是查看“相似”用户对文章的评分,并取平均评分:
在此公式中:
- 是预测的估计 文章和用户 ,
- -用户评分 为 文章
-很多“相似”用户,- -“相似”用户的数量。
预测评分-“相似”用户的加权平均评分
一个稍微复杂些的选择是考虑相似度:更多相似用户的评级比不那么相似用户的评级对最终评级的影响更大:
在此公式中:
- 是预测的估计 文章和用户 ,
- -用户评分 为 文章
-很多“相似”用户,- -用户的“相似度”(余弦距离) 和 。
如何衡量建议的质量?

在创建任何推荐系统时,您应该确定可用来评估模型质量的指标-系统向用户提供新材料的程度。 例如,均方根误差( )是所有用户评分的平均误差的平方根。 正式地,此度量由以下公式描述:
在这个公式中
- -用户对文章的所有评分的集合,
- -预测的用户评分 文章 ,
- -真实用户评分 文章 。
在理想情况下,当预测的收视率与用户的 等于零。
考虑一个例子。 两个参考系统对瓦西里进行了预测。 结果在下表中。
从直觉上很明显,第二推荐系统比第一推荐系统更好地预测收视率。 数 :
预计第二推荐系统的评估误差会大大降低。
实作
我们可以使用网站上的文章和用户的大部分数据:文章,标签,用户喜欢的信息等。
要实施协作过滤,用户评级就足够了。
免责声明在下文中,代码被写为“前额”以演示推荐系统的逻辑。 在现实生活中,最好使用numpy
和pandas
所有功能。
import pandas as pd import numpy as np import os ratings_df = pd.read_csv('./input/Ratings.csv') print(' :', ratings_df.shape[0]) print(' :', ratings_df[ratings_df['Rate']].shape[0]) unique_user_ids = ratings_df[ratings_df['Rate']]['UserId'].unique() print(' :', len(unique_user_ids)) ratings_df.head()
输出[1]资料总数:15313
好评:15121
活跃用户:1007
1007位活跃用户给出了15313个“评分”。 其中有15121个“喜欢”。
数据包含四列:数据库中的行标识符( Id列),对象标识符( DocumentId列),用户喜欢文章的符号( Rate列)和用户标识符( UserId列)。
为了方便起见,添加列RateInt 。 此列中的1表示用户喜欢该文章; -1-不喜欢。
ratings_df['RateInt'] = ratings_df['Rate'].apply(lambda x: 1 if x else -1) ratings_df.head()
为了进行进一步的工作,需要将数据集分为训练和测试:训练将用于训练模型,而测试将确定预测的质量。
from sklearn.model_selection import train_test_split train, test = train_test_split(ratings_df, test_size=0.2)
为了方便起见,我们将每个集合转换为一个表格,其中的行类似于用户的标识符,而列的则与文章开头的示例类似,是文章的标识符。
def create_matrix(df): ratings_per_user = [] post_ids = df['DocumentId'].unique() for user_id in tqdm_notebook(all_users_ids, ''): row = {'user_id': user_id} ratings = df[df['UserId'] == user_id]['DocumentId'].values for post_id in post_ids: row[str(post_id)] = 1 if post_id in ratings else 0 ratings_per_user.append(row) return pd.DataFrame(ratings_per_user) train_df = create_matrix(train) test_df = create_matrix(test)
匹配用户和喜欢的文章的矩阵可让您计算用户之间的余弦距离:
from scipy import spatial def cos_distance(x1, x2): return spatial.distance.cosine(x1, x2) at_least_one_fav_post_users = list(train_valuable_df['user_id'].values) def calculate_distances(df): columns = df.columns[:-1] cp = at_least_one_fav_post_users.copy() data = [] for user_id_1 in tqdm_notebook(at_least_one_fav_post_users, ''): row = {'user_id': user_id_1} for user_id_2 in cp: x1 = df[df['user_id'] == user_id_1][columns].values[0] x2 = df[df['user_id'] == user_id_2][columns].values[0] row[str(user_id_2)] = cos_distance(x1, x2) data.append(row) return pd.DataFrame(data) train_distances = calculate_distances(train_valuable_df)
现在一切准备就绪,可以提示用户他们喜欢的文章。
我们采用两种策略来计算上述建议:相似用户之间的平均评分和加权平均评分。
第一种方式
我们采用10位最接近当前用户的用户,并预测该文章的相似用户的平均评分:
from tqdm import tqdm_notebook import heapq def rmse(predicted, actual): return ((predicted - actual) ** 2).mean() ** 0.5 def get_similar(id, n): df = train_distances[train_distances['user_id'] == id] d = df.to_dict('records')[0] top_similar_ids = heapq.nsmallest(n+1, d, key=d.get) top_similar = df[top_similar_ids] return top_similar.to_dict('records')[0] def get_predictions(id, n): top_similar_users = get_similar(id, n) top_similar_users_ids = list([int(x) for x in top_similar_users.keys()]) ratings_for_top_similar = train_df[train_df['user_id'].isin(top_similar_users_ids)] predicted_ratings = {} for article_id in train_df.columns[:-1]: predicted_ratings[article_id] = ratings_for_top_similar[article_id].mean() return predicted_ratings rand_n_users = train_distances.sample(50)['user_id'].values err = 0 for u in tqdm_notebook(rand_n_users): pred = get_predictions(u, 10) err += rmse(test_df[test_df['user_id'] == u][list(pred.keys())].values, pd.DataFrame(pred, index=[0]).values) print(err / len(rand_n_users))
对于第一种方法,我们得到的误差等于0.855。
第二种方式
第二种方法考虑了用户的相似度。 它的实现几乎与第一个相同:
def get_predictions(id, n): similar_users = get_similar(u, 10) prediction = {} user_ids = list(similar_users.keys()) user_similarities = [] for user_id in user_ids: user_similarities.append(similar_users[user_id]) predicted_ratings = {} for article_id in train_df.columns[:-1]: prediction_for_article = 0 numerator = 0 denominator = 0 for user_id in user_ids: rating = train_df[train_df['user_id'] == int(user_id)][article_id].values[0] numerator += rating * (1 - similar_users[user_id]) denominator += np.abs(similar_users[user_id]) predicted_ratings[article_id] = numerator / denominator return predicted_ratings err = 0 for u in tqdm_notebook(rand_n_users): pred = get_predictions(u, 10) err += rmse(test_df[test_df['user_id'] == u][list(pred.keys())].values, pd.DataFrame(pred, index=[0]).values) print(err / len(rand_n_users))
在这种情况下,他们的错误为0.866。 该错误比第一种情况稍大。
结果可以用于不同的场景。 例如,在每月新文章的新闻通讯中,或在网站上添加“您可能感兴趣”部分。
总结
在本文中,我使用一个实际任务的示例进行了详细的尝试,以弄清楚如何构建基于协作过滤的推荐系统。
这种方法的优点是它的多功能性-建议未考虑建议的对象。 一个系统可以用于博客文章和在线商店中的产品。
缺点包括:
- 在有大量推荐对象的情况下,用户-对象矩阵变得稀疏,并且很难找到足够相似的用户(更少的用户-对象对匹配)
- 冷启动问题-新用户不可能找到相似的用户(有一些策略可以规避此限制,但不是万能药)
- 基于协作过滤的系统倾向于推荐受欢迎的对象,因为 绝大多数用户会喜欢这些对象。
在下一篇文章中,将考虑另一种方法-基于对对象本身的分析。