Réseau profond ou gradient boosting?
- Étude de cas 2
- 120 min
- Facultative · 1 étude de cas requise pour le certificat
Étude de cas 2 sur 4 · L'apprentissage profond et les transformeurs expliquésRéseau profond ou gradient boosting?
1. Ce qu'est un réseau de neurones
2. Les tenseurs et PyTorch
3. Comment les réseaux de neurones apprennent
4. Diagnostiquer les problèmes d'entraînement
5. Représenter les séquences et le sens
6. Assembler un transformeur
7. Utiliser des modèles préentraînés
8. Sous le capot : les mathématiquesFacultatif
Ce que vous saurez faire
Découvrez, sur une vraie table, si un réseau de neurones vaut son coût. Vous entraînerez un réseau et un modèle de gradient boosting (amplification par gradient) avec le même découpage, la même mesure et le même budget de réglage. Ensuite, vous comparerez le score, sa dispersion selon les graines aléatoires, la préparation, le temps d’entraînement et les explications. Vous rédigerez un verdict honnête.
Le carnet (notebook) Jupyter exécute chaque étape de cette page avec le même code, plus trois tâches avec des vérifications. Ses cellules de préparation téléchargent les données (environ 1 Mo) et vérifient leur somme de contrôle. Aucune préparation locale n’est donc nécessaire. Il entraîne environ 70 petits modèles et s’exécute en environ 2 minutes sur un processeur.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- La boucle d’entraînement en cinq étapes, dans La boucle d’entraînement, et
model.eval()avectorch.no_grad(), dans Lots, époques et validation. - L’arrêt anticipé et le dropout (désactivation aléatoire de neurones), dans Limiter le surapprentissage.
- Le gradient boosting, dans Les méthodes d’ensemble du cours d’apprentissage automatique, et la précision moyenne, dans Le compromis entre précision et rappel.
- Le pipeline avec encodage one-hot (une colonne binaire par catégorie) et mise à l’échelle, dans Prétraiter avec un pipeline.
- Utile, mais facultatif : l’étude de cas Prédire le défaut de paiement par carte de crédit du cours d’apprentissage automatique. Elle explique ces données en détail. Cette page reprend seulement ce qu’exige la comparaison.
Ce qu’il vous faut sur votre ordinateur (seulement pour l’exécution locale; le carnet n’en a pas besoin)
- L’environnement virtuel d’Installer PyTorch, avec pandas et scikit-learn ajoutés (
pip install pandas scikit-learn). - Le carnet, téléchargé avec le lien ci-dessus. Sa cellule de préparation télécharge les données dans un nouveau dossier
credit-network. Pour recommencer, supprimez ce dossier.
Testé sur macOS (Apple M4, processeur seulement), dans Jupyter démarré à neuf. Versions : Python 3.12, PyTorch 2.14.1, scikit-learn 1.9.1 et pandas 3.0.6, avec deux fils d’exécution du processeur par modèle. Windows et Linux n’ont pas été testés. Les temps d’entraînement dépendent de votre ordinateur. Les scores étaient identiques à chaque exécution sur cet ordinateur. Sur un autre ordinateur, le dernier chiffre peut varier.
La situation
Section intitulée « La situation »Mei-Ling Chen dirige l’équipe du risque lié aux cartes de crédit d’une banque à Taiwan. Chaque mois, son équipe choisit des clients pour un appel d’aide préventive. Un agent propose un plan de paiement avant que le client prenne du retard. Appeler un client qui aurait payé coûte environ 1 unité en temps d’agent et en bonne volonté. Un défaut manqué coûte environ 5 unités, car une partie de cette dette ne sera jamais remboursée.
Dans le cours d’apprentissage automatique, son analyste Arjun a construit un pipeline de régression logistique pour cette décision. Il a réduit de 13 % le coût de la règle actuelle sur les données de test. Le gradient boosting était un peu meilleur, mais Arjun l’a gardé seulement comme modèle concurrent. La régression logistique donne des raisons exactes pour chaque client.
Daniel rejoint maintenant l’équipe, après un cours d’apprentissage profond : « Les réseaux reconnaissent les visages et traduisent des langues. Un réseau ne devrait pas battre vos deux modèles ici? » Arjun ne conteste pas. Il propose un concours équitable : un réseau contre le gradient boosting, avec les mêmes règles. Mei-Ling décidera selon le résultat.
Ce que vous allez construire
Section intitulée « Ce que vous allez construire »Vous construirez deux modèles concurrents pour la même décision. Le premier est HistGradientBoostingClassifier de scikit-learn, qui lit la table telle quelle. Le second est un petit réseau pour une table dans PyTorch, avec la boucle du cours, l’arrêt anticipé et le dropout. Les deux ont le même budget de réglage et 10 graines aléatoires chacun. Ils passent ensuite une seule fois sur les données de test scellées. À la fin, vous rédigerez un verdict : le score, son incertitude, le travail, le temps d’entraînement et les explications.
Pourquoi cette question compte
Section intitulée « Pourquoi cette question compte »Le module 1 proposait une règle générale dans Quand l’apprentissage profond en vaut la peine. Les réseaux gagnent avec des entrées brutes comme les images, le son et le texte libre. Sur une table de faits choisis par des personnes, le gradient boosting est très difficile à battre. Les comparaisons publiées sur de nombreuses tables concordent : les arbres amplifiés gagnent généralement ou font jeu égal. Mais une règle générale ne prouve rien pour vos données. Arjun et Daniel mesurent donc au lieu de deviner.
Les données
Section intitulée « Les données »Les données proviennent du jeu de données Default of Credit Card Clients du UCI Machine Learning Repository. Il contient 30 000 clients réels et anonymes d’une banque à Taiwan. Chaque client a six mois d’historique, d’avril à septembre 2005, et un résultat : a-t-il payé en octobre?
L’étude de cas du cours d’apprentissage automatique a vérifié ce fichier. Trois constats comptent pour un réseau :
- Des codes qui ressemblent à des nombres. Le statut de remboursement utilise −2, −1, 0 et 1 à 9; −2 et 0 ne sont pas documentés. Ce sont des catégories, pas des montants.
- Des montants asymétriques. Le paiement médian de septembre est de 2 100 $ NT; le plus élevé est de 873 552 $ NT.
- Des colonnes sensibles. Le sexe, l’état matrimonial et l’âge restent exclus des entrées, selon la décision de la banque.
Les entrées sont donc 20 colonnes : la scolarité, six mois de statut de remboursement, la limite de crédit, six factures et six paiements.
Un seul découpage pour les deux
Section intitulée « Un seul découpage pour les deux »Un concours équitable exige le même découpage pour les deux modèles. Arjun reprend celui du cours d’apprentissage automatique : 60 % pour l’entraînement, 20 % pour la validation et 20 % pour le test. Le découpage est stratifié selon la cible, avec random_state=0. Les données n’ont pas de dates, donc un découpage aléatoire convient à cet usage. Son inconvénient : il ne peut pas montrer ce qui arrive quand l’économie change.
from sklearn.model_selection import train_test_split
raw = pd.read_csv("credit_default_taiwan.csv.gz")clients = raw.rename(columns={"PAY_0": "PAY_1"}) # 1 = September in every groupclients["EDUCATION"] = clients["EDUCATION"].replace({0: 4, 5: 4, 6: 4}) # undocumented codes -> "others"STATUS = [f"PAY_{m}" for m in range(1, 7)]AMOUNTS = ["LIMIT_BAL"] + [f"BILL_AMT{m}" for m in range(1, 7)] + [f"PAY_AMT{m}" for m in range(1, 7)]CATEGORIES = ["EDUCATION"] + STATUSFEATURES = CATEGORIES + AMOUNTS # 20 inputs; SEX, MARRIAGE and AGE are never inputsTARGET = "default"
rest, test = train_test_split(clients, test_size=0.2, stratify=clients[TARGET], random_state=0)train, valid = train_test_split(rest, test_size=0.25, stratify=rest[TARGET], random_state=0)y_train, y_valid, y_test = (part[TARGET].to_numpy() for part in (train, valid, test))for name, part in [("train", train), ("valid", valid), ("test", test)]: print(f"{name:5s} {len(part):6d} clients, {part[TARGET].sum():5d} defaults ({part[TARGET].mean():.1%})")La sortie :
train 18000 clients, 3982 defaults (22.1%)valid 6000 clients, 1327 defaults (22.1%)test 6000 clients, 1327 defaults (22.1%)Ce que cela nous apprend. Ce sont les mêmes ensembles que dans le cours d’apprentissage automatique. Les nombres se comparent donc directement à ses résultats publiés. Personne ne regarde les données de test avant la fin.
Les règles du concours
Section intitulée « Les règles du concours »Une comparaison est équitable seulement si les deux modèles diffèrent sur un seul point : le modèle. Arjun écrit les règles avant tout entraînement :
- Le même découpage et les mêmes entrées. Les deux modèles voient les mêmes 20 colonnes des mêmes 18 000 clients d’entraînement.
- La même mesure. La précision moyenne (AP) sur les données de validation sert à choisir les réglages. Elle indique si les scores placent bien les défauts en haut de la liste. Le coût au seuil le moins coûteux (défaut manqué : 5, fausse alerte : 1) indique le coût de la décision. Le seuil est toujours choisi sur les données de validation.
- La même part réservée pour l’arrêt. Le boosting réserve 10 % des données d’entraînement dans
fit(). Il cesse d’ajouter des arbres quand cette partie ne s’améliore plus. Le réseau reçoit des données d’arrêt de même taille pour l’arrêt anticipé. - Le même budget de réglage. Chaque modèle reçoit 8 réglages, chacun exécuté avec 3 graines aléatoires.
- Plusieurs graines aléatoires. Les configurations finales sont exécutées avec 10 graines aléatoires chacune. La page rapporte la dispersion, pas seulement la meilleure exécution.
- Les données de test une seule fois, à la fin, pour chaque modèle.
Description textuelle du diagramme
Un flux de gauche à droite. Une boîte « 30 000 clients, 20 colonnes d’entrée » mène à « Un découpage : entraînement 18 000, validation 6 000, test 6 000 (scellé) ». De là, deux voies avancent côte à côte. La voie du gradient boosting : « Colonnes telles quelles, sans préparation », puis « fit() réserve 10 % et cesse d’ajouter des arbres ». La voie du réseau : « Encodage one-hot, logarithme, mise à l’échelle, de 20 à 55 colonnes », puis « Boucle d’entraînement, arrêt selon 10 % de données d’arrêt ». Les deux voies rejoignent « Même budget : 8 réglages × 3 graines aléatoires, AP médiane sur la validation », puis « 10 graines aléatoires chacun, dispersion sur la validation », puis « Test, une seule fois : AP et coût ».
Le carnet définit trois petites fonctions de calcul des scores, les mêmes que dans le cours d’apprentissage automatique. cost() ajoute 5 pour chaque défaut manqué et 1 pour chaque fausse alerte. cheapest() essaie tous les seuils de 0,01 à 0,99 et renvoie le moins coûteux avec son coût. metrics() renvoie l’AP et l’aire sous la courbe ROC (ROC AUC).
Les modèles à battre
Section intitulée « Les modèles à battre »Avant tout modèle concurrent, Arjun évalue les deux modèles existants. Le premier est la règle actuelle : « appeler chaque client qui était en retard en septembre ». L’autre est la régression logistique du cours d’apprentissage automatique. preprocessor() est la préparation de ce cours. Elle encode les codes en colonnes one-hot. Elle applique aux montants un logarithme signé, log(1 + |x|) avec le signe de x, puis les met à l’échelle. Le réseau aura besoin de la même fonction. Elle a donc des options pour omettre chaque étape :
from sklearn.compose import ColumnTransformerfrom sklearn.linear_model import LogisticRegressionfrom sklearn.pipeline import Pipelinefrom sklearn.preprocessing import FunctionTransformer, OneHotEncoder, StandardScaler
def signed_log(values): return np.sign(values) * np.log1p(np.abs(values))
def preprocessor(log=True, scale=True, codes_as_categories=True): """The machine learning course's preparation: codes one-hot, amounts signed log, then scaled.""" steps = [("log", FunctionTransformer(signed_log))] if log else [] steps += [("scale", StandardScaler())] if scale else [] steps = steps or [("keep", "passthrough")] if not codes_as_categories: return ColumnTransformer([("all", Pipeline(steps), FEATURES)]) return ColumnTransformer([ ("codes", OneHotEncoder(handle_unknown="infrequent_if_exist", min_frequency=50, sparse_output=False), CATEGORIES), ("amounts", Pipeline(steps), AMOUNTS)])
logistic = Pipeline([("prepare", preprocessor()), ("model", LogisticRegression(max_iter=2000))])logistic.fit(train[FEATURES], y_train)logistic_valid = logistic.predict_proba(valid[FEATURES])[:, 1]rule_valid = (valid["PAY_1"] >= 1).to_numpy()print("rule: late in September ", metrics(y_valid, rule_valid.astype(float)), "cost", cost(y_valid, rule_valid))print("logistic regression ", metrics(y_valid, logistic_valid), "cheapest threshold and cost", cheapest(y_valid, logistic_valid))La sortie :
rule: late in September {'ap': 0.3616, 'auc': 0.6817} cost 3934logistic regression {'ap': 0.5546, 'auc': 0.7748} cheapest threshold and cost (0.17, 3355)Ce que cela nous apprend. Ce sont les nombres du cours d’apprentissage automatique : AP de 0,555 et coût de 3 355, contre 3 934 pour la règle. Un modèle concurrent doit dépasser 0,555 assez clairement pour justifier son coût.
Le gradient boosting, tel quel
Section intitulée « Le gradient boosting, tel quel »Le gradient boosting construit de nombreux petits arbres, l’un après l’autre. Chaque arbre corrige les erreurs des arbres précédents. Un arbre pose des questions comme « le statut de septembre dépasse-t-il 0,5? » ou « la facture dépasse-t-elle 12 000 $ NT? ». L’échelle d’une colonne ne compte donc pas, et un code peut être séparé à n’importe quelle valeur. Le boosting lit les 20 colonnes telles quelles :
import timefrom sklearn.ensemble import HistGradientBoostingClassifier
def fit_boosting(params, seed): """Gradient boosting on the 20 columns as they are: no one-hot, no log, no scaling.""" model = HistGradientBoostingClassifier(random_state=seed, **params) start = time.perf_counter() model.fit(train[FEATURES], y_train) seconds = time.perf_counter() - start return model, model.predict_proba(valid[FEATURES])[:, 1], seconds
booster, booster_valid, seconds = fit_boosting({}, seed=0)print("boosting, defaults", metrics(y_valid, booster_valid), f"| {booster.n_iter_} trees in {seconds:.2f} s")La sortie :
boosting, defaults {'ap': 0.5589, 'auc': 0.7827} | 45 trees in 0.73 sCe que cela nous apprend. Avec ses réglages par défaut et sans préparation, le boosting atteint une AP de 0,559. C’est un peu plus que la régression logistique, comme dans le cours d’apprentissage automatique. fit() a réservé 10 % des données d’entraînement et s’est arrêté après 45 arbres. Le temps dépend de l’ordinateur et de sa charge : lors d’une autre exécution sur le même Mac, c’était 0,19 seconde.
Un réseau pour une table
Section intitulée « Un réseau pour une table »Les entrées : pourquoi un réseau a besoin de préparation
Section intitulée « Les entrées : pourquoi un réseau a besoin de préparation »Imaginez une recette qui indique « 2 œufs » et « 300 grammes de farine ». Si vous additionnez les nombres tels quels, la farine gagne chaque comparaison, simplement parce que les grammes sont de petites unités. Un réseau a le même problème. Chaque neurone calcule une somme pondérée, comme dans Neurones et couches. Une limite de crédit de 800 000 à côté d’un code de statut de 2 domine la somme. Les gradients des grandes colonnes dominent aussi ceux des petites.
Le réseau a donc besoin de la préparation nécessaire à la régression logistique. Les codes deviennent des colonnes one-hot, et les montants passent sur une échelle logarithmique. Ensuite, chaque colonne est mise à l’échelle avec une moyenne de 0 et un écart-type de 1. La préparation apprend cette mise à l’échelle à partir des lignes utilisées pour entraîner le réseau. Arjun réserve aussi 10 % des données d’entraînement comme données d’arrêt. Ce sont les petites données de validation propres au réseau, pour l’arrêt anticipé :
fit_part, stop_part = train_test_split(train, test_size=0.1, stratify=train[TARGET], random_state=0)
def tensors(prepare, frames): """Prepared columns as float32 tensors, one per frame.""" return [torch.tensor(prepare.transform(f[FEATURES]), dtype=torch.float32) for f in frames]
prepare = preprocessor().fit(fit_part[FEATURES]) # learn the scaling from the fitting rows onlyx_fit, x_stop, x_valid, x_test = tensors(prepare, (fit_part, stop_part, valid, test))y_fit = torch.tensor(fit_part[TARGET].to_numpy())y_stop = torch.tensor(stop_part[TARGET].to_numpy())for name, x in [("x_fit", x_fit), ("x_stop", x_stop), ("x_valid", x_valid), ("x_test", x_test)]: show_shape(name, x)print("first client, first 8 prepared numbers:", [round(v, 2) for v in x_fit[0, :8].tolist()])La sortie :
x_fit shape (16200, 55)x_stop shape (1800, 55)x_valid shape (6000, 55)x_test shape (6000, 55)first client, first 8 prepared numbers: [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]Ce que cela nous apprend. Les 20 colonnes sont devenues 55. Les 7 colonnes de codes sont devenues 42 colonnes one-hot, et les 13 montants restent 13. Les premiers nombres d’un client sont les 0 et les 1 des codes one-hot. Pourquoi ne pas s’arrêter selon les 6 000 clients de validation? La validation choisit les réglages des deux modèles. Si le réseau s’arrêtait aussi selon ces données, il les utiliserait deux fois, contrairement au boosting.
Le réseau
Section intitulée « Le réseau »TableNet est le réseau de ce cours, avec une table à la place d’un sac de mots. Il a des couches linéaires, ReLU et du dropout, puis deux scores à la fin : « paie » et « fait défaut ». Softmax transforme les deux scores en probabilités, comme dans La perte : à quel point le réseau se trompe-t-il?. La probabilité de « fait défaut » est le score du client.
class TableNet(nn.Module): """A network for a table: (Linear -> ReLU -> Dropout) x depth, then 2 scores."""
def __init__(self, n_inputs, hidden=64, depth=2, dropout=0.2): super().__init__() layers, width = [], n_inputs for _ in range(depth): layers += [nn.Linear(width, hidden), nn.ReLU(), nn.Dropout(dropout)] width = hidden layers.append(nn.Linear(width, 2)) # 2 scores: pays, defaults self.layers = nn.Sequential(*layers)
def forward(self, x): # x: (batch, n_inputs) -> (batch, 2) return self.layers(x)
torch.manual_seed(0)model = TableNet(x_fit.shape[1])print(model)print("parameters:", sum(p.numel() for p in model.parameters()))show_shape("scores", model(x_fit[:8]))La sortie :
TableNet( (layers): Sequential( (0): Linear(in_features=55, out_features=64, bias=True) (1): ReLU() (2): Dropout(p=0.2, inplace=False) (3): Linear(in_features=64, out_features=64, bias=True) (4): ReLU() (5): Dropout(p=0.2, inplace=False) (6): Linear(in_features=64, out_features=2, bias=True) ))parameters: 7874scores shape (8, 2)Ce que cela nous apprend. Comptez les paramètres à la main, comme au module 1 : 55 × 64 + 64 = 3 584, puis 64 × 64 + 64 = 4 160, puis 64 × 2 + 2 = 130. Le total est de 7 874. Un lot de 8 clients donne 8 lignes de 2 scores. C’est la configuration de départ; le réglage en essaiera d’autres.
Entraîner avec l’arrêt anticipé
Section intitulée « Entraîner avec l’arrêt anticipé »train_network() est la boucle d’entraînement du cours. La boucle interne reprend les cinq étapes de La boucle d’entraînement, sur des mini-lots mélangés de 256 clients. Après chaque époque, la fonction mesure la perte sur les données d’arrêt en mode évaluation. Chaque nouveau meilleur résultat conserve une copie des poids. Dix époques sans nouveau meilleur résultat arrêtent l’entraînement, et les meilleurs poids sont restaurés. C’est l’arrêt anticipé de Limiter le surapprentissage, avec une patience de 10. AdamW ajoute une petite décroissance des poids de 0,0001.
import copy
def predict(model, x): """Probability of default, with dropout off and no gradients.""" model.eval() with torch.no_grad(): return torch.softmax(model(x), dim=1)[:, 1].numpy()
def train_network(model, x_fit, y_fit, x_stop, y_stop, lr=1e-3, weight_decay=1e-4, batch_size=256, max_epochs=200, patience=10, seed=0): """The course's five steps per mini-batch; early stopping on the stopping set.""" torch.manual_seed(seed) optimizer = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=weight_decay) loss_fn = nn.CrossEntropyLoss() best_loss, best_state, best_epoch, waited, history = float("inf"), None, 0, 0, [] for epoch in range(1, max_epochs + 1): model.train() order = torch.randperm(len(x_fit)) total = 0.0 for i in range(0, len(order), batch_size): idx = order[i:i + batch_size] optimizer.zero_grad() # 1. forget the last batch's gradients loss = loss_fn(model(x_fit[idx]), y_fit[idx]) # 2-3. forward pass, how wrong? loss.backward() # 4. backward pass: gradients optimizer.step() # 5. update the weights total += loss.item() * len(idx) model.eval() with torch.no_grad(): stop_loss = loss_fn(model(x_stop), y_stop).item() history.append({"epoch": epoch, "train_loss": round(total / len(x_fit), 4), "stop_loss": round(stop_loss, 4)}) if stop_loss < best_loss - 1e-4: # a new best: keep a copy of the weights best_loss, best_state, best_epoch, waited = stop_loss, copy.deepcopy(model.state_dict()), epoch, 0 else: waited += 1 if waited >= patience: break model.load_state_dict(best_state) # early stopping: back to the best epoch return history, best_epoch
history, best_epoch = train_network(model, x_fit, y_fit, x_stop, y_stop, seed=0)print(f"best epoch {best_epoch}, stopped after {len(history)} | valid", metrics(y_valid, predict(model, x_valid)))La sortie :
best epoch 7, stopped after 17 | valid {'ap': 0.5503, 'auc': 0.78}Un appel est nouveau. model.state_dict() renvoie les poids du modèle par nom. copy.deepcopy() conserve une copie que les mises à jour suivantes ne peuvent pas modifier. Enregistrer et examiner les résultats utilise le même state_dict() pour enregistrer les poids dans un fichier.
- Perte d'entraînement
- Perte sur les données d'arrêt
Description textuelle du graphique
Un graphique linéaire allant de l’époque 1 à 17, avec une perte de 0,40 à 0,54. La perte d’entraînement en pointillés commence à 0,529 et baisse à 0,423 à l’époque 17. La perte sur les données d’arrêt, en trait plein, commence à 0,462 et baisse rapidement à 0,434 à l’époque 5. Elle atteint son minimum, 0,433, à l’époque 7. Ensuite, elle reste entre 0,433 et 0,436 sans nouveau meilleur résultat. L’entraînement s’arrête après l’époque 17.
Ce que cela nous apprend. L’essentiel de la baisse arrive dans les trois premières époques. Ensuite, la perte sur les données d’arrêt reste stable, tandis que la perte d’entraînement continue de baisser. C’est le début du portrait du surapprentissage présenté au module 3. Le réseau de départ a une AP de 0,550, un peu inférieure à celle de la régression logistique. Il faut le régler avant que le concours soit équitable.
L’échec : des colonnes brutes dans le réseau
Section intitulée « L’échec : des colonnes brutes dans le réseau »Le boosting n’avait besoin d’aucune préparation. Et si Daniel donnait aussi les colonnes brutes au réseau? run_network() réunit les étapes ci-dessus dans une fonction, avec les options de préparation comme argument. Le carnet montre la fonction complète. Ici, elle entraîne trois fois le réseau de départ sur les colonnes brutes :
START = {"hidden": 64, "depth": 2, "dropout": 0.2}for seed in range(3): result, _ = run_network(START, seed, {"log": False, "scale": False, "codes_as_categories": False}) print("raw columns:", result)La sortie :
raw columns: {'ap': 0.2214, 'auc': 0.5007, 'seed': 0, 'seconds': 1.19, 'best_epoch': 44}raw columns: {'ap': 0.2219, 'auc': 0.5007, 'seed': 1, 'seconds': 0.79, 'best_epoch': 27}raw columns: {'ap': 0.2213, 'auc': 0.5004, 'seed': 2, 'seconds': 0.89, 'best_epoch': 31}Erreur courante
Des montants bruts dans un réseau
Symptôme : le réseau s’entraîne sans erreur, et la perte baisse un peu. Mais son AP est de 0,221, la proportion de défauts, et sa ROC AUC est de 0,50. Il ne classe pas les clients mieux que le hasard. Le boosting sur les mêmes colonnes brutes atteignait 0,559.
Cause : les entrées ont des échelles très différentes. Dans les données d’entraînement, les limites de crédit vont de 10 000 à 800 000, et les codes de statut de −2 à 8. Les premières sommes pondérées sont énormes, et ReLU les transmet. Avec un seul taux d’apprentissage pour toutes les entrées, l’optimiseur ne trouve pas de direction utile. Le réseau adopte une réponse sûre : presque le même score, environ 22 %, pour chaque client.
Correction : mettez les entrées à l’échelle. Avec seulement cette étape (codes encore numériques, sans logarithme), le même réseau atteint une AP de 0,547 à 0,556 sur trois graines aléatoires. Les codes one-hot et le logarithme ajoutent un petit gain. Apprenez la mise à l’échelle sur les lignes d’entraînement seulement, comme dans Déboguer l’expérience.
Conséquence dans le travail en IA : un réseau ne vous avertit pas que les entrées ne sont pas mises à l’échelle. Un modèle au niveau du hasard donne quand même des scores. Son exactitude de 77,9 % (il dit toujours « paie ») peut sembler correcte. Comparez toujours avec une référence de base triviale, ici la proportion de défauts.
Le premier coût du réseau est donc le travail avant l’entraînement. Sans ce travail, le réseau échoue sans avertissement. De l’autre côté, le boosting sur les 55 colonnes préparées du réseau obtient 0,564 (graine aléatoire 0), contre 0,559 sur les colonnes brutes. Les arbres n’ont pas besoin de cette préparation, et elle ne leur nuit pas.
Le même budget de réglage pour les deux
Section intitulée « Le même budget de réglage pour les deux »Un réseau a beaucoup de paramètres (largeur, profondeur, dropout, taux d’apprentissage, taille des lots, patience). Le boosting en a quelques-uns qui comptent (taux d’apprentissage, taille des arbres, taille minimale des feuilles). Pour être juste, chaque modèle reçoit 8 configurations, et chaque configuration utilise 3 graines aléatoires. Avec une autre graine, le réseau commence avec d’autres poids aléatoires et voit les lots dans un autre ordre. Une exécution peut être chanceuse. La médiane de trois exécutions donne donc un jugement plus stable.
NET_GRID = [{"hidden": h, "depth": d, "dropout": p} for h in (64, 256) for d in (1, 2) for p in (0.0, 0.3)]BOOST_GRID = [{"learning_rate": r, "max_leaf_nodes": n, "min_samples_leaf": m} for r in (0.05, 0.1) for n in (15, 31) for m in (20, 100)]rows = []for cfg in NET_GRID: # 8 settings x 3 seeds = 24 networks aps = [run_network(cfg, seed)[0]["ap"] for seed in (0, 1, 2)] rows.append({"model": "network", "setting": str(cfg), "median AP": np.median(aps), "lowest": min(aps)})for cfg in BOOST_GRID: # 8 settings x 3 seeds = 24 boosted models aps = [metrics(y_valid, fit_boosting(cfg, seed)[1])["ap"] for seed in (0, 1, 2)] rows.append({"model": "boosting", "setting": str(cfg), "median AP": np.median(aps), "lowest": min(aps)})tuning = pd.DataFrame(rows)print(tuning.sort_values(["model", "median AP"], ascending=False).groupby("model").head(3).to_string(index=False))print(tuning.groupby("model")["median AP"].agg(["min", "max"]))La sortie :
model setting median AP lowest network {'hidden': 256, 'depth': 1, 'dropout': 0.0} 0.5602 0.5542 network {'hidden': 256, 'depth': 1, 'dropout': 0.3} 0.5593 0.5592 network {'hidden': 64, 'depth': 1, 'dropout': 0.0} 0.5584 0.5560boosting {'learning_rate': 0.05, 'max_leaf_nodes': 15, 'min_samples_leaf': 100} 0.5664 0.5580boosting {'learning_rate': 0.1, 'max_leaf_nodes': 31, 'min_samples_leaf': 100} 0.5656 0.5591boosting {'learning_rate': 0.05, 'max_leaf_nodes': 15, 'min_samples_leaf': 20} 0.5634 0.5603 min maxmodelboosting 0.5589 0.5664network 0.5491 0.5602Ce que cela nous apprend. Trois choses.
- Le meilleur réseau est peu profond. Il a une couche cachée de 256 neurones et aucun dropout (médiane de 0,560). La profondeur n’a pas aidé sur cette table : chaque configuration à deux couches est inférieure à son équivalent à une couche.
- Les paramètres comptent davantage pour le réseau. Ses huit médianes vont de 0,549 à 0,560, contre 0,559 à 0,566 pour le boosting. La pire configuration du boosting vaut à peu près la meilleure configuration du réseau.
- Les paramètres par défaut du boosting étaient déjà bons. Ils donnaient 0,559, près de son meilleur résultat. La configuration initiale du réseau donnait 0,550, sous la régression logistique.
Le réglage a pris environ 12 secondes pour les 24 réseaux et 5 secondes pour les 24 modèles de boosting. Les deux coûtent peu à cette taille, mais le réseau avait davantage besoin de réglage.
Dix graines chacun
Section intitulée « Dix graines chacun »Les configurations choisies utilisent maintenant 10 graines chacune. Pour le réseau, la graine détermine les poids initiaux et l’ordre des lots. Pour le boosting, elle détermine les 10 % des données d’entraînement que fit() réserve. Les deux modèles varient donc d’une exécution à l’autre :
best_net = {"hidden": 256, "depth": 1, "dropout": 0.0}best_boost = {"learning_rate": 0.05, "max_leaf_nodes": 15, "min_samples_leaf": 100}final, parts = {"network": [], "boosting": []}, {"network": {}, "boosting": {}}for seed in range(10): result, (model, prepare, x_test) = run_network(best_net, seed) final["network"].append(result) parts["network"][seed] = (model, prepare, predict(model, x_test)) booster, scores, seconds = fit_boosting(best_boost, seed) final["boosting"].append(metrics(y_valid, scores) | {"seed": seed, "seconds": round(seconds, 2)}) parts["boosting"][seed] = (booster, None, booster.predict_proba(test[FEATURES])[:, 1])for name, results in final.items(): table = pd.DataFrame(results) print(f"{name:9s} valid AP median {table['ap'].median():.3f} (lowest {table['ap'].min():.3f}, " f"highest {table['ap'].max():.3f}) | fit time median {table['seconds'].median():.2f} s")La sortie :
network valid AP median 0.561 (lowest 0.551, highest 0.564) | fit time median 0.29 sboosting valid AP median 0.567 (lowest 0.558, highest 0.571) | fit time median 0.51 s- Réseau
- Boosting
Description textuelle du graphique
Un graphique à points avec deux rangées, et une précision moyenne de 0,535 à 0,58; chaque point représente une graine. Rangée du réseau, dix points : 0,551, 0,554, 0,557, 0,560, 0,561, 0,562, 0,563, 0,563, 0,563 et 0,564; médiane de 0,561. Rangée du boosting, dix points : 0,558, 0,562, 0,564, 0,565, 0,566, 0,567, 0,567, 0,569, 0,570 et 0,571; médiane de 0,567. Les rangées se chevauchent entre 0,558 et 0,564. La régression logistique, comme référence, donne 0,555.
Ce que cela nous apprend. La médiane du boosting est supérieure d’environ 0,006. Les deux modèles dépassent les 0,555 de la régression logistique dans la plupart des exécutions. Mais les nuages se chevauchent. Si Daniel présentait seulement sa meilleure graine (0,564) et Arjun seulement sa pire (0,558), le réseau « gagnerait ». Voilà pourquoi cette page présente toutes les graines.
Beaucoup s’attendent à ce qu’un réseau varie beaucoup plus que le boosting selon les graines. Ce n’est pas le cas ici : l’étendue était de 0,013 pour les deux. Le réseau réglé est petit et s’arrête après environ 5 époques. Le hasard a donc peu de place pour amplifier les écarts. Présentez ce que vous mesurez, pas ce que vous attendez.
Les données de test, une seule fois
Section intitulée « Les données de test, une seule fois »Arjun a tout choisi. Chaque graine de chaque modèle attribue maintenant un score aux 6 000 clients de test, une seule fois. Elle utilise le seuil que cette même graine a choisi sur les données de validation :
logistic_test = logistic.predict_proba(test[FEATURES])[:, 1]print(f"{'rule: late in September':28s} AP - cost {cost(y_test, test['PAY_1'].to_numpy() >= 1)}")print(f"{'logistic regression':28s} AP {metrics(y_test, logistic_test)['ap']:.3f} " f"cost {cost(y_test, logistic_test >= cheapest(y_valid, logistic_valid)[0])}")for name in ("boosting", "network"): costs, aps = [], [] for seed, (model, prepare, test_scores) in parts[name].items(): valid_scores = (model.predict_proba(valid[FEATURES])[:, 1] if prepare is None else predict(model, tensors(prepare, (valid,))[0])) threshold = cheapest(y_valid, valid_scores)[0] # chosen on valid, as before costs.append(cost(y_test, test_scores >= threshold)) aps.append(metrics(y_test, test_scores)["ap"]) print(f"{name + ', median of 10 seeds':28s} AP {np.median(aps):.3f} cost {np.median(costs):.0f} " f"(seeds: {min(costs)} to {max(costs)})")La sortie :
rule: late in September AP - cost 3852logistic regression AP 0.549 cost 3346boosting, median of 10 seeds AP 0.562 cost 3362 (seeds: 3323 to 3381)network, median of 10 seeds AP 0.556 cost 3342 (seeds: 3319 to 3358)Ce que cela nous apprend. Pour le classement, le boosting mène encore : une AP de test de 0,562. Le réseau donne 0,556 et la régression logistique, 0,549. Pour le coût, soit ce que la banque paie, l’ordre change et les écarts sont minuscules. Le réseau coûte 3 342 (médiane), la régression logistique 3 346, et le boosting 3 362. Les graines d’un même modèle s’étalent sur 40 à 60 unités, soit plus que les écarts entre modèles. Les trois coûtent environ 13 % de moins que les 3 852 de la règle. Pour Mei-Ling, les trois modèles coûtent la même chose.
La différence d’AP est-elle réelle?
Section intitulée « La différence d’AP est-elle réelle? »Arjun pose une dernière question : l’avance du boosting en AP est-elle réelle? Ou vient-elle du hasard qui a placé certains clients dans les données de test? Le cours d’apprentissage automatique a répondu à cette question avec un bootstrap, dans Choisir un modèle pour les réadmissions à l’hôpital. Tirez 6 000 clients de test avec remise, mesurez la différence et répétez 1 000 fois. Les 95 % centraux des différences forment l’intervalle.
rng = np.random.default_rng(0)boost_scores, net_scores = parts["boosting"][0][2], parts["network"][0][2] # seed 0 of eachdifferences = []for _ in range(1000): idx = rng.integers(0, len(y_test), len(y_test)) # 6,000 test clients, drawn with replacement differences.append(average_precision_score(y_test[idx], boost_scores[idx]) - average_precision_score(y_test[idx], net_scores[idx]))low, high = np.percentile(differences, [2.5, 97.5])point = average_precision_score(y_test, boost_scores) - average_precision_score(y_test, net_scores)print(f"boosting minus network, test AP: {point:+.4f}, 95% interval {low:+.4f} to {high:+.4f}")print(f"share of resamples where boosting is ahead: {np.mean(np.array(differences) > 0):.0%}")La sortie :
boosting minus network, test AP: +0.0083, 95% interval -0.0038 to +0.0200share of resamples where boosting is ahead: 92%Description textuelle du graphique
Deux panneaux. Premier panneau, précision moyenne de test de 0,53 à 0,58 : régression logistique à 0,549 (un modèle, aucune étendue); réseau avec une médiane de 0,556, graines de 0,554 à 0,559; boosting avec une médiane de 0,562, graines de 0,557 à 0,566. Deuxième panneau, différence d’AP de test entre le boosting et le réseau (graine 0 de chacun) : +0,008. L’intervalle à 95 % va de −0,004 à +0,020. L’intervalle traverse la ligne de zéro.
Ce que cela nous apprend. Le boosting mène dans 92 % des rééchantillons, et la différence ponctuelle est de +0,008. Mais l’intervalle à 95 % va de −0,004 à +0,020 : il contient donc zéro. Avec les scores moyens des 10 graines de chaque modèle, la différence est plus petite : +0,005 (intervalle de −0,006 à +0,016). Le résumé honnête est donc : le boosting classe probablement un peu mieux, et le réseau ne fait pas mieux. Les données de test sont trop peu nombreuses pour en dire davantage.
Explications
Section intitulée « Explications »L’équipe doit expliquer à chaque client appelé pourquoi elle l’appelle. Dans Expliquer les prédictions, le cours d’apprentissage automatique utilisait l’importance par permutation. Mélangez une colonne pour briser son lien avec la cible, puis mesurez la baisse du score. Cette méthode nécessite seulement les scores du modèle. C’est donc un outil équitable pour les deux modèles :
def permutation_importance(score, frame, y, repeats=5, seed=0): """How much AP drops when one column is shuffled (its link to the target is broken).""" rng = np.random.default_rng(seed) base = average_precision_score(y, score(frame)) drops = {} for column in FEATURES: losses = [] for _ in range(repeats): shuffled = frame.copy() shuffled[column] = rng.permutation(shuffled[column].to_numpy()) losses.append(base - average_precision_score(y, score(shuffled))) drops[column] = round(float(np.mean(losses)), 4) return pd.Series(drops).sort_values(ascending=False)
boost0 = parts["boosting"][0][0]net0, prepare0 = parts["network"][0][0], parts["network"][0][1]importance = pd.DataFrame({ "boosting": permutation_importance(lambda f: boost0.predict_proba(f[FEATURES])[:, 1], valid, y_valid), "network": permutation_importance(lambda f: predict(net0, tensors(prepare0, (f,))[0]), valid, y_valid)})print(importance.sort_values("boosting", ascending=False).head(6))La sortie :
boosting networkPAY_1 0.2071 0.1549PAY_2 0.0137 0.0027PAY_AMT3 0.0105 0.0102BILL_AMT1 0.0098 0.0034PAY_AMT6 0.0093 0.0008LIMIT_BAL 0.0089 0.0130Description textuelle du graphique
Deux panneaux, baisse de l’AP de validation de 0 à 0,22. Boosting : statut en septembre 0,207, statut en août 0,014, montant payé en juillet 0,011, facture en septembre 0,010, montant payé en avril 0,009, limite de crédit 0,009. Réseau : statut en septembre 0,155, limite de crédit 0,013, montant payé en juillet 0,010, montant payé en septembre 0,009, statut en avril 0,007, scolarité 0,006.
Ce que cela nous apprend. Les deux modèles s’appuient sur le statut en septembre. Le mélanger coûte 0,21 d’AP au boosting et 0,15 au réseau. Chaque autre colonne ajoute environ 0,01, dans un ordre différent pour chaque modèle. C’est normal quand les colonnes portent la même information, comme les six mois de factures.
Voici maintenant la limite de cet outil. L’importance par permutation répond à « quelles colonnes comptent globalement? », pas à pourquoi un client a reçu un score élevé. La régression logistique décompose le score de chaque client en contributions exactes; ni le boosting ni le réseau ne le peuvent. Des outils estiment les raisons propres à chaque client pour les deux modèles. Mais ce sont des estimations, et elles ajoutent du travail. Pour les explications, le réseau n’est pas pire que le boosting. Les deux sont moins bons que la régression logistique.
Un nouveau client, de bout en bout
Section intitulée « Un nouveau client, de bout en bout »Un client fictif arrive. Il a deux mois de retard en septembre et en août. Sa facture est de 48 200 $ NT pour une limite de 50 000 $ NT. Il n’a rien payé en septembre. Le carnet (notebook) Jupyter attribue un score à ce client avec chaque graine de chaque modèle :
new_client = pd.DataFrame([{ "LIMIT_BAL": 50_000, "EDUCATION": 2, "PAY_1": 2, "PAY_2": 2, "PAY_3": 0, "PAY_4": 0, "PAY_5": 0, "PAY_6": 0, "BILL_AMT1": 48_200, "BILL_AMT2": 47_100, "BILL_AMT3": 46_000, "BILL_AMT4": 30_500, "BILL_AMT5": 29_800, "BILL_AMT6": 29_000, "PAY_AMT1": 0, "PAY_AMT2": 1_500, "PAY_AMT3": 1_600, "PAY_AMT4": 1_200, "PAY_AMT5": 1_100, "PAY_AMT6": 1_000}])for name in ("boosting", "network"): scores = [float(model.predict_proba(new_client[FEATURES])[0, 1]) if prepare is None else float(predict(model, tensors(prepare, (new_client,))[0])[0]) for model, prepare, _ in parts[name].values()] print(f"{name:9s} seed 0: {scores[0]:.3f} | 10 seeds: {min(scores):.3f} to {max(scores):.3f}")print(f"logistic {logistic.predict_proba(new_client[FEATURES])[0, 1]:.3f}")La sortie :
boosting seed 0: 0.596 | 10 seeds: 0.533 to 0.611network seed 0: 0.742 | 10 seeds: 0.658 to 0.742logistic 0.682Ce que cela nous apprend. Chaque modèle et chaque graine placent ce client bien au-dessus des seuils les moins coûteux (0,13 à 0,20). Chaque modèle appelle donc le client. La décision est la même; les scores diffèrent : 0,596, 0,742 et 0,682. Même une seule configuration varie d’environ 0,08 entre les graines. Utilisez donc ces scores pour classer et décider, pas comme probabilités exactes. Vérifiez d’abord l’étalonnage, comme le cours d’apprentissage automatique l’a fait.
Le travail nécessaire pour chaque modèle
Section intitulée « Le travail nécessaire pour chaque modèle »D’abord, le temps d’entraînement. Les deux modèles s’entraînent en moins d’une seconde avec deux fils d’exécution du processeur. Sur trois séries de mesures de 10 graines chacune, la médiane était de 0,29 s pour le réseau. Elle était de 0,45 s pour le boosting. Lors d’une exécution précédente, elle était de 0,42 s et de 0,22 s. D’autres tâches partageaient le Mac. Les temps varient donc d’un facteur deux entre les exécutions. À cette taille, le temps d’entraînement ne décide de rien. Attribuer des scores à 6 000 clients a pris environ 6 ms (réseau) et 12 ms (boosting). Le tableau met toutes les mesures côte à côte :
| Gradient boosting | Réseau | |
|---|---|---|
| Préparation | Aucune : les 20 colonnes telles quelles | Codes one-hot, logarithme signé, mise à l’échelle (55 colonnes); sans eux, résultat au niveau du hasard |
| Données réservées pour l’arrêt | Intégrées à fit() (10 %) |
Des données d’arrêt (10 %) que vous séparez et transmettez vous-même |
| Code à écrire | Un appel à fit() |
Une classe de modèle, une boucle d’entraînement, un arrêt anticipé, predict() |
| Paramètres par défaut (AP de validation) | 0,559, près des 0,567 après réglage | 0,550, sous la régression logistique avant réglage |
| 8 configurations (AP de validation médiane) | 0,559 à 0,566 | 0,549 à 0,560 |
| 10 graines (AP de validation) | 0,558 à 0,571 | 0,551 à 0,564 |
| AP de test, médiane | 0,562 | 0,556 |
| Coût de test, médiane | 3 362 | 3 342 |
| Explications | Importance par permutation; aucune raison exacte par client | La même chose |
Ce que cela nous apprend. Pour le score, c’est une égalité, avec un léger avantage au boosting pour le classement. Pour le travail, le boosting gagne clairement : aucune préparation, aucune boucle à écrire, de bons paramètres par défaut. Il est aussi moins sensible à ses paramètres.
Est-ce aussi vrai sur une autre table?
Section intitulée « Est-ce aussi vrai sur une autre table? »Un seul jeu de données peut induire en erreur. Le script de calcul a donc exécuté le même protocole sur une deuxième table ouverte, UCI Adult (revenu du recensement, CC BY 4.0). Elle compte 48 842 personnes, et la cible est un revenu supérieur à 50 000 $. Le sexe et la race ne faisaient pas partie des entrées. Le réseau utilisait sa configuration de crédit sans nouveau réglage. Cette vérification est donc approximative. Sur 5 graines, le boosting a atteint une AP de test de 0,833, et le réseau, de 0,774. L’aire sous la courbe ROC de test était de 0,929 contre 0,910. Ici, l’écart est grand, et toutes les graines concordent. Cela correspond à la tendance publiée : sur les tables, les arbres de boosting gagnent ou font égalité.
Le verdict
Section intitulée « Le verdict »Arjun écrit le résultat pour Mei-Ling en cinq points :
- Score : le réseau ne bat pas le boosting. Le boosting classe un peu mieux (AP de test de 0,562 contre 0,556); le coût est égal.
- Incertitude : l’intervalle à 95 % de la différence d’AP contient zéro. Les coûts médians des trois modèles sont à moins de 20 unités les uns des autres. La graine seule fait varier le coût d’un modèle de 40 à 60 unités.
- Travail : le réseau nécessite une préparation, échoue sans avertissement sans elle, et doit être réglé pour même atteindre la régression logistique.
- Temps : les deux s’entraînent en moins d’une seconde. Le temps ne décide donc pas.
- Explications : aucun des deux ne donne de raisons exactes par client; la régression logistique le fait.
Recommandation : gardez la régression logistique pour les appels. Elle coûte la même chose que les deux concurrents (3 346) et donne des raisons exactes pour chaque client. Gardez le boosting comme concurrent, comme le cours d’apprentissage automatique l’a décidé. Écartez le réseau pour cette décision.
Daniel lit le résultat et accepte. « Donc, le réseau n’est pas mauvais. Il ne justifie simplement pas son coût ici. » C’est la bonne interprétation.
Quand un réseau serait-il le meilleur choix ici?
Section intitulée « Quand un réseau serait-il le meilleur choix ici? »Le verdict concerne cette table, cette taille et cette décision. Un réseau mériterait une nouvelle comparaison si l’un de ces éléments changeait :
- Beaucoup plus de données. Avec des millions de clients et beaucoup plus de colonnes, un réseau peut trouver des motifs que les arbres manquent. Même alors, mesurez : sur beaucoup de grandes tables, le boosting gagne encore ou fait égalité.
- Des données brutes à côté de la table. Supposons que la banque ajoute le texte des notes d’appels, des documents numérisés ou la séquence de chaque paiement par carte. Un réseau peut les lire directement, comme le système d’acheminement des billets de soutien de ce cours lit du texte. Un seul réseau peut les combiner avec la table. Le boosting nécessite d’abord qu’une personne transforme les données brutes en colonnes.
- Un modèle pour plusieurs sorties. Supposons que l’équipe veuille un modèle pour prédire le défaut de paiement, le meilleur plan de paiement et la probabilité d’une plainte. Un réseau peut partager ses couches cachées entre les trois sorties. Avec le boosting, il faut trois modèles séparés.
- Des caractéristiques apprises à réutiliser. La couche cachée d’un réseau est un résumé appris d’un client, et d’autres modèles peuvent le réutiliser. Les modèles préentraînés sont réutilisés de la même façon, comme le montre Entraînement et inférence.
Aucune de ces conditions ne s’applique à l’équipe de Mei-Ling aujourd’hui.
Utilisation responsable
Section intitulée « Utilisation responsable »- La décision vise l’aide, pas le refus. Les modèles choisissent qui reçoit un appel d’aide précoce. Ne les réutilisez pas pour des limites de crédit, des prix ou des refus sans nouvelle évaluation. Il faut aussi de nouveaux coûts et un examen juridique.
- Les colonnes sensibles restent exclues, mais les variables de substitution restent présentes. Le cours d’apprentissage automatique a montré que la limite de crédit sert de variable de substitution pour l’âge. La limite de crédit est la deuxième colonne la plus importante du réseau. Un réseau n’élimine donc pas cette variable de substitution. Faites les mêmes vérifications par sous-groupe sur tout modèle que vous utilisez.
- Présentez chaque graine. Un modèle présenté seulement par sa meilleure graine semble meilleur qu’il ne l’est. Présentez la médiane et l’étendue, et gardez les données de test pour la fin.
- Les scores ne sont pas des probabilités. Le même client a reçu 0,60, 0,68 ou 0,74 de trois modèles. Ne présentez pas un score à un client comme « votre risque » sans vérifier l’étalonnage.
- Une banque, une période. Toutes les lignes viennent d’une banque à Taïwan en 2005. Rien ici ne montre comment les modèles se comportent dans une autre économie ou une autre année.
Constats et limites
Section intitulée « Constats et limites »- Les données ont pesé plus que le modèle. Les trois modèles obtiennent une AP de test entre 0,549 et 0,562. Les six mois d’historique de paiement portent l’essentiel du signal, et chaque modèle le trouve.
- Une égalité est un résultat. Le réseau n’a pas perdu de beaucoup; il n’a rien apporté de plus pour davantage de travail. Une comparaison équitable sert justement à découvrir cela.
- Limites de cette étude. Les grilles de réglage étaient petites (8 configurations chacune). Une recherche beaucoup plus vaste pourrait faire progresser légèrement l’un ou l’autre modèle. Le réseau utilisait des entrées simples. Les méthodes conçues pour les tables, comme les plongements pour les codes, n’ont pas été essayées. Les temps viennent d’un seul ordinateur partagé. La vérification sur la table Adult utilisait un réseau non réglé.
Faites-en un projet de portfolio
Section intitulée « Faites-en un projet de portfolio »Une comparaison équitable montre à un employeur quelque chose de rare : vous pouvez tester une idée prometteuse et présenter un « non » honnête. Personnalisez-la avec un changement : une troisième famille de modèles, d’autres coûts ou une autre table ouverte.
Quoi mettre dans le dépôt :
README.md, à partir du modèle ci-dessous.- Le carnet, exécuté du début à la fin, avec ses sorties.
results.csv, avec chaque exécution : modèle, configuration, graine, AP de validation, AP de test, coût et temps d’entraînement.- Les règles de la comparaison, écrites avant les résultats, pour que le lecteur voie que vous ne les avez pas changées ensuite.
- La source des données et la licence, avec le texte d’attribution de l’encadré ci-dessus.
Un modèle de README :
# Deep network or gradient boosting? A fair test on credit-card default
## QuestionOn a table of 20 columns about 30,000 credit-card clients, is a neural network worthit compared with gradient boosting and logistic regression?
## Data and licenceUCI "Default of Credit Card Clients" (Yeh, 2009), Taiwan 2005, CC BY 4.0,https://doi.org/10.24432/C55S3H. Sex, marital status and age are not inputs.
## Rules of the contestSame split (60/20/20, stratified), same inputs, same metric (average precision; costwith miss 5, false alarm 1), 10% held out for stopping in both, 8 settings x 3 seedseach, 10 final seeds each, test once.
## Results (test, median of 10 seeds)Boosting AP 0.562, cost 3,362. Network AP 0.556, cost 3,342. Logistic AP 0.549,cost 3,346. Rule cost 3,852. Bootstrap 95% interval of the AP difference(boosting - network): -0.004 to +0.020.
## VerdictA tie in cost; boosting a little ahead in ranking. The network needs preparation andtuning and gives no explanation advantage. Keep logistic regression; boosting staysthe challenger.
## LimitsOne bank, one period; small tuning grids; timings from one shared CPU.Comment le décrire en entrevue, en deux phrases : « J’ai organisé une comparaison équitable entre un réseau PyTorch et le gradient boosting sur une vraie table de crédit, avec un seul découpage, une seule mesure, un seul budget de réglage et dix graines chacun. Le réseau a fait égalité sur le coût, mais demandait plus de travail, alors j’ai recommandé le modèle plus simple et noté quand tester à nouveau un réseau. »
Pour aller plus loin :
- Donnez au réseau un plongement (embedding) pour chaque code de statut au lieu de colonnes one-hot. Le module 5 le faisait pour les mots. Mesurez si cela aide.
- Ajoutez une configuration à chaque grille (le taux d’apprentissage du réseau, le nombre d’arbres du boosting). Vérifiez si le verdict change.
- Répétez tout le protocole avec cinq découpages différents (
random_statede 0 à 4). Présentez la fréquence à laquelle chaque modèle gagne.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
Daniel rapporte sa meilleure exécution du réseau (AP de validation de 0,564) et la pire d'Arjun avec le gradient boosting (0,558). Il conclut que le réseau gagne. Quel est le problème, et que devraient-ils plutôt rapporter?
Afficher la réponse
Il compare l’exécution la plus chanceuse d’un modèle à la moins chanceuse de l’autre. Les deux modèles varient selon la graine aléatoire : le réseau de 0,551 à 0,564, le gradient boosting de 0,558 à 0,571. Le choix des graines peut transformer une petite défaite en victoire. Ils devraient utiliser le même nombre de graines pour les deux modèles et rapporter la médiane et l’étendue des résultats. Les médianes sont de 0,561 pour le réseau et de 0,567 pour le gradient boosting. Ensuite, ils vérifient la différence une seule fois sur les données de test, avec un intervalle obtenu, par exemple, par bootstrap.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Le réseau entraîné sur les colonnes brutes atteint une AP de 0,221 et une aire sous la courbe ROC de 0,50. Le gradient boosting atteint 0,559 sur les mêmes colonnes brutes. Expliquez les deux résultats.
Afficher la réponse
Un arbre pose des questions oui ou non, comme « la limite dépasse-t-elle 140 000? ». L’échelle d’une colonne n’a donc pas d’importance. Un réseau calcule des sommes pondérées. Une limite de 800 000 à côté d’un code de statut de 2 domine les sommes et les gradients. L’optimiseur ne peut donc pas apprendre. Le réseau donne presque le même score à tout le monde. Son AP correspond donc à la proportion de défauts de paiement (0,221). Son aire sous la courbe ROC est de 0,50, soit le hasard. La mise à l’échelle des entrées règle le problème.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
La banque de Mei-Ling prévoit d'ajouter aux données les notes en texte libre de chaque agent du centre d'appels. Elle veut aussi ajouter la liste complète des paiements par carte. L'équipe devrait-elle refaire cette comparaison? Qu'est-ce qui rendrait un réseau plus prometteur?
Afficher la réponse
Oui. Le verdict concernait une table de 20 colonnes choisies. Le texte libre et les séquences de paiements sont des données brutes. Un réseau peut les lire directement et apprendre ses propres caractéristiques, comme le système d’acheminement des billets de soutien de ce cours lit le texte. Un seul réseau peut aussi les combiner avec la table. Pour le gradient boosting, une personne devrait d’abord transformer les notes et les paiements en colonnes. Une plus grande quantité de données aide aussi un réseau. L’équipe devrait refaire la comparaison selon les mêmes règles. Elle devrait aussi vérifier de nouveau les explications et les sous-groupes, car les raisons d’un appel comptent pour les clients.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
- Une comparaison équitable ne change que le modèle. La séparation, les entrées, la mesure, la proportion réservée, le budget de réglage et les graines restent les mêmes. Les données de test servent une seule fois, à la fin.
- Sur cette table de crédit, le gradient boosting classait un peu mieux les cas (AP de test de 0,562 contre 0,556). Mais l’intervalle bootstrap de la différence incluait zéro. Le réseau, le gradient boosting et la régression logistique avaient le même coût.
- Un réseau destiné à une table demande de la préparation. Sans mise à l’échelle, ses résultats sont tombés au niveau du hasard, sans avertissement.
- Les graines comptent pour les deux modèles. Rapportez la médiane et l’étendue des résultats, jamais seulement la meilleure exécution.
- L’importance par permutation fonctionne pour tout modèle, mais ne donne pas de raisons exactes pour chaque client. La régression logistique du cours Apprentissage automatique : du problème à un modèle fiable reste le bon choix pour cette banque.
- Un réseau mérite une nouvelle comparaison avec beaucoup plus de données, des entrées brutes accompagnant la table ou plusieurs sorties d’un même modèle.
Ensuite, une étude de cas où l’apprentissage profond est le bon outil : du texte, et un modèle qui en a déjà beaucoup lu.
Leçon suivante : Faire le réglage fin d'un petit transformeur