基于图卷积神经网络的社交网络分类实验报告
一、实验目的
二、实验内容和原理
Karate club 是一个社交网络,包括 34 个成员,并在俱乐部外互动的成员之间建立成对链接。 俱乐部随后分为两个社区,由教员(节点 0)和俱乐部主席(节点 33)领导。 网络以如下方式可视化,并带有表示社区的颜色(如下图)。
任务:预测给定社交网络本身每个成员倾向于加入哪一侧的社区(0 或 33)。
我们在这个实验里面将使用 GCN 神经网络进行预测。GCN 是一种可以有效地对图进行提取的神经网络,我们利用这一特性来完成俱乐部社交网络的分类。GCN,图卷积神经网络,实际上跟CNN的作用一样,就是一个特征提取器,只不过它的对象是图数据。GCN精妙地设计了一种从图数据中提取特征的方法,从而让我们可以使用这些特征去对图数据进行节点分类(node classification)、图分类(graph classification)、边预测(link prediction),还可以顺便得到图的嵌入表示(graph embedding),可见用途广泛。
三、实验步骤
1.首先导入我们实验所需要的包(包括 networkx、dgl、matplotlib、itertools、torch 和 numpy)。
代码如下:
import dgl
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.nn.functional as F
import matplotlib.animation as animation
from dgl.nn.pytorch import GraphConv
import itertools
2.载入数据,使用 dgl 构造图。(因为在 dgl 里的边是有向边,所以构造无向边时要添加两条有向边)。可以选择加载图进行显示。
代码和解释如下:
def build_karate_club_graph():
# All 78 edges are stored in two numpy arrays. One for source endpoints
# while the other for destination endpoints.
a = b = []
f = open("Karate_Club.txt","r")
data = f.readlines()
for i in data:
c = i.split()
a.append(int(c[0]))
b.append(int(c[1]))
src = np.array(a)
dst = np.array(b)
# Edges are directional in DGL; Make them bi-directional.
u = np.concatenate([src, dst])
v = np.concatenate([dst, src])
# Construct a DGLGraph
return dgl.DGLGraph((u, v))
G = build_karate_club_graph()
# print('We have %d nodes.' % G.number_of_nodes())
# print('We have %d edges.' % G.number_of_edges())
# We have 34 nodes.
# We have 156 edges.
# 由于实际图形是无向的,因此我们去掉边的方向,以达到可视化的目的
nx_G = G.to_networkx().to_undirected()
# 为了图更加美观,我们使用 Kamada-Kawaii layout
pos = nx.kamada_kawai_layout(nx_G)
nx.draw(nx_G, pos, with_labels=True, node_color=[[.7, .7, .7]])
plt.show()
可以得到如下图片:
3.我们先定义一个图卷积网络模型。图卷积模型的具体定义见下图:
我们将 GCN 的卷积层设为两层,两层工作如下:
(1)第一层将大小为 34 的输入特征转换为隐藏的大小为 5。
(2)第二层将隐藏层转换为大小为 2 的输出特征,对应 Karate club 中的两个组。
代码如下:
class GCN(nn.Module):
def __init__(self, in_feats, hidden_size, num_classes):
super(GCN, self).__init__()
self.conv1 = GraphConv(in_feats, hidden_size)
self.conv2 = GraphConv(hidden_size, num_classes)
def forward(self, g, inputs):
#卷积层 1
h = self.conv1(g, inputs)
#采用 ReLu 激活函数
h = torch.relu(h)
#卷积层 2
h = self.conv2(g, h)
return h
4.为节点添加特征,定义好神经网络的输入,建立神经网络
GNN 将特征与节点和边关联进行训练,本题分类中,每个节点对应一个独热编码。在 DGL 中,可通过一个特征向量为所有的节点添加特征,该张量沿着第一维处理。在这里,我们可以使用 nn.embedding 来对数据进行处理。然后用 GCN建立神经网络,
代码和解释如下:
# 对角矩阵
G.ndata['feat'] = torch.eye(34)
# 对 34 个节点做 embedding
# 34 nodes with embedding dim equal to 5
embed = nn.Embedding(34, 5)
G.ndata['feat'] = embed.weight
#定义神经网络输入
inputs = G.ndata['feat']
labeled_nodes = torch.tensor([0, 33])
labels = torch.tensor([0, 1])
#建立神经网络 GCN
net = GCN(5, 5, 2)
#利用 torch 的 adam 算法进行优化,定义一个优化器
optimizer = torch.optim.Adam(itertools.chain(net.parameters(),
embed.parameters()), lr=0.01)
all_logits = []
5.进行训练
我们进行训练,训练 60-80 次,主要是利用非监督学习,利用标记的节点来
计算 loss,然后进行 loss 的后向传播,如此反复 60-80 次,可以打印出 loss
函数的数值来查看训练情况。
for epoch in range(75):
logits = net(G, inputs)
# we save the logits for visualization later
# detach 代表从当前计算图中分离下来的
all_logits.append(logits.detach())
logp = F.log_softmax(logits, 1)
# 半监督学习, 只使用标记的节点计算 loss
loss = F.nll_loss(logp[labeled_nodes], labels)
optimizer.zero_grad()
#loss 的后向传播
loss.backward()
optimizer.step()
print('Epoch %d | Loss: %.4f' % (epoch, loss.item()))
6.定义 draw 函数,以便于打印每次训练后的结果。
def draw(i):
cls1color = '#00FFFF'
cls2color = '#FF00FF'
pos = {}
colors = []
for v in range(34):
pos[v] = all_logits[i][v].numpy()
cls = pos[v].argmax()
colors.append(cls1color if cls else cls2color)
ax.cla()
ax.axis('off')
ax.set_title('Epoch: %d' % i)
nx.draw_networkx(nx_G.to_undirected(), pos,node_color=colors,with_labels=True, node_size=300, ax=ax)
7.进行训练,生成最终的 gif文件。
代码如下:
nx_G = G.to_networkx().to_undirected()
fig = plt.figure(dpi=150)
fig.clf()
ax = fig.subplots()
for i in range(75):
draw(i)
plt.pause(0.2)
ani = animation.FuncAnimation(fig, draw, frames=len(all_logits),interval=200)
ani.save('change1.gif', writer='imagemagick', fps=10)
plt.show()
生成图片如下: