使用libSvm實現文本分類的基本過程,此文參考 使用libsvm實現文本分類 對前期數據准備及后續的分類測試進行了驗證,同時對文中作者的分詞組件修改成hanLP分詞,對數字進行過濾,僅保留長度大於1的詞進行處理。
轉上文作者寫的分類流程:
- 選擇文本訓練數據集和測試數據集:訓練集和測試集都是類標簽已知的;
- 訓練集文本預處理:這里主要包括分詞、去停用詞、建立詞袋模型(倒排表);
- 選擇文本分類使用的特征向量(詞向量):最終的目標是使得最終選出的特征向量在多個類別之間具有一定的類別區分度,可以使用相關有效的技術去實現特征向量的選擇,由於分詞后得到大量的詞,通過選擇降維技術能很好地減少計算量,還能維持分類的精度;
- 輸出libsvm支持的量化的訓練樣本集文件:類別名稱、特征向量中每個詞元素分別到數字編號的映射轉換,以及基於類別和特征向量來量化文本訓練集,能夠滿足使用libsvm訓練所需要的數據格式;
- 測試數據集預處理:同樣包括分詞(需要和訓練過程中使用的分詞器一致)、去停用詞、建立詞袋模型(倒排表),但是這時需要加載訓練過程中生成的特征向量,用特征向量去排除多余的不在特征向量中的詞(也稱為降維);
- 輸出libsvm支持的量化的測試樣本集文件:格式和訓練數據集的預處理階段的輸出相同;
- 使用libsvm訓練文本分類器:使用訓練集預處理階段輸出的量化的數據集文件,這個階段也需要做很多工作(后面會詳細說明),最終輸出分類模型文件;
- 使用libsvm驗證分類模型的精度:使用測試集預處理階段輸出的量化的數據集文件,和分類模型文件來驗證分類的精度;
- 分類模型參數尋優:如果經過libsvm訓練出來的分類模型精度很差,可以通過libsvm自帶的交叉驗證(Cross Validation)功能來實現參數的尋優,通過搜索參數取值空間來獲取最佳的參數值,使分類模型的精度滿足實際分類需要。
文本預處理階段,增加了基於hanLP的分詞,代碼如下:

/** * 使用hanlp進行分詞 * Created by zhouyh on 2018/5/30. */ public class HanLPDocumentAnalyzer extends AbstractDocumentAnalyzer implements DocumentAnalyzer { private static final Log LOG = LogFactory.getLog(HanLPDocumentAnalyzer.class); public HanLPDocumentAnalyzer(ConfigReadable configuration) { super(configuration); } @Override public Map<String, Term> analyze(File file) { String doc = file.getAbsolutePath(); LOG.debug("Process document: file=" + doc); Map<String, Term> terms = Maps.newHashMap(); BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(new FileInputStream(file), charSet)); String line = null; while((line = br.readLine()) != null) { LOG.debug("Process line: " + line); List<com.hankcs.hanlp.seg.common.Term> termList = HanLP.segment(line); if (termList!=null && termList.size()>0){ for (com.hankcs.hanlp.seg.common.Term hanLPTerm : termList){ String word = hanLPTerm.word; if (!word.isEmpty() && !super.isStopword(word)){ if (word.trim().length()>1){ Pattern compile = Pattern.compile("(\\d+\\.\\d+)|(\\d+)|([\\uFF10-\\uFF19]+)"); Matcher matcher = compile.matcher(word); if (!matcher.find()){ Term term = terms.get(word); if (term == null){ term = new TermImpl(word); terms.put(word, term); } term.incrFreq(); } } } else { LOG.debug("Filter out stop word: file=" + file + ", word=" + word); } } } } } catch (IOException e) { throw new RuntimeException("", e); } finally { try { if(br != null) { br.close(); } } catch (IOException e) { LOG.warn(e); } LOG.debug("Done: file=" + file + ", termCount=" + terms.size()); } return terms; } public static void main(String[] args){ String filePath = "/Users/zhouyh/work/yanfa/xunlianji/UTF8/train/ClassFile/C000008/0.txt"; HanLPDocumentAnalyzer hanLPDocumentAnalyzer = new HanLPDocumentAnalyzer(new Configuration()); hanLPDocumentAnalyzer.analyze(new File(filePath)); String str = "測試hanLP分詞"; System.out.println(str); // Pattern compile = Pattern.compile("(\\d+\\.\\d+)|(\\d+)|([\\uFF10-\\uFF19]+)"); // Matcher matcher = compile.matcher("9402"); // if (matcher.find()){ // System.out.println(matcher.group()); // } } }
這里對原作者提供的訓練集資源做了合並,將訓練集擴大到10個類別,每個類別的8000文本中,前6000文本作為訓練集,后2000文本作為測試集,文本結構如下圖所示:
測試集中是同樣的結構。
生成的特征向量與libsvm需要的訓練集格式如下面所示:
libsvm訓練集格式文檔:
針對測試集也通過上述方式處理。
使用libSvm訓練分類文本
文本轉換:
./svm-scale -l 0 -u 1 /Users/zhouyh/work/yanfa/xunlianji/UTF8/heji/train.txt > /Users/zhouyh/work/yanfa/xunlianji/UTF8/heji/train-scale.txt
測試集也做同樣轉換:
./svm-scale -l 0 -u 1 /Users/zhouyh/work/yanfa/xunlianji/UTF8/heji/test.txt > /Users/zhouyh/work/yanfa/xunlianji/UTF8/heji/test-scale.txt
進行模型訓練,此部分耗時較長:
./svm-train -h 0 -t 0 /Users/zhouyh/work/yanfa/xunlianji/UTF8/heji/train-scale.txt /Users/zhouyh/work/yanfa/xunlianji/UTF8/heji/model.txt
訓練過程如下圖所示:
訓練完成會生成model文件
采用預先處理好的測試文本進行分類測試:
./svm-predict /Users/zhouyh/work/yanfa/xunlianji/UTF8/heji/test-scale.txt /Users/zhouyh/work/yanfa/xunlianji/UTF8/heji/model.txt /Users/zhouyh/work/yanfa/xunlianji/UTF8/heji/predict.txt
得到結果為:Accuracy = 81.6568% (16333/20002) (classification)
整體流程做完,得到文件如下圖所列:
至此,仿照原作者的思路,對libsvm的分類流程做了一次實踐。
JAVA代碼測試
建立相關java項目,引入libsvm的jar包,我這里采用maven搭建,引入jar包:
<!-- https://mvnrepository.com/artifact/tw.edu.ntu.csie/libsvm --> <!-- libsvm jar包 --> <dependency> <groupId>tw.edu.ntu.csie</groupId> <artifactId>libsvm</artifactId> <version>3.17</version> </dependency>
同時要把libsvm包中的svm_predict.java及svm_train.java引入,並對svm_predict.java的類做簡單改動,將預測的結果值返回,測試代碼如下:
public class LibSvmAlgorithm { public static void main(String[] args){ String[] testArgs = {"/Users/zhouyh/work/yanfa/xunlianji/UTF8/heji/test-scale.txt", "/Users/zhouyh/work/yanfa/xunlianji/UTF8/heji/model.txt", "/Users/zhouyh/work/yanfa/xunlianji/UTF8/heji/predict1.txt"}; try { Double accuracy = svm_predict.main(testArgs); System.out.println(accuracy); } catch (IOException e) { e.printStackTrace(); } } }