Skip to content

Latest commit

 

History

History
313 lines (220 loc) · 12.5 KB

File metadata and controls

313 lines (220 loc) · 12.5 KB

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

Версия 2.1 — исправление Лапласиана и комплексной активации

Классическая нейросеть работает в вещественном поле $\mathbb{R}$.
Для реализации физики ЕТВП необходимо полноценное погружение в комплексное поле $\mathbb{C}$ и динамическое время $t$.


1. Полноценный Complex-Valued Deep Learning

Проблема

Если вход $x \in \mathbb{R}$, а веса $W \in \mathbb{C}$, то мнимая часть поля изолирована и не участвует в обучении.

Решение

Переводим всю сеть в комплексное пространство:

$$ x \in \mathbb{C}, \quad W \in \mathbb{C}, \quad b \in \mathbb{C} $$

Комплексное умножение

$$ W \cdot x = (W_{\text{Re}} x_{\text{Re}} - W_{\text{Im}} x_{\text{Im}}) + i (W_{\text{Re}} x_{\text{Im}} + W_{\text{Im}} x_{\text{Re}}) $$

Фаза и амплитуда перетекают друг в друга — как в реальном поле.


2. Исправленная комплексная активация (аналитическая)

Проблема

Наивный complex_tanh вида:

$$ \tanh(z) = \tanh(\Re(z)) + i \cdot \tanh(\Im(z)) $$

нарушает условия Коши — Римана и делает функцию неаналитической. Для глубоких физических моделей это критично.

Решение: Cardioid-активация

Используем аналитическую комплексную активацию Cardioid:

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

где $\angle z$ — фаза комплексного числа.

В компонентной форме:

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

Преимущества:

  • Сохраняет аналитичность (удовлетворяет условиям Коши — Римана);
  • Масштабирует амплитуду в зависимости от фазы;
  • Естественно работает с комплексным градиентом.

Альтернатива — uReLU (Unitary ReLU):

$$ f(z) = \begin{cases} z, & \text{если } |z| > c \ 0, & \text{иначе} \end{cases} $$

Cardioid выбран как более гладкий вариант.


3. Исправленный Лапласиан

Проблема

Лапласиан по измерению батча (dim=0) связывает независимые объекты в батче случайным образом, что физически некорректно.

Решение

Лапласиан применяется только к пространственным координатам (если вход — сетка/картинка/спектрограмма) или к соседним признакам (если вход — вектор).

В общем виде для одномерного случая (по признакам):

$$ \nabla^2 z_j = z_{j-1} - 2z_j + z_{j+1} $$

Для 2D-случая (картинка, спектрограмма):

$$ \nabla^2 z_{ij} = z_{i-1,j} + z_{i+1,j} + z_{i,j-1} + z_{i,j+1} - 4z_{ij} $$

Если пространственная структура отсутствует (например, табличные данные), Лапласиан может быть опущен ($\mu = 0$) или заменён на графовый Лапласиан по известным связям признаков.


4. Итоговая математическая модель слоя

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

$$ 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)|) $$

где:

  • $z(t) \in \mathbb{C}$ — комплексное состояние;
  • $W, b \in \mathbb{C}$ — комплексные веса и смещения;
  • $\mu = \text{softplus}(\theta_\mu)$ — обучаемая вязкость;
  • $\eta$ — амплитуда шума;
  • $\xi(t) \sim \mathcal{N}(0, 1)$ — стохастический член;
  • $\nabla^2 z$ — лапласиан по пространственным координатам или признакам.

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

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

где $|W| = \sqrt{W_{\text{Re}}^2 + W_{\text{Im}}^2}$.


5. Код слоя (PyTorch, Complex, Cardioid, исправленный Лапласиан)

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

class ETVP_ComplexLayer(nn.Module):
    def __init__(self, dim_in, dim_out, R_max=1.0, eta_init=0.01, beta_init=0.1,
                 laplace_mode='features', 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
        
        # Режим Лапласиана: 'features' — по признакам, 'spatial' — по пространству (2D), 'none' — отключен
        self.laplace_mode = laplace_mode
        self.use_cardioid = use_cardioid

    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):
        """Полноценное комплексное линейное преобразование"""
        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
        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
        cos_angle = re / magnitude
        
        # Cardioid: f(z) = 0.5 * (1 + cos_angle) * z
        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).
        Не связывает объекты в батче.
        """
        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).
        Для входов вида: [batch, channels, height, width].
        """
        if z.dim() != 4:
            # Если вход не 2D, возвращаем нули
            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 laplacian(self, z):
        """Выбор режима Лапласиана"""
        if self.laplace_mode == 'features':
            return self.laplacian_features(z)
        elif self.laplace_mode == 'spatial':
            return self.laplacian_spatial(z)
        elif self.laplace_mode == 'none':
            return torch.zeros_like(z)
        else:
            raise ValueError(f"Unknown laplace_mode: {self.laplace_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)
        
        # Дифференциальный оператор (давление среды) — исправленный Лапласиан
        lap_re = self.laplacian(x_re)
        lap_im = self.laplacian(x_im)
        
        # 4. Стохастическая самокоррекция (шум под упругим давлением градиентного поля)
        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())
        
        # 5. Интегрирование по методу Эйлера-Маруямы (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__":
    # Тест 1: Лапласиан по признакам (табличные данные)
    layer = ETVP_ComplexLayer(dim_in=16, dim_out=16, laplace_mode='features')
    
    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(curr_re, curr_im, dt=0.1)
    
    loss = (curr_re**2 + curr_im**2).sum()
    loss.backward()
    
    print("--- Тест 1 (features) пройден ---")
    print("Градиент W_re:", layer.W_re.grad.abs().mean().item())
    print("Градиент Вязкости (theta_mu):", layer.theta_mu.grad.item())
    
    # Тест 2: Лапласиан по пространству (2D-картинка)
    layer2 = ETVP_ComplexLayer(dim_in=16, dim_out=16, laplace_mode='spatial')
    
    x_re = torch.randn(4, 4, 8, 8, requires_grad=True)  # [batch, channels, h, w]
    x_im = torch.randn(4, 4, 8, 8, requires_grad=True)
    
    # Приводим к 2D для complex_mul
    batch, ch, h, w = x_re.shape
    x_re_flat = x_re.view(batch, ch * h * w)
    x_im_flat = x_im.view(batch, ch * h * w)
    
    curr_re, curr_im = layer2(x_re_flat, x_im_flat, dt=0.1)
    
    loss = (curr_re**2 + curr_im**2).sum()
    loss.backward()
    
    print("--- Тест 2 (spatial) пройден ---")
    print("Градиент W_re:", layer2.W_re.grad.abs().mean().item())