Новая Школа - онлайн-школа подготовки к ЕГЭ
Задача Задача №3646913
А
Аноним
10 сентября 2026 13:01 · публичный вопрос
А
Аноним

Представьте, что вы выставляете свой автомобиль на продажу на Авито. Вы загружаете фотографии, но не знаете, какую цену поставить, чтобы машина быстро нашла покупателя и при этом…

Запрос пользователя

Представьте, что вы выставляете свой автомобиль на продажу на Авито. Вы загружаете фотографии, но не знаете, какую цену поставить, чтобы машина быстро нашла покупателя и при этом не продешевить. В этой задаче вам предстоит помочь в решении реальной индустриальной проблемы - автоматического прайсинга автомобилей. Такой сервис помогает продавцам корректно оценивать стоимость машины, ускоряя продажу и делая рынок б/у авто более прозрачным. Перед вами фотографии автомобилей с объявлений Авито за 2024 год - максимум 4 изображений на каждый объект, в основном внешний вид. Тренировочная выборка: 70 000 объектов и 273 873 фотографии. Тест: 25 000 автомобилей и 98 075 фотографий. Задание: Постройте модель, которая для каждого автомобиля из тестовой выборки предскажет его стоимость. Данные: train_dataset.parquet - обучающая выборка, целевая переменная: price_TARGET. test_dataset.parquet - тестовая выборка. sample_submission.csv - шаблон отправки (ID,target). по этой ссылке доступны файлы train_images.zip и test_images.zip - это фотографии автомобилей, например, 100000_0.jpg, где 100000 - это ID объявления. Разделение выполнено по времени: train - это объявления с января по ноябрь 2024 года, а test - объявления за декабрь 2024 года. Помимо фотографий автомобилей вам доступны и некоторые атрибуты объектов: body_type - тип кузова drive_type - тип привода engine_type - тип двигателя doors_number - количество дверей color - цвет pts - вид ПТС steering_wheel - руль crashes_count - число ДТП owners_count - число владельцев mileage - пробег latitude, longitude - координаты локации продажи equipment - комплектация Одновыборные опции (строка с одним значением): audiosistema, diski, electropodemniki, fary, salon, upravlenie_klimatom, usilitel_rul Мультивыбор-поля (список опций; отсутствие - [None]): aktivnaya_bezopasnost_mult, audiosistema_mult, shini_i_diski_mult, electroprivod_mult, fary_mult, multimedia_navigacia_mult, obogrev_mult, pamyat_nastroek_mult, podushki_bezopasnosti_mult, pomosh_pri_vozhdenii_mult, protivoygonnaya_sistema_mult, salon_mult, upravlenie_klimatom_mult. Метрика: В качестве метрики качества используется medianAPE (Median Absolute Percentage Error): m e d i a n A P E = median i ( | ^ y i − y i | y i ) где y i - правильный ответ, ^ y i - предсказание модели

Ответ нейросети

Краткий ответ: постройте ансамбль из CatBoost по табличным признакам и модели по эмбеддингам фотографий. Обучать лучше на \(\log(1+\text{price})\), а проверять качество именно через medianAPE. Разбиение для проверки должно быть временным: последние объявления из train имитируют декабрьский test.

1. Метрика и правильная проверка

import numpy as np

def median_ape(y_true, y_pred):
    y_true = np.asarray(y_true, dtype=float)
    y_pred = np.maximum(np.asarray(y_pred, dtype=float), 1e-6)
    return np.median(np.abs(y_pred - y_true) / y_true)

Например, значение 0.18 означает 18%: у половины объектов относительная ошибка не больше 18%.

Случайное разбиение может дать слишком оптимистичный результат, потому что тест содержит объявления за декабрь 2024 года. Лучше:

  • train: январь—октябрь;
  • validation: ноябрь;
  • финальное обучение: весь train, январь—ноябрь.

Если даты в parquet отсутствуют, случайное разбиение можно использовать только для предварительной проверки.

2. Обработка табличных данных

Категориальные признаки

Передайте в CatBoost как категории:

  • body_type;
  • drive_type;
  • engine_type;
  • color;
  • pts;
  • steering_wheel;
  • одновыборные опции;
  • equipment.

Пропуски замените на отдельное значение, например "__NA__".

Мультивыборные признаки

Поле

["подогрев сидений", "климат-контроль"]

лучше преобразовать в несколько признаков:

obogrev_mult__подогрев сидений = 1
upravlenie_klimatom_mult__климат-контроль = 1

Дополнительно создайте число выбранных опций:

obogrev_mult__count = 2

Для [None] число опций равно нулю, а все бинарные признаки равны нулю.

Числовые признаки

Полезно добавить:

for df in (X, X_test):
    if "mileage" in df.columns:
        mileage = pd.to_numeric(df["mileage"], errors="coerce")
        df["mileage_log"] = np.log1p(mileage.clip(lower=0))
        df["mileage_missing"] = mileage.isna().astype("int8")

    if "latitude" in df.columns:
        df["latitude_round"] = df["latitude"].round(2).astype("string")

    if "longitude" in df.columns:
        df["longitude_round"] = df["longitude"].round(2).astype("string")

mileage_log помогает модели лучше работать с большим разбросом пробегов. Округлённые координаты позволяют учитывать регион продажи как категориальный признак.

3. Базовая модель CatBoost

import pandas as pd
import numpy as np
from catboost import CatBoostRegressor

train = pd.read_parquet("train_dataset.parquet")
test = pd.read_parquet("test_dataset.parquet")

TARGET = "price_TARGET"

# При необходимости укажите фактическое имя столбца ID
ID_COL = next(
    c for c in train.columns
    if c.lower() in {"id", "item_id", "ad_id"}
)

y = train[TARGET].astype(float).to_numpy()

X = train.drop(columns=[TARGET, ID_COL]).copy()
X_test = test.drop(columns=[ID_COL]).copy()

# После обработки мультивыборных полей
X_test = X_test.reindex(columns=X.columns)

cat_cols = X.select_dtypes(
    include=["object", "category"]
).columns.tolist()

for c in cat_cols:
    X[c] = X[c].fillna("__NA__").astype(str)
    X_test[c] = X_test[c].fillna("__NA__").astype(str)

Разделение i_train и i_valid должно быть временным. Например, если строки уже упорядочены по дате:

cut = int(len(X) * 0.8)

i_train = np.arange(0, cut)
i_valid = np.arange(cut, len(X))

Если строки не упорядочены, нужно сначала отсортировать их по дате объявления.

Обучение:

y_log = np.log1p(y)

model = CatBoostRegressor(
    iterations=3000,
    depth=8,
    learning_rate=0.03,
    l2_leaf_reg=10,
    loss_function="MAE",
    eval_metric="MAE",
    random_seed=42,
    verbose=300
)

model.fit(
    X.iloc[i_train],
    y_log[i_train],
    cat_features=cat_cols,
    eval_set=(X.iloc[i_valid], y_log[i_valid]),
    early_stopping_rounds=200
)

pred_valid = np.expm1(model.predict(X.iloc[i_valid]))
pred_valid = np.maximum(pred_valid, 1)

score = median_ape(y[i_valid], pred_valid)
print(f"medianAPE = {score:.4f} ({score * 100:.2f}%)")

Почему используется логарифм:

\[ z=\log(1+y). \]

Ошибка в 100 000 рублей для автомобиля за 500 000 рублей составляет 20%, а для автомобиля за 5 000 000 рублей — только 2%. Логарифм заставляет модель больше учитывать относительную, а не абсолютную ошибку.

Полезно сравнить два варианта:

  1. обучение на y;
  2. обучение на log1p(y).

Оставьте тот, у которого меньше medianAPE на временной валидации.

4. Использование фотографий

Для каждого изображения:

100000_0.jpg

ID объявления равен части имени до последнего подчёркивания:

ad_id = filename.rsplit("_", 1)[0]

Для каждого изображения можно получить вектор признаков с помощью предварительно обученной сверточной модели. Затем объединить максимум четыре изображения одного автомобиля:

\[ F_i = [ \operatorname{mean}(e_{i1},\ldots,e_{ik}), \operatorname{max}(e_{i1},\ldots,e_{ik}), e_{i1}, k ]. \]

Где:

  • \(e_{ij}\) — вектор признаков фотографии;
  • mean — средний вектор;
  • max — покомпонентный максимум;
  • \(e_{i1}\) — признаки первой фотографии;
  • \(k\) — число фотографий.

После этого:

  1. сопоставьте признаки с объявлениями по ID;
  2. уменьшите размерность PCA, например до 32–128 признаков;
  3. добавьте эти признаки к табличным;
  4. обучите CatBoost повторно.

PCA нужно обучать только на тренировочной части. Изображения лучше обрабатывать пакетами и сохранять готовые эмбеддинги, иначе повторное обучение будет медленным.

Фотографии можно использовать и отдельной моделью. Тогда на валидации подберите смешивание:

\[ z=\alpha z_{\text{table}}+(1-\alpha)z_{\text{image}}, \]

\[ \hat y=\exp(z)-1. \]

Проверяйте значения \(\alpha\) от 0 до 1 с шагом 0.05 и выбирайте то, при котором medianAPE минимален.

5. Финальное обучение и отправка

После выбора признаков и числа итераций обучите модель на всех 70 000 объектах:

final_model = CatBoostRegressor(
    iterations=model.get_best_iteration() + 1,
    depth=8,
    learning_rate=0.03,
    l2_leaf_reg=10,
    loss_function="MAE",
    random_seed=42,
    verbose=300
)

final_model.fit(
    X,
    np.log1p(y),
    cat_features=cat_cols
)

pred_test = np.expm1(final_model.predict(X_test))
pred_test = np.maximum(pred_test, 1)

Заполните шаблон sample_submission.csv, сохраняя его порядок:

sample = pd.read_csv("sample_submission.csv")

pred_by_id = pd.Series(
    pred_test,
    index=test[ID_COL].astype(str)
)

sample["target"] = (
    sample["ID"]
    .astype(str)
    .map(pred_by_id)
)

assert sample["target"].notna().all()
assert np.isfinite(sample["target"]).all()
assert (sample["target"] > 0).all()

sample.to_csv("submission.csv", index=False)

В итоговом файле должны остаться ровно два столбца:

ID,target

Главная проверка перед отправкой:

print(sample.head())
print(sample.shape)
print(sample["target"].describe())

Если модель на временной валидации не превосходит простой прогноз медианной цены, сначала проверьте ID, обработку пропусков, мультивыборные поля и соответствие фотографий объявлениям.