作者:LogM
本文原載於 https://segmentfault.com/u/logm/articles ,不允許轉載~
1. 源碼來源
TextRank4ZH 源碼:https://github.com/letiantian/TextRank4ZH.git
本文對應的源碼版本:committed on 3 Jul 2018, fb1339620818a0b0c16f5613ebf54153faa41636
TextRank 論文地址:https://www.aclweb.org/anthology/W04-3252
2. 概述
letiantian 大佬的這個版本,應該是所有 TextRank 的 Python 版本中被點贊最多的。代碼寫的也非常的簡單易懂。
3. 開撕
example 文件夾下的程序展示了怎麼使用這個版本的 TextRank。有關鍵詞、關鍵短語、關鍵句抽取三種功能,我們這邊只關注關鍵句的抽取。
應該很容易看懂吧,先實例化 TextRank4Sentence
,然後使用 analyze
抽取。
# 文件:example/example1.py
# 行數:28
tr4s = TextRank4Sentence()
tr4s.analyze(text=text, lower=True, source = 'all_filters') # 這句是重點
print()
print( '摘要:' )
for item in tr4s.get_key_sentences(num=3):
print(item.index, item.weight, item.sentence)
然後,我們知道重點函數是 analyze
,我們再來看它是怎麼工作的。
# 文件:textrank4zh/TextRank4Sentence.py
# 行數:43
def analyze(self, text, lower = False,
source = 'no_stop_words',
sim_func = util.get_similarity,
pagerank_config = {'alpha': 0.85,}):
"""
Keyword arguments:
text -- 文本內容,字符串。
lower -- 是否將文本轉換爲小寫。默認爲False。
source -- 選擇使用words_no_filter, words_no_stop_words, words_all_filters中的哪一個來生成句子之間的相似度。
默認值爲`'all_filters'`,可選值爲`'no_filter', 'no_stop_words', 'all_filters'`。
sim_func -- 指定計算句子相似度的函數。
"""
self.key_sentences = []
result = self.seg.segment(text=text, lower=lower)
self.sentences = result.sentences
self.words_no_filter = result.words_no_filter
self.words_no_stop_words = result.words_no_stop_words
self.words_all_filters = result.words_all_filters
options = ['no_filter', 'no_stop_words', 'all_filters']
if source in options:
_source = result['words_'+source]
else:
_source = result['words_no_stop_words']
# 這句是重點
self.key_sentences = util.sort_sentences(
sentences = self.sentences,
words = _source,
sim_func = sim_func,
pagerank_config = pagerank_config)
很容易發現,我們需要的內容在 util.sort_sentences
這個函數裏。
# 文件:textrank4zh/util.py
# 行數:169
def sort_sentences(sentences, words, sim_func = get_similarity, pagerank_config = {'alpha': 0.85,}):
"""將句子按照關鍵程度從大到小排序
Keyword arguments:
sentences -- 列表,元素是句子
words -- 二維列表,子列表和sentences中的句子對應,子列表由單詞組成
sim_func -- 計算兩個句子的相似性,參數是兩個由單詞組成的列表
pagerank_config -- pagerank的設置
"""
sorted_sentences = []
_source = words
sentences_num = len(_source)
graph = np.zeros((sentences_num, sentences_num))
for x in xrange(sentences_num):
for y in xrange(x, sentences_num):
similarity = sim_func( _source[x], _source[y] ) # 重點1
graph[x, y] = similarity
graph[y, x] = similarity
nx_graph = nx.from_numpy_matrix(graph)
scores = nx.pagerank(nx_graph, **pagerank_config) # 重點2
sorted_scores = sorted(scores.items(), key = lambda item: item[1], reverse=True)
for index, score in sorted_scores:
item = AttrDict(index=index, sentence=sentences[index], weight=score)
sorted_sentences.append(item)
return sorted_sentences
這邊有兩個重點,重點1:句子與句子的相似度是如何計算的;重點2:pagerank的實現。
很明顯,PageRank 的實現是藉助了 networkx
這個第三方庫,在下一節我們會來看看這個第三方庫的源碼。
這邊,我們先來看重點1,句子與句子的相似度是如何計算的,容易看出,計算方式和論文給的公式是一致的。
# 文件:textrank4zh/util.py
# 行數:102
def get_similarity(word_list1, word_list2):
"""默認的用於計算兩個句子相似度的函數。
Keyword arguments:
word_list1, word_list2 -- 分別代表兩個句子,都是由單詞組成的列表
"""
words = list(set(word_list1 + word_list2))
vector1 = [float(word_list1.count(word)) for word in words]
vector2 = [float(word_list2.count(word)) for word in words]
vector3 = [vector1[x]*vector2[x] for x in xrange(len(vector1))]
vector4 = [1 for num in vector3 if num > 0.]
co_occur_num = sum(vector4)
if abs(co_occur_num) <= 1e-12:
return 0.
denominator = math.log(float(len(word_list1))) + math.log(float(len(word_list2))) # 分母
if abs(denominator) < 1e-12:
return 0.
return co_occur_num / denominator
4. networkx 是怎麼實現 PageRank的
不得不說,寫 Python 的好處就是有各種第三方庫可以用。整個PageRank的計算過程,大佬都藉助了 networkx
這個第三方庫。
networkx
中 PageRank 的路徑爲 networkx/algorithms/link_analysis/pagerank_alg.py
。我這邊就不貼出源碼了,共476行,把我驚出一身冷汗。定睛一看,原來註釋佔了一半的行數。再仔細閱讀一遍,原來寫這個庫的大佬用3種不同的方法實現了3個 PageRank 函數,請收下我的膝蓋。
Python 的變量類型不明確,比如代碼中 W
這個變量,我知道是一張圖,但我不知道是用鄰接矩陣還是鄰接表或者是自定義類來表示的,需要向上回溯幾層代碼才能知道。所以閱讀這種大工程的 Python 代碼是需要花一點時間的。
如果有耐心理解源碼的話,可以發現,networkx
中 PageRank 和論文中的數學公式還是有些不一樣的,主要的不一樣的點在於對 dangling_nodes
的處理。
5. 總結
寫 Python 的好處就是有各種第三方庫可以用。
Python 的變量類型不明確,閱讀大工程的 Python 代碼是需要花一點時間的。