第二十六章:基于给定的文档生成倒排索引的编码与实践
作者:July、yansha。
出处:结构之法算法之道
本周实现倒排索引。实现过程中,寻找资料,结果发现找份资料诸多不易:1、网上搜倒排索引实现,结果千篇一律,例子都是那几个同样的单词;2、到谷歌学术上想找点稍微有价值水平的资料,结果下篇论文还收费或者要求注册之类;3、大部分技术书籍只有理论,没有实践。于是,朋友戏言:网上一般有价值的东西不多。希望,本blog的出现能稍稍改变此现状。
在第二十四章、倒排索引关键词不重复Hash编码中,我们针对一个给定的倒排索引文件,提取出其中的关键词,然后针对这些关键词进行Hash不重复编码。本章,咱们再倒退一步,即给定一个正排文档(暂略过文本解析,分词等步骤,日后会慢慢考虑这些且一并予以实现),要求生成对应的倒排索引文件。同时,本章还是基于Hash索引之上(运用暴雪的Hash函数可以比较完美的解决大数据量下的冲突问题),日后自会实现B+树索引。
与此同时,本编程艺术系列逐步从为面试服务而转到实战性的编程当中了,教初学者如何编程,如何运用高效的算法解决实际应用中的编程问题,将逐步成为本编程艺术系列的主旨之一。
OK,接下来,咱们针对给定的正排文档一步一步来生成倒排索引文件,有任何问题,欢迎随时不吝赐教或批评指正。谢谢。
根据信息检索导论(Christtopher D.Manning等著,王斌译)一书给的提示,我们可以选择两种构建索引的算法:BSBI算法,与SPIMI算法。
BSBI算法,基于磁盘的外部排序算法,此算法首先将词项映射成其ID的数据结构,如Hash映射。而后将文档解析成词项ID-文档ID对,并在内存中一直处理,直到累积至放满一个固定大小的块空间为止,我们选择合适的块大小,使之能方便加载到内存中并允许在内存中快速排序,快速排序后的块转换成倒排索引格式后写入磁盘。
建立倒排索引的步骤如下:
合并时,同时打开所有块对应的文件,内存中维护了为10个块准备的读缓冲区和一个为最终合并索引准备的写缓冲区。每次迭代中,利用优先级队列(如堆结构或类似的数据结构)选择最小的未处理的词项ID进行处理。如下图所示(图片引自深入搜索引擎--海里信息的压缩、索引和查询,梁斌译),分块索引,分块排序,最终全部合并(说实话,跟MapReduce还是有些类似的):
读入该词项的倒排记录表并合并,合并结果写回磁盘中。需要时,再次从文件中读入数据到每个读缓冲区(基于磁盘的外部排序算法的更多可以参考:程序员编程艺术 第十章、如何给10^7个数据量的磁盘文件排序)。
BSBI算法主要的时间消耗在排序上,选择什么排序方法呢,简单的快速排序足矣,其时间复杂度为O(N*logN),其中N是所需要排序的项(词项ID-文档ID对)的数目的上界。
SPIMI算法,内存式单遍扫描索引算法小数据量与大数据量的区别
在小数据量时,有足够的内存保证该创建过程可以一次完成;
数据规模增大后,可以采用分组索引,然后再归并索 引的策略。该策略是,
如下,给定如下图所示的正排文档,每一行的信息分别为(中间用##########隔开):文档ID、订阅源(子频道)、 频道分类、 网站类ID(大频道)、时间、 md5、文档权值、关键词、作者等等。
要求基于给定的上述正排文档。生成如第二十四章所示的倒排索引文件(注,关键词所在的文章如果是同一个日期的话,是挨在同一行的,用“#”符号隔开):
我们知道: 为网页建立全文索引是网页预处理 的核心部分,包括分析网页和建立倒排文件。二者是顺序进行,先分析网页,后建立倒排文件(也称为反向索引),如图所示:正如上图粗略所示,我们知道倒排索引创建的过程如下:
建相关的数据结构
根据给定的正排文档,我们可以建立如下的两个结构体表示这些信息:文档ID、订阅源(子频道)、 频道分类、 网站类ID(大频道)、时间、 md5、文档权值、关键词、作者等等。如下所示:
我们知道,通过第二十四章的暴雪的Hash表算法,可以比较好的避免相关冲突的问题。下面,我们再次引用其代码:基于暴雪的Hash之上的改造算法
有了这个Hash表,接下来,我们就可以把词插入Hash表进行存储了。
Hash表实现了(存于HashSearch.h中),还得编写一系列的函数,如下所示(所有代码还只是初步实现了功能,稍后在第四部分中将予以改进与优化):
最后,主函数编写如下:
程序编译运行后,生成的倒排索引文件为index.txt,其与原来给定的正排文档对照如下:
有没有发现关键词奥恰洛夫出现在的三篇文章是同一个日期1210的,貌似与本文开头指定的倒排索引格式要求不符?因为第二部分开头中,已明确说明:“注,关键词所在的文章如果是同一个日期的话,是挨在同一行的,用“#”符号隔开”。OK,有疑问是好事,代表你思考了,请直接转至下文第4部分。
细心的读者可能还是会注意到:在第二部分开头中,要求基于给定的上述正排文档。生成如第二十四章所示的倒排索引文件是下面这样子的,即是:
也就是说,上面建索引的过程本该是如下的:
与第一部分所述的SMIPI算法有什么区别?对的,就在于对在同一个日期的出现的关键词的处理。如果是遇一旧词,则找到其倒排记录表的位置:相同日期,添加到之前同一日期的记录之后(第一个记录的后面记下同一日期的记录数目);不同日期,另起一行新增记录。
相同(单个)日期,根据文档权值排序
不同日期,根据时间排序
代码主要修改如下:
修改后编译运行,生成的index.txt文件如下:
如上图所示,已经满足需求了。但可以再在每个关键词的背后添加一个计数表示索引到了第多少个关键词:
针对本文评论下读者的留言,做了下思考,自觉可以省去二次hash:
综上5.1、5.2两节免去冒泡排序和,省去二次hash和免去冒泡排序,修改后如下:
修改后编译运行的效果图如下(用了另外一份更大的数据文件进行测试):
本章全部源码请到以下两处任一一处下载(欢迎读者朋友们继续优化,若能反馈于我,则幸甚不过了):