forked from BUPT-GAMMA/OpenHGNN
-
Notifications
You must be signed in to change notification settings - Fork 0
/
KGCN.py
187 lines (152 loc) · 7.2 KB
/
KGCN.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import numpy as np
import torch as th
import torch.nn as nn
import dgl.function as fn
import torch.nn.functional as F
from . import BaseModel, register_model
import torch.nn.functional as F
@register_model('KGCN')
class KGCN(BaseModel):
r"""
This module KGCN was introduced in `KGCN <https://dl.acm.org/doi/10.1145/3308558.3313417>`__.
It included two parts:
Aggregate the entity representation and its neighborhood representation into the entity's embedding.
The message function is defined as follow:
:math:`\mathrm{v}_{\mathcal{N}(v)}^{u}=\sum_{e \in \mathcal{N}(v)} \tilde{\pi}_{r_{v, e}}^{u} \mathrm{e}`
where :math:`\mathrm{e}` is the representation of entity,
:math:`\tilde{\pi}_{r_{v, e}}^{u}` is the scalar weight on the edge from entity to entity,
the result :math:`\mathrm{v}_{\mathcal{N}(v)}^{u}` saves message which is passed from neighbor nodes
There are three types of aggregators.
Sum aggregator takes the summation of two representation vectors,
Concat aggregator concatenates the two representation vectors and
Neighbor aggregator directly takes the neighborhood representation of entity as the output representation
:math:`a g g_{s u m}=\sigma\left(\mathbf{W} \cdot\left(\mathrm{v}+\mathrm{v}_{\mathcal{S}(v)}^{u}\right)+\mathbf{b}\right)`
:math:`agg $_{\text {concat }}=\sigma\left(\mathbf{W} \cdot \text{concat}\left(\mathrm{v}, \mathrm{v}_{\mathcal{S}(v)}^{u}\right)+\mathbf{b}\right)$`
:math:`\text { agg }_{\text {neighbor }}=\sigma\left(\mathrm{W} \cdot \mathrm{v}_{\mathcal{S}(v)}^{u}+\mathrm{b}\right)`
In the above equations, :math:`\sigma` is the nonlinear function and
:math:`\mathrm{W}` and :math:`\mathrm{b}` are transformation weight and bias.
the representation of an item is bound up with its neighbors by aggregation
Obtain scores using final entity representation and user representation
The final entity representation is denoted as :math:`\mathrm{v}^{u}`,
:math:`\mathrm{v}^{u}` do dot product with user representation :math:`\mathrm{u}`
can obtain the probability. The math formula for the above function is:
:math:`$\hat{y}_{u v}=f\left(\mathbf{u}, \mathrm{v}^{u}\right)$`
Parameters
----------
g : DGLGraph
A knowledge Graph preserves relationships between entities
args : Config
Model's config
"""
@classmethod
def build_model_from_args(cls, args, g):
return cls(g, args)
def __init__(self, g, args):
super(KGCN, self).__init__()
self.g = g
self.args = args
self.in_dim = args.in_dim
self.out_dim = args.out_dim
self.entity_emb_matrix = nn.Parameter(th.FloatTensor(self.g.num_nodes(), self.in_dim))
self.relation_emb_matrix = nn.Parameter(th.FloatTensor(args.n_relation, self.in_dim))
self.user_emb_matrix = nn.Parameter(th.FloatTensor(args.n_user, self.in_dim))
self.Aggregate = KGCN_Aggregate(args)
self.reset_parameters()
def reset_parameters(self):
nn.init.uniform_(self.entity_emb_matrix, -1, 1)
nn.init.uniform_(self.relation_emb_matrix, -1, 1)
nn.init.uniform_(self.user_emb_matrix, -1, 1)
def get_score(self):
r"""
Obtain scores using final entity representation and user representation
Returns
-------
"""
self.user_embeddings = self.user_emb_matrix[np.array(self.userList)]
self.scores = th.sum(self.user_embeddings * self.item_embeddings, dim=1)
self.scores_normalized = th.sigmoid(self.scores)
def get_embeddings(self):
return self.user_emb_matrix, self.entity_emb_matrix, self.relation_emb_matrix
def forward(self, blocks, inputdata):
r"""
Predict the probability between user and entity
Parameters
----------
blocks : list
Blocks saves the information of neighbor nodes in each layer
inputdata : numpy.ndarray
Inputdata contains the relationship between the user and the entity
Returns
-------
labels : torch.Tensor
the label between users and entities
scores : torch.Tensor
Probability of users clicking on entitys
"""
self.data = inputdata
self.blocks = blocks
self.user_indices = self.data[:,0]
self.itemlist = self.data[:,1]
self.labels = self.data[:,2]
self.item_embeddings, self.userList,self.labelList = self.Aggregate(blocks, inputdata)
self.get_score()
self.labels = th.tensor(self.labelList).to(self.args.device)
return self.labels, self.scores
class KGCN_Aggregate(nn.Module):
def __init__(self, args):
super(KGCN_Aggregate, self).__init__()
self.args = args
self.in_dim = args.in_dim
self.out_dim = args.out_dim
if self.args.aggregate == 'CONCAT':
self.agg = nn.Linear(self.in_dim*2, self.out_dim)
else:
self.agg = nn.Linear(self.in_dim, self.out_dim)
def aggregate(self):
self.sub_g.update_all(fn.u_mul_e('embedding', 'weight', 'm'),fn.sum('m', 'ft'))
self.userList = []
self.labelList = []
embeddingList = []
for i in range(len(self.data)):
weightIndex = np.where(self.itemlist==int(self.sub_g.dstdata['_ID'][i]))
if self.args.aggregate == 'SUM':
embeddingList.append(self.sub_g.dstdata['embedding'][i] + self.sub_g.dstdata['ft'][i][weightIndex])
elif self.args.aggregate == 'CONCAT':
embeddingList.append(th.cat([self.sub_g.dstdata['embedding'][i], self.sub_g.dstdata['ft'][i][weightIndex].squeeze(0)],dim=-1))
elif self.args.aggregate == 'NEIGHBOR':
embeddingList.append(self.sub_g.dstdata['embedding'][i])
self.userList.append(int(self.user_indices[weightIndex]))
self.labelList.append(int(self.labels[weightIndex]))
self.sub_g.dstdata['embedding'] = th.stack(embeddingList).squeeze(1)
output = F.dropout(self.sub_g.dstdata['embedding'],p=0)
if self.layer+1 == len(self.blocks):
self.item_embeddings = th.tanh(self.agg(output))
else:
self.item_embeddings = th.relu(self.agg(output))
def forward(self,blocks,inputdata):
r"""
Aggregate the entity representation and its neighborhood representation
Parameters
----------
blocks : list
Blocks saves the information of neighbor nodes in each layer
inputdata : numpy.ndarray
Inputdata contains the relationship between the user and the entity
Returns
-------
item_embeddings : torch.Tensor
items' embeddings after aggregated
userList : list
Users corresponding to items
labelList : list
Labels corresponding to items
"""
self.data = inputdata
self.blocks = blocks
self.user_indices = self.data[:,0]
self.itemlist = self.data[:,1]
self.labels = self.data[:,2]
for self.layer in range(len(blocks)):
self.sub_g = blocks[self.layer]
self.aggregate()
return self.item_embeddings, self.userList, self.labelList