Lucene暴走之巧用内存倒排索引高效识别垃圾数据

Posted 我是攻城师

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lucene暴走之巧用内存倒排索引高效识别垃圾数据相关的知识,希望对你有一定的参考价值。

识别垃圾数据,在一些大数据项目中的ETL清洗时,非常常见,比如通过关键词 
(1)过滤垃圾邮件 
(2)识别yellow网站 
(3)筛选海量简历招聘信息 
(4)智能机器人问答测试 
........ 
各个公司的业务规则都不一样,那么识别的算法和算法也不一样,这里提供一种思路,来高效快速的根据关键词规则识别垃圾数据。 

下面看下需求: 

业务定义一些主关键词若干少则几百个,多则几千个上万个,例如: 

Java代码  

  1. 公司  

  2. 机车厂  

  3. 化纤厂  

  4. 建设局  

  5. 实业集团  

  6. 中心店  

  7. 桑拿中心  

  8. 托管中心  


然后又定义一些辅助关键词若干: 

Java代码  Lucene暴走之巧用内存倒排索引高效识别垃圾数据

  1. 原告  

  2. 被告  

  3. 委托代理人  

  4. 当事人  

  5. 申请人  

  6. 上诉人  



ok,关键词有了,下面看下业务规则 , 规定如下: 

任意辅助关键词组合主关键词都命中的情况下,并且词组间距不大于20者,即为合法数据。 

嗯,没听懂?,那么来看个例子,一段文本如下: 

Java代码  Lucene暴走之巧用内存倒排索引高效识别垃圾数据

  1. 上诉人北京金建出租汽车有限公司因机动车x通事故责任纠纷一案  



使用IK细粒度分词后可能是这样的: 

Java代码  Lucene暴走之巧用内存倒排索引高效识别垃圾数据

  1. 上诉人|上诉|人|北京|金|建出|出租汽车|出租|汽车|有限公司|有限|有|限|公司|因|机动车|机动|车|x通事故|x通|通事|事故责任|事故|责任|纠纷|一案|  



根据规则,辅助词库与主词库都命中,而且中间的词组间距不超过20的,为合法数据, 
本例子中: 
辅助关键词:上诉人 
    主关键词:  公司 
都出现,中间词组是12个,所以符合业务规则,即为合法数据, 

假设,改变原来的文本的公司为集团,再次测试: 

Java代码  Lucene暴走之巧用内存倒排索引高效识别垃圾数据

  1. 上诉人北京金建出租汽车有限集团因机动车x通事故责任纠纷一案  


使用IK细粒度分词后可能是这样的: 

Java代码  

  1. 上诉人|上诉|人|北京|金|建出|出租汽车|出租|汽车|有限集团|有限|有|限|集团|因|机动车|机动|车|x通事故|x通|通事|事故责任|事故|责任|纠纷|一案|  



这次因为辅助关键词库命中了,但是主关键词库没有命中,所以会被当成垃圾数据。 

上面是帮助理解业务的一个例子,下面再分析下,性能问题,假设主关键词有500个,辅助关键词有10个,那么任意 
两两组合的可能就是500*10=5000个规则条件,也就是意味着需要最坏情况下,需要匹配5000次才能识别一篇垃圾数据,当然如果你参与识别垃圾的文本不是一个字段,而是二个字段,一个是标题,一个是内容,那么最后真正的匹配次数是5000*2=10000词匹配,如果再加上距离条件,那么查询的复杂度将会大幅度增加,这个时候,如果我们使用正则匹配 
效率可想而知,使用正则每次全文扫描定位,耗时非常之慢,这时候我们假设有一种快捷的hash算法,来提升性能,毫无疑问,类似的倒排索引将会是解决这种问题的神器。 

因为只需要构建一次临时索引,不落地磁盘,不与IO打交道,仅仅在内存和cpu之间参与计算匹配,而且规则方式非常灵活,可以有更多的规则制定进来,特别是关键词匹配这块,lucene索引非常完美的解决了这个问题。当然如此这种计算,非常耗CPU,对内存的占用不是非常高,因为一条数据,处理完之后,他占用的资源,会被释放。 

在线情况下:平均几十毫秒左右就能识别一条数据,已经接近实时了 

离线情况下:在集成到hadoop或者Spark这种分布式的集群里面,也是非常给力的,因为通常情况下spark和hadoop比较耗IO和磁盘而加入这种运算将会大大提升集群的资源使用效率。 

本项目只是给出了一个根据关键词识别的例子,这个项目拿到你们本地也许并不能立刻使用,但是相似的业务,但是它提供了一种思路,大部分情况下,改动少许代码,即可适应大部分类似的业务。 
核心代码如下: 


Java代码  

  1. package com.anytrust.algo;  

  2.   

  3. import com.anytrust.model.MonitorType;  

  4. import com.anytrust.tools.DictTools;  

  5. import org.apache.lucene.index.memory.MemoryIndex;  

  6. import org.apache.lucene.queryparser.classic.QueryParser;  

  7. import org.apache.lucene.search.BooleanQuery;  

  8. import org.slf4j.Logger;  

  9. import org.slf4j.LoggerFactory;  

  10. import org.wltea.analyzer.lucene.IKAnalyzer;  

  11.   

  12. /** 

  13.  * Created by qindongliang on 2016/1/7. 

  14.  * 根据规则识别是否为垃圾数据 

  15.  */  

  16. public class CheckOneAlgo {  

  17.   

  18.     //IK中文分词器  

  19.       IKAnalyzer analyzer=new IKAnalyzer(false);  

  20.     //内存索引处理  

  21.       MemoryIndex index = new MemoryIndex();  

  22.   

  23.     static {  

  24.         //设置Lucene的boolean query条件数最大支持个数  

  25.         BooleanQuery.setMaxClauseCount(10000);  

  26.     }  

  27.   

  28.   

  29.   

  30.     static Logger logger= LoggerFactory.getLogger(CheckOneAlgo.class);  

  31.   

  32.   

  33.     /**构建查询query 

  34.      * @param type  根据类型构建 

  35.      * */  

  36.     private   String buildQuery(MonitorType type){  

  37.   

  38.         StringBuffer sb =new StringBuffer("(");  

  39.         for(String kw: DictTools.main_kws){//遍历主词库  

  40.             switch (type) {  

  41.                 case LITIGATION://代表文书  0105  

  42.                     for (String hkw : DictTools.assist_kws) { //遍历辅助词库  

  43.                         sb.append("tc:\"").append(hkw + kw).append("\"~20  ");  

  44.                     }  

  45.                     break;  

  46.                 case ANNOUNCEMENT://公告 0104  

  47.                     sb.append("tc:\"").append(kw).append("\"  ");  

  48.                     break;  

  49.                 default:  

  50.                     logger.error("未知类型:{}",type);  

  51.                     break;  

  52.   

  53.             }  

  54.         }  

  55.         sb.append(" ) ");  

  56.         return  sb.toString();  

  57.     }  

  58.   

  59.   

  60.   

  61.     /*** 

  62.      *  对一段文本执行垃圾数据识别功能 

  63.      *  返回true说明是有效数据 

  64.      *  返回false说明是垃圾数据 

  65.       * @param text 监测的文本 

  66.      * @return 

  67.      */  

  68.   public   boolean checkDoc(String text,MonitorType type){  

  69.         String query=buildQuery(type);  

  70.         QueryParser parser = new QueryParser("", analyzer);  

  71.         index.addField("tc", text, analyzer);  

  72.       try {  

  73.           float score = index.search(parser.parse(query));  

  74.           if(score > 0.0f){  

  75.             return true;//正确数据  

  76.           }else{  

  77.             return false;//垃圾数据  

  78.           }  

  79.   

  80.       }catch (Exception e){  

  81.           logger.error("识别垃圾数据异常!",e);  

  82.       }finally {  

  83.           index.reset();//重置index引擎,服复用类对象  

  84.       }  

  85.       return false;  

  86.   }  

  87.   

  88.   

  89.   

  90.   

  91.   

  92.   

  93.   

  94. }  


以上是关于Lucene暴走之巧用内存倒排索引高效识别垃圾数据的主要内容,如果未能解决你的问题,请参考以下文章

Lucene高性能索引之道

elasticsearch - Lucene分片内部逻辑

倒排索引,正排索引与lucene

番外篇:Lucene索引流程与倒排索引实现

全文搜索Lucene之倒排索引

全文搜索Lucene之倒排索引