zoukankan      html  css  js  c++  java
  • 我是怎么用跳表优化搜索引擎的?

    前言

    对于跳表,我想大家都不陌生吧,这里不多解释,感兴趣的小伙伴可以看我的这篇文章:http://www.cnblogs.com/haolujun/archive/2012/12/24/2830683.html。 这段时间在做我们拍搜的优化,今天我就讲讲我是如何用跳表优化检索系统的。

    搜索引擎的夹角余弦计算

    都知道,搜索引擎利用夹角余弦计算query与文档的相似度,感兴趣的小伙伴可以看我的这篇文章:http://www.cnblogs.com/haolujun/archive/2013/01/08/2847503.html, 这里面需要计算两个向量的余弦值。

    假设查询向量为:$ Q = [q_{1},q_{2},......,q_{n}] ( 假设文档向量为:) D = [d_{1},d_{2},......,d_{n}] $

    query与文档的相似度为:$ sim(Q,D) = frac{QD}{|Q||D|} $ 。这里面需要Q和D模长相乘做分母,对应分量相乘之和做分子。

    模长的计算

    对于query可以在每次查询之前做统一的预处理,在预处理过程中计算模长;对于文档,不能每次查询都计算一遍模长,这样效率很低,可以事先在建立索引的时候计算模长并保存。

    向量相乘

    在搜索引擎检索过程中,首先需要对query进行分词并得到查询向量,之后我们用分出的词从倒排表中拉取文档,不熟悉倒排表的小伙伴可以看这篇文章:http://www.cnblogs.com/haolujun/archive/2013/01/06/2847510.html。 通过倒排拉取出来的只是文档的ID,而文档本身包含的内容其实是以正排的方式存储的:即key=文档ID,$value=(word_{1}, word_{2}, ....word_{n}) $,实际上为了节省存储空间,正排只存储该文档中出现过的词。而今天的向量相乘就是利用正排与query进行两个集合的求交计算(向量相乘只对同时出现在query以及文档中的词进行计算,其它的都计为0),那么这就引出一个新问题,如何求两个集合的交集呢?聪明的小伙伴肯定想到了解决办法:对Q和D分别按照字典序排序,之后求两个有序列表的交集可以在线性时间内完成,示例代码如下:

    int i = 0, j = 0;
    double sum = 0.0;
    while(i < len_q && j < len_doc) {
        if(q[i] < d[j]) {
            i++; 
        } else if(q[i] > d[j]) {
            j++;
        } else {
            sum += sim(q[i], d[j]); 
        }
    }
    

    但是,还能再优化这个代码么?答案是肯定的,那就是利用跳表。

    对于搜索引擎来说,通常query较短,而文档较长,我们估计排完序的文档序列中会有一大段一大段的词都不在query中,可以直接跳过这些段而不用一一遍历,这就启发我们可以用跳表进行加速,优化代码如下:

    double sum = 0.0;
    int step = ceil(sqrt(len_doc)), i = 0, j = 0;
    while(i < len_q && j < len_doc) {
       if(q[i] < d[j]) {
          int k = (j - step + 1) < 0 ? 0 : j - step + 1;
          while(k <= j && d[k] < q[i]) k++;
          if(d[k] == q[i]) {
            sum += sim(i, j); j = k + 1;  i++;
          } else {
            j = k; i++;
          }
        } else if(q[i] > d[j]) {
    	  j = j + step < len_doc ? j + step : j + 1;
        } else {
          sum += sim(i, j); 
    	  ++i;
    	  j = j + step < len_doc ? j + step : j + 1;
        }
      }
    }
    

    我们在这里选择步长为 $ sqrt[]{len_{doc}} $只是表示一种理论指导,实际中还需要不断测试不同的步长从而找到实际最优解。

    当然,优化这个问题并不一定用跳表,如果内存足够大,我们可以为每个文档建立哈希或者map,这样只需用O(1)或者 O(log(len_doc))时间判断文档中是否包含一个词。

    总结

    利用简单的跳表,加起来不过几十行代码,直接使检索的效率提高一倍,优化了50%的硬件成本,可以高高兴兴领取年终奖回家过大年了。 5年前我就研究了跳表,没想到5年后的今天我竟然用到了它,所以没事多看看感兴趣的技术、研究一下感兴趣的问题对你的将来只有利没有弊。

  • 相关阅读:
    sshd
    eclipse运行报java.lang.OutOfMemoryError: PermGen space解决方法
    项目之间依赖
    shell-
    部署记录
    mysql index使用
    GitHub上搭建私人hexo博客操作教程
    关于Vue实例的生命周期(2)
    JS中的五种去重方法
    Vue入门教程(2)
  • 原文地址:https://www.cnblogs.com/haolujun/p/8011776.html
Copyright © 2011-2022 走看看