форкнуто от main/is_dnn
Вы не можете выбрать более 25 тем
Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
31 KiB
31 KiB
Лабораторная работа №2
Обнаружение аномалий с помощью автокодировщиков
Выполнили: Троянов Даниил Сергеевич, Чернов Данила Евгеньевич
Группа: А-01-22
Бригада: 1
Задание 1
1) Импорт необходимых библиотек и модулей
import os
import numpy as np
import matplotlib.pyplot as plt
import lab02_lib as lib
from unittest.mock import patch
import builtins
# Создаем директорию для выходных файлов
os.makedirs('out', exist_ok=True)
print('Библиотеки успешно импортированы')2) Генерация индивидуального набора двумерных данных
Сгенерируем набор данных с координатами центра (1, 1), где 1 – номер бригады.
# Генерация датасета для бригады 1
brigade_num = 1
data = lib.datagen(brigade_num, brigade_num, 1000, 2)
# Вывод данных и размерности
print('Исходные данные (первые 10 строк):')
print(data[:10])
print('\nРазмерность данных:')
print(data.shape)
print(f'\nЦентр кластера: ({brigade_num}, {brigade_num})')3) Создание и обучение автокодировщика AE1 простой архитектуры
Создадим простой автокодировщик с минимальной архитектурой и небольшим количеством эпох обучения.
# Обучение AE1 с использованием функции из lab02_lib
# Для простой архитектуры используем вариант с пользовательской архитектурой
# Архитектура: 2 -> 1 -> 2 (1 скрытый слой с 1 нейроном)
print('Обучение автокодировщика AE1...')
print('Архитектура: 2 -> 1 -> 2 (простая)')
# Автоматизируем ввод для выбора пользовательской архитектуры
with patch('builtins.input', side_effect=['1', '1', '1']):
ae1_trained, IRE1, IREth1 = lib.create_fit_save_ae(
data,
'out/AE1.h5',
'out/AE1_ire_th.txt',
1000, # epochs
True, # verbose_show
300, # patience
early_stopping_delta=0.001
)
# Вычисление MSE из истории обучения (приблизительно)
# Для точного значения нужно сохранить историю, но функция не возвращает её
# Используем предсказание для оценки
X_pred_ae1 = ae1_trained.predict(data, verbose=0)
mse_ae1 = np.mean((data - X_pred_ae1) ** 2)
print(f'\nОбучение завершено!')
print(f'MSE_stop (приблизительно): {mse_ae1:.6f}')
print(f'Порог IRE: {IREth1:.6f}')
print(f'Количество скрытых слоев: 1')
print(f'Количество нейронов в скрытых слоях: 1')4) Построение графика ошибки реконструкции для AE1
# Построение графика ошибки реконструкции
lib.ire_plot('training', IRE1, IREth1, 'AE1')
print(f'Порог ошибки реконструкции (IREth1): {IREth1:.6f}')5) Создание и обучение автокодировщика AE2 с усложненной архитектурой
# Обучение AE2 с использованием функции из lab02_lib
# Для усложненной архитектуры используем вариант с пользовательской архитектурой
# Архитектура: 2 -> 8 -> 4 -> 2 -> 1 -> 2 -> 4 -> 8 -> 2 (6 скрытых слоев)
print('Обучение автокодировщика AE2...')
print('Архитектура: 2 -> 8 -> 4 -> 2 -> 1 -> 2 -> 4 -> 8 -> 2 (усложненная)')
# Автоматизируем ввод для выбора пользовательской архитектуры
# 7 скрытых слоев: 8 4 2 1 2 4 8
with patch('builtins.input', side_effect=['1', '7', '8 4 2 1 2 4 8']):
ae2_trained, IRE2, IREth2 = lib.create_fit_save_ae(
data,
'out/AE2.h5',
'out/AE2_ire_th.txt',
3000, # epochs
True, # verbose_show
300, # patience
early_stopping_delta=0.001
)
# Вычисление MSE из предсказания
X_pred_ae2 = ae2_trained.predict(data, verbose=0)
mse_ae2 = np.mean((data - X_pred_ae2) ** 2)
print(f'\nОбучение завершено!')
print(f'MSE_stop (приблизительно): {mse_ae2:.6f}')
print(f'Порог IRE: {IREth2:.6f}')
print(f'Количество скрытых слоев: 6')
print(f'Количество нейронов в скрытых слоях: 8-4-2-1-2-4-8')6) Построение графика ошибки реконструкции для AE2
# Построение графика ошибки реконструкции
lib.ire_plot('training', IRE2, IREth2, 'AE2')
print(f'Порог ошибки реконструкции (IREth2): {IREth2:.6f}')7) Расчет характеристик качества обучения EDCA для AE1 и AE2
# Расчет EDCA для AE1
numb_square = 20
xx, yy, Z1 = lib.square_calc(numb_square, data, ae1_trained, IREth1, '1', True)
# Сохраняем результаты EDCA для AE1 сразу после расчета
excess_ae1 = None
approx_ae1 = None
try:
with open('out/result.txt', 'r') as f:
content = f.read()
if 'AE1' in content:
lines = content.split('\n')
for line in lines:
if 'Excess' in line and excess_ae1 is None:
excess_ae1 = float(line.split('=')[1].strip())
elif 'Approx' in line and approx_ae1 is None:
approx_ae1 = float(line.split('=')[1].strip())
except:
pass
print(f'\nСохраненные результаты для AE1:')
print(f'Excess: {excess_ae1}')
print(f'Approx: {approx_ae1}')# Расчет EDCA для AE2
xx, yy, Z2 = lib.square_calc(numb_square, data, ae2_trained, IREth2, '2', True)
# Сохраняем результаты EDCA для AE2 сразу после расчета
excess_ae2 = None
approx_ae2 = None
try:
with open('out/result.txt', 'r') as f:
content = f.read()
if 'AE2' in content:
lines = content.split('\n')
for line in lines:
if 'Excess' in line and excess_ae2 is None:
excess_ae2 = float(line.split('=')[1].strip())
elif 'Approx' in line and approx_ae2 is None:
approx_ae2 = float(line.split('=')[1].strip())
except:
pass
print(f'\nСохраненные результаты для AE2:')
print(f'Excess: {excess_ae2}')
print(f'Approx: {approx_ae2}')# Сравнение характеристик качества обучения и областей аппроксимации
lib.plot2in1(data, xx, yy, Z1, Z2)8) Создание тестовой выборки
Создадим тестовую выборку из элементов, которые AE1 распознает как норму, а AE2 детектирует как аномалии.
# Создание тестовой выборки
# Точки, которые находятся на среднем расстоянии от центра (1, 1)
# чтобы AE1 их не распознал как аномалии (IRE < порог AE1 ~2.06),
# но AE2 распознал как аномалии (IRE > порог AE2 ~0.41)
# Выбираем точки на расстоянии примерно 1-1.5 от центра
data_test = np.array([
[-0.5, 0.5],
[1, 0.5],
[0.2, 1.2],
[0, 0.1]
])
print('Тестовая выборка:')
print(data_test)
print(f'\nРазмерность: {data_test.shape}')
print(f'\nЦентр обучающих данных: (1, 1)')
print(f'Расстояния от центра:')
for i, point in enumerate(data_test):
dist = np.sqrt((point[0] - 1)**2 + (point[1] - 1)**2)
print(f' Точка {i+1} {point}: расстояние = {dist:.3f}')9) Применение автокодировщиков к тестовым данным
# Тестирование AE1
predicted_labels1, ire1_test = lib.predict_ae(ae1_trained, data_test, IREth1)
lib.anomaly_detection_ae(predicted_labels1, ire1_test, IREth1)
lib.ire_plot('test', ire1_test, IREth1, 'AE1')# Тестирование AE2
predicted_labels2, ire2_test = lib.predict_ae(ae2_trained, data_test, IREth2)
lib.anomaly_detection_ae(predicted_labels2, ire2_test, IREth2)
lib.ire_plot('test', ire2_test, IREth2, 'AE2')# Визуализация элементов обучающей и тестовой выборки
lib.plot2in1_anomaly(data, xx, yy, Z1, Z2, data_test)# Подсчет обнаруженных аномалий
anomalies_ae1 = int(predicted_labels1.sum())
anomalies_ae2 = int(predicted_labels2.sum())
# Используем сохраненные ранее значения EDCA метрик
# (они были сохранены в ячейках после вызова square_calc)
# Если переменные не определены, пытаемся прочитать из файла
try:
excess_ae1_val = excess_ae1 if excess_ae1 is not None else None
except NameError:
excess_ae1_val = None
try:
approx_ae1_val = approx_ae1 if approx_ae1 is not None else None
except NameError:
approx_ae1_val = None
try:
excess_ae2_val = excess_ae2 if excess_ae2 is not None else None
except NameError:
excess_ae2_val = None
try:
approx_ae2_val = approx_ae2 if approx_ae2 is not None else None
except NameError:
approx_ae2_val = None
print('Таблица 1 - Результаты задания №1')
print('=' * 120)
print(f'{"Модель":<10} {"Скрытых слоев":<15} {"Нейроны":<25} {"Эпох":<10} {"MSE_stop":<12} {"Порог IRE":<12} {"Excess":<10} {"Approx":<10} {"Аномалий":<10}')
print('-' * 120)
print(f'AE1 {1:<15} {1:<25} {1000:<10} {mse_ae1:<12.6f} {IREth1:<12.6f} {excess_ae1_val if excess_ae1_val is not None else "N/A":<10} {approx_ae1_val if approx_ae1_val is not None else "N/A":<10} {anomalies_ae1}/{len(data_test):<10}')
print(f'AE2 {6:<15} {"8-4-2-1-2-4-8":<25} {3000:<10} {mse_ae2:<12.6f} {IREth2:<12.6f} {excess_ae2_val if excess_ae2_val is not None else "N/A":<10} {approx_ae2_val if approx_ae2_val is not None else "N/A":<10} {anomalies_ae2}/{len(data_test):<10}')
print('=' * 120)Задание 2
1) Изучение набора реальных данных Letter
Набор данных Letter представляет собой характеристики букв английского алфавита. Для обнаружения аномалий нормальные примеры используются для обучения, а аномальные - для тестирования.
# Загрузка обучающей и тестовой выборки Letter
train_letter = np.loadtxt('data/letter_train.txt', dtype=float)
test_letter = np.loadtxt('data/letter_test.txt', dtype=float)
print('Обучающая выборка Letter:')
print(f'Размерность: {train_letter.shape}')
print(f'Количество признаков: {train_letter.shape[1]}')
print(f'Количество примеров: {train_letter.shape[0]}')
print(f'\nПервые 5 строк:')
print(train_letter[:5])
print(f'\nТестовая выборка Letter:')
print(f'Размерность: {test_letter.shape}')
print(f'Количество примеров: {test_letter.shape[0]}')2.1) Нормализация данных (дополнительное исследование, для сравнения)
Создадим нормализованную версию данных для сравнения результатов обучения.
# Нормализация данных для сравнения результатов
from sklearn.preprocessing import StandardScaler
# Создаем нормализованные версии данных
scaler = StandardScaler()
train_letter_normalized = scaler.fit_transform(train_letter)
test_letter_normalized = scaler.transform(test_letter)
print('Нормализация данных выполнена')
print(f'\nИсходные данные (первые 3 признака первого примера):')
print(f' train_letter[0, :3] = {train_letter[0, :3]}')
print(f'\nНормализованные данные (первые 3 признака первого примера):')
print(f' train_letter_normalized[0, :3] = {train_letter_normalized[0, :3]}')
print(f'\nСтатистика нормализованных данных:')
print(f' Среднее: {train_letter_normalized.mean(axis=0)[:5]}...')
print(f' Стд. отклонение: {train_letter_normalized.std(axis=0)[:5]}...')2.2) Обучение автокодировщика на ненормализованных данных
# Обучение автокодировщика для Letter на НЕнормализованных данных
# Входной и выходной слои создаются автоматически по размеру данных (32 признака)
# Мы задаем только скрытые слои
# Полная архитектура: 32(вход) -> 100 -> 68 -> 48 -> 32 -> 24 -> 16 -> 8 -> 16 -> 24 -> 32 -> 48 -> 64 -> 100 -> 32(выход)
# 11 скрытых слоев: 100 64 48 32 24 16 8 16 24 32 48 64 100
import warnings
# Подавляем предупреждение о формате сохранения HDF5
warnings.filterwarnings('ignore', category=UserWarning, module='absl')
print('Обучение автокодировщика для данных Letter (НЕнормализованные данные)...')
print('Архитектура: 32(вход) -> 100 -> 68 -> 48 -> 32 -> 24 -> 16 -> 8 -> 16 -> 24 -> 32 -> 48 -> 64 -> 100 -> 32(выход)')
# Автоматизируем ввод для выбора пользовательской архитектуры
with patch('builtins.input', side_effect=['1', '17', ' 100 86 72 64 48 32 24 16 8 16 24 32 48 64 72 86 100']):
ae_letter_raw, IRE_letter_raw, IREth_letter_raw = lib.create_fit_save_ae(
train_letter,
'out/Letter_AE_raw.h5',
'out/Letter_AE_raw_ire_th.txt',
100000, # epochs
False, # verbose_show
10000, # patience
verbose_every_n_epochs=500
)
# Вычисление MSE из предсказания
X_pred_letter_raw = ae_letter_raw.predict(train_letter, verbose=0)
mse_letter_raw = np.mean((train_letter - X_pred_letter_raw) ** 2)
print(f'\nОбучение завершено (НЕнормализованные данные)!')
print(f'MSE_stop (приблизительно): {mse_letter_raw:.6f}')
print(f'Порог IRE: {IREth_letter_raw:.6f}')
print(f'Количество скрытых слоев: 11')
print(f'Количество нейронов в скрытых слоях: 100 -> 68 -> 48 -> 32 -> 24 -> 16 -> 8 -> 16 -> 24 -> 32 -> 48 -> 64 -> 100')2.3) Обучение автокодировщика на нормализованных данных
# Обучение автокодировщика для Letter на нормализованных данных
# Та же архитектура для сравнения: 32(вход) -> 100 -> 68 -> 48 -> 32 -> 24 -> 16 -> 8 -> 16 -> 24 -> 32 -> 48 -> 64 -> 100 -> 32(выход)
# 11 скрытых слоев: 100 86 72 64 48 32 24 16 8 16 24 32 48 64 72 86 100
print('Обучение автокодировщика для данных Letter (нормализованные данные)...')
print('Архитектура: 32(вход) -> 100 -> 68 -> 48 -> 32 -> 24 -> 16 -> 8 -> 16 -> 24 -> 32 -> 48 -> 64 -> 100 -> 32(выход)')
# Автоматизируем ввод для выбора пользовательской архитектуры
with patch('builtins.input', side_effect=['1', '17', ' 100 86 72 64 48 32 24 16 8 16 24 32 48 64 72 86 100']):
ae_letter_norm, IRE_letter_norm, IREth_letter_norm = lib.create_fit_save_ae(
train_letter_normalized,
'out/Letter_AE_norm.h5',
'out/Letter_AE_norm_ire_th.txt',
20000, # epochs
True, # verbose_show
200, # patience
verbose_every_n_epochs=500
)
# Вычисление MSE из предсказания
X_pred_letter_norm = ae_letter_norm.predict(train_letter_normalized, verbose=0)
mse_letter_norm = np.mean((train_letter_normalized - X_pred_letter_norm) ** 2)
print(f'\nОбучение завершено (нормализованные данные)!')
print(f'MSE_stop (приблизительно): {mse_letter_norm:.6f}')
print(f'Порог IRE: {IREth_letter_norm:.6f}')
print(f'Количество скрытых слоев: 11')
print(f'Количество нейронов в скрытых слоях: 100 -> 68 -> 48 -> 32 -> 24 -> 16 -> 8 -> 16 -> 24 -> 32 -> 48 -> 64 -> 100')2.4) Сравнение результатов обучения на нормализованных и ненормализованных данных
# Сравнение результатов
print('=' * 100)
print('СРАВНЕНИЕ РЕЗУЛЬТАТОВ ОБУЧЕНИЯ')
print('=' * 100)
print(f'{"Параметр"} {"НЕнормализованные"} {"Нормализованные"}')
print('-' * 100)
print(f'{"MSE_stop"} {mse_letter_raw} {mse_letter_norm}')
print(f'{"Порог IRE"} {IREth_letter_raw} {IREth_letter_norm:}')
print(f'{"Архитектура":<30} {"17 слоев"} {"17 слоев"}')
print(f'{"Нейроны"} {"32(вход) -> 100 -> 68 -> 48 -> 32 -> 24 -> 16 -> 8 -> 16 -> 24 -> 32 -> 48 -> 64 -> 100 -> 32(выход)"} {"32(вход) -> 100 -> 68 -> 48 -> 32 -> 24 -> 16 -> 8 -> 16 -> 24 -> 32 -> 48 -> 64 -> 100 -> 32(выход)"}')
print('=' * 100)
# Выбираем лучшую модель для дальнейшей работы
if mse_letter_norm < mse_letter_raw:
print('\nЛучшая модель: НОРМАЛИЗОВАННЫЕ данные')
ae_letter = ae_letter_norm
IRE_letter = IRE_letter_norm
IREth_letter = IREth_letter_norm
mse_letter = mse_letter_norm
test_letter_used = test_letter_normalized
print(f'Используем эту модель для дальнейших экспериментов')
else:
print('\nЛучшая модель: НЕнормализованные данные')
ae_letter = ae_letter_raw
IRE_letter = IRE_letter_raw
IREth_letter = IREth_letter_raw
mse_letter = mse_letter_raw
test_letter_used = test_letter
print(f'Используем эту модель для дальнейших экспериментов')3) Построение графика ошибки реконструкции для Letter
# Построение графика ошибки реконструкции для выбранной модели
lib.ire_plot('training', IRE_letter, IREth_letter, 'Letter_AE')
print(f'Порог ошибки реконструкции: {IREth_letter:.6f}')4) Применение автокодировщика к тестовой выборке Letter
# Тестирование автокодировщика на тестовой выборке
# Используем выбранную модель (лучшую из нормализованных/ненормализованных)
predicted_labels_letter, ire_letter_test = lib.predict_ae(ae_letter, test_letter_used, IREth_letter)
# Подсчет обнаруженных аномалий
num_anomalies = int(predicted_labels_letter.sum())
total_test = len(test_letter_used)
anomaly_percentage = (num_anomalies / total_test) * 100
print(f'Обнаружено аномалий: {num_anomalies} из {total_test}')
print(f'Процент обнаруженных аномалий: {anomaly_percentage:.1f}%')
lib.anomaly_detection_ae(predicted_labels_letter, ire_letter_test, IREth_letter)
lib.ire_plot('test', ire_letter_test, IREth_letter, 'Letter_AE')5) Результаты задания №2
Результаты исследования занесены в таблицу:
print('Таблица 2 - Результаты задания №2')
print('=' * 100)
print(f'{"Dataset":<15} {"Скрытых слоев":<15} {"Нейроны":<40} {"Эпох":<10} {"MSE_stop":<12} {"Порог IRE":<12} {"% аномалий":<12}')
print('-' * 100)
print(f'Letter {11:<15} {"32(вход) -> 100 -> 68 -> 48 -> 32 -> 24 -> 16 -> 8 -> 16 -> 24 -> 32 -> 48 -> 64 -> 100 -> 32(выход)":<40} {20000:<10} {mse_letter:<12.6f} {IREth_letter:<12.6f} {anomaly_percentage:.1f}%')
print('=' * 100)Выводы
Требования к данным для обучения:
- Данные для обучения должны быть без аномалий, чтобы автокодировщик смог рассчитать верное пороговое значение
Требования к архитектуре автокодировщика:
- Архитектура должна постепенно сужаться к бутылочному горлышку, а затем постепенно возвращаться к исходным выходным размерам
- Для двумерных данных оптимальное количество скрытых слоев: 3-6
- Для многомерных данных (32 признака) оптимальное количество скрытых слоев: от 7-ми
Требования к количеству эпох обучения:
- Для простых данных (2D): 1000-3000 эпох
- Для сложных данных (многомерных): до 100000 эпох
Требования к ошибке MSE_stop:
- Для двумерных данных: оптимальная ошибка MSE-stop в районе 0.01
- Для многомерных данных: оптимальная ошибка MSE-stop в районе 0.1-0.01
Требования к порогу обнаружения аномалий:
- Для двумерных данных: значение порога в районе 0.4-2.5
- Для многомерных данных: значение порога не больше 1.6
Требования к характеристикам качества обучения EDCA:
- Значение Excess должно быть как можно ближе к 0
- Значение Deficit должно быть как можно ближе к 0
- Значение Coating должно быть как можно ближе к 1
- Значение Approx должно быть как можно ближе к 1