UDN-企业互联网技术人气社区

板块导航

浏览  : 1016
回复  : 1

[其它] 英文关键词提取之RAKE算法

[复制链接]
舞操的头像 楼主
  RAKE算法是由2010年的论文Automatic keyword extraction from individual documents提出的,比TextRank算法效果更好,原始的源码链接是 https://github.com/aneesha/RAKE,已经很久没有维护了,本文重新整理了代码,做了以下3个工作:

  使其支持python 3.0版本

  使其更灵活地用命令行调用

  代码重构,提高可读性

  RAKE算法思想

  RAKE算法用来做关键词(keyword)的提取,实际上提取的是关键的短语(phrase),并且倾向于较长的短语,在英文中,关键词通常包括多个单词,但很少包含标点符号和停用词,例如and,the,of等,以及其他不包含语义信息的单词。

  RAKE算法首先使用标点符号(如半角的句号、问号、感叹号、逗号等)将一篇文档分成若干分句,然后对于每一个分句,使用停用词作为分隔符将分句分为若干短语,这些短语作为最终提取出的关键词的候选词。

  那么,如何来衡量每个短语的重要程度呢?

  我们注意到,每个短语可以再通过空格分为若干个单词,可以通过给每个单词赋予一个得分,通过累加得到每个短语的得分。一个关键点在于将这个短语中每个单词的共现关系考虑进去。

  最终定义的公式是:

 
3.jpg

  即单词w的得分是该单词的度(是一个网络中的概念,每与一个单词共现在一个短语中,度就加1,考虑该单词本身)除以该单词的词频(该单词在该文档中出现的总次数)。

  然后对于每个候选的关键短语,将其中每个单词的得分累加,并进行排序,RAKE将候选短语总数的前三分之一的认为是抽取出的关键词。

  RAKE的实现

  源码


  源码中使用maxPhraseLength参数来限定候选短语的长度,用来过滤掉过长的短语。

  1. import re
  2. import operator
  3. import argparse
  4. import codecs

  5. def isNumber(s):
  6.     try:
  7.         float(s) if '.' in s else int(s)
  8.         return True
  9.     except ValueError:
  10.         return False

  11. class Rake:
  12.    
  13.     def __init__(self, inputFilePath, stopwordsFilePath, outputFilePath, minPhraseChar, maxPhraseLength):
  14.         self.outputFilePath = outputFilePath
  15.         self.minPhraseChar = minPhraseChar
  16.         self.maxPhraseLength = maxPhraseLength
  17.         # read documents
  18.         self.docs = []
  19.         for document in codecs.open(inputFilePath, 'r', 'utf-8'):
  20.             self.docs.append(document)
  21.         # read stopwords
  22.         stopwords = []
  23.         for word in codecs.open(stopwordsFilePath, 'r', 'utf-8'):
  24.             stopwords.append(word.strip())
  25.         stopwordsRegex = []
  26.         for word in stopwords:
  27.             regex = r'\b' + word + r'(?![\w-])'
  28.             stopwordsRegex.append(regex)
  29.         self.stopwordsPattern = re.compile('|'.join(stopwordsRegex), re.IGNORECASE)

  30.     def separateWords(self, text):
  31.         splitter = re.compile('[^a-zA-Z0-9_\\+\\-/]')
  32.         words = []
  33.         for word in splitter.split(text):
  34.             word = word.strip().lower()
  35.             # leave numbers in phrase, but don't count as words, since they tend to invalidate scores of their phrases
  36.             if len(word) > 0 and word != '' and not isNumber(word):
  37.                 words.append(word)
  38.         return words
  39.    
  40.    
  41.     def calculatePhraseScore(self, phrases):
  42.         # calculate wordFrequency and wordDegree
  43.         wordFrequency = {}
  44.         wordDegree = {}
  45.         for phrase in phrases:
  46.             wordList = self.separateWords(phrase)
  47.             wordListLength = len(wordList)
  48.             wordListDegree = wordListLength - 1
  49.             for word in wordList:
  50.                 wordFrequency.setdefault(word, 0)
  51.                 wordFrequency[word] += 1
  52.                 wordDegree.setdefault(word, 0)
  53.                 wordDegree[word] += wordListDegree
  54.         for item in wordFrequency:
  55.             wordDegree[item] = wordDegree[item] + wordFrequency[item]
  56.    
  57.         # calculate wordScore = wordDegree(w)/wordFrequency(w)
  58.         wordScore = {}
  59.         for item in wordFrequency:
  60.             wordScore.setdefault(item, 0)
  61.             wordScore[item] = wordDegree[item] * 1.0 / wordFrequency[item]

  62.         # calculate phraseScore
  63.         phraseScore = {}
  64.         for phrase in phrases:
  65.             phraseScore.setdefault(phrase, 0)
  66.             wordList = self.separateWords(phrase)
  67.             candidateScore = 0
  68.             for word in wordList:
  69.                 candidateScore += wordScore[word]
  70.             phraseScore[phrase] = candidateScore
  71.         return phraseScore
  72.    
  73.         
  74.     def execute(self):
  75.         file = codecs.open(self.outputFilePath,'w','utf-8')
  76.         for document in self.docs:
  77.             # split a document into sentences
  78.             sentenceDelimiters = re.compile(u'[.!?,;:\t\\\\"\\(\\)\\\'\u2019\u2013]|\\s\\-\\s')
  79.             sentences = sentenceDelimiters.split(document)
  80.             # generate all valid phrases
  81.             phrases = []
  82.             for s in sentences:
  83.                 tmp = re.sub(self.stopwordsPattern, '|', s.strip())
  84.                 phrasesOfSentence = tmp.split("|")
  85.                 for phrase in phrasesOfSentence:
  86.                     phrase = phrase.strip().lower()
  87.                     if phrase != "" and len(phrase) >= self.minPhraseChar and len(phrase.split()) <= self.maxPhraseLength:
  88.                         phrases.append(phrase)
  89.    
  90.             # calculate phrase score
  91.             phraseScore = self.calculatePhraseScore(phrases)
  92.             keywords = sorted(phraseScore.items(), key = operator.itemgetter(1), reverse=True)
  93.             file.write(str(keywords[0:int(len(keywords)/3)]) + "\n")
  94.         file.close()
  95.         
  96. def readParamsFromCmd():
  97.     parser = argparse.ArgumentParser(description = "This is a python implementation of rake(rAPId automatic keyword extraction).")
  98.     parser.add_argument('inputFilePath', help = 'The file path of input document(s). One line represents a document.')
  99.     parser.add_argument('stopwordsFilePath', help = 'The file path of stopwords, each line represents a word.')
  100.     parser.add_argument('-o', '--outputFilePath', help = 'The file path of output. (default output.txt in current dir).', default = 'output.txt')
  101.     parser.add_argument('-m', '--minPhraseChar', type = int, help = 'The minimum number of characters of a phrase.(default 1)', default = 1)
  102.     parser.add_argument('-a', '--maxPhraseLength', type = int, help = 'The maximum length of a phrase.(default 3)', default = 3)
  103.     return parser.parse_args()

  104. params = readParamsFromCmd().__dict__

  105. rake = Rake(params['inputFilePath'], params['stopwordsFilePath'], params['outputFilePath'], params['minPhraseChar'], params['maxPhraseLength'])
  106. rake.execute()
复制代码


  用法

  命令行运行。

  
  1. python rake.py [-h] [-o OUTPUTFILEPATH] [-m MINPHRASECHAR] [-a MAXPHRASELENGTH] inputFilePath stopwordsFilePath
复制代码

  positional arguments:

  inputFilePath The file path of input document(s). One line represents a document.

  stopwordsFilePath The file path of stopwords, each line represents a word.

  optional arguments:

  -h, –help show this help message and exit

  -o OUTPUTFILEPATH, –outputFilePath OUTPUTFILEPATH The file path of output. (default output.txt in current dir).

  -m MINPHRASECHAR, –minPhraseChar MINPHRASECHAR The minimum number of characters of a phrase.(default 1)

  -a MAXPHRASELENGTH, –maxPhraseLength MAXPHRASELENGTH The maximum length of a phrase.(default 3)

  实验

  源码中的example中包括两个文档,第一个文档是论文中给出的一个样例,第二个文档是从wikipedia的NLP词条中拷贝的一段话。如下所示:

2.png


  输出是每个文档中抽取的关键短语以及得分,如下:

1.png


  RAKE可以处理中文吗

  使用RAKE算法处理中文,会遇到一些问题,中文使用停用词来划分短语的效果远不及英文,一句话根本分不了几个关键词,几乎全部粘连在一起,因此效果不好。

  参考资料

  1 Automatic keyword extraction from individual documents by Stuart Rose et al.

原文作者:zhikaizhang 来源:开发者头条

相关帖子

发表于 2016-8-11 09:08:15 | 显示全部楼层
RAKE算法用来做关键词(keyword)的提取,实际上提取的是关键的短语(phrase),并且倾向于较长的短语,在英文中,关键词通常包括多个单词,但很少包含标点符号和停用词,例如and,the,of等,以及其他不包含语义信息的单词
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部