图神经网络学习01:图卷积神经网络GCN实战解决论文分类问题(tensorflow实现)

图神经网络学习01:图卷积神经网络GCN实战解决论文分类问题(tensorflow实现)

前言

​ 关于最近兴起的图神级网络,博主是在一次旁听长达多个小时的论文汇报中听到的,当然由于那时博主处于对深度学习只知道概念,而不知道具体是怎么实现,所以我天马行空了整场。。。跳过,最近经过一段时间对于深度学习的学习,博主膨胀了,出于好奇心,我开始了对于图神经网络的学习,今天介绍的是图卷积神经网络GCN。

​ 图卷积神经网络,顾名思义就是对于图(数学意义上的),进行我们类似在图片上的卷积操作,聚合各个节点(图中)之间的特征,从而得出我们想要的输出。他处理的问题一般是每个输入的节点之间存在某个关系,可以让他们之间被连接起来(比如本篇博客中的采用的数据集cora,我们输入的每个节点都有其对应的文本向量,然后每个节点都是一篇论文,论文之间存在引用关系,一个论文可以被其他多个论文引用,也就是相当于每个节点的度可能超过一),那么GCN在这里执行的任务就是将每个节点的特征用图节点之间连接的关系聚合起来(有很多种聚合方式,这里我们之后介绍),然后最终输出的数据的channel,由我们决定(类似我们使用的CNN)

1.数据集的介绍

​ 本次数据集使用的是cora数据集他含有2708篇论文的特征,和这些论文之间的互相引用关系,这里我们可以读写来展示一下:

​ cora.content文件

data=pd.read_csv('cora/cora.content',sep = '\t',header=None)
data.head()

在这里插入图片描述

其中每一行代表的是一个论文,第一列是论文的编号,每个论文拥有一个1433维的向量,这个向量中每一个索引对应的是一个关键单词,如果是0代表该单词没有出现,1则代表该关键词出现在论文中,在最后一列则是我们论文的标签每个论文被分类为七类,这里我们可以展示一下

label=data.iloc[:,-1]
label.value_counts()#切片整个数据的最后一列,然后我们查看最后一列每个数据类数

在这里插入图片描述

可以看到有七类其中关于神经网络的最多有818个(可见火热),这就是我们的真值,我们的输入可不止每一个论文的文本向量,还有他们论文之间的关系,这里我们查看另一个文件,cora.cite

cite=pd.read_csv('cora/cora.cites',sep = '\t',header=None)
cite

在这里插入图片描述

可以看到这是一个拥有5429行的数据,其中每一行代表的意义是,被引用论文-引用论文,比如 35 103482表示的是编号为103482的论文中引用了我们编号为35的论文,我们要根据每个论文之间的引用关系去构造一个图。

2.网络的搭建

​ 整个图神经网络传导公式区别于一般的神经网络W*x+b,即权重乘以我们的输入再加上偏置,那么我们换算到这里x就是我们会为每个论文编码的文本向量。

​ 但是我们这里的X还要经过处理,我们将所有文本看成节点,每个节点的值是对应的文本向量,当两个论文之间存在引用关系,我们连接这两个节点,那么在数学上我们表示图一般会使用邻接矩阵(用A来表示),来表达图之间是否连接(这里我使用无向图),那么聚合每个节点的特征我们就可以用邻接矩阵来聚合:

​ x_new=A*x,这里我们假设x为(n,1433),那么A作为邻接矩阵他的维度就是(n,n),所以这样的矩阵形状仍然是(n,1433)。可以看到经过这样我们提取到了一个全新的矩阵,该矩阵聚合了每个节点与之相邻节点的特征,但是这样提取过于粗糙:

​ 1.我们根据引用关系创造的图的邻接矩阵对角线上都为0(毕竟数据上不会写自己引用自己),所以我们这样提取特征会每个节点自己自带的特征

  1. 聚合出来的数据并未使用归一化,这样会严重影响模型训练,可能会出现梯度消失与梯度爆炸的效果。

​ 所以最终学者们提出可以用一种经过特殊处理的矩阵来聚合我们输入x的特征
在这里插入图片描述

​ 其中我们的A是邻接矩阵,D是图的度矩阵,是一个对角矩阵,每个值,记录了对应节点的度数,I是单位矩阵,加上他是为了聚合每个节点对应的自己的特征,所以我们最终打算用来聚合输入特征的矩阵是Lsym,那么我们的想法也就很简单了,网络前向传播公式如下,y=Lsym* x * w+b。

​ 说了这么多了接下来就开始写代码定义图卷积神经网络,如下

class GraphConv(layers.Layer):
    def __init__(self, num_outputs, A,activation="softmax"):
        super(GraphConv, self).__init__()
        self.num_outputs = num_outputs
        self.activation_function = activation
        self.A = tf.Variable(A, trainable=False)
    #A是我们最终用来聚合输入特征的矩阵
    def build(self, input_shape):
        # Weights
        self.W = self.add_weight("W", shape=[int(input_shape[-1]), self.num_outputs])
        self.W=tf.cast(self.W,tf.float32)
        # bias
        self.bias = self.add_weight("bias", shape=[self.num_outputs])
        self.bias=tf.cast(self.bias,tf.float32)

    def call(self, input):
        if self.activation_function == 'relu':
            return tf.nn.relu(tf.matmul(tf.matmul(self.A, input), self.W) + self.bias)
        else:
            return tf.nn.softmax(tf.matmul(tf.matmul(self.A, input), self.W) + self.bias)

这里我只用一层图卷积神经网络,所以最后就直接softmax激活,输出多分类值

3.数据的处理

网络定义好了,接下来我们开始对于数据的处理,首先提取出我们的标签并转化为数字

1.将标签提取出来并处理为one-hot编码

label=data.iloc[:,-1]
x=np.unique(label)
label_to_index=dict((k,v) for v,k in enumerate(x))#创造单词转换字典
def appply_label(label):
    return label_to_index.get(label)
label_index=label.apply(appply_label)
label_index=keras.utils.to_categorical(label_index)
#这里将标签转换为one-hot编码
label_index=np.array(label_index)
label_index
#可以看到最终所有标签都被用one-hot编码表示
array([[0., 0., 1., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 1., 0.],
       [0., 0., 0., ..., 1., 0., 0.],
       ...,
       [0., 1., 0., ..., 0., 0., 0.],
       [1., 0., 0., ..., 0., 0., 0.],
       [0., 0., 1., ..., 0., 0., 0.]], dtype=float32)

标签提取好了,那就要转换到我们对于输入特征的处理,以及上文提到的提取特征的矩阵的制作

2.输入特征的处理

feature=data.iloc[:,1:-1]
feature.shape
(2708, 1433)

​ 我们这里利用切片获取特征,获取从第2列到最后一列前的所有数据,可以看到从输出形状,我们截取的是对的,然后转换成ndarray格式与数据格式

feature_np=np.array(feature)
feature_np=tf.cast(feature_np,tf.float32)

3.聚合特征矩阵Lsym的制作

我们读取cora.cite文件,然后开始接下来的操作

cite=pd.read_csv('cora/cora.cites',sep = '\t',header=None)
#cite文件含有每个论文之间的引用关系,我们要创造一个图这个就相当于一个告诉我们边的信息
new_id=list(data.index)
id = list(data[0])
m1=zip(id,new_id)
m1=dict(m1)
#因为论文的编号都不是按照2708的顺序排序,所以我将论文编号与数据编号从1到2708之间进行映射
nodes_num=data.shape[0]
adjestive_matrix=np.zeros((nodes_num,nodes_num),dtype=np.float)
for i,j in zip(cite[0],cite[1]):
    i=m1.get(i)
    j=m1.get(j)
    adjestive_matrix[i][j] = 1
    adjestive_matrix[j][i] = 1
	#制作邻接矩阵,根据提供的论文引用关系我们对邻接矩阵赋值

邻接矩阵制作好了,那么我们接下来开始制作下面这个矩阵

在这里插入图片描述

#制作度矩阵
D = np.array(np.sum(adjestive_matrix, axis=0))
D=np.array(np.diag(D))
#这里直接求D**(-1/2)会出现inf等无法计算的字符影响,所以我这里换了种方法参考https://blog.csdn.net/qq_23947237/article/details/88813353
i=np.eye(adjestive_matrix.shape[0])
A_=adjestive_matrix+i
D_=D+i#这里让两个矩阵都先加上单位矩阵
from numpy import linalg as la

# v 为特征值    Q 为特征向量
v, Q = la.eig(D)
# print(v)
V = np.diag(v**(-0.5))
# print(V)
T = Q * V * la.inv(Q)
#最终求出来的T就是D**(-0.5)这样求出来的矩阵类数值不会出现inf
lapician=T*A_*T
lapician

在这里插入图片描述

可以看到这是一个被归一化后的对角矩阵,他的最大值也的确是1

np.max(lapician)
1.0

4.模型的初始化以及训练结果的分析

关于数据集,我们都已经预处理好了那么我们接下来直接开始模型的搭建

net=GraphConv(7,lapician)
#初始化定义图卷积神经网络,给出最终输出的类别数和聚合特征的矩阵
net.build(input_shape=(2708,1433))
#告诉我们的网络输入数据的形状让他可以根据这个初始化权重与偏置
model=keras.Sequential()
model.add(net)
#图卷积神经网络我定义的只是一层,不是模型,所以我这里将该层包装进一个模型里去

然后开始我们自定义训练步骤,对于每次的准确率以及损失函数定义如下

label_index=tf.cast(label_index,tf.float32)
loss_object = tf.keras.losses.CategoricalCrossentropy()
#one-hot编码计算损失采用CategoricalCrossentropy
label_index=keras.utils.to_categorical(label_index)
train_acc=keras.metrics.CategoricalAccuracy(name='train_accuracy')
train_loss=keras.metrics.Mean(name='train_loss')
optimizer=keras.optimizers.Adam(0.1)
#这里的学习率达到0.1是经过调试的结果

定义训练步骤如下

@tf.function
def train_step(feature_np,labels):
    with tf.GradientTape() as t:
        pred=net(feature_np)
        loss_step=loss_object(labels,pred)
    gradies=t.gradient(loss_step,net.trainable_variables)#求解梯度
    optimizer.apply_gradients(zip(gradies,net.trainable_variables))#将梯度应用于优化器从而让模型的可训练参数改变
    train_loss(loss_step)
    train_acc(labels,pred)

然后开始训练

Epoch=10
for epoch in range(Epoch):
    train_loss.reset_states()
    train_acc.reset_states()
    train_step(feature_np,label_index)
    print('-',end='')#标志训练完一个batch
    print('>')
    template = 'Epoch {:.3f}, Loss: {:.3f}, Accuracy: {:.3f}'
    print (template.format(epoch+1,
                           train_loss.result(),
                           train_acc.result()*100
                           ))
        
    

训练结果如下

在这里插入图片描述

​ 可以看到正确率上升的非常快,十次正确率就达到了84.74,最终训练了四十次达到了

在这里插入图片描述

当然最后肯定是学习率过大了,但是99.52我感觉正确率已经够高了,就没有在继续训练下去(这里我只用了单层图卷积神经网络),那么接下来我们可以随机查看结果是否相同(由于预测的只是种类,最终需要的预测值只是一个编号,所以我们查看模型的预测值,是否和原标签一样就可以了),用下列代码随机抽取并查看分类是否相同

choice1=np.random.choice(range(2708))
np.argmax(model(feature_np)[choice1]),np.argmax(label_index[choice1])

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

​ 可以看到通过我们的随机验证,预测值和真实值每次都是一模一样,说明我们到的模型搭建的较为成功。

5.结束语

在本篇博客中,博主完成了对于cora数据的预处理,GCN网络的自定义,并且初始化了模型并训练最终得到了不错的结果。
但这里其实有个问题,博主在这里并没有划分测试与训练集,其实是有划分过但是不知什么原因出现梯度消失爆炸等原因,所以后面就没有展示这方面的代码。

在这里插入图片描述

这里我划分的方法就是在训练的时候只计算训练集的损失,不计算测试集,可能哪里有问题希望有比较会的大佬可以赐教,有任何建议或者疑问欢迎评论区交流,谢谢!

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值