HanLP 关键词提取算法分析详解

简介: 给定若干个句子,提取关键词。而TextRank算法是 graphbased ranking model,因此需要构造一个图,要想构造图,就需要确定图中的顶点如何构造,于是就把句子进行分词,将分得的每个词作为图中的顶点。

l  参考论文:《TextRank: Bringing Order into Texts》

l  TextRank算法提取关键词的Java实现

l  TextRank算法自动摘要的Java实现这篇文章中作者大概解释了一下TextRank公式

1. 论文

In this paper, we introduce the TextRank graphbased ranking model for graphs extracted from natural

language texts

TextRank是一个非监督学习算法,它将文本中构造成一个图,将文本中感兴趣的东西(比如分词)当成一个个顶点,然后应用TextRank算法来抽取文本中的一些信息。

Such keywords may constitute useful entries for building an automatic index for a document collection, can be used to classify a text, or may serve as a concise summary for a given document.

提取出来的关键词,可用来作为文本分类,或者概括文本的中心思想。

TextRank通过不断地迭代来提取关键词,每一轮迭代,算法给图中的顶点打分。直到满足某个条件(比如说迭代次数克到200次,或者设置的某个参数达到一个阈值)为止。

For loosely connected graphs, with the number of edges proportional with the number of vertices,

undirected graphs tend to have more gradual convergence curves.

对于稀疏图而言,边的数目与顶点的数目成线性关系,对这样的图进行关键词提取,有着更平缓的收敛曲线(或者叫收敛得慢吧)

It may be therefore useful to indicate and incorporate into the model the “strength”

of the connection between two vertices $V_i$ and $V_j$ as a weight $w_{ij}$ added to the corresponding edge that connects the two vertices.

有时,图中顶点之间的关系并不完全平等,比如某些顶点之间关系密切,这里可用边的权重来衡量顶点之间的相关性重要程度,而这就是带权图模型。

2. 源码实现

2.1 关键词提取流程

给定若干个句子,提取关键词。而TextRank算法是 graphbased ranking model,因此需要构造一个图,要想构造图,就需要确定图中的顶点如何构造,于是就把句子进行分词,将分得的每个词作为图中的顶点。

在选取某个词作为图的顶点的时候,可以应用一些过滤规则:比如说,去除掉分词结果中的停用词、根据词性来添加顶点(比如只将名词和动词作为图的顶点)……

The vertices added to the graph can be restricted with syntactic filters, which select only lexical units of a certain part of speech. One can for instance consider only nouns and verbs for addition to the graph, and consequently draw potential edges based only on relations that can be established between nouns and verbs.

在确定好哪些词作为图的顶点之后,另一个是确定词与词之间的关系,也即:图中的哪些顶点有边?比如说设置一个窗口大小,落在这个窗口内的词,都添加一条边。

it is the application that dictates the type of relations that are used to draw connections between any two such vertices,

确定了边的关系后,接下来是确定边上权值。这个也是根据实际情况而定。

2.2 根据窗口大小确定词的邻接点

前面提到,若干句话分词之后,得到的一个个的词,或者叫Term。假设窗口大小为5。解释一下TextRank算法提取关键词的Java实现文章中提到的如何确定某个Term有哪些邻接Term。

比如说:'程序员' 这个Term,它在多个句子中出现了,因此分词结果'程序员' 出现在四个地方:

 44bf318bab8e53cff505dbc0ad330729e63ef083

索引0处:'程序员'的邻接点有:

英文、programmer、从事、程序

 042113841dd15c0497178cb5192b63eb533215cb

索引9处:'程序员'的邻接点有:

开发、维护、专业、人员、分为、程序、设计、人员

    77f3aa2518421f1fa076ee48f9ebc20371cfcd80

索引26处,'程序员'的邻接点有:

中国、软件、从业人员、分为、高级、程序员、系统分析员、项目经理

 812d1e51281919f46f97b000cc5c1d993f879643

索引28处,'程序员'的邻接点有:

从业人员、分为、程序员、高级、系统分析员、项目经理、四大

结合这四处窗口中的所有的词,得到'程序员'的邻接点如下:

 108772793ff803d1a1d2bd1aa08de4a1e02148c3

因此,当窗口大小设置为5时,Term的前后四个Term都将视为它的邻接点,并且当这个Term出现多次时,则是将它各次出现位置处的前后4个Term合并,最终作为这个Term的邻接点。

从这里可看出:如果某个Term在句子中出现了多次,意味着该Term会比较重要。因为它的邻接点会比较多,也即有很多其他Term给它投了票。这就有点类似于Term Frequency来衡量Term的重要性。

2.3 得分(score)的更新算法

m.put(key, m.get(key) + d / size * (score.get(element) == null ? 0 : score.get(element)));代码的解读:

m.get(key)如果是第一次进入for (String element : value),则是拿到公式前半部分1-d的结果;如果是已经在for (String element : value)进行了迭代,for循环相当于求和:Σvj∈In(vi)Σvj∈In(vi)

for (String element : value) {

    int size = words.get(element).size();

    m.put(key, m.get(key) + d / size * (score.get(element) == null ? 0 : score.get(element)));

}

”他说的确实在理“ 举例来说:,选取窗口大小为5,经过分词并去除停用词后:

 f6ee76a2c5e4e604d208bad25ca7b032aa76a31a

构造的无向图如下:(每条边的权值都为1)

 168a547bfa86ee0e3399f20350ffbcc1f9cb3ae7

以顶点'理'为例,来看一下'理'的得分是如何被更新的。在for (String element : value)一共有两个顶点对 '理'进行投票,首先是 '确实'顶点,与'确实'顶点邻接的顶点有两个,因此:int size = words.get(element).size();中size=2。接下来,来分解一下这行代码:

m.put(key, m.get(key) + d / size * (score.get(element) == null ? 0 : score.get(element)))

m.get(key)为1-d,因为在外层for循环中,m.put(key, 1 - d)已经公式的前半分部(1-d)存储了。

score.get(element) == null ? 0 : score.get(element)这个是获取上一轮迭代的结果。对于初始第一轮迭代而言,score.get(element)为0.8807971,这个值是每个顶点的得分初始值:

          //依据TF来设置初值,  words 代表的是 一张 无向图

          for (Map.Entry<String, Set<String>> entry : words.entrySet()) {

              score.put(entry.getKey(), sigMoid(entry.getValue().size()));//无向图的每个顶点 得分值 初始化

          }

score.get(element)相当于公式中的WS(Vj)WS(Vj)

最后来分析一个 size,size是由代码int size = words.get(element).size()获得的,由于每条边权值为1,size其实相当于:ΣVk∈Out(Vj)wjkΣVk∈Out(Vj)wjk。

In('理')={'确实','说'}

VjVj为'确实'时,Out(Vj)Out(Vj)为{'说','理'},因此:ΣVk∈Out(Vj)wjk=2ΣVk∈Out(Vj)wjk=2。于是,更新顶点'理'的得分:1−d+d∗(1/2)∗0.8807971=0.52433871−d+d∗(1/2)∗0.8807971=0.5243387。然后再通过m.put将临时结果保存起来。

接下来,for (String element : value)继续,此时:VjVj为顶点'说',由于顶点'说'也有两条邻接边,因此有:ΣVk∈Out(Vj)wjk=2ΣVk∈Out(Vj)wjk=2。于是更新顶点'理'的得分:0.5243387+d∗(1/2)∗0.8807971=0.898677470.5243387+d∗(1/2)∗0.8807971=0.89867747。而这就是第一轮迭代时,顶点'理'的得分。

根据上面的1、2中的步骤,for (String element : value)就相当于:ΣVj∈In(Vi)ΣVj∈In(Vi),因为每次都把计算好的结果再put回HashMap m中。

因此,在第一轮迭代中,顶点'理'的得分就是:0.89867747

类似于,经过:max_iter次迭代,或者达到阈值:

              if (max_diff <= min_diff)

                  break;

时,就不再迭代了。

下面再来对代码作个总体说明:

这里是构造无向图的过程

        for (String w : wordList) {

            if (!words.containsKey(w)) {

                //排除了 wordList 中的重复term, 对每个已去重的term, 用 TreeSet<String> 保存该term的邻接顶点

                words.put(w, new TreeSet<String>());

            }

            // 复杂度O(n-1)

            if (que.size() >= 5) {

                //窗口的大小为5,是写死的. 对于一个term_A而言, 它的前4个term、后4个term 都属于term_A的邻接点

                que.poll();

            }

            for (String qWord : que) {

                if (w.equals(qWord)) {

                    continue;

                }

                //既然是邻居,那么关系是相互的,遍历一遍即可

                words.get(w).add(qWord);

                words.get(qWord).add(w);

            }

            que.offer(w);

        }

这里是对图中每个顶点赋值一个初始score过程:

        Map<String, Float> score = new HashMap<String, Float>();//保存最终每个关键词的得分

        //依据TF来设置初值,  words 代表的是 一张 无向图

        for (Map.Entry<String, Set<String>> entry : words.entrySet()) {

            score.put(entry.getKey(), sigMoid(entry.getValue().size()));//无向图的每个顶点 得分值 初始化

        }

接下来,三个for循环:第一个for循环代表迭代次数;第二个for循环代表:对无向图中每一个顶点计算得分;第三个for循环代表:对某个具体的顶点而言,计算它的每个邻接点给它的投票权重。

for (int i = 0; i < max_iter; ++i) {

    //....

    for (Map.Entry<String, Set<String>> entry : words.entrySet()) {

        //...

        for (String element : value) {

这样,就实现了论文中公式:

WS(vi)=(1−d)+d∗ΣVj∈In(Vi)wjiΣVk∈Out(Vj)wjk∗WS(Vj)

WS(vi)=(1−d)+d∗ΣVj∈In(Vi)wjiΣVk∈Out(Vj)wjk∗WS(Vj)

而最终提取出来的关键词是:

[理, 确实, 说]

上面只是用 ”他说的确实在理“ 这句话 演示了TextRank算法的具体细节,在实际应用中可能不合理。因为会存在:

现有统计信息不足以让TextRank支持 某个词 的重要性,算法有局限性。

可见:TextRank提取关键词是受到分词结果的影响的;其次,也受窗口大小的影响。虽然说代码是大致看懂了,但是还是有一些疑问的:比如,为什么用上面那个公式计算,得分高的词语就是关键词了?根据TextRank求关键词与Term Frequency求关键词有什么优势?选取文本中的哪些词建立模型作为图的顶点?基于文本之间的什么样的关系作为图的边?

相关文章
|
2月前
|
机器学习/深度学习 算法 搜索推荐
从理论到实践,Python算法复杂度分析一站式教程,助你轻松驾驭大数据挑战!
【10月更文挑战第4天】在大数据时代,算法效率至关重要。本文从理论入手,介绍时间复杂度和空间复杂度两个核心概念,并通过冒泡排序和快速排序的Python实现详细分析其复杂度。冒泡排序的时间复杂度为O(n^2),空间复杂度为O(1);快速排序平均时间复杂度为O(n log n),空间复杂度为O(log n)。文章还介绍了算法选择、分而治之及空间换时间等优化策略,帮助你在大数据挑战中游刃有余。
67 4
|
13天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
37 1
|
2月前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
2月前
|
算法
PID算法原理分析
【10月更文挑战第12天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。
|
3月前
|
算法 搜索推荐 开发者
别再让复杂度拖你后腿!Python 算法设计与分析实战,教你如何精准评估与优化!
在 Python 编程中,算法的性能至关重要。本文将带您深入了解算法复杂度的概念,包括时间复杂度和空间复杂度。通过具体的例子,如冒泡排序算法 (`O(n^2)` 时间复杂度,`O(1)` 空间复杂度),我们将展示如何评估算法的性能。同时,我们还会介绍如何优化算法,例如使用 Python 的内置函数 `max` 来提高查找最大值的效率,或利用哈希表将查找时间从 `O(n)` 降至 `O(1)`。此外,还将介绍使用 `timeit` 模块等工具来评估算法性能的方法。通过不断实践,您将能更高效地优化 Python 程序。
63 4
|
3月前
|
算法 程序员 Python
程序员必看!Python复杂度分析全攻略,让你的算法设计既快又省内存!
在编程领域,Python以简洁的语法和强大的库支持成为众多程序员的首选语言。然而,性能优化仍是挑战。本文将带你深入了解Python算法的复杂度分析,从时间与空间复杂度入手,分享四大最佳实践:选择合适算法、优化实现、利用Python特性减少空间消耗及定期评估调整,助你写出高效且节省内存的代码,轻松应对各种编程挑战。
53 1
|
2月前
|
算法
PID算法原理分析及优化
【10月更文挑战第6天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。
|
3月前
|
算法 数据可视化
基于SSA奇异谱分析算法的时间序列趋势线提取matlab仿真
奇异谱分析(SSA)是一种基于奇异值分解(SVD)和轨迹矩阵的非线性、非参数时间序列分析方法,适用于提取趋势、周期性和噪声成分。本项目使用MATLAB 2022a版本实现从强干扰序列中提取趋势线,并通过可视化展示了原时间序列与提取的趋势分量。代码实现了滑动窗口下的奇异值分解和分组重构,适用于非线性和非平稳时间序列分析。此方法在气候变化、金融市场和生物医学信号处理等领域有广泛应用。
167 19
|
3月前
|
机器学习/深度学习 存储 人工智能
文本情感识别分析系统Python+SVM分类算法+机器学习人工智能+计算机毕业设计
使用Python作为开发语言,基于文本数据集(一个积极的xls文本格式和一个消极的xls文本格式文件),使用Word2vec对文本进行处理。通过支持向量机SVM算法训练情绪分类模型。实现对文本消极情感和文本积极情感的识别。并基于Django框架开发网页平台实现对用户的可视化操作和数据存储。
52 0
文本情感识别分析系统Python+SVM分类算法+机器学习人工智能+计算机毕业设计
|
2月前
|
算法 安全 Go
Python与Go语言中的哈希算法实现及对比分析
Python与Go语言中的哈希算法实现及对比分析
42 0