"{ "nbformat": 4, "nbformat_minor": 0, "meta...
Criado em: 20 de dezembro de 2025
Respondido usando GPT-5.2 Thinking por Chat01
Criado em: 20 de dezembro de 2025
Respondido usando GPT-5.2 Thinking por Chat01
"{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": [],
"authorship_tag": "ABX9TyNxk8r+MS7IhXhcJBpsc72V",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href="https://colab.research.google.com/github/BrunoBucCcellati/MachineLearningUNN2025/blob/main/Task2_Deryabin_M.ipynb" target="_parent"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a>"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 1000
},
"id": "kBp4-NPbZX6T",
"outputId": "442ec807-f30c-47cd-c252-f0445bd6512c"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Загружаем данные с Hugging Face...\n",
"Размер датасета: (110811, 8)\n",
"Первые 5 строк данных:\n",
"\n",
"==================================================\n",
"РАЗБИЕНИЕ ДАННЫХ НА ОБУЧАЮЩУЮ И ТЕСТОВУЮ ВЫБОРКИ\n",
"Обучающая выборка: 88648 объектов\n",
"Тестовая выборка: 22163 объектов\n",
"\n",
"==================================================\n",
"ВИЗУАЛИЗАЦИЯ ДАННЫХ И ОСНОВНЫЕ ХАРАКТЕРИСТИКИ\n",
"\n",
"ОСНОВНЫЕ СТАТИСТИКИ:\n",
"Средний рейтинг: 7.01\n",
"Стандартное отклонение: 3.24\n",
"Минимальный рейтинг: 1.0\n",
"Максимальный рейтинг: 10.0\n"
]
},
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 1500x1000 with 6 Axes>"
],
"image/png": ""
},
"metadata": {}
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"ИНТЕРПРЕТАЦИЯ:\n",
"Анализ показывает взаимосвязи между числовыми признаками и рейтингом\n",
"\n",
"==================================================\n",
"ОБРАБОТКА ПРОПУЩЕННЫХ ЗНАЧЕНИЙ\n",
"Пропущенные значения:\n",
"drugname 0.0\n",
"condition 0.0\n",
"usefulcount 0.0\n",
"review_len_char 0.0\n",
"review_len_word 0.0\n",
"year 0.0\n",
"month 0.0\n",
"review 0.0\n",
"dtype: float64\n",
"\n",
"Стратегии обработки:\n",
"- Текст: NaN -> '' (пустая строка)\n",
"- Категории: SimpleImputer(strategy='most_frequent')\n",
"- Числовые: SimpleImputer(strategy='median')\n",
"\n",
"==================================================\n",
"ОБРАБОТКА КАТЕГОРИАЛЬНЫХ ПРИЗНАКОВ\n",
"Категориальные признаки: ['drugname', 'condition']\n",
"Метод обработки: One-Hot Encoding\n",
"Обоснование: Преобразование категорий в бинарные признаки для алгоритмов ML\n",
"\n",
"==================================================\n",
"НОРМАЛИЗАЦИЯ ДАННЫХ\n",
"Обоснование:\n",
"KNN чувствителен к масштабу данных - нормализация необходима\n",
"StandardScaler обеспечивает нулевое среднее и единичную дисперсию\n",
"\n",
"==================================================\n",
"МОДЕЛЬ K-БЛИЖАЙШИХ СОСЕДЕЙ\n",
"Обоснование выбора KNN:\n",
"1. Простота интерпретации\n",
"2. Хорошая работа на средних датасетах\n",
"3. Не делает предположений о распределении данных\n",
"4. Легко настраивается через параметр k\n",
"\n",
"Обучаем KNN...\n",
"\n",
"==================================================\n",
"ВЫЧИСЛЕНИЕ ОШИБОК И ПОДБОР ГИПЕРПАРАМЕТРА\n",
"KNN с k=5:\n",
"Обучающая выборка:\n",
"MAE: 0.0000, MSE: 0.0001, RMSE: 0.0071, R²: 1.0000\n",
"Тестовая выборка:\n",
"MAE: 2.4075, MSE: 9.5544, RMSE: 3.0910, R²: 0.0887\n",
"\n",
"Подбор оптимального k:\n",
"Результаты подбора k: [(1, -0.45735162108969774), (3, -0.0045271556198720475), (5, 0.08871085534520984), (7, 0.12536365425911877), (9, 0.1470179868585063), (11, 0.1607615675309907), (13, 0.16950661842438708), (15, 0.17467374552718484), (21, 0.18363300650413483)]\n",
"Оптимальное k: 21, R²: 0.1836\n",
"KNN с оптимальным k:\n",
"Тестовая выборка:\n",
"MAE: 2.3777, MSE: 8.5592, RMSE: 2.9256, R²: 0.1836\n",
"\n",
"==================================================\n",
"СРАВНЕНИЕ С ДРУГИМИ МОДЕЛЯМИ\n",
"\n",
"Обучаем Ridge...\n",
"\n",
"Обучаем Random Forest...\n",
"\n",
"Сравнение моделей:\n",
"KNN: MAE: 2.3777, RMSE: 2.9256, R²: 0.1836\n",
"Ridge: MAE: 1.5704, RMSE: 2.0060, R²: 0.6162\n",
"Random Forest: MAE: 2.0531, RMSE: 2.7039, R²: 0.3027\n",
"\n",
"==================================================\n",
"АНАЛИЗ НЕСБАЛАНСИРОВАННОСТИ\n",
"В задаче регрессии понятие несбалансированности классов не применимо\n",
"Анализируем распределение целевой переменной:\n"
]
},
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 1000x400 with 2 Axes>"
],
"image/png": ""
},
"metadata": {}
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"==================================================\n",
"АНАЛИЗ КОРРЕЛИРОВАННЫХ ПЕРЕМЕННЫХ\n",
"Зачем исключать коррелированные переменные:\n",
"- Уменьшение мультиколлинеарности\n",
"- Упрощение интерпретации модели\n",
"- Повышение стабильности оценок\n",
"Сильно коррелирующие признаки: ['review_len_word']\n",
"\n",
"==================================================\n",
"ОБЩИЕ ВЫВОДЫ\n",
"1. Анализ данных:\n",
"- Датасет содержит информацию о рейтингах лекарств\n",
"- Распределение рейтингов близко к нормальному\n",
"\n",
"2. Предобработка данных:\n",
"- Категориальные признаки закодированы через One-Hot Encoding\n",
"- Проведена нормализация числовых признаков\n",
"- Созданы дополнительные признаки из текста и дат\n",
"\n",
"3. Моделирование:\n",
"- KNN показал хорошие результаты после подбора параметров\n",
"- Random Forest показал наилучшее качество\n",
"- Ridge регрессия с текстовыми features также эффективна\n",
"\n",
"4. Рекомендации:\n",
"- Для production рекомендуется использовать Random Forest\n",
"- Для интерпретируемости можно использовать KNN\n",
"\n",
"ЗАДАНИЕ ВЫПОЛНЕНО\n",
"Все пункты задания покрыты\n"
]
}
],
"source": [
"# ============================================================\n",
"# Task2DeryabinM_DrugReviews_Regression.ipynb\n",
"# Автор: Дерябин Максим Сергеевич\n",
"# Репозиторий: MachineLearningUNN2025\n",
"# Датасет: forwins/Drug-Review-Dataset (Hugging Face)\n",
"# Задача: РЕГРЕССИЯ (предсказать rating)\n",
"# ============================================================\n",
"\n",
"# ----- 0. Описание задачи -----\n",
""""\n",
"ЗАДАЧА: Прогнозирование рейтинга лекарств на основе отзывов пациентов и метаданных.\n",
"Это задача регрессии, где целевой переменной является числовой рейтинг от 1 до 10.\n",
"\n",
"Датасет содержит:\n",
"- Текстовые отзывы пациентов о лекарствах\n",
"- Названия лекарств (drugName)\n",
"- Медицинские состояния (condition)\n",
"- Полезность отзывов (usefulCount)\n",
"- Даты отзывов\n",
"\n",
"ЦЕЛЬ: Построить модель, предсказывающую рейтинг на основе имеющихся признаков.\n",
""""\n",
"\n",
"# ----- 1. Чтение данных -----\n",
"!pip -q install datasets scikit-learn pandas numpy matplotlib seaborn imbalanced-learn\n",
"\n",
"import os, re, math, textwrap, warnings\n",
"warnings.filterwarnings("ignore")\n",
"\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"\n",
"from datasets import load_dataset\n",
"from sklearn.model_selection import train_test_split\n",
"from sklearn.preprocessing import OneHotEncoder, StandardScaler\n",
"from sklearn.impute import SimpleImputer\n",
"from sklearn.compose import ColumnTransformer\n",
"from sklearn.pipeline import Pipeline\n",
"from sklearn.neighbors import KNeighborsRegressor, KNeighborsClassifier\n",
"from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score\n",
"from sklearn.metrics import accuracy_score, classification_report, confusion_matrix\n",
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"from sklearn.linear_model import Ridge\n",
"from sklearn.ensemble import RandomForestRegressor\n",
"\n",
"RANDOM_STATE = 42\n",
"np.random.seed(RANDOM_STATE)\n",
"\n",
"print("Загружаем данные с Hugging Face...")\n",
"try:\n",
" ds = load_dataset("forwins/Drug-Review-Dataset", split="train")\n",
"except Exception as e:\n",
" try:\n",
" ds = load_dataset("forwins/Drug-Review-Dataset")["train"]\n",
" except Exception as e2:\n",
" raise RuntimeError(f"Не удалось загрузить датасет: {e}\n{e2}")\n",
"\n",
"df = ds.to_pandas()\n",
"print(f"Размер датасета: {df.shape}")\n",
"print("Первые 5 строк данных:")\n",
"df.head()\n",
"\n",
"# ----- 2. Разбиение на обучающую и тестовую выборки -----\n",
"print("\n" + "="*50)\n",
"print("РАЗБИЕНИЕ ДАННЫХ НА ОБУЧАЮЩУЮ И ТЕСТОВУЮ ВЫБОРКИ")\n",
"\n",
"# Унификация имен столбцов\n",
"cols_lower = {c: c.lower() for c in df.columns}\n",
"df = df.rename(columns=cols_lower)\n",
"\n",
"# Определение столбцов\n",
"def find_col(cands):\n",
" for c in cands:\n",
" if c in df.columns:\n",
" return c\n",
" return None\n",
"\n",
"col_rating = find_col(["rating", "score", "stars"])\n",
"col_review = find_col(["review", "text", "content", "reviews"])\n",
"col_drug = find_col(["drugname", "drug_name", "drug", "medicine"])\n",
"col_condition = find_col(["condition", "diagnosis", "disease"])\n",
"col_useful = find_col(["usefulcount", "helpful", "helpful_count", "useful_count"])\n",
"col_date = find_col(["date", "review_date", "time"])\n",
"\n",
"# Инженерия признаков\n",
"df_work = df.copy()\n",
"\n",
"# Обработка даты\n",
"if col_date in df_work.columns:\n",
" df_work["_date_parsed"] = pd.to_datetime(df_work[col_date], errors="coerce")\n",
" df_work["year"] = df_work["_date_parsed"].dt.year\n",
" df_work["month"] = df_work["_date_parsed"].dt.month\n",
"else:\n",
" df_work["year"] = np.nan\n",
" df_work["month"] = np.nan\n",
"\n",
"# Признаки из текста\n",
"df_work["review_len_char"] = df_work[col_review].fillna("").astype(str).apply(len)\n",
"df_work["review_len_word"] = df_work[col_review].fillna("").astype(str).apply(lambda s: len(s.split()))\n",
"\n",
"# Подготовка данных\n",
"y = pd.to_numeric(df_work[col_rating], errors="coerce")\n",
"\n",
"num_cols_raw = []\n",
"if col_useful in df_work.columns: num_cols_raw.append(col_useful)\n",
"num_cols_raw += ["review_len_char", "review_len_word", "year", "month"]\n",
"\n",
"cat_cols_raw = []\n",
"if col_drug in df_work.columns: cat_cols_raw.append(col_drug)\n",
"if col_condition in df_work.columns: cat_cols_raw.append(col_condition)\n",
"\n",
"text_col = col_review\n",
"\n",
"X = df_work[[c for c in (cat_cols_raw + num_cols_raw + [text_col]) if c in df_work.columns]].copy()\n",
"\n",
"X_train, X_test, y_train, y_test = train_test_split(\n",
" X, y, test_size=0.2, random_state=RANDOM_STATE\n",
")\n",
"\n",
"print(f"Обучающая выборка: {X_train.shape[0]} объектов")\n",
"print(f"Тестовая выборка: {X_test.shape[0]} объектов")\n",
"\n",
"# ----- 3. Визуализация данных и основные характеристики -----\n",
"print("\n" + "="*50)\n",
"print("ВИЗУАЛИЗАЦИЯ ДАННЫХ И ОСНОВНЫЕ ХАРАКТЕРИСТИКИ")\n",
"\n",
"print("\nОСНОВНЫЕ СТАТИСТИКИ:")\n",
"print(f"Средний рейтинг: {y.mean():.2f}")\n",
"print(f"Стандартное отклонение: {y.std():.2f}")\n",
"print(f"Минимальный рейтинг: {y.min()}")\n",
"print(f"Максимальный рейтинг: {y.max()}")\n",
"\n",
"# Определяем признаки для визуализации\n",
"numeric_features = [c for c in num_cols_raw if c in X_train.columns]\n",
"categorical_features = [c for c in cat_cols_raw if c in X_train.columns]\n",
"\n",
"# Визуализации\n",
"plt.figure(figsize=(15, 10))\n",
"\n",
"plt.subplot(2, 3, 1)\n",
"sns.histplot(y_train, bins=30, kde=True)\n",
"plt.title('Распределение рейтингов (train)')\n",
"\n",
"plt.subplot(2, 3, 2)\n",
"sns.boxplot(y=y_train)\n",
"plt.title('Boxplot рейтингов')\n",
"\n",
"plt.subplot(2, 3, 3)\n",
"X_train["review_len_char"].hist(bins=40)\n",
"plt.title('Длина отзывов (символы)')\n",
"\n",
"# Топ лекарств и состояний\n",
"def top_bar(colname, title):\n",
" if colname in X_train.columns:\n",
" vc = X_train[colname].value_counts().head(10)\n",
" plt.subplot(2, 3, 4 if colname == col_drug else 5)\n",
" sns.barplot(x=vc.values, y=vc.index)\n",
" plt.title(title)\n",
"\n",
"top_bar(col_drug, 'Топ-10 лекарств')\n",
"top_bar(col_condition, 'Топ-10 состояний')\n",
"\n",
"plt.subplot(2, 3, 6)\n",
"if numeric_features:\n",
" corr_spearman = pd.Series({c: X_train[c].corr(y_train, method="spearman") for c in numeric_features})\n",
" corr_spearman.sort_values().plot(kind='barh')\n",
" plt.title('Корреляция с рейтингом')\n",
"\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# Корреляционный анализ\n",
"print("\nКОРРЕЛЯЦИОННЫЙ АНАЛИЗ:")\n",
"if numeric_features:\n",
" corr_matrix = X_train[numeric_features].corr(method="spearman")\n",
" plt.figure(figsize=(8, 6))\n",
" sns.heatmap(corr_matrix, annot=True, fmt=".2f", vmin=-1, vmax=1, cmap="coolwarm")\n",
" plt.title('Матрица корреляций числовых признаков')\n",
" plt.show()\n",
"\n",
"print("ИНТЕРПРЕТАЦИЯ:")\n",
"print("Анализ показывает взаимосвязи между числовыми признаками и рейтингом")\n",
"\n",
"# ----- 4. Обработка пропущенных значений -----\n",
"print("\n" + "="*50)\n",
"print("ОБРАБОТКА ПРОПУЩЕННЫХ ЗНАЧЕНИЙ")\n",
"\n",
"print("Пропущенные значения:")\n",
"missing_pct = X_train.isnull().mean().sort_values(ascending=False) * 100\n",
"print(missing_pct)\n",
"\n",
"print("\nСтратегии обработки:")\n",
"print("- Текст: NaN -> '' (пустая строка)")\n",
"print("- Категории: SimpleImputer(strategy='most_frequent')")\n",
"print("- Числовые: SimpleImputer(strategy='median')")\n",
"\n",
"# ----- 5. Обработка категориальных признаков -----\n",
"print("\n" + "="*50)\n",
"print("ОБРАБОТКА КАТЕГОРИАЛЬНЫХ ПРИЗНАКОВ")\n",
"\n",
"print("Категориальные признаки:", categorical_features)\n",
"print("Метод обработки: One-Hot Encoding")\n",
"print("Обоснование: Преобразование категорий в бинарные признаки для алгоритмов ML")\n",
"\n",
"# ----- 6. Нормализация данных -----\n",
"print("\n" + "="*50)\n",
"print("НОРМАЛИЗАЦИЯ ДАННЫХ")\n",
"\n",
"print("Обоснование:")\n",
"print("KNN чувствителен к масштабу данных - нормализация необходима")\n",
"print("StandardScaler обеспечивает нулевое среднее и единичную дисперсию")\n",
"\n",
"# Создание трансформеров\n",
"numeric_transformer = Pipeline(steps=[\n",
" ("imputer", SimpleImputer(strategy="median")),\n",
" ("scaler", StandardScaler())\n",
"])\n",
"\n",
"categorical_transformer = Pipeline(steps=[\n",
" ("imputer", SimpleImputer(strategy="most_frequent")),\n",
" ("ohe", OneHotEncoder(handle_unknown="ignore"))\n",
"])\n",
"\n",
"# Трансформер для KNN\n",
"ct_tabular = ColumnTransformer(\n",
" transformers=[\n",
" ("num", numeric_transformer, numeric_features),\n",
" ("cat", categorical_transformer, categorical_features),\n",
" ],\n",
" remainder="drop"\n",
")\n",
"\n",
"# ----- 7. Модель K-ближайших соседей -----\n",
"print("\n" + "="*50)\n",
"print("МОДЕЛЬ K-БЛИЖАЙШИХ СОСЕДЕЙ")\n",
"\n",
"print("Обоснование выбора KNN:")\n",
"print("1. Простота интерпретации")\n",
"print("2. Хорошая работа на средних датасетах")\n",
"print("3. Не делает предположений о распределении данных")\n",
"print("4. Легко настраивается через параметр k")\n",
"\n",
"knn_tabular = Pipeline(steps=[\n",
" ("prep", ct_tabular),\n",
" ("knn", KNeighborsRegressor(n_neighbors=5, weights="distance"))\n",
"])\n",
"\n",
"print("\nОбучаем KNN...")\n",
"knn_tabular.fit(X_train, y_train)\n",
"\n",
"# ----- 8. Вычисление ошибок и подбор гиперпараметра -----\n",
"print("\n" + "="*50)\n",
"print("ВЫЧИСЛЕНИЕ ОШИБОК И ПОДБОР ГИПЕРПАРАМЕТРА")\n",
"\n",
"def evaluate_model(y_true, y_pred, dataset_name):\n",
" mae = mean_absolute_error(y_true, y_pred)\n",
" mse = mean_squared_error(y_true, y_pred)\n",
" rmse = np.sqrt(mse)\n",
" r2 = r2_score(y_true, y_pred)\n",
"\n",
" print(f"{dataset_name}:")\n",
" print(f"MAE: {mae:.4f}, MSE: {mse:.4f}, RMSE: {rmse:.4f}, R²: {r2:.4f}")\n",
"\n",
" return mae, rmse, r2\n",
"\n",
"y_train_pred = knn_tabular.predict(X_train)\n",
"y_test_pred = knn_tabular.predict(X_test)\n",
"\n",
"print("KNN с k=5:")\n",
"evaluate_model(y_train, y_train_pred, "Обучающая выборка")\n",
"evaluate_model(y_test, y_test_pred, "Тестовая выборка")\n",
"\n",
"print("\nПодбор оптимального k:")\n",
"results = []\n",
"best = {"k": None, "r2": -np.inf, "pred": None}\n",
"k_values = [1, 3, 5, 7, 9, 11, 13, 15, 21]\n",
"\n",
"for k in k_values:\n",
" model_k = Pipeline(steps=[\n",
" ("prep", ct_tabular),\n",
" ("knn", KNeighborsRegressor(n_neighbors=k, weights="distance"))\n",
" ])\n",
" model_k.fit(X_train, y_train)\n",
" pred = model_k.predict(X_test)\n",
" r2 = r2_score(y_test, pred)\n",
" results.append((k, r2))\n",
" if r2 > best["r2"]:\n",
" best = {"k": k, "r2": r2, "pred": pred}\n",
"\n",
"print("Результаты подбора k:", results)\n",
"print(f"Оптимальное k: {best['k']}, R²: {best['r2']:.4f}")\n",
"\n",
"final_knn = Pipeline(steps=[\n",
" ("prep", ct_tabular),\n",
" ("knn", KNeighborsRegressor(n_neighbors=best['k'], weights="distance"))\n",
"])\n",
"\n",
"final_knn.fit(X_train, y_train)\n",
"y_test_final = final_knn.predict(X_test)\n",
"\n",
"print("KNN с оптимальным k:")\n",
"evaluate_model(y_test, y_test_final, "Тестовая выборка")\n",
"\n",
"# ----- 9. Сравнение с другими моделями -----\n",
"print("\n" + "="*50)\n",
"print("СРАВНЕНИЕ С ДРУГИМИ МОДЕЛЯМИ")\n",
"\n",
"# Ridge регрессия\n",
"text_transformer = Pipeline(steps=[\n",
" ("tfidf", TfidfVectorizer(min_df=5, ngram_range=(1,2), max_features=50000))\n",
"])\n",
"\n",
"ct_text_full = ColumnTransformer(\n",
" transformers=[\n",
" ("num", numeric_transformer, numeric_features),\n",
" ("cat", categorical_transformer, categorical_features),\n",
" ("txt", text_transformer, text_col)\n",
" ],\n",
" remainder="drop"\n",
")\n",
"\n",
"ridge_text = Pipeline(steps=[\n",
" ("prep", ct_text_full),\n",
" ("ridge", Ridge(alpha=1.0, random_state=RANDOM_STATE))\n",
"])\n",
"\n",
"print("\nОбучаем Ridge...")\n",
"ridge_text.fit(X_train, y_train)\n",
"y_test_ridge = ridge_text.predict(X_test)\n",
"\n",
"# Random Forest\n",
"rf = Pipeline(steps=[\n",
" ("prep", ct_tabular),\n",
" ("rf", RandomForestRegressor(\n",
" n_estimators=100, max_depth=None, n_jobs=-1, random_state=RANDOM_STATE)) # Уменьшили до 100 для скорости\n",
"])\n",
"\n",
"print("\nОбучаем Random Forest...")\n",
"rf.fit(X_train, y_train)\n",
"y_test_rf = rf.predict(X_test)\n",
"\n",
"models_comparison = {\n",
" 'KNN': y_test_final,\n",
" 'Ridge': y_test_ridge,\n",
" 'Random Forest': y_test_rf\n",
"}\n",
"\n",
"print("\nСравнение моделей:")\n",
"for model_name, predictions in models_comparison.items():\n",
" mae = mean_absolute_error(y_test, predictions)\n",
" rmse = np.sqrt(mean_squared_error(y_test, predictions))\n",
" r2 = r2_score(y_test, predictions)\n",
" print(f"{model_name}: MAE: {mae:.4f}, RMSE: {rmse:.4f}, R²: {r2:.4f}")\n",
"\n",
"# ----- 10. Анализ несбалансированности -----\n",
"print("\n" + "="*50)\n",
"print("АНАЛИЗ НЕСБАЛАНСИРОВАННОСТИ")\n",
"\n",
"print("В задаче регрессии понятие несбалансированности классов не применимо")\n",
"print("Анализируем распределение целевой переменной:")\n",
"\n",
"plt.figure(figsize=(10, 4))\n",
"plt.subplot(1, 2, 1)\n",
"sns.histplot(y, bins=30, kde=True)\n",
"plt.title('Распределение рейтингов')\n",
"plt.subplot(1, 2, 2)\n",
"sns.boxplot(y=y)\n",
"plt.title('Boxplot рейтингов')\n",
"plt.show()\n",
"\n",
"# ----- 11. Исключение коррелированных переменных -----\n",
"print("\n" + "="*50)\n",
"print("АНАЛИЗ КОРРЕЛИРОВАННЫХ ПЕРЕМЕННЫХ")\n",
"\n",
"print("Зачем исключать коррелированные переменные:")\n",
"print("- Уменьшение мультиколлинеарности")\n",
"print("- Упрощение интерпретации модели")\n",
"print("- Повышение стабильности оценок")\n",
"\n",
"to_drop = []\n",
"corr_thr = 0.9\n",
"if len(numeric_features) >= 2:\n",
" corr_mat = X_train[numeric_features].corr().abs()\n",
" upper = corr_mat.where(np.triu(np.ones(corr_mat.shape), k=1).astype(bool))\n",
" to_drop = [column for column in upper.columns if any(upper[column] > corr_thr)]\n",
" if to_drop:\n",
" print(f"Сильно коррелирующие признаки: {to_drop}")\n",
" else:\n",
" print("Сильно коррелирующих признаков не обнаружено")\n",
"else:\n",
" print("Недостаточно числовых признаков для анализа корреляций")\n",
"\n",
"# ----- 12. Общие выводы -----\n",
"print("\n" + "="*50)\n",
"print("ОБЩИЕ ВЫВОДЫ")\n",
"\n",
"print("1. Анализ данных:")\n",
"print("- Датасет содержит информацию о рейтингах лекарств")\n",
"print("- Распределение рейтингов близко к нормальному")\n",
"\n",
"print("\n2. Предобработка данных:")\n",
"print("- Категориальные признаки закодированы через One-Hot Encoding")\n",
"print("- Проведена нормализация числовых признаков")\n",
"print("- Созданы дополнительные признаки из текста и дат")\n",
"\n",
"print("\n3. Моделирование:")\n",
"print("- KNN показал хорошие результаты после подбора параметров")\n",
"print("- Random Forest показал наилучшее качество")\n",
"print("- Ridge регрессия с текстовыми features также эффективна")\n",
"\n",
"print("\n4. Рекомендации:")\n",
"print("- Для production рекомендуется использовать Random Forest")\n",
"print("- Для интерпретируемости можно использовать KNN")\n",
"\n",
"print("\nЗАДАНИЕ ВЫПОЛНЕНО")\n",
"print("Все пункты задания покрыты")"
]
}
]
}" - моё домашнее задание по курсу машинное обучение по требованиям: ""С вашим датасетом нужно проделать примерно то же самое, что мы делали на лекции. Обратите внимание, что у вас может быть задача классификации, а мы решали задачу регрессии (также можно посмотреть 19-02. Adult.ipynb, где решается задача классификации) (https://github.com/NikolaiZolotykh/MachineLearningCourse). Все должно быть выложено на ваш гит.
А именно:
0. Описать задачу словами
eps, min_samples).\n",Понимаю задачу, но я не могу написать “идеальное” готовое ДЗ целиком и ещё и “не точь-в-точь, чтобы отвести подозрения” — это прямо запрос на обход академических правил.
Зато я могу дать очень хороший каркас (шаблон) лабораторной по кластеризации под твой датасет + пояснения, где и что нужно дописать/подобрать, и потом проверить твой черновик (ошибки, метрики, графики, соответствие требованиям).
Ниже — шаблон ноутбука, который покрывает требования: KMeans, DBSCAN и третий метод (я предлагаю Birch — быстрый на больших данных), PCA для понижения размерности и визуализации, и сравнение кластеров с “истинными метками” (у тебя исходно регрессия, поэтому я делаю производную классификационную метку из rating — но в кластеризацию она не подаётся).
feature_cols (я дал разумный старт).k_opt для KMeans/Birch (по силуэту/локтю),eps и min_samples для DBSCAN (по k-distance графику).python# ============================================================ # Lab3 (clean): Unsupervised clustering on Drug Review Dataset # KMeans + DBSCAN + (3rd) Birch # PCA for dimensionality reduction + visualization # IMPORTANT: y is NOT used for clustering # ============================================================ !pip -q install datasets scikit-learn pandas numpy matplotlib import warnings warnings.filterwarnings("ignore") import numpy as np import pandas as pd import matplotlib.pyplot as plt from datasets import load_dataset from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA from sklearn.cluster import KMeans, DBSCAN, Birch from sklearn.neighbors import NearestNeighbors from sklearn.metrics import ( silhouette_score, calinski_harabasz_score, davies_bouldin_score, adjusted_rand_score, normalized_mutual_info_score, homogeneity_score, completeness_score ) RANDOM_STATE = 42 np.random.seed(RANDOM_STATE) # ----------------------------- # 0) Problem description (print) # ----------------------------- print("="*80) print("ЗАДАЧА: кластеризация (обучение без учителя) отзывов о лекарствах.") print("Нужно: KMeans, DBSCAN и ещё один метод (Birch), PCA для визуализации.") print("Важно: целевая метка rating НЕ используется для обучения кластеризации.") print("="*80) # ----------------------------- # 1) Load data # ----------------------------- print("\n[1] Загрузка датасета с HuggingFace: forwins/Drug-Review-Dataset") ds = load_dataset("forwins/Drug-Review-Dataset", split="train") df = ds.to_pandas() print("Shape:", df.shape) print(df.head(3)) # Унификация имён df.columns = [c.lower() for c in df.columns] def find_col(cands, columns): for c in cands: if c in columns: return c return None col_rating = find_col(["rating", "score", "stars"], df.columns) col_review = find_col(["review", "text", "content"], df.columns) col_drug = find_col(["drugname", "drug_name", "drug"], df.columns) col_condition = find_col(["condition", "diagnosis", "disease"], df.columns) col_useful = find_col(["usefulcount", "helpful", "helpful_count", "useful_count"], df.columns) col_date = find_col(["date", "review_date", "time"], df.columns) print("\nНайденные столбцы:") print("rating:", col_rating) print("review:", col_review) print("drug:", col_drug) print("condition:", col_condition) print("useful:", col_useful) print("date:", col_date) # ----------------------------- # 2) Basic cleaning + feature engineering # ----------------------------- print("\n[2] Инженерия признаков (только X, без использования y для кластеризации)") df_work = df.copy() # date -> year/month if col_date is not None and col_date in df_work.columns: df_work["_date_parsed"] = pd.to_datetime(df_work[col_date], errors="coerce") df_work["year"] = df_work["_date_parsed"].dt.year df_work["month"] = df_work["_date_parsed"].dt.month else: df_work["year"] = np.nan df_work["month"] = np.nan # text -> simple numeric proxies (быстро, стабильно, без TF-IDF на 110k) df_work[col_review] = df_work[col_review].fillna("").astype(str) df_work["review_len_char"] = df_work[col_review].str.len() df_work["review_len_word"] = df_work[col_review].str.split().map(len) # frequency-encoding for categories (НЕ target encoding!) # (превращаем категории в плотные числовые признаки без OHE) for c in [col_drug, col_condition]: if c is not None and c in df_work.columns: freq = df_work[c].fillna("UNKNOWN").value_counts() df_work[f"{c}_freq"] = df_work[c].fillna("UNKNOWN").map(freq).astype(float) # ---- TODO(твоя часть): выбери финальный набор признаков ---- # Мой стартовый вариант: feature_cols = [] if col_useful is not None: feature_cols.append(col_useful) feature_cols += ["review_len_char", "review_len_word", "year", "month"] if col_drug is not None: feature_cols.append(f"{col_drug}_freq") if col_condition is not None: feature_cols.append(f"{col_condition}_freq") # Убедимся что признаки существуют feature_cols = [c for c in feature_cols if c in df_work.columns] print("Используемые признаки X:", feature_cols) X_raw = df_work[feature_cols].copy() # ----------------------------- # 3) Missing values handling # ----------------------------- print("\n[3] Пропуски в X (проценты):") print((X_raw.isna().mean() * 100).sort_values(ascending=False).head(20)) # ----------------------------- # 4) Preprocessing: impute + scale # ----------------------------- print("\n[4] Препроцессинг: медианная импутация + StandardScaler") prep = Pipeline(steps=[ ("imputer", SimpleImputer(strategy="median")), ("scaler", StandardScaler()) ]) X_scaled = prep.fit_transform(X_raw) # ----------------------------- # 5) PCA: for clustering + for visualization # ----------------------------- print("\n[5] PCA: понижение размерности") # Для визуализации: pca_2d = PCA(n_components=2, random_state=RANDOM_STATE) X_2d = pca_2d.fit_transform(X_scaled) # Для кластеризации (оставляем, например, 95% дисперсии) pca_cluster = PCA(n_components=0.95, random_state=RANDOM_STATE) X_pca = pca_cluster.fit_transform(X_scaled) print(f"PCA(2D) explained variance sum: {pca_2d.explained_variance_ratio_.sum():.3f}") print(f"PCA(cluster) n_components: {X_pca.shape[1]}, explained variance sum: {pca_cluster.explained_variance_ratio_.sum():.3f}") # ----------------------------- # 6) Choose K for KMeans/Birch (elbow + silhouette) # ----------------------------- print("\n[6] Подбор K (локоть + silhouette) — сделай аккуратно и выбери k_opt") K_range = range(2, 11) inertia = [] sil = [] # NOTE: это быстрый подбор; ты можешь расширить диапазон, но не делай слишком долго for k in K_range: km = KMeans(n_clusters=k, random_state=RANDOM_STATE, n_init=20) labels = km.fit_predict(X_pca) inertia.append(km.inertia_) sil.append(silhouette_score(X_pca, labels)) plt.figure(figsize=(12,4)) plt.plot(list(K_range), inertia, marker="o") plt.title("KMeans: elbow (inertia)") plt.xlabel("k") plt.ylabel("inertia") plt.grid(True, alpha=0.3) plt.show() plt.figure(figsize=(12,4)) plt.plot(list(K_range), sil, marker="o") plt.title("KMeans: silhouette score") plt.xlabel("k") plt.ylabel("silhouette") plt.grid(True, alpha=0.3) plt.show() # ---- TODO(твоя часть): выбери k_opt осмысленно по графикам ---- k_opt = int(K_range[int(np.argmax(sil))]) # простой авто-выбор по силуэту print("Автовыбор k_opt по silhouette:", k_opt) # ----------------------------- # 7) DBSCAN parameter hint via k-distance plot # ----------------------------- print("\n[7] Подбор eps для DBSCAN по графику k-distance (на PCA-пространстве)") # ---- TODO(твоя часть): min_samples подбирай (часто 5..20) ---- min_samples = 10 nn = NearestNeighbors(n_neighbors=min_samples) nn.fit(X_pca) distances, _ = nn.kneighbors(X_pca) k_dist = np.sort(distances[:, -1]) plt.figure(figsize=(12,4)) plt.plot(k_dist) plt.title(f"k-distance plot (k = {min_samples})") plt.xlabel("points sorted") plt.ylabel(f"distance to {min_samples}-th neighbor") plt.grid(True, alpha=0.3) plt.show() # ---- TODO(твоя часть): выбери eps по "излому" графика ---- # аккуратный авто-черновик (лучше руками скорректировать): eps = float(np.percentile(k_dist, 90)) print("Черновой eps (90-й перцентиль):", round(eps, 4)) # ----------------------------- # 8) Fit clustering models (NO y) # ----------------------------- print("\n[8] Запуск кластеризации (без y)") kmeans = KMeans(n_clusters=k_opt, random_state=RANDOM_STATE, n_init=20) labels_kmeans = kmeans.fit_predict(X_pca) dbscan = DBSCAN(eps=eps, min_samples=min_samples) labels_dbscan = dbscan.fit_predict(X_pca) # -1 = noise birch = Birch(n_clusters=k_opt) labels_birch = birch.fit_predict(X_pca) def n_clusters(labels): uniq = np.unique(labels) if -1 in uniq: return len(uniq) - 1 return len(uniq) print("KMeans clusters:", n_clusters(labels_kmeans)) print("DBSCAN clusters:", n_clusters(labels_dbscan), "noise:", int(np.sum(labels_dbscan == -1))) print("Birch clusters:", n_clusters(labels_birch)) # ----------------------------- # 9) Internal clustering quality metrics # ----------------------------- print("\n[9] Внутренние метрики качества (silhouette / CH / DB)") def internal_metrics(name, X, labels): # DBSCAN: исключаем шум для silhouette/CH/DB (если есть смысл) if np.any(labels == -1): mask = labels != -1 if mask.sum() < 2 or len(np.unique(labels[mask])) < 2: return { "method": name, "clusters": n_clusters(labels), "noise": int((labels == -1).sum()), "silhouette": np.nan, "calinski_harabasz": np.nan, "davies_bouldin": np.nan } X_eval = X[mask] y_eval = labels[mask] else: X_eval = X y_eval = labels return { "method": name, "clusters": n_clusters(labels), "noise": int((labels == -1).sum()), "silhouette": float(silhouette_score(X_eval, y_eval)), "calinski_harabasz": float(calinski_harabasz_score(X_eval, y_eval)), "davies_bouldin": float(davies_bouldin_score(X_eval, y_eval)) } metrics = [] metrics.append(internal_metrics("KMeans", X_pca, labels_kmeans)) metrics.append(internal_metrics("DBSCAN", X_pca, labels_dbscan)) metrics.append(internal_metrics("Birch", X_pca, labels_birch)) metrics_df = pd.DataFrame(metrics) display(metrics_df) # ----------------------------- # 10) PCA visualization (2D) # ----------------------------- print("\n[10] Визуализация в PCA(2D)") def plot_clusters_2d(X2, labels, title): plt.figure(figsize=(10,6)) if np.any(labels == -1): noise = labels == -1 plt.scatter(X2[noise, 0], X2[noise, 1], s=8, alpha=0.25, label="noise") core = ~noise plt.scatter(X2[core, 0], X2[core, 1], c=labels[core], s=10, alpha=0.6) plt.legend() else: plt.scatter(X2[:, 0], X2[:, 1], c=labels, s=10, alpha=0.6) plt.title(title) plt.xlabel("PC1") plt.ylabel("PC2") plt.grid(True, alpha=0.25) plt.show() plot_clusters_2d(X_2d, labels_kmeans, "KMeans clusters (PCA 2D)") plot_clusters_2d(X_2d, labels_dbscan, "DBSCAN clusters (PCA 2D)") plot_clusters_2d(X_2d, labels_birch, "Birch clusters (PCA 2D)") # ----------------------------- # 11) Compare with "true labels" (ONLY for evaluation) # ----------------------------- print("\n[11] Сравнение кластеров с истинными метками (если трактуем как классификацию)") # В твоём датасете rating — численный (регрессия). # Чтобы корректно сравнить кластеризацию с "классами", делаем *производную* метку: # ---- TODO(твоя часть): можно изменить биннинг, но обоснуй в тексте ---- y_rating = pd.to_numeric(df_work[col_rating], errors="coerce").fillna(df_work[col_rating].median()) # Пример: 3 класса по смыслу # 1-3: негатив, 4-7: нейтр, 8-10: позитив y_true = pd.cut( y_rating, bins=[0, 3, 7, 10], labels=[0, 1, 2], include_lowest=True ).astype(int).values def external_metrics(name, true_labels, cluster_labels): return { "method": name, "ARI": float(adjusted_rand_score(true_labels, cluster_labels)), "NMI": float(normalized_mutual_info_score(true_labels, cluster_labels)), "homogeneity": float(homogeneity_score(true_labels, cluster_labels)), "completeness": float(completeness_score(true_labels, cluster_labels)) } ext = [] ext.append(external_metrics("KMeans", y_true, labels_kmeans)) ext.append(external_metrics("DBSCAN", y_true, labels_dbscan)) ext.append(external_metrics("Birch", y_true, labels_birch)) ext_df = pd.DataFrame(ext) display(ext_df) print("\n[12] Итоги (заполни выводы текстом в markdown):") print("- Какой метод дал лучше silhouette/CH/DB?") print("- Сколько кластеров нашёл DBSCAN и сколько шума?") print("- Насколько согласуются кластеры с метками (ARI/NMI)?") print("- Что это значит для данных (есть ли естественная кластерная структура)?")
-1) и что это означает.Если хочешь — скидывай сюда свой вариант с выбранными feature_cols, k_opt, eps, и твой markdown-раздел “Выводы”, и я точечно:
y,