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

©PaperWeekly· 作者 高开远

学校 上海交通大学

研究方向 自然语言处理

本文主要介绍的是一个 对多种语言的邮件进行无监督摘要抽取的项目 ,非常详细。文本摘要也是非常有意思的 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 [1] 这篇论文,可以分解成以下过程:

以英文邮件为例,看看是怎么得到最终的摘要的。

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 库 [2] 中的部分代码,该代码还可以删除空行。

# 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)

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

Step-2:语言检测

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

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 的思想,也在这篇论文 [3] 中介绍。 

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

1. Encoder Network: Encoder 的结构是典型的 GRU-RNN 框架,对输入的每一个句子 S(i) 都生成一个固定长度的向量表示 h(i); 

2. Decoder Network: Decoder 使用的也是 GRU-RNN 框架,不过有两个 decoder,分别用于生成句子 S(i) 的前一句 S(i−1) 和后一句 S(i+1),输入均为 encoder的输出 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])

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

总结

上述介绍的是一种抽取式文本摘要的方法,对于所有抽取式摘要而言,一大特点就是不适用与长度较短文本的摘要,对于短文本的摘要可能 Seq2Seq 的模型 [5] 效果会更好。

对于 Sentence Embedding 可以进一步优化,使用更有效的句子编码模型可能会使得最终效果有所提高。 

Skip-Thought 编码维度为 4800,如此高维度对后续的聚类可能效果有所影响(Curse of Dimensionality)。可以考虑在聚类之前使用自动编码器或 LSTM-Autoencoder 在压缩表示中传递进一步的序列信息; 

完整的代码实现参考:

https://github.com/jatana-research/email-summarization

参考文献

[1] https://www.cs.utexas.edu/~asaran/reports/summarization.pdf

[2] https://github.com/mailgun/talon/blob/master/talon/signature/bruteforce.py

[3] https://openreview.net/pdf?id=SyK00v5xx

[4] https://arxiv.org/abs/1506.06726

[5] https://machinelearningmastery.com/encoder-decoder-models-text-summarization-keras/

点击以下标题查看更多往期内容:

# 投 稿 通 道 #

让你的论文被更多人看到 

如何才能让更多的优质内容以更短路径到达读者群体,缩短读者寻找优质内容的成本呢? 答案就是:你不认识的人。

总有一些你不认识的人,知道你想知道的东西。PaperWeekly 或许可以成为一座桥梁,促使不同背景、不同方向的学者和学术灵感相互碰撞,迸发出更多的可能性。 

PaperWeekly 鼓励高校实验室或个人,在我们的平台上分享各类优质内容,可以是 最新论文解读 ,也可以是 学习心得技术干货 。我们的目的只有一个,让知识真正流动起来。

:memo:  来稿标准:

• 稿件确系个人 原创作品 ,来稿需注明作者个人信息(姓名+学校/工作单位+学历/职位+研究方向) 

• 如果文章并非首发,请在投稿时提醒并附上所有已发布链接 

• PaperWeekly 默认每篇文章都是首发,均会添加“原创”标志

:mailbox_with_mail: 投稿邮箱:

• 投稿邮箱: hr@paperweekly.site  

• 所有文章配图,请单独在附件中发送 

• 请留下即时联系方式(微信或手机),以便我们在编辑发布时和作者沟通

:mag:

现在,在 「知乎」 也能找到我们了

进入知乎首页搜索 「PaperWeekly」

点击 「关注」 订阅我们的专栏吧

关于PaperWeekly

PaperWeekly 是一个推荐、解读、讨论、报道人工智能前沿论文成果的学术平台。如果你研究或从事 AI 领域,欢迎在公众号后台点击 「交流群」 ,小助手将把你带入 PaperWeekly 的交流群里。

▽ 点击 |  阅读原文   | 获取最新论文推荐