Wykonywanie kodu scoringowego poza systemem AdvancedMiner

Nie da się przewidzieć, w jakim środowisku kody scoringowe będą wykorzystywane. Tak więc kod powinien być jak najbardziej elastyczny. Kod scoringowy jest niezależny od źródła danych, ale wymaga aby programista przekazał dane w odpowiedniej formie.

Informacja o formie i kolejności danych wejściowych jest przechowywana w InputSignature. Klasa ta została zaprojektowana w celu umożliwienia dostępu do informacji na temat struktury danych wejściowych .

Czytanie InputSignature

Przykładowy kod czytający InputSigature jest przedstawiony jest poniżej. Proszę zwrócić uwagę na założenia dotyczące tego kodu:

  • inputSignature jest obiektem klasy Class zawierającym definicje klasy InputSigature

  • wymagane jest, aby zaimportować wszystkie pakiety odpowiedzialne za refleksje w Javie

Należy zauważyć, dwie istotne części: Loop A, Loop B, które są opisane poniżej.

Przykład 10.2. Czytanie InputSignature

// Uzyskanie informacji z klasy inputSignature
Field [] properties = inputSignature.getFields();

//Sprawdzić, czy nazwa klasy jest poprawna
if(inputSignature.getName().equals("ScoringCode$InputSignature")){ 
    try {
        //****** LOOP A********
        //Iteracyjne przejście przez wszystkie pola (właściwości) z inputSignature.
        //Jest tylko jedno pole domyślne w inputSignature, ta 
        //Pętla jest tutaj tylko w sytuacji, gdy użytkownik doda
        //jakieś pole użytkownia w inputSignature
        for(int i=0; i<properties.length; i++) {

            //Jeśli pole name jest równe INPUT_ATTRIBUTES uzyskać nazwę i typ 
            //atrybutu

            Field signatureAttribute = properties[i];

            if(  signatureAttribute.getName().equals("INPUT_ATTRIBUTES") ) {
                
                //Traktuje pole (INPUT_ATTRIBUTES) jako tablice
                Object attributeArray = signatureAttribute.get(null);

                //****** LOOP B********                                                    
                //Każdy wiersz w tablicy zawiera informacje o nazwie i typie.

                //Iteracyjne przejście wierszy tablicy aby uzyskać informacje
                for(int k=0; k<Array.getLength(attributeArray); k++) {     
                    // get k-th row from the array               
                    Object attrib[] = (Object [])Array.get(attributeArray, k);   
 
                    //attrib[0] - contains attribute name
                    //attrib[1] - contains type of the attribute
                }
            }

            ...
            // Reszta kodu odpowiedzialny za interpretację
            // zebranych informacji
            ...
            
        }
    } catch(Exception e) { 
        throw new GSException("Error occurred when retrieving Input and Output "+
"signature information");

    }
}       
    

Opis kodu

LOOP A

Pętla ta chroni przed sytuacją, gdy użytkownik zmienia inputSignature. Zazwyczaj ta sygnatura zawiera tylko jedno pole (właściwość) - jest to publiczna statyczna dwuwymiarowa tabela o nazwie INPUT_ATTRIBUTES.

LOOP B

Pętla ta odczytuje nazwy i typy atrybutów, które są akceptowane przez model. Przykład nie pokazuje, co można zrobić z tymi atrybutami. Użytkownik może przechowywać informacje o atrybutach do dalszego przetwarzania lub użyć w tym etapie, na przykład do stworzenia zapytań SQL lub innej formy dostępu do danych z bazy danych lub pliku.

Notatka

Kolejność atrybutów oczekiwanych przez model do prawidłowego działania jest taka sama jak kolejność atrybutów w InputSignature.

Przykład wykorzystania kodu scoringowego w zewnętrznej aplikacji

Prostą aplikację z użyciem kodu scoringowego przedstawiono poniżej. Kod scoringowy oparty jest na modelu z wykorzystaniem algorytmu drzewa klasyfikacyjnego. Formą zestawu danych wejściowych jest CSV (w formacie CSV), wynikiem jest zestaw danych w tym samym formacie.

Przykład 10.3. Kod scoringowy w zewnętrznej aplikacji

To jest przykładowy kodu scoringowego w aplikacji zewnętrznej.

package test;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;


public class ScoringCodeTest {

      private ScoringCode generatedScoringCode;
      private Object input[];
      private ScoringCode.OutputStructure output;
      private FileReader inputFileReader;
      private FileWriter outputFileWriter;
      private PrintWriter outputPrintWriter;

      public void score() {
          input = new Object[21];
          
          generatedScoringCode = new ScoringCode();
          output = new ScoringCode.OutputStructure();          
          
          //LinkedHashMap jest użyty ponieważ zwraca elementy 
          // w tej samej kolejności, jak zostały one zapisane.
          LinkedHashMap inputSignature = new LinkedHashMap();
          
          //Lista służy do przechowywania nazw atrybutów
          ArrayList inputAttributeName = new ArrayList();
          
          //Napisz atrybuty z inputSignature do mapowania.
          //Będzie łatwiej dostępne dla atrybutów w celu zapewnienia
          // właściwej kolejności        
          for(int i=0; i<ScoringCode.InputSignature.INPUT_ATTRIBUTES.length; i++) {  
      
              inputSignature.put(ScoringCode.InputSignature.INPUT_ATTRIBUTES[i][0], 
ScoringCode.InputSignature.INPUT_ATTRIBUTES[i][1]);

          }
          
          // Czytaj linię z pliku csv, przekształcić go w numeryczne
          // wartości, i scoruj to. Po scoringu zapisz wyniki do
          // pliku csv.

          try {
              String line = "";
              String separator = " ";
              
              inputFileReader = new FileReader("test/input.csv");
              outputFileWriter = new FileWriter("test/output.csv");
              BufferedReader bfr = new BufferedReader( inputFileReader );
              
              outputPrintWriter = new PrintWriter( outputFileWriter );
              boolean headerLine = true;
              
              
              while((line = bfr.readLine()) != null) {
                  
                  // Dzielenie linii na poszczególne wartości za pomocą
                  // separatora
                  String [] values = line.split( separator );
                  
                  //Pierwszy wiersz zawiera nazwy atrybutów.                        
                  if(headerLine) {
                                  for(int i=0; i<values.length; i++)

                                      inputAttributeName.add(values[i]);
                                  headerLine = false;
                  } else {                     
                      //Kolejne wiersze zawierają wartości scoringów
                                      for ( int i = 0; i< values.length; i++ ) {

                                          
                                          //Zwraca attributeType
                                          Object attributeType = inputSignature.
get(inputAttributeName.get(i));
                                          //System.out.println(attributeType);
                                          //Pętla ta jest stosowana w celu uzyskania prawidłowego                                         
										  //indeksu atrybutów 

                                          int attributeIndex = -1;
                                          Iterator iter = inputSignature.keySet().
iterator();

                                          while(iter.hasNext()) {
                                              attributeIndex++;
                                              Object key = iter.next();
                                              if(key.equals(inputAttributeName.get(i)))

                                                  break;
                                          }
                                          
                                          //Przygotuj wartość z odpowiedniego typu                                          //obtained from the signature

                                          if(values[i]!=null && values[i].length()!=0) {

                                              if(attributeType.equals(String.class)) 

                                                  input[attributeIndex] = values[i];

                                              else 
                                                  input[attributeIndex] = new Double
                                                                  (values[i]);
                                              
                                          } else 
                                              input[attributeIndex] = null;                        
  
                                      }
                                    
                                      // Scoruje przygotowane dane
                                      generatedScoringCode.scoreData( input, output);

                                                                            
                                      //Zapisuje clientID 
                                      outputPrintWriter.print( ((
Number)input[2]).intValue() + ", " );

                                      
                                      //Zapisuje przewidywane wartości targetu
                                      outputPrintWriter.print(output.positiveCategoryProbability+
 ", ");
                                      


                                  }                                  
              }
          }
          catch (Exception ex) {
              ex.printStackTrace();
          } 
          finally {
              try {
                  inputFileReader.close();
                  outputPrintWriter.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }          
      }
            
      public static void main(String args[]) {
          ScoringCodeTest scoringCodeExecutionTest = new ScoringCodeTest();
          scoringCodeExecutionTest.score();
      }
}

    

Oto próbka danych csv (umieścić ją w pliku o nazwie input.csv w pakiecie testowym):

preg plas pres skin insu mass pedi age
6.0 148.0 72.0 35.0 0.0 33.6 0.627 50.0
1.0 85.0 66.0 29.0 0.0 26.6 0.351 31.0
8.0 183.0 64.0 0.0 0.0 23.3 0.672 32.0
1.0 89.0 66.0 23.0 94.0 28.1 0.167 21.0
0.0 137.0 40.0 35.0 168.0 43.1 2.288 33.0
5.0 116.0 74.0 0.0 0.0 25.6 0.201 30.0
    

Oto przykładowy kod scoringowego wygenerowany przez AdvancedMiner

package test


import java.util.HashMap;

public class ScoringCode {

        /** Tymczasowe mapowanie stosowane metodą prepareData */
        private HashMap tempInput = new HashMap();

        /** Tymczasowe mapowanie stosowane metodą prepareData */
        private HashMap tempOutput = null;

        /** scoreData jest główną metodą scorowania. Dane wejściowe muszą być dostarczone w odpowiedniej kolejności.
         * Proszę zajrzeć do InputSignature aby zobaczyć, które typy danych są obsługiwane i jak zapewnić dane w odpowiedniej kolejności.
         * Transformacja (jeśli dotyczy) jest tworzona przez metodę prepareData. Metoda ta także zajmuje się kodowaniem kategorycznych zmiennych
         * @param input - zbió wejściowy do scorowania
         * @param output - OutputStructure - będzie zawierać wynik scoringu
         */
        public void scoreData(Object [] input, OutputStructure output) {
                processRow(prepareData(input), output);
        }

         /** getCategoricalValueCode - dostaje zakodowaną podwójną wartość odpowiadającą 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 - przygotowuje dane dla metody processRow. 
		  * To wykonuje wszystkie niezbędne mapowanie, transformacje i kodowania 
	      * atrybutów kategorycznych.
          * @param inputData - dostarcza we właściwy sposób posortowane dane do scorowania - według InputSignature
          * @return - tablica podwójnych rezultatów
          */
        public double [] prepareData(Object [] inputData) { 

                tempInput.clear();

                //Input attributes mapping
                tempInput.put("preg", inputData[0]);
                tempInput.put("plas", inputData[1]);
                tempInput.put("pres", inputData[2]);
                tempInput.put("skin", inputData[3]);
                tempInput.put("insu", inputData[4]);
                tempInput.put("mass", inputData[5]);
                tempInput.put("pedi", inputData[6]);
                tempInput.put("age", inputData[7]);

                //Tam nie ma transformacji stąd wejście jest kopiowane jako wyjście
                tempOutput = tempInput;

                //Mapowanie atrybutów wyjściowych
                double [] output = new double[8];

                //Wywołanie wyjściowej tabeli z Double.NaN's
                java.util.Arrays.fill(output, Double.NaN);

                if(tempOutput.get("preg") != null )
                        if(tempOutput.get("preg") instanceof Number)
                                output[0]=((Number)tempOutput.get("preg")).doubleValue();
                        else
                                throw new RuntimeException("Data type doesn't match to data type specified in inputSignature. Can't continue. ");

                if(tempOutput.get("plas") != null )
                        if(tempOutput.get("plas") instanceof Number)
                                output[1]=((Number)tempOutput.get("plas")).doubleValue();
                        else
                                throw new RuntimeException("Data type doesn't match to data type specified in inputSignature. Can't continue. ");

                if(tempOutput.get("pres") != null )
                        if(tempOutput.get("pres") instanceof Number)
                                output[2]=((Number)tempOutput.get("pres")).doubleValue();
                        else
                                throw new RuntimeException("Data type doesn't match to data type specified in inputSignature. Can't continue. ");

                if(tempOutput.get("skin") != null )
                        if(tempOutput.get("skin") instanceof Number)
                                output[3]=((Number)tempOutput.get("skin")).doubleValue();
                        else
                                throw new RuntimeException("Data type doesn't match to data type specified in inputSignature. Can't continue. ");

                if(tempOutput.get("insu") != null )
                        if(tempOutput.get("insu") instanceof Number)
                                output[4]=((Number)tempOutput.get("insu")).doubleValue();
                        else
                                throw new RuntimeException("Data type doesn't match to data type specified in inputSignature. Can't continue. ");

                if(tempOutput.get("mass") != null )
                        if(tempOutput.get("mass") instanceof Number)
                                output[5]=((Number)tempOutput.get("mass")).doubleValue();
                        else
                                throw new RuntimeException("Data type doesn't match to data type specified in inputSignature. Can't continue. ");

                if(tempOutput.get("pedi") != null )
                        if(tempOutput.get("pedi") instanceof Number)
                                output[6]=((Number)tempOutput.get("pedi")).doubleValue();
                        else
                                throw new RuntimeException("Data type doesn't match to data type specified in inputSignature. Can't continue. ");

                if(tempOutput.get("age") != null )
                        if(tempOutput.get("age") instanceof Number)
                                output[7]=((Number)tempOutput.get("age")).doubleValue();
                        else
                                throw new RuntimeException("Data type doesn't match to data type specified in inputSignature. Can't continue. ");

                return output;
        }

        /** decodeTarget is used to decode integer value to its original representation
         * @param coded value of target
         * @return decoded value of target
         */
        public String decodeTarget(int codedValue) {
                return (String)Signature.OUTPUT_TARGET_VALUE_SET[codedValue];
        }

        /** InputSignature zawiera sygnaturę danych, która ma być przekazana do metody scoreData.
        * Struktura ta pokazuje atrybuty w parach typu name i ich właściwej kolejności w danych wejściowych
        */
        public static class InputSignature {

                /** INPUT_ATTRIBUTES - tablica ta zawiera pary nazwa-typ, które opisują wejściowe atrybuty
                 * Liczba w komentarzu reprezentuje poprawną pozycję określonego atrybutu w danych wejściowych
                 */
                 public static Object [][] INPUT_ATTRIBUTES = new Object [][] {
                        {"preg", double.class },         // inputRow[0]
                        {"plas", double.class },         // inputRow[1]
                        {"pres", double.class },         // inputRow[2]
                        {"skin", double.class },         // inputRow[3]
                        {"insu", double.class },         // inputRow[4]
                        {"mass", double.class },         // inputRow[5]
                        {"pedi", double.class },         // inputRow[6]
                        {"age", double.class }        // inputRow[7]
                };
        }

        /** Signature - klasa ta opisuje jak dane powinny być przekazywane do metody processRow 
        * przez Transformacje. Jeśli nie ma transformacji jest taki sam, jak InputSignature. 
        * Struktura ta pokazuje atrybuty w parach nazwa-typu i odpowiedniej kolejności w danych wejściowych
        *  Przedstawiono również zestaw ustawiania kategorycznych wartości:
        */
        public static class Signature {

                /** INPUT_ATTRIBUTES - tablica ta zawiera pary nazwa-typ, które opisują wejściowe atrybuty
                 * Liczba w komentarzu reprezentuje poprawną pozycję określonego atrybutu w danych wejściowych
                 */
                 public static Object [][] INPUT_ATTRIBUTES = new Object [][] {
                        {"preg", double.class },         // inputRow[0]
                        {"plas", double.class },         // inputRow[1]
                        {"pres", double.class },         // inputRow[2]
                        {"skin", double.class },         // inputRow[3]
                        {"insu", double.class },         // inputRow[4]
                        {"mass", double.class },         // inputRow[5]
                        {"pedi", double.class },         // inputRow[6]
                        {"age", double.class }        // inputRow[7]
                };


                /** INPUT_ATTRIBUTES_VALUE_SET zawiera informacje na temat kodowania atrybutów kategorycznych
                 * Kody dla wartości są przypisane według kolejności atrybutów w tablicy. Pierwszy atrybut ma kod równy 0,
                 * drugi atrybut ma kod równy 1, i tak dalej.
                 */
                 public static Object [][] INPUT_ATTRIBUTES_VALUE_SET = new Object [][] {
                        { null },         // preg value set -> inputRow [0]
                        { null },         // plas value set -> inputRow [1]
                        { null },         // pres value set -> inputRow [2]
                        { null },         // skin value set -> inputRow [3]
                        { null },         // insu value set -> inputRow [4]
                        { null },         // mass value set -> inputRow [5]
                        { null },         // pedi value set -> inputRow [6]
                        { null }        // age value set -> inputRow [7]
                };


                /** OUTPUT_TARGET_VALUE_SET Zawiera informacje o zakodowanych wartości docelowych
                 */
                 public static Object [] OUTPUT_TARGET_VALUE_SET = new Object [] {
                         "tested_negative",        // Target value code = java.util.AbstractList$Itr@1302be2
                         "tested_positive"        // Target value code = java.util.AbstractList$Itr@1302be2
                };
        }


        /** pozytywne modelowane prawdopodobieństwo kategorii (przewidywane z modelu) */

        /** OutputStructure załącza informacje o nazwach i typach, które mogą być uzyskane z metody processRow.
         * Struktura ta może zawierać następujące typy: double, int, string, double [], int [].
         * OutputStructure jest używane przez AdvancedMiner aby określić dane wyjściowe.
         */
        public static class OutputStructure
 {

                public double positiveCategoryProbability;

        }

        /////////////////////////// DANE BRANE Z MOEDLU W AdvancedMiner  //////////////////////////////////////

        //Wartości przypisane do zmiennych wymienionych poniżej podejmowane są z modelu
        public final static int LOGIT_MODEL = 1;
        public final static int PROBIT_MODEL = 2;
        public final static int CUMULATIVE_LOGIT_MODEL = 3;
        public final static int CUMULATIVE_PROBIT_MODEL = 4;
        public final static int MULTINOMIAL_LOGIT_MODEL = 5;
        public final static int MULTINOMIAL_PROBIT_MODEL = 6;

        public final int modelType = LOGIT_MODEL;

        /**aźnik dodatniej kategorii docelowej ("event" category) w Signature.OUTPUT_TARGET_VALUE_SET table.*/
        public final int positiveTargetCategoryIndex = 0;

        public final int varNo = 8;

        public final int intercept = 1; // 0 for model without intercept

        /** Wektor szacowanych parametrów z modelu */
        protected double[] beta = new double[varNo+intercept];

        /////////////////////////////////////  KOD SCORINGOWY ////////////////////////////////////////////

        /** oblicza przewidywany liniowy scoring: y'=sum_i(beta[i]*input[i])
          * @param input        wiersz z danymi do scoringu */
        protected double getLinearScore(double[] input) {

                double linearScore = (intercept > 0 ? beta[0] : 0);

                for(int k=0; k<varNo; k++) {

                        if (Double.isNaN(input[k]))
                                throw new RuntimeException("Missing values are not supported.");

                        linearScore += beta[k+intercept]*input[k];
                }

                return linearScore;
        }


        /** oblicza transformacje logarytmicznej, w oparciu o liniowy score:
          * y = 1 / (1 + e^(-linearScore))
          * @param input        wiersz z danymi do scoringu  */
        protected double score(double[] input) {

                double linearScore = getLinearScore(input);

                return 1.0 / (1.0 + Math.exp(-linearScore));
        }


        //Wejście wierszy do przetwarzania danych

		
        /** processRow - współpracuje z pojedynczym wierszem danych.
         * Dane zwracane przez metodę processRow są umieszczony na wyjściu OutputStructure
         * <b>Uwagi:<b>
	     * Nie należy używać tej funkcji, wykorzystuje odwzorowane i koduje wartości w podwójnej tablicy. Lepiej użyć metody scoreData.
         * @param input - tablica danych
         * @param output - struktura ta będzie wynikiem scoringu
         */
        public void processRow(double[] inputRow, OutputStructure output) {

                //Input data checking
                if (inputRow.length != varNo)
                        throw new RuntimeException("Input array size doesn't match the number of variables in the model [" + varNo + "]");

                output.positiveCategoryProbability = score(inputRow);
        }
        ///////////////////////////////////  KONIEC KODU SCORINGOWEGO  //////////////////////////////////////////
        public ScoringCode() {
                beta[0] = 8.40469636691381;
                beta[1] = -0.12318229835243809;
                beta[2] = -0.035163714606857285;
                beta[3] = 0.013295546904304663;
                beta[4] = -6.18964364875373E-4;
                beta[5] = 0.0011916989841621699;
                beta[6] = -0.08970097003093419;
                beta[7] = -0.945179740621142;
                beta[8] = -0.014869004744466634;

        }
}