Лагранжевы нейронные сети (LNN) в 2026: моделирование физики с помощью ML | Гайд | AiManual
AiManual Logo Ai / Manual.
25 Янв 2026 Гайд

Лагранжевы нейронные сети: архитектура для моделирования физических законов в PyTorch/TensorFlow

Глубокий гайд по лагранжевым нейронным сетям. Объясняем физические основы, архитектуру и даем рабочий код на PyTorch 2.4 и TensorFlow 2.16 для моделирования зак

Физика против черного ящика: почему обычные нейросети терпят фиаско

Давайте начистоту. Вы берете обычный многослойный перцептрон, кормите его данными о движении маятника и надеетесь, что он предскажет траекторию. Он даже как-то справляется. Пока не спросите его, что будет через 10 секунд. Предсказание разваливается, энергия не сохраняется, система ведет себя как пьяный космонавт в невесомости. Почему? Потому что сеть не знает законов физики. Она просто аппроксимирует кривые, не понимая сути.

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

Вот и вся проблема. Мы хотим, чтобы модель не просто угадывала, а соблюдала законы сохранения энергии, импульса, момента. И здесь на сцену выходят лагранжевы нейронные сети (LNN). Не очередной хайповый архитектурный трюк, а фундаментальный подход, заставляющий нейросеть учить физику, а не данные.

Лагранжиан: не страшная математика, а ключ к мирозданию

Забудьте на минуту про нейросети. Вспомните классическую механику. Есть система – маятник, планеты, пружина. Ее состояние описывается обобщенными координатами q (например, угол отклонения) и скоростями . Лагранжиан L(q, q̇) – это просто разность кинетической и потенциальной энергии. Вся магия в принципе наименьшего действия: природа выбирает такую траекторию, чтобы интеграл от лагранжиана был минимален.

💡
Принцип наименьшего действия – это как если бы система говорила: "Я пойду путем, требующим наименьших усилий". Из этого принципа выводятся все уравнения движения (уравнения Эйлера-Лагранжа). LNN использует это напрямую.

Идея LNN до гениальности проста: вместо того чтобы учить уравнения движения, мы учим лагранжиан нейросетью. А потом автоматически, через автоматическое дифференцирование, получаем из него правильные уравнения. Сеть вынуждена соблюдать физическую структуру мира. Гениально? Еще бы.

Архитектура LNN: где физика живет внутри графа вычислений

Архитектура LNN – это не новый тип слоя. Это способ организации вычислительного графа и функции потерь. Вот как она устроена:

  1. Входы: нейросеть принимает на вход текущие обобщенные координаты и скорости (q, q̇).
  2. Сердце: многослойный перцептрон (MLP) или другая архитектура выдает одно число – значение лагранжиана L_θ(q, q̇). θ – параметры сети.
  3. Физический движок: с помощью torch.autograd или tf.GradientTape вычисляются частные производные лагранжиана по q и .
  4. Уравнения движения: подставляем производные в уравнение Эйлера-Лагранжа: d/dt (∂L/∂q̇) - ∂L/∂q = 0. Это дает ускорение .
  5. Интегрирование: зная , можно предсказать состояние в следующий момент времени (например, методом Верле или Рунге-Кутты).

Обучение происходит не на предсказании позиций, а на соблюдении уравнений движения. Мы показываем сети последовательность состояний (q, q̇) и учим ее так, чтобы предсказанные уравнения давали правильную динамику. Сеть не может "сжульничать" – она обязана выучить настоящий лагранжиан системы.

1 Готовим среду: PyTorch 2.4 или TensorFlow 2.16?

На январь 2026 года оба фреймворка активно развиваются. PyTorch 2.4 принес еще больше оптимизаций для компиляции (torch.compile), что критично для быстрого вычисления производных. TensorFlow 2.16 окончательно перешел на tf.math как основное API. Для LNN я рекомендую PyTorch – его autograd интуитивнее для таких исследований. Но код ниже покажу для обоих.

# Установка актуальных версий (январь 2026)
pip install torch==2.4.0 tensorflow==2.16.0 numpy scipy matplotlib

Не используйте старые руководства с torch.autograd.grad без контекста. В PyTorch 2.4 для вычисления вторых производных лучше использовать torch.func.jacrev и torch.func.hessian – они быстрее и надежнее.

2 Пишем ядро LNN на PyTorch 2.4

Создадим класс для лагранжевой сети. Мы моделируем простой гармонический осциллятор, чтобы проверить работу.

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.func import jacrev, hessian

class LagrangianNetwork(nn.Module):
    """Лагранжева нейронная сеть для систем с одной степенью свободы."""
    def __init__(self, hidden_dim=64):
        super().__init__()
        # Простая MLP: вход (q, q_dot) -> скрытые слои -> один выход (L)
        self.net = nn.Sequential(
            nn.Linear(2, hidden_dim),
            nn.Tanh(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.Tanh(),
            nn.Linear(hidden_dim, 1)
        )
        
    def forward(self, state):
        """Возвращает лагранжиан L(q, q_dot)."""
        # state: тензор формы [batch_size, 2], где [:,0]=q, [:,1]=q_dot
        return self.net(state).squeeze(-1)  # Убираем последнюю размерность
    
    def equations_of_motion(self, state):
        """Вычисляет ускорение q_ddot по уравнению Эйлера-Лагранжа."""
        # Требует вычисления градиентов
        q, q_dot = state[:, 0:1], state[:, 1:2]  # Сохраняем размерность для градиентов
        
        # Создаем тензор, требующий градиента
        state_requires_grad = torch.cat([q, q_dot], dim=1).requires_grad_(True)
        
        # Вычисляем лагранжиан
        L = self.forward(state_requires_grad)
        
        # Первые производные L по q и q_dot
        # Используем новый API torch.func для эффективности
        def L_func(s):
            return self.forward(s).sum()  # sum для скаляра
        
        # Якобиан dL/dstate
        jac = jacrev(L_func)(state_requires_grad)  # [batch_size, 2]
        dL_dq = jac[:, 0:1]    # ∂L/∂q
        dL_dqdot = jac[:, 1:2] # ∂L/∂q̇
        
        # Для d/dt (∂L/∂q̇) нам нужна производная dL_dqdot по времени
        # Через цепное правило: d/dt (∂L/∂q̇) = (∂²L/∂q̇∂q) * q̇ + (∂²L/∂q̇²) * q̈
        # Но мы можем вычислить производную dL_dqdot по времени с помощью autograd
        # Упрощенный подход: вычисляем градиент dL_dqdot по state и умножаем на [q̇, q̈]
        # На практике часто используют автоматическое дифференцирование второго порядка.
        
        # Более прямой способ: вычислим гессиан
        hess = hessian(L_func)(state_requires_grad)  # [batch_size, 2, 2]
        d2L_dqdot_dq = hess[:, 1, 0]  # ∂²L/∂q̇∂q
        d2L_dqdot2 = hess[:, 1, 1]    # ∂²L/∂q̇²
        
        # Уравнение Эйлера-Лагранжа: d/dt (∂L/∂q̇) - ∂L/∂q = 0
        # d/dt (∂L/∂q̇) = d2L_dqdot_dq * q_dot + d2L_dqdot2 * q_ddot
        # Тогда q_ddot = (∂L/∂q - d2L_dqdot_dq * q_dot) / d2L_dqdot2
        with torch.no_grad():
            q_ddot = (dL_dq.squeeze() - d2L_dqdot_dq * q_dot.squeeze()) / d2L_dqdot2
        
        return q_ddot.unsqueeze(-1)

Это основа. Но в таком виде есть подводные камни. Деление на d2L_dqdot2 может привести к численной нестабильности, если вторая производная близка к нулю. В реальных реализациях используют более устойчивые методы решения уравнения.

3 Функция потерь: как заставить сеть учить физику

Мы не используем MSE между предсказанными и истинными координатами. Вместо этого мы используем физически информированную функцию потерь. Например, можем потребовать, чтобы уравнения движения, выведенные из предсказанного лагранжиана, совпадали с реальным ускорением, полученным из данных.

def physics_informed_loss(model, states, dt=0.01):
    """
    states: тензор [batch_size, seq_len, 2] - последовательности (q, q_dot).
    Функция потерь основана на согласованности с уравнениями движения.
    """
    batch_size, seq_len, _ = states.shape
    losses = []
    
    for t in range(seq_len - 1):
        state_t = states[:, t, :]          # Текущее состояние
        state_t1 = states[:, t+1, :]       # Следующее состояние (из данных)
        
        # Вычисляем предсказанное ускорение из модели
        q_ddot_pred = model.equations_of_motion(state_t)
        
        # Из данных получаем ускорение через конечные разности
        q_dot_t = state_t[:, 1]
        q_dot_t1 = state_t1[:, 1]
        q_ddot_true = (q_dot_t1 - q_dot_t) / dt
        
        # Потеря: MSE между предсказанным и вычисленным ускорением
        loss = F.mse_loss(q_ddot_pred.squeeze(), q_ddot_true)
        losses.append(loss)
    
    return torch.stack(losses).mean()

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

Типичные грабли, на которые наступают все

  • Численная нестабильность вторых производных. Autograd второго порядка – это не игрушки. Если ваша сеть выдает очень большие или очень маленькие значения, градиенты взрываются или исчезают. Решение: нормализуйте входные данные (q и q̇) и используйте функции активации с ограниченным выходом (Tanh, Sigmoid).
  • Неудачная параметризация. Лагранжиан для механических систем обычно имеет вид L = T(q̇) - V(q). Вы можете помочь сети, разделив сеть на две части: одну для кинетической энергии, другую для потенциальной. Это сильно ускоряет обучение.
  • Слишком маленький датасет. LNN все еще нуждаются в данных. Но не в миллионах точек. Им нужно несколько траекторий, покрывающих разный диапазон энергий. Если вы дадите только малые колебания маятника, сеть не узнает про большие.
  • Игнорирование диссипации. Классический лагранжиан описывает консервативные системы. Если в системе есть трение, нужно добавлять диссипативную функцию Рэлея. Иначе модель будет врать.

Совет: начните с простой системы, где вы знаете аналитический лагранжиан (гармонический осциллятор, маятник). Обучите LNN и сравните предсказанный лагранжиан с истинным. Это даст уверенность, что все работает.

А что с TensorFlow 2.16?

Принцип тот же, но имплементация использует tf.GradientTape более явно. Вот сокращенный вариант:

import tensorflow as tf
import tensorflow.keras as keras

class LNN_TF(keras.Model):
    def __init__(self, hidden_dim=64):
        super().__init__()
        self.dense1 = keras.layers.Dense(hidden_dim, activation='tanh')
        self.dense2 = keras.layers.Dense(hidden_dim, activation='tanh')
        self.out = keras.layers.Dense(1, activation=None)
    
    def call(self, inputs):
        # inputs: [batch, 2]
        x = self.dense1(inputs)
        x = self.dense2(x)
        return tf.squeeze(self.out(x), axis=-1)
    
    def equations_of_motion(self, state):
        q, q_dot = state[:, 0:1], state[:, 1:2]
        with tf.GradientTape(persistent=True) as tape:
            tape.watch(state)
            L = self.call(state)
        dL_dq = tape.gradient(L, q)  # ∂L/∂q
        dL_dqdot = tape.gradient(L, q_dot)  # ∂L/∂q̇
        # Вторые производные... (аналогично PyTorch, но длиннее)
        del tape
        # ... возвращаем q_ddot

TensorFlow код немного более многословен для высших производных. Но суть та же.

Зачем все это? Когда LNN выстрелит по-настоящему

LNN – не панацея. Они сложнее в обучении, чем обычные сети. Но они незаменимы, когда:

  • У вас мало данных (дорогие эксперименты в физике, химии).
  • Требуется гарантированное соблюдение законов сохранения.
  • Нужно интерполировать поведение системы в областях, где данных нет (экстраполяция во времени).
  • Вы хотите открыть новые законы из данных. Обученная LNN может подсказать аналитическую форму лагранжиана.

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

Если вы упираетесь в ограничения GPU при обучении таких моделей (а они требуют много памяти для вторых производных), посмотрите мой разбор про оптимизацию использования GPU. Там есть трюки, которые пригодятся и здесь.

Что дальше? Символическая регрессия и LNN

Самый интересный фронтир – комбинация LNN с символической регрессией. Вы обучаете сеть, а потом анализируете ее веса, чтобы извлечь человекочитаемую формулу лагранжиана. Библиотеки вроде PySR или SymPy могут помочь. Это уже граница между машинным обучением и открытием научных законов.

Попробуйте. Возьмите код выше, запустите на задаче маятника. Увидите, как сеть сначала выдает чушь, а потом внезапно «понимает» закон сохранения энергии. Этот момент стоит всех потраченных часов отладки.

Не верьте слепо потерям. Всегда визуализируйте траектории, предсказанные обученной моделью, и проверяйте сохранение энергии. График энергии от времени должен быть горизонтальной линией (плюс-минус шум). Если она дрейфует – что-то не так.

Для тех, кто хочет углубиться в тему Physics-Informed ML, рекомендую книгу "Physics-Informed Neural Networks" (актуальное издание 2025 года). Там разобраны не только LNN, но и другие архитектуры, например, гамильтоновы нейронные сети.

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