KhiopsML / khiops

Khiops is an AutoML suite for supervised and unsupervised learning
https://khiops.org
BSD 3-Clause Clear License
30 stars 3 forks source link

Extending Khiops entries with a lightweight json structure for scenarios #230

Open marcboulle opened 6 months ago

marcboulle commented 6 months ago

Contexte: Khiops peut-être pilote via la ligne de commande via des scénarios

Ce mode de contrôle est intéressant pour intégrer rapidement des traitements Khiops et les lancer depuis n'importe quel langage de programmation. Par contre, c'est plus complexe dans le cas de de paramétrage plus complexe, comme par exemple l'ensemble des table d'un schema en flocon. Dans ce cas, on doit passer un un algorithme de création de scénario comportant potentiellement des boucles de search/replace.

Proposition d'évolution fonctionnelle:

Intérêt de la fonctionnalité:

Ebauche de spécification, a titre illustratif

Ebauche d'étude d'impact:

folmos-at-orange commented 6 months ago
// Dictionary file and class settings
ClassManagement.OpenFile
ClassFileName __dictionary_file_path__
OK
ClassManagement.ClassName __dictionary_name__

// Here would be a StopIfErrors

// Train/test database settings
TrainDatabase.DatabaseFiles.List.Key __dictionary_name__
TrainDatabase.DatabaseFiles.DataTableName __data_table_path__
__LOOP__ __additional_data_tables__
TrainDatabase.DatabaseFiles.List.Key __datapath__
TrainDatabase.DatabaseFiles.DataTableName __path__
__END_LOOP__
TrainDatabase.HeaderLineUsed __header_line__
TrainDatabase.FieldSeparator __field_separator__
__OPT__ __detect_format__
TrainDatabase.DatabaseFormatDetector.DetectFileFormat
__END_OPT__
TrainDatabase.SampleNumberPercentage __sample_percentage__
TrainDatabase.SamplingMode __sampling_mode__
TrainDatabase.SelectionAttribute __selection_variable__
TrainDatabase.SelectionValue __selection_value__
TrainDatabase.TestDatabaseSpecificationMode __test_database_mode__

// Target variable
AnalysisSpec.TargetAttributeName __target_variable__
AnalysisSpec.MainTargetModality __main_target_value__

// Predictors to train
AnalysisSpec.PredictorsSpec.SelectiveNaiveBayesPredictor __snb_predictor__
AnalysisSpec.PredictorsSpec.AdvancedSpec.UnivariatePredictorNumber __univariate_predictor_number__

// Selective Naive Bayes settings
AnalysisSpec.PredictorsSpec.AdvancedSpec.InspectSelectiveNaiveBayesParameters
TrainParameters.MaxEvaluatedAttributeNumber __max_evaluated_variables__
SelectionParameters.MaxSelectedAttributeNumber __max_selected_variables__
Exit

// Feature engineering
AnalysisSpec.PredictorsSpec.ConstructionSpec.MaxTreeNumber __max_trees__
AnalysisSpec.PredictorsSpec.ConstructionSpec.MaxAttributePairNumber __max_pairs__
AnalysisSpec.PredictorsSpec.AdvancedSpec.InspectAttributePairsParameters
AllAttributePairs __all_possible_pairs__
__LOOP__ __specific_pairs__
SpecificAttributePairs.InsertItemAfter
SpecificAttributePairs.FirstName __first_name__
SpecificAttributePairs.SecondName __second_name__
__END_LOOP__
Exit
AnalysisSpec.PredictorsSpec.ConstructionSpec.MaxConstructedAttributeNumber __max_constructed_variables__
AnalysisSpec.PredictorsSpec.AdvancedSpec.InspectConstructionDomain
__LOOP__ __construction_rules__
UnselectAll
ConstructionRules.List.Key __rule_name__
ConstructionRules.Used __rule_used__
__END_LOOP__
Exit

// Data preparation (discretization & grouping) settings
AnalysisSpec.PreprocessingSpec.TargetGrouped __group_target_value__
AnalysisSpec.PreprocessingSpec.DiscretizerSpec.SupervisedMethodName __discretization_method__
AnalysisSpec.PreprocessingSpec.DiscretizerSpec.UnsupervisedMethodName __discretization_method__
AnalysisSpec.PreprocessingSpec.DiscretizerSpec.MinIntervalFrequency __min_interval_frequency__
AnalysisSpec.PreprocessingSpec.DiscretizerSpec.MaxIntervalNumber __max_intervals__
AnalysisSpec.PreprocessingSpec.GrouperSpec.SupervisedMethodName __grouping_method__
AnalysisSpec.PreprocessingSpec.GrouperSpec.UnsupervisedMethodName __grouping_method__
AnalysisSpec.PreprocessingSpec.GrouperSpec.MinGroupFrequency __min_group_frequency__
AnalysisSpec.PreprocessingSpec.GrouperSpec.MaxGroupNumber __max_groups__

// Output settings
AnalysisResults.ResultFilesDirectory __results_dir__
AnalysisResults.ResultFilesPrefix __results_prefix__

// Build model
ComputeStats
marcboulle commented 6 months ago

Extension du pilotage de Khiops via les scénarios

Résumé

On propose dans ce document une extension du pilotage de Khiops via des scénarios.

Khiops peut-être piloté via la ligne de commande via des scénarios

Ce mode de contrôle est intéressant pour intégrer rapidement des traitements Khiops et les lancer depuis n'importe quel langage de programmation. Par contre, cela est limité dans le cas de paramétrage plus complexe, comme par exemple l'ensemble des tables d'un schéma en flocon. Dans ce cas, on doit passer un un algorithme de création de scénario comportant potentiellement des boucles de search/replace.

On propose d'étendre d'étendre le pilotage de khiops via les scénarios:

L'intérêt de cette extension fonctionnelle est multiple:

Spécification fonctionnelle

Structure de contrôle dans les scénarios

On ajoute quelques structures de contrôle dans les scénario pour permettre un pilotage complet des opérations de search/replace.

Les structures de contrôle sont matérialisées par des instructions en UPPER CASE sur des lignes dédiées.

Boucle

Une structure de boucle permet d'entourer un bloc de lignes de scenario entre deux instructions

Toutes les lignes d'un bloc de boucle sont répétées autant de fois que nécessaire.

Test

Une structure de test permet d'entourer un bloc de lignes de scenario entre deux instructions

Toutes les lignes d'un bloc de test sont prise en compte conditionnellement au test.

Paramétrage par une structure de données

Ajout d'un option sur la ligne de commande des outils Khiops: -j \<file\> json file used to set replay parameters

Le fichier json contient une série de paires clé/valeur:

Format json: https://www.json.org/json-en.html

Contraintes sur les options de la ligne de commande

Pour faciliter la mise au point du pilotage par scenario, on rajoute l'option suivante: -O \<file\> same as -o option, but without replay

Contraintes:

Contraintes sur la structure du json

Seule une petite partie de l'expressivité du format json est gérée

Contraintes sur les clés

Liens entre clés dans le json et dans le scénario

Remarque:

Exemple d'utilisation

Scénario en entrée:

ClassManagement.OpenFile // Open...
ClassFileName __dictionaryFile__ // Dictionary file
OK // Open
ClassManagement.ClassName __dictionaryName__

LOOP __dataTables__
TrainDatabase.DatabaseFiles.List.Key __dataPath__
TrainDatabase.DatabaseFiles.DataTableName __dataFile__
END LOOP

TrainDatabase.SampleNumberPercentage __trainPercentage__  // Sample percentage

IF __detectFormat__
TrainDatabase.DatabaseFormatDetector.DetectFileFormat
END IF

AnalysisSpec.TargetAttributeName __targetName__
ComputeStats // Analyse database

Exit // Close
OK // Close

Fichier json en entrée:

{
  "dictionaryFile": "./SpliceJunction/SpliceJunction.kdic",
  "dictionaryName": "SpliceJunction",
  "dataTables": [
    {
      "dataPath": "SpliceJunction",
      "dataFile": "./SpliceJunction/SpliceJunction.txt"
    },
    {
      "dataPath": "SpliceJunction`DNA",
      "dataFile": "./SpliceJunction/SpliceJunctionDNA.txt"
    }
  ],
  "detectFormat": true,
  "trainPercentage": 10,
  "targetName": "Class"
}

Scénario en sortie:

ClassManagement.OpenFile // Open...
ClassFileName ./SpliceJunction/SpliceJunction.kdic // Dictionary file
OK // Open
ClassManagement.ClassName SpliceJunction

TrainDatabase.DatabaseFiles.List.Key SpliceJunction
TrainDatabase.DatabaseFiles.DataTableName ./SpliceJunction/SpliceJunction.txt
TrainDatabase.DatabaseFiles.List.Key SpliceJunction`DNA
TrainDatabase.DatabaseFiles.DataTableName ./SpliceJunction/SpliceJunctionDNA.txt

TrainDatabase.SampleNumberPercentage 10  // Sample percentage

TrainDatabase.DatabaseFormatDetector.DetectFileFormat

AnalysisSpec.TargetAttributeName Class
ComputeStats // Analyse database

Exit // Close
OK // Close

Encodage des valeurs dans le json

Analyse des besoins et contraintes

Khiops accepte tout types de valeurs en entrée, ce qui permet d'être applicable à toutes sources de données. Cela impose des scénario qui sont sont encodés sous formes de bytes (et non UTF-8 par exemple), ce qui permet par exemple:

Comme la structure json comporte une liste de clés/valeurs, les valeurs doivent donc pouvoir comporter n'importe quelle séquence de bytes.

Khiops doit décoder les valeurs des clés du json en paramètre pour les transformer en chaines de caractères C++, qui sont des tableau de bytes. Il est donc nécessaire de spécifier comment les valeurs sont encodées, au dela du format UTF-8.

Selon la spécification de json https://datatracker.ietf.org/doc/html/rfc8259#section-8.1, on peut éventuellement utiliser le format json sans respecter l'encodage UTF-8, puisque l'on se trouve dans l'éco-système fermé Khiops.

8.1. Character Encoding

JSON text exchanged between systems that are not part of a closed ecosystem MUST be encoded using UTF-8 [RFC3629].

Previous specifications of JSON have not required the use of UTF-8 when transmitting JSON text. However, the vast majority of JSON- based software implementations have chosen to use the UTF-8 encoding, to the extent that it is the only encoding that achieves interoperability.

Choix d'encodage

On choisit un encodage UTF-8 systématique pour le json en paramètre de Khiops, selon la norme Json. Dans le cas de paramètres dont les valeurs peuvent être soit des strings UTF-8, soit des chaines de bytes, on étend le format json de la façon suivantes. Pour un paramètre concerné (ex: dataPath):

Au moment de l'écriture du scénario en sortie, on recherche la clé correspondante dans le json ou sa variante avec préfixe byte pour décoder ou non la valeur dans le search/replace.

Contraintes spécifique sur la structure du json:

Remarques

Le format Quoted-Printable pourrait être une alternative à l'encodage Byte64

Liens avec protobuf

Choix d'implémentation principaux

Choix techniques avec impacts utilisateur:

marcboulle commented 6 months ago

Suite à échanges avec Felipe

Expression de besoin d'étendre les structures de contrôle pour les besoins actuels de pykhiops

Analyse des besoins

Bilan

Fonctionnalité en attente de maturation: attendre pour annuler ou lancer le développement

marcboulle commented 6 months ago

Suite à discussion avec Stéphane

Extension avec expressivité faible

Si expressivité faible tel que proposé initialement (uniquement structures LOOP et IF):

Extension avec expressivité forte

Si expressivité forte, tel que suggérée par Felipe pour les beson de pykhiops (structure DIC et VECTOR additionnelles)

Bilan

sgouache commented 4 months ago

Suite à une étude impliquant un petit code utilisateur de l'API en java (KhiopsAPI.java) un certain nombre de points ont évolué (mis à jour dans la discussion ci-dessus).

Moyennant ces ajustements, la spécification décrite ci-dessus ne semble pas poser de problème d'implémentation et offre un confort d'utilisation satisfaisant pour le programmeur (vérifié pour java).

sgouache commented 4 months ago

Pour référence, un exemple de spécification d'opération - ici TrainPredictor - décrite en langage proto v2 avec les contraintes simplificatrices permettant une conversion simplifiée vers JSON et son utilisation avec le search replace décrit plus haut.

message DatapathParam {
    oneof t1 {
        string data_path = 1;
        bytes byte_data_path = 2;
    }
    oneof t2 {
        string file_path = 3;
        bytes byte_file_path = 4;
    }
}

message PairParam {
    oneof t1 {
        string pair_string_param1 = 1;
        bytes byte_pair_string_param1 = 2;
    }
    oneof t2 {
        string pair_string_param2 = 3;
        bytes byte_pair_string_param2 = 4;
    }
}

message ConstructionRule {
    required string rule = 1;
}

message TrainPredictor {
    oneof v1 {
        string dictionary_file_path = 1; // default = ""
        bytes byte_dictionary_file_path = 2;
    };
    oneof v2 {
        string dictionary_name = 3; // default = ""
        bytes byte_dictionary_name = 4;
    };
    oneof v3 {
        string data_table_path = 5; // default = ""
        bytes byte_data_table_path = 6;
    };
    oneof v4 {
        string target_variable = 7; // default = ""
        bytes byte_target_variable = 8;
    };
    oneof v5 {
        string results_dir = 9; // default = ""
        bytes byte_results_dir = 10;
    };
    optional bool detect_format = 11 [ default = true ];
    optional bool header_line = 12 [ default = true ];
    optional string field_separator = 13 [ default = "" ];
    optional double sample_percentage = 14 [ default = 100.0 ];
    optional string sampling_mode = 15 [ default = "Include sample" ];
    optional string test_database_mode = 16 [ default = "Complementary" ];
    optional string selection_variable = 17 [ default = "" ];
    optional string selection_value = 18 [ default = "" ];
    repeated DatapathParam additional_data_table = 19;
    oneof v6 {
        string main_target_value = 20; // default = ""
        bytes byte_main_target_value = 21;
    };
    optional bool snb_predictor = 22 [ default = true ];
    optional int32 univariate_predictor_number = 23 [ default = 0 ];
    optional int32 max_evaluated_variables = 24 [ default = 0 ];
    optional int32 max_selected_variables = 25 [ default = 0 ];
    optional int32 max_constructed_variables = 26 [ default = 0 ];
    repeated ConstructionRule construction_rule = 27 ;
    optional int32 max_trees = 28 [ default = 10 ];
    optional int32 max_pairs = 29 [ default = 0 ];
    optional bool all_possible_pairs = 30 [ default = true ];
    repeated PairParam specific_pair = 31 ;
    optional bool group_target_value = 32 [ default = false ];
    optional string discretization_method = 33 [ default = "MODL" ];
    optional int32 min_interval_frequency = 34 [ default = 0 ];
    optional int32 max_intervals = 35 [ default = 0 ];
    optional string grouping_method = 36 [ default = "MODL" ];
    optional int32 min_group_frequency = 37 [ default = 0 ];
    optional int32 max_groups = 38 [ default = 0 ];
    optional string results_prefix = 39 [ default = "" ];
    optional string dummy_value = 40;
}

et un JSON conforme à cette spécification:

{
  "byteDictionaryFilePath": "V2hhdGV2ZXIgeW91IHdhbnQh",
  "dictionaryName": "xxx",
  "dataTablePath": "zzz",
  "byteTargetVariable": "S+lr6Q==",
  "byteResultsDir": "eHh4",
  "detectFormat": true,
  "headerLine": true,
  "fieldSeparator": "",
  "samplePercentage": 100.0,
  "samplingMode": "Include sample",
  "testDatabaseMode": "Complementary",
  "selectionVariable": "",
  "selectionValue": "",
  "additionalDataTable": [{
    "dataPath": "xxx",
    "filePath": "yyy"
  }, {
    "byteDataPath": "6Q==",
    "byteFilePath": "U29tZSBvdGhlciBieXRlcw=="
  }],
  "mainTargetValue": "",
  "snbPredictor": true,
  "univariatePredictorNumber": 0,
  "maxEvaluatedVariables": 0,
  "maxSelectedVariables": 0,
  "maxConstructedVariables": 0,
  "constructionRule": [],
  "maxTrees": 10,
  "maxPairs": 0,
  "allPossiblePairs": true,
  "specificPair": [],
  "groupTargetValue": false,
  "discretizationMethod": "MODL",
  "minIntervalFrequency": 0,
  "maxIntervals": 0,
  "groupingMethod": "MODL",
  "minGroupFrequency": 0,
  "maxGroups": 0,
  "resultsPrefix": ""
}