Нейросети в трейдинге: Адаптация прогноза при смене рыночного режима (OpenCL примитивы)
Введение
Прогнозная модель изучает рынок по исторической выборке. Она запоминает связи между ценой, волатильностью, объёмом и другими признаками. Эти связи со временем меняются. Иной становится глубина коррекций и распределение ликвидности. Участники по-другому реагируют на новости и процентные ожидания. Модель продолжает выдавать численно корректный прогноз, но его эффективность снижается.
Полное переобучение решает эту проблему лишь частично. Для нового режима сначала нужна достаточная выборка. Обучение занимает время и может разрушить знания о прежних состояниях рынка. Такой риск особенно заметен в торговых системах. Режимы здесь не только сменяются, но и возвращаются.
Авторы фреймворка OMPB предлагают сохранить базовую прогнозную модель. Поверх неё размещается компактная байесовская голова с затвором. Она корректирует базовый прогноз и ограничивает масштаб вмешательства. При закрытом затворе система использует исходный результат. Базовая модель при этом не изменяется.
Калибровка учитывает:
- риск на исходной области Source;
- расхождение между Source и целевой областью Target;
- сложность апостериорного распределения;
- штраф за его сдвиг.
Риск удерживает связь с уже изученным режимом. Расхождение показывает, как несколько реализаций posterior реагируют на свежие данные. Уровень сложности ограничивает масштаб адаптации. Вместе эти величины образуют вычислимую часть мартингальной PAC-Bayes оценки.
В оригинальном OMPB калибровка выполняется по протоколу Predict-Then-Update. Сначала строится прогноз. Затем становится известен фактический результат. Только после этого байесовская голова может обновиться. Текущий результат влияет только на будущие прогнозы и не влияет на уже принятое решение. Для тестирования торговой модели это обязательное условие.
Для многопериодного прогноза задержка особенно заметна. Модель может оценивать движение на несколько баров вперёд. Полная метка появится лишь после завершения горизонта. К этому моменту рынок уже способен перейти в другое состояние. OMPB использует входной Mismatch до появления новой метки. Поздний фактический результат добавляется отдельно и влияет только на следующие решения.
Сертификат OMPB строится на ограниченных прокси-величинах. Он не ограничивает напрямую MAE, MSE или просадку счёта. Сертификат регулирует калибровку. Прогноз и торговый результат проверяются отдельными метриками.
В предыдущей статье мы рассмотрели архитектуру OMPB и реализовали CNeuronBayesianLinearOCL. Слой хранит средние значения, логарифмы дисперсий для весов и смещений. Рабочий проход использует средние параметры и остаётся детерминированным. Во время обучения слой дополнительно создаёт несколько реализаций апостериорного распределения. Они нужны для оценки неопределённости, а не для случайного выбора торгового действия.
Одного байесовского слоя недостаточно. Его реализации нужно сравнить на Source и Target. Полученный градиент расхождения должен вернуться в параметры распределения. Отдельный расчёт оценивает KL-дивергенцию. Эти операции выполняются над крупными тензорами. Переносить их на CPU после каждого шага невыгодно.
В нашей библиотеке массовые вычисления выполняются на OpenCL-устройстве. MQL5 управляет объектами, буферами и очередью запуска. В этой статье мы разложим фреймворк OMPB на вычислительные операции. Готовые примитивы закроют уже поддерживаемые задачи. Для остальных добавим специализированные OpenCL-ядра.

1. Вычислительная декомпозиция OMPB
В авторском фреймворке фиксированный прогнозный блок получает окно w и строит базовый прогноз z(w). Байесовская голова добавляет остаточную поправку. Её влияние задаёт скалярный затвор s.
ỹ(w) = z(w) + s · [z(w)ΔWᵀ + Δb], s = σ(α) ∈ (0, 1)
Параметры ΔW и Δb принадлежат байесовской голове. При малом s и близкой к нулю поправке итог почти совпадает с базовым прогнозом. Весь глубокий прогнозный блок остаётся детерминированным. Вероятностными являются только параметры байесовской головы.
Байесовская голова остаётся компактной. Полное вероятностное описание глубокого энкодера потребовало бы распределения для миллионов параметров. KL-член стал бы большим и плохо интерпретируемым. Расчёт нескольких реализаций всей сети также заметно замедлил бы советник. Так сохраняется измеримая сложность. При закрытом затворе система использует базовый прогноз.
В нашей модели OMPB размещается внутри MarketEncoder. Он корректирует латентное состояние между RankTCM и ScenarioForecast. Такая адаптация меняет место применения фреймворка, но сохраняет его вычислительную логику. Есть фиксированная основа, байесовская поправка и ограничивающий затвор.
Пусть вход OMPB состоит из V латентных переменных. Каждая переменная представлена вектором размерности D.
H ∈ ℝV×D
Обычный проход использует результат по средним параметрам апостериорного распределения. Во время калибровки к нему добавляются случайные реализации. Так возникают два вычислительных контура: рабочий и вероятностный.
Рабочий контур остаётся детерминированным. Один и тот же вход даёт одинаковое латентное представление. Его формирует средний проход CNeuronBayesianLinearOCL. Ядро GateElementMult смешивает исходное состояние и байесовскую поправку. Обратный проход через затвор поддерживает GateElementMultGrad. Все необходимые операции для этой ветви уже есть в библиотеке.
Калибровочный контур использует две области. Source хранит опорные окна рынка. На них модель уже показала приемлемое поведение. Target содержит свежие окна. В оригинальном OMPB это строго предшествующие целевые окна, доступные до текущего прогноза.
В алгоритме данные разделены на Source (размеченный якорь) и Target (свежие, ещё не размеченные окна). Последний целевой пример добавляет запаздывающий обучающий сигнал после появления фактического результата.
На рынке такое разделение защищает от подглядывания вперёд. В окно Target можно включить только завершённые наблюдения. Будущие бары прогнозного горизонта туда не входят. Source тоже нельзя незаметно обновлять результатами текущего тестового участка. Иначе опорная область перестанет быть независимым якорем.
Для обеих областей используется одно апостериорное распределение Q. Из него формируются K наборов параметров. В нашей реализации один набор семплеров применяется к Source и Target. Это снижает дисперсию численной оценки. Разность результатов в меньшей степени зависит от нового случайного шума.
Общие случайные числа — инженерное решение. Авторская формула требует выборок из одного Q, но не предписывает повторять их. В OpenCL такой подход снижает шум и повторные вычисления.
Hₐₙᵥ(k) = f(Hₐₙᵥ; θ(k)), θ(k) ∼ Q, A ∈ {S, T}
где:
- A задаёт область;
- n указывает состояние;
- v выбирает латентную переменную;
- k обозначает реализацию апостериорного распределения.
История хранится как [V, N, D]. После добавления K реализаций получаем [K, VN, D], затем для расчёта расстояний приводим тензор к [VN, K, D]. Результат попарных сравнений имеет форму [V, N, K, K].
Эти перестановки выполняет CNeuronTransposeRCDOCL. Байесовский слой создаёт реализации. Метод CalcDistance строит матрицы расстояний. Новому коду остаётся свести эти матрицы к Mismatch и сформировать обратный градиент.
OMPB сравнивает не цены Source и Target, а реакцию одного апостериорного распределения на два набора входов. Если реализации Q дают на свежем рынке более разные результаты, неопределённость коррекции растёт.
В авторской работе расстояние между двумя прогнозами ограничивается. В знаменателе стоят горизонт прогноза H, число целевых каналов C и масштаб τ. Такая нормировка превращает сырой квадрат нормы в среднее расхождение прогноза.
d̃τ(h, h′; w) = min{1, ‖h(w) − h′(w)‖₂² / (HCτ²)} ∈ [0, 1]
Порог τ выбирается по Source до адаптации. Авторы используют статистику парных расхождений на опорных окнах. В базовой настройке берётся медиана среднеквадратичного расхождения. Порог не подстраивается по каждому новому окну Target. Иначе сильный сдвиг сам расширил бы допустимый диапазон и ослабил сигнал.
После нормировки рассчитывается среднее по неупорядоченным парам Q:
D(Q, w) = [2/(K(K − 1))] · ∑1 ≤ k < k′ ≤ K d̃τ(h(k), h(k′); w).
При K = 5 получаем десять пар. Рост K снижает шум Монте-Карло, но увеличивает число проходов и матриц расстояний.
В латентной реализации каждая переменная обрабатывается отдельно. Размерность D входит в масштаб полного вектора. Рабочий порог τ задаётся для D-мерного расстояния. Вычислительное ядро получает готовое значение 1/τ².
Для K реализаций число уникальных пар равно:
P = K(K − 1)/2.
Матрица расстояний симметрична. Диагональ равна нулю. Достаточно обработать элементы выше диагонали. Для каждой области и переменной рассчитывается среднее по состояниям и парам.
DA,v = [1/(NAP)] · ∑n=1NA ∑i<j ρA,n,v(i,j)
Авторский Mismatch в момент t — это модуль разности среднего Disagreement на Source и среднего Disagreement на доступных окнах Target.
Mismatcht = |DS(Q) − DT(Q)|
В нашей латентной реализации сначала сохраняется знаковая разность для каждой переменной:
Δᵥ = DS,v − DT,v.
Управляющий код усредняет модули:
M = (1/V) · ∑v=1V |Δᵥ|.
Это координатное расширение авторского скалярного Mismatch. Оно сохраняет знак для обратного прохода и не допускает компенсации между латентными переменными. Рост расхождения по одному каналу не скрывается снижением по другому.
Показатель Mismatch остаётся ограниченным прокси-сигналом. Он не заменяет MAE, MSE или финансовый результат. В оригинальном OMPB этот сигнал входит в вычислимую часть сертификата вместе с риском Source и мартингальным PAC-Bayes-членом сложности.
Низкий Disagreement тоже требует осторожной трактовки. Если Q схлопнулось к почти детерминированному набору параметров, все реализации будут близки при любом рынке. Mismatch тогда станет малым, но это не докажет устойчивость. Байесовский слой должен сохранять осмысленное разнообразие. Его проверка относится к калибровке верхнего уровня.
Знак Δᵥ не является самостоятельным торговым сигналом. Отрицательное значение не означает продажу, а положительное не означает покупку. Знак нужен только для производной. Торговое направление формирует последующая прогнозная модель.
PB̂γ(t) = R̂S,t(Q) + Γₘsubγ(Q, P, δ) + ½Mismatcht
Остаточный член, зависящий от недоступных меток Target, не включается в онлайн-суррогат. Фактическая ошибка подключается после появления результата. Это сохраняет причинный порядок Predict-Then-Update.
Одного Mismatch мало. Байесовская голова может уменьшить расхождение за счёт слишком широкого распределения или сильного сдвига средних. На коротком участке это выглядит как быстрая адаптация. После возврата прежнего режима такая поправка становится источником новой ошибки.
В авторской схеме для вектора параметров θ = vec(ΔW, Δb) задаётся изотропное априорное распределение. Апостериорное распределение остаётся диагональным гауссовским. Здесь Nθ обозначает число параметров байесовской головы.
P(θ) = 𝒩(0, σ₀²I), Q(θ) = 𝒩(μ, diag(σ²))
KL(Q ‖ P) = ½ · ∑i=1Nθ [(σᵢ² + μᵢ²)/σ₀² − 1 − ln(σᵢ²/σ₀²)]
Этот KL входит в мартингальный член Γsubγ. Полный член также использует оценку предсказуемой дисперсии и масштаб потерь на Source. Отдельное значение KL нельзя считать готовым PAC-Bayes сертификатом.
Авторский алгоритм также ограничивает сдвиг текущего апостериорного распределения относительно предыдущего. Этот штраф относится к управляющему контуру онлайн-калибровки.
Риск Source выполняет другую функцию. Он проверяет качество среднего предиктора на размеченном опорном наборе. KL не может заменить этот якорь. Малое отклонение параметров ещё не гарантирует сохранение прогноза. По этой причине полный критерий OMPB объединяет статистический риск, Mismatch и сложность, а не выбирает один из них.
Мартингальный член Γsubγ также использует оценку предсказуемой изменчивости потерь. Она рассчитывается по остаткам Source. При росте шума допустимая неопределённость сертификата меняется.
В нашей библиотеке вероятностный семплер использует стандартное нормальное априорное распределение. Для него σ₀ = 1. В буферах хранится ℓᵢ = ln σᵢ². Поэлементная формула упрощается:
KLᵢ = ½(eℓᵢ + μᵢ² − 1 − ℓᵢ).
Обратные производные уже поддерживает механизм VAE:
∂KLᵢ/∂μᵢ = μᵢ, ∂KLᵢ/∂ℓᵢ = ½(eℓᵢ − 1).
Новое ядро нужно для прямой поэлементной оценки KL. Она используется в диагностике и в контроле допустимого отклонения апостериорного распределения.
Большую часть контура собираем из готовых операций. Новые ядра нужны только там, где общая цепочка породила бы лишние буферы и повторные запуски.
| Задача | Средство | Состояние |
|---|---|---|
| Средний и K случайных проходов | CNeuronBayesianLinearOCL | Готово |
| Перестановка измерений | CNeuronTransposeRCDOCL | Готово |
| Попарные квадраты расстояний | CalcDistance | Точечное расширение |
| Остаточное смешивание | GateElementMult | Готово |
| Ограничение одной пары | OMPBScaledDistance | Новая функция |
| Разность Source–Target Disagreement | OMPBDisagreement | Новое ядро |
| Градиент Mismatch | OMPBDisagreementGrad | Новое ядро |
| Поэлементная KL-дивергенция | OMPBGaussianKL | Новое ядро |
Точечные изменения CalcDistance, матричных операций и режима семплирования остаются за рамками статьи. Они лишь расширяют общие контракты библиотеки. Далее разберём новые алгоритмы.
2. Реализация специализированных OpenCL-ядер
На рынке сильное движение может появиться за один бар. Новостной импульс или краткое исчезновение ликвидности резко увеличивает расстояние между реализациями Q. Без ограничения одна пара начинает определять весь Mismatch. OMPB обрезает её вклад на уровне единицы.
Прямой и обратный проходы должны использовать одинаковое правило. Иначе функция потерь и её градиент будут описывать разные задачи. Общую операцию выполняет OMPBScaledDistance.
//+------------------------------------------------------------------+ //| Scales one OMPB pair distance and applies the bounded loss. | //+------------------------------------------------------------------+ inline float OMPBScaledDistance(const float raw_distance, const float inv_tau2) { if(isnan(raw_distance) || isinf(raw_distance) || raw_distance < 0.0f || isnan(inv_tau2) || isinf(inv_tau2) || inv_tau2 <= 0.0f) return 1.0f;
Аргумент raw_distance содержит квадрат расстояния. Он должен быть конечным и неотрицательным. Отрицательное значение служит признаком ошибки, полученным от расширенного CalcDistance. Аргумент inv_tau2 хранит 1/τ². Его рассчитывает управляющий код до запуска ядра.
При повреждённом входе функция возвращает единицу. Пара получает максимальный штраф. В обратном проходе она считается насыщенной и не передаёт градиент. Нулевая подмена здесь недопустима. Она означала бы полное согласие прогнозов.
const float scaled = raw_distance * inv_tau2; if(isnan(scaled) || isinf(scaled)) return 1.0f; return fmin(scaled, 1.0f); }
Для корректной пары остаются две операции. Расстояние умножается на 1/τ². Функция fmin ограничивает результат единицей. Деление вынесено из внутренних циклов. Это уменьшает число повторных операций при обработке многих состояний и пар.
ρ(d) = min{d/τ², 1}
Пока d меньше τ², функция сохраняет информацию о величине расхождения. После порога она перестаёт расти. Система замечает выброс, но не подстраивает весь байесовский слой под один экстремальный эпизод.
Выбор τ связан с масштабом признаков. Слишком малый порог быстро насытит почти все пары. Градиент Mismatch исчезнет. Слишком большой порог сделает оценку чувствительной к отдельным скачкам. Порог τ фиксируется по Source и сохраняется вместе с конфигурацией калибровки.
К моменту запуска OMPBDisagreement матрицы расстояний уже находятся на OpenCL-устройстве. Буфер reference содержит Source. Буфер current содержит Target. Один рабочий элемент обрабатывает одну латентную переменную.
//+------------------------------------------------------------------+ //| OMPB regularizers: elementwise reductions only | //+------------------------------------------------------------------+ __kernel void OMPBDisagreement(__global const float *reference, __global const float *current, __global float *output, const int source_states, const int target_states, const int samples, const float inv_tau2) { const int variable = (int)get_global_id(0); if(variable >= 0 && source_states > 0 && target_states > 0 && samples > 1) { const int pairs = samples * (samples - 1) / 2; float source_sum = 0.0f; float target_sum = 0.0f;
Первые три аргумента задают входные матрицы и выходной вектор. Остальные передают размеры Source и Target, число реализаций K и коэффициент 1/τ². Проверка samples > 1 исключает бессмысленный случай без пар.
Число пар вычисляется один раз. Переменные source_sum и target_sum накапливают ограниченные расстояния двух областей. Обработка начинается с Source.
for(int state = 0; state < source_states; state++) { const int base = (variable * source_states + state) * samples * samples; for(int left = 0; left < samples; left++) for(int right = left + 1; right < samples; right++) { //--- Scale by 1/tau^2 and clip at one. //--- A negative raw value is the CalcDistance //--- sentinel for a non-finite pair. const float raw_distance = reference[base + left * samples + right]; source_sum += OMPBScaledDistance(raw_distance, inv_tau2); } }
Внешний цикл перебирает опорные состояния. Для каждого состояния рассчитывается начало матрицы K×K. Внутренние циклы читают только верхний треугольник. Пары (i,j) и (j,i) несут одну информацию, поэтому повторный проход не нужен.
Каждое значение проходит через OMPBScaledDistance. Source задаёт привычный уровень чувствительности модели. Это может быть спокойный рынок, трендовый участок или смешанный набор режимов. Главное условие — модель уже показала на этих состояниях приемлемое качество.
Target обрабатывается тем же алгоритмом.
for(int state = 0; state < target_states; state++) { const int base = (variable * target_states + state) * samples * samples; for(int left = 0; left < samples; left++) for(int right = left + 1; right < samples; right++) { const float raw_distance = current[base + left * samples + right]; target_sum += OMPBScaledDistance(raw_distance, inv_tau2); } }
Размеры Source и Target могут различаться. Одинаковое число состояний не требуется. Каждая сумма делится на собственный объём выборки. Смена длины текущего окна сама по себе не меняет масштаб оценки.
const float source_mean = source_sum / (float)(source_states * pairs); const float target_mean = target_sum / (float)(target_states * pairs); output[variable] = IsNaNOrInf(source_mean - target_mean, 0.0f); } }
В конце ядро рассчитывает средние DS,v и DT,v. В выход output записывается Δᵥ = DS,v − DT,v. Знак сохраняется для обратного прохода. Сам Mismatch управляющий код получает как среднее |Δᵥ|.
Отрицательное Δᵥ означает более высокий Disagreement на Target. Положительное значение означает его снижение. Оба случая указывают на изменение поведения апостериорного распределения. Функция потерь учитывает модуль Δᵥ.
Прямое усреднение распараллелено по переменным. Внутри одной переменной состояния и пары обрабатываются последовательно. Такой вариант прост и не требует буфера частичных сумм. Для небольшого K он приемлем. При крупных буферах усреднение можно распределить между элементами рабочей группы, но это уже задача оптимизации.
Выход ядра имеет размер V. На CPU передаётся только диагностический вектор. Матрицы остаются на устройстве и используются обратным проходом.
Mismatch фиксирует факт изменения. Для обучения требуется передать производную к параметрам Q во все реализации, участвовавшие в расчёте.
Для одной переменной действует Δᵥ = DS,v − DT,v. Производная |Δᵥ| равна sgn(Δᵥ). У Source знак равен +1, у Target — −1. Одно ядро обслуживает обе области. Их различает аргумент source_sign.
∂|Δᵥ|/∂Δᵥ = sgn(Δᵥ), ∂Δᵥ/∂DS,v = 1, ∂Δᵥ/∂DT,v = −1
Ядро OMPBDisagreementGrad запускается в трёх измерениях. Первая координата объединяет переменную и состояние. Вторая выбирает реализацию Q. Третья выбирает координату латентного вектора.
//+------------------------------------------------------------------+ //| Computes the OMPB disagreement grad gradient contributions on the| //| GPU. | //+------------------------------------------------------------------+ __kernel void OMPBDisagreementGrad( __global const float *corrections, __global const float *distance, __global const float *disagreement, __global float *gradient, const int states, const int variables, const int samples, const int dimension, const int source_sign, const float multiplier, const float inv_tau2) { const int group = (int)get_global_id(0); const int sample = (int)get_global_id(1); const int coordinate = (int)get_global_id(2);
Один рабочий элемент рассчитывает один элемент градиента. Такая раскладка даёт значительно больше параллельных задач, чем прямое усреднение. Она хорошо подходит для обратного прохода по большому тензору [VN, K, D].
Перед чтением буферов проверяются размеры и реальные границы запуска.
//--- All buffers are indexed as //--- [group, sample, coordinate] with fixed extents. if(states <= 0 || variables <= 0 || samples <= 1 || dimension <= 0 || group < 0 || sample < 0 || coordinate < 0 || group >= states * variables || sample >= samples || coordinate >= dimension || isnan(inv_tau2) || isinf(inv_tau2) || inv_tau2 <= 0.0f) return;
OpenCL может округлить глобальный размер до рабочей группы. Лишние элементы должны завершиться до обращения к памяти. Проверка также отбрасывает нулевое и нечисловое 1/τ².
После проверки восстанавливаются адреса текущей матрицы расстояний и текущей координаты.
const int variable = group / states; const int pairs = samples * (samples - 1) / 2; const int distance_base = group * samples * samples; const int correction_base = (group * samples + sample) * dimension + coordinate; const float delta = IsNaNOrInf(disagreement[variable], 0.0f);
Значение group объединяет переменную и состояние. Деление group / states возвращает номер переменной. Индекс distance_base указывает начало матрицы K×K, а correction_base указывает координату одной реализации.
Вектор disagreement хранит Δᵥ. По нему выбирается направление градиента.
//--- delta = D_S - D_T; //--- its sign drives the pair direction. const float sign_delta = (delta > 0.0f ? 1.0f : (delta < 0.0f ? -1.0f : 0.0f)); float value = 0.0f; const float correction = IsNaNOrInf(corrections[correction_base], 0.0f);
При Δᵥ = 0 используется нулевой субградиент. В остальных случаях sign_delta принимает +1 или −1. Значение текущей координаты читается один раз перед циклом по другим реализациям.
//--- Saturated pair-distance terms //--- do not contribute to gradient. for(int other = 0; other < samples; other++) { if(other == sample) continue; const int didx = distance_base + sample * samples + other; const float pair_distance = OMPBScaledDistance(distance[didx], inv_tau2); if(pair_distance >= 1.0f) continue;
Реализация не сравнивается сама с собой. Для остальных пар снова вызывается OMPBScaledDistance. Это сохраняет один порог в прямом и обратном проходах. Насыщенная пара не меняет функцию потерь. Её производная равна нулю.
Для ненасыщенной пары используется производная квадрата евклидова расстояния.
const int other_index = (group * samples + other) * dimension + coordinate; value += 2.0f * (correction - IsNaNOrInf(corrections[other_index], 0.0f)); }
∂‖yᵢ − yⱼ‖₂²/∂yᵢ = 2(yᵢ − yⱼ)
Рабочий элемент суммирует вклад всех пар с текущей реализацией. К сумме применяются вес регуляризатора, знак области и нормировка.
value *= multiplier * (float)source_sign * sign_delta * inv_tau2 / (float)(states * pairs); gradient[correction_base] += clamp(IsNaNOrInf(value, 0.0f), -MAX_GRAD, MAX_GRAD); }
Множитель source_sign равен +1 для Source и −1 для Target. Значение sign_delta передаёт производную модуля. Коэффициент inv_tau2 появляется из ρ(d). Деление на states * pairs формирует среднее по состояниям и парам.
Среднее по V переменным и вес Mismatch входят во внешний multiplier. Оптимизатор в нашей библиотеке прибавляет содержимое градиентного буфера. В multiplier передаётся отрицательная производная:
multiplier = −λₘ/V.
Градиент добавляется. Это сохраняет сигналы основной ошибки прогноза и других регуляризаторов. Итог ограничивается общим пределом MAX_GRAD. При этом ограничение применяется к производной, а не к расстоянию.
Рассмотрим случай DT,v > DS,v. Тогда Δᵥ отрицательно. Градиент стремится сблизить средние уровни Disagreement двух областей. Сам по себе он не выбирает, какой уровень считать правильным. Эту неоднозначность снимает риск Source. Он удерживает опорное поведение и не позволяет исправлять Target ценой разрушения Source.
Порог τ ограничивает Disagreement. А MAX_GRAD ограничивает уже рассчитанную производную. Смешивать эти величины нельзя.
С точки зрения торговой задачи этот градиент не пытается угадать следующий бар сам по себе. Он возвращает поведение байесовской головы к устойчивому уровню. Фактическая ошибка прогноза задаёт направление полезной коррекции. Mismatch отвечает за её согласованность между режимами.
Следующий примитив контролирует масштаб изменения Q. Для нормальной априорной модели один параметр описывается средним μᵢ и логарифмом дисперсии ℓᵢ. Его вклад равен KLᵢ = ½(eℓᵢ + μᵢ² − 1 − ℓᵢ). Все параметры независимы, поэтому один рабочий элемент обрабатывает одну пару значений.
//+------------------------------------------------------------------+ //| Implements the OMPB gaussian kl kernel on the GPU. | //+------------------------------------------------------------------+ __kernel void OMPBGaussianKL(__global const float *mean, __global const float *logvar, __global float *output, const int total) { const int id = (int)get_global_id(0); if(id < total) { const float mu = mean[id]; const float raw_log_var = logvar[id];
Аргументы mean и logvar содержат параметры Q. Буфер output получает отдельный вклад KL. Аргумент total ограничивает число рабочих элементов.
Перед вычислением ядро проверяет входные значения.
if(isnan(mu) || isinf(mu) || isnan(raw_log_var) || isinf(raw_log_var)) { //--- A valid KL contribution is non-negative. //--- The negative sentinel forces the //--- host-side safety route instead of //--- hiding corruption. output[id] = -1.0f; return; }
Корректная KL-дивергенция не бывает отрицательной. Значение −1 служит признаком повреждённого Q. Управляющий код обнаруживает его и переводит слой на Identity-маршрут без обновления параметров. Подмена NaN нулём превратила бы ошибку в KL = 0 и создала видимость идеального априорного состояния.
Для конечных значений логарифм дисперсии ограничивается численно безопасным диапазоном.
const float lv = clamp(raw_log_var, -30.0f, 30.0f); //--- expm1(lv) = exp(lv) - 1 //--- avoids cancellation for small lv. const float kl = 0.5f * (expm1(lv) - lv + mu * mu);
Функция expm1 вычисляет eˣ−1 без заметной потери точности около нуля. Обычная разность exp(x)−1 вычитает близкие числа и хуже работает в формате float. Ограничение [−30, 30] согласовано с семплированием и KL-градиентом вероятностного слоя.
output[id] = (isnan(kl) || isinf(kl) ? -1.0f : fmax(kl, 0.0f)); } }
Округление иногда даёт малое отрицательное число. Функция fmax возвращает его к нулю. NaN и бесконечность снова заменяются служебным значением −1.
Ядро не рассчитывает весь Γsubγ и не формирует сертификат. Оно создаёт массив [KL₁, KL₂, …, KLₙ]. Управляющий уровень сводит его к общей оценке и проверяет допустимый предел. Производные KL уже добавляет механизм VAE. Отдельное OpenCL-ядро обратного прохода для этой формулы не требуется.
В авторской работе масштаб априорного распределения задаётся σ₀. Наше ядро реализует частный случай σ₀ = 1. Это соответствует стандартному нормальному априорному распределению вероятностного семплера. Иной σ₀ потребовал бы предварительной нормировки μ и σ либо дополнительных аргументов ядра.
Обе ветви объединяются в общем контуре обучения. Source и Target проходят через одинаковые K реализаций Q. CalcDistance строит попарные расстояния. OMPBDisagreement получает Δᵥ. OMPBDisagreementGrad возвращает производную Mismatch в стохастические выходы байесовского слоя.
Параллельно OMPBGaussianKL оценивает сложность Q относительно стандартного нормального априорного распределения. Значение KL ограничивает масштаб коррекции. Mismatch контролирует изменение реакции модели на свежие рыночные состояния.
Крупные тензоры реализаций, расстояний и градиентов остаются на OpenCL-устройстве. MQL5 управляет очередью и читает отдельные диагностические показатели. Здесь точнее говорить о GPU-резидентных тензорных операциях, а не о полном отказе от CPU.
Библиотека получила вычислительные примитивы для ограниченного расхождения, его обратного градиента и базовой KL-оценки. Следующий шаг — объединить их с буферами Source и Target, затвором и режимами работы внутри CNeuronOMPBOCL.
Заключение
Байесовский слой должен не только создавать несколько вариантов коррекции, но и сравнивать их поведение на знакомом и свежем рынке. Сигнал расхождения требуется передать обратно в параметры. Масштаб изменения апостериорного распределения также должен оставаться под контролем.
Авторский OMPB решает эту задачу, объединяя ограниченный Disagreement и Source–Target Mismatch с мартингальным PAC-Bayes-членом сложности. Эти величины дополняют ошибку прогноза. Они не заменяют MAE, MSE или финансовый результат. Их роль состоит в другом. Они показывают устойчивость вероятностной коррекции и цену её изменения.
Мы перенесли эту логику на вычислительный уровень нашей библиотеки. Готовые средства выполняют семплирование, матричные операции, перестановку тензоров и расчёт попарных расстояний. Общие примитивы получили несколько необходимых режимов. Их код не дублировался в новых объектах.
Для недостающих операций добавлены три специализированных OpenCL-примитива. Функция OMPBScaledDistance ограничивает вклад одной пары и задаёт общий контракт прямого и обратного проходов. Ядро OMPBDisagreement сравнивает Source и Target. Ядро OMPBDisagreementGrad передаёт градиент Mismatch во все реализации Q. Ядро OMPBGaussianKL рассчитывает поэлементный KL относительно стандартного нормального априорного распределения.
Код учитывает насыщение расстояния, разные размеры областей, число уникальных пар и знак Source или Target. Градиент добавляется к уже накопленным значениям. Нечисловые данные не маскируются нулевым штрафом. Повреждённая пара получает максимальный вклад и не передаёт градиент.
Основные тензорные операции выполняются на OpenCL-устройстве. Это снижает объём обмена с CPU при росте числа состояний и реализаций Q. Управляющий код MQL5 сохраняет контроль над режимами, очередью вычислений и диагностикой.
В результате библиотека получила параллельную вычислительную основу OMPB. Она связывает байесовский слой из первой статьи с Source–Target Mismatch и KL-контролем. В следующей статье эти операции будут объединены в объекте верхнего уровня.
Ссылки
- Model-Agnostic Online Certificate-Driven Calibration for Time Series Forecasting Under Distribution Shift
- Нейросети в трейдинге: Адаптация прогноза при смене рыночного режима (OMPB)
- Другие статьи серии
Программы, используемые в статье
| # | Имя | Тип | Описание |
|---|---|---|---|
| 1 | Study.mq5 | Советник | Советник офлайн-обучения моделей |
| 2 | StudyOnline.mq5 | Советник | Советник онлайн-обучения моделей |
| 3 | StudyForecast.mq5 | Советник | Советник обучения MarketEncoder |
| 4 | Test.mq5 | Советник | Советник тестирования модели |
| 5 | Trajectory.mqh | Библиотека класса | Описание состояния среды и архитектуры моделей |
| 6 | NeuroNet.mqh | Библиотека класса | Библиотека классов нейронной сети |
| 7 | NeuroNet.cl | Библиотека | Код OpenCL-программы |
Проект представлен на forge.mql5.io/dng.
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
От начального до среднего уровня: Перегрузка операторов (V)
Разработка индикатора Volume Bubble на MQL5 с использованием стандартного отклонения
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования