[前篇]:
【AM-GCN】代码解读之初了解(一)
【AM-GCN】论文解读
一、导入库
import torch.nn.functional as F #常用函数
import torch.optim as optim #优化算法
from utils import *
from models import SFGCN #主模型
from sklearn.metrics import f1_score #计算F1分数,也称为平衡F分数或F测度
import os
import argparse
from config import Config
import torch
import numpy as np
解释说明:
二、参数读取和设置
if __name__ == "__main__":
os.environ["CUDA_VISIBLE_DEVICES"] = "2"
parse = argparse.ArgumentParser()
parse.add_argument("-d", "--dataset", help="dataset", type=str, required=True)
parse.add_argument("-l", "--labelrate", help="labeled data for train per class", type = int, required = True)
args = parse.parse_args()
config_file = "./config/" + str(args.labelrate) + str(args.dataset) + ".ini"
config = Config(config_file)
cuda = not config.no_cuda and torch.cuda.is_available() #cuda可否能用
use_seed = not config.no_seed # cuda
if use_seed:
np.random.seed(config.seed)
torch.manual_seed(config.seed)
if cuda:
torch.cuda.manual_seed(config.seed)
解释说明:
- os.environ[“CUDA_VISIBLE_DEVICES”] = "2"设置使用的标号为"2"的显卡。os.environ[‘环境变量名称’]=‘环境变量值’ :其中key和value均为string类型
- argparse 参数配置的方法,可以打开
cmd
来设置参数的值,具体使用方法见5中链接。1)import argparse 首先导入模块
2)parser = argparse.ArgumentParser() 创建一个解析器
3)parser.add_argument() 向该解析器中添加你要关注的命令行参数和选项
4)parser.parse_args() 进行解析 - config = Config(config_file) 是调用的
config.py
中的class类函数并实例化。其可以读取 'config_file '文件路径中的参数文件并配置参数。 - 'config_file '文件路径,涉及了 标签率
args.labelrate
和 数据集args.dataset
.通过原文可以知道是三个标签率(即每类20、40、60个标签节点)
六个数据集(见《初了解一》) - 区别:import argparse 和 import configparser,↙️具体见链接。
- 可以理解为都是参数配置的模块,两者并非不可互相替代。
- 显著区别是使用方法不同。通俗来讲,
configparser更像是把参数进行分类归纳整理在一个.ini
的文件中,通过读取文件的方式获得文件中的各项参数。
argparse 更像是机器的开关,在开机运作的时候设置各项功能。
6.参数来源以及对照值
对象 | 读取命令 | 读取文件 | 参数值 |
---|---|---|---|
config.no_cuda | conf.getboolean("Model_Setup", "no_cuda") | 20acm | False |
config.no_seed | getboolean("Model_Setup", "no_seed") | 20acm | False |
config.seed | getint("Model_Setup", "seed") | 20acm | 123 |
- 函数解析(具体见超链接):
np.random.seed()函数:用于生成指定随机数。
torch.manual_seed()函数:CPU生成随机数的种子,方便下次复现实验结果
torch.cuda.manual_seed()函数:固定生成随机数的种子,使得每次运行该 .py 文件时生成的随机数相同
- cuda–至结束。含义为是否使用cuda以及有可用的cuda,如果使用cuda则设置随机数种子123,否则使用cpu设置随机数种子123.
二、数据读取
sadj, fadj = load_graph(args.labelrate, config)
features, labels, idx_train, idx_test = load_data(config)
- fadj是特征图矩阵 【3025,3025】–> 在acm中有7282个非零值
- sadj是结构图矩阵 【3025,3025】–>有26256个非零值
- features是特征向量—>(3025, 1870)
- labels是标签向量 —>(3025,1)
- idx_train是训练数据索引 -->(1000,)
- idx_test是测试数据索引 -->(60,)
以上数据结构以acm数据为例。且都是torch中的结构。该函数在utils.py中,并在《utils》该篇中详解。
三、模型准备
- 模型实例化
- 如果cuda可用,将数据放置到cuda上。
model = SFGCN(nfeat = config.fdim,
nhid1 = config.nhid1,
nhid2 = config.nhid2,
nclass = config.class_num,
n = config.n,
dropout = config.dropout)
if cuda:
model.cuda()
features = features.cuda()
sadj = sadj.cuda()
fadj = fadj.cuda()
labels = labels.cuda()
idx_train = idx_train.cuda()
idx_test = idx_test.cuda()
optimizer = optim.Adam(model.parameters(), lr=config.lr, weight_decay=config.weight_decay)
四、模型训练
acc_max = 0 #当前最大精度
f1_max = 0 #‘当前’的f1分数
epoch_max = 0 #‘当前’的epoch
for epoch in range(config.epochs):
loss, acc_test, macro_f1, emb = train(model, epoch)
if acc_test >= acc_max:
acc_max = acc_test
f1_max = macro_f1
epoch_max = epoch
print('epoch:{}'.format(epoch_max),
'acc_max: {:.4f}'.format(acc_max),
'f1_max: {:.4f}'.format(f1_max))
解释说明:
- 调用函数
train(model, epoch)
返回loss(损失),acc_test(),macro_f1(F1得分),emb(). train()函数在附录A中详解。 - if 语句的含义,如果返回的acc_test比当前记录的acc_max值大,则用本次的结果更新acc_max,f1_max,epoch_max.并将这次记录通过print输出。
附录–main.py:
main.py中定义的函数train()和test()
A train()
def train(model, epochs):
model.train()# 调用model中的train()函数
optimizer.zero_grad()#把梯度置零,也就是把loss关于weight的导数变成0.
output, att, emb1, com1, com2, emb2, emb= model(features, sadj, fadj)
loss_class = F.nll_loss(output[idx_train], labels[idx_train])
loss_dep = (loss_dependence(emb1, com1, config.n) + loss_dependence(emb2, com2, config.n))/2
loss_com = common_loss(com1,com2)
loss = loss_class + config.beta * loss_dep + config.theta * loss_com
acc = accuracy(output[idx_train], labels[idx_train])
loss.backward()# 向后传播
optimizer.step()# 参数优化
# 参数优化后调用测试模型,将返回的结果print输出
acc_test, macro_f1, emb_test = main_test(model)
print('e:{}'.format(epochs),
'ltr: {:.4f}'.format(loss.item()),
'atr: {:.4f}'.format(acc.item()),
'ate: {:.4f}'.format(acc_test.item()),
'f1te:{:.4f}'.format(macro_f1.item()))
return loss.item(), acc_test.item(), macro_f1.item(), emb_test
解释说明:
-
optimizer.zero_grad,将loss的导数设置为0.
-
model(features, sadj, fadj)的输出结果为:
-
F.nll_loss:最大似然 / log似然代价函数
a k a_k ak表示第k个神经元的输出值, y k y_k yk表示第k个神经元对应的真实值,取值为0或1。 -
loss_dependence(),common_loss()见本文的附-utils.
-
loss为:(暂时未知??)
-
loss.item():item()仅可用于torch.Tensor的一维情况。测试
print(type(torch.tensor([3]).item())) print(type(torch.tensor([3.2]).item()))
out:
<class 'int'> <class 'float'>
B main_test()
def main_test(model):
model.eval()
output, att, emb1, com1, com2, emb2, emb = model(features, sadj, fadj)
acc_test = accuracy(output[idx_test], labels[idx_test])
label_max = []
for idx in idx_test:
label_max.append(torch.argmax(output[idx]).item())
labelcpu = labels[idx_test].data.cpu()
macro_f1 = f1_score(labelcpu, label_max, average='macro')
return acc_test, macro_f1, emb
解释说明:
附:form utils import *
[1]utils–loss_dependence
def loss_dependence(emb1, emb2, dim):
R = torch.eye(dim).cuda() - (1/dim) * torch.ones(dim, dim).cuda()
K1 = torch.mm(emb1, emb1.t())
K2 = torch.mm(emb2, emb2.t())
RK1 = torch.mm(R, K1)
RK2 = torch.mm(R, K2)
HSIC = torch.trace(torch.mm(RK1, RK2))
return HSIC
解释说明:
- 相关函数
- torch.eye:生成单位矩阵。
- torch.ones:生成全1矩阵。
- emb1.t中
.t
表示转职。- torch.mm表示二维矩阵乘法(非点乘)。
- torch.trace()返回输入二维矩阵的对角线元素的总和
- 设dim=N,则R=E-1/N[ones].假设N=4.R则为
3/4 | -1/4 | -1/4 | -1/4 |
---|---|---|---|
-1/4 | 3/4 | -1/4 | -1/4 |
-1/4 | -1/4 | 3/4 | -1/4 |
-1/4 | -1/4 | -1/4 | 3/4 |
- 设A = emb1, B= emb2.则
H S I C = [ R ∗ ( A ∗ A T ) ] ∗ [ R ∗ ( B ∗ B T ) ] HSIC = [R \ast(A \ast A^T)] \ast [R \ast (B \ast B^T)] HSIC=[R∗(A∗AT)]∗[R∗(B∗BT)]
其中, K 1 = A ∗ A T = [ a i j 2 ] N × N K1=A\ast A^T = [a_{ij}^2]_{N\times N} K1=A∗AT=[aij2]N×N.意为K1矩阵每个元素为矩阵A的每个元素的平方。 - 功能:暂时未知。
[2]utils–common_loss()
def common_loss(emb1, emb2):
emb1 = emb1 - torch.mean(emb1, dim=0, keepdim=True)
emb2 = emb2 - torch.mean(emb2, dim=0, keepdim=True)
emb1 = torch.nn.functional.normalize(emb1, p=2, dim=1)
emb2 = torch.nn.functional.normalize(emb2, p=2, dim=1)
cov1 = torch.matmul(emb1, emb1.t())
cov2 = torch.matmul(emb2, emb2.t())
cost = torch.mean((cov1 - cov2)**2)
return cost
-
相关函数:
- torch.mean:求均值.torch.mean(a,axis=0,keepdim=True).if :a.shape=(N,M),so mean.shape=(1,M).注意都是二维结构。如果keepdim=False,so mean.shape=(M,).
- torch.nn.functional.normalize : 进行L-p范数的标准化。不明白看链接。
- torch.matmul:torch.Tensor[张量]的乘法。二维的时候是矩阵乘法,可以支持广播机制。因此支持多维数据的乘法。
-
设emb1=A,emb2=B
step1 : A = A − [ [ a ˉ 1 , . . . , a ˉ N ] ] , w h e r e a ˉ i = ∑ j = 1 N a i j A=A-[[\bar{a}_1,...,\bar{a}_N]],where \ \bar{a}_i=\sum_{j=1}^N{a_{ij}} A=A−[[aˉ1,...,aˉN]],where aˉi=∑j=1Naij,利用广播机制相减
step2: A的标准化—A的每行求和,每个元素除以行和。
step3: c o v 1 = A ∗ A T → cov1 = A\ast A^T \rightarrow cov1=A∗AT→ A的每个元素求平方
step4: 同理对于B的操作
step5 :
[ ( A ∗ A T ) − ( B ∗ B T ) ] 2 = ( ( a 11 2 − b 11 2 ) 2 ( a 12 2 − b 12 2 ) 2 ⋯ ( a 1 n 2 − b 1 n 2 ) 2 ⋮ ⋮ ⋱ ⋮ ( a n 1 2 − b n 1 2 ) 2 ( a n 2 2 − b n 2 2 ) 2 ⋯ ( a n n 2 − b n n 2 ) 2 ) [(A\ast A^T)-(B\ast B^T)]^2=\begin{pmatrix} (a_{11}^2-b_{11}^2)^2 & (a_{12}^2-b_{12}^2)^2 & \cdots & (a_{1n}^2-b_{1n}^2)^2 \\ \vdots & \vdots & \ddots & \vdots \\ (a_{n1}^2-b_{n1}^2)^2 & (a_{n2}^2-b_{n2}^2)^2 & \cdots & (a_{nn}^2-b_{nn}^2)^2 \\ \end{pmatrix} [(A∗AT)−(B∗BT)]2=⎝⎜⎛(a112−b112)2⋮(an12−bn12)2(a122−b122)2⋮(an22−bn22)2⋯⋱⋯(a1n2−b1n2)2⋮(ann2−bnn2)2⎠⎟⎞
step6:torch.mean { [ ( A ∗ A T ) − ( B ∗ B T ) ] 2 } \{ [(A\ast A^T)-(B\ast B^T)]^2 \} {[(A∗AT)−(B∗BT)]2},由于默认axis=None,即对矩阵内的所有元素求平均。结果为 ∑ j = 1 N ∑ j = 1 N ( ( a i j 2 − b i j 2 ) 2 ] N 2 \frac{\sum_{j=1}^N\sum_{j=1}^N( (a_{ij}^2-b_{ij}^2)^2 ]}{N^2} N2∑j=1N∑j=1N((aij2−bij2)2]