Cora数据集由2708份论文名称,及对应的特征向量组成,分成了七大类,分别是,Case_Based、Genetic_Algorithms、Neural_Networks、Probabilistic_Methods、Reinforcement_Learning、Rule_Learning、Theory。特征向量由1433个独特的单词组成,由01值描述,表示论文中这些单词的频数是否小于10,小于为0,大于为1。该数据集中,每一篇论文至少引用了该数据集里面另外一篇论文或者被另外一篇论文引用。
数据集包含两个文件,分别是Content文件和Cites文件。
包含2708行,代表2708条论文的数据,每一行由 “< paper id > + < word attribution > + < class label >” 组成,一共1435列
包含5429行,两列,< id of cited paper > + < id of citing paper >
< id of cited paper > 表示被引用论文的编号
< id of citing paper> 表示引用论文的编号
import numpy as np
import scipy.sparse as sp # 稀疏矩阵
def load_data(path="../data/cora/", dataset="cora"):
print('Loading {} dataset...'.format(dataset))
# content数据加载
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)
# 获取标签
labels = encode_onehot(idx_features_labels[:, -1])
# 搭建图
idx = np.array(idx_features_labels[:, 0], dtype=np.int32)
# 搭建字典,论文编号-->索引
idx_map = {j: i for i, j in enumerate(idx)}
# cites数据加载,shape:5429,2
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)
# 转换为对称邻接矩阵
adj = adj + adj.T.multiply(adj.T > adj) - adj.multiply(adj.T > adj)
# 归一化特征矩阵和邻接矩阵
features = normalize(features)
adj = normalize(adj + sp.eye(adj.shape[0]))
# 设置训练、验证和测试的数量
idx_train = range(140)
idx_val = range(200, 500)
idx_test = range(500, 1500)
# 转为Tensor格式
features = torch.FloatTensor(np.array(features.todense()) # toarray返回ndarray; todense返回矩阵
labels = torch.LongTensor(np.where(labels)[1])
adj = sparse_mx_to_torch_sparse_tensor(adj)
idx_train = torch.LongTensor(idx_train)
idx_val = torch.LongTensor(idx_val)
idx_test = torch.LongTensor(idx_test)
return adj, features, labels, idx_train, idx_val, idx_test
# adj 2708,2708
# features 2708,1433
# labels 2708, 0~6
class GCN(nn.Module):
def __init__(self, nfeat, nhid, nclass, dropout):
super(GCN, self).__init__()
self.gc1 = GraphConvolution(nfeat, nhid) # 这里nfeat是1433个特征单词向量
self.gc2 = GraphConvolution(nhid, nclass) # 这里nclass是7类
self.dropout = dropout
def forward(self, x, adj):
'''
step1. gc
step2. relu
step3. dropout
step4. gc
step5. log_softmax 并输出
'''
x = F.relu(self.gc1(x, adj))
x = F.dropout(x, self.dropout, training=self.training)
x = self.gc2(x, adj)
return F.log_softmax(x, dim=1)
class GraphConvolution(Module):
"""
Simple GCN layer, similar to https://arxiv.org/abs/1609.02907
"""
def __init__(self, in_features, out_features, bias=True):
super(GraphConvolution, self).__init__()
self.in_features = in_features
self.out_features = out_features
self.weight = Parameter(torch.FloatTensor(in_features, out_features))
if bias:
self.bias = Parameter(torch.FloatTensor(out_features))
else:
self.register_parameter('bias', None)
self.reset_parameters()
def reset_parameters(self):
stdv = 1. / math.sqrt(self.weight.size(1))
self.weight.data.uniform_(-stdv, stdv)
if self.bias is not None:
self.bias.data.uniform_(-stdv, stdv)
# 这里设,特征矩阵feature为H,邻接矩阵adj为A,权重为W,则输出为,
# step1. 求 H 和 W 乘积 HW
# step2. 求 A 和 HW 乘积 AHW,这里 A = D_A ^ -1 · (A + I) 做了归一化, H = D_H^-1 · H
# 对于维度
'''
# adj 2708,2708 A
# features 2708,1433 H0
# labels 2708, 0~6
第一次gc后为2708,nhid
第二次gc后为2708,7 (7个类别)
'''
def forward(self, input, adj):
support = torch.mm(input, self.weight)
output = torch.spmm(adj, support)
if self.bias is not None:
return output + self.bias
else:
return output
def __repr__(self):
return self.__class__.__name__ + ' (' \
+ str(self.in_features) + ' -> ' \
+ str(self.out_features) + ')'
optimizer = optim.Adam(model.parameters(),lr=args.lr, weight_decay=args.weight_decay)
def train(epoch):
t = time.time()
model.train()
optimizer.zero_grad()
output = model(features, adj)
loss_train = F.nll_loss(output[idx_train], labels[idx_train])
acc_train = accuracy(output[idx_train], labels[idx_train])
loss_train.backward()
optimizer.step()
if not args.fastmode:
# Evaluate validation set performance separately,
# deactivates dropout during validation run.
model.eval()
output = model(features, adj)
loss_val = F.nll_loss(output[idx_val], labels[idx_val])
acc_val = accuracy(output[idx_val], labels[idx_val])
print('Epoch: {:04d}'.format(epoch + 1),
'loss_train: {:.4f}'.format(loss_train.item()),
'acc_train: {:.4f}'.format(acc_train.item()),
'loss_val: {:.4f}'.format(loss_val.item()),
'acc_val: {:.4f}'.format(acc_val.item()),
'time: {:.4f}s'.format(time.time() - t))
def test():
model.eval()
output = model(features, adj)
loss_test = F.nll_loss(output[idx_test], labels[idx_test])
acc_test = accuracy(output[idx_test], labels[idx_test])
print("Test set results:",
"loss= {:.4f}".format(loss_test.item()),
"accuracy= {:.4f}".format(acc_test.item()))
def encode_onehot(labels):
classes = set(labels)
classes_dict = {c: np.identity(len(classes))[i, :] for i, c in
enumerate(classes)}
labels_onehot = np.array(list(map(classes_dict.get, labels)),
dtype=np.int32)
return labels_onehot
def normalize(mx):
"""Row-normalize sparse matrix"""
rowsum = np.array(mx.sum(1))
r_inv = np.power(rowsum, -1).flatten()
r_inv[np.isinf(r_inv)] = 0.
r_mat_inv = sp.diags(r_inv)
mx = r_mat_inv.dot(mx)
return mx
def accuracy(output, labels):
preds = output.max(1)[1].type_as(labels)
correct = preds.eq(labels).double()
correct = correct.sum()
return correct / len(labels)
def sparse_mx_to_torch_sparse_tensor(sparse_mx):
"""Convert a scipy sparse matrix to a torch sparse tensor."""
sparse_mx = sparse_mx.tocoo().astype(np.float32)
indices = torch.from_numpy(
np.vstack((sparse_mx.row, sparse_mx.col)).astype(np.int64))
values = torch.from_numpy(sparse_mx.data)
shape = torch.Size(sparse_mx.shape)
return torch.sparse.FloatTensor(indices, values, shape)
...
Epoch: 0188 loss_train: 0.4158 acc_train: 0.9200 loss_val: 0.7011 acc_val: 0.7886 time: 0.0070s
Epoch: 0189 loss_train: 0.4453 acc_train: 0.9467 loss_val: 0.6996 acc_val: 0.7914 time: 0.0080s
Epoch: 0190 loss_train: 0.4042 acc_train: 0.9400 loss_val: 0.6973 acc_val: 0.7914 time: 0.0070s
Epoch: 0191 loss_train: 0.4443 acc_train: 0.9267 loss_val: 0.6966 acc_val: 0.7914 time: 0.0070s
Epoch: 0192 loss_train: 0.4082 acc_train: 0.9400 loss_val: 0.6956 acc_val: 0.7914 time: 0.0070s
Epoch: 0193 loss_train: 0.3909 acc_train: 0.9467 loss_val: 0.6937 acc_val: 0.7971 time: 0.0080s
Epoch: 0194 loss_train: 0.4289 acc_train: 0.9333 loss_val: 0.6921 acc_val: 0.7971 time: 0.0070s
Epoch: 0195 loss_train: 0.4092 acc_train: 0.9267 loss_val: 0.6898 acc_val: 0.8000 time: 0.0070s
Epoch: 0196 loss_train: 0.3790 acc_train: 0.9600 loss_val: 0.6882 acc_val: 0.8029 time: 0.0080s
Epoch: 0197 loss_train: 0.3813 acc_train: 0.9467 loss_val: 0.6867 acc_val: 0.8000 time: 0.0070s
Epoch: 0198 loss_train: 0.4018 acc_train: 0.9533 loss_val: 0.6858 acc_val: 0.8057 time: 0.0080s
Epoch: 0199 loss_train: 0.3730 acc_train: 0.9467 loss_val: 0.6847 acc_val: 0.8114 time: 0.0070s
Epoch: 0200 loss_train: 0.3693 acc_train: 0.9600 loss_val: 0.6845 acc_val: 0.8143 time: 0.0080s
Optimization Finished!
Total time elapsed: 1.7434s
Test set results: loss= 0.7244 accuracy= 0.8230
https://github.com/tkipf/pygcn