PyTorch: автоэнкодер

Автоэнкодер (Autoencoder, AE)— это нейронная сеть для обучения без учителя, которая сжимает входные данные в низкоразмерное латентное пространство посредством обучения, а затем восстанавливает исходные данные из сжатого представления.

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


1. Основные принципы автоэнкодера

Базовая структура автоэнкодера состоит из трёх частей:

  • Энкодер: отображает входные данные \(x\) в низкоразмерное латентное представление \(z\)
  • Латентное пространство: низкоразмерный вектор, выдаваемый энкодером, также называемый слоем узкого места
  • Декодер: восстанавливает латентное представление \(z\) в выход \(\hat{x}\)

1.1 Структура сети

Цель автоэнкодера — сделать выход \(\hat{x}\) как можно более близким к входу \(x\):

\[ \min_{\theta, \phi} \frac{1}{n} \sum_{i=1}^{n} \| x_i - D_\phi(E_\theta(x_i)) \|^2 \]

где \(\theta\) — параметры энкодера, а \(\phi\) — параметры декодера.

1.2 Эффект снижения размерности

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

По сравнению с методом главных компонент (PCA) автоэнкодеры могут изучать нелинейное снижение размерности и улавливать более сложные структуры данных.


2. Базовая реализация автоэнкодера

2.1 Простой автоэнкодер

Пример

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset

# ── Модель автоэнкодера ─────────────────────────────────
class Autoencoder(nn.Module):
    """
Базовый автоэнкодер: симметричная структура
    """

    def __init__(self, input_dim, hidden_dim, latent_dim):
        super().__init__()

        # Энкодер
        self.encoder = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, latent_dim),  # Слой узкого места
        )

        # Декодер
        self.decoder = nn.Sequential(
            nn.Linear(latent_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, input_dim),
        )

    def forward(self, x):
        z = self.encoder(x)
        x_recon = self.decoder(z)
        return x_recon

    def encode(self, x):
        """Кодирование: получение латентного представления"""
        return self.encoder(x)

    def decode(self, z):
        """Декодирование: восстановление из латентного представления"""
        return self.decoder(z)


# ── Пример использования ──────────────────────────────────────
INPUT_DIM = 784   # Например, после уплощения изображения MNIST
HIDDEN_DIM = 256
LATENT_DIM = 32   # Размерность латентного пространства, намного меньше размерности входа

model = Autoencoder(INPUT_DIM, HIDDEN_DIM, LATENT_DIM)
print(f"Размерность входа: {INPUT_DIM}")
print(f"Размерность латентного пространства: {LATENT_DIM}")
print(f"Коэффициент сжатия: {INPUT_DIM / LATENT_DIM:.1f}x")

# Просмотр количества параметров
total_params = sum(p.numel() for p in model.parameters())
print(f"Всего параметров: {total_params:,}")

2.2 Свёрточный автоэнкодер

Для изображений автокодировщики, использующие свёрточные слои, работают лучше:

Пример

import torch
import torch.nn as nn

class ConvAutoencoder(nn.Module):
    """
Свёрточный автокодировщик: подходит для изображений
    """

    def __init__(self, channels=3, latent_dim=128):
        super().__init__()

        # Кодировщик: постепенно уменьшать размер, увеличивать число каналов
        # Вход: (batch, channels, 64, 64)
        self.encoder = nn.Sequential(
            # 32 -> 16
            nn.Conv2d(channels, 32, kernel_size=3, stride=2, padding=1),
            nn.ReLU(),
            # 16 -> 8
            nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1),
            nn.ReLU(),
            # 8 -> 4
            nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1),
            nn.ReLU(),
            # 4 -> 2
            nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1),
            nn.ReLU(),
        )

        # Отображение в латентное пространство
        self.to_latent = nn.AdaptiveAvgPool2d((1, 1))

        # Декодер: постепенно увеличивать размер
        # Вход: (batch, 256, 2, 2)
        self.from_latent = nn.Sequential(
            nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
            nn.Conv2d(256, 128, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
            nn.Conv2d(128, 64, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
            nn.Conv2d(64, 32, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
            nn.Conv2d(32, channels, kernel_size=3, padding=1),
            nn.Sigmoid()  # Выход [0, 1]
        )

    def forward(self, x):
        z = self.encode(x)
        x_recon = self.decode(z)
        return x_recon

    def encode(self, x):
        """Кодирование"""
        features = self.encoder(x)
        z = self.to_latent(features)
        z = z.view(z.size(0), -1)  # (batch, 256)
        return z

    def decode(self, z):
        """Декодирование"""
        # Преобразовать вектор в карту признаков
        batch_size = z.size(0)
        z = z.view(batch_size, 256, 1, 1)
        z = z.expand(-1, -1, 2, 2)  # Апсемплинг до 2x2
        x_recon = self.from_latent(z)
        return x_recon


#
model = ConvAutoencoder(channels=3, latent_dim=128)
x = torch.randn(4, 3, 64, 64)
x_recon = model(x)

print(f"Форма входа: {x.shape}")
print(f"Форма выхода: {x_recon.shape}")
print(f"Форма латентного вектора: {model.encode(x).shape}")

2.3 Обучение и реконструкция

Пример

import torch
import torch.nn as nn
import torch.optim as optim

# ── Конфигурация обучения ─────────────────────────────────────
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = ConvAutoencoder(channels=3, latent_dim=128).to(device)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=1e-3)

# ── Цикл обучения ─────────────────────────────────────
def train_autoencoder(model, dataloader, criterion, optimizer, num_epochs=10):
    model.train()
    for epoch in range(num_epochs):
        total_loss = 0
        for batch in dataloader:
            images = batch[0].to(device)

            # Прямое распространение
            outputs = model(images)
            loss = criterion(outputs, images)

            # Обратное распространение
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            total_loss += loss.item()

        avg_loss = total_loss / len(dataloader)
        print(f"Эпоха [{epoch+1}/{num_epochs}], Потеря: {avg_loss:.6f}")

    return model


# Предполагается, что загрузчик данных уже существует
# train_autoencoder(model, train_loader, criterion, optimizer, num_epochs=10)
print("Обучение автоэнкодера завершено!")

3. Шумоподавляющий автоэнкодер (DAE)

Шумоподавляющий автоэнкодер (DAE)добавляет шум к входным данным во время обучения, а затем учится удалять шум и восстанавливать исходный вход. Это позволяет модели изучать более устойчивые представления признаков.

3.1 Реализация шумоподавляющего автоэнкодера

Пример

import torch
import torch.nn as nn

class DenoisingAutoencoder(nn.Module):
    """
Шумоподавляющий автоэнкодер
    """

    def __init__(self, input_dim, hidden_dim, latent_dim):
        super().__init__()

        self.encoder = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, latent_dim),
        )

        self.decoder = nn.Sequential(
            nn.Linear(latent_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, input_dim),
            nn.Sigmoid()  # Выход [0, 1]
        )

    def forward(self, x):
        z = self.encode(x)
        return self.decode(z)

    def encode(self, x):
        return self.encoder(x)

    def decode(self, z):
        return self.decoder(z)


def add_noise(x, noise_factor=0.3):
    """
Добавить гауссов шум
    """

    noise = torch.randn_like(x) * noise_factor
    noisy_x = x + noise
    return torch.clamp(noisy_x, 0.0, 1.0)


# Обучение шумоподавляющего автоэнкодера
def train_dae(model, dataloader, noise_factor=0.3, lr=1e-3):
    criterion = nn.MSELoss()
    optimizer = optim.Adam(model.parameters(), lr=lr)

    model.train()
    for epoch in range(10):
        for batch in dataloader:
            images = batch[0]

            # Добавить шум
            noisy_images = add_noise(images, noise_factor)
            noisy_images = noisy_images.to(next(model.parameters()).device)
            images = images.to(next(model.parameters()).device)

            # Прямое распространение
            outputs = model(noisy_images)
            loss = criterion(outputs, images)  # Сравнить с исходным изображением, а не с зашумлённым

            # Обратное распространение
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

    return model

3.2 Другие типы шума

Пример

def salt_pepper_noise(x, prob=0.1):
    """Шум «соль и перец»"""
    random_mask = torch.rand_like(x)
    noisy = x.clone()
    noisy[random_mask < prob / 2] = 0.0
    noisy[random_mask > 1 - prob / 2] = 1.0
    return noisy


def mask_noise(x, prob=0.1):
    """Шум маскирования (случайное обнуление)"""
    mask = torch.rand_like(x) > prob
    return x * mask.float()


def dropout_noise(x, rate=0.2):
    """Шум dropout"""
    mask = torch.rand_like(x) > rate
    return x * mask.float() / (1 - rate)

4. Вариационный автоэнкодер (VAE)

Вариационный автоэнкодер (VAE)— это генеративная модель, которая кодирует данные в распределение вероятностей в латентном пространстве, а не в фиксированный вектор. Это позволяет нам производить выборку из латентного пространства для генерации новых данных.

4.1 Основные принципы VAE

Ключевое нововведение VAE заключается в изучении распределения вероятностей латентных переменных:

  • Энкодер выводит среднее \(\mu\) и стандартное отклонение \(\sigma\)
  • Выполните выборку латентного вектора \(z\) из нормального распределения \(\mathcal{N}(\mu, \sigma)\)
  • Декодер восстанавливает данные из \(z\)

Чтобы получить дифференцируемый процесс выборки, используетсятрюк репараметризации.:

\[ z = \mu + \sigma \cdot \epsilon, \quad \epsilon \sim \mathcal{N}(0, 1) \]

4.2 Реализация VAE

Пример

import torch
import torch.nn as nn
import torch.optim as optim

class VAE(nn.Module):
    """
Вариационный автоэнкодер
    """

    def __init__(self, input_dim, hidden_dim, latent_dim):
        super().__init__()

        # Энкодер: вывод среднего и дисперсии
        self.encoder = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
        )
        self.fc_mu = nn.Linear(hidden_dim, latent_dim)
        self.fc_logvar = nn.Linear(hidden_dim, latent_dim)

        # Декодер
        self.decoder = nn.Sequential(
            nn.Linear(latent_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, input_dim),
            nn.Sigmoid()
        )

    def encode(self, x):
        h = self.encoder(x)
        mu = self.fc_mu(h)
        logvar = self.fc_logvar(h)
        return mu, logvar

    def reparameterize(self, mu, logvar):
        """Трюк репараметризации"""
        std = torch.exp(0.5 * logvar)
        eps = torch.randn_like(std)
        return mu + eps * std

    def decode(self, z):
        return self.decoder(z)

    def forward(self, x):
        mu, logvar = self.encode(x)
        z = self.reparameterize(mu, logvar)
        x_recon = self.decode(z)
        return x_recon, mu, logvar


def vae_loss(x_recon, x, mu, logvar, beta=1.0):
    """
Функция потерь VAE
Потеря восстановления + KL-дивергенция
    """

    # Потеря восстановления
    recon_loss = nn.functional.mse_loss(x_recon, x, reduction='sum')

    # KL-дивергенция: -0.5 * sum(1 + log(sigma^2) - mu^2 - sigma^2)
    kl_loss = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())

    return recon_loss + beta * kl_loss, recon_loss, kl_loss


# Пример использования
INPUT_DIM = 784
HIDDEN_DIM = 256
LATENT_DIM = 2  # Двумерное латентное пространство для удобной визуализации

model = VAE(INPUT_DIM, HIDDEN_DIM, LATENT_DIM)

#
x = torch.randn(32, 784)
x_recon, mu, logvar = model(x)

print(f"Форма входа: {x.shape}")
print(f"Форма восстановления: {x_recon.shape}")
print(f"Форма среднего: {mu.shape}")       # (32, 2)
print(f"Форма дисперсии: {logvar.shape}")   # (32, 2)

4.3 Генерация и визуализация VAE

Пример

import matplotlib.pyplot as plt

def visualize_latent_space(model, dataloader, device):
    """Визуализация латентного пространства"""
    model.eval()
    all_mu = []
    all_labels = []

    with torch.no_grad():
        for batch in dataloader:
            images, labels = batch[0].to(device), batch[1]
            mu, _ = model.encode(images)
            all_mu.append(mu.cpu())
            all_labels.append(labels)

    all_mu = torch.cat(all_mu, dim=0).numpy()
    all_labels = torch.cat(all_labels, dim=0).numpy()

    plt.figure(figsize=(10, 8))
    scatter = plt.scatter(all_mu[:, 0], all_mu[:, 1], c=all_labels,
                          cmap='tab10', alpha=0.5, s=10)
    plt.colorbar(scatter)
    plt.xlabel('Латентное измерение 1')
    plt.ylabel('Латентное измерение 2')
    plt.title('Латентное пространство VAE')
    plt.show()


def generate_from_latent(model, z, device):
    """Генерация изображений из латентных векторов"""
    model.eval()
    with torch.no_grad():
        z = z.to(device)
        generated = model.decode(z)
    return generated


def interpolate_latent(model, z1, z2, steps=10, device):
    """Генерация интерполяции латентного пространства"""
    model.eval()

    # Линейная интерполяция
    alphas = torch.linspace(0, 1, steps)
    interpolated = []

    with torch.no_grad():
        for alpha in alphas:
            z = z1 * (1 - alpha) + z2 * alpha
            generated = model.decode(z)
            interpolated.append(generated)

    return torch.cat(interpolated, dim=0)


# Генерация новых изображений
def generate_new_images(model, num_images, latent_dim, device):
    """Генерация новых изображений из случайных латентных векторов"""
    model.eval()
    with torch.no_grad():
        # Выборка из стандартного нормального распределения
        z = torch.randn(num_images, latent_dim).to(device)
        generated = model.decode(z)
    return generated

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


5. Разреженный автоэнкодер

Разреженный автоэнкодерДобавьте ограничение разреженности в функцию потерь, ограничивая количество активаций в латентном векторе. Это позволяет модели изучать более осмысленные признаки.

5.1 Реализация разреженного автоэнкодера

< h2 class="example">Пример
import torch
import torch.nn as nn
import torch.nn.functional as F

class SparseAutoencoder(nn.Module):
    """
Разреженный автоэнкодер
    """

    def __init__(self, input_dim, hidden_dim, latent_dim):
        super().__init__()

        # Энкодер
        self.encoder = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
        )
        # Латентный слой
        self.bottleneck = nn.Linear(hidden_dim, latent_dim)

        # Декодер
        self.decoder = nn.Sequential(
            nn.Linear(latent_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, input_dim),
            nn.Sigmoid()
        )

    def forward(self, x):
        h = self.encoder(x)
        z = self.bottleneck(h)
        z_activated = F.relu(z)  # Разреженная активация
        x_recon = self.decoder(z_activated)
        return x_recon, z_activated


def sparse_loss(z, rho=0.05, beta=1.0):
    """
Разреженная потеря: KL-дивергенция
rho: целевая разреженность (например, 0.05 означает, что только 5% нейронов должны быть активны)
beta: вес члена разреженности
    """

    # Вычислить среднюю активацию
    rho_hat = torch.mean(z, dim=0)

    # KL-дивергенция
    kl = rho * torch.log(rho / (rho_hat + 1e-8)) + \
         (1 - rho) * torch.log((1 - rho) / (1 - rho_hat + 1e-8))

    return beta * torch.sum(kl)


def total_sparse_loss(x_recon, x, z, rho=0.05, beta=1.0):
    """Общая потеря = потеря реконструкции + потеря разреженности"""
    recon_loss = F.mse_loss(x_recon, x)
    sparsity = sparse_loss(z, rho, beta)
    return recon_loss + sparsity

6. Автоэнкодер последовательность-в-последовательность

Для последовательных данных (таких как текст, временные ряды) используйте RNN/LSTM в качестве энкодера и декодера.

6.1 Реализация автоэнкодера последовательностей

Пример

import torch
import torch.nn as nn

class Seq2SeqAutoencoder(nn.Module):
    """
Автоэнкодер «последовательность-в-последовательность»: используется для последовательных данных
    """

    def __init__(self, input_size, hidden_size, latent_size, num_layers=2):
        super().__init__()

        # LSTM-энкодер
        self.encoder = nn.LSTM(
            input_size=input_size,
            hidden_size=hidden_size,
            num_layers=num_layers,
            batch_first=True,
            bidirectional=True
        )

        # Отображение в латентное пространство
        # Выход двунаправленного LSTM равен hidden_size * 2
        self.to_latent = nn.Linear(hidden_size * 2, latent_size)
        self.from_latent = nn.Linear(latent_size, hidden_size * 2)

        # LSTM декодера
        self.decoder = nn.LSTM(
            input_size=hidden_size,
            hidden_size=hidden_size,
            num_layers=num_layers,
            batch_first=True,
            bidirectional=True
        )

        # Отображение выхода
        self.output_proj = nn.Linear(hidden_size * 2, input_size)

    def forward(self, x):
        # Кодирование
        _, (h_n, _) = self.encoder(x)

        # Объединить финальные скрытые состояния двунаправленной сети
        # h_n: (num_layers * 2, batch, hidden_size)
        h_forward = h_n[-2]
        h_backward = h_n[-1]
        h_combined = torch.cat([h_forward, h_backward], dim=-1)

        # Преобразовать в латентное пространство
        z = self.to_latent(h_combined)

        # Преобразовать обратно из латентного пространства
        decoder_init = self.from_latent(z)
        decoder_init = decoder_init.view(2, decoder_init.size(0), -1)  # (2, batch, hidden_size*2)

        # Декодировать (используя исходную длину входных данных)
        decoder_output, _ = self.decoder(
            x,
            (decoder_init, torch.zeros_like(decoder_init))
        )

        # Отображение выхода
        output = self.output_proj(decoder_output)
        return output, z


# Пример использования
model = Seq2SeqAutoencoder(
    input_size=128,   # Размерность входных признаков
    hidden_size=256,  # Размерность скрытого состояния LSTM
    latent_size=64,   # Размерность латентного пространства
    num_layers=2
)

#
x = torch.randn(8, 20, 128)  # (batch, seq_len, input_size)
output, z = model(x)

print(f"Форма входа: {x.shape}")        # (8, 20, 128)
print(f"Форма вывода: {output.shape}")  # (8, 20, 128)
print(f"Форма латентного вектора: {z.shape}")    # (8, 64)

7. Сценарии применения автоэнкодеров

7.1 Обнаружение аномалий

Автоэнкодеры можно использовать для обнаружения аномальных данных. У нормальных данных небольшая ошибка реконструкции, а у аномальных данных большая ошибка реконструкции:

Пример

import torch
import torch.nn as nn

def detect_anomalies(model, data_loader, threshold=None, device='cpu'):
    """
Обнаружение аномалий с помощью автоэнкодеров
    """

    model.eval()
    reconstruction_errors = []

    with torch.no_grad():
        for batch in data_loader:
            images = batch[0].to(device)
            outputs = model(images)

            # Вычислить ошибку реконструкции (среднеквадратичная ошибка)
            errors = torch.mean((outputs - images) ** 2, dim=(1, 2, 3))
            reconstruction_errors.extend(errors.cpu().numpy())

    reconstruction_errors = torch.tensor(reconstruction_errors)

    # Если порог не задан, используйте статистический метод
    if threshold is None:
        # Использовать 95% процентиль
        threshold = torch.quantile(reconstruction_errors, 0.95).item()

    # Пометить аномалии
    anomalies = reconstruction_errors > threshold

    return anomalies, reconstruction_errors, threshold


# Обучение модели обнаружения аномалий
def train_anomaly_detector(normal_data_loader):
    """Обучение автоэнкодера только на нормальных данных"""
    model = ConvAutoencoder(channels=1, latent_dim=32)
    criterion = nn.MSELoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

    model.train()
    for epoch in range(10):
        for batch in normal_data_loader:
            images = batch[0]
            outputs = model(images)
            loss = criterion(outputs, images)

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

    return model

7.2 Колоризация изображений и перенос стиля

Пример

class ColorizationAutoencoder(nn.Module):
    """
Автоэнкодер для колоризации изображений
Вход: изображение в оттенках серого (batch, 1, H, W)
Выход: цветное изображение (batch, 2, H, W) (цветовое пространство ab)
    """

    def __init__(self):
        super().__init__()

        # Энкодер: постепенно извлекает признаки
        self.encoder = nn.Sequential(
            nn.Conv2d(1, 64, kernel_size=4, stride=2, padding=1),   # H/2
            nn.ReLU(),
            nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1),  # H/4
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.Conv2d(128, 256, kernel_size=4, stride=2, padding=1), # H/8
            nn.BatchNorm2d(256),
            nn.ReLU(),
        )

        # Декодер: повышающая дискретизация для генерации цветов
        self.decoder = nn.Sequential(
            nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.ConvTranspose2d(64, 32, kernel_size=4, stride=2, padding=1),
            nn.ReLU(),
            nn.Conv2d(32, 2, kernel_size=3, padding=1),  # Выходные ab-каналы
            nn.Sigmoid()  # диапазон ab-каналов [0, 1]
        )

    def forward(self, x):
        z = self.encoder(x)
        color = self.decoder(z)
        return color

7.3 Визуализация снижения размерности данных

Пример

import matplotlib.pyplot as plt

def visualize_latent_2d(model, dataloader, device, num_samples=1000):
    """
Использование автоэнкодера для снижения размерности данных до 2 измерений для визуализации
    """

    model.eval()
    all_latents = []
    all_labels = []

    with torch.no_grad():
        count = 0
        for batch in dataloader:
            if count >= num_samples:
                break
            images, labels = batch[0], batch[1]
            images = images.to(device)

            # Если это 2D AE, используйте напрямую
            # Если размерность выше, сначала спроецируйте в 2D
            z = model.encode(images)

            all_latents.append(z.cpu())
            all_labels.append(labels)
            count += images.size(0)

    latents = torch.cat(all_latents, dim=0)[:num_samples].numpy()
    labels = torch.cat(all_labels, dim=0)[:num_samples].numpy()

    plt.figure(figsize=(10, 8))
    scatter = plt.scatter(latents[:, 0], latents[:, 1], c=labels,
                          cmap='tab10', alpha=0.6, s=20)
    plt.colorbar(scatter)
    plt.xlabel('Латентное измерение 1')
    plt.ylabel('Латентное измерение 2')
    plt.title('Визуализация 2D латентного пространства автоэнкодера')
    plt.show()

8. Краткий справочник по API

8.1 Распространённые типы автоэнкодеров

Type Характеристики Применимые сценарии
Базовый автоэнкодер Простая симметричная структура Снижение размерности, извлечение признаков
Свёрточный автоэнкодер Использует свёрточные слои для сохранения пространственной структуры Обработка изображений
Шумоподавляющий автоэнкодер Учится удалять шум Шумоподавление изображений, устойчивые признаки
Вариационный автоэнкодер Изучает вероятностные распределения, может генерировать новые данные Генеративные модели, генерация данных
Разреженный автоэнкодер Ограничение разреженности, изучает интерпретируемые признаки Разделение признаков, интерпретируемость
Автоэнкодер последовательностей Использует RNN/LSTM для обработки последовательностей Текст, временные ряды

8.2 Выбор функции потерь

Задача Рекомендуемая функция потерь
Реконструкция изображений MSELoss、SSIMLoss
Бинарные изображения BCELoss、BCEWithLogitsLoss
Реконструкция текста CrossEntropyLoss
VAE MSE + KL-дивергенция
Обнаружение аномалий MSE、MAE

8.3 Выбор латентной размерности

数据维度低(<100维)
    -> 潜在维度设为 2~10

数据维度中等(100~1000维)
    -> 潜在维度设为 10~50

数据维度高(>1000维)
    -> 潜在维度设为 50~200

生成任务(VAE)
    -> 潜在维度 2~32(便于采样和可视化)

异常检测
    -> 潜在维度 16~64(保留足够信息检测异常)
Другие расширения