TextRank算法


TextRank是一種用來做關鍵詞提取的算法,也可以用於提取短語和自動摘要。因為TextRank是基於PageRank的,所以首先簡要介紹下PageRank算法。

(1)PageRank

PageRank設計之初是用於Google的網頁排名的,以該公司創辦人拉里·佩奇(Larry Page)之姓來命名。Google用它來體現網頁的相關性和重要性,在搜索引擎優化操作中是經常被用來評估網頁優化的成效因素之一。PageRank通過互聯網中的超鏈接關系來確定一個網頁的排名,其公式是通過一種投票的思想來設計的:如果我們要計算網頁A的PageRank值(以下簡稱PR值),那么我們需要知道有哪些網頁鏈接到網頁A,也就是要首先得到網頁A的入鏈,然后通過入鏈給網頁A的投票來計算網頁A的PR值。這樣設計可以保證達到這樣一個效果:當某些高質量的網頁指向網頁A的時候,那么網頁A的PR值會因為這些高質量的投票而變大,而網頁A被較少網頁指向或被一些PR值較低的網頁指向的時候,A的PR值也不會很大,這樣可以合理地反映一個網頁的質量水平。那么根據以上思想,佩奇設計了下面的公式:

                                                                                                                                     $PR(V_i) = (1-d) + d * \sum_{j \in In(V_i)} \frac{1}{|Out(V_j)|}PR(V_j)$

該公式中,Vi表示某個網頁,Vj表示鏈接到Vi的網頁(即Vi的入鏈),S(Vi)表示網頁Vi的PR值,In(Vi)表示網頁Vi的所有入鏈的集合,Out(Vj)表示網頁,d表示阻尼系數,是用來克服這個公式中“d *”后面的部分的固有缺陷用的:如果僅僅有求和的部分,那么該公式將無法處理沒有入鏈的網頁的PR值,因為這時,根據該公式這些網頁的PR值為0,但實際情況卻不是這樣,所有加入了一個阻尼系數來確保每個網頁都有一個大於0的PR值,根據實驗的結果,在0.85的阻尼系數下,大約100多次迭代PR值就能收斂到一個穩定的值,而當阻尼系數接近1時,需要的迭代次數會陡然增加很多,且排序不穩定。公式中S(Vj)前面的分數指的是Vj所有出鏈指向的網頁應該平分Vj的PR值,這樣才算是把自己的票分給了自己鏈接到的網頁.

(2)TextRank算法提取關鍵詞

TextRank是由PageRank改進而來,其公式有頗多相似之處,這里給出TextRank的公式:

$WS(V_i) = (1-d) + d * \sum_{j \in In(V_i)} \frac{w_{ji}}{\sum_{V_k \in Out(V_j)} w_{jk}} WS(V_j)$

可以看出,該公式僅僅比PageRank多了一個權重項Wji,用來表示兩個節點之間的邊連接有不同的重要程度。TextRank用於關鍵詞提取的算法如下:

     (1)把給定的文本T按照完整句子進行分割;
  (2)對於每個句子,進行分詞和詞性標注處理,並過濾掉停用詞,只保留指定詞性的單詞,如名詞、動詞、形容詞;
  (3)構建候選關鍵詞圖G = (V,E),其中V為節點集,由(2)生成的候選關鍵詞組成,然后采用共現關系(co-occurrence)構造任兩點之間的邊,兩個節點之間存在邊僅當它們對應的詞匯在長度為K的窗口中共現,K表示窗口大小,即最多共現K個單詞。
  (4)根據上面公式,迭代傳播各節點的權重,直至收斂。
  (5)對節點權重進行倒序排序,從而得到最重要的T個單詞,作為候選關鍵詞。
  (6)由(5)得到最重要的T個單詞,在原始文本中進行標記,若形成相鄰詞組,則組合成多詞關鍵詞。例如,文本中有句子“Matlab code for plotting ambiguity function”,如果“Matlab”和“code”均屬於候選關鍵詞,則組合成“Matlab code”加入關鍵詞序列。

實現細節,具體可以看看源碼.下面是源碼中的一些細節:

 

首先定義一個無向有權圖,然后對句子進行分詞;依次遍歷分詞結果,如果某個詞i滿足過濾條件(詞性在詞性過濾集合中,並且詞的長度大於等於2,並且詞不是停用詞),然后將這個詞之后窗口長度為5范圍內的詞j(這些詞也需要滿足過濾條件),將它們兩兩(詞i和詞j)作為key,出現的次數作為value,添加到共現詞典中;

 

然后,依次遍歷共現詞典,將詞典中的每個元素,key = (詞i,詞j),value = i和詞j出現的次數,其中詞i,詞j作為一條邊起始點和終止點,共現的次數作為邊的權重,添加到之前定義的無向有權圖中。

 

然后對這個無向有權圖進行迭代運算textrank算法,最終經過若干次迭代后,算法收斂,每個詞都對應一個指標值;

 

如果設置了權重標志位,則根據指標值值對無向有權圖中的詞進行降序排序,最后輸出topK個詞作為關鍵詞;

 

其中,無向有權圖的的定義及實現是在UndirectWeightedGraph類中實現的。根據UndirectWeightedGraph類的初始化函數__init__,我們可以發現,所謂的無向有權圖就是一個詞典,詞典的key是后續要添加的詞,詞典的value,則是一個由(起始點,終止點,邊的權重)構成的三元組所組成的列表,表示以這個詞作為起始點的所有的邊。

 

無向有權圖添加邊的操作是在addEdge函數中完成的,因為是無向圖,所以我們需要依次將start作為起始點,end作為終止點,然后再將start作為終止點,end作為起始點,這兩條邊的權重是相同的。

 

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
其中,無向有權圖的的定義及實現是在UndirectWeightedGraph類中實現的。根據UndirectWeightedGraph類的初始化函數__init__,我們可以發現,所謂的無向有權圖就是一個詞典,詞典的key是后續要添加的詞,詞典的value,則是一個由(起始點,終止點,邊的權重)構成的三元組所組成的列表,表示以這個詞作為起始點的所有的邊。
無向有權圖添加邊的操作是在addEdge函數中完成的,因為是無向圖,所以我們需要依次將start作為起始點,end作為終止點,然后再將start作為終止點,end作為起始點,這兩條邊的權重是相同的。
def addEdge(self, start, end, weight):
    # use a tuple (start, end, weight) instead of a Edge object
    self.graph[start].append((start, end, weight))
    self.graph[end].append((end, start, weight))
執行textrank算法迭代是在rank函數中完成的。
首先對每個結點賦予相同的權重,以及計算出該結點的所有出度的次數之和;
然后迭代若干次,以確保得到穩定的結果;
在每一次迭代中,依次遍歷每個結點;對於結點n,首先根據無向有權圖得到結點n的所有
入度結點(對於無向有權圖,入度結點與出度結點是相同的,都是與結點n相連的結點),在前面我們已經計算出這個入度結點的所有出度的次數,而它對於結點n的權值的貢獻等於它本身的權值 乘以 它與結點n的共現次數 / 這個結點的所有出度的次數 ,將各個入度結點得到的權值相加,再乘以一定的阻尼系數,即可得到結點n的權值;
迭代完成后,對權值進行歸一化,並返回各個結點及其對應的權值。
def rank(self):
    ws = defaultdict(float)
    outSum = defaultdict(float)

    wsdef = 1.0 / (len(self.graph) or 1.0)
    # 初始化各個結點的權值
    # 統計各個結點的出度的次數之和
    for n, out in self.graph.items():
        ws[n] = wsdef
        outSum[n] = sum((e[2] for e in out), 0.0)

    # this line for build stable iteration
    sorted_keys = sorted(self.graph.keys())
    # 遍歷若干次
    for x in xrange(10):  # 10 iters
        # 遍歷各個結點
        for n in sorted_keys:
            s = 0
            # 遍歷結點的入度結點
            for e in self.graph[n]:
                # 將這些入度結點貢獻后的權值相加
                # 貢獻率 = 入度結點與結點n的共現次數 / 入度結點的所有出度的次數
                s += e[2] / outSum[e[1]] * ws[e[1]]
            # 更新結點n的權值
            ws[n] = (1 - self.d) + self.d * s

    (min_rank, max_rank) = (sys.float_info[0], sys.float_info[3])

    # 獲取權值的最大值和最小值
    for w in itervalues(ws):
        if w < min_rank:
            min_rank = w
        if w > max_rank:
            max_rank = w

    # 對權值進行歸一化
    for n, w in ws.items():
        # to unify the weights, don't *100.
        ws[n] = (w - min_rank / 10.0) / (max_rank - min_rank / 10.0)

    return ws

 

完結

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM