{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":31254,"databundleVersionId":3103714,"sourceType":"competition"}],"dockerImageVersionId":30918,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"#Установка библиотек\n!pip install catboost --quiet\n!pip install implicit --quiet\n\n#Импорт библиотек\nimport pandas as pd\nimport numpy as np\nfrom catboost import CatBoostClassifier, Pool, cv\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import LabelEncoder\nfrom tqdm import tqdm\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Оптимизированная загрузка данных\ndef load_data():\n    dtypes = {\n        'article_id': 'str',\n        'customer_id': 'str',\n        'price': 'float32',\n        'age': 'float32'\n    }\n    \n    try:\n        trans = pd.read_csv(\n            '/kaggle/input/h-and-m-personalized-fashion-recommendations/transactions_train.csv',\n            dtype=dtypes,\n            usecols=['customer_id', 'article_id', 't_dat', 'price']\n        )\n        cust = pd.read_csv(\n            '/kaggle/input/h-and-m-personalized-fashion-recommendations/customers.csv',\n            usecols=['customer_id', 'age']\n        )\n        arts = pd.read_csv(\n            '/kaggle/input/h-and-m-personalized-fashion-recommendations/articles.csv',\n            dtype={'article_id': 'str'},\n            usecols=['article_id', 'product_type_no', 'index_group_no']\n        )\n    except:\n        trans = pd.read_csv(\n            '../input/h-and-m-personalized-fashion-recommendations/transactions_train.csv',\n            dtype=dtypes,\n            usecols=['customer_id', 'article_id', 't_dat', 'price']\n        )\n        cust = pd.read_csv(\n            '../input/h-and-m-personalized-fashion-recommendations/customers.csv',\n            usecols=['customer_id', 'age']\n        )\n        arts = pd.read_csv(\n            '../input/h-and-m-personalized-fashion-recommendations/articles.csv',\n            dtype={'article_id': 'str'},\n            usecols=['article_id', 'product_type_no', 'index_group_no']\n        )\n    \n    cust['age'] = cust['age'].astype('float16')\n    return trans, cust, arts\n\n# Улучшенная предобработка\ndef prepare_features(trans, cust, arts, last_n_days=90):\n    trans['t_dat'] = pd.to_datetime(trans['t_dat'])\n    last_date = trans['t_dat'].max()\n    \n    trans = trans[trans['t_dat'] > (last_date - pd.Timedelta(days=last_n_days))].copy()\n    \n    df = trans.merge(\n        cust, \n        on='customer_id', \n        how='left'\n    )\n    df = df.merge(\n        arts.astype({'product_type_no': 'int8', 'index_group_no': 'int8'}),\n        on='article_id', \n        how='left'\n    )\n    \n    df['price_bin'] = pd.qcut(df['price'], q=10, labels=False, duplicates='drop').astype('int8')\n    df['age'] = df['age'].fillna(df['age'].median()).astype('float32')\n    \n    age_bins = [0, 18, 25, 35, 45, 55, 65, 100]\n    df['age_bin'] = pd.cut(\n        df['age'], \n        bins=age_bins, \n        labels=False,\n        include_lowest=True\n    ).fillna(len(age_bins)-1).astype('int8')\n    \n    df['days_since_purchase'] = (last_date - df['t_dat']).dt.days.astype('int16')\n    df['month'] = df['t_dat'].dt.month.astype('int8')\n    df['weekday'] = df['t_dat'].dt.weekday.astype('int8')\n    \n    return df\n\n# Загрузка и предобработка данных\nprint(\"Загрузка данных...\")\ntransactions, customers, articles = load_data()\n\nprint(\"Предобработка данных...\")\ndata = prepare_features(transactions, customers, articles)\n\n# Генерация обучающей выборки\ndef prepare_training_data(df, sample_size=1000000):\n    pos_samples = df.sample(min(sample_size, len(df)), random_state=42)\n    pos_samples['target'] = 1\n    \n    user_items = df.groupby('customer_id')['article_id'].apply(set).to_dict()\n    all_items = set(df['article_id'].unique())\n    \n    neg_samples = []\n    for _, row in tqdm(pos_samples.iterrows(), total=len(pos_samples)):\n        user_id = row['customer_id']\n        purchased = user_items.get(user_id, set())\n        available = list(all_items - purchased)\n        \n        if available:\n            neg_item = np.random.choice(available)\n            neg_row = row.copy()\n            neg_row['article_id'] = neg_item\n            neg_row['target'] = 0\n            neg_row['price'] = np.random.choice(df['price'].values)\n            neg_samples.append(neg_row)\n    \n    neg_samples = pd.DataFrame(neg_samples)\n    return pd.concat([pos_samples, neg_samples])\n\nprint(\"Создание обучающей выборки...\")\ntrain_data = prepare_training_data(data)\n\n# Обучение CatBoost\ndef train_catboost(df):\n    for col in ['product_type_no', 'index_group_no']:\n        le = LabelEncoder()\n        df[col] = le.fit_transform(df[col].astype(str))\n    \n    X = df.drop(['customer_id', 'article_id', 't_dat', 'target'], axis=1)\n    y = df['target']\n    \n    cat_features = ['price_bin', 'age_bin', 'month', 'weekday', 'product_type_no', 'index_group_no']\n    train_pool = Pool(X, y, cat_features=cat_features)\n    \n    model = CatBoostClassifier(\n        iterations=500,\n        learning_rate=0.05,\n        depth=8,\n        loss_function='CrossEntropy',\n        eval_metric='AUC',\n        random_seed=42,\n        task_type='CPU',\n        early_stopping_rounds=50,\n        verbose=100\n    )\n    \n    cv_params = model.get_params()\n    cv_params.update({\n        'loss_function': 'Logloss',  # или другая ваша функция потерь\n        'verbose': True\n    })\n    cv_results = cv(\n    params=cv_params,\n    pool=train_pool,\n    fold_count=3,\n    shuffle=True,\n    partition_random_seed=42,\n    plot=False\n    )\n    model.fit(train_pool)\n    return model\n\nprint(\"Обучение модели CatBoost...\")\nmodel = train_catboost(train_data)\n\n# Генерация рекомендаций\ndef generate_recommendations(model, customers, articles, trans, top_k=12):\n    item_popularity = trans['article_id'].value_counts().nlargest(1000).index.tolist()\n    \n    recommendations = []\n    for cust_id in tqdm(customers['customer_id'].unique()[:10000]):  # Ограничиваем для демо\n        user_data = customers[customers['customer_id'] == cust_id].iloc[0]\n        \n        candidates = list(set(item_popularity[:500] + \n                         np.random.choice(articles['article_id'].unique(), 200).tolist()))\n        \n        pred_df = pd.DataFrame({\n            'article_id': candidates,\n            'customer_id': cust_id,\n            'age': user_data['age'],\n            'price': np.random.choice(trans['price'].values, len(candidates)),\n            'days_since_purchase': 30,\n            'month': pd.to_datetime('now').month,\n            'weekday': pd.to_datetime('now').weekday()\n        })\n        \n        pred_df = pred_df.merge(articles, on='article_id', how='left')\n        \n        pred_df['price_bin'] = pd.qcut(\n            pred_df['price'], \n            q=10, \n            labels=False, \n            duplicates='drop'\n        ).astype('int8')\n        pred_df['age_bin'] = pd.cut(\n            pred_df['age'], \n            bins=[0, 18, 25, 35, 45, 55, 65, 100], \n            labels=False\n        ).astype('int8')\n        \n        X = pred_df.drop(['customer_id', 'article_id', 'age', 'price'], axis=1)\n        pred_df['score'] = model.predict_proba(X)[:, 1]\n        \n        top_recs = pred_df.nlargest(top_k, 'score')['article_id'].tolist()\n        recommendations.append({'customer_id': cust_id, 'prediction': ' '.join(top_recs)})\n    \n    return pd.DataFrame(recommendations)\n\nprint(\"Генерация рекомендаций...\")\nsubmission = generate_recommendations(model, customers, articles, transactions)\n\n# Сохранение результатов\nsubmission.to_csv('submission_catboost_optimized.csv', index=False)\nprint(\"Сабмит сохранен как submission_catboost_optimized.csv\")\n\nprint(\"\\nСтатистика рекомендаций:\")\nrec_counts = submission['prediction'].str.split().apply(len)\nprint(rec_counts.value_counts())","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-04-05T06:41:49.041031Z","iopub.status.idle":"2025-04-05T06:41:49.041384Z","shell.execute_reply":"2025-04-05T06:41:49.041236Z"}},"outputs":[],"execution_count":null}]}