你可能要用的最常用的組件(components)是:
l Instances 你的數據
l Filter 對數據的預處理
l Classifiers/Clusterer 被建立在預處理的數據上,分類/聚類
l Evaluating 評價classifier/clusterer
l Attribute selection 去除數據中不相關的屬性
下面將介紹如果在你自己的代碼中使用WEKA,其中的代碼可以在上面網址的尾部找到。
Instances
ARFF文件
3.5.5和3.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接口,這個接口爲比如classifiers,clusterers,filers等提供了設置,獲取參數的功能,函數如下:
l void setOptions(String[] Options)
l 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 filter和J48,刪除一個attribute ID爲1的屬性。
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 set和test 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方法處理分類器的training和evaluation(每一次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 曲線/AUC(ROC 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數組。
l 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 evaluation。Weka 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-classifier和filter可以進行屬性選擇,但是爲了完整性,底層的方法仍然被列出來了。下面就是用CfsSubsetEVal和GreedStepwise方法的例子。
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-classifier和filter都不適合你的要求,你可以直接用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次,並求平均結果,很有可能得到的是不同的結果。