一、SnowNLP的获取文本关键词

前面介绍了SnowNLP的获取关键词的方法,这里再重现一下

1 from snownlp import SnowNLP
2 # 提取文本关键词,总结3个关键词
3 text = '随着顶层设计完成,全国政协按下信息化建设快进键:建设开通全国政协委员移动履职平台,开设主题议政群、全国政协书院等栏目,建设委员履职数据库,拓展网上委员履职综合服务功能;建成网络议政远程协商视频会议系统,开展视频调研、远程讨论活动,增强网络议政远程协商实效;建立修订多项信息化规章制度,优化电子政务网络。'
4 s = SnowNLP(text)
5 print(s.keywords(3))
6
7 --->['全国', '政协', '远程']

二、源码分析

我们进入SnowNLP的源码看一下

1、SnowNLP(text)

SnowNLP——获取关键词(keywords(1))-LMLPHP

 这里我们记住self.doc是我们传入的文本

2、keywords(self, limit=5, merge=False)

SnowNLP——获取关键词(keywords(1))-LMLPHP

 这是SnowNLP源码中keywords方法的源码,我们一行一行的看下具体流程:

70 def keywords(self, limit=5, merge=False):

limit参数:这个参数对应的是调用 keywords(3)方法时传入的形参,默认为5,即默认会返回五个关键词。

71 doc = []
72 sents = self.sentences

    这是做了一个赋值,我看看下源码:

    self.sentences是对输入文本的整理,对文本分句、分词

    SnowNLP——获取关键词(keywords(1))-LMLPHP

     SnowNLP——获取关键词(keywords(1))-LMLPHP

     上图参数中的doc是我们传入的文本,这个方法主要功能是将我们传入的文本进行整理

    33行、34行:定义了两个正则表达式,分别是筛选换行符、部分中文标点

    36行:是根据换行符对文本进行切割后进行遍历。

    40行:再上一次循环中,又将文本根据中文标点符号进行分割。

    44行:将得到的句子放到一个list中,即sentences。

  

我们再回到keywords方法中:

72 sents = self.sentences

由此得到一个sents,里面存储的是我们输入文本分割成的句子的list

73  for sent in sents: #对sents进行遍历,得到每个句子
74      words = seg.seg(sent) #对句子进行分词
75      words = normal.filter_stop(words) #去除停用词
76      doc.append(words) #将得到的每个句子的分词加到doc中
77 rank = textrank.KeywordTextRank(doc) #得到一个KeywordTextRank对象
78 rank.solve() #计算词语的关键度,并进行关键排序

这里我们看一下KeywordTextRank()类以及rank.solve()方法:

rank.solve()是计算关键度的方法,是获取关键词的核心!!

 1 class KeywordTextRank(object):
 2
 3     def __init__(self, docs):
 4         self.docs = docs
 5         self.words = {}
 6         self.vertex = {}
 7         self.d = 0.85
 8         self.max_iter = 200
 9         self.min_diff = 0.001
10         self.top = []
11
12     def solve(self):
13         for doc in self.docs: # self.docs是我们传入的文本被分词后的词语list:[['a','b','c'],['d','e'],['f','g']]
14             que = []
15             for word in doc: # 遍历每个句子的词语,得到该句的词
16                 if word not in self.words: # 如果该词不存在self.words中,则添加进去
17                     self.words[word] = set() # 一个字典集合:{'word':set()}
18                     self.vertex[word] = 1.0 # 一个字典集合:{'word':1.0}
19                 que.append(word) #将词加到que中:['word']
20                 if len(que) > 5:
21                     que.pop(0) # 如果que的长度大于5则移除第一个词
22                 for w1 in que: # 遍历que
23                     for w2 in que: # 遍历que
24                         if w1 == w2:
25                             continue # 如果w1与w2相等则结束这一轮循环,继续下一轮循环
26                         self.words[w1].add(w2) # 将词加到自己字典中
27                         self.words[w2].add(w1) #
28         for _ in range(self.max_iter): # 循环200次
29             m = {}
30             max_diff = 0
31             tmp = filter(lambda x: len(self.words[x[0]]) > 0,
32                          self.vertex.items()) # 过滤每个词,判断值位的set中是否有值,有的话保留,返回:[('a', 1), ('b', 1), ('c', 1)]
33             tmp = sorted(tmp, key=lambda x: x[1] / len(self.words[x[0]])) # 根据值位的set的长度排序,返回:[('a', 1), ('b', 1), ('c', 1)]
34             for k, v in tmp: # k为词,v为相关度
35                 for j in self.words[k]: # 遍历每个词对应的set集合(相关词)
36                     if k == j:
37                         continue
38                     if j not in m:
39                         m[j] = 1 - self.d
40                     m[j] += (self.d / len(self.words[k]) * self.vertex[k]) # m值 = 0.85 / set的长度 * 1
41             for k in self.vertex: # {词1:相关度1,词2:相关度2}
42                 if k in m and k in self.vertex:
43                     if abs(m[k] - self.vertex[k]) > max_diff: # 计算本次相关度与上一次相关度之差的绝对值是否符合设定的阈值
44                         max_diff = abs(m[k] - self.vertex[k]) # 改变阈值
45             self.vertex = m # 获取到本次相关度集合
46             if max_diff <= self.min_diff: # 设定退出条件
47                 break
48         self.top = list(self.vertex.items()) # 将字典转成集合
49         self.top = sorted(self.top, key=lambda x: x[1], reverse=True) # 根据相似度进行排序 [('a', 1), ('b', 2), ('c', 3)]
50
51     def top_index(self, limit):
52         return list(map(lambda x: x[0], self.top))[:limit] # 获取list的值的key,并截取list,[0-limit)
53
54     def top(self, limit):
55         return list(map(lambda x: self.docs[x[0]], self.top)) # 获取字典中top字段对应值的value

我们再回到keywords方法中:

79 ret = []
80 for w in rank.top_index(limit): # 获取按词语关键度排序后并截取长度的list
81   ret.append(w)
82  if merge:
83     wm = words_merge.SimpleMerge(self.doc, ret)
84     return wm.merge()
85  return ret

merge默认为False,如果手动设定为True的话将走SimpleMerge类对结果重新处理

看下SimpleMerge源码:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals


class SimpleMerge(object):

    def __init__(self, doc, words):
        self.doc = doc
        self.words = words

    def merge(self):
        trans = {}
        for w in self.words:
            trans[w] = ''
        for w1 in self.words:
            cw = 0
            lw = len(w1)
            for i in range(len(self.doc)-lw+1):
                if w1 == self.doc[i: i+lw]:
                    cw += 1
            for w2 in self.words:
                cnt = 0
                l2 = len(w1)+len(w2)
                for i in range(len(self.doc)-l2+1):
                    if w1+w2 == self.doc[i: i+l2]:
                        cnt += 1
                if cw < cnt*2:
                    trans[w1] = w2
                    break
        ret = []
        for w in self.words:
            if w not in trans:
                continue
            s = ''
            now = trans[w]
            while now:
                s += now
                if now not in trans:
                    break
                tmp = trans[now]
                del trans[now]
                now = tmp
            trans[w] = s
        for w in self.words:
            if w in trans:
                ret.append(w+trans[w])
        return ret

 三、总结

 SnowNLP获取关键词的流程可以总结如下:

1、输入文段

2、按换行符和中文符号(,。?!;)划分成句子:list[句子]

3、对每个句子分词:list[[词]]

4、统计该词的关联词,去重并排序(关联词:该词所在句的其他词都为关联词)

5、计算每个词的关键度(与该词的关联词的数量相关):
  m[i] += 0.85 / len(list[关联词]) * m[i](old)。默认计算200次,期间前后两次m[i]相减绝对值小于等于0.001退出计算

6、排序,截取

如果对于单句文本来说,关键词的选取与词频无关;如果对多个句子来说,关键词的选取与词频、关联词数都有关系,即一个词出现在多个句子中,那他就越可能成为关键词。

 SnowNLP——获取关键词(keywords(1))-LMLPHP

   

03-05 21:31