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();
}
}
}
See Also
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