代码: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()
)
首先这段是定义了胶囊网络,包括嵌入层、线性变换层和神经网络:
-
self.item_emb = nn.Embedding(item_num+1, hiddim, padding_idx=0)
:\ user 创建嵌入层,用于将项目和用户的ID映射到嵌入向量,hiddim
表示嵌入向量的维度,padding_idx=0
表示用于填充的项目ID索引。 -
self.trans = nn.Linear(hiddim * 2, hiddim)
:创建一个线性变换层,用于将输入特征的维度从hiddim * 2
降低到hiddim
。用于胶囊网络中的的信息传递中使用。 -
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
-
mbk = max(Ks) #max K in batch
:确定批次中最大的 K 值,即Ks
中的最大值。这是为了确定要处理的 Capsule 数量。 -
循环遍历批次中的每个用户。
-
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
,则用零张量进行填充。 -
cap_mask.append([True]*Ks[i]+[False]*(mbk-Ks[i]))
:为每个用户的 Capsule 创建一个掩码,其中前Ks[i]
个元素为True
,表示有效项目,后面的(mbk - Ks[i])
个元素为False
, -
capsules = torch.stack(capsules).to(device).unsqueeze(3)
:将所有用户的 Capsule 堆叠成一个张量,然后,使用unsqueeze
在维度 3 上增加一个维度。 -
cap_mask = torch.tensor(cap_mask).to(device).unsqueeze(2)
:将 Capsule 的掩码列表cap_mask
转换为张量,并将其移动到指定的设备。同样,使用unsqueeze
在维度 2 上增加一个维度。 -
hist_emb\tgt_emb = self.item_emb(hist)
:根据输入的历史序列hist
,通过嵌入层item_emb
\tgt_emb
获取历史序列的嵌入表示。 -
neg_emb = self.item_emb(neg)
:对负样本序列neg
进行嵌入,获取负样本的嵌入表示。 -
user_emb = self.user_emb(user_id)
:根据用户 IDuser_id
,通过嵌入层user_emb
获取用户的嵌入表示。 -
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
。。 -
a = torch.matmul(hist_hat_iter, capsules).squeeze()
:计算hist_hat_iter
和capsules
的点积(矩阵乘法),并使用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])
-
for iter in range(3):
:这是一个循环,迭代3次,每次迭代都会更新 Capsule 的表示。 -
b = torch.softmax(a.masked_fill_(~cap_mask, -float('inf')), dim=1)
:首先,计算注意力分数a
的 softmax,其中~cap_mask
用-float('inf')
进行填充,以使不可用的 Capsule 在 softmax 中的概率为零。这个操作的目的是将注意力分布限制在有效的 Capsule 上。 -
c = torch.matmul(b.unsqueeze(2), hist_hat_iter)
:计算加权的历史信息,这是通过将注意力分数b
与hist_hat_iter
进行矩阵相乘实现的。这将为每个用户的每个 Capsule 计算一个加权历史向量。 -
temp1 = torch.matmul(c.squeeze(), c.squeeze().permute([0,2,1]))
:计算矩阵c
与其转置的乘积,其中c.squeeze()
用于去除c
的不必要维度。这将用于计算投影矩阵。 -
temp2 = torch.inverse(temp1 + 4e-3*torch.eye(mbk).to(device))
:计算矩阵temp1
的逆矩阵,其中4e-3*torch.eye(mbk).to(device)
添加了一个小的对角矩阵,以防止矩阵不可逆。这个操作将用于计算投影矩阵的逆。 -
caps_proj = torch.matmul(torch.matmul(c.squeeze().permute([0,2,1]),temp2),c.squeeze())
:计算投影矩阵caps_proj
,这是通过将temp2
与c
的转置相乘得到的。这个投影矩阵将用于投影 Capsule。 -
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 的表示。 -
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)
,以适应后续计算。 -
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
前向传播函数,用于计算损失和其他输出。
-
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_mask
用float('inf')
进行填充,以排除无效的历史数据。 -
create = torch.sum(create_score < create_trd, dim=1) > create_trd2
:根据创建分数和预定义的阈值create_trd
和create_trd2
,确定是否创建新的 Capsule。如果用户的创建分数低于create_trd
且超过create_trd2
个 Capsule,则创建新的 Capsule。create
是一个布尔值的张量,每个元素对应一个用户是否需要创建新的 Capsule。 -
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 上。 -
attn_capsules = torch.matmul(attn, capsules.squeeze())
:将注意力分布attn
与 Capsules 相乘,得到加权的 Capsule 表示attn_capsules
。这将用于计算正样本和负样本的损失。 -
loss_pos = torch.log(torch.sigmoid(torch.sum(tgt_emb * attn_capsules, dim=2))) * tgt_mask
:计算正样本的损失,即目标项目与加权的 Capsule 表示之间的点积,然后应用 sigmoid 激活函数并取对数。tgt_mask
用于排除填充项的贡献。 -
loss_neg = torch.log(1 - torch.sigmoid(torch.sum(neg_emb * attn_capsules, dim=2))) * neg_mask
:计算负样本的损失,类似于正样本的计算,但对应于负样本的表示。neg_mask
用于排除填充项的贡献。 -
返回正样本损失
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_len
和old_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()
-
BaseCapNet
模型和优化器(Adam):- 创建了一个
BaseCapNet
模型,并将其移到计算设备(如 GPU)上。 - 创建了一个 Adam 优化器,用于更新模型参数。
- 创建了一个
-
创建数据集和数据加载器:
- 使用
Traintask
数据集类创建了数据集basedataset
。 - 创建了一个数据加载器
baseloader
,用于按批次加载数据,并打乱数据顺序。
- 使用
-
开始训练循环:
模型进行了预训练。它迭代了20个周期(epochs),并在每个周期内遍历了训练数据。对于每个批次,模型计算正负损失,并根据这些损失来更新模型的参数。这一过程用于初始化模型的记忆池 -
预训练后的操作:
- 使用
torch.no_grad()
块,禁用梯度计算,以进行后续操作。 - 如果
memory
为True
,则进行记忆池的更新操作:- 创建一个数据加载器
memory_loader
,用于加载数据以更新记忆池。 - 使用模型
basecapnet
计算新的记忆内容(caps
)和是否创建了新的记忆(create
)。 - 更新每个用户的记忆池。
- 创建一个数据加载器
- 使用
-
进行模型的测试和评估:
- 创建一个数据加载器
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())
# 微调过程重复多次,以适应新任务的数据。
-
一旦确定需要创建新的兴趣并更新了记忆池,就可以开始在新任务上进行增量训练。这个训练阶段包括以下操作:
- 加载新任务的数据,包括用户的历史行为和目标行为。
- 准备数据:将数据转换为模型可以处理的张量,并根据需要进行填充(padding)。
- 获取用户的记忆池大小(
Ks
)和旧的记忆池大小(old_Ks
)。 - 使用
basecapnet
模型进行前向传播,计算正向和负向损失(pos_loss
和neg_loss
)。 - 计算总损失,通常是正向和负向损失的加权和。
- 执行反向传播和梯度下降,以更新模型的参数,以便适应新任务的数据。
通过这种方式,增量学习允许模型不断地适应新的任务和数据,而无需重新训练整个模型。