1. 程式人生 > >lucene4.5原始碼分析系列:分析器

lucene4.5原始碼分析系列:分析器

 分析器是lucene中非常重要的一個元件,許多包都是分析器的子包,這是因為分析器需要支援很多不同的語言。

lucene中的分析器

 分析器可能會做的事情有:將文字拆分為單詞,去除標點,將字母變為小寫,去除停用詞,詞幹還原,詞形歸併,敏感詞過濾等等。lucene中預設自帶的分析器有4個:WhitespaceAnalyzer,SimpleAnalyzer,StopAnalyzer, StandardAnalyzer,分別用來過濾空白字元,過濾空白符並自動變小寫,去掉停用詞,標準化分詞。其中,最常用的是StandardAnalyzer。分析器之所以能做這麼多事,與之簡潔而強大的類設計是分不開的。

  事實上,這部分就使用了一個裝飾器模式,但是由此可以做許多事情,因為filter可以不停的新增新功能。下面是類結構圖。


  Analyzer中最重要的就是tokenStream方法,它得到一個TokenStream物件。這個方法最開始從reuseStrategy中取得一個TokenStreamComponent,reuseStrategy是重用的策略,預設有兩個實現GLOBAL_REUSE_STRATEGY和PER_FIELD_REUSE_STRATEGY,前者讓所有field共用一個TokenStreamComponent,後者每個field一個TokenStreamComponent,而實際儲存這些Component的地方是Analyzer中的storedValue。接下來初始化reader並設定到component。最後從component得到TokenStream

  public final TokenStream tokenStream(final String fieldName,
                                       final Reader reader) throws IOException {
    TokenStreamComponents components = reuseStrategy.getReusableComponents(this, fieldName);
    final Reader r = initReader(fieldName, reader);
    if (components == null) {
      components = createComponents(fieldName, r);
      reuseStrategy.setReusableComponents(this, fieldName, components);
    } else {
      components.setReader(r);
    }
    return components.getTokenStream();
  }

  下面是lucene中一些Tokenizer和TokenFilter的列表。無論從名字還是實現都相對簡單,這裡就不過多分析了。


分詞

  接下來來看看分析器的一個比較重要的功能,分詞。所謂分詞,就是為了便於倒排索引的查詢而將一個句子切分為一個一個的term,切分的最好標準就是每個詞儘量符合它在句子中語義,但實際上往往是很難達到的。大家可以試想一下,如果是一句英文的句子,那麼一種簡單的分詞方式可以用split(" "),這樣英文中的每個單詞就是一個term。但是如果是一句中文,我們能夠每個字一個term嗎?如果這樣,那搜素出來的肯定牛頭不對馬嘴。因為漢語中的單詞通常是片語,片語才是組成確定語義的單位。因而如何分詞就是需要注意的問題了。

  關於中文分詞,目前大概有這樣幾種方法:

  1. 基於字串匹配的分詞方法。比較著名的幾個開源專案有IkAnalyzer和paoding。

  這種分詞方法簡單來講就是用待分詞的句子與詞典中的詞進行匹配,找出最合適的匹配。這種方式實現的分詞,優點是快速;但制約因素是詞典本身要好,而且不在詞典中的詞就無法匹配,因為是純粹匹配,碰上歧義時分詞器本身也不認識到底應該識別哪一個,因而消除歧義的能力較差。比如知乎上的這樣一段測試文字,中間有很多歧義的部分:“工信處女幹事每月經過下屬科室都要親口交代24口交換機等技術性器件的安裝工作”。這樣的識別對這類分詞器而言是比較大的挑戰。

  分詞按照方向可以分為正向或者逆向,按照匹配的貪婪程度,可以分為最大匹配,最少次數劃分(有分詞理論),最細粒度匹配。之所以會有這些劃分,是因為基於字串匹配的演算法通常能夠解決一類問題,但是另一類會差一些。而按照這樣的劃分,能夠給予分詞一定的啟發式規則。

  以上面的句子為例,如果隨意劃分,那麼開頭就會是"工信/處女/幹事...",顯然是有問題的。

  按照最大匹配劃分,就是"工信處/女/幹事",但是最大匹配劃分也有問題,matrix67的文中有個例子"北京大學生前來應聘"會被劃分為"北京大學/生前/來/應聘",這也是有問題的。

  所以有逆向的匹配,比如上面這句,就可以匹配成"北京/大學生/前來/應聘"。

  另一種劃分方式,就是最細粒度劃分,還是以最初的歧義句子為例,它可以被最細粒度劃分為"工信/處女/幹事/每月/月經/經過...",可以看到它並沒有消除歧義,只是增加了搜尋到這個句子的詞。

  以上這些方式實際上都是可以舉出反例的,其實就是讓中國人來聽,漢語也只有76%的準確度。

  講講幾個開源專案IkAnalyzer, paoding和mmseg。

  IkAnalyzer是用的正向迭代最細粒度切分演算法以及一個智慧分詞法,目前IkAnalyzer實際上是比較活躍的,作者也經常更新。

  paoding實際上在2010年更新了一版支援lucene3.0之後就銷聲匿跡了。

  mmseg的演算法也挺有意思,它有下面4條啟發式的消除歧義規則:

1)備選詞組合的長度之和最大。
2)備選詞組合的平均詞長最大;
3)備選詞組合的詞長變化最小;
4)備選詞組合中,單字詞的出現頻率統計值最高。

  最開始它從左到右掃描一遍,識別出3個詞的不同組合,然後用上面4條規則匹配出最好的一個片語,然後再次用這4條規則匹配剩下的詞,這樣消歧能力有明顯的提高。參考中有篇文章介紹mmseg,不過居然是96年發表的

  2. 基於統計和機器學習的分詞。比如CRF,HMM(隱式馬爾科夫模型),MEMM(最大熵隱馬模型)。

  這種分詞方式在最初需要提供一個訓練集去喂,這個訓練集就是已經標註好的分詞片語,然後在分詞階段通過構建模型來計算各種可能分法的概率,通過概率來最終判斷。實際上,這是目前公認的比較準確的方式。CRF即條件隨機場(Conditional Random Field),在《數學之美》中已經對其描述過。可以想象,這個訓練集對於分詞的準確性也是非常重要的。CRF比隱馬模型好在,隱馬模型由於簡化,每個觀察值只考慮到了當前狀態,而條件隨機場則是考慮到了前後的狀態。通俗的講,隱馬模型對於現在的輸出也只考慮現在的條件;而CRF對於現在的輸出需要考慮過去,現在和未來的條件。如圖,上面是隱馬模型,對於觀測值x2,只取決於產生的狀態y2;下圖則是條件隨機場,x2不僅取決於y2,還取決於y1以及y3.


  CRF最初是被用來對一個句子做句法成分分析的,比如:我愛地球,通過條件隨機場能判斷出句子成分是:主語/謂語/賓語。而這個判斷的過程是一個分類的過程。如果我們將這個分類換成一個4標記B, S, M, E的方式

  • 詞首,常用B表示
  • 詞中,常用M表示
  • 詞尾,常用E表示
  • 單子詞,常用S表示
  同樣是“我愛地球”,每個字會被分類為"我/S愛/S地/B球/E",從而正確的區分出每個單詞。而這個劃分用的是著名的維特比演算法。維特比演算法是動態規劃演算法,並不特別難。這裡具體就不描述了。可以參考相關資料。

            中文分詞之trie樹 http://blog.csdn.net/wzb56_earl/article/details/7902669 

            數學之美--談談中文分詞