人工智能 – NLP 关键词提取:TF-IDF算法 和 TextRank算法

1.基于TF-IDF算法进行关键词抽取 ---- analyse.extract_tags() 解压标签
from jieba import analyse
# 引入TF-IDF关键词抽取接口
tfidf = analyse.extract_tags
 
# 原始文本
text = "\u3000\u3000,中新网,1月7日电\xa0 恰逢CES 2017拉开大幕,却惊闻“AlphaGo升级版”的Master迎来60连胜,人类顶尖围棋手在一周内纷纷败给这个谷歌旗下DeepMind团队打造的“围棋大脑”,显然也为聚焦于人工智能的本届CES增添了声势。而首次参展,并致力于打造“原创AI大脑”的中国深度学习领军企业的商汤科技,在人工智能的浪潮之巅,及众多业界前辈和巨匠面前,将会交出一份怎样的答卷呢?\u3000\u3000徐立,商汤科技CEO在谈起本次参展时谈到:“作为一个成立刚刚两年的创业公司,这次参展,一方面是展示我们最新的人工智能技术和产品,但另一方面,其实是向外表达,我们对于人工智能的理解。人工智能在特定领域中超越人类,是其广泛应用的标志。这与Master的胜利,为围棋世界开拓的新局面不谋而合。”\u3000\u3000正如最后挑战Master的古力在落败后发表的观点:“人类与人工智能共同探索围棋世界的大幕即将拉开,新一次的围棋革命正在进行着”。商汤科技的展台,尽管只有两块屏幕,但却带来对人工智能带来的变革的最好诠释:一面是可以通过深度学习,"
 
# 基于TF-IDF算法进行关键词抽取
keywords = tfidf(text, 10) # 提取频率最高的10个关键词。默认20
print(keywords) # ['人工智能', '围棋', 'Master', '商汤', '参展', 'CES', '大幕', '科技', '人类', '大脑']

# join()转string输出
print("/".join(keywords)) # 人工智能/围棋/Master/商汤/参展/CES/大幕/科技/人类/大脑
print(" ".join(keywords)) # 人工智能 围棋 Master 商汤 参展 CES 大幕 科技 人类 大脑

# 遍历输出
for keyword in keywords:
  print(keyword + "/")
2.基于TextRank算法进行关键词抽取 ---- analyse.textrank() 文本级别
from jieba import analyse
# 引入TextRank关键词抽取接口
textrank = analyse.textrank
 
# 原始文本
text = "'\u3000\u3000,中新网,1月7日电\xa0 恰逢CES 2017拉开大幕,却惊闻“AlphaGo升级版”的Master迎来60连胜,人类顶尖围棋手在一周内纷纷败给这个谷歌旗下DeepMind团队打造的“围棋大脑”,显然也为聚焦于人工智能的本届CES增添了声势。而首次参展,并致力于打造“原创AI大脑”的中国深度学习领军企业的商汤科技,在人工智能的浪潮之巅,及众多业界前辈和巨匠面前,将会交出一份怎样的答卷呢?\u3000\u3000徐立,商汤科技CEO在谈起本次参展时谈到:“作为一个成立刚刚两年的创业公司,这次参展,一方面是展示我们最新的人工智能技术和产品,但另一方面,其实是向外表达,我们对于人工智能的理解。人工智能在特定领域中超越人类,是其广泛应用的标志。这与Master的胜利,为围棋世界开拓的新局面不谋而合。”\u3000\u3000正如最后挑战Master的古力在落败后发表的观点:“人类与人工智能共同探索围棋世界的大幕即将拉开,新一次的围棋革命正在进行着”。商汤科技的展台,尽管只有两块屏幕,但却带来对人工智能带来的变革的最好诠释:一面是可以通过深度学习,"
 
print "\nkeywords by textrank:"
# 基于TextRank算法进行关键词抽取
keywords = tfidf(text, 10) # 提取频率最高的10个关键词。默认20 
# 与TF-IDF算法算法不同,TextRank算法的参数allowPOS有默认值,参数默认allowPOS=('ns', 'n', 'vn', 'v'),
# ["n", "nr", "ns", "vn", "v"],表示只能从词性为名词、人名(r=ren)、地名(s=site)、动名词、动词这些词性的词中抽取关键词。

print(keywords) # ['人工智能', '围棋', '参展', '世界', '人类', '商汤', '打造', '带来', '科技', '大幕']
print("/".join(keywords)) # 人工智能/围棋/参展/世界/人类/商汤/打造/带来/科技/大幕

可以看到,若两个函数都用自己的参数默认值的话,二者print结果并不同。

算法分析
1. TF-IDF算法分析

TF-IDF在实际中主要是将二者相乘,也即TF * IDF,TF为词频(Term Frequency),表示词t在文档d中出现的频率;IDF为反文档频率(Inverse Document Frequency),表示语料库中包含词t的文档的数目的倒数。

nlp 分段关键词 nlp关键词提取算法_nlp 分段关键词


count(t)表示文档di中包含词t的个数;count(di)表示文档di的词的总数。

nlp 分段关键词 nlp关键词提取算法_TF-IDF和TextRank算法_02


num(corpus)表示语料库corpus中文档的总数;num(t)表示语料库corpus中包含t的文档的数目。

TF-IDF实践步骤,也即是一般的文本处理和模型训练步骤:

  • 获取原始文本内容信息。
  • 转换成纯小写,按空格把文章分成独立的词组成的list。
  • 去除噪音符号: [""","=","\","/",":","-","(",")",",",".","\n"]等
  • 去除停用词
  • 提取词干,把相近的词转换为标准形式,比如把文章中的go,going,went,goes统一成go
  • wordcount,统计每个词出现的次数,去掉出现次数较少的词,比如在一百篇文档中,只出现了1~2次的词,显然是没有意义的。
  • 训练idf模型
  • 对输入的每篇测试文章计算其tfidf向量,然后可以利用tfidf向量求文章之间的相似度(比如用欧拉距离,余弦相似度,Jaccard系数等方法)。
2. TextRank算法分析

类似于PageRank的思想,将文本中的语法单元视作图中的节点,如果两个语法单元存在一定语法关系(例如共现),则这两个语法单元在图中就会有一条边相互连接,通过一定的迭代次数,最终不同的节点会有不同的权重,权重高的语法单元可以作为关键词。

节点的权重不仅依赖于它的入度结点,还依赖于这些入度结点的权重,入度结点越多,入度结点的权重越大,说明这个结点的权重越高;

TextRank迭代计算公式为,

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

节点i的权重取决于节点i的邻居节点中i-j这条边的权重 / j的所有出度的边的权重 * 节点j的权重,将这些邻居节点计算的权重相加,再乘上一定的阻尼系数,就是节点i的权重;

阻尼系数 d 一般取0.85;

源码分析:
def extract_tags(self, sentence, topK=20, withWeight=False, allowPOS=(), withFlag=False):
  # 传入了词性限制集合
  if allowPOS:
    allowPOS = frozenset(allowPOS)
    # 调用词性标注接口
    words = self.postokenizer.cut(sentence)
  # 没有传入词性限制集合
  else:
    # 调用分词接口
    words = self.tokenizer.cut(sentence)
  freq = {}
  for w in words:
    if allowPOS:
      if w.flag not in allowPOS:
        continue
      elif not withFlag:
        w = w.word
    wc = w.word if allowPOS and withFlag else w
    # 判断词的长度是否小于2,或者词是否为停用词
    if len(wc.strip()) < 2 or wc.lower() in self.stop_words:
      continue
    # 将其添加到词频词典中,次数加1
    freq[w] = freq.get(w, 0.0) + 1.0
  # 统计词频词典中的总次数
  total = sum(freq.values())
  for k in freq:
    kw = k.word if allowPOS and withFlag else k
    # 计算每个词的tf-idf值
    freq[k] *= self.idf_freq.get(kw, self.median_idf) / total
   
  # 根据tf-idf值进行排序
  if withWeight:
    tags = sorted(freq.items(), key=itemgetter(1), reverse=True)
  else:
    tags = sorted(freq, key=freq.__getitem__, reverse=True)
  # 输出topK个词作为关键词
  if topK:
    return tags[:topK]
  else:
    return tags
def textrank(self, sentence, topK=20, withWeight=False, allowPOS=('ns', 'n', 'vn', 'v'), withFlag=False):
 
  self.pos_filt = frozenset(allowPOS)
  # 定义无向有权图
  g = UndirectWeightedGraph()
  # 定义共现词典
  cm = defaultdict(int)
  # 分词
  words = tuple(self.tokenizer.cut(sentence))
  # 依次遍历每个词
  for i, wp in enumerate(words):
    # 词i 满足过滤条件
    if self.pairfilter(wp):
      # 依次遍历词i 之后窗口范围内的词
      for j in xrange(i + 1, i + self.span):
        # 词j 不能超出整个句子
        if j >= len(words):
          break
        # 词j不满足过滤条件,则跳过
        if not self.pairfilter(words[j]):
          continue
        # 将词i和词j作为key,出现的次数作为value,添加到共现词典中
        if allowPOS and withFlag:
          cm[(wp, words[j])] += 1
        else:
          cm[(wp.word, words[j].word)] += 1
  # 依次遍历共现词典的每个元素,将词i,词j作为一条边起始点和终止点,共现的次数作为边的权重
  for terms, w in cm.items():
    g.addEdge(terms[0], terms[1], w)
   
  # 运行textrank算法
  nodes_rank = g.rank()
   
  # 根据指标值进行排序
  if withWeight:
    tags = sorted(nodes_rank.items(), key=itemgetter(1), reverse=True)
  else:
    tags = sorted(nodes_rank, key=nodes_rank.__getitem__, reverse=True)
 
  # 输出topK个词作为关键词
  if topK:
    return tags[:topK]
  else:
    return tags