Save you from anything

0%

在seq2seq结构中插入GCN的一个实验

我之所以开始研究GCN是为了用GCN改良itag,而itag本质上是个seq2seq模型。因此我尝试了使用GCN改良一个最简单的单层seq2seq模型。

(这个实验大约进行于2019年11月)

原理分析

任务背景是为文本推荐多个标签。

seq2seq模型由两个部分组成,一个编码器和一个解码器,模型编码器,将输入的序列(seq)编码为一个隐藏向量,然后解码器接受这个编码后的隐藏向量,输出一个序列(seq)。

而GCN通过在节点间进行信息传播,可以聚合临近节点的信息,使得同类节点的输出趋近。对于tag推荐任务,(理论上)可以提高tag推荐的效果。

设想的实现形式

因为GCN是使得同类节点的输出趋向于接近,而seq2seq模型的解码器在接收到编码序列后就开始进行输出了,而在seq2seq模型的编码器对文本进行编码之前,数据的信息量极低。那么自然而然的,我就想到了在编码器和解码器之间插入GCN。

对文档进行编码后,通过GCN对文本的编码向量进行平滑,使得可能拥有相似tag的文本的编码向量更加接近,然后再使用解码器进行输出。

实验部分

实验前有一个问题需要解决:图的构建方法。同类节点的定义是:拥有相似tag的文本节点。但是这里的一个前提是,已经提前知道所有文档的tag才能构建图。但在实际任务中,显然是无法知道所有文档的tag的,必须使用另一个方案建立文档图,而建图的方法对于GCN的效果有很大的影响。

在这个实验中我选择使用LDA进行图的构建,一方面是因为我不久之前刚知道了LDA这个主题算法(来自TLSTM),理论上,LDA可以捕捉到一定的文本的主题信息,而主题接近的文本拥有相似tag的概率应该也较高。另一方面LDA在TLSTM中被证明对于单tag推荐任务确实有正面作用。

另外一个问题是,这时候我还不知道怎么实现对于GCN的验证和测试,因为一次性需要塞入全部数据的话,验证和测试怎么做嘛(当时的想法),所以实验结果是训练时输出的准确度和召回率。这里又引出一个问题,seq2seq模型的训练的准确度和召回率参考意义根本不大,这涉及到seq2seq模型的工作原理,学习和预测时的行为根本不一样,但我姑且是硬着头皮做了先看看结果。

实验条件

由于GCN需要一次读入所有的数据才能进行运算(所以有了一些采样计算的方法),在进行这个实验的时候就被这点卡住了。我的2070S的显存根本扛不住,好在单层GRU+GCN的结构不算太复杂,即使塞内存运算速度也还能接受。但是即使这样也只使用了一部分数据集。

数据集:ask Ubuntu论坛提问数据集(仅使用5%)

总共预测五个tag。

实验模型:

  • itag
  • 单层itag(itag原版为3层GRU,这里修改为1层和普通单层seq2seq对比作为参照)
  • 单层GRU的seq2seq
  • 插入了一层GCN的单层seq2seq

标准itag作为baseline,理论上应该是itag的效果好于单层itag,而使用了各种机制的单层itag又应该好于普通的seq2seq。重点是观察加入一层GCN的seq2seq。选择只加入一层是因为这里是定性用,如果有改善效果,单层GRU应该就已经能体现出来了。

实验指标:使用多tag推荐任务的常用的精确度、召回率、F1

实验结果

模型名 精确度 召回率 F1
itag 0.246077 0.292881 0.267451
itag(单层) 0.271357 0.321762 0.294418
seq2seq 0.358496 0.391182 0.330851
seq2seq+GCN 0.284871 0.312474 0.298034

实验结果和我预想的出入很大,itag、单层itag和seq2seq的结果和我想象的完全是相反的;而在seq2seq中加入GCN之后效果反而更差

前者的原因可能是数据集过少,itag是个比较复杂的模型,即使抽掉两层GRU,单层的GRU的itag仍然是比较复杂的模型。为了控制变量,数据集量和GCN部分一样的少,这种情况下反而是越简单的模型效果较好。

而后者可能的原因较为复杂,我当时推测的原因可能有:

  1. 基于LDA主题词的图构建方法不适合图卷积
  2. 单层卷积效果不够
  3. seq2seq+GCN的数据集不足,过拟合
  4. 模型实现有误(我手动实现了GCN)
  5. 通过GCN平滑seq2seq隐藏状态这条路行不通

由于tensorflow不带GCN,所以我手动实现了GCN,没有直接复制粘贴GCN本身的源码。看过GCN源码的人应该知道,作者自己实现了Model和Layer超类,然后基础这两个类写了GCN等其他层和模型。

当时我认为,要么在作者的框架下实现一个GRU,要么在keras的框架下实现一个GCN,显然是后者比较简单,所以我选择了后者。(其实可以仿照GCN作者的代码对tensorflow的GRU做一个包装,当时还不会)

现在回头来看,2、3是不成立的。

对于2,如果GCN能起到积极作用,那么那么一层也是能表现出来的。

对于3,GCN本身并不是一个对于数据集大小特别敏感的算法,在GCN中,每个节点只和自己的邻居节点交换数据,N层GCN就能让节点拿到N跳外的节点的数据。但是GCN通常都不会叠很多层(何况我只使用了一层),这也是graphSAGE这类图采样算法能正常工作的基本原理。在信息交换范围之外的节点对于当前节点没有任何影响。

对于4,时至今日我也没去验证,验证方法应该是切到自带GCN的框架,比如pytorch,然后直接调用自带的GRU和GCN进行试验。但GCN本身的计算过程并不难,我倾向于我没有实现错。

对于1和5,需要后续试验进行验证。而我后面的一些工作也是在验证1和5