摘要
互联网和信息计算的快速发展,衍生了海量的数据,我们已经进入了一个信息爆炸的时代,每时每刻都有海量信息产生,然而这些信息并不全是个人所关心的,用户从大量的信息中寻找对自己有用的信息也变得越来越困难。另一方面,信息的生产方也在绞尽脑汁的把用户感兴趣的信息送到用户面前,每个人的兴趣又不尽相同,所以可以实现千人千面的推荐系统应运而生。简单来说,推荐系统是根据用户的浏览习惯,确定用户的兴趣,通过发掘用户的行为,将合适的信息推荐给用户,满足用户的个性化需求,帮助用户找到对他胃口但是不易找到的信息或商品。
推荐系统在互联网和传统行业中都有着大量的应用。在互联网行业,几乎所有的互联网平台都应用了推荐系统,如资讯新闻/影视剧/知识社区的内容推荐、电商平台的商品推荐等;在传统行业中,有些用于企业的营销环节,如银行的金融产品推荐、保险公司的保险产品推荐等。根据QM报告,以推荐系统技术为核心的短视频行业在2019年的用户规模已超8.2亿,市场规模达2千亿,由此可见这项技术在现代社会的经济价值。
推荐系统的目标
随着现代工业和互联网的兴起,长尾经济变得越来越流行。在男耕女织的农业时代,人们以“个性化”的模式生产“个性化”的产品;在流水线模式的工业化时代,人们以“规模化”的模式生产“标准化”的产品;而在互联网和智能制造业不断发展的今天,人们以“规模化”的模式生产“个性化”的产品,极大地丰富了商品种类。在此情况下,用户的注意力和消费力变成极为匮乏的资源。如何从海量的产品和服务中选择自己需要的,成为用户第一关心的事,这就是推荐系统的价值所在。但每个人的喜好极具个性化,例如年轻人偏爱健身的内容,而父母一代偏爱做菜的内容,如果推荐内容相反,用户会非常不满。正所谓此之甘露,彼之砒霜,基于个性化需求进行推荐是推荐系统的关键目标。
推荐系统的基本概念
构建推荐系统本质上是要解决“5W”的问题。如下图示例,当用户在晚间上网阅读军事小说时,系统在小说的底部向他推荐三国志游戏,并给出了推荐理由“纸上谈兵不如亲身实践”。
哪些信息可以用于推荐
观察只保留两个核心问题的推荐任务示例,思考有哪些信息可以用于推荐? 图中蕴含的数据可以分为三种:
- 每个用户的不同特征,如性别、年龄;
- 物品的各种描述属性,如品牌、品类;
- 用户对部分物品的兴趣表达,即用户与物品的关联数据,如历史上的评分、评价、点击行为和购买行为。
结合这三种信息可以形成类似“女性A 喜欢 LV包”这样的表达。
基于3的关联信息,人们设计了“协同过滤的推荐算法”。基于2的内容信息,设计出“基于内容的推荐算法”。现在的推荐系统普遍同时利用这三种信息,下面我们就来看看这些方法的原理。
常用的推荐系统算法
常用的推荐系统算法实现方案有三种:
协同过滤推荐(Collaborative Filtering Recommendation)
该算法的核心是分析用户的兴趣和行为,利用共同行为习惯的群体有相似喜好的原则,推荐用户感兴趣的信息。兴趣有高有低,算法会根据用户对信息的反馈(如评分)进行排序,这种方式在学术上称为协同过滤。协同过滤算法是经典的推荐算法,经典意味着简单、好用。协同过滤算法又可以简单分为两种:
基于用户的协同过滤:
根据用户的历史喜好分析出相似兴趣的人,然后给用户推荐其他人喜欢的物品。假如小李,小张对物品A、B都给了十分好评,那么可以认为小李、小张具有相似的兴趣爱好,如果小李给物品C十分好评,那么可以把C推荐给小张,可简单理解为“人以类聚”。
基于物品的协同过滤:
根据用户的历史喜好分析出相似物品,然后给用户推荐同类物品。比如小李对物品A、B、C给了十分好评,小王对物品A、C给了十分好评,从这些用户的喜好中分析出喜欢A的人都喜欢C,物品A、C是相似的,如果小张给了A好评,那么可以把C也推荐给小张,可简单理解为“物以群分”。
基于内容过滤推荐(Content-based Filtering Recommendation)
基于内容的过滤是信息检索领域的重要研究内容,是更为简单直接的算法,该算法的核心是衡量出两个物品的相似度。首先对物品或内容的特征作出描述,发现其相关性,然后基于用户以往的喜好记录,推荐给用户相似的物品。比如,小张对物品A感兴趣,而物品A和物品C是同类物品(从物品的内容描述上判断),可以把物品C也推荐给小张。
组合推荐(Hybrid Recommendation)
以上算法各有优缺点,比如基于内容的过滤推荐是基于物品建模,在系统启动初期往往有较好的推荐效果,但是没有考虑用户群体的关联属性;协同过滤推荐考虑了用户群体喜好信息,可以推荐内容上不相似的新物品,发现用户潜在的兴趣偏好,但是这依赖于足够多且准确的用户历史信息。所以,实际应用中往往不只采用某一种推荐方法,而是通过一定的组合方法将多个算法混合在一起,以实现更好的推荐效果,比如加权混合、分层混合等。具体选择哪种方式和应用场景有很大关系。
如何实现推荐
如何根据上述数据实现推荐系统呢?首先思考下,实现推荐系统究竟需要什么?
如果能将用户A的原始特征转变成一种代表用户A喜好的特征向量,将电影1的原始特征转变成一种代表电影1特性的特征向量。那么,我们计算两个向量的相似度,就可以代表用户A对电影1的喜欢程度。据此,推荐系统可以如此构建:
这样设计的核心是两个特征向量的有效性,它们会决定推荐的效果。
如何获得有效特征
如何获取两种有效代表用户和电影的特征向量?首先,需要明确什么是“有效”?
对于用户评分较高的电影,电影的特征向量和用户的特征向量应该高度相似,反之则相异。
我们已经获得大量评分样本,因此可以构建一个训练模型如下图所示,根据用户对电影的评分样本,学习出用户特征向量和电影特征向量的计算方案(灰色箭头)。
-
第一层结构:特征变换,原始特征集合变换为两个特征向量。
-
第二层结构:计算向量相似度。(计算相似度是代表两个向量的之间的相似程度为多少?)为确保结果与电影评分可比较,两个特征向量的相似度从【0~1】缩放5倍到【0~5】。
-
第三层结构:计算Loss,计算缩放后的相似度与用户对电影的真实评分的“均方误差”。
以在训练样本上的Loss最小化为目标,即可学习出模型的网络参数,这些网络参数本质上就是从原始特征集合到特征向量的计算方法,如灰色箭头所示。根据训练好的网络,我们可以计算任意用户和电影向量的相似度,进一步完成推荐。
从原始特征到特征向量之间的网络如何设计?
将每个原始特征转变成Embedding表示,再合并成一个用户特征向量和一个电影特征向量。计算两个特征向量的相似度后,再与训练样本(已知的用户对电影的评分)做损失计算。
但不同类型的原始特征应该如何变换?有哪些网络设计细节需要考虑?我们将在后续几节结合代码实现逐一探讨,包括四个小节:
- 数据处理,将MovieLens的数据处理成神经网络理解的形式。
- 模型设计,设计神经网络模型,将离散的文字数据映射为向量。
- 配置训练参数并完成训练,提取并保存训练后的数据特征。
- 利用保存的特征构建相似度矩阵完成推荐。
模型设计介绍
神经网络模型设计是电影推荐任务中重要的一环。它的作用是提取图像、文本或者语音的特征,利用这些特征完成分类、检测、文本分析等任务。在电影推荐任务中,我们将设计一个神经网络模型,提取用户数据、电影数据的特征向量,然后计算这些向量的相似度,利用相似度的大小去完成推荐。
神经网络模型的设计包含如下步骤:
- 分别将用户、电影的多个特征数据转换成特征向量。
- 对这些特征向量,使用全连接层或者卷积层进一步提取特征。
- 将用户、电影多个数据的特征向量融合成一个向量表示,方便进行相似度计算。
- 计算特征之间的相似度。
依据这个思路,我们设计一个简单的电影推荐神经网络模型:
该网络结构包含如下内容:
-
提取用户特征和电影特征作为神经网络的输入,其中:
- 用户特征包含四个属性信息,分别是用户ID、性别、职业和年龄。
- 电影特征包含三个属性信息,分别是电影ID、电影类型和电影名称。
-
提取用户特征。使用Embedding层将用户ID映射为向量表示,输入全连接层,并对其他三个属性也做类似的处理。然后将四个属性的特征分别全连接并相加。
-
提取电影特征。将电影ID和电影类型映射为向量表示,输入全连接层,电影名字用文本卷积神经网络得到其定长向量表示。然后将三个属性的特征表示分别全连接并相加。
-
得到用户和电影的向量表示后,计算二者的余弦相似度。最后,用该相似度和用户真实评分的均方差作为该回归模型的损失函数。
衡量相似度的计算有多种方式,比如计算余弦相似度、皮尔森相关系数、Jaccard相似系数等等,或者通过计算欧几里得距离、曼哈顿距离、明可夫斯基距离等方式计算相似度。余弦相似度是一种简单好用的向量相似度计算方式,通过计算向量之间的夹角余弦值来评估他们的相似度,本节我们使用余弦相似度计算特征之间的相似度。在不同的场景下使用的不懂的有利于的完成更好的推荐的功能,这个科研工作的一个热点方向。
为何如此设计网络呢?
-
如何将“数字”转变成“向量”?
如NLP章节的介绍,使用词嵌入(Embedding)的方式可将数字转变成向量。
-
如何合并多个向量的信息?例如:如何将用户四个特征(ID、性别、年龄、职业)的向量合并成一个向量?
最简单的方式是先将不同特征向量(ID 32维、性别 16维、年龄 16维、职业 16维)通过4个全连接层映射到4个等长的向量(200维度),再将4个等长的向量按位相加即可得到1个包含全部信息的向量。这个涉及的到数据矩阵的升维的操作,同时这个涉及的矩阵轮的相关知识需要自行补充。
电影类型的特征是将多个数字(代表出现的单词)转变成的多个向量(6个),可以通过相同的方式合并成1个向量。
-
如何处理文本信息?
如NLP章节的介绍,使用卷积神经网络(CNN)和长短记忆神经网络(LSTM 处理的是简单的文本信息,但是如果是复杂文本信息呢?需要用什么网络处理?)处理文本信息会有较好的效果。因为电影标题是相对简单的短文本,所以我们使用卷积网络结构来处理电影标题。
-
尺寸大小应该如何设计? 这涉及到信息熵的理念:越丰富的信息,维度越高。所以,信息量较少的原始特征可以用更短的向量表示,例如性别、年龄和职业这三个特征向量均设置成16维,而用户ID和电影ID这样较多信息量的特征设置成32维。综合了4个原始用户特征的向量和综合了3个电影特征的向量均设计成200维度,使得它们可以蕴含更丰富的信息。当然,尺寸大小并没有一贯的最优规律,需要我们根据问题的复杂程度,训练样本量,特征的信息量等多方面信息探索出最有效的设计。(矩阵设计成为多少维度这个是没有固定的计算方法,需要依据问题的复杂程度,训练样本量,特征的信息量等多方面信息探索出最有效的设计)
Embedding介绍
原理为:实际上,Embedding层和Conv2D, Linear层一样,Embedding层也有可学习的权重,通过矩阵相乘的方法对输入数据进行映射。Embedding中将输入映射成向量的实际步骤是:
-
将输入数据转换成one-hot格式的向量;
-
one-hot向量和Embedding层的权重进行矩阵相乘得到Embedding的结果。
Embedding是一个嵌入层,将输入的非负整数矩阵中的每个数值,转换为具有固定长度的向量。
import paddle
from paddle.nn import Linear, Embedding, Conv2D
import numpy as np
import paddle.nn.functional as F
# 声明用户的最大ID,在此基础上加1(算上数字0)
USR_ID_NUM = 6040 + 1
# 声明Embedding 层,将ID映射为32长度的向量
usr_emb = Embedding(num_embeddings=USR_ID_NUM,
embedding_dim=32,
sparse=False)
# 声明输入数据,将其转成tensor
arr_1 = np.array([1], dtype="int64").reshape((-1))
print(arr_1)
arr_pd1 = paddle.to_tensor(arr_1)
print(arr_pd1)
# 计算结果
emb_res = usr_emb(arr_pd1)
# 打印结果
print("数字 1 的embedding结果是: ", emb_res.numpy(), "\n形状是:", emb_res.shape)
[1]
Tensor(shape=[1], dtype=int64, place=CPUPlace, stop_gradient=True,
[1])
数字 1 的embedding结果是: [[-0.02908065 0.02972135 0.00829487 0.02207392 0.00958551 -0.00077017
0.00740909 0.01082573 0.01397826 -0.01737736 0.00206096 -0.02563335
0.01654972 -0.00394134 -0.01858945 -0.02632484 -0.00535294 0.02170646
-0.00065703 -0.01276188 0.02922116 0.02545989 0.00814354 -0.01493532
-0.0240198 -0.02508464 0.01944372 0.0245777 -0.01260571 0.01378552
-0.00929949 0.02869129]]
形状是: [1, 32]
使用Embedding时,需要注意num_embeddings
和embedding_dim
这两个参数。num_embeddings
表示词表大小;embedding_dim
表示Embedding层维度。
下面展示了另一个使用Embedding函数的案例。该案例从0到9的10个ID数字中随机取出了3个,查看使用默认初始化方式的Embedding结果,再查看使用KaimingNormal(0均值的正态分布)初始化方式的Embedding结果。实际上,无论使用哪种参数初始化的方式,这些参数都是要在后续的训练过程中优化的,只是更符合任务场景的初始化方式可以使训练更快收敛,部分场景可以取得略好的模型精度。
# 声明用户的最大ID,在此基础上加1(算上数字0)
USR_ID_NUM = 10
# 声明Embedding 层,将ID映射为16长度的向量
usr_emb = Embedding(num_embeddings=USR_ID_NUM,
embedding_dim=16,
sparse=False)
# 定义输入数据,输入数据为不超过10的整数,将其转成tensor
arr = np.random.randint(0, 10, (3)).reshape((-1)).astype('int64')
print("输入数据是:", arr)
arr_pd = paddle.to_tensor(arr)
emb_res = usr_emb(arr_pd)
print("默认权重初始化embedding层的映射结果是:", emb_res.numpy())
# 观察Embedding层的权重
emb_weights = usr_emb.state_dict()
print(emb_weights.keys())
print("\n查看embedding层的权重形状:", emb_weights['weight'].shape)
# 声明Embedding 层,将ID映射为16长度的向量,自定义权重初始化方式
# 定义KaimingNorma初始化方式
init = paddle.nn.initializer.KaimingNormal()
param_attr = paddle.ParamAttr(initializer=init)
usr_emb2 = Embedding(num_embeddings=USR_ID_NUM,
embedding_dim=16,
weight_attr=param_attr)
emb_res = usr_emb2(arr_pd)
print("\KaimingNormal初始化权重embedding层的映射结果是:", emb_res.numpy())
输入数据是: [1 4 9]
默认权重初始化embedding层的映射结果是: [[-0.07559231 0.46109134 -0.17617545 -0.2632709 0.04440361 0.03021485
-0.33481532 0.46221858 0.18059582 -0.310695 0.35020006 -0.2682178
-0.09613737 -0.41793132 -0.29280508 -0.12745944]
[-0.38570142 0.166713 -0.34719235 -0.16140386 -0.06184858 -0.20724228
-0.06870237 0.44924378 -0.38813472 0.3347426 0.40100622 0.05968314
-0.40234727 -0.04549411 -0.41187727 -0.23398468]
[-0.35217947 0.17998767 0.2391913 0.25739622 0.44642985 0.2946385
-0.17868212 -0.32654923 0.39072788 -0.4247006 0.11755955 0.40622634
0.37872875 -0.3500846 0.33671618 -0.382438 ]]
odict_keys(['weight'])
查看embedding层的权重形状: [10, 16]
\KaimingNormal初始化权重embedding层的映射结果是: [[-0.28677347 -0.1705432 0.2987513 0.24275354 -0.6326506 -0.45437163
-0.07351629 -0.21466267 0.63450587 -0.32486862 0.5489589 -1.3371551
-0.1845428 -0.3407158 0.01930108 -0.19336407]
[ 0.5956444 -0.2708108 0.27161372 -0.4332114 -0.473155 -0.5902645
-0.7067252 -0.29935208 -0.40748447 -0.02663262 0.7250387 0.37857506
0.1831376 -0.17772828 -0.22422215 -0.03368116]
[-0.68832195 0.75668186 -0.57853657 0.18433066 0.03109945 0.14204665
0.13284403 -1.57638 0.25036728 -0.32798365 0.104678 -0.28829753
-0.7802544 0.30158752 0.35971668 -0.54211533]]
上面代码中,我们在[0, 10]范围内随机产生了3个整数,因此数据的最大值为整数9,最小为0。因此,输入数据映射为每个one-hot向量的维度是10,定义Embedding权重的第一个维度USR_ID_NUM为10。
这里输入的数据shape是[3, 1],Embedding层的权重形状则是[10, 16],Embedding在计算时,首先将输入数据转换成one-hot向量,one-hot向量的长度和Embedding层的输入参数size的第一个维度有关。比如这里我们设置的是10,所以输入数据将被转换成维度为[3, 10]的one-hot向量,参数size决定了Embedding层的权重形状。最终维度为[3, 10]的one-hot向量与维度为[10, 16]Embedding权重相乘,得到最终维度为[3, 16]的映射向量。我们也可以对Embeding层的权重进行初始化,如果不设置初始化方式,则采用默认的初始化方式。
神经网络处理文本数据时,需要用数字代替文本,Embedding层则是将输入数字数据映射成了高维向量,然后就可以使用卷积、全连接、LSTM等网络层处理数据了,接下来我们开始设计用户和电影数据的特征提取网络。
提供用户特征
用户特征网络主要包括:
- 将用户ID数据映射为向量表示,通过全连接层得到ID特征。
- 将用户性别数据映射为向量表示,通过全连接层得到性别特征。
- 将用户职业数据映射为向量表示,通过全连接层得到职业特征。
- 将用户年龄数据影射为向量表示,通过全连接层得到年龄特征。
- 融合ID、性别、职业、年龄特征,得到用户的特征表示。
在用户特征计算网络中,我们对每个用户数据做embedding处理,然后经过一个全连接层,激活函数使用ReLU,得到用户所有特征后,将特征整合,经过一个全连接层得到最终的用户数据特征,该特征的维度是200维,用于和电影特征计算相似度。
开始构建用户ID的特征提取网络,ID特征提取包括两个部分。首先,使用Embedding将用户ID映射为向量;然后,使用一层全连接层和ReLU激活函数进一步提取用户ID特征。 相比较电影类别和电影名称,用户ID只包含一个数字,数据更为简单。这里需要考虑将用户ID映射为多少维度的向量合适,使用维度过大的向量表示用户ID容易造成信息冗余,维度过低又不足以表示该用户的特征。理论上来说,如果使用二进制表示用户ID,用户最大ID是6040,小于2的13次方,因此,理论上使用13维度的向量已经足够了,为了让不同ID的向量更具区分性,我们选择将用户ID映射为维度为32维的向量。
1. 提取用户ID特征
# 自定义一个用户ID数据
usr_id_data = np.random.randint(0, 6040, (2)).reshape((-1)).astype('int64')
print("输入的用户ID是:", usr_id_data)
USR_ID_NUM = 6040 + 1
# 定义用户ID的embedding层和fc层
usr_emb = Embedding(num_embeddings=USR_ID_NUM,
embedding_dim=32,
sparse=False)
usr_fc = Linear(in_features=32, out_features=32)
usr_id_var = paddle.to_tensor(usr_id_data)
usr_id_feat = usr_fc(usr_emb(usr_id_var))
usr_id_feat = F.relu(usr_id_feat)
print("用户ID的特征是:", usr_id_feat.numpy(), "\n其形状是:", usr_id_feat.shape)
输入的用户ID是: [3673 603]
用户ID的特征是: [[0. 0.01973414 0.02144782 0. 0. 0.
0.00363597 0. 0. 0. 0.00861626 0.03429735
0. 0.01516718 0.03690353 0. 0. 0.01882378
0. 0. 0.01090848 0.02734157 0. 0.01510335
0. 0. 0.00086916 0.00083421 0.0375373 0.01950407
0. 0.0036423 ]
[0.00177084 0.01983142 0. 0. 0. 0.00400646
0.02201955 0. 0. 0. 0. 0.
0. 0.00673456 0.00300174 0. 0.02012531 0.
0.00057964 0.00807733 0. 0. 0. 0.
0.00551907 0.01994854 0.01152299 0.01468143 0. 0.02226564
0.01047609 0. ]]
其形状是: [2, 32]
注意到,将用户ID映射为one-hot向量时,Embedding层参数size的第一个参数是,在用户的最大ID基础上加上1。原因很简单,从上一节数据处理已经发现,用户ID是从1开始计数的,最大的用户ID是6040。并且已经知道通过Embedding映射输入数据时,是先把输入数据转换成one-hot向量。向量中只有一个 1 的向量才被称为one-hot向量,比如,0 用四维的on-hot向量表示是[1, 0 ,0 ,0],同时,4维的one-hot向量最大只能表示3。所以,要把数字6040用one-hot向量表示,至少需要用6041维度的向量。
融合用户特征
特征融合是一种常用的特征增强手段,通过结合不同特征的长处,达到取长补短的目的。简单的融合方法有:特征(加权)相加、特征级联、特征正交等等。此处使用特征融合是为了将用户的多个特征融合到一起,用单个向量表示每个用户,更方便计算用户与电影的相似度。上文使用Embedding加全连接的方法,分别得到了用户ID、年龄、性别、职业的特征向量,可以使用全连接层将每个特征映射到固定长度,然后进行相加,得到融合特征。
这里使用全连接层进一步提取特征,而不是直接相加得到用户特征的原因有两点:
- 一是用户每个特征数据维度不一致,无法直接相加;
- 二是用户每个特征仅使用了一层全连接层,提取特征不充分,多使用一层全连接层能进一步提取特征。而且,这里用高维度(200维)的向量表示用户特征,能包含更多的信息,每个用户特征之间的区分也更明显。
上述实现中需要对每个特征都使用一个全连接层,实现较为复杂,一种简单的替换方式是,先将每个用户特征沿着长度维度进行级联,然后使用一个全连接层获得整个用户特征向量,两种方式的对比见下图:
相似度计算
计算得到用户特征和电影特征后,我们还需要计算特征之间的相似度。如果一个用户对某个电影很感兴趣,并给了五分评价,那么该用户和电影特征之间的相似度是很高的。
衡量向量距离(相似度)有多种方案:欧式距离、曼哈顿距离、切比雪夫距离、余弦相似度等,本节我们使用忽略尺度信息的余弦相似度构建相似度矩阵。余弦相似度又称为余弦相似性,是通过计算两个向量的夹角余弦值来评估他们的相似度,如下图,两条红色的直线表示两个向量,之间的夹角可以用来表示相似度大小,角度为0时,余弦值为1,表示完全相似。
博文参考
今天的文章深度学习——推荐算法基础原理分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/63627.html