{"metadata":{"kernelspec":{"display_name":"hw7","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"colab":{"include_colab_link":true,"provenance":[]},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":132448,"databundleVersionId":15891735}],"dockerImageVersionId":31287,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Competitive Solution: Chris Deotte + CPMP approach\n\nОпираемся на:\n- 1st place (Chris Deotte): UID + magic features + XGB (0.9600 public)\n- 2nd place (CPMP/Giba): data cleaning + UID aggregates + post-processing (0.9672 public)\n\nPlan:\n1. Load & merge data\n2. Basic feature engineering (cents, card combos, time)\n3. UID creation (magic feature)\n4. Group aggregation features (47 new features)\n5. Data cleaning + feature selection\n6. Train XGB + LGB + CatBoost with time-based CV\n7. Ensemble + post-processing\n8. Submission","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os, gc, warnings\nfrom sklearn.metrics import roc_auc_score\nfrom sklearn.preprocessing import LabelEncoder\nfrom catboost import CatBoostClassifier\nimport lightgbm as lgb\nimport xgboost as xgb\nwarnings.filterwarnings('ignore')\n\nINPUT_DIR = '/kaggle/input/competitions/ai-masters-fraud-detection'\n\n# === Experiment config ===\nEXP = {\n    \"velocity_window_sec\": 86400,\n    \"segment_threshold\": 5,\n    \"segment_weights\": {\n        \"known\":        {\"xgb\": 0.5, \"lgb\": 0.3, \"cb\": 0.2},\n        \"unknown\":      {\"xgb\": 0.25, \"lgb\": 0.5, \"cb\": 0.25},\n        \"questionable\": {\"xgb\": 0.25, \"lgb\": 0.25, \"cb\": 0.5},\n    },\n}\n\nHP = {\n    \"xgboost\": {\n        \"max_depth\": 12,\n        \"learning_rate\": 0.02,\n        \"subsample\": 0.8,\n        \"colsample_bytree\": 0.4,\n        \"min_child_weight\": 5,\n        \"n_estimators\": 800,\n        \"eval_metric\": \"auc\",\n        \"objective\": \"binary:logistic\",\n        \"device\": \"gpu\",\n        \"random_state\": 42\n    },\n    \"lightgbm\": {\n        \"n_estimators\": 800,\n        \"learning_rate\": 0.02,\n        \"max_depth\": -1,\n        \"num_leaves\": 256,\n        \"subsample\": 0.8,\n        \"colsample_bytree\": 0.4,\n        \"min_child_samples\": 50,\n        \"random_state\": 42,\n        \"verbose\": -1,\n        \"device\": \"gpu\"\n    },\n    \"catboost\": {\n        \"iterations\": 800,\n        \"learning_rate\": 0.02,\n        \"depth\": 8,\n        \"loss_function\": \"Logloss\",\n        \"eval_metric\": \"AUC\",\n        \"random_seed\": 42,\n        \"verbose\": False,\n        \"task_type\": \"GPU\"\n    },\n    \"ensemble_weights\": {\n        \"xgb\": 0.4,\n        \"lgb\": 0.4,\n        \"cb\": 0.2\n    },\n}\n\nSAMPLE = None\nN_FOLDS = 5","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T19:36:07.679632Z","iopub.execute_input":"2026-03-17T19:36:07.680209Z","iopub.status.idle":"2026-03-17T19:36:07.687000Z","shell.execute_reply.started":"2026-03-17T19:36:07.680172Z","shell.execute_reply":"2026-03-17T19:36:07.686285Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n# Step 1: Load data\ntrain_transaction = pd.read_csv(os.path.join(INPUT_DIR, 'train_transaction.csv'))\ntrain_identity = pd.read_csv(os.path.join(INPUT_DIR, 'train_identity.csv'))\ntest_transaction = pd.read_csv(os.path.join(INPUT_DIR, 'test_transaction.csv'))\ntest_identity = pd.read_csv(os.path.join(INPUT_DIR, 'test_identity.csv'))\nsample_submission = pd.read_csv(os.path.join(INPUT_DIR, 'sample_submission.csv'))\n\ndf_train = train_transaction.merge(train_identity, how='left', on='TransactionID')\ndf_test = test_transaction.merge(test_identity, how='left', on='TransactionID')\ndel train_transaction, train_identity, test_transaction, test_identity; gc.collect()\n\nif SAMPLE:\n    df_train = df_train.head(SAMPLE).copy()\n    df_test = df_test.head(SAMPLE).copy()\n\nprint(f\"Train: {df_train.shape}, Test: {df_test.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T19:36:16.362112Z","iopub.execute_input":"2026-03-17T19:36:16.362766Z","iopub.status.idle":"2026-03-17T19:36:33.394549Z","shell.execute_reply.started":"2026-03-17T19:36:16.362740Z","shell.execute_reply":"2026-03-17T19:36:33.393817Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n# Step 2+3: Feature engineering + UID\n\nfor df in [df_train, df_test]:\n    # Time features\n    df['day'] = df['TransactionDT'] / (24 * 60 * 60)\n    df['DT_M'] = (df['TransactionDT'] / (30 * 24 * 60 * 60)).astype(int)\n    df['DT_hour'] = (df['TransactionDT'] % 86400) // 3600\n    df['DT_weekday'] = (df['day'] % 7).astype(int)\n\n    # Canonicalized D-features: floor(day - Dx) = \"registration day\"\n    df['D1n'] = np.floor(df['day'] - df['D1'])\n    df['D4n'] = np.floor(df['day'] - df['D4'])\n    df['D10n'] = np.floor(df['day'] - df['D10'])\n    df['D15n'] = np.floor(df['day'] - df['D15'])\n\n    # Transaction amount features\n    df['cents'] = (df['TransactionAmt'] % 1 * 100).round(0)\n    df['TransactionAmt_log'] = np.log1p(df['TransactionAmt'])\n\n    # M-features: convert T/F to 1/0 for aggregation\n    for col in ['M1','M2','M3','M4','M5','M6','M7','M8','M9']:\n        df[col] = df[col].map({'T': 1, 'F': 0})\n\n    # Card / address combos\n    df['card1_addr1'] = df['card1'].astype(str) + '_' + df['addr1'].astype(str)\n    df['card1_addr1_P_emaildomain'] = df['card1_addr1'] + '_' + df['P_emaildomain'].astype(str)\n\n    # UID1 (Deotte)\n    df['uid'] = df['card1_addr1'].astype(str) + '_' + df['D1n'].astype(str)\n\n    # UID2 (CPMP — more granular, includes email domain)\n    df['uid2'] = df['uid'] + '_' + df['P_emaildomain'].astype(str)\n\n    # Outsider features\n    df['outsider15'] = (np.abs(df['D1'] - df['D15']) > 3).astype('int8')\n    df['outsider10'] = (np.abs(df['D1'] - df['D10']) > 3).astype('int8')\n\nprint(f\"uid1 unique — train: {df_train['uid'].nunique()}, test: {df_test['uid'].nunique()}\")\nprint(f\"uid2 unique — train: {df_train['uid2'].nunique()}, test: {df_test['uid2'].nunique()}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T19:36:37.947691Z","iopub.execute_input":"2026-03-17T19:36:37.947990Z","iopub.status.idle":"2026-03-17T19:36:39.695294Z","shell.execute_reply.started":"2026-03-17T19:36:37.947963Z","shell.execute_reply":"2026-03-17T19:36:39.694622Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n# Step 4: Group aggregation features\n\ndef encode_FE(train_df, test_df, cols):\n    for col in cols:\n        vc = train_df[col].value_counts(dropna=False, normalize=True)\n        train_df[col + '_FE'] = train_df[col].map(vc).astype('float32')\n        test_df[col + '_FE'] = test_df[col].map(vc).astype('float32').fillna(0)\n\ndef encode_AG(main_cols, uids, aggs, train_df, test_df, fillna=True, usena=True):\n    for main_col in main_cols:\n        for uid in uids:\n            for agg_type in aggs:\n                new_col = f'{main_col}_{uid}_{agg_type}'\n                temp = pd.concat([train_df[[uid, main_col]], test_df[[uid, main_col]]])\n                if usena:\n                    temp.loc[temp[main_col] == -1, main_col] = np.nan\n                temp = temp.groupby(uid)[main_col].agg(agg_type).to_dict()\n                train_df[new_col] = train_df[uid].map(temp).astype('float32')\n                test_df[new_col] = test_df[uid].map(temp).astype('float32')\n                if fillna:\n                    train_df[new_col].fillna(0, inplace=True)\n                    test_df[new_col].fillna(0, inplace=True)\n\ndef encode_AG2(main_cols, uids, train_df, test_df):\n    for main_col in main_cols:\n        for uid in uids:\n            new_col = f'{uid}_{main_col}_ct'\n            temp = pd.concat([train_df[[uid, main_col]], test_df[[uid, main_col]]])\n            temp = temp.groupby(uid)[main_col].agg('nunique').to_dict()\n            train_df[new_col] = train_df[uid].map(temp).astype('float32')\n            test_df[new_col] = test_df[uid].map(temp).astype('float32')\n\ndef encode_shift_lag(train_df, test_df, uid_col):\n    for df in [train_df, test_df]:\n        df.sort_values('TransactionDT', inplace=True)\n        gr = df.groupby(uid_col)\n        df[f'{uid_col}_dt_next'] = gr['TransactionDT'].shift(-1) - df['TransactionDT']\n        df[f'{uid_col}_dt_prev'] = df['TransactionDT'] - gr['TransactionDT'].shift(1)\n        df[f'{uid_col}_amt_next'] = gr['TransactionAmt'].shift(-1)\n        df[f'{uid_col}_amt_prev'] = gr['TransactionAmt'].shift(1)\n        df[f'{uid_col}_dt_mean'] = gr[f'{uid_col}_dt_next'].transform('mean').astype('float32')\n        df[f'{uid_col}_dt_std'] = gr[f'{uid_col}_dt_next'].transform('std').astype('float32')\n\n# Frequency encoding\nencode_FE(df_train, df_test, ['addr1', 'card1', 'card2', 'card3',\n                               'P_emaildomain', 'card1_addr1', 'card1_addr1_P_emaildomain',\n                               'uid', 'uid2'])\n\n# === Aggregations by uid (Deotte) ===\nencode_AG(['TransactionAmt', 'D4', 'D9', 'D10', 'D15'], ['uid'], ['mean', 'std'],\n          df_train, df_test)\nencode_AG(['C1','C2','C4','C5','C6','C7','C8','C9','C10','C11','C12','C13','C14'],\n          ['uid'], ['mean'], df_train, df_test)\nencode_AG(['M1','M2','M3','M4','M5','M6','M7','M8','M9'],\n          ['uid'], ['mean'], df_train, df_test)\nencode_AG2(['P_emaildomain','dist1','DT_M','id_02','cents','C13','V314',\n            'V127','V136','V309','V307','V320'], ['uid'], df_train, df_test)\nencode_AG(['C14'], ['uid'], ['std'], df_train, df_test)\n\n# === Aggregations by uid2 (CPMP) ===\nencode_AG(['TransactionAmt', 'D4', 'D10', 'D15'], ['uid2'], ['mean', 'std'],\n          df_train, df_test)\nencode_AG(['C1','C5','C13','C14'], ['uid2'], ['mean'], df_train, df_test)\nencode_AG2(['DT_M','cents','V307','V314'], ['uid2'], df_train, df_test)\n\n# === Aggregations by card1_addr1 ===\nencode_AG(['TransactionAmt'], ['card1_addr1'], ['mean', 'std'], df_train, df_test)\nencode_AG(['TransactionAmt'], ['card1_addr1_P_emaildomain'], ['mean', 'std'], df_train, df_test)\n\n# === D9, D11 aggregations by card1_addr1 (Deotte non-UID entity stats) ===\nencode_AG(['D9', 'D11'], ['card1_addr1'], ['mean', 'std'], df_train, df_test)\nencode_AG(['D9', 'D11'], ['card1_addr1_P_emaildomain'], ['mean', 'std'], df_train, df_test)\n\n# === Shift-lag features (CPMP) ===\nencode_shift_lag(df_train, df_test, 'uid')\nencode_shift_lag(df_train, df_test, 'uid2')\n\n# === Z-score of TransactionAmt relative to uid/uid2 history ===\nfor uid_col in ['uid', 'uid2']:\n    mean_col = f'TransactionAmt_{uid_col}_mean'\n    std_col = f'TransactionAmt_{uid_col}_std'\n    for df in [df_train, df_test]:\n        df[f'{uid_col}_amt_zscore'] = ((df['TransactionAmt'] - df[mean_col]) / df[std_col].replace(0, 1)).astype('float32')\n\n# === 24h transaction velocity (count of txns within rolling window per uid) ===\nWINDOW = EXP['velocity_window_sec']\nfor uid_col in ['uid', 'uid2']:\n    for df in [df_train, df_test]:\n        df.sort_values('TransactionDT', inplace=True)\n        counts = []\n        grouped = df.groupby(uid_col)['TransactionDT']\n        for name, group in grouped:\n            times = group.values\n            cnt = np.searchsorted(times, times) - np.searchsorted(times, times - WINDOW)\n            counts.append(pd.Series(cnt, index=group.index))\n        df[f'{uid_col}_velocity_24h'] = pd.concat(counts).reindex(df.index).astype('float32')\n\nprint(f\"Total features after FE: {df_train.shape[1]}\")\ngc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T19:36:41.606053Z","iopub.execute_input":"2026-03-17T19:36:41.606303Z","iopub.status.idle":"2026-03-17T19:38:26.787084Z","shell.execute_reply.started":"2026-03-17T19:36:41.606282Z","shell.execute_reply":"2026-03-17T19:38:26.786522Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 5: Data cleaning + feature selection\n\ny_train = df_train['isFraud'].values\ntest_ids = df_test['TransactionID'].values\n\n# Remove helper columns + time-inconsistent features (Deotte)\nremove_cols = ['TransactionID', 'TransactionDT', 'isFraud',\n               'day', 'DT_M', 'D1n', 'D4n', 'D10n', 'D15n',\n               'D6', 'D7', 'D8', 'D9', 'D12', 'D13', 'D14',\n               'C3', 'id_08', 'id_33',\n               'id_07', 'id_14', 'id_21', 'id_30', 'id_32', 'id_34',\n               'id_22', 'id_23', 'id_24', 'id_25', 'id_26', 'id_27']\n# NOTE: keeping ALL V-features (let models decide), keeping uid/uid2 for CatBoost\n\nX_train = df_train.drop(columns=[c for c in remove_cols if c in df_train.columns])\nX_test = df_test.drop(columns=[c for c in remove_cols if c in df_test.columns])\n\ncommon_cols = sorted(set(X_train.columns) & set(X_test.columns))\nX_train = X_train[common_cols]\nX_test = X_test[common_cols]\n\ncat_cols = X_train.select_dtypes(include='object').columns.tolist()\nX_train[cat_cols] = X_train[cat_cols].fillna('missing')\nX_test[cat_cols] = X_test[cat_cols].fillna('missing')\n\n# Label encode for XGB/LGB (drop uid/uid2 — too high cardinality for label encoding)\nuid_cols = ['uid', 'uid2']\nle_drop = [c for c in uid_cols if c in X_train.columns]\nX_train_le = X_train.drop(columns=le_drop).copy()\nX_test_le = X_test.drop(columns=le_drop).copy()\ncat_cols_le = [c for c in cat_cols if c not in uid_cols]\n\nfor col in cat_cols_le:\n    le = LabelEncoder()\n    combined = pd.concat([X_train_le[col], X_test_le[col]]).astype(str)\n    le.fit(combined)\n    X_train_le[col] = le.transform(X_train_le[col].astype(str))\n    X_test_le[col] = le.transform(X_test_le[col].astype(str))\n\nprint(f\"XGB/LGB features: {X_train_le.shape[1]} (no uid/uid2)\")\nprint(f\"CatBoost features: {X_train.shape[1]} (with uid/uid2 as cat)\")\nprint(f\"Cat cols: {len(cat_cols)}\")\ndel df_train, df_test; gc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T19:38:36.154765Z","iopub.execute_input":"2026-03-17T19:38:36.155325Z","iopub.status.idle":"2026-03-17T19:38:48.517574Z","shell.execute_reply.started":"2026-03-17T19:38:36.155298Z","shell.execute_reply":"2026-03-17T19:38:48.516997Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n# Full train → predict test\n\n# XGB\nprint(\"XGB full train...\")\nmodel_xgb = xgb.XGBClassifier(**HP['xgboost'])\nmodel_xgb.fit(X_train_le, y_train, verbose=100)\ntest_preds_xgb_full = model_xgb.predict_proba(X_test_le)[:, 1]\n\n# LGB\nprint(\"LGB full train...\")\nmodel_lgb = lgb.LGBMClassifier(**HP['lightgbm'])\nmodel_lgb.fit(X_train_le, y_train)\ntest_preds_lgb_full = model_lgb.predict_proba(X_test_le)[:, 1]\n\n# CatBoost\nprint(\"CB full train...\")\nmodel_cb = CatBoostClassifier(**HP['catboost'])\nmodel_cb.fit(X_train, y_train, cat_features=cat_cols)\ntest_preds_cb_full = model_cb.predict_proba(X_test)[:, 1]\n\nprint(\"Done.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T19:38:48.518611Z","iopub.execute_input":"2026-03-17T19:38:48.518843Z","iopub.status.idle":"2026-03-17T19:44:50.926136Z","shell.execute_reply.started":"2026-03-17T19:38:48.518824Z","shell.execute_reply":"2026-03-17T19:44:50.925493Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Segmented blend + Post-processing + Submission\n\n# Reconstruct uids\ntrain_t = pd.read_csv(os.path.join(INPUT_DIR, 'train_transaction.csv'),\n                       usecols=['TransactionID','TransactionDT','card1','addr1','D1','isFraud'])\ntest_t = pd.read_csv(os.path.join(INPUT_DIR, 'test_transaction.csv'),\n                      usecols=['TransactionID','TransactionDT','card1','addr1','D1'])\n\nfor df in [train_t, test_t]:\n    df['day'] = df['TransactionDT'] / (24 * 60 * 60)\n    df['uid'] = df['card1'].astype(str) + '_' + df['addr1'].astype(str) + '_' + np.floor(df['day'] - df['D1']).astype(str)\n\n# === Segmented blend ===\ntrain_uids = set(train_t['uid'].unique())\ntest_t['uid_count'] = test_t.groupby('uid')['uid'].transform('count')\nTHRESH = EXP['segment_threshold']\n\nknown_mask = test_t['uid'].isin(train_uids) & (test_t['uid_count'] >= THRESH)\nquestionable_mask = test_t['uid'].isin(train_uids) & (test_t['uid_count'] < THRESH)\nunknown_mask = ~test_t['uid'].isin(train_uids)\n\nprint(f\"Segments — known: {known_mask.sum()}, questionable: {questionable_mask.sum()}, unknown: {unknown_mask.sum()}\")\n\ndef segmented_prob_blend(preds_xgb, preds_lgb, preds_cb, known_mask, unknown_mask, questionable_mask):\n    final = np.zeros(len(preds_xgb))\n    sw = EXP['segment_weights']\n    for mask, segment in [(known_mask, 'known'), (unknown_mask, 'unknown'), (questionable_mask, 'questionable')]:\n        m = mask.values if hasattr(mask, 'values') else mask\n        w = sw[segment]\n        final[m] = w['xgb'] * preds_xgb[m] + w['lgb'] * preds_lgb[m] + w['cb'] * preds_cb[m]\n    return final\n\ntest_preds_seg = segmented_prob_blend(\n    test_preds_xgb_full, test_preds_lgb_full, test_preds_cb_full,\n    known_mask, unknown_mask, questionable_mask)\n\n# === Post-processing ===\nfraud_uids = set(train_t.loc[train_t['isFraud'] == 1, 'uid'].unique())\nprint(f\"UIDs with known fraud in train: {len(fraud_uids)}\")\n\ndef apply_pp(preds, test_df, fraud_uids):\n    df = test_df.copy()\n    df['pred'] = preds\n    pp_train, pp_test = 0, 0\n    for uid_val in fraud_uids:\n        mask = df['uid'] == uid_val\n        if mask.any():\n            cur_max = df.loc[mask, 'pred'].max()\n            df.loc[mask, 'pred'] = max(cur_max, 0.95)\n            pp_train += mask.sum()\n    for uid_val, group in df.groupby('uid'):\n        if len(group) > 1 and (group['pred'] > 0.9).any():\n            df.loc[group.index, 'pred'] = group['pred'].max()\n            pp_test += 1\n    print(f\"  PP train: {pp_train} txns, PP test: {pp_test} UIDs\")\n    return df['pred'].values\n\nprint(\"Segmented blend + PP:\")\npreds_seg_pp = apply_pp(test_preds_seg, test_t, fraud_uids)\n\nsub = pd.DataFrame({'TransactionID': sample_submission['TransactionID'], 'isFraud': preds_seg_pp})\nsub.to_csv('final_mission.csv', index=False)\nprint(f\"Segmented sub: mean={sub['isFraud'].mean():.4f}\")\n\ndel train_t, test_t","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T19:45:32.856032Z","iopub.execute_input":"2026-03-17T19:45:32.856660Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Версия 1 ноутбука:\n\n| Cell | Содержание |\n|------|------------|\n|0 | Markdown: описание |\n|1 | Imports + config |\n|2 | Step 1: Load data |\n|3 | Step 2+3: FE + UID |\n|4 | Step 4: Aggregations |\n|5 | Step 5: Cleaning + feature selection |\n|6 | Step 6a: XGB CV |\n|7 | Step 6b: LGB CV |\n|8 | Step 6c: CB CV |\n|9 | CV ensemble summary |\n|10 | Step 9: Full train (XGB + LGB + CB) |\n|11 | Step 10: PP + 2 submissions |\n\nДве посылки: submission_victory_cv.csv и submission_victory_full.csv. Первая - предикт TS CV, вторая - обучаем на train, делаем предикт на test.\nПосылки дали результат: \n`0.93151` и `0.93398` соответственно\n\nCтал топ-3 на лб со скором `0.93398`","metadata":{}},{"cell_type":"code","source":"","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Версия 2\n\n| Что | Было | Стало |\n|-----|------|-------|\n| Веса | 0.4/0.3/0.3 | 0.4/0.4/0.2 |\n| UID | только uid1 | uid1 + uid2 (CPMP) |\n| uid2 агрегации | нет | +12 фичей (mean/std/nunique) |\n| uid2 shift-lag | нет | +6 фичей |\n| uid/uid2 FE | нет | +2 фичи (frequency) |\n| V-фичи | 120 из 339 | все 339 |\n| uid как фича для CB | удалён | оставлен (target encoding) |\n| n_estimators | 800 | 1500 |\n| learning_rate | 0.02 | 0.01 |\n| M5, card4 | удалены | M5 удалён, card4 удалён |\n\nИз неё сделал 3 посылки:\n\n1. Ансамбль (xgb, lgbm, cb - каждый обучен на ts cv split)\n2. Ансамбль (xgb, lgbm, cb - обучение на train, предсказание на test)\n3. Просто предсказание lgbm, потому что он у меня был самым сильным судя по TS валидации\n\nИтог: 2 посылка попала в топ-1 место с `0.94203`, на текущую секунду 17 марта 14:30:\n\n- AnnaPavlova_8 0.94143\n- _-- 0.93646","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"Идеи, как сделать еще лучше:\n\n1. вернуть сделать веса 0.35, 0.35, 0.3 (всё-таки катбуст отлично спрогрессировал) - сделал, `+0.0066`\n2. провести отбор фичей и добавить всё-таки фичи из `Isolation Forest` и других алгоритмов кластеризации\n3. преобразовать фичи с shift-ом к percintile для совпадения распределений\n4. повысить lr до 0.012\n5. запустить autoML на 2 часа поверх моих текущих моделей для ансамблирования","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"Версия 3: топ-1 (без дурака который заслал тру лейблы украденные на чёрном рынке)\nТекущий результат: 0.94203 (1 место)\nЛучший сабмит: full train ensemble (0.4 XGB + 0.4 LGB + 0.2 CB), n_est=1500, lr=0.01\n","metadata":{}},{"cell_type":"markdown","source":"\nЧто ещё не сделано (низковисящие фрукты из топ-решений)\n\nA. NaN → fillna(-999) для числовых (5 мин, потенциал +0.001-0.003)\nГде: cell 5 (cleaning), перед передачей в модели\nЧто: X_train[num_cols].fillna(-999), X_test[num_cols].fillna(-999)\nЗачем: LGBM/XGB переобучаются на NaN, давая спецобработку на каждом ноде дерева\nИсточник: Deotte writeup\n\nAA. Самое сильное место согласия между моделями — rank-based ensembling. Все три модели независимо советуют проверить rank averaging вместо обычного probability averaging. Это логично именно для твоей метрики: ROC AUC зависит от порядка, а не от калибровки вероятностей. Когда XGB, LGBM и CatBoost дают разные шкалы уверенности, weighted average probabilities может портить общий ранжир, хотя каждая модель по отдельности ранжирует хорошо. Поэтому rank blend — вероятно один из самых дешёвых и безопасных экспериментов прямо сейчас. Если к этому добавить внутри-модельное multi-seed averaging, как предлагают Claude Opus 4.6 Thinking и Gemini 3.1 Pro Thinking, можно получить двойной выигрыш: и более стабильные индивидуальные предикты, и более правильный финальный ранжир.\n\nAB. Второй очень надёжный кластер идей — не-UID агрегаты и sequential/velocity features. GPT-5.4 Thinking особенно полезно напоминает, что в решении Chris Deotte были не только UID_*, но и “классические” group stats по card1, card1+addr1, card1+addr1+P_emaildomain для TransactionAmt, D9, D11. Это критично, потому что такие entity-based признаки лучше переносятся на unknown clients, чем жёстко uid-завязанные конструкции. Claude Opus 4.6 Thinking и Gemini 3.1 Pro Thinking добавляют сюда следующий слой: transaction velocity, time_since_last, time_to_next, count за 24 часа, z-score суммы относительно истории UID. Практически это означает, что твой следующий FE-блок должен быть не “ещё один магический UID”, а моделирование поведения клиента во времени: насколько текущая транзакция похожа на прошлые, насколько она быстрая, насколько она выбивается по сумме и насколько ломает ожидаемую последовательность.\n\nAC. Третья крупная тема — развёртывание архитектуры ансамбля. Здесь самое интересное расхождение. GPT-5.4 Thinking предлагает, пожалуй, наиболее “в духе оригинального IEEE” шаг: segmented blending по known / unknown / questionable UID. Это прямо соответствует публичным выводам первого места: XGB лучше на known clients, LGBM — на unknown, CatBoost — на questionable. Если твой текущий blend использует одни глобальные веса для всех строк, ты, вероятно, ещё недобираешь именно тут. Claude Opus 4.6 Thinking, напротив, считает более надёжным приоритетом seed averaging и ProductCD-specific splits, а Gemini 3.1 Pro Thinking продвигает pseudo-labeling и temporal augmentation. Если оценивать expected value именно для текущего состояния top-1 решения, то segmented blending выглядит наиболее task-native: он использует уже известную структуру данных, а не привносит новую гипотезу. Поэтому это, вероятно, один из самых важных ещё не отработанных экспериментов.\n\nB. Normalize D-features по времени (10 мин, потенциал +0.001)\nГде: cell 3 (FE), после создания D1n/D4n/...\nЧто: df['D3_norm'] = df['D3'] - df.groupby('DT_week')['D3'].transform('mean')\nЗачем: D-фичи растут со временем, модель путает \"свежая транзакция\" с \"fraud\"\nПрименить к: D1, D2, D3, D4, D5, D10, D11, D15\n\nC. Больше interaction features (5 мин, потенциал +0.001-0.002)\nГде: cell 3 (FE)\nЧто добавить:\ncard1_card2, card1_card5, card2_addr1\nProductCD_card6, DeviceType_P_emaildomain\nЗачем: взаимодействия, которые дерево не найдёт за разумное число split'ов\n\nD. Count encoding (5 мин, потенциал +0.0005)\nГде: cell 4, рядом с frequency encoding\nЧто: df['card1_count'] = df['card1'].map(df['card1'].value_counts())\nЗачем: абсолютное число (не доля) — другая информация\n\nE. Adversarial validation для feature selection (20 мин, потенциал: защита от LB shake)\nГде: новая ячейка между cell 4 и cell 5\nЧто: обучить LGBM отличать train от test, удалить фичи с высоким importance\nЗачем: нестабильные фичи могут дать хороший public LB, но провалить private\n\nF. Time consistency test (15 мин, потенциал: защита от LB shake)\nГде: новая ячейка между cell 4 и cell 5\nЧто: обучить модель на month 1, predict month 2. Фичи с AUC < 0.5 — удалить\nЗачем: фичи, которые \"инвертируются\" со временем, опасны\n\nРекомендуемый порядок (одна посылка)\nA + C + D — быстрые изменения в FE/cleaning (15 мин кода)\nПереобучить full train (40 мин compute)\nЗасабмитить\nНа потом (если конкуренты догоняют)\nB — D-normalization\nE + F — adversarial + time consistency (защита private LB)","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}}]}