图神经网络14-TextGCN:基于图神经网络的文本分类

简介: 图神经网络14-TextGCN:基于图神经网络的文本分类

论文题目:Graph Convolutional Networks for Text Classification

论文地址:https://arxiv.org/pdf/1809.05679.pdf

论文代码:https://github.com/yao8839836/text_gcn

发表时间:AAAI 2019

ps:注意这篇论文作者在2018年已经公开在arxiv,我们再此不讨论预训练模型的事情 ^_^


论文摘要与简介


文本分类是自然语言处理过程中一个非常重要和经典的问题,在论文和实践过程中可以说经久不衰的任务。或多或少接触NLP的同学,应该比较清楚目前文本分类的模型众多,比如Text-RNN(LSTM),Text-CNN等,但是当时很少有关于将神经网络用于文本分类的任务中。


本文提出一种将图卷积网络模型用于文本分类的模型,主要思路为基于词语共现以及文本单词之间的关系构建语料库中文本的Graph,然后将GCN学习文本的表示用于文本分类。通过多个基准数据集实验表明,Text-GCN无需额外的单词嵌入或者先验知识就能够取得由于最新的文本分类方法。另一方面,Text-GCN还能够学习和预测词语与文档的嵌入表示。


论文动机与相关工作


图形结构在自然语言处理任务的文本数据中有许多有趣的应用,如语义角色标记(Titov2017)、关系分类(Li,Jin和Luo2018)和机器翻译(Bastings等)。

传统上,针对文本分类的模型一直侧重于单词嵌入的有效性和用于文档嵌入的聚合单词嵌入。这些词嵌入可以是无监督的预训练嵌入(例如word2vec或Glove),然后将其输入分类器中。最近,诸如CNN和RNN的深度学习模型已经成为有用的文本编码器。在这两种情况下,文本表示都是从单词嵌入中学习的。本文作者建议同时学习单词和文档嵌入以进行文本分类。


在本文之前其实也有GCN用于文本分类的研究,但是大部分工作都是将文档或者词语看做节点,相比之下,本文在构建语料库图时,我们将文档和单词视为节点(因此是异构图),并且不需要文档间的关系。本文提出的Text-GCN,获取给定的文档和单词的语料库,并构造一个图形,其中文档和单词为节点(有关的详细构建,我们稍后讨论)。利用此构造的图,`Text-GCN·利用图卷积网络来学习更好的节点表示(单词和文档的表示)。然后可以将这些更新的表示形式输入到分类器中。


GCN:Graph Convolutional Networks


我们快速对GCN进行回顾下,原文可以查看Semi-supervised classification with graph convolutional networks

首先我们定义下GCN的输入Graph。图,由节点集合和边集合组成。连接节点的边可以用林局长了表示。如果不为空,那么表示节点和节点之间存在关系,并且权重为的具体数值。GCN还向节点添加自环,因此邻接矩阵变为:

另外,每个节点都可以有一个向量表示,这个向量可以认为是该节点的特征。因此我们还有一个矩阵,其中是第个节点的节点特征,代表特征向量的大小。对于模型训练需要一个权重矩阵,其中是输出维度大小。最后我们还需要引入一个节点的度对角矩阵,对角线每个值代表了节点的度大小。

42.png


有了以上条件和基础,我们可以给出GCN层的公式表示了:

=

我们一步步解释下这个公式。其中,代表了输入节点的隐层输出向量表示。另外注意本质上是邻接矩阵,但是通过节点的度进行了归一化。

从上面可以看到,GCN本质上是学习了节点邻居和节点本身的节点表示形式(请记住自循环)。

GCN层允许节点从跳远的其他节点接收信息。GCN属于一类图形神经网络,称为消息传递网络,其中消息(在这种情况下,边缘权重乘以节点表示形式)在邻居之间传递。我们可以将这些消息传递网络视为帮助学习节点表示的方法,该节点表示法考虑了其图结构的附近邻居。因此,图的构造方式,即在哪些节点之间形成哪些边,非常重要。接下来我们讨论下图卷积网络如何用于文本分类,文本图如何构造。


Text-GCN:基于图神经网络的文本分类


43.png


文本Graph的构建


构造“文本”图的细节如下。首先,节点总数是文档数加上不同词语的个数。节点特征矩阵是恒等矩阵每个节点表示都是一个one-hot向量。同样,邻接矩阵(文档和单词节点之间的边缘)定义如下


44.png


是包含单词i和单词j的滑动窗口的数量,而是包含单词i的滑动窗口的数量。是滑动窗口的总数。


文本与词语之前的关系比较好刻画,文中直接采用我们常见的Tfidf来构建文档与词的边。对于词与词的关系采用PMI是两个单词节点之间的逐点互信息,用于查看两个单词的共现次数。用于计算共现的窗口大小是模型的超参数。在本文中,作者将其设置为20。直观地,图造尝试将相似的单词和文档放置在图形中彼此靠近的位置。


Text-GCN模型


完成文本Graph构建后,作者只需运行两层GCN,然后运行softmax函数来预测标签。公

式为:

对于损失函数,使用交叉熵损失。


论文实验


作者将他们的模型与 CNN、LSTM 变体和其他基线词或段落嵌入模型进行了比较。比较是在5个数据集上执行的。


45.png


  • 20NG超过18,000个文档平均分布在20个类别中
  • Ohsumed共有23种疾病类别的7000多种心血管疾病摘要
  • R52是Reuters-21578的子集,该文件于1987年出现在路透社新闻专栏中。大约10000个文档,涉及52个类别
  • R8与上述相同,但具有7500个文档和8个类别
  • MR电影评论数据集,包含10000条评论和两个类别:正面情绪和负面情绪

实验结果如下表所示:


46.png


从结果可以看出,TextGCN与CNN,LSTM和其他基准相比,每个数据集的效果最佳或接近最佳。这种性能纯粹来自上一节中定义的边缘和边缘权重,没有输入词向量,可见效果很好~。


GCN网络学习到的向量表示


为了获得对学习的表示的一些见解,作者展示了通过获取的文档嵌入的t-SNE可视化 TextGCN。我们可以看到,即使在应用了GCN的一层之后,文档嵌入也能够很好地区分自己。


47.png


更具体地讲,我们还可以使用中的嵌入来查看每个类的前10个单词的结果 TextGCN。我们可以看到该模型能够预测每个类别的相关词。


48.png


论文核心代码


  • Text Graph的构建

https://github.com/yao8839836/text_gcn/blob/master/build_graph.py

'''
Doc word heterogeneous graph
'''
# word co-occurence with context windows
window_size = 20
windows = []
for doc_words in shuffle_doc_words_list:
    words = doc_words.split()
    length = len(words)
    if length <= window_size:
        windows.append(words)
    else:
        # print(length, length - window_size + 1)
        for j in range(length - window_size + 1):
            window = words[j: j + window_size]
            windows.append(window)
            # print(window)
word_window_freq = {}
for window in windows:
    appeared = set()
    for i in range(len(window)):
        if window[i] in appeared:
            continue
        if window[i] in word_window_freq:
            word_window_freq[window[i]] += 1
        else:
            word_window_freq[window[i]] = 1
        appeared.add(window[i])
word_pair_count = {}
for window in windows:
    for i in range(1, len(window)):
        for j in range(0, i):
            word_i = window[i]
            word_i_id = word_id_map[word_i]
            word_j = window[j]
            word_j_id = word_id_map[word_j]
            if word_i_id == word_j_id:
                continue
            word_pair_str = str(word_i_id) + ',' + str(word_j_id)
            if word_pair_str in word_pair_count:
                word_pair_count[word_pair_str] += 1
            else:
                word_pair_count[word_pair_str] = 1
            # two orders
            word_pair_str = str(word_j_id) + ',' + str(word_i_id)
            if word_pair_str in word_pair_count:
                word_pair_count[word_pair_str] += 1
            else:
                word_pair_count[word_pair_str] = 1
row = []
col = []
weight = []
# pmi as weights
num_window = len(windows)
for key in word_pair_count:
    temp = key.split(',')
    i = int(temp[0])
    j = int(temp[1])
    count = word_pair_count[key]
    word_freq_i = word_window_freq[vocab[i]]
    word_freq_j = word_window_freq[vocab[j]]
    pmi = log((1.0 * count / num_window) /
              (1.0 * word_freq_i * word_freq_j/(num_window * num_window)))
    if pmi <= 0:
        continue
    row.append(train_size + i)
    col.append(train_size + j)
    weight.append(pmi)
# word vector cosine similarity as weights
'''
for i in range(vocab_size):
    for j in range(vocab_size):
        if vocab[i] in word_vector_map and vocab[j] in word_vector_map:
            vector_i = np.array(word_vector_map[vocab[i]])
            vector_j = np.array(word_vector_map[vocab[j]])
            similarity = 1.0 - cosine(vector_i, vector_j)
            if similarity > 0.9:
                print(vocab[i], vocab[j], similarity)
                row.append(train_size + i)
                col.append(train_size + j)
                weight.append(similarity)
'''
# doc word frequency
doc_word_freq = {}
for doc_id in range(len(shuffle_doc_words_list)):
    doc_words = shuffle_doc_words_list[doc_id]
    words = doc_words.split()
    for word in words:
        word_id = word_id_map[word]
        doc_word_str = str(doc_id) + ',' + str(word_id)
        if doc_word_str in doc_word_freq:
            doc_word_freq[doc_word_str] += 1
        else:
            doc_word_freq[doc_word_str] = 1
for i in range(len(shuffle_doc_words_list)):
    doc_words = shuffle_doc_words_list[i]
    words = doc_words.split()
    doc_word_set = set()
    for word in words:
        if word in doc_word_set:
            continue
        j = word_id_map[word]
        key = str(i) + ',' + str(j)
        freq = doc_word_freq[key]
        if i < train_size:
            row.append(i)
        else:
            row.append(i + vocab_size)
        col.append(train_size + j)
        idf = log(1.0 * len(shuffle_doc_words_list) /
                  word_doc_freq[vocab[j]])
        weight.append(freq * idf)
        doc_word_set.add(word)
node_size = train_size + vocab_size + test_size
adj = sp.csr_matrix(
    (weight, (row, col)), shape=(node_size, node_size))


  • Text GCN 实现

https://github.com/yao8839836/text_gcn/blob/master/models.py

from layers import *
from metrics import *
import tensorflow as tf
flags = tf.app.flags
FLAGS = flags.FLAGS
class Model(object):
    def __init__(self, **kwargs):
        allowed_kwargs = {'name', 'logging'}
        for kwarg in kwargs.keys():
            assert kwarg in allowed_kwargs, 'Invalid keyword argument: ' + kwarg
        name = kwargs.get('name')
        if not name:
            name = self.__class__.__name__.lower()
        self.name = name
        logging = kwargs.get('logging', False)
        self.logging = logging
        self.vars = {}
        self.placeholders = {}
        self.layers = []
        self.activations = []
        self.inputs = None
        self.outputs = None
        self.loss = 0
        self.accuracy = 0
        self.optimizer = None
        self.opt_op = None
    def _build(self):
        raise NotImplementedError
    def build(self):
        """ Wrapper for _build() """
        with tf.variable_scope(self.name):
            self._build()
        # Build sequential layer model
        self.activations.append(self.inputs)
        for layer in self.layers:
            hidden = layer(self.activations[-1])
            self.activations.append(hidden)
        self.outputs = self.activations[-1]
        # Store model variables for easy access
        variables = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope=self.name)
        self.vars = {var.name: var for var in variables}
        # Build metrics
        self._loss()
        self._accuracy()
        self.opt_op = self.optimizer.minimize(self.loss)
    def predict(self):
        pass
    def _loss(self):
        raise NotImplementedError
    def _accuracy(self):
        raise NotImplementedError
    def save(self, sess=None):
        if not sess:
            raise AttributeError("TensorFlow session not provided.")
        saver = tf.train.Saver(self.vars)
        save_path = saver.save(sess, "tmp/%s.ckpt" % self.name)
        print("Model saved in file: %s" % save_path)
    def load(self, sess=None):
        if not sess:
            raise AttributeError("TensorFlow session not provided.")
        saver = tf.train.Saver(self.vars)
        save_path = "tmp/%s.ckpt" % self.name
        saver.restore(sess, save_path)
        print("Model restored from file: %s" % save_path)
class MLP(Model):
    def __init__(self, placeholders, input_dim, **kwargs):
        super(MLP, self).__init__(**kwargs)
        self.inputs = placeholders['features']
        self.input_dim = input_dim
        # self.input_dim = self.inputs.get_shape().as_list()[1]  # To be supported in future Tensorflow versions
        self.output_dim = placeholders['labels'].get_shape().as_list()[1]
        self.placeholders = placeholders
        self.optimizer = tf.train.AdamOptimizer(learning_rate=FLAGS.learning_rate)
        self.build()
    def _loss(self):
        # Weight decay loss
        for var in self.layers[0].vars.values():
            self.loss += FLAGS.weight_decay * tf.nn.l2_loss(var)
        # Cross entropy error
        self.loss += masked_softmax_cross_entropy(self.outputs, self.placeholders['labels'],
                                                  self.placeholders['labels_mask'])
    def _accuracy(self):
        self.accuracy = masked_accuracy(self.outputs, self.placeholders['labels'],
                                        self.placeholders['labels_mask'])
    def _build(self):
        self.layers.append(Dense(input_dim=self.input_dim,
                                 output_dim=FLAGS.hidden1,
                                 placeholders=self.placeholders,
                                 act=tf.nn.relu,
                                 dropout=True,
                                 sparse_inputs=True,
                                 logging=self.logging))
        self.layers.append(Dense(input_dim=FLAGS.hidden1,
                                 output_dim=self.output_dim,
                                 placeholders=self.placeholders,
                                 act=lambda x: x,
                                 dropout=True,
                                 logging=self.logging))
    def predict(self):
        return tf.nn.softmax(self.outputs)
class GCN(Model):
    def __init__(self, placeholders, input_dim, **kwargs):
        super(GCN, self).__init__(**kwargs)
        self.inputs = placeholders['features']
        self.input_dim = input_dim
        # self.input_dim = self.inputs.get_shape().as_list()[1]  # To be supported in future Tensorflow versions
        self.output_dim = placeholders['labels'].get_shape().as_list()[1]
        self.placeholders = placeholders
        self.optimizer = tf.train.AdamOptimizer(learning_rate=FLAGS.learning_rate)
        self.build()
    def _loss(self):
        # Weight decay loss
        for var in self.layers[0].vars.values():
            self.loss += FLAGS.weight_decay * tf.nn.l2_loss(var)
        # Cross entropy error
        self.loss += masked_softmax_cross_entropy(self.outputs, self.placeholders['labels'],
                                                  self.placeholders['labels_mask'])
    def _accuracy(self):
        self.accuracy = masked_accuracy(self.outputs, self.placeholders['labels'],
                                        self.placeholders['labels_mask'])
        self.pred = tf.argmax(self.outputs, 1)
        self.labels = tf.argmax(self.placeholders['labels'], 1)
    def _build(self):
        self.layers.append(GraphConvolution(input_dim=self.input_dim,
                                            output_dim=FLAGS.hidden1,
                                            placeholders=self.placeholders,
                                            act=tf.nn.relu,
                                            dropout=True,
                                            featureless=True,
                                            sparse_inputs=True,
                                            logging=self.logging))
        self.layers.append(GraphConvolution(input_dim=FLAGS.hidden1,
                                            output_dim=self.output_dim,
                                            placeholders=self.placeholders,
                                            act=lambda x: x, #
                                            dropout=True,
                                            logging=self.logging))
    def predict(self):
        return tf.nn.softmax(self.outputs)


论文总结


本文提出了一个简单的GCN在文本分类应用中的有趣应用,并且确实显示了令人欣喜的结果。但是该模型确实具有局限性,因为它具有传导性(通常是GCN的局限性)。在训练过程中,模型将训练数据集中的每个单词和文档,包括测试集。尽管在训练过程中没有对测试集进行任何预测,但是该模型不能应用预测一个全新的文档。这导致了将来可能的工作,即如何将新文档合并到已经构建的图形中。总的来说,我认为本文显示了图神经网络的强大能力及其在我们可以定义和构建某种有用图结构的任何领域中的适用性。

相关文章
|
2月前
|
机器学习/深度学习 人工智能 算法
【新闻文本分类识别系统】Python+卷积神经网络算法+人工智能+深度学习+计算机毕设项目+Django网页界面平台
文本分类识别系统。本系统使用Python作为主要开发语言,首先收集了10种中文文本数据集("体育类", "财经类", "房产类", "家居类", "教育类", "科技类", "时尚类", "时政类", "游戏类", "娱乐类"),然后基于TensorFlow搭建CNN卷积神经网络算法模型。通过对数据集进行多轮迭代训练,最后得到一个识别精度较高的模型,并保存为本地的h5格式。然后使用Django开发Web网页端操作界面,实现用户上传一段文本识别其所属的类别。
93 1
【新闻文本分类识别系统】Python+卷积神经网络算法+人工智能+深度学习+计算机毕设项目+Django网页界面平台
|
1月前
|
机器学习/深度学习 存储 自然语言处理
深度学习入门:循环神经网络------RNN概述,词嵌入层,循环网络层及案例实践!(万字详解!)
深度学习入门:循环神经网络------RNN概述,词嵌入层,循环网络层及案例实践!(万字详解!)
|
3月前
|
机器学习/深度学习 人工智能 编解码
【神经网络】基于对抗神经网络的图像生成是如何实现的?
对抗神经网络,尤其是生成对抗网络(GAN),在图像生成领域扮演着重要角色。它们通过一个有趣的概念——对抗训练——来实现图像的生成。以下将深入探讨GAN是如何实现基于对抗神经网络的图像生成的
37 3
|
3月前
|
机器学习/深度学习 算法 文件存储
【博士每天一篇文献-算法】 PNN网络启发的神经网络结构搜索算法Progressive neural architecture search
本文提出了一种名为渐进式神经架构搜索(Progressive Neural Architecture Search, PNAS)的方法,它使用顺序模型优化策略和替代模型来逐步搜索并优化卷积神经网络结构,从而提高了搜索效率并减少了训练成本。
57 9
|
4月前
|
机器学习/深度学习 自然语言处理
像生物网络一样生长,具备结构可塑性的自组织神经网络来了
【7月更文挑战第24天】Sebastian Risi团队发布的arXiv论文探讨了一种模仿生物神经网络生长与适应特性的新型神经网络。LNDP利用结构可塑性和经验依赖学习,能根据活动与奖励动态调整连接,展现自我组织能力。通过基于图变换器的机制,LNDP支持突触动态增删,预先通过可学习随机过程驱动网络发育。实验在Cartpole等任务中验证了LNDP的有效性,尤其在需快速适应的场景下。然而,LNDP在复杂环境下的可扩展性及训练优化仍面临挑战,且其在大规模网络和图像分类等领域的应用尚待探索
90 20
|
3月前
|
机器学习/深度学习 网络安全 TensorFlow
探索操作系统的心脏:内核与用户空间的奥秘云计算与网络安全:技术挑战与未来趋势深度学习中的卷积神经网络(CNN)及其在图像识别中的应用
【8月更文挑战第29天】在数字世界的每一次点击与滑动背后,都隐藏着一个不为人知的故事。这个故事关于操作系统——计算机的灵魂,它如何协调硬件与软件,管理资源,并确保一切运行得井井有条。本文将带你走进操作系统的核心,揭示内核与用户空间的秘密,展现它们如何共同编织出我们日常数字生活的底层结构。通过深入浅出的讲解和代码示例,我们将一同解锁操作系统的神秘面纱,理解其对现代计算的重要性。 【8月更文挑战第29天】本文将深入探讨卷积神经网络(CNN)的基本原理和结构,以及它们如何被广泛应用于图像识别任务中。我们将通过代码示例来展示如何使用Python和TensorFlow库构建一个简单的CNN模型,并训练
|
4月前
|
机器学习/深度学习 搜索推荐 知识图谱
图神经网络加持,突破传统推荐系统局限!北大港大联合提出SelfGNN:有效降低信息过载与数据噪声影响
【7月更文挑战第22天】北大港大联手打造SelfGNN,一种结合图神经网络与自监督学习的推荐系统,专攻信息过载及数据噪声难题。SelfGNN通过短期图捕获实时用户兴趣,利用自增强学习提升模型鲁棒性,实现多时间尺度动态行为建模,大幅优化推荐准确度与时效性。经四大真实数据集测试,SelfGNN在准确性和抗噪能力上超越现有模型。尽管如此,高计算复杂度及对图构建质量的依赖仍是待克服挑战。[详细论文](https://arxiv.org/abs/2405.20878)。
80 5
|
4月前
|
机器学习/深度学习 编解码 计算机视觉
【YOLOv8改进- Backbone主干】BoTNet:基于Transformer,结合自注意力机制和卷积神经网络的骨干网络
【YOLOv8改进- Backbone主干】BoTNet:基于Transformer,结合自注意力机制和卷积神经网络的骨干网络
|
4月前
|
机器学习/深度学习 计算机视觉
【YOLOv8改进- Backbone主干】YOLOv8更换主干网络之ConvNexts,纯卷积神经网络,更快更准,,降低参数量!
YOLOv8专栏探讨了针对目标检测的ConvNet创新,提出ConvNeXt模型,它挑战Transformer在视觉任务中的主导地位。ConvNeXt通过增大卷积核、使用GeLU激活、切换到LayerNorm和改进下采样层,提升了纯ConvNet性能,达到与Transformer相当的准确率和效率。论文和代码已公开。
|
4月前
|
机器学习/深度学习 计算机视觉 网络架构
【YOLOv8改进- Backbone主干】YOLOv8 更换主干网络之 PP-LCNet,轻量级CPU卷积神经网络,降低参数量
YOLO目标检测专栏介绍了PP-LCNet,一种基于MKLDNN加速的轻量级CPU网络,提升了模型在多任务中的性能。PP-LCNet利用H-Swish、大核卷积、SE模块和全局平均池化后的全连接层,实现低延迟下的高准确性。代码和预训练模型可在PaddlePaddle的PaddleClas找到。文章提供了网络结构、核心代码及性能提升的详细信息。更多实战案例和YOLO改进见相关链接。

热门文章

最新文章