Python 人工智能实战:智能推荐
1.背景介绍
在信息爆炸的时代,人们通过多种渠道获取信息,享受信息带来的便利。如今,互联网服务的蓬勃发展已经为用户提供了一个便捷的使用环境。在信息爆炸的时代,用户不断产生新的需求,如何根据用户的兴趣、偏好以及其他相关因素,为其提供更符合用户需求的信息,成为互联网公司亟需解决的问题。传统的推荐系统主要依赖于用户的浏览历史和商品特征进行推荐,而现代推荐系统则通过机器学习和统计学等前沿技术实现了对海量数据的高效处理。协同过滤算法通过分析用户间的互动行为,识别出具有高度相似性的用户群体,并基于此向相似的用户推荐具有相似特性的商品。协同过滤算法的优势在于其能够精准地为用户推荐符合其兴趣和偏好的商品,但其在处理大规模数据时的计算复杂度较高,且可能无法全面捕捉到用户的个性化需求。尽管如此,该算法因其解释性、鲁棒性和高效性而受到广泛认可。
2.核心概念与联系
用户-物品矩阵
首先,我们需要构建一个用户-物品评分矩阵,该矩阵由n行m列的二维数组构成,其中n代表用户数量,m代表物品数量。每个单元格代表一个用户对一个物品的评分。如果某个用户未对某项物品进行评分,则该单元格标记为0。例如,在一个图书馆推荐系统中,每位用户对书籍的评分通常采用1到5分的评分系统,1分表示不喜欢,5分表示非常喜欢。值得注意的是,这里仅列出了一些基本评分信息,而真实情况下,用户的评分往往更加丰富和细致,涵盖了更多的情感和细节。
相似度计算
为确定相似度计算方法,我们首先需要进行定义。给定任意两个用户u和v,我们的目标是通过分析他们对不同物品的评价相似程度,从而判断这两个用户是否属于同一类别。为了实现这一目标,我们可以采用多种相似度计算方法。其中,欧氏距离(Euclidean distance)是最为基础的一种方法,它通过衡量两个向量之间的差距大小来评估相似性。另一种常用的方法是皮尔森相关系数(Pearson correlation coefficient),该方法侧重于评估两个变量间的线性相关关系。在基于电影的推荐系统中,由于物品通常具有固定的属性值,因此我们更倾向于使用余弦相似度(Cosine similarity)作为评估用户间相似度的标准。具体而言,其计算公式如下:
Euclidean distance
其中,u和v代表两个用户,m为物品总数;r_{ui}和r_{vi}分别表示用户u对物品i的评分值和用户v对物品i的评分值;d(u, v)则表示用户u与用户v之间的欧氏距离。
Pearson correlation coefficient
其中,ρ_uv表示用户u与用户v之间的皮尔逊相关系数,该值反映了用户u和用户v之间评分的一致性程度。协方差cov(r_u, r_v)则衡量了用户u和用户v在评分上的波动性关联。具体而言,σ_u和σ_v分别表示用户u和用户v的评分标准差,这些统计量共同构成了皮尔逊相关系数的计算公式。
Cosine similarity
其中,θ代表角度,等于arccos( (r_u·r_v) / (|r_u|·|r_v|) );其中,r_u和r_v分别表示用户u和用户v对所有物品的评分向量。基于用户-物品矩阵的三种相似度计算方法均采用了上述方式。这些方法均比较了两个用户的评分向量,并反映了两者之间的相似程度。当两个用户的评分向量越接近时,说明其兴趣越趋近,因此可以向这两个用户推荐具有相似特性的物品。
推荐策略
具体来说,推荐策略指的是,当用户提供了某个项目的(item)ID后,如何选择并推荐给该用户的其他项目。具体来说,推荐系统通常可以采用以下几种策略。
概率推荐(Probabilistic Recommendation)
概率推荐基于用户历史行为、社交网络、当前时间、位置等多维度信息,动态生成个性化推荐结果。在推荐系统中,最基本也最重要的策略,就是通过分析用户过去行为的关联性,推断出用户的兴趣点,并据此构建推荐列表。该方法的优势在于具有较高的准确性,且无需额外的训练数据。然而,其主要缺点在于无法充分反映用户的个性化需求。
内容-协同过滤推荐(Content-based Filtering Recommendation)
内容-协同过滤推荐基于用户的评分历史进行物品推荐。该方法的核心在于通过收集大量用户的评分数据,研究这些数据之间的关联性,识别出与目标推荐物品高度相似的其他物品,最终将这些相似度较高的物品推荐给当前用户。优点方面,协同过滤推荐能够迅速捕捉到物品间的相似性特征,并通过动态分析用户的个性化偏好,实现精准的推荐。然而,该方法的局限性在于对海量的历史数据依赖较高,同时需要建立有效的物品特征编码体系,这在实际应用中可能会带来一定的技术挑战。
该推荐系统基于模型框架,采用协同过滤机制,旨在实现高精度的个性化推荐。
基于模型的协同过滤推荐是一种先进的推荐系统方法,它利用机器学习和统计学等人工智能技术构建一个推荐模型。该系统通过识别用户的偏好和兴趣,结合用户与物品之间的相似性关系,为用户提供精准的推荐服务。该推荐系统主要包含两种协同过滤机制:第一种是基于用户-物品的协同过滤模型,通过分析用户对多个物品的评分数据,预测用户对未评分物品的潜在兴趣;第二种是基于上下文-物品的协同过滤模型,通过挖掘用户和物品之间的上下文关联性,进一步优化推荐结果。与传统推荐方法相比,基于模型的协同过滤推荐具有无需大量历史数据、精准识别用户个性化需求等显著优势。然而,该方法也存在计算复杂度较高、推荐耗时较长以及推荐精度难以完全满足用户需求等局限性。
聚类算法
聚类算法是一种非监督的机器学习算法,用于将具有相似特征的用户聚合成一组,从而降低推荐系统中的计算复杂度。具体来说,这类算法通常包括以下几种:
k-均值聚类算法
k-均值聚类算法是一种循环更新的迭代算法,能够完成数据的分类。其基本思想是通过选择k个初始质心(centroid),然后循环调整质心以最小化各簇内部分布的密度,并将数据点归类至最近的质心所在簇中。算法运行至收敛状态,最终实现数据聚类。该算法的优点在于高效可靠,处理大数据集时表现出色;然而,其缺点在于容易陷入局部最优解,可能导致聚类结果的不稳定。
DBSCAN聚类算法
DBSCAN聚类算法是一种基于密度的聚类方法,其核心在于实现对数据的自动聚类。其基本概念在于识别数据空间中的核心对象,通过构建密度相连的点集形成簇结构,同时将孤立的噪声点归类为异常数据。该算法在处理异常数据和孤立点方面表现出色,特别适用于处理高维空间中的复杂数据集。然而,其计算复杂度较高,且在处理非凸形状的聚类问题时存在一定局限性。
3.核心算法原理和具体操作步骤以及数学模型公式详细讲解
协同过滤模型
负采样
在协同过滤模型中,负采样是一种旨在防止过拟合的技术。其基本原理是从所有评分数据中随机选择若干负例,并标记为负样本。这些负样本的选取能够有效缓解因训练数据不足导致的模型过拟合问题。具体而言,假设我们有m个用户和n个物品,构建一个用户-物品评分矩阵:\begin{bmatrix} u_1 & p_1 & r_1 \ u_2 & p_2 & r_2 \ \vdots& \vdots&\vdots\ u_m & p_n & r_m \ u_1' & p_k & r'_k \ u_2' & p_l & r'_l \ \vdots& \vdots&\vdots\ u_m' & p_{n'} & r'_{n'} \end{bmatrix}其中,r'_i和r'_{i'}分别代表用户i对物品j的负样本评分以及用户i'对物品j'的负样本评分,这些评分的范围同样是1到5。通过这种方式,模型在训练过程中既能够利用有评分数据的优势,也能处理那些未给出评分的物品,从而更全面地进行学习和预测。
用户相似度计算
协同过滤模型的主要功能是通过计算用户间的相似度,为用户推荐他们可能感兴趣的物品。假设有两名用户u和v,其评分矩阵如下:
R^{(u)}=\begin{bmatrix}r^{(u)}_{1}\r^{(u)}_{2}\\ \vdots\r^{(u)}_{m}\end{bmatrix}, R^{(v)}=\begin{bmatrix}r^{(v)}_{1}\r^{(v)}_{2}\\ \vdots\r^{(v)}_{n}\end{bmatrix}
其中,r^{(u)}_{i}和r^{(v)}_{j}分别代表用户u对物品i的评分和用户v对物品j的评分。
基于余弦相似度的用户相似度计算
用户u与v之间的余弦相似度可通过以下公式计算:其中,评分向量\vec{R}^{(u)}=(r^{(u)}_{1},r^{(u)}_{2},\cdots,r^{(u)}_{m})\in \mathbb{R}^m和\vec{R}^{(v)}=(r^{(v)}_{1},r^{(v)}_{2},\cdots,r^{(v)}_{n})\in \mathbb{R}^n分别对应用户u和v的评分信息。在实际应用中,推荐系统的用户评分通常为浮点数值,基于余弦相似度的用户相似度计算则被视为一种理论上的估算。
基于物品相似度的用户相似度计算
基于物品的相似度,我们可以计算出用户之间的相似度。假设物品i和j的特征向量分别为f_i和f_j,而用户u和v对物品i的评分向量分别为R^{(u)}_i和R^{(v)}_i。假设\gamma > 0是一个超参数。为了确定用户之间的相似度,我们采用了以下的代价函数:
J(R^{(u)},R^{(v)})=\frac{1}{2}\left|R^{(u)}-\gamma f_i - R^{(v)} + \gamma f_j\right|^2_{F}
其中,|x|^2_{F} = ||x||_2^2 = \sum_{i=1}^{m}|x_i|^2。由于J不是一个连续可导的函数,因此无法直接进行优化求解。不过,我们可以通过梯度下降法来进行优化。为此,我们定义了一个梯度函数\nabla J:
\nabla J(R^{(u)},R^{(v)})=\begin{bmatrix}\frac{\partial J}{\partial R^{(u)}_{i}}\\ \frac{\partial J}{\partial R^{(u)}_{j}}\end{bmatrix}_{\phi (R^{(u)},R^{(v)})}
其中,\phi(R^{(u)},R^{(v)})表示模型的参数,即用户u和v的偏好矩阵。可以证明,使得J达到最小值的参数\phi的概率最高,也就是说,最相似的用户对应的参数\phi应该较小。
然后,通过梯度下降法对参数\phi进行优化操作: 其中,\eta为学习率,\gamma为正则化参数。通过调节学习率和正则化参数,我们可以构建出最优的用户相似度计算模型。
推荐策略
以用户间的相似度为基础,我们可以为用户提供个性化的推荐服务。给定用户u的ID后,系统能够生成用户u的相似度列表。随后,系统将遍历这一相似度列表,筛选出与用户u最为接近的k个用户群体。基于此,系统将为用户u推荐其尚未进行评分的物品。具体实施流程如下:首先,确定相似度阈值;其次,收集并分析用户评分数据;最后,生成个性化推荐结果并输出。
基于用户u的ID信息,检索其所有评分记录。通过现有的评分数据,计算出用户u的相似度列表,并按照相似度值由高到低进行排序。为用户u推荐其尚未给出过评分的物品,从中选取前m个进行推荐。将最终的推荐结果返回给用户。
4.具体代码实例和详细解释说明
导入模块
import pandas as pd
from sklearn.metrics import pairwise_distances
import numpy as np
import random
from scipy.spatial.distance import cosine
代码解读
数据准备
rating_df = pd.read_csv('ml-latest-small/ratings.csv')
user_count = rating_df['userId'].unique().shape[0] # 用户数量
item_count = rating_df['movieId'].unique().shape[0] # 物品数量
print("用户数量: %d" % user_count)
print("物品数量: %d" % item_count)
代码解读
负采样
# 负采样函数
def negative_sampling(train_data):
"""
对训练数据进行负采样
:param train_data: 训练数据
:return: 训练数据,加上负样本
"""
n_users, n_items = train_data.shape
pos_user_ids = set()
neg_user_ids = list()
for _, row in train_data.iterrows():
if not row['rating']:
continue
pos_user_ids.add((row['userId'], row['movieId']))
all_user_ids = set([(row['userId'], None) for i, row in rating_df[['userId','movieId']].iterrows()])
neg_user_ids += [user_id for user_id in all_user_ids if user_id not in pos_user_ids and len(neg_user_ids)<len(pos_user_ids)]
neg_samples = []
while True:
neg_sample = random.choice(neg_user_ids)
# 检查该负样本是否在训练数据中出现过
if ((neg_sample[0], neg_sample[1]) in [(row['userId'], row['movieId']) for _, row in train_data.iterrows()] or
(neg_sample[0], neg_sample[1]) == (None, None)):
continue
neg_samples.append({'userId': neg_sample[0],
'movieId': neg_sample[1]})
if len(neg_samples)==len(pos_user_ids)*5:
break
return pd.concat([train_data, pd.DataFrame(neg_samples)], ignore_index=True).reset_index(drop=True)
代码解读
这里基于用户-物品矩阵的结构来存储训练数据。每个用户都有一个对应物品的评分数据。对于未被评分的数据,我们采用负采样的方式,从整个数据集中随机选取一些负例,并将其标注为负标签。
train_data = negative_sampling(rating_df[['userId','movieId', 'rating']])
print(train_data[:5])
代码解读
用户相似度计算
# 用户相似度计算
def get_similar_users(user_id, user_mat, k=10, metric='cosine'):
"""
获取指定用户最相似的k个用户
:param user_id: 指定用户ID
:param user_mat: 用户-物品矩阵
:param k: 最相似的用户个数
:param metric: 相似度计算方法
:return: 指定用户最相似的k个用户及相似度
"""
user_vec = user_mat[user_id]
sim_scores = {}
if metric=='cosine':
dist_func = lambda x: cosine(x, user_vec)
elif metric=='euclidean':
dist_func = lambda x: np.linalg.norm(x-user_vec)
for other_user_id, other_user_vec in enumerate(user_mat):
if other_user_id==user_id:
continue
sim_score = dist_func(other_user_vec)
sim_scores[other_user_id] = sim_score
sorted_users = sorted(sim_scores.items(), key=lambda x: x[1], reverse=True)
similar_users = [user_id]*k + [sorted_user[0] for sorted_user in sorted_users][:min(k, len(sorted_users)-k)]
return similar_users, [sorted_user[1] for sorted_user in sorted_users][:min(k, len(sorted_users)-k)]
def calculate_similarity(train_data, k=10, metric='cosine'):
"""
计算用户相似度
:param train_data: 训练数据
:param k: 最相似的用户个数
:param metric: 相似度计算方法
:return: 用户-相似用户列表字典
"""
users = train_data['userId'].unique()
user_mat = train_data.pivot(columns='userId', index='movieId')['rating'].fillna(0)
result = {}
for user_id in users:
similar_users, similarities = get_similar_users(user_id, user_mat, k, metric)
result[user_id] = {'similarUsers': similar_users,
'similarities': similarities}
return result
代码解读
使用余弦相似度计算
similarities = calculate_similarity(train_data, k=10, metric='cosine')
代码解读
使用皮尔逊相关系数计算
similarities = calculate_similarity(train_data, k=10, metric='pearson')
代码解读
推荐策略
def recommend(user_id, items_liked_by_user, user_sim, item_mat, top_n=10):
"""
为指定的用户推荐最相似用户评分过的物品
:param user_id: 指定用户ID
:param items_liked_by_user: 用户已评分的物品列表
:param user_sim: 用户相似度列表
:param item_mat: 物品-用户矩阵
:param top_n: 每个用户的推荐物品个数
:return: 推荐结果列表
"""
user_rankings = {item_id: 0 for item_id in range(item_mat.shape[0])}
for similar_user_id, score in zip(*user_sim[user_id]['similarUsers'], *user_sim[user_id]['similarities']):
if similar_user_id==user_id:
continue
if similar_user_id not in items_liked_by_user:
continue
sim_items_ranked = rank_similar_items(similar_user_id, item_mat, items_liked_by_user)
for item_id, ranking in sim_items_ranked.items():
user_rankings[item_id] += score*ranking
recommended_items = heapq.nlargest(top_n, user_rankings, key=user_rankings.get)
return [{'itemId': item_id,'score': round(score, 3)} for item_id, score in user_rankings.items() if item_id in recommended_items]
def rank_similar_items(user_id, item_mat, liked_items_by_user):
"""
为指定的用户对推荐物品打分
:param user_id: 指定用户ID
:param item_mat: 物品-用户矩阵
:param liked_items_by_user: 用户已评分的物品列表
:return: 推荐物品列表及对应的打分
"""
known_positives = set(liked_items_by_user[user_id])
scores = pd.Series(index=item_mat.index)
for item_id, ratings in item_mat.iteritems():
similarity = sum([int(item_id in liked_items_by_user.get(other_user_id, [])) for other_user_id, _ in ratings.items()]) / len(ratings)
if item_id in known_positives:
similarity *= 1.1
else:
similarity /= 1.1
scores[item_id] = similarity
rankings = pd.DataFrame({
'itemId': list(scores.index),
'score': list(scores.values)})\
.sort_values(['score', 'itemId'], ascending=[False, False])\
.groupby('itemId').agg({'score':'max'})\
.rename({'score': 'ranking'}, axis=1)\
.reset_index()\
.to_dict(orient='records')
return dict(zip([rec['itemId'] for rec in rankings],
[rec['ranking'] for rec in rankings]))
代码解读
测试推荐效果
test_user_id = 2
items_liked_by_user = defaultdict(list)
for _, row in train_data.loc[train_data['userId']==test_user_id][['userId','movieId', 'rating']].iterrows():
items_liked_by_user[row['userId']] += [row['movieId']]
recommendations = recommend(test_user_id, items_liked_by_user, similarities, train_data.pivot(columns='userId', index='movieId')['rating'].fillna(0), top_n=10)
recommended_items = [rec['itemId'] for rec in recommendations]
print("推荐结果:")
for recommendation in recommendations:
print("%d:%.3f" % (recommendation['itemId'], recommendation['score']))
print("\n用户真实喜欢的物品:")
for movie_id in items_liked_by_user[test_user_id]:
print(movie_id)
print("\n推荐系统喜欢的物品:")
for movie_id in recommended_items:
print(movie_id)
代码解读
