Cet article vous guide, pas à pas, de la préparation des données jusqu’au déploiement d’un modèle, en combinant scikit-learn (excellente boîte à outils pour données tabulaires) et TensorFlow/Keras (référence pour les réseaux de neurones). On y trouve : une méthodologie solide, des métriques adaptées, des pipelines reproductibles, de la mise au point, des interprétations, et des pistes de mise en production.
Le plus courant dans un projet : scikit-learn pour le prétraitement + Keras pour le modèle profond, ou bien tout-scikit-learn lorsque la donnée est tabulaire.
python -m pip install --upgrade pip
pip install numpy pandas scikit-learn tensorflow
# (optionnel) pour intégrer Keras au style scikit-learn :
pip install scikeras
ColumnTransformer/Pipeline.import numpy as np, pandas as pd
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
X, y = load_breast_cancer(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
pipe = Pipeline(steps=[
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler()),
("clf", LogisticRegression(max_iter=1000))
])
pipe.fit(X_train, y_train)
from sklearn.metrics import classification_report, roc_auc_score
y_pred = pipe.predict(X_test)
y_proba = pipe.predict_proba(X_test)[:, 1]
print(classification_report(y_test, y_pred))
print("ROC AUC:", roc_auc_score(y_test, y_proba))
Choix des métriques
- Classification : précision/recall/F1, ROC AUC, PR AUC pour classes très déséquilibrées.
- Régression : RMSE/MAE, R².
- Business first : coût d’une erreur, seuils de décision adaptés (calibration possible).
from sklearn.model_selection import StratifiedKFold, RandomizedSearchCV
from scipy.stats import loguniform
param_grid = {
"clf__C": loguniform(1e-3, 1e2), # régularisation pour la logistique
}
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
search = RandomizedSearchCV(
estimator=pipe,
param_distributions=param_grid,
n_iter=20,
scoring="roc_auc",
cv=cv,
n_jobs=-1,
random_state=42
)
search.fit(X_train, y_train)
print("Meilleurs params:", search.best_params_)
print("AUC (CV):", search.best_score_)
best_model = search.best_estimator_
from sklearn.inspection import permutation_importance
r = permutation_importance(best_model, X_test, y_test, scoring="roc_auc", n_repeats=10, random_state=0)
importances = pd.Series(r.importances_mean, index=X.columns).sort_values(ascending=False)
print(importances.head(10))
Astuce : pour des arbres/forêts, regardez aussi
feature_importances_. Pour des explications locales/globale plus poussées : SHAP/Partial Dependence/ICE.
import tensorflow as tf
(X_train, y_train), (X_test, y_test) = tf.keras.datasets.fashion_mnist.load_data()
# Normalisation et ajout de canal
X_train = X_train.astype("float32") / 255.0
X_test = X_test.astype("float32") / 255.0
X_train = X_train[..., None]
X_test = X_test[..., None]
num_classes = 10
tf.data pour des entrées efficacesbatch_size = 128
train_ds = tf.data.Dataset.from_tensor_slices((X_train, y_train)).shuffle(10_000).batch(batch_size).prefetch(2)
test_ds = tf.data.Dataset.from_tensor_slices((X_test, y_test)).batch(batch_size).prefetch(2)
from tensorflow.keras import layers, models
def make_cnn():
inputs = layers.Input(shape=(28, 28, 1))
x = layers.Conv2D(32, 3, activation="relu")(inputs)
x = layers.MaxPool2D()(x)
x = layers.Conv2D(64, 3, activation="relu")(x)
x = layers.MaxPool2D()(x)
x = layers.Flatten()(x)
x = layers.Dropout(0.3)(x)
x = layers.Dense(128, activation="relu")(x)
outputs = layers.Dense(num_classes, activation="softmax")(x)
model = models.Model(inputs, outputs)
model.compile(
optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"]
)
return model
model = make_cnn()
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint, TensorBoard
import time, os
run_dir = f"runs/fashion_{int(time.time())}"
os.makedirs(run_dir, exist_ok=True)
callbacks = [
EarlyStopping(patience=5, restore_best_weights=True, monitor="val_accuracy"),
ReduceLROnPlateau(patience=3, factor=0.5, monitor="val_loss"),
ModelCheckpoint(os.path.join(run_dir, "best.keras"), save_best_only=True, monitor="val_accuracy"),
TensorBoard(log_dir=run_dir)
]
history = model.fit(
train_ds,
epochs=30,
validation_data=test_ds,
callbacks=callbacks
)
test_metrics = model.evaluate(test_ds, verbose=0)
print(dict(zip(model.metrics_names, test_metrics)))
Bonnes pratiques Keras
- Normalisation directement dans le modèle (ex.
layers.Rescaling(1/255.)), pour éviter les écarts entre train et prod.- EarlyStopping + ReduceLROnPlateau = sur-apprentissage maîtrisé.
- TensorBoard pour suivre loss/accuracy, histogrammes de poids, graphes.
- Sauvegarde : format Keras
.kerasou SavedModel.
model.save("fashion_best.keras")
restored = tf.keras.models.load_model("fashion_best.keras")
from scikeras.wrappers import KerasClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import cross_val_score
def build_mlp(hidden=64, lr=1e-3):
m = tf.keras.Sequential([
layers.Input(shape=(X_train.shape[1],)),
layers.Normalization(), # ou StandardScaler dans le pipeline
layers.Dense(hidden, activation="relu"),
layers.Dropout(0.2),
layers.Dense(num_classes, activation="softmax")
])
m.compile(optimizer=tf.keras.optimizers.Adam(lr), loss="sparse_categorical_crossentropy", metrics=["accuracy"])
return m
clf = KerasClassifier(model=build_mlp, epochs=20, batch_size=128, verbose=0)
pipe = make_pipeline(StandardScaler(with_mean=False), clf) # selon vos features
# Exemple: sur des données tabulaires (ici, illustration générique)
# scores = cross_val_score(pipe, X_tab_train, y_tab_train, cv=5, scoring="accuracy")
Pourquoi SciKeras ?
Il expose un estimateur Keras compatible avec l’API scikit-learn (grid search, CV, pipelines), très pratique pour organiser vos expériences.
class_weight (Keras) ou class_weight='balanced' (scikit-learn), ou des techniques de ré-échantillonnage (SMOTE via imbalanced-learn).# Exemple d’ajustement de seuil (classification binaire)
from sklearn.metrics import precision_recall_curve
proba = best_model.predict_proba(X_test)[:,1]
prec, rec, thr = precision_recall_curve(y_test, proba)
# choisir le seuil qui maximise F1 ~ 2*(P*R)/(P+R) ou selon votre coût
joblib.dump(pipeline, "model.joblib")model.save("model.keras") / SavedModelfrom sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.ensemble import RandomForestClassifier
num_cols = X_train.select_dtypes(include=["float64", "int64"]).columns
cat_cols = X_train.select_dtypes(include=["object", "category", "bool"]).columns
pre = ColumnTransformer([
("num", Pipeline([("imp", SimpleImputer(strategy="median")), ("sc", StandardScaler())]), num_cols),
("cat", Pipeline([("imp", SimpleImputer(strategy="most_frequent")), ("oh", OneHotEncoder(handle_unknown="ignore"))]), cat_cols)
])
rf = RandomForestClassifier(n_estimators=300, random_state=42, class_weight="balanced_subsample")
clf = Pipeline([("pre", pre), ("rf", rf)]).fit(X_train, y_train)
print("AUC:", roc_auc_score(y_test, clf.predict_proba(X_test)[:,1]))
inputs = tf.keras.Input((28,28,1))
x = layers.Rescaling(1/255.0)(inputs)
x = layers.Conv2D(32,3,activation="relu")(x); x = layers.MaxPool2D()(x)
x = layers.Conv2D(64,3,activation="relu")(x); x = layers.MaxPool2D()(x)
x = layers.Flatten()(x); x = layers.Dropout(0.3)(x)
outputs = layers.Dense(10, activation="softmax")(x)
cnn = tf.keras.Model(inputs, outputs)
cnn.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])
cnn.fit(train_ds, validation_data=test_ds, epochs=15, callbacks=[EarlyStopping(patience=3, restore_best_weights=True)])
Objectif : aider un·e conseiller·ère (Mission locale, CFA, organisme de formation, service RH d’une collectivité) à identifier les jeunes NEET (ni en emploi, ni en études, ni en formation) et recommander une formation adaptée, en combinant données tabulaires, règles métier et apprentissage automatique.
Le système reste un outil d’aide à la décision (humain dans la boucle), conforme au RGPD et avec des garde-fous d’équité.
Clés de succès : respect du consentement, minimisation des données, transparence des critères, équité (pas de discrimination directe/indirecte), explications simples pour le public et les conseillers.
jeunes (profils)id_jeune (uid), age (entier), departement (INSEE), diplome_max (CAP/BEP/Bac/Bac+2/…),experience_mois (dernier emploi), mobilite_km (≤5/10/20/illimitée), permis (bool), situation_logement (cat.),disponibilite_h/semaine, contraintes (garde enfant, horaires), situation (NEET oui/non au moment T).Éviter : santé, religion, opinions, données ultrasensibles — non nécessaires à la finalité.
formationsid_form, intitule, rome_codes (métiers visés), competences_cible (liste),niveau_entree (pré-requis), duree_semaines, lieu_departement, modalite (présentiel/distanciel/hybride),sessions (dates), taux_sortie_positif_local (historique placement/stage/emploi), cout (si pertinent), aides.suiviid_jeune, id_form, choisie (bool), terminee (bool), sortie_positif (bool), dates.import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler, MultiLabelBinarizer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, roc_auc_score
# X: colonnes non sensibles, y: "situation_NEET" (0/1) *au moment de l'entrée*
num_cols = ["age", "experience_mois", "disponibilite_h"]
cat_cols = ["departement", "diplome_max", "mobilite_km", "permis", "situation_logement"]
# compétences/intérêts sous forme de listes -> binarisation multilabel
def binarize_multilabel(df, col):
mlb = MultiLabelBinarizer()
M = pd.DataFrame(mlb.fit_transform(df[col]), columns=[f"{col}_{c}" for c in mlb.classes_], index=df.index)
return pd.concat([df.drop(columns=[col]), M], axis=1)
df = pd.read_csv("jeunes.csv") # supposé
df = binarize_multilabel(df, "competences")
df = binarize_multilabel(df, "interets")
y = df["situation_NEET"].astype(int)
X = df.drop(columns=["situation_NEET"])
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, test_size=.2, random_state=42)
pre = ColumnTransformer([
("num", Pipeline([("imp", SimpleImputer(strategy="median")), ("sc", StandardScaler())]), num_cols),
("cat", Pipeline([("imp", SimpleImputer(strategy="most_frequent")),
("oh", OneHotEncoder(handle_unknown="ignore"))]), cat_cols)
], remainder="passthrough")
clf = Pipeline([
("pre", pre),
("lr", LogisticRegression(max_iter=200, class_weight="balanced"))
]).fit(X_train, y_train)
proba = clf.predict_proba(X_test)[:,1]
print(classification_report(y_test, (proba>0.5).astype(int)))
print("AUC:", round(roc_auc_score(y_test, proba), 3))
Pourquoi la régression logistique ? Interprétable, stable, moins sujette au sur-apprentissage sur échantillons modestes. Vous pouvez tester RandomForest / XGBoost ensuite.
diplome_max, âge si nécessaire, niveau d’entrée.mobilite_km + distance (même département/limitrophes), modalité (distanciel si garde d’enfant), durée max.Score compétences (Jaccard / TF-IDF / Word2Vec…) + Score intérêts (overlap simple).
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
# Vecteurs "compétences" des formations et des jeunes
forms = pd.read_csv("formations.csv")
forms["comp_str"] = forms["competences_cible"].apply(lambda L: " ".join(L)) # liste -> phrase
tfidf = TfidfVectorizer(min_df=2)
F = tfidf.fit_transform(forms["comp_str"]) # (n_form, vocab)
def score_jeune(competences_jeune):
q = tfidf.transform([" ".join(competences_jeune)])
return cosine_similarity(q, F).ravel() # score par formation
Utiliser taux_sortie_positif_local (historique) comme facteur de priorisation (mais pas unique, pour éviter l’auto-renforcement).
import numpy as np
def rank_formations(jeune, forms):
mask = filtres_metier(jeune, forms) # bool per formation
s_comp = score_jeune(jeune["competences"])
s_int = jaccard(jeune["interets"], forms["interets_cible"]) # à coder simplement
s_job = forms["taux_sortie_positif_local"].to_numpy()
# Score final (poids à calibrer avec les conseillers)
score = 0.5*s_comp + 0.3*s_int + 0.2*normalize(s_job)
score[~mask] = -1 # exclure ce qui ne passe pas les filtres
top = np.argsort(-score)[:5]
return forms.iloc[top][["id_form","intitule","lieu_departement","duree_semaines","modalite","taux_sortie_positif_local"]], score[top]
Pratique : afficher pourquoi chaque formation est proposée (compétences matching + contraintes respectées + débouchés) et une alternative (ex. même domaine, autre modalité).
Explications affichées :
sklearn (pipeline sauvegardé joblib), recommandation Python (TF-IDF + règles)./score & /recommendations).def normalize_list(x):
if isinstance(x, str):
x = [t.strip().lower() for t in x.split(",") if t.strip()]
return sorted(set(x))
jeunes["competences"] = jeunes["competences"].apply(normalize_list)
jeunes["interets"] = jeunes["interets"].apply(normalize_list)
def jaccard(a, b):
A, B = set(a), set(b)
return len(A&B)/len(A|B) if A|B else 0.0
def filtres_metier(jeune, forms):
ok_niveau = forms["niveau_entree"] <= jeune["diplome_max_niveau"]
ok_mobi = (forms["lieu_departement"]==jeune["departement"]) | (jeune["mobilite_km"]>=20) | (forms["modalite"]!="présentiel")
ok_duree = forms["duree_semaines"]*35 <= jeune["disponibilite_h"]*4 # heuristique
return ok_niveau & ok_mobi & ok_duree
Ce cas pratique met en place un double système :
recommandation (quelle formation convient le mieux ?),
le tout explicable, équitable et piloté par les conseillers.
En démarrant par un pipeline scikit-learn simple et une reco content-based avec filtres métier, vous obtenez rapidement un impact opérationnel tout en gardant la possibilité d’évoluer (embeddings, deep learning, intégration temps réel).
détection (qui orienter en priorité ?),
Commencez simple (baseline claire), verrouillez votre pipeline, choisissez des métriques alignées avec le métier, puis poussez la complexité seulement si elle apporte (et reste maintenable). Avec scikit-learn et TensorFlow, vous couvrez 95 % des cas réels — du score rapide sur CSV au déploiement d’un CNN en prod.
Deux outils concrets pour piloter la qualité sans alourdir vos équipes Un système qualité n’avance…
Un chantier se gagne souvent avant même l’arrivée des équipes. Quand tout est clair dès…
Le mariage a du sens quand il repose sur une décision libre, mûrie et partagée.…
Une étude de cas réussie commence par une structure sûre. Ce modèle Word vous guide…
Les soft skills se repèrent vite sur une fiche, mais elles ne pèsent vraiment que…
Outil de comparaison et repérage des offres étudiantes Choisir des verres progressifs ressemble rarement à…
This website uses cookies.