zoukankan      html  css  js  c++  java
  • 倒排索引

    索引是计算机科学领域中非常常用的数据结构,比如数据库中的索引。索引的目的就是为了加快查找速度,具体到搜索引擎中,索引更是扮演了非常重要的角色,面对海量的网页内容,如何快速找到包含用户查询关键词的所有网页呢?——这其中就用到了倒排索引!
     
    什么是倒排索引?如何建立倒排索引表?倒排索引表有什么作用?......
     
    在回答这些问题之前,先要了解一下“单词-文档矩阵”的概念。
     
    所谓“单词-文档矩阵”,就是一种表达单词和文档之间所具有的包含关系的概念模型。如下:
     
    纵向看:表示某文档包含哪些单词(如文档1包含单词1和单词4)
    横向看:表示某单词出现在哪些文档中(如单词1出现在文档1和文档4中)
     
    而搜索引擎的索引就是实现“单词-文档矩阵”的具体数据结构,如有倒排索引,后缀树等。其中,倒排索引是最佳的实现方式。
     
    概括的说,倒排索引就是一种实现“单词-文档矩阵”的数据结构,它常常用作搜索引擎的索引结构
     
    在继续讲解之前,我们需要明白几个基本概念。
    • 文档:一般搜索引擎的处理对象是网页。而文档的概念则要更加宽泛一些,凡是以文本形式存在的存储对象,比如.doc,.txt,.html等格式的文件都是文档。在这里,可以简单的把文档理解成网页。
    • 文档集合:由若干篇文档构成的集合就称为文档集合。对于搜索引擎而言,文档集合就是从互联网上爬下来的所有网页的集合,这个数据是巨大的,为了存储这些海量的数据从而有了分布式存储。在我的这个小项目中,我就只爬取了几百篇博客作为我的文档集合。
    • 文档编号:在搜索引擎内部,每篇文档会有唯一的编号作为标记,方便数据处理。记文档编号为docID。
    • 倒排列表:倒排列表记录了出现过某个单词的文档列表,以及单词在文档中的一些信息(比如权重,位置等),每条记录称为倒排项。通过倒排列表,即可获得包含某一单词的所有的文档。
    • 单词词典:搜索引擎的索引单位通常是单词,单词词典是由文档集合中出现过的所有单词构成的。
    搜索引擎的网页数以亿计,设想,查找一个关键词,如果从头到尾遍历所有文章(即正向索引),这是极其耗费时间的。而利用倒排索引就能很快找到包含该关键词的文档,这就是倒排索引的厉害之处了。即建立倒排索引的作用是为了提高搜索引擎的查询效率,可以这么说,倒排索引是搜索引擎最关键的一个数据结构!
     
    下面是倒排索引的一个简单实例。
     
    假设文档集合包含5个文档,如下图所示,我们的任务就是对这个文档集合建立倒排索引。
     

    对于英文,各个单词之间是分割开的,很好处理;对于中文,则需要分词算法先把一句连续的话切分成一个个单词,比如,"南京市长江大桥"到底是切分成{"南京", "市长", "江大桥"}还是{"南京市", 长江大桥"}就是分词算法做的事情。但这不是讨论的重点,我们假设,能很好的对中文文档进行分词。对于不同的单词,我们为其赋予唯一的单词编号,同时记录该单词在哪些文档中出现过。基于此,我们可以得到最简单的倒排索引,如下图所示。比如,"跳槽"这一个单词的编号是4,它在文档1、4中出现过,因此该单词对应的倒排列表就是{1, 4}。

     

    下面的倒排索引则稍微复杂了一些,不仅记录了单词在哪篇文档中出现过,还记录了单词在该文档中出现的频率(Term Frequency, TF),之所以记录这个信息,是因为词频信息在搜索结果排序时,计算查询和文档相似度是一个很重要的计算因子。比如,"跳槽"一词的倒排列表为{(1;1), (4;1)},说明该单词在1号文档中出现过1次,在4号文档中出现过1次。

     

     下面的倒排索引则增加了文档频率(Document Frequency,DF)和单词在文中的位置信息(<pos>),还是以"跳槽"为例,其文档频率为2,表示在整个文档集合中,共有2个文档包含"跳槽"这个单词;倒排索引项(1; 1; <4>)表示该单词在1号文档中出现过1次,并且出现的位置是4,即文档中的第4个单词是"跳槽"。

    注意厘清文档频率(DF)和单词频率(TF)的概念,这两个概念非常重要,在搜索结果排序计算中都是非常重要的因子。而单词在文档中的位置信息则不是必须的。
     

    图3-6所示的倒排索引已经是一个非常完备的索引系统了,有了这个索引系统,搜索引擎就可以很方便的响应用户的查询。比如用户输入查询词"Facebook",搜索系统查找倒排索引,从中可以读出包含这个单词的文档,这些文档就是提供给用户的搜索结果,而利用单词频率信息(TF)、文档频率信息(DF)即可对这些候选搜索结果进行排序,计算文档与查询的相似性,按照相似性得分由高到低排序输出。

     

    在自己写的项目中应用如下:
    构建倒排索引表的数据结构是
    unordered_map<string, vector<pair<int, double>>> invertIndexTable; 
     
    即
    单词1   {docID1,weight1}, {docID2,weight2}, {docID3,weight3}...
    单词2   {docID1,weight1}, {docID2,weight2}, {docID3,weight3}...
    ...
    ...
     
    项目部分源码
    void PagelibProcesser::createInvertIndexTable()
    {
        for(auto iter = pagelib_.begin(); iter != pagelib_.end(); ++iter){ //pagelib_的结构是vector<WebPage> 
            auto docWordMap = (*iter).getDocWordMap();//词典,[单词,词频]
            for(auto it = docWordMap.begin(); it != docWordMap.end(); ++it){
                //倒排索引的结构unordered_map<string, vector<pair<int, double>>> invertIndexTable_
                invertIndexTable_[it->first].push_back({iter->getDocID(),it->second});//<单词,<docID,词频>>
            }
        }
     
        size_t totalPageNum = pagelib_.size();//网页库中网页的总数
        map<int,double> pageWeight;//每个网页的权重和
        for(auto iter = invertIndexTable_.begin(); iter != invertIndexTable_.end(); ++iter){
            size_t df = iter->second.size();//文档频率,表示单词在多少篇文档中出现过   
            double idf = log(static_cast<double>(totalPageNum)/(df+1));//逆文档频率
            for(auto & elem : iter->second){
                double weight = elem.second * idf;
                elem.second = weight;
                pageWeight[elem.first] += pow(weight,2);
            }
        }
     
        //归一化处理
        for(auto iter = invertIndexTable_.begin(); iter != invertIndexTable_.end(); ++iter){
            for(auto & elem : iter->second){
                elem.second = elem.second / sqrt(pageWeight[elem.first]);
            }
        }
    }

    建倒排索引时最难的是每个词语的权重值的计算,它涉及到如下几个概念:

    TF:  Term Frequency, 单词在某一篇文档中出现的次数;
    DF:  Document Frequency, 在文档集合中,包含该词语的文档数量;
    IDF: Inverse Document Frequency, 逆文档频率,表示某一单词对于该篇文章的重要性的一个系数,其计算公式为:
    IDF = log2(N/(DF+1)),其中N表示文档的总数或网页库的文档数
    最后,词语的权重w则为:w = TF * IDF
     
    可以看到权重系数与一个词在文档中的出现次数成正比,与该词在整个网页库中的出现次数成反比。
     
    而一篇文档包含多个词语w1,w2,...,wn,还需要对这些词语的权重系数进行归一化处理,其计算公式如下:
    w' = w /sqrt(w1^2 + w2^2 +...+ wn^2)
    w' 才是需要保存下来的,即倒排索引的数据结构中InvertIndexTable的double类型所代表的值。此权重系数的算法称为TF-IDF算法。

    做个记录,方便回顾。

  • 相关阅读:
    Java—CountDownLatch使用详解
    Java—线程的生命周期及线程控制方法详解
    Java反射机制详解
    一点点点点点算法刷题总结
    Java并发编程:线程池ThreadPoolExecutor
    Java并发编程:线程和锁的使用与解析
    MySQL——关于索引的总结
    常用设计模式的实现,以及Netty中的设计模式
    Netty入门与实战教程
    手动搭建I/O网络通信框架4:AIO编程模型,聊天室终极改造
  • 原文地址:https://www.cnblogs.com/kkbill/p/11520398.html
Copyright © 2011-2022 走看看