TextRank算法抽取关键词

PageRank

由于TextRank是由大名鼎鼎的Google的PageRank算法转化而来,所以这里先介绍一下PageRank算法。

PageRank最开始用来计算网页的重要性。在衡量一个网页的排名时,直觉告诉我们:
(1)一个网页被更多网页链接时,就应该越重要,其排名就应该越靠前。
(2)排名高的网页应具有更大的表决权,即当一个网页被排名高的网页所链接时,其重要性也应该提高。
所以基于以上两点,PageRank算法所建立的模型非常简单:一个网页的排名等于所有链接到该网页的网页的加权排名之和。

这里写图片描述

整个www可以看作一张有向图图,节点是网页。如果网页A存在到网页B的链接,那么有一条从网页A指向网页B的有向边。

S(Vi)是网页i的中重要性(PR值)。d是阻尼系数,一般设置为0.85。In(Vi)是存在指向网页i的链接的网页集合。Out(Vj)是网页j中的链接存在的链接指向的网页的集合,|Out(Vj)|是集合中元素的个数。

通俗来说也就是,对于网页i来说,它的重要性,取决于指向网页i的每个网页的重要性之和来决定的。每个指向到网页i的重要性S(Vj)还需要对所有它所指出去的页面平分,所以要除以|Out(Vj)。同时,该页面S(Vj)的重要性不能单单由其他的链接页面决定,还包含一定的概率来决定要不要接受由其他页面来决定,这也就是d的作用。

但是,为了获得某个网页的重要性,而需要知道其他网页的重要性,这不就等同于“是先有鸡还是先有蛋”的问题了么?幸运的是,PageRank采用power iteration方法破解了这个问题怪圈。先给定一个初始值,然后通过多轮迭代求解,迭代到最后就会收敛于一个结果。当差别小于某个阈值时,就可以结束迭代了。


TextRank

正规的TextRank公式在PageRank的公式的基础上,引入了边的权值的概念,代表两个句子的相似度。

TextRank 一般模型可以表示为一个有向有权图 G =(V, E), 由点集合 V和边集合 E 组成, E 是V ×V的子集。图中任两点 Vi , Vj 之间边的权重为 wji , 对于一个给定的点 Vi, In(Vi) 为 指 向 该 点 的 点 集 合 , Out(Vi) 为点 Vi 指向的点集合。点 Vi 的得分定义如下:

这里写图片描述

其中, d 为阻尼系数, 取值范围为 0 到 1, 代表从图中某一特定点指向其他任意点的概率, 一般取值为 0.85。

使用TextRank 算法计算图中各点的得分时, 需要给图中的点指定任意的初值, 并递归计算直到收敛, 即图中任意一点的误差率小于给定的极限值时就可以达到收敛, 一般该极限值取 0.0001。

由于引入了句子的相似度,所以正规的TextRank公式是用来从文章中自动抽取关键句,但是我们现在是想计算关键字,如果把一个单词视为一个句子的话,那么所有句子(单词)构成的边的权重都是0(没有交集,没有相似性),所以分子分母的权值w约掉了,算法退化为PageRank。所以说,关键字提取算法也就是PageRank。

这里写图片描述

对于提取关键词来说,假如设窗口大小为5,In(Vi)是与词i的距离不大于5的词的集合。Out(Vj)是与词j距离不大于5词的集合,|Out(Vj)|是集合中元素的个数。


样例

对于句子:

程序员(英文Programmer)是从事程序开发、维护的专业人员。一般将程序员分为程序设计人员和程序编码人员,但两者的界限并不非常清楚,特别是在中国。软件从业人员分为初级程序员、高级程序员、系统分析员和项目经理四大类。

首先对这句话分词,得出分词结果:

[程序员/n, (, 英文/nz, programmer/en, ), 是/v, 从事/v, 程序/n, 开发/v, 、/w, 维护/v, 的/uj, 专业/n, 人员/n, 。/w, 一般/a, 将/d, 程序员/n, 分为/v, 程序/n, 设计/vn, 人员/n, 和/c, 程序/n, 编码/n, 人员/n, ,/w, 但/c, 两者/r, 的/uj, 界限/n, 并/c, 不/d, 非常/d, 清楚/a, ,/w, 特别/d, 是/v, 在/p, 中国/ns, 。/w, 软件/n, 从业/b, 人员/n, 分为/v, 初级/b, 程序员/n, 、/w, 高级/a, 程序员/n, 、/w, 系统/n, 分析员/n, 和/c, 项目/n, 经理/n, 四/m, 大/a, 类/q, 。/w]

然后去掉里面的停用词,比如标点符号、常用词、以及“名词、动词、形容词、副词之外的词”。得出实际有用的词语:

[程序员, 英文, 程序, 开发, 维护, 专业, 人员, 程序员, 分为, 程序, 设计, 人员, 程序, 编码, 人员, 界限, 特别, 中国, 软件, 人员, 分为, 程序员, 高级, 程序员, 系统, 分析员, 项目, 经理]

然后我们首先计算距离每个词距离不大于窗口大小的词的集合,然后根据公式进行迭代,直到所有词的重要性收敛到某一个值的时候,就可以停止迭代并输出结果。


代码

  1. package Test;
  2.  
  3. import java.util.*;
  4. import java.util.Map.Entry;
  5.  
  6. /**
  7.  * 基于TextRank算法的关键字提取,适用于单文档
  8.  */
  9. public class TextRankKeyword {
  10.     final static float d = 0.85f;//阻尼系数,一般取值为0.85
  11.     final static int max_iter = 200;//最大迭代次数
  12.     final static float min_diff = 0.001f;//最小区别值,当收敛程度小于这个值结束迭代
  13.  
  14.     /**
  15.      * 使用已经分好的词来计算rank
  16.      */
  17.     public static Map<String, Float> getRank(List<String> termList, int windowSize) {
  18.         List<String> wordList = new ArrayList<String>(termList.size());//去掉停用词后的词序列
  19.         for (String str : termList) {
  20.                wordList.add(str);
  21.         }
  22.         //System.out.println(wordList);
  23.         Map<String, Set<String>> words = new LinkedHashMap<String, Set<String>>();//词和它对应的邻居们
  24.         Queue<String> que = new LinkedList<String>();//用一个队列来表示窗口的移动
  25.         for (String w : wordList) {
  26.            if (!words.containsKey(w)) {
  27.                words.put(w, new TreeSet<String>());
  28.            }
  29.            if (que.size() >= windowSize) {//如果队列长度大于窗口大小了,则把队列头元素移除
  30.                que.poll();
  31.            }
  32.            for (String qWord : que) {
  33.                if (w.equals(qWord)) {
  34.                    continue;
  35.                }
  36.                //既然是邻居,那么关系是相互的,遍历一遍即可
  37.                words.get(w).add(qWord);
  38.                words.get(qWord).add(w);
  39.            }
  40.            que.offer(w);//w添加到队列尾部
  41.         }
  42.         //System.out.println(words);
  43.         Map<String, Float> score = new LinkedHashMap<String, Float>();//每一轮迭代后的词和对应的权值
  44.         for (int i = 0; i < max_iter; ++i) {
  45.            Map<String, Float> m = new LinkedHashMap<String, Float>();//此次迭代中的结果
  46.            float max_diff = 0;//此次迭代中变化最大的权值
  47.            for (Map.Entry<String, Set<String>> entry : words.entrySet()) {
  48.                String key = entry.getKey();
  49.                Set<String> value = entry.getValue();
  50.                m.put(key, 1 - d);
  51.                for (String element : value) {
  52.                    int size = words.get(element).size();
  53.                    if (key.equals(element) || size == 0) continue;
  54.                    m.put(key, m.get(key) + d / size * (score.get(element) == null ? 0 : score.get(element)));
  55.                        //按照公式计算新的权值
  56.                }
  57.                max_diff = Math.max(max_diff, Math.abs(m.get(key) - (score.get(key) == null ? 0 : score.get(key))));
  58.            }
  59.            score = m;//新一轮的迭代结果存进score
  60.            if (max_diff <= min_diff) break;//变化最大的权值小于min_diff,结束迭代
  61.         }
  62.  
  63.         return score;
  64.     }
  65.  
  66.     public static void main(String[] args) {
  67.         String[] s={"程序员", "英文", "程序", "开发","维护", "专业",
  68.            "人员", "程序员", "分为", "程序", "设计", "人员",
  69.            "程序", "编码", "人员", "界限", "特别", "中国",
  70.            "软件", "人员", "分为", "程序员", "高级", "程序员",
  71.            "系统", "分析员", "项目", "经理"};
  72.         List<String> termList = Arrays.asList(s);
  73.         int windowSize=5;//窗口大小
  74.         Map<String, Float> map=getRank(termList,windowSize);
  75.         for (Entry<String, Float> entry : map.entrySet()) {
  76.            String key = entry.getKey();
  77.            float value = entry.getValue();
  78.            System.out.println(key+"="+value);
  79.         }
  80.     }
  81.  
  82. }

输出结果

  1. 程序员=1.9249978
  2. 英文=0.7098714
  3. 程序=1.4025854
  4. 开发=0.82074183
  5. 维护=0.9321688
  6. 专业=0.9321688
  7. 人员=1.629035
  8. 分为=1.4027836
  9. 设计=0.6992446
  10. 编码=0.82671607
  11. 界限=0.8220693
  12. 特别=0.9335202
  13. 中国=0.9341458
  14. 软件=0.93525416
  15. 高级=0.97473735
  16. 系统=0.885048
  17. 分析员=0.7710108
  18. 项目=0.7710108
  19. 经理=0.64640945

对结果进行Map排序之后的结果:

  1. 程序员=1.9249978
  2. 人员=1.629035
  3. 分为=1.4027836
  4. 程序=1.4025854
  5. 高级=0.97473735
  6. 软件=0.93525416
  7. 中国=0.9341458
  8. 特别=0.9335202
  9. 维护=0.9321688
  10. 专业=0.9321688
  11. 系统=0.885048
  12. 编码=0.82671607
  13. 界限=0.8220693
  14. 开发=0.82074183
  15. 分析员=0.7710108
  16. 项目=0.7710108
  17. 英文=0.7098714
  18. 设计=0.6992446
  19. 经理=0.64640945

可以看出结果还是有一定说服性的。

你可能感兴趣的:(Java,算法)