Information Retrieval 倒排索引 学习笔记
一,问题描述
在Shakespeare文集(有很多文档Document)中,寻找哪个文档包含了单词“Brutus”和"Caesar",且不包含"Calpurnia"。这其实是一个查询操作(Boolean Queries)。
在Unix中有个工具grep,它能线性扫描一篇文档,然后找出某个单词是否在该文档中。因此,寻找哪篇文档包含了“Brutus”和“Caesar”可以用grep来实现。但是:不包含“Calpurnia”如何实现呢?
有时,还有一些更加复杂的情况:比如寻找“Romans”附近出现“countrymen”的文档有哪些?附近 表示寻找的范围,比如在某篇文档中“Romans”和“countrymen”出现在同一段落中,那么这篇文档就是要找的文档;再比如:“Romans”前后10个词内出现“countrymen”,则这篇文档就是要找的文档。这种情况又如何处理?
再比如,寻找 包含单词“Brutus”和"Caesar"的文档,返回的结果有很多篇文档,哪篇文档最相关呢?(Rank retrieval)。这些复杂的情况都无法用 grep 工具来实现,而是使用了一些特殊的数据结构(文档表示方式)。比如 Term-document incidence matrices 和 倒排索引(Inverted index)
二,Term-document incidence matrices
介绍一个新概念:Term
在处理文档时,经常以单词(word)作为分析处理单元(units),但有一些"专有名词",又不是传统意义上的单词,比如"Hong Kong"或者一些一连串的数字。因此,在IR中,用term来表示"index units"。看到这里,就明白tf-idf(term frequency–inverse document frequency) 中的 term 是什么意思了。
Terms are the indexed units,they are usually words, and for the moment you can think of them as words,
but the information retrieval literature normally speaks of terms because some of them,
such as perhaps I-9 or Hong Kong are not usually thought of as words.
回到文章开头提出的那个问题:哪个文档包含了单词“Brutus”和"Caesar",且不包含"Calpurnia"?,更专业地:
哪个文档包含了 term "Brutus" 和 term "Caesar",且不包含term "Calpurnia"?
首先将文档"拆分"成一个个的 term 来表示,若某个term在这篇文档中出现 就用1标识;未出现则用0标识。

如上图所示:每一列代表一篇文档是否包含了某个term,比如 文档 Julius Caesar 就包含了"Antony"、"Brutus"、"Caesar"、"Calpurnia",但是不包含"Cleopatra"、"mercy"、"worser"。
每一行表示 某个term 出现在哪几篇文档中。比如 term "Antony"出现在第一篇文档、第二篇文档(Julius Caesar)和最后一篇文档中。
有了这个矩阵,就可以回答上面那个问题了。Brutus 在文档中出现情况是 110100;Caesar在文档中出现情况是 110111 ;Calpurnia 出现情况是 101111,将它们进行 与操作:
110100 AND 110111 AND 101111 =
得出:包含了单词“Brutus”和"Caesar",且不包含"Calpurnia"的文档是第一篇文档Antony and Cleopatra 和 第四篇文档 Hamlet。而且对于计算机而言,进行与操作是很快的。
从上面可看出,通过Term-document incidence matrices这种文档的表示形式,将 grep 线性查找操作,变成了 位与 操作。
但是,这种表示方式也存在着问题:这个矩阵会很稀疏。比如中文汉字有几万个(term 很多),一篇新闻文档不会用到所有的中文汉字,因此矩阵中大部分元素为0。而要存储一个很大的稀疏矩阵,对内存就造成了浪费。而倒排索引就可以解决这个问题。
三,inverted index
倒排索引就是:如果某个term在文档中出现了,才记录它。若不出现,则不记录。
A much better representation is to record only the things that do occur, that is, the 1 positions. We keep a dictionary of terms Then for each term, we have a list that records which documents the term occurs in.
Each item in the list – which records that a term appeared in a document– is conventionally called a posting
假设有很多文档,如何构建倒排索引呢?首先是从文档中选取出 term,也就是对文档进行分词,得到一个个的 term。比如有N篇文档如下:
文档一: Friends, Romans, countrymen.
文档二: So let it be with Caesar
....
....
文档N:
对文档文档分词(tokenize),得到一系列的tokens:Friends、Romans、countrymen、So……
有时还需要对分词的结果进行预处理(linguistic preprocessing),这种预处理操作一般是:删除一些 stop words,进行 Stemming 操作 和 lemmatization操作。stemming操作 是从词形上对单词归一化,比如说:复数cats 变成 cat。而 lemmatization 是寻找词根,比如:are, is, am 都归一化成 be
Stemming usually refers to a crude heuristic process that chops off the ends of words in the hope of
achieving this goal correctly most of the time,
and often includes the removal of derivational affixes
Lemmatization usually refers to doing things properly with the use of a vocabulary and morphological analysis
of words normally aiming to remove inflectional endings only and to return the base or dictionary form of a word,
which is known as the lemma。
预处理之后,得到一个个的 可索引的 term 了。倒排索引如下图所示:

"Brutus"就是一个term,它关联着一个链表(list),这个链表称之为posting,链表中的每个元素代表文档的标识,它表示: term "Brutus"出现在 文档1,文档2,文档4,文档11,文档31……文档174中
若干个 term 组合起来就是一个 dictionary,所有的posting的集合就是 postings
从上可看出,使用倒排索引表示时:每个文档都有一个唯一的文档标识(docID),而且链表是有序的。并且上面的倒排索引只关注:某个term是文档中 是否 出现过,并不知道出现了多少次。
现在如何根据倒排索引找出:哪个文档包含了单词“Brutus”和"Caesar",且不包含"Calpurnia"?这其实就是 "Brutus" 指向的链表 和 "Caesar"指向的链表 求并 操作(intersection)---两个有序的链表找公共元素。算法的伪代码如下:
INTERSECT(p1, p2)
answer ← <>
while p1 != NIL and p2 != NIL
do if docID(p1) = docID(p2)
then ADD(answer, docID(p1))
p1 ← next(p1)
p2 ← next(p2)
else if docID(p1) < docID(p2)
then p1 ← next(p1)
else p2 ← next(p2)
return answer
时间复杂度为:O(N),N就是 文档的总个数。对于两个有序链表 求并 操作,时间复杂度会不会小于O(N)呢?那也是有可能的,那就是在链表的某些元素上,存储一个"skip pointer"指针,如下图所示:

举个例子:假设目前已经找到了两个链表中的第一个公共元素8,现在要找下一个公共元素。链表1移动到下一个位置指向16,链表2移动到下一个位置指向41。由于元素16存储了一个skip pointer,该skip pointer指向28,由于链表是有序的而且28小于41,因此链表1可以直接跳过19、23这两个元素,直接移动到28这个元素上(从而不需要将 19和23 这两个元素与 链表2中的41比较)。算法伪代码如下:
INTERSECTWITHSKIPS(p1, p2)
1 answer ← <>
2 while p1 != NIL and p2 != NIL
3 do if docID(p1) = docID(p2)
4 then ADD(answer, docID(p1))
5 p1 ← next(p1)
6 p2 ← next(p2)
7 else if docID(p1) < docID(p2)
8 then if hasSkip(p1) and (docID(skip(p1)) ≤ docID(p2))
9 then while hasSkip(p1) and (docID(skip(p1)) ≤ docID(p2))
10 do p1 ← skip(p1)
11 else p1 ← next(p1)
12 else if hasSkip(p2) and (docID(skip(p2)) ≤ docID(p1))
13 then while hasSkip(p2) and (docID(skip(p2)) ≤ docID(p1))
14 do p2 ← skip(p2)
15 else p2 ← next(p2)
16 return answer
引入skip pointers到底是好还是坏呢?这个不一而足。说几个需要考虑的因素:
①引入skip pointer 需要额外的存储空间。②移动到某个元素上时,需要判断该元素是否存储了 skip pointers。③在哪些元素上存储 skip pointer比较好? skip pointers 跳过多少个元素比较好?……
额外的一点 补充,上面讲到:每个 term 的posting list 长度是未知的。要找某两个term的公共元素,其实就里线性遍历这两个term对应的posting list。因此,这个过程的时间复杂度是O(M+N) 。这里的M是第一个term对应的posting list的长度,N是第二个term对应的posting list的长度。那如果我要找多个term的posting list中的公共元素呢?
比如说:寻找 term : Brutus 、Calpurnia、Caesar这三个term,都在哪些文档中出现了?
这是一个与操作。如果知道 posting list的长度,先将长度比较短的term的posting list进行与操作,这样能提高查询的效率。比如说从上面图中可看出:Calpurnia 的posting list的长度为4,先执行 Calpurnia & Brutus 得出的结果的长度也不会超过4,然后再去Caesar对应的posting list中查询。效率要好。
即:执行顺序为Calpurnia & Brutus & Caesar 比 Caesar & Brutus & Calpurnia 要好。
四,参考资料:
《An Introduction to Information Retrieval》第一章和第二章
原文:http://www.cnblogs.com/hapjin/p/8214254.html
Information Retrieval 倒排索引 学习笔记的更多相关文章
- Hadoop学习笔记(8) ——实战 做个倒排索引
Hadoop学习笔记(8) ——实战 做个倒排索引 倒排索引是文档检索系统中最常用数据结构.根据单词反过来查在文档中出现的频率,而不是根据文档来,所以称倒排索引(Inverted Index).结构如 ...
- Deep Learning for Information Retrieval
最近关注了一些Deep Learning在Information Retrieval领域的应用,得益于Deep Model在对文本的表达上展现的优势(比如RNN和CNN),我相信在IR的领域引入Dee ...
- 学习笔记之Model selection and evaluation
学习笔记之scikit-learn - 浩然119 - 博客园 https://www.cnblogs.com/pegasus923/p/9997485.html 3. Model selection ...
- Python学习笔记—Python基础1 介绍、发展史、安装、基本语法
第一周学习笔记: 一.Python介绍 1.Python的创始人为吉多·范罗苏姆.1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语言 ...
- Linux 学习笔记
Linux学习笔记 请切换web视图查看,表格比较大,方法:视图>>web板式视图 博客园不能粘贴图片吗 http://wenku.baidu.com/view/bda1c3067fd53 ...
- Android M Permission 运行时权限 学习笔记
Android M Permission 运行时权限 学习笔记 从Android 6.0开始, 用户需要在运行时请求权限, 本文对运行时权限的申请和处理进行介绍, 并讨论了使用运行时权限时新老版本的一 ...
- 《Java学习笔记(第8版)》学习指导
<Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...
- linux学习笔记2-linux的常用命令
第一篇博客:linux学习笔记1-ubuntu的安装与基本设置 之中,已经介绍了如何安装linux操作系统,以及一些基本的设置修改. 本篇博客主要介绍linux中的一些常用的终端命令 ======== ...
- X-Cart 学习笔记(二)X-Cart框架1
目录 X-Cart 学习笔记(一)了解和安装X-Cart X-Cart 学习笔记(二)X-Cart框架1 X-Cart 学习笔记(三)X-Cart框架2 X-Cart 学习笔记(四)常见操作 四.X- ...
随机推荐
- Android assets res 文件夹的区别
大家都知道建立一个Android项目后会产生assets与res的两个文件夹,理论上他们都是存放资源的文件夹,那么他们到底有什么区别呢? 1.assets:不会在R.java文件下生成相应的标记,存放 ...
- 【转】STM32: 一种计算CPU使用率的方法及其实现原理
1 前言出于性能方面的考虑,有的时候,我们希望知道CPU的使用率为多少,进而判断此CPU的负载情况和对于当前运行环境是否足够“胜任”.本文将介绍一种计算CPU占有率的方法以及其实现原理. 2 移植 ...
- 利用mysqldump命令导出为csv格式文件
解决方法: 先导出为txt文件,其内容是以逗号“,”分隔的,得到txt文件后,再自行处理为.csv或者.xls文件. 参数说明: -t, --no-create-info Don't write ...
- BZOJ 4562: [Haoi2016]食物链(拓扑排序)
题面: https://www.lydsy.com/JudgeOnline/problem.php?id=4562 一句话题意:给一个DAG,求有多少条不完全相同的链,使链首入度为0,链尾出度为0. ...
- [CTSC2018]暴力写挂——边分树合并
[CTSC2018]暴力写挂 题面不错 给定两棵树,两点“距离”定义为:二者深度相加,减去两棵树上的LCA的深度(深度指到根节点的距离) 求最大的距离. 解决多棵树的问题就是降维了. 经典的做法是边分 ...
- bzoj1271 秦腾与教学评估
SB题!!! 我TM困惑了一下午,三份代码答案全都不一样,后来才发现要用long long来二分... 拿记事本一改就A了. 我TM...... 这SB题目...... 这惨痛的事实充分说明了long ...
- HDU/HDOJ 2087 剪花布条
KMP裸题 (极限5分钟A题) /** freopen("in.in", "r", stdin); freopen("my.out", &q ...
- [luoguU48834][count]
题目链接 思路 这个题可以考虑用全部情况减去不合法的情况,来求解.首先需要知道n个点所组成的图总共有\(C(_n^2)\)种,然后用f[n]表示n个点的图联通的方案数. 然后钦定1在联通图里面,考虑不 ...
- 通过锁字符串达到控制并发的效果C#
lock锁的是地址 而.net有内部机制使得相同的字符串内存地址是相同的(new string)除外 下面上实验代码 using System; using System.Collections.Ge ...
- Tomcat如何发布web项目
tomcat/webapps目录是用来存放Java项目的.每一个文件夹都是一个项目,默认这个目录下已经有了四个项目,都是tomcat自带的. 其中ROOT就是我们测试Tomcat时访问的Tomcat的 ...