电子说
编者按:Kushal Chauhan分享了他在Jatana.ai的NLP研究实习期间基于句嵌入进行无监督文本总结的经验。
什么是文本总结?
文本总结是从一个或多个来源提取最重要的信息,生成一个针对某个或某群特定读者或任务的简化版本的过程。——Advances in Automatic Text Summarization, 1999, 第1页
一般来说,人类相当擅长这一任务,因为我们具有理解文档含义,使用自己的语言总结要点的能力。然而,由于当今世界信息过载,缺乏人力和时间解读数据,自动文本总结技术十分关键。自动文本总结的价值在于:
减少阅读时间。
简化研究的筛选过程。
提高索引的有效性。
在问答系统中,个性化总结提供了个性化信息。
自动总结系统或半自动总结系统的应用让商业摘要服务提高了处理文档的吞吐量。
文本总结方法的类型
根据不同的标准,文本总结方法可以分为不同类型。
基于输入类型
单文档 许多早期的总结系统只能处理单文档。
多文档 支持任意数量的文档作为输入。
基于目的
通用 模型对要总结的文本内容的领域不作任何假定,并将所有输入作为同构文本处理。文本总结领域的大部分工作都属于这类。
领域特定 模型使用领域特定知识以形成更精确的总结。例如,总结特定领域的研究论文,生物医学文档,等等。
基于查询 总结仅仅包括回答关于输入文本的自然语言提问的信息。
基于输出类型
提取 从输入文本中选取最重要的句子,组成总结。现在大多数总结方法本质上都是提取式的。
摘要 模型用自己的词组和句子提供一份更连贯的总结,类似人类所做的总结。这类方法无疑更有吸引力,但比提取式总结要困难得多。
我的任务
我的任务是在电子邮件上应用文本总结,邮件以英语、丹麦语、法语等多种语言撰写。大多数公开的文本总结数据集面向的是长文档和文章。由于长文档和文章的结构和短邮件有很大的不同,以监督方法训练的模型可能在领域自适应方面表现很差。因此,我选择探索无监督方法,期望得到总结的无偏预测。
现在,让我们尝试了解构成模型流程的多个步骤。
文本总结模型流程
我所用的文本总结方法借鉴了Aishwarya Padmakumar和Akanksha Saran的论文Unsupervised Text Summarization Using Sentence Embeddings。这一方法可以分解为以下步骤:
第一步:清洗邮件
让我们先来看下典型的邮件看起来是什么样的:
英文邮件样本:
HiJane,
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,
JohnDoe
CustomerSupport
1600AmphitheatreParkway
MountainView, CA
UnitedStates
挪威语邮件样本:
Hei
Grunnet manglende dekning på deres kort for månedlig trekk, blir dere nå overført til årlig fakturering.
I morgen vil dere motta faktura for hosting og drift av nettbutikk for perioden 05.03.2018-05.03.2019.
Ta gjerne kontakt om dere har spørsmål.
Med vennlig hilsen
JohnDoe - SomeCompany.no
04756 | johndoe@somecompany.no
Husk å sjekk vårt hjelpesenter, kanskje du finner svar der: https://support.somecompany.no/
意大利语邮件样本:
CiaoJohn,
Grazie mille per averci contattato! Apprezziamo molto che abbiate trovato il tempo per inviarci i vostri commenti e siamo lieti che vi piaccia l'App.
Sentitevi liberi di parlare di con i vostri amici o di sostenerci lasciando una recensione nell'AppStore!
Cordiali saluti,
JaneDoe
CustomerSupport
OneInfiniteLoop
Cupertino
CA 95014
如你所见,邮件开头的称呼语和末尾的签名对总结生成任务毫无贡献。所以,有必要从邮件中移除这些应该不会影响总结的行。这简化了输入,使模型表现可以更佳。
由于不同邮件和不同语言的称呼语和签名不一样,移除它们需要使用正则表达式匹配。如果只处理英文邮件,那么我们可以直接使用Mailgun的talon库:
from talon.signature.bruteforce import extract_signature
cleaned_email, _ = extract_signature(email)
不过我需要处理多种语言的邮件,所以我修改了extract_signature函数,以支持英语之外的其他语言。我还顺便移除了换行符。
上面三个邮件样本经过清洗后,是这样的:
清洗过的英语邮件:
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.
清洗过的挪威语邮件:
Grunnet manglende dekning på deres kort for månedlig trekk, blir dere nå overført til årlig fakturering. I morgen vil dere motta faktura for hosting og drift av nettbutikk for perioden 05.03.2018-05.03.2019.Ta gjerne kontakt om dere har spørsmål.
清洗过的意大利语邮件:
Grazie mille per averci contattato! Apprezziamo molto che abbiate trovato il tempo per inviarci i vostri commenti e siamo lieti che vi piaccia l'App. Sentitevi liberi di parlare di con i vostri amici o di sostenerci lasciando una recensione nell'AppStore.
完成这一预处理步骤之后,我们可以进一步探索总结流程剩下的部分。
第二步:检测语言
由于要总结的邮件可能使用任何语言,我们首先需要做的就是判定邮件的语言。有很多使用机器学习技术识别文本语言的Python库,例如polyglot、langdetect、textblob。我使用了langdetect,它支持55种不同的语言。只需一个简单的函数调用就可以检测语言:
from langdetect import detect
lang = detect(cleaned_email) # 如果是英语邮件,那么lang = 'en'
第三步:句子分割
识别了每封邮件的语言后,我们就可以根据不同语言的规则(标点符号)将邮件分割成句子。我们将使用NLTK:
from nltk.tokenize import sent_tokenize
sentences = sent_tokenize(email, language = lang)
第四步:Skip-Thought编码器
我们需要找到一种方式,为邮件中的每句话生成固定长度的向量表示。该表示应当编码句子的内在语义和含义。知名的Skip-Gram Word2Vec词嵌入生成方法可以为模型词汇表收录的词提供词嵌入(FastText这样更酷炫的方法能够基于子词信息为模型词汇表外的单词生成嵌入)。
有了词嵌入,只需对每句话包含的词嵌入进行加权求和,即可得到句嵌入。之所以要加权,是因为“and”、“to”、“the”等一些频繁出现的单词完全没有或几乎没有提供任何关于句子的信息。而只在个别句子中出现的一些罕见词,代表性要高很多。因此,权重的取值和词频逆相关。具体细节可以参考Sanjeev Arora等的论文(ICLR17/SyK00v5xx)
然而,这样的无监督方法没有考虑句子中单词的顺序。这可能对模型的表现造成不利影响。所以我选择在维基百科数据上训练一个Skip-Thought句编码器。Skip-Thoughts模型包括两部分:
编码器网络: 编码器通常是一个GRU循环神经网络,为输入中的每个句子Si生成固定长度的向量表示hi。将GRU单元的最终隐藏状态(即,在它见过整个句子之后得到的隐藏状态)传给多个密集层,得到编码表示hi。
解码器网络: 解码器网络接受向量表示hi作为输入,并尝试生成两个句子——Si-1和Si+1,分别为可能出现在输入句子之前和之后的句子。生成前句和后句的是独立的解码器,均为GRU循环神经网络。向量表示hi作为解码器网络GRU的初始隐藏状态。
给定包含句子序列的数据集,解码器的目标是逐词生成前句和后句。训练编码器-解码器网络以最小化句子的重建损失,在此过程中,编码器学习生成能为解码器编码足够信息的向量表示,以便解码器生成相邻句子。这些学习到的表示满足语义上相似的句子在向量空间中的嵌入互相接近,因此适合用于聚类。在我们的例子中,邮件中的句子作为编码器网络的输入,以得到所需向量表示。获得句嵌入的Skip-Thoughts方法的细节请参考原论文(arXiv:1506.06726)。
给定一个句子(灰点),模型尝试预测前句(红点)和后句(绿点)
至于实现,我使用了论文作者开源的代码。该实现基于Theano,可以通过GitHub仓库ryankiros/skip-thoughts获取。这个实现很容易使用,只需几行代码就可以获取一封邮件的句嵌入:
import skipthoughts
# 你首先需要下载预训练模型
model = skipthoughts.load_model()
encoder = skipthoughts.Encoder(model)
encoded = encoder.encode(sentences)
第五步:聚类
为邮件中的每个句子生成句嵌入后,我们将这些高维向量空间中的嵌入聚类为数量预定义的一组聚类。聚类的数目将等于总结所需的句数。我为总结选择的句数等于邮件总句数的平方根。另一种可能的方案是等于总句数的某个百分比,比如30%. 下面是聚类的代码:
import numpy as np
from sklearn.cluster importKMeans
n_clusters = np.ceil(len(encoded)**0.5)
kmeans = KMeans(n_clusters=n_clusters)
kmeans = kmeans.fit(encoded)
第六步:总结
句嵌入的每个聚类可以看成一组语义上相似的句子,其含义可以通过其中的一个候选句子表达。我们选取向量表示最接近聚类中心的句子作为候选句子。每个聚类选出的候选句子经过排序构成邮件总结。总结中候选句子的顺序取决于其所在聚类中的句子在原邮件中的位置。例如,如果某个候选句子所在聚类中的大多数句子出现在邮件开始,那么该句就被选为总结的第一句。下面几行代码实现了这一算法:
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.
挪威语邮件:
Grunnet manglende dekning på deres kort for månedlig trekk, blir dere nå overført til årlig fakturering. I morgen vil dere motta faktura for hosting og drift av nettbutikk for perioden 05.03.2018-05.03.2019.Ta gjerne kontakt om dere har spørsmål.
意大利语邮件:
Apprezziamo molto che abbiate trovato il tempo per inviarci i vostri commenti e siamo lieti che vi piaccia l'App. Sentitevi liberi di parlare di con i vostri amici o di sostenerci lasciando una recensione nell'AppStore.
训练
前面提到的Skip-Thought的代码仓库已经提供了针对英语的预训练模型。其他一些语言需要自行训练。我们使用了维基百科作为语料,从维基媒体基金会网站下载了.bz2压缩文件,解压缩得到.xml文件。接着解析.xml文件,去除html标记,只剩下纯文本。有很多解析维基百科文件的工具,没有一个是完美的。取决于使用的解析方法,解析可能要花大量时间。我使用的是GitHub上的attardi/wikiextractor,不算最好的,不过是免费的,而且可以在合理的时间内完成解析任务。我还对得到的纯文本进行了一些简单的预处理,比如移除换行符。这样我就得到了大量的训练数据,可以让Skip-Thoughts模型慢慢训练了。
Skip-Thoughts的训练过程还要用到预训练的词向量,我使用了Facebook的FastText预训练词嵌入。由于这些词嵌入也是在维基百科上训练的,所以极少遇到词汇表外的单词。
实现细节
我把实现的模型的一个简化版本放到了GitHub上(jatana-research/email-summarization)。这一简化版只支持英语邮件,但是实现了上面提及的所有步骤,效果也很不错。
结果
你也许已经注意到了,模型在只包含三两句话的邮件上表现要差不少。例如,只包含3句话的邮件的总结会有2句话,而原本的3句话可能各自表达完全不同的事情,漏掉任何一句都是不可接受的。这正是为什么通常情况下在短输入上不使用提取式方法进行总结的原因。序列到序列的监督模型更适合这一任务。不过在我们的例子中,邮件一般没有这么短,所以提取式方法的效果惊人得好。
使用Skip-Thoughts向量的一个劣势是模型需要花很多时间训练。尽管2-3天的训练之后就可以得到可接受的结果,但为了得到较好的结果,我还是训练了大约一周。由于损失被句长归一化了,在迭代过程中损失波动得很厉害。
我们可以看下数据集中最相似的句对,看看Skip-Thoughts模型表现得有多好:
I can assure you that our developers are already aware of the issue and are trying to solve it as soon as possible.
AND
I have already forwarded your problem report to our developers and they will now investigate this issue with the login page in further detail in order to detect the source of this problem.
--------------------------------------------------------------------I am very sorry to hear that.
AND
We sincerely apologize for the inconvenience caused.
--------------------------------------------------------------------Therefore, I would kindly ask you to tell me which operating system you are using the app on.
AND
Can you specify which device you are usingas well as the Androidor iOS version it currently has installed?
从上面的句子来看,这个模型的效果惊人地好,可以找出相似的句子,即使这些句子的长度很不一样,使用的词汇也大不相同。
可能的改进
本文介绍的方法效果相当不错,但还不够完美。通过增加模型复杂度,有很多可以改进的地方:
Quick-Thought向量,Skip-Thoughts的改进版,可能降低训练时间,提升表现。
Skip-Thoughts编码表示的维度为4800。由于维度诅咒,这样的高维向量不是很适合聚类。我们也许可以使用自动编码器或LSTM自动编码器在聚类前降低这些向量的维度。
我们不一定要使用提取式方法。我们可以训练一个解码器网络,让它转换聚类中心的编码表示为自然语言句子,从而实现摘要式总结。Skip-Thoughts编码器生成的数据可以用来训练这一解码器。然而,如果我们希望解码器生成看上去合理的、语法正确的句子,那么需要非常小心地调整超参数和设计架构。
机器配置
所有这些试验都是在一个n1-highmem-8的Google云主机上进行的:十核Intel Xeon CPU,Nvidia Tesla K80 GPU,52GB 内存。
特别感谢我的指导者Rahul Kumar全程给出的意见和建议,没有他我不可能完成这一切。我也很感激Jatana.ai给我提供机会和资源完成这项工作。
全部0条评论
快来发表一下你的评论吧 !