Изучение оптимизации кода
Я хочу создать более простой и надёжный код с использованием pandas, CatBoost и Tfidf с Ridge или SGD. Мне нужно, чтобы он укладывался примерно в 70 МБ памяти. Я предоставлю код, который считывает parquet-файлы и тестовые образцы отправки, а также векторизует название, описание и категорию с помощью Tfidf.
Кроме того, мне нужно рассмотреть target encoding для категориальных признаков. CatBoost может хорошо подойти, поскольку он работает со строками и позволяет указывать cat_features и text_features. Я позабочусь о том, чтобы качество было достаточно высоким!
Изучение метрик прогнозирования
Я пытаюсь понять контекст order_date — возможно, он относится к будущему тесту? Мне следует изучить распределение данных, начав со случайного разбиения. Возможно, я создам метрики out-of-fold (OOF) и кросс-валидации (CV).
Я замечаю, что мне нужно обрабатывать неотрицательные значения целевой переменной. Использование np.log1p кажется подходящим для прогнозов. При вычислении оценки я думаю о макро-логарифмической средней абсолютной ошибке (MAE). Может возникнуть проблема с интерпретацией названий столбцов в sample submission, особенно при различении real_weight и target_weight. Мне нужно уточнить ожидаемые столбцы результата.
Рассмотрение вариантов моделей
Я вычисляю размерность эмбеддингов, равную 2048, и думаю о добавлении их к X в качестве числовых признаков. CatBoost может работать с 2048 числовыми значениями и примерно 20k строк, что кажется приемлемым. Однако использование четырёх моделей с CatBoost может оказаться ресурсоёмким. Я думаю, может ли PCA помочь упростить решение, но всё же сомневаюсь, поэтому, возможно, лучше оставить CatBoost без изменений.
Обработка структуры прогнозов и отправки
Мне нужно убедиться, что pred_log ограничивается неотрицательными значениями перед использованием expm1, например:
pred_log=np.maximum(model.predict(...),0)
pred=np.expm1(pred_log)
Это гарантирует, что целевая переменная останется неотрицательной. Для метрик важно использовать log1p(pred)=pred_log. Также важно динамически создавать столбцы отправки в зависимости от того, содержит ли sample данные item_id. Я также хочу упомянуть, как правильно обрабатывать изображения, чтобы избежать утечки целевой переменной, особенно при использовании предварительно обученной CNN.
Уточнение ожиданий от решения
Похоже, пользователь может запрашивать полное решение конкурсного задания, возможно, озаглавленного «Задание». Вероятно, он ожидает, что я построю модель и предоставлю подробный пример кода, включая каркас для секции с изображениями. Следует упомянуть использование word n-gram TF-IDF + Ridge для улучшения результата, поскольку CatBoost может покрывать не все аспекты. Однако универсального «правильного ответа» не существует: всё зависит от данных, поэтому я объясню конвейер и включу возможную функциональность.
Оценка мультимодальных решений
Я думаю о построении сквозного мультимодального решения, но хочу избежать зависимости от текстовых признаков CatBoost. Причина в том, что обработка текста в CatBoost может работать не оптимально или приводить к проблемам. Мне нужно изучить альтернативные методы, которые смогут эффективно объединить различные типы данных и обеспечить стабильную производительность на всём конвейере. Таким образом, решение будет не только функциональным, но и эффективным и надёжным.
Рассмотрение смешивания моделей
Я думаю об интеграции ResNet с CatBoost или LightGBM. Идея заключается в смешивании логарифмических прогнозов, но сложность кода может стать проблемой. Я представлю минимальный начальный вариант с CatBoost, а затем предложу улучшения.
Запрос заключается в понимании принципа, а не обязательно в достижении максимального результата, поэтому я хочу проверить соответствие метрикам. Я подчеркну важность сохранения единиц измерения целевой переменной и упоминания веса, размеров и целей. Если условия неполные, возможно, мне потребуется задать один короткий уточняющий вопрос.
Уточнение неполных условий
Разработчик советует не делать предположений, если что-то неполно, а определить, чего не хватает, и задать короткий уточняющий вопрос. Здесь нет явного вопроса или предоставленных файлов, что может быть существенно для точного ответа. Я всё же могу дать обобщённое решение, но стоит ли запрашивать файлы? Если я это сделаю, ответ может стать менее понятным.
Я мог бы сказать что-то вроде: «Чтобы получить числовой файл отправки или оценку без parquet-файлов, используйте следующий воспроизводимый метод». Однако мне нужно сделать ответ полезным, не блокируя дальнейшую работу. Кроме того, поскольку формула метрики выглядит необычно из-за двойных знаков абсолютного значения, следует уточнить, что она представляет собой MAE после log1p, чтобы улучшить понимание.
Обсуждение порядка и преобразования целевой переменной
Полагаю, порядок целевых переменных должен быть weight,height,width,length, поскольку это канонический порядок в коде. Хотя указаны real_height, real_width и real_length, набор метрик, судя по всему, содержит вес, высоту, длину и ширину, и порядок не имеет значения. Мне нужно сохранить порядок предсказаний из sample: weight, height, width, length.
Важно объяснить, почему мы используем преобразование z=log(1+y): модель обучается минимизировать MAE по z. При прогнозировании используется ŷ=exp(z_hat)-1. Это необходимо ясно подчеркнуть!
Уточнение типов данных и обработки
Я обнаруживаю, что для типа string в pandas лучше использовать is_string_dtype, хотя is_object_dtype тоже может подойти. Нужно убедиться, что item_price корректно преобразуется в числовой тип:
x["item_price"]=pd.to_numeric(x["item_price"], errors="coerce")
Далее, при обработке дат я определю date_origin с корректными значениями по умолчанию. Проверка на NaT и заполнение значений подходящими диапазонами выглядит приемлемо. Для признаков заказа, например месяца и дня недели, я буду аккуратно обрабатывать NaN, чтобы избежать ошибок при обработке. Наконец, при работе со столбцами целевых переменных потребуется более понятная обработка несовпадений.
Проверка подготовки данных
Я думаю, что после переиндексации можно заполнить пропуски в категориальных и текстовых столбцах следующим образом:
for c in cat_cols + text_cols: X_test[c] = ...
Однако это может быть необязательно. Для CatBoost я могу передавать cat_features как индексы или названия, причём использование названий вполне подходит. Перед определением признаков X нужно убедиться, что названия целевых столбцов в train согласуются между собой, и удалить столбцы, указанные в REAL_TARGETS. Мне следует перепроверить, что train_targets действительно отражает значения real_*.
Рассмотрение признаков CatBoost
Я изучаю, как модель использует MAE на преобразованной целевой переменной. Если в наборе данных все описания пустые, CatBoost может столкнуться с проблемами, поскольку он использует текстовые признаки.
Одним из решений может быть проверка наличия текстовых столбцов в наборе данных и обработка пустых описаний так, чтобы не вызывать ошибок.
Кроме того, нужно хранить текстовые признаки отдельно от категориальных. Также следует учитывать даты и числовые идентификаторы, а обработка цены как логарифмического признака кажется уместной.
Изучение признаков изображений в моделях
Я рассматриваю возможность включения изображений в качестве признаков модели. План состоит в том, чтобы извлечь признаки из обучающего и тестового наборов, а затем добавить их к набору признаков.
Чтобы избежать утечки данных, следует добавить эти признаки изображений до любого разбиения, поскольку при этом не выполняется подгонка по целевой переменной. Можно использовать предварительно обученные модели, например resnet18 с вектором размерности 512 или resnet50 с вектором размерности 2048.
Завершение файла отправки
Я создаю файл отправки, используя функцию expm1 для обработки логарифмических прогнозов. Сначала я копирую структуру sample в новую переменную submission. Затем прохожу по столбцам и предсказанным значениям, заполняя соответствующие данные в submission. В конце сохраняю submission в CSV-файл без индекса, поскольку он не нужен. Всё просто, и, думаю, должно работать корректно!
Рассмотрение требований к изображениям
Я думаю, что потребность модели в изображениях важна; игнорирование этой части может сделать решение неполным. Поэтому, возможно, стоит включить «минимальный фрагмент» с ResNet. Однако я переживаю, что это сделает ответ слишком длинным. Тем не менее, если такой фрагмент добавляет ценность, возможно, его стоит включить. Балансировать эти элементы непросто. Нужно убедиться, что присутствуют все необходимые компоненты, но основной ответ не перегружен. Найдём хорошее решение!
Оценка обучения модели
Я рассматриваю, как обрабатывать обучение без учителя и валидацию. Кажется, лучше подгонять PCA только на обучающем фолде, чтобы избежать утечки валидационных данных, хотя можно использовать и необработанные эмбеддинги. Для соревнования допустимо подгонять PCA на всём обучающем наборе, поскольку информация о целевой переменной не используется, даже если присутствует небольшая утечка. Также следует упомянуть названия архивов с изображениями, сохраняя соответствие элементов по image_name. Добавление эмбеддингов изображений в основной код может быть хорошим решением.
Подготовка деталей кода
Я сосредоточен на тщательной подготовке кода, чтобы обеспечить его корректность. Я импортирую CatBoostRegressor и считываю данные в переменные tr, te и ss из разных форматов. Функция формирования признаков использует REAL_TARGETS, но мне нужно убедиться, что выход sample соответствует ему. Для CatBoost требуется корректная обработка object, поэтому я определю категориальные столбцы с помощью цикла, проверяющего типы данных. Также я позабочусь о правильном импорте is_string_dtype, чтобы избежать проблем с типами данных в X_test.
Синхронизация компонентов кода
Я могу синхронизировать столбцы X_test с text_cols, заполняя отсутствующие значения пустой строкой и преобразуя их к строковому типу. Это должно работать без проблем. Также нужно помнить, что индексы строк X и X_test могут начинаться не с 0, но использование .iloc должно решить эту проблему. CatBoost примет такие данные, особенно поскольку train_test_split возвращает numpy-индексы. Я уверен в этом подходе!
Проверка использования fit_args
Я думаю о том, как использовать model.fit с fit_args. Нужно убедиться, что при наличии текстовых столбцов они включаются в fit_args вместе с категориальными столбцами, определёнными в cat_cols. Важно отметить, что при использовании kwargs исходный словарь fit_args не изменяется при вызове. Поэтому нужно внимательно передавать аргументы, чтобы избежать непреднамеренных изменений.
Оценка вариантов эмбеддингов
Я рассматриваю эмбеддинги изображений, например ImageNet, которые хорошо улавливают категории объектов, но могут не дать существенного улучшения для размеров. CLIP предоставляет более подходящие предварительно обученные варианты с точки зрения семантики, хотя ResNet также доступен. Можно обсудить валидационное разбиение, а если тест является временным, отразить это в разбиении, используя дату отсечения. Для утечки, связанной с продавцом, можно использовать групповое разбиение, хотя случайное разбиение тоже может подойти. Нужно уточнить, как вычислять оценки, например LogMAE, не ссылаясь на конкретный код.
Уточнение деталей отправки
Я вижу, что отправка содержит прогнозы, и важно не использовать в коде значения из sample. Следует последовательно упоминать width и length, включая их в код отправки вместе с weight, height и другими столбцами.
Крайне важно сохранять все единицы измерения и ограничения. Для каждого товара должна быть одна фотография, и нельзя пропускать названия целевых переменных. Поскольку конкретные данные не предоставлены, можно отметить, что числовые прогнозы невозможно вычислить без них. Возможно, также стоит запросить результаты следующей валидации.
Планирование итоговой формулировки
Я думаю об итоговой формулировке ответа. Начну с краткого утверждения, что решение должно обучаться в логарифмическом пространстве. Затем обозначу основные этапы: преобразование целевой переменной, признаки, модель, обратное преобразование и метрика.
Необязательно объяснять каждую строку кода — достаточно описать ключевые шаги. Я включу комментарии на русском языке для пояснения кода и постараюсь аккуратно оформить его, чтобы избежать проблем с Markdown.
Краткий ответ: обучайте четыре регрессора на преобразованных целях
\[
z=\log(1+y),
\]
поскольку метрика считается как MAE именно в лог-шкале. После предсказания выполните обратное преобразование:
\[
\hat y=\exp(\hat z)-1.
\]
Ниже — рабочий baseline на CatBoost для текста, категорий, цены и даты. Точный LogMAE и Points без файлов и результатов других участников вычислить нельзя.
1. Основной принцип
Для каждого товара предсказываются:
real_weight— вес в кг;real_height— высота в см;real_width— ширина в см;real_length— длина в см.
Обучать модель на исходных значениях невыгодно: большие размеры будут сильнее влиять на ошибку. Поэтому:
y_log = np.log1p(y)
Модель обучается с loss_function="MAE" на y_log. Это непосредственно соответствует Log-MAE.
После предсказания:
prediction = np.expm1(prediction_log)
prediction = np.maximum(prediction, 0)
В файл отправляются именно значения в кг и см, а не логарифмы.
2. Базовое решение
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from catboost import CatBoostRegressor
# ----------------------------
# Загрузка данных
# ----------------------------
train = pd.read_parquet("train.parquet")
test = pd.read_parquet("test.parquet")
sample = pd.read_csv("sample_submission.csv")
REAL_TARGETS = [
"real_weight",
"real_height",
"real_width",
"real_length",
]
SUBMISSION_TARGETS = [
"target_weight",
"target_height",
"target_width",
"target_length",
]
# В разных версиях шаблона имена целевых столбцов
# могут быть real_* или target_*.
if all(col in sample.columns for col in SUBMISSION_TARGETS):
output_cols = SUBMISSION_TARGETS
train_target_cols = [
col.replace("target_", "real_")
for col in output_cols
]
elif all(col in sample.columns for col in REAL_TARGETS):
output_cols = REAL_TARGETS
train_target_cols = REAL_TARGETS
else:
raise ValueError(
"Не удалось определить названия целевых столбцов "
"в sample_submission.csv"
)
# ----------------------------
# Построение признаков
# ----------------------------
CAT_COLS = [
"item_condition",
"category_name",
"subcategory_name",
"microcat_name",
"seller_id",
"buyer_id",
]
TEXT_COLS = [
"title",
"description",
]
date_origin = pd.to_datetime(
train["order_date"],
errors="coerce"
).min()
if pd.isna(date_origin):
date_origin = pd.Timestamp("2000-01-01")
def make_features(df, origin):
x = df.copy()
# Цели не должны попасть в признаки
x = x.drop(
columns=[col for col in REAL_TARGETS if col in x.columns],
errors="ignore"
)
# Уникальные идентификаторы и имя изображения
# напрямую обычно не дают полезного обобщения
x = x.drop(
columns=["item_id", "image_name"],
errors="ignore"
)
# Разбор даты
if "order_date" in x.columns:
date = pd.to_datetime(
x.pop("order_date"),
errors="coerce"
)
x["order_year"] = (
date.dt.year.fillna(-1).astype("int16")
)
x["order_month"] = (
date.dt.month.fillna(-1).astype("int8")
)
x["order_day"] = (
date.dt.day.fillna(-1).astype("int8")
)
x["order_weekday"] = (
date.dt.dayofweek.fillna(-1).astype("int8")
)
x["days_from_origin"] = (
(date - origin)
.dt.total_seconds()
.div(86400)
.fillna(-1)
.astype("float32")
)
# Цена в исходном масштабе и в логарифмическом
if "item_price" in x.columns:
price = pd.to_numeric(
x["item_price"],
errors="coerce"
)
x["item_price"] = price
x["item_price_log1p"] = np.log1p(
price.clip(lower=0)
)
# Категориальные признаки
for col in CAT_COLS:
if col in x.columns:
x[col] = (
x[col]
.astype("string")
.fillna("__MISSING__")
.astype(str)
)
# Текстовые признаки
for col in TEXT_COLS:
if col in x.columns:
x[col] = (
x[col]
.astype("string")
.fillna("")
.astype(str)
)
# Все оставшиеся строковые признаки считаем категориальными
for col in x.columns:
is_string = (
pd.api.types.is_object_dtype(x[col])
or pd.api.types.is_string_dtype(x[col])
)
if col not in TEXT_COLS and is_string:
x[col] = (
x[col]
.astype("string")
.fillna("__MISSING__")
.astype(str)
)
return x
X = make_features(train, date_origin)
X_test = make_features(test, date_origin)
# Одинаковый порядок столбцов
X_test = X_test.reindex(columns=X.columns)
cat_cols = [
col for col in CAT_COLS
if col in X.columns
]
text_cols = [
col for col in TEXT_COLS
if col in X.columns
]
# Добавим прочие строковые столбцы в категориальные
for col in X.columns:
is_string = (
pd.api.types.is_object_dtype(X[col])
or pd.api.types.is_string_dtype(X[col])
)
if col not in text_cols and is_string:
if col not in cat_cols:
cat_cols.append(col)
# ----------------------------
# Разбиение для проверки
# ----------------------------
indices = np.arange(len(train))
train_idx, valid_idx = train_test_split(
indices,
test_size=0.2,
random_state=42
)
# ----------------------------
# Обучение четырех моделей
# ----------------------------
base_params = {
"iterations": 2000,
"depth": 8,
"learning_rate": 0.04,
"loss_function": "MAE",
"eval_metric": "MAE",
"l2_leaf_reg": 8,
"random_seed": 42,
"allow_writing_files": False,
}
fit_params = {
"cat_features": cat_cols,
}
if text_cols:
fit_params["text_features"] = text_cols
valid_pred_log = np.zeros(
(len(valid_idx), len(train_target_cols)),
dtype=np.float32
)
test_pred_log = np.zeros(
(len(test), len(train_target_cols)),
dtype=np.float32
)
for target_number, target_col in enumerate(train_target_cols):
print(f"\nОбучение для {target_col}")
y_raw = pd.to_numeric(
train[target_col],
errors="coerce"
).to_numpy(dtype=np.float32)
# Физические величины не должны быть отрицательными
y_log = np.log1p(
np.maximum(y_raw, 0)
)
model = CatBoostRegressor(
**base_params,
verbose=250
)
model.fit(
X.iloc[train_idx],
y_log[train_idx],
eval_set=(
X.iloc[valid_idx],
y_log[valid_idx]
),
use_best_model=True,
**fit_params
)
# Предсказание на валидации в логарифмической шкале
valid_log = model.predict(
X.iloc[valid_idx]
)
valid_pred_log[:, target_number] = np.maximum(
valid_log,
0
)
# Число итераций выбираем по валидации
best_iteration = model.get_best_iteration()
if best_iteration is None or best_iteration < 0:
best_iteration = base_params["iterations"] - 1
# Переобучение на всех доступных данных
final_params = dict(
base_params,
iterations=int(best_iteration) + 1,
verbose=False
)
final_model = CatBoostRegressor(**final_params)
final_model.fit(
X,
y_log,
**fit_params
)
test_log = final_model.predict(X_test)
test_pred_log[:, target_number] = np.maximum(
test_log,
0
)
# ----------------------------
# Проверка Log-MAE
# ----------------------------
valid_true = train.iloc[
valid_idx
][train_target_cols].to_numpy(dtype=float)
valid_pred = np.expm1(valid_pred_log)
valid_true_log = np.log1p(
np.maximum(valid_true, 0)
)
valid_pred_log_checked = np.log1p(
np.maximum(valid_pred, 0)
)
per_target_log_mae = np.mean(
np.abs(valid_true_log - valid_pred_log_checked),
axis=0
)
log_mae = per_target_log_mae.mean()
score = 1 / (1 + log_mae)
print("\nLog-MAE по целям:")
for col, value in zip(train_target_cols, per_target_log_mae):
print(f"{col}: {value:.6f}")
print(f"\nСредний Log-MAE: {log_mae:.6f}")
print(f"Промежуточный Score: {score:.6f}")
# ----------------------------
# Формирование отправки
# ----------------------------
test_predictions = np.expm1(test_pred_log)
test_predictions = np.maximum(test_predictions, 0)
submission = sample.copy()
for number, col in enumerate(output_cols):
submission[col] = test_predictions[:, number]
submission.to_csv(
"submission.csv",
index=False
)
print("\nФайл submission.csv сохранён.")
3. Что делает модель
seller_idиbuyer_idпередаются как категориальные признаки;category_name,subcategory_name,microcat_nameтакже обрабатываются как категории;titleиdescriptionиспользуются CatBoost как текст;- дата превращается в год, месяц, день недели и число дней от начала;
- цена используется в исходном и логарифмическом виде;
- для каждой из четырёх целей обучается отдельная модель.
item_id и image_name не подаются напрямую: это почти уникальные идентификаторы, которые могут привести к переобучению.
4. Как использовать изображения
В baseline выше изображения пока не используются. Для улучшения можно:
- открыть изображение по
image_nameиз соответствующего ZIP-архива; - пропустить его через предобученную ResNet, EfficientNet или CLIP;
- получить вектор признаков, например из 512 или 2048 чисел;
- добавить эти числа в
XиX_testкак обычные числовые признаки.
Важно сопоставлять изображения именно по image_name, а не по порядку файлов в ZIP.
Можно также обучить отдельную модель на визуальных признаках и смешать предсказания в логарифмической шкале:
\[ \hat z = \alpha \hat z_{\text{табличная}} + (1-\alpha)\hat z_{\text{изображение}}. \]
Значение \(\alpha\) выбирают по валидационному Log-MAE. Усреднять лучше логарифмические предсказания, потому что именно в этой шкале считается метрика.
Points нельзя точно получить только по своей валидации: они зависят от промежуточных результатов других участников и от качества sample_submission.csv.