2013年5月23日 星期四

[ Tools ] CRF - Condition Random Field Toolkit in Java 使用入門


來源自 這裡
Preface:
因為我的專案有用到 CRF (Conditional random fields), 因此就去拜 Google 大神找找看有沒有好用的 Toolkit. 如果你是 C 的專家, 那你可以考慮 Wapiti , 或者是 CRF++. 這邊要介紹的是使用 Java 實作 CRF 的另一個選擇. 它是由 Prof. Sunita Sarawagi 開發並且可以在 這裡 得到更多該工具的使用說明與介紹.

有關 CRF 的介紹這邊我也不多說, 有興趣的就去看看 wiki, 而它一個很常見的運用便是在句子 POS (Part of speech) 的偵測: 當給你一連串的 word 的 sequence, 你能否根據 training 的 model 來預測每一個 word 的詞性 (名詞, 動詞 或是 形容詞 etc). 因此在使用這個工具時首先便需要知道如何將 sequence 的資料餵到工具裡, 並進行 training 然後 testing.

底下將一步步從 1) 資料的格式與餵入 2) Training 3) Testing 從新手的角度告訴你如何使用該工具.

Data Preparation and feeding:
因為我們輸入的資料需要是 sequence (序列) 的, 因此在該工具中提供了介面 DataSequence 讓我們來定義我們的資料格式並由它負責來解析輸入的資料. 這邊我們使用下面的訓練資料:
# R->Rail; S->Sun; C->Cloud
# 1->Play; 0->Stay Home;

R:0-S:1-S:1-S:0-C:0-R:0
S:0-R:0-R:0-S:1-S:0-C:0-C:0-C:1
C:0-C:1-C:0-S:0-S:1-C:1-R:0-R:0-S:1
R:0-S:1-S:0-C:1-C:0-R:0-R:0-R:1
R:1-C:0-C:0-S:1-S:0-S:0-C:1-R:0-C:0-R:0-R:0-C:1
R:0-R:0-C:0-S:1-S:0-S:0-S:1-C:0-C:0-C:1
S:1-R:0-R:0-S:1-S:0-C:0-R:0-R:0
R:0-R:0-R:0-C:1-S:0-C:1-S:1-C:0-C:0-R:0
C:0-S:1-S:0-S:0-C:1-R:0-R:0-R:0-C:1-S:1
R:0-R:0-C:0-S:1-S:0-S:0-S:1-C:0-C:0-C:1
S:0-S:1-C:0-C:0-R:0-R:0-C:0-C:1-C:0-S:1

每一行就是一個 sequence, 而 "S:0" 說明欲檢視的輸入資料為 "S" (Suny) 而對應該資料的 Label 為 "0" (Stay Home); 而每一個序列中的輸入資料都是以 "-" 當作分隔符. 而這邊我們建立類別DDataSequence 來實作介面 DataSequence 以讀入訓練或測試資料:
  1. package demo;  
  2.   
  3. import iitb.CRF.DataSequence;  
  4.   
  5. import java.util.ArrayList;  
  6. import java.util.List;  
  7.   
  8. public class DDataSequence implements DataSequence{  
  9.     private static final long serialVersionUID = 1L;  
  10.     public int                  miss = 0;  
  11.     private List         labelList; // List to keep label  
  12.     private List      tokenList; // List to keep token  
  13.   
  14.     public DDataSequence(){}  
  15.     public DDataSequence(String line){loadData(line);}  
  16.       
  17.     @Override  
  18.     public int length() {  
  19.         return labelList.size();  
  20.     }  
  21.   
  22.     @Override  
  23.     public void set_y(int idx, int value) {  
  24.         labelList.set(idx, value);        
  25.     }  
  26.   
  27.     @Override  
  28.     public Object x(int idx) {  
  29.         return tokenList.get(idx);  
  30.     }  
  31.   
  32.     @Override  
  33.     public int y(int idx) {  
  34.         return labelList.get(idx);  
  35.     }  
  36.   
  37.     public int size(){return length();}  
  38.       
  39.     /** 
  40.      * BD: We define how to load in data from file. 
  41.      * @param line: File path 
  42.      * @return 
  43.      */  
  44.     public boolean loadData(String line)  
  45.     {         
  46.         labelList = new ArrayList();  
  47.         tokenList = new ArrayList();  
  48.         String pairs[] = line.split("-");  
  49.         try  
  50.         {  
  51.             for(String pair:pairs)  
  52.             {  
  53.                 String ps[] = pair.split(":");  
  54.                 if(ps.length==2)  
  55.                 {  
  56.                     tokenList.add(ps[0]);  
  57.                     labelList.add(Integer.valueOf(ps[1]));  
  58.                 }  
  59.                 else {  
  60.                     tokenList.add(ps[0]);  
  61.                     labelList.add(-1);  
  62.                 }                 
  63.             }  
  64.         }  
  65.         catch(Exception e){e.printStackTrace(); return false;}  
  66.         return true;  
  67.     }  
  68. }  
接著多個 Sequence 可以包裝在介面 DataIter 裡面 (可以把一個 sequence 看做一個 training 或 testing 的 instance), 這邊我定義類別 DDataIter 來實作該介面:
  1. package demo;  
  2.   
  3. import java.util.List;  
  4. import iitb.CRF.DataIter;  
  5. import iitb.CRF.DataSequence;  
  6.   
  7. public class DDataIter implements DataIter{  
  8.     public List datas = null;  
  9.     public int pos = 0;  
  10.   
  11.     public DDataIter(List datas){this.datas = datas;}  
  12.       
  13.     @Override  
  14.     public boolean hasNext() {  
  15.         return pos < datas.size();  
  16.     }  
  17.   
  18.     @Override  
  19.     public DataSequence next() {  
  20.         return datas.get(pos++);  
  21.     }  
  22.   
  23.     @Override  
  24.     public void startScan() {  
  25.         pos = 0;          
  26.     }  
  27.   
  28.     public int size(){return datas.size();}  
  29. }  
到目前為止我們已經能讀入測試與訓練資料, 接著要處理的便是如何訓練與測試模型了

Handling Model:
同樣的資料, 你可能有多種模型, 但是模型的訓練與測試流程卻是一樣的, 因此下面我定義一個抽象類別 AbstModel 並使用它來實作我們要的模型:
  1. package demo.proto;  
  2.   
  3. import iitb.CRF.DataSequence;  
  4.   
  5. import java.io.BufferedReader;  
  6. import java.io.File;  
  7. import java.io.FileReader;  
  8. import java.util.HashMap;  
  9.   
  10. public abstract class AbstModel {  
  11.     public int          nlabels=5;  /*Number of label*/  
  12.       
  13.     /** 
  14.      * BD : Allocate default feature. 
  15.      */  
  16.     public abstract void allocFeat();  
  17.     /** 
  18.      * BD : Allocate feature as param 'feat' with weight as param 'wt'. 
  19.      * @param feat 
  20.      * @param wt 
  21.      */  
  22.     public abstract void allocFeat(IFeature feat, double wt);  
  23.     /** 
  24.      * BD : Save trained statistic data into file with path as param 'fn'. 
  25.      * @param fn 
  26.      * @throws Exception 
  27.      */  
  28.     public abstract void saveStatisticData(String fn) throws Exception;  
  29.     /** 
  30.      * BD : Load trained statistic data from binary file with path as param 'filename'. 
  31.      * @param filename 
  32.      * @throws Exception 
  33.      */  
  34.     public abstract void loadStatisticData(String filename) throws Exception;  
  35.     /** 
  36.      * BD : Predict label for input sequence as param 'seq'. 
  37.      * @param seq 
  38.      * @return 
  39.      */  
  40.     public abstract int[] predictSeq(DataSequence seq);  
  41.       
  42.     /** 
  43.      * BD : Predict label for input sequence as param 'seq'. 
  44.      * @param seq 
  45.      * @return 
  46.      */  
  47.     public abstract int[] predictSeq(DataSequence seq, PI pi);  
  48.       
  49.     /** 
  50.      * BD : Train the model. Before doing this, please call API parseRawdata(). 
  51.      * @throws Exception 
  52.      */  
  53.     public abstract void train() throws Exception;  
  54.     /** 
  55.      * BD : Load in the sequence data from file with path as param 'fn'. 
  56.      * @param fn 
  57.      */  
  58.     public abstract void parseRawdata(String fn);  
  59.       
  60.     /** 
  61.      * BD : Tagged file indicated by "testDataPath" and output to file indicated by "outputPath". 
  62.      * @param testDataPath 
  63.      * @param outputPath 
  64.      * @throws Exception 
  65.      */  
  66.     public abstract PI validate(String testDataPath, String outputPath) throws Exception;  
  67.       
  68.     /** 
  69.      * BD : Performance Index 
  70.      * @author John 
  71.      */  
  72.     public class PI /*Performance Index*/  
  73.     {  
  74.         public HashMap> missTagStat = new HashMap>();  
  75.         public int hit = 0;  
  76.         public int miss = 0;  
  77.         public int s_hit = 0;  
  78.         public int s_miss = 0;  
  79.         public int size(){return hit+miss;}  
  80.         public int ssize(){return s_hit+s_miss;}  
  81.         public void hit(){hit++;}  
  82.         public void shit(){s_hit++;}  
  83.         public void miss(){miss++;}  
  84.         public void smiss(){s_miss++;}  
  85.         public float hitRate() {return ((float)hit)/(hit+miss);}  
  86.         public float shitRate() {return ((float)s_hit)/(s_hit+s_miss);}  
  87.         public float missRate() {return 1-hitRate();}  
  88.         public float smissRate() {return 1-shitRate();}  
  89.         public void miss(int answer, int tagged){  
  90.             miss++;  
  91.             if(missTagStat.containsKey(answer)) {  
  92.                 HashMap mtagged = missTagStat.get(answer);  
  93.                 if(mtagged.containsKey(tagged)) mtagged.put(tagged, mtagged.get(tagged)+1);  
  94.                 else mtagged.put(tagged, 1);  
  95.                 missTagStat.put(answer, mtagged);  
  96.             }  
  97.             else  
  98.             {  
  99.                 HashMap mtagged = new HashMap();  
  100.                 mtagged.put(tagged, 1);  
  101.                 missTagStat.put(answer, mtagged);  
  102.             }  
  103.         }  
  104.     }         
  105. }  
而一個實作的範例類別 DModel 如下:
  1. package demo;  
  2.   
  3. import iitb.CRF.CRF;  
  4. import iitb.CRF.DataSequence;  
  5. import iitb.Model.FeatureGenImpl;  
  6. import iitb.Utils.Options;  
  7.   
  8. import java.io.BufferedReader;  
  9. import java.io.BufferedWriter;  
  10. import java.io.File;  
  11. import java.io.FileReader;  
  12. import java.io.FileWriter;  
  13. import java.util.HashMap;  
  14. import java.util.LinkedList;  
  15. import java.util.List;  
  16. import demo.proto.AbstModel;  
  17. import demo.proto.IFeature;  
  18. import flib.util.TimeStr;  
  19.   
  20. public class DModel extends AbstModel{  
  21.     String              modelGraphType = "naive";  // "semi-markov" or "naive"  
  22.     FeatureGenImpl      featureGen;  
  23.     CRF                 crfModel;     
  24.     Options             options;  
  25.     DDataIter               dataIter = null;  
  26.     public String       baseDir = "IRProj";  
  27.     public String       outDir = "Test";  
  28.   
  29.     public DModel(Options o){this.options = o;};  
  30.     public DModel()  
  31.     {  
  32.         this(new Options());  
  33.     }  
  34.       
  35.     public void  allocModel() throws Exception {  
  36.         featureGen = new FeatureGenImpl(modelGraphType, nlabels);  
  37.         crfModel=new CRF(featureGen.numStates(),featureGen,options);  
  38.     }  
  39.       
  40.     public void saveModel(String savePath) throws Exception  
  41.     {  
  42.         File bfdr = new File(savePath);  
  43.         if(!bfdr.exists()) bfdr.mkdirs();  
  44.         crfModel.write(new File(bfdr, "crf.txt").getAbsolutePath());  
  45.         featureGen.write(new File(bfdr, "features.txt").getAbsolutePath());  
  46.     }  
  47.       
  48.     public void loadModel(String path) throws Exception  
  49.     {  
  50.         allocModel();  
  51.         File bfdr = new File(path);         
  52.         crfModel.read(new File(bfdr, "crf.txt").getAbsolutePath());  
  53.         featureGen.read(new File(bfdr, "features.txt").getAbsolutePath());  
  54.     }  
  55.   
  56.     public void test(String testDataPath, String outputPath) throws Exception{        
  57.         doTest(testDataPath, outputPath);  
  58.     }  
  59.       
  60.     protected void outputTaggedData(BufferedWriter bw, DataSequence seq) throws Exception{  
  61.         if(seq.length()>0)  
  62.         {  
  63.             bw.append(String.format("%s:%d", seq.x(0), seq.y(0)));  
  64.             for(int i=1; i" %s:%d", seq.x(i), seq.y(i)));  
  65.             bw.append("\r\n");  
  66.         }  
  67.     }  
  68.       
  69.     public void doTest(String testDataPath, String outputPath) throws Exception  
  70.     {  
  71.         BufferedWriter bw = new BufferedWriter(new FileWriter(new File(outputPath)));  
  72.         parseTestData(testDataPath);  
  73.         DataSequence seq = null;  
  74.         int labels[] = null;  
  75.         while(dataIter.hasNext())  
  76.         {  
  77.             seq = dataIter.next();  
  78.             labels = predictSeq(seq);  
  79.             outputTaggedData(bw, seq);  
  80.         }  
  81.         bw.close();  
  82.     }  
  83.   
  84.       
  85.     public void parseTestData(String fn)  
  86.     {  
  87.         List trainReds = new LinkedList();  
  88.         try  
  89.         {  
  90.             File trainData = new File(fn);  
  91.             BufferedReader br = new BufferedReader(new FileReader(trainData));  
  92.             String line = null;  
  93.             DDataSequence seq = null;  
  94.             while((line=br.readLine())!=null)  
  95.             {  
  96.                 if(line.trim().isEmpty()||line.trim().startsWith("#")) continue;  
  97.                 System.out.printf("\t[Test] Read line=%s\n", line);               
  98.                 seq = new DDataSequence();                
  99.                 seq.loadData(line);  
  100.                 if(seq.length()>0) trainReds.add(seq);  
  101.             }  
  102.               
  103.             br.close();  
  104.             dataIter = new DDataIter(trainReds);  
  105.             System.out.printf("\t[Test] Total %d sequence data...\n", dataIter.size());  
  106.         }  
  107.         catch(Exception e)  
  108.         {  
  109.             e.printStackTrace();  
  110.         }  
  111.     }  
  112.   
  113.     @Override  
  114.     public void allocFeat() {  
  115.         // TODO Auto-generated method stub  
  116.           
  117.     }  
  118.   
  119.     @Override  
  120.     public void allocFeat(IFeature feat, double wt) {  
  121.         // TODO Auto-generated method stub  
  122.           
  123.     }  
  124.   
  125.     @Override  
  126.     public void saveStatisticData(String fn) throws Exception {  
  127.         // TODO Auto-generated method stub  
  128.           
  129.     }  
  130.   
  131.     @Override  
  132.     public void loadStatisticData(String filename) throws Exception {  
  133.         // TODO Auto-generated method stub  
  134.           
  135.     }  
  136.   
  137.     @Override  
  138.     public int[] predictSeq(DataSequence seq) {  
  139.         int labels[] = new int[seq.length()];  
  140.         crfModel.apply(seq);  
  141.         featureGen.mapStatesToLabels(seq);  
  142.         for(int i=0; i
  143.         return labels;  
  144.     }  
  145.       
  146.     @Override  
  147.     public int[] predictSeq(DataSequence seq, PI pi)  
  148.     {  
  149.         int labels[] = new int[seq.length()];  
  150.         for(int i=0; i
  151.         crfModel.apply(seq);  
  152.         featureGen.mapStatesToLabels(seq);  
  153.         for(int i=0; i
  154.         {  
  155.             if(labels[i] != seq.y(i))  
  156.             {  
  157.                 pi.miss(); labels[i] = seq.y(i);  
  158.                 if(i+1==seq.length()) pi.smiss();  
  159.             }  
  160.             else  
  161.             {  
  162.                 pi.hit();  
  163.                 if(i+1==seq.length()) pi.shit();  
  164.             }  
  165.         }  
  166.         return labels;  
  167.     }  
  168.   
  169.     @Override  
  170.     public void train() throws Exception {  
  171.         long st = System.currentTimeMillis();  
  172.         allocModel();  
  173.         featureGen.train(dataIter);  
  174.         double featureWts[] = crfModel.train(dataIter);  
  175.         System.out.printf("\t[Test] Training done...\n");          
  176.         System.out.printf("\t[Test] Total spending time : %s...\n", TimeStr.toStringFrom(st));  
  177.           
  178.     }  
  179.   
  180.     @Override  
  181.     public void parseRawdata(String fn) {  
  182.         parseTestData(fn);        
  183.     }  
  184.   
  185.     @Override  
  186.     public PI validate(String testDataPath, String outputPath) throws Exception {  
  187.         BufferedReader br = new BufferedReader(new FileReader(new File(testDataPath)));  
  188.         BufferedWriter bw = new BufferedWriter(new FileWriter(new File(outputPath)));  
  189.           
  190.         PI pi = new PI();  
  191.         String line = null;  
  192.         DDataSequence seq = null;  
  193.         int seqCnt = 0;  
  194.         while((line=br.readLine())!=null)  
  195.         {  
  196.             seq = new DDataSequence(line);  
  197.             if(seq.size()>0)  
  198.             {                 
  199.                 predictSeq(seq, pi);  
  200.                 outputTaggedData(bw, seq);  
  201.                 seqCnt++;  
  202.             }  
  203.         }  
  204.         bw.close();  
  205.         br.close();  
  206.         System.out.printf("\t[CRF-Mode] Total processing seq=%d...\n", seqCnt);  
  207.         return pi;  
  208.     }  
  209. }  
你可以使用屬性 modelGraphType = "naive" 來決定你要使用的模型. 更多支援的模型可以參考 這裡

Training process:
接著下面的範例代碼使用模型類別 DModel 來對輸入訓練資料 "data/wtraining_data.txt" 進行模型的訓練:
  1. String trainData = "data/wtraining_data.txt";         
  2. String outPath = "data/model";  
  3. /*Training model*/  
  4. DModel model = new DModel();  
  5. model.nlabels = 100;  
  6. model.parseTestData(trainData);  
  7. model.train();  
  8. model.saveModel(outPath);  
訓練完後會在 "data/model" 目錄下面產生檔案:
* features.txt : 訓練產生的 features.
* crf.txt : 對應 features 的 weighting.

Testing/Prediction Process:
當你使用上面的步驟產生模型的檔案後, 你便可以使用下面範例代碼載入模型檔案並對測試資料 "data/wtest_data.txt" 進行 Label 的預測並導出結果到 "data/wtag_data.txt":
- "data/wtest_data.txt"
R:0-R:0-C:0-S:1-S:0-C:1-R:0
S:1-C:1-C:0-R:0-R:0-R:0-C:1-C:0-S:1
C:1-C:0-C:0-R:0-S:1-S:1-R:0-C:0-C:1
C:1-R:0-R:0-C:0-R:0-S:1-S:0-C:1-R:0-R:0
S:0-S:1-C:1-C:0-R:0-R:1-R:0-R:0-S:1-C:1
S:1-C:1-C:0-R:0-C:0-S:1-S:0-R:0-R:0-C:1

  1. String testData= "data/wtest_data.txt";  
  2. String tagData = "data/wtag_data.txt";  
  3. String outPath = "data/model";  
  4. /*Training model*/  
  5. DModel model = new DModel();  
  6. model.nlabels = 100;  
  7.   
  8. model.loadModel(outPath);  
  9. PI pi = model.validate(testData, tagData);  
  10. System.out.printf("\t[Test] Hit rate=%.02f; Miss rate=%.02f\n", pi.hitRate(), pi.missRate());  
  11.   
  12. System.out.printf("\t[Info] Done!\n");  
輸出結果如下:
[CRF-Mode] Total processing seq=6...
[Test] Hit rate=0.71; Miss rate=0.29
[Info] Done!
This message was edited 18 times. Last update was at 24/05/2013 14:39:35

14 則留言:

  1. 完整的代码有吗,这个代码都无法运行

    回覆刪除
    回覆
    1. 因為 html 編碼問題, '<' 與 '>' 被移除了. 範例原始碼可以到下面下載:
      https://www.space.ntu.edu.tw/navigate/s/ED9DF0F0ECB94A319F99370D9E7EF407QQY

      但是該專案不包含 CRF library, 所以還需要下在 CRF library 並設定好 classpath.

      刪除
    2. 請問是否可以跟您索取範例原始碼? 謝謝

      刪除
    3. 請到下面連結下載:
      https://www.space.ntu.edu.tw/navigate/s/B8B7F8B96CAF4E5F83FD0464E144924FQQY

      刪除
    4. 你好,

      上面的連結失效了,

      可以提供其他下載連接嗎?

      謝謝!

      刪除
    5. Try below link:
      https://www.space.ntu.edu.tw/navigate/s/FB617C7566A545E48B9E3906285D7311QQY

      刪除
  2. 我读了下您的代码,但是没有发现做特征选择的函数,即feature template的过程, public void allocFeat() {
    // TODO Auto-generated method stub

    } 应该是这个函数,但是为空。默认您的程序中用的是什么特征?

    回覆刪除
    回覆
    1. 如果你有自定義的 feature, 你可以參考 http://crf.sourceforge.net/introduction/features.html
      在我的專案中, 我有需要針對 hostname 進行 sequence 的 probability 計算並影響產生的 label.
      至於在範例代碼中, 我預設就是使用 CRF 的內建 behavior, 故沒有提供任何 feature 的實作...

      刪除
  3. 您好,首先感谢您的回复,也原谅我一直在问你问题。昨天跟踪了下code,我想理清楚这个流程:就是你做了feature type的定义,然后如何根据训练语料对feature做扩展的过程。我只用了默认的featuretype-StartFeture.java,查看一下data目录生成的features.txt文件,发现只有两个特征
    S.:3:1 5
    S.:1:0 0
    我对FeatureImpl.java文件做了System.out.println()
    public String getInfo(){
    return "this feature index is:"+index()+";"+"this feature y label is:"+y()+";this feature prev label is"+
    yprev()+";this feature value(si)is:"+value()+";this feature identifier is :"+identifier()+
    "this feature de "+
    "------end!";
    有点不解的是:因为普通的特征函数一般是这样的形式---f(s,i,li,li-1),但是扩展出来的特征里无法看出 本身的位置信息,即 s,i信息,能否给与解释?谢谢。
    有聊天工具吗?msn或者gtalk或者qq?

    回覆刪除
    回覆
    1. 這個工具跟一般 CRF toolkit 比較不同的是 feature 必須自己撰寫類別或是使用既有的 feature class EdgeFeatures, StartFeatures etc. 如果你不一定要使用 java 的話, 可以考慮其它 CRF 工具如 CRF++:
      http://crfpp.googlecode.com/svn/trunk/doc/index.html?source=navbar

      相信你知道 CRF 是 sequential undirected PGM, 因此在 CRF++ 有提供你所謂的 feature template, 這樣你就可以透過這個檔案告訴工具到底你的 feature 是 1gm, 2gm ~ n-gm 來進行 training. 而這樣對一般使用這來說負擔較輕也避免了需要去寫 code 來針對特殊的 feature 實作.

      FYI

      刪除
  4. 恩,crf理论我是知道些的。我觉得问题所在应该是FeatureIdentifier.java文件,譬如如下形式
    S.:8:1 17
    空格之前的标示“S.:8:1",那个8 应该是FeatureIdentifier class的id字段
    1是stateId字段,
    您能解释下这两个field的意思吗?
    如下
    public class FeatureIdentifier implements Cloneable, Serializable {
    /**
    *
    */
    private static final long serialVersionUID = 4942036159933407311L;
    public int id;

    public int stateId;

    回覆刪除
  5. 你好,有些問題想問:

    "當給你一連串的 word 的 sequence, 你能否根據 training 的 model 來預測每一個 word 的詞性 (名詞, 動詞 或是 形容詞 etc)."

    那請問如果要對一個序列進行預測,但是前幾項都是已知的, 也可用CRF來預測後幾項的詞性嗎?
    舉個例子,如果我想要猜測後兩項的話,該怎麼做呢?
    ex: S:0-S:1-R:?-R:?






    回覆刪除
    回覆
    1. 第一個問題是 Yes. Check:
      - CRF++: http://crfpp.googlecode.com/svn/trunk/doc/index.html
      - The Application of CRFs in Part-of-Speech Tagging: http://dl.acm.org/citation.cfm?id=1678996.1680436&coll=DL&dl=GUIDE&CFID=460077774&CFTOKEN=40566679

      第二個問題比較像是最佳路徑的問題, 我不知道有沒有現有的 CRF 工具可以針對有部分解答的問題進行 Tagging, 如果真要這麼做可以考慮 HMM 或是 MEMM, 它們可以做到根據前面的資訊回答下一步的最佳解答.
      - HMM - http://en.wikipedia.org/wiki/Hidden_Markov_model
      - MEMM - http://en.wikipedia.org/wiki/Maximum-entropy_Markov_model

      FYI

      刪除

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...