Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

230 extending khiops entries with a lightweight json structure for scenarios #419

Conversation

marcboulle
Copy link
Collaborator

@marcboulle marcboulle commented Oct 29, 2024

Tout est implémenté et testé: cf. commit notes

@marcboulle marcboulle added the Type/Enhancement New feature or request label Oct 29, 2024
@marcboulle marcboulle added this to the v11.0.0 milestone Oct 29, 2024
@marcboulle marcboulle force-pushed the 230-extending-khiops-entries-with-a-lightweight-json-structure-for-scenarios branch 4 times, most recently from b76ea38 to 0f72bc7 Compare November 5, 2024 13:56
@marcboulle marcboulle force-pushed the 230-extending-khiops-entries-with-a-lightweight-json-structure-for-scenarios branch 5 times, most recently from 0bb19e8 to 68b47a7 Compare November 12, 2024 14:31
@marcboulle marcboulle force-pushed the 230-extending-khiops-entries-with-a-lightweight-json-structure-for-scenarios branch from 68b47a7 to 3b9985b Compare November 22, 2024 14:35
Preparation du deplacement des services JSON vers Norm
- objectif:
  - reutilisation du parser JSON dans Norm pour le pilotage par fichiers scenarios et json
  - extraction ce qui n'est pas specifique a Learning: tout, sauf JSONFile
  - nettoyage et mise en conformite avec la bibliotheque Norm

Impacts principaux
- suppression des references au type Continuous
- renommages:
  - KWTextService en TextService
  - KWIntVectorSorter en IntVectorSorter
- TextService:
  - enrichissement de la classe en rappatriant des methdoes de services generique provenant de JSONFile
  - maintenant, classe ancetre de
    - JSONFile
    - JSONTokenizer
    - KWTextTokenizer
- IntVectorSorter: nouveau fichier dedie au classes IntPairVector et IntVectorSorter, extrait de KWSortableIndex

Impacts detailles
- Supression des references à Continuous dans les services JSON
  - JsonLex.lex: utilisation desormais de strtod pour parser les doubles
  - supression des include inutiles
    - JSONTokenizer.h: plus d'inclusion de KWContinuous.h
    - KWTextService.h: plus d'inclusion de KWTokenFrequency.h
- JSONTokenizer
  - supression de la dependance au type Continuous
    - supression des methodes ReadContinuousValue et ReadKeyContinuousValue
    - methode interne DoubleToInt, pour se passer de KWContinuous::ContinuousToInt
  - impacts dans CCCoclusteringReport, lecture de fichier json
- TextServive:
  - issue d'un renommage de KWTextService en TextService
  - impacts mineurs lies au renommage
    - KWClass
    - KDTextFeatureConstruction
    - KDTextTokenSampleCollectionTask
    - KWTokenFrequency
    - JSONLex.lex
    - KWDRRuleLIbrary/KWDRTextualAnalysis
    - KWTest/Test
  - recupere l'essentiel des methodes de service provenant de JSONFile
  - maintenant classe ancetre de:
    - JSONFile
    - JSONTokenizer
    - KWTextTokenizer
- IntVectorSorter
  - nouveau fichier dedie au classes IntPairVector et IntVectorSorter
  - issu du renommage de KWIntVectorSorter en IntVectorSorter
    - impacts:
      - KWDatabaseFormatDetector
      - TextService
  - issu de KWSortableIndex, dont il est extraiut
  - usages dans KWDatabaseFormatDetector et TextService
Les fichiers suivants sont deplacés de Learning/KWData vers Norm/base:
- IntVectorSorter.*
- JSONTokenizer.*
- JsonLex.*
- TextService.*

Les fichiers CMakeLists.txt des deux librairies concernees sont adaptes:
- chacun ne decrit que ses cibleq Flex et Bison
- nouvelle gestion de la liste GLOB cppfiles, pour la rendre generique,
  en excluant explicitement les fichiers issus de Flex et Bison
Initialisation d'un analyseur syntaxique de json base sur l'analyseur lexical.
Adaptation de l'analyseur lexical pour etre utilse a la fois dans la classe existante JSONTokenizer
utilise pour le parsing specifique des rapport de coclustering, et le nouveau parser generique genere depuis Yacc.

Modification dans Dans Norm/base

Parametrage des analyseurs lexical et syntaxique
- CMakeLists.txt: mise en place de la compilation d'un parser
- JsonYac.yac: ebauche d'un parser vide
  - Initialisation du parser json avec des regles de grammaire correctes, inspirees de https://www.json.org/json-en.html
- JsonYac.hpp, JsonYac.cpp: fichier generes pour le parser
- JsonYac.lex: adaptation pour que les valeurs associee aux tkens soient utilisable depuis JsonYac.yac et la classe JSONTokenizer

TextService:
- rappatrieement de JsonToCString et AppendSubString en provenance de JSONTokenizer
- Test: rappatriement des test de conversions provenant de JSONTokenizer
- renommages:
  - CStringToJsonString -> CToJsonString
  - CStringToCAnsiString -> CToCAnsiString

Nouveau fichier JsonObject, avec toutes les classes de gestion d'un objet Json, avec implementation vide pour l'instant
- JsonValue
- JsonObject
- JsonArray
- JsonString
- JsonNumber
- JsonBoolean
- JsonNull
- JsonMember

JsonObject:
- debut d'implementation de la methode ReadFile
- acces aux fonctions de l'analyseur lexical, en friend pour la classe JSONTokenizer

JSONTokenizer
- utilisation des methodes protected de JsonObject pour acceder au methode du parser
- valeurs de l'enum initialisee avec les constantes de JsonYac.hpp
- JSONSTYPE jsonLastTokenValue: memorisation de la valeur du dernier token
- on utilise desormais le nouvel analyseur lexical
- la gestion du parametre ForceAnsi est desormais effectuee dans la classe elle meme,
  et non dans l'analyseur lexical genere depuis JsonLex.lex

CCCoclusteringReport (impact dans Learning/MODL_Coclustering)
- ReadJSONInterval: pas de conversion en Continuous des bornes des intervalles, pour pouvoir les comparer correctement
Implementation des classes
- JsonValue
- JsonObject
- JsonArray
- JsonString
- JsonNumber
- JsonBoolean
- JsonNull
- JsonMember

Implementation du parser JsonYac.yac
- gestion des erreurs
  - detection en erreur des cles dupliquees dans les objets json
  - detection basique d'erreur lexicale ou syntaxiques
  - liberation memoire en cas d'erreurs de parsing

Mise au point, par lecture/ecriture de fichier json

Refactoring leger
- Object::GetTranslatedClassLabel: supression de cette methode obsolete et non utlisee
Creation d'une nouvelle classe CommandFile, pour encapsuler la gestion du parametrage par des fichiers de commande
- gestion de fichiers de commande en entree et en sortie
- gestion du parametrage par search/replace
- gestion du fichier de parametrage json

La classe UIObject delegue desormais ses services de gestion des fichiers de commande a cette nouvelle classe

Refactoring de UIObject, qui desormais redirige ses services vers un objet interne statique commandFile
- supression des methodes de gestion des parametres de search/replace
- supression des parametres de gestion des ficgier de commande en entree et sorties
- renommage des fonctions de parametrage des options de la ligne de commande, qui ne font plus que du parametrage
  - OpenInputCommandFile -> InputCommand
  - OpenOutputCommandFile -> OutputCommand
- renommage des services nettoyage lie a la gestioopn de la ligne de commande
  - CloseCommandFiles -> CleanCommandLineManagement
  - ExitHandlerCloseCommandFiles -> ExitHandlerCleanCommandLineManagement
- UIObject::ParseMainParameters: impacts principaux
- renommage
  - CheckOptions -> CheckCommandLineOptions
@marcboulle marcboulle force-pushed the 230-extending-khiops-entries-with-a-lightweight-json-structure-for-scenarios branch 5 times, most recently from f3873f7 to 4ac2db9 Compare December 2, 2024 09:16
@marcboulle marcboulle requested review from lucaurelien and popescu-v and removed request for bruno-at-orange December 2, 2024 09:48
Copy link

@lucaurelien lucaurelien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meets requirements. Functionally Approved.

Copy link
Collaborator

@popescu-v popescu-v left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • A couple of comments on the Python code (see in the review)
  • Some comments on the spec (https://github.com/KhiopsML/khiops/wiki/Feature-V11:-Extension-du-pilotage-de-Khiops-via-les-sc%C3%A9narios):
    • Regarding the "quoted-printable" (QuoPri) format:
      • to my understanding, QuoPri is optimized for readability and was designed for data blobs where there are relatively few non-printable characters (e.g. in e-mails), while Base64 is designed for being more compact when there are lots of such non-printable characters (e.g. in non-textual files such as raw data, images etc)
      • hence, I think that the QuoPri alternative vs Base64 could also be discussed in light of the kinds of data that we would need to keep as bytes, in practice: if Base64 is used for bytes when we need to be "agnostic" about the encoding of the data (such as when we mix several encodings), then it seems that we cannot make any assumption about the ratio of non-printable characters with respect to the payload of the value in question, right? If this is true, then perhaps we could take out QuoPri from the spec?
    • regarding the decision of streaming the reading of the scenario file, as opposed to the JSON file which is read in-memory at once: at first sight, I would imagine the JSON file as being "bigger" (as it can contain rather big arrays for LOOPs), while the scenario file could be quite compact, at least in the "template" form, if used for a specific "task". Was this decision made to support iterative instantiation of a scenario template from potentially several JSON files, applied in a sequence on the scenario template?

@marcboulle
Copy link
Collaborator Author

Concernant le choix d'encodage Base64, comme indiqué, c'est essentiellement parce qu'il s'agit d'un encodage plus populaire.
L'alternative QuoPri n'est ici qu'évoquée comme un choix alternatif: cela ne fait plus partie de la spec, et ne sera pas documenté dans la documentation utilisateur.

Concernant le choix de streamer ou non le json et le scénario:

  • il faut avoir des limites
    • pour éviter les erreur utilisateur (paramétrage par des fichiers énormes erronés, faisant potentiellement planter l'outil)
    • pour éviter de gérer de la mémoire massive, au détriment de la mémoire nécessaire pour les traitements de data mining
  • chargement du json en mémoire
    • on limite la taille des json à 1 Mb, ce qui parait énorme vis-a-vis des besoins, mais reste négligeable par rapport à la gestion de la mémoire pour les traitements de data mining
    • il est nécessaire d'avoir toute la structure json en mémoire, car chaque clé peut être utilisée de façon aléatoire à toute ligne d'un scénario
    • le parsing d'un json est facile quand on charge tout; ce serait nettement plus complexe à gérer en stream
  • chargement des scénarios en stream
    • ce n'était pas vraiment indispensable: on aurait pu se limiter à une taille max
      • mais quelle taille max: toute transformation (par -O) d'un scénario avec paramétrage json résulte en un scénario plus gros (donc dépassant potentiellement alors sa taille max ...)
    • contrairement au json (dictionnaire de paramètres), un scenario est une suite séquentielle d'instructions, ce qui se prête naturellement à une gestion en stream
      • seule exception: le nombre de lignes (non vides) d'une structure de contrôle (IF ou LOOP), que l'on charge intégralement en mémoire
      • mais dans ce cas, on a limité ce nombre de ligne à 1000

@marcboulle marcboulle force-pushed the 230-extending-khiops-entries-with-a-lightweight-json-structure-for-scenarios branch from 4ac2db9 to bcb37d6 Compare December 3, 2024 07:59
Mise en place de tests complet pour la verification des fichiers de parametre json en
entree de la ligne de commande

UIObject
- ParseMainParameters
  - nouvelle option -j pour les fichiers de parametrage json
  - nouvelle option -O, comme -o mais sans rejouer les comandes
- CheckCommandLineOptions: ajout des contraintes entre les options

CommandFile
- LoadJsonParameters: lecture et verification du fichier de parametres json en entree
- CheckVariableName
- IsByteVariableName
- ToByteVariableName, ToStandardVariableName
- CheckStringValue
- ...

CommandFile
- nMaxVariableNameLength = 100: taille max des noms de variable
- lMaxInputParameterFileSize lMB: taille max des fichier de commande
- nMaxStringValueLength = 300: longueur max des avleurs de type chaine de caracteres

KWTextService: service d'encodege et decodage au format base64
- Base64StringToBytes
- BytesToBase64String

Tests unitaires
- ajout du test TextService::Test dans test/UnitTests/Norm_test.cpp, avec la reference base_TestService.txt

LearningTestTool
- extension de kht_test tester la gestion des fichiers de parametre json en entree
  - uniquement s'il existe un fichier test.json associe au test.prm dans le repertoire de test courant
- ajout d'une famille de test LearningTest/TestKhiops/JsonParameters
- kht_export.py: analyse heuristique du fichier de parametre json pour extraire les datasets utilises

Tests intensifs dans LearningTest\TestKhiops\JsonParameters
Reference
- issue #230  Extending Khiops entries with a lightweight json structure for scenarios #230
- #230
  - cette issue contient les specification detaillees de la fonctionnalité, donc on pourra
    extraire la documentation
  - cf. commentaire "Extension du pilotage de Khiops via les scénarios"
  - #230 (comment)

Choix d'implementation principaux
- le fichier de parametre json est lu et traite en entier de facon prealable, avec une taille limitee
- le fichier de commande est traite en flux, ce qui permet de n'avoir aucune limite de taille
- toute ligne de commande peut etre commentee, y compris les lignes du langage de pilotage de type IF ou LOOP
- les lignes d'un fichier de commande template n'ont pas besoin de se terminer par un commentaire
- toute __key__ du fichier de commande doit se trouver dans le fichier json
- toute key du fichier json doit etre utilise dans le fichier de commande
- les __key__ de parametrage json ne peuvent concerner que la partie parametrage utilisateur d'une valeur
- toute erreur ou incoherence dans les fichiers de commande et de parametrage json provoquent une erreur fatale

CommandFile
- methodes publiques
  - ReadInputCommand: refactoring et simplification
  - ReadWriteCommandFiles: pour ecrire le scenario en sortie sans rejouer les commandes
  - CloseInputCommandFile: detction potentielle d'erreur fatale a la fermeture, si scenario ou json incorrecte
- methodes privees principales de parsing des scenario et de leur langage
  - ResetParser
  - RecodeCurrentLineUsingJsonParameters
  - ParseInputCommand: analyse syntaxique des lignes d'un fichier de commande en entree
  - TokenizeInputCommand
  - GetFirstInputToken
- variables de travail du parser
  - prefixees par parser (ex: nParserState, sParserBlockKey, nParserLineIndex...)
  - maintenues le temps d'une session d'analyse, pour gerer le parser de scenario en flux
- contraintes
  - nMaxLineLength = 500: longueur max d'une ligne de fichier de commande
  - nLoopMaxLineNumber = 1000: Taille max d'un bloc d'instruction IF ou LOOP d'un fichier de commande
- constantes: renommees et etendues pour harmonisation
  - sCommentPrefix
  - sJsonKeyDelimiter
  - sByteJsonKeyPrefix
  - ...

JsonLex.lex
- STRINGERROR: variante de STRINGVALUE dans le cas d'erreur d'encodage utf8
JsonYac.yac
- regles impliquant STRINGERROR pour avoir des erreurs interpretable en cas de STRINGERROR

UIObject: prise en compte de l'option -O, pour les scenarios en sortie sans rejouer les commandes
- ParseMainParameters
- CheckCommandLineOptions

TextService::Set|GetForceUnicodeToAnsi
- parametrage avance de la conversion de lencodage Json vers un encodage C pas necessairement Utf8
  pour le cas des rapport Khiops
- modifie le comportement de la methode JsonToCString utilisee par le parser de Json
- utilise uniquement dans CCCoclusteringReport

LearningTestTool
- kht_test.py
  - --nop-output-scenario: "create an output scenario nop_output_test.prm in results dir, without replaying commands"
- _kht_families
  - ajout de la famille JsonParameters

Ajout de tests unitaires: test\UnitTests\Norm\results.ref\base_TextService.txt

Ajout de test CI/CD: test\LearningTest\TestKhiops\Standard\JsonSpliceJunction

Batterie de tests LearningTest\TestKhiops\JsonParameters
- une petite cinquantaine de test, les trois-quart concernant la detection des erreurs
Renommage pour des raisons d'homogenisation avec les classes JSONTokenizer et JSONFile
- classe JSONObject et ses sous classes
- JSONLex.lex, JSONYac.yac: parser de json
…files

La classe JSONFile possede deux types de methodes pour l'ecriture des nombres reel:
- WriteContinuous, WriteKeyContinuous:
  - niveau de precision des donnees elles-memes, gerees via le type Continuous, avec 10 digits de precision
  - null si valeur manquante
  - pour les valeur provenant des donnees (min, max, mean...)
- WriteDouble, WriteKeyDouble
  - niveau de precision des double en sortie (7 digits), suffisant pour l'interpretation utilisateur
  - pour les valeurs provenant des modeles (Level, Importance, Interest, courbes de lift...)

La correction porte ici sur deux indicateurs provenant des donnees, qui passe de double a  Continuous
pour l'ecriture dans les fichiers json:
- CCCoclusteringReport::WriteDimensionSummary: ecriture du min et du max
- KWDescriptiveSymbolStats::WriteJSONFields: ecriture du modeFrequency
@marcboulle marcboulle force-pushed the 230-extending-khiops-entries-with-a-lightweight-json-structure-for-scenarios branch from bcb37d6 to edd97f8 Compare December 3, 2024 08:11
@marcboulle marcboulle requested a review from popescu-v December 3, 2024 08:12
@popescu-v
Copy link
Collaborator

Concernant le choix d'encodage Base64, comme indiqué, c'est essentiellement parce qu'il s'agit d'un encodage plus populaire. L'alternative QuoPri n'est ici qu'évoquée comme un choix alternatif: cela ne fait plus partie de la spec, et ne sera pas documenté dans la documentation utilisateur.

Concernant le choix de streamer ou non le json et le scénario:

* il faut avoir des limites
  
  * pour éviter les erreur utilisateur (paramétrage par des fichiers énormes erronés, faisant potentiellement planter l'outil)
  * pour éviter de gérer de la mémoire massive, au détriment de la mémoire nécessaire pour les traitements de data mining

* chargement du json en mémoire
  
  * on limite la taille des json à 1 Mb, ce qui parait énorme vis-a-vis des besoins, mais reste négligeable par rapport à la gestion de la mémoire pour les traitements de data mining
  * il est nécessaire d'avoir toute la structure json en mémoire, car chaque clé peut être utilisée de façon aléatoire à toute ligne d'un scénario
  * le parsing d'un json est facile quand on charge tout; ce serait nettement plus complexe à gérer en stream

* chargement des scénarios en stream
  
  * ce n'était pas vraiment indispensable: on aurait pu se limiter à une taille max
    
    * mais quelle taille max: toute transformation (par -O) d'un scénario avec paramétrage json résulte en un scénario plus gros (donc dépassant potentiellement alors sa taille max ...)
  * contrairement au json (dictionnaire de paramètres), un scenario est une suite séquentielle d'instructions, ce qui se prête naturellement à une gestion en stream
    
    * seule exception: le nombre de lignes (non vides) d'une structure de contrôle (IF ou LOOP), que l'on charge intégralement en mémoire
    * mais dans ce cas, on a limité ce nombre de ligne à 1000

Okay, I get the argument that, unlike the JSON format, scenario template structure (except the LOOPs) warrants a correct streamed reading.

Copy link
Collaborator

@popescu-v popescu-v left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM:

  • from a functional standpoint.
  • the Python code.

Update the spec with:

  • deletion of the QuoPri format mention
  • statement of the limits for:
    • the size of the JSON file (1 MB)
    • the number of lines of a LOOP structure (1000).

@marcboulle marcboulle merged commit 0509d14 into dev Dec 3, 2024
53 checks passed
@marcboulle marcboulle deleted the 230-extending-khiops-entries-with-a-lightweight-json-structure-for-scenarios branch December 3, 2024 10:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type/Enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Extending Khiops entries with a lightweight json structure for scenarios
3 participants