首页 > 其他分享 >阿里天池新闻推荐项目:召回模型

阿里天池新闻推荐项目:召回模型

时间:2024-10-29 19:18:46浏览次数:5  
标签:items id item 阿里 dict user 召回 天池 click

3. 召回模型

多路召回:

  1. itemCF召回

3.1 协同过滤-itemCF召回

3.1.1 召回准备

获取历史点击中点击数前k篇文章id:get_item_topk_click(trn_hist_click_df, k=50)

对每个用户进行item_based_recommend

3.1.2 item_based_recommend

├── 逻辑
│ ├── 获取该用户历史交互的文章 (user_hist_items)
│ ├── 遍历用户历史文章
│ │ ├── 检查文章是否在i2i_sim相似性矩阵中
│ │ ├── 获取与当前文章最相似的前sim_item_topk篇文章&用户没点过
│ │ └── 计算权重并更新评分created_time_weight * loc_weight * content_weight * wij
│ │ ├── 文章创建时间差权重 (created_time_weight)
│ │ ├── 位置权重 (loc_weight)
│ │ └── 内容相似权重 (content_weight)
│ └── 使用热门文章补全不足的推荐项

└── 输出
└── 召回的文章列表,按分数排序 {item1: score1, item2: score2, …}

def item_based_recommend(user_id, user_item_time_dict, i2i_sim, sim_item_topk, recall_item_num, item_topk_click,
                         item_created_time_dict, emb_i2i_sim):
    """
    基于文章协同过滤的召回
    :param user_id: 用户id
    :param user_item_time_dict: 字典, 根据点击时间获取用户的点击文章序列   {user1: {item1: time1, item2: time2..}...}
    :param i2i_sim: 字典,文章相似性矩阵
    :param sim_item_topk: 整数, 选择与当前文章最相似的前k篇文章
    :param recall_item_num: 整数, 最后的召回文章数量
    :param item_topk_click: 列表,点击次数最多的文章列表,用户召回补全
    :param item_created_time_dict: 文章创建时间字典
    :param emb_i2i_sim: 字典基于内容embedding算的文章相似矩阵
    return: 召回的文章列表 {item1:score1, item2: score2...}
    """
    # 获取用户历史交互的文章
    user_hist_items = user_item_time_dict[user_id]

    item_rank = {}
    for loc, (i, click_time) in enumerate(user_hist_items):
        if i not in i2i_sim:
            print(f"Item {i} not found in i2i_sim.")
            continue  # 跳过这个循环

        for j, wij in sorted(i2i_sim[i].items(), key=lambda x: x[1], reverse=True)[:sim_item_topk]:
            if j in user_hist_items:
                continue

            # 文章创建时间差权重
            created_time_weight = np.exp(0.8 ** np.abs(item_created_time_dict[i] - item_created_time_dict[j]))
            # 相似文章和历史点击文章序列中历史文章所在的位置权重
            loc_weight = (0.9 ** (len(user_hist_items) - loc))

            content_weight = 1.0
            if emb_i2i_sim.get(i, {}).get(j, None) is not None:
                content_weight += emb_i2i_sim[i][j]
            if emb_i2i_sim.get(j, {}).get(i, None) is not None:
                content_weight += emb_i2i_sim[j][i]

            item_rank.setdefault(j, 0)
            item_rank[j] += created_time_weight * loc_weight * content_weight * wij

    # 不足10个,用热门商品补全
    if len(item_rank) < recall_item_num:
        for i, item in enumerate(item_topk_click):
            if item in item_rank.items():  # 填充的item应该不在原来的列表中
                continue
            item_rank[item] = - i - 100  # 随便给个负数就行
            if len(item_rank) == recall_item_num:
                break

    item_rank = sorted(item_rank.items(), key=lambda x: x[1], reverse=True)[:recall_item_num]

    return item_rank

3.1.3 召回评估

├── 提取用户最后一次点击的文章字典 (last_click_item_dict)
├── 获取用户数量 (user_num)
├── 遍历每10个k值(从10到topk)
│ ├── 初始化击中数计数器 (hit_num)
│ ├── 遍历用户的召回文章
│ │ ├── 提取前k个召回文章
│ │ ├── 检查用户是否在最后一次点击字典中
│ │ ├── 检查最后点击的文章是否在召回列表中,若命中则计数
│ └── 计算并打印当前k值的击中率

def metrics_recall(user_recall_items_dict, trn_last_click_df, topk=50):
    """
    依次评估召回的前10, 20, 30, 40, 50个文章中的击中率
    :param user_recall_items_dict: 召回字典
    :param trn_last_click_df: 用户最后一次点击字典
    :param topk:
    :return:
    """
    last_click_item_dict = dict(zip(trn_last_click_df['user_id'], trn_last_click_df['click_article_id']))
    user_num = len(user_recall_items_dict)

    for k in range(10, topk + 1, 10):
        hit_num = 0
        for user, item_list in user_recall_items_dict.items():
            # 获取前k个召回的结果
            tmp_recall_items = [x[0] for x in user_recall_items_dict[user][:k]]
            if user not in (last_click_item_dict.keys()):  # 该用户只有一次点击,当作了训练集
                continue
            if last_click_item_dict[user] in set(tmp_recall_items):
                hit_num += 1

        hit_rate = round(hit_num * 1.0 / user_num, 5)
        print(' topk: ', k, ' : ', 'hit_num: ', hit_num, 'hit_rate: ', hit_rate, 'user_num : ', user_num)

topk: 10 : hit_num: 6499 hit_rate: 0.6499 user_num : 10000
topk: 20 : hit_num: 8003 hit_rate: 0.8003 user_num : 10000
topk: 30 : hit_num: 8662 hit_rate: 0.8662 user_num : 10000
topk: 40 : hit_num: 9036 hit_rate: 0.9036 user_num : 10000
topk: 50 : hit_num: 9273 hit_rate: 0.9273 user_num : 10000

3.2 embedding_sim召回

获取历史点击中点击数前k篇文章id:get_item_topk_click(trn_hist_click_df, k=50)

对每个用户进行item_based_recommend

步骤与3.1相同,把i2i_sim矩阵替换为embbedding_sim矩阵,

topk: 10 : hit_num: 182 hit_rate: 0.0182 user_num : 10000
topk: 20 : hit_num: 623 hit_rate: 0.0623 user_num : 10000
topk: 30 : hit_num: 972 hit_rate: 0.0972 user_num : 10000
topk: 40 : hit_num: 1332 hit_rate: 0.1332 user_num : 10000
topk: 50 : hit_num: 1699 hit_rate: 0.1699 user_num : 10000

3.3 youtubeDNN召回

3.3.1 召回准备

定义SEQ_LEN为30,用于控制用户点击序列的长度

用户ID和文章ID进行类别编码

保存特征最大索引字典 (feature_max_idx)

用户和物品画像:提取user和item的画像信息,生成原始id和编码id之间的映射 (user_index_2_rawid, item_index_2_rawid)
数据集划分与输入整理:滑动窗口划分训练集和测试集 (train_set, test_set)

def gen_data_set(data, negsample=0):
    """
    获取双塔召回时的训练验证数据
    :param data:
    :param negsample: 通过滑窗构建样本的时候,负样本的数量
    :return:
    """
    data.sort_values("click_timestamp", inplace=True)
    item_ids = data['click_article_id'].unique()

    train_set = []
    test_set = []
    for reviewerID, hist in tqdm(data.groupby('user_id')):
        pos_list = hist['click_article_id'].tolist()
        if negsample > 0:
            candidate_set = list(set(item_ids) - set(pos_list))  # 用户没看过的文章里面选择负样本
            neg_list = np.random.choice(candidate_set, size=len(pos_list) * negsample, replace=True) 
        # 长度只有一个的时候,需要把这条数据也放到训练集中,不然的话最终学到的embedding就会有缺失
        if len(pos_list) == 1:
            train_set.append((reviewerID, [pos_list[0]], pos_list[0], 1, len(pos_list)))
            test_set.append((reviewerID, [pos_list[0]], pos_list[0], 1, len(pos_list)))
        # 滑窗构造正负样本
        for i in range(1, len(pos_list)):
            hist = pos_list[:i]
            if i != len(pos_list) - 1:
                # 正样本 [user_id, his_item, pos_item, label, len(his_item)]
                train_set.append((reviewerID, hist[::-1], pos_list[i], 1, len(hist[::-1])))
                for negi in range(negsample):
                    # 负样本 [user_id, his_item, neg_item, label, len(his_item)]
                    train_set.append((reviewerID, hist[::-1], neg_list[i * negsample + negi], 0, len(hist[::-1])))
            else:
                # 将最长的那一个序列长度作为测试数据
                test_set.append((reviewerID, hist[::-1], pos_list[i], 1, len(hist[::-1])))

    random.shuffle(train_set)
    random.shuffle(test_set)

    return train_set, test_set

调整输入数据格式 (train_model_input, test_model_input)

def gen_model_input(train_set, seq_max_len):
    train_uid = np.array([line[0] for line in train_set])
    train_seq = [line[1] for line in train_set]
    train_iid = np.array([line[2] for line in train_set])
    train_label = np.array([line[3] for line in train_set])
    train_hist_len = np.array([line[4] for line in train_set])

    train_seq_pad = pad_sequences(train_seq, maxlen=seq_max_len, padding='post', truncating='post', value=0)
    train_model_input = {"user_id": train_uid, "click_article_id": train_iid, "hist_article_id": train_seq_pad, "hist_len": train_hist_len}

    return train_model_input, train_label

特征列与Embedding:
确定Embedding维度 (embedding_dim)
定义用户和物品特征列 (user_feature_columns, item_feature_columns)

embedding_dim = 16
user_feature_columns = [SparseFeat('user_id', feature_max_idx['user_id'], embedding_dim), VarLenSparseFeat(
                       SparseFeat('hist_article_id', feature_max_idx['click_article_id'], embedding_dim, 	                        embedding_name="click_article_id"), SEQ_LEN, 'mean', 'hist_len'), ]
item_feature_columns = [SparseFeat('click_article_id', feature_max_idx['click_article_id'], embedding_dim)]

3.3.2 youtubeDNN

├── 模型定义与训练
│ ├── 设置负采样 (sampler_config)
│ └── 定义YouTubeDNN模型,训练并编译 (model)
│ ├── 训练模型 (model.fit)
│ └── 提取user和item的Embedding向量

├── 向量归一化与索引构建
│ ├── 归一化user和item的Embedding
│ └── 使用Faiss构建索引并进行相似度搜索 (sim, idx)

├── 生成召回字典 (user_recall_items_dict)

├── 结果保存
│ ├── 保存user和item的Embedding字典
│ └── 保存召回结果

def youtubednn_u2i_dict(data, trn_last_click_df, save_path, metric_recall, user_multi_recall_dict, topk=20):
    SEQ_LEN = 30  # 用户点击序列的长度,短的填充,长的截断
    user_profile_ = data[["user_id"]].drop_duplicates('user_id')
    item_profile_ = data[["click_article_id"]].drop_duplicates('click_article_id')

    # 类别编码
    features = ["click_article_id", "user_id"]
    feature_max_idx = {}
    for feature in features:
        lbe = LabelEncoder()
        data[feature] = lbe.fit_transform(data[feature])
        feature_max_idx[feature] = data[feature].max() + 1

    # 提取user和item的画像,这里具体选择哪些特征还需要进一步的分析和考虑
    user_profile = data[["user_id"]].drop_duplicates('user_id')
    item_profile = data[["click_article_id"]].drop_duplicates('click_article_id')
    user_index_2_rawid = dict(zip(user_profile['user_id'], user_profile_['user_id']))
    item_index_2_rawid = dict(zip(item_profile['click_article_id'], item_profile_['click_article_id']))

    # 划分训练和测试集
    # 由于深度学习需要的数据量通常都是非常大的,所以为了保证召回的效果,往往会通过滑窗的形式扩充训练样本
    train_set, test_set = gen_data_set(data, 0)
    # 整理输入数据,具体的操作可以看上面的函数
    train_model_input, train_label = gen_model_input(train_set, SEQ_LEN)
    test_model_input, test_label = gen_model_input(test_set, SEQ_LEN)

    # 确定Embedding的维度
    embedding_dim = 16
    # 将数据整理成模型可以直接输入的形式
    user_feature_columns = [SparseFeat('user_id', feature_max_idx['user_id'], embedding_dim),
                            VarLenSparseFeat(SparseFeat('hist_article_id', feature_max_idx['click_article_id'],
                                                        embedding_dim, embedding_name="click_article_id"), SEQ_LEN,
                                             'mean', 'hist_len'), ]  #稀疏特征,变长稀疏特征
    item_feature_columns = [SparseFeat('click_article_id', feature_max_idx['click_article_id'], embedding_dim)]

    # 模型的定义
    train_counter = Counter(train_model_input['click_article_id'])
    item_count = [train_counter.get(i, 0) for i in range(item_feature_columns[0].vocabulary_size)]
    sampler_config = NegativeSampler('frequency', num_sampled=5, item_name="click_article_id", item_count=item_count)
    if tf.__version__ >= '2.0.0':
        tf.compat.v1.disable_eager_execution()
    else:
        K.set_learning_phase(True)
    # model = YoutubeDNN(user_feature_columns, item_feature_columns, num_sampled=5, user_dnn_hidden_units=(64, embedding_dim))
    model = YoutubeDNN(user_feature_columns, item_feature_columns, user_dnn_hidden_units=(64, 16, embedding_dim),
                       sampler_config=sampler_config)  # 模型编译
    model.compile(optimizer="adam", loss=sampledsoftmaxloss)

    # 模型训练,这里可以定义验证集的比例,如果设置为0的话就是全量数据直接进行训练
    history = model.fit(train_model_input, train_label, batch_size=256, epochs=2, verbose=1, validation_split=0.0)

    # 训练完模型之后,提取训练的Embedding,包括user端和item端
    test_user_model_input = test_model_input
    all_item_model_input = {"click_article_id": item_profile['click_article_id'].values}

    user_embedding_model = Model(inputs=model.user_input, outputs=model.user_embedding)
    item_embedding_model = Model(inputs=model.item_input, outputs=model.item_embedding)

    # 保存当前的item_embedding 和 user_embedding 排序的时候可能能够用到,但是需要注意保存的时候需要和原始的id对应
    user_embs = user_embedding_model.predict(test_user_model_input, batch_size=2 ** 12)
    item_embs = item_embedding_model.predict(all_item_model_input, batch_size=2 ** 12)

    # embedding保存之前归一化一下
    user_embs = user_embs / np.linalg.norm(user_embs, axis=1, keepdims=True)
    item_embs = item_embs / np.linalg.norm(item_embs, axis=1, keepdims=True)

    # 将Embedding转换成字典的形式方便查询
    raw_user_id_emb_dict = {user_index_2_rawid[k]: v for k, v in zip(user_profile['user_id'], user_embs)}
    raw_item_id_emb_dict = {item_index_2_rawid[k]: v for k, v in zip(item_profile['click_article_id'], item_embs)}
    # 将Embedding保存到本地
    pickle.dump(raw_user_id_emb_dict, open(save_path + 'user_youtube_emb.pkl', 'wb'))
    pickle.dump(raw_item_id_emb_dict, open(save_path + 'item_youtube_emb.pkl', 'wb'))

    # faiss紧邻搜索,通过user_embedding 搜索与其相似性最高的topk个item
    index = faiss.IndexFlatIP(embedding_dim)
    index.add(item_embs)  # 将item向量构建索引
    sim, idx = index.search(np.ascontiguousarray(user_embs), topk)  # 通过user去查询最相似的topk个item

    user_recall_items_dict = collections.defaultdict(dict)
    for target_idx, sim_value_list, rele_idx_list in tqdm(zip(test_user_model_input['user_id'], sim, idx)):
        target_raw_id = user_index_2_rawid[target_idx]
        # 从1开始是为了去掉商品本身, 所以最终获得的相似商品只有topk-1
        for rele_idx, sim_value in zip(rele_idx_list[1:], sim_value_list[1:]):
            rele_raw_id = item_index_2_rawid[rele_idx]
            user_recall_items_dict[target_raw_id][rele_raw_id] = user_recall_items_dict.get(target_raw_id, {}) \
                                                                     .get(rele_raw_id, 0) + sim_value

    user_recall_items_dict = {k: sorted(v.items(), key=lambda x: x[1], reverse=True) for k, v in user_recall_items_dict.items()}

    # 保存召回的结果
    # 这里是直接通过向量的方式得到了召回结果,相比于上面的召回方法,上面的只是得到了i2i及u2u的相似性矩阵,还需要进行协同过滤召回才能得到召回结果
    user_multi_recall_dict['youtubednn_recall'] = user_recall_items_dict
    pickle.dump(user_recall_items_dict, open(save_path + 'youtubednn_recall.pkl', 'wb'))

    if metric_recall:
        metrics_recall(user_multi_recall_dict['youtubednn_recall'], trn_last_click_df, topk=50)

    return user_multi_recall_dict

3.3.3 召回评估 (metrics_recall)

同3.1

3.4 协同过滤-userCF召回

3.4.1 召回准备

同3.1.1:

设置 sim_user_topk = 100:每个用户选择的最相似用户数量。

设置 recall_item_num = 50:为每个用户召回的项目数量。

使用 get_item_topk_click 函数,从 trn_hist_click_df 中获得点击量最高的前50个项目,存储为 item_topk_click

遍历每个用户:user_based_recommend

3.4.2 user_based_recommend

基于用户协同过滤的推荐方法

获取用户历史交互项目:从 user_item_time_dict 中提取目标用户的项目点击时间序列 user_item_time_list,并去重生成 user_hist_items

相似用户项目遍历与权重计算:遍历 u2u_sim 中与目标用户最相似的前 sim_user_topk 个用户:

  • 遍历相似用户的点击文章 i:
    • 若项目 i 在用户历史交互项目 user_hist_items 中,跳过此项目。
    • 初始化项目 i 的得分为 0。
    • 定义三个权重因子:
      • 位置权重 loc_weight:根据项目在用户点击序列中的位置,越靠后权重越高。
      • 内容权重 content_weight:基于内容相似度矩阵 emb_i2i_sim,若项目 i 与用户历史交互项目存在内容相似度,加权累加。
      • 创建时间权重 created_time_weight:计算项目创建时间差,时间差越小权重越高。
    • 将项目 i 的加权得分累加到 items_rank[i]

热度补全:若 items_rank 中的项目数量不足 :

  • 使用 item_topk_click 补充项目,逐项添加到 items_rank 中,为这些项目分配负得分,确保不会影响排序。

排序与返回

  • 按得分从高到低对 items_rank 排序,选取前 recall_item_num 项。返回推荐项目列表 items_rank
def user_based_recommend(user_id, user_item_time_dict, u2u_sim, sim_user_topk, recall_item_num,
                         item_topk_click, item_created_time_dict, emb_i2i_sim):
    """
    基于文章协同过滤的召回
    :param user_id: 用户id
    :param user_item_time_dict: 字典, 根据点击时间获取用户的点击文章序列   {user1: {item1: time1, item2: time2..}...}
    :param u2u_sim: 字典,文章相似性矩阵
    :param sim_user_topk: 整数, 选择与当前用户最相似的前k个用户
    :param recall_item_num: 整数, 最后的召回文章数量
    :param item_topk_click: 列表,点击次数最多的文章列表,用户召回补全
    :param item_created_time_dict: 文章创建时间列表
    :param emb_i2i_sim: 字典基于内容embedding算的文章相似矩阵
    return: 召回的文章列表 {item1:score1, item2: score2...}
    """
    # 历史交互
    user_item_time_list = user_item_time_dict[user_id]  # {item1: time1, item2: time2...}
    user_hist_items = set([i for i, t in user_item_time_list])  # 存在一个用户与某篇文章的多次交互, 这里得去重
    items_rank = {}
    for sim_u, wuv in sorted(u2u_sim[user_id].items(), key=lambda x: x[1], reverse=True)[:sim_user_topk]:
        for i, click_time in user_item_time_dict[sim_u]:
            if i in user_hist_items:
                continue
            items_rank.setdefault(i, 0)
            loc_weight = 1.0
            content_weight = 1.0
            created_time_weight = 1.0
            # 当前文章与该用户看的历史文章进行一个权重交互
            for loc, (j, click_time) in enumerate(user_item_time_list):
                # 点击时的相对位置权重
                loc_weight += 0.9 ** (len(user_item_time_list) - loc)
                # 内容相似性权重
                if emb_i2i_sim.get(i, {}).get(j, None) is not None:
                    content_weight += emb_i2i_sim[i][j]
                if emb_i2i_sim.get(j, {}).get(i, None) is not None:
                    content_weight += emb_i2i_sim[j][i]
                # 创建时间差权重
                created_time_weight += np.exp(0.8 * np.abs(item_created_time_dict[i] - item_created_time_dict[j]))

            items_rank[i] += loc_weight * content_weight * created_time_weight * wuv

    # 热度补全
    if len(items_rank) < recall_item_num:
        for i, item in enumerate(item_topk_click):
            if item in items_rank.items():  # 填充的item应该不在原来的列表中
                continue
            items_rank[item] = - i - 100  # 随便给个复数就行
            if len(items_rank) == recall_item_num:
                break

    items_rank = sorted(items_rank.items(), key=lambda x: x[1], reverse=True)[:recall_item_num]

    return items_rank

3.4.3 召回评估

同上

3.5 冷启动召回

3.5.1 召回准备

为冷启动用户生成推荐项,通过基于文章的召回方法并结合多种筛选规则筛选冷启动项目。

加载预计算相似度数据:加载 i2i_sim

初始化关键参数

设置 sim_item_topk = 150:每个文章考虑的相似文章数量。

设置 recall_item_num = 100:为每个用户召回的文章数量。

itemCF召回得到 user_recall_items_dict:遍历每个用户:使用 item_based_recommend

get_user_hist_item_info_dict 获取以下字典:

  1. 用户历史中的文章类型。
  2. 用户历史中的文章ID。
  3. 用户历史中的文章单词数。
  4. 用户最近交互的文章的时间戳。
  5. 提取唯一的文章ID集合。
def cold_recall(all_click_df, user_item_time_dict, item_info_df, item_created_time_dict, item_type_dict, item_words_dict, emb_i2i_sim, weight_dict, user_multi_recall_dict, save_path):
    ##########冷启动
    # 先进行itemcf召回,这里不需要做召回评估,这里只是一种策略
    trn_hist_click_df = all_click_df

    i2i_sim = pickle.load(open(save_path + 'emb_i2i_sim.pkl', 'rb'))

    sim_item_topk = 200
    recall_item_num = 150  # 稍微召回多一点文章,便于后续的规则筛选
    user_recall_items_dict = collections.defaultdict(dict)
    item_topk_click = get_item_topk_click(trn_hist_click_df, k=50)
    for user in tqdm(trn_hist_click_df['user_id'].unique()):
        user_recall_items_dict[user] = item_based_recommend(user, user_item_time_dict, i2i_sim, sim_item_topk,recall_item_num, item_topk_click, item_created_time_dict, emb_i2i_sim)
    pickle.dump(user_recall_items_dict, open(save_path + 'cold_start_items_raw_dict.pkl', 'wb'))

    all_click_df_ = copy.deepcopy(all_click_df)
    all_click_df_ = all_click_df_.merge(item_info_df, how='left', on='click_article_id')
    user_hist_item_typs_dict, user_hist_item_ids_dict, user_hist_item_words_dict, user_last_item_created_time_dict = get_user_hist_item_info_dict(
        all_click_df_)
    click_article_ids_set = get_click_article_ids_set(all_click_df)
    # 需要注意的是
    # 这里使用了很多规则来筛选冷启动的文章,所以前面再召回的阶段就应该尽可能的多召回一些文章,否则很容易被删掉
    cold_start_user_items_dict = cold_start_items(user_recall_items_dict, user_hist_item_typs_dict,
                                                  user_hist_item_words_dict, user_last_item_created_time_dict,
                                                  item_type_dict, item_words_dict, item_created_time_dict,
                                                  click_article_ids_set, recall_item_num, save_path)

    user_multi_recall_dict['cold_start_recall'] = cold_start_user_items_dict

    return user_multi_recall_dict

3.5.2 cold_start_items

  • 对每个用户及其召回文章列表进行遍历:
    • 获取用户的历史文章信息:
      • hist_item_type_set:用户点击过的文章主题集合。
      • hist_mean_words:用户历史文章的平均字数。
      • hist_last_item_created_time:用户最后点击文章的创建时间(转为日期时间对象)。

筛选适合的当前召回文章

  • 对于每篇召回文章:
    • 获取文章信息:
      • curr_item_type:当前文章主题。
      • curr_item_words:当前文章字数。
      • curr_item_created_time:当前文章创建时间(转为日期时间对象)。
    • 进行筛选:
      • 当前文章不能出现在用户的历史点击中。
      • 当前文章的主题必须在用户历史文章主题集合中。
      • 当前文章的字数与用户历史文章的平均字数差距不能超过 200。
      • 当前文章的创建时间与用户最后点击文章的创建时间差距不能超过 90 天。

添加符合条件的文章

  • 如果文章通过了上述所有筛选条件,将其添加到 cold_start_user_items_dict 中。

控制召回数量

  • 对每个用户的推荐文章列表按得分排序,保留前 recall_item_num 个文章。
def cold_start_items(user_recall_items_dict, user_hist_item_typs_dict, user_hist_item_words_dict, \
                     user_last_item_created_time_dict, item_type_dict, item_words_dict,
                     item_created_time_dict, click_article_ids_set, recall_item_num, save_path):
    """
    冷启动的情况下召回一些文章
    :param user_recall_items_dict: 基于内容embedding相似性召回来的很多文章, 字典, {user1: [item1, item2, ..], }
    :param user_hist_item_typs_dict: 字典, 用户点击的文章的主题映射
    :param user_hist_item_words_dict: 字典, 用户点击的历史文章的字数映射
    :param user_last_item_created_time_dict: 字典,用户点击的历史文章创建时间映射
    :param item_type_dict: 字典,文章主题映射
    :param item_words_dict: 字典,文章字数映射
    :param item_created_time_dict: 字典, 文章创建时间映射
    :param click_article_ids_set: 集合,用户点击过得文章, 也就是日志里面出现过的文章
    :param recall_item_num: 召回文章的数量, 这个指的是没有出现在日志里面的文章数量
    """

    cold_start_user_items_dict = {}
    for user, item_list in tqdm(user_recall_items_dict.items()):
        cold_start_user_items_dict.setdefault(user, [])
        for item, score in item_list:
            # 获取历史文章信息
            hist_item_type_set = user_hist_item_typs_dict[user]
            hist_mean_words = user_hist_item_words_dict[user]
            hist_last_item_created_time = user_last_item_created_time_dict[user]
            hist_last_item_created_time = datetime.fromtimestamp(hist_last_item_created_time)

            # 获取当前召回文章的信息
            curr_item_type = item_type_dict[item]
            curr_item_words = item_words_dict[item]
            curr_item_created_time = item_created_time_dict[item]
            curr_item_created_time = datetime.fromtimestamp(curr_item_created_time)

            # 首先,文章不能出现在用户的历史点击中, 然后根据文章主题,文章单词数,文章创建时间进行筛选
            if curr_item_type not in hist_item_type_set or \
                    item in click_article_ids_set or \
                    abs(curr_item_words - hist_mean_words) > 200 or \
                    abs((curr_item_created_time - hist_last_item_created_time).days) > 90:
                continue

            cold_start_user_items_dict[user].append((item, score))  # {user1: [(item1, score1), (item2, score2)..]...}

    # 需要控制一下冷启动召回的数量
    cold_start_user_items_dict = {k: sorted(v, key=lambda x: x[1], reverse=True)[:recall_item_num] \
                                  for k, v in cold_start_user_items_dict.items()}

    pickle.dump(cold_start_user_items_dict, open(save_path + 'cold_start_user_items_dict.pkl', 'wb'))

    return cold_start_user_items_dict

标签:items,id,item,阿里,dict,user,召回,天池,click
From: https://blog.csdn.net/qq_34517343/article/details/143276226

相关文章

  • 阿里云轻量应用服务器和ECS云服务器有什么不同
    阿里云轻量应用服务器和ECS云服务器,两者均是阿里云提供的服务器服务,它们的主要差别可以从以下几个方面进行概述:1、定位与使用场景;2、价格;3、性能与资源;4、操作和管理。轻量应用服务器的主要定位是为了满足中小型企业或个人开发者的需求,简化了服务器购买和管理的流程。1、定位......
  • Meissonic:消费级 GPU 也能轻松生成高质量图像!阿里联合多所高校推出高效文生图模型
    ❤️如果你也关注大模型与AI的发展现状,且对大模型应用开发非常感兴趣,我会快速跟你分享最新的感兴趣的AI应用和热点信息,也会不定期分享自己的想法和开源实例,欢迎关注我哦!......
  • 阿里云消息团队创新论文被软件工程顶会 FM 2024 录用
    近日,由阿里云消息队列团队发表的关于RocketMQ锁性能优化论文被CCF-A类软件工程顶级会议FM2024录用。FM2024是由欧洲形式化方法协会(FME)组织的第24届国际研讨会,会议汇聚了来自各国的形式化研究学者,是形式化方法领域的顶级会议。FM2021强调形式化方法在广泛领域的开发......
  • 阿里面试:为什么要索引?什么是MySQL索引?底层结构是什么?
    文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录博客园版为您奉上珍贵的学习资源:免费赠送:《尼恩Java面试宝典》持续更新+史上最全+面试必备2000页+面试必备+大厂必备+涨薪必备免费赠送:《尼恩技术圣经+高并发系列PDF》,帮你实现技术自由,完成职业升级,薪......
  • AI之旅-开篇:从云计算之路到AI之旅,从搬上阿里云到留在阿里云
    2012年的金秋十月,刚刚遭遇服务器硬件故障的园子在上海张江浦东软件园,写了一篇小学生作文——如果云计算,做了一个重要决定——开始考虑搬上很少人使用的阿里云,开启了云计算之路。2024年的金秋十月,刚刚被开发者救活的园子在杭州云栖小镇,又写了一篇小学生作文,在经过十一年之痒的纠......
  • 【Android Studio】通过编辑setting.gradle文件,添加阿里仓库
    本人对AndroidStudio的了解非常初级,这篇blog主要是自用备忘性质。因为众所周知的原因,国外仓库访问很不方便,影响项目构建。所以需要添加国内仓库,而阿里云仓库属于比较知名的。阿里云仓库服务自AndroidStudioBumblebee(2021.1.1)开始,仓库地址的存放位置,从项目级别的build.gradl......
  • 精确度和召回率在评估分类模型中有什么区别
    精确度(Precision)和召回率(Recall)是评估分类模型性能的两个关键指标,它们在测量模型对正类预测的准确性和完整性方面具有独特的重要性。它们的区别是:1.基本概念和定义;2.性能评估的重要性;3.不同应用场景的影响;4.实际应用案例。1.基本概念和定义精确度(Precision):这是一个衡量模型预......
  • 实现阿里云短信服务
    导入依赖<!--阿里云短信服务依赖--><dependency><groupId>com.aliyun</groupId><artifactId>dysmsapi20170525</artifactId><version>3.0.0</version></dependen......
  • 【如何安装linux系统】【为什么我要用vmware虚拟机呢?】【阿里云镜像】
    如何安装linux系统为什么我要用vmware虚拟机呢?阿里云镜像(下载镜像)(安装虚拟机)http://mirrors.aliyun.com/......
  • 阿里面试:秒杀的分布式事务, 是如何设计的?
    文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录博客园版为您奉上珍贵的学习资源:免费赠送:《尼恩Java面试宝典》持续更新+史上最全+面试必备2000页+面试必备+大厂必备+涨薪必备免费赠送:《尼恩技术圣经+高并发系列PDF》,帮你实现技术自由,完成职业升级,薪......