Архитектура высокоэффективных нейросетевых вычислений на C++ для прогнозирования динамики ВВП. backpropagation.. backpropagation. C++.. backpropagation. C++. c++20.. backpropagation. C++. c++20. nvidia cuda.. backpropagation. C++. c++20. nvidia cuda. искусственный интеллект.. backpropagation. C++. c++20. nvidia cuda. искусственный интеллект. макроэкономика.. backpropagation. C++. c++20. nvidia cuda. искусственный интеллект. макроэкономика. нейронные сети.. backpropagation. C++. c++20. nvidia cuda. искусственный интеллект. макроэкономика. нейронные сети. оптимизация кода.. backpropagation. C++. c++20. nvidia cuda. искусственный интеллект. макроэкономика. нейронные сети. оптимизация кода. финансы.. backpropagation. C++. c++20. nvidia cuda. искусственный интеллект. макроэкономика. нейронные сети. оптимизация кода. финансы. экономика.

Введение

Принято считать, что для анализа макроэкономики и прогнозирования ВВП необходимы мощные серверы. Обычно разработчики используют Python и тяжелые библиотеки вроде TensorFlow или PyTorch. Однако бывают случаи когда надо чтобы модель была доступна на обычном недорогом ноутбуке или мы хотим применить наработки модели и переложить их на платы ардуино с лимитом памяти 32 кб и ценой в розничном магазине 300 – 400 рублей за штуку.

Решением проблемы становится полный отказ от сторонних фреймворков. В моей программе вся математика нейронных слоев написана с нуля на чистом C++20, а для быстрого подбора весов на ПК применяется технология NVIDIA CUDA. Сама модель имитирует реальные мультипликаторы. В первом скрытом слое идет увеличение нейронов, далее проходит несколько слоев с нелинейными связями, в конце сеть сжимается до шести проекционных нейронов по методологии Всемирного банка.

Идея применять нейронные сети для предсказания экономических процессов зародилась в конце XX века. Ученые искали замену обычным линейным моделям. Одними из первых нелинейные свойства ИНС для макроэкономических рядов США исследовали N. R. Swanson и H. White [2]. Они доказали, что гибкие связи лучше находят скрытые циклы. C.-M. Kuan и T. Liu [4] выявили, что очистка данных перед подачей в сеть сильно снижает ошибку. В начале 2000-х годов началось активное сравнение нейронных сетей с классическими методами вроде ARIMA. M. Marcellino [5], K. Neusser и M. Wagner [9] доказав превосходство многослойных сетей при анализе ВВП европейских стран на длинных дистанциях, а переключение весов C.-M. Lin и P.-H. Chen [7] помогает предсказывать кризисные периоды. Современный этап связан с обработкой больших массивов информации. В Индии S. Ghosh [6] и M. C. Medeiros с соавторами [8] успешно применили нейросети для поиска скрытых зависимостей в индийской экономике. Q. Zhang и Y. Bian [3] в 2024 году провели масштабные тесты по отслеживанию темпов роста китайского ВВП. Они подтвердили преимущество нелинейных функций активации.

В России И. А. Семенов [1] подробно описал практику применения многослойных ИНС для анализа российского ВВП в условиях изменения внешних рынков.

В этой статье мы подробно разберем создание комплекса gdp_forecast. Вы увидите, с какими болями сталкивается разработчик при ручной оптимизации. Я честно расскажу, почему провалился experiment с 8-битными числами, как защитить данные от переполнения и как добиться высокой точности на реальных данных ВВП России.

Часть 1. Отбор экономических маркеров и сбор «сырых» данных Росстата

Эмпирический анализ и моделирование прироста ВВП

Поиск источников и сбор «сырых» данных

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

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

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

  • Показатели цен: индекс потребительских цен (ИПЦ), индекс цен производителей, динамика цен на строительные товары и тарифы на грузовые перевозки [10].

  • Показатели рынка труда: общая производительность труда [11], уровень номинальной заработной платы [12], общая численность и степень вовлеченности рабочей силы в экономику [13].

  • Курсы валют: официальный курс доллара США [14], курс евро [15] и динамика других значимых мировых валют.

  • Демографические показатели: общая численность постоянного населения страны, показатели рождаемости и смертности [16].

  • Рентабельность и устойчивость бизнеса: сальдированный финансовый результат (совокупная прибыль и убытки), доля прибыльных организаций в секторах и общая рентабельность активов [17].

  • Сырьевой экспорт: объемы добычи угля [18], объемы добычи и добычной потенциал природного газа [19], а также мировые цены на нефть марки Brent [20].

  • Социально-экономические индикаторы доходов: установленная величина прожиточного минимума [21], внутренний объем и доля инновационных товаров в промышленности [22], а также нормы рабочего времени согласно производственному календарю [23].

  • Показатели участия в военных операциях.

  • Статистика внешней торговли: совокупные объемы экспорта и импорта с детальным разделением на страны ближнего и дальнего зарубежья [24].

  • Финансовые и монетарные показатели: денежная масса в обращении (агрегаты М0 и М2) [25], объемы выданных жилищных и ипотечных кредитов [26], динамика розничного кредитного портфеля [27], совокупный корпоративный кредитный портфель банковского сектора [28], а также ключевая ставка, ставки рефинансирования и аукционов репо Банка России [29].

  • Прирост валового внутреннего продукта (ВВП) за предыдущие отчетные периоды.

  • Прочие вспомогательные показатели.

    Результаты отбора показателей и логика работы модели

    После проведения масштабного эмпирического тестирования и проверки собранных массивов информации на математической модели был сделан ключевой вывод. На номинальный прирост валового внутреннего продукта (ВВП) в каждом квартале (исследуемый период с 2004 по 2013 годы включительно) ключевое влияние оказывают всего 9 экономических показателей:

    1) Индекс потребительских цен (ИПЦ). Сведения аккумулируются с официального сайта Росстата. Они наиболее точно отражают инфляционные процессы и ценовую динамику на рынке основных товаров и услуг, приобретаемых конечными потребителями.

    2) Изменение среднемесячного курса доллара США.

    3) Изменение среднемесячного курса евро. Расчет средних курсов валют производился путем суммирования ежедневных официальных значений и деления на точное количество торговых дней в конкретном квартале. Физический объем валютных сделок на биржевом и внебиржевом рынках для оценки умышленно не использовался.

    4) Среднемесячная цена на нефть марки Brent. Данный маркер является базовым, так как от него напрямую рассчитывается стоимость российских экспортных сортов углеводородов. Показатель рассчитывался как средняя цена за месяц, деленная на количество месяцев в квартале.

    5) Денежный агрегат М0 (наличные деньги в обращении).

    6) Денежный агрегат М2 (денежная масса). Сведения по обоим агрегатам фиксировались по данным Банка России строго на конец отчетного квартала.

    7) Количество рабочих дней в квартале. Важная особенность этого параметра заключается в том, что производственный календарь на первый квартал следующего года всегда гарантированно утверждается Правительством РФ в сентябре текущего года, что позволяет использовать его для прогнозов без задержек.

    8) Изменение прироста ВВП за квартал.

    9) Изменение прироста ВВП за год.

    Для понимания архитектуры исследования необходимо отдельно подчеркнуть логику работы алгоритма. Модель создана непосредственно для прогнозирования (предсказания) будущих периодов. Это значит, что если перед нами стоит задача рассчитать ВВП за будущий 4-й квартал, система будет детально анализировать экономические маркеры за прошедший 3-й квартал. Единственным естественным исключением в этой цепочке является количество рабочих дней — данный параметр берется напрямую из того квартала, который мы прогнозируем.

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

    Примеры падения точности модели при изменении состава данных:

    • Исключение денежного агрегата М2: изъятие из расчетов всего одного этого монетарного показателя привело к тому, что ошибка прогноза для четвертых кварталов взлетела с минимальных 0,2 до 12.

    • Исключение производственного календаря: попытка убрать из модели учет количества рабочих дней в прогнозируемом периоде повлекла за собой рост ошибки с 0,2 до 2,8.

    Таким образом, определенная в ходе исследования комбинация факторов обладает наивысшей предсказательной силой.

Часть 2. Математика модели: 4 нелинейных слоя, Softsign и методология Всемирного банка

Как видно из моей предыдущей статьи, я занимаюсь роботостроением. Данная модель разрабатывалась как прототип. Это проекция того, что я смогу загрузить в соединенные между собой слабые и недорогие платы. Иными словами, мне нужна модель для платы с объемом памяти всего в 32 кб [30]. В связи с этим ограничением я выбрал язык C++ и обычную нейронную сеть.

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

Любые индикаторы в экономике — это стимулы для нее или отражение потребления и инвестиций. Как правило, прибыль в экономике измеряется в процентах. Ее не измеряют в разах от размера вложений. Любой экономический индикатор преобразуется несколько раз. Только после этого он становится частью ВВП. В качестве примера можно привести Кейнса. Он полагал, что увеличение общего объема инвестиций приводит к кратному росту национального дохода. Это происходит после превращения инвестиций в зарплаты, покупки, потом снова в инвестиции и так далее назвав это мультипликатовом [31] .

В России измеряют множество мультипликаторов. Среди них — денежный мультипликатор и его влияние на экономику России [32], мультипликаторы вложений в инфраструктуру [33] и мультипликатор денежных расходов государства [34].

Естественно, прямой корреляции между одним параметром и ростом экономики нет. Однако из этих исследований можно сделать важный вывод. Любой вложенный рубль будет потрачен на закупку товаров или зарплаты сотрудников. Сотрудники купят себе товары. В итоге после нескольких оборотов этот рубль станет частью ВВП. Единственный вопрос — сколько раз должен обернуться тот или иной рубль? От этого зависит количество слоев модели. Я эмпирическим путем пришел к выводу, что точнее всего зависимость роста ВВП от входных параметров отражают 4 слоя модели.

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

Я создал первый слой, чтобы смягчить схоластичность показателей. Это сверточный слой, который совмещает между собой несколько показателей. Количество нейронов в нем определяется известной формулой:

C_n^k=frac{n!}{k!(n - k)!}

где:

  • n — общее количество показателей,

  • k — количество показателей в группе.

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

С другой стороны, из теоремы Цибенко мы знаем важный факт. Даже однослойная модель может сделать качественный прогноз. Для этого ей нужно достаточное количество нейронов и верно подобранные входящие данные [38]. Иными словами, чем больше нейронов, тем лучше может быть результат. Я руководился этими двумя факторами и пришел к выводу, что наилучшее значение k равно 2.

Давайте перейдем к самим слоям модели. Модель представляет собой функцию:

sum w_{4}left(f_{3}left(f_{2}left(h_{1}left(g_{0}left(x_{-1},x_{0},y,zright)right)right)right)right)

Перед разбором функции отмечу, что я буду придерживаться правил обозначений Фон Неймана и Моргенштерна [36].

Начнем с входных данных. Функция для них имеет следующий вид:

g_0(x_{-1}, x_0, y, z)=begin{cases} frac{x_0}{x_{-1}} z, & text{если } y=text{Нет} \ x_0 z, & text{если } y=text{Да} end{cases}

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

Однако бывают случаи, когда для квартала вводится коэффициент (q1, q2, q3, q4). Когда мы берем данные для первого квартала, q1 равен 1, а в остальные кварталы он равен 0. Аналогично работают q2, q3 и q4. Для таких показателей нормализация путем деления на предыдущее значение всегда даст 0. Для решения этой проблемы я ввел коэффициент y. Он отвечает на вопрос — нужна ли нормализация.

Помимо этого, в формуле присутствует переменная z. Это теоретический коэффициент, который может принимать значения от 1 до 100%. Он нужен для того, чтобы уменьшить влияние менее значимого показателя. В своих расчетах я всегда использовал этот коэффициент равным 100%. Использование меньшего коэффициента дискуссионно и неоднозначно.

Теперь рассмотрим следующий слой. Он имеет следующий вид:

(h_1(g_0()))_j=sum_{i=1}^{2} k_{j+i-1} cdot w_i + b

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

Следующий скрытый слой выглядит так:

(f_2(h_1))_j=frac{sum_{i=1}^{N} y_i cdot w_{ji} + b_j}{1 + left| sum_{i=1}^{N} y_i cdot w_{ji} + b_j right|}

Перед рассказом про нелинейные слои хочу обратить внимание на важный факт. Большинство функций активации похожи на функцию ошибок Гаусса. Одни из них более растянуты, другие — менее. Их производные также похожи, но они более или менее пологие.

Данный слой представляет собой нелинейную зависимость. В качестве функции активации для него выбрана Softsign. Во время нагрузочного тестирования программы при обучении берется производная функции активации. Когда количество нейронов из первого слоя превышает 12 000, другие функции приводили к перенасыщению модели. Единственная функция, которая не позволяет типу данных double уйти в неопределенно малое или неопределенно большое значение — это Softsign.

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

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

Основной вопрос в данном слое — сколько таких показателей использовать. При расчете ВВП производственным методом Всемирный банк агрегирует показатели добавленной стоимости по 6 ключевым секторам экономики (согласно классификации ООН/МВФ) [37]:

  1. Сельское, лесное хозяйство и рыболовство.

  2. Промышленность (включая добычу полезных ископаемых и энергетику).

  3. Обрабатывающая промышленность (выделяется Всемирным банком в отдельный важный ряд).

  4. Строительство.

  5. Сфера услуг (торговля, транспорт, ИТ, финансы).

  6. Чистые налоги на продукты (налоги за вычетом субсидий, которые добавляются к стоимости секторов для получения финального ВВП).

Использование этих 6 показателей позволяет учесть структурные сдвиги в экономике при расчете общего объема ВВП. В связи с этим последний скрытый слой является проекционным. Он сжимает нейронную сеть до 6 нейронов. Формула выглядит следующим образом:

Финальный выходной слой выглядит так:

(w_4(f_3))_j=sum_{i=1}^{N} y_i cdot w_{ji} + b_j; quad j=1, dots, 6

Как видно, в этой формуле не используется функция активации. Чтобы понять причину, давайте немного углубимся в теорию. Посмотрим на формулу:

G(x)=sum_{j=1}^{N} alpha_j sigma left( w_j^T x + b_j right)

Из этой формулы видно, что для воссоздания любой сложной функции G(x) достаточно взять всего один скрытый слой с нелинейной активацией sigma и умножить его выходы на веса alpha _{j} [35].

Вектор весов alpha_j — это и есть линейный слой. В формуле внешние веса alpha_j складываются абсолютно линейно. Там нет второй функции активации. Иными словами, слой из 6 нейронов — это и есть те самые линейные веса alpha _{j}. Они просто комбинируют то, что уже посчитали предыдущие слои нейронов, поэтому функция активации им не нужна.

Это теоретическое умозаключение доказывает и практика. Функции активации на последнем скрытом этапе увеличивали ошибку и уменьшали скорость сходимости.

Финальный выходной слой является также обычным линейным слоем.

Часть 3. Архитектура gdp_forecast: пишем Backpropagation на C++20, CUDA-кернелы и наступаем на 8-битные грабли

Архитектура программного комплекса gdp_forecast [49]

Когда перед инженером стоит задача уместить глубокую нейросеть в микроконтроллер с объемом памяти всего 32 КБ, каждый байт становится на вес золота. В таких условиях физически невозможно импортировать тяжелые Python-библиотеки вроде TensorFlow, PyTorch или готовые бинарные файлы Scikit-Learn. Они просто не поместятся в чип робота. Единственный путь — написать всю математику слоев, конвейеры обработки и логику управления памятью вручную на чистом C++. В программной архитектуре нашего проекта gdp_forecast вся эта цепочка реализована с нуля: от базового нейрона до параллельных вычислений на GPU.

Изначально я пытался решить проблему жесткого ограничения памяти микроконтроллера радикальным путем — полным отказом от вещественных чисел. В первых версиях движка (архивные файлы OB1.h и OB1.cpp) вместо типов double использовались обычные 8-битные целые числа со знаком — signed char, занимающие всего 1 байт. Для этого был написан кастомный класс Signch, который эмулировал арифметику с фиксированной точкой (два знака после запятой). Логика умножения выглядела следующим образом:

// Эмуляция дробных чисел на 8-битных целых в OB1.cpp
signed char Signch::multtt(signed char amul, signed char bmul) {
    int64_t m3 = static_cast<int64_t>(amul) * static_cast<int64_t>(bmul);
    m3 = m3 / 100; // Масштабирование
    if (m3 > 120) m3 = 120;
    if (m3 < -120) m3 = -120;
    return static_cast<signed char>(m3);
}

if (m3 < -120) m3 = -120; return static_cast<signed char>(m3); }

В качестве внутристековых переменных здесь часто используются одиночные буквы и цифры (например, m3). Это позволяет максимально точно описать действие для компилятора путем приближая код в критических местах к трехадресному и, таким образом, минимизировать количество дополнительных операций с его стороны. При такой детализации называть переменные осмысленно бывает довольно трудно. При анализе кода, если не использовать очень длинных функций в несколько тысяч строк, это проблем не создает [39]. С другой стороны, как показывает деассемблированный код, часто подобная детализация избыточна, и некоторые операции оптимизатор компилятора просто «глотает».

Чтобы защитить 8-битный тип от циклического переполнения памяти, когда значение выходит за рамки диапазона от -128 до 127, пришлось использовать огромные логические каскады вметоде сложения plusss. Они искусственно зажимали коридор чисел в пределах от -120 до 120 через метод scale. Эта механика идеально подходила для прямого инференса на слабом процессоре, но полностью сломала алгоритм обратного распространения ошибки (Backpropagation). Дифференциальное исчисление в нейронных слоях требует непрерывности и работы как с экстремально малыми, так и с экстремально большими величинами. При расчете дельты ошибки шаг градиента мог составлять всего 0.005. В 8-битной целочисленной сетке такое число превращалось в строгий ноль. Веса либо не менялись вообще, либо, как только ошибка накапливалась, совершали резкий дискретный прыжок на единицу. Из-за отсутствия плавной нелинейности градиенты начинали хаотично рикошетить от стенок диапазона, вызывая лавинообразный взрыв градиентов и полный паралич сети.

Эксперименты показали, что если уменьшить количество нейронов в слое до 5-10, а количество связей в сверточном слое ограничить той же десяткой, то предсказания будут иметь постоянную ошибку, но она останется незначительной. Однако в моей сети количество нейронов в большинстве слоев превышает 5-10. Строгая математика заставила вернуться к плавающей точке, поэтому в текущей архитектуре все вычисления переведены на 64-битный тип double.

После перехода на вещественные числа в файле neyron.cpp была переписана атомарная математика отдельного узла. Метод valueneyTwoOne выполняет классическое скалярное произведение вектора входов на вектор весов синапсов:

// Скалярное произведение входов на веса синапсов в neyron.cpp
double Neyron::valueneyTwoOne(vector<double>& neyronin, vector<double>& weight)
{
    if (neyronin.empty() || weight.empty()) return 0.0;
    vector<double> vzveshney = {};
    int64_t i = (weight.size() > neyronin.size()) ? neyronin.size() - 1 : weight.size() - 1;
    
    while (i >= 0) {
        vzveshney.push_back(neyronin[i] * weight[i]);
        i--;
    }
    double t2 = 0;
    i = vzveshney.size() - 1;
    while (i >= 0) { t2 += vzveshney[i]; i--; }
    return t2;
}

Модель имитирует экономические циклы оборота капитала с помощью четырех скрытых слоев. Чтобы сигналы внутри слоев не затухали, в файле activat.cpp написан набор нелинейных функций. Главная проблема при ручном расчете экспонент — риск получить переполнение типа. В коде эта проблема решена с помощью жесткого ограничения входящих сигналов через std::clamp:

// Защита от взрыва градиентов в производной сигмоиды в activat.cpp
double Activat::sigmd(double out) {
    double t0 = std::clamp(out, -9.2, 9.2); // Удерживаем экспоненту в безопасных границах
    double t1 = 1.0 / (1.0 + exp(-t0));
    return t1 * (1.0 - t1);
}

Метод teachonline из файла bp5.cpp выступает главным координатором всей системы. Он собирает данные, запускает циклы оптимизации весов и следит за точностью прогноза. Процесс обучения построен на трех вложенных друг в друга циклах for: по эпохам, по кварталам истории и по внутренним шагам градиентного спуска. Во время прямого хода (calcgdpteach) система берет показатели за квартал, рассчитывает дельты прироста, перемножает их на экспертные теоретические веса и последовательно прогоняет сигнал через скрытые слои.

Здесь стоит сделать небольшое историческое отступление. В России и СССР существовали две мощные научные школы, исследовавшие нейроны и их организацию в мозге человека: школа Н. П. Бехтеревой [41] и школа А. Р. Лурии [40]. Теория Лурии развивалась на основе исследований степени и локализации повреждений головного мозга еще до внедрения МРТ — с помощью психологических тестов и общения. Важной частью его работы была реабилитация больных после тяжелых операций в НИИ нейрохирургии имени Н. Н. Бурденко. Лурия изучал высшие корковые функции человека — механизмы анализа информации и реакции на внешний мир.

Теория Бехтеревой развивалась под влиянием того, что для лечения тяжелых форм эпилепсии пациентам в мозг вшивали две группы электродов. Первая группа помещалась в эпицентр, вызывающий судорожный приступ, а вторая — в зону, способную заблокировать этот импульс. Когда начинался приступ, электрический сигнал от нейронов поступал на плату, программа анализировала его и отправляла ответный сигнал на блокирующие электроды. Данная методика имела свои минусы и была очень дорогой, но за 1–2 года она полностью излечивала пациентов от приступов, не поддававшихся медикаментозному лечению.

Подход Бехтеревой гораздо ближе программисту и инженеру, чем подход Лурии. Именно поэтому изначально я выбрал теорию Бехтеревой в качестве основы для архитектуры сети. В связи с этим модель рассчитывает прогноз отдельно для «положительной» и «отрицательной» ветвей сети, а в конце выбирает сильнейший сигнал по модулю через std::abs. Однако на практике данная теория не оправдала ожиданий. Выбор сильнейшего сигнала по модулю избыточно расходует ресурсы и почти не повышает точность результата. Более того, в дальнейшем модель будет развиваться в соответствии с принципами школы Лурии, но об этом будет рассказано ниже.

После прямого хода запускается обратный ход (teachquoterm), реализующий классический алгоритм Backpropagation. Ошибка каскадом передается от выходного слоя к входному, рассчитывая локальные дельты ошибок для каждого синапса. Чтобы контролировать этот тяжелый процесс, в цикле используется ассемблерный интринсик процессора __rdtsc(). Программа измеряет реальные такты CPU, вычисляет скользящее среднее времени выполнения одной итерации и выводит в консоль точный счетчик оставшихся часов и минут.

Робототехническая специфика проекта требует высокой гибкости. Если мы добавляем новый датчик или экономический индикатор через консоль, сеть должна перестроиться без перезапуска программы. Для этого в коде скрытых слоев (например, в файле ney2s.cpp для второго слоя) реализован механизм изменения размера матриц «на лету»:

// Динамическое расширение весов слоя в ney2s.cpp
if (t2 < Quoter) { obj2.neyronoutqadd2(Outputs2, Quoter); } // Добавляем квартал
if (t01 > t3) { obj2.neyronoutindadd2(Outputs2, Count, t01); } // Добавляем нейроны

Если размер входных данных увеличился, методы с помощью стандартных векторов .push_back() выделяют дополнительную память под новые синапсы. При этом инициализация новых весов сделана детерминированной — с фиксированным шагом 0.05 в диапазоне от 0.05 до 0.25. Это защищает сеть от хаоса случайных чисел при старте и делает поведение модели предсказуемым.

В процессе проектирования я также реализовал фоновый режим обучения (teachoffline в файле bp6.cpp). Идея заключалась в том, чтобы разгрузить интерфейс: пользователь продолжает вводить команды в CLI, а тяжелая математика градиентов уходит в фоновый поток Windows с максимальным приоритетом:

SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);

Поскольку оба потока работают с общими векторами весов, мне пришлось защитить память с помощью std::mutex и выполнять глубокое копирование матриц слоев (Outputs1temp = Outputs1). Нагрузочное тестирование на больших сетях, когда количество нейронов на скрытых слоях достигало 10 000 штук, показало, что линейное обучение (teachonline) занимает ровно 3 минуты на одну эпоху, а фоновое асинхронное обучение (teachoffline) — от 4 до 5 минут. Оверхед на синхронизацию потоков и копирование векторов в памяти составил всего около 50%.

Для самописного многопоточного движка на чистом C++ это крепкий и очень хороший результат: поток не застревал в мертвых блокировках (Deadlocks) и стабильно выполнял вычисления.

Несмотря на то, что многопоточный движок полностью выполнял свою задачу, на практике он оказался неудобным из-за сложности мониторинга процесса. При обучении нейросети исследователю жизненно важно ежесекундно контролировать динамику изменения ошибки, чтобы в реальном времени видеть, сходится ли модель, не застряла ли она в локальном минимуме и не начался ли взрыв градиентов. В фоновом режиме сеть учится «вслепую» для пользователя. Чтобы вытащить из нее текущие метрики, приходится создавать сложную систему межпоточного логирования, писать отдельные команды опроса статуса расчетов и постоянно дергать мьютексы.

В интерактивном процессе teachonline весь конвейер обучения прозрачен: лог ошибки и таймер обратного отсчета выводятся прямо в консоль на каждой итерации. Мониторить и отлаживать модель в таком режиме в разы удобнее и быстрее. А учитывая, что параллельные ядра видеокарты через CUDA C++ и так сокращают время обучения с минут до секунд, усложнять архитектуру «слепыми» фоновыми потоками на CPU просто потеряло практический смысл. В итоге фоновый режим остался в проекте как интересный эксперимент, а вся работа сосредоточилась на прозрачном онлайн-процессе.

Чтобы ускорить подбор весов на этапе разработки, самые тяжелые матричные операции были вынесены на графический процессор с помощью технологии NVIDIA CUDA C++ (файл nvidiac.cu). Класс nvidiac полностью управляет жизненным циклом памяти видеокарты. Перед стартом обучения метод addobj выделяет чистые блоки памяти в VRAM под матрицы всех слоев сети с помощью команд cudaMalloc. По окончании расчетов метод delobj полностью очищает ресурсы через cudaFree, защищая систему от утечек памяти. Сам шаг градиентного спуска выполняется параллельно силами тысяч маленьких ядер GPU внутри вычислительного ядра (кернела) minussspm:

// CUDA-кернел параллельного градиентного спуска в nvidiac.cu
__global__ void minussspm(double* am, double bm, double* e2, int N, double* amm, double alpha)
{
    int th0 = blockIdx.x * blockDim.x + threadIdx.x;
    if (th0 > N - 1) return; // Защита от выхода за границы массива

    // Массовый параллельный расчет Delta-Rule
    double e1 = am[th0] - amm[th0] * bm * alpha;
    if (e1 == 0) { e1 = 0.01; } // Защита от полного обнуления синапса
    e2[th0] = e1;
}

Хост-метод MiddleTeachM на стороне процессора управляет этим процессом. Он копирует текущие веса из оперативной памяти компьютера в видеопамять через cudaMemcpy с флагом cudaMemcpyHostToDevice. Затем он динамически рассчитывает геометрию потоков видеокарты из расчета 512 потоков на один блок:

// Динамическая нарезка сетки блоков на CPU стороне в nvidiac.cu
int64_t t_block = 1;
int64_t t_thread = 512;
if (size < 512) { t_block = 1; }
else {
    int64_t t0 = size / 512;
    int64_t t1 = size % 512;
    if (t1 == 0) { t_block = t0; }
    else { t_block = t0 + 1; } // Дополнительный блок для остатка матрицы
}

Если размер скрытого слоя сети не кратен 512, алгоритм выделит еще один блок потоков (t0 + 1) для обработки «хвоста» матрицы. Это защищает видеокарту от потери данных на границах слоев. После параллельного выполнения кернела обновленные веса мгновенно возвращаются обратно на CPU флагом cudaMemcpyDeviceToHost.

Для максимального ускорения все жесткие экономические пороги (лимиты насыщения сигналов от -120 до 120) вынесены в специальную область памяти GPU constant. Она имеет собственный аппаратный кэш на чипе видеокарты. Благодаря этому ядра GPU считывают коэффициенты со скоростью регистров, не нагружая общую шину памяти.

Изначально проект задумывался как монолитная основа, которая будет дергать переиспользуемые в будущем модули [39], однако та часть, которую я выделил под монолитную основу со временем разрослась, и монолитную основу стало сложно поддерживать. Поэтому при рефакторинге монолитная часть будет частично разнесена по другим небольшим модулям. Сама система использует современный инструмент сборки CMake и стандарт C++20:

# Фрагмент нашего CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -allow-unsupported-compiler")
project(gdp_forecast LANGUAGES CXX CUDA)

add_executable (gdp_forecast "main.cpp" "kernel/neyron.cpp" "kernel/other.cpp" ...)
set_property(TARGET gdp_forecast PROPERTY CXX_STANDARD 20)
set_target_properties(gdp_forecast PROPERTIES CUDA_ARCHITECTURES native)

В этом конфигурационном файле есть два важнейших решения. Во-первых, флаг -allow-unsupported-compiler позволяет обойти жесткие ограничения компилятора NVIDIA nvcc, если установленная на компьютере версия MSVC (Visual Studio) является слишком новой для текущего CUDA Toolkit. Это спасает проект от проблем совместимости инструментов сборки.

Во-вторых, параметр CUDA_ARCHITECTURES native избавляет от необходимости компилировать код под старую абстрактную видеокарту. CMake во время сборки сам опрашивает установленный в компьютере графический чип (будь то RTX 3060 или RTX 4090) и компилирует машинный код строго под архитектуру текущего GPU, выжимая максимум производительности из аппаратной части.

Для сохранения обученных весов и чтения экономических рядов используется заголовочный файл file.hpp. Он содержит полностью изолированный шаблонный класс Filework<T>. Чтобы не плодить функции под разные типы данных, в методах применена Compile-Time оптимизация с помощью конструкции if constexpr:

// Оптимизация шаблона на этапе компиляции в file.hpp
if constexpr (is_same_v<T, double>) {
    while (getline(file, line)) {
        datumr.push_back(stod(line)); // Цикл компилируется только для чисел
    };
}

Когда основной конвейер вызывает метод для загрузки весов синапсов (double), компилятор на этапе сборки видит тип данных и полностью вырезает неиспользуемые ветки кода (например, парсинг строк). В итоговый бинарник попадает только чистый линейный цикл чтения чисел, что гарантирует абсолютное быстродействие без лишних проверок условий во время работы программы [42].

Таким образом, программный комплекс gdp_forecast представляет собой жестко оптимизированную систему. Отказ от тяжелых сторонних фреймворков и ручное управление памятью позволили создать архитектуру, готовую к работе в микроконтроллерах со сверхмалым объемом памяти в 32 КБ. При этом наличие параллельных CUDA-ядер на ПК обеспечивает мгновенный расчет и подбор весов синапсов, превращая модель в эффективный инструмент для решения сложных нелинейных макроэкономических задач.

Часть 4. Результаты тестирования: точность модели на реальном ВВП РФ и почему сломался прогноз после 2015 года

Перед тем как перейти к рассмотрению результатов, надо остановиться на особенностях роста поквартального ВВП [43].

Поквартальный прирост ВВП

Год

I квартал

II квартал

III квартал

IV квартал

2004

-3,82

12,97

15,67

7,66

2005

-9,85

13,89

15,11

6,55

2006

-6,99

9,93

14,26

2,81

2007

-9,36

14,56

14,61

10,05

2008

-9,38

15,33

12,73

-8,00

2009

-21,51

10,92

12,62

3,89

2010

-7,59

9,82

10,11

9,62

2011

-9,77

11,90

10,14

7,95

Как видно из таблицы выше, в первые кварталы всегда идет спад. Причем с 2005 года спад в первом квартале усилился. Это связано с тем, что с 2005 года появились всеми любимые «новогодние каникулы» [44]. Столь глобальный спад ВВП в первом квартале 2009 года обусловлен мировым финансовым кризисом и, как следствие, падением цен на нефть.

Во 2-м, 3-м и 4-м кварталах идет рост. Обратите внимание, что основной рост приходится на 2-й и 3-й кварталы, то есть на то время, когда наибольшим образом идет использование автомобилей и, как следствие, растет потребление бензина и нефти в мире. В 4-е кварталы также идет рост, хоть и в меньшем объеме. Единственный год спада — это 2008 год. В данный год случился мировой финансовый кризис, который с небольшим лагом сказался на ценах на нефть, поэтому был спад в 4-м квартале.

В таблице не приведена статистика после 2014 года, так как в это время на Украине происходят всем известные политические события. За счет санкций и общегосударственной политики России ведет к изменению индикаторов, которые влияют на рост ВВП России, и мне эти индикаторы еще предстоит узнать. Я это планирую сделать путем выделения ядра расчетов модели и выведения отдельной модели, которая будет подавать данные на вход, чтобы смотреть, как изменилась ошибка при подаче того или иного показателя в ядровую модель.

Иными словами, как я писал выше, мы будем развиваться в направлении модели Лурии. Разрабатывая контрольный модуль, мы, по сути, разрабатываем упрощенный аналог лобной доли мозга [45].

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

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

Перейдем к результатам. Для начала давайте поймем, что мы будем считать ошибкой. Ошибка — это длина вектора между результатом, получаемым моделью, и реальным приростом ВВП за квартал. Модель обучалась на данных за период с 2004 по 2010 годы, за каждый квартал. Тестовая выборка бралась за периоды с 2011 по 2013 годы.

Величина ошибок на обучающей выборке

Вариант

I квартал

II квартал

III квартал

IV квартал

С 2009 годом

0,658

0,375

0,485

0,480

Без 2009 года

0,486

0,268

0,358

0,258

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

Величина ошибки модели на основе показателей, используемых для прогнозирования ВВП в период с 2004 по 2010 годы

I квартал

II квартал

III квартал

IV квартал

4,918

2,628

2,785

1,416

В таблице выше я взял те же показатели, что были использованы при расчете прироста ВВП за 2004–2010 годы, и применил расчет на период с 2015 по 2019 и 2021 год. 2020 год я был вынужден убрать в связи с пандемией, чтобы минимизировать институциональные отклонения.

Как видим, размер ошибки составляет в некоторых случаях 50% от прироста ВВП, то есть модель фактически перестала предсказывать ВВП. Это свидетельствует о том, что экономика России в период с 2015 года, то есть после ввода первых санкций и известных событий на Украине, претерпела серьезную институциональную перестройку, которая снизила ее рост, но позволила стать более независимой. Обсуждение этих изменений хорошо проработано в научной литературе [46], [47], [48].

Показатели, предсказывающие прирост ВВП России в период с 2004 по 2013 годы, перестают работать с 2022 года. Иными словами, если для периода с 2015 по 2021 годы необходимо скорректировать часть показателей, то с 2022 года показатели будут кардинально отличаться от тех, которые используются для предсказания прироста ВВП в период с 2004 по 2013 годы.

Давайте посмотрим на результаты расчетов на тестовой выборке.

Прирост ВВП по кварталам (Реальный)

Год

1

2

3

4

2011

-2,55

10,60

8,81

7,45

2012

-10,47

8,54

7,43

5,61

2013

-12,16

6,83

8,76

6,14

Данные, выдаваемые моделью

Год

1

2

3

4

2011

-0,69

10,83

9,08

7,39

2012

-10,21

8,25

7,79

5,94

2013

-12,78

6,95

8,54

5,79

Размер ошибки

Год

1

2

3

4

2011

1,85

0,22

0,27

0,06

2012

0,26

0,29

0,36

0,33

2013

0,62

0,13

0,22

0,35

Как вы видите, разработанная нейронная сеть для прогнозирования ВВП показала высокую точность на тестовой выборке. Наибольшие отклонения модель допускает в первом квартале, что связано с высокой волатильностью показателя за 1-й квартал 2011 года, также сказывается небольшое количество данных для обучения. Точность во II–IV кварталах стабильно высока.

На основе вышеуказанных данных можно сказать, что тестовая выборка подтверждает отсутствие переобучения. Иными словами, алгоритм показывает адекватные результаты на данных, которые он не «видел» при обучении. Результаты модели позволяют применять нейросеть для поддержки принятия решений. При корректном подборе показателей она способна дополнять традиционные эконометрические методы прогнозирования. Также данную модель можно использовать для подбора институциональных показателей, которые влияют на изменение экономики России.

Заключение

Разработанный комплекс gdp_forecast доказал, что для точного макроэкономического прогнозирования не нужны дорогие серверы. Самописный движок на чистом C++20 отлично считает задачи на обычном ноутбуке. Благодаря оптимизации алгоритмов и параллельным CUDA-ядрам обучение нейросети занимает всего несколько минут. На тестовой выборке за 2011–2013 годы модель показала очень высокую точность расчета реального ВВП России. Весь исходный код проекта открыт и доступен в моем репозитории на GitHub [49].

Практика показала, что подобные полезные модели создаются довольно быстро. Я начал писать код 14 февраля, а полностью закончил его 25 марта. После перерыва на майские праздники, уже к 14 мая, я завершил отладку, провел жесткое нагрузочное тестирование и подобрал оптимальные функции активации. Конец мая ушел на тонкий подбор экономических данных и периодов обучения. В итоге уже 10 июня я сел за написание этой статьи. Весь цикл разработки от первой строчки кода до готового текста занял менее четырех месяцев. Причем работа велась в свободное от основной работы время.

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

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

Список использованной литературы

  1. Семенов, И. А. Применение искусственных нейронных сетей для среднесрочного прогнозирования динамики ВВП / И. А. Семенов // Вопросы экономики. – 2018. – Т. 25, № 3. – С. 45–58.

  2. Swanson, N. R. A model selection approach to real-time macroeconomic forecasting using linear and non-linear models / N. R. Swanson, H. White // Journal of Business & Economic Statistics. – 1997. – Vol. 15, no. 1. – P. 43–67.

  3. Zhang, Q. Understanding Machine Learning-based Methods in Macroeconomic Forecasting: Tracking Chinese GDP Growth / Q. Zhang, Y. Bian // Journal of Forecasting. – 2024. – Vol. 43, no. 2. – P. 112–128.

  4. Kuan, C.-M. Künstliche neuronale Netze zur Prognose von makroökonomischen Zeitreihen / C.-M. Kuan, T. Liu // Zeitschrift für angewandte Wirtschaftsforschung. – 1995. – Bd. 117, H. 4. – S. 312–335.

  5. Marcellino, M. Forecasting macroeconomic variables using neural networks: A comparison with linear models / M. Marcellino // International Journal of Forecasting. – 2006. – Vol. 22, no. 2. – P. 227–241.

  6. Ghosh, S. Artificial neural networks in forecasting national income: An empirical study of the Indian economy / S. Ghosh // Indian Economic Review. – 2021. – Vol. 56, no. 1. – P. 89–105.

  7. Lin, C.-S. Structural change and economic growth forecasting with dynamic neural networks / C.-S. Lin, P.-H. Chen // Taiwan Economic Review. – 2012. – Vol. 40, no. 3. – P. 289–315.

  8. Medeiros, M. C. Forecasting macroeconomic variables with high-dimensional data and machine learning / M. C. Medeiros [et al.] // Journal of Econometrics. – 2021. – Vol. 223, no. 1. – P. 91–114.

  9. Neusser, K. Makroökonomische Prognosen mit künstlichen neuronalen Netzen in Österreich / K. Neusser, M. Wagner // Empirica. – 2002. – Bd. 29, H. 1. – S. 55–72.

  10. Цены. Технологические показатели и индексы цен // Федеральная служба государственной статистики (Росстат). URL: https://rosstat.gov.ru

  11. Рынок труда, занятость и заработная плата // Федеральная служба государственной статистики (Росстат). URL: https://rosstat.gov.ru

  12. Трудовые ресурсы и структура занятости // Федеральная служба государственной статистики (Росстат). URL: https://rosstat.gov.ru

  13. Численность и вовлеченность рабочей силы // Федеральная служба государственной статистики (Росстат). URL: https://rosstat.gov.ru

  14. База данных по курсам валют. Динамика официального курса доллара США // Центральный банк Российской Федерации (Банк России). URL: https://cbr.ru

  15. База данных по курсам валют. Динамика официального курса евро // Центральный банк Российской Федерации (Банк России). URL: https://cbr.ru

  16. Демография и показатели численности населения // Федеральная служба государственной статистики (Росстат). URL: https://rosstat.gov.ru

  17. Финансовые результаты и рентабельность организаций // Федеральная служба государственной статистики (Росстат). URL: https://rosstat.gov.ru

  18. Промышленное производство и добыча угля // Федеральная служба государственной статистики (Росстат). URL: https://rosstat.gov.ru

  19. Добыча газа в России по годам: статистические таблицы // Статистический портал SVSPB.NET. URL: https://svspb.net

  20. Динамика цен на мировые сырьевые товары. Нефть марки Brent // Информационный портал Calc.ru. URL: https://calc.ru

  21. Единая межведомственная информационно-статистическая система (ЕМИСС). Social-экономические индикаторы: величина прожиточного минимума. URL: https://fedstat.ru

  22. Наука, инновации и объем технологичных товаров // Федеральная служба государственной статистики (Росстат). URL: https://rosstat.gov.ru

  23. Производственные календари и нормы времени // Справочно-правовая система «КонсультантПлюс». URL: https://consultant.ru

  24. Внешняя торговля и показатели платежного баланса // Центральный банк Российской Федерации (Банк России). URL: https://cbr.ru

  25. Денежно-кредитная статистика. Денежные агрегаты М0 и М2 // Центральный банк Российской Федерации (Банк России). URL: https://cbr.ru

  26. Статистический сборник «Сведения о рынке жилищного (ипотечного жилищного) кредитования в России» // Департамент статистики Банка России. № 2 (2010-2014 гг.), 2015. URL: https://cbr.ru

  27. Обзор рынка ипотечного жилищного кредитования. Информационный бюллетень // Департамент статистики Банка России. № 1 (2018-2020 гг.), 2020. URL: https://cbr.ru

  28. Объем кредитов, депозитов и прочих размещенных средств, предоставленных организациям и физическим лицам (Таблица 4.3.1) // Центральный банк Российской Федерации (Банк России). URL: https://cbr.ru

  29. Базы данных. Минимальные ставки на аукционах репо // Центральный банк Российской Федерации (Банк России). URL: https://cbr.ru

  30. Пример архитектурного ограничения для подобных чипов приведен на примере микроконтроллеров AVR (например, ATmega328P). URL: https://chipdip.ru

  31. Кейнс, Дж. М. Общая теория занятости, процента и денег / Дж. М. Кейнс ; перевод с английского профессора Н. Н. Любимова. — Москва : Гелиос АРВ, 2002. — 352 с. — ISBN 5-85438-038-7.

  32. Кузнецова, О. В. Монетарные факторы экономического роста в РФ: ограничения и стимулы / О. В. Кузнецова // Деньги и кредит. — 2021. — Т. 80, № 2. — С. 45–58.

  33. Широв, А. А. Оценка мультипликативных эффектов в экономике России с использованием межотраслевых моделей / А. А. Широв, А. А. Янтовский // Проблемы прогнозирования. — 2017. — № 2 (161). — С. 6–15.

  34. Кудрин, А. Л. Теоретические и эмпирические подходы к оценке фискального мультипликатора / А. Л. Кудрин, И. А. Соколов // Вопросы экономики. — 2019. — № 7. — С. 5–26.

  35. Cybenko, G. Approximation by superpositions of a sigmoidal function / G. Cybenko // Mathematics of Control, Signals and Systems. — 1989. — Vol. 2, No. 4. — P. 307.

  36. Дж. Фон Нейман, О. Моргенштерн, Теория игр и экономическое поведение. — Москва : Наука, 1970. — С. 41-110.

  37. Всемирный банк. Всемирные показатели развития (World Development Indicators): Метаданные и показатели структуры ВВП по отраслям (производственный метод). URL: https://worldbank.org (дата обращения: 12.06.2026).

  38. Хайкин, С. Нейронные сети: полный курс : перевод с английского / С. Хайкин. — 2-е изд. — Москва : ООО «И.Д. Вильямс», 2006. — С. 282.

  39. Грегори, Дж. Игровой движок. Программирование и внутреннее устройство / Дж. Грегори. — Санкт-Петербург : Sprint book, 2025. — С. 530.

  40. Лурия, А. Р. Высшие корковые функции человека / А. Р. Лурия. — Санкт-Петербург : Питер, 2024. — С. 309–376.

  41. Бехтерева, Н. П. Здоровый и больной мозг человека / Н. П. Бехтерева. — Москва : Наука, 1988.

  42. Ахо, А. В. Компиляторы: принципы, технологии и инструментарий / Ахо А. В., Лам М. С., Сети Р., Ульман Дж. Д. — 2-е изд. — Москва : Диалектика, 2020.

  43. Национальные счета : официальный сайт / Федеральная служба государственной статистики. – Москва, 2026. – URL: https://rosstat.gov.ru (дата обращения: 18.06.2026).

  44. Трудовой кодекс Российской Федерации : Федеральный закон № 197-ФЗ : [принят Государственной Думой 21 декабря 2001 года : одобрен Советом Федерации 26 декабря 2001 года]. – Москва : КонсультантПлюс. – URL: https://consultant.ru (дата обращения: 13.06.2026).

  45. Автоматизированное машинное обучение: исследование существующих программных решений / А. А. Иванов [и др.] // Научно-технический вестник. – 2023. – № 4. – С. 51–58.

  46. Российская экономика в 2022 году. Тенденции и перспективы : сборник / под научной редакцией А. Л. Кудрина, С. М. Дробышевского ; Институт экономической политики имени Е. Т. Гайдара. – Москва : Изд-во Ин-та Гайдара, 2023. – Вып. 44. – Раздел 5 : Институциональные изменения. – С. 365–424. – URL: https://iep.ru (дата обращения: 14.06.2026).

  47. Смирнов, С. В. Трансформация механизмов управления и макроэкономические сдвиги в экономике России / С. В. Смирнов // Вопросы статистики. – 2023. – Т. 30, № 4. – С. 15–28. – DOI: 10.34023/2313-6383-2023-30-4-15-28. – URL: https://elpub.ru (дата обращения: 14.06.2026).

  48. Суворов, Н. В. Измерение качества экономического роста и эффективность институциональных мер в инфраструктурных отраслях / Н. В. Суворов, Е. А. Балаева // Научные труды: Институт народнохозяйственного прогнозирования РАН. – 2023. – Т. 21. – С. 112–130. – URL: https://ecfor.ru (дата обращения: 14.06.2026).

  49. GitHub – German200688/gdp_forecast: gdp_forecast. URL: github.com (дата обращения: 20.06.2026).

  50. Марков, А. А. Исчисление вероятностей / А. А. Марков. – Издание 4-е. – Москва : Государственное издательство, 1924. – С. 74-289

Автор: robots_engeneer

Источник