基于句子嵌入的无监督文本摘要(附代码实现)

最近总算是忙完了毕业论文的事情,也放松了一段时间,很久没有写博客了。之前逛Medium有收藏了很多有意思的文章,就做个搬运和大家一起学习。这篇文章主要介绍的是作者做的一个对多种语言的邮件进行无监督摘要抽取的项目,非常详细。文本摘要也是非常有意思的NLP任务之一,可能之后会涉及相关的项目,所以就先提前学习啦~

A Glance at Text Summarization

文本摘要是从一个或多个源中提取最重要信息,并为特定用户(或多个用户)和任务(或多个任务)生成简短版本的过程。
---- Advances in Automatic Text Summarization, 1999.

文本摘要对于人类来说是非常简单的,因为人类天生地具有理解自然语言的能力,并可以提取显著特征以使用自己的文字来总结文档的重点。但是,在当今世界中数据爆炸增长,缺乏人力和时间来解析数据,因此自动文本摘要方法至关重要,主要有以下几个原因:

  • 自动摘要可以缩短文本阅读时间,提高效率;
  • 当搜索我们所需要的文本时,有摘要可以更为容易查找到;
  • 自动摘要提高了索引的效率;
  • 相比于人力摘要,自动摘要更无偏;
  • 个性化的摘要在问答系统中非常有用,因为它们提供了个性化的信息;
  • 使用自动或半自动摘要系统使商业抽象服务能够增加它们处理的文本文档的数量。
文本摘要的分类

文本摘要方法可以被总结为以下不同的类别:
在这里插入图片描述

Based on input type
  1. 单文档:输入长度较短,许多早期的摘要系统主要处理单个文档摘要;
  2. 多文档:输入可以是任意长的。
Based on the purpose
  1. 通用模型:模型对摘要的文本的领域或内容不做任何假设,并将所有输入视为同类输入。目前大部分已经完成的工作都是围绕着通用的总结;
  2. 领域适应模型: 模型使用领域特定的知识来形成更准确的摘要。例如,总结某一特定领域的研究论文、生物医学文献等;
  3. 基于query模型: 摘要只包含回答有关输入文本的自然语言问题的信息。
Based on output type
  1. 抽取式模型:从输入文本中选择重要的句子形成摘要,当今大多数的总结方法本质上都是抽取式的。
  2. 生成式模型:模型形成自己的短语和句子,提供更连贯的总结,就像人类在面对文本摘要时会做的那样。这种方法肯定更有吸引力,但比提取摘要困难得多。

文本摘要流程

文本摘要实现主要是参考Unsupervised Text Summarization Using Sentence Embeddings这篇论文,可以分解成以下过程:

在这里插入图片描述
以英文邮件为例,看看是怎么得到最终的摘要的。

Step-1:数据清洗

常规操作,永远没有干净的数据,自己动手丰衣足食。下面以常见的英文邮件为例:

Hi Jane,

Thank you for keeping me updated on this issue. I'm happy to hear that the issue got resolved after all and you can now use the app in its full functionality again. 
Also many thanks for your suggestions. We hope to improve this feature in the future. 

In case you experience any further problems with the app, please don't hesitate to contact me again.

Best regards,

John Doe
Customer Support

1600 Amphitheatre Parkway
Mountain View, CA
United States

可以看出,邮件起始的问候与末尾的署名对我们的文本摘要任务是毫无作用的,所以我们需要首先去除这些无关因素,否则会使得模型混淆。为此,我们可以借用Mailgun Talon github库中的部分代码,该代码还可以删除空行。

# clean()函数改写了上面github库中代码以清洗邮件
cleaned_email, _ = clean(email)

lines = cleaned_email.split('\n')
lines = [line for line in lines if line != '']
cleaned_email = ' '.join(lines)

当然,如果不想自己写clean()函数的话,也可以直接调用上面链接中的清洗函数:

from talon.signature.bruteforce import extract_signature
cleaned_email, _ = extract_signature(email)

上述原始邮件清洗后得到大概是这样的:

Thank you for keeping me updated on this issue. I’m happy to hear that the issue got resolved after all and you can now use the app in its full functionality again. Also many thanks for your suggestions. We hope to improve this feature in the future. In case you experience any further problems with the app, please don’t hesitate to contact me again.

Step-2:语言检测

对于不同的语言,处理的方式会有所不同,所以首先需要对邮件的语言类型进行检测。得益于python强大的第三方库,语言检测可以很容易实现,比如使用polyglotlangdetecttextblob等。

from langdetect import detect
lang = detect(cleaned_email) # lang = 'en' for an English email
Step-3:句子分割

由上一步检测出邮件语言之后,可以针对该语言对邮件全文进行句子分割。以英文为例,可以使用NLTK包中的sen_tokenize()方法

from nltk.tokenize import sent_tokenize
sentences = sent_tokenize(email, language = lang)

举个栗子

在这里插入图片描述

Step-4:Skip-Thought编码

为了邮件文本表示成机器可以识别的输入,同时融入文本的语义信息,需要对文本进行编码,生成特定长度的向量表示,即Word Embedding。对于word embedding,常见的有word2vec,glove,fasttext等。对于句子embedding,一种简单的思路是对句子中的单词取其word embedding的加权和,认为不同的单词对整体的贡献程度不一样。例如经常出现的单词(‘and’,‘to’,‘the’等)几乎对句子信息没有贡献,一些很少出现的单词具有更大的代表性,类似于tf-idf的思想,也在这篇论文中介绍。

但是,这些无监督的方法没有将单词在句子中的顺序考虑进去,因此会造成性能损失。为了改进这一点,采用了**Skip-Thought Vectors**这篇论文提供的思路,使用wikipedia训练了一个 Skip-Thoughts句子嵌入模型:

  1. Encoder Network: Encoder的结构是典型的GRU-RNN框架,对输入的每一个句子 S ( i ) S(i) S(i)都生成一个固定长度的向量表示 h ( i ) h(i) h(i)
  2. Decoder Network: Decoder使用的也是GRU-RNN框架,不过有两个decoder,分别用于生成句子 S ( i ) S(i) S(i)的前一句 S ( i − 1 ) S(i-1) S(i1)和后一句 S ( i + 1 ) S(i+1) S(i+1),输入均为encoder的输出 h ( i ) h(i) h(i)

整体框架如下所示
在这里插入图片描述
感谢Skip-Thought的开源,我们通过几行简单的代码就可以得到句子向量表示:

import skipthoughts

# 需要预先下载预训练模型
model = skipthoughts.load_model()

encoder = skipthoughts.Encoder(model)
encoded =  encoder.encode(sentences)
Step-5:聚类

在为邮件文本生成句子表示之后,将这些句子编码在高维向量空间中进行聚类,聚类的数量为摘要任务所需要的句子数量。可以将最终摘要的句子数设定为初始输入句子综述的平方根。我们可以使用K-means实现:

import numpy as np
from sklearn.cluster import KMeans

n_clusters = np.ceil(len(encoded)**0.5)
kmeans = KMeans(n_clusters=n_clusters)
kmeans = kmeans.fit(encoded)
Step-6:摘要

聚类之后的每一个簇群都可以认为是一组语义相似的句子集合,而我们只需要其中的一句来表示即可。这一句子的选择为考虑距离聚类中心最接近的句子,然后将每个簇群相对应的候选句子排序,形成最终的文本摘要。摘要中候选句子的顺序由原始电子邮件中句子在其相应簇中的位置确定。 例如,如果位于其群集中的大多数句子出现在电子邮件的开头,则将候选句子选择为摘要中的第一句。

from sklearn.metrics import pairwise_distances_argmin_min

avg = []
for j in range(n_clusters):
    idx = np.where(kmeans.labels_ == j)[0]
    avg.append(np.mean(idx))
closest, _ = pairwise_distances_argmin_min(kmeans.cluster_centers_, encoded)
ordering = sorted(range(n_clusters), key=lambda k: avg[k])
summary = ' '.join([email[closest[idx]] for idx in ordering])

经过上述几个步骤,最终得到的摘要如下所示:

I’m happy to hear that the issue got resolved after all and you can now use the app in its full functionality again. Also many thanks for your suggestions. In case you experience any further problems with the app, please don’t hesitate to contact me again.

总结

  • 上述介绍的是一种抽取式文本摘要的方法,对于所有抽取式摘要而言,一大特点就是不适用与长度较短文本的摘要,对于短文本的摘要可能Seq2Seq的模型效果会更好;
  • 对于Sentence Embedding可以进一步优化,使用更有效的句子编码模型可能会使得最终效果有所提高;
  • Skip-Thought编码维度为4800,如此高维度对后续的聚类可能效果有所影响(Curse of Dimensionality)。可以考虑在聚类之前使用自动编码器或LSTM-Autoencoder在压缩表示中传递进一步的序列信息;
  • 完整的代码实现参考:email-summarization

以上~
2020.01.14

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值