推荐系统,即就是为当用户推荐一些他感兴趣的项目、商品、视频等等,当然在对于小的项目库中能进行很快的推荐,但是随着不断的增加,数据量剧增,这时候就需要我们进行分步骤进行推荐,这就把推荐系统细分为四个部分:召回、粗排、精排、重排。
(1)召回:待计算的候选集合大、计算速度快、模型简单、特征较少时,保证用户相关物品的召回率。
(2)精排:获得精准的排序结果
(3)重排:对排序的结果进行多样化考虑,提高用户的使用体验
召回算法的核心:
算法结构简单 |
计算效率高 |
准确率不需要太高 |
每一种召回算法都会针对性解决某一类问题 |
具体召回算法可以分为以下类别:
基于规则 |
基于协同过滤 |
向量召回 |
基于项目item被点击的次数、购买的次数等等对项目进行召回,但是这种算法需要消耗极强的业务理解和极大的人力投入
(1)基于统计的协同过滤
分为两种:
基于User的协同过滤:相似的用户可能喜欢相同物品
基于item的协同过滤:相似的物品可能被同个用户喜欢
(2)基于MF的协同过滤
计算User-Item的交互概率
(1)核心:通过某种算法生产出Item的向量表征,然后通过向量的相似度来进行召回
(2)序列召回:通过提取用户的序列特征来生产User的向量表征,然后将User的向量在全部的Item向量中进行召回
注意:序列中的每一个节点都是一个向量
(1)取出的向量表征作为序列的特征,这里可以认为包含了的所有信息,所有可以简单的认为的结果代表序列的表征。
(2)对每一个时间步的特征输出做一个Mean Pooling,也就是对做均值处理,以此得到序列的表征。
这里主要是对主要的模型网络层框架进行展示,其中代码来自百度飞浆课程——手把手教你读推荐论文:序列召回基础。
class GRU4Rec(nn.Layer):
def __init__(self, config):
# 定义参数以及网络层的维度变换
super(GRU4Rec, self).__init__()
# 导入config
self.config = config
# embedding的维度 = 16
self.embedding_dim = self.config['embedding_dim']
# 最大长度max_length = 20
self.max_length = self.config['max_length']
# n_items = 15406
self.n_items = self.config['n_items']
# num_layers = 1
self.num_layers = self.config['num_layers']
# 项目过embedding:15406——>16
self.item_emb = nn.Embedding(self.n_items, self.embedding_dim, padding_idx=0)
# 过GRU
self.gru = nn.GRU(
# 输入层维度=16
input_size=self.embedding_dim,
# 隐藏层维度=16
hidden_size=self.embedding_dim,
# 网络层=1
num_layers=self.num_layers,
time_major=False,
)
# 损失函数
self.loss_fun = nn.CrossEntropyLoss()
# 对参数进行初始化
self.reset_parameters()
# 计算loss
def calculate_loss(self,user_emb,pos_item):
all_items = self.item_emb.weight
# transpose()函数的作用就是调换数组的行列值的索引值,类似于求矩阵的转置
scores = paddle.matmul(user_emb, all_items.transpose([1, 0]))
return self.loss_fun(scores,pos_item)
# 输出项目emb的权重
def output_items(self):
return self.item_emb.weight
# 使用优化器对参数进行更新
def reset_parameters(self, initializer=None):
for weight in self.parameters():
paddle.nn.initializer.KaimingNormal(weight)
# 正向传播
def forward(self, item_seq, mask, item, train=True):
# 将项目过embedding
seq_emb = self.item_emb(item_seq)
# 过gru
seq_emb,_ = self.gru(seq_emb)
# 去最后一层隐藏层的输出作为user的embedding
user_emb = seq_emb[:,-1,:] #取GRU输出的最后一个Hidden作为User的Embedding
# 计算损失,对于不同的数据集传回不同
if train:
loss = self.calculate_loss(user_emb,item)
output_dict = {
'user_emb':user_emb,
'loss':loss
}
else:
output_dict = {
'user_emb':user_emb
}
return output_dict
def get_predict(model, test_data, hidden_size, topN=20):
item_embs = model.output_items().cpu().detach().numpy()
item_embs = normalize(item_embs, norm='l2')
gpu_index = faiss.IndexFlatIP(hidden_size)
gpu_index.add(item_embs)
test_gd = dict()
preds = dict()
user_id = 0
for (item_seq, mask, targets) in tqdm(test_data):
# 获取用户嵌入
# 多兴趣模型,shape=(batch_size, num_interest, embedding_dim)
# 其他模型,shape=(batch_size, embedding_dim)
user_embs = model(item_seq,mask,None,train=False)['user_emb']
user_embs = user_embs.cpu().detach().numpy()
# 用内积来近邻搜索,实际是内积的值越大,向量越近(越相似)
if len(user_embs.shape) == 2: # 非多兴趣模型评估
user_embs = normalize(user_embs, norm='l2').astype('float32')
D, I = gpu_index.search(user_embs, topN) # Inner Product近邻搜索,D为distance,I是index
# D,I = faiss.knn(user_embs, item_embs, topN,metric=faiss.METRIC_INNER_PRODUCT)
for i, iid_list in enumerate(targets): # 每个用户的label列表,此处item_id为一个二维list,验证和测试是多label的
test_gd[user_id] = iid_list
preds[user_id] = I[i,:]
user_id +=1
else: # 多兴趣模型评估
ni = user_embs.shape[1] # num_interest
user_embs = np.reshape(user_embs,
[-1, user_embs.shape[-1]]) # shape=(batch_size*num_interest, embedding_dim)
user_embs = normalize(user_embs, norm='l2').astype('float32')
D, I = gpu_index.search(user_embs, topN) # Inner Product近邻搜索,D为distance,I是index
# D,I = faiss.knn(user_embs, item_embs, topN,metric=faiss.METRIC_INNER_PRODUCT)
for i, iid_list in enumerate(targets): # 每个用户的label列表,此处item_id为一个二维list,验证和测试是多label的
recall = 0
dcg = 0.0
item_list_set = []
# 将num_interest个兴趣向量的所有topN近邻物品(num_interest*topN个物品)集合起来按照距离重新排序
item_list = list(
zip(np.reshape(I[i * ni:(i + 1) * ni], -1), np.reshape(D[i * ni:(i + 1) * ni], -1)))
item_list.sort(key=lambda x: x[1], reverse=True) # 降序排序,内积越大,向量越近
for j in range(len(item_list)): # 按距离由近到远遍历推荐物品列表,最后选出最近的topN个物品作为最终的推荐物品
if item_list[j][0] not in item_list_set and item_list[j][0] != 0:
item_list_set.append(item_list[j][0])
if len(item_list_set) >= topN:
break
test_gd[user_id] = iid_list
preds[user_id] = item_list_set
user_id +=1
return test_gd, preds
def evaluate(preds,test_gd, topN=50):
total_recall = 0.0
total_ndcg = 0.0
total_hitrate = 0
for user in test_gd.keys():
recall = 0
dcg = 0.0
item_list = test_gd[user]
for no, item_id in enumerate(item_list):
if item_id in preds[user][:topN]:
recall += 1
dcg += 1.0 / math.log(no+2, 2)
idcg = 0.0
for no in range(recall):
idcg += 1.0 / math.log(no+2, 2)
total_recall += recall * 1.0 / len(item_list)
if recall > 0:
total_ndcg += dcg / idcg
total_hitrate += 1
total = len(test_gd)
recall = total_recall / total
ndcg = total_ndcg / total
hitrate = total_hitrate * 1.0 / total
return {f'recall@{topN}': recall, f'ndcg@{topN}': ndcg, f'hitrate@{topN}': hitrate}
# 指标计算
def evaluate_model(model, test_loader, embedding_dim,topN=20):
test_gd, preds = get_predict(model, test_loader, embedding_dim, topN=topN)
return evaluate(preds, test_gd, topN=topN)
这篇论文主要是讲RNNs应用于推荐系统领域,并提出了一种基于RNN的基于会话的建议的方法,并为了考虑更加实际的方面,在经典的RNNs基础上进行了一定的修改,如:对排名损失函数进行一些修改。
We therefore propose an RNN-based approach for session-based recommendations. Our approach also considers practical aspects of the task and introduces several modififications to classic RNNs such as a ranking loss function that make it more viable for this specifific problem.Experimental results on two data-sets show marked improvements over widelyused approaches.
这里提到了推荐系统领域现有的两种占主要地位的方法——factor models和neighborhood methods。
将推荐问题视为一个矩阵补全/重构问题,然后利用潜在因子向量来填充缺失的项。(未在基于会话的推荐中应用)
Factor models work by decomposing the sparse user-item interactions matrix to a setof d dimensional vectors one for each item and user in the dataset. The recommendation problem is then treated as a matrix completion/reconstruction problem whereby the latent factor vectors are then used to fifill the missing entries by e.g. taking the dot product of the corresponding user–item latent factors.
计算项目(或用户)之间的相似性。(在基于会话的建议中已被广泛应用)
neighborhood methods, which rely on computing similarities between items (or users) are based on co-occurrences of items in sessions (or user profifiles). Neighborhood methods have been used extensively in session-based recommendations.
基于上面介绍的两种方法存在的优点以及不足,论文提出了一个基于RNNs的会话推荐模型。
(1)输入时进行one-hot编码;
(2)过一层embedding层;
(3)将过embedding后的结果输入到GRU网络层(输入的时候可以输出到更深层的GRU网络层);
(4)输出经过一个前馈神经网络层。
General architecture of the network. Processing of one event of the event stream at once.
以下是本文对传统的RNNs进行的一些修改
为了更好的并行计算,论文采用了 mini-batch 的处理,即把不同的session拼接起来:
(1)根据项目的受欢迎程度的比例进行抽样。
(2)基于流行度的抽样,因为一个项目出现在小批量的其他训练例子中的可能性与它的受欢迎程度成正比。
(1)BPR
(2)TOP1
飞桨AI Studio - 人工智能学习与实训社区 (baidu.com)
https://aistudio.baidu.com/aistudio/course/introduce/27783