Skip to content

Latest commit

 

History

History
315 lines (242 loc) · 13.7 KB

File metadata and controls

315 lines (242 loc) · 13.7 KB

ЕТВП-нейросеть: строгая научная формулировка

Версия 2.3 — универсальный слой (2D + 4D + автоопределение)

Объединены все исправления версий 2.1 и 2.2 в единый универсальный слой, который автоматически определяет размерность входного тензора и применяет корректный режим Лапласиана.


1. Ключевые исправления

v2.1

  • Комплексная активация заменена на аналитическую Cardioid (удовлетворяет условиям Коши — Римана).
  • Лапласиан больше не связывает объекты батча — работает по признакам или пространству.

v2.2

  • Слой принимает исходный 4D-тензор без сплющивания.
  • Комплексное умножение переписано на torch.einsum для поддержки 2D и 4D.
  • Пространственный Лапласиан реально работает на изображениях.

v2.3 (объединение)

  • Автоопределение размерности: 2D → features, 4D → spatial.
  • Единый код для табличных данных и изображений.
  • Встроенные тесты для обоих режимов.

2. Математическая модель

Уравнение состояния слоя

$$ z(t + \Delta t) = z(t) + \Delta t \cdot \left[ \text{Cardioid}(W \cdot z(t) + b) - \mu \cdot \nabla^2 z(t) \right] + \sqrt{\Delta t} \cdot \eta \cdot \xi(t) \cdot \tanh(\beta |z(t)|) $$

Cardioid-активация (аналитическая)

$$ f(z) = \frac{1}{2} \left( 1 + \frac{\Re(z)}{|z|} \right) \cdot z $$

Проекция на сферу (Z-принцип)

$$ W \leftarrow W \cdot \frac{R_{\text{max}}}{\max(|W|, R_{\text{max}})} $$


3. Полный код (универсальная версия 2.3)

import torch
import torch.nn as nn
import torch.nn.functional as F
import math

class ETVP_ComplexLayer(nn.Module):
    """
    Универсальный комплексный слой ЕТВП.
    
    Поддерживает:
    - 2D вход: [batch, features] → Лапласиан по признакам (features)
    - 4D вход: [batch, channels, height, width] → Лапласиан по пространству (spatial)
    
    Режим Лапласиана:
    - 'auto' — автоматически определяется по размерности входа (по умолчанию)
    - 'features' — принудительно по признакам (для 2D)
    - 'spatial' — принудительно по пространству (для 4D)
    - 'none' — отключен
    """
    
    def __init__(self, dim_in, dim_out, R_max=1.0, eta_init=0.01, beta_init=0.1,
                 laplace_mode='auto', use_cardioid=True):
        super().__init__()
        
        # Инициализация комплексных весов (адаптация Xavier для комплексных чисел)
        bound = (2.0 / dim_in) ** 0.5
        self.W_re = nn.Parameter(torch.randn(dim_in, dim_out) * bound)
        self.W_im = nn.Parameter(torch.randn(dim_in, dim_out) * bound)
        self.b_re = nn.Parameter(torch.zeros(dim_out))
        self.b_im = nn.Parameter(torch.zeros(dim_out))
        
        # Обучаемая вязкость вакуума
        self.theta_mu = nn.Parameter(torch.tensor(0.0))
        
        # Параметры стохастической самокоррекции
        self.eta = nn.Parameter(torch.tensor(eta_init))
        self.beta = nn.Parameter(torch.tensor(beta_init))
        self.R_max = R_max
        
        # Режим Лапласиана
        self.laplace_mode = laplace_mode
        self.use_cardioid = use_cardioid
        
        # Кэш для отслеживания текущего режима (для отладки)
        self.current_mode = None

    def _project_weights(self):
        """Z-принцип: Стабилизация через проекцию на сферу без разрушения градиентов"""
        with torch.no_grad():
            W_norm = torch.sqrt(self.W_re ** 2 + self.W_im ** 2).sum()
            if W_norm > self.R_max:
                scale = self.R_max / (W_norm + 1e-8)
                self.W_re.copy_(self.W_re * scale)
                self.W_im.copy_(self.W_im * scale)

    def complex_mul(self, x_re, x_im):
        """
        Полноценное комплексное линейное преобразование.
        Поддерживает 2D [batch, features] и 4D [batch, channels, H, W].
        """
        if x_re.dim() == 2:
            # 2D случай: [batch, dim_in] @ [dim_in, dim_out]
            out_re = x_re @ self.W_re - x_im @ self.W_im + self.b_re
            out_im = x_re @ self.W_im + x_im @ self.W_re + self.b_im
        elif x_re.dim() == 4:
            # 4D случай: [batch, C_in, H, W] @ [C_in, C_out] -> [batch, C_out, H, W]
            out_re = (torch.einsum('bihw,io->bohw', x_re, self.W_re) - 
                      torch.einsum('bihw,io->bohw', x_im, self.W_im) + 
                      self.b_re.view(1, -1, 1, 1))
            out_im = (torch.einsum('bihw,io->bohw', x_re, self.W_im) + 
                      torch.einsum('bihw,io->bohw', x_im, self.W_re) + 
                      self.b_im.view(1, -1, 1, 1))
        else:
            raise ValueError(f"Unsupported tensor dimension: {x_re.dim()}. Expected 2D or 4D.")
        
        return out_re, out_im

    def cardioid_activation(self, re, im):
        """
        Аналитическая комплексная активация Cardioid.
        f(z) = 0.5 * (1 + cos(angle(z))) * z
        Удовлетворяет условиям Коши — Римана.
        """
        magnitude = torch.sqrt(re ** 2 + im ** 2 + 1e-8)
        cos_angle = re / magnitude
        scale = 0.5 * (1.0 + cos_angle)
        return scale * re, scale * im

    def complex_tanh(self, re, im):
        """Наивная комплексная активация (запасной вариант)"""
        return torch.tanh(re), torch.tanh(im)

    def laplacian_features(self, z):
        """
        Лапласиан по соседним признакам (dim=1).
        Для 2D входа: [batch, features].
        Не связывает объекты в батче.
        """
        if z.dim() != 2:
            return torch.zeros_like(z)
        
        if z.size(1) <= 2:
            return torch.zeros_like(z)
        
        # Зеркальный padding по признакам
        z_padded = F.pad(z.unsqueeze(1), (1, 1), mode='reflect').squeeze(1)
        
        # Центральная разность
        lap = z_padded[:, :-2] - 2 * z[:, :] + z_padded[:, 2:]
        
        return lap

    def laplacian_spatial(self, z):
        """
        Лапласиан по пространственным координатам (2D).
        Для 4D входа: [batch, channels, height, width].
        Не затрагивает батч и каналы.
        """
        if z.dim() != 4:
            return torch.zeros_like(z)
        
        # Padding по пространственным измерениям (отражательный)
        z_padded = F.pad(z, (1, 1, 1, 1), mode='reflect')
        
        # Дискретный Лапласиан 2D (4 соседа)
        lap = (z_padded[:, :, :-2, 1:-1] + 
               z_padded[:, :, 2:, 1:-1] + 
               z_padded[:, :, 1:-1, :-2] + 
               z_padded[:, :, 1:-1, 2:] - 
               4 * z)
        
        return lap

    def _resolve_laplace_mode(self, z):
        """Автоопределение режима Лапласиана по размерности тензора"""
        if self.laplace_mode == 'auto':
            if z.dim() == 2:
                return 'features'
            elif z.dim() == 4:
                return 'spatial'
            else:
                return 'none'
        else:
            return self.laplace_mode

    def laplacian(self, z):
        """Выбор режима Лапласиана (с автоопределением)"""
        mode = self._resolve_laplace_mode(z)
        self.current_mode = mode  # для отладки
        
        if mode == 'features':
            return self.laplacian_features(z)
        elif mode == 'spatial':
            return self.laplacian_spatial(z)
        elif mode == 'none':
            return torch.zeros_like(z)
        else:
            raise ValueError(f"Unknown laplace_mode: {mode}")

    def forward(self, x_re, x_im, dt=0.1):
        # 1. Применяем Z-принцип (проекция весов)
        self._project_weights()
        
        # 2. Детерминированная динамика (Полевое смещение + активация)
        f_re, f_im = self.complex_mul(x_re, x_im)
        
        if self.use_cardioid:
            f_re, f_im = self.cardioid_activation(f_re, f_im)
        else:
            f_re, f_im = self.complex_tanh(f_re, f_im)
        
        # 3. Вычисление обучаемой вязкости вакуума
        mu = F.softplus(self.theta_mu)
        
        # 4. Дифференциальный оператор (давление среды) — Лапласиан с автоопределением
        lap_re = self.laplacian(x_re)
        lap_im = self.laplacian(x_im)
        
        # 5. Стохастическая самокоррекция (шум под упругим давлением градиентного поля)
        noise_re = self.eta * torch.randn_like(x_re) * torch.tanh(self.beta * x_re.abs())
        noise_im = self.eta * torch.randn_like(x_im) * torch.tanh(self.beta * x_im.abs())
        
        # 6. Интегрирование по методу Эйлера-Маруямы (Neural SDE шаг)
        sqrt_dt = math.sqrt(dt)
        
        z_re_next = x_re + dt * (f_re - mu * lap_re) + sqrt_dt * noise_re
        z_im_next = x_im + dt * (f_im - mu * lap_im) + sqrt_dt * noise_im
        
        return z_re_next, z_im_next


# --- Сквозные тесты (Проверка Autograd для обоих режимов) ---
if __name__ == "__main__":
    print("=" * 60)
    print("ЕТВП Комплексный слой — тестирование v2.3")
    print("=" * 60)
    
    # Тест 1: 2D вход (табличные данные) — Лапласиан по признакам
    print("\n[Тест 1] 2D вход: [batch=10, features=16]")
    layer_2d = ETVP_ComplexLayer(dim_in=16, dim_out=32, laplace_mode='auto')
    
    x_re = torch.randn(10, 16, requires_grad=True)
    x_im = torch.randn(10, 16, requires_grad=True)
    
    curr_re, curr_im = x_re, x_im
    for step in range(5):
        curr_re, curr_im = layer_2d(curr_re, curr_im, dt=0.1)
    
    lap_check = layer_2d.laplacian(x_re)
    print(f"  Режим Лапласиана: {layer_2d.current_mode}")
    print(f"  Норма Лапласиана: {lap_check.abs().sum().item():.4f}")
    assert lap_check.abs().sum() > 0, "Лапласиан не сработал!"
    
    loss = (curr_re**2 + curr_im**2).sum()
    loss.backward()
    
    print(f"  Градиент W_re: {layer_2d.W_re.grad.abs().mean().item():.6f}")
    print(f"  Градиент Вязкости (theta_mu): {layer_2d.theta_mu.grad.item():.6f}")
    print("  ✓ Тест 1 пройден")
    
    # Тест 2: 4D вход (изображение) — Лапласиан по пространству
    print("\n[Тест 2] 4D вход: [batch=2, channels=4, H=8, W=8]")
    layer_4d = ETVP_ComplexLayer(dim_in=4, dim_out=8, laplace_mode='auto')
    
    x_re = torch.randn(2, 4, 8, 8, requires_grad=True)
    x_im = torch.randn(2, 4, 8, 8, requires_grad=True)
    
    # Передаем БЕЗ сплющивания
    curr_re, curr_im = x_re, x_im
    for step in range(3):
        curr_re, curr_im = layer_4d(curr_re, curr_im, dt=0.1)
    
    lap_check = layer_4d.laplacian(x_re)
    print(f"  Режим Лапласиана: {layer_4d.current_mode}")
    print(f"  Норма Лапласиана: {lap_check.abs().sum().item():.4f}")
    assert lap_check.abs().sum() > 0, "Лапласиан не сработал!"
    
    loss = (curr_re**2 + curr_im**2).sum()
    loss.backward()
    
    print(f"  Градиент W_re: {layer_4d.W_re.grad.abs().mean().item():.6f}")
    print(f"  Градиент Вязкости (theta_mu): {layer_4d.theta_mu.grad.item():.6f}")
    print(f"  Форма выхода: {curr_re.shape}")
    print("  ✓ Тест 2 пройден")
    
    # Тест 3: Отключенный Лапласиан
    print("\n[Тест 3] Лапласиан отключен (laplace_mode='none')")
    layer_none = ETVP_ComplexLayer(dim_in=16, dim_out=16, laplace_mode='none')
    
    x_re = torch.randn(10, 16, requires_grad=True)
    x_im = torch.randn(10, 16, requires_grad=True)
    
    curr_re, curr_im = layer_none(x_re, x_im, dt=0.1)
    
    lap_check = layer_none.laplacian(x_re)
    print(f"  Режим Лапласиана: {layer_none.current_mode}")
    print(f"  Норма Лапласиана: {lap_check.abs().sum().item():.4f}")
    assert lap_check.abs().sum() == 0, "Лапласиан должен быть нулевым!"
    
    print("  ✓ Тест 3 пройден")
    
    print("\n" + "=" * 60)
    print("ВСЕ ТЕСТЫ ПРОЙДЕНЫ УСПЕШНО")
    print("=" * 60)