一、使用最后m个item提取短期兴趣
对于短期兴趣的提取来说,我们可以使用最后m个或target item来获取
# 下面是取序列最后面的m个item
# item_seq_len是序列长度,batchsize*1
# sequence_last_m是我们定义m值
# item_seq是整个序列交互过的item,batchsize*max_seq_len
# target_item用于表明当前是否为训练阶段
# 获得m长的子序列的结束item的位置(下标+1)
sequence_last_m_interval_end = torch.squeeze(item_seq_len)
# 获得m长的子序列的开始item的位置(下标+1)
sequence_last_m_interval_start = sequence_last_m_interval_end - self.sequence_last_m
# 下面数组用于存储长度为m的子序列,batchsize*m
sequence_last_m_indexes = []
# 对batchsize里面的每个序列进行遍历
for i in range(item_seq_len.shape[0]):
# 若获得的子序列开始位置的值大于0,证明当前序列长度大于等于m
if sequence_last_m_interval_start[i] >= 0:
# 获得当前i序列的子序列
item_index = item_seq[i, sequence_last_m_interval_start[i]:sequence_last_m_interval_end[i]]
sequence_last_m_indexes.append(item_index)
# 若获得的子序列开始位置的值小于0,证明当前序列长度小于m
else:
# 我们需要将整个序列作为子序列
item_index = item_seq[i, 0:sequence_last_m_interval_end[i]]
# 长度不足的话在序列开始位置用0补齐,使子序列长度为m
item_index = torch.nn.ZeroPad2d(padding=(self.sequence_last_m - sequence_last_m_interval_end[i], 0))(item_index)
sequence_last_m_indexes.append(item_index)
# 将序列堆叠成tensor
sequence_last_m_indexes = torch.stack(sequence_last_m_indexes, dim=0)
# 将子序列padding成max_seq_len,batchsize*max_seq_len
user_short_items_indexes = torch.nn.ZeroPad2d(padding=(0, self.max_seq_length - self.sequence_last_m, 0, 0))(
sequence_last_m_indexes)
# 若为训练阶段则使用最后的target item与子序列组成m+1的序列
if target_item is not None:
user_short_items_indexes[:, self.sequence_last_m] = torch.squeeze(target_item)
# 获得子序列的embedding
user_short_items_emb_ori = self.item_embedding(user_short_items_indexes)
# calculate the mask
mask = torch.ones(
user_short_items_indexes.shape, dtype=torch.float,
device=item_seq.device) * user_short_items_indexes.gt(0)
# size = [batchSize, max_seq_len, 1]
mask = mask.unsqueeze(2)
# 使用dropout
user_short_items_emb = self.emb_dropout(user_short_items_emb_ori) * mask
# 扩大维度
user_short_items_emb = user_short_items_emb.unsqueeze(1)
二、自定义attention
这里的自定义是使用确定的query(用户兴趣)来对整个序列(用户交互系列)进行attention,最后得到维度为batchsize*2的输出和整个序列item的表示
a
j
=
τ
l
(
v
j
∣
∣
q
l
u
∣
∣
(
v
j
−
q
l
u
)
∣
∣
(
v
j
⋅
q
l
u
)
)
a_{j}=\tau_{l}\left(\boldsymbol{v_{j}}||\boldsymbol{q_{l}^{u}}||\left(\boldsymbol{v_{j}}-\boldsymbol{q_{l}^{u}}\right)||\left(\boldsymbol{v_{j}}\cdot\boldsymbol{q_{l}^{u}}\right)\right)
aj=τl(vj∣∣qlu∣∣(vj−qlu)∣∣(vj⋅qlu))
class AttnReadout(nn.Module):
def __init__(
self,
input_dim, # 输入层维度
hidden_dim, # 隐藏层维度
output_dim, # 输出层维度
layer_norm=True, # 是否层归一化
feat_drop=0.0, # 是否dropout
):
super().__init__()
self.layer_norm = nn.LayerNorm(input_dim) if layer_norm else None
self.feat_drop = nn.Dropout(feat_drop)
# 将输入序列embedding维度转化,以获得key
self.fc_key_map = nn.Linear(input_dim, hidden_dim, bias=False)
# 将输入序列embedding维度转化,以获得value
self.fc_value_map = nn.Linear(input_dim, hidden_dim, bias=False)
# 将query与key相乘获得最终的batchsize*2
self.fc_attention = nn.Linear(hidden_dim + hidden_dim + hidden_dim + 1,
output_dim, bias=False)
self.sigmoid = nn.Sigmoid()
self.softmax = nn.Softmax(dim=1)
# item_seq_emb:整个序列各个item的表示,batchsize*max_seq_len*feat_dim
# user_interest_fusion_emb:query,整个序列的融合表示,batchsize*feat_dim
# mask:该序列的mask矩阵,batchsize*max_seq_len*1
def forward(self, item_seq_emb, user_interest_fusion_emb, mask):
if self.layer_norm is not None:
item_seq_emb = self.layer_norm(item_seq_emb)
item_seq_emb = item_seq_emb * mask
item_seq_emb = self.feat_drop(item_seq_emb)
item_seq_emb = item_seq_emb * mask
item_seq_emb_key = self.fc_key_map(item_seq_emb)
item_seq_emb_key = item_seq_emb_key * mask
# 将整个序列的融合表示expand成batchsize*max_seq_len*feat_dim
user_interest_fusion_emb_expand = user_interest_fusion_emb.unsqueeze(1).expand_as(item_seq_emb)
# 特征相减和相乘
emb_subtract = item_seq_emb_key - user_interest_fusion_emb_expand
emb_multiply = torch.matmul(
item_seq_emb_key,
user_interest_fusion_emb.unsqueeze(1).transpose(1, 2).to(torch.float))
item_seq_two_dim_score = self.fc_attention(
torch.cat((item_seq_emb_key, user_interest_fusion_emb_expand,
emb_subtract, emb_multiply), dim=-1)
) * mask
# 这里的维度为batchsize*max_seq_len*2
score = self.sigmoid(item_seq_two_dim_score)
# 获得整个序列经过降噪的item embedding
denoised_items_emb = self.get_denoised_items_emb(
item_seq_emb=item_seq_emb, score=score, mask=mask)
return score, denoised_items_emb
def get_denoised_items_emb(self, item_seq_emb, score, mask):
item_seq_score = score[:, :, 0]
item_seq_score = self.softmax(item_seq_score)
item_seq_score = item_seq_score.unsqueeze(-1).expand_as(item_seq_emb)
# 获得value
item_seq_emb_value = self.fc_value_map(item_seq_emb)
item_seq_emb_value = item_seq_emb_value * mask
# attention scores乘value
denoised_items_emb = item_seq_score * item_seq_emb_value
denoised_items_emb = denoised_items_emb * mask
return denoised_items_emb