36 KiB
ЛАБОРАТОРНАЯ РАБОТА №3
Применение многослойного персептрона. Автоассоциативная ИНС
Цель работы: знакомство с применением многослойного персептрона для решения задач сжатия данных, прогнозирования временных рядов и распознавания образов.
Задание
- Открыть файл с данными по минеральной воде, который использовался при решении задач классификации в предыдущей лабораторной работе. Построить и обучить автоассоциативные нейронные сети с 2-мя и 3-мя нейронами в скрытом слое:
а) для исходных данных из 5-ти классов;
б) для исходных данных из 4-х классов.
Провести визуализацию данных в скрытом слое каждой сети на плоскость и в 3-х мерное пространство. Проанализировать полученные результаты. Выбрать и сохранить автоассоциативные ИНС, обеспечивающие наилучшее сжатие исходных данных.
- Исследовать возможности ИНС по прогнозированию поведения нелинейных динамических систем (построение странного аттрактора) на примере отображения Хенона. Аттрактор Хенона может быть получен из уравнений \(x_{n+1} = 1 - \alpha x_{n}^2 + y_{n}\) и \(y_{n+1} = \beta x_{n}\), где \(α = 1.4\), \(β = 0.3\). Для прогнозирования предлагается использовать многослойный персептрон и сеть с радиально-базисными функциями. Постройте также прогноз курса доллара на один день вперед. В качестве исходных данных загрузить актуальные данные с сайта центрального банка России (http://www.cbr.ru).
- Решить задачу распознавания 9-ти изображений самолетов. Исходные данные (файлы avia1.bmp, …, avia9.bmp) необходимо предварительно преобразовать в набор векторов со значениями признаков 0 или 1. Обученная нейронная сеть должна правильно определять модель самолета и его класс (истребитель/бомбардировщик). Принадлежность модели к определенному классу выбирается студентом самостоятельно.
Импорт библиотек:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import torch
from IPython.display import clear_output
from torch import nn
from sklearn.cluster import KMeans
from sklearn.linear_model import LinearRegression
%matplotlib inlineСреднеквадратическая ошибка:
def mse(y_pred, y_true):
return np.mean((y_pred - y_true) ** 2)Содержание:
1. Подготовка данных
2. Сеть с радиально-базисными функциями
3. Многослойный персептрон
4. Сравнение моделей
5. Прогнозирование курса доллара
1. Подготовка данных
Функция с реализацией отображения Хенона — уравнения \(x_{n+1} = 1 - \alpha x_{n}^2 + y_{n}\) и \(y_{n+1} = \beta x_{n}\), где \(α = 1.4\), \(β = 0.3\):
def xenon_map(x=0, y=0, alpha=1.4, beta=0.3):
x_next = 1 - alpha * x ** 2 + y
y_next = beta * x
return x_next, y_nextУкажите количество точек во временном ряде, который будет получен из отображения Хенона:
n_points = # Ваш код здесьСгенерируем необходимое количество точек — помним, что нужна только переменная \(x\):
x, y = 0, 0
xenon_data = []
for i in range(n_points):
x, y = xenon_map(x, y)
xenon_data.append(x)Поскольку исходная задача является задачей прогнозирования, то обучающая выборка должна включать в себя первый участок временного ряда, на котором будет обучаться модель. Валидационная выборка, на которой мы оцениваем качество предсказания модели, всегда должна идти после обучающей (аналогично с тестовой).
Выделим, к примеру, первые 70% данных временного ряда xenon_data для обучающей выборки, следующие данные по 15% отнесём к вылидационной и тестовой выборкам (вторая и третья части данных соответственно):
train_size = int(0.7 * n_points)
valid_size = int(0.15 * n_points)Представим данные для выборок на графике:
plt.plot(xenon_data, color='k', label='Xenon Data')
plt.axvspan(0, train_size, alpha=0.35, color='blue', label='Train')
plt.axvspan(train_size, train_size+valid_size, alpha=0.35, color='orange', label='Valid')
plt.axvspan(train_size+valid_size, n_points, alpha=0.35, color='green', label='Test')
plt.legend(loc='best')
plt.grid(True, alpha=0.3)
plt.show()Пройдём с единичным шагом по всем данным xenon_data скользящим окном длиною seq_length(например, длиною 10). Таким образом сформируем пары «вход‑выход» для модели:
X_data: последовательности изseq_lengthэлементов (скользящее окно поxenon_data),y_data: элемент, следующий сразу за каждой последовательностью вX_data.
Примечание. С помощью [:-1] мы убираем в X_data последнее окно, для которого нет «следующего значения» в y_data.
seq_length = 10
X_data = np.lib.stride_tricks.sliding_window_view(xenon_data, window_shape=seq_length)[:-1]
y_data = xenon_data[seq_length:]Посмотрим конец полученных данных. Видно, что последнее значение y_data не включено в окна из X_data:
X_data[-2:]y_data[-4:]Длина наших данных — без учёта последнего значения:
assert len(X_data) == len(y_data) == n_points - seq_lengthРазделим данные на обучающую, валидационную и тестовую выборку по заданному выше соотношению 70%/15%/15%:
X_train, X_valid, X_test = X_data[:train_size], X_data[train_size:train_size+valid_size], X_data[train_size+valid_size:]
y_train, y_valid, y_test = y_data[:train_size], y_data[train_size:train_size+valid_size], y_data[train_size+valid_size:]Отнормируйте или отстанлартизируйте входные и выходные данные.
Поскольку нормирующие или стандартизирующие величины (минимум, максимум, среднее, СКО) всегда расчитываются только по значениям обучающей выборки, берём из исходных данных xenon_data также значения, которые попадут в последнее окно (и снова исключаем самое последнее значение):
X_mean = np.mean(xenon_data[:train_size+seq_length-1])
X_std = np.std(xenon_data[:train_size+seq_length-1], ddof=1)
y_mean = np.mean(y_data[:train_size])
y_std = np.std(y_data[:train_size], ddof=1)
X_train_scaled = # Ваш код здесь
X_valid_scaled = # Ваш код здесь
X_test_scaled = # Ваш код здесь
y_train_scaled = # Ваш код здесь
y_valid_scaled = # Ваш код здесь
y_test_scaled = # Ваш код здесь2. Сеть с радиально-базисными функциями
РБФ-сеть (Radial Basis Function Network, RBF) — это нейронная сеть, которая использует радиально-базисные функции в качестве функций активации нейронов скрытого слоя. Архитектура такой сети обычно включает три слоя:
- Входной слой — принимает входные данные (значения временного ряда).
- Скрытый слой — содержит нейроны с радиальными базисными функциями. Каждый нейрон вычисляет расстояние между входным вектором и заранее определённым центром функции, а затем преобразует это расстояние с помощью радиальной функции.
- Выходной слой — линейно комбинирует (взвешенно суммирует) выходы скрытых нейронов, чтобы получить итоговый результат.
Таким образом, выходной сигнал РБФ‑сети вычисляется по формуле:
\[ y(\mathbf{x}) = \sum_{j=1}^{H} w_j \cdot \phi(\|\mathbf{x} - \mathbf{c}_j\|_2) \] где:
- \(y(\mathbf{x})\) — выходной сигнал сети для входного вектора \(\mathbf{x}\);
- \(H\) — количество нейронов в скрытом слое;
- \(w_j\) — вес связи от \(j\)-го нейрона скрытого слоя к выходному слою;
- \(\mathbf{c}_j\) — центр \(j\)-й радиально-базисной функции;
- \(\phi\) — радиально-базисная функция;
- \(\|\mathbf{x} - \mathbf{c}_j\|_2\) — евклидово расстояние между входным вектором \(\mathbf{x}\) и центром \(\mathbf{c}_j\).
Радиально-базисная функция — это функция, которая зависит только от расстояния между входным вектором и центром функции. Чаще всего используется гауссова функция: \[ \phi(r) = \exp\left(-\frac{r^2}{2\sigma^2}\right) \] где \(r = \|\mathbf{x} - \mathbf{c}\|_2\), \(\sigma\) — параметр ширины окна.
Обучение РБФ‑сетей обычно проходит в два этапа:
- Определение параметров радиально-базисной функции (центров \(\mathbf{c}_i\) и ширин \(\sigma_i\)):
- кластеризация (например, k‑means) для нахождения центров;
- эвристические методы или кросс‑валидация для ширин.
- Обучение выходных весов \(w_j\):
- решение задачи линейной регрессии (с использованием обучения с учителем, например, методом наименьших квадратов или градиентного спуска).
Таким образом, на первом этапе сеть определяет, где в пространстве признаков сосредоточены основные закономерности данных. Для этого, например, используются алгоритмы кластеризации: объекты (значения временного ряда) внутри одного кластера получатся максимально похожи друг на друга, а объекты разных классов — максимально различны (т.о. определим важные зоны или паттерны ряда).
После определения центров сеть анализирует, как каждая точка соотносится с ними. Это показывает гауссова РБФ — для каждой точки вычисляется расстояние до каждого из центров, и чем ближе точка к центру, тем выше значение функции (близко к 1), а также наоборот — чем дальше точка к центру, тем ниже (РБФ стремится к 0). Для каждой точки получается вектор активаций — набор чисел, показывающих, насколько она «похожа» на каждый из центров. Так, точка между двумя центрами даст средние значения для обоих, а точка рядом с одним центром — высокое значение для него и низкие для остальных.
На этих активациях для получения весов между скрытым и выходным слоями обучается линейная регрессия. Она находит оптимальные веса для каждого выхода скрытого слоя: какие локальные паттерны важнее для прогноза, какие — менее значимы.
Реализуем с помощью класса RBFPredictor РБФ-сеть с описанной выше архитектурой.
При инициализации (.__init__()) задаётся n_centers — число центров (нейронов скрытого слоя), устанавливается параметр ширины окна sigma для гауссовой функции, создаётся модель линейной регрессии linear_model для выходного слоя (взята из библиотеки sklearn).
Выбор n_centers центров из входных данных происходит в методе .fit(). Для этого используется алгоритм k‑means (взят из библиотеки sklearn).
Далее в скрытом слое (метод ._radial_basis()) для каждого входного вектора (объекта) вычисляются расстояния до всех центров. Расстояния преобразуются в значения гауссовой РБФ. На выходе получается матрица активаций размером (число объектов, число центров).
На данной матрице активаций скрытого слоя phi и целевых значениях y обучается модель линейной регрессии linear_model. Веса регрессии становятся весами связей от скрытого слоя к выходному.
При прогнозировании (метод .predict()) для новых данных вычисляются активации скрытого слоя (через гауссовы РБФ относительно тех же центров), а обученная линейная модель применяет веса к этим активациям и выдаёт финальный прогноз.
class RBFPredictor:
def __init__(self, n_centers, sigma=1.0):
self.n_centers = n_centers
self.sigma = sigma
self.centers = None
self.linear_model = LinearRegression()
def _radial_basis(self, X, centers):
distances = np.zeros((X.shape[0], centers.shape[0]))
for i, center in enumerate(centers):
distances[:, i] = np.sqrt(np.sum((X - center) ** 2, axis=1))
return np.exp(-(distances ** 2) / (2 * self.sigma ** 2))
def fit(self, X, y):
# Выбираем центры с помощью кластеризации
kmeans = KMeans(n_clusters=self.n_centers, random_state=42)
self.centers = kmeans.fit(X).cluster_centers_
# Вычисляем выход скрытого слоя
phi = self._radial_basis(X, self.centers)
# Обучаем линейный выходной слой
self.linear_model.fit(phi, y)
def predict(self, X):
phi = self._radial_basis(X, self.centers)
return self.linear_model.predict(phi)Обучите модель model_rbf. Для этого подберите подходящее количество центров n_centers и ширину окна sigma.
model_rbf = RBFPredictor(
# Ваш код здесь
)
model_rbf.fit(X_train_scaled, y_train_scaled)При подборе добейтесь примерно одинаковой (или хотя бы сопоставимой) ошибки на обучающей и вадилационной выборках:
pred_rbf_train = model_rbf.predict(X_train_scaled)
pred_rbf_valid = model_rbf.predict(X_valid_scaled)
print('Loss')
print(f'Train: {mse(pred_rbf_train, y_train_scaled):.6f}')
print(f'Valid: {mse(pred_rbf_valid, y_valid_scaled):.6f}')Проверка model_rbf на тестовых данных будет ниже.
3. Многослойный персептрон
Представим входные и выходные данные в виде тензоров PyTorch:
X_train_tensor = torch.tensor(X_train_scaled).float()
y_train_tensor = torch.tensor(y_train_scaled).reshape(-1, 1).float()
X_valid_tensor = torch.tensor(X_valid_scaled).float()
y_valid_tensor = torch.tensor(y_valid_scaled).reshape(-1, 1).float()
X_test_tensor = torch.tensor(X_test_scaled).float()
y_test_tensor = torch.tensor(y_test_scaled).reshape(-1, 1).float()Реализуйте в классе MLPPredictor с помощью полносвязных слоёв nn.Linear многослойный персептрон. В качестве промежуточных функций активации используйте nn.ReLU(), а поскольку решается задача прогнозирования вещественных данных, на выходе сети функцию активации можно не добавлять.
class MLPPredictor(nn.Module):
def __init__(self, input_size):
super().__init__()
self.seq = nn.Sequential(
# Ваш код здесь
)
def forward(self, x):
return self.seq(x)Создайте экземпляр модели:
model_mlp = MLPPredictor(input_size=seq_length)
model_mlpПроверим, как модель обучается. Зададим оптимизатор и среднеквадратическую функцию потерь:
optimizer = torch.optim.SGD(model_mlp.parameters(), lr=0.01)
criterion = nn.MSELoss()Рассчитаем значение функции потерь:
pred_mlp_train = model_mlp(X_train_tensor)
loss = criterion(pred_mlp_train, y_train_tensor)
lossВыполните несколько раз эту и предыдущую ячейку, чтобы убедиться в уменьшении ошибки:
loss.backward()
optimizer.step()
optimizer.zero_grad()Задайте параметры для обучения нейронной сети:
# Перебор seed для инициализации параметров
torch.manual_seed(seed=42)
model_mlp = # Ваш код здесь
epochs = # Ваш код здесь
learning_rate = # Ваш код здесь
momentum = # Ваш код здесь
optimizer = # Ваш код здесь
criterion = # Ваш код здесьОбучение нейронной сети:
loss_train_history, loss_valid_history = [], []
for epoch in range(epochs):
# Ваш код здесь
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()4. Сравнение моделей
Получим прогнозы на тестовой выборке от РБФ-сети и многослойного персептрона:
pred_rbf_test = model_rbf.predict(X_test_scaled)
with torch.no_grad():
pred_mlp_test = model_mlp(X_test_tensor).squeeze().numpy()Поскольку обучение шло на нормированных или стандартизированных данных, приведём прогнозы к исходной шкале и рассчитаем среднеквадратическую ошибку:
pred_rbf_test_descaled = (pred_rbf_test * y_std + y_mean)
pred_mlp_test_descaled = (pred_mlp_test * y_std + y_mean)
print('Loss')
print(f'RBF: {mse(pred_rbf_test_descaled, y_test):.6f}')
print(f'MLP: {mse(pred_mlp_test_descaled, y_test):.6f}')Построим графики прогнозов для сравнения с исходными тестовыми данными:
plt.figure(figsize=(12, 4))
plt.plot(y_test, label='True Values')
plt.plot(pred_rbf_test_descaled, label='RBF Pred')
plt.plot(pred_mlp_test_descaled, label='MLP Pred')
plt.grid(True, alpha=0.3)
plt.legend(loc='best')
plt.show()5. Прогнозирование курса доллара
По аналогии с отображением Хенона самостоятельно реализуйте прогнозирование курса доллара.
Для этого скачайте актуальную информацию по курсу с сайта http://www.cbr.ru в формате .xlsx.
Введите в виже строки имя скачанного файла с расширением:
dollar_course_filename = # Ваш код здесьЗагрузим данные в массив NumPy:
dollar_data = pd.read_excel(dollar_course_filename).curs[::-1]
# По необходимости можно сгладить данные скользящим окном -
# в данном случае берём среднее за 7 дней
dollar_data = dollar_data.rolling(window=7).mean().dropna()
dollar_data = dollar_data.values# Ваш код здесь# Ваш код здесь# Ваш код здесьЛитература:
- Бородкин А.А., Елисеев В.Л. Основы и применение искусственных нейронных сетей. Сборник лабораторных работ: методическое пособие. – М.: Издательский дом МЭИ, 2017.
- MachineLearning.ru — профессиональный информационно-аналитический ресурс, посвященный машинному обучению, распознаванию образов и интеллектуальному анализу данных: http://www.machinelearning.ru
- Modern State of Artificial Intelligence — Online Masters program at MIPT: https://girafe.ai/