首先,簡(jiǎn)單闡述一下文本摘要的任務(wù):該任務(wù)的輸入是一段文本(例如新聞),輸出是能夠概括輸入的簡(jiǎn)短文本。
目前文本摘要問題主要分為兩類,extractive summarization (抽取式摘要)與 abstractive summarization(生成式摘要)。其中抽取式摘要是從原文本中,挑選出重要的句子,并將這些句子組成摘要。而生成式摘要?jiǎng)t是讓模型理解輸入,使用encoder-decoder模型,生成出摘要。工業(yè)界普遍使用抽取式摘要,學(xué)術(shù)界主要研究生成式摘要。就摘要語句的流暢度而言,抽取式摘要要好于生成式摘要。
1.基于TextRank的抽取式摘要
1.1 PageRank
在了解textrank之前,首先我們需要了解PageRank,推薦大家去閱讀《統(tǒng)計(jì)學(xué)習(xí)方法》(第二版)第21章
(1)概述:
PageRank算法是圖的鏈接分析的代表性算法,屬于圖數(shù)據(jù)上的無監(jiān)督學(xué)習(xí)方法。最初作為互聯(lián)網(wǎng)網(wǎng)頁(yè)重要度的計(jì)算方法,用于谷歌搜索引擎的網(wǎng)頁(yè)排序,后來被應(yīng)用到社會(huì)影響力分析,文本摘要等多個(gè)問題。
它的基本思想為:在有向圖上定義一個(gè)隨機(jī)游走模型,即一階馬爾可夫鏈,描述隨機(jī)游走者沿著有向圖隨機(jī)訪問各個(gè)結(jié)點(diǎn)的行為。在一定條件下,極限情況訪問的各個(gè)結(jié)點(diǎn)的概率收斂到平穩(wěn)分布,這時(shí)各個(gè)結(jié)點(diǎn)的平穩(wěn)概率值就是其PageRank值,表示結(jié)點(diǎn)的重要度。
(2)隨機(jī)游走模型(Random Walk)

如上圖可知,每個(gè)結(jié)點(diǎn)訪問其鏈接的結(jié)點(diǎn)的概率是相同的。
(3)PageRank的基本定義
給定一個(gè)包含n個(gè)結(jié)點(diǎn)的強(qiáng)連通且非周期性的有向圖,在其基礎(chǔ)上定義隨機(jī)游走模型,假設(shè)轉(zhuǎn)移矩陣為M,在時(shí)刻0,1,2,3,...,t,...訪問各個(gè)結(jié)點(diǎn)的概率分布為R0,MR0,M平方R0,M立方R0,...,以此類推。
則極限?= R存在(M的t次方乘以R0),極限向量R表示馬爾科夫鏈的平穩(wěn)分布,滿足MR=R。平穩(wěn)分布R稱為這個(gè)有向圖的PageRank,R的各個(gè)分量稱為各個(gè)結(jié)點(diǎn)的PageRank值
極限存在定理:不可約且非周期的有限狀態(tài)馬爾可夫鏈,有唯一平穩(wěn)分布存在,而且當(dāng)時(shí)間趨于無窮時(shí)狀態(tài)分布收斂于唯一的平穩(wěn)分布。
(4)PageRank的一般定義
我們?cè)O(shè)想一下,在成千上萬個(gè)網(wǎng)頁(yè)中,大部分網(wǎng)頁(yè)并沒有超鏈接,所以基本定義不適用于這種情況,于是提出了PageRank的一般定義,一般定義的思想就是在基本定義上引入平滑項(xiàng),這里就不細(xì)述了。
(5)PageRank的計(jì)算
①迭代計(jì)算法:更新足夠小時(shí),停止迭代。
②冪法:近似計(jì)算矩陣的主特征值和主特征向量。
③代數(shù)算法:R = ( I - dM)^-1 *(1-d)/n? *?
1.2 TextRank
將文本中的每個(gè)句子分別看作一個(gè)節(jié)點(diǎn),如果兩個(gè)句子有相似性,那么認(rèn)為這兩個(gè)句子對(duì)應(yīng)的節(jié)點(diǎn)之間存在一條無向邊??疾炀渥酉嗨贫鹊姆椒ǎ?img class="math-inline" src="https://math.jianshu.com/math?formula=Similarity(si%2Csj)%20%3D%20%5Cfrac%7B%5Cvert%20%5Cleft%5C%7B%20wk%7Cwk%5Cin%20si%2Cwk%5Cin%20sj%20%5Cright%5C%7D%20%20%5Cvert%20%7D%7Blog(%5Cvert%20si%20%5Cvert%20)%2Blog(%5Cvert%20sj%20%5Cvert%20)%7D%20" alt="Similarity(si,sj) = \frac{\vert \left\{ wk|wk\in si,wk\in sj \right\} \vert }{log(\vert si \vert )+log(\vert sj \vert )} " mathimg="1">(分母這么設(shè)置的原因是可以遏制較長(zhǎng)的句子在相似度計(jì)算上的優(yōu)勢(shì))
根據(jù)以上相似度公式循環(huán)計(jì)算任意兩個(gè)節(jié)點(diǎn)之間的相似度,根據(jù)閾值去掉兩個(gè)節(jié)點(diǎn)之間相似度較低的邊連接。構(gòu)建出節(jié)點(diǎn)連接圖,然后計(jì)算TextRank值,最后對(duì)所有的TextRank值排序,選出TextRank值最高的幾個(gè)節(jié)點(diǎn)對(duì)應(yīng)的句子作為摘要。
1.3 實(shí)戰(zhàn)
參考github:https://github.com/DengYangyong/textrank_summarization
import numpy as np
import pandas as pd
import re,os,jieba
from itertools import chain
from sklearn.metrics.pairwise import cosine_similarity
import networkx as nx
def seg_depart(sentence):
? ? # 去掉非漢字字符
? ? sentence = re.sub(r'[^\u4e00-\u9fa5]+','',sentence)
? ? sentence_depart = jieba.cut(sentence.strip())
? ? word_list = []
? ? for word in sentence_depart:
? ? ? ? if word not in stopwords:
? ? ? ? ? ? word_list.append(word)?
? ? # 如果句子整個(gè)被過濾掉了,如:'02-2717:56'被過濾,那就返回[],保持句子的數(shù)量不變
? ? return word_list
word_embeddings = {}
f = open('D://文本摘要/textrank/搜狗詞向量/sgns.sogounews.bigram-char', encoding='utf-8')
for line in f:
? ? # 把第一行的內(nèi)容去掉
? ? if '365113 300\n' not in line:
? ? ? ? values = line.split()
#? ? ? ? print(values)
? ? ? ? # 第一個(gè)元素是詞語
? ? ? ? word = values[0]
? ? ? ? try:
? ? ? ? ? ? embedding = np.asarray(values[1:], dtype='float32')
? ? ? ? ? ? word_embeddings[word] = embedding
? ? ? ? except:
? ? ? ? ? ? word = values[0]+values[1]
? ? ? ? ? ? try:
? ? ? ? ? ? ? ? embedding = np.asarray(values[2:], dtype='float32')
? ? ? ? ? ? ? ? word_embeddings[word] = embedding
? ? ? ? ? ? except:
? ? ? ? ? ? ? ? print('這個(gè)問題我還暫時(shí)無法處理:')
? ? ? ? ? ? ? ? print(values[:10])
f.close()
print("一共有"+str(len(word_embeddings))+"個(gè)詞語/字。")
# 文檔所在的文件夾
c_root = 'D://文本摘要/textrank/textrank_易會(huì)滿/cnews/'
f = open('D://newsdata.txt','r',encoding='utf-8')
data_list = f.readlines()
f.close()
stopwords = [line.strip() for line in open('D://文本摘要/textrank/textrank_易會(huì)滿/stopwords.txt',encoding='UTF-8').readlines()]
stopwords = list(set(stopwords))
for i in range(len(stopwords)):
? ? stopwords[i] = stopwords[i].replace('\n','')
f1 = open("D://sum.txt",'w',encoding='utf-8')
for wja in range(len(data_list)):
? ? article = data_list[wja]
? ? sentences_list = []
? ? if article.strip():
? ? ? ? line_split = re.split(r'[。??;?]',article.strip())
? ? ? ? line_split = [line.strip() for line in line_split if line.strip() not in ['。','!','?',';'] and len(line.strip())>1]
? ? ? ? sentences_list.append(line_split)
? ? sentences_list = list(chain.from_iterable(sentences_list))
? ? sentence_word_list = []
? ? for sentence in sentences_list:?
? ? ? ? line_seg = seg_depart(sentence)
? ? ? ? sentence_word_list.append(line_seg)
? ? # print(sentence_word_list)
? ? # for i in range(len(sentence_word_list)):
? ? #? ? print(sentences_list[i])
? ? #? ? print(sentence_word_list[i])
? ? sentence_vectors = []
? ? for i in sentence_word_list:
? ? ? ? if len(i)!=0:
? ? ? ? # 如果句子中的詞語不在字典中,那就把embedding設(shè)為300維元素為0的向量。
? ? ? ? # 得到句子中全部詞的詞向量后,求平均值,得到句子的向量表示
? ? ? ? ? ? v = sum([word_embeddings.get(w, np.zeros((300,))) for w in i])/(len(i))
? ? ? ? else:
? ? ? ? # 如果句子為[],那么就向量表示為300維元素為0個(gè)向量。
? ? ? ? ? ? v = np.zeros((300,))
? ? ? ? sentence_vectors.append(v)
? ? sim_mat = np.zeros([len(sentences_list), len(sentences_list)])
? ? for i in range(len(sentences_list)):
? ? ? ? for j in range(len(sentences_list)):
? ? ? ? ? ? if i != j:
? ? ? ? ? ? ? ? sim_mat[i][j] = cosine_similarity(sentence_vectors[i].reshape(1,300), sentence_vectors[j].reshape(1,300))[0,0]
? ? nx_graph = nx.from_numpy_array(sim_mat)
? ? # 得到所有句子的textrank值
? ? scores = nx.pagerank(nx_graph)
? ? # 根據(jù)textrank值對(duì)未處理的句子進(jìn)行排序
? ? ranked_sentences = sorted(((scores[i],s) for i,s in enumerate(sentences_list)), reverse=True)
? ? # 取出得分最高的前10個(gè)句子作為摘要
? ? sn = 5
? ? if sn > len(sentences_list):
? ? ? ? sn = len(sentences_list)
? ? # print(sentences_list)
? ? summary = list()
? ? for i in range(sn):
? ? ? ? summary.append(ranked_sentences[i][1])
? ? sum2 = list()#存儲(chǔ)最終摘要
? ? for kkk in sentences_list:
? ? ? ? if kkk in summary:
? ? ? ? ? ? sum2.append(kkk)
? ? summarization = '。'.join(sum2)
? ? f1.write(summarization+'\n')
? ? if wja%100 == 0:
? ? ? ? print('已處理了'+str(wja)+'條')
? ? ? ? if wja%1000 == 0:
? ? ? ? ? ? print(summarization)
f1.close()
原github的代碼里有非常詳細(xì)的備注,請(qǐng)大家移步至https://github.com/DengYangyong/textrank_summarization
以上代碼,也是借鑒了該作者的代碼,進(jìn)行了少量修改,用于我自己的一個(gè)新聞?wù)降娜蝿?wù)。
2. 基于BERT的抽取式摘要
paper地址:https://arxiv.org/abs/1903.10318
code地址:https://github.com/nlpyang/BertSum
2.1 Paper解讀

此前的DL在抽取式摘要任務(wù)方面已經(jīng)達(dá)到了瓶頸,直到BERT橫空出世。作者嘗試了多種BERT變體,最終定義了如下模型:

首先,BERT原型并不適合文本摘要,BERT有表示不同句子的分段嵌入,但它只有兩個(gè)標(biāo)簽,而文本摘要?jiǎng)t是多個(gè)句子,因此需要做一些改進(jìn)。作者在每個(gè)句子前加【CLS】標(biāo)簽,后加【SEP】標(biāo)簽。利用【CLS】獲取到每個(gè)句子的sentence-level的特征。之后的Summarization layer作者做了不同的嘗試:①設(shè)置一個(gè)簡(jiǎn)單的線性分類器+sigmoid ②使用transformer+sigmoid ③使用LSTM+sigmoid

我們可以看出,最好的方法是使用②transformer(作者又做了對(duì)比實(shí)驗(yàn),分別測(cè)試了1,2,3層的transformer的效果,實(shí)驗(yàn)結(jié)果顯示2層的transformer效果最好),值得注意的是使用LSTM的方法甚至不如一個(gè)簡(jiǎn)單的線性分類器。