时隔好久好久,文本分类的第三篇终于来了!
言归正传,在分词之后就要特征表达和提取了,什么意思呢?分类器要对文章分类,可是分类器怎么识别每一篇文章呢,它又怎么来对每一篇文章进行数据化的处理呢?
对文章分类有这么几种方法:
- 基于词的匹配
- 基于知识规则
- 基于统计的方法(机器学习)
可以看出来,这三种分类方法正是一个分类的发展过程.词的匹配太过死板,很难准确的提高分类程度.知识规则要由人来总结,而且还要将知识规则表达成数据,也不可靠.现在的分类方法,大多是第三种的机器学习.
机器学习就是给计算机一种能够自主挖掘规则的方法.对文本分类来说,就是用一部分语料库训练计算机,然后给它测试集来进行测试.因此,要将文本表达成计算机识别的数据.
有一种叫做向量空间模型的表达方法-VSM,意思就是把每一篇文章表达成一个词汇的堆积,这种方法也叫词袋.在这种模型中,一篇文章被看作单词特征项集合来看,利用加权特征项构成向量进行文本表示.
当然,它有很大的缺点,它基本上完全忽略了除词的信息以外所有的部分,这使得它能表达的信息量存在上限.
还有主题模型的表达方式(LSI,LDA),这里先不说这种方法了,着重说VSM方法.
VSM是怎样具体表达一篇文章的呢?这里我们举例”我爱天安门,我的家.”这句话就是一篇文章.
把它先用上一篇文章的预处理,分词后就变成了”我 爱 天安门 我 家”.
带上权值表示,那么就是:
D=(“我”, 2, “爱”, 1, “天安门”,……) 在Weka中,每篇文章都要表示为
@attribute @@class@@ auto @attribute 我 numeric @attribute 爱 numeric {0 auto, 1 2,2 1}
这样的形式,其中auto为类别名.”{}”的意义就是,它是auto类别,第一个词”我”出现2次,第二个词”爱”出现1次.
这样就把每一篇文章表达成了计算机可保存的形式.Weka中,这样的一篇文章就是一个Instance,全部文章所形成的数据词典就是Instances.
那怎么把之前分好词的txt文件转化为Weka需要的表达形式呢?
先定义好训练集测试集,还有获取的数据与处理过的数据变量.
Instances dataRaw;
Instances vectorDataRaw;
Instances trainingSet;
Instances testingSet;
然后开始进行处理.
private void initInstance(String segmentedDataPath) {
TextDirectoryLoader loader = new TextDirectoryLoader();
try {
loader.setDirectory(new File(segmentedDataPath));
dataRaw = loader.getDataSet();
// System.out.println("\nImported data:\n" + dataRaw);
convertToARFF("segmentedArticlesRaw.arff", dataRaw);
if (dataRaw.classIndex() == -1) {
dataRaw.setClassIndex(dataRaw.numAttributes() - 1);
}
vectorDataRaw = stringToVector(dataRaw);
convertToARFF("vectorArticleRaw.arff", vectorDataRaw);
vectorDataRaw.setClassIndex(0);
System.out.println('Dimensions:' + vectorDataRaw.numAttributes());
divideTrainSetAndTestSet(2, 1);
naiveBayesFunction(trainingSet, testingSet);
// use Naive Bayes classifier
SMOFunction(trainingSet, testingSet);
// use sequential minimal optimization
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
其中TextDirectoryLoader就是一个txt转Weka需要的arff文件的方法.给它传一个目录就可以.然后再把获取的数据集设置类别.
stringToVector函数是实现对数据词典的处理,如去停用词等一些设置.
private Instances stringToVector(Instances instance) {
StringToWordVector filter = new StringToWordVector();
Instances dataFiltered;
try {
filter.setStemmer(new NullStemmer());
filter.setStopwords(new File("resources/stop_words_ch.txt"));
filter.setUseStoplist(true);
// delete the stop words
filter.setOutputWordCounts(true);
filter.setInputFormat(instance);
dataFiltered = Filter.useFilter(instance, filter);
return dataFiltered;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return instance;
}
函数内容是先设置词干,然后载入停用词,比如哪些词是没意义的就去掉它.然后是设置表达的方式,就是说是想要某个词出现过就标记为1还是说出现过几次就标记为几次.最后函数的返回就是一个用户词典了.
获取到可用的词典后就可以进行分类训练与测试了.divideTrainSetAndTestSet(2, 1)是将数据集分成平等大小2部分给训练集和测试集.
private void divideTrainSetAndTestSet(int totalSize, int testingSize) {
StratifiedRemoveFolds filter = new StratifiedRemoveFolds();
String[] options = new String[6];
options[0] = "-N";
options[1] = Integer.toString(totalSize);
options[2] = "-F";
options[3] = Integer.toString(testingSize);
options[4] = "-S";
options[5] = Integer.toString(1);
try {
filter.setOptions(options);
filter.setInputFormat(vectorDataRaw);
filter.setInvertSelection(false);
testingSet = Filter.useFilter(vectorDataRaw, filter);
// System.out.println(testingSet);
filter.setInvertSelection(true);
trainingSet = Filter.useFilter(vectorDataRaw, filter);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
最后一步就是分类了.这里用naiveBayesFunction,SMOFunction这两个函数就是分别对应朴素贝耶斯和SVM分类方法.至于这两个分类算法就不多提了,直接调用Weka的API就可以.
private void naiveBayesFunction(Instances training, Instances testing) {
long trainingStartTime = System.currentTimeMillis();
NaiveBayes classifier = new NaiveBayes();
try {
classifier.buildClassifier(training);
long trainingEndTime = System.currentTimeMillis();
// System.out.println("\n\nClassifier model:\n\n" + classifier);
evaluation("evaluation/NaiveBayes.txt", classifier, testing, trainingStartTime, trainingEndTime);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
evaluation函数是将评测结果存到”evaluation/NaiveBayes.txt”这个文件里.
private void evaluation(String filename, Classifier classifier, Instances data,
long trainingStartTime, long trainingEndTime) {
try {
Evaluation eval = new Evaluation(data);
eval.crossValidateModel(classifier, data, 3, new Random(1));
String class_detail = eval.toClassDetailsString();
// System.out.println(class_detail);
String summary = eval.toSummaryString();
// System.out.println(summary);
String confusion_matrix = eval.toMatrixString();
// System.out.println(confusion_matrix);
long endTime = System.currentTimeMillis();
if (!filename.equals("")) {
FileWriter fw = new FileWriter(filename);
BufferedWriter bw = new BufferedWriter(fw);
String result = classifier + "\n" + class_detail + "\n"
+ summary + "\n" + confusion_matrix + "\n"
+ "Training Time:\n" + (long)(trainingEndTime - trainingStartTime)/1000.0 + "s\n"
+ "Testing Time:\n" + (long)(endTime - trainingEndTime)/1000.0 + "s";
bw.write(result);
bw.close();
fw.close();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// evaluate the classifier by confusion matrix
Evaluation是Weka提供的评估类,里面有交叉校验等方法,这里就用交叉校验,然后用混淆矩阵来查看分类情况.
这篇的分类方法是调用Weka的API来是实现的,现在成熟的第三方分类器有很多,所以我们只要把重点放在数据的预处理上就好.