Aller au contenu

L' IA est ce que c'était mieux avant ?

Cet article revient sur le machine learning classique à travers une compétition Kaggle de classification stellaire. Il montre que des modèles supervisés et une bonne ingénierie des données restent très performants sur des problèmes d’astrophysique concrets.

L'astrophysique prouve l'efficacité du ML.

Aujourd'hui, l'ingénieur IA est devenu la nouvelle coqueluche du marché. On ne parle plus que de lui, partout. Véritable vedette des entreprises en quête d'innovation, il doit sa popularité croissante à la domination des LLM et à la mort programmée du prompt engineer.

Pourtant, si l'IA semble aujourd'hui se cantonner aux outils des géants de la Tech — OpenAI, Anthropic ou Google — pour générer du code ou déployer des cohortes d'agents au succès parfois mitigé auprès des métiers, il fut un temps (lointain, plus de cinq ans, une éternité) où le Graal absolu était la data-driven company.

Il y avait une croyance quasi mystique dans la capacité d'une random forest et d'un data scientist démiurge à faire parler la donnée et à faire surgir de la valeur du marasme du data lake. Tel un alchimiste équipé d'un Jupyter notebook — puisqu'après tout, le temps de la mise en production viendrait plus tard —, le praticien transformerait le plomb de la donnée en or et guiderait l'organisation vers un monde meilleur en interprétant les arcanes des obscurs fichiers CSV et JSON. C'était une époque incroyable durant laquelle prononcer le mot « Kaggle » vous différenciait de la plèbe. Certains n'y voyaient qu'un réflexe de déglutition et s'inquiétaient pour votre santé**, tandis que d'autres**, initiés, comprenaient que vous parliez d'un site web de compétitions de data science. Mais ces jours fastes ne durèrent pas. On se rendit vite compte que les projets ne quittaient jamais l'étape de l'expérimentation et du notebook. Ils ne passaient jamais en production et restaient au stade de jouets algorithmiques.


Aujourd'hui, les choses ont changé. Des outils comme Vertex AI, AutoML ou BigQuery ML ont rendu le machine learning et, dans une moindre mesure, la data science accessibles en production à un public averti mais non spécialiste. Le nombre d'epochs d'entraînement des modèles a été remplacé par le nombre de tokens dépensés. Les questions sur la manière d'augmenter le nombre de features (les données d'entraînement fournies au modèle) ou sur la façon de les préparer pour que le modèle puisse les utiliser pour son entraînement ou ses prédictions ont fait place à : « mets tout dans un fichier markdown et ça passera ». Mais, si les LLM et les agents sont des outils puissants et versatiles, sont-ils pour autant la panacée ? Dans une ère où le mot IA est devenu synonyme de LLM, les approches classiques de modèles spécialistes ont-elles encore leur place ?

Dans cet article, j'ai voulu retourner dans ces temps anciens et oubliés d'avant 2022, avec un petit retour à kaggle, dans cette compétition: Predicting Stellar Class.

Cette compétition "amicale" avait pour objectif de classer des étoiles en différentes catégories, GALAXY, STAR et QSO


Une ÉTOILE est un objet unique lumineux qui produit de l'énergie grâce à la fusion nucléaire dans son cœur. La fusion transforme des éléments légers en éléments plus lourds et libère d'énormes quantités d'énergie au cours de ce processus. C'est cette énergie qui la fait briller. Notre Soleil est une étoile ordinaire. Lorsque les astronomes observent des étoiles dans le cadre de relevés stellaires, ils observent généralement des objets individuels situés dans notre propre galaxie ou dans des régions relativement proches de l'espace. Comme les étoiles sont comparativement proches, leur lumière n'a pas parcouru d'immenses distances cosmiques, ce qui explique pourquoi elles présentent souvent des valeurs de décalage vers le rouge (redshift) relativement faibles (ces étoiles étant proches, leur décalage spectral est surtout causé par leur mouvement d'éloignement par rapport à nous et à l'effet doppler résultant) . À l'échelle de l'Univers, une étoile constitue l'élément de base à partir duquel les galaxies sont formées.

Une GALAXIE est une structure beaucoup plus massive. Contrairement à une étoile, ce n'est pas un objet unique, mais un immense système lié par la gravité, contenant des étoiles, du gaz, de la poussière, des vestiges stellaires, des planètes et de grandes quantités de matière noire. Une galaxie typique peut renfermer de quelques millions à plusieurs milliers de milliards d'étoiles. La galaxie dans laquelle nous vivons, la Voie lactée, contient plusieurs centaines de milliards d'étoiles. Une analogie utile consiste à considérer une étoile comme une maison individuelle et une galaxie comme une ville entière regroupant des milliards de maisons, ainsi que les routes, les infrastructures et tout ce qui les relie. Lorsqu'un télescope observe une galaxie lointaine, la lumière qui nous parvient est généralement la lumière combinée d'innombrables étoiles, plutôt que celle d'un seul objet. Comme les galaxies sont souvent situées à des distances bien plus grandes que les étoiles individuelles, elles présentent fréquemment des valeurs de décalage vers le rouge (redshift) plus importantes. Pour les galaxies les plus éloignées, ce redshift est principalement dû à l'expansion de l'Univers, qui étire les longueurs d'onde de la lumière au cours de son trajet jusqu'à nous (quand une étoile s'éloigne de nous, le signal part dans les rayons infrarouges, dans les ultra-violets quand elle se rapproche).

Un QSO (Quasi-Stellar Object, ou objet quasi stellaire) est sans doute la classe d'objets la plus fascinante. Nous savons aujourd'hui qu'un quasar est en réalité un type de galaxie active.
Au centre de la plupart des grandes galaxies se trouve un trou noir supermassif. Dans la majorité des cas, ce trou noir est relativement calme. En revanche, dans un quasar, il engloutit activement d'énormes quantités de matière. En spiralant vers le trou noir, le gaz et la poussière forment un disque d'accrétion extrêmement chaud, qui émet un rayonnement intense sur une large gamme de longueurs d'onde. L'énergie produite peut devenir si considérable que la région centrale surpasse en luminosité toute la galaxie qui l'entoure.
Les premiers astronomes ne pouvaient pas distinguer la galaxie hôte. Ils n'observaient qu'un minuscule point lumineux, ressemblant à une étoile. C'est de là que vient le nom « Quasi-Stellar Object », qui signifie « objet ressemblant à une étoile ». Des observations ultérieures ont montré que ces objets n'étaient pas des étoiles, mais des galaxies actives extrêmement éloignées.
Les quasars occupent une place particulière en astronomie, car ils comptent parmi les objets les plus lumineux de l'Univers. Certains peuvent être observés à des distances de plusieurs milliards d'années-lumière. Comme leur lumière a traversé d'immenses distances dans un Univers en expansion, les quasars présentent souvent des valeurs de décalage vers le rouge (redshift) très élevées, bien supérieures à celles des étoiles ordinaires et de nombreuses galaxies. C'est pourquoi le redshift est une caractéristique particulièrement discriminante dans les jeux de données utilisés pour la classification des objets astronomiques.

C'est donc à partir de données tabulaires que l'on doit entraîner un modèle de classification qui prédira le type d'objet stellaire. Il est d'ailleurs assez remarquable de se dire que l'humanité est passée d'investissements en R&D à plusieurs milliards à aujourd'hui un fichier csv pour faire la classification...


Les features fournies étaient:

Résumé des caractéristiques des étoiles

Résumé des caractéristiques des étoiles

Caractéristique Nom en français Description Unité / Valeurs
alpha Ascension droite (RA) Coordonnée céleste indiquant la position de l'étoile, analogue à la longitude terrestre. Degrés (°) ou heures (0–24 h)
delta Déclinaison (Dec) Coordonnée céleste nord/sud. Degrés (°), de −90° à +90°
u Magnitude bande u Luminosité ultraviolet. Magnitude apparente
g Magnitude bande g Luminosité bande verte-bleue. Magnitude apparente
r Magnitude bande r Luminosité bande rouge. Magnitude apparente
i Magnitude bande i Luminosité dans le proche infrarouge. Magnitude apparente
z Magnitude bande z Luminosité dans la bande infrarouge. Magnitude apparente
redshift Décalage vers le rouge Variation de la longueur d'onde due au mouvement relatif ou à l'expansion de l'Univers. Généralement très faible pour les étoiles de la Voie lactée. Sans unité (z)
spectral_type Type spectral Classification selon la température de surface et les caractéristiques spectrales. O, B, A, F, G, K, M (éventuellement L, T, Y)
galaxy_population Population galactique Population stellaire indiquant l'âge, la métallicité et l'environnement galactique de l'étoile. Population I, II ou III

La donnée ressemble à :

alpha delta u g r i z redshift spectral_type galaxy_population label
0 147.734256 16.959273 25.472123 21.895559 20.357926 19.257113 18.621057 0.408982 M Red_Sequence GALAXY
1 127.988677 32.346716 20.778509 19.087062 17.587208 17.226067 16.786433 0.157976 M Red_Sequence GALAXY
2 179.792648 35.344843 21.035203 21.079128 21.171840 20.582629 20.557366 2.823770 O/B Blue_Cloud QSO
3 225.818295 48.569421 23.305056 21.050736 19.017754 18.365658 17.914952 0.536099 M Red_Sequence GALAXY
4 141.836135 19.342852 21.703158 19.471680 18.234449 17.899447 17.616185 0.555761 M Red_Sequence GALAXY

Ces features sont intéressantes, mais elles ne peuvent être utilisées directement. Les modèles ne peuvant lire du texte directement. Il faut donc appliquer du label encoding, sur les label à prédire (la catégorie d'étoile), spectral_type, galaxy_population et label doivent être convertis.

import pandas as pd
df['spectral_type'] = df['spectral_type'].map({'M':0, 'O/B':1, 'G/K':2, 'A/F':3})
df['galaxy_population'] = df['galaxy_population'].map({'Red_Sequence':0, 'Blue_Cloud':1})
df['label'] = df['label'].map({'GALAXY':0, 'QSO':1, 'STAR':2})

Mais ces features, si elles permettent déjà d'avoir une idée du phénomène observé, sont assez limitées et ne permettent pas systématiquement de bien différencier ces astres. C'est pourquoi il faut procéder à une augmentation des features

df['u_g'] = df['u'] - df['g']
df['g_r'] = df['g'] - df['r']
df['r_i'] = df['r'] - df['i']
df['i_z'] = df['i'] - df['z']
df['u_z'] = df['u'] - df['z']
mag_cols = ['u', 'g', 'r', 'i', 'z']
df['mag_std'] = df[mag_cols].std(axis=1)
df['mag_max_min_diff'] = df[mag_cols].max(axis=1) - df[mag_cols].min(axis=1)
Résumé des indices de couleur et features dérivées

Résumé des indices de couleur et features dérivées

Paramètre Nom en français Description Interprétation
u_g Indice de couleur u − g Différence entre magnitude ultraviolet (u) et bleu-vert (g). Estime la température et la forme du spectre stellaire.
g_r Indice de couleur g − r Différence entre les bandes g et r. Très bon indicateur de température stellaire (chaud vs froid).
r_i Indice de couleur r − i Différence entre les bandes rouge et proche infrarouge. Aide à distinguer les types d’étoiles et leur évolution.
i_z Indice de couleur i − z Différence entre proche et plus lointain infrarouge. Sensible aux étoiles froides et objets rouges.
u_z Indice de couleur u − z Différence entre ultraviolet et infrarouge. Mesure globale de la couleur spectrale de l’étoile.
mag_std Écart-type des magnitudes Dispersion des magnitudes u, g, r, i, z. Mesure la variabilité spectrale entre bandes.
mag_max_min_diff Amplitude des magnitudes Différence entre la magnitude maximale et minimale. Quantifie l’étendue du spectre lumineux.

Augmenté des nouvelles features, cela nous donne donc:

alpha delta u g r i z redshift spectral_type galaxy_population label u_g g_r r_i i_z u_z mag_std mag_max_min_diff
230.038946 12.838985 24.930610 21.672056 21.470317 21.382870 21.422043 0.142472 3 0 2 3.258554 0.201739 0.087446 -0.039173 3.508566 1.544128 3.547739
357.964012 27.573050 23.653019 21.898960 21.691751 21.924827 20.801286 0.160551 3 1 2 1.754059 0.207209 -0.233076 1.123541 2.851733 1.034418 2.851733
326.261464 0.174719 22.358633 20.559970 20.181406 20.238590 19.887146 0.047593 3 1 2 1.798663 0.378565 -0.057184 0.351444 2.471487 0.987193 2.471487
150.634889 36.788132 22.602868 21.234153 21.149836 21.160034 20.778436 0.045054 3 1 2 1.368715 0.084317 -0.010198 0.381598 1.824432 0.703525 1.824432
117.774495 21.773958 25.908386 23.043032 20.986304 19.631611 19.084015 0.055746 0 0 2 2.865354 2.056728 1.354693 0.547596 6.824372 2.790185 6.824372

Enfin, afin de rendre l'apprentissage plus fiable et éviter qu'une des features domine sur les autres à cause des valeurs qu'elle contient, j'ai appliqué un standarscaler sur les données:

x′=(x−μ​)/σ où μ = moyenne de la variable et σ = écart-type

En mettant les variables sur la "même échelle", on réduit l'effet des valeurs élevées sur le modèle, normalise la descente du gradient qui converge plus rapidement.

Pour les besoins de la compétition, cela a été embarqué dans une fonction de transformation.

import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler

def transformations_pipeline(df, is_train=True, scaler=None):
    """
    Cleans, maps, selects features, and scales the dataset for the autoencoders.
    
    Parameters:
    -----------
    df : pd.DataFrame
        The input raw dataframe.
    is_train : bool, default=True
        If True, fits and returns a new StandardScaler. 
        If False, uses the passed 'scaler' to transform the data.
    scaler : StandardScaler, optional
        A pre-fitted scaler object. Required if is_train=False.
        
    Returns:
    --------
    X_scaled : np.ndarray
        The scaled feature array ready for the autoencoder.
    y : np.ndarray or None
        The target labels (if 'label' exists in the dataframe), else None.
    scaler : StandardScaler
        The fitted or utilized scaler object.
    """
    # 1. Make a copy to avoid SettingWithCopyWarning
    processed_df = df.copy()
    
    # 2. Map Categorical Variables
    spectral_mapping = {'M': 0, 'O/B': 1, 'G/K': 2, 'A/F': 3}
    galaxy_mapping = {'Red_Sequence': 0, 'Blue_Cloud': 1}
    
    processed_df['spectral_type'] = processed_df['spectral_type'].map(spectral_mapping)
    processed_df['galaxy_population'] = processed_df['galaxy_population'].map(galaxy_mapping)
    
    # Fill NaN values just in case any unmapped categories slip through
    processed_df['spectral_type'] = processed_df['spectral_type'].fillna(-1)
    processed_df['galaxy_population'] = processed_df['galaxy_population'].fillna(-1)
    processed_df['u_g'] = processed_df['u'] - processed_df['g']
    processed_df['g_r'] = processed_df['g'] - processed_df['r']
    processed_df['r_i'] = processed_df['r'] - processed_df['i']
    processed_df['i_z'] = processed_df['i'] - processed_df['z']
    processed_df['u_z'] = processed_df['u'] - processed_df['z']
    mag_cols = ['u', 'g', 'r', 'i', 'z']
    processed_df['mag_std'] = processed_df[mag_cols].std(axis=1)
    processed_df['mag_max_min_diff'] = processed_df[mag_cols].max(axis=1) - processed_df[mag_cols].min(axis=1)

    # 3. Define Features (Dropping 'alpha' as an index/ID-like column)
    features = ['alpha', 'delta', 'u', 'g', 'r', 'i', 'z', 'redshift', 'spectral_type','galaxy_population', 'u_g', 'g_r', 'r_i', 'i_z', 'u_z','mag_std', 'mag_max_min_diff']
    X = processed_df[features].values
    
    # 4. Extract Labels if they exist (Kaggle hidden test sets won't have 'label')
    y = processed_df['label'].values if 'label' in processed_df.columns else None
    
    # 5. Handle Scaling
    if is_train:
        scaler = StandardScaler()
        X_scaled = scaler.fit_transform(X)
    else:
        if scaler is None:
            raise ValueError("A pre-fitted 'scaler' object must be provided when is_train=False.")
        X_scaled = scaler.transform(X)
        
    return X_scaled, y, scaler
    

L'ensemble des données, celles d'entraînement et celles de test du modèle sont ensuite préparées

pipeline_scaler = StandardScaler()
X_train_scaled = pipeline_scaler.fit_transform(X_train)
X_test_scaled = pipeline_scaler.transform(X_test)

Après une grid search qui permet de tester plusieurs combinaisons de modèles, les meilleures performances ont été atteintes pour l'algorithme classique sur kaggle du gradient boosting:

from lightgbm import LGBMClassifier
clf = LGBMClassifier(
    class_weight='balanced', 
    n_estimators=300,          # Increased trees
    learning_rate=0.03,        # Slower learning rate requires more trees but generalizes better
    num_leaves=31,             # Controls tree complexity
    subsample=0.8,             # Row masking (adds randomness)
    colsample_bytree=0.8,      # Feature masking 
    random_state=42,
    n_jobs=-1
)
clf.fit(X_train_final, y_train)

Le classement de la compétition se faisait via la métrique du balanced accuracy score. En effet, les trois catégories n'étant pas présentes dans les mêmes proportions, cette métrique est appropriée, car elle mesure la capacité du modèle à généraliser y compris sur les classes minoritaires. Sur cette compétition, mon modèle a eu un score de 0.96269 et le meilleur de 0.97075.


Au delà d'un certain voyage dans le temps (et les étoiles) nostalgique à l'époque du data scientist roi et du notebook ultime, et en taquinant en passant les nouvelles coqueluches de linkedin qui sont les AI Engineers.

Cette expérience illustre bien l’évolution des approches en machine learning au cours des dernières années, entre exploration de modèles spécialisés et intégration progressive d’outils plus industrialisés et accessibles.

Les méthodes classiques, combinées à un travail rigoureux de préparation des données et d’ingénierie des variables, restent aujourd’hui encore très performantes sur des problématiques structurées. Elles offrent également des avantages importants en termes de contrôle, de transparence et de déploiement local, notamment dans des contextes contraints en ressources ou nécessitant une forte interprétabilité.

Par ailleurs, l’écosystème actuel, marqué par l’essor des modèles de fondation et des outils automatisés de machine learning, ne remplace pas ces approches mais les complète. Il permet d’accélérer certaines étapes du cycle de développement, tout en ouvrant de nouvelles possibilités en matière d’applications et d’intégration en production.

Au-delà des évolutions technologiques, ce type de projet rappelle que la valeur d’un modèle ne réside pas uniquement dans sa complexité, mais dans sa capacité à répondre efficacement à un problème donné, avec des choix méthodologiques adaptés aux contraintes et aux objectifs du cas d’usage.

Dernier