graphSAGE的python实现
时间:2022-07-23
本文章向大家介绍graphSAGE的python实现,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
上一节实现了基础的GCN,
这一节我们继续实现graphSAGE。
加载数据:load_cora.py
import numpy as np
import scipy.sparse as sp
import torch
from sklearn.preprocessing import LabelBinarizer
def normalize_adj(adjacency):
adjacency += sp.eye(adjacency.shape[0])
degree = np.array(adjacency.sum(1))
d_hat = sp.diags(np.power(degree, -0.5).flatten())
return d_hat.dot(adjacency).dot(d_hat).tocoo()
def normalize_features(features):
return features / features.sum(1)
def load_data(path="/content/drive/My Drive/nlpdata/cora/", dataset="cora"):
"""Load citation network dataset (cora only for now)"""
print('Loading {} dataset...'.format(dataset))
idx_features_labels = np.genfromtxt("{}{}.content".format(path,dataset), dtype=np.dtype(str))
features = sp.csr_matrix(idx_features_labels[:, 1:-1], dtype=np.float32)
encode_onehot = LabelBinarizer()
labels = encode_onehot.fit_transform(idx_features_labels[:, -1])
# build graph
idx = np.array(idx_features_labels[:, 0], dtype=np.int32)
idx_map = {j: i for i, j in enumerate(idx)}
edges_unordered = np.genfromtxt("{}{}.cites".format(path, dataset), dtype=np.int32)
edges = np.array(list(map(idx_map.get, edges_unordered.flatten())), dtype=np.int32).reshape(edges_unordered.shape)
adj = sp.coo_matrix((np.ones(edges.shape[0]), (edges[:, 0], edges[:, 1])), shape=(labels.shape[0], labels.shape[0]), dtype=np.float32)
features = normalize_features(features)
adj = normalize_adj(adj)
idx_train = range(140)
idx_val = range(200, 500)
idx_test = range(500, 1500)
features = torch.FloatTensor(np.array(features))
labels = torch.LongTensor(np.where(labels)[1])
num_nodes = features.shape[0]
train_mask = np.zeros(num_nodes, dtype=np.bool)
val_mask = np.zeros(num_nodes, dtype=np.bool)
test_mask = np.zeros(num_nodes, dtype=np.bool)
train_mask[idx_train] = True
val_mask[idx_val] = True
test_mask[idx_test] = True
return adj, features, labels, train_mask, val_mask, test_mask
"""
adj, features, labels, train_mask, val_mask, test_mask= load_data()
print(adj.shape)
print(features.shape)
print(labels.shape)
print(train_mask.shape, val_mask.shape, test_mask.shape)
"""
采样:sampling.py
import numpy as np
def sampling(src_nodes, sample_num, neighbor_table):
"""根据源节点采样指定数量的邻居节点,注意使用的是有放回的采样;
某个节点的邻居节点数量少于采样数量时,采样结果出现重复的节点
Arguments:
src_nodes {list, ndarray} -- 源节点列表
sample_num {int} -- 需要采样的节点数
neighbor_table {dict} -- 节点到其邻居节点的映射表
Returns:
np.ndarray -- 采样结果构成的列表
"""
results = []
for sid in src_nodes:
# 从节点的邻居中进行有放回地进行采样
res = np.random.choice(neighbor_table[sid], size=(sample_num, ))
results.append(res)
return np.asarray(results).flatten()
def multihop_sampling(src_nodes, sample_nums, neighbor_table):
"""根据源节点进行多阶采样
Arguments:
src_nodes {list, np.ndarray} -- 源节点id
sample_nums {list of int} -- 每一阶需要采样的个数
neighbor_table {dict} -- 节点到其邻居节点的映射
Returns:
[list of ndarray] -- 每一阶采样的结果
"""
sampling_result = [src_nodes]
for k, hopk_num in enumerate(sample_nums):
hopk_result = sampling(sampling_result[k], hopk_num, neighbor_table)
sampling_result.append(hopk_result)
return sampling_result
建立模型:grapgsage.py
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.nn.init as init
class NeighborAggregator(nn.Module):
def __init__(self, input_dim, output_dim,
use_bias=False, aggr_method="mean"):
"""聚合节点邻居
Args:
input_dim: 输入特征的维度
output_dim: 输出特征的维度
use_bias: 是否使用偏置 (default: {False})
aggr_method: 邻居聚合方式 (default: {mean})
"""
super(NeighborAggregator, self).__init__()
self.input_dim = input_dim
self.output_dim = output_dim
self.use_bias = use_bias
self.aggr_method = aggr_method
self.weight = nn.Parameter(torch.Tensor(input_dim, output_dim))
if self.use_bias:
self.bias = nn.Parameter(torch.Tensor(self.output_dim))
self.reset_parameters()
def reset_parameters(self):
init.kaiming_uniform_(self.weight)
if self.use_bias:
init.zeros_(self.bias)
def forward(self, neighbor_feature):
if self.aggr_method == "mean":
aggr_neighbor = neighbor_feature.mean(dim=1)
elif self.aggr_method == "sum":
aggr_neighbor = neighbor_feature.sum(dim=1)
elif self.aggr_method == "max":
aggr_neighbor = neighbor_feature.max(dim=1)
else:
raise ValueError("Unknown aggr type, expected sum, max, or mean, but got {}"
.format(self.aggr_method))
neighbor_hidden = torch.matmul(aggr_neighbor, self.weight)
if self.use_bias:
neighbor_hidden += self.bias
return neighbor_hidden
def extra_repr(self):
return 'in_features={}, out_features={}, aggr_method={}'.format(
self.input_dim, self.output_dim, self.aggr_method)
class SageGCN(nn.Module):
def __init__(self, input_dim, hidden_dim,
activation=F.relu,
aggr_neighbor_method="mean",
aggr_hidden_method="sum"):
"""SageGCN层定义
Args:
input_dim: 输入特征的维度
hidden_dim: 隐层特征的维度,
当aggr_hidden_method=sum, 输出维度为hidden_dim
当aggr_hidden_method=concat, 输出维度为hidden_dim*2
activation: 激活函数
aggr_neighbor_method: 邻居特征聚合方法,["mean", "sum", "max"]
aggr_hidden_method: 节点特征的更新方法,["sum", "concat"]
"""
super(SageGCN, self).__init__()
assert aggr_neighbor_method in ["mean", "sum", "max"]
assert aggr_hidden_method in ["sum", "concat"]
self.input_dim = input_dim
self.hidden_dim = hidden_dim
self.aggr_neighbor_method = aggr_neighbor_method
self.aggr_hidden_method = aggr_hidden_method
self.activation = activation
self.aggregator = NeighborAggregator(input_dim, hidden_dim,
aggr_method=aggr_neighbor_method)
self.weight = nn.Parameter(torch.Tensor(input_dim, hidden_dim))
self.reset_parameters()
def reset_parameters(self):
init.kaiming_uniform_(self.weight)
def forward(self, src_node_features, neighbor_node_features):
neighbor_hidden = self.aggregator(neighbor_node_features)
self_hidden = torch.matmul(src_node_features, self.weight)
if self.aggr_hidden_method == "sum":
hidden = self_hidden + neighbor_hidden
elif self.aggr_hidden_method == "concat":
hidden = torch.cat([self_hidden, neighbor_hidden], dim=1)
else:
raise ValueError("Expected sum or concat, got {}"
.format(self.aggr_hidden))
if self.activation:
return self.activation(hidden)
else:
return hidden
def extra_repr(self):
output_dim = self.hidden_dim if self.aggr_hidden_method == "sum" else self.hidden_dim * 2
return 'in_features={}, out_features={}, aggr_hidden_method={}'.format(
self.input_dim, output_dim, self.aggr_hidden_method)
class GraphSage(nn.Module):
def __init__(self, input_dim, hidden_dim,
num_neighbors_list):
super(GraphSage, self).__init__()
self.input_dim = input_dim
self.hidden_dim = hidden_dim
self.num_neighbors_list = num_neighbors_list
self.num_layers = len(num_neighbors_list)
self.gcn = nn.ModuleList()
self.gcn.append(SageGCN(input_dim, hidden_dim[0]))
for index in range(0, len(hidden_dim) - 2):
self.gcn.append(SageGCN(hidden_dim[index], hidden_dim[index+1]))
self.gcn.append(SageGCN(hidden_dim[-2], hidden_dim[-1], activation=None))
def forward(self, node_features_list):
hidden = node_features_list
for l in range(self.num_layers):
next_hidden = []
gcn = self.gcn[l]
for hop in range(self.num_layers - l):
src_node_features = hidden[hop]
src_node_num = len(src_node_features)
neighbor_node_features = hidden[hop + 1]
.view((src_node_num, self.num_neighbors_list[hop], -1))
h = gcn(src_node_features, neighbor_node_features)
next_hidden.append(h)
hidden = next_hidden
return hidden[0]
def extra_repr(self):
return 'in_features={}, num_neighbors_list={}'.format(
self.input_dim, self.num_neighbors_list
)
主函数:main.py
import torch
import numpy as np
import torch.nn as nn
import torch.optim as optim
from graphsage import GraphSage
from sampling import multihop_sampling
from load_cora import load_data
import pickle
import sys
sys.path.append("/content/drive/My Drive/nlpdata/cora/")
INPUT_DIM = 1433 # 输入维度
# Note: 采样的邻居阶数需要与GCN的层数保持一致
HIDDEN_DIM = [128, 7] # 隐藏单元节点数
NUM_NEIGHBORS_LIST = [10, 10] # 每阶采样邻居的节点数
assert len(HIDDEN_DIM) == len(NUM_NEIGHBORS_LIST)
BTACH_SIZE = 16 # 批处理大小
EPOCHS = 100
NUM_BATCH_PER_EPOCH = 20 # 每个epoch循环的批次数
LEARNING_RATE = 0.01 # 学习率
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
adjacency, x, y, train_mask, val_mask, test_mask = load_data()
out = pickle.load(open("/content/drive/My Drive/nlpdata/cora/ind.cora.graph", "rb"), encoding="latin1")
graph = out.toarray() if hasattr(out, "toarray") else out
adjacency_dict = graph
train_index = np.where(train_mask)[0]
train_label = y[train_index]
test_index = np.where(test_mask)[0]
model = GraphSage(input_dim=INPUT_DIM, hidden_dim=HIDDEN_DIM,
num_neighbors_list=NUM_NEIGHBORS_LIST).to(DEVICE)
print(model)
criterion = nn.CrossEntropyLoss().to(DEVICE)
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE, weight_decay=5e-4)
def train():
model.train()
for e in range(EPOCHS):
for batch in range(NUM_BATCH_PER_EPOCH):
batch_src_index = np.random.choice(train_index, size=(BTACH_SIZE,))
batch_src_label = train_label[batch_src_index].long().to(DEVICE)
batch_sampling_result = multihop_sampling(batch_src_index, NUM_NEIGHBORS_LIST, adjacency_dict)
batch_sampling_x = [x[idx].to(DEVICE) for idx in batch_sampling_result]
batch_train_logits = model(batch_sampling_x)
loss = criterion(batch_train_logits, batch_src_label)
optimizer.zero_grad()
loss.backward() # 反向传播计算参数的梯度
optimizer.step() # 使用优化方法进行梯度更新
#print("Epoch {:03d} Batch {:03d} Loss: {:.4f}".format(e, batch, loss.item()))
test()
def test():
model.eval()
with torch.no_grad():
test_sampling_result = multihop_sampling(test_index, NUM_NEIGHBORS_LIST, adjacency_dict)
test_x = [x[idx].to(DEVICE) for idx in test_sampling_result]
test_logits = model(test_x)
test_label = y[test_index].long().to(DEVICE)
predict_y = test_logits.max(1)[1]
accuarcy = torch.eq(predict_y, test_label).float().mean().item()
print("Test Accuracy: ", accuarcy)
if __name__ == '__main__':
train()
运行之后:
Loading cora dataset...
GraphSage(
in_features=1433, num_neighbors_list=[10, 10]
(gcn): ModuleList(
(0): SageGCN(
in_features=1433, out_features=128, aggr_hidden_method=sum
(aggregator): NeighborAggregator(in_features=1433, out_features=128, aggr_method=mean)
)
(1): SageGCN(
in_features=128, out_features=7, aggr_hidden_method=sum
(aggregator): NeighborAggregator(in_features=128, out_features=7, aggr_method=mean)
)
)
)
Epoch 000 Batch 000 Loss: 1.9266
Epoch 000 Batch 001 Loss: 1.8985
Epoch 000 Batch 002 Loss: 1.8120
Epoch 000 Batch 003 Loss: 1.6504
Epoch 000 Batch 004 Loss: 1.7163
Epoch 000 Batch 005 Loss: 1.6697
Epoch 000 Batch 006 Loss: 1.4684
Epoch 000 Batch 007 Loss: 1.2102
Epoch 000 Batch 008 Loss: 1.2884
Epoch 000 Batch 009 Loss: 1.0167
Epoch 000 Batch 010 Loss: 0.9699
Epoch 000 Batch 011 Loss: 0.9754
Epoch 000 Batch 012 Loss: 0.5973
Epoch 000 Batch 013 Loss: 0.8455
Epoch 000 Batch 014 Loss: 0.9120
Epoch 000 Batch 015 Loss: 0.6430
Epoch 000 Batch 016 Loss: 0.7662
Epoch 000 Batch 017 Loss: 0.8074
Epoch 000 Batch 018 Loss: 0.5895
Epoch 000 Batch 019 Loss: 0.4272
Test Accuracy: 0.3270000219345093......
Epoch 098 Batch 000 Loss: 0.0403
Epoch 098 Batch 001 Loss: 0.1180
Epoch 098 Batch 002 Loss: 0.0855
Epoch 098 Batch 003 Loss: 0.0344
Epoch 098 Batch 004 Loss: 0.0437
Epoch 098 Batch 005 Loss: 0.0531
Epoch 098 Batch 006 Loss: 0.0513
Epoch 098 Batch 007 Loss: 0.0787
Epoch 098 Batch 008 Loss: 0.0396
Epoch 098 Batch 009 Loss: 0.0373
Epoch 098 Batch 010 Loss: 0.0398
Epoch 098 Batch 011 Loss: 0.0337
Epoch 098 Batch 012 Loss: 0.0427
Epoch 098 Batch 013 Loss: 0.0315
Epoch 098 Batch 014 Loss: 0.0720
Epoch 098 Batch 015 Loss: 0.0827
Epoch 098 Batch 016 Loss: 0.1221
Epoch 098 Batch 017 Loss: 0.0374
Epoch 098 Batch 018 Loss: 0.0427
Epoch 098 Batch 019 Loss: 0.0373
Test Accuracy: 0.5540000200271606
Epoch 099 Batch 000 Loss: 0.0577
Epoch 099 Batch 001 Loss: 0.0373
Epoch 099 Batch 002 Loss: 0.0462
Epoch 099 Batch 003 Loss: 0.0511
Epoch 099 Batch 004 Loss: 0.0849
Epoch 099 Batch 005 Loss: 0.0571
Epoch 099 Batch 006 Loss: 0.0478
Epoch 099 Batch 007 Loss: 0.0598
Epoch 099 Batch 008 Loss: 0.0376
Epoch 099 Batch 009 Loss: 0.0414
Epoch 099 Batch 010 Loss: 0.0478
Epoch 099 Batch 011 Loss: 0.0321
Epoch 099 Batch 012 Loss: 0.1014
Epoch 099 Batch 013 Loss: 0.0617
Epoch 099 Batch 014 Loss: 0.0529
Epoch 099 Batch 015 Loss: 0.0325
Epoch 099 Batch 016 Loss: 0.0334
Epoch 099 Batch 017 Loss: 0.0432
Epoch 099 Batch 018 Loss: 0.0939
Epoch 099 Batch 019 Loss: 0.0517
Test Accuracy: 0.5260000228881836
参考:
https://github.com/FighterLYL/GraphNeuralNetwork
- 基于TensorFlow实现自编码器(附源码)
- Selenium2+python自动化53-unittest批量执行(discover)
- HTML/CSS/JavaScript学习笔记【持续更新】
- Selenium2+python自动化54-unittest生成测试报告(HTMLTestRunner)
- Selenium2+python自动化55-unittest之装饰器(@classmethod)
- 每天一个Linux命令(4)——mkdir
- 每天一个Linux命令(3)——pwd
- 11-移动端开发教程-zepto.js入门教程
- 【OpenCV学习笔记之一】图像加载,修改及保存
- 【干货】一种直观的方法认识梯度下降
- 漫谈Java IO之普通IO流与BIO服务器
- 浅谈强化学习的方法及学习路线
- 【亲测有效】Win10家庭版Microsoft Edge页面出现乱码的两种解决方案及gpedit.msc命令无法使用的解决策略
- Fiddler抓包7-post请求(json)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- SAP WebClient UI component模型元数据解析工具
- 设计模式之抽象工厂模式
- 测试面试题集锦(四)| Linux 与 Python 编程篇(附答案)
- 设计模式之代理模式
- SQL Server 中的 ROW_NUMBER 函数
- NHibernate 批量数据插入测试
- MvvmCross 框架中 ViewModel 之间的导航以及生命周期
- 在 mono 下尝试 ASP.NET vNext
- 设计模式之享元模式
- 设计模式之外观模式
- 设计模式之装饰器模式
- 设计模式之桥模式
- 笔试编程 | 二分查找、数组、排序
- Spark SQL解析查询parquet格式Hive表获取分区字段和查询条件
- Apache Hive