搜索絕對不只僅是搭起框架,跑出結果就完成的工做,以後分詞、排序等等的優化纔是重頭戲。框架
先交代下背景:這個搜索是我一我的負責搭建並優化的項目,主要索引對象爲歌曲、歌手MV等等。函數
使用技術:Lucene、IK_Analyzer優化
既然這篇博客是關於中文分詞的優化,那麼先看我如今的搜索有什麼問題存在:spa
分詞不許確 code
(1)若是搜索"沒有你陪伴"時,排序在前面的歌曲爲"陪伴",而本應排第一的"沒有你陪伴真的好孤單"這首歌卻在後面幾頁(由於沒有"沒有你陪伴"這首歌)。 對象
(2)再好比搜索"阿哥阿妹不分手"這首歌時,並不能搜出"阿哥阿妹不分離"這首歌。 blog
(3)若是搜索"沒有你陪伴真的好孤單",那麼包含"陪伴"、"沒有"、"你"等詞的歌曲都會被顯示,致使顯示結果過多(由於沒有你陪伴真的好孤單自己就是一個歌名)。 排序
如今最主要的問題就是這三類。那麼就針對這三類開始優化吧: 索引
關於分詞的問題,由於使用的是Ik分詞器,自己自帶詞庫,可是對於個人搜索來講,絕大多數搜索是針對歌曲和歌手進行搜索,那麼很天然的想到IK的擴展詞庫:ext.dic 能夠簡單的將全部歌曲和歌手添加進擴展詞庫,而且使用IK的最大細粒度進行切分,這樣當用戶搜索完整的歌曲名和歌手名時,能夠獲得最準確的分詞。 博客
這裏須要注意,由於不少歌曲名帶有字符和其它語言,好比上述提到的 《 和 日語英文夾雜的狀況,若是不經處理直接將"母親日劇《排球女將》插曲"插入擴展詞,其實並無什麼卵用,爲何呢? 由於基本沒有用戶會完整的在搜索框中輸入" 母親日劇《排球女將》插曲",更多的用戶在查找這首歌時,會輸入"母親"、"日劇插曲"、"排球女將"或"排球女將插曲"等等
注意到一個共同點,中國用戶通常不會去搜索中文與符號夾雜、中文與英文夾雜(分狀況,在搜美劇或英文歌時比較多),通常都是隻搜中文。
那麼很天然的想到去除歌曲名和歌手名中的特殊字符,只留下中文加入到擴展詞中(等因而用英文或字符將標題分割成幾個獨立的詞加入到擴展詞庫中):
如:"日劇《排球女將》插曲"就拆分紅:"日劇"、"排球女將"和"插曲"加入到擴展詞庫中。
代碼以下:
public Set<String> extWordProcesse(String extWord) { if(extWord == null) return null; char[] array = extWord.toCharArray(); Set<String> result = new HashSet<String>(); String subString; Integer start = 0; while(start < array.length) { for(int i=start;i<array.length;i++) { if(!String.valueOf(array[i]).matches("[\\u4E00-\\u9FA5]+")) { if(start < i) { subString = extWord.substring(start, i); if(subString.length() != 1) result.add(subString); start = i + 1; } else if(start == i) { start++; } break; } else { if(i == array.length - 1) { subString = extWord.substring(start, i + 1); if(subString.length() != 1) result.add(subString); start = i + 1; } } } } return result; }
下面是添加擴展詞先後分詞的對比(使用最大粒度分詞):用"沒有你孤單真的好孤獨"做爲例子
加入擴展詞前:
0-2 : 沒有 : CN_WORD 2-3 : 你 : CN_WORD 3-5 : 陪伴 : CN_WORD 5-7 : 真的 : CN_WORD 7-10 : 好孤單 : CN_WORD
加入擴展詞後使用最大粒度分詞:
0-10 : 沒有你陪伴真的好孤單 : CN_WORD
那麼若是用戶輸入搜索"沒有你陪伴真的好孤單"時,由於有徹底一致的歌曲名與關鍵詞匹配,那麼只須要返回徹底匹配的記錄,而像包括"沒有"、"你"、"陪伴"等等的記錄能夠不顯示,由於這個時候用戶的搜索是目的性很是明確的。
這樣經過添加歌曲名和歌手名到擴展詞庫中,能夠解決分詞問題下的(3)小點。
而分詞問題下剩餘的(1)(2)問題能夠歸爲一類,有點相似搜索校訂的感受,(1)是前綴匹配,(2)是拼寫糾錯。
其實這兩個問題均可以經過分詞來解決:
先看下在加入擴展詞後用最大粒度分詞的結果:
0-7 : 阿哥阿妹不分手 : CN_WORD
0-3 : 沒有你 : CN_WORD
3-5 : 陪伴 : CN_WORD
可是上面剛纔提到了,加入擴展詞後用最大粒度分詞,"沒有你陪伴真的好孤單"分詞時將會做爲一個總體,並不會分紅"沒有你","陪伴"。那麼搜索"沒有你陪伴時"並不會命中"沒有你陪伴真的好孤單",那麼怎麼辦呢?
咱們知道Lucene,索引和搜索時,都須要分詞,索引時是分詞後構建倒排索引,搜索時分詞後進行空間向量的計算得分。
那麼咱們能夠索引時不使用最大粒度,而使用最小粒度分詞。
什麼意思呢? 先看實例:
"沒有你陪伴真的好孤單"最小粒度分詞的結果:
0-3 : 沒有你 : CN_WORD 0-2 : 沒有 : CN_WORD 1-5 : 有你陪伴 : CN_WORD 1-4 : 有你陪 : CN_WORD 1-3 : 有你 : CN_WORD 1-2 : 有 : CN_WORD 2-3 : 你 : CN_WORD 3-5 : 陪伴 : CN_WORD
這樣是否是明白不少了? 若是咱們使用最小粒度分詞的話,那麼就能夠解決(1)(2)問題了,可是這樣又不能解決(3)問題了,由於若是索引和搜索時都使用最小粒度分詞,那麼搜索出來的結果會不少,影響用戶體驗和搜索速度。
因此(1)(2)和(3)是對立的,解決其中一個,另一個變會出現,那怎麼辦呢? 最簡單的就是分狀況處理。
咱們能夠看到,若是用戶搜索的一個關鍵詞key,若是徹底匹配歌曲名或者歌手名時,搜索時應該使用最大粒度分詞,以減小不想關的搜索結果,若是不徹底匹配,那麼須要使用最小粒度分詞,用以匹配全部可能的結果。固然,索引永遠是使用最小粒度分詞。
那麼個人作法是,將全部的歌曲名和歌手名通過上述代碼處理後,加入到一個Set中,每次搜索時,對關鍵字在Set中查找,若是存在,那麼說明時徹底匹配歌曲或歌手的,使用最大粒度分詞,若是不存在,那麼不匹配。使用最小粒度匹配。
上述的最大粒度最小粒度,IK分詞器已經提供了相關實現:
在IKAnalyzer的構造函數中有一個boolean參數,true爲最大粒度,false爲最小粒度:
new IKAnalyzer(false)