文本相似搜索算法以及改进

以前做的一个相关博文推荐的项目,整理了一下   

目的:针对于博客,推荐内容相关博客 。

方法:将博客分词、去除停用词、tf-idf、标题加权等做成vsm向量,将一篇文章和其它的所有文章求相似度(文本一般采用cos相似度),然后取相似度最大的N篇文章 。

总共300万篇文章左右,遇到的困难:每篇文章都要和其它的300W篇做计算,然后去相似度的TopN,总共要计算的是300W*300W=90000亿次的计算,在初始计算的时候,计算量会特别大 ,当时计算的是大约要一个月左右的时间重跑一遍,当时也没有机器,只能借了2台服务服务器多线程跑,最后大致缩短到一个星期,但后续改进后整个算法的时间缩短到了3个小时左右

文本的向量化

将一篇文章向量化其实有很多步骤要做,这里简单列下,列出最终的文本向量的数据结构

1、文本切词、去除停用词、计算idf、去除无用词、只取名词 。

2、单个文本切词后,统计并对此加权求权重。

3、统计文本中的所有词,每个词赋予整形的id,按照id排序,然后以稀疏矩阵的方式存储文本向量vsm 。

4、所有文本向量归一化,为了计算方便,将长度归一化到127(byte表示的最大数),向量的维度值都取整 。

几点:最初我们是将向量归一化到1,每个维度的取值是浮点小数 ,当时以为浮点计算会比较慢,所以将所有的替换成整数,但是效果提升的并不明显,所以在这样密集型的计算中,浮点数和byte整数的效率差别不是很大 。

结构如下:

文章id     

{1,[{5,13},{2018,75},{5001,10},{6040,73},{6701,25}]}

{2,[{1,20},{12,71},{300,40},{5555,73}]}

.........................................................

{N,[{5,15},{300,71},{2018,37},{5001,73},{5555,23},{6701,15},{6900,20}]}

id是文章的id ,后续存储的是{词id,该词的维度值}

 

计算两篇文章的相似度算法

类似于归并排序的方法,对于两个vsm从最初的id开始扫描,start_w_id_1 ,start_w_id_2 ,每次较小的向后移动一下,若是相等的话,则维度值相乘,不相等则跳过,继续完后扫描,总的时间复杂度是O(m+n)

一般的算法
针对每篇文章和其它的文章求相似度,然后选中相似度最大的前N篇文章 。

改进

因为向量很稀疏,所以有很多向量之间根本就没有共同的词,其cos值是0,所以可以直接过掉 ,这样需要做一个倒排向量的,以词的id和词所在的文章来做倒排表,这样每次要计算某一篇文章的时候,先根据这篇文章包含的词id到倒排表重寻找这些词的倒排链,然后合并倒排表(合并的算法和归并算法一样),然后只和筛选后的文章做相似度计算并选出TopN,这样筛选后的数量平均在20W向量左右(这个过滤还是挺大的)。

 

最终改进后的算法

修改倒排表,倒排链的每个文章id中附加上这个词在这篇文章中的权重,具体如下

 

 

首先将300万篇一次读入内存,建立如上图结构的基于单词的倒排索引,每篇文章经过归一化后向量长度均是127byte型表示的最大数),并统计所有文章的最大id

2、在针对某篇文章计算前,在内存中初始化一个数据,数组的size是所有文章的最大id+1 。

shot[]  cosValue=new shot[所有文章的最大id+1] ,如下  

 

存储每篇文章的余弦值的数组cosValuePS:数组的第0个元素舍去,不用做存储实际值),数组中只存储余弦公式中分子的值(因为向量经过归一化处理后,长度固定127,是以分母大小固定为127*127,所以比较cosValue的大小可以直接比较分母,经过计算后,cosValue数组的第k个值,即cosValue[k]即所求原文章与  idk的文章  之间余弦值(的分子

3、 计算

Ø  对于每一个要求的文章向量,扫描其文本向量中的每个词。

Ø  对于该向量的每个词(假设向量中该词的维度值为weight),找到该词在倒排序表中的索引数据 

Ø  该行索引中的每个值分别与weight(该词在这篇文章中的维度值)相乘,累加到其id对应在 cosValue中的值,伪代码如下:

ForType  e :该行索引数据 

   cosValue[e.id]=  cosValue[e.id]+e.value * weight  

Ø  计算完毕后,在数组中查找出值最大的前N(现取得是5,需要一个大小是5的小顶堆,整个扫描一遍数组即可,其时间复杂度是O(m*logN))个,作为最相似的文章,其数组下标即为相似文章的id 。

 

 

相对于原先计算提升的地方:

内存改进的地方:原先以对象形式来存储向量数据,以集合类来存储这些数据 ,现在改为用基本类型来存储,以数组的形式存储 ,在计算和存储效率上面确实有一些改进。

效率改进的地方:前后最大的计算改进,原先的算法(在算两个向量的cosValue)的时候会大量使用if-else的操作(可以的话,自己实现下这块代码看看),这样在会严重阻断cpu的流水线操作,过量的判断分支操作使得cpu的流水线基本没有发挥作用,但是切换待第二种以后,所有的只是乘法、加法两种操作,速度提升了很多,所以在高密集型的计算操作中,尽量避免判断分支等阻断流水线操作,这样会使得性能下降很多,远不止流水线的5倍 。

 

分布式的问题

由于把所有的数据均存入内存中,是以在内存消耗这一块着较大(300万左右的博文,平均取单词数目在40左右,功耗内存400M+);若条件允许的话,可以将倒排索引才用分布式在几台机器上存储,并行计算,最后结果累加。

 

github代码地址:https://github.com/ddc496601562/nlp-datamining-tools/tree/master/nlp-datamining-tools/vsm-similar-search

posted @ 2014-05-20 09:37  xiao晓  阅读(2411)  评论(0编辑  收藏  举报