English 中文 Español Deutsch 日本語 Português
preview
Машинное обучение и Data Science (Часть 11): Наивный байесовский классификатор и теория вероятностей в трейдинге

Машинное обучение и Data Science (Часть 11): Наивный байесовский классификатор и теория вероятностей в трейдинге

MetaTrader 5Торговые системы | 13 апреля 2023, 15:15
1 727 4
Omega J Msigwa
Omega J Msigwa

Четвертый закон термодинамики: если вероятность успеха не близка к единице, она близка к нулю.

А. Кондрашов

Введение

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

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


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

Подготовка обучающих данных

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

Пойдем по порядку:

matrix Matrix(TrainBars, 6);
int handles[5];

double buffer[];
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Preparing Data
   handles[0] = iBearsPower(Symbol(),TF, bears_period);
   handles[1] = iBullsPower(Symbol(),TF, bulls_period);
   handles[2] = iRSI(Symbol(),TF,rsi_period, rsi_price);
   handles[3] = iVolumes(Symbol(),TF,VOLUME_TICK);
   handles[4] = iMFI(Symbol(),TF,mfi_period,VOLUME_TICK );

//---   
   vector col_v;   
   for (ulong i=0; i<5; i++) //Независимые переменные
     { 
       CopyBuffer(handles[i],0,0,TrainBars, buffer);
       col_v = matrix_utils.ArrayToVector(buffer);
       
       Matrix.Col(col_v, i);
     }     
//-- Целевые переменные
   vector open, close;
   col_v.Resize(TrainBars);

   close.CopyRates(Symbol(),TF, COPY_RATES_CLOSE,0,TrainBars);
   open.CopyRates(Symbol(),TF, COPY_RATES_OPEN,0,TrainBars);

   for (int i=0; i<TrainBars; i++)
      {
         if (close[i] > open[i]) //price went up
            col_v[i] = 1;
         else 
            col_v[i] = 0;
      }
   
   Matrix.Col(col_v, 5); //Добавляем независимую переменную в последний столбец матрицы   
//---

Переменные TF, bears_period и прочие являются входными определенными переменными, они определены в верхней части кода выше.

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

CS      0       13:21:15.457    Naive Bayes Test (EURUSD,H1)    "Bears"           "Bulls"        "Rsi"      "Volumes" "MFI"     "Target Var"
CS      0       13:21:15.457    Naive Bayes Test (EURUSD,H1)    [-3.753148029472797e-06,0.008786246851970603,67.65238281791684,13489,55.24611392389958,0]
CS      0       13:21:15.457    Naive Bayes Test (EURUSD,H1)    [-0.002513216984025402,0.005616783015974569,50.29835423473968,12226,49.47293811405203,1]
CS      0       13:21:15.457    Naive Bayes Test (EURUSD,H1)    [-0.001829900272021678,0.0009700997279782353,47.33479153312328,7192,46.84320886771249,1]
CS      0       13:21:15.457    Naive Bayes Test (EURUSD,H1)    [-0.004718485947447171,-0.0001584859474472733,39.04848493977027,6267,44.61564654651691,1]
CS      0       13:21:15.457    Naive Bayes Test (EURUSD,H1)    [-0.004517273669240485,-0.001367273669240276,45.4127802340401,3867,47.8438816641815,0]

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

График распределения индикатора Bears График распределения индикатора Bulls Нормальное распределение rsi Нормальное распределение mfi Нормальное распределение Volumes

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

Посмотрим внимательнее на матрицу коэффициентов корреляции всех независимых переменных:

   string header[5] = {"Bears","Bulls","Rsi","Volumes","MFI"};

   matrix vars_matrix = Matrix; //Только независимые переменные
   matrix_utils.RemoveCol(vars_matrix, 5); //Удаляем целевую переменную
   
   ArrayPrint(header);
   Print(vars_matrix.CorrCoef(false));

Выводимая информация:

CS      0       13:21:15.481    Naive Bayes Test (EURUSD,H1)    "Bears"   "Bulls"   "Rsi"     "Volumes" "MFI"    
CS      0       13:21:15.481    Naive Bayes Test (EURUSD,H1)    [[1,0.7784600081627714,0.8201955846987788,-0.2874457184671095,0.6211980865273238]
CS      0       13:21:15.481    Naive Bayes Test (EURUSD,H1)     [0.7784600081627714,1,0.8257210032763984,0.2650418244580489,0.6554288778228361]
CS      0       13:21:15.481    Naive Bayes Test (EURUSD,H1)     [0.8201955846987788,0.8257210032763984,1,-0.01205084357067248,0.7578863565293196]
CS      0       13:21:15.481    Naive Bayes Test (EURUSD,H1)     [-0.2874457184671095,0.2650418244580489,-0.01205084357067248,1,0.0531475992791923]
CS      0       13:21:15.481    Naive Bayes Test (EURUSD,H1)     [0.6211980865273238,0.6554288778228361,0.7578863565293196,0.0531475992791923,1]]

Можно заметить, что за исключением корреляции объемов с остальными, все переменные сильно коррелируют друг с другом. В некоторых случаях корреляция очень сильная, например, между RSI и Bulls и Bears корреляция составила около 82%. В основе индикаторов Объемов и MFI есть общие части — а именно, сами объемы, поэтому их корреляция в 62% совершенно объяснима. Поскольку гауссовский наивный байесовский метод не вдается в подробности таких причин, давайте двигаться дальше. Я просто подумал, что неплохо бы проверить и проанализировать переменные.

Обучение модели

Обучение для гауссовского наивного байесовского метода простое и быстрое. Давайте посмотрим, как проходит процесс:

     Print("\n---> Training the Model\n");
     
     matrix x_train, x_test;
     vector y_train, y_test;
     
     matrix_utils.TrainTestSplitMatrices(Matrix,x_train,y_train,x_test,y_test,0.7,rand_state);
     
//--- Обучение  
     gaussian_naive = new CGaussianNaiveBayes(x_train,y_train);  //Инициализация и обучение модели    
     vector train_pred = gaussian_naive.GaussianNaiveBayes(x_train); //делаем прогнозы на обученных данных
     vector c= gaussian_naive.classes; //определенные в наборе данных классы    
     metrics.confusion_matrix(y_train,train_pred,c);  //анализ предсказания в матрице путаницы
//---

Функция TrainTestSplitMatrices разбивает данные на x обучающих и x тестовых матриц и соответствующие им целевые векторы. Аналогично функции train_test_split в sklearn python. Основная функция выглядит следующим образом:

void CMatrixutils::TrainTestSplitMatrices(matrix &matrix_,matrix &x_train,vector &y_train,matrix &x_test, vector &y_test,double train_size=0.7,int random_state=-1)

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

Что многих людей запутало в этой функции, так это параметр random_state. В коммьюнити Python ML люди часто устанавливают random_state = 42, хотя на самом деле подойдет любое число, поскольку этот параметр предназначен только для того, чтобы рандомизированная/перетасованная матрица каждый раз генерировалась одинаково, чтобы упростить отладку, поскольку для генерации случайных чисел для перетасовки строк в матрице используется Random seed.

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

Вот что мы имеем на выходе из этого блока кода:

CS      0       14:33:04.001    Naive Bayes Test (EURUSD,H1)    ---> Training the Model
CS      0       14:33:04.001    Naive Bayes Test (EURUSD,H1)    
CS      0       14:33:04.002    Naive Bayes Test (EURUSD,H1)    ---> GROUPS [0,1]
CS      0       14:33:04.002    Naive Bayes Test (EURUSD,H1)    
CS      0       14:33:04.002    Naive Bayes Test (EURUSD,H1)    ---> Prior_proba [0.5457142857142857,0.4542857142857143] Evidence [382,318]

CS      0       14:33:04.294    Naive Bayes Test (EURUSD,H1)    Confusion Matrix
CS      0       14:33:04.294    Naive Bayes Test (EURUSD,H1)    [[236,146]
CS      0       14:33:04.294    Naive Bayes Test (EURUSD,H1)     [145,173]]
CS      0       14:33:04.294    Naive Bayes Test (EURUSD,H1)    
CS      0       14:33:04.294    Naive Bayes Test (EURUSD,H1)    Classification Report
CS      0       14:33:04.294    Naive Bayes Test (EURUSD,H1)    
CS      0       14:33:04.294    Naive Bayes Test (EURUSD,H1)    _    Precision  Recall  Specificity  F1 score  Support
CS      0       14:33:04.294    Naive Bayes Test (EURUSD,H1)    0.0    0.62     0.62     0.54       0.62     382.0
CS      0       14:33:04.294    Naive Bayes Test (EURUSD,H1)    1.0    0.54     0.54     0.62       0.54     318.0
CS      0       14:33:04.294    Naive Bayes Test (EURUSD,H1)    
CS      0       14:33:04.294    Naive Bayes Test (EURUSD,H1)    Accuracy                                   0.58
CS      0       14:33:04.294    Naive Bayes Test (EURUSD,H1)    Average   0.58    0.58    0.58      0.58    700.0
CS      0       14:33:04.294    Naive Bayes Test (EURUSD,H1)    W Avg     0.58    0.58    0.58      0.58    700.0
CS      0       14:33:04.294    Naive Bayes Test (EURUSD,H1)    

Согласно отчету о классификации матрицы путаницы, точность обученной модели составила 58%. Из этого отчета можно многое понять, например, точность, которая говорит, насколько точно каждый класс был классифицирован (подробности). В целом кажется, класс 0 классифицируется лучше, чем класс 1. Вообще это понятно, поскольку модель предсказала его больше, чем второй класс1, не говоря уже об априорной вероятности (основная первичная вероятность в выборке данных). В этом наборе данных имеем такие априорные вероятности:

Prior_proba [0.5457142857142857,0.4542857142857143] Evidence [382,318]. Априорная вероятность рассчитывается как:

Априорная вероятность = доказательство/общее количество событий/результатов

В нашем случае Prior Proba [382/700, 318/700]. Напомню, 700 — это размер обучающей выборки, получившейся в результате разбиения данных для выделения 70% под обучение из 1000 общей выборки.

Наивная гауссовская байесовская модель сначала ищет вероятности появления классов в наборе данных, а затем использует их, чтобы угадать, что может произойти в будущем на основе расчета по доказательствам (evidence). Класс с более высокими доказательствами будет иметь более высокую вероятность, чем другой класс, и поэтому будет предпочтительнее для алгоритма при обучении и тестировании. Вполне логично, не так ли? Это один из недостатков этого алгоритма, потому что, когда класс отсутствует в обучающих данных, модель предполагает, что этого класса не существует, поэтому она дает ему нулевую вероятность, а это означает, что для этого класса не будет предсказаний в тестовой выборке или когда-либо в будущем.


Тестирование модели

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

//--- Test   
     Print("\n---> Testing the model\n");     
     vector test_pred = gaussian_naive.GaussianNaiveBayes(x_test); //подаем на вход модели тестовые данные для прогнозирования и получения предсказаний в вектор     
     metrics.confusion_matrix(y_test,test_pred, c); //анализ тестируемой модели

Результат

CS      0       14:33:04.294    Naive Bayes Test (EURUSD,H1)    ---> Testing the model
CS      0       14:33:04.294    Naive Bayes Test (EURUSD,H1)    
CS      0       14:33:04.418    Naive Bayes Test (EURUSD,H1)    Confusion Matrix
CS      0       14:33:04.418    Naive Bayes Test (EURUSD,H1)    [[96,54]
CS      0       14:33:04.418    Naive Bayes Test (EURUSD,H1)     [65,85]]
CS      0       14:33:04.418    Naive Bayes Test (EURUSD,H1)    
CS      0       14:33:04.418    Naive Bayes Test (EURUSD,H1)    Classification Report
CS      0       14:33:04.418    Naive Bayes Test (EURUSD,H1)    
CS      0       14:33:04.418    Naive Bayes Test (EURUSD,H1)    _    Precision  Recall  Specificity  F1 score  Support
CS      0       14:33:04.418    Naive Bayes Test (EURUSD,H1)    0.0    0.60     0.64     0.57       0.62     150.0
CS      0       14:33:04.418    Naive Bayes Test (EURUSD,H1)    1.0    0.61     0.57     0.64       0.59     150.0
CS      0       14:33:04.418    Naive Bayes Test (EURUSD,H1)    
CS      0       14:33:04.418    Naive Bayes Test (EURUSD,H1)    Accuracy                                   0.60
CS      0       14:33:04.418    Naive Bayes Test (EURUSD,H1)    Average   0.60    0.60    0.60      0.60    300.0
CS      0       14:33:04.418    Naive Bayes Test (EURUSD,H1)    W Avg     0.60    0.60    0.60      0.60    300.0

Что ж, модель показала себя немного лучше на тестовом выборке, показав точность в 60%, что на 2% больше, чем точность обучающих данных, так что это хорошие новости.


Наивная гауссовская байесовская модель в тестере стратегий 

Использование моделей машинного обучения в тестере стратегий часто не дает хороших результатов не потому, что модели не способны делать прогнозы, а потому, что мы обычно смотрим на график прибыли в тестере стратегий. Модель машинного обучения способна угадать, куда рынок пойдет дальше, но это не обязательно означает, что вы заработаете на этом деньги, особенно с учетом простой логики, которую я использовал для сбора и подготовки нашего набора данных. Посмотрим выборки данных, которые были собраны на каждом баре для установленного в параметре таймфрейма, в данном случае PERIOD_H1 (часовой ТФ).

   close.CopyRates(Symbol(),TF, COPY_RATES_CLOSE,0,TrainBars);
   open.CopyRates(Symbol(),TF, COPY_RATES_OPEN,0,TrainBars);

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

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if (!train_state)
      TrainTest();
  
    train_state = true;   
     
//---
   vector v_inputs(5); //5 независимых переменных
   double buff[1];  //текущее значение индикатора
    
   for (ulong i=0; i<5; i++) //Независимые переменные
     { 
       CopyBuffer(handles[i],0,0,1, buff);
       
       v_inputs[i] = buff[0];
     }

//---
   MqlTick ticks;
   SymbolInfoTick(Symbol(), ticks);

   int signal = -1;
   double min_volume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
   
   if (isNewBar())
     { 
       signal = gaussian_naive.GaussianNaiveBayes(v_inputs);       
       Comment("SIGNAL ",signal);    
       CloseAll();
        
        if (signal == 1)
          { 
            if (!PosExist())
              m_trade.Buy(min_volume, Symbol(), ticks.ask, 0 , 0,"Naive Buy");
          }
        else if (signal == 0)
          {
            if (!PosExist())
              m_trade.Sell(min_volume, Symbol(), ticks.bid, 0 , 0,"Naive Sell");
          }
     } 
  }

Чтобы эта функция работала как в реальной торговле, так и в тестере стратегий, пришлось немного изменить логику. Функция CopyBuffer()и обучение теперь находятся внутри функции TrainTest(). Эта функция запускается один раз в функции OnTick. Можно сделать так, чтобы она запускалась чаще, при этом и модель будет обучаться чаще. Оставим это на ваше усмотрение.

Поскольку функция Init не подходит для всех этих методов copy buffer и copy rates (gри таком использовании они возвращают нулевые значения в тестере стратегий), теперь все перемещено в функцию TrainTest().

int OnInit()
  {    
   handles[0] = iBearsPower(Symbol(),TF, bears_period);
   handles[1] = iBullsPower(Symbol(),TF, bulls_period);
   handles[2] = iRSI(Symbol(),TF,rsi_period, rsi_price);
   handles[3] = iVolumes(Symbol(),TF,VOLUME_TICK);
   handles[4] = iMFI(Symbol(),TF,mfi_period,VOLUME_TICK );

//---

   m_trade.SetExpertMagicNumber(MAGIC_NUMBER);
   m_trade.SetTypeFillingBySymbol(Symbol());
   m_trade.SetMarginMode();
   m_trade.SetDeviationInPoints(slippage);

   return(INIT_SUCCEEDED);
  }

Одиночный тест: часовой таймфрейм

Я запускал тестирование на данных двух месяцев с 1 января 2023 года по 14 февраля 2023 года.

Я выбрал именно такой период для тестирования (2 месяца), потому что 1000 часовых баров — это не такой уж большой период обучения. Это почти 41 день, так что период обучения короткий, как и тестирование. Когда функция TrainTest() работала в тестере, у нас получилось тестирование на 700 барах. 

Что случилось?

Модель произвела первое впечатление при тестировании в тестере стратегий, обеспечив впечатляющую 60-процентную точность обучающих данных.

CS      0       08:30:13.816    Tester  initial deposit 1000.00 USD, leverage 1:100
CS      0       08:30:13.818    Tester  successfully initialized
CS      0       08:30:13.818    Network 80 Kb of total initialization data received
CS      0       08:30:13.819    Tester  Intel Core i5  660 @ 3.33GHz, 6007 MB
CS      0       08:30:13.900    Symbols EURUSD: symbol to be synchronized
CS      0       08:30:13.901    Symbols EURUSD: symbol synchronized, 3720 bytes of symbol info received
CS      0       08:30:13.901    History EURUSD: history synchronization started
....
....
....
CS      0       08:30:14.086    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   ---> Training the Model
CS      0       08:30:14.086    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   
CS      0       08:30:14.086    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   ---> GROUPS [0,1]
CS      0       08:30:14.086    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   
CS      0       08:30:14.086    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   ---> Prior_proba [0.4728571428571429,0.5271428571428571] Evidence [331,369]
CS      0       08:30:14.377    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   Confusion Matrix
CS      0       08:30:14.378    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   [[200,131]
CS      0       08:30:14.378    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00    [150,219]]
CS      0       08:30:14.378    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   
CS      0       08:30:14.378    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   Classification Report
CS      0       08:30:14.378    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   
CS      0       08:30:14.378    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   _    Precision  Recall  Specificity  F1 score  Support
CS      0       08:30:14.378    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   0.0    0.57     0.60     0.59       0.59     331.0
CS      0       08:30:14.378    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   1.0    0.63     0.59     0.60       0.61     369.0
CS      0       08:30:14.378    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   
CS      0       08:30:14.378    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   Accuracy                                   0.60
CS      0       08:30:14.378    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   Average   0.60    0.60    0.60      0.60    700.0
CS      0       08:30:14.378    Naive Bayes Test (EURUSD,H1)    2023.01.02 01:00:00   W Avg     0.60    0.60    0.60      0.60    700.0

Однако далее советник не смог совершать прибыльные сделки с обещанной точностью или близкой к ней. Ниже мои наблюдения:

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

Для этого мы запустили оптимизацию на шестичасовом таймфрейме и получили значения Train Bars = 80, TF = 12 hours. Тестирование проводилось на данных за два месяца с использованием новых параметров. Все параметры доступны в *set-файле, приложенном в конце статьи.

На этот раз точность обучения гауссовской наивной байесовской модели составила 58%.

93 сделки за период в 2 месяца — это можно называть здоровой торговой активностью. В среднем получилось 2,3 сделки в день. На этот раз 63% сделок, совершенных советником Gaussian Naïve Bayes, оказались прибыльными. Прибыль составила 10%.

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


Теория наивного Байеса

Не путайте с Гауссовским наивным байесовским метод.

Алгоритм называется

  • Наивным, потому что он предполагает, что переменные/функции независимы, что в реальности бывает редко.
  • Байес, потому что он основан на теореме Байеса

Формула теоремы Байеса приведена ниже:

Где:

P(A|B) — апостериорная вероятность гипотезы (состояния) A при наблюдении события B

P(B|A) — вероятность наступления события при условия, что вероятность теории верна. Простыми словами, это вероятность B при условии, что A верно

P(A) — априорная (безусловная) вероятность A или вероятность гипотезы до наступления события

P(B) — предельная вероятность наступления события

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

Работа с классификатором

Давайте рассмотрим простой пример с выборкой данных о погоде. Обратите внимание на первый столбец "Прогноз". Сначала поймем, что происходит в нем, а затем займемся добавлением других столбцов в качестве независимых переменных — это точно такой же процесс.

Прогноз Играть в теннис
 Солнечно  Нет
 Солнечно   Нет
 Пасмурно  Да
 Дождь  Да
 Дождь  Да
 Дождь  Нет
 Пасмурно  Да
 Солнечно  Нет
 Солнечно  Да
 Дождь   Да
 Солнечно   Да
 Пасмурно  Да
 Пасмурно  Да
 Дождь  Нет

Теперь давайте сделаем то же самое в MetaEditor:

void OnStart()
  {
//---
   matrix Matrix = matrix_utils.ReadCsvEncode("weather dataset.csv");
   
   int cols[3] = {1,2,3};
   
   matrix_utils.RemoveMultCols(Matrix, cols); //удалим данные о температуре, влажности и ветре
   ArrayRemove(matrix_utils.csv_header,1,3); //удаление заголовков столбцов
   
   ArrayPrint(matrix_utils.csv_header);
   Print(Matrix);
   
   matrix x_matrix; vector y_vector;
   
   matrix_utils.XandYSplitMatrices(Matrix, x_matrix, y_vector);
       
  } 

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

CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)   "Outlook" "Play"   
CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)   [[0,0]
CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)    [0,0]
CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)    [1,1]
CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)    [2,1]
CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)    [2,1]
CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)    [2,0]
CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)    [1,1]
CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)    [0,0]
CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)    [0,1]
CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)    [2,1]
CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)    [0,1]
CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)    [1,1]
CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)    [1,1]
CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)    [2,0]]

Теперь найдем априорную вероятность в нашем конструкторе класса NaïveBayes:

CNaiveBayes::CNaiveBayes(matrix &x_matrix, vector &y_vector)
 {
   XMatrix.Copy(x_matrix);
   YVector.Copy(y_vector); 
   
   classes = matrix_utils.Classes(YVector);
   
   c_evidence.Resize((ulong)classes.Size());
   
   n = YVector.Size();
   
   if (n==0) { Print("--> n == 0 | Naive Bayes class failed"); return; }
   
//---

   vector v = {};
   for (ulong i=0; i<c_evidence.Size(); i++)
       {
         v = matrix_utils.Search(YVector,(int)classes[i]);
         
         c_evidence[i] = (int)v.Size();
       }

//---
     
   c_prior_proba.Resize(classes.Size());
   
   for (ulong i=0; i<classes.Size(); i++)
      c_prior_proba[i] = c_evidence[i]/(double)n;
  
 
   #ifdef DEBUG_MODE
      Print("---> GROUPS ",classes);
      Print("Prior Class Proba ",c_prior_proba,"\nEvidence ",c_evidence);
   #endif   

 }

Результат

CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)   ---> GROUPS [0,1]
CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)   Prior Class Proba [0.3571428571428572,0.6428571428571429]
CS      0       12:59:37.386    Naive Bayes theory script (EURUSD,H1)   Evidence [5,9]

Априорная вероятность [Yes, No] приблизительно равна [0.36, 0.64].

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

P(Yes | Sunny) = P(Sunny | Yes) * P(Yes) / P(Sunny) 

More details in simple English:

Вероятность того, что кто-то будет играть в солнечный день = сколько раз по вероятности было солнечно и кто-то играл в теннис * сколько раз с точки зрения вероятности люди играли в теннис / сколько раз с точки зрения вероятности это был солнечный день.

P(Sunny | Yes) = 2/9

P(Yes) = 0.64

P(Sunny) = 5/14 = 0.357

Итого получается P(Yes | Sunny) = 0.333 x 0.64 / 0.357 = 0.4

Что касается вероятности (No| Sunny), ее можно вычислить так: 1 - вероятность Yes  = 1 - 0.5972 = 0.4027. Это так просто, но все же посмотрим и на эту часть.

P(No|Sunny) = (3/5) x 0.36 / (0.357) = 0.6

В коде это происходит так:

vector CNaiveBayes::calcProba(vector &v_features)
 {
    vector proba_v(classes.Size()); //вектор для возврата
    
    if (v_features.Size() != XMatrix.Cols())
      {
         printf("FATAL | Can't calculate probability,  features columns size = %d is not equal to XMatrix columns =%d",v_features.Size(),XMatrix.Cols());
         return proba_v;
      }

//---
    
    vector v = {}; 
    
    for (ulong c=0; c<classes.Size(); c++)
      {
        double proba = 1;
          for (ulong i=0; i<XMatrix.Cols(); i++)
            {
                v = XMatrix.Col(i);
                
                int count =0;
                for (ulong j=0; j<v.Size(); j++)
                  {
                     if (v_features[i] == v[j] && classes[c] == YVector[j])
                        count++;
                  }
                  
                proba *= count==0 ? 1 : count/(double)c_evidence[c]; //не считаем, если нет достаточно доказательств
            }
          
        proba_v[c] = proba*c_prior_proba[c];
     }
     
    return proba_v;
 }

Вектор вероятности, предоставленный этой функцией для солнечной погоды:

2023.02.15 16:34:21.519 Naive Bayes theory script (EURUSD,H1)   Probabilities [0.6,0.4]

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

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

probability_v = v[i]/probability_v.Sum()

Этот небольшой процесс выполняется внутри функции NaiveBayes(), которая предсказывает результат класса или класс с наибольшей вероятностью из всех:

int CNaiveBayes::NaiveBayes(vector &x_vector)
 {   
   vector v = calcProba(x_vector);
   
   double sum = v.Sum();
   
   for (ulong i=0; i<v.Size(); i++) //преобразуем значения в вероятности
      v[i] = NormalizeDouble(v[i]/sum,2);       
   
   vector p = v;
   
   #ifdef   DEBUG_MODE
      Print("Probabilities ",p);
   #endif 
   
   return((int)classes[p.ArgMax()]);
 }

И это все. Наивный байесовский алгоритм настолько прост. Теперь сосредоточимся на гауссовском наивном байесовском алгоритме, который мы использовали в начале этой статьи.


Гауссовский наивный байесовский метод

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

Вспомним о нормальном распределении

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

Кривая нормального распределения

Эта колоколообразная кривая вероятности настолько эффективна, что считается одним из самых полезных инструментов статистического анализа. Она показывает, что существует приблизительная 34% вероятность найти что-то на одно стандартное отклонение от среднего и 34% найти что-то по другую сторону кривой нормального распределения. Это означает, что вероятность найти значение, которое находится на расстоянии одного стандартного отклонения от среднего значения с обеих сторон, составляет около 68%. Если хотите узнать об этом подробнее, рекомендую почитать здесь

Из этого нормального распределения/распределения Гаусса нам нужно найти плотность вероятности. Он рассчитывается по следующей формуле.

Функция плотности вероятности

где:

 μ — среднее значение

𝜎 — стандартное отклонение

х — входное значение  

Поскольку это часть гауссовского наивного байесовского алгоритма, напишем соответствующий код.

class CNormDistribution
  {

public:
   
   double m_mean; //присваем значение среднего
   double m_std;  //присваиваем значение дисперсии
   
                     CNormDistribution(void);
                    ~CNormDistribution(void);
                    
                     double PDF(double x); //функция плотности вероятности
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+

CNormDistribution::CNormDistribution(void)
 {
   
 }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+

CNormDistribution::~CNormDistribution(void)
 {
   ZeroMemory(m_mean);
   ZeroMemory(m_std);
 }
 
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+

double CNormDistribution::PDF(double x)
 {
   double nurm = MathPow((x - m_mean),2)/(2*MathPow(m_std,2));
   nurm = exp(-nurm);
   
   double denorm = 1.0/(MathSqrt(2*M_PI*MathPow(m_std,2)));
      
  return(nurm*denorm);
 }


Создание гауссовской наивной байесовской модели

Конструктор класса Gaussian Naïve Bayes похож на конструктор класса Naïve Bayes. Поэтому не буду показывать и объяснять код конструктора здесь. Ниже представлена наша основная функция, отвечающая за вычисление вероятности.

vector CGaussianNaiveBayes::calcProba(vector &v_features)
 {    
    vector proba_v(classes.Size()); //вектор для возврата
    
    if (v_features.Size() != XMatrix.Cols())
      {
         printf("FATAL | Can't calculate probability, features columns size = %d is not equal to XMatrix columns =%d",v_features.Size(),XMatrix.Cols());
         return proba_v;
      }

//---
    vector v = {}; 
    
    for (ulong c=0; c<classes.Size(); c++)
      {
        double proba = 1;
          for (ulong i=0; i<XMatrix.Cols(); i++)
            {
                v = XMatrix.Col(i);                
                int count =0;
                vector calc_v = {};
                
                for (ulong j=0; j<v.Size(); j++)
                  {
                     if (classes[c] == YVector[j])
                       {
                         count++;
                         calc_v.Resize(count);
                         
                         calc_v[count-1] = v[j];
                       }
                  } 
                
                norm_distribution.m_mean = calc_v.Mean(); //назначаем нормальному распределению Гаусса
                norm_distribution.m_std = calc_v.Std();   
                
                #ifdef DEBUG_MODE
                  printf("mean %.5f std %.5f ",norm_distribution.m_mean,norm_distribution.m_std);
                #endif 
                
                proba *= count==0 ? 1 : norm_distribution.PDF(v_features[i]); //не считаем, если нет достаточно доказательств
            }
          
        proba_v[c] = proba*c_prior_proba[c]; //превращение плотности вероятности в вероятность
        
        #ifdef DEBUG_MODE
         Print(">> Proba ",proba," prior proba ",c_prior_proba);
        #endif 
     }
     
    return proba_v;
 }

Посмотрим, как эта модель поведет себя в действии.

Для примера используем выборку о гендерных данных.

Высота (футы)
 Вес (фунты)  Размер стопы (дюймы) Пол (0 - мужчина, 1 - женщина)
 6  180  12   0
 5.92  190   11   0
 5.58  170   12   0
 5.92  165   10   0
 5  100   6   1
 5.5  150   8   1
 5.42  130   7   1
 5.75  150   9   1
//--- гауссовский наивный байес
   Matrix = matrix_utils.ReadCsv("gender dataset.csv");
   
   ArrayPrint(matrix_utils.csv_header);
   Print(Matrix);
   
   matrix_utils.XandYSplitMatrices(Matrix, x_matrix, y_vector);   
   gaussian_naive = new CGaussianNaiveBayes(x_matrix, y_vector);      

Выводимая информация:

CS      0       18:52:18.653    Naive Bayes theory script (EURUSD,H1)   ---> GROUPS [0,1]
CS      0       18:52:18.653    Naive Bayes theory script (EURUSD,H1)   
CS      0       18:52:18.653    Naive Bayes theory script (EURUSD,H1)   ---> Prior_proba [0.5,0.5] Evidence [4,4]

Поскольку 4 из 8 были мужчинами, а остальные 4 были женщинами, вероятность того, что модель предскажет мужчину или женщину, составляет 50-50.

Давайте попробуем модель с новыми данными человека ростом 5,3, весом 140 и размером стопы 7,5. Мы с вами понимаем, что этот человек, скорее всего, женщина.

   vector person = {5.3, 140, 7.5};
   
   Print("The Person is a ",gaussian_naive.GaussianNaiveBayes(person));

Выводимая информация:

2023.02.15 19:14:40.424 Naive Bayes theory script (EURUSD,H1)   The Person is a 1

Отлично, предсказание получилось верным — это женщина.

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

CS      0       19:21:22.951    Naive Bayes theory script (EURUSD,H1)   Confusion Matrix
CS      0       19:21:22.951    Naive Bayes theory script (EURUSD,H1)   [[4,0]
CS      0       19:21:22.951    Naive Bayes theory script (EURUSD,H1)    [0,4]]
CS      0       19:21:22.951    Naive Bayes theory script (EURUSD,H1)   
CS      0       19:21:22.951    Naive Bayes theory script (EURUSD,H1)   Classification Report
CS      0       19:21:22.951    Naive Bayes theory script (EURUSD,H1)   
CS      0       19:21:22.951    Naive Bayes theory script (EURUSD,H1)   _    Precision  Recall  Specificity  F1 score  Support
CS      0       19:21:22.951    Naive Bayes theory script (EURUSD,H1)   0.0    1.00     1.00     1.00       1.00     4.0
CS      0       19:21:22.951    Naive Bayes theory script (EURUSD,H1)   1.0    1.00     1.00     1.00       1.00     4.0
CS      0       19:21:22.951    Naive Bayes theory script (EURUSD,H1)   
CS      0       19:21:22.951    Naive Bayes theory script (EURUSD,H1)   Accuracy                                   1.00
CS      0       19:21:22.951    Naive Bayes theory script (EURUSD,H1)   Average   1.00    1.00    1.00      1.00    8.0
CS      0       19:21:22.951    Naive Bayes theory script (EURUSD,H1)   W Avg     1.00    1.00    1.00      1.00    8.0

Точность обучения составляет 100%! Наша модель может классифицировать, является ли человек мужчиной или женщиной, при использовании всех данных в качестве обучающей выборки.


Преимущества наивных и гауссовых байесовских классификаторов

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

Недостатки этих классификаторов

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

Наивный Байес

  1. Предположение о независимости. Наивный байесовский подход предполагает, что все функции независимы друг от друга, что не всегда может быть верным на практике. Это предположение может привести к снижению точности классификации, если признаки сильно зависят друг от друга.
  2. Разреженность данных. Наивный Байес полагается на наличие достаточного количества обучающих примеров для каждого класса для точной оценки априорных значений класса и условных вероятностей. Если выборка данных слишком мала, оценки могут быть неточными, а классификация неэффективной.
  3. Чувствительность к несущественным функциям. Наивный байесовский метод рассматривает все признаки одинаково, независимо от их значимости для задачи классификации. Это может привести худшей классификации, если в набор данных включены нерелевантные функции. При этом сложно спорить с тем, что некоторые функции в наборе данных более важны, чем другие.
  4. Неспособность обрабатывать непрерывные переменные. Наивный байесовский подход предполагает, что все функции являются дискретными или категориальными и не могут напрямую обрабатывать непрерывные переменные. Чтобы использовать Наивный Байес с непрерывными переменными, данные должны быть дискретизированы, что может привести к потере информации и снижению точности классификации.
  5. Ограниченная выразительность. Наивный байесовский алгоритм может моделировать только линейные границы решений, чего может быть недостаточно для более сложных задач классификации. Из-за этого показатели могут быть хуже, если граница решения нелинейна.
  6. Дисбаланс классов. Наивный байесовский метод может работать хуже, когда распределение примеров по классам сильно несбалансировано, поскольку это может привести к смещенным априорным оценкам класса и плохой оценке условных вероятностей для класса меньшинства. Если нет достаточных доказательств, класс вообще не будет предсказан.

Гауссовский наивный байесовский метод

Гауссовский наивный байесовский метод повторяет вышеуказанные недостатки и плюс имеет два дополнительных:

  1. Чувствительность к выбросам. Гауссовский наивный байесовский метод предполагает, что признаки нормально распределены, что означает, что экстремальные значения или выбросы могут оказывать значительное влияние на оценки среднего значения и дисперсии. Это может привести к снижению производительности классификации, если набор данных содержит выбросы.
  2. Не подходит для функций с тяжелыми хвостами. Гауссовский наивный байесовский метод предполагает, что признаки имеют нормальное распределение с конечной дисперсией. Если же есть тяжелые хвосты, такие как распределение Коши, алгоритм может работать плохо.

Заключение

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

С наилучшими пожеланиями.

Следите за развитием темы в моем репозитории GitHub https://github.com/MegaJoctan/MALE5

Файл  Содержание и использование
 Naive Bayes.mqh  Содержит классы наивных байесовских моделей
 Naive Bayes theory script.mq5  Скрипт для тестирования библиотеки 
 Naive Bayes Test.mq5  Советник для торговли по изученным моделям
 matrix_utils.mqh  Содержит дополнительные матричные функции 
 metrics.mqh  Содержит функции для анализа производительности моделей машинного обучения, как матрица путаницы
 naive bayes visualize.py  Скрипт Python для рисования графиков распределения по всем независимым переменным, используемым в модели
 gender datasets.csv & weather dataset.csv  Выборки данных, используемые в качестве примеров в этой статье

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


Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/12184

Прикрепленные файлы |
MQL5.zip (15.93 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
Maxim Kuznetsov
Maxim Kuznetsov | 13 апр. 2023 в 15:31

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

Байес ничего не сделает

Ivan Butko
Ivan Butko | 13 апр. 2023 в 17:11
Как "оно" работает?

Просто скачать и запустить в тестере - какой-то слив. В статье не нашёл инструкции "чтобы обучить, нажмите X"

Опять что-то для академиков
Valeriy Yastremskiy
Valeriy Yastremskiy | 14 апр. 2023 в 14:22
Maxim Kuznetsov #:

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

Байес ничего не сделает

А какая разница где рандомные выборки делать. Он и применяет Байеса на связанных данных и отмечает это в статье.
Valeriy Yastremskiy
Valeriy Yastremskiy | 14 апр. 2023 в 14:23
Ivan Butko #:
Как "оно" работает?

Просто скачать и запустить в тестере - какой-то слив. В статье не нашёл инструкции "чтобы обучить, нажмите X"

Опять что-то для академиков
Это просто рандом)))
Разработка торговой системы на основе Индекса облегчения рынка MFI от Билла Вильямса Разработка торговой системы на основе Индекса облегчения рынка MFI от Билла Вильямса
Это новая статья из серии, в которой мы учимся создавать торговые системы на основе популярных технических индикаторов. В этой новой статье мы рассмотрим Индекс облегчения рынка (Market Facilitation Index, MFI), разработанный Биллом Вильямсом.
Понимание и эффективное использование OpenCL API путем воссоздания встроенной поддержки в виде DLL в Linux (Часть 1): Мотивация и проверка Понимание и эффективное использование OpenCL API путем воссоздания встроенной поддержки в виде DLL в Linux (Часть 1): Мотивация и проверка
Встроенная поддержка OpenCL в MetaTrader 5 по-прежнему имеет серьезную проблему, особенно связанную с ошибкой выбора устройства 5114, возникающей из-за невозможности создать контекст OpenCL с использованием CL_USE_GPU_ONLY или CL_USE_GPU_DOUBLE_ONLY, хотя GPU определяется правильно. Программа отлично работает с прямым использованием порядкового номера устройства GPU, который можно найти на вкладке "Журнал", однако пользователям не следует жестко кодировать устройство. Мы решим эту проблему, воссоздав поддержку OpenCL в виде DLL с помощью C++ в Linux. По пути мы познакомимся с OpenCL, от концепции до лучших практик в использовании его API, ровно настолько, чтобы мы могли широко использовать его позже, когда будем иметь дело с реализацией DLL на C++ и использовать его с MQL5.
Теория категорий в MQL5 (Часть 3) Теория категорий в MQL5 (Часть 3)
Теория категорий представляет собой разнообразный и расширяющийся раздел математики, который пока относительно не освещен в MQL5-сообществе. Эта серия статей призвана осветить некоторые из ее концепций для создания открытой библиотеки и дальнейшему использованию этого замечательного раздела в создании торговых стратегий.
Эксперименты с нейросетями (Часть 5): Нормализация входных параметров для передачи в нейросеть Эксперименты с нейросетями (Часть 5): Нормализация входных параметров для передачи в нейросеть
Нейросети наше все. Проверяем на практике, так ли это. MetaTrader 5 как самодостаточное средство для использования нейросетей в трейдинге. Простое объяснение.