推荐系统代码阅读:ICDE 2023 IMSR 基于增量学习的多兴趣序列推荐

代码:https://github.com/Scofield666/MBSSLhttps://github.com/Cloudcatcher888/IMSRhttps://github.com/Scofield666/MBSSL 

论文:https://cloudcatcher888.github.io/files/icde.pdf
模型图:

作者是在17号的时候上传了代码,但是代码还是有一些不全的地方。 我将从他放出来的两个文件,model和IMSR里进行我理解的代码阅读。

Model

class BaseCapNet(nn.Module):
    def __init__(self,K,dynamic):
        super(BaseCapNet, self).__init__()
        self.K = K
        self.item_emb = nn.Embedding(item_num+1, hiddim, padding_idx=0)
        self.user_emb = nn.Embedding(user_num + 1, hiddim, padding_idx=0)
        self.create_mask = {}
        self.trans = nn.Linear(hiddim * 2, hiddim)
        self.dnn = nn.Sequential(
            nn.Linear(hiddim*2,hiddim),
            nn.ReLU(),
            nn.Linear(hiddim,1),
            nn.Sigmoid()
        )

 首先这段是定义了胶囊网络,包括嵌入层、线性变换层和神经网络:

  1. self.item_emb = nn.Embedding(item_num+1, hiddim, padding_idx=0):\ user 创建嵌入层,用于将项目和用户的ID映射到嵌入向量,hiddim 表示嵌入向量的维度,padding_idx=0 表示用于填充的项目ID索引。

  2. self.trans = nn.Linear(hiddim * 2, hiddim):创建一个线性变换层,用于将输入特征的维度从 hiddim * 2 降低到 hiddim。用于胶囊网络中的的信息传递中使用。

  3. self.dnn = nn.Sequential(...):创建一个包含多个线性层和非线性激活函数的神经网络。这个神经网络将用于生成损失函数的一部分。

前向传播过程:

        if memflag:
            hist = torch.cat([hist, tgt], dim=1)
            hist_mask = torch.cat([hist_mask, tgt_mask], dim=1)
        else:
            neg = []
            for i in range(user_id.shape[0]):
                neg_list = random.choices(neg_set[user_id[i]], k=int(nsr * tgtlen[i]))
                neg.append(torch.nn.functional.pad(torch.tensor(neg_list, dtype=torch.int64), (0, tgt.shape[1] - tgtlen[i]), 'constant', 0))
            neg = torch.stack(neg).to(device)
            neg_mask = (neg != 0)

如果在增量学习阶段,需要对过去兴趣序列和当前兴趣序列沿着时间步骤的维度进行拼接,包括嵌入序列和mask序列。

如果在测试阶段,需要处理负样本:

  • 首先循环遍历用户的数量,对每个用户生成负样本。

  • neg_list = random.choices(neg_set[user_id[i]], k=int(nsr * tgtlen[i])):对于每个用户,从负样本集合 neg_set 中随机选择一定数量的负样本,数量由 nsr * tgtlen[i] 决定,其中 nsr 是负样本采样率,tgtlen[i] 是目标序列的长度。

  • neg.append(torch.nn.functional.pad(torch.tensor(neg_list, dtype=torch.int64), (0, tgt.shape[1] - tgtlen[i]), 'constant', 0)):将生成的负样本列表 neg_list 转换为 PyTorch 张量,并使用 pad 函数在序列末尾添加零填充,以使所有负样本序列的长度与目标序列 tgt 的长度相等。

  • neg_mask = (neg != 0):根据负样本序列 neg 创建一个掩码张量 neg_mask,其中非零元素表示有效项目,零元素表示填充项目。

bk = max(Ks)  #max K in batch
        capsules = []
        cap_mask = []
        old_cap_mask = []
        for i in range(user_id.shape[0]):
            capsules.append(torch.stack(caps[i] + [torch.zeros([hiddim], dtype=torch.float32).to(device)] * (mbk - Ks[i])))
            cap_mask.append([True]*Ks[i]+[False]*(mbk-Ks[i]))
            old_cap_mask.append([True]*oldKs[i]+[False]*(mbk-oldKs[i]))
        capsules = torch.stack(capsules).to(device).unsqueeze(3)
        cap_mask = torch.tensor(cap_mask).to(device).unsqueeze(2)
        old_cap_mask = torch.tensor(old_cap_mask).to(device).unsqueeze(2)
        

        hist_emb = self.item_emb(hist)
        tgt_emb = self.item_emb(tgt)
        if not memflag:
            neg_emb = self.item_emb(neg)
        user_emb = self.user_emb(user_id)
        #tgt_emb:batchsize*maxlen*hiddim
        # hist_hat = torch.stack([trans(torch.cat([hist_emb,user_emb.repeat([1,hist_emb.shape[1],1])],dim=2)) for trans in self.transes[:mbk]]).permute([1,0,2,3])
        hist_hat = self.trans(torch.cat([hist_emb,user_emb.repeat([1,hist_emb.shape[1],1])],dim=2)).unsqueeze(1).repeat([1,mbk,1,1])
        # hist_hat = self.trans(hist_emb).unsqueeze(1).repeat([1,mbk,1,1])
        # hist_hat = torch.stack([trans(hist_emb) for trans in self.transes[:mbk]]).permute([1,0,2,3])
        hist_hat_iter = hist_hat.detach()        
        #hist_hat: batchsize*K*maxlen*hiddim
        #capsules: batchsize*K*hiddim*1
        
        a = torch.matmul(hist_hat_iter, capsules).squeeze()

计算注意力分数 a

  1. mbk = max(Ks) #max K in batch:确定批次中最大的 K 值,即 Ks 中的最大值。这是为了确定要处理的 Capsule 数量。

  2. 循环遍历批次中的每个用户。

  3. capsules.append(torch.stack(caps[i] + [torch.zeros([hiddim], dtype=torch.float32).to(device)] * (mbk - Ks[i]))):对于每个用户,将其对应的 Capsule 列表 caps[i] 扩展到最大 K 值 mbk,如果 Ks[i] 不足 mbk,则用零张量进行填充。

  4. cap_mask.append([True]*Ks[i]+[False]*(mbk-Ks[i])):为每个用户的 Capsule 创建一个掩码,其中前 Ks[i] 个元素为 True,表示有效项目,后面的 (mbk - Ks[i]) 个元素为 False

  5. capsules = torch.stack(capsules).to(device).unsqueeze(3):将所有用户的 Capsule 堆叠成一个张量,然后,使用 unsqueeze 在维度 3 上增加一个维度。

  6. cap_mask = torch.tensor(cap_mask).to(device).unsqueeze(2):将 Capsule 的掩码列表 cap_mask 转换为张量,并将其移动到指定的设备。同样,使用 unsqueeze 在维度 2 上增加一个维度。

  7. hist_emb\tgt_emb = self.item_emb(hist):根据输入的历史序列 hist,通过嵌入层 item_emb \tgt_emb获取历史序列的嵌入表示。

  8. neg_emb = self.item_emb(neg):对负样本序列 neg 进行嵌入,获取负样本的嵌入表示。

  9. user_emb = self.user_emb(user_id):根据用户 ID user_id,通过嵌入层 user_emb 获取用户的嵌入表示。

  10. hist_hat = self.trans(torch.cat([hist_emb,user_emb.repeat([1,hist_emb.shape[1],1])],dim=2)).unsqueeze(1).repeat([1,mbk,1,1]):计算 hist_emb 和用户嵌入的拼接,并通过线性变换 trans 得到 hist_hat。。

  11. a = torch.matmul(hist_hat_iter, capsules).squeeze():计算 hist_hat_itercapsules 的点积(矩阵乘法),并使用 squeeze() 函数去除多余的维度,得到注意力分数 a。此时,a 的维度为 (batch_size, mbk, maxlen),表示每个用户对每个 Capsule 的注意力分数

   for iter in range(3):
            b = torch.softmax(a.masked_fill_(~cap_mask, -float('inf')), dim=1)  #force one item into one interest but not one interest is determined by just one or a few items
            #b: batchsize*K*maxlen
            if iter<2:                
                c = torch.matmul(b.unsqueeze(2), hist_hat_iter)#zero item has zero embedding and is no contribute to c, so no padding needed
                #c:batchsize*K*1*hiddim
                if proj:
                    temp1 = torch.matmul(c.squeeze(), c.squeeze().permute([0,2,1]))
                    temp2 = torch.inverse(temp1 + 4e-3*torch.eye(mbk).to(device))
                    caps_proj = torch.matmul(torch.matmul(c.squeeze().permute([0,2,1]),temp2),c.squeeze())
                    c = c - torch.matmul(c.squeeze(),caps_proj).unsqueeze(2)*(~old_cap_mask).unsqueeze(3)
                capsules = (torch.norm(c, dim=3,keepdim=True) / (torch.norm(c, dim=3,keepdim=True)** 2 + 1) * c).permute([0, 1, 3, 2])
                #capsules: batchsize*K*hiddim*1
                
                
                a = torch.matmul(hist_hat_iter, capsules).squeeze() + a
                
            else:
                c = torch.matmul(b.unsqueeze(2), hist_hat)#
                if proj:
                    temp1 = torch.matmul(c.squeeze(), c.squeeze().permute([0,2,1]))
                    temp2 = torch.inverse(temp1 + 2e-2*torch.eye(mbk).to(device))
                    caps_proj = torch.matmul(torch.matmul(c.squeeze().permute([0,2,1]),temp2),c.squeeze())
                    c = c - torch.matmul(c.squeeze(),caps_proj).unsqueeze(2)*(~old_cap_mask).unsqueeze(3)
                
                capsules = (torch.norm(c, dim=3,keepdim=True) / (torch.norm(c, dim=3,keepdim=True)** 2 + 1) * c).permute([0, 1, 3, 2])

  1. for iter in range(3)::这是一个循环,迭代3次,每次迭代都会更新 Capsule 的表示。

  2. b = torch.softmax(a.masked_fill_(~cap_mask, -float('inf')), dim=1):首先,计算注意力分数 a 的 softmax,其中 ~cap_mask-float('inf') 进行填充,以使不可用的 Capsule 在 softmax 中的概率为零。这个操作的目的是将注意力分布限制在有效的 Capsule 上。

  3. c = torch.matmul(b.unsqueeze(2), hist_hat_iter):计算加权的历史信息,这是通过将注意力分数 bhist_hat_iter 进行矩阵相乘实现的。这将为每个用户的每个 Capsule 计算一个加权历史向量。

  4. temp1 = torch.matmul(c.squeeze(), c.squeeze().permute([0,2,1])):计算矩阵 c 与其转置的乘积,其中 c.squeeze() 用于去除 c 的不必要维度。这将用于计算投影矩阵。

  5. temp2 = torch.inverse(temp1 + 4e-3*torch.eye(mbk).to(device)):计算矩阵 temp1 的逆矩阵,其中 4e-3*torch.eye(mbk).to(device) 添加了一个小的对角矩阵,以防止矩阵不可逆。这个操作将用于计算投影矩阵的逆。

  6. caps_proj = torch.matmul(torch.matmul(c.squeeze().permute([0,2,1]),temp2),c.squeeze()):计算投影矩阵 caps_proj,这是通过将 temp2c 的转置相乘得到的。这个投影矩阵将用于投影 Capsule。

  7. c = c - torch.matmul(c.squeeze(),caps_proj).unsqueeze(2)*(~old_cap_mask).unsqueeze(3):将原始的加权历史向量 c 投影到新的向量空间中。这个操作将使用投影矩阵 caps_proj 来减去原始向量的投影,并且只在旧的 Capsule 上执行此操作,因为乘以 (~old_cap_mask).unsqueeze(3) 会将投影应用于旧的 Capsule。这将更新 Capsule 的表示。

  8. capsules = (torch.norm(c, dim=3,keepdim=True) / (torch.norm(c, dim=3,keepdim=True)** 2 + 1) * c).permute([0, 1, 3, 2]):对更新后的向量 c 进行归一化操作,然后将其重新排列,以便将维度从 (batch_size, mbk, 1, hiddim) 转换为 (batch_size, mbk, hiddim, 1),以适应后续计算。

  9. a = torch.matmul(hist_hat_iter, capsules).squeeze() + a:计算更新后的 Capsule 表示与历史信息的点积,然后将其与之前的注意力分数 a 相加。这将用于下一轮迭代的注意力计算。

        if memflag:
            # created_mask = (torch.max(b, dim=1).values > create_trd) * torch.cat([hist_mask, tgt_mask], dim=1)
            #calculate KL
            create_score = (torch.log(torch.sum(b, dim=1)) - torch.mean(torch.log(b), dim=1)).masked_fill_(~hist_mask,float('inf'))
            create = torch.sum(create_score<create_trd,dim=1)>create_trd2
            # create = torch.sum(torch.max(b, dim=1).values < create_trd, dim=1) > create_trd2+b.shape[2]-histlen-tgtlen
            return capsules.squeeze()*cap_mask, create
        attn = torch.softmax(torch.matmul(tgt_emb.unsqueeze(1), capsules).squeeze().masked_fill_(~cap_mask, -float('inf')),dim=1).permute([0,2,1])
        #attn:batchsize*maxlen*K
        attn_capsules = torch.matmul(attn, capsules.squeeze())
        #attn_capsules:batchsize*maxlen*hiddim
        loss_pos = torch.log(torch.sigmoid(torch.sum(tgt_emb*attn_capsules,dim=2)))*tgt_mask
        loss_neg = torch.log(1-torch.sigmoid(torch.sum(neg_emb*attn_capsules,dim=2)))*neg_mask

        #dnn
        # loss_pos = torch.log(self.dnn(torch.cat([tgt_emb,user_emb.repeat([1,tgt_emb.shape[1],1])],dim=2))).squeeze()*tgt_mask
        # loss_neg = torch.log(1-self.dnn(torch.cat([neg_emb,user_emb.repeat([1,neg_emb.shape[1],1])],dim=2))).squeeze()*neg_mask
        return loss_pos,loss_neg

前向传播函数,用于计算损失和其他输出。

  1. create_score = (torch.log(torch.sum(b, dim=1)) - torch.mean(torch.log(b), dim=1)).masked_fill_(~hist_mask, float('inf')):计算创建分数,它衡量了用户的历史行为是否足够有信息量以创建新的 Capsule。具体来说,这个分数计算了注意力分布 b 在每个用户的历史序列上的熵。如果历史信息足够丰富,熵会较低,分数会较高。~hist_maskfloat('inf') 进行填充,以排除无效的历史数据。

  2. create = torch.sum(create_score < create_trd, dim=1) > create_trd2:根据创建分数和预定义的阈值 create_trdcreate_trd2,确定是否创建新的 Capsule。如果用户的创建分数低于 create_trd 且超过 create_trd2 个 Capsule,则创建新的 Capsule。create 是一个布尔值的张量,每个元素对应一个用户是否需要创建新的 Capsule。

  3. attn = torch.softmax(torch.matmul(tgt_emb.unsqueeze(1), capsules).squeeze().masked_fill_(~cap_mask, -float('inf')), dim=1).permute([0, 2, 1]):计算目标项目(tgt_emb)与 Capsules 之间的注意力分布。首先,将目标项目与 Capsules 进行点积计算,然后应用 softmax 操作以获得注意力分布。masked_fill_ 操作用于将无效的 Capsule 掩盖,以确保不会计算到无效的 Capsule 上。

  4. attn_capsules = torch.matmul(attn, capsules.squeeze()):将注意力分布 attn 与 Capsules 相乘,得到加权的 Capsule 表示 attn_capsules。这将用于计算正样本和负样本的损失。

  5. loss_pos = torch.log(torch.sigmoid(torch.sum(tgt_emb * attn_capsules, dim=2))) * tgt_mask:计算正样本的损失,即目标项目与加权的 Capsule 表示之间的点积,然后应用 sigmoid 激活函数并取对数。tgt_mask 用于排除填充项的贡献。

  6. loss_neg = torch.log(1 - torch.sigmoid(torch.sum(neg_emb * attn_capsules, dim=2))) * neg_mask:计算负样本的损失,类似于正样本的计算,但对应于负样本的表示。neg_mask 用于排除填充项的贡献。

  7. 返回正样本损失 loss_pos 和负样本损失 loss_neg

IMSR 

with open(item_cate_file,'r') as item_cate_f, open(train_file,'r') as train_f:
    for line in item_cate_f.readlines():
        cate = int(line.split(',')[1])
        cate_num = max(cate_num, cate)
        item_cate[int(line.split(',')[0])] = cate
    # item_cate[0] = cate_num + 1
    cate_num += 1
    # item 0 is not an item, cate[last] is the unknown cate
    print('cate_num',cate_num)
    for line in train_f.readlines():
        user_id, item_id, timestamp = int(line.split(',')[0]), int(line.split(',')[1]), int(line.split(',')[2])
        min_timestamp = min(timestamp, min_timestamp)
        max_timestamp = max(timestamp, max_timestamp)
        if int(user_id/merge_num) != current_user_group:
            current_user_group += 1
            trainset.append([])
        try:
            trainset[current_user_group].append([item_id, item_cate[item_id], timestamp])
        except:
            item_cate[item_id] = cate_num
            trainset[current_user_group].append([item_id, cate_num, timestamp])

user_num = len(trainset)
item_num = max(item_cate)
print(f'user number is {user_num}')
print(f'item number is {item_num}')
for i in range(item_num):
    if i not in item_cate:
        item_cate[i] = cate_num
# print([len(trainset[i]) for i in range(len(trainset))])

for user in trainset:
    user.sort(key=lambda x: x[2])
# print(trainset[0])
neg_set = []
for user in trainset:
    neg_set.append(random.choices(list(set(range(1, item_num + 1)) - set([item[0] for item in user])),k=1000))


#task split

timespan = (max_timestamp - min_timestamp) * (1 - base_task_ratio) / task_num
incre_time_start = min_timestamp + base_task_ratio * (max_timestamp - min_timestamp)
timezone = [incre_time_start+i*timespan for i in range(task_num+1)]

trainset_split = []
for user in trainset:
    user_split = [[] for _ in range(task_num+1)]
    idx = 0
    for item in user:
        if item[2] > timezone[idx]:
            idx += 1
        user_split[idx].append(item)
    trainset_split.append(user_split)

maxbaselen = 0
maxtasklen = 0
for i, user in enumerate(trainset_split):
    if i < 0:        
        print(i, [len(s) for s in user])
        print(user)
    maxbaselen = max(maxbaselen, len(user[0]))
    maxtasklen = max(maxtasklen, max([len(s) for s in user[1:]]))
print('maxbaselen', maxbaselen)
print('maxtasklen', maxtasklen)

 数据预处理,包括对负样本的选择以及划分用户交互的时间,根据用户交互的时间戳将其分开

#memory
memory_pool = [[torch.randn([hiddim], dtype=torch.float32).to(device).detach() for _ in range(K)] for _ in range(user_num)]
memory_len = [len(user) for user in memory_pool]
old_memoryLens = [len(user) for user in memory_pool]

用户的多种兴趣(记忆池)。每个用户都有一个记忆池,记忆池的大小为 K,表示每个用户可以存储 K 个项目的记忆。

  • memory_pool 是一个二维列表,外层列表的长度为 user_num,表示用户的数量,内层列表的长度为 K,表示每个用户的内存池大小。

  • 初始化时,使用 torch.randn 生成了一个形状为 [hiddim](的随机张量,然后将其转移到 device 上,并使用 detach() 方法将其从计算图中分离,最终得到一个张量的列表,表示每个用户的记忆池。

  • 同时,还创建了两个列表 memory_lenold_memoryLens,它们分别用于存储每个用户记忆池的当前长度和旧长度。

pretraining
basecapnet = BaseCapNet(K, False).to(device)
opt = torch.optim.Adam(
    basecapnet.parameters(),
    weight_decay = 10e-6,
    lr=10e-3
)

basedataset = Traintask(trainset_split, 0)
baseloader = DataLoader(basedataset, shuffle = True, batch_size = batchsize)


for idx in range(1):
    basecapnet.train()
    
    for epoch in range(20):
        losses = []
        for i, (userid, histseq, tgtseq, histlen, tgtlen, histseq_mask, tgtseq_mask) in enumerate(baseloader):
            Ks = [memory_len[i] for i in userid.flatten().numpy().tolist()]
            capsules = [memory_pool[i] for i in userid.flatten().numpy().tolist()]
            pos_loss, neg_loss = basecapnet(
                    user_id=userid.to(device),
                    hist=histseq.to(device),
                    tgt=tgtseq.to(device),
                    histlen=histlen.to(device),
                    tgtlen=tgtlen.to(device),
                    caps=capsules,
                    Ks=Ks,
                    oldKs = Ks,
                    proj=False,
                    hist_mask=histseq_mask.to(device),
                    tgt_mask=tgtseq_mask.to(device),
                    memflag=False
            )
            loss = -(torch.sum(pos_loss) + torch.sum(neg_loss))/(1+nsr)/(torch.sum(tgtlen))
            opt.zero_grad()
            loss.backward()
            opt.step()
            # if i % 1 == 0:
            #     print(loss.item())
            losses.append(loss.item())
            # break
        print('loss', sum(losses) / len(losses))
    
    with torch.no_grad():
        basecapnet.eval()
        if memory ==True:
            #memory
            memory_loader = DataLoader(Traintask(trainset_split,0),shuffle=False,batch_size=batchsize)
            for i, (userid,histseq,tgtseq,histlen,tgtlen,histseq_mask,tgtseq_mask) in enumerate(memory_loader):
                Ks = [memory_len[i] for i in userid.flatten().numpy().tolist()]
                capsules = [memory_pool[i] for i in userid.flatten().numpy().tolist()]
                caps,create = basecapnet(
                            user_id=userid.to(device),
                            hist=histseq.to(device),
                            tgt=tgtseq.to(device),
                            histlen=histlen.to(device),
                            tgtlen=tgtlen.to(device),
                            caps=capsules,
                            Ks=Ks,
                            oldKs = Ks,
                            proj=False,
                            hist_mask=histseq_mask.to(device),
                            tgt_mask=tgtseq_mask.to(device),
                            memflag=True
                )
                # print(create)
                for j,user in enumerate(userid):
                    user = user.item()
                    for K_id in range(Ks[j]):
                        memory_pool[user][K_id] = caps[j, K_id].detach()

  1. BaseCapNet 模型和优化器(Adam):

    • 创建了一个 BaseCapNet 模型,并将其移到计算设备(如 GPU)上。
    • 创建了一个 Adam 优化器,用于更新模型参数。
  2. 创建数据集和数据加载器:

    • 使用 Traintask 数据集类创建了数据集 basedataset
    • 创建了一个数据加载器 baseloader,用于按批次加载数据,并打乱数据顺序。
  3. 开始训练循环:

      模型进行了预训练。它迭代了20个周期(epochs),并在每个周期内遍历了训练数据。对于每个批次,模型计算正负损失,并根据这些损失来更新模型的参数。这一过程用于初始化模型的记忆池
  4. 预训练后的操作:

    • 使用 torch.no_grad() 块,禁用梯度计算,以进行后续操作。
    • 如果 memoryTrue,则进行记忆池的更新操作:
      • 创建一个数据加载器 memory_loader,用于加载数据以更新记忆池。
      • 使用模型 basecapnet 计算新的记忆内容(caps)和是否创建了新的记忆(create)。
      • 更新每个用户的记忆池。
  5. 进行模型的测试和评估:

    • 创建一个数据加载器 testdataloader,用于加载测试数据。
    • 使用模型 basecapnet 进行测试,计算正向和负向损失。
    • 计算总的测试损失和测试数据的总长度。
    • 打印测试损失。

增量学习

新兴趣检测

# 在每个新任务开始之前,使用 basecapnet 模型进行推断以确定是否需要创建新的记忆项。

# 首先加载新任务的数据
memory_loader = DataLoader(Traintask(trainset_split, idx), shuffle=False, batch_size=batchsize)

for i, (userid, histseq, tgtseq, histlen, tgtlen, histseq_mask, tgtseq_mask) in enumerate(memory_loader):
    # 获取用户的记忆池大小(Ks)和旧的记忆池大小(old_Ks)
    Ks = [memory_len[i] for i in userid.flatten().numpy().tolist()]
    old_Ks = [old_memoryLens[i] for i in userid.flatten().numpy().tolist()]
    
    # 使用 basecapnet 模型进行推断,确定是否需要创建新的记忆
    capsules, create = basecapnet(
        user_id=userid.to(device),
        hist=histseq.to(device),
        tgt=tgtseq.to(device),
        histlen=histlen.to(device),
        tgtlen=tgtlen.to(device),
        caps=capsules,  # 使用当前的记忆池
        Ks=Ks,
        oldKs=Ks,
        proj=False,
        hist_mask=histseq_mask.to(device),
        tgt_mask=tgtseq_mask.to(device),
        memflag=True
    )
    
    # 如果模型认为需要创建新的记忆,则执行以下操作
    for j, user in enumerate(userid):
        user = user.item()
        old_memoryLens[user] = memory_len[user]
        if create[j].item():
            for iter in range(delta_K):
                memory_pool[user].append(torch.randn([hiddim], dtype=torch.float32).to(device).detach())
                memory_len[user] += 1

在每个新任务开始之前,首先使用 basecapnet 模型对数据进行推断,以确定是否需要创建新的记忆:

  • 加载新任务的数据,包括用户的历史兴趣和目标兴趣。
  • 使用 basecapnet 模型进行推断。这包括将用户的历史兴趣和目标兴趣传递给模型,并获取模型的输出。
  • 分析模型的输出,检查模型的预测或置信度分数,以确定是否需要创建新的记忆。

如果模型认为需要创建新的记忆,就会执行以下操作:

  • 根据任务要求,创建新的兴趣。
  • 将新的兴趣添加到用户的记忆池(memory_pool)中,并增加相应的记忆池长度(memory_len)。
# 一旦确定需要创建新的记忆并更新了记忆池,就可以开始在新任务上进行增量训练。
# taskloader 包含了新任务的数据。

taskloader = DataLoader(Traintask(trainset_split, idx), shuffle=True, batch_size=batchsize)

for epoch in range(10):
    losses = []
    for i, (userid, histseq, tgtseq, histlen, tgtlen, histseq_mask, tgtseq_mask) in enumerate(taskloader):
        # 获取用户的记忆池大小(Ks)和旧的记忆池大小(old_Ks)
        Ks = [memory_len[i] for i in userid.flatten().numpy().tolist()]
        old_Ks = [old_memoryLens[i] for i in userid.flatten().numpy().tolist()]
        
        # 使用 basecapnet 模型进行前向传播,计算正向和负向损失
        pos_loss, neg_loss = basecapnet(
            user_id=userid.to(device),
            hist=histseq.to(device),
            tgt=tgtseq.to(device),
            histlen=histlen.to(device),
            tgtlen=tgtlen.to(device),
            caps=capsules,  # 使用当前的记忆池
            Ks=Ks,
            oldKs=old_Ks,
            proj=proj_btn,
            hist_mask=histseq_mask.to(device),
            tgt_mask=tgtseq_mask.to(device),
            memflag=False
        )
        
        # 计算总损失,执行反向传播和梯度下降
        loss = -(torch.sum(pos_loss) + torch.sum(neg_loss)) / (1 + nsr) / (torch.sum(tgtlen))
        opt.zero_grad()
        loss.backward()
        opt.step()
        
        losses.append(loss.item())

# 微调过程重复多次,以适应新任务的数据。
  1. 一旦确定需要创建新的兴趣并更新了记忆池,就可以开始在新任务上进行增量训练。这个训练阶段包括以下操作:

    • 加载新任务的数据,包括用户的历史行为和目标行为。
    • 准备数据:将数据转换为模型可以处理的张量,并根据需要进行填充(padding)。
    • 获取用户的记忆池大小(Ks)和旧的记忆池大小(old_Ks)。
    • 使用 basecapnet 模型进行前向传播,计算正向和负向损失(pos_lossneg_loss)。
    • 计算总损失,通常是正向和负向损失的加权和。
    • 执行反向传播和梯度下降,以更新模型的参数,以便适应新任务的数据。

通过这种方式,增量学习允许模型不断地适应新的任务和数据,而无需重新训练整个模型。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值