吴恩达强化学习复盘(5)如何构建推荐系统

推荐系统简介

推荐系统这一主题在学术界并不热门,但在商业领域的实际影响和应用案例数量远超其在学术界的受关注程度。

比如以京东/淘宝(在线购物)、B站(视频流媒体)、(美团)外卖网站等为例,这些网站或者app会根据用户情况推荐可能购买的商品、观看的电影或尝试的餐馆。对许多公司而言,推荐系统推动了很大一部分的销售,带来了显著的经济效益。

系统数据准备

  • 示例背景:假设运营一个电影流媒体网站,用户对电影进行 1 到 5 星(为方便示例,改为 0 到 5 星)的评分。
  • 用户和电影示例:假设 4 个用户(Alice、Bob、Caro、Dave)和 5 部电影(《Love Alas》《Romance Forever》《Two Puppies of Love》《Non Stop Car Chases》《Small Versus Karate》),不同用户对不同电影有不同评分情况,未评分的用问号表示。
  • 符号定义
    • n_u表示用户数量,此例中 n_u = 4
    • 用 n_m表示电影(项目)数量,此例中 n_m = 5
    • r_{ij} 表示如果用户 j 给电影 i 打分则为 1,否则为 0 ,如 r_{11}=1(Alice 给电影 1 打分),r_{31}=0(Alice 未给电影 3 打分)。
    • y_{ij} 表示用户 j 对电影 i 的评分,如用户 2 对电影 C 的评分为 4,即 y_{C2}=4
  • 系统原理:由于不是每个用户都给每部电影打分,通过定义 r_{ij} 让系统知道哪些用户给哪些电影打过分。推荐系统的一种解决思路是预测用户未评分电影的评分,进而推荐用户可能评高分(如五星)的电影。

引入特征

构建推荐系统,包括预测用户对电影的评分模型、制定成本函数以及学习所有用户参数的方法。

背景与数据介绍

基本数据准备完成后,引入电影的两个特征x_1 和 x_2,分别表示电影是爱情片和动作片的程度,并举例说明了不同电影的特征值,如《Love Alas》是一部非常浪漫的电影,其 x_1 = 0.9x_2 = 0;《Non Soft car Chases》只有一点浪漫,x_1 = 0.1,但动作性很强,x_2 = 1.0。同时,明确了一些符号的含义,n_u 表示用户数量(n_u = 4),n_m 表示电影数量(n_m = 5),n 表示特征数量(n = 2)。

用户对电影的评分模型

以用户 1(Alice)为例,介绍预测电影评分的模型。

预测电影 i 的评分为 \mathbf{w}^T \cdot \mathbf{x}_i + b,这与线性回归类似。

通过假设\mathbf{w} = [5, 0]b_1 = 0,计算出对第三部电影(特征为 [0.99, 0])的预测评分为 0.99×5 + 0×0 = 4.95,并结合 Alice 对其他浪漫电影和动作电影的评分情况,说明这个预测是合理的。

由于存在多个用户,为每个用户 j 引入上标 j,更一般地,用户 j 对电影 i 的预测评分为\mathbf{w}_j^T \cdot \mathbf{x}_i + b_j,即针对每个用户分别拟合不同的线性回归模型。

制定成本函数

  • 明确一些符号的含义,r_{ij} 表示用户 j 是否给电影 i 评分(若评分则r_{ij} = 1,否则为 0),y_{ij} 表示用户 j 对电影 i 的实际评分,m_j 表示用户 j 评分的电影数量。
  • 对于单个用户 j,使用均方误差标准定义成本函数 J(\mathbf{w}_j, b_j)。即预测评分 \mathbf{w}_j^T \cdot \mathbf{x}_i + b_j与实际评分 y_{ij} 之差的平方,只对用户 j 评分过的电影(r_{ij} = 1)求和,并乘以 \frac{1}{2m_j}进行归一化。
  • 为防止过拟合,添加正则化项 \frac{\lambda}{2m_j} \sum_{k = 1}^{n} w_{jk}^2,其中\lambda 是正则化参数,n 是特征数量。由于 m_j 是常数,在实际应用中可以消除除以 m_j 这一项,且不会影响 \mathbf{w}_j 和 b_j 的取值。

所有用户的参数

不局限于单个用户,要学习所有用户的参数 \mathbf{w}_1, b_1, \mathbf{w}_2, b_2, \cdots, \mathbf{w}_{n_u}, b_{n_u}

将单个用户的成本函数对所有 n_u 个用户求和,得到学习所有用户参数的成本函数。通过使用梯度下降法或其他优化算法最小化这个成本函数,就可以得到预测所有用户电影评分的参数。这种方法类似于线性回归,只是为每个用户训练不同的线性回归模型。

协同过滤算法

若已知每部电影的特征(如 \(x_1\)、\(x_2\) 分别表示电影是爱情片和动作片的程度),可使用基本线性回归预测电影收视率。但当没有这些特征时,如何从数据中学习得到这些特征。

假设已知用户参数来学习电影特征

  • 给出之前的数据,假设已知四个用户对部分电影的评分,但电影的特征值未知,用问号代替。为便于说明,假设已学习到四个用户的参数\mathbf{w} 和 b (如用户 1 的 \mathbf{w}_1 = [5, 0]b_1 = 0等),且为简化例子,令所有 b 值为 0。
  • 以电影 1 为例
  • 用户 1(Alice)给电影 1 打分 5,可得\mathbf{w}_1 \cdot \mathbf{x}_1 \approx 5
  • 用户 2(Bob)给电影 1 打分 5,则 \mathbf{w}_2 \cdot \mathbf{x}_1 \approx 5
  • 用户 3 和用户 4 未对电影 1 的评分情况表明\mathbf{w}_3 \cdot \mathbf{x}_1 \approx 0\mathbf{w}_4 \cdot \mathbf{x}_1 \approx 0
  • 由此推测电影 1 可能的特征向量 \mathbf{x}_1 = [1, 0],能满足上述条件。
  • 类似地,可尝试为其他电影想出合适的特征向量,目标是使算法的预测接近用户给出的实际评级。这里强调了多个用户评分的重要性,在典型线性回归中单个用户信息不足无法确定特征,而协同过滤因多个用户对同一电影评分,使得猜测合适特征成为可能。

构建学习电影特征的代价函数

  • 已知用户的参数 \mathbf{w}_1, b_1, \mathbf{w}_2, b_2, \cdots, \mathbf{w}_{n_u}, b_{n_u}n_u 为用户数量),对于特定电影 i,定义代价函数来学习其特征 \mathbf{x}_i。预测评分与实际评分 y_{ij} 的平方差,对所有给电影 i 评分的用户 j(r_{ij} = 1r_{ij}表示用户 j 是否给电影 i 评分)求和,并乘以 \frac{1}{2}
  • 为防止过拟合,添加正则化项\frac{\lambda}{2} \sum_{k = 1}^{n} x_{ik}^2\lambda 为正则化参数,n 为特征数量)。要学习所有电影的特征\mathbf{x}_1, \cdots, \mathbf{x}_{n_m}n_m 为电影数量),将上述代价函数对所有电影求和。
  • 通过最小化这个代价函数(使用梯度下降等算法),可以较好地猜测出电影的特征,这与大多数机器学习应用中特征需外部给定不同,此算法能学习电影特征。

推导协同过滤算法的代价函数

  • 前面假设了已知用户参数,现在将上一个视频中学习用户参数 \mathbf{w} 和 b 的内容与本视频学习电影特征 \mathbf{x}的内容结合,得到协同过滤算法。
  • 把学习特征的代价函数和学习用户参数的代价函数合并,发现一些项相同。合并后的总代价函数对所有有评分的用户 - 电影对(r_{ij} = 1)求和,分别加上从学习用户参数和学习特征得到的正则化项。
  • 详细说明了最小化这个总代价函数(是 \mathbf{w}、b 和 \mathbf{x}的函数)的两种求和方式,一种是先对用户求和再对有评分的电影求和,另一种是先对电影求和再对给该电影评分的用户求和,两种方式本质相同,都是对所有有评分的用户 - 电影对求和。

优化代价函数与协同过滤的意义

  • 使用梯度下降法来最小化代价函数,通过对不同参数(\mathbf{w}、b 和 \mathbf{x})求偏导数来更新参数,从而找到临界值。强调该模型的参数为 \mathbf{w}、b 和 \mathbf{x},最小化代价函数就是对这三个参数进行优化。
  • 协同过滤算法的意义在于多个用户对同一电影的评分,使我们能猜测电影合适的特征,进而预测其他未评分用户对该电影的评分,实现对多个用户数据的筛选和利用,帮助预测其他用户未来的评分。

简单的python代码

import numpy as np

# 模拟用户 - 电影评分矩阵,行代表用户,列代表电影
ratings_matrix = np.array([
    [5, 3, 0, 1],
    [4, 0, 0, 1],
    [1, 1, 0, 5],
    [1, 0, 0, 4],
    [0, 1, 5, 4]
])


# 计算用户之间的相似度,这里使用余弦相似度
def cosine_similarity(user1, user2):
    dot_product = np.dot(user1, user2)
    norm_user1 = np.linalg.norm(user1)
    norm_user2 = np.linalg.norm(user2)
    if norm_user1 == 0 or norm_user2 == 0:
        return 0
    return dot_product / (norm_user1 * norm_user2)


# 为指定用户生成推荐
def recommend_movies(target_user_index, ratings_matrix, top_n=3):
    num_users, num_movies = ratings_matrix.shape
    similarities = []

    # 计算目标用户与其他用户的相似度
    for i in range(num_users):
        if i != target_user_index:
            similarity = cosine_similarity(ratings_matrix[target_user_index], ratings_matrix[i])
            similarities.append((i, similarity))

    # 按相似度排序
    similarities.sort(key=lambda x: x[1], reverse=True)

    # 找到最相似的top_n个用户
    top_similar_users = [user[0] for user in similarities[:top_n]]

    # 找到目标用户未评分的电影
    unrated_movies = np.where(ratings_matrix[target_user_index] == 0)[0]

    movie_scores = {}
    for movie in unrated_movies:
        score = 0
        total_similarity = 0
        for user in top_similar_users:
            if ratings_matrix[user][movie] != 0:
                score += ratings_matrix[user][movie] * similarities[user][1]
                total_similarity += similarities[user][1]
        if total_similarity != 0:
            movie_scores[movie] = score / total_similarity

    # 按评分排序
    sorted_movies = sorted(movie_scores.items(), key=lambda x: x[1], reverse=True)

    # 提取推荐的电影索引
    recommended_movies = [movie[0] for movie in sorted_movies]
    return recommended_movies


# 假设为第0个用户生成推荐
target_user_index = 0
recommended_movies = recommend_movies(target_user_index, ratings_matrix)
print(f"为用户 {target_user_index} 推荐的电影索引:{recommended_movies}")
    

基于二元标签推荐简介

二元标签的应用背景

推荐系统和集体过滤算法的很多重要应用中,用户不再给出 1 到 5 星的具体评级,而是以二元标签的形式(即喜欢或不喜欢)来表达对物品的态度。这种二元标签的应用在实际中更为常见和直接。

二元标签的含义

  • 以电影为例,标签 1 表示用户喜欢或参与了某部电影,比如爱丽丝完整看完了《Love at Last》(终于有爱)和《Romance Forever》(永远的浪漫),这可能意味着她明确点赞或标记为最喜欢;标签 0 表示不喜欢,如爱丽丝在观看了几分钟某电影(有不间断的电话追逐情节)后就停止了视频;问号表示用户尚未看过该物品,所以无法做出是否喜欢的判断。
  • 在不同场景中,二元标签有不同的具体含义:
    • 在线购物网站中,1 表示用户看到商品后选择购买,0 表示未购买,问号表示未接触到该商品。
    • 社交媒体上,1 或 0 表示用户看到项目后是否收藏或喜欢,问号表示该项目尚未展示给用户。
    • 一些网站通过用户行为来隐式判断喜好,如用户在某商品上停留至少 30 秒则标记为 1(认为用户感兴趣),停留不足 30 秒标记为 0,未展示给用户标记为问号;在在线广告中,用户点击展示内容标记为 1,未点击标记为 0,未展示标记为问号。
  • 总体而言,1 通常代表用户对展示物品有参与行为(如点击、花费时间、购买、喜欢等),0 代表无参与行为,问号代表物品未展示。

基于二元标签算法

  • 先前预测 y_{ij}(用户 j 对物品 i 的评分)采用类似线性回归的模型 y_{ij} = \mathbf{w}_j \cdot \mathbf{x}_i + b。对于二元标签,现在要预测 y_{ij}=1的概率。
  • 这个概率不再由 \mathbf{w}_j \cdot \mathbf{x}_i + b 给出,而是通过逻辑函数 G(z) = \frac{1}{1 + e^{-z}} 来计算,即P(y_{ij}=1) = G(\mathbf{w}_j \cdot \mathbf{x}_i + b),从而将线性回归模型转变为逻辑回归模型。

代价函数的调整

  • 原有的代价函数类似于平方误差形式,在二元标签情况下不再适用。当有二元标签 y_{ij}(1、0 或问号)时,预测值变为 G(\mathbf{w}_j \cdot \mathbf{x}_i + b_j)
  • 类似于逻辑回归的推导,引入二元交叉熵代价函数L(\hat{y}, y) = -y \log \hat{y} - (1 - y) \log (1 - \hat{y}),其中 \hat{y} 是预测值,y 是真实标签。这是逻辑回归用于二元分类问题的标准代价函数。
  • 为适应协同过滤设置,代价函数变为所有参数 w、b 以及物品特征 \mathbf{x} 的函数。对所有 r_{ij} = 1(即用户 j 对物品 i 有评分)的 i-j 对求和,使用二元交叉熵损失函数而非平方误差函数,得到适用于二元标签协同过滤的代价函数。

基于二元标签的推荐系统python代码

import numpy as np


# 计算用户之间的余弦相似度
def cosine_similarity(user1, user2):
    dot_product = np.dot(user1, user2)
    norm_user1 = np.linalg.norm(user1)
    norm_user2 = np.linalg.norm(user2)
    if norm_user1 == 0 or norm_user2 == 0:
        return 0
    return dot_product / (norm_user1 * norm_user2)


# 基于用户的协同过滤推荐
def user_based_collaborative_filtering(ratings_matrix, target_user_index, top_n=5):
    num_users, num_movies = ratings_matrix.shape
    similarities = []

    # 计算目标用户与其他用户的相似度
    for i in range(num_users):
        if i != target_user_index:
            similarity = cosine_similarity(ratings_matrix[target_user_index], ratings_matrix[i])
            similarities.append((i, similarity))

    # 按相似度排序
    similarities.sort(key=lambda x: x[1], reverse=True)

    # 找到最相似的top_n个用户
    top_similar_users = [user[0] for user in similarities[:top_n]]

    # 找到目标用户未评分的电影
    unrated_movies = np.where(ratings_matrix[target_user_index] == 0)[0]

    movie_scores = {}
    for movie in unrated_movies:
        score = 0
        total_similarity = 0
        for user in top_similar_users:
            if ratings_matrix[user][movie] != 0:
                score += ratings_matrix[user][movie] * similarities[user - 1][1]  # 这里减1是因为之前append时索引从0开始
                total_similarity += similarities[user - 1][1]
        if total_similarity != 0:
            movie_scores[movie] = score / total_similarity

    # 按评分排序
    sorted_movies = sorted(movie_scores.items(), key=lambda x: x[1], reverse=True)

    # 提取推荐的电影索引
    recommended_movies = [movie[0] for movie in sorted_movies]
    return recommended_movies


# 模拟用户-电影评分矩阵(二元标签),行代表用户,列代表电影
# 1表示喜欢,0表示不喜欢
ratings_matrix = np.array([
    [1, 0, 1, 0],
    [0, 1, 0, 1],
    [1, 0, 0, 1],
    [0, 1, 1, 0],
    [1, 0, 0, 1]
])

# 假设为第0个用户生成推荐
target_user_index = 0
recommended_movies = user_based_collaborative_filtering(ratings_matrix, target_user_index)
print(f"为用户 {target_user_index} 推荐的电影索引:{recommended_movies}")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值