Как обучить XGBoost на большом датасете и улучшить детекцию мошенничества?
Только начинаю разбираться в ML, поэтому буду рад любым советам.
Пытаюсь обучить модель для детекции мошеннических транзакций. Данные сильно несбалансированы (~96% обычных и ~4% мошеннических).
Первая проблема – потребление памяти. Трейн-файл весит 32 ГБ, но уже при чтении 1 млн строк получаю ошибку выделения памяти:
xgboost.core.XGBoostError: bad_malloc: Failed to allocate 25547999900 bytes.
Вторая проблема – низкое качество предсказаний. Запускаю обучение на 100 тыс. строк, но даже при разных параметрах XGBoost модель почти не находит мошенничества.
Какие лучшие практики балансировки классов для XGBoost в такой задаче? Как работать с таким большим датасетом? Что посоветуете изменить?
df = pd.read_csv('train.csv', nrows=100000)
df.drop(['transaction_id', 'card_holder_first_name', 'card_holder_last_name', 'is_verified', 'browser', 'browser_version',
'operating_system', 'operating_system_version', 'card_id', 'ip_address', 'merchant_customer_id', 'merchant_id', 'user_agent',
'merchant_customer_last_name', 'merchant_customer_first_name', 'merchant_customer_phone', 'merchant_customer_email', 'bin','device',
'traffic_source', 'transaction_source', 'merchant_city', 'merchant_shop_id', 'merchant_shop_name', 'order_number'],
axis=1, inplace=True)
df['bank'].replace(' ', '_', regex=True, inplace=True)
df['created_at'] = pd.to_datetime(df['created_at'])
df['seconds_since_midnight'] = df['created_at'].dt.hour * 3600 + df['created_at'].dt.minute * 60 + df['created_at'].dt.second
df['day_of_week'] = df['created_at'].dt.weekday
df.drop('created_at', axis=1, inplace=True)
df.loc[pd.isna(df['merchant_language']), 'merchant_language'] = 'unknown'
df.loc[pd.isna(df['payment_type']), 'payment_type'] = 0
X = df.drop('is_fraud', axis=1).copy()
y = df['is_fraud'].copy()
X_encoded = pd.get_dummies(X, columns=['merchant_country',
'transaction_type',
'merchant_language',
'platform',
'ip_country',
'bank',
'cardbrand',
'cardcountry',
'cardtype',
'payment_type'])
X_train, X_test, y_train, y_test = train_test_split(X_encoded, y, random_state=42, stratify=y)
clf_xgb = xgb.XGBClassifier(
objective="binary:logistic",
seed=42,
eval_metric="aucpr",
early_stopping_rounds=10,
max_depth=6,
subsample=0.8,
colsample_bytree=0.8
)
clf_xgb.fit(
X_train,
y_train,
eval_set=[(X_test, y_test)],
verbose=True
)
disp = ConfusionMatrixDisplay.from_estimator(
clf_xgb,
X_test,
y_test,
display_labels=["Not fraudsters", "Fraud"],
cmap="Blues"
)
disp.plot(values_format='d')
plt.show()
Ответы (1 шт):
Итак, проблемы.
Слишком большой датасет не умещается в памяти.
Эта проблема решается несколькими способами:
- Сэмплирование (выбор только части строк датасета для обучения). Читаем только часть строк с помощью параметров
skiprows
иnumrows
. Причём, в вашем случае хорошо бы ещё и дисбаланс классов как-то учесть, если использовать только сэмплирование без дообучения. - Чтение только нужных колонок датасета, задаётся параметром
usecols
при чтении черезread_csv
. Это лучше, чем читать все колонки и потом выбрасывать не нужные. До того, как вы их выбросите, этим колонкам нужна будет лишняя память. - Дообучение модели - используете модель, обученную на части данных, при обучении на очередной части данных. Тренированная ранее модель указывается с помощью параметра
xgb_model
при вызове методаfit
илиtrain
.
Для начала можете попробовать дообучение - читаете часть строк, учите модель, читаете ещё часть, опять учите и так пока все данные не скормите.
Более сложный метод - считать из датасета только колонку с таргетом, выбрать строки с таргетом True
, выбрать случайным образом столько же сколько этих строк с True
строк с False
(или в несколько раз больше, если память позволит), у вас получатся индексы, которые нужно выбрать из датасета. Берёте только эти индексы из датасета и учите модель на них. Получится более-менее сбалансированная выборка.
Далее, можно по одной из получившихся моделей посмотреть, какие признаки важные, а какие нет, и потом читать только важные признаки, может быть модель тогда целиком в память влезет и без семплирования. В общем, это итеративный процесс.
Низкое качество предсказаний.
- Попробуйте выставить при обучении параметр
scale_pos_weight=100
, чтобы сбалансировать веса сэмплов. Обучение всё-равно идёт подobjective
, а там обычный логлосс и балансировка нужна в том или ином виде. - Возможно, вы недообработали фичи вашего датасета. Не сгенерили какие-то полезные преобразования и интеракции фич. Возможно. Это нужно пробовать, проверять.
- Возможно, что нужно учитывать не только статику, но и динамику. Наверняка, банки при выявлении мошеннических транзакций учитывают не только одну транзанкцию, но и предыдущие транзакции этого же пользователя. Не знаю, возможно ли получить такую информацию в вашем датасете.
- Есть вероятность, что нужной информации для построения качественной модели в ваших данных просто недостаточно, такое тоже бывает. Если это учебный датасет - нужно выяснить, добивался ли кто-то с ним лучшего качества. Если же это реальная задача, то тогда стараются добыть каких-то ещё связанных данных.
- А, ну и есть ещё типовые методики - подбор гиперпараметров, отбор фич. Но сильного прироста при использовании сложных моделей таких как
XGBoost
они обычно не дают, это нужно обычно только на соревнованиях по машинному обучению, когда битва идёт за сотые доли скора. Но можете попробовать.