⾃然语⾔处理(⼀)--关键词提取
最近学习使⽤了传统的⾃然语⾔处理技术进⾏关键词的提取,接下来我介绍⼀下两种常⽤的算法:TFIDF和TextRank。⽬前BiLSTM 也可以⽤于提取⽂本关键词,有空再学。
1.TF-IDF
TF-IDF(term frequency-inverse document frequency)是⼀种⽤于信息检索与数据挖掘的常⽤加权技术。TF-IDF是⼀种统计⽅法,⽤来评估⼀个字词对于⼀个⽂件集或语料库中的⼀份⽂件的重要程度。
⾸先解释⼀下TF-IDF的意思:
TF(term frequency):词语在⼀篇⽂章中出现的频率
IDF(inverse document frequency):反⽂档频率,与词语在其他⽂档中出现的频率负相关
TF-IDF的主要思想是:如果某个词或短语在⼀篇⽂章中出现的频率⾼,即TF值⾼;并且在其他⽂章中很少出现,即IDF值⾼,那么认为这个词或短语具有很好的类别区分能⼒,适合作为该⽂章的关键词。
TF-IDF的具体计算公式为:
⽂档中词的tfidf值越⾼,便认为该词越可以代表该⽂档的主题。TF-IDF算法的python实现如下,同时jieba库中也实现了TF-IDF,有兴趣的话也可以去了解⼀下。
# TF-IDf算法python实现
import re
import math
# 获取⼀个⽂档中每个词的TF值,doc参数保存⽂档中的句⼦列表,返回单词与其tf值的字典
# ⾸先对⽂档中的单词进⾏切分,然后统计每个词的词频
def GetWordTF(doc):
words_count =0# 单词总数
words_map ={}# 单词与单词数的映射
tf_map ={}# tf值映射词典,格式: tf_map[word] = tf_word
for sentence in doc:# 遍历⽂档中的每个句⼦
# 单词的切分⽅式可以根据所给的数据格式进⾏修改
# 我将提取英⽂句⼦中的每个单词,使⽤正则表达式提取并去除空字符串
words_arr =[word for word in re.split(r'\W+',sentence)if word]
words_count +=len(words_arr)# 统计有效词的总长度
for word in words_arr:# 遍历每⼀个词并进⾏统计单词数
words_map[word]= (word,0)+1
for key,val in words_map.items():# 计算每个单词的tf值
tf_map[key]= val / words_count
return tf_map
# 获取⽂档每个单词在⽂档集docSet中的IDF值映射
def GetWordIDF(tfMap,docSet):
docs_num =len(docSet)# ⽂档集中⽂档的总数
word_doc_num ={}# 包含word的⽂档数,格式为word_doc_num[word] = num of doc that contains word
idf_map ={}# idf值映射字典,格式idf_map[word] = idf_word
for key,val in tfMap.items():# 遍历⽂档中出现的单词
for doc in docSet:# 遍历每个⽂档,检查该⽂档中是否出现了单词key
for sentence in doc:# 遍历⽂档中的每个句⼦
words_arr =[word for word in re.split(r'\W+', sentence)if word]# 提取句⼦中的每个单词
if key in words_arr:# 如果该⽂档中有该词,则统计
word_doc_num[key]= word_(key,0)+1
break
for key,val in word_doc_num.items():# 计算每个单词的idf值
idf_map[key]= math.log(docs_num / val)
return idf_map
# 使⽤TFIDF算法获取⽂档的前topNum个关键词,其中每个⽂档是以列表表⽰的,列表项为⽂档的⼀个句⼦
def GetKeywordsByTFIDF(entityDescriptionList,docSet,topNum):
tf_map = GetWordTF(entityDescriptionList)# 获取每个单词的tf值
idf_map = GetWordIDF(tf_map,docSet)# 获取每个单词的idf值
tfidf_map ={}
for key,val in tf_map.items():# 计算每个词的tfidf值
tfidf_map[key]= tf_map[key]* idf_map[key]
tfidf_sorted_list =sorted(tfidf_map.items(),key =lambda x:x[1],reverse=True)# 将字典按值从⼤到⼩排序
if topNum >len(tfidf_sorted_list):# 保证topNum不⼤于⽂档中词的总数
topNum =len(tfidf_sorted_list)
keywords =[]# 保存⽂档的前topNum个关键字
for i in range(topNum):
keywords.append(tfidf_sorted_list[i][0])# 关键字保存在元组的第0个元素中
return keywords
2.TextRank
TF-IDF算法对于有多段⽂本的关键词提取⾮常有效,但是对于单篇或⽂档集较少的⽂本则表现得不很好。对于单篇⽂档,可以使⽤TextRank算法实现关键词提取。
TextRank是⼀种基于图排序的算法,思想源于⾕歌的PageRank算法,通过把⽂本分割为若⼲组成单元(单词、句⼦)并建⽴图模型,利⽤投票机制对⽂本中的重要成分进⾏排序,仅利⽤单篇⽂档本⾝的信息即可实现关键词提取。
TextRank利⽤投票的原理,让每⼀个单词给它的邻居投赞成票,票的权重取决于⾃⼰的票数。假设每⼀个词是⼀个顶点(Vertex),那么所有的词就构成了⼀个⽹络,这个⽹络⾥⾯每个顶点会有指向其他顶点的边,也会有其他顶点指向⾃⼰的边。通过计算每个顶点所连接的指向⾃⼰的顶点的权重和,最终得到该顶点的权重值。
TextRank存在的主要问题是初始值的确定,为了后续计算的简便性,这⾥会给初值赋为⼀个⾮0值。同时,引⼊了⼀个阻尼系数的概念,该参数表⽰从某⼀个指定的顶点,到任意⼀个其他顶点的概率。TextRank的具体公式如下:
于是,使⽤TextRank算法提取关键词时,⾸先需要把图构建出来。图的节点就是单词,⾄于边可以利⽤n-gram的思路,认为某个单词只与它附近的n个单词有关,即与它附近的n个词对应的节点连⼀条⽆向边。也可以做⼀些其他操作,⽐如把某类词性的词删掉,⼀些⾃定义词删掉,只保留⼀部分单词等。我的代码实现中,假设每个长为k的滑动窗⼝中的任意两个单词对应的节点之间存在⼀条⽆向⽆权边。当构图成功后,就可以使⽤上述公式进⾏迭代求解了。Python实现的代码如下:
# 使⽤TextRank算法实现关键词提取,返回关键词列表,参数含义如下:
# sentence 保存待提取关键字的句⼦
# windowLength 保存滑动窗⼝的⼤⼩
# topNum 表⽰需要返回排名前topNum的关键词
# d 表⽰textrank算法的阻尼系数,默认为0.85
# maxIter 表⽰算法最⼤迭代次数
# minDiff 迭代后变化值⼩于minDiff时也停⽌迭代
def GetKeywordsByTextRank(sentence,windowLength,topNum=3,d=0.85,maxIter=10000,minDiff=0.0001):
# 单词的切分⽅式可以根据所给的数据格式进⾏修改
# 我将提取英⽂句⼦中的每个单词,使⽤正则表达式提取并去除空字符串
words_arr =[word for word in re.split(r'\W+', sentence)if word]
words_num =len(words_arr)# 句⼦的长度
word_graph ={}# 保存每个单词的连接状态,格式为word_graph[word] = [与该词存在边的单词的集合]
textrank_map ={}# 保存每个textrank值的字典,格式为textrank_map[word] = textrank value of the word
textrank_map_t ={}# ⽤于保存前⼀次迭代的tankrank结果
for words_index in range(words_num):# 遍历句⼦中的每个单词,开始根据给定的窗⼝值构图
textrank_map[words_arr[words_index]]=1- d # 为每个词初始化⼀个textrank值
window_lower =max(0, words_index - windowLength)# 滑动窗⼝的下边界
window_upper =min(words_num, words_index + windowLength)# 滑动窗⼝的上边界
for window_index in range(window_lower,window_upper):# 遍历窗⼝中的单词,构建单词的连接关系
if window_index == words_index:# ⾃⼰与⾃⼰认为没有边
continue
if not words_arr[window_index]in (words_arr[words_index],[]):# 检查两词节点之间是否有边
if (words_arr[words_index],0)==0:# 检查该词的边集是否为空
word_graph[words_arr[words_index]]=[words_arr[window_index]]# 为空则⽣成包含该点的边集
else:
word_graph[words_arr[words_index]].append(words_arr[window_index])# 将该边添加到边集中
正则表达式提取中文for iter_i in range(maxIter):# 利⽤textrank计算公式迭代计算
max_diff =0# 表⽰迭代前后两次的变化
for word,neibor_list in word_graph.items():# 遍历每个单词
for con_word in neibor_list:# 遍历与每个单词存在相邻关系的单词
con_word_out_len =len(word_graph[con_word])# 计算当前节点连接的节点个数
if word == con_word or con_word_out_len ==0:
continue# 如果是该节点本⾝或⽆连出节点则不更新
# 使⽤公式对textrank值进⾏更新
textrank_map[word]=1- d + d * textrank_(con_word,0)/con_word_out_len
max_diff =max(max_diff,abs(textrank_map[word]-textrank_(word,0)))
for word,val in textrank_map.items():
textrank_map_t[word]= val
if(max_diff < minDiff):# 各个单词节点的textrank值如果均⽆明显变化,则可结束迭代
break
textrank_sorted_list =sorted(textrank_map.items(),key=lambda x:x[1],reverse=True)# 按照textrank值从⼤到⼩排序
if topNum >len(textrank_sorted_list):# 保证topNum不⼤于⽂档中词的总数
topNum =len(textrank_sorted_list)
if topNum <1:# 保证topNum⼤于0
topNum =1
keywords =[]# 保存将要返回的关键词
for i in range(topNum):
keywords.append(textrank_sorted_list[i][0])
return keywords
可以看出TextRank算法对于⼀段⽂本中多次出现的词,会赋予更⼤的权重,因为它连出的节点更多,所以当各个节点初始权重⼀致时,则最终出现次数最多的词权重就会更⼤。这也会使该算法对类似于“的”、“你、我、他”等常⽤词,会出现⽐较⼤的误差。对于这种情况,可以在最开始构建边时进⾏处理,去掉⼀些停⽤词或者选择⾃⼰需要的词性的词,从⽽得出实际有⽤的词语。
后记:前端暂时不⽀持Latex,公式我只能贴图了。深度学习最近⽐较流⾏,还有很多需要学的呀!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论