相似度计算

计算得到用户特征和电影特征后,我们还需要计算特征之间的相似度。如果一个用户对某个电影很感兴趣,并给了五分评价,那么该用户和电影特征之间的相似度是很高的。

衡量向量距离(相似度)有多种方案:欧式距离、曼哈顿距离、切比雪夫距离、余弦相似度等,本节我们使用忽略尺度信息的余弦相似度构建相关性矩阵。余弦相似度又称为余弦相似性,是通过计算两个向量的夹角余弦值来评估他们的相似度,如下图,两条红色的直线表示两个向量,之间的夹角可以用来表示相似度大小,角度为0时,余弦值为1,表示完全相似。

相似度计算 - 图1

余弦相似度的公式为:

相似度计算 - 图2

下面是计算相似度的实现方法,输入用户特征和电影特征,计算出两者之间的相似度。另外,我们将用户对电影的评分作为相似度衡量的标准,由于相似度的数据范围是[0, 1],还需要把计算的相似度扩大到评分数据范围,评分分为1-5共5个档次,所以需要将相似度扩大5倍。飞桨已实现的scale API,可以对输入数据进行缩放。同时计算余弦相似度可以使用cos_sim API完成。

  1. def similarty(usr_feature, mov_feature):
  2. res = fluid.layers.cos_sim(usr_feature, mov_feature)
  3. res = fluid.layers.scale(res, scale=5)
  4. return usr_feat, mov_feat, res
  5. # 使用上文计算得到的用户特征和电影特征计算相似度
  6. with fluid.dygraph.guard():
  7. _sim = similarty(usr_feat, mov_feat)
  8. print("相似度是:", np.squeeze(_sim[-1].numpy()))
  1. 相似度是: -0.91126823

从结果中我们发现相似度很小,主要有以下原因:

  • 神经网络并没有训练,模型参数都是随机初始化的,提取出的特征没有规律性。
  • 计算相似度的用户数据和电影数据相关性很小。

在下一节我们就开始训练,让这个网络能够输出有效的用户特征向量和电影特征向量。

总结

本节中,我们介绍了个性化推荐的模型设计部分。包括用户特征网络、电影特征网络和特征相似度计算三部分。

其中,用户特征网络将用户数据映射为固定长度的特征向量,电影特征网络将电影数据映射为固定长度的特征向量,最终利用余弦相似度计算出用户特征和电影特征的相似度。相似度越大,表示用户对该电影更喜欢。

以下为模型设计的完整代码:

  1. class Model(dygraph.layers.Layer):
  2. def __init__(self, use_poster, use_mov_title, use_mov_cat, use_age_job):
  3. super(Model, self).__init__()
  4. # 将传入的name信息和bool型参数添加到模型类中
  5. self.use_mov_poster = use_poster
  6. self.use_mov_title = use_mov_title
  7. self.use_usr_age_job = use_age_job
  8. self.use_mov_cat = use_mov_cat
  9. # 获取数据集的信息,并构建训练和验证集的数据迭代器
  10. Dataset = MovieLen(self.use_mov_poster)
  11. self.Dataset = Dataset
  12. self.trainset = self.Dataset.train_dataset
  13. self.valset = self.Dataset.valid_dataset
  14. self.train_loader = self.Dataset.load_data(dataset=self.trainset, mode='train')
  15. self.valid_loader = self.Dataset.load_data(dataset=self.valset, mode='valid')
  16. """ define network layer for embedding usr info """
  17. USR_ID_NUM = Dataset.max_usr_id + 1
  18. # 对用户ID做映射,并紧接着一个Linear层
  19. self.usr_emb = Embedding([USR_ID_NUM, 32], is_sparse=False)
  20. self.usr_fc = Linear(32, 32)
  21. # 对用户性别信息做映射,并紧接着一个Linear层
  22. USR_GENDER_DICT_SIZE = 2
  23. self.usr_gender_emb = Embedding([USR_GENDER_DICT_SIZE, 16])
  24. self.usr_gender_fc = Linear(16, 16)
  25. # 对用户年龄信息做映射,并紧接着一个Linear层
  26. USR_AGE_DICT_SIZE = Dataset.max_usr_age + 1
  27. self.usr_age_emb = Embedding([USR_AGE_DICT_SIZE, 16])
  28. self.usr_age_fc = Linear(16, 16)
  29. # 对用户职业信息做映射,并紧接着一个Linear层
  30. USR_JOB_DICT_SIZE = Dataset.max_usr_job + 1
  31. self.usr_job_emb = Embedding([USR_JOB_DICT_SIZE, 16])
  32. self.usr_job_fc = Linear(16, 16)
  33. # 新建一个Linear层,用于整合用户数据信息
  34. self.usr_combined = Linear(80, 200, act='tanh')
  35. """ define network layer for embedding usr info """
  36. # 对电影ID信息做映射,并紧接着一个Linear层
  37. MOV_DICT_SIZE = Dataset.max_mov_id + 1
  38. self.mov_emb = Embedding([MOV_DICT_SIZE, 32])
  39. self.mov_fc = Linear(32, 32)
  40. # 对电影类别做映射
  41. CATEGORY_DICT_SIZE = len(Dataset.movie_cat) + 1
  42. self.mov_cat_emb = Embedding([CATEGORY_DICT_SIZE, 32], is_sparse=False)
  43. self.mov_cat_fc = Linear(32, 32)
  44. # 对电影名称做映射
  45. MOV_TITLE_DICT_SIZE = len(Dataset.movie_title) + 1
  46. self.mov_title_emb = Embedding([MOV_TITLE_DICT_SIZE, 32], is_sparse=False)
  47. self.mov_title_conv = Conv2D(1, 1, filter_size=(3, 1), stride=(2,1), padding=0, act='relu')
  48. self.mov_title_conv2 = Conv2D(1, 1, filter_size=(3, 1), stride=1, padding=0, act='relu')
  49. # 新建一个FC层,用于整合电影特征
  50. self.mov_concat_embed = Linear(96, 200, act='tanh')
  51. # 定义计算用户特征的前向运算过程
  52. def get_usr_feat(self, usr_var):
  53. """ get usr features"""
  54. # 获取到用户数据
  55. usr_id, usr_gender, usr_age, usr_job = usr_var
  56. # 将用户的ID数据经过embedding和Linear计算,得到的特征保存在feats_collect中
  57. feats_collect = []
  58. usr_id = self.usr_emb(usr_id)
  59. usr_id = self.usr_fc(usr_id)
  60. usr_id = fluid.layers.relu(usr_id)
  61. feats_collect.append(usr_id)
  62. # 计算用户的性别特征,并保存在feats_collect中
  63. usr_gender = self.usr_gender_emb(usr_gender)
  64. usr_gender = self.usr_gender_fc(usr_gender)
  65. usr_gender = fluid.layers.relu(usr_gender)
  66. feats_collect.append(usr_gender)
  67. # 选择是否使用用户的年龄-职业特征
  68. if self.use_usr_age_job:
  69. # 计算用户的年龄特征,并保存在feats_collect中
  70. usr_age = self.usr_age_emb(usr_age)
  71. usr_age = self.usr_age_fc(usr_age)
  72. usr_age = fluid.layers.relu(usr_age)
  73. feats_collect.append(usr_age)
  74. # 计算用户的职业特征,并保存在feats_collect中
  75. usr_job = self.usr_job_emb(usr_job)
  76. usr_job = self.usr_job_fc(usr_job)
  77. usr_job = fluid.layers.relu(usr_job)
  78. feats_collect.append(usr_job)
  79. # 将用户的特征级联,并通过Linear层得到最终的用户特征
  80. usr_feat = fluid.layers.concat(feats_collect, axis=1)
  81. usr_feat = self.usr_combined(usr_feat)
  82. return usr_feat
  83. # 定义电影特征的前向计算过程
  84. def get_mov_feat(self, mov_var):
  85. """ get movie features"""
  86. # 获得电影数据
  87. mov_id, mov_cat, mov_title, mov_poster = mov_var
  88. feats_collect = []
  89. # 获得batchsize的大小
  90. batch_size = mov_id.shape[0]
  91. # 计算电影ID的特征,并存在feats_collect中
  92. mov_id = self.mov_emb(mov_id)
  93. mov_id = self.mov_fc(mov_id)
  94. mov_id = fluid.layers.relu(mov_id)
  95. feats_collect.append(mov_id)
  96. # 如果使用电影的种类数据,计算电影种类特征的映射
  97. if self.use_mov_cat:
  98. # 计算电影种类的特征映射,对多个种类的特征求和得到最终特征
  99. mov_cat = self.mov_cat_emb(mov_cat)
  100. mov_cat = fluid.layers.reduce_sum(mov_cat, dim=1, keep_dim=False)
  101. mov_cat = self.mov_cat_fc(mov_cat)
  102. feats_collect.append(mov_cat)
  103. if self.use_mov_title:
  104. # 计算电影名字的特征映射,对特征映射使用卷积计算最终的特征
  105. mov_title = self.mov_title_emb(mov_title)
  106. mov_title = self.mov_title_conv2(self.mov_title_conv(mov_title))
  107. mov_title = fluid.layers.reduce_sum(mov_title, dim=2, keep_dim=False)
  108. mov_title = fluid.layers.relu(mov_title)
  109. mov_title = fluid.layers.reshape(mov_title, [batch_size, -1])
  110. feats_collect.append(mov_title)
  111. # 使用一个全连接层,整合所有电影特征,映射为一个200维的特征向量
  112. mov_feat = fluid.layers.concat(feats_collect, axis=1)
  113. mov_feat = self.mov_concat_embed(mov_feat)
  114. return mov_feat
  115. # 定义个性化推荐算法的前向计算
  116. def forward(self, usr_var, mov_var):
  117. # 计算用户特征和电影特征
  118. usr_feat = self.get_usr_feat(usr_var)
  119. mov_feat = self.get_mov_feat(mov_var)
  120. # 根据计算的特征计算相似度
  121. res = fluid.layers.cos_sim(usr_feat, mov_feat)
  122. # 将相似度扩大范围到和电影评分相同数据范围
  123. res = fluid.layers.scale(res, scale=5)
  124. return usr_feat, mov_feat, res