Accueil / Articles

Jouer au volley-ball avec des agents IA pour prédire le chiffre d’affaires des restaurants.

Un hackathon sur le chiffre d’affaires des restaurants basé sur des agents IA contrôlés par des humains : audit des données, validation adaptée aux spécificités, ingénierie de caractéristiques, ensemblement et l’écart par rapport aux scores des leaders.

Éditeur : REACTAPP.TOP18 min de lecture4112 mots

Sources: **How ‘Playing Volleyball’ with AI Agents Helped Me Predict Restaurant Turnover —**and Win a Hackathon

Imaginez un hackathon où le modèle lui-même ne représente que la moitié du travail. L’autre moitié consiste à occuper un petit nombre d’agents IA avec des idées concurrentes, sans leur permettre de prendre une décision qui relève d’une personne. Ce texte décrit l’un de ces projets, issu d’un hackathon sur la prédiction du taux de rotation des clients dans les restaurants, organisé par Great Learning en partenariat avec l’UT Austin et son école de commerce McCombs. Il aborde un flux de travail d’apprentissage automatique simplifié, une validation stricte ainsi qu’un ensemble de méthodes qui, selon les propres dires du projet, a performé bien dans la compétition.

La couche d’orchestration était un framework appelé DontPanic. Ses flux de travail suivent la logique d’un échange de balles en volley-ball : un agent reçoit une tâche, planifie la prochaine action, transmet le travail à des rôles spécialisés et remet finalement le résultat, mais la décision revient toujours à un humain. Les agents accélèrent l’exécution, mais ils ne sont pas responsables du jugement.

1. La tâche

Les concurrents devaient prédire le chiffre d’affaires annuel des restaurants dans toute l’Inde à partir de 3 493 enregistrements d’entraînement étiquetés et de 500 enregistrements de test non étiquetés. Il existait environ 32 indicateurs bruts concernant l’emplacement, la cuisine, le format du restaurant, les équipements, la popularité sur les réseaux sociaux et les notes de satisfaction des clients. Ici, « chiffre d’affaires » désigne les ventes annuelles, et non le taux de turnover du personnel. La métrique utilisée était l’RMSE, qui évalue les erreurs au carré avant de calculer une moyenne, de sorte que quelques prédictions extrêmement erronées peuvent fausser fortement le résultat.

L’objectif n’était pas de créer le modèle le plus sophistiqué. Il s’agissait plutôt d’un système de prévision qui puisse être justifié : auditer les données, comprendre les aspects économiques du secteur cible, concevoir des indicateurs pertinents sur le plan commercial, valider les modèles grâce à une validation croisée fiable, éliminer les améliorations qui ne se reproduisaient pas, et enfin séparer la véritable capacité de généralisation du modèle des ajustements spécifiques à la compétition.

2. Les outils

Tout était géré dans VS Code sous forme de package Python modulaire situé dans src/, au lieu d’un amas d’expériences de notebooks non liées entre elles. Ce choix s’est avéré payant, car le nettoyage, la construction des fonctionnalités, la validation et l’inférence se faisaient tous à l’aide du même code réutilisable. Ainsi, une fonctionnalité ne pouvait pas se comporter différemment pendant l’entraînement et lors de la production finale.

3. Orchestration agente

DontPanic était le cadre qui structurait le projet. L’autonomie totale n’était jamais l’objectif ; il s’agissait plutôt de permettre à plusieurs agents d’agir rapidement tout en définissant clairement la structure de décision : qui explore, qui construit, qui remet en question les résultats, quel artefact est fiable et quand le travail doit être renvoyé à une personne pour validation.

Cela est important car les expérimentations en apprentissage automatique proposent sans cesse une étape suivante plausible. Un agent fonctionnant de manière autonome continuera d’optimiser le signal qu’il a vu récemment, et un humain peut perdre de vue si l’hypothèse, le protocole de validation ou le critère de promotion a changé en sous-main. Pensez à DontPanic comme au règlement selon lequel s’est déroulée l’expérience : les agents avaient la liberté d’avancer dans le processus, mais jamais de réécrire les règles sans que personne ne s’en aperçoive.

Chaque exécution d’agent était décrite par un manifeste. Un exemple, pour une exécution via la CLI de Claude, précise les commandes et fichiers requis, la manière dont la prêtresse est vérifiée, quels éléments relèvent du framework, ce que l’opérateur doit faire et quelles actions sont interdites :

{
  "schema_version": "1.0.0",
  "id": "agent-claude-cli",
  "title": "Claude CLI agent runtime",
  "kind": "agent_cli",
  "category": "agent-cli",
  "summary": "External agent CLI used by DontPanic as an implementer, auditor, or goal-auditor runtime.",
  "setup_required": true,
  "setup_doc": "docs/GETTING_STARTED.md#setup-tracks--pick-yours",
  "default_in_profiles": ["core"],
  "use_cases": ["U1", "U3", "U4", "U5", "U6", "U7"],
  "requires": {
    "commands": ["claude"],
    "env": [],
    "files": ["~/.dontpanic/config.json"],
    "services": ["Claude account / Claude Code or CLI runtime"],
    "auth": ["Claude CLI authenticated by operator"],
    "config": [
      "roles.implementer",
      "roles.auditor",
      "roles.goal_auditor"
    ]
  },
  "verify": {
    "doctor_profiles": ["core"],
    "probes": ["cli:claude", "agent_role_config"],
    "readiness": "Ready when the claude command is present and the selected DontPanic role points at claude."
  },
  "owner_boundary": {
    "dontpanic_core": [
      "agent registry entry",
      "dispatch envelope",
      "audit/signoff capture",
      "quota and breaker handling",
      "role configuration"
    ],
    "adapter": [
      "subprocess executor for claude",
      "stream/output parsing",
      "timeout and envelope normalization"
    ],
    "operator": [
      "Claude CLI installation",
      "account authentication",
      "model/quota management",
      "local CLI updates"
    ]
  },
  "mutation_boundary": {
    "writes_dontpanic_state": true,
    "external_mutation": "agent_subprocess_execution",
    "allowed_path": "DontPanic supervisor invokes claude through the governed executor and captures artifacts through audit/signoff writers.",
    "forbidden": [
      "Bypassing supervisor gates",
      "Untracked agent writes outside declared plan scope",
      "Treating Claude account configuration as DontPanic-owned state"
    ]
  },
  "notes": [
    "Claude is an external runtime, not bundled by DontPanic.",
    "Core profile may also support other agent CLIs; this manifest records the Claude-specific path."
  ],
  "setup_steps": [
    {
      "id": "install_claude_cli",
      "what": "Install the Claude CLI so DontPanic can spawn agent runs through the governed executor.",
      "automatable": true,
      "command_template": "npm install -g @anthropic-ai/claude-cli",
      "verify_probe": "cli:claude",
      "human_required_reason": null
    },
    {
      "id": "authenticate_claude",
      "what": "Authenticate the Claude CLI against the operator's Claude account or API key.",
      "automatable": false,
      "command_template": "claude auth login --token <YOUR_CLAUDE_API_TOKEN>",
      "verify_probe": "agent_role_config",
      "human_required_reason": "Operator must approve scopes for their Claude account and treat the API token as a secret; DontPanic does not provision Claude credentials on the operator's behalf."
    }
  ]
}

3.1 Le rallye en cinq étapes

  1. Servir : avant tout début d’expérience, l’être humain a défini la question métier, les contraintes, les métriques, les critères d’acceptation ainsi que le modèle actuel de référence.
  2. Recevoir : un agent de découverte a examiné les données, identifié les anomalies et proposé des hypothèses testables au lieu de réécrire immédiatement le modèle.
  3. Fixer : une fois l’hypothèse approuvée, un agent d’implémentation l’a traduite en code modulaire, en artefacts déterministes et en une définition d’expérience pouvant être réexécutée.
  4. Voler : un agent d’évaluation a vérifié la présence de fuites, une validation croisée défectueuse, des valeurs aléatoires instables et une origine incertaine des données, tout en s’assurant que les gains apparents résistaient à une comparaison avec le modèle de référence actuel.
  • L’envoi aux humains : la décision de promouvoir, rejeter, étendre ou modifier une stratégie revenait à la personne. Les agents pouvaient proposer des solutions, mais c’était l’humain qui décidait.
  • Le véritable avantage résidait dans un parallélisme contrôlé plutôt que dans une vitesse de codage brute. Différents agents pouvaient se concentrer sur différentes façons d’échouer, tandis que le processus géré par l’humain protégeait l’intention du produit, sa crédibilité commerciale et la responsabilité. Le résultat ressemblait davantage à celui d’une petite équipe de développement qu’à une simple session avec un chatbot.

    3.2 Livraison Lean ML

    Le flux de travail des agents a également été adapté à un cycle de vie du produit « lean », dans le but de rendre les expérimentations peu coûteuses en phase de développement, d’empêcher la falsification des données en phase de test utilisateur et de rendre les états de production difficiles à modifier par accident. La séparation entre DEV, UAT et PROD est devenue d’autant plus précieuse que le nombre d’expérimentations augmentait. Un nouvel agent pouvait rejoindre le système sans dépendre de la mémoire conversationnelle, car les données fiables étaient conservées sous forme de fichiers, de manifestes, de prédictions hors cadre (OOF), d’un registre des meilleurs agents et de décisions de promotion écrites. Le principe à retenir : laisser les agents assurer la vitesse, tandis que les artefacts et les contrôles garantissent l’exactitude des informations.

    4. Vérifier les données avant de les croire

    Au préalable du modélisage, les fichiers d’entraînement et de test ont été analysés avec SHA-256 et vérifiés colonne par colonne. Il ne s’agissait pas de bureaucratie : plusieurs particularités auraient pu modifier l’espace des caractéristiques au moment de l’inférence et nuire aux prédictions sans provoquer d’erreur visible.

    • Le jeu de données d’entraînement comptait 3 493 lignes et 34 colonnes, tandis que le jeu de données de test comptait 500 lignes et 33 colonnes, car il ne contenait pas de variable cible.
    • Les colonnes Ambience et Restaurant Tier présentaient des types de données différents dans les jeux d’entraînement et de test, ce qui nécessitait une conversion explicite afin que les modèles reçoivent des caractéristiques identiques.
    • Une colonne était orthographiée Endorsed By dans le jeu d’entraînement et Endoresed By dans le jeu de test. Sans mise en correspondance, le pipeline aurait pu générer une caractéristique de test complètement absente.
  • Il n’y avait ni lignes dupliquées ni vecteurs de prédicteurs redondants, donc les données étaient propres au niveau des lignes.
  • Numéro d’inscription agissait comme un identifiant, et non comme un prédicteur commercial (corrélation de Pearson d’environ -0,017, corrélation de Spearman d’environ -0,022). Il a été exclu des modèles et conservé uniquement pour associer les lignes à la fin.
  • 5. Exploration des données

    5.1 Un objectif fortement biaisé vers la droite

    Le chiffre d’affaires annuel présentait une longue queue droite. Le 90e percentile se situait autour de 48 millions de roupies, le 95e autour de 57 millions de roupies, le 99e autour de 100 millions de roupies, et la valeur maximale atteignait 400 millions de roupies, avec un degré d’asymétrie brut d’environ 6,65. Une transformation log1p a rendu la distribution presque symétrique, et les valeurs cibles brutes, celles obtenues après log1p ainsi que celles selon la méthode Yeo-Johnson ont été comparées, une variante basée sur la surface au sol ayant été testée par la suite. En termes commerciaux, la plupart des restaurants se situent dans une fourchette de revenus assez étroite, tandis qu’un petit groupe de établissements très grands génère des erreurs financières importantes chaque fois que leurs données ne sont pas prises en compte.

    Cette queue n’était pas seulement visuellement inhabituelle : elle dominait les pertes. L’analyse des erreurs a montré qu’un petit nombre de restaurants pour lesquels les prédictions étaient les plus fausses représentaient une grande part de l’erreur totale au carré, ce qui a fait de la détection de cette queue une composante essentielle de l’évaluation.

    5.2 Les données manquantes nécessitent plus d’une règle

    Les valeurs manquantes n’ont pas été traitées comme un phénomène unique. Certaines reflétaient de simples lacunes de qualité des données. D’autres reflétaient la structure commerciale : par exemple, une note d’animation laissée vide parce que le lieu ne propose aucune animation. Une règle d’imputation unique pour les deux cas détruirait des informations utiles.

    Les cas de perte de données ordinaires, tels que la popularité sur les réseaux sociaux, la note globale, l’atmosphère et le niveau, ont été gérés grâce à une imputation basée sur les données d’entraînement, accompagnée d’indicateurs explicites de perte de données. Cela permet à un modèle de distinguer une valeur estimée du fait qu’aucune observation n’a été faite. Les cas de perte de données liés à la structure, concernant la musique en direct, les spectacles de comédie, les offres avantageuses et les sports en direct, ont été représentés par des indicateurs d’disponibilité tels que has_live_music et has_comedy. Une note laissée vide peut signifier que ce service n’existe pas, ce qui représente un état commercial différent d’une note inconnue.

    ORDINARY_MISSINGNESS_FLAGS = {
        "facebook_popularity_quotient": "facebook_missing",
        "instagram_popularity_quotient": "instagram_missing",
        "overall_restaurant_rating": "overall_rating_missing",
        "ambience": "ambience_missing",
        "restaurant_tier": "tier_missing",
    }
    
    STRUCTURAL_AVAILABILITY_FLAGS = {
        "live_music_rating": "has_live_music",
        "comedy_gigs_rating": "has_comedy",
        "value_deals_rating": "has_value_deals",
        "live_sports_rating": "has_live_sports",
    }
    
    
    def add_missingness_features(df: pd.DataFrame) -> pd.DataFrame:
        """Add ordinary-null and structural-availability indicators before imputation."""
        required_columns = set(ORDINARY_MISSINGNESS_FLAGS) | set(
            STRUCTURAL_AVAILABILITY_FLAGS
        )
        missing_columns = sorted(required_columns - set(df.columns))
        if missing_columns:
            raise KeyError(f"Missing required columns for missingness features: {missing_columns}")
    
        df = df.copy()
        for column, flag in ORDINARY_MISSINGNESS_FLAGS.items():
            df[flag] = df[column].isna().astype("int8")
        for column, flag in STRUCTURAL_AVAILABILITY_FLAGS.items():
            df[flag] = df[column].notna().astype("int8")
        return df
    

    5.3 Chaos catégoriel : 297 orthographes de villes

    La colonne « Ville » contenait 297 orthographes brutes pour un ensemble bien plus restreint de marchés réels. La casse et les espaces variaient, des fautes d’orthographe et des alias apparaissaient, et certaines entrées étaient des chaînes composées désignant plusieurs villes. Un processus de nettoyage reproductible combinait une vérification par correspondance floue à l’aide de rapidfuzz, un dictionnaire d’alias soigneusement sélectionné et une analyse explicite des entrées composées. Les indicateurs de villes rares ainsi que les caractéristiques de fréquence des villes ont été conservés, car la taille du marché et la qualité des données peuvent toutes deux influencer les estimations du volume d’affaires. L’étape de correspondance ne propose que des candidats à l’examen humain et ne modifie jamais les valeurs elles-mêmes :

    def generate_city_candidate_matches(
        city: pd.Series,
        minimum_similarity: float = 80.0,
    ) -> pd.DataFrame:
        """Return nearest fuzzy matches for review; this never changes city values."""
        normalized = normalize_city_values(city, aliases={})
        counts = normalized.value_counts()
        names = [name for name in counts.index if name != "unknown"]
        rows = []
        for raw_city in names:
            candidates = [candidate for candidate in names if candidate != raw_city]
            match = process.extractOne(raw_city, candidates, scorer=fuzz.ratio)
            if match is not None and match[1] >= minimum_similarity:
                rows.append(
                    {
                        "Raw City": raw_city,
                        "Candidate": match[0],
                        "Similarity": float(match[1]),
                        "Count": int(counts[raw_city]),
                    }
                )
        return pd.DataFrame(
            rows,
            columns=["Raw City", "Candidate", "Similarity", "Count"],
        ).sort_values(["Similarity", "Count", "Raw City"], ascending=[False, False, True])
    

    5.4 Paires de variables

    Tant le type de restaurant que le niveau de la ville expliquent de manière significative le chiffre d’affaires. Le format du restaurant influence la moyenne du montant de la commande, le volume d’activité et le modèle opérationnel, tandis que le niveau de la ville constitue un indicateur approximatif de la densité de la demande, du pouvoir d’achat et de l’échelle du marché. Les évaluations individuelles étaient utiles mais sujets à de nombreuses variations. La nourriture, l’hygiène, la relation qualité-prix, le service et l’atmosphère ne sont pas des facteurs indépendants ; ils interagissent avec le format, l’âge du restaurant, le marché, la visibilité sociale et sa positionnement, ce qui a justifié l’utilisation à la fois d’algorithmes d’apprentissage non linéaires et de mécanismes d’interaction soigneusement contrôlés.

    5.5 Le contexte plus large

    Les corrélations de Pearson et de Spearman révélaient des résultats différents. Plusieurs facteurs prédictifs présentaient un lien monotone bien plus fort que linéaire avec le taux de rotation du personnel, ce qui permettait d’obtenir des signaux utiles même lorsque l’ajustement à une ligne droite était insuffisant. Des vérifications de soutien pair-à-pair ont été ajoutées afin que des corrélations extrêmes reposant sur un très petit nombre d’observations superposées ne soient pas confondues avec une structure solide.

    5.6 Caractéristiques basées sur le temps

    Les dates d’ouverture ont été décomposées en composantes calendaires et en caractéristiques liées à l’âge. Les restaurants plus anciens avaient tendance à générer davantage de revenus, mais cela était considéré comme une association plutôt que comme une affirmation causale, étant donné que l’âge pouvait représenter un indicateur du niveau de reconnaissance de la marque, des locaux encore en activité, de la demande répétée ou de l’échelle accumulée. Une date de référence fixe permettait de garantir la reproductibilité de ces caractéristiques.

    6. Un problème d’déséquilibre dans une tâche de régression

    La régression ne présente pas de déséquilibre de classes au sens de la classification, mais ce problème existait sous une forme similaire : les valeurs extrêmes étaient rares et coûteuses. Avec le RMSE, quelques résidus importants peuvent dominer la somme des erreurs carrées. Lors des diagnostics, les observations les plus défavorables représentaient une part disproportionnée, de sorte qu’un modèle pouvait sembler performant pour la plupart des restaurants tout en obtenant de mauvais résultats après avoir manqué quelques entreprises à très hauts revenus.

    La validation a donc fait appel à une validation croisée en 5 groupes stratifiés par cible. La variable cible continue a été divisée en 10 tranches de quantiles uniquement pour attribuer les groupes, tandis que les modèles continuaient d’être entraînés et évalués sur le chiffre d’affaires brut. Les vérifications finales de promotion ont été répétées avec des séquences aléatoires indépendantes (17, 42 et 101). Cela a réduit les chances qu’un candidat devienne le meilleur simplement parce qu’une division chanceuse avait placé les valeurs extrêmes dans des groupes plus faciles.

    7. Ingénierie des caractéristiques

    Les fonctionnalités ont été divisées en transformations sans état et en statistiques ajustées. La logique sans état fonctionnait de manière identique sur les données d’entraînement et de test. Tout ce qui était ajusté, qu’il s’agisse de médianes, de cartes de fréquence ou d’encodages, était appris uniquement à partir des données d’entraînement, versionné puis réutilisé lors de l’inférence. Cela permettait de garder le processus auditable et de prévenir les écarts subtils entre données d’entraînement et de test induits par le prétraitement.

    • Des indicateurs de manque et de disponibilité, tels que décrits ci-dessus.
    • Des caractéristiques calendaires issues de la date de début (année, mois, trimestre, jour de la semaine), ainsi que l’âge en mois et en années et une catégorie d’« ère de début » divisée en quatre groupes.
    • Un comptage des cuisines, obtenu en analysant la liste des cuisines séparées par des virgules.
    • Une hiérarchie des villes allant de la ville brute à la ville nettoyée, puis à la ville principale, enfin à l’État, avec des indicateurs pour les entrées composées, les régions et les alias, ainsi que des indicateurs de fréquence et de rareté pour trois seuils différents.
  • Interactions catégorielles qui capturent des combinaisons ayant une économie différente de celle de leurs composants : thème avec type de restaurant, thème avec emplacement, alcool avec type, ainsi que certaines associations entre divertissements et format.
  • Interactions numériques pour la famille linéaire régularisée : niveau du restaurant avec niveau de la ville, visibilité sociale avec le niveau, expérience client avec visibilité sociale, et âge avec popularité. Celles-ci ont transformé certaines relations commerciales non linéaires en une forme que Ridge pouvait utiliser.
  • Encodages de fréquence et ordinaux pour chaque colonne catégorielle, appris sur les données d’entraînement et réutilisés sur celles de test.
  • Le pipeline principale utilisait 71 prédicteurs. D’autres familles de variables artificielles ont été testées par ablation plutôt que d’être conservées automatiquement, et une caractéristique a obtenu sa place grâce à une amélioration progressive de la validation, et non en raison d’un attrait intuitif.

    8. Intégration de données de référence externes

    Une autre branche de recherche a associé les données relatives aux villes et États nettoyées à des données de référence géographiques et économiques publiques. La question était de savoir si la capacité du marché local, l’urbanisation, l’intensité du tourisme et l’ampleur de la population apportaient des informations supplémentaires par rapport aux seuls attributs du restaurant lui-même.

    • Pour chaque ville : population, nombre et taille des ménages, logarithme de la population, coordonnées, ainsi que des indicateurs oui/non pour les métropoles, la région de la capitale nationale, les villes côtières, les capitales d’État et les villes comptant plus d’un million d’habitants.
    • Au niveau de l’État : population, population urbaine et son pourcentage, densité, revenu national par habitant, PIB et revenu national par habitant, ainsi que le nombre de visiteurs touristiques nationaux et étrangers, avec une valeur dérivée du tourisme par habitant.

    Le résultat a été délibérément conservateur. Plusieurs caractéristiques externes ont montré des signaux lors de tests isolés, mais la famille n’a pas amélioré l’ensemble final selon le protocole complet de substitution et de promotion, ce qui l’a empêchée d’intégrer la catégorie des champions purs. C’est une leçon utile pour la modélisation commerciale : une variable peut sembler plausible et prédictive à elle seule, mais son impact diminue fortement en présence de signaux corrélés plus forts.

    9. Modèles et pourquoi

    Les données comprenaient des évaluations continues, des lacunes sporadiques et structurelles ainsi que des catégories à haute cardinalité, ce qui a conduit plusieurs familles de modèles à concourir en tant qu’hypothèses, au lieu d’assumer qu’un seul algorithme dominerait. Ridge et ElasticNet se sont avérés inattendument compétitifs face à CatBoost. Ce n’était pas une défaite pour les méthodes de boosting : cela reflétait plutôt le niveau élevé de structure présent dans les caractéristiques générées artificiellement et l’utilité de la régularisation lorsque de nombreux prédicteurs liés coexistent. Les modèles en arbre restaient importants, car ils permettaient de capturer des interactions non linéaires que la famille linéaire ne peut pas exprimer directement.

    10. Ajustement

    Pendant le développement, chaque famille a bénéficié de son propre essai Optuna, évalué à l’aide du RMSE hors pli provenant de plis stratifiés par cible et répétés. La sélection des candidats et leur promotion reposaient sur une validation interne, et non sur le classement des meilleurs résultats, distinction importante car la phase ultérieure d’optimisation pour la compétition ne doit pas être confondue avec la généralisation.

    Le mélange le plus performant combinait CatBoost, Ridge, ElasticNet ainsi qu’une variante de Ridge à racine carrée stabilisée en variance (WR01) dans des proportions d’environ 30 %, 10 %, 6 % et 54 %. Son RMSE hors pli répété était d’environ ₹20,10 M. La forte proportion de WR01 était révélatrice : le modèle linéaire transformé n’était pas le meilleur apprenant indépendant, mais sa géométrie d’erreur différente en faisait un élément très précieux au sein de l’ensemble.

    11. Ce qui a aidé et ce qui n’a pas aidé

    Chaque expérience a été enregistrée avec sa famille de caractéristiques, sa configuration de modèle, son plan de validation, ses prédictions en dehors du domaine d’entraînement et la décision relative à sa promotion. Les progrès ont été réalisés en rejetant fermement les idées faibles tout en promouvant activement celles qui se révélaient solides.

    • D’abord, une ligne de référence a été établie. Les prédicteurs moyens et médians ont fixé une limite inférieure pour l’RMSE à environ 21,6 à 21,7 millions, ce qui a permis d’identifier facilement les modèles qui n’étaient que des versions complexes.
    • La régularisation était cruciale. Les méthodes Ridge et ElasticNet ont surpassé le OLS classique d’une marge importante, car l’espace généré contenait des signaux corrélés ainsi que des interactions.
    • CatBoost a apporté des capacités non linéaires et catégorielles, mais on ne pensait pas que son ajustement puisse être utile. Un résultat prometteur avec CatBoost a amélioré la séparation d’optimisation d’environ 258 000, mais n’a pas tenu lors de tests indépendants, ce qui a conduit à son rejet en raison d’un surapprentissage lors de la sélection.
  • L’ingénierie des caractéristiques dépendait du modèle. Les encodages de la cible, les interactions, les fréquences des catégories et les caractéristiques liées à la durée d’occupation figuraient parmi les familles de caractéristiques les plus utiles, tandis que plusieurs agrégats intuitifs apportaient peu d’avantage une fois des prédicteurs plus puissants disponibles.
  • L’enrichissement avec des données externes relatives à la ville et à l’État a donné un résultat négatif utile : certaines tests de composants se sont améliorés, la substitution par des ensembles s’est détériorée, et cette approche n’a pas réussi à atteindre le niveau du meilleur modèle.
  • L’ensemblement a procuré l’amélioration la plus fiable, car CatBoost et les variantes linéaires régularisées commettaient des erreurs de manière différente. L’objectif était d’obtenir l’erreur carrée validée la plus faible parmi les modèles complémentaires, et non une complexité maximale.
  • Les classificateurs de queue et les modèles de correction des résidus ont montré un potentiel diagnostique prometteur, mais une amélioration de l’RMSE insuffisante pour justifier leur utilisation. L’utilité en matière de diagnostic et l’utilité en prévision ont été considérées comme deux questions distinctes.
  • 12. Scores des différents champions

    Le projet a produit deux types de champions : un modèle propre sélectionné par validation croisée répétée, et un champion de compétition ajusté à l’environnement d’évaluation. Un troisième point de contrôle, S08, montre l’effet d’une correction délibérément limitée du « tail », avant les étapes plus poussées spécifiques à la compétition qui ont suivi. Ces scores répondent à des questions différentes et ne constituent pas des mesures interchangeables de généralisation.

    Du côté de la compétition, la soumission LB05 a réduit le RMSE public de ₹9,195,461,72 par rapport à S08, soit environ 76,1 %. Il s’agit d’un résultat sur le classement, et non d’une preuve que le modèle déployable généralise soudainement avec un RMSE de ₹2,89 M. Voici le script qui intègre les composants figés et écrit la première soumission mixte :

    from __future__ import annotations
    
    from datetime import datetime, timezone
    from pathlib import Path
    import time
    import json
    
    import joblib
    import numpy as np
    import pandas as pd
    
    from src import config, data, models, submission
    from src.pipeline import FeaturePipeline
    from main import _prepare_catboost_matrices, _prepare_model_matrices
    
    
    S01_ID = "S01"
    S01_WEIGHTS = {"catboost": 0.50, "ridge": 0.45, "elasticnet": 0.05}
    
    
    def run(output_dir: Path | None = None) -> tuple[Path, Path]:
        output_dir = output_dir or config.SUBMISSIONS_DIR
        output_dir.mkdir(parents=True, exist_ok=True)
        manifest_path = config.EXPERIMENTS_DIR / "frozen_v1_manifest.json"
        if not manifest_path.exists():
            raise FileNotFoundError(f"Frozen V1 manifest is required: {manifest_path}")
        manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
        if manifest.get("feature_pipeline") != "processed_v1" or manifest.get("predictor_count") != 71:
            raise ValueError("Frozen V1 manifest does not describe the processed_v1 71-predictor contract")
    
        train_raw = data.load_train()
        test_raw = data.load_test()
        pipeline = FeaturePipeline().fit(train_raw)
        train = pipeline.transform(train_raw)
        test = pipeline.transform(test_raw)
        target = config.INTERNAL_TARGET
    
        X_train = train.drop(columns=[target, config.INTERNAL_ID_COL], errors="ignore")
        X_test = test.drop(columns=[config.INTERNAL_ID_COL], errors="ignore")
        y = train[target].astype(float).to_numpy()
        X_numeric, X_test_numeric = _prepare_model_matrices(X_train, X_test)
        X_cat, X_test_cat = _prepare_catboost_matrices(X_train, X_test)
    
        predictions = {}
        timings = {}
    
        start = time.perf_counter()
        catboost = models.get_catboost(**{
            key: value for key, value in manifest["catboost_config"].items()
            if key != "native_categorical"
        })
        cat_features = [
            index for index, dtype in enumerate(X_cat.dtypes)
            if str(dtype) in {"object", "string", "category"}
        ]
        catboost.fit(X_cat, y, cat_features=cat_features, verbose=False)
        predictions["catboost"] = catboost.predict(X_test_cat)
        timings["catboost_seconds"] = time.perf_counter() - start
    
        start = time.perf_counter()
        ridge = models.get_ridge(**manifest["ridge_config"])
        ridge.fit(X_numeric, y)
        predictions["ridge"] = ridge.predict(X_test_numeric)
        timings["ridge_seconds"] = time.perf_counter() - start
    
        start = time.perf_counter()
        elasticnet = models.get_elasticnet(**manifest["elasticnet_config"])
        elasticnet.fit(X_numeric, y)
        predictions["elasticnet"] = elasticnet.predict(X_test_numeric)
        timings["elasticnet_seconds"] = time.perf_counter() - start
    
        weights = manifest["ensemble_weights"]
        blend_prediction = sum(weights[name] * predictions[name] for name in weights)
        submission_df = submission.build_submission(test_raw, blend_prediction)
        submission_path = output_dir / "S01_frozen_v1_blend.csv"
        submission.write_submission(submission_df, submission_path, expected_rows=len(test_raw))
    
        component_paths = {}
        for name, prediction in predictions.items():
            component_paths[name] = output_dir / f"S01_{name}_predictions.csv"
            pd.DataFrame({config.ID_COL: test_raw[config.ID_COL].to_numpy(), config.TARGET: prediction}).to_csv(
                component_paths[name], index=False
            )
    
        metadata = {
            "submission_id": S01_ID,
            "model": "Frozen V1 Blend",
            "feature_version": "frozen production",
            "oof_rmse": 20097389.205713525,
            "catboost_weight": S01_WEIGHTS["catboost"],
            "ridge_weight": S01_WEIGHTS["ridge"],
            "elasticnet_weight": S01_WEIGHTS["elasticnet"],
            "public_leaderboard_rmse": None,
            "leaderboard_rank": None,
            "timestamp_utc": datetime.now(timezone.utc).isoformat(timespec="seconds"),
            "submission_file": str(submission_path),
            "component_files": {name: str(path) for name, path in component_paths.items()},
            "fit_method": "fit_each_frozen_component_on_all_3493_processed_v1_training_rows",
            "catboost_production_iterations_rule": "fixed_manifest_value_no_early_stopping",
            "manifest_path": str(manifest_path),
            **timings,
        }
        metadata_path = output_dir / "S01_metadata.json"
        metadata_path.write_text(json.dumps(metadata, indent=2), encoding="utf-8")
        return submission_path, metadata_path
    
    
    if __name__ == "__main__":
        for path in run():
            print(path)
    

    13. Performance en compétition versus généralisation

    Lorsque le modèle finalisé et son protocole de validation furent fixés, une étape d’optimisation spécifique pour la compétition améliora les prédictions sur l’ensemble d’évaluation réservé. Les détails techniques en dehors du champ d’étude ici ; ce qui est important sur le plan méthodologique, c’est que cette étape s’appuya sur des informations provenant de l’environnement de compétition, et non sur de nouveaux données d’entraînement étiquetées.

    Ainsi, le RMSE final affiché, d’environ 2,893 millions de roupies, doit être interprété comme spécifique à cette compétition et potentiellement surajusté à cet ensemble d’évaluation. L’estimation fiable de la performance du modèle sur de nouveaux restaurants reste le résultat du CV itératif, soit environ 20,10 millions de roupies. Il est essentiel de conserver ces deux valeurs distinctes pour une interprétation à la fois scientifique et commerciale.

    Dans un déploiement réel de prévision, la couche optimisée pour les compétitions serait supprimée à moins qu’un signal de calibration équivalent ne soit disponible à l’avance. Le système déployable utiliserait le pipeline de caractéristiques et l’ensemble déjà prêts, surveillerait les résidus et les écarts de distribution, puis se reformerait dès l’arrivée des données réelles sur les ventes.

    14. Hypothèses

    • On a supposé que les données d’entraînement et de test provenaient de processus commerciaux suffisamment similaires pour que la validation croisée ait un sens. Aucune correction formelle liée au décalage des covariables n’a été appliquée, se limitant à des vérifications de schéma et de distribution.
    • Le numéro d’enregistrement n’était qu’un identifiant strict et a été exclu du modèle final, car il ne présentait aucun lien valable avec les ventes.
    • On a supposé que les évaluations manquantes des services offerts contenaient des informations structurelles lorsque ce service était absent, ce qui a guidé la conception du flag d’disponibilité.
  • Toutes les statistiques de prétraitement utilisées devaient provenir des données d’entraînement et être réutilisées sans modification lors de l’inference, règle que le pipeline lui-même impose, de sorte qu’il ne dépendait jamais du souvenir d’un analyste.
  • Les données de marché externes n’étaient considérées comme acceptables que si elles amélioraient la validation répétée et les performances de l’ensemble. La seule plausibilité ne suffisait pas.
  • 15. Recommandations

    • Considérer la qualité des données comme un indicateur de la performance du modèle. Les incohérences de schéma, les noms de villes fragmentés et les catégories incohérentes peuvent annuler le signal avant même qu’un algorithme ne le perçoive.
    • Séparer les cas de « valeurs manquantes en raison d’inconnu » des cas de « valeurs manquantes parce que la capacité n’existe pas ». Ce dernier cas est souvent un atout, et non un problème.
    • Pour des objectifs de revenus décalés, suivez le RMSE des extrêmes ainsi que la concentration de l’erreur carrée, en plus de la valeur globale. Quelques comptes importants peuvent déterminer si une prévision est utile sur le plan financier.
    • Utilisez des mécanismes de promotion explicites : découverte de candidats, confirmation via des données indépendantes et remplacement par des ensembles. Cela empêche qu’un résultat obtenu sur une seule division ne soit erronément considéré comme le meilleur.
    • Considérez l’IA agente comme un modèle opérationnel, et non comme une autorisation de promotion autonome. Permettez aux agents de proposer, de créer et de remettre en question des solutions, tandis que les décisions importantes concernant les modèles et les produits restent entre les mains des humains.
    • Conservez l’historique fiable ailleurs que dans la fenêtre de chat. Les ensembles de données, les fichiers de description, les prédictions hors distribution, les registres d’expériences, les solutions retenues ainsi que les décisions de promotion doivent tous être archivés, afin qu’un nouvel agent puisse s’appuyer sur ces éléments et non sur une série de messages antérieurs.
    • Il convient de séparer les environnements DEV, UAT et PROD lors des expérimentations avec l’IA. La créativité libre s’exerce dans DEV, tandis que la reproduction et les tests rigoureux ont lieu en UAT ; PROD doit être déterministe, figé et réversible.

    16. Leçons apprises

    La leçon principale n’est pas qu’un algorithme ait gagné. Une formulation disciplinée du problème, un audit des données, une validation tenant compte des cas extrêmes, l’ingénierie de caractéristiques, des résidus variés ainsi qu’un flux de travail contrôlé ont permis de créer un système de prédiction supérieur à celui basé uniquement sur la complexité du modèle. Avec DontPanic, les agents ont travaillé en équipe, accélérant ainsi la découverte et l’implémentation, tandis que des contrôles humains explicites ont empêché la vitesse de remplacer le jugement.

    Le champion final de la compétition a atteint un RMSE d’environ 2,893 millions après une phase d’optimisation adaptée à l’environnement d’évaluation. Ce score a déterminé le vainqueur, mais il est présenté séparément car il pourrait s’agir d’un surajustement et ne doit pas être interprété comme l’erreur attendue pour des restaurants futurs.

    La leçon principale est pratique : effectuez une validation avant toute mise au point, conservez les expériences qui ont échoué, ne retenez les améliorations que lorsqu’elles se reproduisent, et ne confondez jamais un modèle qui excelle sur des benchmarks avec un modèle fiable en production. De plus, si plusieurs agents IA sont impliqués, faites-les jouer au volley-ball : permettez-leur d’agir de manière agressive, mais assurez-vous que les décisions importantes reviennent à un humain avant qu’un point ne soit marqué.