English Deutsch 日本語
preview
Машинное обучение и Data Science (Часть 20): Выбор между LDA и PCA в задачах алготрейдинга на MQL5

Машинное обучение и Data Science (Часть 20): Выбор между LDA и PCA в задачах алготрейдинга на MQL5

MetaTrader 5Трейдинг | 27 июня 2024, 14:26
152 0
Omega J Msigwa
Omega J Msigwa

-- Чем больше имеешь, тем меньше замечаешь.

Что такое линейный дискриминантный анализ (Linear Discriminant Analysis, ЛДА)?

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

Как и Анализ главных компонентов (PCA), это алгоритм уменьшения размерности. Эти алгоритмы часто используют для уменьшения размерности. В этой статье мы сравним их и посмотрим, в какой ситуации каждый из алгоритмов работает лучше всего. Мы уже обсуждали PCA в предыдущих статьях этой серии. Давайте начнем со знакомства с алгоритмом ЛДА, поскольку основной объем статьи будет посвящен этому алгоритму и равнению его с PCA. Мы будем сравнивать производительность этих двух алгоритмов на простом наборе данных и в тестере стратегий.



Цели/теория

Для чего используется метод линейного дискриминантного анализа:

  • Улучшение разделимости классов: LDA стремится найти линейные комбинации признаков, с которыми будет максимальное разделение между классами в данных. Проецируя данные на эти дискриминационные измерения, LDA помогает усилить различия между классами, делая классификацию более эффективной.
  • Уменьшение размерности: LDA уменьшает размерность пространства признаков, проецируя данные на подпространство меньшей размерности. При этом при уменьшении размерности сохраняется как можно большее количество информации о классовых различиях. Уменьшение пространства признаков может привести к упрощению моделей, ускорению вычислений и повышению производительности.
  • Минимизация внутриклассовой вариативности: LDA стремится минимизировать разброс или вариативность внутри класса, гарантируя, что точки данных, принадлежащие одному классу, плотно сгруппированы вместе в преобразованном пространстве. За счет уменьшения внутриклассовой вариативности LDA помогает улучшить разделение между классами и повысить надежность модели классификации.
  • Увеличение межклассового расстояния: И наоборот, LDA стремится максимизировать различие между классами за счет установки максимально возможного расстояния между классами в преобразованном пространстве. Таким образом, алгоритм ЛДА позволяет проводить более четкие различия между классами и делать классификацию более точной.
  • Возможности мультиклассовой классификации: ЛДА подходит для группировки признаков на более чем два класса. Учитывая отношения между всеми классами одновременно, алгоритм находит общее подпространство, которое оптимально разделяет все классы. При этом мы получаем эффективные границы классификации в многомерных пространствах признаков.


Предположения:

Метод линейного дискриминантного анализа делает несколько предположений. Итак, предполагается, что

  1. Измерения независимы друг от друга.
  2. Данные обычно распределяются внутри объектов.
  3. Классы в наборе данных имеют одинаковую матрицу ковариаций.

Шаги алгоритма ЛДА

1. Расчет матрицы рассеяния внутри класса (SW):

Вычисляем матрицу рассеяния для каждого класса.

  matrix SW, SB; //within and between scatter matrices 
  SW.Init(num_features, num_features);
  SB.Init(num_features, num_features);
  
  for (ulong i=0; i<num_classes; i++)
   {
     matrix class_samples = {};
      for (ulong j=0, count=0; j<x.Rows(); j++)
         {
           if (y[j] == classes[i]) //Collect a matrix for samples belonging to a particular class
            {
               count++;
               class_samples.Resize(count, num_features);
               class_samples.Row(x.Row(j), count-1);
            }
         }

         
     matrix diff = Base::subtract(class_samples, class_means.Row(i)); //Each row subtracted to the mean
     if (diff.Rows()==0 && diff.Cols()==0) //if the subtracted matrix is zero stop the program for possible bugs or errors
      {
        DebugBreak();
        return x_centered;
      }
     
     SW += diff.Transpose().MatMul(diff); //Find within scatter matrix 
     
     vector mean_diff = class_means.Row(i) - x_centered.Mean(0);
     SB += class_samples.Rows() * mean_diff.Outer(mean_diff); //compute between scatter matrix 
   }

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

2. Вычисление матрицы рассеяния между классами (SB):

Находим средний вектор для каждого класса.

  matrix SW, SB; //within and between scatter matrices 
  SW.Init(num_features, num_features);
  SB.Init(num_features, num_features);
  
  for (ulong i=0; i<num_classes; i++)
   {

     matrix class_samples = {};
      for (ulong j=0, count=0; j<x.Rows(); j++)
         {
           if (y[j] == classes[i]) //Collect a matrix for samples belonging to a particular class
            {
               count++;
               class_samples.Resize(count, num_features);
               class_samples.Row(x.Row(j), count-1);
            }
         }
         
     matrix diff = Base::subtract(class_samples, class_means.Row(i)); //Each row subtracted to the mean
     if (diff.Rows()==0 && diff.Cols()==0) //if the subtracted matrix is zero stop the program for possible bugs or errors
      {
        DebugBreak();
        return x_centered;
      }
     
     SW += diff.Transpose().MatMul(diff); //Find within scatter matrix 
     
     vector mean_diff = class_means.Row(i) - x_centered.Mean(0);
     SB += class_samples.Rows() * mean_diff.Outer(mean_diff); //compute between scatter matrix 
   }

Рассчитываем матрицу рассеяния между классами.

     SB += class_samples.Rows() * mean_diff.Outer(mean_diff); //compute between scatter matrix 


3. Расчет собственных значений и собственных векторов

Решаем обобщенную задачу собственных значений, включающую SW и SB, получаем собственные значения и соответствующие им собственные векторы.

  matrix eigen_vectors;
  vector eigen_values;
  
  matrix SBSW = SW.Inv().MatMul(SB);
  
  SBSW += this.m_regparam * MatrixExtend::eye((uint)SBSW.Rows());
  
  if (!SBSW.Eig(eigen_vectors, eigen_values))
    {
      Print("%s Failed to calculate eigen values and vectors Err=%d",__FUNCTION__,GetLastError());
      DebugBreak();
      
      matrix empty = {};
      return empty;
    }


Выбор отличительных признаков:

Сортируем собственные значения в порядке убывания.

   vector args = MatrixExtend::ArgSort(eigen_values);
   MatrixExtend::Reverse(args);
   
   eigen_values = Base::Sort(eigen_values, args);
   eigen_vectors = Base::Sort(eigen_vectors, args);   

Выбираем k лучших собственных векторов, чтобы сформировать матрицу преобразования.

   this.m_components = extract_components(eigen_values);

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

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

  if (this.m_components == NULL)
    this.m_components = extract_components(eigen_values);
  else //plot the scree plot 
    extract_components(eigen_values);


Проецирование данных на новое пространство объектов

Умножаем исходные данные на выбранные собственные векторы, чтобы получить новое пространство признаков.

  this.projection_matrix = Base::Slice(eigen_vectors, this.m_components);
    
  return x_centered.MatMul(projection_matrix.Transpose());

Весь этот код выполняется внутри функции fit_transform. Эта функция отвечает за обучение и подготовку алгоритма линейного дискриминантного анализа. Чтобы наш класс мог обрабатывать новые/невидимые данные, нужно добавить функции для дальнейших преобразований.

matrix CLDA::transform(const matrix &x)
 {
   if (this.projection_matrix.Rows() == 0)
    {
      printf("%s fit_transform method must be called befor transform",__FUNCTION__);
      matrix empty = {};
      return empty; 
    }
  matrix x_centered = Base::subtract(x, this.mean);
  
  return x_centered.MatMul(this.projection_matrix.Transpose());  
 }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
vector CLDA::transform(const vector &x)
 {
   matrix m = MatrixExtend::VectorToMatrix(x, this.num_features); 
   
   if (m.Rows()==0)
    {
      vector empty={};
      return empty; //return nothing since there is a failure in converting vector to matrix
    }
   
   m = transform(m);
   return MatrixExtend::MatrixToVector(m);
 }


Обзор класса LDA

Общий класс алгоритма ЛДА теперь выглядит так:

enum lda_criterion //selecting best components criteria selection
  {
    CRITERION_VARIANCE,
    CRITERION_KAISER,
    CRITERION_SCREE_PLOT
  };

class CLDA
  {  
CPlots   plt;

protected:
   uint m_components;
   lda_criterion m_criterion;
   
   matrix projection_matrix;
   ulong num_features;
   double m_regparam;
   vector mean;
   
   uint CLDA::extract_components(vector &eigen_values, double threshold=0.95);
   
public:
                     CLDA(uint k=NULL, lda_criterion CRITERION_=CRITERION_SCREE_PLOT, double reg_param =1e-6);
                    ~CLDA(void);
                    
                     matrix fit_transform(const matrix &x, const vector &y);
                     matrix transform(const matrix &x);
                     vector transform(const vector &x);
  };

Параметр регуляризации reg_param имеет меньшее значение, поскольку он используется только для регуляризации матриц SW и SB. Это позволяет избегать ошибок при вычислении собственных значений и векторов.

   SW += this.m_regparam * MatrixExtend::eye((uint)num_features);
   SB += this.m_regparam * MatrixExtend::eye((uint)num_features);


Использование линейного дискриминантного анализа на выборке

Применим наш класс ЛДА на популярной выборке данных Iris-csv и посмотрим, что он сделает.

   string headers;
   matrix data = MatrixExtend::ReadCsv("iris.csv",headers); //Read csv

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

   matrix x;
   vector y;
   MatrixExtend::XandYSplitMatrices(data, x, y);   
#include <MALE5\Dimensionality Reduction\LDA.mqh>

CLDA *lda;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

   string headers;
   matrix data = MatrixExtend::ReadCsv("iris.csv",headers); //Read csv
   
   matrix x;
   vector y;
   MatrixExtend::XandYSplitMatrices(data, x, y);
   
   Print("Original X\n",x);
   
   lda = new CLDA();
   matrix transformed_x = lda.fit_transform(x, y);
   
   Print("Transformed X\n",transformed_x);
   
   return(INIT_SUCCEEDED);
  }

Результат

HH      0       10:18:21.210    LDA Test (EURUSD,H1)    Original X
IQ      0       10:18:21.210    LDA Test (EURUSD,H1)    [[5.1,3.5,1.4,0.2]
HF      0       10:18:21.210    LDA Test (EURUSD,H1)     [4.9,3,1.4,0.2]
...
...
ES      0       10:18:21.211    LDA Test (EURUSD,H1)     [6.5,3,5.2,2]
ML      0       10:18:21.211    LDA Test (EURUSD,H1)     [6.2,3.4,5.4,2.3]
EI      0       10:18:21.211    LDA Test (EURUSD,H1)     [5.9,3,5.1,1.8]]
IL      0       10:18:21.243    LDA Test (EURUSD,H1)    
DD      0       10:18:21.243    LDA Test (EURUSD,H1)    Transformed X
DM      0       10:18:21.243    LDA Test (EURUSD,H1)    [[-1.058063221542643,2.676898315513957]
JD      0       10:18:21.243    LDA Test (EURUSD,H1)     [-1.060778666796316,2.532150351483708]
DM      0       10:18:21.243    LDA Test (EURUSD,H1)     [-0.9139922886488467,2.777963946569435]
...
...
IK      0       10:18:21.244    LDA Test (EURUSD,H1)     [1.527279343196588,-2.300606221030168]
QN      0       10:18:21.244    LDA Test (EURUSD,H1)     [0.9614855249192527,-1.439559895222919]
EF      0       10:18:21.244    LDA Test (EURUSD,H1)     [0.6420061576026481,-2.511057690832021…]

Получился красивый график:

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

Я решил сохранить компоненты нашего советника в файле csv и построить их с помощью Python, используя https://www.kaggle.com/code/omegajoctan/lda-vs-pca-comComponents-iris-data

  MatrixExtend::WriteCsv("iris-data lda-components.csv",transformed_x);


Компоненты выглядят чистыми, что доказывает успешную реализацию. Теперь давайте посмотрим, как выглядят компоненты PCA:


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


Сравнение эффективности алгоритмов LDA и PCA при обучении и тестировании

Используем модели дерева решений с одинаковыми параметрами для отдельных данных, полученных с помощью алгоритмов LDA и PCA соответственно.

#include <MALE5\Dimensionality Reduction\LDA.mqh>
#include <MALE5\Dimensionality Reduction\PCA.mqh>
#include <MALE5\Decision Tree\tree.mqh>
#include <MALE5\Metrics.mqh>

CLDA *lda;
CPCA *pca;
CDecisionTreeClassifier *classifier_tree;

input int random_state_ = 42;
input double training_sample_size = 0.7;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

   string headers;
   matrix data = MatrixExtend::ReadCsv("iris.csv",headers); //Read csv
  
   Print("<<<<<<<< LDA Applied >>>>>>>>>");
   
   matrix x_train, x_test;
   vector y_train, y_test;
   
   MatrixExtend::TrainTestSplitMatrices(data,x_train,y_train,x_test,y_test,training_sample_size,random_state_);
   
   lda = new CLDA(NULL);
   
   matrix x_transformed = lda.fit_transform(x_train, y_train); //Transform the training data
   
   classifier_tree = new CDecisionTreeClassifier();
   classifier_tree.fit(x_transformed, y_train); //Train the model using the transformed data
   
   vector preds = classifier_tree.predict(x_transformed); //Make predictions using the transformed data
   
   Print("Train accuracy: ",Metrics::confusion_matrix(y_train, preds).accuracy);
   
   x_transformed = lda.transform(x_test);
   preds = classifier_tree.predict(x_transformed);
   
   Print("Test accuracy: ",Metrics::confusion_matrix(y_test, preds).accuracy);
   
   delete (classifier_tree);
   delete (lda);
   
//---
   
   Print("<<<<<<<< PCA Applied >>>>>>>>>");
   
   pca = new CPCA(NULL);
   
   x_transformed = pca.fit_transform(x_train);
   
   classifier_tree = new CDecisionTreeClassifier();
   classifier_tree.fit(x_transformed, y_train);
   
   preds = classifier_tree.predict(x_transformed); //Make predictions using the transformed data
   
   Print("Train accuracy: ",Metrics::confusion_matrix(y_train, preds).accuracy);
   
   x_transformed = pca.transform(x_test);
   preds = classifier_tree.predict(x_transformed);
   
   Print("Test accuracy: ",Metrics::confusion_matrix(y_test, preds).accuracy);
   
   delete (classifier_tree);
   delete(pca);

   return(INIT_SUCCEEDED);
  }

Результаты алгоритма LDA:

GM      0       18:23:18.285    LDA Test (EURUSD,H1)    <<<<<<<< LDA Applied >>>>>>>>>
MR      0       18:23:18.302    LDA Test (EURUSD,H1)    
JP      0       18:23:18.344    LDA Test (EURUSD,H1)    Confusion Matrix
FK      0       18:23:18.344    LDA Test (EURUSD,H1)    [[39,0,0]
CR      0       18:23:18.344    LDA Test (EURUSD,H1)     [0,30,5]
QF      0       18:23:18.344    LDA Test (EURUSD,H1)     [0,2,29]]
IS      0       18:23:18.344    LDA Test (EURUSD,H1)    
OM      0       18:23:18.344    LDA Test (EURUSD,H1)    Classification Report
KF      0       18:23:18.344    LDA Test (EURUSD,H1)    
QQ      0       18:23:18.344    LDA Test (EURUSD,H1)    _    Precision  Recall  Specificity  F1 score  Support
FF      0       18:23:18.344    LDA Test (EURUSD,H1)    1.0    1.00     1.00     1.00       1.00     39.0
GI      0       18:23:18.344    LDA Test (EURUSD,H1)    2.0    0.94     0.86     0.97       0.90     35.0
ML      0       18:23:18.344    LDA Test (EURUSD,H1)    3.0    0.85     0.94     0.93       0.89     31.0
OS      0       18:23:18.344    LDA Test (EURUSD,H1)    
FN      0       18:23:18.344    LDA Test (EURUSD,H1)    Accuracy                                   0.93
JO      0       18:23:18.344    LDA Test (EURUSD,H1)    Average   0.93    0.93    0.97      0.93    105.0
KJ      0       18:23:18.344    LDA Test (EURUSD,H1)    W Avg     0.94    0.93    0.97      0.93    105.0
EQ      0       18:23:18.344    LDA Test (EURUSD,H1)    Train accuracy: 0.933
JH      0       18:23:18.344    LDA Test (EURUSD,H1)    Confusion Matrix
LS      0       18:23:18.344    LDA Test (EURUSD,H1)    [[11,0,0]
IJ      0       18:23:18.344    LDA Test (EURUSD,H1)     [0,13,2]
RN      0       18:23:18.344    LDA Test (EURUSD,H1)     [0,1,18]]
IK      0       18:23:18.344    LDA Test (EURUSD,H1)    
OE      0       18:23:18.344    LDA Test (EURUSD,H1)    Classification Report
KN      0       18:23:18.344    LDA Test (EURUSD,H1)    
QI      0       18:23:18.344    LDA Test (EURUSD,H1)    _    Precision  Recall  Specificity  F1 score  Support
LN      0       18:23:18.344    LDA Test (EURUSD,H1)    1.0    1.00     1.00     1.00       1.00     11.0
CQ      0       18:23:18.344    LDA Test (EURUSD,H1)    2.0    0.93     0.87     0.97       0.90     15.0
QD      0       18:23:18.344    LDA Test (EURUSD,H1)    3.0    0.90     0.95     0.92       0.92     19.0
OK      0       18:23:18.344    LDA Test (EURUSD,H1)    
FF      0       18:23:18.344    LDA Test (EURUSD,H1)    Accuracy                                   0.93
GD      0       18:23:18.344    LDA Test (EURUSD,H1)    Average   0.94    0.94    0.96      0.94    45.0
HQ      0       18:23:18.344    LDA Test (EURUSD,H1)    W Avg     0.93    0.93    0.96      0.93    45.0
CF      0       18:23:18.344    LDA Test (EURUSD,H1)    Test accuracy: 0.933

LDA создала стабильную модель с точностью 93% как при обучении, так и при тестировании. Теперь посмотрим на работу PCA:

Результаты алгоритма PCA:

MM      0       18:26:40.994    LDA Test (EURUSD,H1)    <<<<<<<< PCA Applied >>>>>>>>>

LS      0       18:26:41.071    LDA Test (EURUSD,H1)    Confusion Matrix
LJ      0       18:26:41.071    LDA Test (EURUSD,H1)    [[39,0,0]
ER      0       18:26:41.071    LDA Test (EURUSD,H1)     [0,34,1]
OE      0       18:26:41.071    LDA Test (EURUSD,H1)     [0,4,27]]
KD      0       18:26:41.071    LDA Test (EURUSD,H1)    
IL      0       18:26:41.071    LDA Test (EURUSD,H1)    Classification Report
MG      0       18:26:41.071    LDA Test (EURUSD,H1)    
CR      0       18:26:41.071    LDA Test (EURUSD,H1)    _    Precision  Recall  Specificity  F1 score  Support
DE      0       18:26:41.071    LDA Test (EURUSD,H1)    1.0    1.00     1.00     1.00       1.00     39.0
EH      0       18:26:41.071    LDA Test (EURUSD,H1)    2.0    0.89     0.97     0.94       0.93     35.0
KL      0       18:26:41.071    LDA Test (EURUSD,H1)    3.0    0.96     0.87     0.99       0.92     31.0
ID      0       18:26:41.071    LDA Test (EURUSD,H1)    
NO      0       18:26:41.071    LDA Test (EURUSD,H1)    Accuracy                                   0.95
CH      0       18:26:41.071    LDA Test (EURUSD,H1)    Average   0.95    0.95    0.98      0.95    105.0
KK      0       18:26:41.071    LDA Test (EURUSD,H1)    W Avg     0.95    0.95    0.98      0.95    105.0
NR      0       18:26:41.071    LDA Test (EURUSD,H1)    Train accuracy: 0.952
LK      0       18:26:41.071    LDA Test (EURUSD,H1)    Confusion Matrix
FR      0       18:26:41.071    LDA Test (EURUSD,H1)    [[11,0,0]
FJ      0       18:26:41.072    LDA Test (EURUSD,H1)     [0,14,1]
MM      0       18:26:41.072    LDA Test (EURUSD,H1)     [0,3,16]]
NL      0       18:26:41.072    LDA Test (EURUSD,H1)    
HD      0       18:26:41.072    LDA Test (EURUSD,H1)    Classification Report
LO      0       18:26:41.072    LDA Test (EURUSD,H1)    
FJ      0       18:26:41.072    LDA Test (EURUSD,H1)    _    Precision  Recall  Specificity  F1 score  Support
KM      0       18:26:41.072    LDA Test (EURUSD,H1)    1.0    1.00     1.00     1.00       1.00     11.0
EP      0       18:26:41.072    LDA Test (EURUSD,H1)    2.0    0.82     0.93     0.90       0.88     15.0
HD      0       18:26:41.072    LDA Test (EURUSD,H1)    3.0    0.94     0.84     0.96       0.89     19.0
HL      0       18:26:41.072    LDA Test (EURUSD,H1)    
OG      0       18:26:41.072    LDA Test (EURUSD,H1)    Accuracy                                   0.91
PS      0       18:26:41.072    LDA Test (EURUSD,H1)    Average   0.92    0.93    0.95      0.92    45.0
IP      0       18:26:41.072    LDA Test (EURUSD,H1)    W Avg     0.92    0.91    0.95      0.91    45.0
PE      0       18:26:41.072    LDA Test (EURUSD,H1)    Test accuracy: 0.911

Алгоритм PCA дал еще более точную модель, обеспечивающую точность 95% при обучении и точность 91,1% при тестировании.


Преимущества линейного дискриминантного анализа (ЛДА, LDA):

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

  • Эффективно уменьшает размерность. LDA уменьшает размерность пространства объектов, преобразуя исходные объекты в пространство меньшей размерности. Такое снижение размерности позволяет получить более простые модели, борется с проклятием размерности и повышает эффективность вычислений.
  • Сохраняет информацию о межклассовых различиях. Метод LDA стремится найти линейные комбинации признаков, с которыми будет максимальное разделение между классами. Метод фокусируется на отличиях между классами, чтобы сохранить важные шаблоны и структуры конкретных классов.
  • Он извлекает признаки и классифицирует их за один шаг. Метод LDA одновременно выполняет извлечение и классификацию признаков. Он изучает преобразование исходных признаков и улучшает разделимость классов, что делает его по своей сути подходящим для задач классификации. Такой комплексный подход позволяет создавать более эффективные и интерпретируемые модели.
  • Устойчив к переобучению. Метод LDA менее склонен к переобучению по сравнению с другими алгоритмами классификации, особенно когда количество выборок невелико по сравнению с количеством признаков. Снижая размерность пространства признаков и концентрируясь на наиболее отличительных признаках, метод может хорошо обобщать невидимые данные.
  • Подходит для многоклассовой классификации. Метод хорошо применим для задач классификации с участием более чем двух классов. Он учитывает отношения между всеми классами одновременно, что приводит к созданию эффективных границ разделения в многомерных пространствах объектов.
  • Вычислительная эффективность. Внутри метода решаются задачи поиска собственных значений и умножения матриц — это эффективные расчеты, которые можно удобно реализовать с помощью встроенных методов MQL5. Благодаря этому алгоритм ЛДА подходит для крупных наборов данных и приложений реального времени.
  • Легко интерпретировать. Преобразованные признаки, полученные с помощью LDA, легко интерпретировать и анализировать, чтобы позволяет лучше понять основные закономерности в данных. Линейные комбинации признаков, полученные с помощью метода LDA, могут дать представление о дискриминационных факторах, влияющих на решение о классификации.
  • Его предположения часто оправдываются. Методом LDA предполагается, что данные обычно распределяются внутри каждого класса с равными ковариационными матрицами. Хотя такое не всегда оправдывается на практике, LDA все равно может работать хорошо, даже если такое предположение выполняется частично.

Хотя линейный дискриминантный анализ (LDA) имеет ряд преимуществ, он также имеет некоторые ограничения и недостатки:


Недостатки метода линейного дискриминантного анализа (ЛДА, LDA):

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


LDA против PCA в торговой среде

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

Мы будем использовать 5 индикаторов в нашем наборе данных, которые будем уменьшать, используя оба этих метода:

int OnInit()
  {
  
//--- Trend following indicators

    indicator_handle[0] = iAMA(Symbol(), PERIOD_CURRENT, 9 , 2 , 30, 0, PRICE_OPEN);
    indicator_handle[1] = iADX(Symbol(), PERIOD_CURRENT, 14);
    indicator_handle[2] = iADXWilder(Symbol(), PERIOD_CURRENT, 14);
    indicator_handle[3] = iBands(Symbol(), PERIOD_CURRENT, 20, 0, 2.0, PRICE_OPEN);
    indicator_handle[4] = iDEMA(Symbol(), PERIOD_CURRENT, 14, 0, PRICE_OPEN);
 }

Поскольку эти алгоритмы не были запрограммированы на изучение и отображение значимых торговых сигналов ввиду их природы, мы будем использовать дерево решений, чтобы делать прогнозы на основе преобразованных данных как в обучении, так и в тестировании.
void TrainTest()
 {
   vector buffer = {};
   for (int i=0; i<ArraySize(indicator_handle); i++)
    {
      buffer.CopyIndicatorBuffer(indicator_handle[i], 0, 0, bars); //copy indicator buffer
      dataset.Col(buffer, i); //add the indicator buffer values to the dataset matrix 
    }

//---
  
   vector y(bars);
   MqlRates rates[];
   CopyRates(Symbol(), PERIOD_CURRENT,0,bars, rates);
   for (int i=0; i<bars; i++) //Creating the target variable 
     {
       if (rates[i].close > rates[i].open) //if bullish candle assign 1 to the y variable else assign the 0 class
        y[i] = 1;
       else
        y[0] = 0;
     }  
     
//---
   
   dataset.Col(y, dataset.Cols()-1); //add the y variable to the last column
   
//---

   matrix x_train, x_test;
   vector y_train, y_test;
      
   MatrixExtend::TrainTestSplitMatrices(dataset,x_train,y_train,x_test,y_test,training_sample_size,random_state_);
   
   matrix x_transformed = {};
   switch(dimension_reduction)
     {
      case  LDA:
         
         lda = new CLDA(NULL);
         
         x_transformed = lda.fit_transform(x_train, y_train); //Transform the training data   
         
        break;
      case PCA:
      
         pca = new CPCA(NULL);
         
         x_transformed = pca.fit_transform(x_train);
         
        break;
     }
     
   
   classifier_tree = new CDecisionTreeClassifier();
   classifier_tree.fit(x_transformed, y_train); //Train the model using the transformed data
   
   vector preds = classifier_tree.predict(x_transformed); //Make predictions using the transformed data
   
   Print("Train accuracy: ",Metrics::confusion_matrix(y_train, preds).accuracy);
   
   switch(dimension_reduction)
     {
      case  LDA:
        
        x_transformed = lda.transform(x_test); //Transform the testing data   
        
        break;
        
      case PCA:
        
        x_transformed = pca.transform(x_test); 
        
        break;
     }
   preds = classifier_tree.predict(x_transformed);
   
   Print("Test accuracy: ",Metrics::confusion_matrix(y_test, preds).accuracy);
   
 }

После обучения данных их необходимо протестировать. Ниже приведены результаты обоих методов. Первым идет LDA: 

JK      0       01:00:24.440    LDA Test (EURUSD,H1)    
GK      0       01:00:37.442    LDA Test (EURUSD,H1)    Confusion Matrix
QR      0       01:00:37.442    LDA Test (EURUSD,H1)    [[60,266]
FF      0       01:00:37.442    LDA Test (EURUSD,H1)     [46,328]]
DR      0       01:00:37.442    LDA Test (EURUSD,H1)    
RN      0       01:00:37.442    LDA Test (EURUSD,H1)    Classification Report
FE      0       01:00:37.442    LDA Test (EURUSD,H1)    
LP      0       01:00:37.442    LDA Test (EURUSD,H1)    _    Precision  Recall  Specificity  F1 score  Support
HD      0       01:00:37.442    LDA Test (EURUSD,H1)    0.0    0.57     0.18     0.88       0.28     326.0
FI      0       01:00:37.442    LDA Test (EURUSD,H1)    1.0    0.55     0.88     0.18       0.68     374.0
RM      0       01:00:37.442    LDA Test (EURUSD,H1)    
QH      0       01:00:37.442    LDA Test (EURUSD,H1)    Accuracy                                   0.55
KQ      0       01:00:37.442    LDA Test (EURUSD,H1)    Average   0.56    0.53    0.53      0.48    700.0
HP      0       01:00:37.442    LDA Test (EURUSD,H1)    W Avg     0.56    0.55    0.51      0.49    700.0
KK      0       01:00:37.442    LDA Test (EURUSD,H1)    Train accuracy: 0.554
DR      0       01:00:37.443    LDA Test (EURUSD,H1)    Confusion Matrix
CD      0       01:00:37.443    LDA Test (EURUSD,H1)    [[20,126]
LO      0       01:00:37.443    LDA Test (EURUSD,H1)     [12,142]]
OK      0       01:00:37.443    LDA Test (EURUSD,H1)    
ME      0       01:00:37.443    LDA Test (EURUSD,H1)    Classification Report
QN      0       01:00:37.443    LDA Test (EURUSD,H1)    
GI      0       01:00:37.443    LDA Test (EURUSD,H1)    _    Precision  Recall  Specificity  F1 score  Support
JM      0       01:00:37.443    LDA Test (EURUSD,H1)    0.0    0.62     0.14     0.92       0.22     146.0
KR      0       01:00:37.443    LDA Test (EURUSD,H1)    1.0    0.53     0.92     0.14       0.67     154.0
MF      0       01:00:37.443    LDA Test (EURUSD,H1)    
MQ      0       01:00:37.443    LDA Test (EURUSD,H1)    Accuracy                                   0.54
MJ      0       01:00:37.443    LDA Test (EURUSD,H1)    Average   0.58    0.53    0.53      0.45    300.0
OI      0       01:00:37.443    LDA Test (EURUSD,H1)    W Avg     0.58    0.54    0.52      0.45    300.0
QP      0       01:00:37.443    LDA Test (EURUSD,H1)    Test accuracy: 0.54

PCA показал лучшие результаты во время обучения и немного хуже при тестировании:

GE      0       01:01:57.202    LDA Test (EURUSD,H1)    
MS      0       01:01:57.202    LDA Test (EURUSD,H1)    Classification Report
IH      0       01:01:57.202    LDA Test (EURUSD,H1)    
OS      0       01:01:57.202    LDA Test (EURUSD,H1)    _    Precision  Recall  Specificity  F1 score  Support
KG      0       01:01:57.202    LDA Test (EURUSD,H1)    0.0    0.62     0.28     0.85       0.39     326.0
GL      0       01:01:57.202    LDA Test (EURUSD,H1)    1.0    0.58     0.85     0.28       0.69     374.0
MP      0       01:01:57.202    LDA Test (EURUSD,H1)    
JK      0       01:01:57.202    LDA Test (EURUSD,H1)    Accuracy                                   0.59
HL      0       01:01:57.202    LDA Test (EURUSD,H1)    Average   0.60    0.57    0.57      0.54    700.0
CG      0       01:01:57.202    LDA Test (EURUSD,H1)    W Avg     0.60    0.59    0.55      0.55    700.0
EF      0       01:01:57.202    LDA Test (EURUSD,H1)    Train accuracy: 0.586
HO      0       01:01:57.202    LDA Test (EURUSD,H1)    Confusion Matrix
GG      0       01:01:57.202    LDA Test (EURUSD,H1)    [[26,120]
GJ      0       01:01:57.202    LDA Test (EURUSD,H1)     [29,125]]
KN      0       01:01:57.202    LDA Test (EURUSD,H1)    
QJ      0       01:01:57.202    LDA Test (EURUSD,H1)    Classification Report
MQ      0       01:01:57.202    LDA Test (EURUSD,H1)    
CL      0       01:01:57.202    LDA Test (EURUSD,H1)    _    Precision  Recall  Specificity  F1 score  Support
QP      0       01:01:57.202    LDA Test (EURUSD,H1)    0.0    0.47     0.18     0.81       0.26     146.0
GE      0       01:01:57.202    LDA Test (EURUSD,H1)    1.0    0.51     0.81     0.18       0.63     154.0
QI      0       01:01:57.202    LDA Test (EURUSD,H1)    
MD      0       01:01:57.202    LDA Test (EURUSD,H1)    Accuracy                                   0.50
RE      0       01:01:57.202    LDA Test (EURUSD,H1)    Average   0.49    0.49    0.49      0.44    300.0
IL      0       01:01:57.202    LDA Test (EURUSD,H1)    W Avg     0.49    0.50    0.49      0.45    300.0
PP      0       01:01:57.202    LDA Test (EURUSD,H1)    Test accuracy: 0.503

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

void OnTick()
  {
//---

   if (!train_once) //call the function to train the model once on the program lifetime
    {
      TrainTest();
      train_once = true; 
    }

//--- 
   
   vector inputs(indicator_handle.Size());
   vector buffer;
   
   for (uint i=0; i<indicator_handle.Size(); i++)
     {
       buffer.CopyIndicatorBuffer(indicator_handle[i], 0, 0, 1); //copy the current indicator value
       inputs[i] = buffer[0]; //add its value to the inputs vector 
     }

//---
    
    SymbolInfoTick(Symbol(), ticks);
      
     if (isnewBar(PERIOD_CURRENT)) // We want to trade on the bar opening 
      {
        vector transformed_inputs = {};
         switch(dimension_reduction) //transform every new data to fit the dimensions selected during training
           {
            case  LDA:
               transformed_inputs = lda.transform(inputs); //Transform the new data   
              break;
            case PCA:
               transformed_inputs = pca.transform(inputs);
              break;
           }
     
        int signal = (int)classifier_tree.predict(transformed_inputs);
        double min_lot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
        SymbolInfoTick(Symbol(), ticks);
        
         if (signal == -1)
           {
              if (!PosExists(MAGICNUMBER, POSITION_TYPE_SELL)) // If a sell trade doesnt exist
                m_trade.Sell(min_lot, Symbol(), ticks.bid, ticks.bid+stoploss*Point(), ticks.bid - takeprofit*Point());
           }
         else
           {
             if (!PosExists(MAGICNUMBER, POSITION_TYPE_BUY))  // If a buy trade doesnt exist
               m_trade.Buy(min_lot, Symbol(), ticks.ask, ticks.ask-stoploss*Point(), ticks.ask + takeprofit*Point());
           }
      }
  }

Я тестировал в режиме цен открытия с января 2023 года по февраль 2024 года. Оба метода работали на простой стратегии:

Линейный дискриминантный анализ (LDA):

Тестирование с анализом главных компонентов (PCA):

Результаты почти идентичны: модель с LDA принесла убыток на 8 долларов больше, чем PCA. С точки зрения работы с данными, тестер стратегий менее релевантен для методов сокращения размерностей, поскольку их основная задача — для упрощения переменных, особенно при работе с большими данными. Также при запуске этого советника в тестере стратегий я столкнулся с некоторыми несоответствиями в расчетах, вызванными неожиданными ошибками в работе матричных и векторных методов. Я рекомендую запускать программу несколько раз, пока не получите значимый результат, если вдруг столкнетесь с ошибками.

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

Необходимость нормализации данных из алгоритмов PCA или LDA для модели машинного обучения зависит от конкретных характеристик вашей выборки, используемого алгоритма и целей. Что нужно учитывать:

  • Преобразование — методы работают с ковариационной матрицей исходных признаков и находят ортогональные компоненты (главные компоненты), которые передают максимальную дисперсию данных. Преобразованные данные, полученные этими методами, состоят из этих основных компонентов.
  • Нормализация до использования PCA или LDA. Часто нормализуют исходные объектов перед выполнением метода PCA или LDA, особенно если объекты имеют разные масштабы или единицы измерения. Нормализация гарантирует, что все признаки вносят равный вклад в ковариационную матрицу, и предотвращает доминирование признаков с более крупными масштабами над главными компонентами.
  • Нормализация после методов PCA или LDA. Необходимость нормализовать преобразованные данные зависит от конкретных требований вашего алгоритма машинного обучения и характеристик преобразованных признаков. Некоторые алгоритмы, такие как логистическая регрессия или k ближайших соседей, чувствительны к различиям в масштабах признаков, для них может быть полезным нормализовать признаки даже после методов PCA или LDA.

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

  • Влияние нормализации на интерпретируемость. Нормализация после методов LDA и PCA может повлиять на интерпретируемость главных компонентов. Если вам нужно понимать вклад исходных признаков в основные компоненты, нормализация преобразованных данных может скрыть эти взаимосвязи.
  • Влияние на производительность. Поэкспериментируйте как с нормализованными, так и с ненормализованными преобразованными данными, чтобы оценить влияние на производительность модели. В некоторых случаях нормализация может привести к лучшей сходимости, улучшению обобщения или более быстрому обучению, тогда как в других случаях она может практически не иметь эффекта.


За развитием этой модели машинного обучения и многого другого из этой серии статей можно следить в моем репозиторий на GitHub.

Содержимое вложения:

Файл Описание/назначение
tree.mqh   Модель классификатора дерева решений. 
MatrixExtend.mqh    Дополнительные функции для работы с матрицами.
metrics.mqh         Функции и код для измерения производительности моделей машинного обучения. 
preprocessing.mqh         Библиотека для предварительной обработки необработанных входных данных, чтобы сделать их пригодными для использования в моделях машинного обучения.
base.mqh  Базовая библиотека для использования методов PCA и LDA, содержит функции, упрощающие написание кода.
pca.mqh  Библиотека метода анализа главных компонентов PCA
lda.mqh  Библиотека метода линейного дискриминантного анализа LDA
plots.mqh  Библиотека для построения векторов и матриц
lda vs pca script.mq5     Скрипт для демонстрации алгоритмов pca и lda
LDA Test.mq5  Основной советник для тестирования большей части кода 
iris.csv    Набор данных для проверки модели iris 


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

Прикрепленные файлы |
Code.zip (30.34 KB)
Нейросети это просто (Часть 97): Обучение модели с использованием MSFformer Нейросети это просто (Часть 97): Обучение модели с использованием MSFformer
При изучении различных архитектур построения моделей мы мало уделяем внимания процессу обучения моделей. В этой статье я попытаюсь восполнить этот пробел.
Разработка торгового робота на Python (Часть 3): Реализация торгового алгоритма на основе модели Разработка торгового робота на Python (Часть 3): Реализация торгового алгоритма на основе модели
Продолжаем цикл статей по созданию торгового робота на Python и MQL5. Сегодня решим задачу создания торгового алгоритма на Python.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Алгоритм поиска в окрестности — Across Neighbourhood Search (ANS) Алгоритм поиска в окрестности — Across Neighbourhood Search (ANS)
Статья раскрывает потенциал алгоритма ANS, как важного шага в развитии гибких и интеллектуальных методов оптимизации, способных учитывать специфику задачи и динамику окружающей среды в пространстве поиска.