Skip to content

Commit

Permalink
Choose a more interpretable default granularity for histograms
Browse files Browse the repository at this point in the history
Impact uniquement dans MHDiscretizerMODLHistogram::DiscretizeValues
- application generale de la regle de Terell-Scott
  // Les histogrammes MODL sont optimaux.
  // Pour chaque variable numerique, on produit une serie d'histogrammes interpretables, par granularite croissante.
  // Le probleme est que l'histogramme interpretable le plus fin est parfois tres complexe, de type "herisson",
  // ce qui peut derouter un utilisateur non expert. Ce probleme se produit dans environ 20% des cas.
  // Plutot que de montrer par defaut l'histogramme interpretable le plus fin, on propose d'utiliser la
  // regle de Terell-Scott  (cf. https://en.wikipedia.org/wiki/Histogram), avec un nombre de bins a (2 N)^1/3
  // Ce choix permet d'avoir par defaut un histogramme a la fois fin et plus simple a interpreter:
  // - en evitant des choix trop heuristiques de type "regle du coude", ou nombre maximal de "peaks"
  // - pas trop parcimonieux (comme la regle de Sturges en log(N))
  // - ne dependant pas des valeurs (comme la regle de Scott, qui utilise l'ecart type)
  // - tres simple a calculer
  // - avec une justification theorique, facile a defendre
- sauf dans le cas des discretisation avec nombre d'intervalles raisonnable
  // La regle de Terell-Scott est pertinente pour eviter les histogrammes de type "herisson" comportant des
  // centaines, voire des milliers d'intervalles: cela ameliore vraiment le choix de la granularite par defaut.
  // Par contre, dans le cas des discretisation avec faible nombre d'intervalles, l'application de cette
  // regle aboutit parfois a un choix de granulatite trop grossiere, avec perte d'information
  // De facon heuristique, on choisit donc de ne pas appliquer la regle de Terell-Scott s'il y a moins
  // de 100 intervalles

Deux jeux de test CICD de LearningTest\TestKhiops\Standard impactes:
- TestKhiops/Standard/AdultU
- TestKhiops/Standard/BugMPIWithErrors

Plus une dizaine de jeux de test de LearningTest impactes:
- TestKhiops/MissingValues/PreparationU
- TestKhiops/MultiTables/AuslanSecondary
- TestKhiops/Histograms/Adult
- TestKhiops/Histograms/ChallengingHistograms
- TestKhiops/Histograms/Criteo
- TestKhiops/Histograms/LunarCrater
- TestKhiops/Histograms/PredictionAgeMaxPartNumber
- TestKhiops/HistogramsLimits/Cauchy
- ...

Resultats satisfaisants dans la vaste majorite des cas, mais parfois perte significative d'information (cf. TenNormals)
Necessite d'etendre l'outil de visualisation pour explorer l'ensemble des granularites (fonctionnalite deja identifiee)
  • Loading branch information
marcboulle committed Nov 22, 2024
1 parent e3974a1 commit 89f3fcd
Show file tree
Hide file tree
Showing 13 changed files with 282 additions and 2,776 deletions.
6 changes: 3 additions & 3 deletions src/Learning/KWDataPreparation/KWClassStats.h
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,8 @@ class KWClassStats : public KWLearningReport

// Ecriture d'un rapport
// Accessible pour un rapport complet uniquement si statistiques calculees
// Sinon, seul un rapport minimaliste est produit, avec le specifications d'apprentissage
// plus eventuellement les statistiques descriptives cible si elles sont disponible, et les erreur
// Sinon, seul un rapport minimaliste est produit, avec les specifications d'apprentissage
// plus eventuellement les statistiques descriptives cibles si elles sont disponibles, et les erreurs
void WriteReport(ostream& ost) override;

// Parametrage de l'ecriture des rapports des attribut natif ou construits (defaut: false)
Expand Down Expand Up @@ -295,7 +295,7 @@ class KWClassStats : public KWLearningReport

// Ecriture du contenu d'un rapport JSON
// Accessible pour un rapport complet uniquement si statistiques calculees
// Sinon, seul un rapport minimaliste est produit, avec le specifications d'apprentissage
// Sinon, seul un rapport minimaliste est produit, avec les specifications d'apprentissage
void WriteJSONFields(JSONFile* fJSON) override;

// Verification de la validite des specifications
Expand Down
2 changes: 1 addition & 1 deletion src/Learning/KWLearningProblem/KWLearningProblemView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ void KWLearningProblemView::ComputeStats()
// On verifie a minima qu'une base est specifiee
bOk = GetLearningProblem()->CheckTrainDatabaseName();

// Test si on a pas specifie de dictionnaire d'analyse, pour le construire automatiquement a la volee
// Test si on n'a pas specifie de dictionnaire d'analyse, pour le construire automatiquement a la volee
if (bOk and GetLearningProblem()->GetTrainDatabase()->GetClassName() == "")
bOk = BuildClassFromDataTable();

Expand Down
61 changes: 40 additions & 21 deletions src/Learning/MHHistograms/MHDiscretizerMODLHistogram.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,14 @@ void MHDiscretizerMODLHistogram::DiscretizeValues(ContinuousVector* cvSourceValu
int n;
int nMissingValueNumber;
ContinuousVector cvActualValues;
int nTerrellScottMaxPartNumber;
int nRequiredMaxPartNumber;
int nMaxPartNumber;

require(GetHistogramSpec()->GetHistogramCriterion() == "G-Enum-fp");

// On memorise le nombre de partie max demandees
// En effet, on ne passe pas par lea contrainte de discretisatuion dans le cas des histogrammes
// En effet, on ne passe pas par la contrainte de discretisation dans le cas des histogrammes
// On exploite si necessaire la granularite des histogrammes pour gerer cette contrainte,
// en ayant prealablement optimise les histogrammes comme dans le cas classique
nRequiredMaxPartNumber = GetMaxIntervalNumber();
Expand Down Expand Up @@ -109,33 +111,50 @@ void MHDiscretizerMODLHistogram::DiscretizeValues(ContinuousVector* cvSourceValu
oaResultHistograms.Add(postprocessedOptimizedHistogram);
oaResultHistograms.Add(optimizedHistogram);

// Recherche du meilleur histogramme
if (postprocessedOptimizedHistogram != NULL)
bestHistogram = postprocessedOptimizedHistogram;
else
bestHistogram = optimizedHistogram;

// Cas avec contrainte sur le nombre max d'intervalles
// Les histogrammes MODL sont optimaux.
// Pour chaque variable numerique, on produit une serie d'histogrammes interpretables, par granularite croissante.
// Le probleme est que l'histogramme interpretable le plus fin est parfois tres complexe, de type "herisson",
// ce qui peut derouter un utilisateur non expert. Ce probleme se produit dans environ 20% des cas.
// Plutot que de montrer par defaut l'histogramme interpretable le plus fin, on propose d'utiliser la
// regle de Terrell-Scott (cf. https://en.wikipedia.org/wiki/Histogram), avec un nombre de bins de (2 N)^1/3
// Ce choix permet d'avoir par defaut un histogramme a la fois fin et plus simple a interpreter:
// - en evitant des choix trop heuristiques de type "regle du coude", ou nombre maximal de "peaks"
// - pas trop parcimonieux (comme la regle de Sturges en log(N))
// - ne dependant pas des valeurs (comme la regle de Scott, qui utilise l'ecart type)
// - tres simple a calculer
// - avec une justification theorique, facile a defendre
nTerrellScottMaxPartNumber = int(ceil(pow(2 * cvSourceValues->GetSize(), 1.0 / 3)));
nMaxPartNumber = nTerrellScottMaxPartNumber;

// La regle de Terrell-Scott est pertinente pour eviter les histogrammes de type "herisson" comportant des
// centaines, voire des milliers d'intervalles: cela ameliore vraiment le choix de la granularite par defaut.
// Par contre, dans le cas des discretisations avec faible nombre d'intervalles, l'application de cette
// regle aboutit parfois a un choix de granularite trop grossiere, avec perte d'information
// De facon heuristique, on choisit donc de ne pas appliquer la regle de Terell-Scott s'il y a moins
// de 100 intervalles
nMaxPartNumber = max(100, nMaxPartNumber);

// Nombre max partie en prenant en compte l'eventuelle contrainte utilisateur
if (nRequiredMaxPartNumber > 0)
nMaxPartNumber = min(nRequiredMaxPartNumber, nMaxPartNumber);

// On recherche l'histogramme interpretable le plus fin qui respecte la contrainte
bestHistogram = NULL;
for (n = oaResultHistograms.GetSize() - 1; n >= 0; n--)
{
// On garde le premier histogramme qui respecte la contrainte
if (bestHistogram->GetIntervalNumber() > nRequiredMaxPartNumber)
histogram = cast(MHHistogram*, oaResultHistograms.GetAt(n));

// On ne considere que les histogrammes interpretables, c'est a dire tous sauf eventuellement le dernier
if (not histogram->GetRaw())
{
// Parcours des histogramme candidats en partant de l'avant dernier, ce qui permet
// de traiter les cas ou le meilleur histogramme etait la version post-optimise ou non
bestHistogram = NULL;
for (n = oaResultHistograms.GetSize() - 2; n >= 0; n--)
if (histogram->GetIntervalNumber() <= nMaxPartNumber)
{
histogram = cast(MHHistogram*, oaResultHistograms.GetAt(n));
if (histogram->GetIntervalNumber() <= nRequiredMaxPartNumber)
{
bestHistogram = histogram;
break;
}
bestHistogram = histogram;
break;
}
assert(bestHistogram != NULL);
}
}
assert(bestHistogram != NULL);

// Transformation du meilleur histogramme en une table de contingence non supervisee
BuildOutputFrequencyTableAndBounds(bestHistogram, nMissingValueNumber, kwftTarget, cvBounds);
Expand Down
2 changes: 1 addition & 1 deletion src/Learning/MODL_Coclustering/CCLearningProblem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ boolean CCLearningProblem::CheckCoclusteringSpecifications() const
AddError("No coclustering variable specified");
bOk = false;
}
// Il doit y avoir au moins deux variables specifiee pour un co-clustering
// Il doit y avoir au moins deux variables specifiees pour un co-clustering
else if (analysisSpec->GetCoclusteringSpec()->GetAttributeNames()->GetSize() < 2)
{
AddError("At least two coclustering variables must be specified");
Expand Down
2 changes: 1 addition & 1 deletion src/Learning/MODL_Coclustering/CCLearningProblemView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ void CCLearningProblemView::BuildCoclustering()
// On verifie a minima qu'une base est specifiee
bOk = GetLearningProblem()->CheckDatabaseName();

// Test si on a pas specifie de dictionnaire d'analyse, pour le construire automatiquement a la volee
// Test si on n'a pas specifie de dictionnaire d'analyse, pour le construire automatiquement a la volee
if (bOk and GetLearningProblem()->GetDatabase()->GetClassName() == "")
bOk = BuildClassFromDataTable();

Expand Down
Loading

0 comments on commit 89f3fcd

Please sign in to comment.