本文介绍了如何使用深度学习执行文本实体提取。作者尝试了分别使用深度学习和传统方法来提取文章信息,结果深度学习的准确率达到了 85%,远远领先于传统算法的 65%。
传统的NLP建模流程和使用深度学习技术的NLP建模流程如下图所示:
文本实体提取是自然语言处理(NLP)的主要任务之一。随着近期深度学习领域快速发展,我们可以将这些算法应用到 NLP 任务中,并得到准确率远超传统方法的结果。我尝试过分别使用深度学习和传统方法来提取文章信息,结果非常惊人:深度学习的准确率达到了 85%,远远领先于传统算法的 65%。
本项目的目标是把文章中的每个单词标注为以下四种类别之一:组织、个人、杂项以及其他;然后找到文中最突出的组织和名称。深度学习模型对每个单词完成上述标注,随后,我们使用基于规则的方法来过滤掉我们不想要的标注,并确定最突出的名称和组织。
模型的高级架构
在这里要感谢 Guillaume Genthial 这篇关于序列标注的文章,本项目建立在这篇文章的基础之上。
上图是对每个单词进行分类标注的模型高级架构。在建模过程中,最耗时间的部分是单词分类。我将解释模型的每个组成部分,帮助读者对模型组件有一个全面的、更高层次的理解。通常,模型组件可分为三部分:
- 单词表征:在建模第一步,我们需要做的是加载一些预训练词嵌入(GloVe)。同时,我们需要从字符中提取出一些含义。
- 语境单词表征:我们需要利用 LSTM,对语境中的每一个单词得到一个有意义的表征
- 解码:当我们得到表示单词的向量后,我们就可以用它进行预测。
hot encoding(用数值表示单词)
深度学习算法只接受数值型数据作为输入,而无法处理文本数据。如果想要在大量的非数值场景下使用深度神经网络,就需要将输入数据转变数值形式。这个过程就是 hot encoding:
下面是一小段实现 hot encoding 的代码示例:
1 | word_counts = Counter(words) |
同样地,我们必须获取输入数据中的所有字符,然后将其转化为向量,作为字符嵌入。
单词嵌入 & 字符嵌入
单词嵌入是处理文本问题时使用的一种通过学习得到的表征方式,其中含义相同的单词表征相近。通常,我们利用神经网络来实现单词嵌入,其中使用的单词或短语来自于词库,并需要转变为实数构成的向量形式。
但是,在数据集上生成词向量计算成本很高,我们可以使用一些预训练的单词嵌入来避免这个问题:比如使用斯坦福大学的 NLP 研究者提供的 GloVe 向量。
符嵌入是字符的向量表征,可用于推导词向量。之所以会使用字符嵌入,是因为许多实体并没有对应的预训练词向量,所以我们需要用字符向量来计算词向量。详细的可以点击链接
LSTM
传统神经网络 VS 循环神经网络(RNN)
循环神经网络(RNN)是人工神经网络的一种,用于序列数据中的模式识别,例如文本、基因组、手写笔迹、口语词汇,或者来自传感器、股市和政府机构的数值型时间序列数据。它可以「理解」文本的语境含义。
LSTM 是一种特殊的循环神经网络,相比于简单的循环神经网络,它可以存储更多的语境信息。简单的 RNN 和 LSTM 之间的主要区别在于它们各自神经元的结构不同。
对于语境中的每一个单词,我们都需要利用 LSTM 得到它在所处语境中的有意义表征。
如果你想了解更多关于 LSTM 和 RNN 的知识,可以参阅以下文章:
条件随机场(CRF)
在预测标注最后的解码步骤中,我们可以使用 softmax 函数。当我们使用 softmax 函数时,它给出单词属于每个分类的概率。但这个方法给出的是局部选择;换句话说,即使我们从文本语境中提取出了一些信息,标注决策过程依然是局部的,我们在使用 softmax 激活函数时,并没有使用到邻近单词的标注决策。例如,在「New York」这个词中,我们将「York」标注为一个地方,事实上,这应该可以帮助我们确定『New』对应地方的开始。
在 CRF 中,我们的输入数据是序列数据;同时,我们在某个数据点上进行预测时,需要考虑先前文本的语境。在本项目中,我们使用的是线性链 CRF。在线性链 CRF 中,特征只依赖当前标注和之前的标注,而不是整个句子中的任意标注。
为了对这个行为建模,我们将使用特征函数,该函数包含多个输入值:
- 句子s
- 单词在句子中的位置i
- 当前单词的标注
- 前一个单词的标注
接下来,对每一个特征函数 赋予权重 。给定一个句子s,现在我们可以根据下式计算s的标注l:对句子中所有单词的加权特征求和。
基于词性标注的特征函数示例:
-
如果 ,且第 i 个单词以『-ly』结尾,则 ,否则取 0。如果对应的权重 为正,且非常大,那么这个特征基本上就表示我们倾向于把以『-ly』结尾的单词标注为 ADVERB。
-
如果 i=1,,且句子以问号结尾,则$ f_2(s,i,l_i,l_{i−1})=1$,否则取 0。如果对应的权重 为正,且非常大,那么这个特征基本上就表示我们倾向于把疑问句的第一个单词标为 VERB。(例,「Is this a sentence beginning with a verb?」)
-
如果$ l_{i−1}= ADJECTIVE l_i= NOUN f_3(s,i,l_i,l_{i−1})=1$,否则为0。对应权重为正时,表示我们倾向于认为名词跟在形容词之后。
-
如果$ l_{i−1}= PREPOSITION$,且 ,则 。此函数对应的权重$ λ_4$ 为负,表示介词不应该跟着另一个介词,因此我们应该避免这样的标注出现。
最后,我们可以通过取指数和归一化,将这些得分转换为 0~1 之间的概率 p(l|s)。
总之,要建立一个条件随机场,你只需要定义一组特征函数(可以依赖于整个句子、单词的当前位置和附近单词的标注)、赋予权重,然后加起来,最后如果有需要,转化为概率形式。简单地说,需要做两件事情:
- 找到得分最高的标注序列;
- 在全体标注序列上求出概率分布。
幸运的是,TensorFlow 提供了相关的库,帮助我们可以很容易地实现 CRF。
1 | log_likelihood, transition_params=tf.contrib.crf.crf_log_likelihood(scores, labels, sequence_lengths) |
CRF r 相关阅读资料:
1.http://blog.echen.me/2012/01/03/introduction-to-conditional-random-fields/
2.http://homepages.inf.ed.ac.uk/csutton/publications/crftut-fnt.pdf
模型的运行原理
对于每一个单词,我们希望建立一个向量来捕捉其意义以及和任务相关的特征。我们将该向量构建为 GloVe 单词嵌入与包含字符级特征的向量的级联。我们还可以选择使用一些特定的神经网络,自动提取出这些特征。在本文中,我们将在字符层面上使用双向 LSTM 算法。
我们将 CONLL 数据集中的所有单词都进行 hot-encode,这些单词都在 GloVe 单词嵌入中有对应的实体。如上文所述,神经网络只接受向量,不接受文本,因此我们需要将单词转换为向量。CONLL 数据集包含单词及其对应标注。在 hot encoding 后,单词和标注都被转换成了向量
用于 hot encoding 单词及其对应标注的代码:
1 | with open(self.filename) as f: |
用于提取单词、标注和字符向量的代码
1 | if vocab_chars is not None and chars == True: |
现在,我们使用 TensorFlow 内置的函数加载单词嵌入。假定 embeddings 是一个 GloVe 嵌入的 numpy 数组,其中 embeddings[i] 表示第 i 个单词的向量形式。
1 | L = tf.Variable(embeddings, dtype=tf.float32, trainable=False) |
现在,我们可以构建根据字符得到的单词嵌入。这里,我们不需要任何预训练字符嵌入。
1 | _char_embeddings = tf.get_variable( |
一旦得到了单词表征,我们就可以直接在词向量序列上运行 bi-LSTM,得到另一个向量序列。
1 | cell_fw = tf.contrib.rnn.LSTMCell(self.config.hidden_size_lstm) |
现在,每个单词都和一个向量对应,其中向量记录了这个单词的含义、字符和语境。我们使用向量来做最后的预测。我们可以使用全连接神经网络求出一个向量,该向量中每个条目对应每个标注的得分。
1 | W = tf.get_variable("W", dtype=tf.float32, |
最后,我们使用 CRF 方法来计算每个单词的标注。实现 CRF 只需要一行代码!下面的代码计算出了损失,同时返回了在预测时很有用的 trans_params。
1 | log_likelihood, _trans_params = tf.contrib.crf.crf_log_likelihood( |
现在,我们可以定义我们的训练算子:
1 | optimizer = tf.train.AdamOptimizer(self.lr_tensor) |
一旦我们定义好模型,在数据集上完成很少的几次迭代,就可以得到训练好的模型了。
如何使用训练好的模型
TensorFlow 提供了存储模型权重的功能,这样我们就可以在之后的场景中复原训练好的模型。无论什么时候需要进行预测,我们都可以加载模型权重,这样就不需要重新训练了。
1 | def save_session(self): |
每篇文章都被分解为单词再输入到模型中,然后经过上文所述一系列过程,得到输出结果。模型最终输出结果将每个单词分为 4 类:组织、个人、杂项以及其他。这个算法通过基于规则的方法过滤结果,然后进一步正确提取出文本中最突出的名称和组织,它并没有达到 100% 的准确率。
具体的源码地址: https://github.com/lonePatient/entity_recoginition_deep_learning