NLG文本生成算法一TextRank(TextRank: Bringing Order into Texts)(jieba,TextRank4ZH,gensim實現比較)

一.NLG文本生成任務

       文本生成NLG,不同於文本理解NLU(例如分詞、詞向量、分類、實體提取),是重在文本生成的另一種關鍵技術(常用的有翻譯、摘要、同義句生成等)。

       傳統的文本生成NLG任務主要是抽取式的,生成式的方法看起來到現在使用也沒有那麼普遍。現在,我記錄的是textrank,一種使用比較廣泛的抽取式關鍵句提取算法。

二、TextRank前世今生

        要說TextRank(TextRank: Bringing Order into Texts),還得從它的爸爸PageRank說起。

        PageRank(The PageRank Citation Ranking: Bringing Order to the Web),是上世紀90年代末提出的一種計算網頁權重的算法。當時,互聯網技術突飛猛進,各種網頁網站爆炸式增長,業界急需一種相對比較準確的網頁重要性計算方法,是人們能夠從海量互聯網世界中找出自己需要的信息。

       百度百科如是介紹他的思想:PageRank通過網絡浩瀚的超鏈接關係來確定一個頁面的等級。Google把從A頁面到B頁面的鏈接解釋爲A頁面給B頁面投票,Google根據投票來源(甚至來源的來源,即鏈接到A頁面的頁面)和投票目標的等級來決定新的等級。簡單的說,一個高等級的頁面可以使其他低等級頁面的等級提升。

       具體說來就是,PageRank有兩個基本思想,也可以說是假設,即數量假設:一個網頁被越多的其他頁面鏈接,就越重);質量假設:一個網頁越是被高質量的網頁鏈接,就越重要。 

        總的來說就是一句話,從全局角度考慮,獲取重要的信息。

三、TextRank原理

        首先不得不說的是原始論文的PageRank的公式,d是阻力系數,取0.85;In(Vi)是鏈入Vi頁面的網頁集合, Out(Vj)是鏈出Vi頁面的網頁集合:

        TextRank的公式是這樣的,Wji表示兩節點邊的重要程度,入度和初度的意思同PageRank:

        當然,雖然TextRank和PageRank的思想相近,但是它們還是有不同點的。一是邊權重的構建不同,PageRank利用的是網頁的連接關係,而TextRank利用的是詞的共現信息(或者是兩兩句子的文本相似度);二是邊關係的不同,PageRank是有向無權邊,而TextRank的是無向有權邊。

四、jieba,TextRank4ZH,gensim的TextRank實現比較(推薦TextRank4ZH

        1.jieba

               defaultdict字典存儲出入度,10 iters循環,只有關鍵詞的textrank

        2.TextRank4ZH

               數據預處理爲(不變-去停用詞-或保留重要tag)句子的詞共現構建numpy矩陣, networkx的pagerank作爲textrank優化,使用了networkx和jieba外部包。支持關鍵詞和關鍵句。

        3.gensim

               數據預處理比較多且針對英文(比如說關鍵詞取3,這樣中文較多的二字的詞就取不到,不如TextRank4ZH的保留重要tag;再比如切句全是英文的分隔符.?!,不如TextRank4ZH的。?!......);相似度計算爲BM25比詞共現矩陣先進;gensim自己實現構建Graph、PageRank等不需要額外包;輸入最小句子長度設置爲10。

 

五、碼源分析

       主要參考jieba,TextRank4ZH,gensim,

       Jieba的TextRank使用defaultdict字典存儲句子的相鄰詞語之間的共現關係,代碼如下:

        g = UndirectWeightedGraph()
        cm = defaultdict(int) # 這個字典, 不存在時候會返回空,而不像dict那樣報錯
        words = tuple(self.tokenizer.cut(sentence))
        for i, wp in enumerate(words): # textrank和pagerank一樣,會有兩個for循環
            if self.pairfilter(wp):
                for j in xrange(i + 1, i + self.span):
                    if j >= len(words):
                        break
                    if not self.pairfilter(words[j]):
                        continue
                    if allowPOS and withFlag:
                        cm[(wp, words[j])] += 1
                    else:
                        cm[(wp.word, words[j].word)] += 1 # 例如: dict[('我','喜歡')]=1

        for terms, w in cm.items():
            g.addEdge(terms[0], terms[1], w) # 保存(詞1, 詞1的下一個詞[詞2], 頻次[詞1詞2的])
        nodes_rank = g.rank()

      TextRank4ZH使用的是numpy保存共現句子間相鄰詞語的共現信息,然後再用了networkx.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] )
            graph[x, y] = similarity
            graph[y, x] = similarity
            
    nx_graph = nx.from_numpy_matrix(graph)   # 調用pagerank實現
    scores = nx.pagerank(nx_graph, **pagerank_config)              # this is a dict
    sorted_scores = sorted(scores.items(), key = lambda item: item[1], reverse=True)


# 切句
sentence_delimiters = ['?', '!', ';', '?', '!', '。', ';', '……', '…', '\n']
# 提取重要tag的中文詞語
allow_speech_tags = ['an', 'i', 'j', 'l', 'n', 'nr', 'nrfg', 'ns', 'nt', 'nz', 't', 'v', 'vd', 'vn', 'eng']

 

    gensim的textrank是gensim.summarization.summarizer.summarize。

##########先是分句和數據預處理(依次是: 小寫, html, 標點符號, 多個空格, 數字時間等, 英文停用詞,大於3的句子,提取英文單詞詞幹[如複數])
###############################
DEFAULT_FILTERS = [
    lambda x: x.lower(), strip_tags, strip_punctuation,
    strip_multiple_whitespaces, strip_numeric,
    remove_stopwords, strip_short, stem_text
]

# 1.切分句子, 非貪婪搜索, 以英文句號-感嘆號-問號.!?加空格, 或者是換行符\n爲標誌,不適合中文
RE_SENTENCE = re.compile(r'(\S.+?[.!?])(?=\s+|$)|(\S.+?)(?=[\n]|$)', re.UNICODE)
# \w 的釋義都是指包含大 小寫字母數字和下劃線
# \s 空格
# ^ 匹配字符串的開始
# $ 匹配字符串的結束


# 2.去除標點符號(punctuation),可以發現,有一些特殊字符和中文的
punctuation = """!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""


# 3.去除html標籤
RE_TAGS = re.compile(r"<([^>]+)>", re.UNICODE)

# 4.空格,時間數字等, 時間等,例如"24.0hours"改爲"24.0 hours"

# 5.大於len=3的單詞, 感覺這點中文不適用,strip_short(s, minsize=2)比較合適



# 構建子典等
corpus = _build_corpus(sentences)
# 關鍵
most_important_docs = summarize_corpus(corpus, ratio=ratio if word_count is None else 1)
# page rank
def pagerank_weighted(graph, damping=0.85):
    """Get dictionary of `graph` nodes and its ranks.

    Parameters
    ----------
    graph : :class:`~gensim.summarization.graph.Graph`
        Given graph.
    damping : float
        Damping parameter, optional

    Returns
    -------
    dict
        Nodes of `graph` as keys, its ranks as values.

    """
    coeff_adjacency_matrix = build_adjacency_matrix(graph, coeff=damping)
    probabilities = (1 - damping) / float(len(graph))

    pagerank_matrix = coeff_adjacency_matrix.toarray()
    # trying to minimize memory allocations
    pagerank_matrix += probabilities

    vec = principal_eigenvector(pagerank_matrix.T)

    # Because pagerank_matrix is positive, vec is always real (i.e. not complex)
    return process_results(graph, vec.real)


 

希望對你有所幫助!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章