torch-rechub 0.0.1__py3-none-any.whl → 0.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- torch_rechub/basic/activation.py +54 -52
- torch_rechub/basic/callback.py +32 -32
- torch_rechub/basic/features.py +94 -57
- torch_rechub/basic/initializers.py +92 -0
- torch_rechub/basic/layers.py +720 -240
- torch_rechub/basic/loss_func.py +34 -0
- torch_rechub/basic/metaoptimizer.py +72 -0
- torch_rechub/basic/metric.py +250 -0
- torch_rechub/models/matching/__init__.py +11 -0
- torch_rechub/models/matching/comirec.py +188 -0
- torch_rechub/models/matching/dssm.py +66 -0
- torch_rechub/models/matching/dssm_facebook.py +79 -0
- torch_rechub/models/matching/dssm_senet.py +75 -0
- torch_rechub/models/matching/gru4rec.py +87 -0
- torch_rechub/models/matching/mind.py +101 -0
- torch_rechub/models/matching/narm.py +76 -0
- torch_rechub/models/matching/sasrec.py +140 -0
- torch_rechub/models/matching/sine.py +151 -0
- torch_rechub/models/matching/stamp.py +83 -0
- torch_rechub/models/matching/youtube_dnn.py +71 -0
- torch_rechub/models/matching/youtube_sbc.py +98 -0
- torch_rechub/models/multi_task/__init__.py +5 -4
- torch_rechub/models/multi_task/aitm.py +84 -0
- torch_rechub/models/multi_task/esmm.py +55 -45
- torch_rechub/models/multi_task/mmoe.py +58 -52
- torch_rechub/models/multi_task/ple.py +130 -104
- torch_rechub/models/multi_task/shared_bottom.py +45 -44
- torch_rechub/models/ranking/__init__.py +11 -3
- torch_rechub/models/ranking/afm.py +63 -0
- torch_rechub/models/ranking/bst.py +63 -0
- torch_rechub/models/ranking/dcn.py +38 -0
- torch_rechub/models/ranking/dcn_v2.py +69 -0
- torch_rechub/models/ranking/deepffm.py +123 -0
- torch_rechub/models/ranking/deepfm.py +41 -41
- torch_rechub/models/ranking/dien.py +191 -0
- torch_rechub/models/ranking/din.py +91 -81
- torch_rechub/models/ranking/edcn.py +117 -0
- torch_rechub/models/ranking/fibinet.py +50 -0
- torch_rechub/models/ranking/widedeep.py +41 -41
- torch_rechub/trainers/__init__.py +2 -1
- torch_rechub/trainers/{trainer.py → ctr_trainer.py} +128 -111
- torch_rechub/trainers/match_trainer.py +170 -0
- torch_rechub/trainers/mtl_trainer.py +206 -144
- torch_rechub/utils/__init__.py +0 -0
- torch_rechub/utils/data.py +360 -0
- torch_rechub/utils/match.py +274 -0
- torch_rechub/utils/mtl.py +126 -0
- {torch_rechub-0.0.1.dist-info → torch_rechub-0.0.3.dist-info}/LICENSE +21 -21
- torch_rechub-0.0.3.dist-info/METADATA +177 -0
- torch_rechub-0.0.3.dist-info/RECORD +55 -0
- {torch_rechub-0.0.1.dist-info → torch_rechub-0.0.3.dist-info}/WHEEL +1 -1
- torch_rechub/basic/utils.py +0 -168
- torch_rechub-0.0.1.dist-info/METADATA +0 -105
- torch_rechub-0.0.1.dist-info/RECORD +0 -26
- {torch_rechub-0.0.1.dist-info → torch_rechub-0.0.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Date: create on 24/05/2022
|
|
3
|
+
References:
|
|
4
|
+
paper: (KDD'2020) Embedding-based Retrieval in Facebook Search
|
|
5
|
+
url: https://arxiv.org/abs/2006.11632
|
|
6
|
+
Authors: Mincai Lai, laimincai@shanghaitech.edu.cn
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import torch
|
|
10
|
+
import torch.nn.functional as F
|
|
11
|
+
from ...basic.layers import MLP, EmbeddingLayer
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FaceBookDSSM(torch.nn.Module):
|
|
15
|
+
"""Embedding-based Retrieval in Facebook Search
|
|
16
|
+
It's a DSSM match model trained by hinge loss on pair-wise samples.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
user_features (list[Feature Class]): training by the user tower module.
|
|
20
|
+
pos_item_features (list[Feature Class]): negative sample features, training by the item tower module.
|
|
21
|
+
neg_item_features (list[Feature Class]): positive sample features, training by the item tower module.
|
|
22
|
+
temperature (float): temperature factor for similarity score, default to 1.0.
|
|
23
|
+
user_params (dict): the params of the User Tower module, keys include:`{"dims":list, "activation":str, "dropout":float, "output_layer":bool`}.
|
|
24
|
+
item_params (dict): the params of the Item Tower module, keys include:`{"dims":list, "activation":str, "dropout":float, "output_layer":bool`}.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self,
|
|
28
|
+
user_features,
|
|
29
|
+
pos_item_features,
|
|
30
|
+
neg_item_features,
|
|
31
|
+
user_params,
|
|
32
|
+
item_params,
|
|
33
|
+
temperature=1.0):
|
|
34
|
+
super().__init__()
|
|
35
|
+
self.user_features = user_features
|
|
36
|
+
self.pos_item_features = pos_item_features
|
|
37
|
+
self.neg_item_features = neg_item_features
|
|
38
|
+
self.temperature = temperature
|
|
39
|
+
self.user_dims = sum([fea.embed_dim for fea in user_features])
|
|
40
|
+
self.item_dims = sum([fea.embed_dim for fea in pos_item_features])
|
|
41
|
+
|
|
42
|
+
self.embedding = EmbeddingLayer(user_features + pos_item_features + neg_item_features)
|
|
43
|
+
self.user_mlp = MLP(self.user_dims, output_layer=False, **user_params)
|
|
44
|
+
self.item_mlp = MLP(self.item_dims, output_layer=False, **item_params)
|
|
45
|
+
self.mode = None
|
|
46
|
+
|
|
47
|
+
def forward(self, x):
|
|
48
|
+
user_embedding = self.user_tower(x)
|
|
49
|
+
pos_item_embedding, neg_item_embedding = self.item_tower(x)
|
|
50
|
+
if self.mode == "user":
|
|
51
|
+
return user_embedding
|
|
52
|
+
if self.mode == "item":
|
|
53
|
+
return pos_item_embedding
|
|
54
|
+
|
|
55
|
+
# calculate cosine score
|
|
56
|
+
pos_score = torch.mul(user_embedding, pos_item_embedding).sum(dim=1)
|
|
57
|
+
neg_score = torch.mul(user_embedding, neg_item_embedding).sum(dim=1)
|
|
58
|
+
|
|
59
|
+
return pos_score, neg_score
|
|
60
|
+
|
|
61
|
+
def user_tower(self, x):
|
|
62
|
+
if self.mode == "item":
|
|
63
|
+
return None
|
|
64
|
+
input_user = self.embedding(x, self.user_features, squeeze_dim=True) #[batch_size, num_features*deep_dims]
|
|
65
|
+
user_embedding = self.user_mlp(input_user) #[batch_size, user_params["dims"][-1]]
|
|
66
|
+
user_embedding = F.normalize(user_embedding, p=2, dim=1)
|
|
67
|
+
return user_embedding
|
|
68
|
+
|
|
69
|
+
def item_tower(self, x):
|
|
70
|
+
if self.mode == "user":
|
|
71
|
+
return None, None
|
|
72
|
+
input_item_pos = self.embedding(x, self.pos_item_features, squeeze_dim=True)
|
|
73
|
+
if self.mode == "item": #inference embedding mode, the zeros is just for placefolder
|
|
74
|
+
return self.item_mlp(input_item_pos), None
|
|
75
|
+
input_item_neg = self.embedding(x, self.neg_item_features, squeeze_dim=True)
|
|
76
|
+
pos_embedding, neg_embedding = self.item_mlp(input_item_pos), self.item_mlp(input_item_neg)
|
|
77
|
+
pos_embedding = F.normalize(pos_embedding, p=2, dim=1)
|
|
78
|
+
neg_embedding = F.normalize(neg_embedding, p=2, dim=1)
|
|
79
|
+
return pos_embedding, neg_embedding
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Date: create on 12/19/2024
|
|
3
|
+
References:
|
|
4
|
+
url: https://zhuanlan.zhihu.com/p/358779957
|
|
5
|
+
Authors: @1985312383
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import torch
|
|
9
|
+
import torch.nn.functional as F
|
|
10
|
+
from ...basic.layers import MLP, EmbeddingLayer, SENETLayer
|
|
11
|
+
from ...basic.features import SparseFeature, SequenceFeature
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DSSM(torch.nn.Module):
|
|
15
|
+
"""Deep Structured Semantic Model
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
user_features (list[Feature Class]): training by the user tower module.
|
|
19
|
+
item_features (list[Feature Class]): training by the item tower module.
|
|
20
|
+
temperature (float): temperature factor for similarity score, default to 1.0.
|
|
21
|
+
user_params (dict): the params of the User Tower module, keys include:`{"dims":list, "activation":str, "dropout":float, "output_layer":bool`}.
|
|
22
|
+
item_params (dict): the params of the Item Tower module, keys include:`{"dims":list, "activation":str, "dropout":float, "output_layer":bool`}.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, user_features, item_features, user_params, item_params, temperature=1.0):
|
|
26
|
+
super().__init__()
|
|
27
|
+
self.user_features = user_features
|
|
28
|
+
self.item_features = item_features
|
|
29
|
+
self.temperature = temperature
|
|
30
|
+
self.user_dims = sum([fea.embed_dim for fea in user_features])
|
|
31
|
+
self.item_dims = sum([fea.embed_dim for fea in item_features])
|
|
32
|
+
|
|
33
|
+
self.embedding = EmbeddingLayer(user_features + item_features)
|
|
34
|
+
self.user_mlp = MLP(self.user_dims, output_layer=False, **user_params)
|
|
35
|
+
self.item_mlp = MLP(self.item_dims, output_layer=False, **item_params)
|
|
36
|
+
self.user_num_features = len([fea.embed_dim for fea in self.user_features if isinstance(fea, SparseFeature) or isinstance(fea, SequenceFeature) and fea.shared_with == None])
|
|
37
|
+
self.item_num_features = len([fea.embed_dim for fea in self.item_features if isinstance(fea, SparseFeature) or isinstance(fea, SequenceFeature) and fea.shared_with == None])
|
|
38
|
+
self.user_senet = SENETLayer(self.user_num_features)
|
|
39
|
+
self.item_senet = SENETLayer(self.item_num_features)
|
|
40
|
+
self.mode = None
|
|
41
|
+
|
|
42
|
+
def forward(self, x):
|
|
43
|
+
user_embedding = self.user_tower(x)
|
|
44
|
+
item_embedding = self.item_tower(x)
|
|
45
|
+
if self.mode == "user":
|
|
46
|
+
return user_embedding
|
|
47
|
+
if self.mode == "item":
|
|
48
|
+
return item_embedding
|
|
49
|
+
|
|
50
|
+
# calculate cosine score
|
|
51
|
+
y = torch.mul(user_embedding, item_embedding).sum(dim=1)
|
|
52
|
+
y = y / self.temperature
|
|
53
|
+
return torch.sigmoid(y)
|
|
54
|
+
|
|
55
|
+
def user_tower(self, x):
|
|
56
|
+
if self.mode == "item":
|
|
57
|
+
return None
|
|
58
|
+
input_user = self.embedding(x, self.user_features, squeeze_dim=True) #[batch_size, num_features * embed_dim]
|
|
59
|
+
input_user = input_user.view(input_user.size(0), self.user_num_features, -1) #[batch_size, num_features, embed_dim]
|
|
60
|
+
input_user = self.user_senet(input_user) #[batch_size, num_features, embed_dim]
|
|
61
|
+
input_user = input_user.view(input_user.size(0), -1) #[batch_size, num_features * embed_dim]
|
|
62
|
+
user_embedding = self.user_mlp(input_user) #[batch_size, user_params["dims"][-1]]
|
|
63
|
+
user_embedding = F.normalize(user_embedding, p=2, dim=1) # L2 normalize
|
|
64
|
+
return user_embedding
|
|
65
|
+
|
|
66
|
+
def item_tower(self, x):
|
|
67
|
+
if self.mode == "user":
|
|
68
|
+
return None
|
|
69
|
+
input_item = self.embedding(x, self.item_features, squeeze_dim=True) #[batch_size, num_features * embed_dim]
|
|
70
|
+
input_item = input_item.view(input_item.size(0), self.item_num_features, -1) #[batch_size, num_features, embed_dim]
|
|
71
|
+
input_item = self.item_senet(input_item) #[batch_size, num_features, embed_dim]
|
|
72
|
+
input_item = input_item.view(input_item.size(0), -1) #[batch_size, num_features * embed_dim]
|
|
73
|
+
item_embedding = self.item_mlp(input_item) #[batch_size, item_params["dims"][-1]]
|
|
74
|
+
item_embedding = F.normalize(item_embedding, p=2, dim=1)
|
|
75
|
+
return item_embedding
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Date: create on 03/06/2022
|
|
3
|
+
References:
|
|
4
|
+
paper: SESSION-BASED RECOMMENDATIONS WITH RECURRENT NEURAL NETWORKS
|
|
5
|
+
url: http://arxiv.org/abs/1511.06939
|
|
6
|
+
Authors: Kai Wang, 306178200@qq.com
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import torch
|
|
10
|
+
|
|
11
|
+
from ...basic.layers import MLP, EmbeddingLayer
|
|
12
|
+
from torch import nn
|
|
13
|
+
import torch.nn.functional as F
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GRU4Rec(torch.nn.Module):
|
|
17
|
+
"""The match model mentioned in `Deep Neural Networks for YouTube Recommendations` paper.
|
|
18
|
+
It's a DSSM match model trained by global softmax loss on list-wise samples.
|
|
19
|
+
Note in origin paper, it's without item dnn tower and train item embedding directly.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
user_features (list[Feature Class]): training by the user tower module.
|
|
23
|
+
history_features (list[Feature Class]): training history
|
|
24
|
+
item_features (list[Feature Class]): training by the embedding table, it's the item id feature.
|
|
25
|
+
neg_item_feature (list[Feature Class]): training by the embedding table, it's the negative items id feature.
|
|
26
|
+
user_params (dict): the params of the User Tower module, keys include:`{"dims":list, "activation":str, "dropout":float, "output_layer":bool`}.
|
|
27
|
+
temperature (float): temperature factor for similarity score, default to 1.0.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, user_features, history_features, item_features, neg_item_feature, user_params, temperature=1.0):
|
|
31
|
+
super().__init__()
|
|
32
|
+
self.user_features = user_features
|
|
33
|
+
self.item_features = item_features
|
|
34
|
+
self.history_features = history_features
|
|
35
|
+
self.neg_item_feature = neg_item_feature
|
|
36
|
+
self.temperature = temperature
|
|
37
|
+
self.user_dims = sum([fea.embed_dim for fea in user_features+history_features])
|
|
38
|
+
|
|
39
|
+
self.embedding = EmbeddingLayer(user_features + item_features + history_features)
|
|
40
|
+
self.gru = nn.GRU(input_size = history_features[0].embed_dim,
|
|
41
|
+
hidden_size = history_features[0].embed_dim,
|
|
42
|
+
num_layers = user_params.get('num_layers',2),
|
|
43
|
+
batch_first = True,
|
|
44
|
+
bias = False)
|
|
45
|
+
self.user_mlp = MLP(self.user_dims, output_layer=False, **user_params)
|
|
46
|
+
self.mode = None
|
|
47
|
+
|
|
48
|
+
def forward(self, x):
|
|
49
|
+
user_embedding = self.user_tower(x)
|
|
50
|
+
item_embedding = self.item_tower(x)
|
|
51
|
+
if self.mode == "user":
|
|
52
|
+
return user_embedding
|
|
53
|
+
if self.mode == "item":
|
|
54
|
+
return item_embedding
|
|
55
|
+
|
|
56
|
+
y = torch.mul(user_embedding, item_embedding).sum(dim=1)
|
|
57
|
+
|
|
58
|
+
return y
|
|
59
|
+
|
|
60
|
+
def user_tower(self, x):
|
|
61
|
+
if self.mode == "item":
|
|
62
|
+
return None
|
|
63
|
+
input_user = self.embedding(x, self.user_features, squeeze_dim=True) #[batch_size, num_features*deep_dims]
|
|
64
|
+
|
|
65
|
+
history_emb = self.embedding(x, self.history_features).squeeze(1)
|
|
66
|
+
_, history_emb = self.gru(history_emb)
|
|
67
|
+
history_emb = history_emb[-1]
|
|
68
|
+
|
|
69
|
+
input_user = torch.cat([input_user,history_emb],dim=-1)
|
|
70
|
+
|
|
71
|
+
user_embedding = self.user_mlp(input_user).unsqueeze(1) #[batch_size, 1, embed_dim]
|
|
72
|
+
user_embedding = F.normalize(user_embedding, p=2, dim=-1) # L2 normalize
|
|
73
|
+
if self.mode == "user":
|
|
74
|
+
return user_embedding.squeeze(1) #inference embedding mode -> [batch_size, embed_dim]
|
|
75
|
+
return user_embedding
|
|
76
|
+
|
|
77
|
+
def item_tower(self, x):
|
|
78
|
+
if self.mode == "user":
|
|
79
|
+
return None
|
|
80
|
+
pos_embedding = self.embedding(x, self.item_features, squeeze_dim=False) #[batch_size, 1, embed_dim]
|
|
81
|
+
pos_embedding = F.normalize(pos_embedding, p=2, dim=-1) # L2 normalize
|
|
82
|
+
if self.mode == "item": #inference embedding mode
|
|
83
|
+
return pos_embedding.squeeze(1) #[batch_size, embed_dim]
|
|
84
|
+
neg_embeddings = self.embedding(x, self.neg_item_feature,
|
|
85
|
+
squeeze_dim=False).squeeze(1) #[batch_size, n_neg_items, embed_dim]
|
|
86
|
+
neg_embeddings = F.normalize(neg_embeddings, p=2, dim=-1) # L2 normalize
|
|
87
|
+
return torch.cat((pos_embedding, neg_embeddings), dim=1) #[batch_size, 1+n_neg_items, embed_dim]
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Date: create on 08/06/2022
|
|
3
|
+
References:
|
|
4
|
+
paper: Multi-Interest Network with Dynamic Routing
|
|
5
|
+
url: https://arxiv.org/pdf/1904.08030v1
|
|
6
|
+
code: https://github.com/ShiningCosmos/pytorch_ComiRec/blob/main/MIND.py
|
|
7
|
+
Authors: Kai Wang, 306178200@qq.com
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import torch
|
|
11
|
+
|
|
12
|
+
from ...basic.layers import MLP, EmbeddingLayer, MultiInterestSA, CapsuleNetwork
|
|
13
|
+
from torch import nn
|
|
14
|
+
import torch.nn.functional as F
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MIND(torch.nn.Module):
|
|
18
|
+
"""The match model mentioned in `Multi-Interest Network with Dynamic Routing` paper.
|
|
19
|
+
It's a ComirecDR match model trained by global softmax loss on list-wise samples.
|
|
20
|
+
Note in origin paper, it's without item dnn tower and train item embedding directly.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
user_features (list[Feature Class]): training by the user tower module.
|
|
24
|
+
history_features (list[Feature Class]): training history
|
|
25
|
+
item_features (list[Feature Class]): training by the embedding table, it's the item id feature.
|
|
26
|
+
neg_item_feature (list[Feature Class]): training by the embedding table, it's the negative items id feature.
|
|
27
|
+
max_length (int): max sequence length of input item sequence
|
|
28
|
+
temperature (float): temperature factor for similarity score, default to 1.0.
|
|
29
|
+
interest_num (int): interest num
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, user_features, history_features, item_features, neg_item_feature, max_length, temperature=1.0, interest_num=4):
|
|
33
|
+
super().__init__()
|
|
34
|
+
self.user_features = user_features
|
|
35
|
+
self.item_features = item_features
|
|
36
|
+
self.history_features = history_features
|
|
37
|
+
self.neg_item_feature = neg_item_feature
|
|
38
|
+
self.temperature = temperature
|
|
39
|
+
self.interest_num = interest_num
|
|
40
|
+
self.max_length = max_length
|
|
41
|
+
self.user_dims = sum([fea.embed_dim for fea in user_features+history_features])
|
|
42
|
+
|
|
43
|
+
self.embedding = EmbeddingLayer(user_features + item_features + history_features)
|
|
44
|
+
self.capsule = CapsuleNetwork(self.history_features[0].embed_dim,self.max_length,bilinear_type=0,interest_num=self.interest_num)
|
|
45
|
+
self.convert_user_weight = nn.Parameter(torch.rand(self.user_dims, self.history_features[0].embed_dim), requires_grad=True)
|
|
46
|
+
self.mode = None
|
|
47
|
+
|
|
48
|
+
def forward(self, x):
|
|
49
|
+
user_embedding = self.user_tower(x)
|
|
50
|
+
item_embedding = self.item_tower(x)
|
|
51
|
+
if self.mode == "user":
|
|
52
|
+
return user_embedding
|
|
53
|
+
if self.mode == "item":
|
|
54
|
+
return item_embedding
|
|
55
|
+
|
|
56
|
+
pos_item_embedding = item_embedding[:,0,:]
|
|
57
|
+
dot_res = torch.bmm(user_embedding, pos_item_embedding.squeeze(1).unsqueeze(-1))
|
|
58
|
+
k_index = torch.argmax(dot_res, dim=1)
|
|
59
|
+
best_interest_emb = torch.rand(user_embedding.shape[0], user_embedding.shape[2]).to(user_embedding.device)
|
|
60
|
+
for k in range(user_embedding.shape[0]):
|
|
61
|
+
best_interest_emb[k, :] = user_embedding[k, k_index[k], :]
|
|
62
|
+
best_interest_emb = best_interest_emb.unsqueeze(1)
|
|
63
|
+
|
|
64
|
+
y = torch.mul(best_interest_emb, item_embedding).sum(dim=1)
|
|
65
|
+
return y
|
|
66
|
+
|
|
67
|
+
def user_tower(self, x):
|
|
68
|
+
if self.mode == "item":
|
|
69
|
+
return None
|
|
70
|
+
input_user = self.embedding(x, self.user_features, squeeze_dim=True).unsqueeze(1) #[batch_size, num_features*deep_dims]
|
|
71
|
+
input_user = input_user.expand([input_user.shape[0], self.interest_num, input_user.shape[-1]])
|
|
72
|
+
|
|
73
|
+
history_emb = self.embedding(x, self.history_features).squeeze(1)
|
|
74
|
+
mask = self.gen_mask(x)
|
|
75
|
+
multi_interest_emb = self.capsule(history_emb,mask)
|
|
76
|
+
|
|
77
|
+
input_user = torch.cat([input_user,multi_interest_emb],dim=-1)
|
|
78
|
+
|
|
79
|
+
# user_embedding = self.user_mlp(input_user).unsqueeze(1) #[batch_size, interest_num, embed_dim]
|
|
80
|
+
user_embedding = torch.matmul(input_user,self.convert_user_weight)
|
|
81
|
+
user_embedding = F.normalize(user_embedding, p=2, dim=-1) # L2 normalize
|
|
82
|
+
if self.mode == "user":
|
|
83
|
+
return user_embedding #inference embedding mode -> [batch_size, interest_num, embed_dim]
|
|
84
|
+
return user_embedding
|
|
85
|
+
|
|
86
|
+
def item_tower(self, x):
|
|
87
|
+
if self.mode == "user":
|
|
88
|
+
return None
|
|
89
|
+
pos_embedding = self.embedding(x, self.item_features, squeeze_dim=False) #[batch_size, 1, embed_dim]
|
|
90
|
+
pos_embedding = F.normalize(pos_embedding, p=2, dim=-1) # L2 normalize
|
|
91
|
+
if self.mode == "item": #inference embedding mode
|
|
92
|
+
return pos_embedding.squeeze(1) #[batch_size, embed_dim]
|
|
93
|
+
neg_embeddings = self.embedding(x, self.neg_item_feature,
|
|
94
|
+
squeeze_dim=False).squeeze(1) #[batch_size, n_neg_items, embed_dim]
|
|
95
|
+
neg_embeddings = F.normalize(neg_embeddings, p=2, dim=-1) # L2 normalize
|
|
96
|
+
return torch.cat((pos_embedding, neg_embeddings), dim=1) #[batch_size, 1+n_neg_items, embed_dim]
|
|
97
|
+
|
|
98
|
+
def gen_mask(self, x):
|
|
99
|
+
his_list = x[self.history_features[0].name]
|
|
100
|
+
mask = (his_list > 0).long()
|
|
101
|
+
return mask
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Date: created on 06/09/2022
|
|
3
|
+
References:
|
|
4
|
+
paper: Neural Attentive Session-based Recommendation
|
|
5
|
+
url: http://arxiv.org/abs/1711.04725
|
|
6
|
+
official Theano implementation: https://github.com/lijingsdu/sessionRec_NARM
|
|
7
|
+
another Pytorch implementation: https://github.com/Wang-Shuo/Neural-Attentive-Session-Based-Recommendation-PyTorch
|
|
8
|
+
Authors: Bo Kang, klinux@live.com
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import torch
|
|
12
|
+
import torch.nn as nn
|
|
13
|
+
import torch.nn.utils.rnn as rnn_utils
|
|
14
|
+
from torch import sigmoid
|
|
15
|
+
from torch.nn import GRU, Embedding, Dropout, Parameter
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NARM(nn.Module):
|
|
19
|
+
def __init__(self, item_history_feature, hidden_dim, emb_dropout_p, session_rep_dropout_p):
|
|
20
|
+
super(NARM, self).__init__()
|
|
21
|
+
|
|
22
|
+
# item embedding layer
|
|
23
|
+
self.item_history_feature = item_history_feature
|
|
24
|
+
self.item_emb = Embedding(item_history_feature.vocab_size, item_history_feature.embed_dim, padding_idx=0)
|
|
25
|
+
|
|
26
|
+
# embedding dropout layer
|
|
27
|
+
self.emb_dropout = Dropout(emb_dropout_p)
|
|
28
|
+
|
|
29
|
+
# gru unit
|
|
30
|
+
self.gru = GRU(input_size=item_history_feature.embed_dim, hidden_size=hidden_dim)
|
|
31
|
+
|
|
32
|
+
# attention projection matrices
|
|
33
|
+
self.a_1, self.a_2 = Parameter(torch.randn(hidden_dim, hidden_dim)), Parameter(torch.randn(hidden_dim, hidden_dim))
|
|
34
|
+
|
|
35
|
+
# attention context vector
|
|
36
|
+
self.v = Parameter(torch.randn(hidden_dim, 1))
|
|
37
|
+
|
|
38
|
+
# session representation dropout layer
|
|
39
|
+
self.session_rep_dropout = Dropout(session_rep_dropout_p)
|
|
40
|
+
|
|
41
|
+
# bilinear projection matrix
|
|
42
|
+
self.b = Parameter(torch.randn(item_history_feature.embed_dim, hidden_dim * 2))
|
|
43
|
+
|
|
44
|
+
def forward(self, input_dict):
|
|
45
|
+
# Eq. 1-4, index item embeddings and pass through gru
|
|
46
|
+
## Fetch the embeddings for items in the session
|
|
47
|
+
input = input_dict[self.item_history_feature.name]
|
|
48
|
+
value_mask = (input != 0)
|
|
49
|
+
value_counts = value_mask.sum(dim=1, keepdim=False).to("cpu").detach()
|
|
50
|
+
embs = rnn_utils.pack_padded_sequence(self.emb_dropout(self.item_emb(input)), value_counts, batch_first=True, enforce_sorted=False)
|
|
51
|
+
|
|
52
|
+
## compute hidden states at each time step
|
|
53
|
+
h, h_t = self.gru(embs)
|
|
54
|
+
h_t = h_t.permute(1, 0, 2)
|
|
55
|
+
h, _ = rnn_utils.pad_packed_sequence(h, batch_first=True)
|
|
56
|
+
|
|
57
|
+
# Eq. 5, set last hidden state of gru as the output of the global encoder
|
|
58
|
+
c_g = h_t.squeeze(1)
|
|
59
|
+
|
|
60
|
+
# Eq. 8, compute similarity between final hidden state and previous hidden states
|
|
61
|
+
q = sigmoid(h_t @ self.a_1.T + h @ self.a_2.T) @ self.v
|
|
62
|
+
|
|
63
|
+
# Eq. 7, compute attention
|
|
64
|
+
alpha = torch.exp(q) * value_mask.unsqueeze(-1)
|
|
65
|
+
alpha /= alpha.sum(dim=1, keepdim=True)
|
|
66
|
+
|
|
67
|
+
# Eq. 6, compute the output of the local encoder
|
|
68
|
+
c_l = (alpha * h).sum(1)
|
|
69
|
+
|
|
70
|
+
# Eq. 9, compute session representation by concatenating user sequential behavior (global) and main purpose in the current session (local)
|
|
71
|
+
c = self.session_rep_dropout(torch.hstack((c_g, c_l)))
|
|
72
|
+
|
|
73
|
+
# Eq. 10, compute bilinear similarity between current session and each candidate items
|
|
74
|
+
s = c @ self.b.T @ self.item_emb.weight.T
|
|
75
|
+
|
|
76
|
+
return s
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Date: create on 2022/5/8, update on 2022/5/8
|
|
3
|
+
References:
|
|
4
|
+
paper: (ICDM'2018) Self-attentive sequential recommendation
|
|
5
|
+
url: https://arxiv.org/pdf/1808.09781.pdf
|
|
6
|
+
code: https://github.com/kang205/SASRec
|
|
7
|
+
Authors: Yuchen Wang, 615922749@qq.com
|
|
8
|
+
"""
|
|
9
|
+
import numpy as np
|
|
10
|
+
import torch
|
|
11
|
+
import torch.nn as nn
|
|
12
|
+
|
|
13
|
+
from torch_rechub.basic.features import DenseFeature, SparseFeature, SequenceFeature
|
|
14
|
+
from torch_rechub.basic.layers import EmbeddingLayer, MLP
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SASRec(torch.nn.Module):
|
|
18
|
+
"""SASRec: Self-Attentive Sequential Recommendation
|
|
19
|
+
Args:
|
|
20
|
+
features (list): the list of `Feature Class`. In sasrec, the features list needs to have three elements in order: user historical behavior sequence features, positive sample sequence, and negative sample sequence.
|
|
21
|
+
max_len: The length of the sequence feature.
|
|
22
|
+
num_blocks: The number of stacks of attention modules.
|
|
23
|
+
num_heads: The number of heads in MultiheadAttention.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
def __init__(self,
|
|
27
|
+
features,
|
|
28
|
+
max_len=50,
|
|
29
|
+
dropout_rate=0.5,
|
|
30
|
+
num_blocks=2,
|
|
31
|
+
num_heads=1,
|
|
32
|
+
):
|
|
33
|
+
super(SASRec, self).__init__()
|
|
34
|
+
|
|
35
|
+
self.features = features
|
|
36
|
+
|
|
37
|
+
self.item_num = self.features[0].vocab_size
|
|
38
|
+
self.embed_dim = self.features[0].embed_dim
|
|
39
|
+
|
|
40
|
+
self.item_emb = EmbeddingLayer(self.features)
|
|
41
|
+
self.position_emb = torch.nn.Embedding(max_len, self.embed_dim)
|
|
42
|
+
self.emb_dropout = torch.nn.Dropout(p=dropout_rate)
|
|
43
|
+
|
|
44
|
+
self.attention_layernorms = torch.nn.ModuleList()
|
|
45
|
+
self.attention_layers = torch.nn.ModuleList()
|
|
46
|
+
self.forward_layernorms = torch.nn.ModuleList()
|
|
47
|
+
self.forward_layers = torch.nn.ModuleList()
|
|
48
|
+
self.last_layernorm = torch.nn.LayerNorm(self.embed_dim, eps=1e-8)
|
|
49
|
+
|
|
50
|
+
for _ in range(num_blocks):
|
|
51
|
+
new_attn_layernorm = torch.nn.LayerNorm(self.embed_dim, eps=1e-8)
|
|
52
|
+
self.attention_layernorms.append(new_attn_layernorm)
|
|
53
|
+
|
|
54
|
+
new_attn_layer = torch.nn.MultiheadAttention(self.embed_dim,
|
|
55
|
+
num_heads,
|
|
56
|
+
dropout_rate)
|
|
57
|
+
self.attention_layers.append(new_attn_layer)
|
|
58
|
+
|
|
59
|
+
new_fwd_layernorm = torch.nn.LayerNorm(self.embed_dim, eps=1e-8)
|
|
60
|
+
self.forward_layernorms.append(new_fwd_layernorm)
|
|
61
|
+
|
|
62
|
+
new_fwd_layer = PointWiseFeedForward(self.embed_dim, dropout_rate)
|
|
63
|
+
self.forward_layers.append(new_fwd_layer)
|
|
64
|
+
|
|
65
|
+
def seq_forward(self, x, embed_x_feature):
|
|
66
|
+
x = x['seq']
|
|
67
|
+
|
|
68
|
+
embed_x_feature *= self.features[0].embed_dim ** 0.5
|
|
69
|
+
embed_x_feature = embed_x_feature.squeeze() # (bacth_size, max_len, embed_dim)
|
|
70
|
+
|
|
71
|
+
positions = np.tile(np.array(range(x.shape[1])), [x.shape[0], 1])
|
|
72
|
+
|
|
73
|
+
embed_x_feature += self.position_emb(torch.LongTensor(positions))
|
|
74
|
+
embed_x_feature = self.emb_dropout(embed_x_feature)
|
|
75
|
+
|
|
76
|
+
timeline_mask = torch.BoolTensor(x == 0)
|
|
77
|
+
embed_x_feature *= ~timeline_mask.unsqueeze(-1)
|
|
78
|
+
|
|
79
|
+
attention_mask = ~torch.tril(torch.ones((embed_x_feature.shape[1], embed_x_feature.shape[1]), dtype=torch.bool))
|
|
80
|
+
|
|
81
|
+
for i in range(len(self.attention_layers)):
|
|
82
|
+
embed_x_feature = torch.transpose(embed_x_feature, 0, 1)
|
|
83
|
+
Q = self.attention_layernorms[i](embed_x_feature)
|
|
84
|
+
mha_outputs, _ = self.attention_layers[i](Q, embed_x_feature, embed_x_feature,
|
|
85
|
+
attn_mask=attention_mask)
|
|
86
|
+
|
|
87
|
+
embed_x_feature = Q + mha_outputs
|
|
88
|
+
embed_x_feature = torch.transpose(embed_x_feature, 0, 1)
|
|
89
|
+
|
|
90
|
+
embed_x_feature = self.forward_layernorms[i](embed_x_feature)
|
|
91
|
+
embed_x_feature = self.forward_layers[i](embed_x_feature)
|
|
92
|
+
embed_x_feature *= ~timeline_mask.unsqueeze(-1)
|
|
93
|
+
|
|
94
|
+
seq_output = self.last_layernorm(embed_x_feature)
|
|
95
|
+
|
|
96
|
+
return seq_output
|
|
97
|
+
|
|
98
|
+
def forward(self, x):
|
|
99
|
+
embedding = self.item_emb(x, self.features) # (batch_size, 3, max_len, embed_dim)
|
|
100
|
+
seq_embed, pos_embed, neg_embed = embedding[:, 0], embedding[:, 1], embedding[:, 2] # (batch_size, max_len, embed_dim)
|
|
101
|
+
|
|
102
|
+
seq_output = self.seq_forward(x, seq_embed) # (batch_size, max_len, embed_dim)
|
|
103
|
+
|
|
104
|
+
pos_logits = (seq_output * pos_embed).sum(dim=-1)
|
|
105
|
+
neg_logits = (seq_output * neg_embed).sum(dim=-1) # (batch_size, max_len)
|
|
106
|
+
|
|
107
|
+
return pos_logits, neg_logits
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class PointWiseFeedForward(torch.nn.Module):
|
|
111
|
+
def __init__(self, hidden_units, dropout_rate):
|
|
112
|
+
super(PointWiseFeedForward, self).__init__()
|
|
113
|
+
|
|
114
|
+
self.conv1 = torch.nn.Conv1d(hidden_units, hidden_units, kernel_size=1)
|
|
115
|
+
self.dropout1 = torch.nn.Dropout(p=dropout_rate)
|
|
116
|
+
self.relu = torch.nn.ReLU()
|
|
117
|
+
self.conv2 = torch.nn.Conv1d(hidden_units, hidden_units, kernel_size=1)
|
|
118
|
+
self.dropout2 = torch.nn.Dropout(p=dropout_rate)
|
|
119
|
+
|
|
120
|
+
def forward(self, inputs):
|
|
121
|
+
outputs = self.dropout2(self.conv2(self.relu(self.dropout1(self.conv1(inputs.transpose(-1, -2))))))
|
|
122
|
+
outputs = outputs.transpose(-1, -2)
|
|
123
|
+
outputs += inputs
|
|
124
|
+
return outputs
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == '__main__':
|
|
128
|
+
seq = SequenceFeature('seq', vocab_size=17, embed_dim=7, pooling='concat')
|
|
129
|
+
pos = SequenceFeature('pos', vocab_size=17, embed_dim=7, pooling='concat', shared_with='seq')
|
|
130
|
+
neg = SequenceFeature('neg', vocab_size=17, embed_dim=7, pooling='concat', shared_with='seq')
|
|
131
|
+
|
|
132
|
+
seq = [seq, pos, neg]
|
|
133
|
+
|
|
134
|
+
hist_seq = torch.tensor([[1, 2, 3, 4], [2, 3, 7, 8]])
|
|
135
|
+
pos_seq = hist_seq
|
|
136
|
+
neg_seq = hist_seq
|
|
137
|
+
|
|
138
|
+
x = {'seq': hist_seq, 'pos': pos_seq, 'neg': neg_seq}
|
|
139
|
+
model = SASRec(features=seq)
|
|
140
|
+
print('out', model(x))
|