From 453189d6acbd9d7c2999c23b87762595cab7b41a Mon Sep 17 00:00:00 2001 From: NovikovVN Date: Thu, 19 Feb 2026 13:49:12 +0000 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?lab2=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab2/2_perceptron.ipynb | 1370 +++++++++++++++++++++++++++++++++++++++ lab2/min_water.csv | 41 ++ 2 files changed, 1411 insertions(+) create mode 100644 lab2/2_perceptron.ipynb create mode 100644 lab2/min_water.csv diff --git a/lab2/2_perceptron.ipynb b/lab2/2_perceptron.ipynb new file mode 100644 index 0000000..3e5ccc7 --- /dev/null +++ b/lab2/2_perceptron.ipynb @@ -0,0 +1,1370 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ЛАБОРАТОРНАЯ РАБОТА №2\n", + "## МНОГОСЛОЙНЫЙ ПЕРСЕПТРОН" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> Цель работы: изучение алгоритмов обучения многослойного персептрона, выбор структуры и контроль качества обучения нейронной сети, решение задачи\n", + "классификации многомерных данных.\n", + ">\n", + "> Задание\n", + "> 1. Открыть хранящиеся в файле `min_water.csv` данные о сорока образцах минеральной воды. Проведя предварительный анализ данных, выделить из 23-х признаков наиболее информативные для классификации образцов признаки. Разделить имеющуюся выборку на три части: для обучения, для верификации, для тестирования. Сохранить исходные данные.\n", + "> 2. Создать многослойный персептрон с одним нейроном в выходном слое. Решить задачу классификации имеющихся данных об образцах минеральной воды по пяти классам, используя при обучении алгоритм обратного распространения ошибки. Проанализировать полученные результаты.\n", + "> 3. Создать многослойный персептрон с пятью нейронами в выходном слое и снова решить задачу классификации имеющихся данных об образцах минеральной воды по пяти классам. Проанализировать\n", + "полученные результаты. По результатам п.2 выбрать наилучшую структуру ИНС для решения поставленной задачи.\n", + "> 4. Решить задачу классификации образцов минеральной воды по четырем классам, предварительно пометив один из классов, как недоступный в процессе обучения. Проанализировать результаты классификации, предъявив обученной ИНС неизвестный сорт минеральной воды. Объяснить полученные результаты." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Импорт библиотек:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "executionInfo": { + "elapsed": 4, + "status": "ok", + "timestamp": 1768213447856, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "FwCw1KfEpbsm" + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "import seaborn as sns\n", + "import torch\n", + "import matplotlib.pyplot as plt\n", + "from IPython.display import clear_output\n", + "from sklearn.model_selection import train_test_split\n", + "\n", + "from torch import nn\n", + "\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Pandas — это библиотека Python, представляющая собой «обёртку» над NumPy, специально разработанную для работы с табличными (структурированными) данными. Она предоставляет два ключевых типа данных: `Series` (одномерный массив с метками) и `DataFrame` (двумерная таблица с колонками разного типа). В отличие от «чистых» массивов NumPy, pandas позволяет: удобно индексировать данные по строкам и столбцам, обрабатывать пропуски (`NaN`), работать с разнородными типами данных в одной таблице, легко выполнять фильтрацию, группировку, агрегацию и слияния таблиц. При этом pandas сохраняет высокую производительность за счёт внутренней опоры на оптимизированные операции NumPy и C‑код.\n", + "\n", + "Официальная документация: https://pandas.pydata.org/docs/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Содержание: \n", + "[1. Подготовка данных](#p_1) \n", + "[2. Задача бинарной классификации](#p_2) \n", + "[3. Задача многоклассовой классификации](#p_3) \n", + "[4. Задача многоклассовой классификации (с исключением одного из классов)](#p_4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Подготовка данных" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Загрузим в датафрейм `data` данные о сорока образцах минеральной воды, хранящиеся в файле `min_water.txt`.\n", + "\n", + "Первый столбец содержит разметку для задачи бинарной классификации образцов воды (метки `0`и `1`), второй столбец — разметку для задачи многоглассовой классификации (каждому из 5 сортов минеральной воды соответствуют по 5 елассов их образцов). Оставшиеся 23 столбца рассматриваются как входные признаки." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 236 + }, + "executionInfo": { + "elapsed": 112, + "status": "ok", + "timestamp": 1768213447972, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "PkNWYbQCpsl_", + "outputId": "a19eff1c-593c-47b6-ed64-0aa6f84f56c2" + }, + "outputs": [], + "source": [ + "data = pd.read_csv('min_water.csv')\n", + "data.head(n=5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Вынесем в отдельные переменные:\n", + " - `y_binary` — выходной признак для задачи бинарной классификации (первый столбец датафрейма);\n", + " - `y_multiclass` — выходной признак для задачи многоклассовой классификации (второй столбец датафрейма);\n", + " - `X_data` — входные признаки (оставшиеся столбцы)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "executionInfo": { + "elapsed": 17, + "status": "ok", + "timestamp": 1768213447974, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "D_QiYrPkpsu0" + }, + "outputs": [], + "source": [ + "y_binary = data.iloc[:, 0]\n", + "y_multiclass = data.iloc[:, 1]\n", + "\n", + "X_data = data.iloc[:, 2:]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Нам необходимо отобрать из исходных 23 признаков наиболее информативные и полезные для задач бинарной и многоклассовой классификации.\n", + "\n", + "Вычислим матрицу парных коэффициентов корреляции Пирсона для всех признаков в `X_data`. Это позволит нам:\n", + "- выявить сильно коррелирующие признаки (|r| > 0,7–0,8), которые несут избыточную информацию;\n", + "- снизить риск переобучения за счёт удаления дублирующих признаков;\n", + "- упростить модель и ускорить обучение без потери качества;\n", + "- понять структуру данных и взаимосвязи между признаками;\n", + "- избежать проблем мультиколлинеарности в линейных моделях (когда коэффициенты становятся неустойчивыми из‑за сильной взаимной зависимости признаков)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 820 + }, + "executionInfo": { + "elapsed": 27, + "status": "ok", + "timestamp": 1768213448004, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "-6Mo3AnWusFT", + "outputId": "92e51428-c6a4-4e18-eab6-96e3af0a4eee" + }, + "outputs": [], + "source": [ + "X_data_correlation = X_data.corr(method='pearson')\n", + "X_data_correlation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Для наглядности построим тепловую карту корреляционной матрицы. Отметим на карте только те пары признаков, у которых коэффициент корреляции Пирсона больше 0.75:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 456 + }, + "executionInfo": { + "elapsed": 369, + "status": "ok", + "timestamp": 1768213448385, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "usQSk41iuVuW", + "outputId": "143b4366-59ca-48c8-ff7a-10442a2f0d24" + }, + "outputs": [], + "source": [ + "plt.figure(figsize=(10, 8))\n", + "sns.heatmap(X_data_correlation > 0.75)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Выпишите в список `features` отобранные в процессе анализа признаки (формат: `features = ['VAR1', 'VAR2']`):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "features = # Ваш код здесь" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Датафрейм с отобранными входными признаками `X_data_filtered`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X_data_filtered = X_data.loc[:, features]\n", + "X_data_filtered.head(n=5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Метрика accuracy (доля правильно классифицированных примеров от общего числа) будет общей и для задачи бинарной классификации, и для задачи многоклассовой классификации:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "executionInfo": { + "elapsed": 21, + "status": "ok", + "timestamp": 1768213448636, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "vcEZHV2G1fSQ" + }, + "outputs": [], + "source": [ + "def accuracy(y_pred, y_true):\n", + " return torch.sum(y_pred == y_true) / len(y_true)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Задача бинарной классификации" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Изучите [документацию](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html) к функции `train_test_split`, чтобы разделить исходные данные на обучающую, валидационную и тестовую выборки — стандартный этап подготовки данных для обучения и оценки моделей машинного обучения.\n", + "\n", + "В первом её вызове отделите, например, 20% исходных данных (`X_data_filtered`, `y_binary`) в тестовую выборку (`X_binary_test`, `y_binary_test`), 80% оставьте для обучения (`X_binary_train`, `y_binary_train`). Через параметр `stratify` сохраните соотношение классов в обеих выборках.\n", + "\n", + "Во втором вызове отделите 20% данных для обучения в тестовую выборку (`X_binary_valid`, `y_binary_valid`), 80% оставьте для финального обучения (`X_binary_train`, `y_binary_train`). Не забудьте про параметр `stratify`.\n", + "\n", + "*Примечание*. Валидационная выборка помогает подбирать гиперпараметры (например, число эпох, скорость обучения) и следить за переобучением. Тестовая выборка «заморожена» до конца обучения и используется только для итоговой оценки, чтобы избежать смещения в оценке качества. Их соотношение с обучающей выборкой может быть и иным." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X_binary_train, X_binary_test, y_binary_train, y_binary_test = # Ваш код здесь\n", + "\n", + "X_binary_train, X_binary_valid, y_binary_train, y_binary_valid = # Ваш код здесь" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Входные признаки для нейронной сети необходимо либо нормализовывать, либо стандартизировать — чтобы привести их к единому масштабу. Это ускоряет сходимость градиентного спуска и делает обучение стабильнее (спуск движется более прямо к минимуму, требует меньше итераций для сходимости, менее подвержен «взрыву» или «исчезновению» градиентов).\n", + "\n", + "Нормализация (Min‑Max Scaling) приводит значения признака к фиксированному диапазону (обычно `[0, 1]`):\n", + " \n", + "$$\n", + "x_{\\text{norm}} = \\frac{x - x_{\\min}}{x_{\\max} - x_{\\min}}\n", + "$$ \n", + "где: \n", + "- $x$ — исходное значение признака; \n", + "- $x_{\\min}$ — минимальное значение признака в выборке; \n", + "- $x_{\\max}$ — максимальное значение признака в выборке.\n", + "\n", + "\n", + "Стандартизация преобразует распределение признака к среднему `0` и стандартному отклонению `1`: \n", + "$$\n", + "x_{\\text{standard}} = \\frac{x - \\mu}{\\sigma}\n", + "$$ \n", + "где: \n", + "- $x$ — исходное значение признака; \n", + "- $\\mu$ — среднее значение признака в выборке; \n", + "- $\\sigma$ — стандартное отклонение признака в выборке." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Произведём стандартизацию входных признаков (но по аналогии можете произвести нормализацию):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X_means = X_binary_train.mean(axis=0)\n", + "X_stds = X_binary_train.std(axis=0, ddof=1)\n", + "\n", + "X_binary_train = # Ваш код здесь\n", + "X_binary_valid = # Ваш код здесь\n", + "X_binary_test = # Ваш код здесь" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rP2B8eJk2sYX" + }, + "source": [ + "Представим данные обучающей, валидационной и тестовой выборок в виде тензоров:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "executionInfo": { + "elapsed": 54, + "status": "ok", + "timestamp": 1768213448693, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "jsWiE7Ke1p9S" + }, + "outputs": [], + "source": [ + "X_binary_train = torch.tensor(X_binary_train.values).float()\n", + "X_binary_valid = torch.tensor(X_binary_valid.values).float()\n", + "X_binary_test = torch.tensor(X_binary_test.values).float()\n", + "\n", + "y_binary_train = torch.tensor(y_binary_train.values).reshape(-1, 1).float()\n", + "y_binary_valid = torch.tensor(y_binary_valid.values).reshape(-1, 1).float()\n", + "y_binary_test = torch.tensor(y_binary_test.values).reshape(-1, 1).float()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Создайте экземпляр нейронной сети для решения задачи бинарной классификации — например, через `nn.Sequential()`, как было показано в первой лабораторной работе.\n", + "\n", + "Важно соблюсти два ключевых условия при проектировании архитектуры:\n", + "- число входных признаков в первом слое (`in_features`) должно точно соответствовать размерности признаков в обучающих данных (количеству столбцов в `X_binary_train`) (воспользуйтесь свойством `X_binary_train.shape`);\n", + "- в выходном слое необходимо использовать один нейрон с сигмоидальной функцией активации — напомним, что это гарантирует, что сеть будет выдавать значение в диапазоне [0, 1], интерпретируемое как вероятность принадлежности к положительному классу в задаче бинарной классификации." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model_binary = nn.Sequential(\n", + " # Ваш код здесь\n", + ")\n", + "\n", + "model_binary" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Удостоверимся, что наша сеть работает — рассчитаем по её выходу, который представляет собой предсказанные вероятности принадлежности к классу 1:\n", + " - значение функции потерь (бинарная кросс-энтропия);\n", + " - метрику accuracy." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 22, + "status": "ok", + "timestamp": 1768213448710, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "zP1t3NsT3xiH", + "outputId": "b722665a-ee83-4393-b45a-457076a3ac29" + }, + "outputs": [], + "source": [ + "y_binary_train_prob = model_binary(X_binary_train)\n", + "\n", + "criterion = nn.BCELoss()\n", + "print(f'Loss: {criterion(y_binary_train_prob, y_binary_train).item():.6f}')\n", + "print(f'Accuracy: {accuracy((y_binary_train_prob > 0.5).float(), y_binary_train).item():.3f}')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 17, + "status": "ok", + "timestamp": 1768213448728, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "tfLMe06S5m4V", + "outputId": "281962d3-e691-439e-9b39-bd463c7ad8a8" + }, + "source": [ + "Теперь перейдём к обучению нейронной сети для решения поставленной задачи бинарной классификации.\n", + "\n", + "Зададим \n", + " - экземпляр нейронной сети;\n", + " - количество эпох или итераций в процессе обучения;\n", + " - скорость обучение и коэффициент импульса;\n", + " - оптимизатор;\n", + " - функцию потерь (поскольку решается задача бинарной классификации, выберем бинарную кросс-энтропию `nn.BCELoss()`).\n", + " \n", + " \n", + "*Примечание*. Вы можете использовать для обучения сети либо код ниже, либо модифицированный на его основе — например, реализовать функцию обучения нейронной сети и проверки её работы." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "executionInfo": { + "elapsed": 9, + "status": "ok", + "timestamp": 1768213448730, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "z-pABN4e6bAf" + }, + "outputs": [], + "source": [ + "# Перебор seed для инициализации параметров\n", + "torch.manual_seed(seed=42)\n", + "\n", + "model_binary = # Ваш код здесь\n", + "\n", + "epochs = # Ваш код здесь\n", + "\n", + "learning_rate = # Ваш код здесь\n", + "momentum = # Ваш код здесь\n", + "\n", + "optimizer = # Ваш код здесь\n", + "criterion = nn.BCELoss()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Обучение нейронной сети для решения задачи бинарной классификации:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 504 + }, + "executionInfo": { + "elapsed": 22752, + "status": "ok", + "timestamp": 1768213471478, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "qKimvoStsZXv", + "outputId": "e903f88f-542f-4ab7-c291-2be72f726d39" + }, + "outputs": [], + "source": [ + "loss_train_history, loss_valid_history = [], []\n", + "\n", + "for epoch in range(epochs):\n", + " model_binary.train()\n", + "\n", + " optimizer.zero_grad()\n", + "\n", + " y_binary_train_prob = model_binary(X_binary_train)\n", + "\n", + " loss_train = criterion(y_binary_train_prob, y_binary_train)\n", + " loss_train_history.append(loss_train.item())\n", + "\n", + " loss_train.backward()\n", + "\n", + " optimizer.step()\n", + "\n", + " model_binary.eval()\n", + " \n", + " # Отключаем градиенты для этапа валидации\n", + " with torch.no_grad():\n", + " y_binary_valid_prob = model_binary(X_binary_valid) \n", + " loss_valid = criterion(y_binary_valid_prob, y_binary_valid)\n", + " \n", + " loss_valid_history.append(loss_valid.item())\n", + "\n", + " if (epoch + 1) % 5 == 0:\n", + "\n", + " clear_output(True)\n", + " plt.plot(range(1, epoch+2), loss_train_history, label='Train', color='green')\n", + " plt.plot(range(1, epoch+2), loss_valid_history, label='Valid', color='red')\n", + " plt.title(f'Epoch: {epoch + 1}, Loss Train: {loss_train_history[-1]:.6f}, Loss Valid: {loss_valid_history[-1]:.6f}')\n", + " plt.grid(True, alpha=0.3)\n", + " plt.legend(loc='best')\n", + " plt.show()\n", + " \n", + " print('Accuracy')\n", + " print(f'Train: {accuracy((y_binary_train_prob > 0.5).float(), y_binary_train).item():.3f}')\n", + " print(f'Valid: {accuracy((y_binary_valid_prob > 0.5).float(), y_binary_valid).item():.3f}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "После того, как вы подобрали параметры и добились приемлемого качества работы сети на обучающей и валидационной выборках, в качестве финального шага проверьте работу сети на отложенной тестовой выборке:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 35, + "status": "ok", + "timestamp": 1768213471518, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "V__6qHAe-7VS", + "outputId": "61ab4f43-fc4d-4a07-d1a9-921f91a2d0fc" + }, + "outputs": [], + "source": [ + "with torch.no_grad():\n", + " y_binary_test_prob = model_binary(X_binary_test)\n", + " loss_test = criterion(y_binary_test_prob, y_binary_test)\n", + " \n", + "print(f'Loss Test: {loss_test.item():.6f}')\n", + "print(f'Accuracy Test: {accuracy((y_binary_valid_prob > 0.5).float(), y_binary_valid).item():.3f}')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "executionInfo": { + "elapsed": 30, + "status": "ok", + "timestamp": 1768213471544, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "ZtJ4WeKCATqU" + }, + "source": [ + "Если качество схожее, нейронная сеть не переобучена.\n", + "\n", + "Поэкспериметируйте — сделайте несколько вариантов с разными архитектурами сети, оптимизаторами, значениями скорости обучения и коэффициента импульса. Выберите лучшие по качеству обучения и количеству эпох." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Задача многоклассовой классификации" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Для решения задачи многоклассовой классификации по аналогии с задачей бинарной классификации:\n", + " - с помощью функции `train_test_split` разбейте данные (`X_data_filtered`, `y_multiclass`) на обучающую (`X_multiclass_train`, `y_multiclass_train`), валидационную (`X_multiclass_valid`, `y_multiclass_valid`) и тестовую выборки (`X_multiclass_test`, `y_multiclass_test`) с сохранением соотншений классов (сортов минеральной воды);\n", + " - стандартизируйте или нормализуйте входные признаки;\n", + " - из векторов с выходным признаком вычтите единицу и представьте их в виде тензоров целочисленного формата (метод `.long()`)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X_multiclass_train, X_multiclass_test, y_multiclass_train, y_multiclass_test = # Ваш код здесь\n", + "\n", + "X_multiclass_train, X_multiclass_valid, y_multiclass_train, y_multiclass_valid = # Ваш код здесь" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Ваш код здесь" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 25, + "status": "ok", + "timestamp": 1768213471611, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "CsGt0n6-B5WA", + "outputId": "c555da32-2652-48a2-8954-21b76ffaa019" + }, + "source": [ + "Для задач многоклассовой классификации в машинном обучении применяют функцию активации `softmax`, которая посредством нормализации преобразует вектор логитов (выходов последнего слоя) в распределение вероятностей: \n", + " $$\n", + " p_k = \\frac{e^{z_k}}{\\sum_{k=1}^{K} e^{z_k}},\n", + " $$ \n", + " где $z_k$ — выход для $k$-го класса, $K$ — число классов. \n", + " - Свойства: \n", + " - все выходы ∈ [0, 1]; \n", + " - сумма выходов = 1; \n", + " - максимальный выход соответствует наиболее вероятному классу.\n", + "\n", + "Реализованная в PyTorch функция потерь `nn.CrossEntropyLoss()` объединяет в себе `log_softmax` (численно устойчивая версия softmax + логарифм) и `NLLLoss` (отрицательная логарифмическая правдоподобность). Она принимает на вход именно логиты.\n", + "\n", + "Создадим экземпляр нейронной сети для решения задачи многоклассовой классификации — например, также через `nn.Sequential()`:\n", + " - число входных признаков в первом слое (`in_features`) должно точно соответствовать размерности признаков в обучающих данных (количеству столбцов в `X_binary_train`) (воспользуйтесь свойством `X_binary_train.shape`);\n", + " - количество нейроной в выходном слое должно совпадать с количеством классов;\n", + " - в связи с тем, что сеть нам должна выдавать логиты, функцию активации на выходном слое можно не использовать (хотя можно взять `nn.ReLU()`)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 9, + "status": "ok", + "timestamp": 1768213471622, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "l2wN73_-BlzP", + "outputId": "b630d430-2215-41f3-9a62-93db01a5207a" + }, + "outputs": [], + "source": [ + "model_multiclass = nn.Sequential(\n", + " nn.Linear(in_features=X_multiclass_train.shape[1], out_features=y_multiclass.nunique()),\n", + " nn.ReLU()\n", + ")\n", + "\n", + "model_multiclass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Пропустив данные через эту сеть, получим логиты, представляющие собою вектора, чья длина равна количеству классов:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 21, + "status": "ok", + "timestamp": 1768213471644, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "4BTXxrftCJzK", + "outputId": "e3568629-6f98-4b92-ef3b-92c607a92f9b" + }, + "outputs": [], + "source": [ + "y_multiclass_train_logits = model_multiclass(X_multiclass_train)\n", + "y_multiclass_train_logits[:3]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Проверим работу функции потерь с ними:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 15, + "status": "ok", + "timestamp": 1768213471707, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "4ZQizLNZAT8_", + "outputId": "113bf8b1-8c80-4a82-d09a-7db97eb2222f" + }, + "outputs": [], + "source": [ + "criterion = nn.CrossEntropyLoss()\n", + "criterion(y_multiclass_train_logits, y_multiclass_train)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Отметим, что для задачи классификации нормализация через `softmax` не требуется — в качестве ответа (метки класса) берем индекс максимального элемента в логите:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 8, + "status": "ok", + "timestamp": 1768213471654, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "RBufWh2CDW1P", + "outputId": "7c14a8d4-9306-429f-b870-50337e29ccaa" + }, + "outputs": [], + "source": [ + "y_multiclass_train_logits.argmax(dim=1)[:3]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Метрика accuracy:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 48, + "status": "ok", + "timestamp": 1768213471704, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "Cz58yMsGDYmx", + "outputId": "8c93fa6f-09d5-43b6-dadf-67022c236dc5" + }, + "outputs": [], + "source": [ + "accuracy(y_multiclass_train_logits.argmax(dim=1), y_multiclass_train).item()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "По аналогии с задачей бинарной классификации создайте, обучите и проверьте нейронную сеть для решения задачи многоклассовой классификации:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "executionInfo": { + "elapsed": 6, + "status": "ok", + "timestamp": 1768213471720, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "V1CgE0O7Lz0g" + }, + "outputs": [], + "source": [ + "# Ваш код здесь" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3lHwSPaz35VQ" + }, + "source": [ + "Поэкспериметируйте — сделайте несколько вариантов с разными архитектурами сети, оптимизаторами, значениями скорости обучения и коэффициента импульса. Выберите лучшие по качеству обучения и количеству эпох." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1RTQgj4gLz5O" + }, + "source": [ + "## 4. Задача многоклассовой классификации (с исключением одного из классов)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Введите метку класса, который хотите исключить (1, 2, 3, 4 или 5):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "label_to_exclude = # Ваш код здесь" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Маска (логическое условие) исключения в выходном признаке меток с этим классом:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "executionInfo": { + "elapsed": 83, + "status": "ok", + "timestamp": 1768217647959, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "Ge_M2-suvOfq", + "outputId": "7562cae1-5c7c-4ee8-c013-6428ec959db6" + }, + "outputs": [], + "source": [ + "mask_to_exclude = y_multiclass != label_to_exclude\n", + "y_multiclass[mask_to_exclude]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3jVGVtJGw3Du" + }, + "source": [ + "Значения входных признаков, которые соответствуют исключённому классу (`X_data_exclude`):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 300 + }, + "executionInfo": { + "elapsed": 139, + "status": "ok", + "timestamp": 1768217650943, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "i1AGN9v0w3eC", + "outputId": "9c78af34-6e8d-4b3d-f535-51d00ac100a6" + }, + "outputs": [], + "source": [ + "X_data_exclude = X_data_filtered.loc[~mask_to_exclude, :]\n", + "X_data_exclude" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Значения входных признаков, которые соответствуют оставленным классам (`X_data_include`):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "executionInfo": { + "elapsed": 109, + "status": "ok", + "timestamp": 1768217652781, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "81fKKw_xvyfc", + "outputId": "c31b50ed-6c3b-4362-d8c4-65dde73b5615" + }, + "outputs": [], + "source": [ + "X_data_include = X_data_filtered.loc[mask_to_exclude, :]\n", + "X_data_include" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Для корректной работы с нейронной сетью модифицируем выходной признак с метками оставленных классов — значения меток выше исключённого класса уменьшим на единицу, чтобы выровнять их порядок (в дальнейшем вернём исходные). Результатом будет вектор `y_include`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "executionInfo": { + "elapsed": 15, + "status": "ok", + "timestamp": 1768217655753, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "YeSoZPu1vakq", + "outputId": "4d1b5c42-161c-4b41-8441-d4f77f16c68c" + }, + "outputs": [], + "source": [ + "y_include = y_multiclass[mask_to_exclude].apply(lambda y: y - 1 if y >= label_to_exclude else y)\n", + "y_include" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "executionInfo": { + "elapsed": 39, + "status": "ok", + "timestamp": 1768217660798, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "uvneeNEq2gkW" + }, + "source": [ + "По аналогии с задачами выше:\n", + " - с помощью функции `train_test_split` разбейте данные (`X_data_include`, `y_include`) на обучающую (`X_include_train`, `y_include_train`), валидационную (`X_include_valid`, `y_include_valid`) и тестовую выборки (`X_include_test`, `y_include_test`) с сохранением соотншений классов (сортов минеральной воды);\n", + " - стандартизируйте или нормализуйте входные признаки и представьте их в виде тензоров;\n", + " - из векторов с выходным признаком вычтите единицу и представьте их в виде тензоров целочисленного формата (метод `.long()`)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "vXnQuhb7vaoL" + }, + "outputs": [], + "source": [ + "# Ваш код здесь" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LdbLhuA0vasw" + }, + "source": [ + "По аналогии с задаче многоклассовой классификации создайте нейронную сеть для этих данных:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model_include = nn.Sequential(\n", + " # Ваш код здесь\n", + ")\n", + "\n", + "model_include" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BjsyXLHgva1X" + }, + "source": [ + "Получим логиты (длина векторов уменьшилась до 4):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 44, + "status": "ok", + "timestamp": 1768217668726, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "HIsQ_5MR3Jal", + "outputId": "74349107-0245-4696-eb9d-36f5e50ceaec" + }, + "outputs": [], + "source": [ + "y_include_train_logits = model_include(X_include_train)\n", + "y_include_train_logits[:3]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Проверим работу функцию потерь на этих логитах и модимцированном выходном признаке:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 51, + "status": "ok", + "timestamp": 1768217670810, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "2XQimt7T3Xcc", + "outputId": "1bf129bc-aed1-433e-8083-2cea475f86ee" + }, + "outputs": [], + "source": [ + "criterion = nn.CrossEntropyLoss()\n", + "criterion(y_include_train_logits, y_include_train)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "qu9a65IQva5f" + }, + "source": [ + "По аналогии с задачей многоклассовой классификации с полным количеством классов создайте, обучите и проверьте нейронную сеть:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "8wQa5g4Bva-I" + }, + "outputs": [], + "source": [ + "# Ваш код здесь" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "l4l-aUPz37ju" + }, + "source": [ + "Поэкспериметируйте — сделайте несколько вариантов с разными архитектурами сети, оптимизаторами, значениями скорости обучения и коэффициента импульса. Выберите лучшие по качеству обучения и количеству эпох." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "executionInfo": { + "elapsed": 9, + "status": "ok", + "timestamp": 1768217700545, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "k4sqBf5q41AC" + }, + "source": [ + "Значения входных признаков, которые соответствуют исключённому классу (`X_data_exclude`) так же, как и значения входных признаков из выборок выше, стандартизируйте или нормализируйте и представьте их в виде тензоров: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X_data_exclude = # Ваш код здесь" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Пропустим обработанные признаки через обученную и проверенную нейронную сеть `model_include`, чтобы получить логиты.\n", + "\n", + "По данным логитам произведём классификацию (с учётом метки исключённого класса), чтобы ответить на вопрос — к каким сортам минеральной воды из оставленных соответствуют образцы исключённого класса:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 14, + "status": "ok", + "timestamp": 1768217702125, + "user": { + "displayName": "V N", + "userId": "10751591882973173608" + }, + "user_tz": -180 + }, + "id": "4bflOWXX5YqN", + "outputId": "5bea95d9-0e02-455e-9163-8f14ce3664c3" + }, + "outputs": [], + "source": [ + "y_exclude_logits = model_include(X_data_exclude)\n", + "\n", + "(y_exclude_logits.argmax(dim=1) + 1).apply_(lambda y: y + 1 if y >= label_to_exclude else y)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Проделайте данный пункт, исключив каждый из пяти сортов. Результатом должны стать пять тензоров с восемью метками классов. Проанализируйте результаты." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Литература:\n", + "1. Бородкин А.А., Елисеев В.Л. Основы и применение искусственных нейронных сетей. Сборник лабораторных работ: методическое пособие. – М.: Издательский дом МЭИ, 2017.\n", + "2. MachineLearning.ru — профессиональный информационно-аналитический ресурс, посвященный машинному обучению, распознаванию образов и интеллектуальному анализу данных: http://www.machinelearning.ru\n", + "3. Modern State of Artificial Intelligence — Online Masters program at MIPT: https://girafe.ai/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " " + ] + } + ], + "metadata": { + "colab": { + "authorship_tag": "ABX9TyP49vkWVMyMLjNYOlwKa9fi", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/lab2/min_water.csv b/lab2/min_water.csv new file mode 100644 index 0000000..edca252 --- /dev/null +++ b/lab2/min_water.csv @@ -0,0 +1,41 @@ +BINARY,MULTICLASS,VAR1,VAR2,VAR3,VAR4,VAR5,VAR6,VAR7,VAR8,VAR9,VAR10,VAR11,VAR12,VAR13,VAR14,VAR15,VAR16,VAR17,VAR18,VAR19,VAR20,VAR21,VAR22,VAR23 +1,1,-496.4,82.6,-258.6,202.7,168.9,7.1,-228.2,-149.0,-167.1,-352.2,-282.9,-227.8,-352.7,32.6,86.0,111.0,407.5,151.7,85.1,108.6,78.2,126.2,110.1 +0,1,-499.0,69.9,-269.0,201.2,166.4,1.0,-250.0,-158.8,-171.1,-369.9,-285.9,-232.7,-369.6,22.0,88.6,106.9,400.1,156.2,83.9,116.6,81.4,116.7,114.2 +1,1,-498.6,74.3,-266.7,202.9,167.4,-0.1,-266.6,-167.3,-180.6,-382.6,-287.1,-243.7,-380.5,25.7,93.1,103.6,406.6,154.9,76.1,112.0,96.6,120.4,116.7 +1,1,-500.3,68.1,-269.3,197.7,165.0,-1.9,-252.3,-157.4,-167.5,-375.5,-285.2,-238.6,-370.7,22.3,91.2,107.9,400.5,154.6,82.7,115.1,100.7,122.9,116.3 +1,1,-499.7,73.8,-269.6,189.7,164.9,4.1,-282.6,-211.9,-191.6,-390.3,-311.7,-247.3,-388.3,31.4,86.7,118.6,417.7,157.2,99.5,122.3,91.8,121.4,106.7 +1,1,-498.6,66.1,-270.5,193.8,167.6,4.3,-327.3,-222.0,-183.0,-399.0,-309.8,-290.0,-400.2,30.3,92.0,115.0,423.0,166.1,81.9,118.0,136.7,122.2,113.8 +1,1,-499.3,71.1,-271.2,193.6,168.5,1.0,-329.2,-247.6,-195.5,-400.5,-297.0,-285.4,-402.4,21.6,92.3,117.6,412.5,165.9,90.2,118.7,117.2,128.0,111.7 +1,1,-498.6,65.0,-270.1,196.9,167.6,-0.5,-336.5,-236.3,-197.3,-408.9,-298.0,-300.5,-406.5,17.6,97.2,117.7,414.9,177.1,85.2,125.5,127.1,131.9,116.8 +1,2,-452.8,69.3,-267.5,174.2,215.4,-30.9,-251.8,-153.0,-172.6,-372.1,-294.9,-247.0,-372.9,10.1,93.4,123.0,414.5,141.2,54.0,86.7,68.5,99.3,104.2 +0,2,-455.2,59.7,-275.5,170.8,212.2,-35.9,-269.1,-162.1,-174.1,-382.9,-296.9,-252.7,-385.5,2.1,96.6,119.5,405.0,140.7,53.5,95.8,73.0,99.8,106.8 +1,2,-456.3,60.0,-277.9,173.1,211.9,-38.9,-287.9,-166.2,-183.1,-398.8,-300.7,-264.6,-396.4,-1.7,103.3,119.4,407.9,138.4,44.9,92.4,89.2,108.9,113.9 +1,2,-458.8,52.5,-284.3,169.5,209.2,-40.7,-281.4,-163.1,-172.2,-397.6,-303.0,-259.8,-391.8,-8.2,104.4,127.2,396.8,138.2,44.8,93.8,111.6,108.2,113.0 +1,2,-458.4,56.5,-284.0,160.4,211.1,-35.6,-308.9,-216.0,-184.5,-401.7,-306.0,-271.6,-399.3,2.9,97.9,136.6,429.6,146.8,56.0,98.4,104.5,106.5,106.3 +1,2,-454.5,54.0,-287.3,163.3,214.0,-38.9,-333.3,-244.1,-189.6,-408.7,-308.2,-295.3,-407.0,-0.2,109.8,139.0,435.2,149.7,60.9,109.7,135.8,115.3,115.2 +1,2,-458.1,50.3,-284.1,163.8,210.7,-39.2,-341.2,-266.0,-205.5,-415.1,-310.4,-306.2,-412.1,-6.0,98.7,134.6,416.1,150.4,50.3,95.7,106.6,112.4,108.6 +1,2,-458.7,51.6,-284.9,166.5,213.7,-40.8,-351.5,-247.9,-201.2,-417.7,-313.4,-317.6,-415.8,-10.7,104.3,136.7,417.5,165.0,50.7,98.6,117.1,115.7,113.1 +1,3,-467.5,90.4,-252.7,168.5,178.5,-2.4,-223.5,-147.6,-169.1,-347.5,-268.4,-224.6,-343.1,61.9,82.2,98.1,435.5,146.7,95.6,125.1,76.2,110.7,125.0 +0,3,-468.5,86.2,-255.0,164.6,177.3,-2.2,-235.7,-153.3,-167.8,-359.7,-272.4,-227.4,-353.8,63.6,84.7,95.0,435.2,149.4,105.5,135.4,82.3,118.0,129.4 +1,3,-468.6,79.4,-257.0,166.2,176.1,-6.4,-244.0,-157.1,-176.5,-361.8,-268.4,-227.9,-356.3,58.3,93.1,94.6,438.6,147.4,100.2,136.5,108.4,133.5,139.1 +1,3,-469.7,81.1,-256.1,163.7,174.8,-3.8,-240.0,-152.0,-166.5,-359.7,-270.5,-223.4,-355.3,58.2,93.4,98.5,434.1,148.4,106.4,136.1,125.5,125.4,134.6 +1,3,-468.6,79.4,-259.4,155.5,178.7,-5.6,-307.6,-187.9,-175.0,-384.7,-279.8,-249.7,-381.3,55.8,87.9,110.3,449.0,152.2,102.0,135.7,127.5,126.6,128.7 +1,3,-468.0,81.9,-261.3,158.9,181.0,-7.5,-324.2,-209.1,-180.5,-389.0,-286.8,-278.5,-386.9,53.7,94.3,109.5,449.3,158.6,97.3,136.4,136.8,129.4,133.5 +1,3,-468.8,72.5,-263.4,158.4,174.7,-10.9,-318.4,-233.3,-189.7,-393.8,-277.5,-276.3,-390.9,45.2,96.2,111.8,440.7,153.3,95.2,135.4,122.4,132.3,132.9 +1,3,-467.9,71.2,-262.3,162.3,177.2,-11.8,-321.1,-218.4,-189.7,-398.4,-279.4,-283.0,-392.6,43.1,104.5,113.9,443.0,173.1,99.5,142.4,145.7,145.3,137.6 +1,4,-478.5,68.0,-269.3,237.6,212.1,-8.6,-255.5,-152.8,-175.0,-373.6,-307.3,-248.6,-375.5,18.7,88.9,115.0,405.0,163.8,51.5,84.7,82.1,116.0,104.1 +0,4,-479.3,67.1,-277.2,235.0,211.8,-11.0,-271.5,-161.3,-174.1,-390.2,-298.3,-255.9,-387.7,14.1,85.4,108.2,401.6,164.7,52.3,88.7,81.0,120.9,98.2 +1,4,-479.7,66.5,-278.6,238.5,211.1,-14.1,-295.9,-162.4,-182.2,-395.0,-300.5,-263.5,-397.3,9.3,94.7,108.6,405.1,162.5,50.8,90.6,94.8,133.9,109.7 +1,4,-481.0,54.8,-279.7,233.7,205.5,-14.1,-288.0,-158.3,-172.1,-394.3,-300.2,-258.6,-392.0,3.6,92.6,113.7,396.8,161.2,49.9,87.9,97.3,128.8,106.1 +1,4,-480.3,54.5,-281.2,226.4,210.5,-11.0,-339.7,-231.4,-186.8,-405.4,-311.5,-279.9,-404.0,7.8,89.4,129.9,417.4,167.3,51.4,92.5,102.7,108.6,99.6 +1,4,-479.2,52.0,-283.4,230.3,212.1,-12.7,-345.2,-236.7,-189.9,-411.1,-311.6,-304.6,-408.4,4.3,98.4,130.9,417.7,180.3,47.9,90.9,117.6,113.4,104.2 +1,4,-480.3,48.5,-291.7,229.5,210.1,-14.9,-343.2,-256.8,-202.0,-416.2,-313.0,-313.5,-412.4,-2.8,94.3,132.1,408.1,176.9,46.7,89.6,109.6,117.9,104.3 +1,4,-480.4,47.2,-285.9,233.8,213.4,-15.7,-351.1,-239.1,-197.9,-419.5,-315.6,-318.7,-416.2,-8.8,101.3,133.6,408.5,194.0,46.9,93.9,119.4,123.8,107.1 +1,5,-480.4,62.8,-276.9,228.8,214.2,-31.6,-249.2,-163.3,-180.0,-384.7,-324.3,-230.2,-380.2,-35.8,96.0,133.0,375.0,152.6,3.5,58.6,79.2,102.3,103.5 +0,5,-486.0,56.9,-281.4,227.0,212.2,-34.2,-275.6,-166.1,-175.8,-398.9,-328.0,-251.3,-395.3,-43.0,91.6,122.4,370.2,148.5,5.5,64.5,79.0,112.1,101.8 +1,5,-485.0,60.4,-281.3,229.3,210.4,-36.0,-269.9,-165.1,-177.4,-388.9,-327.0,-242.0,-386.0,-42.2,97.2,123.5,375.6,146.5,12.9,66.7,96.5,116.4,106.7 +1,5,-485.8,57.7,-279.0,224.2,207.6,-35.8,-268.5,-159.4,-169.1,-394.0,-326.9,-228.1,-387.2,-42.9,107.3,136.6,363.8,150.0,9.0,65.2,104.2,114.0,106.3 +1,5,-488.7,49.3,-288.0,218.8,210.7,-32.9,-309.7,-223.7,-186.0,-398.6,-332.0,-275.3,-401.1,-42.4,104.5,147.7,389.1,156.5,17.1,72.4,105.3,92.0,102.6 +1,5,-488.9,52.8,-283.6,223.3,212.0,-32.6,-328.7,-244.9,-189.4,-400.6,-334.1,-275.3,-400.3,-42.4,112.2,146.5,389.9,161.7,13.6,72.3,111.0,95.4,105.8 +1,5,-486.8,48.5,-287.1,220.6,212.8,-36.3,-322.4,-246.0,-197.7,-400.5,-334.3,-289.1,-400.1,-45.3,104.8,152.0,382.4,170.6,13.5,73.3,103.4,102.3,105.6 +1,5,-489.1,47.6,-285.7,226.4,211.2,-33.6,-327.5,-233.6,-194.3,-403.4,-333.7,-297.0,-399.0,-45.1,110.6,154.3,385.0,181.0,18.6,77.4,109.8,106.6,110.6