完整代码——SASRec 基于自注意力的序列推荐

关于“SASRec 基于自注意力的序列推荐”这篇论文的学习笔记和代码复现可以看之前写的这两篇:

学习笔记——SASRec 基于自注意力的序列推荐-优快云博客

代码复现——SASRec 基于自注意力的序列推荐-优快云博客

这次是关于这篇论文的代码展示,全文一万六千多字,难免会有所疏漏,有任何问题欢迎指出。

需要完整代码压缩包的伙伴可以私信我(复现不易,分享难得,小偿即可,白嫖勿扰)。

1、项目代码结构

1.1、data文件夹中存放了各种经过处理的数据集

 文本内容长这样

其中user-course_order.txt是由user-course_order.json得来的, user-course_order.json长这样:

1.2、jsonProcessing文件夹中存放的是各种数据处理代码

如何将json文件转换成模型所需的数据格式呢,在jsonProcessing文件夹下运行json2txt.py代码

import json


def read_json(file_path):
    """
    读取JSON文件,文件中每一行都是一个独立的JSON对象。它将每一行解析为Python字典,并将所有用户存储在一个列表中。
    """
    data = []
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            data.append(json.loads(line))
    return data


def write_to_txt(users, output_file):
    """
    遍历用户列表,对于每个用户,提取新用户ID和课程编号列表,并将它们转换为指定的格式(每行包含用户ID和课程ID),然后写入TXT文件。
    """
    with open(output_file, 'w', encoding='utf-8') as file:
        for user in users:
            new_user_id = user['新用户id']
            for course_id in user['course_order_numbered']:
                file.write(f"{new_user_id} {course_id}\n")


# 主函数
def main():
    """
    读取JSON文件中的用户数据。
    遍历每个用户的数据,提取新用户ID和课程编号列表。
    将这些数据转换为指定的格式,即每行包含用户ID和课程ID。
    将转换后的数据写入TXT文件。
    """
    user_json_path = '../data/selected_user_numbered.json'  # 筛选后的用户JSON文件路径
    output_txt_path = '../data/user-course_order.txt'  # 输出TXT文件路径

    # 读取筛选后的用户数据
    filtered_users = read_json(user_json_path)

    # 将用户数据写入TXT文件
    write_to_txt(filtered_users, output_txt_path)

    print(f'用户数据已写入 {output_txt_path}')


if __name__ == '__main__':
    main()

 要想得到selected_user_numbered_large.json文件,要先将原始慕课数据集进行处理。

1.2.1、原始的user.json,存放了所有学生的id、姓名、课程观看序列等信息

1.2.2、原始的course.json,存放了所有课程的id、课程名等信息 

 1.2.3、在jsonProcessing文件夹下运行course_number.py代码

先给课程重新编号,从1开始,得到course_numbered.json,其文件内容为:

代码为: 

import json


def read_course_json(file_path):
    """
    读取JSON文件,假设每个课程占一行。
    """
    courses = []
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            courses.append(json.loads(line))
    return courses


def assign_numbers(courses):
    """
    为课程分配从1开始的编号。
    """
    course_id_map = {}
    for index, course in enumerate(courses, start=1):
        course_id_map[course['id']] = index
    return course_id_map


def write_courses_with_numbers(courses, course_id_map, output_file):
    """
    将编号后的数据写入新的JSON文件。
    """
    with open(output_file, 'w', encoding='utf-8') as file:
        for course in courses:
            # 创建一个新的字典,只包含编号、原编号和课程名
            new_course = {
                '新课程编号id': course_id_map[course['id']],
                '原课程编号id': course['id'],
                '课程名name': course['name']
            }
            # 将新的课程字典转换为JSON格式的字符串,并确保中文字符不被转义
            json.dump(new_course, file, ensure_ascii=False)
            file.write('\n')
# def write_courses_with_numbers(courses, course_id_map, output_file):
#     """
#     将编号后的数据写入新的JSON文件。
#     """
#     # 创建一个新的列表,包含所有课程的编号信息
#     numbered_courses = [
#         {
#             '新课程编号id': course_id_map[course['id']],
#             '原课程编号id': course['id'],
#             '课程名name': course['name']
#         } for course in courses
#     ]
#     # 将新的课程列表转换为JSON格式的字符串,并确保中文字符不被转义
#     with open(output_file, 'w', encoding='utf-8') as file:
#         json.dump(numbered_courses, file, ensure_ascii=False, indent=4)

# 主函数
def main():
    course_json_path = '/Dataset/MOOCCube/entities/course.json'  # 课程JSON文件路径
    output_file = '../data/course_numbered.json'  # 输出文件路径
    courses = read_course_json(course_json_path)
    course_id_map = assign_numbers(courses)
    write_courses_with_numbers(courses, course_id_map, output_file)
    print(f'课程编号完成,共编号了 {len(courses)} 门课程。')


if __name__ == '__main__':
    main()

1.2.4、在jsonProcessing文件夹下运行set_user_number.py代码

得到course_numbered.json文件后,给学生重新编号,从1开始,得到user_numbered.json

第一行内容代表用户1的课程观看顺序为:29, 63, 28, 52, 66,一共上了5节课。以此类推.....

import json


def read_json(file_path):
    """
    读取JSON文件,假设文件中每一行都是一个独立的JSON对象。
    """
    data = []
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            data.append(json.loads(line))
    return data


def assign_numbers(data):
    """
    为数据中的每个项分配从1开始的编号。
    """
    id_map = {}
    for index, item in enumerate(data, start=1):
        id_map[item['id']] = index
    return id_map

def update_course_orders(users, course_id_map):
    """
    更新用户的课程订单为新的课程编号。
    """
    for user in users:
        user['course_order_numbered'] = [course_id_map[course_id] for course_id in user['course_order']]
        user['新课程编号id'] = user['course_order_numbered']
        del user['course_order']


def write_json(data, file_path):
    """
    将数据写入指定的JSON文件。
    """
    with open(file_path, 'w', encoding='utf-8') as file:
        for item in data:
            json.dump(item, file, ensure_ascii=False)
            file.write('\n')



# 主函数
def main():
    course_json_path = '../data/course_numbered.json'
    user_json_path = 'D:/pythonCode/推荐系统/Dataset/MOOCCube/entities/user.json'
    output_user_path = '../data/user_numbered.json'

    # 读取课程数据并创建编号映射字典
    courses = read_json(course_json_path)
    course_id_map = {course['原课程编号id']: course['新课程编号id'] for course in courses}

    # 读取用户数据
    users = read_json(user_json_path)

    # 为用户分配编号
    user_id_map = assign_numbers(users)

    # 更新用户课程订单为新编号
    update_course_orders(users, course_id_map)

    # print("新的用户编号映射(部分):")
    # for key, value in list(selected_user_id_map.items())[:5]:  # 打印前5个条目
    #     print(f"原用户id: {key}, 新用户id: {value}")
    # 写入新的用户编号数据
    write_json(
        [{'新用户id': user_id_map[user['id']], '原用户id': user['id'], 'course_order_numbered': user['新课程编号id']}
         for user in users], output_user_path)
    print(f'用户编号完成,共编号了 {len(users)} 个用户。')


if __name__ == '__main__':
    main()

 1.2.5、在jsonProcessing文件夹下运行selected_user.py代码

对course_numbered.json文件进行筛选,去除课程观看数小于10的用户,其历史交互序列太少....

得到selected_user_numbered.json。

import json


def read_json(file_path):
    """
    读取JSON文件,假设文件中每一行都是一个独立的JSON对象。
    """
    data = []
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            data.append(json.loads(line))
    return data


def assign_numbers(data):
    """
    为数据中的每个项分配从1开始的编号。
    """
    id_map = {}
    for index, item in enumerate(data, start=1):
        id_map[item['id']] = index
    return id_map

def update_course_orders(users, course_id_map):
    """
    更新用户的课程订单为新的课程编号。
    """
    for user in users:
        user['course_order_numbered'] = [course_id_map[course_id] for course_id in user['course_order']]
        user['新课程编号id'] = user['course_order_numbered']
        del user['course_order']


def write_json(data, file_path):
    """
    将数据写入指定的JSON文件。
    """
    with open(file_path, 'w', encoding='utf-8') as file:
        for item in data:
            json.dump(item, file, ensure_ascii=False)
            file.write('\n')


def filter_users(users):
    """
    剔除学习课程小于10门的用户。
    """
    return [user for user in users if len(user['course_order_numbered']) >= 10]


# 主函数
def main():
    course_json_path = '../data/course_numbered.json'
    user_json_path = 'D:/pythonCode/推荐系统/Dataset/MOOCCube/entities/user.json'
    selected_user_path = '../data/selected_user_numbered.json'

    # 读取课程数据并创建编号映射字典
    courses = read_json(course_json_path)
    course_id_map = {course['原课程编号id']: course['新课程编号id'] for course in courses}

    # 读取用户数据
    users = read_json(user_json_path)

    # 更新用户课程订单为新编号
    update_course_orders(users, course_id_map)

    # 剔除学习课程小于5门的用户
    selected_users = filter_users(users)
    # 重新编号,从1开始
    selected_user_id_map = assign_numbers(selected_users)

    # 写入筛选后的用户编号数据
    write_json(
        [{'新用户id': selected_user_id_map[user['id']], '原用户id': user['id'], 'course_order_numbered': user['新课程编号id']}
         for user in selected_users], selected_user_path)
    print(f'筛选后的用户编号完成,共编号了 {len(selected_users)} 个用户。')


if __name__ == '__main__':
    main()

 1.2.6、统计分析数据集情况

在jsonProcessing文件夹下运行statistics_user_course.py代码,得到的结果为:

import json


def read_json(file_path):
    """
    读取JSON文件,假设文件中每一行都是一个独立的JSON对象。
    """
    data = []
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            data.append(json.loads(line))
    return data


def calculate_averages(filtered_users):
    """
    计算平均每个用户观看的课程数和平均每个课程的被观看数。
    """
    # 初始化变量,用于计算总的课程数量
    total_courses = 0
    # 初始化一个空字典,用于记录每个课程被观看的次数
    every_course_view_count = {}

    # 遍历筛选后的用户列表
    for user in filtered_users:
        # 获取当前用户的课程编号列表
        course_list = user['course_order_numbered']
        # 将当前用户的课程数量累加到总课程数量中
        total_courses += len(course_list)
        # 遍历当前用户的所有课程编号
        for course_id in course_list:
            # 如果课程编号已经在字典中,则增加该课程的观看次数
            if course_id in every_course_view_count:
                every_course_view_count[course_id] += 1
            # 如果课程编号不在字典中,将其添加到字典中,并设置观看次数为1
            else:
                every_course_view_count[course_id] = 1

    # 通过将总的课程数量除以用户数量,计算平均每个用户观看的课程数
    average_courses_per_user = total_courses / len(filtered_users)
    # 通过将所有课程的观看次数之和除以课程数量,计算平均每个课程的被观看数
    average_views_per_course = sum(every_course_view_count.values()) / len(every_course_view_count)

    # 返回总用户数、总课程观看数、平均每个用户观看的课程数和平均每个课程的被观看数
    return len(filtered_users), total_courses, average_courses_per_user, average_views_per_course


# 主函数
def main():

    user_json_path = '../data/selected_user_numbered.json'
    """已经有了一个经过筛选的用户JSON文件(即selected_user_numbered.json),
    其中包含学习课程数量大于或等于10门的用户。代码读取这个文件,然后计算所需的平均值。"""
    # 读取筛选后的用户数据
    filtered_users = read_json(user_json_path)

    # 计算平均值
    sum_users, sum_courses, avg_courses_per_user, avg_views_per_course = calculate_averages(filtered_users)

    print(f'总用户数: {sum_users:.2f}')
    print(f'总课程观看数: {sum_courses:.2f}')
    print(f'平均每个用户观看的课程数: {avg_courses_per_user:.2f}')
    print(f'平均每个课程的被观看数: {avg_views_per_course:.2f}')


if __name__ == '__main__':
    main()

 1.2.7、在jsonProcessing文件夹下运行json2txt.py代码

最终就得到了符合模型输入的txt文本文件,其文本内容在1.1章中查看。

import json


def read_json(file_path):
    """
    读取JSON文件,文件中每一行都是一个独立的JSON对象。它将每一行解析为Python字典,并将所有用户存储在一个列表中。
    """
    data = []
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            data.append(json.loads(line))
    return data


def write_to_txt(users, output_file):
    """
    遍历用户列表,对于每个用户,提取新用户ID和课程编号列表,并将它们转换为指定的格式(每行包含用户ID和课程ID),然后写入TXT文件。
    """
    with open(output_file, 'w', encoding='utf-8') as file:
        for user in users:
            new_user_id = user['新用户id']
            for course_id in user['course_order_numbered']:
                file.write(f"{new_user_id} {course_id}\n")


# 主函数
def main():
    """
    读取JSON文件中的用户数据。
    遍历每个用户的数据,提取新用户ID和课程编号列表。
    将这些数据转换为指定的格式,即每行包含用户ID和课程ID。
    将转换后的数据写入TXT文件。
    """
    user_json_path = '../data/selected_user_numbered_large.json'  # 筛选后的用户JSON文件路径
    output_txt_path = '../data/user-course_order_large.txt'  # 输出TXT文件路径

    # 读取筛选后的用户数据
    filtered_users = read_json(user_json_path)

    # 将用户数据写入TXT文件
    write_to_txt(filtered_users, output_txt_path)

    print(f'用户数据已写入 {output_txt_path}')


if __name__ == '__main__':
    main()

1.3、日志

这四个文件夹是日志文件夹,在main.py运行后会自动生成,如果项目从未运行过,应该是不会有这四个文件夹的。每个文件夹有Test和Val日志,并且会有模型的权重文件(pth)

其文件结构长这样:

1.4、main.py,主函数

 这是主函数的参数设置截图,都有详细的中文注释。默认数据集是ml-1m,我指定为课程数据集。

1.5、model.py

对transformer模型的定义,没有采用对比学习。

import numpy as np
import torch


class PointWiseFeedForward(torch.nn.Module):
    def __init__(self, hidden_units, dropout_rate):
        super(PointWiseFeedForward, self).__init__()

        self.conv1 = torch.nn.Conv1d(hidden_units, hidden_units, kernel_size=1)
        self.dropout1 = torch.nn.Dropout(p=dropout_rate)
        self.relu = torch.nn.ReLU()
        self.conv2 = torch.nn.Conv1d(hidden_units, hidden_units, kernel_size=1)
        self.dropout2 = torch.nn.Dropout(p=dropout_rate)

    def forward(self, inputs):
        outputs = self.dropout2(self.conv2(self.relu(self.dropout1(self.conv1(inputs.transpose(-1, -2))))))
        outputs = outputs.transpose(-1, -2)  # as Conv1D requires (N, C, Length)
        outputs += inputs
        return outputs


# pls use the following self-made multihead attention layer
# in case your pytorch version is below 1.16 or for other reasons
# https://github.com/pmixer/TiSASRec.pytorch/blob/master/model.py

class SASRec(torch.nn.Module):
    def __init__(self, user_num, item_num, args):
        super(SASRec, self).__init__()

        self.user_num = user_num
        self.item_num = item_num
        self.dev = args.device

        # TODO: loss += args.l2_emb for regularizing embedding vectors during training
        # https://stackoverflow.com/questions/42704283/adding-l1-l2-regularization-in-pytorch
        self.item_emb = torch.nn.Embedding(self.item_num + 1, args.hidden_units, padding_idx=0)
        self.pos_emb = torch.nn.Embedding(args.maxlen + 1, args.hidden_units, padding_idx=0)
        self.emb_dropout = torch.nn.Dropout(p=args.dropout_rate)

        self.attention_layernorms = torch.nn.ModuleList()  # to be Q for self-attention
        self.attention_layers = torch.nn.ModuleList()
        self.forward_layernorms = torch.nn.ModuleList()
        self.forward_layers = torch.nn.ModuleList()

        self.last_layernorm = torch.nn.LayerNorm(args.hidden_units, eps=1e-8)

        for _ in range(args.num_blocks):
            new_attn_layernorm = torch.nn.LayerNorm(args.hidden_units, eps=1e-8)
            self.attention_layernorms.append(new_attn_layernorm)

            new_attn_layer = torch.nn.MultiheadAttention(args.hidden_units,
                                                         args.num_heads,
                                                         args.dropout_rate)
            self.attention_layers.append(new_attn_layer)

            new_fwd_layernorm = torch.nn.LayerNorm(args.hidden_units, eps=1e-8)
            self.forward_layernorms.append(new_fwd_layernorm)

            new_fwd_layer = PointWiseFeedForward(args.hidden_units, args.dropout_rate)
            self.forward_layers.append(new_fwd_layer)

    def log2feats(self, log_seqs):  # TODO: fp64 and int64 as default in python, trim?
        seqs = self.item_emb(torch.LongTensor(log_seqs).to(self.dev))
        seqs *= self.item_emb.embedding_dim ** 0.5
        poss = np.tile(np.arange(1, log_seqs.shape[1] + 1), [log_seqs.shape[0], 1])
        # TODO: directly do tensor = torch.arange(1, xxx, device='cuda') to save extra overheads
        poss *= (log_seqs != 0)
        seqs += self.pos_emb(torch.LongTensor(poss).to(self.dev))
        seqs = self.emb_dropout(seqs)

        tl = seqs.shape[1]  # time dim len for enforce causality
        attention_mask = ~torch.tril(torch.ones((tl, tl), dtype=torch.bool, device=self.dev))

        for i in range(len(self.attention_layers)):
            seqs = torch.transpose(seqs, 0, 1)
            Q = self.attention_layernorms[i](seqs)
            mha_outputs, _ = self.attention_layers[i](Q, seqs, seqs,
                                                      attn_mask=attention_mask)
            # need_weights=False) this arg do not work?
            seqs = Q + mha_outputs
            seqs = torch.transpose(seqs, 0, 1)

            seqs = self.forward_layernorms[i](seqs)
            seqs = self.forward_layers[i](seqs)

        log_feats = self.last_layernorm(seqs)  # (U, T, C) -> (U, -1, C)

        return log_feats

    def forward(self, user_ids, log_seqs, pos_seqs):  # for training
        log_feats = self.log2feats(log_seqs)  # user_ids hasn't been used yet

        pos_embs = self.item_emb(torch.LongTensor(pos_seqs).to(self.dev))

        pos_logits = (log_feats * pos_embs).sum(dim=-1)

        return pos_logits  # pos_pred

    def predict(self, user_ids, log_seqs, item_indices):  # for inference
        log_feats = self.log2feats(log_seqs)  # user_ids hasn't been used yet

        final_feat = log_feats[:, -1, :]  # only use last QKV classifier, a waste

        item_embs = self.item_emb(torch.LongTensor(item_indices).to(self.dev))  # (U, I, C)

        logits = item_embs.matmul(final_feat.unsqueeze(-1)).squeeze(-1)

        return logits  # preds # (U, I)

 1.6、utils.py

工具库,没有采用对比学习,代码太长了,三百多行,我这里只展示evaluate_valid和evaluate_test,并且能够计算NDCG@k 和 HR@k,k=1, 2, 5, 10。官方源码只能计算NDCG@10 和 HR@10。

def evaluate_test(model, dataset, args):
    """
    在测试集上评估模型的性能。

    参数:
    model: 要评估的推荐模型。
    dataset: 包含训练集、验证集、测试集、用户总数和物品总数的数据集。
    args: 包含模型参数的命名空间。

    返回:
    tuple: 包含 NDCG 和 HR 的评估结果,每个都有一个 k=1, 2, 5, 10 的值。
    """
    # 深拷贝数据集,以免在评估过程中修改原始数据
    [train, valid, test, usernum, itemnum] = copy.deepcopy(dataset)

    # 初始化 NDCG 和 HR 的累计值
    NDCG = {k: 0.0 for k in [1, 2, 5, 10]}
    HR = {k: 0.0 for k in [1, 2, 5, 10]}
    valid_user = 0.0  # 有效用户计数器

    # 根据用户数量,选择一定数量的用户进行评估
    if usernum > 10000:
        users = random.sample(range(1, usernum + 1), 10000)
    else:
        users = range(1, usernum + 1)

    # 遍历用户,进行评估
    for u in users:
        if len(train[u]) < 1 or len(test[u]) < 1:
            continue

        seq = np.zeros([args.maxlen], dtype=np.int32)
        idx = args.maxlen - 1
        # 将验证集中的第一个物品放在序列的最后
        seq[idx] = valid[u][0]
        idx -= 1
        for i in reversed(train[u]):
            seq[idx] = i
            idx -= 1
            if idx == -1:
                break

        rated = set(train[u])
        rated.add(0)
        item_idx = [test[u][0]]
        for _ in range(100):
            t = np.random.randint(1, itemnum + 1)
            while t in rated:
                t = np.random.randint(1, itemnum + 1)
            item_idx.append(t)

        # 使用模型对用户u的序列和物品列表进行预测
        predictions = -model.predict(*[np.array(l) for l in [[u], [seq], item_idx]])
        predictions = predictions[0]  # 获取第一个用户u的预测结果
        # 获取测试集中第一个物品的预测排名
        rank = predictions.argsort().argsort()[0].item()
        # 更新有效用户计数器
        valid_user += 1

        for k in [1, 2, 5, 10]:
            if rank < k:
                HR[k] += 1
                NDCG[k] += 1 / np.log2(rank + 2)

        # 每处理100个用户,打印一个点以显示进度
        if valid_user % 100 == 0:
            print('.', end="")
            sys.stdout.flush()

    # 计算并返回平均 NDCG 和 HR
    NDCG_avg = {k: NDCG[k] / valid_user if valid_user > 0 else 0 for k in [1, 2, 5, 10]}
    HR_avg = {k: HR[k] / valid_user if valid_user > 0 else 0 for k in [1, 2, 5, 10]}

    return NDCG_avg, HR_avg


def evaluate_valid(model, dataset, args):
    """
    在验证集上评估模型的性能。

    参数:
    model: 要评估的推荐模型。
    dataset: 包含训练集、验证集、测试集、用户总数和物品总数的数据集。
    args: 包含模型参数的命名空间。

    返回:
    tuple: 包含 NDCG@k 和 HR@k 的评估结果,k=1, 2, 5, 10。
    """
    # 深拷贝数据集,以免在评估过程中修改原始数据
    [train, valid, test, usernum, itemnum] = copy.deepcopy(dataset)

    # 初始化 NDCG 和 HR 的累计值
    NDCG = {k: 0.0 for k in [1, 2, 5, 10]}  # 使用字典存储不同 k 值的 NDCG
    HR = {k: 0.0 for k in [1, 2, 5, 10]}  # 使用字典存储不同 k 值的 HR
    valid_user = 0.0  # 有效用户计数器

    # 根据用户数量,选择一定数量的用户进行评估
    if usernum > 10000:
        users = random.sample(range(1, usernum + 1), 10000)
    else:
        users = range(1, usernum + 1)

    # 遍历用户,进行评估
    for u in users:
        if len(train[u]) < 1 or len(valid[u]) < 1: continue

        seq = np.zeros([args.maxlen], dtype=np.int32)
        idx = args.maxlen - 1
        for i in reversed(train[u]):
            seq[idx] = i
            idx -= 1
            if idx == -1: break

        rated = set(train[u])
        rated.add(0)
        item_idx = [valid[u][0]]
        for _ in range(100):
            t = np.random.randint(1, itemnum + 1)
            while t in rated: t = np.random.randint(1, itemnum + 1)
            item_idx.append(t)

        predictions = -model.predict(*[np.array(l) for l in [[u], [seq], item_idx]])
        predictions = predictions[0]

        rank = predictions.argsort().argsort()[0].item()
        valid_user += 1

        for k in [1, 2, 5, 10]:
            if rank < k:
                HR[k] += 1
                NDCG[k] += 1 / np.log2(rank + 2)

        if valid_user % 100 == 0:
            print('.', end="")
            sys.stdout.flush()

    # 计算并返回平均 NDCG@k 和 HR@k
    NDCG_avg = {k: NDCG[k] / valid_user if valid_user > 0 else 0 for k in [1, 2, 5, 10]}
    HR_avg = {k: HR[k] / valid_user if valid_user > 0 else 0 for k in [1, 2, 5, 10]}

    return NDCG_avg, HR_avg

2、项目运行效果

未引入对比学习其结果为:

avg_test_ndcg@10=0.338, avg_test_hr@10=0.5933, avg_time=22.17s/10epoch,RX1650

引入对比学习后,其结果为:

avg_test_ndcg@10=0.4773, avg_test_hr@10=0.7467, avg_time=23.36s/10epoch

对比原先结果,性能提升明显:

avg_test_ndcg@10=0.338,avg_test_hr@10=0.5933,avg_time=22.17s/10epoch

 3、改进,使得精度提升

将原先筛选用户的条件由10改为5,使得数据集增加。

经过筛选,统计出用户和课程的数量、平均每个用户观看的课程数、平均每个课程的被观看数

总用户数: 34917.00;总课程观看数: 273397.00 

平均每个用户观看的课程数: 7.83;平均每个课程的被观看数: 391.69。如下图:

用户平均交互序列长度为5.83,交互比之前更加稀疏:

 再次运行,其结果为:

avg_test_ndcg@10=0.5587,avg_test_hr@10=0.8184,avg_time= 148.14s/10epoch

对比第二节的结果,性能提升巨大:

avg_test_ndcg@10=0.4773,avg_test_hr@10=0.7467,avg_time=23.36s/10epoch

以上就是我对于这篇论文的一个复现代码,大家可以借鉴借鉴,学习代码思想,自己创作。

需要完整代码压缩包的伙伴可以私信我(复现不易,分享难得,小偿即可,白嫖勿扰)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜深人静打代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值