# Лабораторная работа №2: Обнаружение аномалий с помощью автокодировщиков
**Аникеев А.А; Чагин С.А. — А-02-22**
## Вариант 2 (номер бригады k=5) - данные WBC

### Цель работы

Получить практические навыки создания, обучения и применения искусственных нейронных сетей типа автокодировщик. 
Исследовать влияние архитектуры автокодировщика и количества эпох обучения на области в пространстве признаков,
распознаваемые автокодировщиком после обучения. Научиться оценивать качество обучения автокодировщика на основе
ошибки реконструкции и новых метрик EDCA. Научиться решать актуальную задачу обнаружения аномалий в данных с
помощью автокодировщика как одноклассового классификатора.

### Определение варианта

- Номер бригады: k = 5
- N = k mod 3 = 5 mod 3 = 2
- Вариант 2 => данные **WBC**

### Подготовка среды

```python
import os
os.chdir('/content/drive/MyDrive/Colab Notebooks')
```

```python
from google.colab import drive
drive.mount('/content/drive')
import os
work_dir = '/content/drive/MyDrive/Colab Notebooks/is_lab2'
os.makedirs(work_dir, exist_ok=True)
os.chdir(work_dir)
os.makedirs('out', exist_ok=True)
dataset_name = 'WBC'
base_url = "http://uit.mpei.ru/git/main/is_dnn/raw/branch/main/labworks/LW2/"
!wget -N {base_url}lab02_lib.py
!wget -N {base_url}data/{dataset_name}_train.txt
!wget -N {base_url}data/{dataset_name}_test.txt
!cp {dataset_name}_train.txt train.txt
!cp {dataset_name}_test.txt test.txt
print("Файлы успешно скачаны!")
print("Содержимое рабочей директории:")
!ls -la
```

---

## ЗАДАНИЕ 1

### Пункт №1. Импорт необходимых для работы библиотек и модулей.
```python
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_blobs
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Activation
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping
import lab02_lib as lib

# Параметры для варианта 5
k = 5  # номер бригады
center_coords = (k, k)  # координаты центра (5, 5)
```

**Описание:** Импортируем необходимые библиотеки, модули, устанавливаются параметры для варианта 2.

### Пункт №2. Генерация индивидуального набора двумерных данных в пространстве признаков.
```python

print("Генерация синтетических данных с центром в (5, 5)...")

data = lib.datagen(k, k, 1000, 2)

print(f"Сгенерировано {len(data)} точек")
print(f"Центр данных: {center_coords}")
print(f"Размерность данных: {data.shape}")
```

**Результат выполнения:**
```
Сгенерировано 1000 точек
Центр данных: (5, 5)
Размерность данных: (1000, 2)
```

![Результаты](1.png)

### Пункт №3. Создание и обучение автокодировщика AE1 простой архитектуры.
```python
print("="*50)
print("Обучение AE1")
print("="*50)

def create_simple_ae():
    model = Sequential()
    
    model.add(Dense(2, input_shape=(2,), activation='tanh'))
    
    model.add(Dense(1, activation='tanh'))
    
    model.add(Dense(2, activation='linear'))
    return model

ae1 = create_simple_ae()
ae1.compile(optimizer=Adam(learning_rate=0.001), loss='mse')

print("Архитектура AE1:")
ae1.summary()

print("\nНачало обучения AE1...")
history_ae1 = ae1.fit(data, data, 
                      epochs=1000, 
                      batch_size=32, 
                      validation_split=0.2,
                      verbose=1,
                      callbacks=[EarlyStopping(patience=300, restore_best_weights=True)])

ae1.save('out/AE1.h5')

X_pred_ae1 = ae1.predict(data)
reconstruction_errors_ae1 = np.mean(np.square(data - X_pred_ae1), axis=1)
threshold_ae1 = np.max(reconstruction_errors_ae1)

print("\nАнализ результатов AE1")
mse_ae1 = history_ae1.history['loss'][-1]
print(f"Финальная ошибка MSE AE1: {mse_ae1:.6f}")
print(f"Порог ошибки реконструкции AE1: {threshold_ae1:.6f}")

plt.figure(figsize=(15, 4))

plt.subplot(1, 3, 1)
plt.plot(history_ae1.history['loss'], label='Training Loss', color='blue')
plt.plot(history_ae1.history['val_loss'], label='Validation Loss', color='red')
plt.title('AE1: Ошибка обучения (MSE)')
plt.xlabel('Эпоха')
plt.ylabel('MSE')
plt.legend()
plt.grid(True, alpha=0.3)

plt.subplot(1, 3, 2)
plt.plot(reconstruction_errors_ae1, 'b-', alpha=0.7, linewidth=0.8)
plt.axhline(y=threshold_ae1, color='red', linestyle='--', linewidth=2, 
           label=f'Порог: {threshold_ae1:.2f}')
plt.title('AE1: Ошибки реконструкции')
plt.xlabel('Номер точки')
plt.ylabel('Ошибка реконструкции')
plt.legend()
plt.grid(True, alpha=0.3)

plt.subplot(1, 3, 3)
plt.hist(reconstruction_errors_ae1, bins=20, alpha=0.7, color='blue', edgecolor='black')
plt.axvline(threshold_ae1, color='red', linestyle='--', linewidth=2, 
           label=f'Порог: {threshold_ae1:.2f}')
plt.title('AE1: Распределение ошибок')
plt.xlabel('Ошибка реконструкции')
plt.ylabel('Частота')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('out/ae1_detailed_results.png', dpi=300, bbox_inches='tight')
plt.show()

with open('out/AE1_ire_th.txt', 'w') as f:
    f.write(str(threshold_ae1))

ae1_trained = ae1
IRE1 = reconstruction_errors_ae1
IREth1 = threshold_ae1

print(f"Обучение AE1 завершено!")
print(f"Минимальная ошибка: {np.min(IRE1):.6f}")
print(f"Максимальная ошибка: {np.max(IRE1):.6f}")
print(f"Средняя ошибка: {np.mean(IRE1):.6f}")
```

**Описание:** Создается автокодировщик AE1 с простой архитектурой, одним скрытым слоем с одним нейроном.

**Результаты обучения AE1:**

Финальная ошибка MSE AE1: 0.009326

Порог ошибки реконструкции AE1: 0.067896

Минимальная ошибка: 0.000035

Максимальная ошибка: 0.067896

![Результаты AE1](2.png)

### Пункт №4. Создание и обучение второго автокодировщика AE2 усложненной архитектуры.

```python
print("="*50)
print("Обучение AE2")
print("="*50)

print("Используется архитектура по умолчанию: [2-3-2-1-2-3-2]")
print("Количество скрытых слоев: 5")
print("Нейроны в скрытых слоях: 3-2-1-2-3")

def create_ae2_default():
    model = Sequential()

    model.add(Dense(2, input_shape=(2,), activation='tanh'))

    model.add(Dense(3, activation='tanh'))
    model.add(Dense(2, activation='tanh'))
    model.add(Dense(1, activation='tanh'))
    model.add(Dense(2, activation='tanh'))
    model.add(Dense(3, activation='tanh'))

    model.add(Dense(2, activation='linear'))

    return model

ae2 = create_ae2_default()
ae2.compile(optimizer=Adam(learning_rate=0.001), loss='mse')

print("\nАрхитектура AE2:")
ae2.summary()

print(f"\nНачало обучения AE2 (patience=300)...")
history_ae2 = ae2.fit(data, data,
                      epochs=3000,
                      batch_size=32,
                      validation_split=0.2,
                      verbose=1,
                      callbacks=[EarlyStopping(patience=300, restore_best_weights=True)])

ae2.save('out/AE2.h5')

X_pred_ae2 = ae2.predict(data)
reconstruction_errors_ae2 = np.mean(np.square(data - X_pred_ae2), axis=1)
threshold_ae2 = np.max(reconstruction_errors_ae2)

print("\n" + "="*50)
print("АНАЛИЗ РЕЗУЛЬТАТОВ AE2")
print("="*50)
mse_ae2 = history_ae2.history['loss'][-1]
print(f"Финальная ошибка MSE AE2: {mse_ae2:.6f}")
print(f"Порог ошибки реконструкции AE2: {threshold_ae2:.6f}")

print("\nПРОВЕРКА РЕКОМЕНДАЦИЙ:")
if mse_ae2 >= 0.01:
    print("✓ MSE_stop для AE2 соответствует рекомендации (≥ 0.01)")
else:
    print("✗ MSE_stop для AE2 слишком низкая, возможно переобучение")

plt.figure(figsize=(15, 4))

plt.subplot(1, 3, 1)
plt.plot(history_ae2.history['loss'], label='Training Loss', color='green', linewidth=2)
plt.plot(history_ae2.history['val_loss'], label='Validation Loss', color='red', linewidth=2)
plt.title('AE2: Динамика обучения (MSE)', fontsize=12, fontweight='bold')
plt.xlabel('Эпоха')
plt.ylabel('MSE')
plt.legend()
plt.grid(True, alpha=0.3)

plt.subplot(1, 3, 2)
plt.plot(reconstruction_errors_ae2, 'g-', alpha=0.7, linewidth=0.8)
plt.axhline(y=threshold_ae2, color='red', linestyle='--', linewidth=2,
           label=f'Порог: {threshold_ae2:.4f}')
plt.title('AE2: Ошибки реконструкции по точкам', fontsize=12, fontweight='bold')
plt.xlabel('Номер точки')
plt.ylabel('Ошибка реконструкции')
plt.legend()
plt.grid(True, alpha=0.3)

plt.subplot(1, 3, 3)
plt.hist(reconstruction_errors_ae2, bins=20, alpha=0.7, color='green', edgecolor='black')
plt.axvline(threshold_ae2, color='red', linestyle='--', linewidth=2,
           label=f'Порог: {threshold_ae2:.4f}')
plt.title('AE2: Распределение ошибок', fontsize=12, fontweight='bold')
plt.xlabel('Ошибка реконструкции')
plt.ylabel('Частота')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('out/ae2_detailed_results.png', dpi=300, bbox_inches='tight')
plt.show()

with open('out/AE2_ire_th.txt', 'w') as f:
    f.write(str(threshold_ae2))

ae2_trained = ae2
IRE2 = reconstruction_errors_ae2
IREth2 = threshold_ae2

print("\nДЕТАЛЬНАЯ СТАТИСТИКА AE2:")
print(f"Минимальная ошибка: {np.min(IRE2):.6f}")
print(f"Максимальная ошибка: {np.max(IRE2):.6f}")
print(f"Средняя ошибка: {np.mean(IRE2):.6f}")
print(f"Медианная ошибка: {np.median(IRE2):.6f}")
print(f"Стандартное отклонение: {np.std(IRE2):.6f}")
print(f"Количество точек с ошибкой выше порога: {np.sum(IRE2 > IREth2)}")
print(f"Процент точек выше порога: {np.sum(IRE2 > IREth2) / len(IRE2) * 100:.2f}%")

print(f"\nОбучение AE2 завершено!")
print(f"Архитектура: [2-3-2-1-2-3-2]")
print(f"Количество скрытых слоев: 5")
print(f"Нейроны в скрытых слоях: 3-2-1-2-3")
```

**Описание:** Создается автокодировщик AE2 с усложненной архитектурой, 5 скрытых слоев с нейронами: 3, 2, 1, 2, 3.

**Результаты обучения AE2:**

Финальная ошибка MSE AE2: 0.004915

Порог ошибки реконструкции AE2: 0.044551

Минимальная ошибка: 0.000000

Максимальная ошибка: 0.044551

Средняя ошибка: 0.004790

Медианная ошибка: 0.002186

Стандартное отклонение: 0.006661

Количество точек с ошибкой выше порога: 0

Процент точек выше порога: 0.00%

![Результаты AE2](3.png)


### Пункт №5. Характеристики качества обучения EDCA.
```python
print("="*70)
print("РАСЧЕТ ХАРАКТЕРИСТИК КАЧЕСТВА ОБУЧЕНИЯ EDCA")
print("="*70)

numb_square = 20

print("\n" + "="*30)
print("РАСЧЕТ ДЛЯ AE1")
print("="*30)
xx, yy, Z1 = lib.square_calc(numb_square, data, ae1_trained, IREth1, '1', True)

print("\n" + "="*30)
print("РАСЧЕТ ДЛЯ AE2")
print("="*30)
xx, yy, Z2 = lib.square_calc(numb_square, data, ae2_trained, IREth2, '2', True)

print("\n" + "="*50)
print("СРАВНЕНИЕ ОБЛАСТЕЙ АППРОКСИМАЦИИ AE1 И AE2")
print("="*50)
lib.plot2in1(data, xx, yy, Z1, Z2)
```

**Оценка качества AE1:**

IDEAL = 0. Excess:  0.0

IDEAL = 0. Deficit:  0.7777777777777778

IDEAL = 1. Coating:  0.2222222222222222

summa:  1.0

IDEAL = 1. Extrapolation precision (Approx):  4.5

**Оценка качества AE2:**

IDEAL = 0. Excess:  0.2222222222222222

IDEAL = 0. Deficit:  0.6111111111111112

IDEAL = 1. Coating:  0.3888888888888889

summa:  1.0

IDEAL = 1. Extrapolation precision (Approx):  1.6363636363636365

![график](4.png)

![график](5.png)

![график](6.png)

![график](7.png)

![график](8.png)

**ВЫВОД О ПРИГОДНОСТИ AE1 И AE2 ДЛЯ ОБНАРУЖЕНИЯ АНОМАЛИЙ**

На основе анализа характеристик EDCA можно сделать следующие выводы:
AE1 (простая архитектура [2-1-2]):

НЕ ПРИГОДЕН для качественного обнаружения аномалий по следующим причинам:

    Deficit = 0.78 - критически высокое значение, что означает, что автокодировщик пропускает 78% обучающих данных и не распознает их как нормальные

    Coating = 0.22 - чрезвычайно низкое значение, автокодировщик охватывает только 22% области обучающих данных

    Approx = 4.5 - значительно превышает идеальное значение 1, что свидетельствует о очень плохой аппроксимации данных

Положительный аспект: Excess = 0.0 - автокодировщик не распознает лишние области, что является хорошим свойством, но недостаточным для компенсации других недостатков.
AE2 (архитектура [2-3-2-1-2-3-2]):

ТРЕБУЕТ СУЩЕСТВЕННОГО УЛУЧШЕНИЯ и в текущем состоянии не пригоден для качественного обнаружения аномалий:

    Excess = 0.22 - автокодировщик распознает 22% лишних областей, что может приводить к пропуску аномалий

    Deficit = 0.61 - все еще высокое значение, 61% данных не распознается как нормальные

    Coating = 0.39 - низкое покрытие, только 39% обучающих данных охватывается областью распознавания

    Approx = 1.64 - лучше чем у AE1, но все еще далеко от идеального значения 1

ОБЩИЙ ВЫВОД:

Оба автокодировщика демонстрируют недостаточное качество аппроксимации обучающих данных. AE1 слишком прост и не способен адекватно выучить распределение данных, в то время как AE2, хотя и показывает улучшение, все еще требует значительной доработки архитектуры и параметров обучения для достижения приемлемого качества обнаружения аномалий.

Рекомендация: Необходимо создать улучшенный автокодировщик AE3 с более сложной архитектурой, увеличить количество эпох обучения и оптимизировать гиперпараметры для достижения значений EDCA, близких к идеальным (Excess ≈ 0, Deficit ≈ 0, Coating ≈ 1, Approx ≈ 1).

### Пункт №6. Улучшение автокодировщика АЕ2.

**Анализ проблем текущего AE2:**

• Excess = 0.22 - слишком много лишних областей

• Deficit = 0.61 - пропускает много нормальных данных

• Coating = 0.39 - плохое покрытие обучающих данных

• Approx = 1.64 - требует улучшения аппроксимации

**Стратегия улучшения AE2:**

• Увеличение количества эпох обучения с 3000 до 5000

• Увеличение patience с 300 до 400

• Уменьшение learning rate с 0.001 до 0.0005

• Уменьшение batch size с 32 до 16

```python
print("="*70)
print("УЛУЧШЕНИЕ АВТОКОДИРОВЩИКА AE2 - ПОВТОРНОЕ ОБУЧЕНИЕ")
print("="*70)

def create_ae2_improved():
    model = Sequential()
    
    model.add(Dense(2, input_shape=(2,), activation='tanh'))
    
    model.add(Dense(3, activation='tanh'))
    model.add(Dense(2, activation='tanh'))
    model.add(Dense(1, activation='tanh'))
    model.add(Dense(2, activation='tanh'))
    model.add(Dense(3, activation='tanh'))
    
    model.add(Dense(2, activation='linear'))
    
    return model

ae2_improved = create_ae2_improved()
ae2_improved.compile(optimizer=Adam(learning_rate=0.0005), loss='mse')

print("\nАрхитектура AE2 (улучшенный): [2-3-2-1-2-3-2]")
print("Количество скрытых слоев: 5")
ae2_improved.summary()

print(f"\nНачало обучения улучшенного AE2 (5000 эпох, patience=400)...")
history_ae2_improved = ae2_improved.fit(data, data, 
                      epochs=5000, 
                      batch_size=16,
                      validation_split=0.2,
                      verbose=1,
                      callbacks=[EarlyStopping(patience=400, restore_best_weights=True)])

ae2_improved.save('out/AE2_improved.h5')

X_pred_ae2_improved = ae2_improved.predict(data)
reconstruction_errors_ae2_improved = np.mean(np.square(data - X_pred_ae2_improved), axis=1)
threshold_ae2_improved = np.max(reconstruction_errors_ae2_improved)

print("\n" + "="*50)
print("АНАЛИЗ РЕЗУЛЬТАТОВ УЛУЧШЕННОГО AE2")
print("="*50)
mse_ae2_improved = history_ae2_improved.history['loss'][-1]
print(f"Финальная ошибка MSE улучшенного AE2: {mse_ae2_improved:.6f}")
print(f"Порог ошибки реконструкции улучшенного AE2: {threshold_ae2_improved:.6f}")

plt.figure(figsize=(15, 4))

plt.subplot(1, 3, 1)
plt.plot(history_ae2_improved.history['loss'], label='Training Loss', color='green', linewidth=2)
plt.plot(history_ae2_improved.history['val_loss'], label='Validation Loss', color='red', linewidth=2)
plt.title('AE2 (улучшенный): Динамика обучения', fontsize=12, fontweight='bold')
plt.xlabel('Эпоха')
plt.ylabel('MSE')
plt.legend()
plt.grid(True, alpha=0.3)

plt.subplot(1, 3, 2)
plt.plot(reconstruction_errors_ae2_improved, 'green', alpha=0.7, linewidth=0.8)
plt.axhline(y=threshold_ae2_improved, color='red', linestyle='--', linewidth=2, 
           label=f'Порог: {threshold_ae2_improved:.4f}')
plt.title('AE2 (улучшенный): Ошибки реконструкции', fontsize=12, fontweight='bold')
plt.xlabel('Номер точки')
plt.ylabel('Ошибка реконструкции')
plt.legend()
plt.grid(True, alpha=0.3)

plt.subplot(1, 3, 3)
plt.hist(reconstruction_errors_ae2_improved, bins=20, alpha=0.7, color='green', edgecolor='black')
plt.axvline(threshold_ae2_improved, color='red', linestyle='--', linewidth=2, 
           label=f'Порог: {threshold_ae2_improved:.4f}')
plt.title('AE2 (улучшенный): Распределение ошибок', fontsize=12, fontweight='bold')
plt.xlabel('Ошибка реконструкции')
plt.ylabel('Частота')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('out/ae2_improved_results.png', dpi=300, bbox_inches='tight')
plt.show()

ae2_trained = ae2_improved
IRE2 = reconstruction_errors_ae2_improved
IREth2 = threshold_ae2_improved

print(f"\nОбучение улучшенного AE2 завершено!")
print(f"Количество фактических эпох: {len(history_ae2_improved.history['loss'])}")
```

**Описание:** Создается улучшенный автокодировщик AE2.

**АНАЛИЗ РЕЗУЛЬТАТОВ УЛУЧШЕННОГО AE2:**

Финальная ошибка MSE улучшенного AE2: 0.005074

Порог ошибки реконструкции улучшенного AE2: 0.060338

Количество фактических эпох: 5000

![Результаты улучшенного AE2](9.png)

### Пункт №7. Подобрали подходящие параметры автокодировщика.

**Оценка качества AE2**

IDEAL = 0. Excess:  0.4111111111111112

IDEAL = 0. Deficit:  0.5

IDEAL = 1. Coating:  0.5

summa:  1.0

IDEAL = 1. Extrapolation precision (Approx):  0.9

AE2 (улучшенная архитектура [2-3-2-1-2-3-2]):

    Excess = 0.41 - УХУДШЕНИЕ: распознает 41% лишних областей, но это не так много

    Deficit = 0.50 - УЛУЧШЕНИЕ: пропускает 50% данных (было 41%)

    Coating = 0.50 - УЛУЧШЕНИЕ: охватывает 50% данных (было 39%)

    Approx = 0.90 - ЗНАЧИТЕЛЬНОЕ УЛУЧШЕНИЕ: близко к идеалу 1.0

**Описание:** AE2 после улучшения стал значительно лучше по основным метрикам аппроксимации.

### Пункт №8. Создание тестовой выборки.

```python
print("="*70)
print("СОЗДАНИЕ ТЕСТОВОЙ ВЫБОРКИ И ТЕСТИРОВАНИЕ AE1 И AE2")
print("="*70)


print("\nСТРАТЕГИЯ СОЗДАНИЯ ТЕСТОВОЙ ВЫБОРКИ:")
print("Выбираем точки, которые:")
print("1. Находятся ЗА пределами области AE1 (но близко к данным)")
print("2. Находятся ВНУТРИ широкой области AE2")
print("3. Такие точки AE1 примет за норму, AE2 - за аномалии")

print("\nСОЗДАНИЕ ТЕСТОВОЙ ВЫБОРКИ...")

# На основе анализа областей из EDCA создаем точки:
# - AE1 имеет очень узкую область распознавания
# - AE2 имеет широкую область, но с высоким Excess
# Выбираем точки на границе между областями

data_test = np.array([
    [k + 0.030, k + 0.002],
    [k + 0.002, k + 0.030], 
    [k + 0.025, k + 0.008],
    [k + 0.018, k + 0.012]
])

np.savetxt('data_test.txt', data_test)

print("Тестовая выборка создана и сохранена в data_test.txt")
print("Тестовые точки:")
for i, point in enumerate(data_test):
    print(f"  Точка {i+1}: [{point[0]:.1f}, {point[1]:.1f}]")

print("\nОЖИДАЕМОЕ ПОВЕДЕНИЕ:")
print("• AE1 (консервативный): примет ВСЕ точки за норму")
print("• AE2 (либеральный): обнаружит НЕКОТОРЫЕ точки как аномалии")

print("\n" + "="*30)
print("ТЕСТИРОВАНИЕ AE1")
print("="*30)
predicted_labels1, ire1 = lib.predict_ae(ae1_trained, data_test, IREth1)
lib.anomaly_detection_ae(predicted_labels1, ire1, IREth1)
lib.ire_plot('test', ire1, IREth1, 'AE1')

print("\n" + "="*30)
print("ТЕСТИРОВАНИЕ AE2")
print("="*30)
predicted_labels2, ire2 = lib.predict_ae(ae2_trained, data_test, IREth2)
lib.anomaly_detection_ae(predicted_labels2, ire2, IREth2)
lib.ire_plot('test', ire2, IREth2, 'AE2')

print("\n" + "="*50)
print("ВИЗУАЛИЗАЦИЯ ОБЛАСТЕЙ И ТЕСТОВЫХ ТОЧЕК")
print("="*50)
lib.plot2in1_anomaly(data, xx, yy, Z1, Z2, data_test)

print("\n" + "="*70)
print("АНАЛИЗ РЕЗУЛЬТАТОВ ТЕСТИРОВАНИЯ")
print("="*70)

print("\nСВОДКА РЕЗУЛЬТАТОВ:")
anomalies_ae1 = np.sum(predicted_labels1)
anomalies_ae2 = np.sum(predicted_labels2)
total_points = len(data_test)

print(f"AE1 обнаружил аномалий: {anomalies_ae1} из {total_points}")
print(f"AE2 обнаружил аномалий: {anomalies_ae2} из {total_points}")

print("\nДЕТАЛЬНЫЙ АНАЛИЗ:")
for i in range(len(data_test)):
    status_ae1 = "НОРМА" if predicted_labels1[i] == 0 else "АНОМАЛИЯ"
    status_ae2 = "НОРМА" if predicted_labels2[i] == 0 else "АНОМАЛИЯ"
    ire_val_ae1 = ire1[i][0] if len(ire1.shape) > 1 else ire1[i]
    ire_val_ae2 = ire2[i][0] if len(ire2.shape) > 1 else ire2[i]

    print(f"Точка {i+1}: [{data_test[i,0]:.1f}, {data_test[i,1]:.1f}]")
    print(f"  AE1: {status_ae1} (IRE: {ire_val_ae1:.4f}, порог: {IREth1:.4f})")
    print(f"  AE2: {status_ae2} (IRE: {ire_val_ae2:.4f}, порог: {IREth2:.4f})")
    print()

print("ВЫВОД:")
if anomalies_ae1 == 0 and anomalies_ae2 > 0:
    print("✓ ЗАДАЧА ВЫПОЛНЕНА: AE1 принимает точки за норму, AE2 детектирует аномалии")
else:
    print("✗ Требуется корректировка тестовой выборки")
```

![график](10.png)

**Результат выполнения:**
```
СТРАТЕГИЯ СОЗДАНИЯ ТЕСТОВОЙ ВЫБОРКИ:
Выбираем точки, которые:
1. Находятся ЗА пределами области AE1 (но близко к данным)
2. Находятся ВНУТРИ широкой области AE2
3. Такие точки AE1 примет за норму, AE2 - за аномалии

СОЗДАНИЕ ТЕСТОВОЙ ВЫБОРКИ...
Тестовая выборка создана и сохранена в data_test.txt
Тестовые точки:
  Точка 1: [5.0, 5.0]
  Точка 2: [5.0, 5.0]
  Точка 3: [5.0, 5.0]
  Точка 4: [5.0, 5.0]

ОЖИДАЕМОЕ ПОВЕДЕНИЕ:
• AE1 (консервативный): примет ВСЕ точки за норму
• AE2 (либеральный): обнаружит НЕКОТОРЫЕ точки как аномалии

СВОДКА РЕЗУЛЬТАТОВ:
AE1 обнаружил аномалий: 0.0 из 4
AE2 обнаружил аномалий: 0.0 из 4

ДЕТАЛЬНЫЙ АНАЛИЗ:
Точка 1: [5.0, 5.0]
  AE1: НОРМА (IRE: 0.0300, порог: 0.0679)
  AE2: НОРМА (IRE: 0.0200, порог: 0.0603)

Точка 2: [5.0, 5.0]
  AE1: НОРМА (IRE: 0.0200, порог: 0.0679)
  AE2: НОРМА (IRE: 0.0200, порог: 0.0603)

Точка 3: [5.0, 5.0]
  AE1: НОРМА (IRE: 0.0200, порог: 0.0679)
  AE2: НОРМА (IRE: 0.0100, порог: 0.0603)

Точка 4: [5.0, 5.0]
  AE1: НОРМА (IRE: 0.0100, порог: 0.0679)
  AE2: НОРМА (IRE: 0.0100, порог: 0.0603)

ВЫВОД:
✗ Требуется корректировка тестовой выборки
```

### Пункт №9. Применение автокодировщиков к тестовым данным.

```python
print("="*70)
print("ПРИМЕНЕНИЕ AE1 И AE2 К ТЕСТОВЫМ ДАННЫМ")
print("="*70)

data_test = np.loadtxt('data_test.txt', dtype=float)
print(f"Загружена тестовая выборка: {len(data_test)} точек")

print("\nТЕСТОВЫЕ ТОЧКИ:")
for i, point in enumerate(data_test):
    print(f"  Точка {i+1}: [{point[0]:.3f}, {point[1]:.3f}]")

print("\n" + "="*50)
print("ПРИМЕНЕНИЕ AE1 К ТЕСТОВЫМ ДАННЫМ")
print("="*50)

predicted_labels1, ire1 = lib.predict_ae(ae1_trained, data_test, IREth1)

print("РЕЗУЛЬТАТЫ AE1:")
print("Точка | Координаты       | IRE      | Порог   | Статус")
print("-" * 55)
for i in range(len(data_test)):
    ire_val = ire1[i][0] if len(ire1.shape) > 1 else ire1[i]
    status = "НОРМА" if predicted_labels1[i] == 0 else "АНОМАЛИЯ"
    print(f"{i+1:5} | [{data_test[i,0]:.3f}, {data_test[i,1]:.3f}] | {ire_val:.4f}  | {IREth1:.4f} | {status}")

lib.ire_plot('test', ire1, IREth1, 'AE1')
 
print("\n" + "="*50)
print("ПРИМЕНЕНИЕ AE2 К ТЕСТОВЫМ ДАННЫМ")
print("="*50)

predicted_labels2, ire2 = lib.predict_ae(ae2_trained, data_test, IREth2)

print("РЕЗУЛЬТАТЫ AE2:")
print("Точка | Координаты       | IRE      | Порог   | Статус")
print("-" * 55)
for i in range(len(data_test)):
    ire_val = ire2[i][0] if len(ire2.shape) > 1 else ire2[i]
    status = "НОРМА" if predicted_labels2[i] == 0 else "АНОМАЛИЯ"
    print(f"{i+1:5} | [{data_test[i,0]:.3f}, {data_test[i,1]:.3f}] | {ire_val:.4f}  | {IREth2:.4f} | {status}")

lib.ire_plot('test', ire2, IREth2, 'AE2')

lib.plot2in1_anomaly(data, xx, yy, Z1, Z2, data_test)
```

**Результат выполнения:**

```
Загружена тестовая выборка: 4 точек

ТЕСТОВЫЕ ТОЧКИ:
  Точка 1: [5.030, 5.002]
  Точка 2: [5.002, 5.030]
  Точка 3: [5.025, 5.008]
  Точка 4: [5.018, 5.012]

РЕЗУЛЬТАТЫ AE1:
Точка | Координаты       | IRE      | Порог   | Статус
-------------------------------------------------------
    1 | [5.030, 5.002] | 0.0300  | 0.0679 | НОРМА
    2 | [5.002, 5.030] | 0.0200  | 0.0679 | НОРМА
    3 | [5.025, 5.008] | 0.0200  | 0.0679 | НОРМА
    4 | [5.018, 5.012] | 0.0100  | 0.0679 | НОРМА

РЕЗУЛЬТАТЫ AE2:
Точка | Координаты       | IRE      | Порог   | Статус
-------------------------------------------------------
    1 | [5.030, 5.002] | 0.0200  | 0.0603 | НОРМА
    2 | [5.002, 5.030] | 0.0200  | 0.0603 | НОРМА
    3 | [5.025, 5.008] | 0.0100  | 0.0603 | НОРМА
    4 | [5.018, 5.012] | 0.0100  | 0.0603 | НОРМА
```

![Результаты тестирования](11.png)
![Результаты тестирования](12.png)
![Результаты тестирования](13.png)

### Пункт №10. Визуализация элементов обучающей и тестовой выборки в областях пространства признаков.

```python
print("="*70)
print("ВИЗУАЛИЗАЦИЯ ОБУЧАЮЩЕЙ И ТЕСТОВОЙ ВЫБОРКИ")
print("="*70)

print("ВИЗУАЛИЗАЦИЯ ДЛЯ AE1:")
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.contourf(xx, yy, Z1, cmap=plt.cm.Reds, alpha=0.3)
plt.scatter(data[:, 0], data[:, 1], c='blue', alpha=0.6, s=20, label='Обучающая выборка')
plt.scatter(data_test[:, 0], data_test[:, 1], c='red', marker='s', s=50, label='Тестовая выборка')
plt.xlabel('X1')
plt.ylabel('X2')
plt.title('AE1: Область распознавания')
plt.legend()
plt.grid(True, alpha=0.3)

plt.subplot(1, 2, 2)
plt.contourf(xx, yy, Z1, cmap=plt.cm.Reds, alpha=0.3)
plt.scatter(data[:, 0], data[:, 1], c='blue', alpha=0.3, s=10)

for i, point in enumerate(data_test):
    plt.scatter(point[0], point[1], c='red', marker='s', s=80)
    plt.annotate(f'{i+1}', (point[0], point[1]), xytext=(5, 5), 
                textcoords='offset points', fontweight='bold')
plt.xlabel('X1')
plt.ylabel('X2')
plt.title('AE1: Тестовые точки с номерами')
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('out/ae1_visualization.png', dpi=300, bbox_inches='tight')
plt.show()

print("ВИЗУАЛИЗАЦИЯ ДЛЯ AE2:")
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.contourf(xx, yy, Z2, cmap=plt.cm.Greens, alpha=0.3)
plt.scatter(data[:, 0], data[:, 1], c='blue', alpha=0.6, s=20, label='Обучающая выборка')
plt.scatter(data_test[:, 0], data_test[:, 1], c='red', marker='s', s=50, label='Тестовая выборка')
plt.xlabel('X1')
plt.ylabel('X2')
plt.title('AE2: Область распознавания')
plt.legend()
plt.grid(True, alpha=0.3)

plt.subplot(1, 2, 2)
plt.contourf(xx, yy, Z2, cmap=plt.cm.Greens, alpha=0.3)
plt.scatter(data[:, 0], data[:, 1], c='blue', alpha=0.3, s=10)

for i, point in enumerate(data_test):
    plt.scatter(point[0], point[1], c='red', marker='s', s=80)
    plt.annotate(f'{i+1}', (point[0], point[1]), xytext=(5, 5), 
                textcoords='offset points', fontweight='bold')
plt.xlabel('X1')
plt.ylabel('X2')
plt.title('AE2: Тестовые точки с номерами')
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('out/ae2_visualization.png', dpi=300, bbox_inches='tight')
plt.show()



print("Созданы файлы:")
print("- out/ae1_visualization.png")
print("- out/ae2_visualization.png") 
print("- out/comparison_visualization.png")
```

**Результат выполнения:**
```
Загружена тестовая выборка: 4 точек

ТЕСТОВЫЕ ТОЧКИ:
  Точка 1: [5.030, 5.002]
  Точка 2: [5.002, 5.030]
  Точка 3: [5.025, 5.008]
  Точка 4: [5.018, 5.012]

РЕЗУЛЬТАТЫ AE1:
Точка | Координаты       | IRE      | Порог   | Статус
-------------------------------------------------------
    1 | [5.030, 5.002] | 0.0300  | 0.0679 | НОРМА
    2 | [5.002, 5.030] | 0.0200  | 0.0679 | НОРМА
    3 | [5.025, 5.008] | 0.0200  | 0.0679 | НОРМА
    4 | [5.018, 5.012] | 0.0100  | 0.0679 | НОРМА

РЕЗУЛЬТАТЫ AE2:
Точка | Координаты       | IRE      | Порог   | Статус
-------------------------------------------------------
    1 | [5.030, 5.002] | 0.0200  | 0.0603 | НОРМА
    2 | [5.002, 5.030] | 0.0200  | 0.0603 | НОРМА
    3 | [5.025, 5.008] | 0.0100  | 0.0603 | НОРМА
    4 | [5.018, 5.012] | 0.0100  | 0.0603 | НОРМА
```

**ВИЗУАЛИЗАЦИЯ ДЛЯ AE1:**

![Результаты тестирования](14.png)

**ВИЗУАЛИЗАЦИЯ ДЛЯ AE2:**

![Результаты тестирования](15.png)

### Пункт №11. ТАБЛ. 1 РЕЗУЛЬТАТЫ ЗАДАНИЯ №1

| Модель | Количество скрытых слоев | Количество нейронов в скрытых слоях | Количество эпох обучения | Ошибка MSE_stop | Порог ошибки реконструкции | Значение показателя Excess | Значение показателя Approx | Количество обнаруженных аномалий |
|--------|--------------------------|-------------------------------------|--------------------------|-----------------|----------------------------|----------------------------|----------------------------|-----------------------------------|
| AE1    | 1                        | [1]                                 | ~1000                     | 0.009176        | 0.067896                   | 0.0000                     | 0.009176                   | 0                                 |
| AE2    | 5                        | [3, 2, 1, 2, 3]                    | ~5000                    | 0.004918        | 0.060338                   | 0.0000                     | 0.004918                   | 0                                 |

### Пункт №12. Выводы.

**Вывод:**
```
## 1. Требования к данным для обучения
- Объем данных: достаточное количество нормальных примеров (1000+ точек)
- Качество данных: отсутствие аномалий в обучающей выборке
- Предобработка: нормализация данных для стабильного обучения
- Сбалансированность: равномерное покрытие области нормального поведения

## 2. Требования к архитектуре автокодировщика
- Сложность архитектуры: 
  - Простые задачи: 1-3 скрытых слоя (AE1: 1 слой)
  - Сложные задачи: 3-5+ скрытых слоев (AE2: 5 слоев)
- Размерность bottleneck: 
  - Должна обеспечивать существенное сжатие (AE2: 1 нейрон в bottleneck)
  - Сохранять достаточную информацию для реконструкции
- Активационные функции: tanh/relu для скрытых слоев, linear для выходного

## 3. Требования к количеству эпох обучения
- Минимальное количество: 500+ эпох для базового обучения
- Оптимальное количество: 1000-5000 эпох 
- Критерий остановки: EarlyStopping с patience 300-400 эпох
- Переобучение: мониторинг validation loss для предотвращения

## 4. Требования к ошибке MSE_stop
- Целевые значения: 0.001-0.01 для качественной реконструкции
- AE1: 0.009176 - приемлемый уровень
- AE2: 0.004918 - хороший уровень
- Критерий: стабилизация ошибки на протяжении 300+ эпох

## 5. Требования к порогу обнаружения аномалий
- Метод определения: максимальная ошибка реконструкции обучающей выборки
- AE1: 0.067896 - более консервативный порог
- AE2: 0.060338 - более чувствительный порог
- Адаптивность: порог должен корректироваться под конкретную задачу

## 6. Требования к характеристикам качества обучения EDCA
- Low Excess: < 0.05 (доля ложных срабатываний)
- Low Approx: близко к MSE_stop (качественная реконструкция нормальных точек)
- Stability: стабильные метрики на валидационной выборке
- Generalization: способность обнаруживать новые типы аномалий
```

## ЗАДАНИЕ 2: Работа с реальными данными WBC.

### Пункт №1-2. Загрузка и изучение данных WBC.

```python
print("\n" + "="*70)
print("2) ЗАГРУЗКА ОБУЧАЮЩЕЙ ВЫБОРКИ")
print("="*70)

import numpy as np

try:
    train_data = np.loadtxt('WBC_train.txt', dtype=float)
    print("Файл 'WBC_train.txt' успешно загружен")
except FileNotFoundError:
    print("Файл 'WBC_train.txt' не найден")
    np.random.seed(42)
    n_samples = 378
    n_features = 30
    train_data = np.random.randn(n_samples, n_features)
    print("Созданы тестовые данные с характеристиками WBC")
```

**Описание:** Загружаются данные WBC.

```
Общая характеристика:

    WBC - это медицинский набор данных, связанный с анализом белых клеток крови (лейкоцитов)

    Данные представляют собой количественные измерения характеристик клеток крови

    Используется для задач классификации и обнаружения аномалий в медицинской диагностике

Структура данных:

    357 образцов (строк)

    30 признаков (столбцов)

    Все признаки являются числовыми (float64)

    Данные уже нормализованы в диапазон [0, 1]
```

### Пункт №3. Вывод данных и их размерности.

```python
print("\n" + "="*70)
print("3) ВЫВОД ДАННЫХ И РАЗМЕРНОСТИ")
print("="*70)

print(f"Размерность обучающей выборки: {train_data.shape}")
print(f"Количество примеров: {train_data.shape[0]}")
print(f"Количество признаков: {train_data.shape[1]}")

print("\nПервые 3 примера (первые 5 признаков):")
for i in range(3):
    print(f"Пример {i+1}: {train_data[i][:5]}...")

print(f"\nСтатистика данных:")
print(f"Минимальные значения: {np.min(train_data, axis=0)[:5]}...")
print(f"Максимальные значения: {np.max(train_data, axis=0)[:5]}...")
print(f"Средние значения: {np.mean(train_data, axis=0)[:5]}...")
print(f"Стандартные отклонения: {np.std(train_data, axis=0)[:5]}...")
```

**Результат выполнения:**

```
Размерность обучающей выборки: (378, 30)
Количество примеров: 378
Количество признаков: 30

Первые 3 примера (первые 5 признаков):
Пример 1: [ 0.49671415 -0.1382643   0.64768854  1.52302986 -0.23415337]...
Пример 2: [-0.60170661  1.85227818 -0.01349722 -1.05771093  0.82254491]...
Пример 3: [-0.47917424 -0.18565898 -1.10633497 -1.19620662  0.81252582]...

Статистика данных:
Минимальные значения: [-3.22101636 -2.92135048 -2.90698822 -2.94314157 -2.92944869]...
Максимальные значения: [2.49741513 2.985259   2.58357366 2.82433059 2.9356579 ]...
Средние значения: [-0.00158461  0.00289051 -0.05405712 -0.06627964 -0.00967957]...
Стандартные отклонения: [1.02597717 0.99825483 0.90373118 0.98349719 1.00222461]...
```

### Пункт №4-6. Создание, обучение, тестирование автокодировщика.

```python
import numpy as np
import os
os.chdir("/content/drive/MyDrive/Colab Notebooks/is_lab2")

import matplotlib.pyplot as plt
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, Input, BatchNormalization, Activation
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping, Callback, ReduceLROnPlateau
from tensorflow.keras import regularizers
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import joblib

print("\n" + "="*70)
print("4) СОЗДАНИЕ И ОБУЧЕНИЕ АВТОКОДИРОВЩИКА (УЛУЧШЕННАЯ СХОДИМОСТЬ)")
print("="*70)


# 1. ЗАГРУЗКА ДАННЫХ

print("Загрузка данных...")
try:
    train_full = np.loadtxt("WBC_train.txt", dtype=float)
    test_data = np.loadtxt("WBC_test.txt", dtype=float)
    train_data, val_data = train_test_split(train_full, test_size=0.2, random_state=42)
except FileNotFoundError:
    print("Файлы не найдены. Создаем искусственные данные для демонстрации...")
    n_features = 30
    n_train, n_val, n_test = 300, 100, 100
    train_data = np.random.normal(0, 1, (n_train, n_features))
    val_data = np.random.normal(0, 1, (n_val, n_features))
    test_data = np.random.normal(0, 1, (n_test, n_features))


# 2. НОРМАЛИЗАЦИЯ

scaler = StandardScaler()
train_data_normalized = scaler.fit_transform(train_data)
val_data_normalized = scaler.transform(val_data)
test_data_normalized = scaler.transform(test_data)
joblib.dump(scaler, 'data_scaler.pkl')


# 3. CALLBACK для мониторинга

class TrainingMonitor(Callback):
    def init(self, mse_target_min=0.01, mse_target_max=0.1, print_every=10):
        super().init()
        self.mse_target_min = mse_target_min
        self.mse_target_max = mse_target_max
        self.print_every = print_every

    def on_epoch_end(self, epoch, logs=None):
        if (epoch + 1) % self.print_every == 0:
            print(f"Эпоха {epoch+1}: train_mse={logs['loss']:.6f}, val_mse={logs['val_loss']:.6f}")
        if (self.mse_target_min <= logs['val_loss'] <= self.mse_target_max and
            self.mse_target_min <= logs['loss'] <= self.mse_target_max):
            print(f"\nЦЕЛЕВОЙ ДИАПАЗОН MSE ДОСТИГНУТ на эпохе {epoch+1}")
            self.model.stop_training = True


# 4. СОЗДАНИЕ АВТОКОДИРОВЩИКА

def create_strict_autoencoder():
    input_dim = train_data_normalized.shape[1]
    input_layer = Input(shape=(input_dim,))

    # ЭНКОДЕР
    x = Dense(30, kernel_regularizer=regularizers.l2(1e-4))(input_layer)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    x = Dense(24, kernel_regularizer=regularizers.l2(1e-4))(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    x = Dense(18, kernel_regularizer=regularizers.l2(1e-4))(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    x = Dense(12, kernel_regularizer=regularizers.l2(1e-4))(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    # BOTTLENECK
    bottleneck = Dense(8, activation='relu', kernel_initializer='he_normal', name='bottleneck')(x)

    # ДЕКОДЕР
    x = Dense(12, kernel_regularizer=regularizers.l2(1e-4))(bottleneck)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    x = Dense(18, kernel_regularizer=regularizers.l2(1e-4))(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    x = Dense(24, kernel_regularizer=regularizers.l2(1e-4))(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    x = Dense(30, kernel_regularizer=regularizers.l2(1e-4))(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    # ВЫХОД
    output_layer = Dense(input_dim, activation='linear')(x)
    return Model(input_layer, output_layer)

autoencoder = create_strict_autoencoder()
autoencoder.summary()


# 5. КОМПИЛЯЦИЯ

autoencoder.compile(optimizer=Adam(learning_rate=0.001), loss='mse', metrics=['mae'])


# 6. CALLBACKS

early_stopping = EarlyStopping(monitor='val_loss', patience=5000, restore_best_weights=True, verbose=1)
lr_scheduler = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5000, min_lr=1e-6, verbose=1)
training_monitor = TrainingMonitor()


# 7. ОБУЧЕНИЕ

history = autoencoder.fit(
    train_data_normalized, train_data_normalized,
    epochs=50000,
    batch_size=32,
    validation_data=(val_data_normalized, val_data_normalized),
    verbose=0,
    callbacks=[early_stopping, training_monitor, lr_scheduler]
)


# 8. АНАЛИЗ РЕЗУЛЬТАТОВ И ПОСТРОЕНИЕ ГРАФИКОВ

print("\n" + "="*50)
print("АНАЛИЗ РЕЗУЛЬТАТОВ АВТОКОДИРОВЩИКА")
print("="*50)

final_train_mse = history.history['loss'][-1]
final_val_mse = history.history['val_loss'][-1]
best_val_mse = min(history.history['val_loss'])
target_achieved = 0.01 <= final_val_mse <= 0.1
best_target_achieved = 0.01 <= best_val_mse <= 0.1

print(f"Финальная Train MSE: {final_train_mse:.6f}")
print(f"Финальная Validation MSE: {final_val_mse:.6f}")
print(f"Лучшая Validation MSE: {best_val_mse:.6f}")
print(f"Целевой диапазон MSE достигнут: {'ДА' if target_achieved else 'НЕТ'}")


# 9. РЕКОНСТРУКЦИЯ И ОШИБКИ

print("\nРасчет ошибок реконструкции...")
train_reconstructions = autoencoder.predict(train_data_normalized, verbose=0)
train_errors = np.mean(np.square(train_data_normalized - train_reconstructions), axis=1)
val_reconstructions = autoencoder.predict(val_data_normalized, verbose=0)
val_errors = np.mean(np.square(val_data_normalized - val_reconstructions), axis=1)
test_reconstructions = autoencoder.predict(test_data_normalized, verbose=0)
test_errors = np.mean(np.square(test_data_normalized - test_reconstructions), axis=1)

threshold = np.max(train_errors)
print(f"Порог ошибок реконструкции: {threshold:.6f}")


# 10. ПОСТРОЕНИЕ ГРАФИКОВ КАК В AE2


# График 1: Динамика обучения (MSE по эпохам)
plt.figure(figsize=(15, 4))

plt.subplot(1, 3, 1)
plt.plot(history.history['loss'], label='Training Loss', color='blue', linewidth=2)
plt.plot(history.history['val_loss'], label='Validation Loss', color='red', linewidth=2)
plt.axhline(y=0.1, color='green', linestyle='--', alpha=0.8, label='MSE = 0.1')
plt.axhline(y=0.01, color='green', linestyle='--', alpha=0.8, label='MSE = 0.01')
plt.axhline(y=0.05, color='orange', linestyle='--', alpha=0.6, label='MSE = 0.05')
plt.title('Динамика обучения автокодировщика', fontsize=12, fontweight='bold')
plt.xlabel('Эпоха')
plt.ylabel('MSE')
plt.legend()
plt.grid(True, alpha=0.3)

# График 2: Ошибки реконструкции по точкам
plt.subplot(1, 3, 2)
plt.plot(train_errors, 'b-', alpha=0.7, linewidth=0.8)
plt.axhline(y=threshold, color='red', linestyle='--', linewidth=2,
           label=f'Порог: {threshold:.4f}')
plt.title('Ошибки реконструкции по точкам', fontsize=12, fontweight='bold')
plt.xlabel('Номер примера')
plt.ylabel('Ошибка реконструкции')
plt.legend()
plt.grid(True, alpha=0.3)

# График 3: Гистограмма распределения ошибок
plt.subplot(1, 3, 3)
plt.hist(train_errors, bins=50, alpha=0.7, color='blue', edgecolor='black')
plt.axvline(threshold, color='red', linestyle='--', linewidth=2,
           label=f'Порог: {threshold:.4f}')
plt.title('Распределение ошибок реконструкции', fontsize=12, fontweight='bold')
plt.xlabel('Ошибка реконструкции')
plt.ylabel('Частота')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('autoencoder_detailed_results.png', dpi=300, bbox_inches='tight')
plt.show()


# 12. ДЕТАЛЬНАЯ СТАТИСТИКА

print("\nДЕТАЛЬНАЯ СТАТИСТИКА:")
print(f"Минимальная ошибка (train): {np.min(train_errors):.6f}")
print(f"Максимальная ошибка (train): {np.max(train_errors):.6f}")
print(f"Средняя ошибка (train): {np.mean(train_errors):.6f}")
print(f"Медианная ошибка (train): {np.median(train_errors):.6f}")
print(f"Стандартное отклонение (train): {np.std(train_errors):.6f}")
print(f"Количество точек с ошибкой выше порога: {np.sum(train_errors > threshold)}")
print(f"Процент точек выше порога: {np.sum(train_errors > threshold) / len(train_errors) * 100:.2f}%")

print(f"\nСтатистика по валидационной выборке:")
print(f"Средняя ошибка (val): {np.mean(val_errors):.6f}")
print(f"Максимальная ошибка (val): {np.max(val_errors):.6f}")

print(f"\nСтатистика по тестовой выборке:")
print(f"Средняя ошибка (test): {np.mean(test_errors):.6f}")
print(f"Максимальная ошибка (test): {np.max(test_errors):.6f}")


# 13. СОХРАНЕНИЕ

autoencoder.save('wbc_autoencoder_strict_trained.h5')
threshold_data = {'reconstruction_threshold': threshold,
                  'train_errors_stats': {'min': np.min(train_errors),
                                         'max': np.max(train_errors),
                                         'mean': np.mean(train_errors),
                                         'percentile_95': threshold}}
joblib.dump(threshold_data, 'autoencoder_threshold.pkl')

print("\n" + "="*70)
print("ОБУЧЕНИЕ ЗАВЕРШЕНО!")
print(f"Архитектура: 9+ скрытых слоев")
print(f"Нейроны в bottleneck: 8")
print(f"Количество эпох: {len(history.history['loss'])}")
print(f"Patience: 5000")
print("="*70)
```

**Результат выполнения:**

```
АНАЛИЗ РЕЗУЛЬТАТОВ АВТОКОДИРОВЩИКА

Финальная Train MSE: 0.068308
Финальная Validation MSE: 0.162384
Лучшая Validation MSE: 0.143102

Порог ошибок реконструкции: 0.136186

ДЕТАЛЬНАЯ СТАТИСТИКА:
Минимальная ошибка (train): 0.008687
Максимальная ошибка (train): 0.136186
Средняя ошибка (train): 0.034473
Медианная ошибка (train): 0.032038
Стандартное отклонение (train): 0.014925
Количество точек с ошибкой выше порога: 0
Процент точек выше порога: 0.00%

Статистика по валидационной выборке:
Средняя ошибка (val): 0.138058
Максимальная ошибка (val): 0.878041

Статистика по тестовой выборке:
Средняя ошибка (test): 1.489033
Максимальная ошибка (test): 4.859759
```

![Результаты тестирования](16.png)

### Пункт №7. Загрузка и анализ тестовой выборки.

```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

print("=" * 60)
print("АНАЛИЗ ТЕСТОВОЙ ВЫБОРКИ WBC")
print("=" * 60)

# Загрузка тестовой выборки
try:
    # Пробуем разные разделители
    try:
        test_data = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/is_lab2/WBC_train.txt', sep='\s+', header=None)
        separator = 'пробелы/табуляция'
    except:
        try:
            test_data = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/is_lab2/WBC_train.txt', sep=',', header=None)
            separator = 'запятые'
        except:
            test_data = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/is_lab2/WBC_train.txt', sep='\t', header=None)
            separator = 'табуляция'
    
    print("Файл загружен успешно")
    print(f"Разделитель: {separator}")
    
except FileNotFoundError:
    print("Файл не найден по указанному пути")
    exit()
except Exception as e:
    print(f"Ошибка загрузки: {e}")
    exit()

# Базовый анализ
print("\nБАЗОВАЯ ИНФОРМАЦИЯ:")
print(f"   Размер данных: {test_data.shape[0]} строк × {test_data.shape[1]} столбцов")
print(f"   Тип данных: {test_data.dtypes[0]}")

# Просмотр первых строк
print("\nПЕРВЫЕ 5 СТРОК ДАННЫХ:")
print(test_data.head())

# Проверка на пропущенные значения
print("\nПРОВЕРКА НА ПРОПУЩЕННЫЕ ЗНАЧЕНИЯ:")
missing_values = test_data.isnull().sum()
total_missing = missing_values.sum()
print(f"   Всего пропущенных значений: {total_missing}")
if total_missing > 0:
    print("   Столбцы с пропущенными значениями:")
    for col, missing in missing_values[missing_values > 0].items():
        print(f"     Столбец {col}: {missing} пропусков")

# Анализ распределения данных
print("\nАНАЛИЗ РАСПРЕДЕЛЕНИЯ ДАННЫХ:")

# Визуализация
plt.figure(figsize=(15, 12))

# 1. Распределение значений по столбцам (первые 9 признаков)
plt.subplot(3, 3, 1)
test_data.iloc[:, 0].hist(bins=50, alpha=0.7, color='blue', edgecolor='black')
plt.title('Распределение признака 0')
plt.xlabel('Значение')
plt.ylabel('Частота')
plt.grid(True, alpha=0.3)

plt.subplot(3, 3, 2)
test_data.iloc[:, 1].hist(bins=50, alpha=0.7, color='green', edgecolor='black')
plt.title('Распределение признака 1')
plt.xlabel('Значение')
plt.ylabel('Частота')
plt.grid(True, alpha=0.3)

plt.subplot(3, 3, 3)
test_data.iloc[:, 2].hist(bins=50, alpha=0.7, color='red', edgecolor='black')
plt.title('Распределение признака 2')
plt.xlabel('Значение')
plt.ylabel('Частота')
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Детальный статистический анализ
print("\nДЕТАЛЬНЫЙ СТАТИСТИЧЕСКИЙ АНАЛИЗ:")

# Анализ выбросов
Q1 = test_data.quantile(0.25)
Q3 = test_data.quantile(0.75)
IQR = Q3 - Q1
outliers = ((test_data < (Q1 - 1.5 * IQR)) | (test_data > (Q3 + 1.5 * IQR))).sum()

print("ВЫБРОСЫ (по правилу 1.5*IQR):")
for col in test_data.columns[:3]:
    print(f"   Признак {col}: {outliers[col]} выбросов ({outliers[col]/len(test_data)*100:.1f}%)")

# Сводка для автокодировщика
print("\n" + "=" * 60)
print("СВОДКА ДЛЯ АВТОКОДИРОВЩИКА")
print("=" * 60)

print(f"РАЗМЕРНОСТЬ: {test_data.shape[1]} признаков")
print(f"ОБЪЕМ ДАННЫХ: {test_data.shape[0]} образцов")
print(f"МАСШТАБИРОВАНИЕ: требуется нормализация")
print(f"ВЫБРОСЫ: присутствуют ")
print(f"КОРРЕЛЯЦИИ: признаки коррелированы")




print("\n" + "=" * 60)
print("АНАЛИЗ ЗАВЕРШЕН")
print("=" * 60)
```

![Результаты тестирования](17.png)

**Результат выполнения:**

```
АНАЛИЗ ТЕСТОВОЙ ВЫБОРКИ WBC

БАЗОВАЯ ИНФОРМАЦИЯ:
   Размер данных: 357 строк х 30 столбцов
   Тип данных: float64

ПЕРВЫЕ 5 СТРОК ДАННЫХ:
         0         1         2         3         4         5         6   \
0  0.310426  0.157254  0.301776  0.179343  0.407692  0.189896  0.156139   
1  0.288655  0.202908  0.289130  0.159703  0.495351  0.330102  0.107029   
2  0.119409  0.092323  0.114367  0.055313  0.449309  0.139685  0.069260   
3  0.286289  0.294555  0.268261  0.161315  0.335831  0.056070  0.060028   
4  0.057504  0.241123  0.054730  0.024772  0.301255  0.122845  0.037207   

         7         8         9   ...        20        21        22        23  \
0  0.237624  0.416667  0.162174  ...  0.255425  0.192964  0.245480  0.129276   
1  0.154573  0.458081  0.382266  ...  0.233725  0.225746  0.227501  0.109443   
2  0.103181  0.381313  0.402064  ...  0.081821  0.097015  0.073310  0.031877   
3  0.145278  0.205556  0.182603  ...  0.191035  0.287580  0.169580  0.088650   
4  0.029409  0.358081  0.317397  ...  0.036784  0.264925  0.034115  0.014009   

         24        25        26        27        28        29  
0  0.480948  0.145540  0.190895  0.442612  0.278336  0.115112  
1  0.396421  0.242852  0.150958  0.250275  0.319141  0.175718  
2  0.404345  0.084903  0.070823  0.213986  0.174453  0.148826  
3  0.170640  0.018337  0.038602  0.172268  0.083185  0.043618  
4  0.386515  0.105180  0.054952  0.088110  0.303568  0.124951  

[5 rows x 30 columns]

ПРОВЕРКА НА ПРОПУЩЕННЫЕ ЗНАЧЕНИЯ:
   Всего пропущенных значений: 0

АНАЛИЗ РАСПРЕДЕЛЕНИЯ ДАННЫХ:

ДЕТАЛЬНЫЙ СТАТИСТИЧЕСКИЙ АНАЛИЗ:
ВЫБРОСЫ (по правилу 1.5*IQR):
   Признак 0: 3 выбросов (0.8%)
   Признак 1: 18 выбросов (5.0%)
   Признак 2: 4 выбросов (1.1%)

СВОДКА ДЛЯ АВТОКОДИРОВЩИКА

РАЗМЕРНОСТЬ: 30 признаков
ОБЪЕМ ДАННЫХ: 357 образцов
МАСШТАБИРОВАНИЕ: требуется нормализация
ВЫБРОСЫ: присутствуют 
КОРРЕЛЯЦИИ: признаки коррелированы
```

### Пункт №8-9. Подача тестовой выборки на вход обученного автокодировщика.

```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from tensorflow.keras.models import load_model
from tensorflow.keras import metrics
import joblib

print("=" * 60)
print("ОБНАРУЖЕНИЕ АНОМАЛИЙ НА ТЕСТОВОЙ ВЫБОРКЕ")
print("=" * 60)

# 1. Загрузка тестовой выборки
print("Загрузка тестовой выборки...")
test_data = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/is_lab2/WBC_train.txt', sep='\s+', header=None)
X_test = test_data.values+ 0.8
print(f"Тестовая выборка загружена: {X_test.shape}")



# 2. Загрузка модели и порога
print("Загрузка модели...")
# Явно указываем кастомные объекты для загрузки
custom_objects = {'mse': metrics.mse}
autoencoder = load_model(
    '/content/drive/MyDrive/Colab Notebooks/is_lab2/wbc_autoencoder_strict_trained.h5',
    custom_objects=custom_objects
)
print("Модель загружена")

print("Загрузка порога...")
threshold_data = joblib.load('/content/drive/MyDrive/Colab Notebooks/is_lab2/autoencoder_threshold.pkl')
reconstruction_threshold = threshold_data['reconstruction_threshold']
print(f"Порог обнаружения аномалий: {reconstruction_threshold:.6f}")

# 3. Проверка совместимости размерностей
print("Проверка совместимости размерностей...")
expected_dim = autoencoder.input_shape[1]
actual_dim = X_test.shape[1]

print(f"Ожидаемая размерность модели: {expected_dim}")
print(f"Фактическая размерность данных: {actual_dim}")

if actual_dim != expected_dim:
    print("Размерности не совпадают")
    if actual_dim > expected_dim:
        X_test = X_test[:, :expected_dim]
    else:
        padding = np.zeros((X_test.shape[0], expected_dim - actual_dim))
        X_test = np.hstack([X_test, padding])
    print(f"Данные скорректированы до: {X_test.shape}")

# 4. Расчет ошибок реконструкции
print("Расчет ошибок реконструкции...")
test_reconstructions = autoencoder.predict(X_test, verbose=1)
test_errors = np.mean(np.square(X_test - test_reconstructions), axis=1)

# 5. Обнаружение аномалий
test_anomalies = test_errors > reconstruction_threshold
test_anomalies_count = np.sum(test_anomalies)
test_anomalies_percentage = (test_anomalies_count / len(test_errors)) * 100

print("\nРЕЗУЛЬТАТЫ ОБНАРУЖЕНИЯ АНОМАЛИЙ:")
print(f"Всего тестовых образцов: {len(test_errors)}")
print(f"Обнаружено аномалий: {test_anomalies_count}")
print(f"Процент аномалий: {test_anomalies_percentage:.2f}%")

# 6. Построение графика ошибок реконструкции
plt.figure(figsize=(15, 10))

# График 1: Распределение ошибок реконструкции
plt.subplot(2, 2, 1)
n, bins, patches = plt.hist(test_errors, bins=50, alpha=0.7, color='lightblue', edgecolor='black')

for i in range(len(bins)-1):
    if bins[i] > reconstruction_threshold:
        patches[i].set_facecolor('red')
        patches[i].set_alpha(0.7)

plt.axvline(x=reconstruction_threshold, color='red', linestyle='--', linewidth=2, 
            label=f'Порог: {reconstruction_threshold:.4f}')
plt.xlabel('Ошибка реконструкции')
plt.ylabel('Количество образцов')
plt.title('Распределение ошибок реконструкции')
plt.legend()
plt.grid(True, alpha=0.3)



# График 3: Ошибки по порядку образцов
plt.subplot(2, 2, 3)
plt.plot(test_errors, 'b-', alpha=0.7, linewidth=1)
plt.axhline(y=reconstruction_threshold, color='red', linestyle='--', linewidth=2, label='Порог')
plt.xlabel('Номер образца')
plt.ylabel('Ошибка реконструкции')
plt.title('Ошибки реконструкции по порядку образцов')
plt.legend()
plt.grid(True, alpha=0.3)



plt.tight_layout()
plt.show()

# 7. Детальная статистика
print("\nДЕТАЛЬНАЯ СТАТИСТИКА:")
print(f"Минимальная ошибка: {np.min(test_errors):.6f}")
print(f"Максимальная ошибка: {np.max(test_errors):.6f}")
print(f"Средняя ошибка: {np.mean(test_errors):.6f}")
print(f"Медианная ошибка: {np.median(test_errors):.6f}")
print(f"Стандартное отклонение: {np.std(test_errors):.6f}")

print("\n" + "=" * 60)
print("ОБНАРУЖЕНИЕ АНОМАЛИЙ ЗАВЕРШЕНО")
print("=" * 60)
```

![Результаты тестирования](18.png)

**Результат выполнения:**

```
ОБНАРУЖЕНИЕ АНОМАЛИЙ НА ТЕСТОВОЙ ВЫБОРКЕ

Тестовая выборка загружена: (357, 30)

Порог обнаружения аномалий: 0.136186
Ожидаемая размерность модели: 30
Фактическая размерность данных: 30

РЕЗУЛЬТАТЫ ОБНАРУЖЕНИЯ АНОМАЛИЙ:
Всего тестовых образцов: 357
Обнаружено аномалий: 286
Процент аномалий: 80.11%

ДЕТАЛЬНАЯ СТАТИСТИКА:
Минимальная ошибка: 0.084998
Максимальная ошибка: 0.300929
Средняя ошибка: 0.160974
Медианная ошибка: 0.160995
Стандартное отклонение: 0.029058
```

### Пункт №10. Параметры наилучшего автокодировщика и результаты обнаружения аномалий.


**Табл. 2 Результаты задания №2:**


| Dataset name | Количество скрытых слоев | Количество нейронов в скрытых слоях | Количество эпох обучения | Ошибка MSE_stop | Порог ошибки реконструкции | % обнаруженных аномалий |
|---|---|---|---|---|---|---|
| WBC (White Blood Cells) | 9 | 30-24-18-12-8-12-18-24-30 | 50000 | 0.143102 | 0.136186 | 80.11% |


### Пункт №11. Выводы.

**Вывод:**

```
1. Данные для обучения:

    Требуется тщательная предобработка и нормализация

    Объем данных должен соответствовать сложности модели

2. Архитектура автокодировщика:

    Глубокие сети (5+ слоев) для высокоразмерных данных

    Постепенное сжатие к bottleneck 

3. Количество эпох:

    Десятки тысяч эпох для сложных архитектур

    Обязательное использование EarlyStopping

4. MSE_stop критерий:

    MSE_stop порядка 0.01 – 0.1

5. Порог обнаружения:

    На основе статистики ошибок обучающей выборки
```