0

I trained and created a MultilayerPerceptron model using weka.jar version 3.6.10 . I saved the model file to my computer and now I would like to use it to classify a single instance in my Java code. I would like to get a prediction for the attribute "class". i found answer here And I changed the values to what I needed. What I do is the following:

import weka.classifiers.Classifier;
import weka.classifiers.functions.MultilayerPerceptron;
import weka.core.Attribute;
import weka.core.FastVector;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.SparseInstance;
import weka.core.SerializationHelper;
public class JavaApplication {

    public static void main(String[] args) {
        JavaApplication q = new JavaApplication();
        double result = q.classify(-1.18,12.76,1.7297841);
        System.out.println(result);
    }

    private Instance inst_co;

    public double classify(double x, double y, double z)  {

        // Create attributes to be used with classifiers
        // Test the model
        double result = -1;
        try {

            FastVector attributeList = new FastVector();

            Attribute x_acc= new Attribute("x_acc");
            Attribute y_acc= new Attribute("y_acc");
            Attribute z_acc= new Attribute("z_acc");

            FastVector classVal = new FastVector();
            classVal.addElement("Walking");
            classVal.addElement("Jogging");
            classVal.addElement("Downstairs");
            classVal.addElement("Sitting");
            classVal.addElement("Upstairs");


            attributeList.addElement(x_acc);
            attributeList.addElement(y_acc);
            attributeList.addElement(z_acc);
            attributeList.addElement(new Attribute("@@class@@",classVal));

            Instances data = new Instances("TestInstances",attributeList,0);


            // Create instances for each pollutant with attribute values latitude,
            // longitude and pollutant itself
            inst_co = new SparseInstance(data.numAttributes());
            data.add(inst_co);

            // Set instance's values for the attributes "latitude", "longitude", and
            // "pollutant concentration"
            inst_co.setValue(x_acc, x);
            inst_co.setValue(y_acc, y);
            inst_co.setValue(z_acc, z);
            // inst_co.setMissing(cluster);
            

            // load classifier from file
           Classifier cls_co = (MultilayerPerceptron) SerializationHelper
                   .read("/Users/ALL-TECH/Desktop/Sensors application/FewDataGenerated/model.model");

            result = cls_co.classifyInstance(inst_co);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return result;
    }
}

and my arff file looks like this :

@RELATION fewOfDataCsv

@ATTRIBUTE x_acc  NUMERIC
@ATTRIBUTE y_acc  NUMERIC
@ATTRIBUTE z_acc  NUMERIC
@ATTRIBUTE class  {Upstairs,Downstairs,Walking,Jogging,Sitting}

@DATA
-1.18,12.76,1.7297841 ,Upstairs
0.93,10.99,0.08172209 ,Upstairs
0.08,11.35,0.46309182 ,Upstairs
1.88,9.47,3.405087 ,Walking
0.89,9.38,3.3778462 ,Walking
1.38,11.54,3.336985 ,Walking
2.83,3.68,-3.255263 ,Jogging
-1.8,2.45,7.082581 ,Jogging
16.63,9.89,-1.56634 ,Jogging
12.53,1.88,-6.3198414 ,Jogging
7.46,2.3,6.4 ,Sitting
7.5,2.3,6.44 ,Sitting
7.46,2.3,6.47 ,Sitting
-1.23,8.28,0.040861044 ,Downstairs
-1.92,6.28,1.1441092 ,Downstairs
-1.73,5.75,2.152015 ,Downstairs

the result (i really don't know where that number coming from ):

run:
3.0
BUILD SUCCESSFUL (total time: 1 second)

there is somthing missing in my code ? if someone can help i will be thankful .

ILii
  • 83
  • 3
  • 10

2 Answers2

0

In your main method, you are calling the JavaApplication.classify method, which returns the classification from the classifier (a double). Which in turn you are outputting to stdout via System.out.println. Why are you confused where the number is coming from?

The Classifier.classifyInstance method returns a double. In case of regression algorithms this is the predicted numeric value and in case of classification algorithms this is the 0-based index of the predicted class label. The number 3.0 corresponds to Jogging in your case.

fracpete
  • 2,448
  • 2
  • 12
  • 17
  • The number 3.0 doesn't corresponds to Jogging in my case. i tried to change the place of each element and put jogging in somwhere else but the number is still 3.0 & as u can see in my arff file the first element is -1.18,12.76,1.7297841 ,Upstairs so not jogging – ILii May 01 '21 at 10:51
  • `3.0` is the *prediction* of the model, which corresponds to `Jogging`. `Upstairs` is the *actual* value as stored in your ARFF file. – fracpete May 02 '21 at 05:45
  • i changed the order of the values in my code i put Jogging at the end of FastVector & then at the top but when i run the code keep giving me the same value whcich is 3.0 @fracpete any seggustion ? – ILii May 02 '21 at 11:14
0

Here is a rewrite of your code to address the following short comings:

  • You recreate the dataset structure every time you make a prediction
  • You load the model every time you make a prediction
  • Using weka.core.SparseInstance makes no sense as you only have three attributes and you always seem to supply them

When saving a model from the Weka Explorer or through the command-line, Weka not only stores the weka.classifiers.Classifier object in that file, but also the header (aka structure) of the training data. You can simply use that header for the structure of your weka.core.Instance object that you are constructing.

The following class expects to get the path to the model as the first parameter and then loads model and header from that file in the constructor.

I also added a get-method for the header to allow turning the classification label index (which Classifier.classifyInstance returns in case of a nominal class attribute) into an actual class label string.

public class JavaApplication {

  private Instances header;

  private Classifier model;

  public JavaApplication(String modelPath) throws Exception {
    Object[] objects = SerializationHelper.readAll(modelPath);
    model = (Classifier) objects[0];
    header = (Instances) objects[1];
  }

  public Instances getHeader() {
    return header;
  }

  public int classify(double x, double y, double z)  {
    int result = -1;
    try {
      double[] values = new double[header.numAttributes()];
      values[0] = x;
      values[1] = y;
      values[2] = z;
      Instance inst = new DenseInstance(1.0, values);
      inst.setDataset(header);
      result = (int) model.classifyInstance(inst);
    }
    catch (Exception e) {
      System.err.println("Failed to classify instance!");
      e.printStackTrace();
    }
    return result;
  }

  public static void main(String[] args) throws Exception {
    JavaApplication q = new JavaApplication(args[0]);
    int labelIndex = q.classify(-1.18, 12.76, 1.7297841);
    System.out.println(labelIndex);
    System.out.println(q.getHeader().classAttribute().value(labelIndex));
  }
}
fracpete
  • 2,448
  • 2
  • 12
  • 17
  • thank u for ur time, but ur code doesn't work for me it return me Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1 .. in this ligne (header = (Instances) objects[1];) so i think now maybe the problem im my model .. – ILii May 03 '21 at 22:14
  • Train/save your model with either the Weka Explorer or the command-line, then the header will be present (2nd object in the model file). – fracpete May 04 '21 at 03:55