Main Content

Evaluate Deployed Machine Learning Models Using Java Client

This example shows how to write a client application that uses the MATLAB® Production Server™ Java® client library to evaluate a machine learning model deployed to MATLAB Production Server. The Classification Learner app is used to train and export the model in this example. Typically, a MATLAB developer trains a model and exports the trained model as a deployable archive (CTF file) using either the Classification Learner app or Regression Learner app. For details, see Deploy Model Trained in Classification Learner to MATLAB Production Server (Statistics and Machine Learning Toolbox) and Deploy Model Trained in Regression Learner to MATLAB Production Server (Statistics and Machine Learning Toolbox). The Statistics and Machine Learning Toolbox™ is required to use Classification Learner and Regression Learner. A server administrator deploys the archive to a MATLAB Production Server instance.

The example provides and explains how to use a sample Java client, PredictFunctionPatientData.java, for sending patient data that is in patients.csv to a MATLAB function predictFunction deployed on the server. The result of predictFunction classifies the patient data as a smoker or nonsmoker. The example also uses helper classes PatientData.java, PatientDataBeanInfo.java, and Utils.java.

The files in the example are available online at MATLAB Production Server Client Libraries. In an on-premises MATLAB Production Server installation, the example files are located in $MPS_INSTALL/client/java/examples/ClassificationModelPatientData, where $MPS_INSTALL is the MATLAB Production Server installation location. The examples directory also contains a sample Java client to evaluate a deployed machine learning model created using the Regression Learner app. For an overview of how to write a client using the Java client library, see MATLAB Production Server Java Client Basics.

Determine Type of Input Argument for Deployed Function

In the scenario for this example, the MATLAB developer determines whether the deployed MATLAB model requires input data as a matrix or a table.

If the model requires a table, Java client programs must send an array of objects instead, since the MATLAB Production Server Java client library does not support the table (MATLAB) data type. When used as an input to a MATLAB function, Java objects get marshaled into MATLAB struct (MATLAB), since Java does not natively support MATLAB structures.

In this example, the deployed predictFunction MATLAB function requires the input in the form of an array of objects that get marshaled as structs.

Represent Input Data as Array of Objects

The file patients.csv contains the input patient data. Each row in patients.csv corresponds to one patient, and the comma separated values in each row correspond to a diagnostic variable. The Smoker variable is the response variable, and the rest of the variables—Age, Diastolic, Gender, Height, SelfAssessedHealthStatus, Systolic, Weight—are predictors.

The following sections explain how to convert the patient data from the CSV file into an array of objects. The deployed MATLAB function predictFunction requires the patient data input to be an array of objects.

Create Java Class to Represent Input Data

Create a Java class PatientData to represent data from the patients.csv file. Each object of the PatientData class represents a single row in patients.csv. The instance variable names in PatientData must be the same as the predictor variable names in patients.csv. The predictor variable names start with an uppercase letter. However, the instance variable names start with a lowercase letter, by default. Later, you see how to override this default behavior to match the casing of the predictor variable names and instance variable names.

The class definition for PatientData.java follows.

package com.mathworks;

public class PatientData {
    double age;
    double diastolic;
    String gender;
    double height;
    String selfAssessedHealthStatus;
    double systolic;
    double weight;

    public PatientData(double age, double diastolic, String gender, double height, 
                       String selfAssessedHealthStatus, double systolic, double weight) {
        this.age = age;
        this.diastolic = diastolic;
        this.gender = gender;
        this.height = height;
        this.selfAssessedHealthStatus = selfAssessedHealthStatus;
        this.systolic = systolic;
        this.weight = weight;
    }

    public double getAge() {
        return age;
    }

    public void setAge(double age) {
        this.age = age;
    }

    public double getDiastolic() {
        return diastolic;
    }

    public void setDiastolic(double diastolic) {
        this.diastolic = diastolic;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    public String getSelfAssessedHealthStatus() {
        return selfAssessedHealthStatus;
    }

    public void setSelfAssessedHealthStatus(String selfAssessedHealthStatus) {
        this.selfAssessedHealthStatus = selfAssessedHealthStatus;
    }

    public double getSystolic() {
        return systolic;
    }

    public void setSystolic(double systolic) {
        this.systolic = systolic;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "PatientData{" +
                "age=" + age +
                ", diastolic=" + diastolic +
                ", gender='" + gender + '\'' +
                ", height=" + height +
                ", selfAssessedHealthStatus='" + selfAssessedHealthStatus + '\'' +
                ", systolic=" + systolic +
                ", weight=" + weight +
                '}';
    }
}

Map Java Instance Variables to MATLAB Structure Members

Java classes that represent MATLAB structures use the Java Beans Introspector class to map instance variables to fields of the structure. For more information about the Introspector class, see the Oracle Documentation for Class Introspector. These classes also use the default naming conventions of the Introspector class. The default convention uses the decapitalize method that maps the first letter of a Java variable name into a lowercase letter. Therefore, using the default, you cannot define a Java instance variable that maps to a MATLAB structure member that starts with an uppercase letter. You can override this behaviour by implementing a SimpleBeanInfo class with a custom getPropertyDescriptors() method.

The example uses a PatientDataBeanInfo class that implements the SimpleBeanInfo interface and sets the first letter of the instance variables of PatientDataBeanInfo to uppercase. The code for PatientDataBeanInfo class follows.

package com.mathworks;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.beans.SimpleBeanInfo;

public class PatientDataBeanInfo extends SimpleBeanInfo
{
    @Override
    public PropertyDescriptor[] getPropertyDescriptors()
    {
        PropertyDescriptor[] props = new  PropertyDescriptor[7];
        try
        {
            props[0] = new PropertyDescriptor("Age",PatientData.class,
                    "getAge","setAge");
            props[1] = new PropertyDescriptor("Diastolic",PatientData.class,
                    "getDiastolic","setDiastolic");
            props[2] = new PropertyDescriptor("Gender",PatientData.class,
                    "getGender","setGender");
            props[3] = new PropertyDescriptor("Height",PatientData.class,
                    "getHeight","setHeight");
            props[4] = new PropertyDescriptor("SelfAssessedHealthStatus",PatientData.class,
                    "getSelfAssessedHealthStatus","setSelfAssessedHealthStatus");
            props[5] = new PropertyDescriptor("Systolic",PatientData.class,
                    "getSystolic","setSystolic");
            props[6] = new PropertyDescriptor("Weight",PatientData.class,
                    "getWeight","setWeight");

            return props;
        }
        catch (IntrospectionException e)
        {
            e.printStackTrace();
        }

        return null;
    }
}

Prepare Data for Input to Deployed Function

The example provides a class Utils.java that contains utility functions that convert the data from the patients.csv file to an array of objects as expected by the deployed predictFunction MATLAB function. Utils.java contains a function that reads the data from the CSV file and converts it into an ArrayList class. Utils.java contains another function that converts the ArrayList class into an array of PatientData objects.

Code for Utils.java follows.

package com.mathworks;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.StringTokenizer;

public class Utils {
    String fileName;

    Utils(String fileName) {
        this.fileName = fileName;
    }

    public static ArrayList<String[]> readFromCsv() {
        Scanner sc = null;
        ArrayList<String[]> inputs = new ArrayList<String[]>();

        try {
            sc = new Scanner(new File("patients.csv"));

            sc.useDelimiter("\n");  
            sc.next();
            String line;

            while (sc.hasNext()) 
            {
                line = sc.next();
                StringTokenizer tokenizer = new StringTokenizer(line, ",");
                String[] tmp = new String[tokenizer.countTokens() - 1];
                for (int i = 0; i < tmp.length; i++) {
                    String s = tokenizer.nextToken();
                    if (s.compareTo("Inf") == 0) {
                        tmp[i] = String.valueOf(Double.POSITIVE_INFINITY);
                    } else if (s.compareTo("-Inf") == 0) {
                        tmp[i] = String.valueOf(Double.NEGATIVE_INFINITY);
                    } else {
                        tmp[i] = s;
                    }
                }
                inputs.add(tmp);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally{
            sc.close();
        }

        return inputs;
    }

    public static PatientData[] prepareDataForTableInput() {
        ArrayList<String[]> inputs = readFromCsv();
        int i = 0;
        PatientData[] datas = new PatientData[inputs.size()];
        for (String[] mat : inputs) {
            datas[i] = new PatientData(Double.valueOf(mat[0]), Double.valueOf(mat[1]), mat[2],
                                       Double.valueOf(mat[3]), mat[4] ,Double.valueOf(mat[5]), Double.valueOf(mat[6]));
            i++;
        }

        return datas;
    }
}

Write Client Application

In the client application, define a Java interface that represents the deployed MATLAB function. Then, instantiate a proxy object to communicate with the server and call the deployed function.

Since the deployed function expects MATLAB structures as input, you must use the MWStructureList annotation for the interface to include the PatientData class that you created earlier. For an additional example on how to marshal MATLAB structures in Java, see Marshal MATLAB Structures (Structs) in Java. For an example on instantiating a proxy object and calling deployed functions, see Create MATLAB Production Server Java Client Using MWHttpClient Class.

The code for the client application PredictFunctionPatientData.java follows.

package com.mathworks;

import com.mathworks.mps.client.MATLABException;
import com.mathworks.mps.client.MWClient;
import com.mathworks.mps.client.MWHttpClient;
import com.mathworks.mps.client.annotations.MWStructureList;

import java.io.IOException;
import java.net.URL;

interface CallMethod {
    @MWStructureList({PatientData.class})
    boolean[] predictFunction(PatientData[] dataSet) throws IOException, MATLABException;
}

public class PredictFunctionPatientData {

    public static void main(String[] args) {
        PatientData[] datas = Utils.prepareDataForTableInput();

        MWClient client = new MWHttpClient();
        try {            
            CallMethod s = client.createProxy(new URL("http://localhost:9910/DeployedClassificationModel"),
                                              CallMethod.class);
          
            boolean[] results = s.predictFunction(datas);
            for (int j = 0; j < results.length; j++) {
                System.out.println(results[j]);
            }
        } catch (MATLABException ex) {
            System.out.println(ex);
        } catch (IOException ex) {          
            System.out.println(ex);
        } finally {
            client.close();
        }
    }
}

Related Topics

External Websites