Сквозной проект — цены на жильё в Мельбурне
Практический маршрут для первой регрессионной модели на табличных данных: датасет Melbourne Housing (объявления о продаже жилья в Мельбурне, ~35 тыс. строк, 21 столбец), очистка в pandas, градиентный бустинг из scikit-learn, метрика MAE и настройка через GridSearchCV. Код можно запустить локально или в Kaggle Notebook — Jupyter не обязателен.
Датасет
| Источник | Melbourne Housing Market на Kaggle (зеркала встречаются под именем Melbourne_housing_FULL.csv) |
| Целевая переменная | Price — цена в австралийских долларах |
| Признаки (после базовой очистки) | Suburb, Rooms, Type, Distance, Bedroom2, Bathroom, Car, Landsize, BuildingArea, YearBuilt, CouncilArea |
В исходном файле опечатки в названиях столбцов (Lattitude, Longtitude) — их можно исправить в CSV или просто не использовать эти поля.
Шесть шагов pipeline
1–2. Импорт и загрузка
import pandas as pd
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn import ensemble
from sklearn.metrics import mean_absolute_error
df = pd.read_csv("Melbourne_housing_FULL.csv")
print(df.shape)
print(df.head())
Путь к файлу подставьте свой. Удобно держать CSV рядом с notebook или скриптом.
3. Очистка
Удаление лишних столбцов
Столбцы с адресом, датой продажи, координатами и служебными полями для базовой модели не нужны — пригород (Suburb) и район (CouncilArea) уже несут географию.
cols_to_drop = [
"Address", "Method", "SellerG", "Date", "Postcode",
"Lattitude", "Longtitude", "Regionname", "Propertycount",
]
df = df.drop(columns=[c for c in cols_to_drop if c in df.columns])
Удаление нерелевантных столбцов ускоряет обучение и снижает шум. Позже можно вернуть, например, SellerG (агентство) и проверить, улучшилась ли MAE на validation.
Пропуски
Для первого прохода — простой вариант: удалить строки с любыми NaN.
df = df.dropna(axis=0, how="any")
На проде чаще imputation (медиана, SimpleImputer) или отдельная обработка по столбцам — см. Кодирование категориальных признаков и Data Science.
Кодирование категорий
Категориальные Suburb, CouncilArea, Type переводим в числа one-hot через pd.get_dummies (подробнее — 4.md):
df = pd.get_dummies(df, columns=["Suburb", "CouncilArea", "Type"])
Подробнее о выборе кодировщика — 4.md.
X и y
X = df.drop("Price", axis=1)
y = df["Price"]
4. Разбиение 70 / 30
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, shuffle=True
)
random_state фиксирует воспроизводимость. Для финальной оценки не подбирайте гиперпараметры по test — только по train (или отдельному validation). Test — для финальной проверки; для перебора параметров нужна validation-выборка или k-fold на train.
5. Модель — градиентный бустинг
Стартовая конфигурация (агрессивная глубина → риск переобучения):
model = ensemble.GradientBoostingRegressor(
n_estimators=150,
learning_rate=0.1,
max_depth=30,
min_samples_split=4,
min_samples_leaf=6,
max_features=0.6,
loss="huber",
random_state=42,
)
model.fit(X_train, y_train)
| Гиперпараметр | Роль |
|---|---|
n_estimators | Число деревьев в ансамбле |
learning_rate | Вклад каждого следующего дерева |
max_depth | Глубина деревьев — главный рычаг bias/variance |
max_features | Доля признаков на split (как в random forest) |
loss='huber' | Устойчивость к выбросам в ценах |
6. Оценка — MAE
Mean Absolute Error — средняя абсолютная ошибка в тех же единицах, что и Price (AUD):
mae_train = mean_absolute_error(y_train, model.predict(X_train))
mae_test = mean_absolute_error(y_test, model.predict(X_test))
print(f"MAE train: {mae_train:,.0f}")
print(f"MAE test: {mae_test:,.0f}")
При max_depth=30 MAE на train получается намного ниже, чем на test — типичный сигнал переобучения: модель запомнила train, на новых домах ошибается сильнее.
Контекст: при ценах до миллионов AUD ошибка ~28 тыс. на train может быть приемлемой; разрыв train/test важнее абсолютного числа.
Оптимизация гиперпараметров
Смягчение переобучения вручную:
model_tuned = ensemble.GradientBoostingRegressor(
n_estimators=250,
learning_rate=0.1,
max_depth=5,
min_samples_split=4,
min_samples_leaf=6,
max_features=0.6,
loss="huber",
random_state=42,
)
model_tuned.fit(X_train, y_train)
max_depth с 30 до 5 — train MAE растёт, test часто улучшается, разрыв сужается. Меняйте по одному гиперпараметру и смотрите на обе выборки.
GridSearchCV
Автоматический перебор (может занять десятки минут на полном датасете):
base = ensemble.GradientBoostingRegressor(random_state=42)
param_grid = {
"n_estimators": [200, 300],
"max_depth": [4, 6],
"min_samples_split": [3, 4],
"min_samples_leaf": [5, 6],
"learning_rate": [0.01, 0.1],
"max_features": [0.8, 0.9],
"loss": ["huber", "squared_error"],
}
grid = GridSearchCV(base, param_grid, n_jobs=-1, cv=3, scoring="neg_mean_absolute_error")
grid.fit(X_train, y_train)
print(grid.best_params_)
best = grid.best_estimator_
print("MAE test:", mean_absolute_error(y_test, best.predict(X_test)))
cv=3 — кросс-валидация внутри train; test трогаем один раз в конце. Альтернатива при большой сетке — RandomizedSearchCV.
Не подбирайте столбцы, imputation и гиперпараметры, «подглядывая» в test. Весь GridSearchCV — только на train (или train+val). Иначе MAE на test будет оптимистичной.
Production-вариант — Pipeline
Чтобы кодирование и модель всегда применялись одинаково:
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
from sklearn.impute import SimpleImputer
num_cols = ["Rooms", "Distance", "Bedroom2", "Bathroom", "Car",
"Landsize", "BuildingArea", "YearBuilt"]
cat_cols = ["Suburb", "CouncilArea", "Type"]
preprocess = ColumnTransformer([
("num", SimpleImputer(strategy="median"), num_cols),
("cat", Pipeline([
("imputer", SimpleImputer(strategy="constant", fill_value="missing")),
("onehot", OneHotEncoder(handle_unknown="ignore")),
]), cat_cols),
])
pipe = Pipeline([
("prep", preprocess),
("model", ensemble.GradientBoostingRegressor(random_state=42)),
])
pipe.fit(df[num_cols + cat_cols], df["Price"])
Такой каркас ближе к тому, что описано в Машинное обучение — предобработка.
Что дальше
- Сравнить с RandomForestRegressor или LinearRegression — baseline за 5 минут.
- Добавить validation-слой и early stopping для бустинга.
- Перейти к классификации — Titanic на Kaggle.
- Углубить метрики регрессии (RMSE, R²) — 1.md.
Связанные материалы
См. также
Другие статьи этого же раздела в боковом меню (как на странице "О разделе"). Машинное обучение - идея обучения моделей на данных вместо полного ручного задания правил поведения системы. > Справочник по алгоритмам (~40 разделов). Базовые понятия ML, метрики и практика — в статье Машинное обучение. Старт ML на Python — Kaggle Learn, маршрут по разделу, Titanic и Melbourne Housing без тяжёлой математики. Transfer learning, fine-tuning, multitask и federated learning — чем отличаются подходы, куда идут градиенты и когда что выбирать. Семь базовых техник преобразования категорий (цвет, размер, тип) в числа для моделей ML — one-hot, dummy, effect, label, ordinal, count и binary encoding. Четыре категории ML — supervised, unsupervised, semi-supervised и reinforcement; Q-обучение; три «отделения» инструментария — данные, инфраструктура, алгоритмы. Train, validation и test — пропорции 70/30, shuffle, stratify, k-fold, утечка данных и когда нужна validation-выборка. Bias–variance tradeoff, недо- и переобучение, гиперпараметры как «ручки» модели и связь с MAE на train/test. Как строится дерево — энтропия, information gain, переобучение, бэггинг, random forest и градиентный бустинг для новичков. Итоги раздела Машинное обучение — вопросы для самопроверки в энциклопедии Вселенная IT.Машинное обучение
Алгоритмы ИИ
Как начать с машинного обучения на Python без глубоких знаний математики
Обучение на базе готовой модели
Кодирование категориальных признаков
Категории обучения и стек инструментов
Разбиение данных и кросс-валидация
Смещение, дисперсия и переобучение
Деревья решений с нуля
Чек-лист самопроверки