import torch.nn as nn
import torch
import torch.nn.init as init
import scipy.sparse as sp
import numpy as np
import torch.nn.functional as F
import torch.optim as optim
from random import shuffle
class GraphConvolutionLayerWithAttention(nn.Module):
'''定义一个图卷积层'''
def __init__(self, nodes_num, input_dim, output_dim, use_bias = True):
'''
节点输入特征维度
:param input_dim: int
输出特征维度
:param output_dim: int
是否使用偏置项
:param use_bias: bool, optional
'''
# 构造函数
# 按照框架继承了nn.Module类,就必须首先调用父类的构造函数
super(GraphConvolutionLayerWithAttention, self).__init__()
# 声明网络结构和参数
self.input = input_dim
self.labels = output_dim
self.use_bias = use_bias
self.weight = nn.Parameter(torch.Tensor(input_dim, output_dim))
self.attention = nn.Parameter(torch.Tensor(nodes_num, nodes_num))
if self.use_bias:
self.bias = nn.Parameter(torch.Tensor(output_dim))
else:
self.register_parameter('bias', None)
self.reset_parameters()
def reset_parameters(self):
# 正态分布初始化
init.kaiming_uniform_(self.weight)
init.kaiming_uniform_(self.attention)
if self.use_bias:
# 零值初始化
init.zeros_(self.bias)
def forward(self, adjacency, input_feature):
'''
邻接矩阵
邻接矩阵是稀疏矩阵,因此在计算时使用稀疏矩阵乘法。
:param adjacency: torch.sparse.FloatTensor
输入特征
:param input_feature: torch.Tensor
:return:
'''
# 前向传播
# 矩阵乘法用torch.mm哈达玛积用torch.mul或*
support = torch.mm(input_feature, self.weight)
output = torch.mm(self.attention * adjacency, support)
#output = torch.sparse.mm(adjacency, support)
if self.use_bias:
output += self.bias
return output
class GCN_ATT(nn.Module):
def __init__(self, BatchNodesNum, dims, neighbor_k):
super(GCN_ATT, self).__init__()
self.BatchNodesNum = BatchNodesNum
self.gcns = []
assert len(dims)-1 >= neighbor_k
for i in range(len(dims)-1):
gcn = GraphConvolutionLayerWithAttention(BatchNodesNum, dims[i], dims[i+1])
self.gcns.append(gcn)
self.neighbor_k = neighbor_k
def forward(self, adjacency, feature):
h = feature
for i in range(len(dims)-1):
h = F.relu(self.gcns[i](adjacency, h))
return h[0] #以第一行作为本次Batch的中心目标node
def LoadByIndices(url, Indices):
# 逐个按照idx加载数据文件,存入BatchSamples并返回。
BatchSamples = []
for idx in Indices:
file = url + str(idx) + '.npy'
BatchSamples.append(np.load(file))
return BatchSamples
def normalization(adjacency):
'''计算 L = A+I'''
# 构造单位阵
adjacency += sp.eye(adjacency.shape[0])
# tocoo将矩阵转化为scipy.coo_matrix稀疏矩阵
return adjacency.tocoo()
# 邻域采样阶数
neighbor_k = 2
# 输入点属性的维度
input_dim = 25
# 模型各层的维度
dims = [input_dim, 16, 2]
# 采样得到的点的总数
BatchNodeNum = 100
# 超参数定义
learning_rate = 0.1
weight_decay = 5e-4
epochs = 200
# 模型定义,包括模型实例化、损失函数与优化器定义
device = "cuda" if torch.cuda.is_available() else "cpu"
model = GCN_ATT(BatchNodeNum, dims, neighbor_k).to(device)
# 损失函数使用交叉熵
criterion = nn.CrossEntropyLoss().to(device)
# 优化器使用Adam
optimizer = optim.Adam(model.parameters(), lr=learning_rate, weight_decay=weight_decay)
# 加载数据,并转换为torch.Tensor
#dataset = CoraData().data
# 样本总数
AllSampleNum = 20000
train_per = 0.3
train_sample_num = int(AllSampleNum*train_per)
indices = np.random.permutation(AllSampleNum)
train_indices = indices[:train_sample_num]
test_indeces = indices[train_sample_num:]
# 样本加载地址
samples_url = 'D:\\Samples\\Sample'
adjacencys_url = 'D:\\Adjacencys\\Adjacencys'
y_url = 'D:\\y\\y'
def train():
loss_history = []
val_acc_history = []
train_y = LoadByIndices(y_url, train_indices)
test_y = LoadByIndices(y_url, test_indeces)
for epoch in range(epochs):
# 每个epoch开始时,对样本索引进行重新打散
shuffle(train_indices)
for idx in train_indices:
tensor_x = torch.from_numpy(LoadByIndices(samples_url, idx)[0])
tensor_adjacency = torch.from_numpy(LoadByIndices(adjacencys_url, idx)[0])
normal_adjacency = normalization(tensor_adjacency)
logits = model(normal_adjacency, tensor_x)# 前向传播
loss = criterion(logits, train_y[idx])
# 梯度归零
optimizer.zero_grad()
# 反向传播计算梯度
loss.backward()
# 更新参数
optimizer.step()
train_acc = test(train_indices, train_y) # 计算当前模型在训练集上的准确率
val_acc = test(test_indeces, test_y) # 计算当前模型在验证集上的准确率
# 记录训练过程中的损失值和准确率变化,用于画图
loss_history.append(loss.item())
val_acc_history.append(val_acc.item())
print('Epoch {:03d}: Loss{:.4f}, TrainAcc{:.4}, ValAcc{:.4f}'.format(epoch, loss.item(), train_acc.item(), val_acc.item()))
return loss_history, val_acc_history
def test(indices_now, y):
model.eval()
acc = []
with torch.no_grad():
for idx in indices_now:
tensor_x = torch.from_numpy(LoadByIndices(samples_url, idx)[0])
tensor_adjacency = torch.from_numpy(LoadByIndices(adjacencys_url, idx)[0])
normal_adjacency = normalization(tensor_adjacency)
logits = model(normal_adjacency, tensor_x)
predict_y = logits.max(1)[1]
acc.append(torch.eq(predict_y, y[idx]).float())
accuracy = np.mean(acc)
return accuracy
小批量样本训练的GCN
于 2020-07-21 12:01:08 首次发布