Alfrick Opidi作者杜伟 张倩参与

文章太长不想看?ML 文本自动摘要了解一下

我们在阅读新闻报道等实时性文章时,需要快速归纳出文章的大意。但是,如果将一篇很长的文章归纳成一个能够涵盖原文中心思想的小段落,则需要我们耗费大量时间。本文介绍了自然语言处理中的两种文本自动摘要生成方法——抽取式和抽象式文本摘要。这两种方法通过计算文本中句子成分的权重来生成摘要,可以大大节省通读全文以及归纳总结主要信息的时间,为读者提供方便。

你是否曾将一篇冗长的文档归纳为一个小的段落?你用了多长时间呢?手动归纳总结耗费时间、枯燥乏味。文本自动摘要可以克服此类难题,帮你轻松归纳出一篇文章的中心思想。

文本摘要方法能够对冗长文本进行简洁准确的总结,同时将重点放在传达有用信息的章节,而又不失去文章大意。

文本自动摘要旨在将冗长文档变成缩写版本,若手动完成则可能非常麻烦且成本高昂。

在生成需要的摘要文本之前,机器学习算法可被训练用以理解文档,识别传达重要事实和信息的章节。

使用文本摘要机器学习算法生成一篇在线新闻文章的摘要

文本自动摘要的必要性

随着目前数字空间中数据的爆炸式增长,而大多又是非结构化的文本数据,因而需要开发文本自动摘要工具,使人们可以轻易获知文本大意。当前,我们可以快速访问大量信息。但是,大多数信息冗长、无关紧要,还可能无法传达其本意。例如,如果你想从一篇在线新闻报道中搜寻一些特定信息,你也许要吃透报道内容,花费大量时间剔除无用信息,之后才能找到自己想要了解的信息。所以,使用能够提取有用信息并剔除无关紧要和无用数据的自动文本摘要生成器变得非常重要。文本摘要的实现可以增强文档的可读性,减少搜寻信息的时间,获得更多适用于特定领域的信息。

文本自动摘要的主要类型

从广义的角度看,自然语言处理(NLP)中有两种文本摘要生成方法:抽取式和抽象式。

抽取式摘要(extraction-based summarization)

在抽取式摘要中,抽取一段文本中表示重点内容的单词子集,并结合起来生成摘要。我们可以将抽取式摘要看作是一支荧光笔-从源文本中抽取主要信息。

荧光笔 = 抽取式摘要

机器学习中,抽取式摘要通常需要衡量基本句子成分的权重,并根据权重结果生成摘要。

不同类型的算法和方法均可用于衡量句子的权重,之后根据各成分之间的关联性和相似性进行排序-并进一步将这些成分连接起来以生成摘要。

如下例所示:

抽取式摘要

如上例所示,抽取式摘要由荧光笔标黄的单词组成,生成摘要的语法可能不准确。

抽象式摘要

在抽象式摘要中,高级深度学习方法(advanced deep learning technique)用于解释和缩写原始文档,就像人类所做的一样。将抽象式摘要想象成一支钢笔-它能生成或许不属于源文档的新句子。

钢笔 = 抽象式摘要

由于抽象式机器学习算法能够生成表示源文本中最重要信息的新短语和句子,所以这些抽象式算法有助于克服抽取式摘要中的语法不准确问题。

如下例所示:

抽象式摘要。

尽管抽象式文本摘要的表现更好,但开发相关算法需要复杂的深度学习技巧和语言模型

为了获得合理产出,抽象式摘要方法必须能够解决诸多自然语言处理问题,如自然语言生成、语义表征和推理排序(inference permutation)。

同样地,抽取式文本摘要方法依然大受欢迎。在本文中,我们将重点介绍抽象式文本摘要方法。

如何执行文本摘要

我们使用以下一段话展示如何执行文本摘要抽取:

我们依照以下步骤对这段话作总结,同时尽可能保留原意。

第一步:将这段话转换成句子

首先,我们将这段话分割成相应的句子。转换成句子的最佳方法是在句点(period)出现时提取一个句子。

第二步:文本处理

接下来,我们在文本处理中移除停止词(那些没有实际意义的常见词,如「and」和「the」)、数字、标点符号以及句子中的其他特殊字符。

句子成分的过滤有助于移除冗余和不重要的信息,这些信息对文本意图的表达或许没有任何价值。

以下是文本处理结果:

第三步:分词

切分各个句子,列出句子中的所有单词。

以下是单词列表:

['peter','elizabeth','took','taxi','attend','night','party','city','party','elizabeth','collapse','rush','hospital', 'diagnose','brain', 'injury', 'doctor','told','peter','stay','besides','get','well','peter', 'stayed','hospital','days','without','leaving']

第四步:评估单词的加权出现频率(occurrence frequency)

紧接着,我们计算所有单词的加权出现频率。为此,我们用每个单词的出现频率除以这段话中出现最多次的单词的频率,在这段话中出现最多的是 Peter,总共出现了三次。

下表给出了每个单词的加权出现频率。

第五步:用相应的加权频率替代原句中的各个单词,然后计算总和。

我们在文本处理步骤中已经移除了停止词和特殊字符等无关紧要的单词,因而它们的加权频率为零,也就没有必要在计算时加上。

根据所有单词的加权频率总和,我们可以推导出:第一个句子在整段话中的权重最大。所以,第一个句子能够对这段话的意思作出最具代表性的总结。

此外,如果第一个句子与第三个句子(该句的权重在整段话中排第二)相结合,则可以作出更好的总结。

以上例子只是基本说明了如何在机器学习中执行抽取式文本摘要。现在,我们看看如何在创建实际摘要生成器中运用上述概念。

维基百科文章的文本摘要

让我们动手创建一个可以简化冗长 web 文章中信息的文本摘要生成器。为简单起见,除了 Python 的 NLTK toolkit,我们不使用任何其他机器学习库(machine learning library)。

以下是摘要生成器的代码 blueprint:

# Creating a dictionary for the word frequency table
frequency_table = _create_dictionary_table(article)

# Tokenizing the sentences
sentences = sent_tokenize(article)

# Algorithm for scoring a sentence by its words
sentence_scores = _calculate_sentence_scores(sentences, frequency_table)

# Getting the threshold
threshold = _calculate_average_score(sentence_scores)

# Producing the summary
article_summary = _get_article_summary(sentences, sentence_scores, 1.5 * threshold)

print(article_summary)

依照下列步骤使用 Python 语言创建一个简单的文本摘要生成器。

第一步:准备数据

在这个例子中,我们想总结一下这篇 Wikipedia 文章的信息,这篇文章只是对 20 世纪发生的主要事件进行概述。

为了获取这篇文章的文本,我们将使用 Beautiful Soup 库。

以下是抓取文章内容的代码:

import bs4 as BeautifulSoup
import urllib.request  

# Fetching the content from the URL
fetched_data = urllib.request.urlopen('https://en.wikipedia.org/wiki/20th_century')

article_read = fetched_data.read()

# Parsing the URL content and storing in a variable
article_parsed = BeautifulSoup.BeautifulSoup(article_read,'html.parser')

# Returning <p> tags
paragraphs = article_parsed.find_all('p')

article_content = ''

# Looping through the paragraphs and adding them to the variable
for p in paragraphs:  
    article_content += p.text

在以上代码中,我们首先导入抓取网页数据所必需的库。BeautifulSoup 库用于解析网页内容,而 urllib library 用于连接网页和检索 HTML。

BeautifulSoup 将输入文本转化为 Unicode 字符,将输出文本转化为 UTF-8 字符,省去了从 web 上抓取文本时处理不同字符集编码的麻烦。

我们使用 urllib.request 程序中的 urlopen 函数打开网页。之后,使用 read 函数读取所抓取的数据对象。为了解析数据,我们调用 BeautifulSoup 对象,并向它传递两个参数,即 article_read 和 html.parser。

find_all 函数用于传回 HTML 中出现的所有<p>元素。此外,.text 使我们只能选择<p>元素中的文本。

第二步:处理数据

为尽可能确保废弃的文本数据无噪声,我们将执行一些基本的文本清理(text cleaning)。为协助完成这一处理过程,我们将从 NLTK 库中导入一个停止词列表。

我们还将引入 PorterStemmer,这是一种将单词还原成词根形式的算法。例如,单词 cleaning、cleaned 和 cleaner 都可以还原成词根 clean。

此外,我们还将创建一个包含文本中每一单词出现频率的字典表。我们将依次读取文本及相应单词,以消除所有停止词。

之后,我们将检查单词是否出现在 frequency_table 中。如果一个单词之前就在字典中,则其值更新 1。否则,如果一个单词首次被识别到,则其值设置为 1。

例如,频率表应如下所示:

代码如下:

from nltk.corpus import stopwords
from nltk.stem import PorterStemmer
def _create_dictionary_table(text_string) -> dict:

    # Removing stop words
    stop_words = set(stopwords.words("english"))

    words = word_tokenize(text_string)

    # Reducing words to their root form
    stem = PorterStemmer()

    # Creating dictionary for the word frequency table
    frequency_table = dict()
    for wd in words:
        wd = stem.stem(wd)
        if wd in stop_words:
            continue
        if wd in frequency_table:
            frequency_table[wd] += 1
        else:
            frequency_table[wd] = 1

    return frequency_table

第三步:将文章分割成句子

为了将 article_content 分割成一个句子集,我们将使用 NLTK 库中的内置方法。

from nltk.tokenize import word_tokenize, sent_tokenize

sentences = sent_tokenize(article)

第四步:确定句子的加权频率

为了评估文本中每个句子的分数,我们将分析每个单词的出现频率。在这种情况下,我们将根据句子中的单词对该句进行评分,也就是加上句子中每个重要单词的出现频率。

请看以下代码:

def _calculate_sentence_scores(sentences, frequency_table) -> dict:   

    # Algorithm for scoring a sentence by its words
    sentence_weight = dict()

    for sentence in sentences:
        sentence_wordcount = (len(word_tokenize(sentence)))
        sentence_wordcount_without_stop_words = 0
        for word_weight in frequency_table:
            if word_weight in sentence.lower():
                sentence_wordcount_without_stop_words += 1
                if sentence[:7] in sentence_weight:
                    sentence_weight[sentence[:7]] += frequency_table[word_weight]
                else:
                    sentence_weight[sentence[:7]] = frequency_table[word_weight]

        sentence_weight[sentence[:7]] = sentence_weight[sentence[:7]] /        sentence_wordcount_without_stop_words

    return sentence_weight

重要的是,为了避免长句的分数必然高于短句,我们用每个句子的分数除以该句中的单词数。

另外,为了优化字典内存,我们任意添加 sentence[:7],这指的是每个句子的前七个字符。但在较长的文档中,你很可能遇到具有相同首个 n_chars 的句子,这时最好使用哈希函数(hash function)或 index 函数(index function)来处理此类极端情况(edge-cases),避免冲突。

第五步:计算句子阈值

为了进一步调整适合摘要的句子类型,我们将创建句子的平均分。借助于这个阈值,我们可以避免选择分数低于平均分的句子。

代码如下:

def _calculate_average_score(sentence_weight) -> int:

    # Calculating the average score for the sentences
    sum_values = 0
    for entry in sentence_weight:
        sum_values += sentence_weight[entry]

    # Getting sentence average value from source text
    average_score = (sum_values / len(sentence_weight))

    return average_score

第六步:生成摘要

最后,我们拥有了所有必需的参数,因而现在可以生成文章摘要了。

代码如下:

def _get_article_summary(sentences, sentence_weight, threshold):
    sentence_counter = 0
    article_summary = ''

    for sentence in sentences:
        if sentence[:7] in sentence_weight and sentence_weight[sentence[:7]] >= (threshold):
            article_summary += " " + sentence
            sentence_counter += 1

    return article_summary

总结

下图展示了创建文本摘要算法的工作流程。

以下是机器学习中简单抽取式文本摘要生成器的完整代码:

#importing libraries
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer
from nltk.tokenize import word_tokenize, sent_tokenize
import bs4 as BeautifulSoup
import urllib.request  

#fetching the content from the URL
fetched_data = urllib.request.urlopen('https://en.wikipedia.org/wiki/20th_century')

article_read = fetched_data.read()

#parsing the URL content and storing in a variable
article_parsed = BeautifulSoup.BeautifulSoup(article_read,'html.parser')

#returning <p> tags
paragraphs = article_parsed.find_all('p')

article_content = ''

#looping through the paragraphs and adding them to the variable
for p in paragraphs:  
    article_content += p.text


def _create_dictionary_table(text_string) -> dict:

    #removing stop words
    stop_words = set(stopwords.words("english"))

    words = word_tokenize(text_string)

    #reducing words to their root form
    stem = PorterStemmer()

    #creating dictionary for the word frequency table
    frequency_table = dict()
    for wd in words:
        wd = stem.stem(wd)
        if wd in stop_words:
            continue
        if wd in frequency_table:
            frequency_table[wd] += 1
        else:
            frequency_table[wd] = 1

    return frequency_table


def _calculate_sentence_scores(sentences, frequency_table) -> dict:   

    #algorithm for scoring a sentence by its words
    sentence_weight = dict()

    for sentence in sentences:
        sentence_wordcount = (len(word_tokenize(sentence)))
        sentence_wordcount_without_stop_words = 0
        for word_weight in frequency_table:
            if word_weight in sentence.lower():
                sentence_wordcount_without_stop_words += 1
                if sentence[:7] in sentence_weight:
                    sentence_weight[sentence[:7]] += frequency_table[word_weight]
                else:
                    sentence_weight[sentence[:7]] = frequency_table[word_weight]

        sentence_weight[sentence[:7]] = sentence_weight[sentence[:7]] / sentence_wordcount_without_stop_words



    return sentence_weight

def _calculate_average_score(sentence_weight) -> int:

    #calculating the average score for the sentences
    sum_values = 0
    for entry in sentence_weight:
        sum_values += sentence_weight[entry]

    #getting sentence average value from source text
    average_score = (sum_values / len(sentence_weight))

    return average_score

def _get_article_summary(sentences, sentence_weight, threshold):
    sentence_counter = 0
    article_summary = ''

    for sentence in sentences:
        if sentence[:7] in sentence_weight and sentence_weight[sentence[:7]] >= (threshold):
            article_summary += " " + sentence
            sentence_counter += 1

    return article_summary

def _run_article_summary(article):

    #creating a dictionary for the word frequency table
    frequency_table = _create_dictionary_table(article)

    #tokenizing the sentences
    sentences = sent_tokenize(article)

    #algorithm for scoring a sentence by its words
    sentence_scores = _calculate_sentence_scores(sentences, frequency_table)

    #getting the threshold
    threshold = _calculate_average_score(sentence_scores)

    #producing the summary
    article_summary = _get_article_summary(sentences, sentence_scores, 1.5 * threshold)

    return article_summary

if __name__ == '__main__':
    summary_results = _run_article_summary(article_content)
    print(summary_results)

点击原文中的以下按钮在 FloydHub Notebook 上运行代码:

在这个例子中,我们所采用的阈值是平均分的 1.5 倍。这个超参数值(hyperparameter value)在几次试验后为我们生成了良好的结果。当然,你可以根据自身的偏好对数值进行微调,并改进摘要生成效果。

下图是 Wikipedia 文章的生成摘要。

使用文本摘要算法生成的 Wikipedia 文章摘要。

如你所见,运行代码可以对冗长的 Wikipedia 文章进行总结,并简要概述 20 世纪发生的主要事件。

尽管如此,我们还可以改进摘要生成器,使之更好地生成长篇幅文本的简洁、精确摘要。

更多内容

当然,本文只是简要介绍了机器学习中使用文本摘要算法所能实现的功能。

若想了解更多有关该主题,特别是抽象式文本摘要的知识,下面一些有用的资源可以为你提供帮助:

有没有可能将两种方法(抽象式和抽取式文本自动摘要)相结合?这是指针生成网络(pointer generator network)的主要原理,该网络通过结合抽取(指向)和抽象(生成)取得了最佳效果。

图源:"Taming Recurrent Neural Networks for Better Summarization"

《WikiHow: A Large Scale Text Summarization Dataset》一文提出了一个新的大规模文本自动摘要数据集 WikiHow,该数据集包含提取自 WikiHow 在线知识库的 230000 多篇文章。目前可用的大多数数据集的规模不足以训练序列到序列模型,它们也许只能提供有限的摘要,并且更适合执行抽取式摘要。但是,WikiHow 数据集规模大,质量高,能够在抽象式文本摘要中获得最优结果。

《Pretraining-Based Natural Language Generation for Text Summarization》一文提出了一个基于序列到序列范式的独特二阶段模型。该模型同时在编码器和解码器侧利用 BERT,并在学习过程中注重强化目标。当该模型在一些基准数据集上进行评估时,结果显示,该方法在文本自动摘要中表现更好,尤其相较于其他传统系统而言。

原文链接:https://blog.floydhub.com/gentle-introduction-to-text-summarization-in-machine-learning/

工程分词信息抽取NLP文本摘要
2
相关数据
深度学习技术

深度学习(deep learning)是机器学习的分支,是一种试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法。 深度学习是机器学习中一种基于对数据进行表征学习的算法,至今已有数种深度学习框架,如卷积神经网络和深度置信网络和递归神经网络等已被应用在计算机视觉、语音识别、自然语言处理、音频识别与生物信息学等领域并获取了极好的效果。

权重技术

线性模型中特征的系数,或深度网络中的边。训练线性模型的目标是确定每个特征的理想权重。如果权重为 0,则相应的特征对模型来说没有任何贡献。

机器学习技术

机器学习是人工智能的一个分支,是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、计算复杂性理论等多门学科。机器学习理论主要是设计和分析一些让计算机可以自动“学习”的算法。因为学习算法中涉及了大量的统计学理论,机器学习与推断统计学联系尤为密切,也被称为统计学习理论。算法设计方面,机器学习理论关注可以实现的,行之有效的学习算法。

基准技术

一种简单的模型或启发法,用作比较模型效果时的参考点。基准有助于模型开发者针对特定问题量化最低预期效果。

参数技术

在数学和统计学裡,参数(英语:parameter)是使用通用变量来建立函数和变量之间关系(当这种关系很难用方程来阐述时)的一个数量。

超参数技术

在机器学习中,超参数是在学习过程开始之前设置其值的参数。 相反,其他参数的值是通过训练得出的。 不同的模型训练算法需要不同的超参数,一些简单的算法(如普通最小二乘回归)不需要。 给定这些超参数,训练算法从数据中学习参数。相同种类的机器学习模型可能需要不同的超参数来适应不同的数据模式,并且必须对其进行调整以便模型能够最优地解决机器学习问题。 在实际应用中一般需要对超参数进行优化,以找到一个超参数元组(tuple),由这些超参数元组形成一个最优化模型,该模型可以将在给定的独立数据上预定义的损失函数最小化。

知识库技术

知识库是用于知识管理的一种特殊的数据库,以便于有关领域知识的采集、整理以及提取。知识库中的知识源于领域专家,它是求解问题所需领域知识的集合,包括基本事实、规则和其它有关信息。

哈希函数技术

散列函数(英语:Hash function)又称散列算法、哈希函数,是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值(hash values,hash codes,hash sums,或hashes)的指纹。散列值通常用一个短的随机字母和数字组成的字符串来代表。好的散列函数在输入域中很少出现散列冲突。在散列表和数据处理中,不抑制冲突来区别数据,会使得数据库记录更难找到。

自然语言处理技术

自然语言处理(英语:natural language processing,缩写作 NLP)是人工智能和语言学领域的分支学科。此领域探讨如何处理及运用自然语言;自然语言认知则是指让电脑“懂”人类的语言。自然语言生成系统把计算机数据转化为自然语言。自然语言理解系统把自然语言转化为计算机程序更易于处理的形式。

序列到序列技术

自然语言生成技术

自然语言生成(NLG)是自然语言处理的一部分,从知识库或逻辑形式等等机器表述系统去生成自然语言。这种形式表述当作心理表述的模型时,心理语言学家会选用语言产出这个术语。自然语言生成系统可以说是一种将资料转换成自然语言表述的翻译器。不过产生最终语言的方法不同于编译程式,因为自然语言多样的表达。NLG出现已久,但是商业NLG技术直到最近才变得普及。自然语言生成可以视为自然语言理解的反向: 自然语言理解系统须要厘清输入句的意涵,从而产生机器表述语言;自然语言生成系统须要决定如何把概念转化成语言。

语言模型技术

语言模型经常使用在许多自然语言处理方面的应用,如语音识别,机器翻译,词性标注,句法分析和资讯检索。由于字词与句子都是任意组合的长度,因此在训练过的语言模型中会出现未曾出现的字串(资料稀疏的问题),也使得在语料库中估算字串的机率变得很困难,这也是要使用近似的平滑n元语法(N-gram)模型之原因。

暂无评论
暂无评论~