Re: How to classify unseen multi instance data?

Peter Reutemann <[email protected]> Wed, 7 Feb 2024 10:58:30 +1300
Newsgroups gmane.comp.ai.weka
Message-ID <CAHoQ12JsgCWn5fs29dHc0K5VS_ekGGHfkpEBquhFmYFac0uBgw@mail.gmail.com>
Apologies, took a bit longer to reply, had a lot of work going on...

> >>> At prediction time you re-use (rather than re-create) the header from
> >>> the training data to create new Instance objects. These Instance
> >>> objects you then present to the classifier to obtain predictions.
> >>
> >> Are there any examples for how this procedure works with multi-instance
> >> data?
> >
> > There is no difference to non-multi-instance data.
> > The wiki example demonstrates how to fill a relational attribute with
> > values (I may have already posted that before):
> > https://waikato.github.io/weka-wiki/formats_and_processing/creating_arff_file/
>
> I meant actual Java code that demonstrates how to re-use an existing
> header during classification. I did not find any such examples and could
> not get it to work myself.

See attached example code that trains a MIWrapper classifier on the
east-west relational dataset (also attached), serializes the
model/header, reloads the models/header, makes predictions on data.

> > It depends. If you've saved a model with the Explorer, then it would
> > contain the header (an empty Instances object with the structure) as
> > the second object in the serialized file.
>
> If that was the case, then shouldn't I find a call to
> SerializationHelper.writeAll somewhere in the Weka sources? However,
> there's only SerializationHelper.write in core/Debug.java. I downloaded
> the sources from https://git.cms.waikato.ac.nz/weka/weka/-/tree/main/trunk
>
> > If you don't actually save your model anywhere, but just keep in
> > memory, then you would create an empty copy of the training data with
> > something like:
> > Instances train = ...  // your training data
> > Instances header = new Instances(train, 0);
>
> When I create the header like that and save it with the
> SerializationHelper I am getting a file of 110MB, of which the actual
> classifier has less than 1 MB. So somehow this header appears to be very
> large, even though it does not contain any instances. My guess is that
> it still contains all the instances from the training data, even though
> they're not accessible anymore.

The problem is that the relational attribute values are still being
kept (I never really used such data much). You can use the
stringFreeStructure method to remove unnecessary references:
https://weka.sourceforge.io/doc.dev/weka/core/Instances.html#stringFreeStructure--

NB: This method dates back to days before relational attributes were a
thing, when string attributes required cleaning up.

> >>> The RELAGGS filter is specifically designed for processing data from a
> >>> relational database (the original code worked straight off JDBC
> >>> databases):
> >>> https://weka.sourceforge.io/doc.stable/weka/filters/unsupervised/attribute/RELAGGS.html
> >>
> >> Are there any examples how to use this filter in Java? What kind of
> >> processing does it do?
> >
> > You can apply it straight to your multi-instance data. Works just like
> > any other filter.
>
> Yes, sure, but what does this filter actually do? What is my benefit
> from using it?

RELAGGS uses SQL aggregation methods like COUNT, MIN, MAX, AVG, SUM,
etc on the relational data to flatten it into a propositional, single
table.
A long time ago, when I was working with relational/multi-instance
data, algorithms trained on the RELAGGS-flattened data tended to
outperform other multi-instance algorithms that worked on the
multi-instance data.

Cheers, Peter
-- 
Peter Reutemann
Dept. of Computer Science
University of Waikato, Hamilton, NZ
Mobile +64 22 190 2375
https://www.cs.waikato.ac.nz/~fracpete/
http://www.data-mining.co.nz/

_______________________________________________
Wekalist mailing list -- [email protected]
Send posts to [email protected]
To unsubscribe send an email to [email protected]
To subscribe, unsubscribe, etc., visit https://list.waikato.ac.nz/postorius/lists/wekalist.list.waikato.ac.nz
List etiquette: http://www.cs.waikato.ac.nz/~ml/weka/mailinglist_etiquette.html
MultiInstData.java (text/x-java, 3.9 KB)
import weka.classifiers.Classifier;
import weka.classifiers.mi.MIWrapper;
import weka.classifiers.trees.J48;
import weka.core.DenseInstance;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.SerializationHelper;
import weka.core.Utils;
import weka.core.converters.ConverterUtils.DataSource;

public class MultiInstData {

  public static void main(String[] args) throws Exception {
    // build classifier
    System.out.println("Train/save classifier...");
    Instances data = DataSource.read("/home/fracpete/development/datasets/multi_instance/eastwest_relational.arff");
    data.setClassIndex(data.numAttributes() - 1);
    MIWrapper cls = new MIWrapper();
    cls.setClassifier(new J48());
    cls.buildClassifier(data);
    String modelFile = System.getProperty("java.io.tmpdir") + "/mi.model";
    SerializationHelper.writeAll(modelFile, new Object[]{cls, data.stringFreeStructure()});

    // make predictions on training set
    System.out.println("Predict training set...");
    for (int i = 0; i < data.numInstances(); i++) {
      double act = data.instance(i).classValue();
      double pred = cls.classifyInstance(data.instance(i));
      System.out.println(i + ": a=" + act + ", p=" + pred + ", error=" + (act != pred));
    }

    // load classifier/header
    System.out.println("Load classifier...");
    Object[] objs = SerializationHelper.readAll(modelFile);
    Classifier cls2 = (Classifier) objs[0];
    Instances header = (Instances) objs[1];

    // make predictions
    // https://waikato.github.io/weka-wiki/formats_and_processing/creating_arff_file/
    System.out.println("Make predictions...");
    // bag data
    double[][][] test = new double[][][]{
      {
	// first instance from training data: class 0
	{1,1,0,0,0,0,0,1,0,1,0,0,0,1,0,2,0,0,0,1,0,0,1,0},
	{2,0,0,0,1,0,1,0,0,1,0,1,0,0,0,2,0,0,0,0,0,1,3,0},
	{1,1,0,0,0,0,0,1,0,1,0,0,0,1,0,2,0,0,0,1,0,0,1,1},
	{2,0,0,0,1,0,1,0,0,1,0,1,0,0,0,2,0,0,0,0,0,1,3,1},
      },
      {
	// second instance from training data: class 1
	{1,0,0,0,1,0,0,1,0,1,0,0,0,1,0,2,1,0,0,0,0,0,1,0},
	{2,0,0,0,1,0,1,0,0,1,0,0,0,1,0,3,0,0,1,0,0,0,1,0},
	{3,0,0,0,1,0,0,1,0,1,0,0,0,0,1,2,0,0,0,0,1,0,1,0},
	{4,0,0,0,1,0,1,0,0,1,0,0,0,1,0,2,0,0,0,1,0,0,3,0},
	{1,0,0,0,1,0,0,1,0,1,0,0,0,1,0,2,1,0,0,0,0,0,1,1},
	{2,0,0,0,1,0,1,0,0,1,0,0,0,1,0,3,0,0,1,0,0,0,1,1},
	{3,0,0,0,1,0,0,1,0,1,0,0,0,0,1,2,0,0,0,0,1,0,1,1},
	{4,0,0,0,1,0,1,0,0,1,0,0,0,1,0,2,0,0,0,1,0,0,3,1},
	{1,0,0,0,1,0,0,1,0,1,0,0,0,1,0,2,1,0,0,0,0,0,1,2},
	{2,0,0,0,1,0,1,0,0,1,0,0,0,1,0,3,0,0,1,0,0,0,1,2},
	{3,0,0,0,1,0,0,1,0,1,0,0,0,0,1,2,0,0,0,0,1,0,1,2},
	{4,0,0,0,1,0,1,0,0,1,0,0,0,1,0,2,0,0,0,1,0,0,3,2},
	{1,0,0,0,1,0,0,1,0,1,0,0,0,1,0,2,1,0,0,0,0,0,1,3},
	{2,0,0,0,1,0,1,0,0,1,0,0,0,1,0,3,0,0,1,0,0,0,1,3},
	{3,0,0,0,1,0,0,1,0,1,0,0,0,0,1,2,0,0,0,0,1,0,1,3},
	{4,0,0,0,1,0,1,0,0,1,0,0,0,1,0,2,0,0,0,1,0,0,3,3},
      },
      {
	// fifth instance from training data: class 0
	{1,0,0,0,0,1,0,1,0,1,0,0,0,1,0,2,1,0,0,0,0,0,1,0},
	{2,0,0,0,1,0,1,0,0,1,0,1,0,0,0,3,0,0,0,1,0,0,1,0},
	{1,0,0,0,0,1,0,1,0,1,0,0,0,1,0,2,1,0,0,0,0,0,1,1},
	{2,0,0,0,1,0,1,0,0,1,0,1,0,0,0,3,0,0,0,1,0,0,1,1},
      },
    };
    for (int i = 0; i < test.length; i++) {
      double[] values = new double[header.numAttributes()];
      values[0] = i;   // bag id
      values[2] = Utils.missingValue();  // class value
      // bag
      Instances dataRel = new Instances(header.attribute(1).relation(), 0);
      for (int n = 0; n < test[i].length; n++) {
	double[] valuesRel = new double[dataRel.numAttributes()];
	for (int m = 0; m < test[i][n].length; m++)
	  valuesRel[m] = test[i][n][m];
	dataRel.add(new DenseInstance(1.0, valuesRel));
      }
      values[1] = header.attribute(1).addRelation(dataRel);
      Instance inst = new DenseInstance(1.0, values);
      inst.setDataset(header);
      double pred = cls2.classifyInstance(inst);
      String label = header.classAttribute().value((int) pred);
      System.out.println(i + ": " + label);
    }
  }
}
eastwest_relational.arff (application/octet-stream, 11.3 KB) - not displayed