Estimer le prix des maisons
- Étude de cas 2
- 120 min
- Facultative · 1 étude de cas requise pour le certificat
Étude de cas 2 sur 7 · Apprentissage automatique : du problème à un modèle fiableEstimer le prix des maisons
1. Comprendre l'apprentissage automatique
2. Cadrer le problème de prédiction
3. Évaluer les modèles supervisés
4. Créer les premiers modèles
5. Essayer d'autres familles de modèles
6. Régler et finaliser le modèle
7. Comprendre et choisir le modèle
8. Préparer la passation du modèle
Ce que vous saurez faire
Résolvez un deuxième problème réel de régression, de la question d’affaires au modèle testé et à la recommandation. Vous estimez le prix de vente d’une maison pour un prêteur. Les prix sont asymétriques, chaque erreur est un pourcentage et le marché évolue pendant que le modèle reste inchangé.
Le carnet (notebook) Jupyter contient toutes les cellules de code de cette page, trois tâches avec des vérifications et les consignes. Ses cellules de préparation installent les paquets et téléchargent les données. Il ne demande donc aucune configuration locale. Il s’exécute en environ deux minutes. Lisez le récit ici et exécutez le code dans le carnet.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
Cette étude de cas utilise l’ensemble du cours. Voici les leçons sur lesquelles elle s’appuie le plus :
- La MAPE et l’erreur moyenne, et pourquoi un pourcentage convient aux prix, dans Les métriques de régression.
- La validation croisée et la division selon le temps, dans Données d’entraînement, de validation et de test.
- La règle d’une erreur-type, dans Régler les hyperparamètres.
- Les pipelines avec encodage un à un, dans Prétraiter avec un pipeline, et le boosting par gradient (renforcement séquentiel), dans Les méthodes d’ensemble.
- Les sous-groupes avec leurs effectifs, dans L’analyse par sous-groupes, et la fiche du modèle, dans Documenter les limites d’utilisation.
- La première étude de cas, Prévoir la demande de vélopartage, est utile, mais pas obligatoire.
Ce qu’il vous faut sur votre ordinateur (seulement pour l’exécution locale; le carnet n’en a pas besoin)
- Un environnement Python avec pandas 3.0.6, scikit-learn 1.9.1 et matplotlib, comme le
.venvde votre projet dans Définir une référence de base : préparer votre projet. Téléchargez le carnet depuis l’encadré ci-dessus dans un dossier de votre choix. Ouvrez-le comme dans Définir une référence de base : ouvrir un carnet dans VS Code. Le carnet télécharge lui-même les données dans un nouveau dossierhouse-prices/. Pour recommencer, supprimez ce dossier.
Testé sur macOS avec Python 3.14.6, pandas 3.0.6, scikit-learn 1.9.1 et matplotlib 3.11.2. Le carnet a été testé dans Jupyter. Windows n’a pas été testé.
La situation
Section intitulée « La situation »Nadia Haddad dirige l’équipe d’évaluation d’un prêteur hypothécaire régional dans la région de Seattle. Un prêt hypothécaire sert à acheter une maison, qui constitue la garantie. Si l’emprunteur ne peut pas payer, le prêteur vend la maison pour récupérer l’argent. Avant d’approuver un prêt, le prêteur doit donc connaître la valeur de la maison. Un évaluateur agréé visite la maison et rédige une évaluation. Cela prend plusieurs jours et entraîne des frais. La politique du prêteur l’exige pour chaque prêt.
Nadia veut une première estimation le jour où une demande arrive, à partir de la description de la maison seulement. Elle ne remplace pas l’évaluateur. Elle indique rapidement à l’équipe quand le prix convenu semble loin de la valeur. L’évaluateur examine alors ces dossiers en premier, et l’agent de prêts peut avertir l’acheteur.
Les erreurs dans les deux sens ont un coût, mais pas le même. Si l’estimation est trop élevée, la maison semble offrir une meilleure garantie qu’en réalité. Supposons que le prêteur prête 80 % de l’estimation et que celle-ci est trop élevée de 10 %. Le prêt représente alors 88 % de la valeur réelle. Une vente après un défaut de paiement pourrait ne pas le couvrir. Si l’estimation est trop basse, une bonne demande semble risquée. L’équipe perd du temps et mécontente un client. L’objectif de Nadia : la plupart des estimations à ±10 % du prix de vente, et moins d’une sur cinq trop élevée de plus de 10 %.
Samir Patel, l’analyste de l’équipe, crée le modèle. Chris, de l’équipe des prêts, propose des idées rapides.
Ce que vous allez créer
Section intitulée « Ce que vous allez créer »Vous créerez un modèle qui estime le prix de vente d’une maison selon sa taille, sa qualité et son emplacement. Vous établirez aussi une règle qui indique quand utiliser l’estimation. Vous le comparerez à la méthode rapide d’un évaluateur. Vous le vérifierez par validation croisée et sur une période de validation ultérieure, puis documenterez ses échecs. À la fin, vous aurez un carnet, un journal de huit expériences, un modèle enregistré, une fiche du modèle et une recommandation pour Nadia.
L’étude de cas utilise des compétences de tout le cours. Vous vérifiez les données (module 2), choisissez les métriques et la division des données (module 3), puis créez des pipelines (module 4). Vous utilisez le boosting et un journal d’expériences (module 5), ainsi que la règle d’une erreur-type (module 6). Ensuite, vous vérifiez les sous-groupes (module 7) et rédigez une fiche du modèle (module 8). Elle ajoute un nouvel outil, expliqué au moment de l’utiliser : un modèle qui apprend le logarithme du prix.
Les données
Section intitulée « Les données »Les données sont un registre public des ventes de maisons dans King County, dans l’État de Washington, où se trouve Seattle. Elles couvrent mai 2014 à mai 2015. Un utilisateur nommé harlfoxem les a d’abord publiées sur Kaggle. Elles constituent maintenant le jeu de données 42092 sur OpenML. Les deux pages indiquent la licence CC0 1.0 : les données sont dans le domaine public, et chacun peut les réutiliser sans conditions. Nous indiquons tout de même la source : House Sales in King County, USA (harlfoxem, Kaggle 2016; OpenML 42092). Nadia, Samir et le prêteur sont fictifs. Les ventes sont réelles.
L’équipe de Nadia ne connaît pas encore les données, et la personne qui les a téléversées n’a pas décrit les colonnes. Leur sens vient des registres immobiliers du comté. La fiche du jeu de données dans le dépôt des laboratoires les décrit. Nextia héberge le fichier en format CSV compressé, avec la date écrite comme 2014-10-13. Rien d’autre n’est modifié.
Charger les données et regarder trois lignes
Section intitulée « Charger les données et regarder trois lignes »Chaque bloc de code de cette page est une cellule du carnet. Les cellules de préparation ont déjà chargé pandas sous le nom pd et numpy sous le nom np. Elles ont aussi téléchargé le fichier dans le dossier de travail.
houses = pd.read_csv("king_county_house_sales.csv.gz", parse_dates=["date"])print(houses.shape)print("sales from", houses["date"].min().date(), "to", houses["date"].max().date())print(houses[["date", "price", "bedrooms", "bathrooms", "sqft_living", "grade", "zipcode"]].head(3).to_string())La sortie :
(21613, 21)sales from 2014-05-02 to 2015-05-27 date price bedrooms bathrooms sqft_living grade zipcode0 2014-10-13 221900.0 3 1.00 1180 7 981781 2014-12-09 538000.0 3 2.25 2570 7 981252 2015-02-25 180000.0 2 1.00 770 6 98028Ce que cela nous apprend. Une ligne représente une vente d’une maison : 21 613 ventes en environ 13 mois. price est la cible, en dollars américains. La première maison, vendue en octobre 2014, compte 3 chambres, 1 salle de bain et 1 180 pieds carrés habitables. Elle s’est vendue 221 900 dollars. (Un pied carré vaut environ 0,093 m²; 1 180 pieds carrés font donc environ 110 m².)
Les colonnes utilisées
Section intitulée « Les colonnes utilisées »Voici les colonnes qui décrivent une maison, en mots simples :
| Colonne | Signification |
|---|---|
bedrooms, bathrooms, floors |
nombres; 0,5 salle de bain désigne une toilette sans douche; 1,5 étage désigne un étage supérieur partiel |
sqft_living, sqft_above, sqft_basement |
superficie habitable, partie au-dessus du sol et sous-sol (0 = aucun sous-sol) |
sqft_lot |
superficie du terrain |
waterfront, view |
1 si la maison est au bord de l’eau; qualité de la vue de 0 (aucune vue évaluée) à 4 |
condition |
état de la maison, de 1 (mauvais) à 5 (très bon) |
grade |
cote du bâtiment attribuée par le comté, de 1 à 13 : qualité de la construction et de la conception (7 correspond à la moyenne) |
yr_built, yr_renovated |
année de construction; année de la dernière rénovation (0 = jamais) |
zipcode, lat, long |
code postal (70 codes), latitude et longitude |
sqft_living15, sqft_lot15 |
superficies habitables et des terrains des 15 maisons les plus proches |
id, date |
maison et date de vente : servent seulement à diviser les données, jamais comme entrées |
Premières vérifications
Section intitulée « Premières vérifications »print("missing values:", int(houses.isna().sum().sum()), "| duplicate rows:", int(houses.duplicated().sum()))print("houses sold more than once:", int(houses["id"].duplicated().sum()), "extra sales")print("bedrooms:", houses["bedrooms"].value_counts().sort_index().to_dict())print("bathrooms 0:", int((houses["bathrooms"] == 0).sum()), "| yr_renovated 0:", int((houses["yr_renovated"] == 0).sum()), "| sqft_basement 0:", int((houses["sqft_basement"] == 0).sum()))La sortie :
missing values: 0 | duplicate rows: 0houses sold more than once: 177 extra salesbedrooms: {0: 13, 1: 199, 2: 2760, 3: 9824, 4: 6882, 5: 1601, 6: 272, 7: 38, 8: 13, 9: 6, 10: 3, 11: 1, 33: 1}bathrooms 0: 10 | yr_renovated 0: 20699 | sqft_basement 0: 13126Ce que cela nous apprend. Aucune cellule n’est vide et aucune ligne n’est une copie d’une autre. Mais certains nombres sont des codes : yr_renovated vaut 0 pour les 20 699 maisons jamais rénovées. sqft_basement vaut 0 pour les maisons sans sous-sol. Un modèle ne doit pas lire « rénovée en l’an 0 ». Les arbres gèrent bien cela, car ils peuvent séparer 0 de 1900. Nous en tenons compte pour le modèle linéaire. Treize maisons ont 0 chambre (un studio ou une valeur manquante) et une en a 33. Et 177 ventes sont des deuxièmes ou troisièmes ventes d’une maison déjà présente dans les données.
Comparez la maison de 33 chambres aux autres grandes maisons :
print(houses.loc[houses["bedrooms"] >= 10, ["bedrooms", "bathrooms", "sqft_living", "price"]].to_string())La sortie :
bedrooms bathrooms sqft_living price8757 11 3.00 3000 520000.013314 10 5.25 4590 1148000.015161 10 2.00 3610 650000.015870 33 1.75 1620 640000.019254 10 3.00 2920 660000.0Ce que cela nous apprend. Les autres maisons de 10 ou 11 chambres ont une superficie de 2 900 à 4 600 pieds carrés. Une maison de 33 chambres sur 1 620 pieds carrés aurait des pièces d’environ 50 pieds carrés. C’est une erreur de saisie : il fallait écrire 3. Samir la corrige dans sa copie des données et note la correction dans la fiche du jeu de données. Ainsi, chacun peut la reproduire :
houses.loc[houses["bedrooms"] == 33, "bedrooms"] = 3 # 1,620 sq ft: a typing error for 3print("largest number of bedrooms now:", int(houses["bedrooms"].max()))La sortie :
largest number of bedrooms now: 11Ce que cela nous apprend. La correction a fonctionné : aucune maison ne compte maintenant plus de 11 chambres, ce qui correspond aux autres grandes maisons. La correction figure dans le carnet et dans la fiche du jeu de données. Le fichier hébergé reste donc tel que la source l’a publié.
Ventes répétées
Section intitulée « Ventes répétées »Passons aux maisons vendues plus d’une fois. De combien leur prix a-t-il changé?
repeat = houses[houses["id"].duplicated(keep=False)].sort_values(["id", "date"])first, last = repeat.groupby("id")["price"].first(), repeat.groupby("id")["price"].last()print("houses:", repeat["id"].nunique(), "| median change from first to last sale:", f"{100 * ((last / first).median() - 1):.1f}%")La sortie :
houses: 176 | median change from first to last sale: 54.4%Ce que cela nous apprend. 176 maisons ont été vendues au moins deux fois en un an. La hausse médiane du prix entre la première et la dernière vente est de 54 %. La plupart ont été achetées, rénovées, puis revendues. Mais la description dans les données est identique pour les deux ventes. Le modèle ne peut donc pas voir les travaux. Ces ventes nous renseignent aussi sur la division des données : deux ventes d’une même maison ne sont pas deux exemples indépendants. Si une vente sert à l’entraînement et l’autre à la validation, la vérification ne porte plus sur une nouvelle maison. Toutes les ventes d’une maison doivent donc rester dans la même partie des données.
Cadrer le problème
Section intitulée « Cadrer le problème »Selon les termes de Traduire un besoin :
- Décision : quelles demandes l’évaluateur examine en premier, et quand l’agent de prêts avertit un acheteur que le prix semble élevé.
- Unité de prédiction : une vente d’une maison.
- Cible :
price, le prix de vente. C’est un nombre, donc une régression. - Moment de la prédiction : le jour de la demande, avant l’évaluation. La description de la maison est connue, grâce aux registres du comté et à l’annonce.
- Informations indisponibles : le prix de vente lui-même et tout ce que l’évaluateur découvre dans la maison (état de la cuisine, humidité, toit neuf).
- Fuites à surveiller : les superficies des 15 maisons voisines ont probablement été mesurées en 2015. Pour une vente en 2014, elles pourraient décrire la rue quelques mois plus tard. L’effet est faible, et Samir les garde, avec une note.
- Personnes et équité : aucune colonne ne décrit l’acheteur ou le vendeur. Mais l’emplacement est une entrée importante et, aux États-Unis, il est lié au revenu et à la race. Un modèle qui sous-évalue certains quartiers reproduirait un préjudice connu dans l’octroi de prêts. Samir vérifie donc les erreurs par secteur, et les limites d’utilisation reviennent sur ce point.
Explorer les besoins de la décision
Section intitulée « Explorer les besoins de la décision »Nous n’explorons pas chaque colonne. La décision de Nadia soulève trois questions. À quoi ressemblent les prix, et comment mesurer une erreur? Le marché évolue-t-il pendant l’année? Et quelle importance a l’emplacement?
print(houses["price"].describe().round(0).to_dict())print("share above 1 million:", round(float((houses["price"] > 1_000_000).mean()), 3))La sortie :
{'count': 21613.0, 'mean': 540088.0, 'std': 367127.0, 'min': 75000.0, '25%': 321950.0, '50%': 450000.0, '75%': 645000.0, 'max': 7700000.0}share above 1 million: 0.068Le prix va de 75 000 à 7 700 000 dollars. La médiane est de 450 000, mais la moyenne est de 540 088. Quelques maisons très chères font monter la moyenne. Regardez la forme de la distribution des prix.
Description textuelle du graphique
Un histogramme des prix de 2 000 ventes choisies au hasard, de 0 à 3 millions de dollars, par tranches de 100 000 dollars. Une seule vente est sous 100 000. Les barres montent rapidement jusqu’au sommet : 427 ventes entre 300 000 et 400 000. Elles descendent ensuite lentement : 354 entre 400 000 et 500 000, et 254 entre 500 000 et 600 000. Il y en a 112 entre 700 000 et 800 000, et 29 entre 1 million et 1,1 million. Une longue queue mince se poursuit vers la droite. Les 4 ventes au-dessus de 3 millions sont exclues du graphique.
Ce que cela nous apprend. La distribution des prix est asymétrique : beaucoup de maisons ordinaires et une longue queue de maisons chères. Cela change la façon de mesurer les erreurs et d’apprendre. Une erreur de 50 000 dollars représente 20 % du prix d’une maison de 250 000 dollars. Elle représente seulement 2,5 % du prix d’une maison de 2 millions de dollars. L’objectif de Nadia est déjà un pourcentage, « à ±10 % ». C’est la bonne façon de considérer ces erreurs.
Penser les prix en pourcentage : le logarithme
Section intitulée « Penser les prix en pourcentage : le logarithme »Quand vous parlez de prix, vous pensez déjà en pourcentage. Un magasin annonce « 20 % de réduction », pas « 100 dollars de réduction ». Une réduction de 100 dollars sur des chaussettes et sur une voiture, ce n’est pas la même chose. Les prix des maisons fonctionnent de la même façon. Une belle vue ajoute une part du prix, pas un montant fixe. Elle ajoute davantage à une grande maison qu’à une petite.
Le logarithme transforme les pourcentages en pas égaux. Sur une échelle logarithmique, chaque pas « × 10 » a la même taille : 100 000 → 1 000 000 → 10 000 000 sont trois pas égaux. Ainsi, « 10 % de plus » représente la même distance pour une maison bon marché et une maison chère. Le prochain graphique montre les mêmes prix sous forme de logarithme en base 10.
Description textuelle du graphique
Un histogramme des mêmes 2 000 prix sur une échelle logarithmique, de 4,9 (environ 79 000 dollars) à 6,7 (environ 5 millions de dollars), par tranches de 0,05. Les barres forment une colline presque symétrique. Son sommet est la tranche de 5,6 à 5,65, soit environ 400 000 à 450 000 dollars, avec 208 ventes. La longue queue à droite du premier graphique est devenue une courte queue de chaque côté.
Les modèles de cette étude de cas apprennent donc le logarithme du prix, puis reconvertissent la réponse en dollars à la fin. Un modèle qui apprend le logarithme donne la même taille à une erreur de 10 % pour chaque maison. Cela correspond exactement au point de vue de Nadia.
Le marché évolue
Section intitulée « Le marché évolue »months = houses.groupby(houses["date"].dt.to_period("M"))["price"].agg(sales="size", median_price="median")print(months.T.to_string())La sortie :
date 2014-05 2014-06 2014-07 2014-08 2014-09 2014-10 2014-11 2014-12 2015-01 2015-02 2015-03 2015-04 2015-05sales 1768.0 2180.0 2211.0 1940.0 1774.0 1878.0 1411.0 1471.0 978.0 1250.0 1875.0 2231.0 646.0median_price 465000.0 465000.0 465000.0 442100.0 450000.0 446900.0 435000.0 432500.0 438500.0 425545.0 450000.0 476500.0 455000.0- Entraînement
- Validation
- Test
Description textuelle du graphique
Un graphique linéaire sur 13 mois, avec un prix médian de 400 000 à 500 000 dollars. De mai à juillet 2014, la médiane est de 465 000. Elle baisse lentement en automne et en hiver, jusqu’à 432 500 en décembre 2014 (entraînement), 438 500 en janvier 2015 et 425 545 en février 2015 (validation). Au printemps, elle remonte : 450 000 en mars, 476 500 en avril, le mois le plus élevé, et 455 000 en mai 2015 (test). Le nombre de ventes baisse aussi en hiver, à 978 en janvier 2015. Il remonte au printemps, à 2 231 en avril.
Ce que cela nous apprend. Les prix des maisons suivent les saisons : moins de ventes et des prix plus bas en hiver. Au printemps, les ventes et les prix augmentent. Un modèle qui apprend sur l’année jusqu’à février aura tendance à sous-estimer les prix au printemps. Nous le reverrons lors du test final.
Trois secteurs
Section intitulée « Trois secteurs »L’emplacement compte le plus dans le prix des maisons. Pour vérifier plus tard les erreurs par emplacement, Samir classe chaque vente dans l’un de trois secteurs. Le premier regroupe les codes postaux qui commencent par 981 : Seattle et ses voisins proches. Les deux autres couvrent le reste du comté, au nord et au sud de la latitude 47,5.
def area(part): """Three areas: zip codes 981xx (Seattle and close neighbours), then north/east and south.""" seattle = part["zipcode"] // 100 == 981 return np.select([seattle, part["lat"] >= 47.5], ["Seattle", "North and east"], "South")
houses["area"] = area(houses)per_sqft = (houses["price"] / houses["sqft_living"]).groupby(houses["area"]).median().round(0)print(houses["area"].value_counts().to_dict())print("median price per sq ft:", per_sqft.to_dict())La sortie :
{'Seattle': 8977, 'North and east': 7209, 'South': 5427}median price per sq ft: {'North and east': 264.0, 'Seattle': 291.0, 'South': 165.0}Ce que cela nous apprend. Un pied carré à Seattle se vend à un prix médian de 291 dollars, contre 165 dans le sud du comté. La même maison vaut beaucoup plus à un endroit qu’à un autre. Les secteurs servent seulement à vérifier les erreurs. Les modèles utilisent plutôt la latitude, la longitude ou le code postal.
Répartition et référence de base
Section intitulée « Répartition et référence de base »Une répartition chronologique, avec chaque maison dans une seule partie
Section intitulée « Une répartition chronologique, avec chaque maison dans une seule partie »Le modèle de Nadia estimera les ventes du mois prochain à partir des ventes passées. La répartition suit donc le temps, comme dans la première étude de cas. Train (entraînement) contient les ventes de 2014, valid (validation), celles de janvier et février 2015, et test, celles de mars à mai 2015. Mais avec un changement : toutes les ventes d’une maison vont dans la partie de sa première vente. Aucune maison ne se retrouve donc dans deux parties. C’est une répartition par groupes : le groupe est la maison.
TARGET = "price"FEATURES = ["bedrooms", "bathrooms", "sqft_living", "sqft_lot", "floors", "waterfront", "view", "condition", "grade", "sqft_above", "sqft_basement", "yr_built", "yr_renovated", "lat", "long", "sqft_living15", "sqft_lot15", "zipcode"]first_sale = houses.groupby("id")["date"].transform("min") # every sale of a house goes where its first sale goestrain = houses[first_sale < "2015-01-01"]valid = houses[(first_sale >= "2015-01-01") & (first_sale < "2015-03-01")]test = houses[first_sale >= "2015-03-01"]print("train", len(train), "| valid", len(valid), "| test", len(test))print("sales moved to an earlier part:", int((houses["date"] != first_sale).sum()))Le résultat :
train 14768 | valid 2191 | test 4654sales moved to an earlier part: 177Ce que cela nous apprend. Les données d’entraînement comptent 14 768 ventes, celles de validation, 2 191, et celles de test, 4 654. Les 177 ventes répétées ont été déplacées dans la partie de la première vente de leur maison. Cela perturbe un peu l’ordre chronologique : quelques ventes de 2015 se trouvent maintenant dans les données d’entraînement. Elles représentent moins de 1 % des données d’entraînement. L’autre option, une maison dans deux parties, est pire.
Comment nous mesurons les erreurs
Section intitulée « Comment nous mesurons les erreurs »Les erreurs sont des pourcentages du prix réel. La fonction suivante rapporte quatre nombres que Nadia comprend :
- l’erreur médiane en pourcentage : une valeur positive signifie que l’estimation est trop basse. Elle montre le sens de l’erreur, comme l’erreur moyenne dans Les métriques de régression. Quelques ventes extrêmes ne font pas dévier la médiane;
- la MAPE, l’erreur absolue moyenne en pourcentage. Les prix ne sont jamais proches de zéro, donc la MAPE convient bien ici;
- la proportion à 10 % près : la cible de Nadia;
- la proportion surestimée de 10 % ou plus : le sens dangereux pour un prêteur.
def report(real, estimate): """Errors as percentages of the real price (positive = the estimate is too low).""" pct = 100 * (real - estimate) / real return {"median error %": round(float(np.median(pct)), 1), "MAPE %": round(float(np.mean(np.abs(pct))), 1), "within 10%": round(float(np.mean(np.abs(pct) <= 10)), 3), "over by 10%+": round(float(np.mean(pct < -10)), 3)}Trois références de base
Section intitulée « Trois références de base »Un évaluateur sans modèle fait une estimation rapide sur papier. Samir reproduit trois méthodes de ce type, chacune apprise uniquement sur les données d’entraînement. La première est le prix médian : la même estimation pour chaque maison. La deuxième multiplie le prix médian par pied carré du comté entier par la superficie habitable de la maison. La troisième multiplie le prix médian par pied carré du même code postal par la superficie habitable. Elle combine la taille et l’emplacement.
Prédisez. Quelle référence de base donne le plus d’estimations à 10 % près? Quelle proportion environ?
median_estimate = np.full(len(valid), train[TARGET].median())per_sqft = (train[TARGET] / train["sqft_living"]).median()zip_per_sqft = (train[TARGET] / train["sqft_living"]).groupby(train["zipcode"]).median()baselines = {"median price": median_estimate, "price per sq ft": per_sqft * valid["sqft_living"], "zip price per sq ft": valid["zipcode"].map(zip_per_sqft) * valid["sqft_living"]}baseline_results = {name: report(valid[TARGET], estimate) for name, estimate in baselines.items()}print(pd.DataFrame(baseline_results).T.to_string())Le résultat :
median error % MAPE % within 10% over by 10%+median price -4.7 45.3 0.154 0.461price per sq ft -2.8 35.4 0.188 0.446zip price per sq ft 2.9 19.2 0.372 0.285Ce que cela nous apprend. Le prix médian est à 10 % près pour seulement 15 % des maisons. La taille seule fait un peu mieux. La taille et l’emplacement combinés, soit la référence par code postal, donnent 37 % des maisons à 10 % près. La MAPE est de 19,2 %. C’est la méthode qu’un évaluateur peut utiliser pour un calcul rapide sur papier. Chaque modèle doit faire mieux. Elle surestime aussi 28,5 % des maisons de 10 % ou plus : trop risqué pour Nadia.
Le journal d’expériences
Section intitulée « Le journal d’expériences »Le journal reprend les colonnes de ce cours, avec les métriques de ce problème : id (identifiant), date, change (changement), cv_mape et cv_std (moyenne et dispersion de 5 plis sur les données d’entraînement), valid_mape (MAPE de validation), valid_within_10 (proportion à 10 % près en validation), decision (décision) et reason (raison). Comme dans L’apprentissage automatique est itératif, chaque essai y est inscrit.
import datetimefrom pathlib import Path
LOG_FILE = Path("experiment_log.csv")LOG_COLUMNS = ["id", "date", "change", "cv_mape", "cv_std", "valid_mape", "valid_within_10", "decision", "reason"]
def log_experiment(id, change, cv, result, decision, reason): """Add one row (cv = (mean, std) or None), or replace the row with the same id.""" log = pd.read_csv(LOG_FILE) if LOG_FILE.exists() else pd.DataFrame(columns=LOG_COLUMNS) row = {"id": id, "date": datetime.date.today().isoformat(), "change": change, "cv_mape": cv[0] if cv else None, "cv_std": cv[1] if cv else None, "valid_mape": result["MAPE %"], "valid_within_10": result["within 10%"], "decision": decision, "reason": reason} log = pd.concat([log[log["id"] != id], pd.DataFrame([row])], ignore_index=True) log.sort_values("id").to_csv(LOG_FILE, index=False)
def show_log(): log = pd.read_csv(LOG_FILE) print(log[["id", "change", "cv_mape", "cv_std", "valid_mape", "valid_within_10", "decision"]].to_string(index=False))
LOG_FILE.unlink(missing_ok=True) # start a new logLes références de base n’ont rien à régler. Elles n’ont donc aucun score de validation croisée :
log_experiment("E01", "Baseline: the median price of train", None, baseline_results["median price"], "baseline", "The floor.")log_experiment("E02", "Baseline: median price per sq ft x living area", None, baseline_results["price per sq ft"], "baseline", "Size alone.")log_experiment("E03", "Baseline: median price per sq ft in the zip code x living area", None, baseline_results["zip price per sq ft"], "baseline", "Size and place: what a quick desk estimate does.")show_log()Le résultat :
id change cv_mape cv_std valid_mape valid_within_10 decisionE01 Baseline: the median price of train NaN NaN 45.3 0.154 baselineE02 Baseline: median price per sq ft x living area NaN NaN 35.4 0.188 baselineE03 Baseline: median price per sq ft in the zip code x living area NaN NaN 19.2 0.372 baselineCe que cela nous apprend. Le journal contient les trois références de base. Chaque modèle suivant doit battre E03 : une MAPE de validation sous 19,2 % et plus de 37,2 % des maisons à 10 % près.
Les ventes de maisons dépendent moins du temps que les locations horaires. Samir compare donc les modèles comme vous l’avez fait dans Régler les hyperparamètres. Il utilise une validation croisée à 5 plis sur les données d’entraînement, puis une vérification sur les données de validation. GroupKFold garde toutes les ventes d’une maison dans le même pli, pour la même raison que la répartition.
from sklearn.model_selection import GroupKFold, cross_val_score
def cv_mape(model): """5-fold CV on train, the sales of one house in one fold; mean and std of the MAPE, in %.""" scores = -cross_val_score(model, train, train[TARGET], groups=train["id"], cv=GroupKFold(n_splits=5), scoring="neg_mean_absolute_percentage_error") return round(float(100 * scores.mean()), 2), round(float(100 * scores.std()), 2)Régression ridge, en dollars et en logarithmes
Section intitulée « Régression ridge, en dollars et en logarithmes »Le premier modèle est une régression ridge (avec une pénalité sur les coefficients). Le code postal devient 70 colonnes de codage one-hot (une colonne indicatrice par catégorie). Il désigne un lieu, pas une quantité : 98178 n’est pas « plus » que 98125. La latitude et la longitude sont exclues. Une droite en latitude ne peut pas suivre la forme d’une ville. Les autres nombres sont mis à l’échelle.
from sklearn.compose import ColumnTransformer, TransformedTargetRegressorfrom sklearn.linear_model import Ridgefrom sklearn.pipeline import Pipelinefrom sklearn.preprocessing import OneHotEncoder, StandardScaler
NUMBERS = [c for c in FEATURES if c not in ("zipcode", "lat", "long")]prepare = ColumnTransformer([("zipcode", OneHotEncoder(handle_unknown="ignore"), ["zipcode"]), ("numbers", StandardScaler(), NUMBERS)])ridge = Pipeline([("prepare", prepare), ("model", Ridge(alpha=1.0))])ridge_cv = cv_mape(ridge)ridge_result = report(valid[TARGET], ridge.fit(train, train[TARGET]).predict(valid))print("CV", ridge_cv, "| valid", ridge_result)Le résultat :
CV (19.53, 0.37) | valid {'median error %': 2.4, 'MAPE %': 21.3, 'within 10%': 0.337, 'over by 10%+': 0.298}Ce que cela nous apprend. Le modèle ridge fait moins bien que la référence par code postal sur les données de validation : MAPE de 21,3 % contre 19,2 %. Sa MAPE de validation croisée de 19,5 % donne le même constat. Sur l’échelle des dollars, un modèle avec 70 codes postaux et toutes les colonnes de taille ne bat pas la méthode rapide d’un évaluateur. Donnez maintenant au même pipeline le logarithme du prix. TransformedTargetRegressor de scikit-learn le fait pour vous. Il prend le logarithme de la cible avant fit() (ajuster), puis reconvertit chaque estimation en dollars après predict() (prédire).
Prédisez. La MAPE diminuera-t-elle un peu ou beaucoup?
ridge_log = TransformedTargetRegressor(regressor=ridge, func=np.log, inverse_func=np.exp)ridge_log_cv = cv_mape(ridge_log)ridge_log_result = report(valid[TARGET], ridge_log.fit(train, train[TARGET]).predict(valid))print("CV", ridge_log_cv, "| valid", ridge_log_result)Le résultat :
CV (13.55, 0.05) | valid {'median error %': 2.3, 'MAPE %': 14.9, 'within 10%': 0.486, 'over by 10%+': 0.234}Ce que cela nous apprend. Le même modèle, avec les mêmes entrées, passe de 21,3 % à 14,9 % sur les données de validation. En validation croisée, il passe de 19,5 % à 13,6 %. Seule l’échelle de la cible a changé. Sur l’échelle des dollars, le modèle concentre ses efforts sur les maisons chères, où les erreurs en dollars sont les plus grandes. Sur l’échelle logarithmique, chaque maison compte autant, en pourcentage. La dispersion en validation croisée est minuscule (0,05), donc les cinq plis concordent.
log_experiment("E04", "Ridge, zip code one-hot, scaled numbers", ridge_cv, ridge_result, "reject", "Worse than the zip baseline on valid: its errors grow with the price.")log_experiment("E05", "E04 on the log of the price", ridge_log_cv, ridge_log_result, "keep", "MAPE down by about 6 points: errors scale with the price.")show_log()Le résultat :
id change cv_mape cv_std valid_mape valid_within_10 decisionE01 Baseline: the median price of train NaN NaN 45.3 0.154 baselineE02 Baseline: median price per sq ft x living area NaN NaN 35.4 0.188 baselineE03 Baseline: median price per sq ft in the zip code x living area NaN NaN 19.2 0.372 baselineE04 Ridge, zip code one-hot, scaled numbers 19.53 0.37 21.3 0.337 rejectE05 E04 on the log of the price 13.55 0.05 14.9 0.486 keepCe que cela nous apprend. E05 est le premier modèle qui bat la référence de base. Le journal lui attribue donc keep (conserver). E04 reste dans le journal avec sa raison : il justifie l’apprentissage du logarithme du prix.
Amplification de gradient avec la carte
Section intitulée « Amplification de gradient avec la carte »Un modèle linéaire ajoute un effet par code postal. Mais dans un même code postal, une rue au bord de l’eau peut différer beaucoup d’une rue près d’une autoroute. Les arbres peuvent découper la carte en petits morceaux. « Latitude supérieure à 47,6 et longitude inférieure à −122,2 » définit un quartier. Le modèle d’amplification de gradient utilise donc lat (latitude) et long (longitude) plutôt que le code postal. Il apprend aussi le logarithme du prix.
from sklearn.ensemble import HistGradientBoostingRegressor
TREE_FEATURES = [c for c in FEATURES if c != "zipcode"] # trees use lat and long for the place
def boosting(**settings): select = ColumnTransformer([("features", "passthrough", TREE_FEATURES)]) model = Pipeline([("select", select), ("model", HistGradientBoostingRegressor(random_state=0, **settings))]) return TransformedTargetRegressor(regressor=model, func=np.log, inverse_func=np.exp)
boosting_cv = cv_mape(boosting())boosting_result = report(valid[TARGET], boosting().fit(train, train[TARGET]).predict(valid))print("CV", boosting_cv, "| valid", boosting_result)Le résultat :
CV (11.71, 0.19) | valid {'median error %': 2.7, 'MAPE %': 13.2, 'within 10%': 0.554, 'over by 10%+': 0.194}Ce que cela nous apprend. L’amplification donne 55 % des maisons de validation à 10 % près. C’est 49 % pour ridge sur le logarithme du prix et 37 % pour la référence par code postal. Sa MAPE de validation croisée, 11,71 %, est meilleure de 1,8 point que celle de ridge.
Régler un paramètre avec la règle d’une erreur-type
Section intitulée « Régler un paramètre avec la règle d’une erreur-type »L’étape suivante évidente est une amplification plus lente : un taux d’apprentissage de 0,05 et 600 arbres. Le gain est-il réel ou dû au bruit? Ici, une MAPE plus basse est meilleure. La règle d’une erreur-type fonctionne donc dans l’autre sens. Prenez la meilleure moyenne et ajoutez son erreur-type : l’écart-type des 5 plis divisé par √5. Tout réglage à cette limite ou en dessous est à égalité avec le meilleur.
slower_cv = cv_mape(boosting(learning_rate=0.05, max_iter=600))print("E06", boosting_cv, "| E07", slower_cv)standard_error = slower_cv[1] / np.sqrt(5) # the std of the 5 fold scores / the square root of 5print("limit = best mean + one standard error =", round(slower_cv[0] + standard_error, 2))Le résultat :
E06 (11.71, 0.19) | E07 (11.57, 0.24)limit = best mean + one standard error = 11.68Ce que cela nous apprend. E07 est le meilleur, avec 11,57 %. Sa limite est de 11,68 %. E06, à 11,71 %, dépasse légèrement la limite. Il n’est donc pas à égalité avec E07. La règle dit : conservez E07. Le gain est faible, et E07 prend environ deux fois plus de temps à entraîner. Mais le gain est réel, et le modèle reste petit et rapide. Samir vérifie E07 une fois sur les données de validation et inscrit les deux modèles au journal :
slower_result = report(valid[TARGET], boosting(learning_rate=0.05, max_iter=600).fit(train, train[TARGET]).predict(valid))print("E07 on valid:", slower_result)log_experiment("E06", "Gradient boosting on the log price, lat and long, defaults", boosting_cv, boosting_result, "reject", "1.8 points better than E05 in CV, but just outside the limit of E07.")log_experiment("E07", "E06 with learning_rate 0.05 and 600 trees", slower_cv, slower_result, "keep", "Best CV; E06 is above the one-standard-error limit. Valid agrees.")show_log()Le résultat :
E07 on valid: {'median error %': 2.7, 'MAPE %': 12.9, 'within 10%': 0.556, 'over by 10%+': 0.194} id change cv_mape cv_std valid_mape valid_within_10 decisionE01 Baseline: the median price of train NaN NaN 45.3 0.154 baselineE02 Baseline: median price per sq ft x living area NaN NaN 35.4 0.188 baselineE03 Baseline: median price per sq ft in the zip code x living area NaN NaN 19.2 0.372 baselineE04 Ridge, zip code one-hot, scaled numbers 19.53 0.37 21.3 0.337 rejectE05 E04 on the log of the price 13.55 0.05 14.9 0.486 keepE06 Gradient boosting on the log price, lat and long, defaults 11.71 0.19 13.2 0.554 rejectE07 E06 with learning_rate 0.05 and 600 trees 11.57 0.24 12.9 0.556 keepCe que cela nous apprend. La validation concorde : 12,9 % contre 13,2 %. E07 est le modèle retenu.
À vous de jouer : un modèle sans l’emplacement
Section intitulée « À vous de jouer : un modèle sans l’emplacement »Chris demande : « Avons-nous vraiment besoin de la carte? La maison elle-même devrait suffire. » Dans le carnet (notebook) Jupyter, vous retirez lat et long du modèle d’amplification. Vous utilisez les réglages par défaut de E06 pour aller vite, l’inscrivez comme E08 et comparez. Prédisez d’abord : de combien de points la MAPE augmentera-t-elle?
Afficher la réponse
TREE_FEATURES = [c for c in FEATURES if c not in ("zipcode", "lat", "long")]no_place_cv = cv_mape(boosting())no_place_result = report(valid[TARGET], boosting().fit(train, train[TARGET]).predict(valid))print("CV", no_place_cv, "| valid", no_place_result)log_experiment("E08", "E06 without lat and long", no_place_cv, no_place_result, "reject", "Much worse: the place is a large part of the price.")TREE_FEATURES = [c for c in FEATURES if c != "zipcode"] # back to the E06 featuresLe résultat :
CV (22.13, 0.39) | valid {'median error %': -0.0, 'MAPE %': 23.9, 'within 10%': 0.274, 'over by 10%+': 0.37}Ce que cela nous apprend. Sans l’emplacement, la MAPE de validation croisée double presque, passant de 11,71 % à 22,13 %. Seulement 27 % des maisons de validation sont à 10 % près. C’est pire que la référence par code postal, qui connaît l’emplacement, mais presque rien sur la maison. Pour le prix des maisons, l’emplacement apporte autant d’information que la maison elle-même.
Le graphique résume le journal sur les données de validation.
Description textuelle du graphique
Un graphique à colonnes de huit expériences, avec la MAPE de validation en pourcentage, de 0 à 50. Références de base : E01, prix médian, 45,3; E02, prix par pied carré, 35,4; E03, prix par pied carré dans le code postal, 19,2. Modèles : E04, ridge en dollars, 21,3; E05, ridge sur le logarithme du prix, 14,9; E06, amplification sur le logarithme du prix, 13,2; E07, amplification plus lente, 12,9, la barre la plus basse; E08, amplification sans latitude ni longitude, 23,9.
Ce que cela nous apprend. Deux étapes ont apporté l’essentiel du gain : apprendre le logarithme du prix (E04 à E05) et donner la carte aux arbres (E05 à E06). L’amplification plus lente a ajouté un petit gain. Sans l’emplacement, l’amplification fait moins bien que même la référence par code postal.
Erreur courante
La répartition aléatoire qui mélange les mois
Symptôme : Chris construit le même modèle d’amplification avec une répartition aléatoire de toutes les ventes, à 80 % et 20 %. Sa MAPE est de 11,9 %, meilleure que les 13,2 % de Samir pour le même modèle. Il dit que la répartition chronologique « écarte de bonnes données ».
from sklearn.model_selection import train_test_split
shuffled_train, shuffled_test = train_test_split(houses, test_size=0.2, random_state=0)model = boosting().fit(shuffled_train, shuffled_train[TARGET])print("random split:", report(shuffled_test[TARGET], model.predict(shuffled_test)))twins = shuffled_test["id"].isin(shuffled_train["id"])print("test sales of a house that is also in train:", int(twins.sum()))Le résultat :
random split: {'median error %': 0.5, 'MAPE %': 11.9, 'within 10%': 0.57, 'over by 10%+': 0.221}test sales of a house that is also in train: 53Cause : une répartition aléatoire mélange les mois. Le modèle apprend sur des ventes du printemps et est testé sur d’autres ventes du printemps. Il connaît donc le marché printanier. Le modèle de Nadia ne connaît jamais le marché du mois prochain. La répartition aléatoire place aussi des ventes d’une même maison des deux côtés : 53 d’entre elles.
Correction : répartissez les données selon l’utilisation prévue du modèle. Ici, cela signifie suivre le temps, avec chaque maison dans une seule partie. Acceptez l’erreur plus grande : c’est celle que Nadia verra. Si vous avez besoin de plus de données d’entraînement, réajustez sur les données d’entraînement et de validation avant le test final. C’est ce que fait Samir.
Conséquence dans le travail en IA : un modèle d’évaluation testé sur une répartition aléatoire paraît meilleur qu’il ne l’est. Cela masque aussi l’effet d’un marché qui change. Le premier mois d’utilisation réelle révèle un biais que personne n’attendait.
Métrique et point de fonctionnement
Section intitulée « Métrique et point de fonctionnement »Samir a choisi les mesures de Nadia avant tout modèle : la proportion d’estimations à 10 % près et celle surestimée de 10 % ou plus. Ils décident maintenant dans quels cas l’équipe utilise l’estimation. D’abord, voici le modèle choisi, E07, ajusté sur les données d’entraînement et vérifié sur les données de validation :
chosen = boosting(learning_rate=0.05, max_iter=600).fit(train, train[TARGET])valid_estimate = chosen.predict(valid)print(report(valid[TARGET], valid_estimate))Le résultat :
{'median error %': 2.7, 'MAPE %': 12.9, 'within 10%': 0.556, 'over by 10%+': 0.194}Ce que cela nous apprend. Sur les données de validation, 55,6 % des estimations sont à 10 % près. De plus, 19,4 % sont trop élevées de 10 % ou plus. La cible de Nadia est donc atteinte en moyenne, mais de justesse. Le graphique compare l’estimation au prix réel pour un échantillon fixe de 400 ventes de validation.
Description textuelle du graphique
Un nuage de points de 396 ventes de validation. L’axe horizontal montre le prix de vente réel et l’axe vertical, l’estimation, tous deux de 0 à 2 millions de dollars. Une droite diagonale indique une estimation parfaite. La plupart des points forment une bande autour de la droite, entre 200 000 et 1 million de dollars. La bande s’élargit quand le prix augmente. Sept points sont loin au-dessus de la droite, avec une estimation supérieure à 1,5 fois le prix. Cinq sont loin en dessous. Sur les 396 points, 230 sont à 10 % près du prix réel. Les quatre ventes au-dessus de 2 millions de dollars sont exclues.
Ce que cela nous apprend. Les estimations suivent les prix réels, et la bande autour de la droite s’élargit quand le prix augmente. Une erreur du même pourcentage représente plus de dollars pour une maison chère. Les maisons chères sont aussi les plus difficiles à estimer. Quelques ventes sont loin de la droite. Les plus grandes erreurs présentées plus bas montrent pourquoi.
Pour quelles maisons Nadia peut-elle se fier à l’estimation? Comme dans L’analyse par sous-groupes, nous examinons des groupes sur les données de validation, avec l’effectif à côté de chaque nombre. Le premier sous-groupe repose sur le prix estimé, pas le prix réel. Personne ne connaît le prix réel le jour de l’estimation. Une règle peut seulement utiliser ce qui est connu.
BANDS = [0, 400_000, 700_000, 1_000_000, np.inf]BAND_NAMES = ["under 400k", "400k-700k", "700k-1M", "over 1M"]
def slice_table(part, estimate, by): pct = 100 * (part[TARGET] - estimate) / part[TARGET] errors = part.assign(pct=pct, abs_pct=pct.abs(), within=pct.abs() <= 10) return errors.groupby(by, observed=True).agg(sales=("pct", "size"), median_error=("pct", "median"), MAPE=("abs_pct", "mean"), within_10=("within", "mean")).round({"median_error": 1, "MAPE": 1, "within_10": 3})
estimate_band = pd.cut(valid_estimate, BANDS, labels=BAND_NAMES)print(slice_table(valid, valid_estimate, estimate_band).to_string())Le résultat :
sales median_error MAPE within_10under 400k 1015 3.0 13.8 0.519400k-700k 813 2.7 11.8 0.599700k-1M 239 -0.1 10.9 0.653over 1M 124 7.6 17.1 0.395Description textuelle du graphique
Un graphique à colonnes de quatre tranches de prix estimé. Il montre la proportion d’estimations à 10 % près du prix de vente, de 0 à 100 %. Moins de 400 000 dollars : 51,9 %, pour 1 015 ventes. De 400 000 à 700 000 : 59,9 %, pour 813 ventes. De 700 000 à 1 million : 65,3 %, pour 239 ventes. Plus de 1 million : 39,5 %, pour 124 ventes, la barre la plus basse.
Ce que cela nous apprend. Les estimations au-dessus de 1 million de dollars sont bien moins bonnes : seulement 39,5 % à 10 % près. Leur MAPE est de 17,1 %. Les maisons chères sont rares (124 sur 2 191), et chacune est différente : une vue, un grand jardin, un architecte. La tranche la moins chère est aussi moins fiable que celles du milieu. Les tranches du milieu répondent le mieux à la cible de Nadia.
Dans quels cas utiliser l’estimation
Section intitulée « Dans quels cas utiliser l’estimation »Nadia n’a pas besoin que l’estimation fonctionne pour chaque maison. Elle doit savoir dans quels cas elle fonctionne. Le prix estimé est connu au moment de la demande, donc une règle peut l’utiliser. Sous une limite, l’équipe utilise l’estimation comme premier montant. Au-dessus, un évaluateur commence immédiatement. Samir essaie une limite de 1 million de dollars sur les données de validation :
auto = valid_estimate < 1_000_000print("share of valid sales with an estimate under 1M:", round(float(auto.mean()), 3))print("those sales:", report(valid[TARGET][auto], valid_estimate[auto]))print("the others: ", report(valid[TARGET][~auto], valid_estimate[~auto]))Le résultat :
share of valid sales with an estimate under 1M: 0.943those sales: {'median error %': 2.6, 'MAPE %': 12.7, 'within 10%': 0.566, 'over by 10%+': 0.193}the others: {'median error %': 7.6, 'MAPE %': 17.1, 'within 10%': 0.395, 'over by 10%+': 0.202}Ce que cela nous apprend. Pour 94,3 % des demandes, l’estimation est sous 1 million. Parmi celles-ci, 56,6 % des estimations sont à 10 % près, et 19,3 % sont trop élevées de 10 % ou plus. Les deux mesures répondent à la cible de Nadia, mais de justesse. Les 5,7 % restants vont directement à un évaluateur. Cette limite est le point de fonctionnement de ce problème. Comme un seuil dans Seuils et calibration, Samir la choisit sur les données de validation selon le coût de chaque erreur.
Dans le carnet Jupyter, vous essayez une limite plus basse de 700 000 dollars. Prédisez d’abord : la proportion à 10 % près augmentera-t-elle beaucoup?
Afficher la réponse
under_700k = valid_estimate < 700_000policy_700k = report(valid[TARGET][under_700k], valid_estimate[under_700k])print(round(float(under_700k.mean()), 3), policy_700k)Le résultat :
0.834 {'median error %': 2.9, 'MAPE %': 12.9, 'within 10%': 0.555, 'over by 10%+': 0.191}Ce que cela nous apprend. Avec une limite de 700 000, seulement 83,4 % des demandes obtiennent une estimation. La proportion à 10 % près reste autour de 56 %. La limite plus basse envoie davantage de maisons à l’évaluateur, sans gain. La tranche de 700 000 à 1 million est l’une des meilleures. Samir garde la limite de 1 million.
Erreurs et sous-groupes
Section intitulée « Erreurs et sous-groupes »Les tranches de prix ont servi à choisir le point de fonctionnement. Cherchez maintenant d’autres cas où le modèle échoue, sur les données de validation, avec la même fonction slice_table(). Dans le carnet Jupyter, vous faites le même tableau par secteur. Prédisez d’abord : quel secteur est le plus difficile?
Afficher la réponse
area_slices = slice_table(valid, valid_estimate, valid["area"])print(area_slices.to_string())Le résultat :
sales median_error MAPE within_10areaNorth and east 688 2.3 11.3 0.622Seattle 873 3.2 15.0 0.474South 630 2.4 12.0 0.598Ce que cela nous apprend. Seattle est le secteur le plus difficile : 47,4 % à 10 % près, contre environ 60 % ailleurs. Sa MAPE est de 15,0 %. Ses maisons sont plus anciennes : l’année de construction médiane est 1951, contre 1984 dans les deux autres secteurs. Elles sont aussi plus petites : la superficie médiane est de 1 640 pieds carrés. La valeur d’une vieille maison dépend de son état intérieur, que les données ne montrent pas. C’est une raison probable, pas une raison prouvée. Le biais est semblable dans les trois secteurs : les estimations sont trop basses de 2 % à 3 %. Sur ces données, le modèle ne sous-évalue donc pas un secteur davantage que les autres. C’est une première vérification partielle de l’équité. Les données n’ont aucune information sur les personnes, et des secteurs aussi grands peuvent masquer des différences entre quartiers.
Les plus grandes erreurs montrent ce que le modèle ne peut pas voir :
check = valid.assign(estimate=valid_estimate.round(-3), error_pct=(100 * (valid[TARGET] - valid_estimate) / valid[TARGET]).round(0))columns = ["date", "price", "estimate", "error_pct", "sqft_living", "grade", "condition", "yr_built", "area"]print(check.sort_values("error_pct")[columns].head(3).to_string(index=False))print(check.sort_values("error_pct")[columns].tail(3).to_string(index=False))Le résultat :
date price estimate error_pct sqft_living grade condition yr_built area2015-02-09 130000.0 416000.0 -220.0 1110 7 4 1960 North and east2015-02-19 900000.0 2556000.0 -184.0 7120 12 3 2007 North and east2015-02-18 169317.0 479000.0 -183.0 790 7 3 1908 Seattle date price estimate error_pct sqft_living grade condition yr_built area2015-02-04 550000.0 252000.0 54.0 1270 5 4 1908 South2015-02-16 555750.0 247000.0 56.0 1060 6 2 1913 Seattle2015-01-30 325000.0 135000.0 58.0 410 4 2 1953 SeattleCe que cela nous apprend. La plus grande surestimation concerne une maison vendue 130 000 dollars, alors que le modèle donne 416 000. Rien dans sa description ne l’explique. Ce n’est probablement pas une vente normale sur le marché, par exemple une vente familiale ou une vente forcée. Les données ne signalent pas ces ventes. Les plus grandes sous-estimations concernent de vieilles petites maisons vendues plus du double de l’estimation, probablement pour leur terrain. Un évaluateur reconnaît immédiatement les deux cas; le modèle ne le peut pas.
Le test final, une seule fois
Section intitulée « Le test final, une seule fois »Samir écrit le plan avant de l’exécuter. Il réentraîne E07 sur les données d’entraînement et de validation réunies, puis estime les prix de vente du test une seule fois. Il rapporte ensuite le résultat, quel qu’il soit.
train_and_valid = pd.concat([train, valid])final = boosting(learning_rate=0.05, max_iter=600).fit(train_and_valid, train_and_valid[TARGET])test_estimate = final.predict(test)final_result = report(test[TARGET], test_estimate)print("final model on test:", final_result)La sortie :
final model on test: {'median error %': 6.4, 'MAPE %': 12.8, 'within 10%': 0.502, 'over by 10%+': 0.126}Et la méthode de l’évaluateur, la référence de base par code postal, entraînée de la même façon :
zip_per_sqft = (train_and_valid[TARGET] / train_and_valid["sqft_living"]).groupby(train_and_valid["zipcode"]).median()print("zip price per sq ft:", report(test[TARGET], test["zipcode"].map(zip_per_sqft) * test["sqft_living"]))La sortie :
zip price per sq ft: {'median error %': 5.8, 'MAPE %': 18.5, 'within 10%': 0.349, 'over by 10%+': 0.233}Ce que cela nous apprend. Sur les mois de test, la MAPE est de 12,8 %, presque comme sur les données de validation. La moitié des estimations (50,2 %) se situent à moins de 10 % du prix réel. Seulement 12,6 % le dépassent de 10 % ou plus. Le modèle bat la méthode de l’évaluateur sur tous les chiffres. Mais regardez l’erreur médiane : +6,4 %. Les estimations sont maintenant trop basses pour la maison typique.
Les sous-groupes des mois de test servent à produire un rapport, pas à modifier le modèle :
auto = test_estimate < 1_000_000print("estimate under 1M:", round(float(auto.mean()), 3), report(test[TARGET][auto], test_estimate[auto]))print(slice_table(test, test_estimate, test["area"]).to_string())print(slice_table(test, test_estimate, test["date"].dt.month).to_string())La sortie :
estimate under 1M: 0.942 {'median error %': 6.3, 'MAPE %': 12.7, 'within 10%': 0.509, 'over by 10%+': 0.122} sales median_error MAPE within_10areaNorth and east 1520 5.6 11.3 0.561Seattle 1936 8.3 15.1 0.418South 1198 4.8 11.2 0.563 sales median_error MAPE within_10date3 1835 5.1 12.4 0.5344 2189 7.0 12.9 0.4925 630 8.3 13.9 0.448Ce que cela nous apprend. Le biais passe de +5,1 % en mars à +8,3 % en mai. Les prix ont augmenté au printemps, après la dernière vente vue par le modèle, et celui-ci ne peut pas le savoir. Sous la limite de 1 million, 50,9 % des estimations se situent à moins de 10 % du prix réel. La cible est atteinte, mais de justesse. Seattle reste la zone la plus difficile, avec 41,8 % à moins de 10 %. Le modèle ne se trompe pas sur les maisons; il a du retard sur le marché.
Que signifie une erreur de 12,8 % pour Nadia? La maison médiane du test s’est vendue 465 000 dollars. Un écart typique représente donc environ 60 000 dollars. C’est trop pour fixer le montant d’un prêt, d’où le maintien de l’évaluateur agréé. Mais le modèle remplit assez bien son rôle. Dès le premier jour, il repère les demandes où le prix convenu dépasse largement l’estimation.
Enfin, enregistrez le modèle et examinez le journal complet :
import joblib
joblib.dump(final, "house_price_model.joblib")show_log()La sortie :
id change cv_mape cv_std valid_mape valid_within_10 decisionE01 Baseline: the median price of train NaN NaN 45.3 0.154 baselineE02 Baseline: median price per sq ft x living area NaN NaN 35.4 0.188 baselineE03 Baseline: median price per sq ft in the zip code x living area NaN NaN 19.2 0.372 baselineE04 Ridge, zip code one-hot, scaled numbers 19.53 0.37 21.3 0.337 rejectE05 E04 on the log of the price 13.55 0.05 14.9 0.486 keepE06 Gradient boosting on the log price, lat and long, defaults 11.71 0.19 13.2 0.554 rejectE07 E06 with learning_rate 0.05 and 600 trees 11.57 0.24 12.9 0.556 keepE08 E06 without lat and long 22.13 0.39 23.9 0.274 rejectCe que cela nous apprend. Le journal contient huit lignes et la raison de chaque décision. Avec le modèle enregistré, il permet à un autre analyste de reproduire le travail ou de refaire l’entraînement le mois suivant.
Recommandations
Section intitulée « Recommandations »Samir écrit une courte note pour Nadia, avec ses mots à elle :
- Utilisez l’estimation comme premier chiffre pour les maisons estimées à moins de 1 million de dollars (environ 94 % des demandes). Sur les derniers mois, la moitié des estimations se situaient à moins de 10 % du prix de vente. Environ 1 sur 8 dépassait le prix de plus de 10 %.
- Envoyez directement à un évaluateur agréé les maisons estimées à plus de 1 million. Pour elles, l’estimation se situe à moins de 10 % du prix réel seulement 4 fois sur 10.
- Quand le prix convenu dépasse l’estimation de plus de 10 %, demandez d’abord l’avis de l’évaluateur agréé. C’est pour ce cas que l’estimation est conçue. Mesurez la fréquence à laquelle l’évaluation confirme ce résultat, puis revoyez la règle après trois mois.
- Attendez-vous à ce que l’estimation suive le marché avec du retard. Au printemps 2015, elle était environ 6 % trop basse. Réentraînez le modèle chaque mois avec les dernières ventes et suivez l’erreur médiane chaque mois.
- Ne fixez jamais le montant d’un prêt à partir de l’estimation. L’évaluation professionnelle reste la valeur retenue.
Et voici à quoi ne pas l’utiliser :
- toute décision concernant le demandeur, comme approuver ou refuser un prêt;
- les maisons hors de King County ou les prix d’aujourd’hui : les données datent de 2014 et de 2015;
- les propriétés inhabituelles, comme les très grands terrains ou les maisons au bord de l’eau de plus de 1 million;
- tout usage où le lieu pourrait représenter les personnes qui y vivent, sans examen par l’équipe de conformité du prêteur.
Limites et prochaines étapes
Section intitulée « Limites et prochaines étapes »- Le marché évolue. Le modèle estimait les prix 6 % trop bas au printemps. Une prochaine étape consiste à ajouter un indice récent des prix comme entrée. Une autre consiste à ajuster les estimations chaque mois selon la variation du prix médian.
- Le modèle ne voit pas l’intérieur. L’état et la cote de qualité sont approximatifs. Les rénovations postérieures aux dossiers du comté, l’humidité ou une nouvelle cuisine sont invisibles. Les ventes répétées le montrent : +54 % après une rénovation absente des données.
- Les ventes hors marché ne sont pas indiquées. Les ventes entre membres d’une famille ou les ventes forcées ressemblent aux ventes normales. Elles comptent parmi les pires erreurs.
- L’équité est vérifiée seulement par zone. Les erreurs sont semblables dans les trois zones, mais les données ne contiennent aucune information sur les personnes. De grandes zones peuvent aussi masquer des différences entre quartiers. Un vrai prêteur doit vérifier les biais d’évaluation avec de meilleures données. Il doit aussi examiner l’utilisation du lieu avec son équipe de conformité.
- Un comté, une année. Rien ici ne montre que le modèle fonctionne ailleurs ou à une autre période.
La fiche du modèle, comme dans Documenter les limites d’utilisation, en bref :
| Section | Contenu |
|---|---|
| Utilisation prévue | Une première estimation du prix de vente le jour d’une demande de prêt hypothécaire. Elle fixe l’ordre des évaluations professionnelles et signale les prix qui dépassent largement l’estimation. |
| Utilisateurs | L’équipe d’évaluation et les agents de prêt. |
| Utilisations exclues | Fixer les montants des prêts, approuver ou refuser un prêt, maisons estimées à plus de 1 million de dollars, autres comtés, prix d’aujourd’hui. |
| Données | Ventes de maisons de King County, de mai 2014 à mai 2015, CC0. Entraînement : 2014; validation : janvier et février 2015; test : mars à mai 2015. Chaque maison figure dans une seule partie. Une erreur corrigée (33 chambres → 3). |
| Modèle | Amplification par gradient sur le logarithme du prix, taux d’apprentissage de 0,05, jusqu’à 600 arbres (l’arrêt anticipé en a retenu 332), 17 entrées avec latitude et longitude. |
| Évaluation | MAPE en validation croisée : 11,57 % (écart-type : 0,24). Validation : MAPE de 12,9 %, 55,6 % à moins de 10 %. Test (une seule fois) : MAPE de 12,8 %, 50,2 % à moins de 10 %, 12,6 % trop hautes de 10 % ou plus, erreur médiane de +6,4 %. Référence de base par code postal sur le test : MAPE de 18,5 %. |
| Sous-groupes | Au-dessus de 1 million : 39,5 % à moins de 10 % sur la validation. Seattle : 47,4 % sur la validation, 41,8 % sur le test. Biais semblable entre les zones. |
| Point de fonctionnement | Les estimations sous 1 million de dollars sont utilisées (94,3 % des demandes de validation); au-dessus, recours immédiat à un évaluateur agréé. |
| Limites | Retard sur le marché; intérieur de la maison invisible; ventes hors marché; équité vérifiée seulement par zone. |
| Responsable et révision | Samir Patel; réentraînement mensuel et vérification mensuelle de l’erreur médiane et de la proportion à moins de 10 %. Cesser d’utiliser l’estimation si moins de 45 % des estimations du dernier mois sont à moins de 10 %. |
Faites-en un projet pour votre portfolio
Section intitulée « Faites-en un projet pour votre portfolio »Cette étude de cas montre à un employeur que vous savez traiter une cible asymétrique, une séparation par groupe et un marché changeant. Elle montre aussi que vous savez ce qu’un modèle ne doit pas décider. Personnalisez-la en apportant un changement. Par exemple, utilisez les données ouvertes de ventes d’une autre ville, ajoutez un indice mensuel des prix ou vérifiez les erreurs par code postal. Publiez ensuite un petit dépôt.
Quoi mettre dans le dépôt :
house-price-estimate/ README.md problem, data and licence, approach, results, limits MODEL_CARD.md the table above, in your words notebook.ipynb the notebook, run from top to bottom, with outputs experiment_log.csv every attempt, kept and rejected requirements.txt pandas==3.0.6, scikit-learn==1.9.1, matplotlibLe README est la page que la plupart des gens lisent. Un modèle de README :
# A first estimate of house prices for a mortgage lender
## ProblemA lender wants a first price estimate on the day of an application, before the appraisal.Target: within 10% of the sale price for most houses; few estimates more than 10% too high.
## Data and licenceKing County house sales, May 2014 - May 2015 (OpenML 42092), CC0. 21,613 sales.Fixed one data error (33 bedrooms -> 3). Repeat sales of a house kept in one part.
## ApproachSplit by time, grouped by house. Three valuer-style baselines. Ridge and gradient boostingon the log of the price, compared with grouped 5-fold CV and the one-standard-error rule. 8 experiments, logged.
## Results (test set, used once)Gradient boosting: MAPE 12.8%, 50% within 10%, 13% over by 10% or more.Zip-code price per sq ft (baseline): MAPE 18.5%, 35% within 10%.The model was 6% too low in spring 2015: it lags a rising market.
## LimitsCannot see inside the house; non-market sales not marked; one county, one year;fairness checked only by area. Not for loan decisions.Comment le décrire en entrevue, en deux phrases : « J’ai créé une première estimation du prix des maisons pour un prêteur hypothécaire, testée chronologiquement avec chaque maison dans une seule partie : la moitié des estimations étaient à moins de 10 %, contre un tiers pour la méthode rapide d’un évaluateur. Le test a montré un retard de 6 % sur un marché en hausse, donc j’ai recommandé un réentraînement mensuel et une limite de prix au-dessus de laquelle un évaluateur agréé décide. »
Allez plus loin :
- Ajoutez un indice mensuel des prix comme entrée : le prix médian par pied carré des 30 derniers jours. Vérifiez si le biais du printemps diminue.
- Estimez une plage plutôt qu’un seul chiffre : deux modèles avec
loss="quantile"à 0,1 et 0,9. Vérifiez à quelle fréquence le prix de vente se situe dans cette plage. - Vérifiez les erreurs par code postal, avec le nombre de ventes à côté de chacun. Cherchez les codes postaux où les estimations sont trop basses de façon répétée.
- Expliquez une estimation avec l’importance par permutation et les maisons les plus proches sur la carte, comme dans Expliquer les prédictions.
- Reprenez l’étude avec un autre jeu de données public de ventes et comparez ce qui se transfère ou non.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
La MAPE du modèle ridge sur la validation est passée de 21,3 % à 14,9 % en apprenant le logarithme du prix plutôt que le prix. Les entrées n'ont pas changé. Expliquez pourquoi.
Afficher la réponse
Sur l’échelle en dollars, le modèle minimise les erreurs au carré en dollars. Les maisons chères dominent donc. Un écart de 200 000 sur une maison de 2 millions de dollars compte beaucoup plus qu’un écart de 50 000 sur une maison de 250 000 dollars. Pourtant, en pourcentage, le deuxième écart est plus grand. Les effets de la taille, de la qualité et du lieu sur le prix se multiplient aussi plutôt que de s’additionner. Sur l’échelle logarithmique, un écart de 10 % a la même taille pour chaque maison. Les effets multiplicatifs deviennent des additions, qu’un modèle linéaire peut apprendre. Le modèle correspond donc maintenant à ce que mesure la MAPE.
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
Chris veut utiliser la tranche du prix de vente réel (moins de 400 000, plus de 1 million, etc.) pour choisir les demandes qui reçoivent l'estimation automatique. Les sous-groupes par prix réel semblent plus clairs. Quel est le problème, et qu'utilisez-vous à la place?
Afficher la réponse
Le prix de vente réel est la cible. Il est inconnu le jour de la demande, donc une règle qui l’utilise ne peut pas fonctionner. Utilisez seulement ce qui est connu au moment de la prédiction : le prix estimé. C’est pourquoi les sous-groupes et la limite de 1 million sur cette page utilisent le prix estimé. Un sous-groupe par prix réel convient pour comprendre les erreurs après coup, mais pas pour établir une règle.
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
Sur les mois de test, la MAPE est restée autour de 13 %, mais l'erreur médiane est passée de +2,7 % sur la validation à +6,4 %. Que s'est-il passé, et que feriez-vous avant le mois suivant?
Afficher la réponse
Le modèle a appris les prix jusqu’à février 2015, et les prix ont augmenté au printemps. Il estime donc maintenant trop bas pour la maison typique : il a du retard sur le marché. La taille des erreurs a peu changé, mais leur direction a changé. Avant le mois suivant, réentraînez le modèle avec toutes les ventes jusqu’au dernier mois disponible. Suivez l’erreur médiane chaque mois. Envisagez une entrée qui suit le marché, comme le prix médian récent par pied carré. Ne réglez pas le modèle sur les mois de test; utilisez les nouveaux mois à mesure qu’ils arrivent.
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 cible asymétrique avec des coûts en pourcentage exige des métriques en pourcentage : MAPE, proportion à moins de 10 % et proportion trop haute. Un modèle qui apprend le logarithme du prix leur correspond : la MAPE du modèle ridge est passée de 21,3 % à 14,9 %.
- Les données demandaient du soin avant tout modèle : une faute de saisie indiquant 33 chambres, des codes ressemblant à des nombres et 177 ventes répétées à garder dans une seule partie.
- La méthode rapide d’un évaluateur, le prix par pied carré par code postal, était la référence de base à battre (37 % à moins de 10 % sur la validation). L’amplification avec latitude et longitude a atteint 55,6 %; sans le lieu, l’erreur a presque doublé.
- La règle d’une erreur-type a retenu l’amplification plus lente, avec une marge faible mais réelle. Le prix estimé fixait le point de fonctionnement : utiliser l’estimation sous 1 million de dollars, envoyer le reste à un évaluateur agréé.
- Le test, utilisé une seule fois, a donné une MAPE de 12,8 % et 50 % à moins de 10 %, mais un biais de +6,4 %. Le modèle suit un marché en hausse avec du retard. Il faut donc le réentraîner et le vérifier chaque mois.
Ensuite, la première étude de cas de classification : l’équipe de risque de crédit d’une banque, une matrice de coûts et un seuil choisi selon le coût attendu.
Leçon suivante : Prédire le défaut de paiement par carte de crédit