锁定老帖子 主题:全文检索中近义词、关联词的解决方案
该帖已经被评为良好帖
|
|
---|---|
作者 | 正文 |
发表时间:2008-12-23
最后修改:2008-12-23
一直想找到一个好的同义词解决方案,在百度和google查找,大家对于这个问题都只是寥寥数语,不愿讲清,我在javaeye搜此类信息也求不到,后来发了个提问贴也只有浏览数而无回复,不知道这是什么原因,无奈之下我只有自己研究。 因为没有其它的解决方案可以借鉴,以下纯为我个人的见解。
我认为所谓近义词、关联词检索不外乎以下三种形式: 1.类似google suggest,用户输入关键字后自动提示功能。 2.假如“奥运会”的关联词是“北京”,用户输入“奥运会”搜索时,将“奥运会”的搜索结果以及“北京”的搜索结果都搜出来。 3.用户输入“奥运会”搜索,只显示“奥运会”的结果,它的关联词在结果集的底端用“相关搜索”的形式提示给用户。
这三种方式中看似2和3是类似的,其实还是有些很大区别的,我个人认为在实际的应用中,第3种方式是最多见的。
先说说第1种方式 google suggest的在网上可以搜到完整的解决方案,跟我此前想的一样。 建一个用来存储关键字的表 例如: create table KEYS( ITEM_ID varcha2(50) not null, SEARCH_KEY varchar2(100), ); SEARCH_KEY里存储一些检索的关键字,如“奥运”,“奥运会”,“北京奥运会”等. 当用户在文本框敲入时,使用AJAX将当前文本框的内容发到Action,进行下面类似的操作 String key = request.getParameter("inputValue"); String sql = "select * from KEYS as k where k.SEARCH_KEY like '%key%'"; 这个办法也就是把当前文本框中的字放到数据库中做模糊查询,比如用户输入“奥”这个字,会从搜出所有包含“奥”字的词,然后将这些词包装好,发回至页面,页面上用JS画出下拉框,将这些包含“奥”字的词填入就可以了。 google做的是右边匹配,即是用的 like 'key%' ,输入“奥”字,会出现所有以“奥”字开头的词的提示。 大家应该注意到google suggest的提示词的最后面都写有“约XXXXX条记录”,我现在唯一能想到的解决方案是,当我们在Action中拿到匹配的词之后,将匹配的词搜索一次即可得出具体记录的条数,其实这样也不耗多少资源。
第2种方式 将关键词和关键词所联的词的结果都搜出来 我个人认为这个功能在实际的应用中只需用到其5%就可以了,因为在用户体验方面考虑,我们必须保障搜索结果的质量。关键词的近义词可以在结果页的底端提示给用户,也就是我上边说到的第3种方式。 我所说的只需用到5%的意思是只需为少数的、必须转义的词实现这样的功能 比如搜索“China”,那么“中国”这个词的结果肯定得出现在结果集里,还有比如搜索“08”,那么“2008”的结果也得出现在结果集里,这些都是一些特定情况,其它的词没必要做成这样,只需做提示即可。 现在中文检索一般都是基于词库的检索,实现第2种方式这个功能必须将之前的词库以及分词算法进行修改。 一般的方法是在词库中把同义词写成一行,如将“中国 China”写在同一行,然后修改Token算法(这个大家可以去研究一下,我现在使用的Analyzer包是公司商业上的伙伴提供的测试包,这个测试包已经可以满足很大一部分需求了,我还不知道能不能共享) 其实原理是在索引时将原词和原词的近义词一起索引 具体给大家个例子 有这样一句话 ---- “中国是世界上人口最多的国家” 如果没有为其做同义词,大家可能会索引成这样 [中国][世界][人口][最多][国家] 如果做了同义词,会索引成 [中国][China][世界][人口][最多][国家] 即是说在索引时就已经将“中国”这个词存成[中国][China]了,搜索时无论搜“中国”或是“China”都可以搜到这句话。 其实这个功能相当大一部分是依赖第三方的jar包,说来说去也没多大意思,并且大多数情况下我们需要的并不同这种功能,而是更人性化的查询提示的功能,也就是第3种方式。
第3种方式 这种方式是我在原有的系统上改进完成的 原有的系统是 compass + paoding + lucene 由于我不太熟compass的搜索,所以我还是采用的lucene搜索。相信大家对这种搭配的全文检索已经非常熟悉了,paoding的词库是可以自己配置的。 那么怎样在原有的基础实现关键词提示功能呢??? 我的做法是这样的,我按照paoding词库的特点新建了一个mydictionary.dic文件放在classpath下,里面的内容大致如下。 第1行:奥运会 北京 2008 第29届奥运会 第2行:中国 China 中华人民共和国 中国电信 中国人民银行 ........
然后在服务器启动时,将mydictionary.dic这个文件中的文字一行行读入,分别做索引 InputStream fi = this.getClass().getClassLoader().getResourceAsStream("mydictionary.dic"); File indexDir = new File("d:\\tong"); Analyzer luceneAnalyzer = new StandardAnalyzer(); IndexWriter indexWriter = new IndexWriter(indexDir, luceneAnalyzer,true); BufferedReader reader = new BufferedReader(new InputStreamReader(fi, "UTF-8")); String line = new String(); while ((line = reader.readLine()) != null) { Document document = new Document(); Field FieldName = new Field("line", line,Field.Store.YES, Field.Index.TOKENIZED); document.add(FieldName); indexWriter.addDocument(document); } indexWriter.optimize(); indexWriter.close(); reader.close();
在搜索时,对用户输入的关键字正常搜索出结果后,对关键字进行第二次搜索,搜的是同义词的索引,得到的结果按空格分开,即可得到所有同义词,然后将同义词发送至页面即可。 public List getTongYi(String searchword) throws Exception { List list = new ArrayList(); Hits hits = null; String queryString = searchword; Query query = null; String result = ""; IndexSearcher searcher = new IndexSearcher("d:\\tong"); Analyzer analyzer = new StandardAnalyzer(); QueryParser qp = new QueryParser("line", analyzer); query = qp.parse(queryString); if (searcher != null) { hits = searcher.search(query); for(int i=0;i<hits.length();i++) { Document doc = hits.doc(i); System.out.println((i+1)+"."+doc.get("line")); result = doc.get("line"); } } if(result!=null && !result.equals("")) { String [] manyresult = result.split(" "); for(int i=0;i<manyresult.length;i++) { if(manyresult[i]!=null && !manyresult[i].trim().equals("")) { list.add(manyresult[i]); } } } return list; } 我觉得做同义词索引和搜索的时候最好用StandardAnalyzer,切成单字是最符合的。
写了这么多,如果各位有更好的想法不妨发出来一起研究一下。
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2008-12-23
提点意见,以供讨论
可以将搜索关键词及其命中数量,进行索引存储,这样就不走数据库了 另外,相关词/近义词库的积累,可否优先调用通用搜索引擎(比如google)的suggest接口获取并积累呢? |
|
返回顶楼 | |
发表时间:2008-12-23
好像应该用贝叶斯方法的说
|
|
返回顶楼 | |
发表时间:2008-12-23
最后修改:2008-12-23
你提的方案除了3可以尝试外 其他的方案在大数据量下其实是不可行的
一般的做法是: 1. 算。通过一些规则结合自然语言的一些算法算出哪些是重要性相关的,也就是同义的、近意的。 2. 一个好的搜索结构,一般情况下,不使用lucene等等,而是自己实现一个比如trie的紧凑高效的数据结构。 特别是suggestion,suggest的ajax一般是用户输入的时候定时发送请求 一般一个用户输入一个词,会向服务器发送若干个请求,这样的搜索量还是比较大的。数据库肯定顶不住,更别提like了。 这种东西应该是算法密集型的。 |
|
返回顶楼 | |
发表时间:2008-12-23
楼上的回答很精确。
关于trie, 你去看我的blog吧。 paoding的算法完全可以通过我提供的的代码取代掉。 算法密集的应用, 如果仅仅是访问不高的网站, 其实lucence就足够了。lucence做个比较大规模的论坛还是没有问题的, 但是中文分词,相关性比较困难。 不知道你的网站的访问量高不高, 如果很多高的话, 你的办法还真是行不通。 |
|
返回顶楼 | |
发表时间:2008-12-23
lucene提供同义词的处理方法。
|
|
返回顶楼 | |
发表时间:2008-12-23
整个中文search体系是非常复杂的, 如果很简单, 也不知道跑出来几个baidu类似的公司了。 别跟我说其他的几个, 好多都是麒麟操作系统的来源。
|
|
返回顶楼 | |
发表时间:2008-12-23
哪有免费的同义词词典可以下载啊?
|
|
返回顶楼 | |
发表时间:2008-12-24
这个Google suggest每输入几个字,到数据库查询一下,而且还用 ‘like’,这样效率不是很低啊,有没有更好的办法啊
|
|
返回顶楼 | |
发表时间:2008-12-24
其实看你做什么了。
如果做垂直专业类搜索,那么其特点专业用语,简写,拼写准确这些为主,建议人工为主(当然有现有资源那就充分利用) 如果做类似谷歌百度类海量搜索,我觉得必须要考虑分词的智能性,也就是自动识别新鲜词语,靠字典+算法+人工修正来完成,应该能达到比较好的分词。当然说起来容易做起来难。 我认为baidu和google都还在研究怎么才能更加精确,用户在最短时间能快速获得最想要的信息。所以我不相信使用现有任何一种算法就能完成很精确的分词。 好的分词不是几个算法,一个程序就能搞定的,它需要不断研究分析,尤其是中文。 以上为我个人观点,呵呵,有点打击一些人积极性了。 |
|
返回顶楼 | |