将写入 double 的 Java 代码更改为将 double[] 写入 CSV(用例 = WEKA 库)

Changing Java code that writes double to CSV to write double[] to CSV (use case = WEKA library)

提问人:Hack-R 提问时间:1/30/2017 最后编辑:Hack-R 更新时间:1/31/2017 访问量:1457

问:

我使用 WEKA 库编写了一个 Java 程序,

  1. 训练分类算法
  2. 使用经过训练的算法对未标记的数据集运行预测
  3. 将结果写出到 .csv 文件

问题在于它目前写出离散的分类结果(即算法猜测某行属于哪个类别)。我想要的是写出给定类的概率(例如,如果我将行分类为“垃圾邮件”或“非垃圾邮件”,那么我希望垃圾邮件的概率是结果)。

我的理解是,要做到这一点,我需要使用而不是在我的代码中。来自WEKA:distributionForInstanceclassifyInstance

如果您对所有类的分布感兴趣,请使用 方法 distributionForInstance(Instance)。此方法返回双精度 数组,其中包含每个类的概率。

我遇到的问题是,使用 classifyInstance 时,我正在处理 double 数据类型,而使用 distributionForInstance 时,我正在处理 double[] 数据类型,并且显然没有正确调整我的代码。

下面是写出谨慎预测的工作代码:

public class runPredictions {
public static void runPredictions(ArrayList al2) throws IOException, Exception{
    // Retrieve objects
    Instances newTest = (Instances) al2.get(0);
    Classifier clf = (Classifier) al2.get(1);

    // Print status
    System.out.println("Generating predictions...");

    // create copy
    Instances labeled = new Instances(newTest);

    // label instances
    for (int i = 0; i < newTest.numInstances(); i++) {
      double clsLabel = clf.classifyInstance(newTest.instance(i));
      labeled.instance(i).setClassValue(clsLabel);

    }
    System.out.println("Predictions complete! Writing output file to csv...");
    BufferedWriter outFile = new BufferedWriter(new FileWriter("C:/Users/hackr/Desktop/silverbullet_output.csv"));

    for (int i = 0; i < labeled.size(); i++)
    {
        outFile.write(labeled.get(i).toString());
        outFile.write("\n");
    }
    System.out.println("Output file written.");
    System.out.println("Completed successfully!");
    outFile.close();    
}    
}

现在我正在处理的代码如下:

   for (int i = 0; i < labeled.size(); i++)

{
    double[] clsLabel = clf.distributionForInstance(newTest.instance(i));
    //outFile.write(labeled.get(i).toString());
    outFile.write(Double.toString(clsLabel[i]));
    outFile.write("\n");
}

并抛出一个

索引越界

错误。

我还移动了 的创建,因为显然当数据类型更改时它无法再找到符号,除非我将其移动到循环内。clsLabelfor

爪哇 维卡

评论

0赞 Brendan Lesniak 1/31/2017
粗略一瞥,索引可能没有对齐,因此可能会导致您越界。该函数返回一个结果数组,而不是存储在索引中的单个结果。您需要遍历结果集才能获得预期的结果。iifor(double d : clsLabel) { write(Double.toString(d)) }
1赞 Brendan Lesniak 1/31/2017
@HackR(好吧,当使用“-”时,它会截断你的名字)。这可能不是全部,但我相信这是一个开始。如果这可行,我会将我的评论改写为答案。
0赞 Hack-R 1/31/2017
@Brendan更新 -- 是的,这完全奏效了!:)谢谢

答:

1赞 Mark Giaconia 1/31/2017 #1

假设你的输出将类似于数据透视表,类标签为列,并且我假设从你的类中返回的每个类的分数,你需要遍历数组并为每个值创建一个字段,或者只列出值。我不知道double[]数组中的值如何与类标签相关,但不知何故,您必须进行这种关联。 也许如果分类器无法分类,它会返回一个空数组,这就是您收到 IOOB 异常的原因。

评论

0赞 Hack-R 1/31/2017
谢谢。听起来你和布兰登在描述同一件事。我现在正在尝试一下。更新:这是正确的。Brendon 答案中的代码使其更容易测试,所以我会将他的答案标记为答案,但我也会为您的答案投赞成票。再次感谢。
1赞 Brendan Lesniak 1/31/2017 #2

改写我的评论。

你得到的结果本身就是一个.这意味着你不是从分布函数中得到一个值,而是将整个分布作为一个值数组。clf.distributionForInstance(newTest.instance(i));double[]

若要正确显示整体分布,需要单独遍历结果集并打印值:

for (int i = 0; i < labeled.size(); i++) {
     double[] clsLabel = clf.distributionForInstance(newTest.instance(i));
     for(double d : clsLabel) {
         outFile.write(Double.toString(d));
     }
     outFile.write("\n");
}

假设有 2 个类别(预测了 2 个类别,如“垃圾邮件”和“非垃圾邮件”),则以下工作有效:

BufferedWriter outFile = new BufferedWriter(new FileWriter("silverbullet_rro_output.csv"));
StringBuilder builder = new StringBuilder();

for (int i = 0; i < labeled.size(); i++)      
{
    double[] clsLabel = clf.distributionForInstance(newTest.instance(i));
    for(int j=0;j<2;j++){
       builder.append(clsLabel[j]+""); 
       if(j < clsLabel.length - 1)
           builder.append(",");
    }
    builder.append("\n");
}
outFile.write(builder.toString());//save the string representation
System.out.println("Output file written.");
System.out.println("Completed successfully!");
outFile.close();    

评论

0赞 Hack-R 1/31/2017
非常感谢。我现在唯一不同的是,我把新行部分放在循环中。由于每行有 2 个类,这给了我 2 倍于我需要的行数,但我可以很容易地解决这个问题。