整体流程:
- 构建dataset类:处理数据集,构建m_item, UserItemNet等统计量
- 实例化LightGCN模型(继承自
nn.module
):初始化embedding
、设置激活函数、调用dataset
中的SparseGraph
函数(构建邻接矩阵、度矩阵) - 指定loss,BPRLoss()
- 调用Procedure.Test & Procedure.BPR_train_original进行测试和训练。① 采样
(u,i,j)
,正负采样都是随机选的。② 根据LightGCN的聚合公式 ( D − 1 / 2 A D − 1 / 2 ) e i (D^{-1/2}AD^{-1/2})e_i (D−1/2AD−1/2)ei,得到聚合三次后(u,i,j)
的embedding③ 传入embedding,通过bpr_loss()计算loss。
理解成,LightGCN就是通过信息传播聚合得到embedding,然后采集正负样本,使用bpr_loss训练就好了。
1. import过程
1.1 world
这部分是获取参数、路径:
- 获取路径,记得将
ROOT_PATH = "/Users/gus/Desktop/light-gcn"
换成当前路径:ROOT_PATH = os.path.dirname(__file__)
args = parse_args()
获取参数,存到config字典中- 定义了
cprint
,相当于高光输出。
1.2 utils
这部分指定评价指标
- 检查是否含有
cpp
文件 - 评价指标都在里面哦
1.3 register
- 输出参数
- 定义MODELS
MODELS = {
'mf': model.PureMF,
'lgn': model.LightGCN
}
2. 指定模型和损失函数
传入初始化参数(config和dataset),进行实例化
Recmodel = register.MODELS[world.model_name](world.config, dataset)
Recmodel = Recmodel.to(world.device)
bpr = utils.BPRLoss(Recmodel, world.config)
- 初始化LightGCN时,就构建好了邻接矩阵什么的
3. 训练过程
3.1 测试
每隔十轮就进行测试 Procedure.Test(dataset, Recmodel, epoch, w, world.config['multicore'])
- 调用
rating = Recmodel.getUsersRating(batch_users_gpu)
得到用户对所有物品的评分(两个embedding相乘) - 修改历史交互物品的评分(不会被选入TopN):
rating[exclude_index, exclude_items] = -(1<<10)
- 传入真实值和预测值,查看各个指标结果:
test_one_batch(x)
;调用了utils
中的函数。
3.2 训练
调用Procedure进行训练:output_information = Procedure.BPR_train_original(dataset, Recmodel, bpr, epoch, neg_k=Neg_k,w=w)
- 先进行正负样本采样
- 调用
utils.stageOne
计算loss和更新 - 调用
all_users, all_items = self.computer()
获取embedding;初始embedding是随机的,经过三次聚合得到最终的embedding - 根据
bpr loss
计算损失 - 使用Adam进行更新
函数用法:
os.path.join()
用法: 获取当前目录,并组合成新目录CODE_PATH = join(ROOT_PATH, 'code')
argparse
命令行选项、参数和子命令解析器。
①import argparse
导包
②parser = argparse.ArgumentParser()
创建对象
③parser.add_argument()
添加值
④args = parse_args()
进行解析,之后调用参数直接args.xx
就好了simplefilter
是一个模块,它提供了构建卷积分类网络所需的工具strip()
删除空格/规定字符,split()
拆分
l = '1 2 3 4 5 6 '
l=l.strip()
l=l.split(' ')
print(l)# ['1', '2', '3', '4', '5', '6']
@property
:是一种装饰器,将方法变成属性调用dict.get(key, default=None)
返回指定键的值,如果键不在字典中返回默认值 None 或者设置的默认值。numpy.random.randint(low, high=None, size=None, dtype='l')
指定上下界和sizenp.unique( )
的用法 该函数是去除数组中的重复数字,并进行排序之后输出。
import numpy as np
a = [1,2,3,3,5,1,6,2,4]
print(np.unique(a)) # [1 2 3 4 5 6]
csr_matrix
构造稀疏矩阵;indices为[1,4,12,34,1,3,56,12,45,10];indptr是[ 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10](非零的位置)
res = csr_matrix((np.ones(10), ([1,2,3,4,5,6,7,8,9,10], [1,4,12,34,1,3,56,12,45,10])), shape=(11, 57))
# 输出:
out:
(1, 1) 1.0
(2, 4) 1.0
(3, 12) 1.0
(4, 34) 1.0
(5, 1) 1.0
......
nonzero
函数是numpy中用于得到数组array中非零元素的位置(数组索引)的函数。
print(np.nonzero([0,0,3,0,3,5]))
# output: (array([2, 4, 5], dtype=int64),)
pprint
用于打印复杂的数据结构对象,例如多层嵌套的列表、元组和字典等。sp.dok_matrix
:采用字典来记录矩阵中不为0的元素(有“稀疏字典”那味)这篇文章解释了各种稀疏矩阵的构建方法和区别(coo, csr, dok)
from scipy.sparse import dok_matrix
S = dok_matrix((3, 3), dtype=np.float32)
for i in range(3):
for j in range(3):
S[i, j] = i + j # Update element
print(S)
>>> output:
(0, 1) 1.0
(0, 2) 2.0
(1, 0) 1.0
(1, 1) 2.0
(1, 2) 3.0
(2, 0) 2.0
(2, 1) 3.0
(2, 2) 4.0
_row = np.array([0, 3, 1, 0])
_col = np.array([0, 3, 1, 2])
_data = np.array([4, 5, 7, 9])
# 稀疏矩阵存储方式 (row, col, data)
coo = coo_matrix((_data, (_row, _col)), shape=(4, 4), dtype=np.int)
print(coo)
# (0, 0) 4
# (3, 3) 5
# (1, 1) 7
# (0, 2) 9
# 按行压缩的稀疏矩阵存储方式 indptr, indices, data
csr = csr_matrix((_data, (_row, _col)), shape=(4, 4), dtype=np.int)
print(csr)
# (0, 0) 4
# (0, 2) 9
# (1, 1) 7
# (3, 3) 5
torch.nn.Embedding(num_embeddings=self.num_users, embedding_dim=self.latent_dim)
构建embedding;这个模块常用来保存词嵌入和用下标检索它们。
emb = nn.Embedding(num_embeddings=5,embedding_dim=3)
user = torch.tensor(([1,2,3],[2,1,0]))
print(emb(user))
>>>output:
tensor([[[-0.7928, -0.7821, 0.3877],
[ 0.2589, -0.0153, 1.4402],
[ 0.1919, 0.8395, 1.2728]],
[[ 0.2589, -0.0153, 1.4402],
[-0.7928, -0.7821, 0.3877],
[ 0.9853, 0.6987, -2.0332]]], grad_fn=<EmbeddingBackward>)
torch.nn.init.normal(tensor, mean=0, std=1)
从给定均值和标准差的正态分布N(mean, std)中生成值,填充输入的张量或变量。def shuffle(*arrays, **kwargs)
:表示不确定具体参数个数,前者是tuple类型,后者是dict类型
def add(*num):
res = 0
for i in num:
res += i
return res
print(add(1,2,3))
def infor(**diec_args):
res = 0
for i,j in diec_args.items():
print(i,":",j)
#注意输入格式 k1=v1,k2=v2
infor(name='shiqi',sex='girl')
- 带有
yield
的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。 torch.topk(rating, k=max_K)
用来求tensor中某个dim的前k大或者前k小的值以及对应的indexassert expression
用于判断一个表达式,在表达式条件为 false 的时候触发异常。
# 相当于:
if not expression:
raise AssertionError
# 用法
assert 1<2 # 不会报错
assert 1>2 # 会报错AssertionError
- with结构,基本思想是with所求值的对象必须有一个
__enter__()
方法,一个__exit__()
方法。紧跟with后面的语句被求值后,返回对象的__enter__()
方法被调用,这个方法的返回值将被赋值给as后面的变量。当with后面的代码块全部被执行完之后,将调用前面返回对象的__exit__()
方法。下面这个例子是计算运行时间的:
with timer(name="Sample"):
S = utils.UniformSample_original(dataset)
time_info = timer.dict()
def __enter__(self):
self.start = timer.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
timer.NAMED_TAPE[self.named] += timer.time() - self.start
def dict(select_keys=None):
hint = "|"
for key, value in timer.NAMED_TAPE.items():
hint = hint + f"{key}:{value:.2f}|"
return hint