TextRank算法完成摘要提取
一、实验简介
1.1 实验内容
上节实验我们完成了一个简单的“关键字提取”算法,初步了解了自然语言处理。本节实验,我们将实现TextRank算法完成新闻摘要提取。
1.2 实验知识点
- Python基础知识
- TextRank算法
1.3 实验环境
- Xfce终端
- python3
1.4 实验结果
我们最终获取了与上一节实验不同摘要
The PHE website and app has a quiz that gives users a health score based on their lifestyle habits by asking questions such as, "Which snacks do you eat in a normal day?"
The campaign's clinical adviser, Prof Muir Gray, said it was about trying to make people have a different attitude to an "environmental problem".
二、TextRank算法介绍
我们在上一次实验当中运用了比较基础的“关键字提取”法来完成我们的摘要提取功能,但是我们需要优化我们的算法。这次我们使用TextRank算法来完成摘要提取工作。
关于TextRank的论文请看这里: TextRank: Bring Order into Texts
在介绍 TextRank 之前我们首先需要了解 PageRank 算法。
2.1 PageRank算法
PageRank 是根据网页之间相互的超链接计算网页重要性 的技术,是Google的创始人拉里·佩奇和谢尔盖·布林于1998年在斯坦福大学发明了这项技术。
提到 重要性 我们就会联想到我们上一个实验也计算了每一个单词的 重要性 ,但是在PageRank算法中是为每个页面计算 重要性 ,而TextRank对PageRank算法做了改进,使其可以计算每一个 句子 的 重要性 。
我们通过一个例子来理解PageRank。
我们可以看到这个图中有不同颜色的球,代表的是不同的页面,而图中的箭头代表的是 超链接 。如图中D球指向A球,代表的就是D界面有指向A界面的超链接,相当于是D界面“推荐”了A界面。PageRank就是基于“推荐”的算法。
我们看到各个界面,既有推荐出去 (Out) 的箭头,也有别的界面推荐进来 (In) 的箭头。于是我们就可以列出一张邻接矩阵来描述整张图(忽略紫色的球!)。
0代表没有推荐,1代表的是有推荐。
每一列代表的是你推荐的页面 (Out)。如A列,代表的就是A推荐的页面。如表格所描述的,A谁也没有推荐。
每一行代表的是谁推荐了你 (In)。如A行,代表的就是推荐A的页面。如表格所描述的,D推荐了A。
In\Out | A | B | C | D | E | F |
---|---|---|---|---|---|---|
A | 0 | 0 | 0 | 1 | 0 | 0 |
B | 0 | 0 | 1 | 1 | 1 | 1 |
C | 0 | 1 | 0 | 0 | 0 | 0 |
D | 0 | 0 | 0 | 0 | 1 | 0 |
E | 0 | 0 | 0 | 0 | 0 | 1 |
F | 0 | 0 | 0 | 0 | 1 | 0 |
有了这张邻接矩阵作为基础,我们就能够计算PageRank的核心:每一个页面的分数 S。
Vi 代表的是每一个顶点,及页面。
d 代表的是一个阻尼常数 (0<d<1),用来确保每一个页面都至少有 (1-d)的分数。
In(Vi) 代表的是推荐Vi的页面。
Out(Vi) 代表的是Vi推荐的页面。
|Out(Vi)| 代表的是Vi推荐页面的数量。
∑ 符号相对复杂请参考求和符号∑
我们会发现,每一个页面的分数 S( Vi ) 都是依赖于别的页面的分数 S( Vj ) 的。我们需要对每个页面的分数进行初始化。初始化的数字并不重要,因为通过数学可以证明初始化的值对于最终的结果并不影响,只不过会影响算法迭代的次数。我们这里为了计算方便,把初始的 S 都设为 1 。 d 设为 0.85 (参考 S. Brin, L Page 1998年的论文)
我们来尝试求一求 页面 B 的分数。有三个页面D,E,F推荐页面B。按照顺序计算他们的值。
S(VB) = (1 - 0.85) + 0.85 * ( (1 / 2 * 1) + (1 / 2 * 1 ) + (1 / 2 * 1 ))
S(VB) 的值为1.425
。
就这样一步一步算出每一个顶点的值,然后接着从A页面开始继续算,一遍一遍迭代,直到每个页面的分数都不再变化为止。这样,我们就得到了每一个页面的评分 S
2.2 TextRank算法
当我们把PageRank应用到我们的文本当中去的时候,我们首先会发现的问题是,句子和句子之间 如何相互“推荐”?
TextRank给出了相应的算法。
注意!这里 S 代表的不是PageRank中的分数,它代表的是 Sentence 代表的是每一个句子。
Si 代表的是第 i 个句子。
wk 代表的是句子中第 k 个单词。
|Si| 代表的是句子中单词的个数。
{ wk| wk ∈ Si & wk ∈ Sj } 代表着同时在 Si 和 Sj 中出现的单词。
根据这个就可以求出两个句子之间的相似度,也就是推荐程度。
举个例子。
I am a fool. A big fool. I like fool.
前两句句子当中都有的单词是 a 和 fool,两个单词。
第一句话和第三句话都有的单词是 I 和 fool。
后两句句子当中都有的单词是 fool .
第一句话长度是 4,第二句话长度是3, 第三句话长度是3.
Similarity(S1,S2) = 2 / ( log(3) + log(4) ) = 0.80
Similarity(S1,S3) = 2 / ( log(3) + log(3) ) = 0.91
Similarity(S2,S3) = 1 / ( log(3) + log(4) ) = 0.40
注意!
- 两个句子的相似度没有指向性. 也就是说Similarity(S1,S2) = Similarity(S2,S1)
- 相似度不再只有0 和 1 ,它是浮点数的集合。
相似度 | 第一句 | 第二句 | 第三句 |
---|---|---|---|
第一句 | None | 0.80 | 0.91 |
第二句 | 0.80 | None | 0.40 |
第三句 | 0.91 | 0.40 | None |
我们写出来类似PageRank中的邻接矩阵。
在此基础上,我们可以应用新的PageRank算法来完成分数的计算
WS(Vi) 代表的是Vi这个页面的分数
d 代表的是一个阻尼常数 (0<d<1),用来确保每一个页面都至少有 (1-d)的分数。
In(Vi) 代表的是推荐Vi的页面。
Out(Vi) 代表的是Vi推荐的页面。
wji 代表的是 Vi 和 Vj 之间的相似度。
我们拿第一句句子作为例子,看一看它的得分。同样的,初始分数都是 1 。
WS(V1) = (1 - 0.85) + 0.85 *
( /*第二句句子*/ (0.80 * 1) / (0.80 + 0.40) +
/*第三句句子*/ (0.91 * 1) / (0.91 + 0.40) ) = 1.30
至此我们进一步迭代,计算出第二句和第三句的 分数 ,然后继续从第一句开始计算。直到最终每一句的分数不再变化为止。
三、实验步骤
3.1 准备工作
弄清楚了原理,我们要开始写代码了。首先导入我们需要的包。基本和实验一需要的包是一样的。
from nltk.tokenize import sent_tokenize, word_tokenize
from nltk.corpus import stopwords
import math
from itertools import product, count
from string import punctuation
from heapq import nlargest
stopwords = set(stopwords.words('english') + list(punctuation))
3.2 计算相似性
这个函数计算两个句子之间的相似性。有关相似性的知识,请看上文的算法介绍。
"""
传入两个句子
返回这两个句子的相似度
"""
def calculate_similarity(sen1, sen2):
# 设置counter计数器
counter = 0
for word in sen1:
if word in sen2:
counter += 1
return counter / (math.log(len(sen1)) + math.log(len(sen2)))
3.3 创建相似度邻接矩阵
"""
传入句子的列表
返回各个句子之间相似度的图
(邻接矩阵表示)
"""
def create_graph(word_sent):
num = len(word_sent)
# 初始化表
board = [[0.0 for _ in range(num)] for _ in range(num)]
for i, j in product(range(num), repeat=2):
if i != j:
board[i][j] = calculate_similarity(word_sent[i], word_sent[j])
return board
3.4 根据PageRank算法,算出句子分数
"""
输入相似度邻接矩阵
返回各个句子的分数
"""
def weighted_pagerank(weight_graph):
# 把初始的分数值设置为0.5
scores = [0.5 for _ in range(len(weight_graph))]
old_scores = [0.0 for _ in range(len(weight_graph))]
# 开始迭代
while different(scores, old_scores):
for i in range(len(weight_graph)):
old_scores[i] = scores[i]
for i in range(len(weight_graph)):
scores[i] = calculate_score(weight_graph, scores, i)
return scores
"""
判断前后分数有没有变化
这里认为前后差距小于0.0001
分数就趋于稳定
"""
def different(scores, old_scores):
flag = False
for i in range(len(scores)):
if math.fabs(scores[i] - old_scores[i]) >= 0.0001:
flag = True
break
return flag
"""
根据公式求出指定句子的分数
"""
def calculate_score(weight_graph, scores, i):
length = len(weight_graph)
d = 0.85
added_score = 0.0
for j in range(length):
fraction = 0.0
denominator = 0.0
# 先计算分子
fraction = weight_graph[j][i] * scores[j]
# 计算分母
for k in range(length):
denominator += weight_graph[j][k]
added_score += fraction / denominator
# 算出最终的分数
weighted_score = (1 - d) + d * added_score
return weighted_score
3.5 找出分数最高的两个句子
def Summarize(text,n):
# 首先分出句子
sents = sent_tokenize(text)
# 然后分出单词
# word_sent是一个二维的列表
# word_sent[i]代表的是第i句
# word_sent[i][j]代表的是
# 第i句中的第j个单词
word_sent = [word_tokenize(s.lower()) for s in sents]
# 把停用词去除
for i in range(len(word_sent)):
for word in word_sent[i]:
if word in stopwords:
word_sent[i].remove(word)
similarity_graph = create_graph(word_sent)
scores = weighted_pagerank(similarity_graph)
sent_selected = nlargest(n, zip(scores, count()))
sent_index = []
for i in range(n):
sent_index.append(sent_selected[i][1])
return [sents[i] for i in sent_index]
3.6 运行程序
最后加上我们的main:
if __name__ == '__main__':
# 可以替换为别的新闻
with open("news.txt", "r") as myfile:
text = myfile.read().replace('\n' , '')
# 可以更改参数 2 来获得不同长度的摘要。
# 但是摘要句子数量不能多于文本本身的句子数。
print(Summarize(text, 2)
这里我们提供了一个样本news.txt。可以在命令行中输入如下代码来获取。
wget http://labfile.oss.aliyuncs.com/courses/741/news.txt
四、实验总结
我们来对比一下两个算法获取的摘要
关键字提取算法
"Modern life is dramatically different to even 30 years ago," Prof Gray told Radio 4's Today programme, "people now drive to work and sit at work."
"The How Are You Quiz will help anyone who wants to take a few minutes to take stock and find out quickly where they can take a little action to make a big difference to their health."
TextRank算法
The PHE website and app has a quiz that gives users a health score based on their lifestyle habits by asking questions such as, "Which snacks do you eat in a normal day?"
The campaign's clinical adviser, Prof Muir Gray, said it was about trying to make people have a different attitude to an "environmental problem".
结论是两个算法的效果都一般,都没能把主题句找出来。相比较而言,比较简单的关键字提取算法可能选出的摘要要更加贴切一些。但是我们不能就这一个特例就断定TextRank算法不好。大家可以自己上传一个txt来对比两个算法的优劣。
五、实验代码
在命令行中输入
wget http://labfile.oss.aliyuncs.com/courses/741/NewsSummary2.py