Если вы когда-нибудь пытались натравить классическую LSTM на минутные свечи волатильных активов, вы знаете эту боль. Сначала Loss красиво падает на трейне, вы предвкушаете покупку острова, а на тесте модель превращается в тыкву. Она либо предсказывает скользящую среднюю со сдвигом на один шаг, либо упирается в «стену» Loss = 0.693 (то есть −ln(0.5)), сводя всё к подбрасыванию монетки.
Проблема не в вас. Проблема в том, что рекуррентные сети (RNN, LSTM, GRU) живут в дискретном времени. Для них шаг между 10:00 и 10:01 абсолютно идентичен шагу между пятницей и утром понедельника. Они не умеют сжимать и растягивать восприятие времени, когда волатильность взрывается.
В этой статье мы отойдем от мейнстримных архитектур и напишем с нуля Liquid Neural Network (Жидкую Нейронную Сеть). Мы заставим время течь непрерывно, используя численные методы дифференциальных уравнений прямо внутри PyTorch-графа, и посмотрим, как она вытаскивает скрытый макро-тренд из абсолютного рыночного хаоса.
Теория без воды: Что делает сеть «жидкой»?
Концепция Liquid Time-Constant Networks была представлена исследователями из Лаборатории искусственного интеллекта MIT (CSAIL). Их изначальная цель — управление дронами и автопилотами в непредсказуемой среде. Но финансовые рынки — это та же турбулентность, только выраженная в долларах , рублях и других валютах.
В классической RNN скрытое состояние $h_t$ обновляется по дискретным шагам:
В Liquid Network мы отказываемся от дискретности. Состояние нейрона h(t) — это непрерывная переменная, описываемая обыкновенным дифференциальным уравнением (ОДУ):
Главная магия скрыта в обучаемом параметре $\tau$ (tau). Это постоянная времени, или «вязкость» нейрона:
Если $\tau$ маленькое (стремится к нулю), нейрон «быстрый». Он мгновенно реагирует на рыночный шум, скачки спреда и минутные импульсы.
Если $\tau$ большое, нейрон «медленный» и вязкий. Он игнорирует шум и накапливает долгосрочный контекст.
Позволяя сети самой выучить распределение $\tau$ для каждого нейрона, мы получаем систему, которая автоматически балансирует между мгновенной реакцией на новостные шоки и удержанием глобального тренда.
Часть 1. Пишем Liquid Layer на PyTorch (Хардкор)
Хватит теории, давайте писать код. Нам нужно реализовать слой, который будет решать дифференциальное уравнение Эйлеровым шагом прямо во время forward прохода.
В отличие от LSTM с ее громоздкой системой ворот (Forget/Update gates), ядро «жидкой» сети выглядит удивительно минималистично:
Python
import torch import torch.nn as nn class LiquidLayer(nn.Module): def __init__(self, in_features, hidden_features, dt=0.05): super(LiquidLayer, self).__init__() self.dt = dt # Шаг времени для интеграции Эйлера self.hidden_features = hidden_features # Линейные трансформации self.W_in = nn.Linear(in_features, hidden_features) self.W_h = nn.Linear(hidden_features, hidden_features) # Обучаемый параметр "вязкости" для каждого нейрона self.tau = nn.Parameter(torch.rand(hidden_features) * 2.0 + 0.5) def forward(self, x): batch_size, seq_len, _ = x.size() h = torch.zeros(batch_size, self.hidden_features, device=x.device) outputs = [] for t in range(seq_len): # 1. Вычисляем входящий "поток" (Forcing term) forcing_term = torch.tanh(self.W_in(x[:, t, :]) + self.W_h(h)) # 2. Защита от взрыва градиентов # Ограничиваем tau снизу, иначе при tau -> 0 производная улетит в NaN safe_tau = torch.clamp(self.tau, min=0.01) # 3. Решаем ОДУ: dh/dt = -h/tau + forcing_term dh = (-h / safe_tau) + forcing_term # 4. Шаг интеграции (Euler method) h = h + self.dt * dh outputs.append(h.unsqueeze(1)) return torch.cat(outputs, dim=1)
Важная деталь архитектуры: мы ограничиваем $\tau$ снизу через torch.clamp. Если $\tau$ приблизится к нулю, наша «жидкость» вскипит, уравнение уйдет в бесконечность, и мы получим классическую проблему Exploding Gradients.
Для полноценной работы оборачиваем это в простую сеть с линейным слоем (readout) на конце:
Python
class LiquidNet(nn.Module): def __init__(self, in_features, hidden_features, out_features): super(LiquidNet, self).__init__() self.liquid = LiquidLayer(in_features, hidden_features) self.readout = nn.Linear(hidden_features, out_features) def forward(self, x): liquid_states = self.liquid(x) # Берем последнее состояние для предсказания return self.readout(liquid_states[:, -1, :])
Часть 2. Стенд испытаний: Генерируем хаос со скрытым сигналом
Проверять сеть на обычном синусе скучно. Мы сгенерируем суровый финансовый датасет (смесь Geometric Brownian Motion с прыжками).
Чтобы задача имела математическое решение, мы спрячем внутри сильный шум очень слабую синусоиду (макро-тренд). Задача сети — бинарная классификация: предсказать, будет ли цена через 5 шагов выше текущей.
Python
import numpy as np def generate_market_data(seq_len=60, samples=10000, future_steps=5): X_data, Y_data = [], [] for _ in range(samples): # 1. Скрытый макро-тренд (очень слабый сигнал) phase = np.random.uniform(0, 2 * np.pi) t = np.linspace(0, 4 * np.pi, seq_len + future_steps) macro_trend = np.sin(t + phase) * 0.03 # 2. Рыночный хаос (Случайный шум + Прыжки волатильности) noise = np.random.normal(0, 0.02, seq_len + future_steps) jumps = np.random.choice([0, 1, -1], size=seq_len + future_steps, p=[0.96, 0.02, 0.02]) * np.random.uniform(0.1, 0.25) returns = macro_trend + noise + jumps price_path = np.cumsum(returns) x_window = price_path[:seq_len] - price_path[0] # Нормализация # Таргет: 1 если цена выросла, 0 если упала y_label = 1.0 if price_path[-1] > price_path[seq_len-1] else 0.0 X_data.append(x_window) Y_data.append(y_label) return np.array(X_data), np.array(Y_data)
Мы обучали сеть (hidden_features=64, Adam lr=0.002, BCEWithLogitsLoss) на 50 эпохах. Как было сказано в начале, любая попытка предсказать чистое случайное блуждание упирается в Loss: 0.693 и точность в районе 50%.
Но наша Liquid Network пробила эту стену:

Точность взлетела до стабильных 83%. Сеть успешно отфильтровала высокочастотный хаос и зацепилась за скрытый паттерн.
Часть 3. Вскрытие: Смотрим, как течет время
Самое интересное происходит под капотом во время инференса. Давайте достанем из сети обученные параметры $\tau$ и посмотрим на непрерывную динамику внутренних состояний $h(t)$ нескольких нейронов в момент предсказания.

Посмотрите на распределение обученных параметров (в консоли под графиком):
Минимальная вязкость (\tau): 0.548
Максимальная вязкость (\tau): 2.549
Эта разница в 5 раз визуально бросается в глаза на нижнем графике!
Фиолетовая и синяя линии (Быстрые нейроны): У них низкий $\tau$. Они вибрируют, реагируя на локальный шум «стакана» и скачки волатильности входного сигнала.
Оранжевая и красная линии (Медленные нейроны): У них высокий $\tau$. Посмотрите, какие они плавные. Они почти полностью игнорируют высокочастотный хаос и накапливают долгосрочную память о глобальном тренде.
Сеть самостоятельно, исключительно через Backpropagation, распределила роли: кто-то из нейронов работает скальпером, а кто-то — долгосрочным инвестором.
Вывод
Liquid Neural Networks — это не просто красивый академический концепт из MIT. Это элегантный, "white-box" способ внедрить физику непрерывного времени в дискретный мир тензоров.
Если вы работаете с высоко-волатильными временными рядами, где время и интенсивность событий непредсказуемы (крипта, HFT, снятие показаний с датчиков IoT) — попробуйте заменить ваши громоздкие LSTM-блоки на компактный «жидкий» слой. Возможно, именно он поможет вам пробить стену в 0.693.
Код экспериментов доступен для воспроизведения. Пишите в комментариях, пробовали ли вы уже интегрировать дифференциальные уравнения в свои DL-пайплайны!
