从前年开始使用weka最数据挖掘方面的研究,到现在有一年半的时间了。看到我们同组的兄弟写了关于weka方面的总结,我也想整理一下。因为网上的资料实在是太少,记得刚接手的时候,真是硬着头皮看代码。不过到现在看来,也积累了很多的代码了。希望能够在这里跟大家分享一下学习weka的乐趣与经验。
Weka是来之新西兰怀卡托大学的一款开源软件,主要是数据挖掘方面的一些算法的集合。这款软件大概是当前数据挖掘领域最好的开源软件了,当然国外还有其它一些组织维护的有自己的开源软件,但是只有这款软件应用是比较广泛的了。具体关于weka的信息可以到官网去查看 http://www.cs.waikato.ac.nz/ml/weka/ ,软件的下载也可大家到官网去。
我是从weka3.4一直用到现在的3.6版本的,其间weka在图形界面上有一些变动,但是底层的框架结构没有太大的变化,主要是添加一些新的算法什么的。总之大家可以放心的使用。我现在积累的代码是从3.5版本积累下来的,到现在3.6版本,集成起来一点问题都没有,这大概也是我喜欢weka 的一个原因。
数据挖掘的过程一般如下:
1. 读入训练、测试样本
2. 初始化分类器
3. 使用训练样本训练分类器
4. 使用测试样本测试分类器的学习效果
5. 打印分类结果
我们现在看看一个简单的实例
Java代码
package com.csdn;
import java.io.File;
import weka.classifiers.Classifier;
import weka.classifiers.Evaluation;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.converters.ArffLoader;
public class Test {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Instances ins = null;
Classifier cfs = null;
try{
/*
* 1.读入训练、测试样本
* 在此我们将训练样本和测试样本作为同一个样本
*/
File file= new File("C:\\Program Files\\Weka-3-6\\data\\contact-lenses.arff");
ArffLoader loader = new ArffLoader();
loader.setFile(file);
ins = loader.getDataSet();
//在使用样本之前一定要首先设置instances的classIndex,否则在使用instances对象是会抛出异常
ins.setClassIndex(ins.numAttributes()-1);
/*
* 2.初始化分类器
* 具体使用哪一种特定的分类器可以选择,请将特定分类器的class名称放入forName函数
* 这样就构建了一个简单的分类器
*/
cfs = (Classifier)Class.forName("weka.classifiers.bayes.NaiveBayes").newInstance();
/*
* 3.使用训练样本训练分类器
*/
cfs.buildClassifier(ins);
/*
* 4.使用测试样本测试分类器的学习效果
* 在这里我们使用的训练样本和测试样本是同一个,在实际的工作中需要读入一个特定的测试样本
*/
Instance testInst;
/*
* Evaluation: Class for evaluating machine learning models
* 即它是用于检测分类模型的类
*/
Evaluation testingEvaluation = new Evaluation(ins);
int length = ins.numInstances();
for (int i =0; i < length; i++) {
testInst = ins.instance(i);
//通过这个方法来用每个测试样本测试分类器的效果
testingEvaluation.evaluateModelOnceAndRecordPrediction(
cfs, testInst);
}
/*
* 5.打印分类结果
* 在这里我们打印了分类器的正确率
* 其它的一些信息我们可以通过Evaluation对象的其它方法得到
*/
System.out.println( "分类器的正确率:" + (1- testingEvaluation.errorRate()));
}catch(Exception e){
e.printStackTrace();
}
}
}
通过这个实例,我们可以看到在weka上做开发非常简单的,每个模块weka都提供了很好的支持。同时,我们可以在此基础上对weka进行一个简单的封装。设计一个Util类,将数据读取,以及分类器测试等功能放在这个Util类中共以后其它的程序使用。
获取样本Instances
Java代码
/*
* 从.arff文件中获取样本Instances;
* 1.fileName instances的文件名
*/
public static Instances getInstances(String fileName) throws Exception{
File file= new File(fileName);
return getInstances(file);
}
/*
* 从.arff文件中获取样本Instances;
* 1.file 获得instances的File对象
*/
public static Instances getInstances(File file) throws Exception{
Instances inst = null;
try{
ArffLoader loader = new ArffLoader();
loader.setFile(file);
inst = loader.getDataSet();
}
catch(Exception e){
throw new Exception(e.getMessage());
}
return inst;
}
获得一个Evaluation对象:
/*
* 获得一个Evaluation对象
* 1.h 一个已经训练过的分类器
* 2.ins 测试样本
*/
public static Evaluation getEvaluation(Classifier h,Instances ins){
try{
Instance testInst;
/*
* Evaluation: Class for evaluating machine learning models
* 即它是用于检测分类模型的类
*/
Evaluation testingEvaluation = new Evaluation(ins);
int length = ins.numInstances();
for (int i =0; i < length; i++) {
testInst = ins.instance(i);
//通过这个方法来用每个测试样本测试分类器的效果
testingEvaluation.evaluateModelOnceAndRecordPrediction(
h, testInst);
}
return testingEvaluation;
}
catch(Exception e){
System.out.println("haha bug!");
System.out.println(e);
}
return null;
}
通过这几个函数我们就将读取instances对象的功能以及测试分类器的效果的功能封装到两个static函数中了,下次我们使用的时候就可以方便的调用这个接口了,可以省去我们写这些代码的时间了。
=========================
Weka学习(ensemble算法)
前两次我们讲了数据挖掘中比较常见的两类方法。这次我来介绍一下ensemble(集成技术),总的来说,ensemble技术是归类在分类中的。它的主要原理是通过集成多个分类器的效果来达到提高分类效果的目的。简单我们可以通过两张图片来看看集成的效果
图一为多个基分类器单独工作时的分类效果图。图二为集成分类器的分类效果。我们可以看到集成分类器的分类曲线明显会平滑的多。来个比喻,在一件事情的表决上面,一个人的意见与多个人的意见相比,往往是多个人的意见来的准确一些。这是为什么我们要提倡民主决策的原因。
Ensemble技术在数据挖掘方向主要在以下三个方向做工作:
1. 在样本上做文章,基分类器为同一个分类算法,主要的技术有bagging,boosting;
2. 在分类算法上做工作,即用于训练基分类器的样本相同,基分类器的算法不同,这是本文采用的方法;
3. 在样本属性集上做文章,即在不同的属性空间上构建基分类器,比较出名的是randomforestTree算法,这个在weka中也有实现。
现在我们来看看ensemble技术在weka中的实现过程。
Java代码
package com.csdn;
import java.io.File;
import weka.classifiers.Classifier;
import weka.classifiers.Evaluation;
import weka.classifiers.meta.Vote;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.SelectedTag;
import weka.core.converters.ArffLoader;
public class SimpleEnsemble {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Instances trainIns = null;
Instances testIns = null;
Classifier cfs1 = null;
Classifier cfs2 = null;
Classifier cfs3 = null;
Classifier[] cfsArray = new Classifier[3];
try{
/*
* 1.读入训练、测试样本
* 在此我们将训练样本和测试样本是由weka提供的segment数据集构成的
*/
File file= new File("C:\\Program Files\\Weka-3-6\\data\\segment-challenge.arff");
ArffLoader loader = new ArffLoader();
loader.setFile(file);
trainIns = loader.getDataSet();
file = new File("C:\\Program Files\\Weka-3-6\\data\\segment-test.arff");
loader.setFile(file);
testIns = loader.getDataSet();
//在使用样本之前一定要首先设置instances的classIndex,否则在使用instances对象是会抛出异常
trainIns.setClassIndex(trainIns.numAttributes()-1);
testIns.setClassIndex(testIns.numAttributes()-1);
/*
* 2.初始化基分类器
* 具体使用哪一种特定的分类器可以选择,请将特定分类器的class名称放入forName函数
* 这样就构建了一个简单的分类器
*/
//贝叶斯算法
cfs1 = (Classifier)Class.forName("weka.classifiers.bayes.NaiveBayes").newInstance();
//决策树算法,是我们常听说的C45的weka版本,不过在我看代码的过程中发现有一些与原始算法有点区别的地方。
//即在原始的C45算法中,我们规定没有一个属性节点在被使用(即被作为一个分裂节点以后,他将被从属性集合中去除掉)。
//但是在J48中没有这样做,它依然在下次分裂点前,使用全部的属性集合来探测一个合适的分裂点。这样做好不好?
cfs2 = (Classifier)Class.forName("weka.classifiers.trees.J48").newInstance();
//什么东东,不知道做什么用,平常很少用。本想要用LibSVM的,但是由于要加载一些包,比较麻烦。
cfs3 = (Classifier)Class.forName("weka.classifiers.rules.ZeroR").newInstance();
/*
* 3.构建ensemble分类器
*/
cfsArray[0] = cfs1;
cfsArray[1] = cfs2;
cfsArray[2] = cfs3;
Vote ensemble = new Vote();
/*
* 订制ensemble分类器的决策方式主要有:
* AVERAGE_RULE
* PRODUCT_RULE
* MAJORITY_VOTING_RULE
* MIN_RULE
* MAX_RULE
* MEDIAN_RULE
* 它们具体的工作方式,大家可以参考weka的说明文档。
* 在这里我们选择的是多数投票的决策规则
*/
SelectedTag tag1 = new SelectedTag(
Vote.MAJORITY_VOTING_RULE, Vote.TAGS_RULES);
ensemble.setCombinationRule(tag1);
ensemble.setClassifiers(cfsArray);
//设置随机数种子
ensemble.setSeed(2);
//训练ensemble分类器
ensemble.buildClassifier(trainIns);
/*
* 4.使用测试样本测试分类器的学习效果
* 在这里我们使用的训练样本和测试样本是同一个,在实际的工作中需要读入一个特定的测试样本
*/
Instance testInst;
/*
* Evaluation: Class for evaluating machine learning models
* 即它是用于检测分类模型的类
*/
Evaluation testingEvaluation = new Evaluation(testIns);
int length = testIns.numInstances();
for (int i =0; i < length; i++) {
testInst = testIns.instance(i);
//通过这个方法来用每个测试样本测试分类器的效果
testingEvaluation.evaluateModelOnceAndRecordPrediction(
ensemble, testInst);
}
/*
* 5.打印分类结果
* 在这里我们打印了分类器的正确率
* 其它的一些信息我们可以通过Evaluation对象的其它方法得到
*/
System.out.println( "分类器的正确率:" + (1- testingEvaluation.errorRate()));
}catch(Exception e){
e.printStackTrace();
}
}
}
在weka中主要是通过weka.classifiers.meta.Vote来实现,基分类器是通过基分类器数组来设置的。同时我们可以自己设置集成分类器的决策方法,较为常用的是多数投票算法。
我在写下这篇文章是对ensemble分类器在segment数据集上的分类效果做了一个小小的测试,它对测试集segement-test的分类效果是0.8309的正确率。而我在使用单个分类器NaiveBayes,J48, ZeroR时它们的分类效果分别为0.7704、0.9617、0.1106 这样我们可以看到,ensemble分类器是可以矫正弱分类器的分类效果的。
总的来说,我在做实验的过程中,大部分的实验结果都表明ensemble技术确实是可以提高分类效果。当然这也是有国际上大牛的证明的,可不是我一个人这样说的。
===========================
Weka使用之属性选择
在这一节我们看看属性选择。在数据挖掘的研究中,通常要通过距离来计算样本之间的距离,而样本距离是通过属性值来计算的。我们知道对于不同的属性,它们在样本空间的权重是不一样的,即它们与类别的关联度是不同的,因此有必要筛选一些属性或者对各个属性赋一定的权重。这样属性选择的方法就应运而生了。
在属性选择方面InfoGain和GainRatio的比较常见,也是最通俗易懂的方法。它们与Decision Tree的构造原理比较相似,哪个节点拥有的信息量就为哪个节点赋较高的权重。其它的还有根据关联度的办法来进行属性选择(Correlation- based Feature Subset Selection for Machine Learning)。具体它的工作原理大家可以在网上看论文。
现在我将简单的属性选择实例给大家展示一下:
Java代码
package com.csdn;
import java.io.File;
import weka.attributeSelection.InfoGainAttributeEval;
import weka.attributeSelection.Ranker;
import weka.classifiers.Classifier;
import weka.core.Instances;
import weka.core.converters.ArffLoader;
public class SimpleAttributeSelection {
public static void main(String[] args) {
// TODO Auto-generated method stub
Instances trainIns = null;
try{
File file= new File("C:\\Program Files\\Weka-3-6\\data\\segment-challenge.arff");
ArffLoader loader = new ArffLoader();
loader.setFile(file);
trainIns = loader.getDataSet();
//在使用样本之前一定要首先设置instances的classIndex,否则在使用instances对象是会抛出异常
trainIns.setClassIndex(trainIns.numAttributes()-1);
Ranker rank = new Ranker();
InfoGainAttributeEval eval = new InfoGainAttributeeval_r();
eval.buildEvaluator(trainIns);
//System.out.println(rank.search(eval, trainIns));
int[] attrIndex = rank.search(eval, trainIns);
StringBuffer attrIndexInfo = new StringBuffer();
StringBuffer attrInfoGainInfo = new StringBuffer();
attrIndexInfo.append("Selected attributes:");
attrInfoGainInfo.append("Ranked attributes:\n");
for(int i = 0; i < attrIndex.length; i ++){
attrIndexInfo.append(attrIndex[i]);
attrIndexInfo.append(",");
attrInfoGainInfo.append(eval.evaluateAttribute(attrIndex[i]));
attrInfoGainInfo.append("\t");
attrInfoGainInfo.append((trainIns.attribute(attrIndex[i]).name()));
attrInfoGainInfo.append("\n");
}
System.out.println(attrIndexInfo.toString());
System.out.println(attrInfoGainInfo.toString());
}catch(Exception e){
e.printStackTrace();
}
}
}
在这个实例中,我用了InfoGain的属性选择类来进行特征选择。InfoGainAttributeEval主要是计算出各个属性的 InfoGain信息。同时在weka中为属性选择方法配备的有搜索算法(seacher method),在这里我们用最简单的Ranker类。它对属性进行了简单的排序。在Weka中我们还可以对搜索算法设置一些其它的属性,例如设置搜索的属性集,阈值等等,如果有需求大家可以进行详细的设置。
在最后我们打印了一些结果信息,打印了各个属性的InfoGain的信息。
================
Weka使用之聚类分析
上次我介绍了分类器的使用方法,这次我来介绍一下聚类算法。聚类算法在数据挖掘里面被称之为无监督学习(unsupervised learning),这是与分类算法(supervised learning)相对的。在它们两者之间还一种叫做半监督学习(semi-supervised learning)这个我会在后面的文章中重点介绍。所谓无监督学习就是在预先不知道样本类别的情况下,由聚类算法来判别样本的类别的一种学习方法。
聚类算法的一般过程分为:
1. 读入需预测样本
2. 初始化聚类算法(并设置参数)
3. 使用聚类算法对样本进行聚类
4. 打印聚类结果
我们来看下面的一个实例:
Java代码
package com.csdn;
import java.io.File;
import weka.clusterers.SimpleKMeans;
import weka.core.DistanceFunction;
import weka.core.EuclideanDistance;
import weka.core.Instances;
import weka.core.converters.ArffLoader;
public class SimpleCluster {
public static void main(String[] args) {
// TODO Auto-generated method stub
Instances ins = null;
Instances tempIns = null;
SimpleKMeans KM = null;
DistanceFunction disFun = null;
try{
File file= new File("C:\\Program Files\\Weka-3-6\\data\\contact-lenses.arff");
ArffLoader loader = new ArffLoader();
loader.setFile(file);
ins = loader.getDataSet();
KM = new SimpleKMeans();
//设置聚类要得到的类别数量
KM.setNumClusters(2);
KM.buildClusterer(ins);
tempIns = KM.getClusterCentroids();
System.out.println("CentroIds: " + tempIns);
}catch(Exception e){
e.printStackTrace();
}
}
}
我们可以看到读入样本的过程是与上一节的一样的方法。在构建聚类器时也是通过现有的类来实现的。现在weka的聚类算法有11种之多,我所了解的也就是一两种算法而已。SimpleKMean是最简单的KMeans算法,因为聚类算法的核心是通过距离来得到类别(类间相异,类内相似),所以需要有一个计算距离的公式常见的就是欧几里得距离了。在3.5版本中weka没有考虑其它距离公式的情况,将SimpleKMean计算距离的方法默认为欧几里得距离。在3.6中就weka提供了setDistanceFunction(DistanceFunction df)的接口可以方便我们设置自己的距离计算方法。
有一点要注意,在上面的聚类过程中,我们将样本的类别属性也放在里面了,这样做是不符合常识的,因为样本类别属性包含了大量的类别信息,可以诱导聚类算法得到很好的效果。但是这与我们的初衷是相背离的,所以在聚类之前我们要记住删除掉类别属性。
在第四步打印聚类结果是一个很简单的信息,里面包括了聚类的几个中心点。在我们写程序时,可以使用ClusterEvaluation类来打印更多的信息。