热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:推荐系统基础:使用PyTorch进行矩阵分解进行动漫的推荐

篇首语:本文由编程笔记#小编为大家整理,主要介绍了推荐系统基础:使用PyTorch进行矩阵分解进行动漫的推荐相关的知识,希望对你有一定的参考价值。

篇首语:本文由编程笔记#小编为大家整理,主要介绍了推荐系统基础:使用PyTorch进行矩阵分解进行动漫的推荐相关的知识,希望对你有一定的参考价值。




我们一天会遇到很多次推荐——当我们决定在Netflix/Youtube上看什么,购物网站上的商品推荐,Spotify上的歌曲推荐,Instagram上的朋友推荐,LinkedIn上的工作推荐……列表还在继续!推荐系统的目的是预测用户对某一商品的“评价”或“偏好”。这些评级用于确定用户可能喜欢什么,并提出明智的建议。

推荐系统主要有两种类型:

基于内容的系统:这些系统试图根据项目的内容(类型、颜色等)和用户的个人资料(喜欢、不喜欢、人口统计信息等)来匹配用户。例如,Youtube可能会根据我是一个厨师的事实,以及/或者我过去看过很多烘焙视频来推荐我烹饪视频,从而利用它所拥有的关于视频内容和我个人资料的信息。

协同过滤:他们依赖于相似用户喜欢相似物品的假设。用户和/或项目之间的相似性度量用于提出建议。

本文讨论了一种非常流行的协同过滤技术——矩阵分解。


矩阵分解

推荐系统有两个实体-用户和物品(物品的范围十分广泛,可以是实际出售的产品,也可以是视频,文章等)。假设有m个用户和n个物品。我们推荐系统的目标是构建一个mxn矩阵(称为效用矩阵),它由每个用户-物品对的评级(或偏好)组成。最初,这个矩阵通常非常稀疏,因为我们只对有限数量的用户-物品对进行评级。

这是一个例子。假设我们有4个用户和5个超级英雄,我们试图预测每个用户对每个超级英雄的评价。这是我们的评分矩阵最初的样子:

推荐系统基础:使用PyTorch进行矩阵分解进行动漫的推荐

针对超级英雄等级4x5评分矩阵

现在,我们的目标是通过寻找用户和项目之间的相似性来填充这个矩阵。例如,我们看到User3和User4对蝙蝠侠给出了相同的评级,所以我们可以假设用户是相似的,他们对蜘蛛侠的感觉也是一样的,并预测User3会给蜘蛛侠评级为4。然而,在实践中,这并不是那么简单,因为有多个用户与许多不同的项交互。

在实践中,通过将评分矩阵分解成两个高而细的矩阵来填充矩阵。分解得到:

推荐系统基础:使用PyTorch进行矩阵分解进行动漫的推荐

用户-产品对的评分的预测是用户和产品的点积

推荐系统基础:使用PyTorch进行矩阵分解进行动漫的推荐

矩阵因式分解(为了方便说明,数字是随机取的)


PyTorch实现

使用PyTorch实现矩阵分解,可以使用PyTorch提供的嵌入层对用户和物品的嵌入矩阵(Embedding)进行分解,利用梯度下降法得到最优分解。

数据集

我使用了来自Kaggle的动画推荐数据集:

https://www.kaggle.com/CooperUnion/anime-recommendations-database

我们有69600个用户和9927个动漫。提供了6337241个评分。

目标

给定一组动画的用户评分,预测每一对用户动画的评分。

数据探索

我们看到有很多评级为-1的行,这表示缺少评级,我们可以去掉这些行。

 anime_ratings_df = pd.read_csv("rating.csv")
 anime_ratings_df.shape
 print(anime_ratings_df.head())

推荐系统基础:使用PyTorch进行矩阵分解进行动漫的推荐

我们还可以查看评分的分布和每个用户的评分数量。

 Counter(anime_ratings.rating)

推荐系统基础:使用PyTorch进行矩阵分解进行动漫的推荐

每个用户的平均评分数

 np.mean(anime_ratings.groupby(['user_id']).count()['anime_id'])

输出 91.05231321839081

数据预处理

因为我们将使用PyTorch的嵌入层来创建用户和物品嵌入,所以我们需要连续的id来索引嵌入矩阵并访问每个用户/项目嵌入。

 def encode_column(column):
    """ Encodes a pandas column with continous IDs"""
    keys = column.unique()
    key_to_id = {key:idx for idx,key in enumerate(keys)}
    return key_to_id, np.array([key_to_id[x] for x in column]), len(keys)
 anime_df, num_users, num_anime, user_ids, anime_ids = encode_df(train_df)
 print("Number of users :", num_users)
 print("Number of anime :", num_anime)
 anime_df.head()

推荐系统基础:使用PyTorch进行矩阵分解进行动漫的推荐

训练

我们的目标是为每个用户和每个物品找到最佳的嵌入向量。然后,我们可以通过获取用户嵌入和物品嵌入的点积,对任何用户和物品进行预测

成本函数:我们目标是使评分矩阵的均方误差最小。这里的N是评分矩阵中非空白元素的数量。

推荐系统基础:使用PyTorch进行矩阵分解进行动漫的推荐

 def cost(df, emb_user, emb_anime):
    """ Computes mean square error"""
    Y = create_sparse_matrix(df, emb_user.shape[0], emb_anime.shape[0])
    predicted = create_sparse_matrix(predict(df, emb_user, emb_anime), emb_user.shape[0], emb_anime.shape[0], 'prediction')
    return np.sum((Y-predicted).power(2))/df.shape[0]

预测

 def predict(df, emb_user, emb_anime):
    """ This function computes df["prediction"] without doing (U*V^T).
     
    Computes df["prediction"] by using elementwise multiplication of the corresponding embeddings and then
    sum to get the prediction u_i*v_j. This avoids creating the dense matrix U*V^T.
    """
    df['prediction'] = np.sum(np.multiply(emb_anime[df['anime_id']],emb_user[df['user_id']]), axis=1)
    return df

用户和物品向量的初始化

有许多方法来初始化嵌入权重,并没有一个统一的答案,例如,fastai使用一种叫做截断标准初始化器(Truncated Normal initializer)的东西。在我的实现中,我刚刚用(0,11 /K)的uniform值初始化了嵌入(随机初始化在我的例子中运行得很好!)其中K是嵌入矩阵中因子的数量。K是一个超参数,通常是由经验决定的——它不应该太小,因为你想让你的嵌入学习足够的特征,但你也不希望它太大,因为它会开始过度拟合你的训练数据,增加计算时间。

 def create_embeddings(n, K):
    """
    Creates a random numpy matrix of shape n, K with uniform values in (0, 11/K)
    n: number of items/users
    K: number of factors in the embedding
    """
    return 11*np.random.random((n, K)) / K

创建稀疏效用矩阵:由于我们的成本函数需要效用矩阵,我们需要一个函数来创建这个矩阵。

 def create_sparse_matrix(df, rows, cols, column_name="rating"):
    """ Returns a sparse utility matrix"""
    return sparse.csc_matrix((df[column_name].values,(df['user_id'].values, df['anime_id'].values)),shape=(rows, cols))

梯度下降

梯度下降方程为:

推荐系统基础:使用PyTorch进行矩阵分解进行动漫的推荐

我在实现过程中使用了动量,该动量可以帮助加快相关方向上的梯度下降并抑制振荡,从而加快收敛速度。我还添加了正则化功能,以确保我的模型不会过度适合训练数据。因此,我的代码中的梯度下降方程比上述方程稍微复杂。

正则成本函数为:

 def gradient_descent(df, emb_user, emb_anime, iteratiOns=2000, learning_rate=0.01, df_val=None):
    """
    Computes gradient descent with momentum (0.9) for given number of iterations.
    emb_user: the trained user embedding
    emb_anime: the trained anime embedding
    """
    Y = create_sparse_matrix(df, emb_user.shape[0], emb_anime.shape[0])
    beta = 0.9
    grad_user, grad_anime = gradient(df, emb_user, emb_anime)
    v_user = grad_user
    v_anime = grad_anime
    for i in range(iterations):
        grad_user, grad_anime = gradient(df, emb_user, emb_anime)
        v_user = beta*v_user + (1-beta)*grad_user
        v_anime = beta*v_anime + (1-beta)*grad_anime
        emb_user = emb_user - learning_rate*v_user
        emb_anime = emb_anime - learning_rate*v_anime
        if(not (i+1)%50):
            print("\niteration", i+1, ":")
            print("train mse:", cost(df, emb_user, emb_anime))
            if df_val is not None:
                print("validation mse:", cost(df_val, emb_user, emb_anime))
    return emb_user, emb_anime

在验证集上预测

因为我们无法为我们的训练集中未遇到的用户和动漫(冷启动问题)做出预测,所以我们需要从看不见的数据集中删除它们。

 def encode_new_data(valid_df, user_ids, anime_ids):
    """ Encodes valid_df with the same encoding as train_df.
    """
    df_val_chosen = valid_df['anime_id'].isin(anime_ids.keys()) & valid_df['user_id'].isin(user_ids.keys())
    valid_df = valid_df[df_val_chosen]
    valid_df['anime_id'] = np.array([anime_ids[x] for x in valid_df['anime_id']])
    valid_df['user_id'] = np.array([user_ids[x] for x in valid_df['user_id']])
    return valid_df

我们的模型略微过拟合了训练数据,因此可以增加正则化因子(lambda)以使其更好地泛化。

 train_mse = cost(train_df, emb_user, emb_anime)
 val_mse = cost(valid_df, emb_user, emb_anime)
 print(train_mse, val_mse)

输出:6.025304207874527 11.735503902293352

让我们看看预测:

 valid_df[70:80].head()

鉴于这些评分仅基于用户行为之间的相似性,在1-10的评分范围内,均方根值仅为3.4算是不错了。它显示了即使如此简单,矩阵分解仍然具有多么强大的功能。


矩阵分解的局限性

矩阵分解是一种非常简单和方便的方法。但是,它也有缺陷,其中之一已经在我们的实现中遇到:

冷启动问题

我们无法对训练数据中从未遇到过的项目和用户进行预测,因为我们没有为它们提供嵌入。

冷启动问题可以通过许多方式来解决,包括推荐流行的项目,让用户对一些项目进行评级,使用基于内容的方法,直到我们有足够的数据来使用协同过滤。

很难包含关于用户/物品的附加上下文

我们只使用用户id和物品id来创建嵌入。我们不能在实现中使用关于用户和项的任何其他信息。有一些复杂的基于内容的协同过滤模型可以用来解决这个问题。

评级并不总是可用的

很难从用户那里得到反馈。大多数用户只有在真正喜欢或绝对讨厌某样东西的时候才会给它打分。在这种情况下,我们通常不得不想出一种方法来衡量隐性反馈,并使用负采样技术来想出一个合理的训练集。


结论

推荐系统确实很有趣,但也可能变得太复杂或者太容易,尤其是在有数百万用户和数百万条目的大规模应用中。通常,你可以找到与这些案例研究相对应的研究论文/视频/工程博客。这里有一些有用的资源:

https://developers.google.com/machine-learning/recommendation/collaborative/matrix

https://labs.pinterest.com/user/themes/pin_labs/assets/paper/p2p-www17.pdf

https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/45530.pdf


本文的代码:https://jovian.ml/aakanksha-ns/anime-ratings-matrix-factorization

deephub翻译组



推荐阅读
  • 一个产品数组拼图|集合 2 (O(1)空间) ... [详细]
  • Shiro功能拓展:登录失败重试次数限制
    本文详细介绍了如何在Apache Shiro框架中实现对用户登录失败重试次数的限制,通过自定义密码匹配器来增强系统的安全性。该方法不仅能够有效防止暴力破解攻击,还能确保合法用户的账户安全。 ... [详细]
  • 本文详细探讨了在Windows Server 2003环境下遇到MySQL连接失败(错误代码10061)的解决方案,包括通过卸载特定的Windows更新和调整系统注册表设置的方法。 ... [详细]
  • 本文介绍了如何在 Linux 系统上构建网络路由器,特别关注于使用 Zebra 软件实现动态路由功能。通过具体的案例,展示了如何配置 RIP 和 OSPF 协议,以及如何利用多路由器查看工具(MRLG)监控网络状态。 ... [详细]
  • Web安全入门:MySQL基础操作与SQL注入防范
    本文详细介绍了MySQL数据库的基础操作命令,包括数据库和表的基本管理,以及数据的增删查改等常用操作。同时,针对Web安全领域常见的SQL注入问题,提供了初步的理解和防范措施。 ... [详细]
  • OpenWrt 是一款高度可定制的嵌入式 Linux 发行版,广泛应用于无线路由器等领域,拥有超过百个预装软件包。本文详细探讨如何在 OpenWrt 上通过 Luci 构建自定义模块,以扩展其功能。 ... [详细]
  • UnityNGUIScrollView苹果式滑动
    又回来写博客了,这回已经开始上班了,所以就发一发工作中解决的难题吧。单个展示Panel(苹果式)以前对UI的滑动组件很烦心,不是很会用,这回项目要求写一个类似于苹果的文件滑动效果, ... [详细]
  • 实现‘点击恢复’功能 - Tap-to-Resume Feature in SpriteKit
    了解如何在应用程序从非活动状态返回时,在SpriteKit游戏中添加一个‘点击恢复’的文字提示。 ... [详细]
  • 使用 NDB 提升 Node.js 应用调试体验
    本文介绍了由 Google Chrome 实验室推出的新一代 Node.js 调试工具 NDB,旨在为开发者提供更加高效和便捷的调试解决方案。 ... [详细]
  • 本文探讨了如何利用自定义URI方案和注册表编辑,在Windows操作系统中实现从Web浏览器启动本地应用程序的方法,同时强调了这一过程中的安全考虑。 ... [详细]
  • DP:InitiallyIthinkof1DDP,dp[i]standsfortheshorteststringoffirsticharacters,then:dp[i]minLe ... [详细]
  • 本文介绍了在处理财务凭证查询时,如何实现从插入或修改页面返回至原始查询结果页面,并确保数据保持最新状态的方法。通过使用JavaScript和Java的Session管理技术,解决了动态页面中AJAX调用失效的问题。 ... [详细]
  • 右下角动态通知实现
    本文介绍如何在网页中实现右下角滑动通知的功能,包括前后台代码示例及具体实现步骤。 ... [详细]
  • 本文详细介绍了RPM包构建过程中Spec文件的结构和各部分的作用,包括包描述、准备阶段、构建过程、安装步骤、清理操作以及文件列表等关键环节。同时,提供了关于RPM宏命令、打包目录结构及常见标签的深入解析。 ... [详细]
  • 本文介绍了如何通过十折交叉验证方法评估回归模型的性能。我们将使用PyTorch框架,详细展示数据处理、模型定义、训练及评估的完整流程。 ... [详细]
author-avatar
冒泡鱼的快乐2011
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有