Оценка подхода к модели
Мне нужно сформулировать краткий ответ относительно наилучшего практического решения: использовать CatBoost для табличных признаков вместе с image embeddings из предварительно обученной модели. Я рассматриваю валидацию по времени, агрегацию image embeddings для каждого автомобиля и их смешивание. Однако есть неоднозначность, поскольку метрика оценки не указана — следует ли использовать RMSE или log? Это может изменить подход, поэтому, возможно, нужно задать уточняющий вопрос о том, что метрика не предоставлена.
Рассмотрение разработки модели
Я понимаю, что если метрика влияет на выбор модели, но не меняет принципиально сам подход, можно продолжить со стандартной методологией и предложить скорректировать objective. Вероятно, стоит предоставить код, но нужно избегать ссылок вроде «скачать по ссылке», поскольку ссылка не указана. Вместо этого я создам код, использующий пути к файлам, и объясню схемы данных. Учитывая данные, нужно корректно обрабатывать изображения, чтобы код надёжно определял ID и эффективно считывал файлы parquet/csv. Также следует учитывать вычислительные ограничения.
Рассмотрение подготовки данных
Я думаю о том, как очистить данные в табличном формате. Сначала нужно убедиться, что target больше нуля, и заполнить категориальные значения строкой "__NA__". Для числовых значений CatBoost может обрабатывать NaN, поэтому импутация может не потребоваться. Я также рассматриваю синусоидальные и косинусоидальные преобразования для признаков широты и долготы. Кластеризация KMeans тоже может быть полезной, а для лучшего определения регионов можно добавить geo_hash или округлённые координаты. Нужно будет применить логарифмическое преобразование к target price и подумать о хронологическом разделении данных для валидации.
Настройка CatBoost для регрессии
Я думаю о том, как настроить CatBoostRegressor. Следует сосредоточиться на использовании RMSE или MAE для логарифмически преобразованных цен, а если метрика оценки неизвестна, RMSE может быть хорошим вариантом по умолчанию. Использование GPU необязательно; можно задать 3000 итераций, learning rate 0.05, depth 8, а также добавить параметры l2_leaf_reg, random_seed и eval_set с ранней остановкой после 200 раундов. Я также хочу использовать все признаки, а для image embeddings получать ID из имён файлов.
Рассмотрение стратегий обучения модели
Я изучаю, как объединить два подхода к обучению с CatBoost. Первый — табличная модель, второй использует агрегированные признаки изображений вместе с табличными данными. Возможно, объединение этих методов даст лучший результат. Похоже, интеграция разных типов данных может повысить качество модели, но сначала нужно изучить, как эффективно это реализовать. Посмотрим, что лучше всего подходит для достижения целей пользователя!
Уточнение кодирования данных
Я рассматриваю возможность того, что ID кодирует время, и тогда его следует исключить. Если доступна дата, лучше использовать её. Похоже, ID может быть нужен только для объединения с изображениями, а не для модели. В sample ID column, судя по всему, называется id, поэтому следует определить её и полностью удалить id из признаков. Это делает обработку данных более понятной.
Рассмотрение управления данными
Я изучаю, как обрабатывать столбцы с датами в наборе данных. Если дата присутствует, я преобразую её в datetime и отсортирую данные, используя квантиль для определения границы. Если даты нет, можно использовать индексы строк и предупредить о возможных проблемах с порядком.
Я хочу, чтобы код был надёжным, но лаконичным; он не должен быть чрезмерно длинным. Поскольку запрос пользователя, скорее, предполагает понимание, чем полностью готовое решение, следует сосредоточиться на описании алгоритма и каркасе кода.
Уточнение представления информации
Я думаю о том, как сформулировать вопрос, не создавая избыточности. Возможно, яснее будет сказать: «Метрика в условии не указана; ниже — универсальная схема…», а затем в финале попросить указать конкретную метрику. В этом нет противоречия. Также не нужно включать внешние официальные ссылки, поскольку это учебная задача. Теперь следует сосредоточиться на разработке кода.
Оценка кода для метрик и изображений
Я работаю над функцией, которая гарантирует неотрицательность предсказаний с помощью np.maximum. Нужно рассчитать различные метрики, такие как RMSE, MAE и MAPE, и использовать root_mean_squared_error из sklearn для совместимости. Также у меня есть потенциальный фрагмент псевдокода, который перечисляет image IDs в папке, проверяя наличие файлов .jpg. Это важно, поскольку ID могут быть записаны с символами подчёркивания и, вероятно, являются числовыми. Для более корректного разбора я использую rsplit!
Определение необходимости включения кода модели
Думаю, код включить нужно, но он не должен быть перегруженным. Запрос пользователя «Постройте модель» предполагает конкретную модель, а не только объяснение принципов. Полный шаблон кода может быть действительно полезен, но нужно решить, сколько именно кода предоставить. Важно найти баланс между информативностью и краткостью, чтобы код оставался эффективным и не был слишком большим.
Рассмотрение оценки модели
Я думаю о создании минимального рабочего baseline-кода с CatBoost и псевдокодом для интеграции изображений. Если я заявляю, что строю модель, нужно предоставить достаточно деталей для запуска — возможно, около 100 строк. Можно сосредоточиться на наиболее вероятных метриках, например в задаче прогнозирования цены на Avito, но без самих данных числовые предсказания получить невозможно. Следует уточнить, что без файлов нельзя вычислить числовые цены, но можно предоставить общие рекомендации и стартовый код CatBoost для табличных данных, упомянув ограничения из-за отсутствия данных.
Подготовка кода для модели изображений
Создание кода для модели изображений может быть довольно сложным, поэтому лучше разделить задачу на части.
Я сосредоточусь на регрессии с target price_TARGET, используя log1p для более корректной работы с ценами, имеющими правостороннюю асимметрию. Планируется неслучайное разделение по дате, соответствующее временным периодам и предотвращающее утечку target. Подход будет включать CatBoost для табличных данных с категориальными переменными и pooling для максимум четырёх изображений на автомобиль.
Я подготовлю запускаемый baseline-код с заполнителями для image embeddings и добавлю фрагмент извлечения признаков из изображений, убедившись, что не возникает ошибок, связанных с sample IDs.
Подготовка данных для обучения модели
Я задаю target как price_TARGET и определяю наборы train, test и sample. Также определю столбец ID, соответствующий sample.
Возможный sample_submission.csv содержит ID,target, тогда как train data может иметь другое имя ID. Я создам список возможных столбцов для поиска, но точное совпадение не обязательно. Можно просто использовать id_col = 'id', однако следует отметить, что ID_COL нужно заменить на соответствующее имя столбца с учётом регистра.
Проверка изменений в коде
Я вижу, что определение корректного столбца ID в данных крайне важно! Первый подход проверяет наличие 'ID', но лучше сделать его гибким и добавить другие варианты, например 'item_id' или 'ad_id'. Если найти подходящий столбец не удаётся, можно свериться с первым столбцом sample dataset. Также нужно продумать обработку столбцов с датами и убедиться, что разделение выполнено корректно. При этом исходные столбцы с датами не следует удалять до этапа подготовки.
Уточнение извлечения признаков
Я сосредоточен на добавлении таких признаков, как month и dayofweek, а также рассматриваю порядковую дату, вычисленную относительно некоторой reference timestamp, для учёта временного сдвига. Это может быть полезно! Не следует выполнять преобразования для столбцов, связанных со словом «time», например engine_type. Для географических данных я планирую создавать новые столбцы при обнаружении числовых типов. При преобразовании latitude в numeric я также округлю её и преобразую в строку для категориального анализа. Это поможет лучше организовать признаки для пользователя.
Оценка логики определения даты
Я изучаю, как определить столбцы с датами. Мой подход проверяет, содержат ли имена столбцов такие слова, как «date», «time» или «created». Я опасаюсь случайно выбрать похожие подстроки, например engine_type, поскольку в нём может встречаться «time», хотя это не имеет отношения к дате. Аналогично, crashes_count тоже не подходит. Нужно уточнить, что именно проверяется, чтобы избежать ошибок.
Краткий ответ. Это задача регрессии: для каждого автомобиля нужно предсказать price_TARGET. Надёжное решение:
- обучить
CatBoostRegressorна табличных признаках; - предсказывать не саму цену, а
log1p(price_TARGET); - использовать временную валидацию: ранние месяцы — обучение, последние — проверка;
- из фотографий получить эмбеддинги предобученной CNN и усреднить их по автомобилю;
- объединить табличные признаки и признаки фотографий, затем сформировать
submission.csv.
Точную модель нельзя окончательно выбрать без указанной метрики соревнования и самих файлов, но ниже приведён рабочий план.
1. Почему нужна временная валидация
Тест содержит объявления за декабрь 2024 года, а обучение — за январь–ноябрь 2024 года. Поэтому случайное разделение может дать слишком оптимистичную оценку.
Правильнее сделать, например:
- обучение: январь–октябрь;
- валидация: ноябрь.
Если в таблице есть дата объявления, используйте её. Если даты нет, можно взять последние 20% строк, но только если строки действительно упорядочены по времени.
Фото нужно разделять на уровне автомобиля, а не отдельно: у одного объекта может быть до 4 изображений.
2. Табличный baseline
CatBoost хорошо подходит для этой задачи, потому что умеет работать с категориальными признаками:
body_type;drive_type;engine_type;color;pts;steering_wheel.
Числовые признаки:
mileage;owners_count;crashes_count;doors_number;latitude;longitude.
Идентификатор объявления не следует подавать в модель: он нужен только для соединения с фотографиями.
import numpy as np
import pandas as pd
from catboost import CatBoostRegressor
train = pd.read_parquet("train_dataset.parquet")
test = pd.read_parquet("test_dataset.parquet")
sample = pd.read_csv("sample_submission.csv")
TARGET = "price_TARGET"
# В sample_submission.csv обычно первый столбец — ID
sample_id_col = sample.columns[0]
id_col = next(
c for c in train.columns
if c.lower() == sample_id_col.lower()
)
# Удаляем некорректные цены
train = train[
train[TARGET].notna() &
(train[TARGET] > 0)
].reset_index(drop=True)
# Целевая переменная с логарифмированием
y_raw = train[TARGET].astype(float).to_numpy()
y = np.log1p(y_raw)
Добавим простые признаки даты и геолокации:
date_col = next(
(
c for c in train.columns
if any(word in c.lower() for word in ["date", "time", "created"])
),
None
)
def prepare_features(df):
x = df.drop(
columns=[TARGET, id_col],
errors="ignore"
).copy()
# Преобразование даты в числовые признаки
if date_col is not None and date_col in x.columns:
date = pd.to_datetime(x[date_col], errors="coerce")
x[date_col + "_year"] = date.dt.year
x[date_col + "_month"] = date.dt.month
x[date_col + "_dayofweek"] = date.dt.dayofweek
x[date_col + "_day"] = date.dt.day
x = x.drop(columns=[date_col])
numeric_columns = [
"mileage",
"latitude",
"longitude",
"doors_number",
"owners_count",
"crashes_count",
]
for col in numeric_columns:
if col in x.columns:
x[col] = pd.to_numeric(
x[col].astype(str).str.replace(",", ".", regex=False),
errors="coerce"
)
# Укрупнённая ячейка расположения
if {"latitude", "longitude"} <= set(x.columns):
lat = pd.to_numeric(x["latitude"], errors="coerce")
lon = pd.to_numeric(x["longitude"], errors="coerce")
x["geo_cell"] = (
lat.round(2).astype("string").fillna("NA")
+ "_"
+ lon.round(2).astype("string").fillna("NA")
)
return x
X = pd.concat(
[
prepare_features(train),
prepare_features(test)
],
axis=0,
ignore_index=True
)
# Подготовка категориальных признаков
cat_cols = []
for col in X.columns:
if (
pd.api.types.is_object_dtype(X[col])
or pd.api.types.is_string_dtype(X[col])
or pd.api.types.is_categorical_dtype(X[col])
or pd.api.types.is_bool_dtype(X[col])
):
X[col] = X[col].fillna("__MISSING__").astype(str)
cat_cols.append(col)
X_train_all = X.iloc[:len(train)].copy()
X_test = X.iloc[len(train):].copy()
3. Временное разделение
if date_col is not None:
dates = pd.to_datetime(
train[date_col],
errors="coerce"
)
if dates.notna().mean() > 0.8:
border = dates.quantile(0.8)
train_idx = np.where(dates < border)[0]
valid_idx = np.where(dates >= border)[0]
else:
border_idx = int(len(train) * 0.8)
train_idx = np.arange(border_idx)
valid_idx = np.arange(border_idx, len(train))
else:
# Запасной вариант, если даты нет
border_idx = int(len(train) * 0.8)
train_idx = np.arange(border_idx)
valid_idx = np.arange(border_idx, len(train))
Обучение модели:
model = CatBoostRegressor(
loss_function="RMSE",
eval_metric="RMSE",
iterations=3000,
learning_rate=0.05,
depth=8,
l2_leaf_reg=5,
random_seed=42,
verbose=200,
allow_writing_files=False
)
model.fit(
X_train_all.iloc[train_idx],
y[train_idx],
cat_features=cat_cols,
eval_set=(
X_train_all.iloc[valid_idx],
y[valid_idx]
),
use_best_model=True,
early_stopping_rounds=200
)
Проверим результат:
valid_pred_log = model.predict(
X_train_all.iloc[valid_idx]
)
valid_pred = np.maximum(
np.expm1(valid_pred_log),
0
)
rmse_log = np.sqrt(
np.mean(
(valid_pred_log - y[valid_idx]) ** 2
)
)
rmse_price = np.sqrt(
np.mean(
(valid_pred - y_raw[valid_idx]) ** 2
)
)
mape = np.mean(
np.abs(
(valid_pred - y_raw[valid_idx])
/ np.maximum(y_raw[valid_idx], 1)
)
) * 100
print("RMSE по log-цене:", rmse_log)
print("RMSE по цене:", rmse_price)
print("MAPE:", mape)
Логарифмирование помогает, потому что цена автомобиля имеет длинный хвост: несколько дорогих автомобилей не должны полностью определять обучение. После предсказания выполняется обратное преобразование:
\[ \widehat{price} = \exp(\widehat{\log(1+price)}) - 1. \]
4. Как использовать фотографии
Каждая фотография сначала преобразуется предобученной нейронной сетью, например ResNet-18 или ResNet-50, в вектор признаков.
Для файла:
100000_0.jpg
ID автомобиля равен:
100000
У одного автомобиля может быть несколько векторов. Их удобно объединить:
\[ z_{\text{car}} = [ \operatorname{mean}(z_1,\ldots,z_k), \operatorname{std}(z_1,\ldots,z_k), k ]. \]
То есть для каждого автомобиля используются:
- средний визуальный вектор;
- стандартное отклонение по фотографиям;
- количество фотографий.
Среднее и стандартное отклонение лучше простого соединения векторов, потому что порядок фотографий не важен.
Практический процесс:
# Для каждой фотографии:
photo_id = filename.stem.rsplit("_", 1)[0]
# Получаем embedding:
embedding = image_model(image)
# Группируем:
embeddings_by_car[photo_id].append(embedding)
Затем:
car_mean = np.mean(car_embeddings, axis=0)
car_std = np.std(car_embeddings, axis=0)
car_features = np.concatenate([
car_mean,
car_std,
[len(car_embeddings)]
])
Если вектор ResNet имеет размер 512, можно дополнительно применить PCA, например до 64 или 128 признаков. PCA нужно обучать только на фотографиях обучающей выборки, а к тесту применять уже готовое преобразование.
После этого признаки фотографий добавляются к X_train_all и X_test, и обучается второй CatBoostRegressor.
Полезно сравнить:
- модель только на таблицах;
- модель на таблицах и фотографиях.
Если фотографии не улучшают результат на временной валидации, их лучше не использовать: визуальные признаки могут содержать шум.
5. Формирование отправки
test_pred_log = model.predict(X_test)
test_pred = np.maximum(
np.expm1(test_pred_log),
0
)
submission = sample.copy()
submission[submission.columns[1]] = test_pred
assert len(submission) == 25_000
assert submission.iloc[:, 0].tolist() == sample.iloc[:, 0].tolist()
assert submission.iloc[:, 1].notna().all()
assert (submission.iloc[:, 1] >= 0).all()
submission.to_csv(
"submission.csv",
index=False
)
Что считать хорошей проверкой
Нужно проверить, что:
- валидация имитирует декабрь 2024 года;
IDне используется как числовой признак;- цена после обратного преобразования не содержит
NaN; - в отправке ровно 25 000 строк;
- порядок и значения
IDсовпадают сsample_submission.csv; - модель с фотографиями действительно лучше табличного baseline;
- выбранная оценка соответствует метрике соревнования.
Если метрика считается на исходной цене, сравнивайте предсказания после expm1. Если используется RMSLE или ошибка по логарифму, сравнивайте значения в логарифмическом масштабе.