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
,
where $MPS_INSTALL
/client/java/examples/ClassificationModelPatientData$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 behavior 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
- Deploy Model Trained in Classification Learner to MATLAB Production Server (Statistics and Machine Learning Toolbox)
- Deploy Model Trained in Regression Learner to MATLAB Production Server (Statistics and Machine Learning Toolbox)
- Marshal MATLAB Structures (Structs) in Java
- Create MATLAB Production Server Java Client Using MWHttpClient Class
- MATLAB Production Server Java Client Basics