姓名:崔子寒 学号:161220026 Email: cuizihan@nju.edu.cn
本次实验提交文件分为三个部分,包括训练测试结果、可执行jar包和完整工程。其中训练测试结果是调用weka的API,使用不同的分类方法,对所有数据进行十折交叉验证得到的结果。可执行jar包可以在命令行下执行java -jar来运行,复现实验结果。完整工程是完整的工程代码,使用maven作为构建工具。
本次实验提供的文件格式是arff文件,这是weka工具包使用的数据的文件格式,因此使用weka工具包提供的机器学习算法进行训练。本次实验需要比较:
- J48决策树算法
- 朴素贝叶斯(Naive Bayes) 算法
- 支持向量机(SVM)算法
- 神经网络(Neural Network)
- K近邻(KNN)算法
以及这些算法的使用Bagging集成学习在给定的10个arff数据文件上的表现。weka提供了GUI界面,可以直接在界面上选中文件和所用的算法进行学习,不过对于10个文件和总共10个分类器来说,要进行100次操作,是比较低效的。所以编写程序,调用weka提供的API,自动的完成上述过程,并将结果输出至文件。
maven项目比较容易添加weka依赖,在pom.xml中添加如下的依赖:
<!-- https://mvnrepository.com/artifact/nz.ac.waikato.cms.weka/weka-stable -->
<dependency>
<groupId>nz.ac.waikato.cms.weka</groupId>
<artifactId>weka-stable</artifactId>
<version>3.8.0</version>
</dependency>
之后就可以调用weka的API进行机器学习。
weka通过Instances类来加载arff文件,Instances类有一个单参数构造器,需要传入一个java.io.Reader对象,t通过Reader,Instances类导入arff文件信息。
通过阅读weka源码可以发现,所有的基分类器都实现了classifier接口,这个接口的定义如下:
public interface Classifier {
void buildClassifier(Instances var1) throws Exception;
double classifyInstance(Instance var1) throws Exception;
double[] distributionForInstance(Instance var1) throws Exception;
Capabilities getCapabilities();
}
这个接口有两个方法buildClassifier和classifyInstance,从字面意思理解,分别用来训练和测试。
如果要进行集成学习,weka.classifier.meta下提供了一些集成学习算法。以Bagging为例,Bagging继承了抽象类SingleClassifierEnhancer,这个抽象类有下面这个方法:
public void setClassifier(Classifier newClassifier) {
this.m_Classifier = newClassifier;
}
所以我们创建了集成学习器后,调用setClassifier来设置基学习器,之后就可以进行训练了。
如果要进行十折交叉验证,可以使用weka.classifiers.Evaluation类,这个类的部分定义如下:
public class Evaluation implements Serializable, Summarizable, RevisionHandler {
...
public void crossValidateModel(Classifier classifier, Instances data, int numFolds, Random random, Object...){...}
public String toSummaryString() {...}
public String toClassDetailsString(){...}
...
我们主要用到三个方法:crossValidateModel方法需要传入的参数有:一个Classifier接口、数据集、交叉验证的折数和随机数种子。通过设定numFolds = 10,就可以进行十折交叉验证。toSummaryString将返回交叉验证的总体结果,包括正确分类数目,准确度。toClassDetailsString返回更多的性能度量标准,包括TPR,FPR,Precision,Recall等指标。
因此,大致的思路如下:
- 首先加载所有的arff文件,将它们名称存储在一个List <String> L1中。
- 创建所有的分类器对象,将它们储存在一个List<Classifier> L2中。
- 两层循环,先从L2中取出一个分类器,然后在L1中的所有数据上分别进行十折交叉验证,输出结果。
for(MyClassifier classifier : classifiers) {
for(String file : files) {
MyEvaluation.tenCrossValidate(classifier,file);
...
}
}
程序运行后,会在控制台输出每个分类器训练测试所用的总时长,输出的格式如下:
并将每个分类器的表现输入至result目录下的文件中:
经过统计,得到每一个分类器和对应的集成分类器在每个文件上的表现,得到下表:
J48 | 贝叶斯 | SVM | 神经网络 | KNN | |||||||
---|---|---|---|---|---|---|---|---|---|---|---|
Acc(%) | ROC | Acc(%) | ROC | Acc(%) | ROC | Acc(%) | ROC | Acc(%) | ROC | ||
breast-w | 普通 | 94.56 | 0.955 | 95.99 | 0.988 | 97.00 | 0.968 | 95.28 | 0.986 | 95.14 | 0.973 |
集成 | 96.28 | 0.985 | 95.85 | 0.991 | 97.00 | 0.975 | 95.99 | 0.989 | 95.85 | 0.987 | |
colic | 普通 | 85.33 | 0.813 | 77.99 | 0.842 | 82.61 | 0.809 | 80.43 | 0.857 | 81.25 | 0.802 |
集成 | 85.59 | 0.864 | 77.99 | 0.842 | 83.97 | 0.868 | 84.51 | 0.876 | 81.25 | 0.824 | |
credit-a | 普通 | 86.09 | 0.887 | 77.68 | 0.896 | 84.93 | 0.856 | 83.62 | 0.895 | 81.16 | 0.808 |
集成 | 86.81 | 0.928 | 77.83 | 0.896 | 85.22 | 0.888 | 85.07 | 0.908 | 81.30 | 0.886 | |
credit-g | 普通 | 70.50 | 0.639 | 75.40 | 0.787 | 75.10 | 0.671 | 71.50 | 0.730 | 72.00 | 0.660 |
集成 | 73.30 | 0.753 | 74.80 | 0.787 | 75.40 | 0.754 | 76.10 | 0.776 | 72.10 | 0.694 | |
diabetes | 普通 | 73.83 | 0.751 | 76.30 | 0.819 | 77.34 | 0.720 | 75.39 | 0.793 | 70.18 | 0.650 |
集成 | 74.60 | 0.798 | 76.56 | 0.817 | 77.47 | 0.747 | 76.82 | 0.822 | 71.09 | 0.725 | |
hapatitis | 普通 | 83.87 | 0.708 | 84.52 | 0.860 | 85.16 | 0.756 | 80.00 | 0.823 | 80.64 | 0.653 |
集成 | 83.87 | 0.865 | 85.81 | 0.890 | 85.81 | 0.828 | 84.52 | 0.846 | 81.29 | 0.782 | |
mozilla4 | 普通 | 94.80 | 0.954 | 68.64 | 0.829 | 83.21 | 0.838 | 91.19 | 0.940 | 88.99 | 0.877 |
集成 | 95.11 | 0.976 | 68.74 | 0.830 | 83.12 | 0.849 | 91.28 | 0.945 | 88.86 | 0.928 | |
pc1 | 普通 | 93.33 | 0.668 | 89.18 | 0.650 | 92.97 | 0.500 | 93.60 | 0.723 | 92.06 | 0.74 |
集成 | 93.60 | 0.855 | 88.91 | 0.628 | 93.15 | 0.512 | 93.33 | 0.833 | 91.07 | 0.793 | |
pc5 | 普通 | 97.46 | 0.817 | 96.42 | 0.830 | 97.17 | 0.541 | 97.10 | 0.941 | 97.29 | 0.932 |
集成 | 97.53 | 0.959 | 96.48 | 0.842 | 97.18 | 0.572 | 97.31 | 0.954 | 97.37 | 0.953 | |
waveform-5000 | 普通 | 75.08 | 0.813 | 80.00 | 0.941 | 86.68 | 0.918 | 83.56 | 0.954 | 73.62 | 0.779 |
集成 | 81.20 | 0.936 | 79.98 | 0.941 | 86.26 | 0.944 | 85.68 | 0.962 | 74.46 | 0.880 |
计算每个分类器在所有数据集上的平均准确率,以及它们的集成训练的平均准确率,得到下表:
J48 | 贝叶斯 | SVM | 神经网络 | KNN | |
---|---|---|---|---|---|
平均ACC | 85.49 | 82.21 | 86.22 | 85.17 | 83.23 |
平均ACC(集成) | 86.79 | 82.29 | 86.46 | 87.06 | 83.46 |
平均提升 | 1.30 | 0.08 | 0.24 | 1.89 | 0.23 |
可视化数据:
在十折交叉验证的过程中,记录了每一个分类器一级它们的集成的时间消耗,数据如下表:
J48 | 贝叶斯 | SVM | 神经网络 | KNN | ||||||
---|---|---|---|---|---|---|---|---|---|---|
普通 | 集成 | 普通 | 集成 | 普通 | 集成 | 普通 | 集成 | 普通 | 集成 | |
用时(分钟) | 0.2 | 1.6 | 0.02 | 0.2 | 1.15 | 11.23 | 24.83 | 261.2 | 1.1 | 10.2 |
可视化数据:
综合性能度量和时间代价,可以得到如下的结论:
- 复杂的分类器例如神经网络,SVM在面对不同的数据集时都有较好的性能,而简单的分类器如:贝叶斯,决策树,KNN在某些数据上表现较好,但是在某些数据集上表现不好。
- 集成学习可以有效提高分类器的性能。
- 集成学习应该遵循"好而不同"的原则,选择基分类器时既应该考虑基分类器的性能,也应该考虑多个基分类器会不会效果相似,从而无法达到集成学习的目的。从结果来看,使用决策树和神经网络作为基分类器效果较好,而一些简单的分类器如朴素贝叶斯,KNN则不会带来性能的显著提升。
- 简单的学习器训练速度快,而复杂的学习器训练速度很慢。所以在可以接受性能略低的情况下,可以选择决策树这样的分类器。
Bagging是直接基于自助采样法的集成学习方法,通过有放回的随机采样,可以得到T个含m个样本的采样集,然后基于每个采样集训练出一个基学习器,再将这些基学习器进行结合。对于分类任务,采用简单投票法;对于回归任务,采用简单平均法。
从偏差-方差分解的角度看,Bagging关注降低方差,所以在决策树、神经网络等容易受样本扰动的学习器上的效果更为明显。
Weka中的Bagging集成学习有如下的参数:
参数名 | 类型 | 描述 |
---|---|---|
bagSizePercent | int | Size of each bag, as a percentage of the training set size. |
calcOutOfBag | boolean | Whether the out-of-bag error is calculated. |
classifier | Classifier | The base classifier to be used. |
numIterations | int | The number of iterations to be performed. |
seed | int | The random number seed to be used. |
基学习器我们指定为KNN,可以供调整的参数有bagSizePercent,calOutOfBag,numIterations和seed. 其中seed仅用于随机抽样,和性能关系不大,所以探究bagSizePercent,calOutOfBag和numIterations这三个参数和性能的关系。
以colic.arff的数据为例,通过调整参数得到准确率随着参数变化的数据
calOutOfBag | bagSizePercent | numIterations | |||
---|---|---|---|---|---|
True | 10 | 15 | 20 | 25 | |
100 | 81.25% | 81.52% | 81.52% | 81.25% | |
False | bagSizePercent | 10 | 15 | 20 | 25 |
70 | 80.71% | 80.52% | 80.43% | 80.98% | |
80 | 80.98% | 80.98% | 80.71% | 81.25% | |
90 | 81.25% | 81.25% | 80.98% | 80.71% | |
100 | 80.71% | 81.25% | 81.25% | 80.98% |
可以看出在使用包外误差进行辅助计算,并且适当调整包的个数是可以带来性能提升的。当使用包外误差,包的个数为15个时,达到了81.52%,比默认参数提高了0.6个百分点。接着我们固定Bagging的参数,对KNN的参数进行调整。
KNN的主要参数包括近邻的个数K,距离度量函数,通过调整这两个参数得到如下数据:
距离度量函数 | K | ||||
---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | |
欧式距离 | 81.52% | 81.52% | 82.01% | 82.88% | 82.34% |
曼哈顿距离 | 80.43% | 83.15% | 83.42% | 81.52% | 82.34% |
在尝试不同的K和距离度量函数后,发现使用曼哈顿距离,K=3时正确率最高,为83.42%,较默认参数提升了2.6个百分点。
因此,可以总结出提升Bagging KNN的一些方法:
- 使用包外误差进行辅助计算
- 调整Bag的个数,确定一个比较好的基分类器数目
- 多尝试一些度量函数和近邻个数K
这次实验学到了关于Weka工具包的用法,锻炼了自己的编程能力和数据分析能力,获益匪浅。
参考: