代码:
import sys
import torch
import torch.nn.functional as F
import random
import numpy as np
import copy
from aggregator import Aggregator
class KGCN(torch.nn.Module):
def __init__(self, num_user, num_ent, num_rel, kg, args, device):
super(KGCN, self).__init__()
self.num_user = num_user #分别存储用户、实体和关系的数量
self.num_ent = num_ent
self.num_rel = num_rel
self.n_iter = args.n_iter #图卷积网络中的迭代次数或层数。每一次迭代代表从当前实体出发进一步探索其邻居的过程
self.batch_size = args.batch_size #批量大小
self.dim = args.dim #嵌入维度,这是用户、实体和关系向量的维度
self.n_neighbor = args.neighbor_sample_size #邻居采样大小
self.kg = kg #接收知识图谱数据
self.device = device #指定模型运行的设备(如CPU或GPU)
self.aggregator = Aggregator(self.batch_size, self.dim, args.aggregator) #聚合器初始化
self._gen_adj() #预处理知识图谱数据
#嵌入层初始化
self.usr = torch.nn.Embedding(num_user, args.dim)
self.ent = torch.nn.Embedding(num_ent, args.dim)
self.rel = torch.nn.Embedding(num_rel, args.dim)
def _gen_adj(self):
'''
Generate adjacency matrix for entities and relations
Only cares about fixed number of samples
''' #为知识图谱中的实体生成邻接矩阵
self.adj_ent = torch.empty(self.num_ent, self.n_neighbor, dtype=torch.long)
self.adj_rel = torch.empty(self.num_ent, self.n_neighbor, dtype=torch.long)
#通过 torch.empty 方法创建两个空的PyTorch张量(self.adj_ent 和 self.adj_rel)
#,分别用于存储实体的邻居和与这些邻居相关联的关系。这些张量的维度都是 [self.num_ent, self.n_neighbor],
#其中 self.num_ent 是知识图谱中实体的总数,self.n_neighbor 是为每个实体考虑的邻居数量。
for e in self.kg:
if len(self.kg[e]) >= self.n_neighbor:
neighbors = random.sample(self.kg[e], self.n_neighbor) #通过 random.sample 从 self.kg[e] 中随机选择 self.n_neighbor 个邻居
else:
neighbors = random.choices(self.kg[e], k=self.n_neighbor) #通过 random.choices 允许重复地选择邻居,直到达到 self.n_neighbor 个邻居为止
self.adj_ent[e] = torch.LongTensor([ent for _, ent in neighbors])
self.adj_rel[e] = torch.LongTensor([rel for rel, _ in neighbors])
#对于选定的邻居,使用列表推导式提取邻居实体和关系,并将它们转换为 torch.LongTensor,分别填充到 self.adj_ent[e] 和 self.adj_rel[e]。
def forward(self, u, v):
'''
input: u, v are batch sized indices for users and items
u: [batch_size]
v: [batch_size]
'''
batch_size = u.size(0)
if batch_size != self.batch_size:
self.batch_size = batch_size
# change to [batch_size, 1]
#将用户和物品索引的维度从 [batch_size] 重塑为 [batch_size, 1],这是为了使它们能够被嵌入层正确处理。
u = u.view((-1, 1))
v = v.view((-1, 1))
# [batch_size, dim]
user_embeddings = self.usr(u).squeeze(dim = 1) #通过用户嵌入层 self.usr 获取用户的嵌入向量。self.usr(u) 返回的维度是 [batch_size, 1, dim],使用 .squeeze(dim = 1) 将其压缩为 [batch_size, dim],去除多余的中间维度。
#这里做改进
entities, relations = self._get_neighbors(v) #获取每个物品的邻居实体和关系
item_embeddings = self._aggregate(user_embeddings, entities, relations) #将用户嵌入、物品的邻居实体和邻居关系进行聚合,以得到最终的物品嵌入表示
#这里做改进
scores = (user_embeddings * item_embeddings).sum(dim = 1) #计算用户嵌入和物品嵌入之间的相似度。这通过对应元素相乘(element-wise multiplication)并在 dim = 1(即每个用户-物品对的嵌入维度上)求和来完成。结果是一个包含每个用户-物品对相似度得分的向量,维度为 [batch_size]。
return torch.sigmoid(scores) #将得分转换为一个介于0和1之间的预测值,代表用户对物品的偏好程度。
def _get_neighbors(self, v): #参数 v 是物品的索引,其形状为 [batch_size, 1],表示这个批次中有 batch_size 个物品。
'''
v is batch sized indices for items
v: [batch_size, 1]
'''
entities = [v] #entities 列表被初始化为包含输入物品索引 v 的列表。这意味着 entities 列表的第一个元素是初始的物品索引。
relations = []
'''循环执行 self.n_iter 次,其中 self.n_iter 表示图卷积网络中的迭代次数或层数。每一次迭代代表从当前实体出发进一步探索其邻居的过程。
在每次迭代中,通过访问邻接矩阵 self.adj_ent 和 self.adj_rel 来获取当前实体的邻居实体和关系。这里使用 entities[h] 来索引邻接矩阵,h 是当前迭代步骤。由于 entities 初始时包含了物品的索引 v,因此在第一次迭代时,它实际上是获取每个物品的直接邻居。
获取的邻居实体和关系索引通过 .view((self.batch_size, -1)) 调整形状,以确保它们与批量大小相兼容,并通过 .to(self.device) 移动到指定的计算设备上(如CPU或GPU)。
这些邻居实体和关系随后被添加到 entities 和 relations 列表中,为下一次迭代准备。'''
for h in range(self.n_iter):
neighbor_entities = torch.LongTensor(self.adj_ent[entities[h]]).view((self.batch_size, -1)).to(self.device)
neighbor_relations = torch.LongTensor(self.adj_rel[entities[h]]).view((self.batch_size, -1)).to(self.device)
entities.append(neighbor_entities)
relations.append(neighbor_relations)
return entities, relations
def _aggregate(self, user_embeddings, entities, relations):
'''
Make item embeddings by aggregating neighbor vectors
'''
entity_vectors = [self.ent(entity) for entity in entities] #对于输入的每个实体层(entities 列表中的每个元素),使用实体嵌入层(self.ent)获取对应的嵌入向量。这个列表的每一层代表不同跳数(hop)的邻居实体嵌入。
relation_vectors = [self.rel(relation) for relation in relations] #类似地,对于输入的每个关系层(relations 列表中的每个元素),使用关系嵌入层(self.rel)获取对应的嵌入向量。
#聚合邻居向量,通过多次迭代完成,在每次迭代中,根据迭代的次数选择激活函数。最后一层使用 torch.tanh 作为激活函数,其他层使用 torch.sigmoid。
for i in range(self.n_iter):
if i == self.n_iter - 1:
act = torch.tanh
else:
act = torch.sigmoid
entity_vectors_next_iter = [] #存储该次迭代后的实体向量
for hop in range(self.n_iter - i): #内层循环遍历当前迭代步骤之后的每一跳(hop)的实体向量和关系向量,将它们与用户嵌入一起,通过聚合器 self.aggregator 进行聚合。
vector = self.aggregator( #self.aggregator 接收当前层的实体向量(self_vectors)、下一层的邻居实体向量(neighbor_vectors)、与这些邻居相关的关系向量(neighbor_relations)和用户嵌入(user_embeddings)作为输入,并通过指定的激活函数 act 生成聚合后的向量。
self_vectors=entity_vectors[hop],
neighbor_vectors=entity_vectors[hop + 1].view((self.batch_size, -1, self.n_neighbor, self.dim)),
neighbor_relations=relation_vectors[hop].view((self.batch_size, -1, self.n_neighbor, self.dim)),
user_embeddings=user_embeddings,
act=act)
entity_vectors_next_iter.append(vector) #聚合后的向量被添加到 entity_vectors_next_iter 列表中,为下一次迭代更新 entity_vectors
entity_vectors = entity_vectors_next_iter
#经过 self.n_iter 次迭代后,entity_vectors 列表中仅包含最终聚合后的实体向量(在最后一次迭代中生成)。函数最终将这个向量的形状调整为 (self.batch_size, self.dim) 并返回,作为物品的最终嵌入表示。
return entity_vectors[0].view((self.batch_size, self.dim))
KGCN类:
__init__:
1.self.num_user,self.num_ent,self.num_rel:
分别存储用户、实体和关系的数量
2.self.n_iter
图卷积网络中的迭代次数或层数。每一次迭代代表从当前实体出发进一步探索其邻居的过程。
3.self.batch_size,self.dim
批量大小,嵌入维度(用户、实体和关系向量的维度)
4.self.n_neighbor
邻居采样大小
5.self.kg
接收的知识图谱数据
6.self.device
指定模型运行的设备(如CPU或GPU)
7.self.aggregator
8.self._gen_adj()
预处理知识图谱数据
9.self.usr,self.ent,self.rel
嵌入层初始化
_gen_adj:(预处理知识图谱数据)
- 为知识图谱中的实体生成邻接矩阵
- 邻接矩阵中,每个实体对应着self.n_neighbor个邻居以及对应的关系(不足self.n_neighbor个邻居的需要随机重复选择)
- self.adj_ent 邻居实体邻接矩阵
- self.adj_rel 邻居关系邻接矩阵
forward(self, u, v):
u: [batch_size],v: [batch_size]
获取用户嵌入
获取项目邻居
聚合项目及邻居,得到项目编码
得到分数
对分数进行SIGMOD处理
_get_neighbors(self, v):
- 输入:v: [batch_size, 1]
- 输出:entities:[v,[xxxxxxx],[xxxxxxxx],...[xxxxxxxx]],relations:[[xxxxxxx],[xxxxxxxx],...[xxxxxxxx]]
_aggregate:
- 对于entities和relations中的每一组元素(即知识图谱中的每一层)进行嵌入表示。
迭代self.n_iter次,得到项目的编码(最后一次用tanh激活函数,其他用sigmoid激活函数)
获得项目编码[self.batch_size, self.dim]