本文主要用于实现使用TextRank算法的关键字提取
TextRank是PageRank算法的变种,用于文本关键字 关键句的提取
主要参考为原作者Rada Mihalcea论文《TextRank:Bring Order into texts》
整个算法步骤:
【1】文本分词
可以使用常见的java分词,本例使用的是IKAnalyer
【2】词性标注
这个暂时没有实现 【只是对提取效果会有一定的影响 一般会选择名次和动词作为关键字】 不影响算法思想实现
【3】构建初始转置矩阵【图的一种表示方式】
我们以文本对象中的每一个独立文本分词作为一个顶点,采用邻接矩阵来表示文本之间的关联,比如:
“A B C D”
"B C A"
"A C
A | B | C | D | |
A | 0 | 0 | 0 | 0 |
B | 1 | 0 | 0 | 0 |
C | 1 | 1 | 0 | 0 |
D | 0 | 0 | 1 | 0 |
(不知道怎么搞的 这表格删除不了多余 请无视)
words=【A,B,C,D】整个文档的单词数(去重复)
(每个单元格的含义:M[i,j]代表从j->i有指向 意即单词words[j] 后面有单词words[i])
假设上面为3段待提取的文字,分词以空格形式切分,那么我们可以得到下面的一个矩阵M
对上面的矩阵稍作处理 使 每一列的值结果如下:[有点像单元化处理m[i][j]=m[i][j]/(sum[0-N][j])
A | B | C | D | |
A | 0 | 0 | 0 | 0 |
B | 1/2 | 0 | 0 | 0 |
C | 1/2 | 1 | 0 | 0 |
D | 0 | 0 | 1 | 0 |
具体实现:部分源码
/**
* 传入经过分词处理后的wordsWrapper对象
*matrix[i][j]代表从j->i有指向 现在为未带权处理
* @param wordsWrapper
* @return 返回生成的double矩阵
*/
public static double[][] listToGraph(WordsWrapper wordsWrapper) {
int wordCount = wordsWrapper.wordcount;
List<String> nodulWords = Arrays.asList(wordsWrapper.nodulWords);
double[][] a = new double[wordCount][wordCount];
ArrayList<String> content = wordsWrapper.words;
for (String x : content) {
String xs[] = x.split(" ");
int curindex = -1, preindex = -1, nextindex = -1;
for (int i = 0; i < xs.length; i++) {
String tx = xs[i];
int index = nodulWords.indexOf(tx);
if (i != 0) {
preindex = nodulWords.indexOf(xs[i - 1]);
}
if (preindex != -1) {
a[index][preindex] = 1d;
}
}
}
//just for test
// System.out.println("before ...");
//~~~test end here
//初始矩阵处理
for (int j = 0; j < wordCount; j++) {
//这里可以修正 为带权值的
int nozero = 0;
for (int i = 0; i < wordCount; i++) {
if (a[i][j] != 0) {
nozero++;
}
}
if (nozero != 0) {
for (int i = 0; i < wordCount; i++) {
a[i][j] = a[i][j] / nozero;
}
}
}
return a;
}
【4】迭代运算
采用最大迭代次数和误差控制判断是否要求迭代
关于每个单词的PR(PageRank)值计算,在论文总是得分计算公式:
其中各个参数含义:
S(Vi)--顶点i的得分 可以理解为单词i的PR值
d-----阻尼系数 默认为;0.85
In(Vi)---所有指向顶点i的顶点 【比如:单词A 面跟着单词B 那么A属于In(B)】
|Out(Vj)|---节点j的出度【与图算法中的出度意义一样 上面的是入度】
具体实现:【最大迭代次数(20)和 最大容错率由自己指定(0.000001)】
/**
* 判断是否需要下一次pagerank迭代 会进行精度计算和总的迭代次数考虑
*
* @param before 之前迭代结果 PR值
* @param cur当前迭代结果 PR值
* @param curIteration 已经迭代次数
* @return 是否需要下一次迭代
*/
public static boolean neededDoNext(double[] before, double[] cur, int curIteration) {
//先检查迭代次数是否已经超过最大迭代次数
if (curIteration > MAX_ITERATE_NUM) {
return false;
} else {
//精度要求
int n = before.length;
for (int i = 0; i < n; i++) {
if (Math.abs(cur[i] - before[i]) > DEFAULT_ERROR_RATE) {
return true;
}
}
System.out.println("All is ok???");
outputArray(before);
outputArray(cur);
//所有都满足精度要求
return false;
}
}
迭代计算处理核心:
double []prevPR//记录前一次的算出来的pr值 初始值设置为全0
double []curPR//记录当前算出的PR值 初始值设定为全1/wordCount;
while (neededDoNext(prevPR, curPR, iterationCount)) {
//计算下一次迭代
prevPR=curPR;
curPR = doPageRank(transMatrix,prevPR,outDegrees);
System.out.println("当前迭代次数:"+iterationCount+"RESULT:");
outputArray(curPR);
iterationCount++;
}
效果演示:
总共迭代次数为:21
word->d score->0.4023749808259771
word->b score->0.3210029191796684
word->c score->0.5938468678121206
word->a score->0.4023749808259771
可以看出本文的关键字应该是C 注意pagerank可能不会收敛 所以设置最大迭代次数
【5】后处理
提供将临近的关键字 结合起来形成新的关键字【未完成】
【6】写在最后
由于看的是英文的论文 可能有的理解有失偏颇 有的地方理解的不到位还望指教,
还有如果使用带权图可以获得更好的计算效果【论文中有叙述】实现起来也就是修改PR[i] 或者score的计算,原理是一样的
关于如何确定文本单词词性的问题 暂时还没有头绪