Java 人工智能初学者实用手册(二)
五、处理属性
在本章中,您将学习如何筛选属性、如何离散化属性以及如何执行属性选择。当我们过滤属性时,我们希望从数据集中移除某些属性。为此,我们将使用一个来自无人监管的filters包的Remove类,以及一个名为-R的属性。在本章中,我们还将使用离散化和宁滨。
我们将在本章中讨论以下主题:
- 过滤属性
- 离散化属性
- 属性选择
我们开始吧!
过滤属性
在这一节中,我们将学习如何过滤属性。让我们从代码开始。
我们将首先导入以下包和类:
import weka.core.Instances;
import weka.core.converters.ArffSaver;
import java.io.File;
import weka.core.converters.ConverterUtils.DataSource;
import weka.filters.Filter;
import weka.filters.unsupervised.attribute.Remove;
我们从各自的包中导入了Instances、ArffSaver、File和DataSource类,如前面的代码所示。我们在前一章中也使用了它们。Instance类将数据库放入内存,我们将在内存中处理数据集。ArffSaver类将帮助我们将数据集保存到磁盘上。File类将为磁盘命名,而DataSource类将从磁盘打开数据集。
正如您在前面的代码片段中看到的,我们从weka.filters包中导入了一个新类Filter。我们可以使用Filter类来应用过滤器。我们将应用的过滤器将是来自unsupervised.attribute包的非监督过滤器。
我们将首先把 ARFF 文件放入我们的DataSource对象中;然后,我们将使用一个Instances类dt对象将它存储在内存中,如下所示:
DataSource src = new DataSource("weather.arff");//path to the ARFF file on your system.
Instances dt = src.getDataSet();
我们已经创建了一个String对象,在这里我们可以放置我们想要用来过滤属性的所有选项。因为我们想要删除一个属性,我们将使用-R,并且我们将包括我们想要删除的属性的编号:
String[] op = new String[]{"-R","2"};
然后我们将为Remove类创建一个对象,并使用我们的String对象为Remove类设置选项,如下所示:
Remove rmv = new Remove();
rmv.setOptions(op);
我们还将把setInputFormat方法放入应该使用它的数据集:
rmv.setInputFormat(dt);
然后,我们将创建一个新的数据集,并对其应用Filter.useFilter方法,提供应该应用过滤器的数据集:
Instances nd = Filter.useFilter(dt, rmv);
一旦我们完成了这些,我们将为ArffSaver类创建一个对象;然后,我们将新数据集nd分配给ArffSaver对象,并使用setFile方法命名该对象:
ArffSaver s = new ArffSaver();
s.setInstances(nd);
s.setFile(new File("fw.arff"));
最后,我们将使用writeBatch()方法将其写入磁盘:
s.writeBatch();
运行代码,您将看到以下输出:
如果构建成功,我们可以比较两个 ARFF 文件,如下面的屏幕截图所示:
正如您在前面的屏幕截图中看到的,temperature属性已经从新的数据集(fw.arff文件)中删除。如果我们想从文件中删除多个属性,我们在代码的String[] op = new String[]{"-R","2"};部分使用一个破折号(-)操作符。
例如,String[] op = new String[]{"-R","2-3"};将删除2到3的属性。如果我们使用2-4而不是2-3,它将从数据集中删除从2到4的属性。
让我们尝试使用2-4删除属性,并再次比较文件,如下所示:
在左侧,我们可以看到我们拥有的属性,在右侧,我们可以看到经过筛选的属性。这意味着我们已经删除了第二、第三和第四个属性。我们只剩下第一个和最后一个属性。
这就是我们如何对数据集应用过滤。
离散化属性
我们现在将看看如何使用 Weka 离散化属性。首先,我们来解释一下什么是离散化。离散化属性是指将数据集中的一系列数值属性离散化为名义属性。因此,离散化实际上是将数字数据分类。为此,我们将使用宁滨;它跳过class属性,如果设置的话。
假设我们有从 1 到 60 的值,我们想把它们分成三个不同的类别。我们希望创建分类数据,而不是数字数据。我们将创建三个箱子。让我们为从 0 到 20 的所有值创建一个库,为从 20 到 40 的值创建另一个库,为从 40 到 60 的值创建第三个库。使用离散化,每个数字数据都将成为分类数据。
我们现在将使用以下选项:
-B<num>:指定数字属性被分割的箱数。默认值为 10。- 我们必须指定要进行宁滨的列。
-R帮助我们创建这些箱子。请注意,离散化将始终适用于数值数据,但不适用于任何名义数据或其他类型的数据。-R指定要离散化的列列表。第一个和最后一个是有效的索引;如果我们没有指定任何东西,那么默认是first-last。
现在,让我们看一下代码。我们将使用到目前为止一直在使用的类,它们是Instances、ArffSaver、File、DataSource和Filter,如下所示:
import weka.core.Instances;
import weka.core.converters.ArffSaver;
import java.io.File;
import weka.core.converters.ConverterUtils.DataSource;
import weka.filters.Filter;
我们还将使用一个新的属性,它是来自unsupervised.attribute包的一个非监督属性。我们将使用unsupervised.attribute包中的Discretize类:
import weka.filters.unsupervised.attribute.Discretize;
首先,我们将数据集读入我们的DataSource类的src对象;然后,我们将使用我们的Instances类的dt对象把它放入内存。一旦我们完成了这些,我们将设置options。我们将设置的第一个选项是-B。
让我们假设我们想要创建3bin,我们想要对第二个和第三个属性应用离散化;下面的代码显示了需要设置的options:
DataSource src = new DataSource("weather.arff");
Instances dt = src.getDataSet();
String[] options = new String[4];
options[0] = "-B";
options[1] = "3";
options[2] = "-R";
options[3] = "2-3";
然后,我们将为Discretize类创建一个dis对象,并使用setOptions方法将这些options设置为Discretize类。然后我们将把我们的Instances类的dt对象提供给setInputFormat方法,如下所示:
Discretize dis = new Discretize();
dis.setOptions(options);
dis.setInputFormat(dt);
然后,我们将使用Filter.useFilter方法创建一个新实例,我们将使用什么options指定这个过滤应该应用于哪个数据集(dt);因此,我们将包含一个Discretize类的dis对象,如下所示:
Instances nd = Filter.useFilter(dt, dis);
之后,我们将使用ArffSaver类保存它,并且我们将使用setInstance方法向ArffSaver提供实例,以及一个新的nd数据集。我们将为ArffSaver类提供名称,即weather-dis.arff,我们将使用writeBatch方法编写它:
ArffSaver as = new ArffSaver();
as.setInstances(nd);
as.setFile(new File("weather-dis.arff"));
as.writeBatch();
运行代码。一旦我们的构建成功,我们将看到实际发生了什么。以下是我们在weather.arff文件中的属性:
我们已经对第二个和第三个属性应用了宁滨,所以temperature和humidity属性值将被转换成 bins 我们要求创建三个存储箱。我们来看看是不是在weather-dis.arff文件里做的,如下截图所示:
我们可以看到,我们已经为temperature和humidity属性创建了 bin,它们是数值。为temperature创建的箱子有(inf-71]、( 71-78]和(78-inf)。湿度箱为(-inf-75.333333]、(75.333333-85.666667]和(85.666667-inf)。如@data部分所示,这些值也已被转换成箱。
如果我们想要创建五个而不是三个库,我们可以简单地如下更新options代码段,并构建代码:
options[0] = "-B";
options[1] = "5";
options[2] = "-R";
options[3] = "2-3";
现在,temperature属性有五个容器,而humidity属性有五个容器,而不是三个容器,如下图所示:
这就是我们如何执行离散化并将数值数据转换为分类数据。
属性选择
我们现在将看看如何执行属性选择。属性选择是一种决定哪些属性是执行分类或聚类的最有利属性的技术。
所以,让我们看一下代码,看看会发生什么,如下所示:
import weka.core.Instances;
import weka.core.converters.ArffSaver;
import java.io.File;
import weka.core.converters.ConverterUtils.DataSource;
import weka.filters.Filter;
import weka.filters.supervised.attribute.AttributeSelection;
import weka.attributeSelection.CfsSubsetEval;
import weka.attributeSelection.GreedyStepwise;
前五个类将与我们之前使用的相同。我们还将使用一种新的属性类型,它将是来自filters.supervised包和AttributeSelection类的一个被监督的属性。然后,我们有一个attribute.Selection包,从那里,我们将使用CfsSubsetEval类和GreedyStepwise类。
在下面的代码中,我们首先将 ARFF 文件读入DataSource类的src对象;然后,我们将把src对象分配给Instance类的dt对象。然后我们将为AttributeSelection、CfsSubsetEval和GreedyStepwise类创建对象,如下所示:
DataSource src = new DataSource("/Users/admin/Documents/NetBeansProjects/Datasets/weather.arff");
Instances dt = src.getDataSet();
AttributeSelection asel = new AttributeSelection();
CfsSubsetEval evl = new CfsSubsetEval();
GreedyStepwise sh = new GreedyStepwise();
然后,我们将把CfsSubsetEval和GreedyStepwise(实际上是一个搜索过程)类的evl和sh对象分配给AttributeSelection类的asel对象。然后,我们将数据集dt分配给asel对象,如以下代码所示:
asel.setEvaluator(evl);
asel.setSearch(sh);
asel.setInputFormat(dt);
之后,我们将创建一个新的数据集;我们将使用Filter.useFilter方法,给出应该对其进行过滤的数据集的名称(dt),以及我们希望使用哪些选项(asel)来执行属性选择:
Instances nd = Filter.useFilter(dt, asel);
最后,我们将为ArffSaver类创建一个as对象;我们将把新的数据集(nd)分配给as对象。我们还将把文件名(weather-sel.arff)分配给as对象,并将其写入磁盘,如下所示:
ArffSaver as = new ArffSaver();
as.setInstances(nd);
as.setFile(new File("weather-sel.arff"));
as.writeBatch();
让我们运行代码并比较weather.arff文件和新生成的数据集,如下所示:
该文件是使用属性选择创建的。GreedyStepwise搜索确定两个数字属性temperature和humidity对我们的分类/聚类算法最不重要,并从文件中删除了它们。
摘要
在本章中,您学习了如何筛选属性,如何使用宁滨离散化属性,以及如何应用属性选择。过滤和离散化属性的过程使用非监督过滤器,而属性选择则使用监督过滤器。
在下一章,你将看到如何应用监督学习。
六、监督学习
在这一章中,我们将看看如何使用分类器来训练、开发、评估和进行预测,以及如何使用我们开发的模型来保存、加载和进行预测。
我们将在本章中讨论以下主题:
- 开发分类器
- 模型评估
- 做预测
- 加载和保存模型
开发分类器
我们将使用weka.classifiers包开发一个非常简单的基于决策树的分类器。对于决策树分类,我们将使用 J48 算法,这是一种非常流行的算法。为了开发一个分类器,我们将设置两个标志,如下所示:
-C:设置剪枝的置信度阈值。其默认值为0.25。-M:设置开发决策树分类器的最大实例数。其默认值为2。
所有其他分类器都可以基于类似的方法开发,我们将在开发决策树分类器时合并这些方法。我们将开发另一个分类器——朴素贝叶斯分类器——基于我们将遵循的开发决策树分类器的相同机制。
让我们看看代码,看看怎么做。我们将从导入以下类开始:
import weka.core.Instances;
import weka.core.converters.ConverterUtils.DataSource;
import weka.classifiers.trees.J48;
现在,让我们继续下面的代码:
public static void main(String[] args) {
// TODO code application logic here
try{
DataSource src = new DataSource("/Users/admin/Documents/NetBeansProjects/DevelopClassifier/vote.arff");
Instances dt = src.getDataSet();
dt.setClassIndex(dt.numAttributes()-1);
String[] options = new String[4];
options[0] = "-C";
options[1] = "0.1";
options[2] = "-M";
options[3] = "2";
J48 tree = new J48();
tree.setOptions(options);
tree.buildClassifier(dt);
System.out.println(tree.getCapabilities().toString());
System.out.println(tree.graph());
//NaiveBayes nb = new NaiveBayes();
}
catch(Exception e){
System.out.println("Error!!!!\n" + e.getMessage());
}
这一次,我们使用了一个vote.arff数据集,因为它有非常大的数据量。这是 1984 年美国国会投票记录数据库,其中有许多元组。它包括诸如成员是否残疾的属性。基于这些属性,它可以预测一个人是民主党人还是共和党人。
首先,我们将通过使用一个DataSource类为数据集创建一个对象。然后,我们将创建一个Instances对象,并将数据集放入Instances对象。一旦我们打开了数据集,我们就必须告诉 Weka 哪个属性是类属性(哪个属性将用于分类)。正如您在前面代码的属性列表中看到的,class 属性位于末尾。因此,我们将采取setClassIndex;而且,由于-1属性是类属性,(dt.numAttributed()-1)将获得该特定属性的索引。
然后我们将创建一个数组Strings;而且,因为我们需要设置-C和-M,我们将用四个元素初始化我们的String数组。第一个元素是-C,第二个是阈值,第三个是-M,第四个是迭代次数。然后,我们将为J48创建一个对象。一旦我们为J48创建了一个对象,我们将通过使用setOptions为J48分配选项。然后,我们将不得不使用数据集构建一个分类器。
因此,我们将使用我们的J48对象和它的buildClassifier方法,并且我们将为它提供我们的数据集。这将为tree对象创建一个分类器。
一旦我们完成了这些,我们就可以用toString方法打印它的功能。这将打印它可以分类的属性类型。一旦我们这样做了,我们可以打印它的图表。这将为我们提供它已经开发并训练过的精确的决策树图。
运行代码将提供以下输出:
因为第一个打印声明是getCapabilities,所以已经打印了。分类器已经被训练过,它可以包含Nominal、Binary、Unary,以及一系列它可以自我训练的属性。digraph J48Tree输出中是用那些属性生成的树。这就是我们如何开发一个分类器。
假设我们想使用朴素贝叶斯再训练一个分类器;首先,我们必须合并weka.classifiers类的bayes包中的NaiveBayes类:
import weka.classifiers.bayes.NaiveBayes;
接下来,我们将为NaiveBayes创建一个对象nb,并将dt数据集传递给nb的buildClassifier方法:
NaiveBayes nb = new NaiveBayes();
nb.buildClassifier(dt);
System.out.println(nb.getCapabilities().toString());
完成后,分类器将被训练,我们将能够打印它的能力。
再次运行代码以获得以下输出:
在前面的屏幕截图中,您可以看到 Naive Bayes 分类器已经被训练,并且它已经提供了可以用来训练分类器的属性。
模型评估
我们现在来看看如何评估我们已经训练好的分类器。让我们从代码开始。
我们将从导入以下类开始:
import weka.core.Instances;
import weka.core.converters.ConverterUtils.DataSource;
import weka.classifiers.trees.J48;
import weka.classifiers.Evaluation;
import java.util.Random;
这一次,我们将使用来自weka.classifiers包的Evaluation类,以及一个用于生成随机值的Random类。
我们将使用的DataSource是segment-challenge.arff文件。我们使用它是因为它有一个test数据集,它也是 Weka 附带的数据集之一。我们将把它分配给我们的Instances对象,然后我们将告诉 Weka 哪个属性是类属性。我们将为决策树分类器设置标志,并为决策树分类器创建一个对象。然后,我们将设置options,并构建分类器。我们在上一节中执行了相同的操作:
public static void main(String[] args) {
try {
DataSource src = new DataSource("/Users/admin/Documents/NetBeansProjects/ModelEvaluation/segment-challenge.arff");
Instances dt = src.getDataSet();
dt.setClassIndex(dt.numAttributes()- 1);
String[] options = new String[4];
options[0] = "-C";
options[1] = "0.1";
options[2] = "-M";
options[3] = "2";
J48 mytree = new J48();
mytree.setOptions(options);
mytree.buildClassifier(dt);
接下来,我们将为Evaluation和Random类创建一个对象。一旦我们完成了这些,我们将为我们的测试数据集创建一个新的DataSource对象src1,以及一个segment-test.arff文件。我们将把它分配给一个新的Instances对象,并告诉 Weka 哪个特定属性是类属性。然后,我们将使用eval.evaluateModel对象和一个分类器,该分类器已经用我们想要评估的新测试数据集进行了训练:
Evaluation eval = new Evaluation(dt);
Random rand = new Random(1);
DataSource src1 = new DataSource("/Users/admin/Documents/NetBeansProjects/ModelEvaluation/segment-test.arff");
Instances tdt = src1.getDataSet();
tdt.setClassIndex(tdt.numAttributes() - 1);
eval.evaluateModel(mytree, tdt);
完成后,我们可以打印Evaluation结果,如下所示:
System.out.println(eval.toSummaryString("Evaluation results:\n", false));
正如您在前面的代码中看到的,我们通过使用toSummaryString方法获得了Evaluation结果。如果我们想单独打印它们,我们可以键入以下代码:
System.out.println("Correct % = " + eval.pctCorrect());
System.out.println("Incorrect % = " + eval.pctIncorrect());
System.out.println("kappa = " + eval.kappa());
System.out.println("MAE = " + eval.meanAbsoluteError());
System.out.println("RMSE = " + eval.rootMeanSquaredError());
System.out.println("RAE = " + eval.relativeAbsoluteError());
System.out.println("Precision = " + eval.precision(1));
System.out.println("Recall = " + eval.recall(1));
System.out.println("fMeasure = " + eval.fMeasure(1));
System.out.println(eval.toMatrixString("=== Overall Confusion Matrix ==="));
最后,我们将打印混淆矩阵。运行代码以获得以下输出:
toSummaryString方法打印了所有的值。使用pctCorrect、pctIncorrect、kappa、meanAbsoluteError等分别打印这些值。最后,我们打印了混淆矩阵。
a的124实例已被正确分类,机器对a的6进行了更多分类,分别为c、d或e。同样,对于b,110实例被正确分类,只有b的110实例。有a的125个实例;其中,机器分类为124,以此类推。这就是我们如何创建混淆矩阵并对我们的分类器进行评估。
做预测
现在,我们将看看如何使用我们的测试数据集来预测一个类。让我们从代码开始。我们将使用以下软件包:
import weka.core.Instances;
import weka.core.converters.ConverterUtils.DataSource;
import weka.classifiers.trees.J48;
import weka.core.Instance;
注意,这一次,我们将使用一个新类:来自weka.core包的Instance类。这将有助于我们使用测试数据集预测类别。然后,像往常一样,我们将把数据集读入src对象,并把它分配给一个dt对象。我们将告诉 Weka 哪个类属性将在这个数据集中为我们的决策树分类器设置属性。然后,我们将创建一个决策树分类器,为决策树分类器设置对象,并构建分类器,如下所示:
public static void main(String[] args) {
// TODO code application logic here
try {
DataSource src = new DataSource("/Users/admin/Documents/NetBeansProjects/MakingPredictions/segment-challenge.arff");
Instances dt = src.getDataSet();
dt.setClassIndex(dt.numAttributes() - 1);
String[] options = new String[4];
options[0] = "-C";
options[1] = "0.1";
options[2] = "-M";
options[3] = "2";
J48 mytree = new J48();
mytree.setOptions(options);
mytree.buildClassifier(dt);
接下来,我们将为DataSource类创建一个新的src1对象,在这里我们将提供我们的segment-test数据集。我们将把它分配给一个新的tdt对象,这个对象将把它放入内存。然后,我们将不得不使用setClassIndex方法再次设置目标变量。一旦我们做到了这一点,我们就可以走了:
DataSource src1 = new DataSource("/Users/admin/Documents/NetBeansProjects/MakingPredictions/segment-test.arff");
Instances tdt = src1.getDataSet();
tdt.setClassIndex(tdt.numAttributes()-1);
System.out.println("ActualClass \t ActualValue \t PredictedValue \t PredictedClass");
for (int i = 0; i < tdt.numInstances(); i++)
{
String act = tdt.instance(i).stringValue(tdt.instance(i).numAttributes()-1);
double actual = tdt.instance(i).classValue();
Instance inst = tdt.instance(i);
double predict = mytree.classifyInstance(inst);
String pred = inst.toString(inst .numAttributes()-1);
System.out.println(act + " \t\t " + actual + " \t\t " + predict + " \t\t " + pred);
}
现在,我们想得到实际的类和预测的类。Weka 只给实际类和预测类赋值;因此,我们将打印以下四项内容:
- 实际的类
- 实际价值
- 预测值
- 预测的类
由于我们的测试数据集中有 n 行,我们将一行一行地执行。因此,我们将使用一个for循环,从0到我们测试数据集中的实例数量。我们首先将实际的类分配给一个String对象。使用它,我们将使用我们的tdt.instance并设置一个值。然后,我们将获取第 i ^(th) 属性,并打印 class 属性。之后,我们将创建一个actual变量,它将是double类型,我们将使用classValue方法打印它的类值。一旦我们完成了这些,我们将为这个特定数据集的第 i ^(th) 实例创建一个对象。然后,我们将创建一个predict变量。应该是double型的。我们将通过使用我们的树对象和一个classifyInstance方法对它进行分类。我们将把inst对象分配给它;这将有我们的predict类值。现在,由于我们有了一个类值,我们可以通过使用toString方法将其转换成一个字符串,最后,我们可以打印所有四个值。
运行代码将提供以下输出:
正如我们所料,我们可以看到ActualClass、ActualValue、PredictedClass和PredictedValue。预测就是这样进行的。
加载和保存模型
现在,我们将看看如何保存我们已经训练好的模型,然后将该模型加载到硬盘上。所以,让我们快速进入代码。
在这个特殊的部分,我们将保存一个模型;因此,我们将使用以下三个类:
import weka.core.Instances;
import weka.core.converters.ConverterUtils.DataSource;
import weka.classifiers.trees.J48;
我们将把 ARFF 文件放入我们的src对象(属于DataSource类),并将它分配给Instances类的dt对象。然后,我们将把src对象分配给我们的dt对象;在dt对象中,我们将指出哪个特定属性是类属性。我们将为我们的决策树分类器设置某些options,并且我们将为我们的决策树分类器创建一个对象。然后,我们将为它设置选项,并构建它:
public static void main(String[] args) {
// TODO code application logic here
try {
DataSource src = new DataSource("/Users/admin/Documents/NetBeansProjects/SaveModel/segment-challenge.arff");
Instances dt = src.getDataSet();
dt.setClassIndex(dt.numAttributes() - 1);
String[] options = new String[4];
options[0] = "-C";
options[1] = "0.1";
options[2] = "-M";
options[3] = "2";
J48 mytree = new J48();
mytree.setOptions(options);
mytree.buildClassifier(dt);
一旦我们建立了决策树分类器,我们将把它保存到我们的硬盘上。为此,我们将使用以下方法:
weka.core.SerializationHelper.write("/Users/admin/Documents/NetBeansProjects/SaveModel/myDT.model", mytree);
我们将这个模型命名为myDT.model,并且我们将为它提供一个对象:mytree。因此,我们训练过的分类器将以myDT.model的名字保存在我们的硬盘上。
运行代码以获得以下输出:
如果构建成功,分类器将被保存到硬盘上。如果我们想确认的话,可以在硬盘上查一下。
现在,我们想从硬盘加载分类器。分类器的名字是myDT.model。我们将使用前四个类,如下所示:
import weka.core.Instances;
import weka.core.converters.ConverterUtils.DataSource;
import weka.classifiers.trees.J48;
import weka.core.Instance;
这一次,我们想通过阅读它们来做出某些预测。我们将为决策树创建一个对象,并对其进行类型转换。由于 Weka 不知道正在加载哪个分类器(哪个模型),首先,我们必须使用特定的类对其进行类型转换,如以下代码所示:
public static void main(String[] args) {
// TODO code application logic here
try{
J48 mytree = (J48) weka.core.SerializationHelper.read("/Users/admin/Documents/NetBeansProjects/LoadModel/myDT.model");
DataSource src1 = new DataSource("/Users/admin/Documents/NetBeansProjects/LoadModel/segment-test.arff");
Instances tdt = src1.getDataSet();
tdt.setClassIndex(tdt.numAttributes() - 1);
System.out.println("ActualClass \t ActualValue \t PredictedValue \t PredictedClass");
for (int i = 0; i < tdt.numInstances(); i++) {
String act = tdt.instance(i).stringValue(tdt.instance(i).numAttributes() - 1);
double actual = tdt.instance(i).classValue();
Instance inst = tdt.instance(i);
double predict = mytree.classifyInstance(inst);
String pred = inst.toString(inst.numAttributes() - 1);
System.out.println(act + " \t\t " + actual + " \t\t " + predict + " \t\t " + pred);
}
}
catch(Exception e){
System.out.println("Error!!!!\n" + e.getMessage());
}
}
那么,我们就拿weka.core.SerializationHelper;这一次,我们将使用一个read方法,并用分类器的名称来命名分类器或完整路径。然后,我们将创建一个DataSource对象,我们将把我们的测试数据集分配给我们的Instances,我们将告诉 Weka 哪个特定属性是目标属性。然后,我们将得到我们想要打印的四个值(来自上一章)。我们将对测试数据集的所有实例执行一个for循环,打印ActualClass,打印ActualValue,并初始化Instance的对象。我们将获取Instance对象,并为其提供测试数据集的第i ^个实例;我们将使用classifyInstance方法进行预测。一旦我们完成了这些,我们将打印它的String,我们将把String分配给pred,我们将打印所有的值。
运行代码将提供以下输出:
摘要
在本章中,你学习了如何开发和评估一个分类器。您还学习了如何使用经过训练的模型进行预测,以及如何将特定的模型保存到硬盘上。然后,您学习了如何从硬盘加载模型,以便将来使用它。
在下一章,我们将看看如何执行半监督和无监督学习。
七、半监督和非监督学习
在这一章,我们将看看如何建立和评估一个无监督的模型。我们还将了解半监督学习、无监督学习和半监督学习之间的区别、如何构建半监督模型,以及如何使用半监督模型进行预测。
在本章中,我们将讨论以下主题:
- 使用 k 均值聚类
- 评估聚类模型
- 利用余弦相似性形成距离矩阵
- 无监督和半监督学习的区别
- 自我训练和共同训练机器学习模型
- 使用半监督机器学习模型进行预测
使用 k 均值聚类
让我们看看如何构建一个聚类模型。我们将使用 k-means 聚类构建一个无监督模型。
我们将使用Instances类和DataSource类,就像我们在前面的章节中所做的那样。由于我们正在使用集群,我们将使用weka.clusterers包来导入SimpleKMeans类,如下所示:
import weka.core.Instances;
import weka.core.converters.ConverterUtils.DataSource;
import weka.clusterers.SimpleKMeans;
首先,我们将把我们的 ARFF 文件读入一个数据集对象,并将它分配给一个Instances对象。现在,由于这是我们必须做的全部工作(在分类中,我们还必须分配目标变量,class 属性),我们必须告诉 Weka class 属性是什么,然后我们将为 k-means 聚类创建一个对象。首先,我们必须告诉 Weka 我们想要创建多少个集群。假设我们想要创建三个集群。我们将使用 k-means 对象,并将setNumClusters设置为3;然后,我们将使用buildClusterer构建我们的集群,并且我们将把集群分配到其中。然后,我们将打印我们的模型,如下所示:
public static void main(String[] args) {
// TODO code application logic here
try{
DataSource src = new DataSource("/Users/admin/Documents/NetBeansProjects/Datasets/weather.arff");
Instances dt = src.getDataSet();
SimpleKMeans model = new SimpleKMeans();
model.setNumClusters(3);
model.buildClusterer(dt);
System.out.println(model);
}
catch(Exception e){
System.out.println(e.getMessage());
}
}
运行之后,我们将看到以下输出:
在前面的屏幕截图中,我们可以看到,最初,使用初始值创建了三个集群。在执行聚类之后,我们得到最终的三个聚类,因此Cluster 0具有7.0值,Cluster 1具有3.0值,Cluster 2具有4.0值。因为我们没有为我们的聚类算法提供一个类,所以字符串实际上试图将相似的数据分成组(我们称之为聚类)。这就是集群的工作方式。
评估聚类模型
现在,我们将了解如何评估已训练好的聚类分析模型。让我们看看代码,看看这是如何做到的。
我们将使用以下类:
import weka.core.Instances;
import weka.core.converters.ConverterUtils.DataSource;
import weka.clusterers.SimpleKMeans;
import weka.clusterers.ClusterEvaluation;
我们将使用weka.clusterers包中的ClusterEvaluation类进行评估。
首先,我们将把数据集读入我们的DataSource对象,并把它分配给Instances对象。然后,我们将创建我们的 k-means 对象,并指定我们想要创建的聚类数。接下来,我们将使用buildClusterer方法训练我们的聚类算法;然后,我们将使用println打印它。这与您之前看到的类似:
public static void main(String[] args) {
// TODO code application logic here
try{
DataSource src = new DataSource("/Users/admin/Documents/NetBeansProjects/ClusterEval/weather.arff");
Instances dt = src.getDataSet();
SimpleKMeans model = new SimpleKMeans();
model.setNumClusters(3);
model.buildClusterer(dt);
System.out.println(model);
接下来,我们将为ClusterEvaluation类创建一个对象。然后,我们将读入一个新的测试数据集,并将其分配给我们的DataSource对象。最后,我们将使用我们的Instances对象将它放入内存,我们将使用setClusterer设置Clusterer模型,并将训练好的Clusterer对象传递给setClusterer方法。一旦我们这样做了,我们将需要评估集群;因此,我们必须将测试数据集传递给evaluateClusterer方法。然后,我们将打印结果字符串,这样我们就可以得到我们已经训练的分类数:
ClusterEvaluation eval = new ClusterEvaluation();
DataSource src1 = new DataSource("/Users/admin/Documents/NetBeansProjects/ClusterEval/weather.test.arff");
Instances tdt = src1.getDataSet();
eval.setClusterer(model);
eval.evaluateClusterer(tdt);
运行上述代码将产生以下输出:
我们现在有了集群的数量,它们是使用我们的eval对象单独打印的。因此,这些分类的值如下:22%表示第一个分类,33%表示第二个分类,44%表示第三个分类。集群的总数是3。
半监督学习导论
半监督学习是一类考虑未标记数据的监督学习。如果我们有非常大量的数据,我们很可能希望对其进行学习。然而,用监督学习训练特定数据是一个问题,因为监督学习算法总是需要一个目标变量:一个可以分配给数据集的类。
假设我们有数百万个特定类型数据的实例。给这些实例分配一个类将是一个非常大的问题。因此,我们将从该特定数据中提取一小部分,并手动标记该数据(这意味着我们将手动为该数据提供一个类)。一旦我们做到了这一点,我们将使用它来训练我们的模型,以便我们可以使用未标记的数据(因为我们现在有一小组已标记的数据,这是我们创建的)。通常,少量的标记数据与大量的未标记数据一起使用。半监督学习介于监督学习和非监督学习之间,因为我们正在获取少量已标记的数据,并用它训练我们的模型;然后,我们试图通过使用未标记数据上的训练模型来分配类别。
许多机器学习研究人员发现,未标记数据在与少量标记数据结合使用时,可以在学习准确性方面产生相当大的提高。这是半监督学习的工作方式:监督学习和非监督学习的结合,其中我们获取非常少量的数据,对其进行标记,尝试对其进行分类,然后尝试将未标记的数据融入标记的数据中。
无监督和半监督学习的区别
在这一节,我们将看看无监督学习和半监督学习之间的区别。
无监督学习基于未标记的数据开发模型,而半监督学习使用标记和未标记的数据。
我们在无监督学习中使用期望最大化、层次聚类和 k-means 聚类算法,而在半监督学习中,我们应用主动学习或自举算法。
在 Weka 中,我们可以使用collective-classification包进行半监督学习。我们将在本章的后面看一下如何安装collective-classification包,您将看到如何使用集体分类来执行半监督学习。
自我训练和共同训练机器学习模型
您现在将学习如何开发半监督模型。
我们要做的第一件事是下载一个半监督学习包,然后我们将为半监督模型创建一个分类器。
下载半监督包
前往github . com/frac Pete/collective-class ification-Weka-package获取collective-classification Weka 包。这是一个半监督学习包,在 Weka 中提供。
有两种安装软件包的方法,如下所示:
- 从 GitHub 下载源代码并编译它,然后创建一个 JAR 文件
- 转到 Weka 包管理器,从那里安装集合分类
在执行上述方法之一后,您将得到一个 JAR 文件。您将需要这个 JAR 文件来训练分类器。我们将获得的源代码将为 JAR 文件提供代码。让我们看看这是如何做到的。
为半监督模型创建分类器
让我们从下面的代码开始:
import weka.core.Instances;
import weka.core.converters.ConverterUtils.DataSource;
import weka.classifiers.collective.functions.LLGC;
我们首先需要的是Instances和DataSource类,我们从一开始就一直在使用它们。我们需要的第三个类是一个LLGC类,它可以在collective-classification JAR 文件的functions包中找到。
因此,我们需要将两个 JAR 文件导入到项目中;一个是我们已经在使用的常规weka.jar文件,第二个是半监督学习文件,即collective-classification-<date>.jar文件,如下面的截图所示:
现在,我们将创建一个DataSource对象,并将我们的 ARFF 文件分配给DataSource对象,如下所示:
try{
DataSource src = new DataSource("weather.arff");
Instances dt = src.getDataSet();
dt.setClassIndex(dt.numAttributes()-1);
LLGC model = new LLGC();
model.buildClassifier(dt);
System.out.println(model.getCapabilities());
}
catch(Exception e){
System.out.println("Error!!!!\n" + e.getMessage());
}
然后,我们将创建一个Instances对象,我们将把 ARFF 文件分配给这个Instances对象,并将我们的数据放入内存。一旦我们的数据集在内存中可用,我们将告诉 Weka 哪个属性是我们在分类中使用的类属性。接下来,我们将初始化LLGC对象。LLGC是用于执行半监督学习的类。我们将使用model.buildClassifier(dt),并且我们将打印分类器的能力。
这些功能将被打印出来,如下面的屏幕截图所示:
正如您在前面的截图中看到的,这些是LLGC类可以执行半监督学习的属性,以便构建模型。这就是我们将如何建立一个半监督模型。
使用半监督机器学习模型进行预测
现在,我们将研究如何使用训练好的模型进行预测。考虑以下代码:
import weka.core.Instances;
import weka.core.converters.ConverterUtils.DataSource;
import weka.classifiers.collective.functions.LLGC;
import weka.classifiers.collective.evaluation.Evaluation;
我们将导入两个 JAR 库,如下所示:
weka.jar图书馆collective-classification-<date>.jar图书馆
因此,我们将采用两个基类,Instances和DataSource,并且我们将使用来自collective-classifications包的LLGC类(因为我们已经使用LLGC训练了我们的模型),以及来自collective-classifications包的Evaluation类。
我们将首先给我们的DataSource对象分配一个 ARFF 文件;我们将把它读入内存,在一个Instances对象中。我们将为我们的Instances对象分配一个类属性,然后,我们将构建我们的模型:
public static void main(String[] args) {
try{
DataSource src = new DataSource("weather.arff");
Instances dt = src.getDataSet();
dt.setClassIndex(dt.numAttributes()-1);
LLGC model = new LLGC();
model.buildClassifier(dt);
System.out.println(model.getCapabilities());
Evaluation eval = new Evaluation(dt);
DataSource src1 = new DataSource("weather.test.arff");
Instances tdt = src1.getDataSet();
tdt.setClassIndex(tdt.numAttributes()-1);
eval.evaluateModel(model, tdt);
System.out.println(eval.toSummaryString("Evaluation results:\n", false));
System.out.println("Correct % = "+eval.pctCorrect());
System.out.println("Incorrect % = "+eval.pctIncorrect());
System.out.println("AUC = "+eval.areaUnderROC(1));
System.out.println("kappa = "+eval.kappa());
System.out.println("MAE = "+eval.meanAbsoluteError());
System.out.println("RMSE = "+eval.rootMeanSquaredError());
System.out.println("RAE = "+eval.relativeAbsoluteError());
System.out.println("RRSE = "+eval.rootRelativeSquaredError());
System.out.println("Precision = "+eval.precision(1));
System.out.println("Recall = "+eval.recall(1));
System.out.println("fMeasure = "+eval.fMeasure(1));
System.out.println("Error Rate = "+eval.errorRate());
//the confusion matrix
System.out.println(eval.toMatrixString("=== Overall Confusion Matrix ===\n"));
}
catch(Exception e)
{
System.out.println("Error!!!!\n" + e.getMessage());
}
}
一旦我们完成了这些,我们将为我们的Evaluation类创建一个对象,并且我们将指定我们想要在哪个数据集上执行评估。因此,我们将把数据集传递给Evaluation类构造函数。然后,我们将为DataSource类创建一个新对象,我们将带着weather.test.arff文件进行测试。我们将创建一个Instances对象tdt,并将数据集分配给测试数据集tdt。
然后,我们将需要通知 Weka,tdt对象中的哪个属性是我们的类属性;因此,我们将调用setClassIndex方法。然后,我们将使用我们的Evaluation类的evaluateModel方法,并传入model和我们的测试数据集。
一旦完成,我们将一次性打印出Evaluation结果;或者,如果您愿意,您可以单独打印结果,就像我们在半监督学习练习中所做的那样。
让我们运行代码。我们将获得以下输出:
我们的模型建造成功。一旦模型建立起来,我们打印出全部结果,然后我们分别打印出结果和混淆矩阵。这就是用半监督数据建立模型的方法。
摘要
在本章中,您学习了如何训练模型以及如何评估聚类分析模型。然后,我们看了半监督学习的概念,以及它与无监督学习的区别。我们的半监督模型已经训练好了,我们现在可以根据它进行预测。
由于这是本书的最后一章,我们将总结一下我们所取得的成就。你学过机器学习的基础知识;我们已经安装了 JDK、JRE 和 NetBeans。我们研究了搜索算法,研究并实现了其中的两个:一个是 Dijkstra 的算法,另一个是它的改进(A*算法)。
你学了玩游戏,我们用井字游戏实现了一个玩游戏的算法。我们介绍了什么是基于规则的系统,并且用 Prolog 实现了一个基本的基于规则的系统;然后,我们在 Java 程序中使用了这个基于规则的系统。我们安装了 Weka 并使用数据集。我们将 CSV 文件转换成 ARFF 文件,反之亦然。然后,我们将不同种类的过滤器(监督和非监督过滤器)应用于我们的数据集。我们应用了非常发达的分类模型。我们对这些模型进行评估、保存、加载和预测。我们对聚类模型做了同样的工作;我们训练了聚类模型,并对聚类模型进行了评估。然后,您学习了半监督学习的基础知识,包括如何使用半监督学习模型。
这本书就讲到这里。谢谢你。