This section describes generating scoring code based on models. We assume that the user is familiar with model building (see the chapter AdvancedMiner in Practice and the Modules part).
Scoring code is generated for an existing model. However, it is also possible to generate scoring code without any model. In such case the user has to write the whole scoring code manually.
The only requirement is to use a model for which scoring code generation is supported.
Table 22.1. AdvancedMiner Modules supporting scoring code generation
| Module name | Scoring code generation support |
|---|---|
| Classification Tree | yes |
| KMeans | yes |
| Discriminant Analysis | yes |
| Feed Forward Neural Networks | yes |
| Kohonen Networks | yes |
| Linear Regression | yes |
| Logistic Regression | yes |
| Bivariate | yes |
| Survival Analysis | yes |
| Time Series | yes |
| Scoring Card | no |
This example scoring code is based on a model built using the KMeans clustering algorithm. It is assumed that the user has already built the model using the KMeans algorithm.
In the first step create a task which generates the scoring code.
Next set the following properties:
Table 22.2. Properties of ScoringCodeBuildTask
| Property name | Description |
|---|---|
| Code Language | Programming language used for the scoring code |
| Can Generate With Compilation Errors | If this option is set, the scoring model will be created despite compilation errors. If this option is not set, in the case of errors in the scoring code, the scoring code model will not be created. |
Next, set the following values in ScoringCodeBuildTask in the Repository
Table 22.3. ScoringCodeBuildTask properties
| Property name | Description |
|---|---|
| Input Model | The name of the input model on which the scoring code will be based on. If this property is empty, the scoring code will consist only of an empty template and the body of the code has to be written by the user. |
| Output Model | The name of the output model. This model can be used for ScoringCodeApplyTask |
| Transformation | The transformation. This transformation will be included in the scoring code. |
After adjusting the settings the scoring code build task can be executed in the standard way.
If the code was based on a model there should be no compile errors since the code syntax is checked at this stage. Functional correctness is not verified at this step.
The model created at this stage has following properties:
Table 22.4. ScoringCodeModel properties
| Property name | Description |
|---|---|
| Code Body | The generated scoring code. This is a standalone Java class called ScoringCode. The details are in the section Architecture of Java scoring code section. |
| Code Language | The language used for generating the scoring code. This property cannot be modified. It should be set during the generation precess. |
| Missing Values Supported | This flag is set by the generated code and is used for informational purposes only. The flag is set to true if the scoring code works with missing values, and to false otherwise. |
Usually the data taken from a database or data warehouse cannot be immediately used in the model. This is caused by missing values, unnormalized values, or other factors. The transformation is used to prepare the data for processing. The transformation code is added to the ScoringCode class as a method. This method is executed in the specified order before the processRow method. The details are in the Architecture of Java scoring code section. The user has to supply the input data according to the Input signature.
The mapping of attributes can be done in two exclusive ways. It depends on whether the code contains transformations or not.
The first posiibility: There is no transformation in the code. The data is in accordance with InputSignature, which is the same as Signature.
The second possibilty: The code contains a transofrmation. The data is mapped according to InputSignature. Next, the transformation code is executed. After that the data is mapped according to Signature.
The correct scoring code consists of a class named ScoringCode which contains the following elements:
a public static class InputSignature
a public static class Signature
a public static class OutputStructure
a public method prepareDataemphasis>
a public method processRow
optionally, a method scoreData, which executes prepareData and processRow.
All these elements are described in the subsections below.
Below is a sample scoring code with a transformation based on a model generated by the k-Means algorithm:
Example 22.1. Scoring code with transformations based on the K-means algorithm
import java.util.HashMap;
public class ScoringCode {
/** Temporary map used by prepareData method */
private HashMap tempInput = new HashMap();
/** Temporary map used by prepareData method */
private HashMap tempOutput = null;
/** Transformation object */
private Transformation0 trans0 = new Transformation0();
/** scoreData is main scoring method. Input data have to be provided in correct order.
* Please refer to InputSignature to see which data types are supported and how to provide data in correct order.
* Transformation (if apply) is done by prepareData method. PrepareData method also deals with categorical data encoding
* @param input - input data to score
* @param output - OutputStructure - will contain scoring output
*/
public void scoreData(Object [] input, OutputStructure output) {
processRow(prepareData(input), output);
}
/** getCategoricalValueCode - gets codeded double value for corresponding catgorical
*/
public double getCategoricalValueCode(HashMap data, String attrName, int attrIndex) {
double output=Double.NaN; for(int i=0; i<Signature.INPUT_ATTRIBUTES_VALUE_SET[attrIndex].length; i++){
if(data.get(attrName).equals(Signature.INPUT_ATTRIBUTES_VALUE_SET[attrIndex][i])){
output=i;
break;
}
}
if( output==Double.NaN )
throw new RuntimeException("Value "+tempOutput.get(attrName).toString()+" is not in input attributes value set");
return output;
}
/** prepareData - prepares data for processRow method.
* It does all necesary mapping, transformation and categorical
* attributes encoding.
* @param inputData - provided in correct order data for scoring - according to InputSignature
* @return - array of double consits of ready to score data
*/
public double [] prepareData(Object [] inputData) {
tempInput.clear();
//Input attributes mapping
tempInput.put("x", inputData[0]);
tempInput.put("y", inputData[1]);
tempInput.put("z", inputData[2]);
tempOutput = trans0.transformRow(tempInput);
//Output attributes mapping
double [] output = new double[3];
//Initialize output array with Double.NaN's
java.util.Arrays.fill(output, Double.NaN);
if(tempOutput.get("x") != null )
if(tempOutput.get("x") instanceof Number)
output[0]=((Number)tempOutput.get("x")).doubleValue();
else
throw new RuntimeException("Data type doesn't match to data type specified in inputSignature. Can't continue. ");
if(tempOutput.get("y") != null )
if(tempOutput.get("y") instanceof Number)
output[1]=((Number)tempOutput.get("y")).doubleValue();
else
throw new RuntimeException("Data type doesn't match to data type specified in inputSignature. Can't continue. ");
if(tempOutput.get("z") != null )
if(tempOutput.get("z") instanceof Number)
output[2]=((Number)tempOutput.get("z")).doubleValue();
else
throw new RuntimeException("Data type doesn't match to data type specified in inputSignature. Can't continue. ");
return output;
}
/** InputSignature class contains data signature, which has to be passed to scoreData method.
* This structure shows attributes in name-type pairs and their correct order in input data
*/
public static class InputSignature {
public static Object [][] INPUT_ATTRIBUTES = new Object [][] {
{"x", Number.class }, // inputRow[0]
{"y", Number.class }, // inputRow[1]
{"z", Number.class } // inputRow[2]
};
}
/** Signature - this class describes how data should be passed to method processRow
* by Transformation. If there is no transformation this is the same as InputSignature.
* This structure shows attributes in pairs name-type and their correct order in input data
* It also presents the set of categorical values set.
*/
public static class Signature {
/** INPUT_ATTRIBUTES - this array contains names-types pairs which describes input attrubutes
* The Number in comment represents valid position of particular attribute in input data
*/
public static Object [][] INPUT_ATTRIBUTES = new Object [][] {
{"x", double.class }, // inputRow[0]
{"y", double.class }, // inputRow[1]
{"z", double.class } // inputRow[2]
};
/** INPUT_ATTRIBUTES_VALUE_SET contains information about the encoding of categorical attributes
* Codes for values are assigned according to order of attributes in array. First attribute has code equal to 0,
* second atribute has code equal to 1, and so on.
*/
public static Object [][] INPUT_ATTRIBUTES_VALUE_SET = new Object [][] {
{ null }, // x value set -> inputRow [0]
{ null }, // y value set -> inputRow [1]
{ null } // z value set -> inputRow [2]
};
}
/** OutputStructure encloses information about names and types which can be obtained from processRow method
* This structure can contain the following types: double, int, String, double [], int [].
* OutputStructure is used by AdvancedMiner to determine the output.
*/
public static class OutputStructure
{
public int clusterID;
public double[] distance = new double [3];
}
/////////////////////////// DATA TAKEN FROM AdvancedMiner MODEL //////////////////////////////////////
public final int clustersCount = 3;
public final int clustersCoordinatesCount = 3;
protected double clustersCoordinates[][] = new double[clustersCount][clustersCoordinatesCount];
///////////////////////////////////// SCORING CODE ////////////////////////////////////////////
/** calculateDistance - this method is used to calculate distances between
* sample coordinaties and cluster determined by clusterID
* @param sampleCoordinates - array of sample coordinates
* @param clusterID - cluster identifier
* @return distance between cluster and sample
*/
protected double calculateDistance(double [] sampleCoordinates, int clusterID) {
double distance=0;
for(int i=0; i<clustersCoordinatesCount; i++) {
double dx = sampleCoordinates[i]-clustersCoordinates[clusterID][i];
distance+=(dx*dx);
}
return Math.sqrt(distance);
}
/** processRow - works with a single data row.
* Data returned by processRow method is placed in OutputStructure output
* <b>Remarks:<b>
* Do not use this function, it uses mapped, and coded values in double array. Better use scoreData method.
* @param input - array of double data
* @param output - this structure will be the scoring output.
*/
public void processRow(double [] input, OutputStructure output) {
//Input data checking
for(int i=0; i<input.length; i++){
if(input[i]==Double.NaN)
throw new RuntimeException("Non numerical input data not supported");
}
if(input.length != clustersCoordinatesCount)
throw new RuntimeException("Input array size doesn't match clusters count ("+clustersCount+")");
//Computing distances between input data and cluster coordinates
double minDistance = 0;
int bestClusterID = 0;
for(int i=0; i<clustersCount; i++) {
double distance = calculateDistance(input, i);
if(i == 0)
minDistance = distance;
else
if(distance < minDistance) {
minDistance = distance;
bestClusterID = i;
}
output.distance[i]=distance;
}
//Calculated best cluster identifier is assigned to output
output.clusterID = bestClusterID;
}
/////////////////////////////////// END OF SCORING CODE //////////////////////////////////////////
/**Transformation0 - transformation.
* Transformation uses HashMaps so no mapping of attributes is needed.
*/
public static class Transformation0 {
public Transformation0(){}
/** transformRow - transforms one data row
* @param inputRow - input data row in HashMap
* @return transformed data row in HashMap
*/
public HashMap transformRow(HashMap inputRow) {
HashMap outputRow = new HashMap();
outputRow.putAll(inputRow);
if (inputRow.get("z") == null) {
outputRow.put("z", new Double(7.6));
}
else
outputRow.put("z", inputRow.get("z"));
if (inputRow.get("y") == null) {
outputRow.put("y", new Double(6.6));
}
else
outputRow.put("y", inputRow.get("y"));
if (inputRow.get("x") == null) {
outputRow.put("x", new Double(7.4));
}
else
outputRow.put("x", inputRow.get("x"));
return outputRow;
}
}
public ScoringCode() {
clustersCoordinates[0][0] = 5.666666666666667;
clustersCoordinates[0][1] = 3.3333333333333335;
clustersCoordinates[0][2] = 4.333333333333333;
clustersCoordinates[1][0] = 10.877564619118836;
clustersCoordinates[1][1] = 9.53952535719102;
clustersCoordinates[1][2] = 7.950674407041298;
clustersCoordinates[2][0] = 10.0;
clustersCoordinates[2][1] = 11.5;
clustersCoordinates[2][2] = 12.5;
}
}
The scoring code includes two input signatures:
InputSignature is a signature for the incoming data. It describes the order and data types which are in the input data set for the scoreData method (the data set is in a table of Java Objects, it can also be the input data for the prepareData method in the case when this method is executed from an API).
Signature describes the order and data types of the input data for the procesRow method. The processRow method maps the data and encodes the categorical attributes.
The InputSignature class contains a public static two-dimensional table named INPUT_ATTRIBUTES. It contains the information about names and types of all attributes. The comments in the code contain the information about the correct order of input attributes.
public static class InputSignature {
public static Object [][] INPUT_ATTRIBUTES = new Object [][] {
{"x", Number.class }, // inputRow[0]
{"y", Number.class }, // inputRow[1]
{"z", Number.class } // inputRow[2]
};
}
The first element at each row of the INPUT_ATTRIBUTES table is the name of the attribute. This value is contained in a String object. The second element in the row is the name of the class which represents the type of the attribute. The type of then attribute depends on the input data in model or the transformation.
If there are transformations in the scoring code, the InputSignature describes the signature of the first transformation.
The Signature class contains at least two two-dimensional tables. The first one is named INPUT_ATTRIBUTES and describes the order and types of attributes in the input data. This table is similar to the INPUT_ATTRIBUTES table in the InputSignature class.
The types of attributes have a more detailed representation (example double.class). The order of attributes is different than in InputSignature.
The other table in Signature is called INPUT_ATTRIBUTES_VALUE_SET. This table is a public static two-dimension table. One row in this table corresponds to one attribute. In the scoring source code each row of this table is commented. Comments contain the information about the attribute which concerns the data set
In the case of numerical attributes there will be null values in INPUT_ATTRIBUTES_VALUE_SET. In the case of categorical attributes it will contain all possible values of this attribute. The indexes of these values are used to code this attribute in the prepareData method. I.e. for the set {"Alpha", "Beta", "Theta"}, the "Alpha" will be coded as 0, "Beta" as 1 and so on.
public static class Signature {
/** INPUT_ATTRIBUTES - this array contains names-types pairs which describes input attrubutes
* The Number in comment represents valid position of particular attribute in input data
*/
public static Object [][] INPUT_ATTRIBUTES = new Object [][] {
{"x", double.class }, // inputRow[0]
{"y", double.class }, // inputRow[1]
{"z", double.class } // inputRow[2]
};
/** INPUT_ATTRIBUTES_VALUE_SET contains information about the encoding of categorical attributes
* Codes for values are assigned according to order of attributes in array. First attribute has code equal to 0,
* second atribute has code equal to 1, and so on.
*/
public static Object [][] INPUT_ATTRIBUTES_VALUE_SET = new Object [][] {
{ null }, // x value set -> inputRow [0]
{ null }, // y value set -> inputRow [1]
{ null } // z value set -> inputRow [2]
};
}
This construction of input signatures gathers the information about the manner of automatic input data preparation.
OutputSturcture contains the specification of the model output. It may contain simple types or arrays of simple types. Arrays of String objects cannot be used.
/** OutputStructure encloses information about names and types which can be obtained from processRow method
* This structure can contain the following types: double, int, String, double [], int [].
* OutputStructure is used by AdvancedMiner to determine the output.
*/
public static class OutputStructure
{
public int clusterID;
public double[] distance = new double [3];
}
Data scoring is divided into two steps. The First ithe preparation of the input data (for example coding categorical attributes, execution of the chain of transformations (this is supplemental)). The second is the processing of the data. All these steps are included in the scoreData method. This methods is called for each row of data.
/** scoreData is the main scoring method. Input data has to be provided in the correct order.
* Please refer to InputSignature to see which data types are supported and how to provide data in the correct order.
* Transformation (if applies) is done by the prepareData method. PrepareData method also deals with categorical data encoding
* @param input - input data to score
* @param output - OutputStructure - will contain scoring output
*/
public void scoreData(Object [] input, OutputStructure output) {
processRow(prepareData(input), output);
}
In the beginning prepareData is called, next processRow is called.
The scoreData method takes the input data as a table of objects. The types of objects and order is in accord with InputSignature.
The output data for each row is returned in OutputStructure.
The prepareData method is responsible for data preprocessing and passing data in suitable form to the processRow method. The example below illustartes the prepareData method with a transformation chain
The first step is to map the data according to InputSignature and execute the transformation chain.
After the transformations the data is mapped once again, this time according to Signature. Finally, a table of double values is returned.
/** prepareData - prepares data for processRow method.
* It does all necesary mapping, transformation and categorical
* attributes encoding.
* @param inputData - provided in correct order data for scoring - according to InputSignature
* @return - array of double consits of ready to score data
*/
public double [] prepareData(Object [] inputData) {
tempInput.clear();
//Input attributes mapping
tempInput.put("x", inputData[0]);
tempInput.put("y", inputData[1]);
tempInput.put("z", inputData[2]);
tempOutput = trans0.transformRow(tempInput);
//Output attributes mapping
double [] output = new double[3];
//Initialize output array with Double.NaN's
java.util.Arrays.fill(output, Double.NaN);
if(tempOutput.get("x") != null )
if(tempOutput.get("x") instanceof Number)
output[0]=((Number)tempOutput.get("x")).doubleValue();
else
throw new RuntimeException("Data type doesn't match to data type specified in inputSignature. Can't continue. ");
if(tempOutput.get("y") != null )
if(tempOutput.get("y") instanceof Number)
output[1]=((Number)tempOutput.get("y")).doubleValue();
else
throw new RuntimeException("Data type doesn't match to data type specified in inputSignature. Can't continue. ");
if(tempOutput.get("z") != null )
if(tempOutput.get("z") instanceof Number)
output[2]=((Number)tempOutput.get("z")).doubleValue();
else
throw new RuntimeException("Data type doesn't match to data type specified in inputSignature. Can't continue. ");
return output;
}
The processRow method does the main data processing. An example listing of processRow is presented below. The parameters of this method are a table of doubles and the structure in which the output values will be stored.
This method partly verifies the correctness of data. Scoring code generator only needs to know whether the data type is numerical or ordinal. Numerical values are stored as doubles and categorical as strings. The order of attributes in the data set is not verified. The processRow method expects attributes in the order specified in the model signature (see Signature).
It is not recommended to use this method directly because it requires the data to be prepared in a special way. It is safer and easier use the scoreData method. If it is necessary to call this function directly, the prepareData method should be called and its output should be used as the parameter for the processRow method.
/** processRow - works with a single data row.
* Data returned by processRow method is placed in OutputStructure output
* <b>Remarks:<b>
* Do not use this function, it uses mapped, and coded values in double array. Better use scoreData method.
* @param input - array of double data
* @param output - this structure will be the scoring output.
*/
public void processRow(double [] input, OutputStructure output) {
//Input data checking
for(int i=0; i<input.length; i++){
if(input[i]==Double.NaN)
throw new RuntimeException("Non numerical input data not supported");
}
if(input.length != clustersCoordinatesCount)
throw new RuntimeException("Input array size doesn't match clusters count ("+clustersCount+")");
//Computing distances between input data and cluster coordinates
double minDistance = 0;
int bestClusterID = 0;
for(int i=0; i<clustersCount; i++) {
double distance = calculateDistance(input, i);
if(i == 0)
minDistance = distance;
else
if(distance < minDistance) {
minDistance = distance;
bestClusterID = i;
}
output.distance[i]=distance;
}
//Calculated best cluster identifier is assigned to output
output.clusterID = bestClusterID;
}
The scoring of data by executing scoring code is similar to the application of data to a model with ApplyTask. The only difference between these tasks is that in the case of scoring code (ScoringCodeApplyTask) there is no ApplyOutput element, since the output data set is defined in the scoring code. The structure of the output is specified in OutputStructure .
Table 22.5. Properties of ScoringCodeApplyTask
| Property name | Description |
|---|---|
| Replace Existing Data | If this option is set and Apply Target Data points to existing data, the data structure will be changed and the data will be replaced. |
Table 22.6. Objects to set in ScoringCodeApplyTask
| Status | Name | Description | Type |
|---|---|---|---|
| included | directMapping | an object which determines which columns from the scored set should appear in the result set. The selected columns will be copied to the result set | own |
| included | modelAssignment | an object used for mapping the variables onto model signature | own |
| obligatory | applyOutput | currently not used by ScoringCodeApplyTask | own |
| obligatory | sourceData | the data for scoring | by name |
| obligatory | targetData | the set which will store the scoring results | by name |
| obligatory | model | a model in the repository, created as the outcome of SCBT | by name |
After the paramters are set ScoringCodeApplyTask can be executed.
During the execution of ScoringCodeApplyTask the scoring code is compiled and checked. The following elements are verified during the execution:
the name and availability of the main class - see Architecture of Java scoring code
the existence of the prepareData and processRow methods
the existence of InputSignautre
the existence of OutputStructure
Next, for each row from the input data the following actions are preformed:
preparing the data according to InputSignature
execution of the prepareData method (data preprocessing)
processing the data with the processRow method
saving OutputStructure into the output table
saving the data from DirectMapping into to output table
Scoring code differs between modules in many ways. The scoring procedures and the possible outputs are not the same. The table below presents the outputs and their descriptions for each model type.
Table 22.7. Scoring Code Output
| Model | Output Field | Field Description |
|---|---|---|
| Bivariate | target1Score | linear score for the target1 variable |
| target2Score | linear score for the target2 variable | |
| predictedConditionalProbability | predicted conditional probability of positive value for target1 variable on the condition of positive target2 | |
| predictedJointProbability | predicted joint probability of positive value for both target1 and target2 | |
| Discriminant | discriminantScore (double) | predicted value of the linear discriminant function. This value should be compared with the classification threshold to obtain the predicted category |
| Neural Networks | output (double[]) | array of output values of all neurons in the output layer |
| KMeans | clusterID (int) | the best cluster identified (i.e. the one for which the distance between the observation and the cluster centroid is minimal) |
| distance (double[]) | array with distances between the observation and all cluster centroids | |
| Kohonen | clusterID (String) | Id of the best cluster, i.e. the one with the minimum distance between its centroids and an observation |
| distance (double) | distance between an observation and the best cluster calculated for it | |
| LinearRegression | predictedTargetValue (double) | predicted (fitted) value of the target attribute |
| LogisticRegression | positiveCategoryProbability (double) | predicted propability of positive category |
| Survival | survivalTimeProbability (double[]) | predicted probabilities of the survivorship function at each time point from the training data (specified in the timeMap array). |
| Classification Trees | predictedTargetIndex (int) | index of the predicted target category; the order of the target categories is the same as in the Signature.OUTPUT_TARGET_VALUES table. |
| predictedTargetValue (TARGET_TYPE) | the predicted category | |
| nodeId (int) | id of the best leaf for the observation. | |
| probabilities (double[]) | ttable with aposteriori probabilities for each target category. The order of elements is the same as in the Signature.OUTPUT_TARGET_VALUES table. | |
| Time Series | regressedMean (double) | linear score calculated from the linear part of the GARCH model |
| forecastedVariance (double) | model-forecasted value of the variance at the timepoint t + 1 | |
| forecastLowerBound (double) | lower bound of the confidence interval for the forecasted variance at the timepoint t + 1; | |
| forecastUpperBound (double) | upper bound of the confidence interval for the forecasted variance at the timepoint t + 1; | |
| actualValue (double) | target value |