Объединены все исправления версий 2.1 и 2.2 в единый универсальный слой, который автоматически определяет размерность входного тензора и применяет корректный режим Лапласиана.
- Комплексная активация заменена на аналитическую Cardioid (удовлетворяет условиям Коши — Римана).
- Лапласиан больше не связывает объекты батча — работает по признакам или пространству.
- Слой принимает исходный 4D-тензор без сплющивания.
- Комплексное умножение переписано на
torch.einsumдля поддержки 2D и 4D. - Пространственный Лапласиан реально работает на изображениях.
- Автоопределение размерности: 2D →
features, 4D →spatial. - Единый код для табличных данных и изображений.
- Встроенные тесты для обоих режимов.
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)