人工智能学习与实训笔记(五):神经网络之推荐系统处理

目录

​​​​​​​七、智能推荐系统处理

7.1 常用的推荐系统算法

7.2 如何实现推荐​​​​​​​

7.3 基于飞桨实现的电影推荐模型

7.3.1 电影数据类型

7.3.2 数据处理

7.3.4 数据读取器

7.3.4 网络构建

7.3.4.1用户特征提取

7.3.4.2 电影特征提取

7.3.4.3 相似度计算

7.3.4.4 网络模型完整代码

7.3 根据推荐案例的思考


七、智能推荐系统处理

7.1 常用的推荐系统算法

常用的推荐系统算法实现方案有三种:

  1. 协同过滤推荐(Collaborative Filtering Recommendation):该算法的核心是分析用户的兴趣和行为,利用共同行为习惯的群体有相似喜好的原则,推荐用户感兴趣的信息。兴趣有高有低,算法会根据用户对信息的反馈(如评分)进行排序,这种方式在学术上称为协同过滤。协同过滤算法是经典的推荐算法,经典意味着简单、好用。协同过滤算法又可以简单分为两种:

    a)基于用户的协同过滤:根据用户的历史喜好分析出相似兴趣的人,然后给用户推荐其他人喜欢的物品。假如小李,小张对物品A、B都给了十分好评,那么可以认为小李、小张具有相似的兴趣爱好,如果小李给物品C十分好评,那么可以把C推荐给小张,可简单理解为“人以类聚”。

    基于用户初始选择后续选择系统推荐
    小李ABC 
    小张AB C

    b)基于物品的协同过滤:根据用户的历史喜好分析出相似物品,然后给用户推荐同类物品。比如小李对物品A、B、C给了十分好评,小王对物品A、C给了十分好评,从这些用户的喜好中分析出喜欢A的人都喜欢C,物品A、C是相似的,如果小张给了A好评,那么可以把C也推荐给小张,可简单理解为“物以群分”。

    基于物品初始选择后续选择系统推荐
    小李ABC  
    小王AC  
    小张A C
  2. 基于内容过滤推荐(Content-based Filtering Recommendation):基于内容的过滤是信息检索领域的重要研究内容,是更为简单直接的算法,该算法的核心是衡量出两个物品的相似度。首先对物品或内容的特征作出描述,发现其相关性,然后基于用户以往的喜好记录,推荐给用户相似的物品。比如,小张对物品A感兴趣,而物品A和物品C是同类物品(从物品的内容描述上判断),可以把物品C也推荐给小张。

  3. 组合推荐(Hybrid Recommendation):以上算法各有优缺点,比如基于内容的过滤推荐是基于物品建模,在系统启动初期往往有较好的推荐效果,但是没有考虑用户群体的关联属性;协同过滤推荐考虑了用户群体喜好信息,可以推荐内容上不相似的新物品,发现用户潜在的兴趣偏好,但是这依赖于足够多且准确的用户历史信息。所以,实际应用中往往不只采用某一种推荐方法,而是通过一定的组合方法将多个算法混合在一起,以实现更好的推荐效果,比如加权混合、分层混合等。具体选择哪种方式和应用场景有很大关系。

7.2 如何实现推荐

以电影推荐为例:

如果能将用户A的原始特征转变成一种代表用户A喜好的特征向量,将电影1的原始特征转变成一种代表电影1特性的特征向量。那么,我们计算两个向量的相似度,就可以代表用户A对电影1的喜欢程度。据此,推荐系统可以如此构建:

假如给用户A推荐,计算电影库中“每一个电影的特征向量”与“用户A的特征向量”的余弦相似度,根据相似度排序电影库,取 Top k的电影推荐给A。


这样设计的核心是两个特征向量的有效性,它们会决定推荐的效果。

基于以上思路,可以将电影推荐网络模型设计如下:

  • 将用户信息和电影信息的各类数据提取为向量(通过embedding,分别得到ID,性别,职业等等的特征向量)
  • 再将各个类别的特征向量进行整合计算(全连接),综合得到每个用户的特征向量(200维)和每个电影的特征向量(200维),
  • 将两个特征向量进行比较(余弦相似度),得到预测评分
  • 将预测评分和实际评分进行比较并使得分差收敛,从而完成网络训练

最终在完成模型训练时,将获得所有用户和电影的特征向量,正是基于这些向量,我们可以比较用户和电影的特征相似度,从而进行推荐。所以我们并不是直接使用网络来进行电影推荐。

7.3 基于飞桨实现的电影推荐模型

7.3.1 电影数据类型

本次实践我们采用ml-1m电影推荐数据集,它是GroupLens Research从MovieLens网站上收集并提供的电影评分数据集。包含了6000多位用户对近3900个电影的共100万条评分数据,评分均为1~5的整数,其中每个电影的评分数据至少有20条。该数据集包含三个数据文件,分别是:

  • users.dat:存储用户属性信息的文本格式文件。
  • movies.dat:存储电影属性信息的文本格式文件。
  • ratings.dat:存储电影评分信息的文本格式文件。
  • posters:包含部分电影海报图像。
  • new_rating.txt:存储包含海报图像的新评分数据文件。

用户信息、电影信息和评分信息包含的内容如下表所示。

用户信息UserIDGenderAgeOccupation
样例1F【M/F】110
电影信息MovieIDTitleGenresPosterID
样例1Toy StoryAnimation| Children's|Comedy1
评分信息UserIDMovieIDRating
样例111935【1~5】

其中部分数据并不具有真实的含义,而是编号。年龄编号和部分职业编号的含义如下表所示。

年龄编号职业编号
  • 1: "Under 18"
  • 18: "18-24"
  • 25: "25-34"
  • 35: "35-44"
  • 45: "45-49"
  • 50: "50-55"
  • 56: "56+"
  • 0: "other" or not specified
  • 1: "academic/educator"
  • 2: "artist"
  • 3: "clerical/admin"
  • 4: "college/grad student"
  • 5: "customer service"
  • 6: "doctor/health care"
  • 7: "executive/managerial"

海报对应着尺寸大约为180*270的图片,每张图片尺寸稍有差别。


从样例的特征数据中,我们可以分析出特征一共有四类:

  1. ID类特征:UserID、MovieID、Gender、Age、Occupation,内容为ID值,前两个ID映射到具体用户和电影,后三个ID会映射到具体分档。
  2. 列表类特征:Genres,每个电影有多个类别标签。如果将电影类别编号,使用数字ID替换原始类别,特征内容是对应几个ID值的列表。
  3. 图像类特征:Poster,内容是一张180×270的图片。
  4. 文本类特征:Title,内容是一段英文文本。

因为特征数据有四种不同类型,所以构建模型网络的输入层预计也会有四种子结构。

7.3.2 数据处理

用户数据处理:

import numpy as np
def get_usr_info(path):# 性别转换函数,M-0, F-1def gender2num(gender):return 1 if gender == 'F' else 0# 打开文件,读取所有行到data中with open(path, 'r') as f:data = f.readlines()# 建立用户信息的字典use_info = {}max_usr_id = 0#按行索引数据for item in data:# 去除每一行中和数据无关的部分item = item.strip().split("::")usr_id = item[0]# 将字符数据转成数字并保存在字典中use_info[usr_id] = {'usr_id': int(usr_id),'gender': gender2num(item[1]),'age': int(item[2]),'job': int(item[3])}max_usr_id = max(max_usr_id, int(usr_id))return use_info, max_usr_idusr_file = "./work/ml-1m/users.dat"
usr_info, max_usr_id = get_usr_info(usr_file)
print("用户数量:", len(usr_info))
print("最大用户ID:", max_usr_id)
print("第1个用户的信息是:", usr_info['1'])
------------------------------------------------------------------------------
用户数量: 6040
最大用户ID: 6040
第1个用户的信息是: {'usr_id': 1, 'gender': 1, 'age': 1, 'job': 10}

电影数据处理:

def get_movie_info(path):# 打开文件,编码方式选择ISO-8859-1,读取所有数据到data中 with open(path, 'r', encoding="ISO-8859-1") as f:data = f.readlines()# 建立三个字典,分别用户存放电影所有信息,电影的名字信息、类别信息movie_info, movie_titles, movie_cat = {}, {}, {}# 对电影名字、类别中不同的单词计数t_count, c_count = 1, 1# 初始化电影名字和种类的列表titles = []cats = []count_tit = {}# 按行读取数据并处理for item in data:item = item.strip().split("::")v_id = item[0]v_title = item[1][:-7]cats = item[2].split('|')v_year = item[1][-5:-1]titles = v_title.split()# 统计电影名字的单词,并给每个单词一个序号,放在movie_titles中for t in titles:if t not in movie_titles:movie_titles[t] = t_countt_count += 1# 统计电影类别单词,并给每个单词一个序号,放在movie_cat中for cat in cats:if cat not in movie_cat:movie_cat[cat] = c_countc_count += 1# 补0使电影名称对应的列表长度为15v_tit = [movie_titles[k] for k in titles]while len(v_tit)<15:v_tit.append(0)# 补0使电影种类对应的列表长度为6v_cat = [movie_cat[k] for k in cats]while len(v_cat)<6:v_cat.append(0)# 保存电影数据到movie_info中movie_info[v_id] = {'mov_id': int(v_id),'title': v_tit,'category': v_cat,'years': int(v_year)}return movie_info, movie_cat, movie_titlesmovie_info_path = "./work/ml-1m/movies.dat"
movie_info, movie_cat, movie_titles = get_movie_info(movie_info_path)
print("电影数量:", len(movie_info))
ID = 1
print("原始的电影ID为 {} 的数据是:".format(ID), data[ID-1])
print("电影ID为 {} 的转换后数据是:".format(ID), movie_info[str(ID)])print("电影种类对应序号:'Animation':{} 'Children's':{} 'Comedy':{}".format(movie_cat['Animation'], movie_cat["Children's"], movie_cat['Comedy']))
print("电影名称对应序号:'The':{} 'Story':{} ".format(movie_titles['The'], movie_titles['Story']))
--------------------------------------------------------------------------------
电影数量: 3883
原始的电影ID为 1 的数据是: 1::Toy Story (1995)::Animation|Children's|Comedy电影ID为 1 的转换后数据是: {'mov_id': 1, 'title': [1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'category': [1, 2, 3, 0, 0, 0], 'years': 1995}
电影种类对应序号:'Animation':1 'Children's':2 'Comedy':3
电影名称对应序号:'The':26 'Story':2 

评分数据处理:

def get_rating_info(path):# 打开文件,读取所有行到data中with open(path, 'r') as f:data = f.readlines()# 创建一个字典rating_info = {}for item in data:item = item.strip().split("::")# 处理每行数据,分别得到用户ID,电影ID,和评分usr_id,movie_id,score = item[0],item[1],item[2]if usr_id not in rating_info.keys():rating_info[usr_id] = {movie_id:float(score)}else:rating_info[usr_id][movie_id] = float(score)return rating_info# 获得评分数据
#rating_path = "./work/ml-1m/ratings.dat"
rating_info = get_rating_info(rating_path)
print("ID为1的用户一共评价了{}个电影".format(len(rating_info['1'])))
-------------------------------------------------------------------------------
ID为1的用户一共评价了53个电影

海报数据处理:

from PIL import Image
import matplotlib.pyplot as plt# 使用海报图像和不使用海报图像的文件路径不同,处理方式相同
use_poster = True
if use_poster:rating_path = "./work/ml-1m/new_rating.txt"
else:rating_path = "./work/ml-1m/ratings.dat"with open(rating_path, 'r') as f:data = f.readlines()# 从新的rating文件中收集所有的电影ID
mov_id_collect = []
for item in data:item = item.strip().split("::")usr_id,movie_id,score = item[0],item[1],item[2]mov_id_collect.append(movie_id)# 根据电影ID读取图像
poster_path = "./work/ml-1m/posters/"# 显示mov_id_collect中第几个电影ID的图像
idx = 1poster = Image.open(poster_path+'mov_id{}.jpg'.format(str(mov_id_collect[idx])))plt.figure("Image") # 图像窗口名称
plt.imshow(poster)
plt.axis('on') # 关掉坐标轴为 off
plt.title("poster with ID {}".format(mov_id_collect[idx])) # 图像题目
plt.show()

7.3.4 数据读取器

首先,构造一个函数,把读取并处理后的数据整合到一起,即在rating数据中补齐用户和电影的所有特征字段。

def get_dataset(usr_info, rating_info, movie_info):trainset = []# 按照评分数据的key值索引数据for usr_id in rating_info.keys():usr_ratings = rating_info[usr_id]for movie_id in usr_ratings:trainset.append({'usr_info': usr_info[usr_id],'mov_info': movie_info[movie_id],'scores': usr_ratings[movie_id]})return trainsetdataset = get_dataset(usr_info, rating_info, movie_info)
print("数据集总数据数:", len(dataset))
--------------------------------------------------------------------------
数据集总数据数: 1000209

接下来构建数据读取器函数load_data():

import random
use_poster = False
def load_data(dataset=None, mode='train'):# 定义数据迭代Batch大小BATCHSIZE = 256data_length = len(dataset)index_list = list(range(data_length))# 定义数据迭代加载器def data_generator():# 训练模式下,打乱训练数据if mode == 'train':random.shuffle(index_list)# 声明每个特征的列表usr_id_list,usr_gender_list,usr_age_list,usr_job_list = [], [], [], []mov_id_list,mov_tit_list,mov_cat_list,mov_poster_list = [], [], [], []score_list = []# 索引遍历输入数据集for idx, i in enumerate(index_list):# 获得特征数据保存到对应特征列表中usr_id_list.append(dataset[i]['usr_info']['usr_id'])usr_gender_list.append(dataset[i]['usr_info']['gender'])usr_age_list.append(dataset[i]['usr_info']['age'])usr_job_list.append(dataset[i]['usr_info']['job'])mov_id_list.append(dataset[i]['mov_info']['mov_id'])mov_tit_list.append(dataset[i]['mov_info']['title'])mov_cat_list.append(dataset[i]['mov_info']['category'])mov_id = dataset[i]['mov_info']['mov_id']if use_poster:# 不使用图像特征时,不读取图像数据,加快数据读取速度poster = Image.open(poster_path+'mov_id{}.jpg'.format(str(mov_id)))poster = poster.resize([64, 64])if len(poster.size) <= 2:poster = poster.convert("RGB")mov_poster_list.append(np.array(poster))score_list.append(int(dataset[i]['scores']))# 如果读取的数据量达到当前的batch大小,就返回当前批次if len(usr_id_list)==BATCHSIZE:# 转换列表数据为数组形式,reshape到固定形状usr_id_arr = np.array(usr_id_list)usr_gender_arr = np.array(usr_gender_list)usr_age_arr = np.array(usr_age_list)usr_job_arr = np.array(usr_job_list)mov_id_arr = np.array(mov_id_list)mov_cat_arr = np.reshape(np.array(mov_cat_list), [BATCHSIZE, 6]).astype(np.int64)mov_tit_arr = np.reshape(np.array(mov_tit_list), [BATCHSIZE, 1, 15]).astype(np.int64)if use_poster:mov_poster_arr = np.reshape(np.array(mov_poster_list)/127.5 - 1, [BATCHSIZE, 3, 64, 64]).astype(np.float32)else:mov_poster_arr = np.array([0.])scores_arr = np.reshape(np.array(score_list), [-1, 1]).astype(np.float32)# 返回当前批次数据yield [usr_id_arr, usr_gender_arr, usr_age_arr, usr_job_arr], \[mov_id_arr, mov_cat_arr, mov_tit_arr, mov_poster_arr], scores_arr# 清空数据usr_id_list, usr_gender_list, usr_age_list, usr_job_list = [], [], [], []mov_id_list, mov_tit_list, mov_cat_list, score_list = [], [], [], []mov_poster_list = []return data_generator# 声明数据读取类
dataset = MovieLen(False)
# 定义数据读取器
train_loader = dataset.load_data(dataset=dataset.train_dataset, mode='train')
# 迭代的读取数据, Batchsize = 256
for idx, data in enumerate(train_loader()):usr, mov, score = dataprint("打印用户ID,性别,年龄,职业数据的维度:")for v in usr:print(v.shape)print("打印电影ID,名字,类别数据的维度:")for v in mov:print(v.shape)break
----------------------------------------------------------------------------------
##Total dataset instances:  1000209
##MovieLens dataset information: 
usr num: 6040
movies num: 3883
打印用户ID,性别,年龄,职业数据的维度:
(256,)
(256,)
(256,)
(256,)
打印电影ID,名字,类别数据的维度:
(256,)
(256, 6)
(256, 1, 15)
(1,)

7.3.4 网络构建

7.3.4.1用户特征提取

用户特征网络主要包括:

  1. 将用户ID数据映射为向量表示,通过全连接层得到ID特征。
  2. 将用户性别数据映射为向量表示,通过全连接层得到性别特征。
  3. 将用户职业数据映射为向量表示,通过全连接层得到职业特征。
  4. 将用户年龄数据影射为向量表示,通过全连接层得到年龄特征。
  5. 融合ID、性别、职业、年龄特征,得到用户的特征表示。

在用户特征计算网络中,我们对每个用户数据做embedding处理,然后经过一个全连接层,激活函数使用ReLU,得到用户所有特征后,将特征整合,经过一个全连接层得到最终的用户数据特征,该特征的维度是200维,用于和电影特征计算相似度。

1. 提取用户特征。首先分别对用户不同类别的数据(ID, 性别,职业,年龄)使用embedding提取特征向量,下面以提取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是: [404  55]
用户ID的特征是: [[0.02347164 0.         0.00114313 0.         0.         0.010863940.         0.         0.         0.01131915 0.02601143 0.01917450.         0.         0.         0.03315771 0.         0.0.0051814  0.01694535 0.         0.02378889 0.00864096 0.029596940.0082613  0.         0.02341099 0.         0.         0.0.         0.0023096 ][0.03071208 0.         0.         0.00296267 0.01383776 0.0.         0.         0.00759998 0.00332768 0.00829613 0.02288110.         0.01196356 0.         0.01992911 0.01161416 0.009632540.         0.01359453 0.00222658 0.03191457 0.         0.028979260.         0.         0.03653618 0.         0.         0.005121430.         0.        ]] 
其形状是: [2, 32]

2. 融合用户所有特征。有两种方式实现:

        2.1 每个特征使用一个全连接层:

        每个特征向量通过全连接层统一扩展为200的维度,再进行相加

FC_ID = Linear(in_features=32, out_features=200)
FC_JOB = Linear(in_features=16, out_features=200)
FC_AGE = Linear(in_features=16, out_features=200)
FC_GENDER = Linear(in_features=16, out_features=200)# 收集所有的用户特征
_features = [usr_id_feat, usr_job_feat, usr_age_feat, usr_gender_feat]
_features = [k.numpy() for k in _features]
_features = [paddle.to_tensor(k) for k in _features]id_feat = F.tanh(FC_ID(_features[0]))
job_feat = F.tanh(FC_JOB(_features[1]))
age_feat = F.tanh(FC_AGE(_features[2]))
genger_feat = F.tanh(FC_GENDER(_features[-1]))# 对特征求和
usr_feat = id_feat + job_feat + age_feat + genger_feat
print("用户融合后特征的维度是:", usr_feat.shape)
----------------------------------------------------------------------
用户融合后特征的维度是: [2, 200]

        2.2 使用一个全连接层

        上述实现中需要对每个特征都使用一个全连接层,实现较为复杂,一种简单的替换方式是,先将每个用户特征沿着长度维度进行级联,然后使用一个全连接层获得整个用户特征向量,两种方式的对比见下图:

usr_combined = Linear(in_features=80, out_features=200)# 收集所有的用户特征
_features = [usr_id_feat, usr_job_feat, usr_age_feat, usr_gender_feat]print("打印每个特征的维度:", [f.shape for f in _features])_features = [k.numpy() for k in _features]
_features = [paddle.to_tensor(k) for k in _features]# 对特征沿着最后一个维度级联
usr_feat = paddle.concat(_features, axis=1)
usr_feat = F.tanh(usr_combined(usr_feat))
print("用户融合后特征的维度是:", usr_feat.shape)    
---------------------------------------------------------------------------
打印每个特征的维度: [[2, 32], [2, 16], [2, 16], [2, 16]]
用户融合后特征的维度是: [2, 200]
7.3.4.2 电影特征提取

与用户特征提取类似。

7.3.4.3 相似度计算

得到用户特征和电影特征后,我们还需要计算特征之间的相似度。可以通过余弦相似度来计算。

def similarty(usr_feature, mov_feature):res = F.cosine_similarity(usr_feature, mov_feature)res = paddle.scale(res, scale=5)return usr_feat, mov_feat, res# 使用上文计算得到的用户特征和电影特征计算相似度
usr_feat, mov_feat, _sim = similarty(usr_feat, mov_feat)
print("相似度得分是:", np.squeeze(_sim.numpy()))
7.3.4.4 网络模型完整代码
class Model(nn.Layer):def __init__(self, use_poster, use_mov_title, use_mov_cat, use_age_job):super(Model, self).__init__()# 将传入的name信息和bool型参数添加到模型类中self.use_mov_poster = use_posterself.use_mov_title = use_mov_titleself.use_usr_age_job = use_age_jobself.use_mov_cat = use_mov_cat# 获取数据集的信息,并构建训练和验证集的数据迭代器Dataset = MovieLen(self.use_mov_poster)self.Dataset = Datasetself.trainset = self.Dataset.train_datasetself.valset = self.Dataset.valid_datasetself.train_loader = self.Dataset.load_data(dataset=self.trainset, mode='train')self.valid_loader = self.Dataset.load_data(dataset=self.valset, mode='valid')""" define network layer for embedding usr info """USR_ID_NUM = Dataset.max_usr_id + 1# 对用户ID做映射,并紧接着一个Linear层self.usr_emb = Embedding(num_embeddings=USR_ID_NUM, embedding_dim=32, sparse=False)self.usr_fc = Linear(in_features=32, out_features=32)# 对用户性别信息做映射,并紧接着一个Linear层USR_GENDER_DICT_SIZE = 2self.usr_gender_emb = Embedding(num_embeddings=USR_GENDER_DICT_SIZE, embedding_dim=16)self.usr_gender_fc = Linear(in_features=16, out_features=16)# 对用户年龄信息做映射,并紧接着一个Linear层USR_AGE_DICT_SIZE = Dataset.max_usr_age + 1self.usr_age_emb = Embedding(num_embeddings=USR_AGE_DICT_SIZE, embedding_dim=16)self.usr_age_fc = Linear(in_features=16, out_features=16)# 对用户职业信息做映射,并紧接着一个Linear层USR_JOB_DICT_SIZE = Dataset.max_usr_job + 1self.usr_job_emb = Embedding(num_embeddings=USR_JOB_DICT_SIZE, embedding_dim=16)self.usr_job_fc = Linear(in_features=16, out_features=16)# 新建一个Linear层,用于整合用户数据信息self.usr_combined = Linear(in_features=80, out_features=200)""" define network layer for embedding usr info """# 对电影ID信息做映射,并紧接着一个Linear层MOV_DICT_SIZE = Dataset.max_mov_id + 1self.mov_emb = Embedding(num_embeddings=MOV_DICT_SIZE, embedding_dim=32)self.mov_fc = Linear(in_features=32, out_features=32)# 对电影类别做映射CATEGORY_DICT_SIZE = len(Dataset.movie_cat) + 1self.mov_cat_emb = Embedding(num_embeddings=CATEGORY_DICT_SIZE, embedding_dim=32, sparse=False)self.mov_cat_fc = Linear(in_features=32, out_features=32)# 对电影名称做映射MOV_TITLE_DICT_SIZE = len(Dataset.movie_title) + 1self.mov_title_emb = Embedding(num_embeddings=MOV_TITLE_DICT_SIZE, embedding_dim=32, sparse=False)self.mov_title_conv = Conv2D(in_channels=1, out_channels=1, kernel_size=(3, 1), stride=(2,1), padding=0)self.mov_title_conv2 = Conv2D(in_channels=1, out_channels=1, kernel_size=(3, 1), stride=1, padding=0)# 新建一个FC层,用于整合电影特征self.mov_concat_embed = Linear(in_features=96, out_features=200)user_sizes = [200] + self.fc_sizesacts = ["relu" for _ in range(len(self.fc_sizes))]self._user_layers = []for i in range(len(self.fc_sizes)):linear = Linear(in_features=user_sizes[i],out_features=user_sizes[i + 1],weight_attr=paddle.ParamAttr(initializer=nn.initializer.Normal(std=1.0 / math.sqrt(user_sizes[i]))))self.add_sublayer('linear_user_%d' % i, linear)self._user_layers.append(linear)if acts[i] == 'relu':act = nn.ReLU()self.add_sublayer('user_act_%d' % i, act)self._user_layers.append(act)#电影特征和用户特征使用了不同的全连接层,不共享参数movie_sizes = [200] + self.fc_sizesacts = ["relu" for _ in range(len(self.fc_sizes))]self._movie_layers = []for i in range(len(self.fc_sizes)):linear = nn.Linear(in_features=movie_sizes[i],out_features=movie_sizes[i + 1],weight_attr=paddle.ParamAttr(initializer=nn.initializer.Normal(std=1.0 / math.sqrt(movie_sizes[i]))))self.add_sublayer('linear_movie_%d' % i, linear)self._movie_layers.append(linear)if acts[i] == 'relu':act = nn.ReLU()self.add_sublayer('movie_act_%d' % i, act)self._movie_layers.append(act)# 定义计算用户特征的前向运算过程def get_usr_feat(self, usr_var):""" get usr features"""# 获取到用户数据usr_id, usr_gender, usr_age, usr_job = usr_var# 将用户的ID数据经过embedding和Linear计算,得到的特征保存在feats_collect中feats_collect = []usr_id = self.usr_emb(usr_id)usr_id = self.usr_fc(usr_id)usr_id = F.relu(usr_id)feats_collect.append(usr_id)# 计算用户的性别特征,并保存在feats_collect中usr_gender = self.usr_gender_emb(usr_gender)usr_gender = self.usr_gender_fc(usr_gender)usr_gender = F.relu(usr_gender)feats_collect.append(usr_gender)# 选择是否使用用户的年龄-职业特征if self.use_usr_age_job:# 计算用户的年龄特征,并保存在feats_collect中usr_age = self.usr_age_emb(usr_age)usr_age = self.usr_age_fc(usr_age)usr_age = F.relu(usr_age)feats_collect.append(usr_age)# 计算用户的职业特征,并保存在feats_collect中usr_job = self.usr_job_emb(usr_job)usr_job = self.usr_job_fc(usr_job)usr_job = F.relu(usr_job)feats_collect.append(usr_job)# 将用户的特征级联,并通过Linear层得到最终的用户特征usr_feat = paddle.concat(feats_collect, axis=1)user_features = F.tanh(self.usr_combined(usr_feat))#通过3层全链接层,获得用于计算相似度的用户特征和电影特征for n_layer in self._user_layers:user_features = n_layer(user_features)return user_features# 定义电影特征的前向计算过程def get_mov_feat(self, mov_var):""" get movie features"""# 获得电影数据mov_id, mov_cat, mov_title, mov_poster = mov_varfeats_collect = []# 获得batchsize的大小batch_size = mov_id.shape[0]# 计算电影ID的特征,并存在feats_collect中mov_id = self.mov_emb(mov_id)mov_id = self.mov_fc(mov_id)mov_id = F.relu(mov_id)feats_collect.append(mov_id)# 如果使用电影的种类数据,计算电影种类特征的映射if self.use_mov_cat:# 计算电影种类的特征映射,对多个种类的特征求和得到最终特征mov_cat = self.mov_cat_emb(mov_cat)mov_cat = paddle.sum(mov_cat, axis=1, keepdim=False)mov_cat = self.mov_cat_fc(mov_cat)feats_collect.append(mov_cat)if self.use_mov_title:# 计算电影名字的特征映射,对特征映射使用卷积计算最终的特征mov_title = self.mov_title_emb(mov_title)mov_title = F.relu(self.mov_title_conv2(F.relu(self.mov_title_conv(mov_title))))mov_title = paddle.sum(mov_title, axis=2, keepdim=False)mov_title = F.relu(mov_title)mov_title = paddle.reshape(mov_title, [batch_size, -1])feats_collect.append(mov_title)# 使用一个全连接层,整合所有电影特征,映射为一个200维的特征向量mov_feat = paddle.concat(feats_collect, axis=1)mov_features = F.tanh(self.mov_concat_embed(mov_feat))for n_layer in self._movie_layers:mov_features = n_layer(mov_features)return mov_features# 定义个性化推荐算法的前向计算def forward(self, usr_var, mov_var):# 计算用户特征和电影特征usr_feat = self.get_usr_feat(usr_var)mov_feat = self.get_mov_feat(mov_var)#通过3层全连接层,获得用于计算相似度的用户特征和电影特征for n_layer in self._user_layers:user_features = n_layer(user_features)for n_layer in self._movie_layers:mov_features = n_layer(mov_features)# 根据计算的特征计算相似度res = F.cosine_similarity(user_features, mov_features)# 将相似度扩大范围到和电影评分相同数据范围res = paddle.scale(res, scale=5)return usr_feat, mov_feat, res

7.3 根据推荐案例的思考

  1. Deep Learning is all about “Embedding Everything”。不难发现,深度学习建模是套路满满的。任何事物均用向量的方式表示,可以直接基于向量完成“分类”或“回归”任务;也可以计算多个向量之间的关系,无论这种关系是“相似性”还是“比较排序”。在深度学习兴起不久的2015年,当时AI相关的国际学术会议上,大部分论文均是将某个事物Embedding后再进行挖掘,火热的程度仿佛即使是路边一块石头,也要Embedding一下看看是否能挖掘出价值。直到近些年,能够Embedding的事物基本都发表过论文,Embeddding的方法也变得成熟,这方面的论文才逐渐有减少的趋势。

  2. 在深度学习兴起之前,不同领域之间的迁移学习往往要用到很多特殊设计的算法。但深度学习兴起后,迁移学习变得尤其自然。训练模型和使用模型未必是同样的方式,中间基于Embedding的向量表示,即可实现不同任务交换信息。例如本章的推荐模型使用用户对电影的评分数据进行监督训练,训练好的特征向量可以用于计算用户与用户的相似度,以及电影与电影之间的相似度。对特征向量的使用可以极其灵活,而不局限于训练时的任务。

  3. 网络调参:神经网络模型并没有一套理论上可推导的最优规则,实际中的网络设计往往是在理论和经验指导下的“探索”活动。例如推荐模型的每层网络尺寸的设计遵从了信息熵的原则,原始信息量越大对应表示的向量长度就越长。但具体每一层的向量应该有多长,往往是根据实际训练的效果进行调整。所以,建模工程师被称为数据处理工程师和调参工程师是有道理的,大量的精力花费在处理样本数据和模型调参上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/685532.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一周学会Django5 Python Web开发-Django5应用配置

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计14条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

SpringBoot+Tess4J实现本地与远程图片的文字识别

Spring Boot应用程序里集成Tess4J来实现OCR&#xff08;光学字符识别&#xff09;&#xff0c;以识别出本地和远程图片中的文字 一、添加依赖 <dependency><groupId>net.sourceforge.tess4j</groupId><artifactId>tess4j</artifactId><vers…

上位机图像处理和嵌入式模块部署(图像项目处理过程)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 对于一般的图像项目来说&#xff0c;图像处理只是工作当中的一部分。在整个项目处理的过程中有很多的内容需要处理&#xff0c;比如说了解需求、评…

通过写代码学习AWS DynamoDB (3)- 一致性hash

简介 在本文中&#xff0c;我们将简单介绍一致性hash&#xff08;consistent hash&#xff09;的概念&#xff0c;以及一致性hash可以解决的问题。然后我们将在模拟的DDB实现中实现一个简单版本的基于一致性harsh实现的partition。 问题 在《通过写代码学习AWS DynamoDB &am…

嵌入式——Flash(W25Q64)

目录 一、初识W25Q64 1. 基本认识 2. 引脚介绍 ​编辑 二、W25Q64特性 1. SPI模式 2. 双输出SPI方式 三、状态寄存器 1. BUSY位 2. WEL位 3. BP2、BP1、 BP0位 4. TB位 5. 保留位 6. SRP位 四、常用操作指令 1. 写使能指令&#xff08;06h&#xff09; 2. 写禁…

react【三】受控组件/高阶组件/portals/fragment/严格模式/动画

文章目录 1、受控组件1.1 认识受控组件1.2 checkout1.3 selected1.4 非受控组件 2、高阶组件2.1 认识高阶组件2.2 应用1-props增强的基本使用2.3 对象增强的应用场景-context共享2.4 应用2-鉴权2.5 应用3 – 生命周期劫持2.6、高阶组件的意义 3、Portals4、fragment5、StrictMo…

交大论文下载器

原作者地址&#xff1a; https://github.com/olixu/SJTU_Thesis_Crawler 问题&#xff1a; http://thesis.lib.sjtu.edu.cn/的学位论文下载系统&#xff0c;该版权保护系统用起来很不方便&#xff0c;加载起来非常慢&#xff0c;所以该下载器实现将网页上的每一页的图片合并…

GPU独显下ubuntu屏幕亮度不能调节解决方法

GPU独显下屏幕亮度不能调节&#xff08;假设你已经安装了合适的nvidia显卡驱动&#xff09;&#xff0c;我试过修改 /etc/default/grub 的 GRUB_CMDLINE_LINUX_DEFAULT"quiet splash acpi_backlightvendor" &#xff0c;没用。修改和xorg.conf相关的文件&#xff0c;…

不花一分钱,在 Mac 上跑 Windows(M1/M2 版)

这是在 MacOS M1 上体验最新 Windows11 的效果&#xff1a; VMware Fusion&#xff0c;可以运行 Windows、Linux 系统&#xff0c;个人使用 licence 免费 安装流程见 &#x1f449; https://zhuanlan.zhihu.com/p/452412091 从申请 Fusion licence 到下载镜像&#xff0c;再到…

安装 Windows Server 2019

1.镜像安装 镜像安装:Windows Server 2019 2.安装过程(直接以图的形式呈现) 先选择""我没有产品密钥"",选择桌面体验 选择自定义 设置密码后继续 安装成功

07-k8s中secret资源02-玩转secret

一、回顾secret资源的简单实用 第一步&#xff1a;将想要的数据信息【key&#xff1a;value】中的value值&#xff0c;使用base64编码后&#xff0c;写入secret资源清单中&#xff1b; 第二步&#xff1a;创建secret资源&#xff1b; 第三步&#xff1a;pod资源引用secret资源&…

第2讲springsecurity+vue通用权限系统

阿里云 maven阿里云镜像 <?xml version"1.0" encoding"UTF-8"?><!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for addition…

C++,stl,常用排序算法,常用拷贝和替换算法

目录 1.常用排序算法 sort random_shuffle merge reverse 2.常用拷贝和替换算法 copy replace replace_if swap 1.常用排序算法 sort 默认从小到大排序 #include<bits/stdc.h> using namespace std;int main() {vector<int> v;v.push_back(1);v.push_ba…

【友塔笔试面试复盘】八边形取反问题

问题&#xff1a;一个八边形每条边都是0&#xff0c;现在有取反操作&#xff0c;选择一条边取反会同时把当前边和2个邻边取反&#xff08;如果是0变为1&#xff0c;如果是1变为0&#xff09; 现在问你怎么取反能使得八条边都变为1. 当时陷入了暴力递归漩涡&#xff0c;给出一个…

问题:内存时序参数 CASLatency 是() #学习方法#微信#微信

问题&#xff1a;内存时序参数 CASLatency 是&#xff08;&#xff09; A&#xff0e;行地址控制器延迟时间 B&#xff0e;列地址至行地址延迟时间 C&#xff0e;列地址控制器预充电时间 D&#xff0e;列动态时间 参考答案如图所示

[职场] 求职如何设置预期 #笔记#经验分享

求职如何设置预期 在求职的道路上&#xff0c;无论处于哪个年龄阶段&#xff0c;合理的就业期望值才能使我们的愿望与社会的需求相吻合&#xff0c;才能让自己在今后的工作中发挥出最大的实力与能力。 一、结合测评软件&#xff0c;明确求职目标 根据霍兰德职业兴趣测试结果&a…

题目:3.神奇的数组(蓝桥OJ 3000)

问题描述&#xff1a; 解题思路&#xff1a; 官方&#xff1a; 我的总结&#xff1a; 利用双指针遍历每个区间并判断是否符合条件&#xff1a;若一个区间符合条件则该区间在其左端点不变的情况下的每一个子区间都符合条件&#xff0c;相反若一个区间内左端点固定情况下有一个以…

javax.servlet 和 jakarta.servlet的关系和使用tomcat部署 jakarta.servlet

1&#xff0c;javax.servlet 和 jakarta.servlet的关系 javax.servlet 和 jakarta.servlet 是 Java Servlet API 的两个版本。 Java Servlet API 是由 Sun Microsystems&#xff08;现在是 Oracle&#xff09;开发和维护的&#xff0c;其包名以 javax.servlet 开头。从 Java …

mysql数据库 mvcc

在看MVCC之前我们先补充些基础内容&#xff0c;首先来看下事务的ACID和数据的总体运行流程 数据库整体的使用流程: ACID流程图 mysql核心日志: 在MySQL数据库中有三个非常重要的日志binlog,undolog,redolog. mvcc概念介绍&#xff1a; MVCC&#xff08;Multi-Version Concurr…

17.3.1.3 灰度

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 灰度的算法主要有以下三种&#xff1a; 1、最大值法: 原图像&#xff1a;颜色值color&#xff08;R&#xff0c;G&#xff0c;B&a…