Weka開發[-1]——在你的代碼中使用Weka

你可能要用的最常用的組件(components)是:

Instances 你的數據

Filter 對數據的預處理

Classifiers/Clusterer 被建立在預處理的數據上,分類/聚類

Evaluating 評價classifier/clusterer

Attribute selection 去除數據中不相關的屬性

下面將介紹如果在你自己的代碼中使用WEKA,其中的代碼可以在上面網址的尾部找到。

Instances

ARFF文件

3.5.53.4.X版本

ARFF文件中讀取是一個很直接的

import weka.core.Instances;

import java.io.BufferedReader;

import java.io.FileReader;

...

Instances data = new Instances(

                         new BufferedReader(

                                 new FileReader("/some/where/data.arff")));

// setting class attribute

data.setClassIndex(data.numAttributes() - 1);

Class Index是指示用於分類的目標屬性的下標。在ARFF文件中,它被默認爲是最後一個屬性,這也就是爲什麼它被設置成numAttributes-1.

你必需在使用一個Weka函數(ex: weka.classifiers.Classifier.buildClassifier(data))之前設置Class Index

3.5.5和更新的版本

DataSource類不僅限於讀取ARFF文件,它同樣可以讀取CSV文件和其它格式的文件(基本上Weka可以通過它的轉換器(converters)導入所有的文件格式)

import weka.core.converters.ConverterUtils.DataSource;

...

DataSource source = new DataSource("/some/where/data.arff");

Instances data = source.getDataSet();

// setting class attribute if the data format does not provide this

//information

// E.g., the XRFF format saves the class attribute information as well

if (data.classIndex() == -1) 

data.setClassIndex(data.numAttributes() - 1);

數據庫

從數據庫中讀取數據稍微難一點,但是仍然是很簡單的,首先,你需要修改你的DatabaseUtils.props(自己看一下原文,基本上都有鏈接)重組(resemble)你的數據庫連接。比如,你想要連接一個MySQL服務器,這個服務器運行於3306端口(默認)MySQL JDBC驅動被稱爲Connector/J(驅動類是org.gjt.mm.mysql.Driver)。假設存放你數據的數據庫是some_database。因爲你只是讀取數據,你可以用默認用戶nobody,不設密碼。你需要添加下面兩行在你的props文件中:

jdbcDriver=org.gjt.mm.mysql.Driver

        jdbcURL=jdbc:mysql://localhost:3306/some_database

其次,你的讀取數據的Java代碼,應該寫成下面這樣:

         import weka.core.Instances;

import weka.experiment.InstanceQuery;

...

InstanceQuery query = new InstanceQuery();

    query.setUsername("nobody");

    query.setPassword("");

    query.setQuery("select * from whatsoever");

    // if your data is sparse, then you can say so too

// query.setSparseData(true);

Instances data = query.retrieveInstances();

注意:

l  別忘了把JDBC驅動加入你的CLASSPATH

l  如果你要用MS Access,你需要用JDBC-ODBC-bridge,它是JDK的一部分。

參數設置(Option handling)

Weka中實現了weka.core.OptionHandler接口,這個接口爲比如classifiersclusterersfilers等提供了設置,獲取參數的功能,函數如下:

void setOptions(String[] Options)

String[] getOptions()

下面依次介紹幾種參數設置的方法:

l  手工建立一個String數組

String[] options = new String[2];

options[0] = "-R";

options[1] = "1";

l  用weka.core.Utils類中的函數splitOptions將一個命令行字符串轉換成一下數組

String[] options = weka.core.Utils.splitOptions("-R 1");

l  用OptionsToCode.java類自動將一個命令行轉換成代碼,對於命令行中包含nested classes,這些類又有它們自己的參數,如果SMO的核參數這種情況很有幫助。

java OptionsToCode weka.classifiers.functions.SMO

將產生以下輸出:

         //create new instance of scheme

weka.classifiers.functions.SMO scheme = new

weka.classifiers.functions.SMO();

// set options

scheme.setOptions(weka.core.Utils.splitOptions("-C 1.0 -L 0.0010 -P

1.0E-12 -N 0 -V -1 -W 1 -K \"

weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E

1.0\""));

並且,OptionTree.java工具可以使你觀察一個nested參數字符串。

Filter

一個filter有兩種不同的屬性

l  監督的或是監督的(supervised or unsupervised)

是否受用戶控制

l  基於屬性的或是基於樣本的(attribute- or instance-based)

比如:刪除滿足一定條件的屬性或是樣本

多數filters實現了OptionHandler接口,這意味着你可以通過String數組設置參數,而不用手工地用set-方法去依次設置。比如你想刪除數據集中的第一個屬性,你可用這個filter

weka.

通過設置參數

         -R 1

如果你有一個Instances對象,比如叫data,你可以用以下方法產生並使用filter

import weka.core.Instances;

    import weka.filters.Filter;

    import weka.filters.unsupervised.attribute.Remove;

    ...

    String[] options = new String[2];

    options[0] = "-R";                             // "range"

    options[1] = "1";                              // first attribute

    Remove remove = new Remove();                 // new instance of filter

    remove.setOptions(options);                  // set options

// inform filter about dataset //**AFTER** setting options

remove.setInputFormat(data); 

    Instances newData = Filter.useFilter(data, remove);   // apply filter

運行中過濾(Filtering on-the-fly)

FilteredClassifier meta-classifier是一種運行中過濾的方式。它不需要在分類器訓練之前先對數據集過濾。並且,在預測的時候,你也不需要將測試數據集再次過濾。下面的例子中使用meta-classifier with Remove filterJ48,刪除一個attribute ID1的屬性。

import weka.core.Instances;

    import weka.filters.Filter;

    import weka.filters.unsupervised.attribute.Remove;

    ...

    String[] options = new String[2];

    options[0] = "-R";                             // "range"

    options[1] = "1";                              // first attribute

    Remove remove = new Remove();                 // new instance of filter

    remove.setOptions(options);                  // set options

// inform filter about dataset **AFTER** setting options

remove.setInputFormat(data);  

    Instances newData = Filter.useFilter(data, remove);   // apply filter

    import weka.classifiers.meta.FilteredClassifier;

    import weka.classifiers.trees.J48;

    import weka.filters.unsupervised.attribute.Remove;

    ...

    Instances train = ...         // from somewhere

    Instances test = ...          // from somewhere

    // filter

    Remove rm = new Remove();

    rm.setAttributeIndices("1");  // remove 1st attribute

    // classifier

    J48 j48 = new J48();

    j48.setUnpruned(true);        // using an unpruned J48

    // meta-classifier

    FilteredClassifier fc = new FilteredClassifier();

    fc.setFilter(rm);

    fc.setClassifier(j48);

    // train and make predictions

    fc.buildClassifier(train);

    for (int i = 0; i < test.numInstances(); i++) {

       double pred = fc.classifyInstance(test.instance(i));

       System.out.printn("ID: " + test.instance(i).value(0));

       System.out.print(", actual: " + test.classAttribute().value((int)

           test.instance(i).classValue()));

       System.out.println(", predicted: " +

           test.classAttribute().value((int) pred));

     }

其它Weka中便利的meta-schemes:

weka.clusterers.FilteredClusterer (since 3.5.4)

weka.associations.FilteredAssociator (since 3.5.6)

批過濾(Batch filtering)

在命令行中,你可以用-b選項enable第二個input/ouput對,用對第一個數據集過濾的設置來過濾第二個數據集。如果你正使用特徵選擇(attribute selection)或是正規化(standardization),這是必要的,否則你會得到兩個不兼容的數據集。其實這做起來很容易,只需要用setInputFormat(Instances)去初始化一個過濾器,即用training set,然後將這個過濾器依次用於training settest set。下面的例子將展示如何用Standardize過濾器過濾一個訓練集和測試集的。

Instances train = ...   // from somewhere

    Instances test = ...    // from somewhere

    // initializing the filter once with training set

    Standardize filter = new Standardize();

    filter.setInputFormat(train); 

    // configures the Filter based on train instances and returns filtered

    //instances

Instances newTrain = Filter.useFilter(train, filter); 

// create new test set

    Instances newTest = Filter.useFilter(test, filter);   

調用轉換(Calling conventions)

setInputFormat(Instances)方法總是必需是應用過濾器時最後一個調用,比如用Filter.useFilter(Instances,Filter)。爲什麼?首先,它是使用過濾器的轉換,其實,很多過濾器在setInputFormat(Instances)方法中用當前的設置參數產生輸出格式(output format)(在這個調用後設置參數不再有任何作用)。

分類(classification)

一些必要的類可以在下面的包中找到:

weka.

建立一個分類器(Build a classifier)

(Batch)

在一個給定的數據集上訓練一個Weka

分類器是非常簡單的事。例如,我們可以訓練一個C4.5樹在一個給定的數據集data上。訓練是通過buildClassifier(Instances)來完成的。

         import weka.classifiers.trees.J48;

    ...

    String[] options = new String[1];

    options[0] = "-U";            // unpruned tree

    J48 tree = new J48();         // new instance of tree

    tree.setOptions(options);     // set the options

    tree.buildClassifier(data);   // build classifier

增量式(Incremental)

實現了weka.classifiers.UpdateabeClassifier接口的分類器可以增量式的訓練,它可以節約內存,因爲你不需要把數據一次全部讀入內存。你可以查一下文檔,看哪些分類器實現了這個接口。

真正學習一個增量式的分類器是很簡單的:

l  調用buildClassifier(Instances),其中Instances包話這種數據集的結構,其中Instances可以有數據,也可以沒有。

l  順序調用updateClassifier(Instances)方法,通過一個新的weka.core.Instances,更新分類器。

這裏有一個用weka.core.converters.ArffLoader讀取數據,並用weka.classifiers.bayes.NaiveBayesUpdateable訓練分類器的例子。

         //    load data

    ArffLoader loader = new ArffLoader();

    loader.setFile(new File("/some/where/data.arff"));

    Instances structure = loader.getStructure();

    structure.setClassIndex(structure.numAttributes() - 1);

    // train NaiveBayes

    NaiveBayesUpdateable nb = new NaiveBayesUpdateable();

    nb.buildClassifier(structure);

    Instance current;

    while ((current = loader.getNextInstance(structure)) != null)

         nb.updateClassifier(current);

Evaluating

交叉檢驗

如果你一個訓練集並且沒有測試集,你也話想用十次交叉檢驗的方法來評價分類器。這可以很容易地通過Evaluation類來實現。這裏,我們用1作爲隨機種子進行隨機選擇,查看Evaluation類,可以看到更多它輸出的統計結果。

import weka.classifiers.Evaluation;

import java.util.Random;

...

Evaluation eval = new Evaluation(newData);

eval.crossValidateModel(tree, newData, 10, new Random(1));

注意:分類器(在這個例子中是tree)不應該在作爲crossValidateModel參數之前訓練,爲什麼?因爲每當buildClassifier方法被調用時,一個分類器必需被重新初始化(換句話說:接下來調用buildClassifier 方法總是返回相同的結果),你將得到不一致,沒有任何意義的結果。crossValidateModel方法處理分類器的trainingevaluation(每一次cross-validation,它產生一個你作爲參數的原分類器的複本(copy))。

Train/Set set

如果你有一個專用的測試集,你可以在訓練集上訓練一個分類器,再在測試集上測試。在下面的例子中,一個J48被實例化,訓練,然後評價。在控制檯輸出一些統計值。

import weka.core.Instances;

import weka.classifiers.Evaluation;

import weka.classifiers.trees.J48;

...

Instances train = ...   // from somewhere

Instances test = ...    // from somewhere

// train classifier

Classifier cls = new J48();

cls.buildClassifier(train);

// evaluate classifier and print some statistics

Evaluation eval = new Evaluation(train);

eval.evaluateModel(cls, test);

System.out.println(eval.toSummaryString("\nResults\n======\n",

 false));

統計(statistics)

下面是一些獲取評價結果的方法

l  數值型類別

Ø  Correct() 分類正確的樣本數(還有incorrect()

Ø  pctCorrect() 分類正確的百分比(還有pctIncorrect()

Ø  kappa() Kappa statistics

l  離散型類別

Ø  correlationCoefficient() 相關係數

l  通用

Ø  meanAbsoluteError() 平均絕對誤差

Ø  rootMeanSquaredError() 均方根誤差

Ø  unclassified() 未被分類的樣本數

Ø  pctUnclassified() 未被分類的樣本百分比

如果你想通過命令行獲得相同的結果,使用以下方法:

import weka.classifiers.trees.J48;

import weka.classifiers.Evaluation;

...

String[] options = new String[2];

options[0] = "-t";

options[1] = "/some/where/somefile.arff";

System.out.println(Evaluation.evaluateModel(new J48(), options));

ROC 曲線/AUCROC curves/AUC

Weka3.5.1開始,你可以在測試中產生ROC曲線/AUC。你可以調用Evaluation類中的predictions()方法去做。你可從Generating Roc curve這篇文章中找到許多產生ROC曲線的例子。

分類樣本(classifying instances)

如果你想用你新訓練的分類器去分類一個未標記數據集(unlabeled dataset),你可以使用下面的代碼段,它從/some/where/unlabeled.arff中讀取數據,並用先前訓練的分類器tree去標記樣本,並保存標記樣本在/some/where/labeled.arff

import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.FileReader;

import java.io.FileWriter;

import weka.core.Instances;

...

// load unlabeled data

Instances unlabeled = new Instances(

                       new BufferedReader(

                          new FileReader("/some/where/unlabeled.arff")));

 

// set class attribute

unlabeled.setClassIndex(unlabeled.numAttributes() - 1);

 

// create copy

Instances labeled = new Instances(unlabeled);

 

// label instances

for (int i = 0; i < unlabeled.numInstances(); i++) {

        double clsLabel = tree.classifyInstance(unlabeled.instance(i));

        labeled.instance(i).setClassValue(clsLabel);

}

// save labeled data

BufferedWriter writer = new BufferedWriter(

                          new FileWriter("/some/where/labeled.arff"));

writer.write(labeled.toString());

writer.newLine();

writer.flush();

writer.close();

數值型類別注意事項

l  如果你對所有類別在分佈感興趣,那麼使用distributionForInstance(Instance)。這個方法返回一個針對每個類別概率的double數組。

classifyInstance返回的是一個double(或者是distributionForInstance返回的數組中的下標),它僅僅是屬性的下標,例如,如果你想用字符串形式來表現返回的類別結果clsLabel,你可以這樣輸出:

System.out.println(clsLabel + " -> " +

        unlabeled.classAttribute().value((int) clsLabel));

聚類(Clustering)

聚類與分類相似,必要的類可以在下面的包中找到

weka.clusterers

建立一個Clusterer

批(Batch

一個clusterer建立與建立一個分類器的方式相似,只是不是使用buildClassifier(Instances)方法,它使用buildClusterer(Instances),下面的代碼段展示瞭如何用EM clusterer使用最多100次迭代的方法。

import weka.clusterers.EM;

...

String[] options = new String[2];

options[0] = "-I";                 // max. iterations

options[1] = "100";

EM clusterer = new EM();   // new instance of clusterer

clusterer.setOptions(options);     // set the options

clusterer.buildClusterer(data);    // build the clusterer

增量式

實現了weka.clusterers.UpdateableClusterer接口的Clusterers可以增量式的被訓練(3.5.4版開始)。它可以節省內存,因爲它不需要一次性將數據全部讀入內存。查看文檔,看哪些clusterers實現了這個接口。

真正訓練一個增量式的clusterer是很簡單的:

l  調用buildClusterer(Instances) 其中Instances包話這種數據集的結構,其中Instances可以有數據,也可以沒有。

l  順序調用updateClusterer(Instances)方法,通過一個新的weka.core.Instances,更新clusterer

l  當全部樣本被處理完之後,調用updateFinished(),因爲clusterer還要進行額外的計算。

下面是一個用weka.core.converters.ArffLoader讀取數據,並訓練weka.clusterers.Cobweb的代碼:

//load data

ArffLoader loader = new ArffLoader();

loader.setFile(new File("/some/where/data.arff"));

Instances structure = loader.getStructure();

   

// train Cobweb

Cobweb cw = new Cobweb();

cw.buildClusterer(structure);

Instance current;

while ((current = loader.getNextInstance(structure)) != null)

        cw.updateClusterer(current);

cw.updateFinished();

評價(Evaluating)

評價一個clusterer,你可用ClusterEvaluation類,例如,輸出聚了幾個類:

import weka.clusterers.ClusterEvaluation;

import weka.clusterers.Clusterer;

...

ClusterEvaluation eval = new ClusterEvaluation();

// new clusterer instance, default options

Clusterer clusterer = new EM();

clusterer.buildClusterer(data);        // build clusterer

eval.setClusterer(clusterer);              // the cluster to evaluate

// data to evaluate the clusterer on

eval.evaluateClusterer(newData);      

// output # of clusters

System.out.println("# of clusters: " + eval.getNumClusters());

density based clusters這種情況下,你可用交叉檢驗的方法去做(注意:MakeDensityBasedClusterer你可將任何clusterer轉換成一下基於密度(density based)clusterer)

import weka.clusterers.ClusterEvaluation;

import weka.clusterers.DensityBasedClusterer;

import java.util.Random;

...

ClusterEvaluation eval = new ClusterEvaluation();

eval.setClusterer(clusterer);       // the clusterer to evaluate

eval.crossValidateModel(                 // cross-validate

         clusterer, newData, 10,               // with 10 folds

        new Random(1));        // and random number generator with seed 1

如果你想用命令行方式得到相同的結果,用以下方法:

import weka.clusterers.EM;

import weka.clusterers.ClusterEvaluation;

...

String[] options = new String[2];

options[0] = "-t";

options[1] = "/some/where/somefile.arff";

System.out.println(ClusterEvaluation.evaluateClusterer(new EM(),

 options));

聚類數據集(Clustering instances)

與分類唯一不同是名字不同。它不是用classifyInstances(Instance),而是用clusterInstance(Instance)。獲得分佈的方法仍然是distributionForInstance(Instance)

Classes to cluster evaluation

如果你的數據包含一個類別屬性,並且你想檢查一下產生的clusters與類別吻合程度,你可進行所謂的classes to clusters evaluationWeka Exporer提供了這個功能,並用它也很容易實現,下面是一些必要的步驟。

l  讀取數據,設置類別屬性下標

Instances data = new Instances(new BufferedReader(new

FileReader("/some/where/file.arff")));

data.setClassIndex(data.numAttributes() - 1);

l  產生無類別的數據,並用下面代碼訓練

weka.filters.unsupervised.attribute.Remove filter = new

        eka.filters.unsupervised.attribute.Remove();

filter.setAttributeIndices("" + (data.classIndex() + 1));

filter.setInputFormat(data);

Instances dataClusterer = Filter.useFilter(data, filter);

l  學習一個clusterer,比如EM

EM clusterer = new EM();

// set further options for EM, if necessary...

clusterer.buildClusterer(dataClusterer);

l  用仍然包含類別屬性的數據集評價這個clusterer

ClusterEvaluation eval = new ClusterEvaluation();

eval.setClusterer(clusterer);

eval.evaluateClusterer(data)

l  輸出評價結果

System.out.println(eval.clusterResultsToString());

屬性選擇(Attribute selection)

其實沒有必要在你的代碼中直接使用屬性選擇類,因爲已經有meta-classifierfilter可以進行屬性選擇,但是爲了完整性,底層的方法仍然被列出來了。下面就是用CfsSubsetEValGreedStepwise方法的例子。

Meta-Classifier

下面的meta-classifier在數據在傳給classifier之前,進行了一個預外理的步驟:

Instances data = ...  // from somewhere

AttributeSelectedClassifier classifier = new

AttributeSelectedClassifier();

CfsSubsetEval eval = new CfsSubsetEval();

GreedyStepwise search = new GreedyStepwise();

search.setSearchBackwards(true);

J48 base = new J48();

classifier.setClassifier(base);

classifier.setEvaluator(eval);

classifier.setSearch(search);

// 10-fold cross-validation

Evaluation evaluation = new Evaluation(data);

evaluation.crossValidateModel(classifier, data, 10, new Random(1));

System.out.println(evaluation.toSummaryString());

Filter

過濾器方法是很直接的,在設置過濾器之後,你就可以通過過濾器過濾並得到過濾後的數據集。

Instances data = ...  // from somewhere

AttributeSelection filter = new AttributeSelection(); 

// package weka.filters.supervised.attribute!

CfsSubsetEval eval = new CfsSubsetEval();

GreedyStepwise search = new GreedyStepwise();

search.setSearchBackwards(true);

filter.setEvaluator(eval);

filter.setSearch(search);

filter.setInputFormat(data);

// generate new data

Instances newData = Filter.useFilter(data, filter);

System.out.println(newData);

Low-Level

如果meta-classifierfilter都不適合你的要求,你可以直接用attribute selection類。

Instances data = ...  // from somewhere

// package weka.attributeSelection!

AttributeSelection attsel = new AttributeSelection(); 

CfsSubsetEval eval = new CfsSubsetEval();

GreedyStepwise search = new GreedyStepwise();

search.setSearchBackwards(true);

attsel.setEvaluator(eval);

attsel.setSearch(search);

attsel.SelectAttributes(data);

// obtain the attribute indices that were selected

int[] indices = attsel.selectedAttributes();

System.out.println(Utils.arrayToString(indices));

Note on Randomization

大多數機器學習方法,比較分類器和clusterer,都會受據的順序影響。用不同的隨機數種子隨機化數據集很可能得到不同的結果,比如Explorer或是一個分類器/clusterer在只使用一個seeded java.util.Random number generator。而weka.core.Instances.getgetRandomNumberGenerator(int),同樣考慮了對樣本的隨機,如果不是用10-fold cross-validation 10次,並求平均結果,很有可能得到的是不同的結果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章