無監督文字自動摘要野生技術

重炮手東方未明發表於2018-09-19

前言

本文簡單介紹文字自動摘要的概念,提供一些簡單可行的思路和解決方法,主要功能是記錄一些心得和希望讀者能得到一些啟發。現階段有監督的文字自動摘要,存在非常成熟和強大的解決方法,奈何巧婦難為無米之炊,沒有人工的摘要標記,有監督的方法寸步難行。在缺少標記資料的情況下,無監督的方法更加實用。

基本概念

什麼是文字自動摘要?

Automatic text summarization is the task of producing a concise and fluent summary while preserving key information content and overall meaning.

簡而言之,文字自動摘要就是從源文字中生成簡明流暢的總結,同時保留關鍵內容和主旨的一項任務。一般來說,文字自動摘要的來源可以是一個或多個文字,摘要後的字數是原文的一半左右。

文字自動摘要的兩種方法

1. Extraction

顧名思義,就是識別出文字中的重要部分並且抽取出來作為文字的摘要。

2. Abstraction

這種方法則是,用比較先進的自然語言處理技術去生成包含原文重要資訊的摘要,這種方法涉及語意表徵、推斷和自然語言生成等,這些都是比較難解決的問題。

一般來說extraction的效果要比abstraction要好,更況且現成的abstractive summarizers或多或少都要包括一些extraction的成分,我們重點來看exraction方法。

Extraction summarization方法論

Exraction自動摘要系統可以大致分為一下獨立的三步:

  1. 構建一個包含文字主要資訊的表現形式(表徵)。
  2. 基於構建的表現形式對句子評分。
  3. 根據評分選出構成摘要的句子。

這樣我們就把自動摘要這個複雜問題,轉化成三個獨立的子問題,當第一個和第二個問題解決後,第三部便是選擇top k問題,也就是說,解決了前兩個問題,第三個問題便迎刃而解。

表徵方法介紹

概覽

常用的文字表徵方式基本可分為兩種:topic representation和 indicator representation。

topic representation就是把文字轉化為詮釋文字涉及的話題的表徵形式,其中topic representation中有比較基本的詞頻驅動方法、LSA和LDA等等,表徵完成後用統計的方法確定閾值來篩選重要的句子。

indicator representation就是把文字轉化成某些特徵,例如句子長度、是否含有某些詞彙和句子的位置等等。然後直接評分排序抽取出重要的句子。

下面重點介紹兩種簡單的indicator representation和評分方法.

基於圖的方法:從pagerank到textrank

基本思路:

圖的方法受大名鼎鼎的pagerank演算法影響,pagerank演算法的主要思想是一個網頁的重要程度取決於指向該網頁的所有其它網頁的重要程度,以及這些網頁中包含的連結數。同樣地,在文字中,一個句子的重要程度也取決於周圍句子的重要程度。

通常,在構建圖的時候,我們以頂點表示句子,以邊表示句子之間的相似關係。最常用的方法是度量兩個句子間TFIDF的cosine相似度,超過某閾值便把兩個句子連線起來。在這裡採用一種更加trivial,naive的方法,就是用視窗滑動的方式建立句子間的區域性關係。

首先我們的輸入是一段長文字,如:

雖然至今夏普智慧手機在市場上無法排得上號,已經完全沒落,並於 2013 年退出中國市場,但是今年 3 月份官方突然宣佈迴歸中國,預示著很快就有夏普新機在中國登場了。那麼,第一款夏普手機什麼時候登陸中國呢?又會是怎麼樣的手機呢?\r\n近日,一款型號為 FS8016 的夏普神祕新機悄然出現在 GeekBench 的跑分庫上。從其中相關資訊瞭解到,這款機子並非旗艦定位,所搭載的是高通驍龍 660 處理器,配備有 4GB 的記憶體。驍龍 660 是高通今年最受矚目的晶片之一,採用 14 奈米工藝,八個 Kryo 260 核心設計,整合 Adreno 512 GPU 和 X12 LTE 調變解調器。\r\n當前市面上只有一款機子採用了驍龍 660 處理器,那就是已經上市銷售的 OPPO R11。驍龍 660 儘管並非旗艦晶片,但在多核新能上比去年驍龍 820 強,單核改進也很明顯,所以放在今年仍可以讓很多手機變成高階機。不過,由於 OPPO 與高通簽署了排他性協議,可以獨佔兩三個月時間。\r\n考慮到夏普既然開始測試新機了,說明只要等獨佔時期一過,夏普就能釋出驍龍 660 新品了。按照之前被曝光的渲染圖瞭解,夏普的新機核心競爭優勢還是全面屏,因為從 2013 年推出全球首款全面屏手機 EDGEST 302SH 至今,夏普手機推出了多達 28 款的全面屏手機。\r\n在 5 月份的媒體溝通會上,惠普羅忠生表示:“我敢打賭,12 個月之後,在座的各位手機都會換掉。因為全面屏時代的到來,我們懷揣的手機都將成為傳統手機。”

我們需要在標點符號處把句子分開如

import re

def split_sentences(text,p='[。.,,?:]',filter_p='\s+'):
    f_p = re.compile(filter_p)
    text = re.sub(f_p,'',text)
    pattern = re.compile(p)
    split = re.split(pattern,text)
    return split

print(split_sentences(text)[:10])
#out:
'''
['雖然至今夏普智慧手機在市場上無法排得上號', '已經完全沒落', '並於2013年退出中國市場', '但是今年3月份官方突然宣佈迴歸中國', '預示著很快就有夏普新機在中國登場了']
'''
複製程式碼

這個函式會將文字分成一個個句子的list。

然後我們可以用networkx庫構建圖:

import networkx

def get_sen_graph(text,window=3):
    split_sen = split_sentences(text)
    sentences_graph = networkx.graph.Graph()
    for i,sen in enumerate(split_sen):
        sentences_graph.add_edges_from([(sen,split_sen[ii]) for ii in range(i-window,i+window+1)
                                        if ii >= 0 and ii < len(split_sen)])
    return sentences_graph
複製程式碼

接著用pagerank演算法計算出句子的評分並排序

def text_rank(text):
    sentences_graph = get_sen_graph(text)
    ranking_sentences = networkx.pagerank(sentences_graph)
    ranking_sentences_sorted = sorted(ranking_sentences.items(),key=lambda x:x[1],reverse=True)
    return ranking_sentences_sorted
複製程式碼

最後是選擇重要的句子並拼接起來

def get_summarization(text,score_fn,sum_len):
    sub_sentences = split_sentences(text)
    ranking_sentences = score_fn(text)
    selected_sen = set()
    current_sen = ''
    
    for sen, _ in ranking_sentences:
        if len(current_sen)<sum_len:
            current_sen += sen
            selected_sen.add(sen)
        else:
            break
            
    summarized = []
    for sen in sub_sentences:
        if sen in selected_sen:
            summarized.append(sen)
    return summarized
    
def get_summarization_by_text_rank(text,sum_len=200):
    return get_summarization(text,text_rank,sum_len)
    
print(' '.join(get_summarization_by_text_rank(text)))

#out:
'''
但是今年3月份官方突然宣佈迴歸中國 預示著很快就有夏普新機在中國登場了 那麼 第一款夏普手機什麼時候登陸中國呢
又會是怎麼樣的手機呢 近日 一款型號為FS8016的夏普神祕新機悄然出現在GeekBench的跑分庫上 從其中相關資訊瞭解到
因為從2013年推出全球首款全面屏手機EDGEST302SH至今 夏普手機推出了多達28款的全面屏手機 在5月份的媒體溝通會上 
惠普羅忠生表示 “我敢打賭 12個月之後 在座的各位手機都會換掉
'''
複製程式碼

這樣就初步實現了一個基於圖的簡單文字摘要,但是按照這個實現,由於方法簡單,效果也馬馬虎虎。但是構建圖的時候,你是否有一些比較好的構建方式呢?這裡,如何界定一個句子"周圍"的句子顯得至關重要。除了TFIDF之外,LSA是否是一個不錯的表徵方法呢,word2vec和Doc2vec也許值得一試呢!

基於sentence embedding(句嵌入)的方法

基本思路:

實際上,識別一個文字的重要句子,可以看作是測度文中每個句子和全文的相似度,相似度越高的話,表示這個句子越重要。所以我們只要對全文及其分句進行sentence embedding後,計算分句表徵向量和全文表徵向量的cosine相似度,就可以大致抽取出重要句子。

這部分需要做的準備工作比較多,首先讀入資料進行分詞,再訓練詞向量(word2vec),當然也可以使用預訓練的詞向量

import pandas as pd
import numpy as np
import jieba
from gensim.models import FastText

FILE_PATH = 'news.zip'
news_df = pd.read_csv(FILE_PATH,compression='zip',encoding='gb18030')
#定義分詞函式
def cut(text): return ' '.join(jieba.cut(text)) 

main_content = pd.DataFrame()
main_content['title'] = news_df['title']
main_content['content'] = news_df['content'].fillna('')
main_content['tokenized_content'] = main_content['content'].apply(cut)

#訓練詞向量
with open('all_corpus.txt','w',encoding='utf-8') as f:
    f.write(' '.join(main_content['tokenized_content'].tolist()))

from gensim.models.word2vec import LineSentence
model = FastText(LineSentence('all_corpus.txt'),window=8,size=200,iter=10,min_count=1)
tokens = [token for line in main_content['tokenized_content'].tolist() for token in line.split()]
複製程式碼

計算詞頻

from collections import Counter
token_counter = Counter(tokens)
word_frequency = {w:counts/len(tokens) for w,counts in token_counter.items()}
複製程式碼

對於句嵌入,使用比較簡單的SIF(smooth-inverse-frequency)embedding,具體演算法

無監督文字自動摘要野生技術

簡單起見,跳過了其中的pca/svd部分

def SIF_sentence_embedding(text,alpha=1e-4):
    global word_frequency
    
    max_fre = max(word_frequency.values())
    sen_vec = np.zeros_like(model.wv['測試'])
    words = cut(text).split()
    words = [w for w in words if w in model]
    
    for w in words:
        fre = word_frequency.get(w,max_fre)
        weight = alpha/(fre+alpha)
        sen_vec += weight*model.wv[w]
        
    sen_vec /= len(words)
    #skip SVD
    return sen_vec
複製程式碼

計算cosine得分並把抽取的句子組合起來

from scipy.spatial.distance import cosine

def get_corr(text,embed_fn=SIF_sentence_embedding):
    if isinstance(text,list): text = ' '.join(text)
        
    sub_sentences = split_sentences(text)
    sen_vec = embed_fn(text)
    
    corr_score = {}
    
    for sen in sub_sentences:
        sub_sen_vec = embed_fn(sen)
        corr_score[sen] = cosine(sen_vec,sub_sen_vec)
        
    return sorted(corr_score.items(),key=lambda x:x[1],reverse=True)
    
def get_summarization_by_sen_emb(text,max_len=200):
    return get_summarization(text,get_corr,max_len)
    
print(''.join(get_summarization_by_sen_emb(main_content['content'].iloc[6])))

#out:
'''
已經完全沒落並於2013年退出中國市場 但是今年3月份官方突然宣佈迴歸中國 預示著很快就有夏普新機在中國登場了 
那麼 又會是怎麼樣的手機呢 近日從其中相關資訊瞭解到 八個Kryo260核心設計 但在多核新能上比去年驍龍820強單核改進也很明顯
不過由於OPPO與高通簽署了排他性協議 可以獨佔兩三個月時間 說明只要等獨佔時期一過 按照之前被曝光的渲染圖瞭解 
在5月份的媒體溝通會上 惠普羅忠生表示 “我敢打賭12個月之後在座的各位手機都會換掉 因為全面屏時代的到來
'''
複製程式碼

這樣基於sentence embedding的文字摘要就完成,這裡只是討論了一個簡單的方法,可以結合介紹的這兩個方法,構建一個更加強大,效果更好的文字自動摘要系統。

改良思路

  1. 結合KNN演算法的思路,使生成的文字摘要更流暢,更具可讀性。根據句子的評分,畫出句子評分的分佈:

無監督文字自動摘要野生技術
我們會發現,句子的評分分佈是這樣起伏很大的尖銳曲線,這樣抽取的句子會斷斷續續,顯得很突兀,因此我們需要根據句子自身的重要性和周圍句子的重要性,結合KNN演算法使得結果更加平滑。

  1. 考慮關鍵詞。在文字中,存在關鍵詞的句子往往包含了比較重要的資訊,當一個句子包含比關鍵詞時,應適當增加此句子的評分。
  2. 考慮標題。如果你文字存在標題,那麼標題已經包含了其中的重要資訊,摘要時應當把標題考慮進去。
  3. 考慮句子的位置。在閱讀理解時,開頭、結尾、段首和段尾等幾乎都起著總領全文、總結或承上啟下的作用,在摘要時,必須也要把句子的位置考慮進去。
  4. 結合LDA等話題模型。根據LDA總結出來的話題,我們可以用分句與其對比,把話題也考慮到摘要中。

討論到這裡,相信聰明的你已經有相當多的思路,可以建立自己的文字自動摘要系統啦!

下面展示經過一些細微的改良後的摘要:

無監督文字自動摘要野生技術

等一下,還有一個問題,分句再合併後,原來的標點符號丟失了,怎麼辦?(提示:這裡應該建立分句——標點之間的對應關係。) 花點時間,思考一下,聰明的你就可以實現一個完整的文字摘要系統啦!

無監督文字自動摘要野生技術

參考資料

  1. Text Summarization Techniques: A Brief Survey
  2. A SIMPLE BUT TOUGH-TO-BEAT BASELINE FOR SENTENCE EMBEDDINGS

致謝

感謝你耐心閱讀完我的文章,不足之處歡迎批評指正,希望和你共同交流進步。

感謝我的指導老師高老師,還有積極討論解決問題的同學朋友們!

相關文章