1. 程式人生 > >轉:從頭開始編寫基於隱含馬爾可夫模型HMM的中文分詞器

轉:從頭開始編寫基於隱含馬爾可夫模型HMM的中文分詞器

lan reverse single trim 地址 note str rip resources

http://blog.csdn.net/guixunlong/article/details/8925990

從頭開始編寫基於隱含馬爾可夫模型HMM的中文分詞器之一 - 資源篇

首先感謝52nlp的系列博文(http://www.52nlp.cn/),提供了自然語言處理的系列學習文章,讓我學習到了如何實現一個基於隱含馬爾可夫模型HMM的中文分詞器。

在編寫一個中文分詞器前,第一步是需要找到一些基礎的詞典庫等資源,用以訓練模型參數,並進行後續的結果評測,這裏直接轉述52nlp介紹的“中文分詞入門之資源”:

原文地址:http://www.52nlp.cn/%E4%B8%AD%E6%96%87%E5%88%86%E8%AF%8D%E5%85%A5%E9%97%A8%E4%B9%8B%E8%B5%84%E6%BA%90

這裏我從http://sighan.cs.uchicago.edu/bakeoff2005/下載icwb2-data.zip詞庫做模型訓練與後續評測使用。

作為中文信息處理的“橋頭堡”,中文分詞在國內的關註度似乎遠遠超過了自然語言處理的其他研究領域。在中文分詞中,資源的重要性又不言而喻,最大匹配法等需要一個好的詞表,而基於字標註的中文分詞方法又需要人工加工好的分詞語料庫。所以想研究中文分詞,第一步需要解決的就是資源問題,這裏曾經介紹過“LDC上免費的中文信息處理資源”,其中包括一個有頻率統計的詞表,共計44405條,就可以作為一個不錯的中文分詞詞表使用。而一個好的人工分詞語料庫,需要很大的人力物力投入,所以無論研究還是商用往往需要一定的費用購買,好在SIGHAN Bakeoff為我們提供了一個非商業使用(non-commercial)的免費獲取途徑,以下將介紹SIGHAN Bakeoff及相關的中文分詞入門資源。
  SIGHAN是國際計算語言學會(ACL)中文語言處理小組的簡稱,其英文全稱為“Special Interest Group for Chinese Language Processing of the Association for Computational Linguistics”,又可以理解為“SIG漢“或“SIG漢“。而Bakeoff則是SIGHAN所主辦的國際中文語言處理競賽,第一屆於2003年在日本劄幌舉行(Bakeoff 2003),第二屆於2005年在韓國濟州島舉行(Bakeoff 2005), 而2006年在悉尼舉行的第三屆(Bakeoff 2006)則在前兩屆的基礎上加入了中文命名實體識別評測。目前SIGHAN Bakeoff已成功舉辦了6屆,其中Bakeoff 2005的數據和結果在其主頁上是完全免費和公開的,但是請註意使用的前提是非商業使用(non-commercial):

  The data and results for the 2nd International Chinese Word Segmentation Bakeoff are now available for non-commercial use.

  在Bakeoff 2005的主頁上,我們可以找到如下一行:“The complete training, testing, and gold-standard data sets, as well as the scoring script, are available for research use”,在這一行下面提供了三個版本的icwb2-data。下載解壓後,通過README就可以很清楚的了解到它包含哪些中文分詞資源,特別需要說明的是這些中文分詞語料庫分別由臺灣中央研究院(Academia Sinica)、香港城市大學(City University of Hong Kong)、北京大學(Peking University)及微軟亞洲研究院(Microsoft Research)提供,其中前二者是繁體中文,後二者是簡體中文,以下按照README簡要介紹icwb2-data:

1) 介紹(Introduction):
  本目錄包含了訓練集、測試集及測試集的(黃金)標準切分,同時也包括了一個用於評分的腳本和一個可以作為基線測試的簡單中文分詞器。(This directory contains the training, test, and gold-standard data used in the 2nd International Chinese Word Segmentation Bakeoff. Also included is the script used to score the results submitted by the bakeoff participants and the simple segmenter used to generate the baseline and topline data.)

2) 文件列表(File List)
  在gold目錄裏包含了測試集標準切分及從訓練集中抽取的詞表(Contains the gold standard segmentation of the test data along with the training data word lists.)
  在scripts目錄裏包含了評分腳本和簡單中文分詞器(Contains the scoring script and simple segmenter.)
  在testing目錄裏包含了未切分的測試數據(Contains the unsegmented test data.)
  在training目錄裏包含了已經切分好的標準訓練數據(Contains the segmented training data.)
  在doc目錄裏包括了bakeoff的一些指南(Contains the instructions used in the bakeoff.)

3) 編碼(Encoding Issues)
  文件包括擴展名”.utf8”則其編碼為UTF-8(Files with the extension “.utf8″ are encoded in UTF-8 Unicode.)
  文件包括擴展名”.txt”則其編碼分別為(Files with the extension “.txt” are encoded as follows):
  前綴為as_,代表的是臺灣中央研究院提供,編碼為Big Five (CP950);
  前綴為hk_,代表的是香港城市大學提供,編碼為Big Five/HKSCS;
  前綴為msr_,代表的是微軟亞洲研究院提供,編碼為 EUC-CN (CP936);
  前綴為pku_,代表的北京大學提供,編碼為EUC-CN (CP936);
  EUC-CN即是GB2312(EUC-CN is often called “GB” or “GB2312″ encoding, though technically GB2312 is a character set, not a character encoding.)

4) 評分(Scoring)
  評分腳本“score”是用來比較兩個分詞文件的,需要三個參數(The script ‘score’ is used to generate compare two segmentations. The script takes three arguments):
  1. 訓練集詞表(The training set word list)
  2. “黃金”標準分詞文件(The gold standard segmentation)
  3. 測試集的切分文件(The segmented test file)
 
  以下利用其自帶的中文分詞工具進行說明。在scripts目錄裏包含一個基於最大匹配法的中文分詞器mwseg.pl,以北京大學提供的人民日報語料庫為例,用法如下:
  ./mwseg.pl ../gold/pku_training_words.txt < ../testing/pku_test.txt > pku_test_seg.txt
  其中第一個參數需提供一個詞表文件pku_training_word.txt,輸入為pku_test.txt,輸出為pku_test_seg.txt。
  利用score評分的命令如下:
  ./score ../gold/pku_training_words.txt ../gold/pku_test_gold.txt pku_test_seg.txt > score.txt
  其中前三個參數已介紹,而score.txt則包含了詳細的評分結果,不僅有總的評分結果,還包括每一句的對比結果。這裏只看最後的總評結果:


= SUMMARY:
=== TOTAL INSERTIONS: 9274
=== TOTAL DELETIONS: 1365
=== TOTAL SUBSTITUTIONS: 8377
=== TOTAL NCHANGE: 19016
=== TOTAL TRUE WORD COUNT: 104372
=== TOTAL TEST WORD COUNT: 112281
=== TOTAL TRUE WORDS RECALL: 0.907
=== TOTAL TEST WORDS PRECISION: 0.843
=== F MEASURE: 0.874
=== OOV Rate: 0.058
=== OOV Recall Rate: 0.069
=== IV Recall Rate: 0.958
### pku_test_seg.txt 9274 1365 8377 19016 104372 112281 0.907 0.843 0.874 0.058 0.069 0.958

  說明這個中文分詞器在北大提供的語料庫上的測試結果是:召回率為90.7%,準確率為84.3%,F值為87.4%等。
  SIGHAN Bakeoff公開資源的一個重要意義在於這裏提供了一個完全公平的平臺,任何人都可以拿自己研究的中文分詞工具進行測評,並且可以和其公布的比賽結果對比,是驢子是馬也就一目了然了。

下面是我自己實現的中文分詞器在msra的測試集上的分詞效果:

[plain] view plain copy
  1. === SUMMARY:
  2. === TOTAL INSERTIONS: 7303
  3. === TOTAL DELETIONS: 4988
  4. === TOTAL SUBSTITUTIONS: 15988
  5. === TOTAL NCHANGE: 28279
  6. === TOTAL TRUE WORD COUNT: 106873
  7. === TOTAL TEST WORD COUNT: 109188
  8. === TOTAL TRUE WORDS RECALL: 0.804
  9. === TOTAL TEST WORDS PRECISION: 0.787
  10. === F MEASURE: 0.795
  11. === OOV Rate: 0.026
  12. === OOV Recall Rate: 0.402
  13. === IV Recall Rate: 0.815
  14. ### msr_test_result.txt 7303 4988 15988 28279 106873 109188 0.804 0.787 0.795 0.026 0.402 0.815

分詞效果如下,個人感覺對於一個花三小時實現的程序來說,效果還不錯 技術分享圖片

[plain] view plain copy
    1. 而/大量/“/看/圖學/計算機/”/教材/的/出現/,/和/蔡誌忠/的/漫畫/諸子/相映/成趣/,/掀起/了/一股/成人/“/看/圖識/字/”/的/出版潮/,/到/後/來竟/說/不清/是/誰/影響/了/誰/。/
    2. words.length=38 timecost:0
    3. 在/日常/生活/中/,/像/“/軟件/”/、/“/硬件/”/、/“/死/循環/”/一類/的/新詞/充實/了/我們/的/語言庫/。/
    4. words.length=74 timecost:0
    5. 而/計算/機造/詞/功能/的/運用/,/形成/了/一種/被/稱為/“/語詞/傳染/”/的/新/現象/:/如果/有人/在/電腦/裏/造出/了/一個/錯誤/的/詞/,/所有/使用/該/電腦/的/人/都/會出/現同/一/錯誤/,/而且/防不/勝防/。/
    6. words.length=35 timecost:0
    7. 至於/計算機/的/使用/對/學生/書法/、/筆順/、/發音/的/影響/,/更/是/教育/學家/關心/的/話題/。/
    8. words.length=56 timecost:0
    9. 再往/遠些/看/,/隨著/漢字識別/和/語音識別技術/的/發展/,/中文/計算/機用/戶/將/跨越/語言/差異/的/鴻溝/,/在/錄入/上/走向/中西/文求/同/的/道路/。/
    10. words.length=35 timecost:0
    11. 而/計算機/翻譯/系統/與/現代/漢語/分析/相輔/相成/,/正/推動/著/人工/智能/科學/的/前進/…/…/
    12. words.length=68 timecost:0
    13. 每/跨過/一道/障礙/,/人們/都會/發現/一片/新/的/開闊/地/,/每/經過/一次/劇烈/的/碰撞/,/都/將/激起/一團/耀眼/的/火花/,/這/就/是/嶄新/的/計算機/技術/和/古老/的/中華/文化/的/對話/。/

分詞效果如下,個人感覺對於一個花三小時實現的程序來說,效果還不錯 技術分享圖片

http://blog.csdn.net/guixunlong/article/details/8926167

從頭開始編寫基於隱含馬爾可夫模型HMM的中文分詞器之二 - 模型訓練與使用

我們使用/icwb2-data.rar/training/msr_training.utf8 用以訓練模型,這個詞庫裏包含已分詞匯約2000000個。

使用經典的字符標註模型,首先需要確定標註集,在前面的介紹中,我們使用的是{B,E}的二元集合,研究表明基於四類標簽的字符標註模型明顯優於兩類標簽,原因是兩類標簽過於簡單而損失了部分信息。四類標簽的集合是 {B,E,M,S},其含義如下:
B:一個詞的開始
E:一個詞的結束
M:一個詞的中間
S:單字成詞
舉例:你S現B在E應B該E去S幼B兒M園E了S

用四類標簽為msr_training.utf8做好標記後,就可以開始用統計的方法構建一個HMM。我們打算構建一個2-gram(bigram)語言模型,也即一個1階HMM,每個字符的標簽分類只受前一個字符分類的影響。現在,我們需要求得HMM的狀態轉移矩陣 A 以及混合矩陣 B。其中:
Aij = P(Cj|Ci) = P(Ci,Cj) / P(Ci) = Count(Ci,Cj) / Count(Ci)
Bij = P(Oj|Ci) = P(Oj,Ci) / P(Ci) = Count(Oj,Ci) / Count(Ci)
公式中C = {B,E,M,S},O = {字符集合},Count代表頻率。在計算Bij時,由於數據的稀疏性,很多字符未出現在訓練集中,這導致概率為0的結果出現在B中,為了修補這個問題,我們采用加1的數據平滑技術,即:
Bij = P(Oj|Ci) = (Count(Oj,Ci) + 1)/ Count(Ci)
這並不是一種最好的處理技術,因為這有可能低估或高估真實概率,更加科學的方法是使用復雜一點的Good—Turing技術,這項技術的的原始版本是圖靈當年和他的助手Good在破解德國密碼機時發明的。

我完成的分詞器暫時沒有用這個技術,只是簡單的為沒出現的詞賦一個很小的值來實現簡單的情況模擬,後續會嘗試使用Good-Turing技術,試下效果怎麽樣。

求得的PI跟矩陣A如下:

[java] view plain copy
  1. private static double[] PI = new double[] {0.529918835192331, 0.0, 0.0, 0.470081164807669}; //B, M, E, S
  2. private static double[][] A = new double[][] {
  3. {0.0, 0.17142595344427136, 0.8285740465557286, 0.0}, //B
  4. {0.0, 0.49616531193870117, 0.5038346880612988, 0.0}, //M
  5. {0.4741680643477776, 0.0, 0.0, 0.5258319356522224}, //E
  6. {0.5927662448712697, 0.0, 0.0, 0.4072328569272888} //S
  7. };

相關訓練代碼:

[java] view plain copy
  1. public static void buildPiAndMatrixA() {
  2. /**
  3. * count matrix:
  4. * ALL B M E S
  5. * B * * * * *
  6. * M * * * * *
  7. * E * * * * *
  8. * S * * * * *
  9. *
  10. * NOTE:
  11. * count[2][0] is the total number of complex words
  12. * count[3][0] is the total number of single words
  13. */
  14. long[][] count = new long[4][5];
  15. try {
  16. BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream("icwb2-data/training/msr_training.utf8"),"UTF-8"));
  17. String line = null;
  18. String last = null;
  19. while ((line = br.readLine()) != null) {
  20. String[] words = line.split(" ");
  21. for (int i=0; i<words.length; i++) {
  22. String word = words[i].trim();
  23. int length = word.length();
  24. if (length < 1)
  25. continue;
  26. if (length == 1) {
  27. count[3][0]++;
  28. if (last != null) {
  29. if (last.length() == 1)
  30. count[3][4]++;
  31. else
  32. count[2][4]++;
  33. }
  34. } else {
  35. count[2][0]++;
  36. count[0][0]++;
  37. if (length > 2) {
  38. count[1][0] += length-2;
  39. count[0][2]++;
  40. if (length-2 > 1) {
  41. count[1][2] += length-3;
  42. }
  43. count[1][3]++;
  44. } else {
  45. count[0][3]++;
  46. }
  47. if (last != null) {
  48. if (last.length() == 1) {
  49. count[3][1]++;
  50. } else {
  51. count[2][1]++;
  52. }
  53. }
  54. }
  55. last = word;
  56. }
  57. //System.out.println("Finish " + words.length + " words ...");
  58. }
  59. } catch (FileNotFoundException e) {
  60. e.printStackTrace();
  61. } catch (UnsupportedEncodingException e) {
  62. e.printStackTrace();
  63. } catch (IOException e) {
  64. e.printStackTrace();
  65. }
  66. for (int i=0; i<count.length; i++)
  67. System.out.println(Arrays.toString(count[i]));
  68. System.out.println(" ===== So Pi array is: ===== ");
  69. double[] pi = new double[4];
  70. long allWordCount = count[2][0] + count[3][0];
  71. pi[0] = (double)count[2][0] / allWordCount;
  72. pi[3] = (double)count[3][0] / allWordCount;
  73. System.out.println(Arrays.toString(pi));
  74. System.out.println(" ===== And A matrix is: ===== ");
  75. double[][] A = new double[4][4];
  76. for (int i=0; i<A.length; i++)
  77. for (int j=0; j<A[i].length; j++)
  78. A[i][j] = (double)count[i][j+1]/ count[i][0];
  79. for (int i=0; i<A.length; i++)
  80. System.out.println(Arrays.toString(A[i]));
  81. }

矩陣中出現的概率為0的元素表明B-B, B-S, M-B, M-S, E-M, E-E, S-M, S-E這8種組合是不可能出現的。這是合乎邏輯的。

矩陣B內容比較大,格式如下:

[plain] view plain copy
  1. 1.0 3.174041419653506E-6 0.0016687522763828306 1.0633038755839245E-4 4.7610621294802586E-6
  2. 1.0 2.313791818894887E-6 5.738203710859319E-4 4.8589628196792625E-5 2.313791818894887E-6
  3. 1.0 7.935103549133765E-7 0.005299062150111528 6.348082839307012E-6 7.935103549133765E-7
  4. 1.0 1.7881026800082968E-6 0.005061224635763484 4.470256700020742E-6 8.940513400041484E-7

矩陣B訓練代碼:

[java] view plain copy
  1. public static void buildMatrixB(String charMapFile, String charMapCharset, String matrixBFileName) {
  2. /**
  3. * Chinese Character count => 5167
  4. *
  5. * count matrix:
  6. * ALL C1 C2 C3 CN C5168
  7. * B * * * * * 1/ALL+5168
  8. * M * * * * * 1/ALL+5168
  9. * E * * * * * 1/ALL+5168
  10. * S * * * * * 1/ALL+5168
  11. *
  12. * NOTE:
  13. * count[0][0] is the total number of begin count
  14. * count[0][0] is the total number of middle count
  15. * count[2][0] is the total number of end count
  16. * count[3][0] is the total number of single cound
  17. *
  18. * B row -> 4
  19. * B col -> 5169
  20. */
  21. long[][] matrixBCount = new long[4][5169];
  22. for (int row = 0; row < matrixBCount.length; row++) {
  23. Arrays.fill(matrixBCount[row], 1);
  24. matrixBCount[row][0] = 5168;
  25. }
  26. Map<Character, Integer> dict = new HashMap<Character, Integer>();
  27. Utils.readDict(charMapFile, charMapCharset, dict, null);
  28. try {
  29. BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream("icwb2-data/training/msr_training.utf8"),"UTF-8"));
  30. String line = null;
  31. while ((line = br.readLine()) != null) {
  32. String[] words = line.split(" ");
  33. for (int i=0; i<words.length; i++) {
  34. String word = words[i].trim();
  35. if (word.length() < 1)
  36. continue;
  37. if (word.length() == 1) {
  38. int index = dict.get(word.charAt(0));
  39. matrixBCount[3][0]++;
  40. matrixBCount[3][index]++;
  41. } else {
  42. for (int j=0; j<word.length(); j++) {
  43. int index = dict.get(word.charAt(j));
  44. if (j == 0) {
  45. matrixBCount[0][0]++;
  46. matrixBCount[0][index]++;
  47. } else if (j == word.length()-1) {
  48. matrixBCount[2][0]++;
  49. matrixBCount[2][index]++;
  50. } else {
  51. matrixBCount[1][0]++;
  52. matrixBCount[1][index]++;
  53. }
  54. }
  55. }
  56. }
  57. }
  58. } catch (FileNotFoundException e) {
  59. e.printStackTrace();
  60. } catch (UnsupportedEncodingException e) {
  61. e.printStackTrace();
  62. } catch (IOException e) {
  63. e.printStackTrace();
  64. }
  65. System.out.println(" ===== matrixBCount ===== ");
  66. for (int i=0; i<matrixBCount.length; i++)
  67. System.out.println(Arrays.toString(matrixBCount[i]));
  68. System.out.println(" ========= B matrix =========");
  69. double[][] B = new double[matrixBCount.length][matrixBCount[0].length];
  70. for (int row = 0; row < B.length; row++) {
  71. for (int col = 0; col < B[row].length; col++) {
  72. B[row][col] = (double) matrixBCount[row][col] / matrixBCount[row][0];
  73. if (col < 50) {
  74. System.out.print(B[row][col] + " ");
  75. }
  76. }
  77. System.out.println("");
  78. }
  79. try {
  80. PrintWriter bOut = new PrintWriter(new File(matrixBFileName));
  81. for (int row = 0; row < B.length; row++) {
  82. for (int col = 0; col < B[row].length; col++) {
  83. bOut.print(B[row][col] + " ");
  84. }
  85. bOut.println("");
  86. bOut.flush();
  87. }
  88. bOut.close();
  89. System.out.println("Finish write B to file " + matrixBFileName);
  90. } catch (FileNotFoundException e) {
  91. e.printStackTrace();
  92. }
  93. }

有了矩陣PI,A跟B,我們就可以寫入一個觀察序列,用隱含馬爾可夫模型跟Viterbi算法獲得一個隱藏序列(分詞結果)了。如果你對隱含馬爾可夫模型還有什麽疑問,請參考52nlp的博文:

http://www.52nlp.cn/hmm-learn-best-practices-one-introduction

以下是我用Java自己實現的Viterbi算法,有一點需要註意的是,Java的double能表示最小的值約為1E-350,如果一個文本串很長,例如大於200個字符,算得的結果值很有可能會小於double的最小值,而由於精度問題變為0,這樣最終的計算結果就失去意義了。當然,如果能保證輸入串的長度比較短,可以不care這個,但為了程序的健壯性,我這裏在計算某一列的結果值小於1E-250時,將停止使用double而改用Java提供的高精度類BigDecimal,雖然計算速度會比double慢很多(尤其隨著串越來越長),但總比變為0,結果失去意義要強一些。但即便是這樣,這個函數也期望輸入串不長於200,否則就有可能在1S內得不到最終計算結果。

[java] view plain copy
  1. public static String Viterbi(double[] PI, double[][] A, double[][] B, int[] sentences) {
  2. StringBuilder ret = new StringBuilder();
  3. double[][] matrix = new double[PI.length][sentences.length];
  4. int[][] past = new int[PI.length][sentences.length];
  5. int supplementStartColumn = -1;
  6. BigDecimal[][] supplement = null; //new BigDecimal[][];
  7. for (int row=0; row<matrix.length; row++)
  8. matrix[row][0] = PI[row] * B[row][sentences[0]];
  9. for (int col=1; col<sentences.length; col++) {
  10. if (supplementStartColumn > -1) { //Use supplement BigDecimal matrix
  11. for (int row=0; row<matrix.length; row++) {
  12. BigDecimal max = new BigDecimal(0d);
  13. int last = -1;
  14. for (int r=0; r<matrix.length; r++) {
  15. BigDecimal value = supplement[r][col-1-supplementStartColumn].multiply(new BigDecimal(A[r][row])).multiply(new BigDecimal(B[row][sentences[col]]));
  16. if (value.compareTo(max) > 0) {
  17. max = value;
  18. last = r;
  19. }
  20. }
  21. supplement[row][col-supplementStartColumn] = max;
  22. past[row][col] = last;
  23. }
  24. } else {
  25. boolean switchSupplement = false;
  26. for (int row=0; row<matrix.length; row++) {
  27. double max = 0;
  28. int last = -1;
  29. for (int r=0; r<matrix.length; r++) {
  30. double value = matrix[r][col-1] * A[r][row] * B[row][sentences[col]];
  31. if (value > max) {
  32. max = value;
  33. last = r;
  34. }
  35. }
  36. matrix[row][col] = max;
  37. past[row][col] = last;
  38. if (max < 1E-250)
  39. switchSupplement = true;
  40. }
  41. //Really small data, should switch to supplement BigDecimal matrix now, or we will loose accuracy soon
  42. if (switchSupplement) {
  43. supplementStartColumn = col;
  44. supplement = new BigDecimal[PI.length][sentences.length - supplementStartColumn];
  45. for (int row=0; row<matrix.length; row++) {
  46. supplement[row][col - supplementStartColumn] = new BigDecimal(matrix[row][col]);
  47. }
  48. }
  49. }
  50. }
  51. int index = -1;
  52. if (supplementStartColumn > -1) {
  53. BigDecimal max = new BigDecimal(0d);
  54. int column = supplement[0].length-1;
  55. for (int row=0; row<supplement.length; row++) {
  56. if (supplement[row][column].compareTo(max) > 0) {
  57. max = supplement[row][column];
  58. index = row;
  59. }
  60. }
  61. } else {
  62. double max = 0;
  63. for (int row=0; row<matrix.length; row++)
  64. if (matrix[row][sentences.length-1] > max) {
  65. max = matrix[row][sentences.length-1];
  66. index = row;
  67. }
  68. }
  69. /*for (int i=0; i<matrix.length; i++)
  70. System.out.println(Arrays.toString(matrix[i]));*/
  71. ret.append(getDesc(index));
  72. for (int col=sentences.length-1; col>=1; col--) {
  73. index = past[index][col];
  74. ret.append(getDesc(index));
  75. }
  76. return ret.reverse().toString();
  77. }

測試一下,對如下串進行分詞:

[java] view plain copy
  1. String str2 = "這並不是一種最好的處理技術,因為這有可能低估或高估真實概率,更加科學的方法是使用復雜一點的Good—Turing技術,這項技術的原始版本是圖靈當年和他的助手Good在破解德國密碼機時發明的。";

結果為:

[java] view plain copy
    1. Switch to BigDecimal at length = 79
    2. words.length=95 timecost:63
    3. 這/並/不是/一種/最好/的/處理/技術/,/因為/這有/可能/低估/或/高估/真實/概率/,/更加/科學/的/方法/是/使用/復雜/一點/的/Good—Turing技術/,/這項/技術/的/原始/版本/是/圖靈/當年/和/他/的/助手/Good/在/破解/德國/密碼/機時/發明/的/。/
    4. timeCost: 63

轉:從頭開始編寫基於隱含馬爾可夫模型HMM的中文分詞器