2012年5月17日 星期四

全文檢索、資料採擷、推薦引擎系列3---全文內容推薦引擎之中文分詞


基於內容的推薦引擎有兩種實現途徑,一種是根據條目的中繼資料(可以將中繼資料理解為屬性),另一種是根據條目的文本描述資訊。本系列中將先描述基於條目描述資訊的全文檢索實現方式,然後描述基於中繼資料的內容推薦引擎實現方式。
對於基於條目文本描述資訊的內容推薦引擎,目前有很多資料可以參考,基本步聚是先對文本內容進行分詞,包括提取出單詞、去掉常用詞如的地得、加入同意詞、對英語還有去掉複數形式和過去分詞形式等;第二步是計算各個詞在每篇文章中的出現頻率,以及在所有文章中的出現頻率,即TF/IDF;第三步計算文章向量;最後是利用自動聚類演算法,對條目進行聚類,這樣就可以實現向使用者推薦同類產品的需求了。
但是在這裡有一個非常重要的問題沒有解決,就是中文分詞的問題,這些文章中絕大部分都是以英文為背景的,而英文分詞方面,分出單詞很簡單,只需要空格作為分隔符號就可以了,而中文中詞與詞之間沒有空格,其次是英文中單複數、過去分詞等比較多,需要還原成單數現在式,但是中文中這個問題基本不存在,再有就是英文需要在分詞後識別長的片語,而中文這一步也不需進行。
針對以上這些難題,在我的專案中,採用了MMSeg4j中文分詞模組,這個專案集成了據說是搜狗輸入法的10萬多詞庫(大家知道中文分詞的關鍵是中文詞庫)。
另外,我還希望中文分詞可以在全文檢索引擎和全文內容推薦引擎共用,由於全文檢索引擎採用了Apache Lucene 3.x版本,需要中文分詞模組符合Lucene的體系架構,幸運的是MMSeg4j提供了Lucene所需的Tokenizer實現類,同時還需要重點解決如下問題:
  • 由於打開索引檔比較慢,所以整個程式共用一個indexersearcher
  • 考慮到准即時性需求,採用了Lucene新版本中reopen機制,每次查詢前讀入索引增量
  • 採用Lucene默鎖機制
在專案中我定義了全文檢索引擎類:
public class FteEngine {
    public static void initFteEngine(String _indexPathname) {
        indexPathname = _indexPathname;
    }
    public static FteEngine getInstance() {                         // Singleton模式
        if (null == engine) {
            engine = new FteEngine();
        }
        return engine;
    }
    public IndexWriter getIndexWriter() {
        return writer;
    }
    public IndexSearcher getIndexSearcher() {
        try {
            IndexReader newReader = reader.reopen();     
 // 讀入新增加的增量索引內容,滿足即時索引需求
            if (!reader.equals(newReader)) {
                reader.close();
                reader = newReader;
            }
            searcher = new IndexSearcher(reader);
        } catch (CorruptIndexException e) { ....      
        } catch (IOException e) {....
        }
        return searcher;
    }
    public Analyzer getAnalyzer() {
        return analyzer;
    }
    public void stop() {
        try {
            if (searcher != null) {
                searcher.close();
            }
            reader.close();
            writer.close();
            indexDir.close();
        } catch (IOException e) {....
        }
    }
    private FteEngine() {
        analyzer = new MMSegAnalyzer();                                                                              
 // 初始化中文分詞模組,會讀入中文字典
        IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_31, analyzer);
        iwc.setOpenMode(OpenMode.CREATE_OR_APPEND);
        try {
            indexDir = FSDirectory.open(new File(indexPathname));                   
 
            writer = new IndexWriter(indexDir, iwc);                                          
 // writerreader整個程式共用
            reader = IndexReader.open(writer, true);
        } catch (CorruptIndexException e) {......
        } catch (LockObtainFailedException e) {......
        } catch (IOException e) {.....
        }
    }
    private static FteEngine engine = null;
    private static String indexPathname = null;
    private Directory indexDir = null;
    private IndexWriter writer = null;
    private IndexSearcher searcher = null;
    private Analyzer analyzer = null;
    private IndexReader reader = null;
}
具體中文分詞可以使用如下代碼:
FteEngine fteEngine = FteEngine.getInstance();
        Analyzer analyzer = fteEngine.getAnalyzer();
        String text = "
測試2011年如java有意見 分岐其中華人民共合國,oracle咬死獵人的狗!";
        TokenStream tokenStrm = analyzer.tokenStream("contents", new StringReader(text));
        OffsetAttribute offsetAttr = tokenStrm.getAttribute(OffsetAttribute.class);
        CharTermAttribute charTermAttr = tokenStrm.getAttribute(CharTermAttribute.class);
        String term = null;
        int i = 0;
        int len = 0;
        char[] charBuf = null;
        try {
            while (tokenStrm.incrementToken()) {
                charBuf = charTermAttr.buffer();
                for (i = (charBuf.length - 1); i >= 0; i--) {
                    if (charBuf[i] > 0) {
                        len = i + 1;
                        break;
                    }
                }
                //term = new String(charBuf, offsetAttr.startOffset(), offsetAttr.endOffset());
                term = new String(charBuf, 0, offsetAttr.endOffset() - offsetAttr.startOffset());
                System.out.println(term);   
 
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
列印的內容如下:
測試 2011 java 意見 其中 人民 oracle 獵人
當我們在缺省詞庫中加入單詞:分岐 中華人民共合國後,那麼分詞結果可以變為:
測試 2011 java 意見 分岐 中華人民共合國 oracle 獵人
由此可見,可以通過完善中文詞庫,得到越來越好的中文分詞效果。


沒有留言: