Современные подходы и применения#

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

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

Модификация#

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

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

Формально можно определить весовые функции \(W(t) = \exp(-\lambda t)\) c некоторым \(\lambda > 0\) и домножать квадрат невязки на них, таким образом можно переписать функцию ошибки для ОДУ 1 порядка (и аналогично для любого другого):

\[ \begin{equation*} J(\theta)=\frac{1}{M} \sum_{j=1}^{M}\exp(-\lambda t_{j})\left(\frac{\partial \hat{x}\left(t_{j} ; \theta\right)}{\partial t}-G\left(t_{j}, \hat{x}\left(t_{j} ; \theta\right)\right)\right)^{2} \end{equation*} \]

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

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

Для такого случая нам надо модифицировать пробные функции, взяв их в виде

\[ \begin{equation*} \hat{\mathbf{x}}\left(t, \mathbf{x}_{\mathbf{0}} ; \theta\right)=\mathbf{x}_{\mathbf{0}}+B(t) N\left(t, \mathbf{x}_{\mathbf{0}} ; \theta\right) \end{equation*} \]

Здесь необходимо выбрать \(B(t_{0}) = 0 \), чтобы как всегда обеспечить выполнение начальных условий, подойдёт \(B(t) = 1-e^{-(t-t_{0})}\) Такой подход называется Solution Bundles (то есть «пакеты» или «пучки» решений).

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

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

Curriculum learning#

Иногда не удаётся сойтись к решению диффференциального уравнения - зачастую такое случается в системах ОДУ, в таких случаях может помочь техника «curriculum learning» (дословно - «изучение учебной программы»).

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

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

Пример из всё той же классической статьи:

\[\begin{split} \begin{aligned} & f_1^{\prime}(x)=\cos (x)+f_1(x)^2+f_2(x)-1-x^2-\sin ^2(x) \\ & f_2^{\prime}(x)=2 x-\left(1+x^2\right) \sin (x)+f_1(x) f_2(x) \end{aligned} \end{split}\]

на отрезке \([0, 3]\) с условиями \(f_{1} (0) = 0\), \(f_{2} (0) = 1\).

Приведём результаты запусков с разными «учебными программами»

\[\begin{split} \begin{array}{|l|l|l|l|l|} \hline \text { Row } & \text { Curriculum } & \text { Solution } & \text { Solution Inaccuracy } \\ \hline \hline 1 & {[0,3]} & \text { Incorrect } & 4.97 \\ \hline 2 & {[0,1.5],[0,3]} & \text { Correct } & 3.14 \times 10^{-4} \\ \hline 3 & {[0,1],[0,2],[0,3]} & \text { Correct } & 2.63 \times 10^{-4} \\ \hline 4 & {[0,1.5],[1.5,3]} & \text { Correct } & 1.85 \times 10^{-3} \\ \hline 5 & {[0,1],[1,2],[2,3]} & \text { Correct } & 1.62 \times 10^{-3} \\ \hline 6 & {[0,1.5],[1.5,3],[0,3]} & \text { Correct } & 1.84 \times 10^{-4} \\ \hline 7 & {[0,1],[1,2],[2,3],[0,3]} & \text { Correct } & 1.69 \times 10^{-4} \\ \hline 8 & {[1.5,3],[0,3]} & \text { Incorrect } & 5.80 \\ \hline 9 & {[2,3],[1,3],[0,3]} & \text { Incorrect } & 6.35 \\ \hline 10 & {[2.25,3],[1.5,3],[0.75,3],[0,3]} & \text { Incorrect } & 7.75 \\ \hline 11 & {[1.5,3],[0,1.5],[0,3]} & \text { Correct } & 4.39 \times 10^{-4} \\ \hline 12 & {[2,3],[1,2],[0,1],[0,3]} & \text { Correct }& 7.85 \times 10^{-4} \\ \hline 13 & {[1.5,3],[0,1.5]} & \text { Correct } & 1.62 \times 10^{-3} \\ \hline 14 & {[2,3],[1,2],[0,1]} & \text { Correct } & 6.78 \times 10^{-3} \\ \hline \end{array} \end{split}\]

Нетрудно заметить, что нейронка плохо справляется с решением системы ОДУ при обучении на всём отрезке, однако показывает хорошие результаты при обучении «по кусочкам», более того, отрезок \([0, 1.5]\) судя по всему «проще», чем отрезок \([1.5, 3]\).

Применение для нахождения параметров дифференциального уравнения#

Поставим в некотором смысле обратную задачу. Пусть дано дифференциальное уравнение

\[ \begin{equation*} D\left(x, u(x), \nabla u(x), \nabla^{2} u(x), \ldots, \nabla^{m} u(x) ; \lambda_{1}, \ldots, \lambda_{p}\right)=0 \end{equation*} \]

с неизвестными параметрами \(\lambda_{1}, \ldots, \lambda_{p}\).

Пусть нам известны значения решения данного дифференциального уравнения \(u_{1}, \ldots, u_{M}\) в некоторых точках \(x_{1}, \ldots, x_{M}\). По этим данным мы хотим восстановить значения параметров \(\lambda\).

Сделать это можно следующим образом. Возьмём в качестве функции, которую мы будем минимизировать

\[ \begin{equation*} J\left(\theta, \lambda_{1}, \ldots, \lambda_{p}\right)=J_{u}(\theta)+J_{D}\left(\theta, \lambda_{1}, \ldots, \lambda_{p}\right) \end{equation*} \]
\[\begin{split} \begin{align*} J_{u} & =\frac{1}{M} \sum_{j=1}^{M}\left(N\left(x_{j} ; \theta\right)-u_{j}\right)^{2} \\ J_{D} & =\frac{1}{M} \sum_{j=1}^{M} D\left(x_{j}, N\left(x_{j} ; \theta\right), \nabla N\left(x_{j} ; \theta\right), \nabla^{2} N\left(x_{j} ; \theta\right), \ldots, \nabla^{m} N\left(x_{j} ; \theta\right) ; \lambda_{1}, \ldots, \lambda_{p}\right)^{2} \end{align*} \end{split}\]

Таким образом, необходимо оптимизировать при обучении нейронной сети не только параметры \(\theta\), но и \(\lambda\). При обнулении функции \(J\) нейронная сеть:

  1. Предсказывает решение (причём не только в точках, где функция была дана - а в любых, так как на вход нейронки можно подать любые \(x\))

  2. Определяет параметры дифференциального уравнения

Разумеется, если бы мы знали не только значение функции, но и все её производные в данных точках, первое слагаемое, а значит и сама нейронная сеть были бы нам не нужны - можно было бы просто спуститься градиентным спуском и найти оптимальные параметры \(\lambda\).

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