50 KiB
ЛАБОРАТОРНАЯ РАБОТА №2
МНОГОСЛОЙНЫЙ ПЕРСЕПТРОН
Цель работы: изучение алгоритмов обучения многослойного персептрона, выбор структуры и контроль качества обучения нейронной сети, решение задачи классификации многомерных данных.
Задание
- Открыть хранящиеся в файле
min_water.csvданные о сорока образцах минеральной воды. Проведя предварительный анализ данных, выделить из 23-х признаков наиболее информативные для классификации образцов признаки. Разделить имеющуюся выборку на три части: для обучения, для верификации, для тестирования. Сохранить исходные данные.- Создать многослойный персептрон с одним нейроном в выходном слое. Решить задачу классификации имеющихся данных об образцах минеральной воды по пяти классам, используя при обучении алгоритм обратного распространения ошибки. Проанализировать полученные результаты.
- Создать многослойный персептрон с пятью нейронами в выходном слое и снова решить задачу классификации имеющихся данных об образцах минеральной воды по пяти классам. Проанализировать полученные результаты. По результатам п.2 выбрать наилучшую структуру ИНС для решения поставленной задачи.
- Решить задачу классификации образцов минеральной воды по четырем классам, предварительно пометив один из классов, как недоступный в процессе обучения. Проанализировать результаты классификации, предъявив обученной ИНС неизвестный сорт минеральной воды. Объяснить полученные результаты.
Импорт библиотек:
import numpy as np
import pandas as pd
import seaborn as sns
import torch
import matplotlib.pyplot as plt
from IPython.display import clear_output
from sklearn.model_selection import train_test_split
from torch import nn
%matplotlib inlinePandas — это библиотека Python, представляющая собой «обёртку» над NumPy, специально разработанную для работы с табличными (структурированными) данными. Она предоставляет два ключевых типа данных: Series (одномерный массив с метками) и DataFrame (двумерная таблица с колонками разного типа). В отличие от «чистых» массивов NumPy, pandas позволяет: удобно индексировать данные по строкам и столбцам, обрабатывать пропуски (NaN), работать с разнородными типами данных в одной таблице, легко выполнять фильтрацию, группировку, агрегацию и слияния таблиц. При этом pandas сохраняет высокую производительность за счёт внутренней опоры на оптимизированные операции NumPy и C‑код.
Официальная документация: https://pandas.pydata.org/docs/
Содержание:
1. Подготовка данных
2. Задача бинарной классификации
3. Задача многоклассовой классификации
4. Задача многоклассовой классификации (с исключением одного из классов)
1. Подготовка данных
Загрузим в датафрейм data данные о сорока образцах минеральной воды, хранящиеся в файле min_water.txt.
Первый столбец содержит разметку для задачи бинарной классификации образцов воды (метки 0и 1), второй столбец — разметку для задачи многоглассовой классификации (каждому из 5 сортов минеральной воды соответствуют по 5 елассов их образцов). Оставшиеся 23 столбца рассматриваются как входные признаки.
data = pd.read_csv('min_water.csv')
data.head(n=5)Вынесем в отдельные переменные:
y_binary— выходной признак для задачи бинарной классификации (первый столбец датафрейма);y_multiclass— выходной признак для задачи многоклассовой классификации (второй столбец датафрейма);X_data— входные признаки (оставшиеся столбцы).
y_binary = data.iloc[:, 0]
y_multiclass = data.iloc[:, 1]
X_data = data.iloc[:, 2:]Нам необходимо отобрать из исходных 23 признаков наиболее информативные и полезные для задач бинарной и многоклассовой классификации.
Вычислим матрицу парных коэффициентов корреляции Пирсона для всех признаков в X_data. Это позволит нам:
- выявить сильно коррелирующие признаки (|r| > 0,7–0,8), которые несут избыточную информацию;
- снизить риск переобучения за счёт удаления дублирующих признаков;
- упростить модель и ускорить обучение без потери качества;
- понять структуру данных и взаимосвязи между признаками;
- избежать проблем мультиколлинеарности в линейных моделях (когда коэффициенты становятся неустойчивыми из‑за сильной взаимной зависимости признаков).
X_data_correlation = X_data.corr(method='pearson')
X_data_correlationДля наглядности построим тепловую карту корреляционной матрицы. Отметим на карте только те пары признаков, у которых коэффициент корреляции Пирсона больше 0.75:
plt.figure(figsize=(10, 8))
sns.heatmap(X_data_correlation > 0.75)
plt.show()Выпишите в список features отобранные в процессе анализа признаки (формат: features = ['VAR1', 'VAR2']):
features = # Ваш код здесьДатафрейм с отобранными входными признаками X_data_filtered:
X_data_filtered = X_data.loc[:, features]
X_data_filtered.head(n=5)Метрика accuracy (доля правильно классифицированных примеров от общего числа) будет общей и для задачи бинарной классификации, и для задачи многоклассовой классификации:
def accuracy(y_pred, y_true):
return torch.sum(y_pred == y_true) / len(y_true)2. Задача бинарной классификации
Изучите документацию к функции train_test_split, чтобы разделить исходные данные на обучающую, валидационную и тестовую выборки — стандартный этап подготовки данных для обучения и оценки моделей машинного обучения.
В первом её вызове отделите, например, 20% исходных данных (X_data_filtered, y_binary) в тестовую выборку (X_binary_test, y_binary_test), 80% оставьте для обучения (X_binary_train, y_binary_train). Через параметр stratify сохраните соотношение классов в обеих выборках.
Во втором вызове отделите 20% данных для обучения в тестовую выборку (X_binary_valid, y_binary_valid), 80% оставьте для финального обучения (X_binary_train, y_binary_train). Не забудьте про параметр stratify.
Примечание. Валидационная выборка помогает подбирать гиперпараметры (например, число эпох, скорость обучения) и следить за переобучением. Тестовая выборка «заморожена» до конца обучения и используется только для итоговой оценки, чтобы избежать смещения в оценке качества. Их соотношение с обучающей выборкой может быть и иным.
X_binary_train, X_binary_test, y_binary_train, y_binary_test = # Ваш код здесь
X_binary_train, X_binary_valid, y_binary_train, y_binary_valid = # Ваш код здесьВходные признаки для нейронной сети необходимо либо нормализовывать, либо стандартизировать — чтобы привести их к единому масштабу. Это ускоряет сходимость градиентного спуска и делает обучение стабильнее (спуск движется более прямо к минимуму, требует меньше итераций для сходимости, менее подвержен «взрыву» или «исчезновению» градиентов).
Нормализация (Min‑Max Scaling) приводит значения признака к фиксированному диапазону (обычно [0, 1]):
\[
x_{\text{norm}} = \frac{x - x_{\min}}{x_{\max} - x_{\min}}
\]
где:
- \(x\) — исходное значение признака;
- \(x_{\min}\) — минимальное значение признака в выборке;
- \(x_{\max}\) — максимальное значение признака в выборке.
Стандартизация преобразует распределение признака к среднему 0 и стандартному отклонению 1:
\[
x_{\text{standard}} = \frac{x - \mu}{\sigma}
\]
где:
- \(x\) — исходное значение признака;
- \(\mu\) — среднее значение признака в выборке;
- \(\sigma\) — стандартное отклонение признака в выборке.
Произведём стандартизацию входных признаков (но по аналогии можете произвести нормализацию):
X_means = X_binary_train.mean(axis=0)
X_stds = X_binary_train.std(axis=0, ddof=1)
X_binary_train = # Ваш код здесь
X_binary_valid = # Ваш код здесь
X_binary_test = # Ваш код здесьПредставим данные обучающей, валидационной и тестовой выборок в виде тензоров:
X_binary_train = torch.tensor(X_binary_train.values).float()
X_binary_valid = torch.tensor(X_binary_valid.values).float()
X_binary_test = torch.tensor(X_binary_test.values).float()
y_binary_train = torch.tensor(y_binary_train.values).reshape(-1, 1).float()
y_binary_valid = torch.tensor(y_binary_valid.values).reshape(-1, 1).float()
y_binary_test = torch.tensor(y_binary_test.values).reshape(-1, 1).float()Создайте экземпляр нейронной сети для решения задачи бинарной классификации — например, через nn.Sequential(), как было показано в первой лабораторной работе.
Важно соблюсти два ключевых условия при проектировании архитектуры:
- число входных признаков в первом слое (
in_features) должно точно соответствовать размерности признаков в обучающих данных (количеству столбцов вX_binary_train) (воспользуйтесь свойствомX_binary_train.shape); - в выходном слое необходимо использовать один нейрон с сигмоидальной функцией активации — напомним, что это гарантирует, что сеть будет выдавать значение в диапазоне [0, 1], интерпретируемое как вероятность принадлежности к положительному классу в задаче бинарной классификации.
model_binary = nn.Sequential(
# Ваш код здесь
)
model_binaryУдостоверимся, что наша сеть работает — рассчитаем по её выходу, который представляет собой предсказанные вероятности принадлежности к классу 1:
- значение функции потерь (бинарная кросс-энтропия);
- метрику accuracy.
y_binary_train_prob = model_binary(X_binary_train)
criterion = nn.BCELoss()
print(f'Loss: {criterion(y_binary_train_prob, y_binary_train).item():.6f}')
print(f'Accuracy: {accuracy((y_binary_train_prob > 0.5).float(), y_binary_train).item():.3f}')Теперь перейдём к обучению нейронной сети для решения поставленной задачи бинарной классификации.
Зададим
- экземпляр нейронной сети;
- количество эпох или итераций в процессе обучения;
- скорость обучение и коэффициент импульса;
- оптимизатор;
- функцию потерь (поскольку решается задача бинарной классификации, выберем бинарную кросс-энтропию
nn.BCELoss()).
Примечание. Вы можете использовать для обучения сети либо код ниже, либо модифицированный на его основе — например, реализовать функцию обучения нейронной сети и проверки её работы.
# Перебор seed для инициализации параметров
torch.manual_seed(seed=42)
model_binary = # Ваш код здесь
epochs = # Ваш код здесь
learning_rate = # Ваш код здесь
momentum = # Ваш код здесь
optimizer = # Ваш код здесь
criterion = nn.BCELoss()Обучение нейронной сети для решения задачи бинарной классификации:
loss_train_history, loss_valid_history = [], []
for epoch in range(epochs):
model_binary.train()
optimizer.zero_grad()
y_binary_train_prob = model_binary(X_binary_train)
loss_train = criterion(y_binary_train_prob, y_binary_train)
loss_train_history.append(loss_train.item())
loss_train.backward()
optimizer.step()
model_binary.eval()
# Отключаем градиенты для этапа валидации
with torch.no_grad():
y_binary_valid_prob = model_binary(X_binary_valid)
loss_valid = criterion(y_binary_valid_prob, y_binary_valid)
loss_valid_history.append(loss_valid.item())
if (epoch + 1) % 5 == 0:
clear_output(True)
plt.plot(range(1, epoch+2), loss_train_history, label='Train', color='green')
plt.plot(range(1, epoch+2), loss_valid_history, label='Valid', color='red')
plt.title(f'Epoch: {epoch + 1}, Loss Train: {loss_train_history[-1]:.6f}, Loss Valid: {loss_valid_history[-1]:.6f}')
plt.grid(True, alpha=0.3)
plt.legend(loc='best')
plt.show()
print('Accuracy')
print(f'Train: {accuracy((y_binary_train_prob > 0.5).float(), y_binary_train).item():.3f}')
print(f'Valid: {accuracy((y_binary_valid_prob > 0.5).float(), y_binary_valid).item():.3f}')После того, как вы подобрали параметры и добились приемлемого качества работы сети на обучающей и валидационной выборках, в качестве финального шага проверьте работу сети на отложенной тестовой выборке:
with torch.no_grad():
y_binary_test_prob = model_binary(X_binary_test)
loss_test = criterion(y_binary_test_prob, y_binary_test)
print(f'Loss Test: {loss_test.item():.6f}')
print(f'Accuracy Test: {accuracy((y_binary_valid_prob > 0.5).float(), y_binary_valid).item():.3f}')Если качество схожее, нейронная сеть не переобучена.
Поэкспериметируйте — сделайте несколько вариантов с разными архитектурами сети, оптимизаторами, значениями скорости обучения и коэффициента импульса. Выберите лучшие по качеству обучения и количеству эпох.
3. Задача многоклассовой классификации
Для решения задачи многоклассовой классификации по аналогии с задачей бинарной классификации:
- с помощью функции
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) с сохранением соотншений классов (сортов минеральной воды); - стандартизируйте или нормализуйте входные признаки;
- из векторов с выходным признаком вычтите единицу и представьте их в виде тензоров целочисленного формата (метод
.long()).
X_multiclass_train, X_multiclass_test, y_multiclass_train, y_multiclass_test = # Ваш код здесь
X_multiclass_train, X_multiclass_valid, y_multiclass_train, y_multiclass_valid = # Ваш код здесь# Ваш код здесьДля задач многоклассовой классификации в машинном обучении применяют функцию активации softmax, которая посредством нормализации преобразует вектор логитов (выходов последнего слоя) в распределение вероятностей:
\[
p_k = \frac{e^{z_k}}{\sum_{k=1}^{K} e^{z_k}},
\]
где \(z_k\) — выход для \(k\)-го класса, \(K\) — число классов.
- Свойства:
- все выходы ∈ [0, 1];
- сумма выходов = 1;
- максимальный выход соответствует наиболее вероятному классу.
- все выходы ∈ [0, 1];
Реализованная в PyTorch функция потерь nn.CrossEntropyLoss() объединяет в себе log_softmax (численно устойчивая версия softmax + логарифм) и NLLLoss (отрицательная логарифмическая правдоподобность). Она принимает на вход именно логиты.
Создадим экземпляр нейронной сети для решения задачи многоклассовой классификации — например, также через nn.Sequential():
- число входных признаков в первом слое (
in_features) должно точно соответствовать размерности признаков в обучающих данных (количеству столбцов вX_binary_train) (воспользуйтесь свойствомX_binary_train.shape); - количество нейроной в выходном слое должно совпадать с количеством классов;
- в связи с тем, что сеть нам должна выдавать логиты, функцию активации на выходном слое можно не использовать (хотя можно взять
nn.ReLU()).
model_multiclass = nn.Sequential(
nn.Linear(in_features=X_multiclass_train.shape[1], out_features=y_multiclass.nunique()),
nn.ReLU()
)
model_multiclassПропустив данные через эту сеть, получим логиты, представляющие собою вектора, чья длина равна количеству классов:
y_multiclass_train_logits = model_multiclass(X_multiclass_train)
y_multiclass_train_logits[:3]Проверим работу функции потерь с ними:
criterion = nn.CrossEntropyLoss()
criterion(y_multiclass_train_logits, y_multiclass_train)Отметим, что для задачи классификации нормализация через softmax не требуется — в качестве ответа (метки класса) берем индекс максимального элемента в логите:
y_multiclass_train_logits.argmax(dim=1)[:3]Метрика accuracy:
accuracy(y_multiclass_train_logits.argmax(dim=1), y_multiclass_train).item()По аналогии с задачей бинарной классификации создайте, обучите и проверьте нейронную сеть для решения задачи многоклассовой классификации:
# Ваш код здесьПоэкспериметируйте — сделайте несколько вариантов с разными архитектурами сети, оптимизаторами, значениями скорости обучения и коэффициента импульса. Выберите лучшие по качеству обучения и количеству эпох.
4. Задача многоклассовой классификации (с исключением одного из классов)
Введите метку класса, который хотите исключить (1, 2, 3, 4 или 5):
label_to_exclude = # Ваш код здесьМаска (логическое условие) исключения в выходном признаке меток с этим классом:
mask_to_exclude = y_multiclass != label_to_exclude
y_multiclass[mask_to_exclude]Значения входных признаков, которые соответствуют исключённому классу (X_data_exclude):
X_data_exclude = X_data_filtered.loc[~mask_to_exclude, :]
X_data_excludeЗначения входных признаков, которые соответствуют оставленным классам (X_data_include):
X_data_include = X_data_filtered.loc[mask_to_exclude, :]
X_data_includeДля корректной работы с нейронной сетью модифицируем выходной признак с метками оставленных классов — значения меток выше исключённого класса уменьшим на единицу, чтобы выровнять их порядок (в дальнейшем вернём исходные). Результатом будет вектор y_include:
y_include = y_multiclass[mask_to_exclude].apply(lambda y: y - 1 if y >= label_to_exclude else y)
y_includeПо аналогии с задачами выше:
- с помощью функции
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) с сохранением соотншений классов (сортов минеральной воды); - стандартизируйте или нормализуйте входные признаки и представьте их в виде тензоров;
- из векторов с выходным признаком вычтите единицу и представьте их в виде тензоров целочисленного формата (метод
.long()).
# Ваш код здесьПо аналогии с задаче многоклассовой классификации создайте нейронную сеть для этих данных:
model_include = nn.Sequential(
# Ваш код здесь
)
model_includeПолучим логиты (длина векторов уменьшилась до 4):
y_include_train_logits = model_include(X_include_train)
y_include_train_logits[:3]Проверим работу функцию потерь на этих логитах и модимцированном выходном признаке:
criterion = nn.CrossEntropyLoss()
criterion(y_include_train_logits, y_include_train)По аналогии с задачей многоклассовой классификации с полным количеством классов создайте, обучите и проверьте нейронную сеть:
# Ваш код здесьПоэкспериметируйте — сделайте несколько вариантов с разными архитектурами сети, оптимизаторами, значениями скорости обучения и коэффициента импульса. Выберите лучшие по качеству обучения и количеству эпох.
Значения входных признаков, которые соответствуют исключённому классу (X_data_exclude) так же, как и значения входных признаков из выборок выше, стандартизируйте или нормализируйте и представьте их в виде тензоров:
X_data_exclude = # Ваш код здесьПропустим обработанные признаки через обученную и проверенную нейронную сеть model_include, чтобы получить логиты.
По данным логитам произведём классификацию (с учётом метки исключённого класса), чтобы ответить на вопрос — к каким сортам минеральной воды из оставленных соответствуют образцы исключённого класса:
y_exclude_logits = model_include(X_data_exclude)
(y_exclude_logits.argmax(dim=1) + 1).apply_(lambda y: y + 1 if y >= label_to_exclude else y)Проделайте данный пункт, исключив каждый из пяти сортов. Результатом должны стать пять тензоров с восемью метками классов. Проанализируйте результаты.
Литература:
- Бородкин А.А., Елисеев В.Л. Основы и применение искусственных нейронных сетей. Сборник лабораторных работ: методическое пособие. – М.: Издательский дом МЭИ, 2017.
- MachineLearning.ru — профессиональный информационно-аналитический ресурс, посвященный машинному обучению, распознаванию образов и интеллектуальному анализу данных: http://www.machinelearning.ru
- Modern State of Artificial Intelligence — Online Masters program at MIPT: https://girafe.ai/