放牧代码和思想
专注自然语言处理、机器学习算法
    This thing called love. Know I would've. Thrown it all away. Wouldn't hesitate.

全文检索Solr集成HanLP中文分词

目录

以前发布过HanLP的Lucene插件,后来很多人跟我说其实Solr更流行(反正我是觉得既然Solr是Lucene的子项目,那么稍微改改配置就能支持Solr),于是就抽空做了个Solr插件出来,开源在Github上,欢迎改进。

HanLP中文分词solr插件支持Solr5.x,兼容Lucene5.x。

Fork HanLP 给HanLP点赞 关注HanLP

 

快速上手

  1. hanlp-portable.jarhanlp-solr-plugin.jar共两个jar放入${webapp}/WEB-INF/lib

  2. 修改solr core的配置文件${core}/conf/schema.xml

  <fieldType name="text_cn" class="solr.TextField">
      <analyzer type="index">
          <tokenizer class="com.hankcs.lucene.HanLPTokenizerFactory" enableIndexMode="true"/>
      </analyzer>
      <analyzer type="query">
          <!-- 切记不要在query中开启index模式 -->
          <tokenizer class="com.hankcs.lucene.HanLPTokenizerFactory" enableIndexMode="false"/>
      </analyzer>
  </fieldType>
  <!-- 业务系统中需要分词的字段都需要指定type为text_cn -->
  <field name="my_field1" type="text_cn" indexed="true" stored="true"/>
  <field name="my_field2" type="text_cn" indexed="true" stored="true"/>

Solr5中文分词器详细配置

对于新手来说,上面的两步可能太简略了,不如看看下面的step by step。本教程使用Solr5.2.1,理论上兼容solr5.x。

放置jar

将上述两个jar放到solr-5.2.1/server/solr-webapp/webapp/WEB-INF/lib目录下。如果你想自定义词典等数据,将hanlp.properties放到solr-5.2.1/server/resources,该目录也是log4j.properties等配置文件的放置位置。HanLP文档一直在说“将配置文件放到resources目录下”,指的就是这个意思。作为Java程序员,这是基本常识。

启动solr

首先在solr-5.2.1\bin目录下启动solr:

solr start -f

用浏览器打开http://localhost:8983/solr/#/,看到如下页面说明一切正常:

创建core

在solr-5.2.1\server\solr下新建一个目录,取个名字比如叫one,将示例配置文件solr-5.2.1\server\solr\configsets\sample_techproducts_configs\conf拷贝过来,接着修改schema.xml中的默认域type,搜索

    <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
        ...
    </fieldType>

替换为

    <!-- 默认文本类型: 指定使用HanLP分词器,同时开启索引模式。
	 通过solr自带的停用词过滤器,使用"stopwords.txt"(默认空白)过滤。
	 在搜索的时候,还支持solr自带的同义词词典。-->
    <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
      <analyzer type="index">
        <tokenizer class="com.hankcs.lucene.HanLPTokenizerFactory" enableIndexMode="true"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
        <!-- 取消注释可以启用索引期间的同义词词典
        <filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
        -->
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
      <analyzer type="query">
        <tokenizer class="com.hankcs.lucene.HanLPTokenizerFactory" enableIndexMode="true"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>

意思是默认文本字段类型启用HanLP分词器,text_general还开启了solr默认的各种filter。

solr允许为不同的字段指定不同的分词器,由于绝大部分字段都是text_general类型的,可以说这种做法比较适合新手。如果你是solr老手的话,你可能会更喜欢单独为不同的字段指定不同的分词器及其他配置。如果你的业务系统中有其他字段,比如location,summary之类,也需要一一指定其type="text_general"。切记,否则这些字段仍旧是solr默认分词器,会造成这些字段“搜索不到”。

另外,切记不要在query中开启indexMode,否则会影响PhaseQuery。indexMode只需在index中开启一遍即可,要不然它怎么叫indexMode呢。

如果你不需要solr提供的停用词、同义词等filter,如下配置可能更适合你:

  <fieldType name="text_cn" class="solr.TextField">
      <analyzer type="index">
          <tokenizer class="com.hankcs.lucene.HanLPTokenizerFactory" enableIndexMode="true"/>
      </analyzer>
      <analyzer type="query">
          <!-- 切记不要在query中开启index模式 -->
          <tokenizer class="com.hankcs.lucene.HanLPTokenizerFactory" enableIndexMode="false"/>
      </analyzer>
  </fieldType>
  <!-- 业务系统中需要分词的字段都需要指定type为text_cn -->
  <field name="my_field1" type="text_cn" indexed="true" stored="true"/>
  <field name="my_field2" type="text_cn" indexed="true" stored="true"/>

完成了之后在solr的管理界面导入这个core one:

接着就能在下拉列表中看到这个core了:

上传测试文档

修改好了,就可以拿一些测试文档来试试效果了。hanlp-solr-plugin代码库中的src/test/resources下有个测试文档集合documents.csv,其内容如下:

id,title
1,你好世界
2,商品和服务
3,和服的价格是每镑15便士
4,服务大众
5,hanlp工作正常

代表着id从1到5共五个文档,接下来复制solr-5.2.1\example\exampledocs下的上传工具post.jar到resources目录,利用如下命令行将数据导入:

java -Dc=one -Dtype=application/csv -jar post.jar *.csv

Windows用户的话直接双击该目录下的upload.cmd即可,Linux用户运行upload.sh。

正常情况下输出如下结果:

SimplePostTool version 5.0.0
Posting files to [base] url http://localhost:8983/solr/one/update using content-
type application/csv...
POSTing file documents.csv to [base]
1 files indexed.
COMMITting Solr index changes to http://localhost:8983/solr/one/update...
Time spent: 0:00:00.059
请按任意键继续. . .

同时刷新一下core one的Overview,的确看到了5篇文档:

搜索文档

是时候看看HanLP分词的效果了,点击左侧面板的Query,输入“和服”试试:

发现精确地查到了“和服的价格是每镑15便士”,而不是“商品和服务”这种错误文档:

这说明HanLP工作良好。

要知道,不少中文分词器眉毛胡子一把抓地命中“商品和服务”这种错误文档,降低了查准率,拉低了用户体验,跟原始的MySQL LIKE有何区别?

索引模式的功能

索引模式可以对长词进行全切分,得到其中蕴含的所有词汇。比如“中医药大学附属医院”在HanLP索引分词模式下的切分结果为:

中0 医1 药2 大3 学4 附5 属6 医7 院8 
[0:3 1] 中医药/n
[0:2 1] 中医/n
[1:3 1] 医药/n
[3:5 1] 大学/n
[5:9 1] 附属医院/nt
[5:7 1] 附属/vn
[7:9 1] 医院/n

开启indexMode后,无论用户搜索“中医”“中医药”还是“医药”,都会搜索到“中医药大学附属医院”:

2017-04-26_14-34-37.png

高级配置

目前本插件支持如下基于schema.xml的配置:

配置项名称 功能 默认值
enableIndexMode 设为索引模式 true
enableCustomDictionary 是否启用用户词典 true
customDictionaryPath 用户词典路径(绝对路径或程序可以读取的相对路径,多个词典用空格隔开) null
stopWordDictionaryPath 停用词词典路径 null
enableNumberQuantifierRecognize 是否启用数词和数量词识别 true
enableNameRecognize 开启人名识别 true
enableTranslatedNameRecognize 是否启用音译人名识别 false
enableJapaneseNameRecognize 是否启用日本人名识别 false
enableOrganizationRecognize 开启机构名识别 false
enablePlaceRecognize 开启地名识别 false
enableNormalization 是否执行字符正规化(繁体->简体,全角->半角,大写->小写) false
enableTraditionalChineseMode 开启精准繁体中文分词 false

对于更高级的配置,HanLP分词器主要通过class path下的hanlp.properties进行配置,请阅读HanLP自然语言处理包文档以了解更多相关配置,如:

  1. 停用词

  2. 用户词典

  3. 词性标注

  4. ……

代码调用

在Query改写的时候,可以利用HanLPAnalyzer分词结果中的词性等属性,如

String text = "中华人民共和国很辽阔";
for (int i = 0; i < text.length(); ++i)
{
    System.out.print(text.charAt(i) + "" + i + " ");
}
System.out.println();
Analyzer analyzer = new HanLPAnalyzer();
TokenStream tokenStream = analyzer.tokenStream("field", text);
tokenStream.reset();
while (tokenStream.incrementToken())
{
    CharTermAttribute attribute = tokenStream.getAttribute(CharTermAttribute.class);
    // 偏移量
    OffsetAttribute offsetAtt = tokenStream.getAttribute(OffsetAttribute.class);
    // 距离
    PositionIncrementAttribute positionAttr = kenStream.getAttribute(PositionIncrementAttribute.class);
    // 词性
    TypeAttribute typeAttr = tokenStream.getAttribute(TypeAttribute.class);
    System.out.printf("[%d:%d %d] %s/%s\n", offsetAtt.startOffset(), offsetAtt.endOffset(), positionAttr.getPositionIncrement(), attribute, typeAttr.type());
}

在另一些场景,支持以自定义的分词器(比如开启了命名实体识别的分词器、繁体中文分词器、CRF分词器等)构造HanLPTokenizer,比如:

tokenizer = new HanLPTokenizer(HanLP.newSegment()
                    .enableJapaneseNameRecognize(true)
                    .enableIndexMode(true), null, false);
tokenizer.setReader(new StringReader("林志玲亮相网友:确定不是波多野结衣?"));
...

反馈

技术问题请在Github上发issue ,大家一起讨论,也方便集中管理。博客留言、微博私信、邮件不受理任何HanLP相关的问题,谢谢合作!

反馈问题的时候请一定附上版本号触发代码、输入输出否则无法处理

版权

Apache License Version 2.0

知识共享许可协议 知识共享署名-非商业性使用-相同方式共享码农场 » 全文检索Solr集成HanLP中文分词

我的作品

HanLP自然语言处理包《自然语言处理入门》