TextRank算法
TextRank算法基於PageRank,用於爲文本生成關鍵字和摘要。其論文是:
Mihalcea R, Tarau P. TextRank: Bringing order into texts[C]. Association for Computational Linguistics, 2004.
先從PageRank講起
在淺入淺出:PageRank算法這篇博客中我做過簡要的介紹,這裏再補充一下。
PageRank最開始用來計算網頁的重要性。整個www可以看作一張有向圖圖,節點是網頁。如果網頁A存在到網頁B的鏈接,那麼有一條從網頁A指向網頁B的有向邊。
構造完圖後,使用下面的公式:
S(Vi)是網頁i的中重要性(PR值)。d是阻尼係數,一般設置爲0.85。In(Vi)是存在指向網頁i的鏈接的網頁集合。Out(Vj)是網頁j中的鏈接存在的鏈接指向的網頁的集合。|Out(Vj)|是集合中元素的個數。
也就是說:
對於一篇網頁來說,可以這麼理解:它的重要性,取決於到他的每個鏈接頁面的重要性之和來決定的。每個鏈接到該頁面的頁面的重要性S(Vj)S(Vj)還需要對所有它所出去的頁面所評分。所以除以了OUT(Vj)。同時,該頁面S(Vj)S(Vj)的重要性不能單單由其他的鏈接頁面決定,還包含一定的概率來決定要不要接受由其他頁面來決定,這也就是d的作用。
PageRank需要使用上面的公式多次迭代才能得到結果。初始時,可以設置每個網頁的重要性爲1。上面公式等號左邊計算的結果是迭代後網頁i的PR值,等號右邊用到的PR值全是迭代前的。
舉個例子:
上圖表示了三張網頁之間的鏈接關係,直覺上網頁A最重要。
依然能判斷出A、B、C的重要性。
使用TextRank提取關鍵字
將原文本拆分爲句子,在每個句子中過濾掉停用詞(可選),並只保留指定詞性的單詞(可選)。由此可以得到句子的集合和單詞的集合。
每個單詞作爲pagerank中的一個節點。設定窗口大小爲k,假設一個句子依次由下面的單詞組成:
w1,w2,w3,w4,w5,…,wn
[w1,w2,…,wk]、[w2,w3,…,wk+1]、[w3,w4,…,wk+2]等都是一個窗口。在一個窗口中的任兩個單詞對應的節點之間存在一個無向無權的邊。
基於上面構成圖,可以計算出每個單詞節點的重要性。最重要的若干單詞可以作爲關鍵詞。
使用TextRank提取關鍵短語
參照“使用TextRank提取關鍵詞”提取出若干關鍵詞。若原文本中存在若干個關鍵詞相鄰的情況,那麼這些關鍵詞可以構成一個關鍵短語。
例如,在一篇介紹“支持向量機”的文章中,可以找到三個關鍵詞支持、向量、機,通過關鍵短語提取,可以得到支持向量機。 使用TextRank提取摘要
將每個句子看成圖中的一個節點,若兩個句子之間有相似性,認爲對應的兩個節點之間有一個無向有權邊,權值是相似度。
通過pagerank算法計算得到的重要性最高的若干句子可以當作摘要。
論文中使用下面的公式計算兩個句子Si和Sj的相似度:
分子是在兩個句子中都出現的單詞的數量。|Si|是句子i的單詞數。
由於是有權圖,PageRank公式略做修改:
但是很明顯我只想計算關鍵字,如果把一個單詞視爲一個句子的話,那麼所有句子(單詞)構成的邊的權重都是0(沒有交集,沒有相似性),所以分子分母的權值w約掉了,算法退化爲PageRank。所以說,這裏稱關鍵字提取算法爲PageRank也不爲過。
TextRank代碼
使用textrank源代碼可以抽取摘要,也可以抽取關鍵詞。
下面是一個textrank算法的python源碼:
import numpy as np
import jieba
import jieba.posseg as pseg
class TextRank(object):
def __init__(self, sentence, window, alpha, iternum):
self.sentence = sentence
self.window = window
self.alpha = alpha
self.edge_dict = {} #記錄節點的邊連接字典
self.iternum = iternum#迭代次數
#對句子進行分詞
def cutSentence(self):
jieba.load_userdict('user_dict.txt')
tag_filter = ['a','d','n','v']
seg_result = pseg.cut(self.sentence)
self.word_list = [s.word for s in seg_result if s.flag in tag_filter]
print(self.word_list)
#根據窗口,構建每個節點的相鄰節點,返回邊的集合
def createNodes(self):
tmp_list = []
word_list_len = len(self.word_list)
for index, word in enumerate(self.word_list):
if word not in self.edge_dict.keys():
tmp_list.append(word)
tmp_set = set()
left = index - self.window + 1#窗口左邊界
right = index + self.window#窗口右邊界
if left < 0: left = 0
if right >= word_list_len: right = word_list_len
for i in range(left, right):
if i == index:
continue
tmp_set.add(self.word_list[i])
self.edge_dict[word] = tmp_set
#根據邊的相連關係,構建矩陣
def createMatrix(self):
self.matrix = np.zeros([len(set(self.word_list)), len(set(self.word_list))])
self.word_index = {}#記錄詞的index
self.index_dict = {}#記錄節點index對應的詞
for i, v in enumerate(set(self.word_list)):
self.word_index[v] = i
self.index_dict[i] = v
for key in self.edge_dict.keys():
for w in self.edge_dict[key]:
self.matrix[self.word_index[key]][self.word_index[w]] = 1
self.matrix[self.word_index[w]][self.word_index[key]] = 1
#歸一化
for j in range(self.matrix.shape[1]):
sum = 0
for i in range(self.matrix.shape[0]):
sum += self.matrix[i][j]
for i in range(self.matrix.shape[0]):
self.matrix[i][j] /= sum
#根據textrank公式計算權重
def calPR(self):
self.PR = np.ones([len(set(self.word_list)), 1])
for i in range(self.iternum):
self.PR = (1 - self.alpha) + self.alpha * np.dot(self.matrix, self.PR)
#輸出詞和相應的權重
def printResult(self):
word_pr = {}
for i in range(len(self.PR)):
word_pr[self.index_dict[i]] = self.PR[i][0]
res = sorted(word_pr.items(), key = lambda x : x[1], reverse=True)
print(res)
if __name__ == '__main__':
s = '程序員(英文Programmer)是從事程序開發、維護的專業人員。一般將程序員分爲程序設計人員和程序編碼人員,但兩者的界限並不非常清楚,特別是在中國。軟件從業人員分爲初級程序員、高級程序員、系統分析員和項目經理四大類。'
tr = TextRank(s, 3, 0.85, 700)
tr.cutSentence()
tr.createNodes()
tr.createMatrix()
tr.calPR()
tr.printResult()