Pytorch----模型的保存, 加载和直接测试

模型的保存和加载:

  • 保存参数表
torch.save(the_model.state_dict(), PATH)

the_model = TheModelClass(*args, **kwargs)
the_model.load_state_dict(torch.load(PATH))
  • 直接保存模型 (不是的, 你的pth变量会保存很多东西)
torch.save(the_model, PATH)

the_model = torch.load(PATH)

 

模型的直接测试:

  • 在prepareModel的时候:  不初始化模型, 直接load模型
  • 在run的时候, 直接把train的部分注释掉

master直接进行测试  +   加载模型直接从断点开始训练:     (普天同庆, 成功了......)

  • 直接进行测试的全部代码:

import numpy as np
from numpy import random
import pickle
from scipy.sparse import csr_matrix
import math
import gc
import time
import random
import datetime
import torch as t
import torch.nn as nn
import torch.utils.data as dataloader
import torch.nn.functional as F
import hypergraph_utils
import DataHandler
import myModel
from Params import args
from Utils.TimeLogger import log
import evaluate

if t.cuda.is_available():
    use_cuda = True
else:
    use_cuda = False


modelTime = str(int(time.time()))[4:]  #TODO: 因为这个所以会过一会儿保存一下

class Model():
    def __init__(self):
        """
        1.准备dataset的数据: 包括训练集和测试集(但是, 以我现在的能力, 先训练集, 再测试集, 再多行为)
            (1)加载数据: 获得矩阵(H,G,E)并转为tensor, 获得itemNum, userNum
            (2)获得训练数据非零的最大最小值(按找师兄给的这个大矩阵, 确实要处理): 用以制作dataset, dataloader
        2.准备要运算的矩阵
        3.准备各种其它参数, 随机种子, 评价指标
        """

        self.trn_file = args.path + args.dataset + '/trn_'
        self.tst_file = args.path + args.dataset + '/tst_int'

        self.t_max = -1 # 处理后数据集的最大时间, 最小时间
        self.t_min = 0x7FFFFFFF
        self.time_number = -1 # 时间段数
        # self.matrixs  # 不加后面的看看会不会出错
        self.user_num = -1
        self.item_num = -1
        self.subgraphs = {}  #不知道字典可不可以以这种方式初始化
        self.behaviors = []#到底为什么要加self?  因为东西初始化的时候, 只在init里面.  如果想要:在这个类的其它函数中也使用, 必须要用它
        self.behaviors_data = {}
        # self.trainMat = csr_matrix()
        # self.train_loader
        # self.test_loader

        #history
        self.train_loss = []
        self.his_hr = []
        self.his_ndcg = []
        gc.collect()  #


        if args.dataset == 'Tmall':
            # self.behaviors = ['buy']
            self.behaviors = ['pv','fav', 'cart', 'buy']  #TODO: 暂时把pv去掉了, 因为数据还没处理好
        elif args.dataset == 'IJCAI_15':
            # self.behaviors = ['click','fav', 'cart', 'buy']
            self.behaviors = ['buy']
        elif args.dataset == 'JD':
            # self.behaviors = ['review','browse', 'buy']
            self.behaviors = ['buy']

        # data_loader 到底组织在代码的哪个地方: 在model init的地方, 在train_model的地方调用
        #加载每个行为的数据   #因为这里的大小不确定: 所以, 是不是需要一个mask, 然后embedding的padding的时候使用
        #加载训练数据
        for i in range(0, len(self.behaviors)):
            with open(self.trn_file + self.behaviors[i], 'rb') as fs:  # r是读取人工数据, rb是读取二进制文件
                data = pickle.load(fs)
                self.behaviors_data[i] = data  # TODO: 这样是不是对内存的要求比较高.  是先存到数据结构当中, 还是到时候再读一遍

                if data.get_shape()[0] > self.user_num:  # TODO: 先按最大的矩阵来, 如果效果不好是不是有pad
                    self.user_num = data.get_shape()[0]  # TODO: 但是, 这样就存在一个问题: 某个时段,会学出一张整表: 这张表中的某个user或者item是不存在的
                if data.get_shape()[1] > self.item_num:  # TODO: 配合运算的矩阵也变成相同大小, 是可以运算.
                    self.item_num = data.get_shape()[1]  # TODO: 本来没有的embedding也会初始化

                # value = data.data  #得到数据
                # row, col = data.nonzero()  # TODO: ????百度也没查到为什么会返回矩阵的行和列

                # """
                # 处理掉出问题的数据:   为什么把这一段注释掉??? 因为我不知道处理过后的行列各是多少?? 而且buy的数据好像没有出错
                # """
                # dataset_t_min=1511539200  #获得最早和最晚时间戳
                # dataset_t_max=1512316799  #13位时间戳的单位是毫秒, 10位时间戳的单位为秒
                #
                # new_row = []
                # new_col = []
                # new_value = []
                # for i in range(value.size):  #处理掉数据集中的错误
                #     cur_value = value[i]
                #     if cur_value > dataset_t_min and cur_value < dataset_t_max:
                #         new_row.append(row[i])
                #         new_col.append(col[i])
                #         new_value.append(cur_value)

                if data.data.max() > self.t_max:
                    self.t_max = data.data.max()
                if data.data.min() < self.t_min:
                    self.t_min = data.data.min()

                # original_matrix = sp.csr_matrix(new_value, (new_row, new_col), shape=(len(new_row), len(new_col)))  #csr矩阵的另一种初始化方式
                # ========>至此得到处理数据后的矩阵
                if self.behaviors[i]==args.target:
                    self.trainMat = data
                    self.trainLabel = 1*(self.trainMat != 0)  #TODO: 看看没用的数据到底是0还是, None
                    self.labelP = np.squeeze(np.array(np.sum(self.trainLabel, axis=0)))  #TODO: 这里为什么要squeeze: 因为做了reduce_sum之后维度会变为1, 所以要把变为1维的那个维度消掉


            tmp_time_number = (self.t_max - self.t_min) / args.time_slot + 1  #TODO: 原来一直没算对, 现在看看是什么情况
            tmp_time_number = tmp_time_number.astype(int)
            # print("\n")
            # print("每次时间缝隙的数量:", tmp_time_number)
            # print("\n")
            # print("最大的时间:", self.t_max)
            # print("\n")
            # print("最小的时间:", self.t_min)
            # print("\n")
            # print("时间缝隙:", args.time_slot)
            # print("\n")

            if tmp_time_number > self.time_number:
                self.time_number = tmp_time_number  # 浮点数向上取整 TODO:做到了吗???

            print("打印看看有多少个时间段: ", self.time_number)
            print("\n")


        time = datetime.datetime.now()
        print("开始构建子图:  ", time)

        # 子图构建
        for i in range(0, len(self.behaviors)):
            beh_subgraphs = hypergraph_utils.subgraph_construction(self.behaviors_data[i], self.time_number, self.user_num, self.item_num, self.t_min)
            self.subgraphs[i] = beh_subgraphs  # TODO: 上面的代码段 要保证得到这个数据结构

        time = datetime.datetime.now()
        print("结束构建子图:  ", time)

        # 将子图由csr转为tensor
        # self.subgraphs = hypergraph_utils.sparse_matrix_to_torch_sparse_tensor(self.subgraphs, self.time_number, self.behaviors)  #因为这里是一个dict所以没有cuda  看看都有哪些变量需要移到.cuda()上
        print("user_num: ", self.user_num)
        print("item_num: ", self.item_num)
        print("\n")


        #--------------------------------------------------------至此: 获得多个时间段下的矩阵------------------------------------------>>>>>

        train_u, train_v = self.trainMat.nonzero()
        # assert np.sum(self.trainMat.data ==0) == 0
        # log("train data size = %d"%(train_u.size))
        train_data = np.hstack((train_u.reshape(-1,1), train_v.reshape(-1,1))).tolist()
        train_dataset = DataHandler.RecDataset(train_data, self.item_num, self.trainMat, True)
        self.train_loader = dataloader.DataLoader(train_dataset, batch_size=args.batch, shuffle=True, num_workers=0)

        #valid_data


        # test_data  TODO: 直接把传入的数据做成ui对就可以了
        with open(self.tst_file, 'rb') as fs:
            data = pickle.load(fs)

        test_user = np.array([idx for idx, i in enumerate(data) if i is not None])
        test_item = np.array([i for idx, i in enumerate(data) if i is not None])
        # tstUsrs = np.reshape(np.argwhere(data!=None), [-1])
        test_data = np.hstack((test_user.reshape(-1,1), test_item.reshape(-1,1))).tolist()
        # testbatch = np.maximum(1, args.batch * args.sampNum // 100)  # TODO: 改变batch的大小----比train_batch大.  至少是1的大小(dataloader的参数)
        test_dataset = DataHandler.RecDataset(test_data, self.item_num, self.trainMat, 0, False)  #TODO: train的data_loader和test的data_loader不一样
        self.test_loader = dataloader.DataLoader(test_dataset, batch_size=args.batch, shuffle=False, num_workers=0)  #如果test的时候, 这个batch出了什么错误


        # --------------------------------------------------------至此: 构建出dataset----------------------------------------------->>>>>


        # #准备好各个时间段的空矩阵, 以备之后赋值
        # for i in range(0, self.time_number):  # TODO: 确认这里不会到time_number, 不会使矩阵的数量多一个, 少一个
        #     tmp_mat = sp.dok_matrix(user_num, item_num)
        #     self.matrixs.appened(tmp_mat)
        #
        # for i in range(len(original_matrix)):
        #     # if mat_time > self.t_min and mat_time < self.t_max:
        #     matrixs[original_matrix.data[i]/agrs.time_slot][][]

    def prepareModel(self):
        self.modelName = self.getModelName()  #这句话只有在存储模型或者直接加载模型的时候才能用到
        self.setRandomSeed()
        self.gnn_layer = eval(args.gnn_layer)  #解析并执行字符串, 单引号和双引号解析为int, 三引号解析为str
        self.hidden_dim = args.hidden_dim
        self.loadModel()
        # self.model = myModel.myModel(self.user_num, self.item_num, self.time_number, self.behaviors, self.subgraphs)  #这里导入包之后, 使用类的语法debug好半天的错误
        self.opt = t.optim.Adam(self.model.parameters(), lr = args.lr, weight_decay = 0)
    

        if use_cuda:
            self.model = self.model.cuda()

    def innerProduct(self, u, i, j):  #user_embeding 和 item_embedding 的时候使用内积.  而且是和正负样本相关
        """
        正,负embedding分别和user做内积, 做两个内积的结果
        """
        pred_i = t.sum(t.mul(u,i), dim=1)  #*args.mult  #因为不知道是 极小还是极大  所以, 这里先往大变
        pred_j = t.sum(t.mul(u,j), dim=1)  #*args.mult
        return pred_i, pred_j

    def run(self):
        #TODO: 向师兄看齐(已完成: 这部分的架构应该和师兄一样)
        # 1.准备模型
        # 2.(加载和保存模型, 之后有机会再做)
        # 3.循环进行每个epoch的训练:
        #       (1)设置多久test一次
        #       (2)训练模型 + 打印
        #       (3)如果需要test模型: test + 打印
        #       (4)(如果需要保存模型.  保存)
        # 4.整体test模型 + 打印
        # 5.(整体保存模型)

        self.prepareModel()
        log('Model Prepared')


        cvWait = 0  
        self.best_HR = 0 
        self.best_NDCG = 0
        flag = 0

        for e in range(0, args.epoch+1):  
            test = (e % args.tstEpoch == 0)  
            self.curEpoch = e
            log("*****************开始第%d个epoch的测试 ************************"%e)  


            # epoch_loss = self.trainEpoch()
            # self.train_loss.append(epoch_loss)  
            # log("epoch %d/%d, epoch_loss=%.2f"% (e, args.epoch, epoch_loss))


            # self.train_loss.append(epoch_loss)


            HR, NDCG = self.testEpoch(self.test_loader)
            self.his_hr.append(HR)
            self.his_ndcg.append(NDCG)


            if HR > self.best_HR:
                # self.saveHistory()
                # self.saveModel()
                self.best_HR = HR
                self.best_NDCG = NDCG
                print("--------------------------------------------------------------------------------------------------------------------------best_HR", self.best_HR)
                print("--------------------------------------------------------------------------------------------------------------------------best_NDCG", self.best_NDCG)
            


                # print(f"epoch{e}/{args.epoch}, test HR = {HR}, valid NDCG = {NDCG}")  TODO:深深地怀疑这个用法不能打印函数

                # print("epoch %d/%d, test HR = %.4f, valid NDCG = %.4f"%(e, \
                #                                                         args.epoch, \
                #                                                         HR, \
                #                                                         NDCG))  #TODO:这段打印报了很多错
                # log("epoch %d/%d, test HR = %.4f, valid NDCG = %.4f" % (e, args.epoch, HR, NDCG))


            # 调节学习率
            # self.adjust_learning_rate(self.opt, e)

            # 提前终止  TODO: 自己再搞一下
            # if HR > best_HR:
            #     best_HR = HR
            #     cvWait = 0
            #     best_epoch = self.curEpoch
            #     self.saveModel()
            # else:
            #     cvWait += 1
            #     log("cvWait = %d"%(cvWait))
            # 
            # self.saveHistory()
            #
            #
            # if cvWait == args.patience:
            #     log('Early stopping! best epoch = %d'%(best_epoch))
            #     self.loadModel(self.modelName)
            #     break

        # 和上面的test不同的是: 这个是最后的测试. 而上面的是在每个epoch进行的过程中 每三个一次, 免得自己等太久看不到结果
        HR, NDCG = self.testEpoch(self.test_loader)
        self.his_hr.append(HR)
        self.his_ndcg.append(NDCG)
        # log("epoch %d/%d, test HR = %.4f, valid NDCG = %.4f" % (e, args.epoch, HR, NDCG))

    def trainEpoch(self):
        """
        1.将groundtruth送进来
        2.通过model, 获得所有的embedding
        3.计算各种loss, 反向传播, 更新
        :return:
        """
        train_loader = self.train_loader

        log("start negative sample...")
        train_loader.dataset.ng_sample()
        log("finish negative sample...")

        epoch_loss = 0
        cnt = 0
        for user, item_i, item_j in train_loader:  #TODO:train_loader----理解成一个迭代器,每次返回要训练的数据.
            # time = datetime.datetime.now()
            # print("step_time_start:  ", time)

            # if cnt==1:
            #     break

            """
            上面三个量的数据类型都是: tensor
            上面三个量的数据大小都是: 32, 即跟着参数batch走
            """
            # TODO: 即, 现在是将所有的train数据 或者 test数据都送进dataloader. 我们要做的是要将什么送进dataloader
            # print("\n")  #因为我这里要看看data_loader和batch_size的关系
            # print("看看user的数据类型", type(user))
            # print("看看user的大小", user.shape)
            # print("看看item_i的数据类型", type(item_i))
            # print("看看item_i的大小", item_i.shape)
            # print("看看item_j的数据类型", type(item_j))
            # print("看看item_j的大小", item_j.shape)
            # print("cnt: ", cnt)
            user = user.long().cuda()
            item_i = item_i.long().cuda()
            item_j = item_j.long().cuda()

            # print("***********************************************************************")
            # print("***********************************************************************")
            # print("******************************cnt: %d ************************************" % cnt)
            # print("***********************************************************************")
            # print("***********************************************************************")
            # print(self.model.state_dict())

            user_embed, item_embed = self.model(self.subgraphs)

            userEmbed = user_embed[user]
            posEmbed = item_embed[item_i]
            negEmbed = item_embed[item_j]

            pred_i, pred_j = self.innerProduct(userEmbed, posEmbed, negEmbed)

            bprloss = - (pred_i.view(-1) - pred_j.view(-1)).sigmoid().log().sum()  #TODO:  因为loss为nan才设置的 bpr_loss只是正负样本对的预测结果, 不涉及ground_truth  TODO:可以用mean做
            regLoss = (t.norm(userEmbed) ** 2 + t.norm(posEmbed) ** 2 + t.norm(negEmbed) ** 2)

            loss = 0.5 * (bprloss + args.reg * regLoss) / args.batch
            epoch_loss = epoch_loss + bprloss.item()
            self.opt.zero_grad()
            loss.backward()
            self.opt.step()

            # time = datetime.datetime.now()
            # print("step_time_end: ", time)

            cnt+=1
            # if cnt%20 == 0:  #TODO:一共642916条数据, 每次取batch=32来, 一个epoch确实需要训练很长时间
            #     print("cnt: %d  step_loss: %f \n" % (cnt, loss.item()))  # python打印输出的语法: 如果要打印的话, 用%连接
            log('step %d, step_loss = %f'%(cnt, loss.item()), save=False, oneline=True)  #log打印不了
        log("finish train")
        return epoch_loss

    def testEpoch(self, data_loader, save=False):
        # load test dataset
        # HR, NDCG = self.validModel(self.test_loader)
        # log("test HR = %.4f, test NDCG = %.4f" % (HR, NDCG))
        # log("model name : %s" % (self.modelName))
        # epochHit, epochNdcg = [0] * 2
        epochHR, epochNDCG = [0]*2
        user_embed, item_embed = self.model(self. subgraphs)

        cnt = 0
        tot = 0
        for user, item_i in data_loader:
            user_compute, item_compute, user_item1, user_item100 = self.sampleTestBatch(user, item_i)  #TODO: 这里.cuda()会报错, 可能是函数返回的东西不是tensor. 但是感觉不是cuda会有问题, 之后再移动.
            userEmbed = user_embed[user_compute]  #[614400, 16], [147894, 16]
            # bug = [ (index, value)  for index, value in enumerate(item_compute)  if (value>=22734 or value<0)]
            # print("**************************bug:  ", bug)
            # print("**************************max(item_compute):  ", max(item_compute))
            # print("**************************min(item_compute):  ", min(item_compute))
            # print("**************************item_embed: ", len(item_embed))

            itemEmbed = item_embed[item_compute]
            # itemEmbed = item_embed[ \
            #     item_compute \
            #     ]  #[], []
            pred_i = t.sum(t.mul(userEmbed, itemEmbed), dim=1)  #原来是*点乘, 维度相同, 按一行合成一个数: 就是预测的分数

            hit, ndcg = self.calcRes(t.reshape(pred_i, [user.shape[0], 100]), user_item1, user_item100)  #TODO: 是按照自己的想法写的, 非常有可能报错
            epochHR = epochHR + hit  #TODO: 看看这里到底是一个列表, 还是加法
            epochNDCG = epochNDCG + ndcg  #
            # print(f"Step {cnt}:  hit:{hit}, ndcg:{ndcg}")
            cnt += 1 #TODO: 可以看看像这种情况会在哪些地方报错
            tot += user.shape[0]


        result_HR = epochHR / tot
        result_NDCG = epochNDCG / tot
        print(f"Step {cnt}:  hit:{result_HR}, ndcg:{result_NDCG}")

        return result_HR, result_NDCG
    # def validModel(self, data_loader, save=False):
    #     HR, NDCG = [], []
    #     user_embed, item_embed = self.model(self.subgraphs)
    #
    #     for user, item_i in data_loader:
    #         user = user.long().cuda()  #TODO: 问问环测师兄为什么这里要用long()
    #         item_i = item_i.long().cuda()
    #
    #         userEmbed = user_embed[user]
    #         testItemEmbed = item_embed[item_i]
    #         pred_i = t.sum(t.mul(userEmbed, testItemEmbed), dim=1)  #还是所有的embedding一起算
    #
    #         #TODO: 按照每个batch计算 评价指标
    #         batch = int(user.cpu().numpy().size / 101)
    #         assert user.cpu().numpy().size % 101 == 0
    #         for i in range(batch):  # TODO: 问题来了, 为什么哪里都会有一个101??
    #             batch_scores = pred_i[i * 101: (i + 1) * 101].view(-1)
    #             _, indices = t.topk(batch_scores, self.args.top_k)
    #             tmp_item_i = item_i[i * 101: (i + 1) * 101]
    #             recommends = t.take(tmp_item_i, indices).cpu().numpy().tolist()
    #             gt_item = tmp_item_i[0].item()
    #             HR.append(evaluate.hit(gt_item, recommends))
    #             NDCG.append(evaluate.ndcg(gt_item, recommends))
    #
    #     return np.mean(HR), np.mean(NDCG)
    def sampleTestBatch(self, batch_user_id, batch_item_id):
        #TODO:
        # 输入: 一个batch的user_id
        # 需求:
        # (1) [batch, 1]ground_truth------其实外层函数是有的, 直接传进来就好
        # (2) buy邻接矩阵------self.应该是有的, 直接用就好
        # (3) []
        # (4) []
        # [batch, item_num]可以负采样的------从矩阵中取出的一条, 有值为:false, 无值为:true
        # 输出: batch*100个user_id, batch*100个item_id, [batch, 1]ground_truth, [batch, 100]
        batch = len(batch_user_id)
        tmplen = (batch*100)

        sub_trainMat = self.trainMat[batch_user_id].toarray()  #取到sub矩阵
        user_item1 = batch_item_id  #得到每个user对应的正例
        user_compute = [None] * tmplen
        item_compute = [None] * tmplen
        user_item100 = [None] * (batch)

        cur = 0
        for i in range(batch):
            pos_item = user_item1[i]  #得到每个user对应的正例
            negset = np.reshape(np.argwhere(sub_trainMat[i]==0), [-1])  #TODO:得到一个sub矩阵, 并且把负例挑出来------!!!这里按理来说应该得到所有负的id.  不定长,所有负样本的id做成一列
            pvec = self.labelP[negset]  #取到未交互的item的频数 
            pvec = pvec / np.sum(pvec)  #对所有取到的频数做归一化
            # random_neg_sam = np.random.choice(negset, 99, replace=False, p=pvec)  #在negset里面取, 取99个, 以pvec的概率
            
            random_neg_sam = np.random.permutation(negset)[:99]  #随机打乱, 取前99个: 如果报错你的包有问题, 很有可能是你自己的拼写错误
            user_item100_one_user = np.concatenate(( random_neg_sam, np.array([pos_item])))  #TODO: np.concatenate 这个接口一次性完成数组的拼接. 但是, 有一个很神奇的事情让我debug了半天: 第一个参数自己就要加括号
            user_item100[i] = user_item100_one_user

            for j in range(100):
                user_compute[cur] = batch_user_id[i]
                item_compute[cur] = user_item100_one_user[j]
                cur += 1

        return user_compute, item_compute, user_item1, user_item100
# 参数一: batch*100即每个user重复100遍[batch*100] 参数二:随机采样的99个负例item+正例item[batch*100]
# 参数三: 正例对应的item_id[batch]                参数四:随机采样+负例[batch, 100]
    def calcRes(self, pred_i, user_item1, user_item100):  #[6144, 100] [6144] [6144, (ndarray:(100,))]
        #TODO:
        # 1.遍历: 每一个预测结果
        #       (1)
        #       (2)
        #       (3)
        #       (4)

        hit = 0
        ndcg = 0

        # print("等一下: 这个函数你进来了吗?")

        for j in range(pred_i.shape[0]):
            # predvals = list(zip(pred_i[j], user_item100[j]))  #100:100预测分数和id放在一起
            # predvals.sort(key=lambda x:x[0], reverse=True)  #按分数降序排列
            # shoot = list(map(lambda x:x[1], predvals[:args.shoot]))  #取预测分数的前shoot个的item_id

            _, shoot_index = t.topk(pred_i[j], args.shoot) 
            shoot_index = shoot_index.cpu()
            shoot = user_item100[j][shoot_index]
            shoot = shoot.tolist()

            if user_item1[j] in shoot:  #如果正样本的id在前k个

                hit += 1  #HR只要在里面, 就计数: 所以HR的分子是一个正数
                ndcg += np.reciprocal( \
                    np.log2( \
                        shoot.index( \
                            user_item1[j])+2))  #取在里面的位置做分母, 作为惩罚:  所以ndcg的分子是一个分数
        # print(f"hit: {}")

        return hit, ndcg  #int, float

    def setRandomSeed(self):
        np.random.seed(args.seed)
        t.manual_seed(args.seed)
        t.cuda.manual_seed(args.seed)
        random.seed(args.seed)

    def getModelName(self):  #TODO: 再确认一下自己的参数名都是否正确
        title = "master_behavior_attention_mean"
        ModelName = title + args.dataset + "_" + modelTime + \
        "_lr_" + str(args.lr) + \
        "_batch_size_" + str(args.batch) + \
        "_reg_" + str(args.reg) + \
        "_time_slot_" + str(args.time_slot) + \
        "_hidden_dim_" + str(args.hidden_dim) + \
        "_gnn_layer_" + str(args.gnn_layer)

        return ModelName

    #TODO: 需要的东西主要从history中加载
    def saveHistory(self):  #获得训练到某一个epoch为止的各种loss和指标, 存储到某个文件中
        #要画图的东西存到history里面
        history = dict()
        history['loss'] = self.train_loss  #获得训练到某一个epoch为止的loss
        history['HR'] = self.his_hr
        history['NDCG'] = self.his_ndcg
        ModelName = self.modelName

        with open(r'./History/' + args.dataset + r'/' + ModelName + '.his', 'wb') as fs:  # r代表不转义 TODO: 这个路径还要再确认一下吧, 主要是这个ModelName的问题
            pickle.dump(history, fs)


    def saveModel(self):  #这个模型没什么, 就是保存函数. TODO: 主要思考这里保存什么函数
        ModelName = self.getModelName()
        ModelName = self.modelName

        history = dict()
        # history['loss'] = self.train_loss
        # history['HR'] = self.his_hr
        # history['NDCG'] = self.his_ndcg
        savePath = r'./Model/' + args.dataset + r'/' + ModelName + r'.pth'
        params = {
            # 'epoch': self.curEpoch,
            # 'lr': self.lr,
            'model': self.model,
            # 'reg': self.reg,
            # 'history': history,
        }
        t.save(params, savePath)

    def loadModel(self): #TODO: 问一下环测师兄这个有用吗
        ModelName = self.modelName
        # loadPath = r'./Model/' + args.dataset + r'/' + ModelName + r'.pth'
        loadPath = "/mnt/hdd10/weiw/master_behavior_attention/Model/IJCAI_15/master_behavior_attention_meanIJCAI_15_675679_lr_0.001_batch_size_4096_reg_0.01_time_slot_15552000_hidden_dim_16_gnn_layer_[16, 16].pth"
        checkpoint = t.load(loadPath)
        self.model = checkpoint['model']

        # self.curEpoch = checkpoint['epoch'] + 1
        # self.lr = checkpoint['lr']
        # self.model = checkpoint['model']
        # self.args.reg = checkpoint['reg']
        # # 恢复history
        # history = checkpoint['history']
        # self.train_loss = history['loss']
        # self.his_hr = history['HR']
        # self.his_ndcg = history['NDCG']
        # log("load model %s in epoch %d"%(modelPath, checkpoint['epoch']))



if __name__ == '__main__':
    # TODO:   follow师兄的实验设置, 将模型向和大家一个水平调整
    #      -3.把embedding写成para和, 把embedding写成embedding是不一样
    #      -2.初始化只有一次: 所有weight初始的值只有一次
    #      -1.到底什么是一个模型需要训练的参数:  (1)写进去的weights    (2)embedding算不算???:  embedding到底是自己计算更新, 还是和模型一起会计算
    #      0.查看自己的参数有没有更新:  因为, 现在明显感觉, 自己的loss只是通过embedding计算的. 所以,即感觉参数好像没有更新???
    #      1.将run的过程, 调整成和师兄一样
    #      2.确定, 自己的dataloader到底要如何组织
    #      3.理解大图采样
    #      4.理解batch采样
    #      5.理解测评----最终目的, 知道模型要向着一个好的结果, 中间的计算过程是什么, 会有哪些影响因素
    #      6.查看师兄的代码, 看看有哪些巧妙的做法, 能够使得模型结果上升.

    my_model = Model()  #为了偷懒想将整个训练的模型移到GPU上, 看看这样做会不会报错: 不是pytorch框架里的东西是没有cuda()的
    my_model.run()
    # my_model.test()

  • 直接从断点开始训练:

import numpy as np
from numpy import random
import pickle
from scipy.sparse import csr_matrix
import math
import gc
import time
import random
import datetime
import torch as t
import torch.nn as nn
import torch.utils.data as dataloader
import torch.nn.functional as F
import hypergraph_utils
import DataHandler
import myModel
from Params import args
from Utils.TimeLogger import log
import evaluate

if t.cuda.is_available():
    use_cuda = True
else:
    use_cuda = False


# modelTime = str(int(time.time()))[4:]  #TODO: 因为这个所以会过一会儿保存一下
now_time = datetime.datetime.now()
modelTime = datetime.datetime.strftime(now_time,'%Y_%m_%d__%H_%M_%S')



class Model():
    def __init__(self):
        """
        1.准备dataset的数据: 包括训练集和测试集(但是, 以我现在的能力, 先训练集, 再测试集, 再多行为)
            (1)加载数据: 获得矩阵(H,G,E)并转为tensor, 获得itemNum, userNum
            (2)获得训练数据非零的最大最小值(按找师兄给的这个大矩阵, 确实要处理): 用以制作dataset, dataloader
        2.准备要运算的矩阵
        3.准备各种其它参数, 随机种子, 评价指标
        """

        self.trn_file = args.path + args.dataset + '/trn_'
        self.tst_file = args.path + args.dataset + '/tst_int'

        self.t_max = -1 # 处理后数据集的最大时间, 最小时间
        self.t_min = 0x7FFFFFFF
        self.time_number = -1 # 时间段数
        # self.matrixs  # 不加后面的看看会不会出错
        self.user_num = -1
        self.item_num = -1
        self.subgraphs = {}  #不知道字典可不可以以这种方式初始化
        self.behaviors = []#到底为什么要加self?  因为东西初始化的时候, 只在init里面.  如果想要:在这个类的其它函数中也使用, 必须要用它
        self.behaviors_data = {}
        # self.trainMat = csr_matrix()
        # self.train_loader
        # self.test_loader

        #history
        self.train_loss = []
        self.his_hr = []
        self.his_ndcg = []
        gc.collect()  #


        #
        self.curEpoch = 0
        # self.isLoadModel = args.isload

        if args.dataset == 'Tmall':
            # self.behaviors = ['buy']
            self.behaviors = ['pv','fav', 'cart', 'buy']  #TODO: 暂时把pv去掉了, 因为数据还没处理好
        elif args.dataset == 'IJCAI_15':
            # self.behaviors = ['click','fav', 'cart', 'buy']
            self.behaviors = ['buy']
        elif args.dataset == 'JD':
            self.behaviors = ['review','browse', 'buy']
            # self.behaviors = ['buy']

        # data_loader 到底组织在代码的哪个地方: 在model init的地方, 在train_model的地方调用
        #加载每个行为的数据   #因为这里的大小不确定: 所以, 是不是需要一个mask, 然后embedding的padding的时候使用
        #加载训练数据
        for i in range(0, len(self.behaviors)):
            with open(self.trn_file + self.behaviors[i], 'rb') as fs:  # r是读取人工数据, rb是读取二进制文件
                data = pickle.load(fs)
                self.behaviors_data[i] = data  # TODO: 这样是不是对内存的要求比较高.  是先存到数据结构当中, 还是到时候再读一遍

                if data.get_shape()[0] > self.user_num:  # TODO: 先按最大的矩阵来, 如果效果不好是不是有pad
                    self.user_num = data.get_shape()[0]  # TODO: 但是, 这样就存在一个问题: 某个时段,会学出一张整表: 这张表中的某个user或者item是不存在的
                if data.get_shape()[1] > self.item_num:  # TODO: 配合运算的矩阵也变成相同大小, 是可以运算.
                    self.item_num = data.get_shape()[1]  # TODO: 本来没有的embedding也会初始化

                # value = data.data  #得到数据
                # row, col = data.nonzero()  # TODO: ????百度也没查到为什么会返回矩阵的行和列

                # """
                # 处理掉出问题的数据:   为什么把这一段注释掉??? 因为我不知道处理过后的行列各是多少?? 而且buy的数据好像没有出错
                # """
                # dataset_t_min=1511539200  #获得最早和最晚时间戳
                # dataset_t_max=1512316799  #13位时间戳的单位是毫秒, 10位时间戳的单位为秒
                #
                # new_row = []
                # new_col = []
                # new_value = []
                # for i in range(value.size):  #处理掉数据集中的错误
                #     cur_value = value[i]
                #     if cur_value > dataset_t_min and cur_value < dataset_t_max:
                #         new_row.append(row[i])
                #         new_col.append(col[i])
                #         new_value.append(cur_value)

                if data.data.max() > self.t_max:
                    self.t_max = data.data.max()
                if data.data.min() < self.t_min:
                    self.t_min = data.data.min()

                # original_matrix = sp.csr_matrix(new_value, (new_row, new_col), shape=(len(new_row), len(new_col)))  #csr矩阵的另一种初始化方式
                # ========>至此得到处理数据后的矩阵
                if self.behaviors[i]==args.target:
                    self.trainMat = data
                    self.trainLabel = 1*(self.trainMat != 0)  #TODO: 看看没用的数据到底是0还是, None
                    self.labelP = np.squeeze(np.array(np.sum(self.trainLabel, axis=0)))  #TODO: 这里为什么要squeeze: 因为做了reduce_sum之后维度会变为1, 所以要把变为1维的那个维度消掉


            tmp_time_number = (self.t_max - self.t_min) / args.time_slot + 1  #TODO: 原来一直没算对, 现在看看是什么情况
            tmp_time_number = tmp_time_number.astype(int)
            # print("\n")
            # print("每次时间缝隙的数量:", tmp_time_number)
            # print("\n")
            # print("最大的时间:", self.t_max)
            # print("\n")
            # print("最小的时间:", self.t_min)
            # print("\n")
            # print("时间缝隙:", args.time_slot)
            # print("\n")

            if tmp_time_number > self.time_number:
                self.time_number = tmp_time_number  # 浮点数向上取整 TODO:做到了吗???

            print("打印看看有多少个时间段: ", self.time_number)
            print("\n")


        time = datetime.datetime.now()
        print("开始构建子图:  ", time)

        # 子图构建
        for i in range(0, len(self.behaviors)):
            beh_subgraphs = hypergraph_utils.subgraph_construction(self.behaviors_data[i], self.time_number, self.user_num, self.item_num, self.t_min)
            self.subgraphs[i] = beh_subgraphs  # TODO: 上面的代码段 要保证得到这个数据结构

        time = datetime.datetime.now()
        print("结束构建子图:  ", time)

        # 将子图由csr转为tensor
        # self.subgraphs = hypergraph_utils.sparse_matrix_to_torch_sparse_tensor(self.subgraphs, self.time_number, self.behaviors)  #因为这里是一个dict所以没有cuda  看看都有哪些变量需要移到.cuda()上
        print("user_num: ", self.user_num)
        print("item_num: ", self.item_num)
        print("\n")


        #--------------------------------------------------------至此: 获得多个时间段下的矩阵------------------------------------------>>>>>

        train_u, train_v = self.trainMat.nonzero()
        # assert np.sum(self.trainMat.data ==0) == 0
        # log("train data size = %d"%(train_u.size))
        train_data = np.hstack((train_u.reshape(-1,1), train_v.reshape(-1,1))).tolist()
        train_dataset = DataHandler.RecDataset(train_data, self.item_num, self.trainMat, True)
        self.train_loader = dataloader.DataLoader(train_dataset, batch_size=args.batch, shuffle=True, num_workers=0)

        #valid_data


        # test_data  TODO: 直接把传入的数据做成ui对就可以了
        with open(self.tst_file, 'rb') as fs:
            data = pickle.load(fs)

        test_user = np.array([idx for idx, i in enumerate(data) if i is not None])
        test_item = np.array([i for idx, i in enumerate(data) if i is not None])
        # tstUsrs = np.reshape(np.argwhere(data!=None), [-1])
        test_data = np.hstack((test_user.reshape(-1,1), test_item.reshape(-1,1))).tolist()
        # testbatch = np.maximum(1, args.batch * args.sampNum // 100)  # TODO: 改变batch的大小----比train_batch大.  至少是1的大小(dataloader的参数)
        test_dataset = DataHandler.RecDataset(test_data, self.item_num, self.trainMat, 0, False)  #TODO: train的data_loader和test的data_loader不一样
        self.test_loader = dataloader.DataLoader(test_dataset, batch_size=args.batch, shuffle=False, num_workers=0)  #如果test的时候, 这个batch出了什么错误


        # --------------------------------------------------------至此: 构建出dataset----------------------------------------------->>>>>


        # #准备好各个时间段的空矩阵, 以备之后赋值
        # for i in range(0, self.time_number):  # TODO: 确认这里不会到time_number, 不会使矩阵的数量多一个, 少一个
        #     tmp_mat = sp.dok_matrix(user_num, item_num)
        #     self.matrixs.appened(tmp_mat)
        #
        # for i in range(len(original_matrix)):
        #     # if mat_time > self.t_min and mat_time < self.t_max:
        #     matrixs[original_matrix.data[i]/agrs.time_slot][][]

    def prepareModel(self):
        self.modelName = self.getModelName()  #这句话只有在存储模型或者直接加载模型的时候才能用到
        self.setRandomSeed()
        self.gnn_layer = eval(args.gnn_layer)  #解析并执行字符串, 单引号和双引号解析为int, 三引号解析为str
        self.hidden_dim = args.hidden_dim
        # self.out_dim =
        self.model = myModel.myModel(self.user_num, self.item_num, self.time_number, self.behaviors, self.subgraphs)  #这里导入包之后, 使用类的语法debug好半天的错误
        self.opt = t.optim.Adam(self.model.parameters(), lr = args.lr, weight_decay = 0)
    

        if use_cuda:
            self.model = self.model.cuda()

    def innerProduct(self, u, i, j):  #user_embeding 和 item_embedding 的时候使用内积.  而且是和正负样本相关
        """
        正,负embedding分别和user做内积, 做两个内积的结果
        """
        pred_i = t.sum(t.mul(u,i), dim=1)  #*args.mult  #因为不知道是 极小还是极大  所以, 这里先往大变
        pred_j = t.sum(t.mul(u,j), dim=1)  #*args.mult
        return pred_i, pred_j

    def run(self):
        #TODO: 向师兄看齐(已完成: 这部分的架构应该和师兄一样)
        # 1.准备模型
        # 2.(加载和保存模型, 之后有机会再做)
        # 3.循环进行每个epoch的训练:
        #       (1)设置多久test一次
        #       (2)训练模型 + 打印
        #       (3)如果需要test模型: test + 打印
        #       (4)(如果需要保存模型.  保存)
        # 4.整体test模型 + 打印
        # 5.(整体保存模型)

        self.prepareModel()
        if args.isload == True:
            self.loadModel(args.loadModelPath)
            print("----------------------pre test:")
            HR, NDCG = self.testEpoch(self.test_loader)
            print(f"HR: {HR} , NDCG: {NDCG}")    #以f开头的字符串, {}中间的值会被替换掉

        log('Model Prepared')


        cvWait = 0  
        self.best_HR = 0 
        self.best_NDCG = 0
        flag = 0

        for e in range(self.curEpoch, args.epoch+1):  
            test = (e % args.tstEpoch == 0)  
            self.curEpoch = e
            log("*****************开始第%d个epoch的训练 ************************"%e)  


            epoch_loss = self.trainEpoch()
            self.train_loss.append(epoch_loss)  
            log("epoch %d/%d, epoch_loss=%.2f"% (e, args.epoch, epoch_loss))


            self.train_loss.append(epoch_loss)


            HR, NDCG = self.testEpoch(self.test_loader)
            self.his_hr.append(HR)
            self.his_ndcg.append(NDCG)


            if HR > self.best_HR:
                # self.saveHistory()
                self.saveModel()
                self.best_HR = HR
                self.best_NDCG = NDCG
                self.best_epoch = self.curEpoch 
                print("--------------------------------------------------------------------------------------------------------------------------best_HR", self.best_HR)
                print("--------------------------------------------------------------------------------------------------------------------------best_NDCG", self.best_NDCG)
            


                # print(f"epoch{e}/{args.epoch}, test HR = {HR}, valid NDCG = {NDCG}")  TODO:深深地怀疑这个用法不能打印函数

                # print("epoch %d/%d, test HR = %.4f, valid NDCG = %.4f"%(e, \
                #                                                         args.epoch, \
                #                                                         HR, \
                #                                                         NDCG))  #TODO:这段打印报了很多错
                # log("epoch %d/%d, test HR = %.4f, valid NDCG = %.4f" % (e, args.epoch, HR, NDCG))


            # 调节学习率
            # self.adjust_learning_rate(self.opt, e)

            # 提前终止  TODO: 自己再搞一下
            # if HR > best_HR:
            #     best_HR = HR
            #     cvWait = 0
            #     best_epoch = self.curEpoch
            #     self.saveModel()
            # else:
            #     cvWait += 1
            #     log("cvWait = %d"%(cvWait))
            # 
            # self.saveHistory()
            #
            #
            # if cvWait == args.patience:
            #     log('Early stopping! best epoch = %d'%(best_epoch))
            #     self.loadModel(self.modelName)
            #     break

        # 和上面的test不同的是: 这个是最后的测试. 而上面的是在每个epoch进行的过程中 每三个一次, 免得自己等太久看不到结果
        HR, NDCG = self.testEpoch(self.test_loader)
        self.his_hr.append(HR)
        self.his_ndcg.append(NDCG)
        # log("epoch %d/%d, test HR = %.4f, valid NDCG = %.4f" % (e, args.epoch, HR, NDCG))

    def trainEpoch(self):
        """
        1.将groundtruth送进来
        2.通过model, 获得所有的embedding
        3.计算各种loss, 反向传播, 更新
        :return:
        """
        train_loader = self.train_loader

        log("start negative sample...")
        train_loader.dataset.ng_sample()
        log("finish negative sample...")

        epoch_loss = 0
        cnt = 0
        for user, item_i, item_j in train_loader:  #TODO:train_loader----理解成一个迭代器,每次返回要训练的数据.
            # time = datetime.datetime.now()
            # print("step_time_start:  ", time)

            # if cnt==1:
                # break

            """
            上面三个量的数据类型都是: tensor
            上面三个量的数据大小都是: 32, 即跟着参数batch走
            """
            # TODO: 即, 现在是将所有的train数据 或者 test数据都送进dataloader. 我们要做的是要将什么送进dataloader
            # print("\n")  #因为我这里要看看data_loader和batch_size的关系
            # print("看看user的数据类型", type(user))
            # print("看看user的大小", user.shape)
            # print("看看item_i的数据类型", type(item_i))
            # print("看看item_i的大小", item_i.shape)
            # print("看看item_j的数据类型", type(item_j))
            # print("看看item_j的大小", item_j.shape)
            # print("cnt: ", cnt)
            user = user.long().cuda()
            item_i = item_i.long().cuda()
            item_j = item_j.long().cuda()

            # print("***********************************************************************")
            # print("***********************************************************************")
            # print("******************************cnt: %d ************************************" % cnt)
            # print("***********************************************************************")
            # print("***********************************************************************")
            # print(self.model.state_dict())

            user_embed, item_embed = self.model(self.subgraphs)

            userEmbed = user_embed[user]
            posEmbed = item_embed[item_i]
            negEmbed = item_embed[item_j]

            pred_i, pred_j = self.innerProduct(userEmbed, posEmbed, negEmbed)

            bprloss = - (pred_i.view(-1) - pred_j.view(-1)).sigmoid().log().sum()  #TODO:  因为loss为nan才设置的 bpr_loss只是正负样本对的预测结果, 不涉及ground_truth  TODO:可以用mean做
            regLoss = (t.norm(userEmbed) ** 2 + t.norm(posEmbed) ** 2 + t.norm(negEmbed) ** 2)

            loss = 0.5 * (bprloss + args.reg * regLoss) / args.batch
            epoch_loss = epoch_loss + bprloss.item()
            self.opt.zero_grad()
            loss.backward()
            self.opt.step()

            # time = datetime.datetime.now()
            # print("step_time_end: ", time)

            cnt+=1
            # if cnt%20 == 0:  #TODO:一共642916条数据, 每次取batch=32来, 一个epoch确实需要训练很长时间
            #     print("cnt: %d  step_loss: %f \n" % (cnt, loss.item()))  # python打印输出的语法: 如果要打印的话, 用%连接
            log('step %d, step_loss = %f'%(cnt, loss.item()), save=False, oneline=True)  #log打印不了
        log("finish train")
        return epoch_loss

    def testEpoch(self, data_loader, save=False):
        # load test dataset
        # HR, NDCG = self.validModel(self.test_loader)
        # log("test HR = %.4f, test NDCG = %.4f" % (HR, NDCG))
        # log("model name : %s" % (self.modelName))
        # epochHit, epochNdcg = [0] * 2
        epochHR, epochNDCG = [0]*2
        user_embed, item_embed = self.model(self. subgraphs)

        cnt = 0
        tot = 0
        for user, item_i in data_loader:
            user_compute, item_compute, user_item1, user_item100 = self.sampleTestBatch(user, item_i)  #TODO: 这里.cuda()会报错, 可能是函数返回的东西不是tensor. 但是感觉不是cuda会有问题, 之后再移动.
            userEmbed = user_embed[user_compute]  #[614400, 16], [147894, 16]
            # bug = [ (index, value)  for index, value in enumerate(item_compute)  if (value>=22734 or value<0)]
            # print("**************************bug:  ", bug)
            # print("**************************max(item_compute):  ", max(item_compute))
            # print("**************************min(item_compute):  ", min(item_compute))
            # print("**************************item_embed: ", len(item_embed))

            itemEmbed = item_embed[item_compute]
            # itemEmbed = item_embed[ \
            #     item_compute \
            #     ]  #[], []
            pred_i = t.sum(t.mul(userEmbed, itemEmbed), dim=1)  #原来是*点乘, 维度相同, 按一行合成一个数: 就是预测的分数

            hit, ndcg = self.calcRes(t.reshape(pred_i, [user.shape[0], 100]), user_item1, user_item100)  #TODO: 是按照自己的想法写的, 非常有可能报错
            epochHR = epochHR + hit  #TODO: 看看这里到底是一个列表, 还是加法
            epochNDCG = epochNDCG + ndcg  #
            # print(f"Step {cnt}:  hit:{hit}, ndcg:{ndcg}")
            cnt += 1 #TODO: 可以看看像这种情况会在哪些地方报错
            tot += user.shape[0]


        result_HR = epochHR / tot
        result_NDCG = epochNDCG / tot
        print(f"Step {cnt}:  hit:{result_HR}, ndcg:{result_NDCG}")

        return result_HR, result_NDCG
    # def validModel(self, data_loader, save=False):
    #     HR, NDCG = [], []
    #     user_embed, item_embed = self.model(self.subgraphs)
    #
    #     for user, item_i in data_loader:
    #         user = user.long().cuda()  #TODO: 问问环测师兄为什么这里要用long()
    #         item_i = item_i.long().cuda()
    #
    #         userEmbed = user_embed[user]
    #         testItemEmbed = item_embed[item_i]
    #         pred_i = t.sum(t.mul(userEmbed, testItemEmbed), dim=1)  #还是所有的embedding一起算
    #
    #         #TODO: 按照每个batch计算 评价指标
    #         batch = int(user.cpu().numpy().size / 101)
    #         assert user.cpu().numpy().size % 101 == 0
    #         for i in range(batch):  # TODO: 问题来了, 为什么哪里都会有一个101??
    #             batch_scores = pred_i[i * 101: (i + 1) * 101].view(-1)
    #             _, indices = t.topk(batch_scores, self.args.top_k)
    #             tmp_item_i = item_i[i * 101: (i + 1) * 101]
    #             recommends = t.take(tmp_item_i, indices).cpu().numpy().tolist()
    #             gt_item = tmp_item_i[0].item()
    #             HR.append(evaluate.hit(gt_item, recommends))
    #             NDCG.append(evaluate.ndcg(gt_item, recommends))
    #
    #     return np.mean(HR), np.mean(NDCG)
    def sampleTestBatch(self, batch_user_id, batch_item_id):
        #TODO:
        # 输入: 一个batch的user_id
        # 需求:
        # (1) [batch, 1]ground_truth------其实外层函数是有的, 直接传进来就好
        # (2) buy邻接矩阵------self.应该是有的, 直接用就好
        # (3) []
        # (4) []
        # [batch, item_num]可以负采样的------从矩阵中取出的一条, 有值为:false, 无值为:true
        # 输出: batch*100个user_id, batch*100个item_id, [batch, 1]ground_truth, [batch, 100]
        batch = len(batch_user_id)
        tmplen = (batch*100)

        sub_trainMat = self.trainMat[batch_user_id].toarray()  #取到sub矩阵
        user_item1 = batch_item_id  #得到每个user对应的正例
        user_compute = [None] * tmplen
        item_compute = [None] * tmplen
        user_item100 = [None] * (batch)

        cur = 0
        for i in range(batch):
            pos_item = user_item1[i]  #得到每个user对应的正例
            negset = np.reshape(np.argwhere(sub_trainMat[i]==0), [-1])  #TODO:得到一个sub矩阵, 并且把负例挑出来------!!!这里按理来说应该得到所有负的id.  不定长,所有负样本的id做成一列
            pvec = self.labelP[negset]  #取到未交互的item的频数 
            pvec = pvec / np.sum(pvec)  #对所有取到的频数做归一化
            # random_neg_sam = np.random.choice(negset, 99, replace=False, p=pvec)  #在negset里面取, 取99个, 以pvec的概率
            
            random_neg_sam = np.random.permutation(negset)[:99]  #随机打乱, 取前99个: 如果报错你的包有问题, 很有可能是你自己的拼写错误
            user_item100_one_user = np.concatenate(( random_neg_sam, np.array([pos_item])))  #TODO: np.concatenate 这个接口一次性完成数组的拼接. 但是, 有一个很神奇的事情让我debug了半天: 第一个参数自己就要加括号
            user_item100[i] = user_item100_one_user

            for j in range(100):
                user_compute[cur] = batch_user_id[i]
                item_compute[cur] = user_item100_one_user[j]
                cur += 1

        return user_compute, item_compute, user_item1, user_item100
# 参数一: batch*100即每个user重复100遍[batch*100] 参数二:随机采样的99个负例item+正例item[batch*100]
# 参数三: 正例对应的item_id[batch]                参数四:随机采样+负例[batch, 100]
    def calcRes(self, pred_i, user_item1, user_item100):  #[6144, 100] [6144] [6144, (ndarray:(100,))]
        #TODO:
        # 1.遍历: 每一个预测结果
        #       (1)
        #       (2)
        #       (3)
        #       (4)

        hit = 0
        ndcg = 0

        # print("等一下: 这个函数你进来了吗?")

        for j in range(pred_i.shape[0]):
            # predvals = list(zip(pred_i[j], user_item100[j]))  #100:100预测分数和id放在一起
            # predvals.sort(key=lambda x:x[0], reverse=True)  #按分数降序排列
            # shoot = list(map(lambda x:x[1], predvals[:args.shoot]))  #取预测分数的前shoot个的item_id

            _, shoot_index = t.topk(pred_i[j], args.shoot) 
            shoot_index = shoot_index.cpu()
            shoot = user_item100[j][shoot_index]
            shoot = shoot.tolist()

            if user_item1[j] in shoot:  #如果正样本的id在前k个

                hit += 1  #HR只要在里面, 就计数: 所以HR的分子是一个正数
                ndcg += np.reciprocal( \
                    np.log2( \
                        shoot.index( \
                            user_item1[j])+2))  #取在里面的位置做分母, 作为惩罚:  所以ndcg的分子是一个分数
        # print(f"hit: {}")

        return hit, ndcg  #int, float

    def setRandomSeed(self):
        np.random.seed(args.seed)
        t.manual_seed(args.seed)
        t.cuda.manual_seed(args.seed)
        random.seed(args.seed)

    def getModelName(self):  #TODO: 再确认一下自己的参数名都是否正确
        title = args.title
        ModelName = \
        args.point + \
        "___" + title + \
        "___" +  args.dataset +\
        "___" + modelTime + \
        "___lr_" + str(args.lr) + \
        "___reg_" + str(args.reg) + \
        "___batch_size_" + str(args.batch) + \
        "___time_slot_" + str(args.time_slot) + \
        "___gnn_layer_" + str(args.gnn_layer)

        return ModelName

    #TODO: 需要的东西主要从history中加载
    def saveHistory(self):  #获得训练到某一个epoch为止的各种loss和指标, 存储到某个文件中
        #要画图的东西存到history里面
        history = dict()
        history['loss'] = self.train_loss  #获得训练到某一个epoch为止的loss
        history['HR'] = self.his_hr
        history['NDCG'] = self.his_ndcg
        ModelName = self.modelName

        with open(r'./History/' + args.dataset + r'/' + ModelName + '.his', 'wb') as fs:  # r代表不转义 TODO: 这个路径还要再确认一下吧, 主要是这个ModelName的问题
            pickle.dump(history, fs)


    def saveModel(self):  #这个模型没什么, 就是保存函数. TODO: 主要思考这里保存什么函数
        ModelName = self.modelName

        history = dict()
        history['loss'] = self.train_loss
        history['HR'] = self.his_hr
        history['NDCG'] = self.his_ndcg
        savePath = r'./Model/' + args.dataset + r'/' + ModelName + r'.pth'
        params = {
            'epoch': self.curEpoch,
            # 'lr': self.lr,
            'model': self.model,
            # 'reg': self.reg,
            'history': history,
        }
        t.save(params, savePath)

    def loadModel(self, loadPath):     #第二个参数: 
        ModelName = self.modelName
        # loadPath = r'./Model/' + args.dataset + r'/' + ModelName + r'.pth'
        loadPath = loadPath
        checkpoint = t.load(loadPath)
        self.model = checkpoint['model']

        self.curEpoch = checkpoint['epoch'] + 1
        # self.lr = checkpoint['lr']
        # self.args.reg = checkpoint['reg']
        # # 恢复history
        history = checkpoint['history']
        self.train_loss = history['loss']
        self.his_hr = history['HR']
        self.his_ndcg = history['NDCG']
        # log("load model %s in epoch %d"%(modelPath, checkpoint['epoch']))



if __name__ == '__main__':
    # TODO:   follow师兄的实验设置, 将模型向和大家一个水平调整
    #      -3.把embedding写成para和, 把embedding写成embedding是不一样
    #      -2.初始化只有一次: 所有weight初始的值只有一次
    #      -1.到底什么是一个模型需要训练的参数:  (1)写进去的weights    (2)embedding算不算???:  embedding到底是自己计算更新, 还是和模型一起会计算
    #      0.查看自己的参数有没有更新:  因为, 现在明显感觉, 自己的loss只是通过embedding计算的. 所以,即感觉参数好像没有更新???
    #      1.将run的过程, 调整成和师兄一样
    #      2.确定, 自己的dataloader到底要如何组织
    #      3.理解大图采样
    #      4.理解batch采样
    #      5.理解测评----最终目的, 知道模型要向着一个好的结果, 中间的计算过程是什么, 会有哪些影响因素
    #      6.查看师兄的代码, 看看有哪些巧妙的做法, 能够使得模型结果上升.
    print(args)
    my_model = Model()  #为了偷懒想将整个训练的模型移到GPU上, 看看这样做会不会报错: 不是pytorch框架里的东西是没有cuda()的
    my_model.run()
    # my_model.test()

 

 

おすすめ

転載: blog.csdn.net/weiwei935707936/article/details/113774515