English 中文 Español Deutsch 日本語
preview
Взаимная информация как критерий для поэтапного отбора признаков

Взаимная информация как критерий для поэтапного отбора признаков

MetaTrader 5Статистика и анализ |
261 0
Francis Dube
Francis Dube

Введение

Взаимная информация является ценным инструментом для выявления эффективных предикторов, особенно когда речь идет о сложных, нелинейных взаимосвязях. Он может выявлять зависимости, которые могут быть упущены другими методами, что делает его особенно подходящим для моделей, которые могут использовать такие сложности. В настоящей статье рассматривается применение взаимной информации при выборе признаков, основное внимание уделяется алгоритму, предложенному Ханчуан Пенгом (Hanchuan Peng), Фухуэй Лонгом (Fuhui Long) и Крисом Дином (Chris Ding) в их исследовательской работе под названием "Выбор признаков на основе взаимной информации: Критерии максимальной зависимости, максимальной релевантности и минимальной избыточности» ("Feature Selection Based on Mutual Information: Criteria of Max-Dependency, Max-Relevance, and Min-Redundancy").

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


Оценка взаимной информации непрерывных переменных

Взаимная информация, обозначаемая как I(X;Y), количественно определяет общую информацию между двумя случайными величинами, X и Y. Для дискретных переменных ее вычисление относительно просто и включает суммирование общих и предельных вероятностей.

Discrete Mutual Information

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

Continuous Mutual Information

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

Parzen Density Approximation

Этот метод особенно полезен в сценариях, где базовое распределение неизвестно или является сложным. Адаптируя форму и размер окна, мы можем повысить плавность и точность оценки плотности. Однако важно отметить, что выбор параметров окна может существенно повлиять на качество оценки. Эффективность оценки плотности Парцена зависит от выбора подходящей весовой функции, которая в идеале должна быть равна единице и демонстрировать быстрое затухание по мере удаления аргумента от нуля. Популярным выбором для этой весовой функции является Гауссовская функция, характеризуемая масштабным параметром sigma.

Gaussian Weight Function

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

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

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

Adaptive Partitioning

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

Чтобы определить, следует ли далее разбивать раздел на подразделы, используется тест хи-квадрат. Этот тест оценивает независимость между двумя переменными внутри раздела. Раздел разделен на четыре подраздела, создавая таблицу сопряженности 2x2. Подсчитывается количество точек данных, попадающих в каждый из четырех подразделов. В соответствии с нулевой гипотезой о независимости двух переменных ожидаемое количество точек данных в каждом подразделе рассчитывается на основе предельных итоговых значений. Затем вычисленная статистика хи-квадрат сравнивается с критическим значением из распределения хи-квадрат с 1 степенью свободы. Если вычисленное значение превышает критическое, нулевая гипотеза независимости отклоняется, что указывает на существенную взаимосвязь между двумя переменными в рамках раздела.

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

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

Если тест хи-квадрат 2х2 или 4х4 указывает на неравномерное распределение в пределах раздела, раздел делится на четыре меньших подраздела. Эти подразделы обрабатываются по-разному путем проверки их размера. Если подраздел слишком мал, он считается конечным. Вычисляется его вклад в общую взаимную информацию, и раздел больше не рассматривается для дальнейшего разделения. В противном случае, если подраздел достаточно велик, он помечается как кандидат на будущее дополнительное разделение.

Стандартная статистика по тесту хи-квадрат два на два приведена в указанном ниже уравнении.

2 by 2 Chi-Squate test statistic


Адаптивное разбиение на MQL5

Класс Capm в mutualinfo.mqh реализует алгоритм адаптивного разделения для оценки взаимной информации для непрерывных переменных. Для управления рекурсивным процессом разбиения он использует пользовательскую структуру IntStack. Эта структура содержит информацию о разделах и любых подразделах, содержащихся в ней. Структура состоит из шести членов, подробно описанных ниже:

  • Xstart и Xstop: Они определяют диапазон индексов для одной переменной в пределах текущего охватывающего раздела.
  • Ystart и Ystop: Они определяют диапазон индексов для второй переменной в пределах текущего охватывающего раздела.
  • DataStart и DataStop: Они задают начальный и конечный индексы точек данных внутри охватывающего раздела, размечая подразделы.
//+------------------------------------------------------------------+
//| int type stack structure                                         |
//+------------------------------------------------------------------+
struct IntStack
  {
   int Xstart ;     // X value (rank) at which this rectangle starts
   int Xstop ;      // And stops
   int Ystart ;     // Ditto for Y
   int Ystop ;
   int DataStart ;  // Starting index into indices for the cases in this
   int DataStop ;   // rectangle, and the (inclusive) ending index
  };

Конструктор Capm принимает три аргумента:

  • no_split: Логический флаг, определяющий, как обрабатывать значения, которые приблизительно равны. Если установлено значение true, алгоритм гарантирует, что такие значения будут сгруппированы во время разбиения, предотвращая их разделение по разным разделам.
  • dep_vals: Вектор, содержащий значения одной из анализируемых переменных. Эта переменная, как правило, является той, взаимная информация о которой будет рассчитываться на основе множества других переменных.
  • chi_critical_value: Пороговое значение для тестов хи-квадрат, используемых в процессе разбиения. Это значение определяет уровень значимости для тестов и влияет на глубину разбиения. Как правило, подходят значения от 4,0 до 8,0, при этом 6,0 является обычным выбором с универсальной полезностью.
//+------------------------------------------------------------------+
//|  constructor                                                     |
//+------------------------------------------------------------------+
Capm::Capm(bool no_split,vector &dep_vals, double chi_critical_value = 6.0)
  {
   m_no_split = no_split;
   m_size = int(dep_vals.Size()) ;
   m_chi_crit = chi_critical_value;

   if(ArrayResize(m_indices,m_size)<0 || !m_tempvals.Resize(m_size) || ArrayResize(m_ranks,m_size)<0
      || (m_no_split && ArrayResize(m_tied_ranks,m_size)<0))
     {
      Print(__FUNCTION__, " Arrayresize error ", GetLastError());
      return;
     }

   for(int i=0 ; i<m_size ; i++)
     {
      m_tempvals[i] = dep_vals[i] ;
      m_indices[i] = i ;
     }

   qsortdsi(0, m_size-1, m_tempvals, m_indices) ;

   for(int i=0 ; i<m_size ; i++)
     {
      m_ranks[m_indices[i]] = i ;
      if(! m_no_split)
         continue ;
      if(i < m_size-1  &&
         m_tempvals[i+1] - m_tempvals[i] < 1.e-12 * (1.0+fabs(m_tempvals[i])+fabs(m_tempvals[i+1])))
         m_tied_ranks[i] = 1 ;
      else
         m_tied_ranks[i] = 0 ;
     }
  }

Конструктор класса Capm начинается с установки флага no_split, который определяет, следует ли учитывать связи в данных, и параметра chi_critical_value, который устанавливает пороговое значение для теста хи-квадрат, используемого для выявления значимых взаимосвязей между переменными. Затем конструктор выделяет память для массивов для хранения данных, рангов и связанных значений. Он копирует значения переменных во временный вектор и сортирует их. Исходные индексы точек данных сохраняются в массиве m_indices. Наконец, конструктор присваивает ранги отсортированным точкам данных и идентифицирует любые связанные значения, отмечая их в массиве m_tied_ranks, если установлен флаг no_split.

//+------------------------------------------------------------------+
//| Mutual information using the Adaptive partitioning method        |
//+------------------------------------------------------------------+
class Capm
  {

public:
                     Capm(bool no_split,vector &dep_vals, double chi_critical_value = 6.0) ;
                    ~Capm(void) ;
   double            fit(vector &raw) ;

private:
   bool               m_no_split;  // respect ties
   int m_size ;                        // Number of cases
   int m_ranks[] ;                     // 'Dependent' variable ranks
   int m_tied_ranks[] ;                // tied[i] != 0 if case with rank i == case with rank i+1
   double m_chi_crit ;                 // Chi-square test criterion
   int               m_indices[];      // indices
   vector            m_tempvals;       // temp values
  } ;

Метод fit() в классе Capm предназначен для оценки взаимной информации между одной переменной (предоставленной конструктору) и другой переменной, предоставленной в качестве аргумента. В качестве входных данных он принимает один вектор, представляя значения второй переменной. Этот вектор должен иметь тот же размер, что и вектор, предоставленный конструктору. Вызывая метод fit() множество раз с разными векторами, мы можем эффективно вычислять взаимную информацию между одной переменной и различными переменными.

//+------------------------------------------------------------------+
//|  mutual information for continuous variables                     |
//+------------------------------------------------------------------+
double Capm::fit(vector &raw)
  {
   int i, k, ix, iy, nstack, splittable ;
   int current_indices[], x[], fullXstart, fullXstop, fullYstart, fullYstop, ipos ;
   int trialXstart[4], trialXstop[4], trialYstart[4], trialYstop[4] ;
   int ipx, ipy, xcut[4], ycut[4], iSubRec, x_tied[], ioff ;
   int X_AllTied, Y_AllTied ;
   int centerX, centerY, currentDataStart, currentDataStop ;
   int actual[4], actual44[16] ;
   vector expected(16), xfrac(4), yfrac(4) ;
   double diff, testval;
   double px, py, pxy, MI ;

   IntStack stack[];

   if(ArrayResize(stack,1,256)<0)
     {
      Print(__FUNCTION__," arrayresize error ", GetLastError());
      return EMPTY_VALUE;
     }

   if(ArrayResize(current_indices,m_size)<0 || ArrayResize(x,m_size)<0
      || (m_no_split && ArrayResize(x_tied,m_size)<0))
     {
      Print(__FUNCTION__, " Arrayresize error ", GetLastError());
      return EMPTY_VALUE;
     }

   for(i=0 ; i<m_size ; i++)
     {
      m_tempvals[i] = raw[i] ;
      m_indices[i] = i ;
     }

   if(!qsortdsi(0, m_size-1, m_tempvals, m_indices))
     {
      Print(__FUNCTION__, " Arrayresize error ", GetLastError());
      return EMPTY_VALUE;
     }

   for(i=0 ; i<m_size ; i++)
     {
      x[m_indices[i]] = i ;
      if(! m_no_split)
         continue ;
      if(i < m_size-1  &&
         m_tempvals[i+1] - m_tempvals[i] < 1.e-12 * (1.0+fabs(m_tempvals[i])+fabs(m_tempvals[i+1])))
         x_tied[i] = 1 ;
      else
         x_tied[i] = 0 ;
     }

   for(i=0 ; i<m_size ; i++)
     {
      m_indices[i] = i ;
     }

   stack[0].Xstart = 0 ;
   stack[0].Xstop = m_size-1 ;
   stack[0].Ystart = 0 ;
   stack[0].Ystop = m_size-1 ;
   stack[0].DataStart = 0 ;
   stack[0].DataStop = m_size-1 ;
   nstack = 1 ;

   MI = 0.0 ;
   while(nstack > 0)
     {

      --nstack ;
      fullXstart = stack[nstack].Xstart ;
      fullXstop = stack[nstack].Xstop ;
      fullYstart = stack[nstack].Ystart ;
      fullYstop = stack[nstack].Ystop ;
      currentDataStart = stack[nstack].DataStart ;
      currentDataStop = stack[nstack].DataStop ;


      centerX = (fullXstart + fullXstop) / 2 ;
      X_AllTied = (x_tied.Size())  && (x_tied[centerX] != 0) ;
      if(X_AllTied)
        {
         for(ioff=1 ; centerX-ioff >= fullXstart ; ioff++)
           {
            if(! x_tied[centerX-ioff])
              {
               X_AllTied = 0 ;
               centerX -= ioff ;
               break ;
              }
            if(centerX + ioff == fullXstop)
               break ;
            if(! x_tied[centerX+ioff])
              {
               X_AllTied = 0 ;
               centerX += ioff ;
               break ;
              }
           }
        }

      centerY = (fullYstart + fullYstop) / 2 ;
      Y_AllTied = (m_tied_ranks.Size())  && (m_tied_ranks[centerY] != 0) ;
      if(Y_AllTied)
        {
         for(ioff=1 ; centerY-ioff >= fullYstart ; ioff++)
           {
            if(! m_tied_ranks[centerY-ioff])
              {
               Y_AllTied = 0 ;
               centerY -= ioff ;
               break ;
              }
            if(centerY + ioff == fullYstop)
               break ;
            if(! m_tied_ranks[centerY+ioff])
              {
               Y_AllTied = 0 ;
               centerY += ioff ;
               break ;
              }
           }
        }

      if(X_AllTied  ||  Y_AllTied)
         splittable = 0 ;
      else
        {
         trialXstart[0] = trialXstart[1] = fullXstart ;
         trialXstop[0] = trialXstop[1] = centerX ;
         trialXstart[2] = trialXstart[3] = centerX+1 ;
         trialXstop[2] = trialXstop[3] = fullXstop ;
         trialYstart[0] = trialYstart[2] = fullYstart ;
         trialYstop[0] = trialYstop[2] = centerY ;
         trialYstart[1] = trialYstart[3] = centerY+1 ;
         trialYstop[1] = trialYstop[3] = fullYstop ;

         for(i=0 ; i<4 ; i++)
            expected[i] = (currentDataStop - currentDataStart + 1) *
                          (trialXstop[i]-trialXstart[i]+1.0) / (fullXstop-fullXstart+1.0) *
                          (trialYstop[i]-trialYstart[i]+1.0) / (fullYstop-fullYstart+1.0) ;

         actual[0] = actual[1] = actual[2] = actual[3] = 0 ;
         for(i=currentDataStart ; i<=currentDataStop ; i++)
           {
            k = m_indices[i] ;
            if(x[k] <= centerX)
              {
               if(m_ranks[k] <= centerY)
                  ++actual[0] ;
               else
                  ++actual[1] ;
              }
            else
              {
               if(m_ranks[k] <= centerY)
                  ++actual[2] ;
               else
                  ++actual[3] ;
              }
           }

         testval = 0.0 ;
         for(i=0 ; i<4 ; i++)
           {
            diff = fabs(actual[i] - expected[i]) - 0.5 ;
            testval += diff * diff / expected[i] ;
           }

         splittable = (testval > m_chi_crit)  ?  1 : 0 ;

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

if(! splittable && fullXstop-fullXstart > 30 && fullYstop-fullYstart > 30)
           {
            ipx = fullXstart - 1 ;
            ipy = fullYstart - 1 ;
            for(i=0 ; i<4 ; i++)
              {
               xcut[i] = (fullXstop - fullXstart + 1) * (i+1) / 4 + fullXstart - 1 ;
               xfrac[i] = (xcut[i] - ipx) / (fullXstop - fullXstart + 1.0) ;
               ipx = xcut[i] ;
               ycut[i] = (fullYstop - fullYstart + 1) * (i+1) / 4 + fullYstart - 1 ;
               yfrac[i] = (ycut[i] - ipy) / (fullYstop - fullYstart + 1.0) ;
               ipy = ycut[i] ;
              }
            for(ix=0 ; ix<4 ; ix++)
              {
               for(iy=0 ; iy<4 ; iy++)
                 {
                  expected[ix*4+iy] = xfrac[ix] * yfrac[iy] *
                                      (currentDataStop - currentDataStart + 1) ;
                  actual44[ix*4+iy] = 0 ;
                 }
              }
            for(i=currentDataStart ; i<=currentDataStop ; i++)
              {
               k = m_indices[i] ;
               for(ix=0 ; ix<3 ; ix++)
                 {
                  if(x[k] <= xcut[ix])
                     break ;
                 }
               for(iy=0 ; iy<3 ; iy++)
                 {
                  if(m_ranks[k] <= ycut[iy])
                     break ;
                 }
               ++actual44[ix*4+iy] ;
              }
            testval = 0.0 ;
            for(ix=0 ; ix<4 ; ix++)
              {
               for(iy=0 ; iy<4 ; iy++)
                 {
                  diff = fabs(actual44[ix*4+iy] - expected[ix*4+iy]) - 0.5 ;
                  testval += diff * diff / expected[ix*4+iy] ;
                 }
              }
            splittable = (testval > 3 * m_chi_crit)  ?  1 : 0 ;
           }
        }

      if(splittable)
        {

         for(i=currentDataStart ; i<=currentDataStop ; i++)
            current_indices[i] = m_indices[i] ;

         ipos = currentDataStart ;
         for(iSubRec=0 ; iSubRec<4 ; iSubRec++)
           {

            if(actual[iSubRec] >= 3)
              {
               stack[nstack].Xstart = trialXstart[iSubRec] ;
               stack[nstack].Xstop = trialXstop[iSubRec] ;
               stack[nstack].Ystart = trialYstart[iSubRec] ;
               stack[nstack].Ystop = trialYstop[iSubRec] ;
               stack[nstack].DataStart = ipos ;
               stack[nstack].DataStop = ipos + actual[iSubRec] - 1 ;
               ++nstack ;
               if(ArrayResize(stack,nstack+1,256)<0)
                 {
                   Print(__FUNCTION__," arrayresize error ", GetLastError());
                   return EMPTY_VALUE;
                 }

               if(iSubRec == 0)
                 {
                  for(i=currentDataStart ; i<=currentDataStop ; i++)
                    {
                     k = current_indices[i] ;
                     if(x[k] <= centerX  &&  m_ranks[k] <= centerY)
                        m_indices[ipos++] = current_indices[i] ;
                    }
                 }
               else
                  if(iSubRec == 1)
                    {
                     for(i=currentDataStart ; i<=currentDataStop ; i++)
                       {
                        k = current_indices[i] ;
                        if(x[k] <= centerX  &&  m_ranks[k] > centerY)
                           m_indices[ipos++] = current_indices[i] ;
                       }
                    }
                  else
                     if(iSubRec == 2)
                       {
                        for(i=currentDataStart ; i<=currentDataStop ; i++)
                          {
                           k = current_indices[i] ;
                           if(x[k] > centerX  &&  m_ranks[k] <= centerY)
                              m_indices[ipos++] = current_indices[i] ;
                          }
                       }
                     else
                       {
                        for(i=currentDataStart ; i<=currentDataStop ; i++)
                          {
                           k = current_indices[i] ;
                           if(x[k] > centerX  &&  m_ranks[k] > centerY)
                              m_indices[ipos++] = current_indices[i] ;
                          }
                       }
              }

            else
              {
               if(actual[iSubRec] > 0)
                 {
                  px = (trialXstop[iSubRec] - trialXstart[iSubRec] + 1.0) / m_size ;
                  py = (trialYstop[iSubRec] - trialYstart[iSubRec] + 1.0) / m_size ;
                  pxy = (double) actual[iSubRec] / m_size ;
                  MI += pxy * log(pxy / (px * py)) ;
                 }
              }
           }
        }
      else
        {
         px = (fullXstop - fullXstart + 1.0) / m_size ;
         py = (fullYstop - fullYstart + 1.0) / m_size ;
         pxy = (currentDataStop - currentDataStart + 1.0) / m_size ;
         MI += pxy * log(pxy / (px * py)) ;
        }
     }

   return MI;

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

Mutual Information Contribution

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


Отбор предиктора с использованием взаимной информации

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

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

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

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

Пэнг (Peng), Лонг (Long) и Дин (Ding) предложили алгоритм отбора признаков, основанный на взаимной информации, известный как критерий максимальной релевантности и минимальной избыточности (MRMR). Этот алгоритм эффективно аппроксимирует оптимальное подмножество признаков без явного вычисления совместной зависимости, что является трудноразрешимым с точки зрения вычислений. Критерий MRMR основан на концепции релевантности и избыточности. Соответствие набора признаков S целевой переменной Y определяется как среднее значение взаимной информации между Y и каждым признаком в S.

Relevance

Здесь ∣S∣ - количество признаков в S, а I(Xi,Y) - взаимная информация между признаками Xi и Y.

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

Избыточность потенциального предиктора Xi по отношению к уже отобранному набору признаков S определяется как средняя взаимная информация между Xi и каждым признаком в S:

Redundancy

Где I(Xi,Xj) - это взаимная информация между потенциальным признаком Xi и признаком Xj, уже имеющимся в S.

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

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

Подводя итог, можно сказать, что алгоритм MRMR работает следующим образом:

  1. В качестве первого признака отбирается признак с наибольшей взаимной информацией с целевой переменной Y.
  2. На каждом последующем этапе алгоритм отбирает признак, максимизирующий следующий критерий.

MRMR Criterion

Этот критерий уравновешивает релевантность признака целевой переменной Y (т.е. I(X_i, Y)) с его избыточностью по отношению к уже отобранному признаку. Замечательным аспектом алгоритма MRMR является то, что он эффективно аппроксимирует оптимальное подмножество признаков, даже несмотря на то, что прямая оптимизация совместной зависимости является трудноразрешимой с точки зрения вычислений. Этот результат, хотя и не очевиден сразу, математически доказан в оригинальной статье.

Для оценки статистической значимости отобранных признаков можно использовать два теста на перестановку Монте-Карло (MCP):

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


Реализация поэтапного отбора на MQL5 на основе взаимной информации

Класс Cmrmr в mutualinfo.mqh реализует алгоритм MRMR и принимает в своем конструкторе следующие параметры:
  • num_reps: Количество повторений для теста на перестановку Монте-Карло. Установка этого параметра на значение, меньшее или равное 1, отключает тесты.
  • max_preds: Максимальное количество отбираемых признаков.
  • chisquare_thresh: Пороговое значение теста хи-квадрат для метода адаптивного разбиения, используемого для оценки взаимной информации.
  • m_verbose: Логический флаг, определяющий, следует ли отображать подробные выходные данные во время выполнения алгоритма.
//+-----------------------------------------------------------------------+
//|Relevance minus redundancy for building an optimal subset of predictors|
//+-----------------------------------------------------------------------+
class Cmrmr
  {
private:
   int               m_max_preds;
   int               m_reps;
   bool              m_verbose;
   int               m_samples;
   int               m_vars;
   matrix            m_preds;
   vector            m_target;
   vector            m_crits;
   double            m_chisquare_thresh;
   ulong             m_selected_vars[];
   matrix            m_critical_values;
   vector            mutualinfo(int which_size,int &which[],vector &targs);
public:
                     Cmrmr(int num_reps,int max_preds, double chisquare_thresh,bool verbose);
                    ~Cmrmr(void);
   bool              StepWise(matrix &predictors, vector &targets);
   matrix            GetCriticalValues(void);
   bool              GetSelectedVars(ulong &output[]);
  };

Чтобы инициировать процесс отбора признаков с помощью класса Cmrmr, необходимо инстанцировать объект этого класса, а затем вызвать его метод StepWise(). Метод StepWise() использует два основных входных сигнала:

  • Матрица потенциальных предикторных переменных: Эта матрица должна содержать данные по всем потенциальным признакам, которые должен рассмотреть практик.
  • Вектор целевой переменной: Этот вектор должен содержать значения целевой переменной.

Метод возвращает логическое значение, указывающее на успех или неудачу процесса отбора признаков. Метод StepWise() в классе Cmrmr инициализирует необходимые структуры данных, а затем переходит к циклу тестирования MCP. Если MCP-тесты не запрашиваются (`num_reps` меньше или равно 1), цикл выполняется только один раз. В рамках цикла метод вычисляет взаимную информацию между каждым потенциальным предиктором и целевой переменной, используя экземпляр класса Capm.

//+------------------------------------------------------------------+
//|  Stepwise feature selection based on mutual information          |
//+------------------------------------------------------------------+
bool Cmrmr::StepWise(matrix &predictors,vector &targets)
  {
   if(m_selected_vars.Size())
      ArrayFree(m_selected_vars);

   m_preds = predictors;
   m_samples = int(m_preds.Rows());
   m_vars = int(m_preds.Cols());
   m_max_preds = m_max_preds>=m_vars?m_vars:m_max_preds;
   m_target = targets;
   
   
   int i, j, k, ivar, irep;
   int index[], stepwise_mcpt_count[], solo_mcpt_count[], stepwise_ivar[], which_preds[],original_stepwise_ivar[] ;

   int nkept,best_ivar;
   vector casework(m_samples), sorted(m_samples), mutual(m_vars);
   vector crit(m_vars), relevance(m_vars), original_relevance(m_vars), current_crits(m_vars), sorted_crits(m_vars);
   double best_crit, dtemp, group_pvalue,solo_pvalue;
   vector stepwise_crit(m_vars), original_stepwise_crit(m_vars);
   double sum_relevance;
   vector original_sum_relevance(m_vars), sum_redundancy(m_vars);
   vector target = m_target;

   best_crit = -DBL_MAX;
   best_ivar = -1;
   nkept = m_max_preds;

   if(ArrayResize(index,m_vars)<0 || ArrayResize(stepwise_mcpt_count,m_vars)<0 ||
      ArrayResize(solo_mcpt_count,m_vars)<0 || ArrayResize(which_preds,m_vars)<0 || ArrayResize(stepwise_ivar,m_vars)<0 ||
      ArrayResize(original_stepwise_ivar,m_vars)<0)
     {
      Print(__FUNCTION__," array resize error ", GetLastError());
      return false;
     }

   int unif_error;
   for(irep=0 ; irep<m_reps ; irep++)
     {
      if(irep)
        {
         i = m_samples ;
         while(i > 1)
           {
            j = (int)(MathRandomUniform(0.0,1.0,unif_error) * i);
            if(unif_error)
              {
               Print(__FUNCTION__," Mathrandomuniform error ", GetLastError());
               return false;
              }
            if(j >= i)
               j = i - 1 ;
            dtemp = target[--i] ;
            target[i] = target[j] ;
            target[j] = dtemp ;
           }
        }

      for(i=0 ; i<m_vars ; i++)
         which_preds[i] = i ;

      crit = mutualinfo(m_vars,which_preds,target);

      for(ivar=0 ; ivar<m_vars ; ivar++)
        {
         relevance[ivar] = crit[ivar] ;
         if(ivar == 0  ||  crit[ivar] > best_crit)
           {
            best_crit = crit[ivar] ;
            best_ivar = ivar ;
           }
        }

      stepwise_crit[0] = best_crit ;
      stepwise_ivar[0] = best_ivar ;
      sum_relevance = best_crit ;
      if(irep == 0)
        {

         original_stepwise_crit[0] = best_crit ;
         original_stepwise_ivar[0] = best_ivar ;
         original_sum_relevance[0] = sum_relevance ;
         stepwise_mcpt_count[0] = 1 ;

         for(ivar=0 ; ivar<m_vars ; ivar++)
           {
            index[ivar] = ivar ;
            original_relevance[ivar] = sorted_crits[ivar] = current_crits[ivar] = crit[ivar] ;
            solo_mcpt_count[ivar] = 1 ;
           }

         if(!qsortdsi(0, m_vars-1, sorted_crits, index))
           {
            Print(__FUNCTION__, " failed qsort ");
            return false;
           }

         if(m_verbose)
            Print("       Variable         MI") ;

         for(i=m_vars-1 ; i>=0 ; i--)
           {
            k = index[i] ;

            if(m_verbose)
               PrintFormat("%15s %12.4lf",string(k), current_crits[k]) ;
           }
        }

      else
        {
         if(sum_relevance >= original_sum_relevance[0])
            ++stepwise_mcpt_count[0] ;
         for(ivar=0 ; ivar<m_vars ; ivar++)
           {
            if(relevance[ivar] >= original_relevance[ivar])
               ++solo_mcpt_count[ivar] ;
           }
        }

      for(i=0 ; i<m_vars ; i++)
         sum_redundancy[i] = 0.0 ;

      for(nkept=1 ; nkept<m_max_preds ; nkept++)
        {

         if(irep == 0 && m_verbose)
           {
            Print("Predictors so far   Relevance   Redundancy   Criterion") ;
            for(i=0 ; i<nkept ; i++)
              {
               k = stepwise_ivar[i] ;
               PrintFormat("%15s %12.4lf %12.4lf %12.4lf",string(k), relevance[k], relevance[k] - stepwise_crit[i], stepwise_crit[i]) ;
              }
           }

         k = 0 ;
         for(i=0 ; i<m_vars ; i++)
           {
            for(j=0 ; j<nkept ; j++)
              {
               if(stepwise_ivar[j] == i)
                  break ;
              }
            if(j == nkept)
               which_preds[k++] = i ;
           }
         if(k != (m_vars - nkept))
           {
            Print(__FUNCTION__, " failed assertion ", __LINE__);
            return false;
           }

         k = stepwise_ivar[nkept-1] ;

         casework = m_preds.Col(k);

         crit = mutualinfo(m_vars-nkept,which_preds,casework);

         for(i=0 ; i<(m_vars-nkept) ; i++)
           {
            k = which_preds[i] ;
            sum_redundancy[k] += crit[i] ;
            index[i] = k ;
            sorted_crits[i] = current_crits[i] = ((relevance[k] - sum_redundancy[k]) / double(nkept)) ;
            if(i == 0  ||  current_crits[i] > best_crit)
              {
               best_crit = current_crits[i] ;
               best_ivar = k ;
              }
           }

         stepwise_crit[nkept] = best_crit ;
         stepwise_ivar[nkept] = best_ivar ;
         sum_relevance += relevance[best_ivar] ;

         if(irep == 0)
           {
            original_stepwise_crit[nkept] = best_crit ;
            original_stepwise_ivar[nkept] = best_ivar ;
            original_sum_relevance[nkept] = sum_relevance ;
            stepwise_mcpt_count[nkept] = 1 ;

            if(!qsortdsi(0, m_vars-nkept-1, sorted_crits, index))
              {
               Print(__FUNCTION__, " failed qsort ");
               return false;
              }

            if(m_verbose)
              {
               Print("Additional candidates, in order of decreasing relevance minus redundancy") ;
               Print("       Variable     Relevance   Redundancy   Criterion") ;

               for(i=m_vars-nkept-1 ; i>=0 ; i--)
                 {
                  k = index[i] ;
                  PrintFormat("%15s %12.4lf %12.4lf %12.4lf",
                              string(k), relevance[k], sum_redundancy[k] / nkept,
                              relevance[k] - sum_redundancy[k] / nkept) ;
                 }
              }
           }

         else
           {
            if(sum_relevance >= original_sum_relevance[nkept])
               ++stepwise_mcpt_count[nkept] ;
           }

        }

     }
//---
   m_critical_values = matrix::Zeros(nkept,m_reps>1?5:3);
//---
   if(ArrayResize(m_selected_vars,nkept)<0)
     {
      Print(__FUNCTION__, " failed array resize ", GetLastError());
      return false;
     }
//---
   if(m_verbose)
     {
      if(m_reps > 1)
         Print("Final predictors ||  Relevance ||  Redundancy || Criterion  ||  Solo pval || Group pval") ;
      else
         Print("Final predictors  ||  Relevance ||  Redundancy ||  Criterion") ;
     }
//---
   for(i=0 ; i<nkept ; i++)
     {
      k = original_stepwise_ivar[i] ;
      m_selected_vars[i] = ulong(k);

      m_critical_values[i][0] = original_relevance[k];
      m_critical_values[i][1] = original_relevance[k] - original_stepwise_crit[i];
      m_critical_values[i][2] = original_stepwise_crit[i];

      if(m_critical_values.Cols()>3)
        {
         group_pvalue = (double) stepwise_mcpt_count[i] / (double) m_reps;
         solo_pvalue = (double) solo_mcpt_count[k] / (double) m_reps;
         m_critical_values[i][3] = solo_pvalue;
         m_critical_values[i][4] = group_pvalue;
        }

      if(m_verbose)
        {
         if(m_reps > 1)
            PrintFormat("%15s %12.4lf %12.4lf %12.4lf    %8.3lf    %8.3lf",string(k), m_critical_values[i][0], m_critical_values[i][1], m_critical_values[i][2],solo_pvalue,group_pvalue) ;
         else
            PrintFormat("%15s %12.4lf %12.4lf %12.4lf",string(k), m_critical_values[i][0], m_critical_values[i][1], m_critical_values[i][2]);
        }
     }

   return true;

  }

Метод mutualinfo(), который является частным методом в классе Cmrmr, отвечает за оценку взаимной информации между данным предиктором и целевой переменной с использованием метода адаптивного разбиения. Он возвращает вектор взаимных информационных приближений для выбранных предикторов, указанных в качестве индексов столбцов, в mutualinfo().

//+------------------------------------------------------------------+
//| continuous mutual infomation                                     |
//+------------------------------------------------------------------+
vector Cmrmr::mutualinfo(int which_size,int &which[],vector &targs)
  {
   vector res = vector::Zeros(m_vars);

   res.Fill(-DBL_MAX);

   vector vars;

   Capm mia(true,targs,m_chisquare_thresh);

   for(int i = 0; i<which_size; i++)
     {
      vars = m_preds.Col(which[i]);
      res[i] = mia.fit(vars);
     }

   return res;

  }

Метод StepWise() сохраняет вычисленные значения взаимной информации в векторе "релевантности", поскольку эти значения понадобятся в последующих итерациях для вычисления коэффициента избыточности. Признак с наибольшей взаимной информацией отбирается в качестве первого признака, а информация о нем сохраняется в переменных stepwise_crit и stepwise_ivar. Кроме того, инициализируется переменная sum_relevance для отслеживания совокупной релевантности выбранных признаков, которая позже будет использоваться для тестирования MCP.

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

Если мы не находимся в начальном, неизмененном повторении, алгоритм переходит к выполнению двух тестов MCP, предназначенных для оценки статистической значимости набора признаков, отобранного алгоритмом MRMR. Для эффективного управления растущей избыточностью по мере добавления новых признаков к выбранному набору алгоритм использует массив `sum_redundancy`. Массив инициализируется нулями. Этот массив используется для хранения совокупной избыточности для каждого потенциального признака относительно уже отобранных признаков.

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

После обновления значений избыточности алгоритм вычисляет критерий MRMR (Максимальная релевантность, минимальная избыточность) для каждого из оставшихся потенциальных признаков. Критерий MRMR рассчитывается путем вычитания средней избыточности каждого признака из его релевантности. Как только критерий MRMR будет рассчитан для всех оставшихся потенциальных признаков, будет выбран признак с наивысшим показателем MRMR. Алгоритм повторяет этот процесс итеративно, обновляя массив `sum_redundancy` и пересчитывая критерий MRMR каждый раз при добавлении нового признака. По мере того как отбирается все больше признаков, избыточность оставшихся признаков отобранным набором постоянно обновляется, гарантируя, что в процессе отбора признаков будут учитываться развивающиеся взаимосвязи между признаками.

Если это первоначальная, неизмененная репликация, исходные значения этих величин сохраняются для последующего сравнения во время тестов на перестановку. В противном случае счетчики для "групповых" и "одиночных" тестов на перестановку увеличиваются. После выполнения требуемого количества итераций алгоритм завершает работу, выводя на экран таблицу с краткой информацией об отобранных признаках, их релевантности и p-значениях, полученных в результате тестов на перестановку. После вызова и успешного выполнения `StepWise()` можно вызвать `GetCriticalValues()`, чтобы получить матрицу таблицы значений, отображаемую в подробном режиме. В то время как `GetSelectedVars()` извлекает индексы отобранных переменных (столбцов), соответствующие свойству `m_max_preds` класса.

//+------------------------------------------------------------------+
//| get the matrix of decision variables                             |
//| matrix rows arranged in terms of descending relevance of each    |
//| candidate predictor. Most relevant predictors critical values are|
//| in the first row, etc.                                           |
//| Column 0 is the calculated relevance                             |
//| Column 1 is the calculated redundancy                            |
//| Column 2 is the calculated Stepwise Mutual information           |
//| Column 3 and 4 will exist only if MCP test is specified          |
//| Column 3 is the Solo probability value                           |
//| Column 4 are the Group probability values                        |
//+------------------------------------------------------------------+
matrix Cmrmr::GetCriticalValues(void)
{
  return m_critical_values;
}
//+--------------------------------------------------------------------------+
//|get the indices of the most relevant predictors as determined by algorithm|
//+--------------------------------------------------------------------------+
bool Cmrmr::GetSelectedVars(ulong &output[])
{
 return (ArrayCopy(output,m_selected_vars)>0);
}

In the next section, we will see how the Cmrmr class is applied using synthetic and real-world datasets.


Примеры релевантности минус избыточность

Чтобы эффективно проиллюстрировать применение алгоритма MRMR, мы начнем с его применения к синтетическому набору данных. Набор данных состоит из 100 образцов и 10 потенциальных предикторных переменных (признаков). Целевая переменная Y вычисляется путем суммирования первых четырех столбцов матрицы, поэтому связь между целевой величиной и предикторами является детерминистской для этих столбцов. Однако столбцы с 4 по 7 не содержат никакой реальной информации о целевом объекте и служат несущественными признаками. Переменные в столбцах 8 и 9 представляют собой комбинации переменных с индексами столбцов {1,3} и {0,2} соответственно.

//---
   MathSrand(125);

   matrix rdata(100,10);

   rdata.Random(0.0,1.0);

   vector dep = rdata.Col(0) + rdata.Col(1) + rdata.Col(2) + rdata.Col(3);

   vector sum02 = rdata.Col(0) + rdata.Col(2);

   vector sum13 = rdata.Col(1) + rdata.Col(3);

   if(!rdata.Col(sum13,8) || !rdata.Col(sum02,9))
     {
      Print(" Failed column insertion ", GetLastError());
      return;
     }

Цель состоит в том, чтобы использовать алгоритм MRMR для определения столбцов как наиболее релевантных предикторов. Этот эксперимент реализован в скрипте RelevanceMinusRedundancy.mq5, который позволяет настраивать различные гиперпараметры, включая количество перестановок по методу Монте-Карло и максимальное количество отбираемых признаков. Скрипт настроен на запуск в подробном режиме, предоставляя подробные выходные данные в процессе отбора признаков.

//inputs
input int NumReplications = 100;
input int MaxPredictors = 10;
input double ChiSquareThreshold = 6.0;

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

Variable         MI
      9       0.2675
      8       0.1696
      0       0.1662
      3       0.1336
      2       0.0823
      1       0.0645
      6       0.0000
      7       0.0000
      4       0.0000
      5       0.0000

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

Predictors so far   Relevance   Redundancy   Criterion
            9       0.2675       0.0000       0.2675

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

Predictors so far   Relevance   Redundancy   Criterion
            9       0.2675       0.0000       0.2675
            8       0.1696       0.0000       0.1696

Третий отбираемый признак демонстрирует, как избыточность влияет на процесс отбора. Несмотря на то, что признаки в столбцах 0, 1, 2 и 3 непосредственно связаны с целевой переменной, их высокая избыточность по отношению к уже отобранным признакам (столбцы 8 и 9) снижает общий критерий MRMR. Следовательно, алгоритм отбирает менее непосредственно связанные функции, которые имеют меньшую избыточность по сравнению с существующим набором. Только после отбора нескольких несвязанных признаков алгоритм начнет определять приоритетность признаков в столбцах 0, 1, 2 и 3, поскольку их избыточность с отобранным набором уменьшается.

Additional candidates, in order of decreasing relevance minus redundancy
Variable     Relevance   Redundancy   Criterion
    5       0.0000       0.0000       0.0000
    4       0.0000       0.0000       0.0000
    7       0.0000       0.0000       0.0000
    6       0.0000       0.0000       0.0000
    1       0.0645       0.0600       0.0044
    0       0.1662       0.1351       0.0311
    2       0.0823       0.1426      -0.0603
    3       0.1336       0.1781      -0.0445

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

Final predictors ||  Relevance ||  Redundancy || Criterion  || Solo pval|| Group pval
9                     0.2675        0.0000       0.2675       0.010       0.010
8                     0.1696        0.0000       0.1696       0.010       0.010
4                     0.0000        0.0000       0.0000       1.000       0.010
5                     0.0000        0.0000       0.0000       1.000       0.010
6                     0.0000        0.0000       0.0000       1.000       0.010
7                     0.0000        0.0000       0.0000       1.000       0.010
1                     0.0645        0.0738      -0.0093       0.010       0.010
0                     0.1662        0.1811      -0.0149       0.010       0.010
2                     0.0823        0.1077      -0.0254       0.010       0.010
3                     0.1336        0.1584      -0.0247       0.010       0.010

P-значения, полученные в результате тестов на перестановку Монте-Карло, дают ценную информацию о статистической значимости отобранных признаков. Более низкие p-значения указывают на более веские доказательства того, что эта функция действительно информативна, а не случайна. Для определения статистической значимости обычно используется пороговое p-значение, равное 0,05.

Далее приводится демонстрация алгоритма на более реалистичном наборе данных. Скрипт StepWiseFeatureSelectionByMutualInformation.mq5 обеспечивает практическое применение алгоритма MRMR по финансовым данным. Он основан на предпосылке прогнозирования будущей доходности определенного инструмента с использованием набора технических индикаторов. Скрипт рассчитывает четыре индикатора: Индекс денежного потока (MFI), Скользящая средняя (MA), индекс силы медведей и индекс силы быков за различные периоды ретроспективы. Эти индикаторы служат в качестве потенциальных признаков для процесса отбора признаков. Применяя алгоритм MRMR, скрипт стремится определить наиболее информативную комбинацию индикаторов, которые могут эффективно прогнозировать будущую доходность. Сюда включен отбор признаков, которые соответствуют целевой переменной (будущим доходам), при минимизации избыточности с другими отобранными признаками.

//+------------------------------------------------------------------+
//|                  StepWiseFeatureSelectionByMutualInformation.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
#resource "\\Indicators\\LogReturns.ex5"
#include<mutualinfo.mqh>
#include<ErrorDescription.mqh>
//+------------------------------------------------------------------+
//|indicator type                                                    |
//+------------------------------------------------------------------+
enum SELECT_INDICATOR
  {
   MFI=0,//MFI
   MA,//MA
   BEARS,//BEARS
   BULLS//BULLS
  };
//--- input parameters
input int NumReplications = 100;
input int MaxPredictors = 10;
input double ChiSquareThreshold = 6.0;
input bool VerboseOutPut = false;
input uint     period_inc=2;//lookback increment
input uint     max_lookback=6;
input ENUM_MA_METHOD         AppliedMA = MODE_SMA;
input ENUM_APPLIED_PRICE     AppliedPrice = PRICE_CLOSE;
input datetime SampleStartDate=D'2019.12.31';
input datetime SampleStopDate=D'2022.12.31';
input string   SetSymbol="BTCUSD";
input ENUM_TIMEFRAMES SetTF = PERIOD_D1;
//----
string predictor_names[];                      // variable names
int size_sample,                      //training set size
    size_observations,                //size of of both training and testing sets combined
    maxperiod,                        //maximum lookback
    indicator_handle=INVALID_HANDLE;  //long moving average indicator handle
//---
vector indicator[];                   //indicator indicator values;
//---
matrix feature_matrix;          //full matrix of features;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---get relative shift of sample set
   int samplestart,samplestop,num_features;
   samplestart=iBarShift(SetSymbol!=""?SetSymbol:NULL,SetTF,SampleStartDate);
   samplestop=iBarShift(SetSymbol!=""?SetSymbol:NULL,SetTF,SampleStopDate);
   num_features = int((max_lookback/period_inc)*4);
//---check for errors from ibarshift calls
   if(samplestart<0 || samplestop<0)
     {
      Print(ErrorDescription(GetLastError()));
      return;
     }
//---set the size of the sample sets
   size_observations=(samplestart - samplestop) + 1 ;
   maxperiod=int(max_lookback);
//---check for input errors
   if(size_observations<=0 || maxperiod<=0)
     {
      Print("Invalid inputs ");
      return;
     }
//---allocate memory
   if(ArrayResize(indicator,num_features+1)<0)
     {
      Print(ErrorDescription(GetLastError()));
      return;
     }
//----get the full collection of indicator values
   int period_len;
   int k=0;
//---
   for(SELECT_INDICATOR select_indicator = 0; select_indicator<4; select_indicator++)
     {
      for(int iperiod=0; iperiod<int((indicator.Size()-1)/4); iperiod++)
        {
         period_len=int((iperiod+1) * period_inc);
         int try
               =10;
         predictor_names.Push(EnumToString(select_indicator)+"_"+string(period_len));
         while(try)
           {
            switch(select_indicator)
              {
               case MFI:
                  indicator_handle=iMFI(SetSymbol!=""?SetSymbol:NULL,SetTF,period_len,VOLUME_TICK);
                  break;
               case MA:
                  indicator_handle=iMA(SetSymbol!=""?SetSymbol:NULL,SetTF,period_len,0,AppliedMA,AppliedPrice);
                  break;
               case BEARS:
                  indicator_handle=iBearsPower(SetSymbol!=""?SetSymbol:NULL,SetTF,period_len);
                  break;
               case BULLS:
                  indicator_handle=iBullsPower(SetSymbol!=""?SetSymbol:NULL,SetTF,period_len);
                  break;
              }

            if(indicator_handle==INVALID_HANDLE)
               try
                  --;
            else
               break;
           }

         if(indicator_handle==INVALID_HANDLE)
           {
            Print("Invalid indicator handle ",EnumToString(select_indicator)," ", GetLastError());
            return;
           }

         Comment("copying data to buffer for indicator ",period_len);
         try
               = 0;
         while(!indicator[k].CopyIndicatorBuffer(indicator_handle,0,samplestop,size_observations) && try
                  <10)
              {
               try
                  ++;
               Sleep(5000);
              }

         if(try
               <10)
               ++k;
         else
           {
            Print("error copying to indicator buffers ",GetLastError());
            Comment("");
            return;
           }

         if(indicator_handle!=INVALID_HANDLE && IndicatorRelease(indicator_handle))
            indicator_handle=INVALID_HANDLE;
        }
     }

//---resize matrix
   if(!feature_matrix.Resize(size_observations,indicator.Size()-1))
     {
      Print(__LINE__);
      Print(ErrorDescription(GetLastError()));
      Comment("");
      return;
     }
//---copy collected data to matrix
   for(ulong i = 0; i<feature_matrix.Cols(); i++)
      if(!feature_matrix.Col(indicator[i],i))
        {
         Print(__LINE__);
         Print(ErrorDescription(GetLastError()));
         Comment("");
         return;
        }
//---
   int try
         = 10;
   while(try
            >-1 && indicator_handle == INVALID_HANDLE)
        {
         indicator_handle=iCustom(SetSymbol!=""?SetSymbol:NULL,SetTF,"\\Indicators\\LogReturns",0,1,1);
         try
            --;
        }
//---
   if(try
         <0)
        {
         Print("Could not initialize returns indicator ");
         Print(ErrorDescription(GetLastError()));
         Comment("");
         return;
        }
   else
     {
      try
            = 10;
     }
//---
   while(try
            >-1 && !indicator[indicator.Size()-1].CopyIndicatorBuffer(indicator_handle,0,samplestop-1,size_observations))
        {
         Sleep(2000);
         try
            --;
        }
//---
   if(try
         <0)
        {
         Print("Could not collect returns indicator info ");
         Print(ErrorDescription(GetLastError()));
         Comment("");
         return;
        }
   else
     {
      IndicatorRelease(indicator_handle);
      Comment("");
     }
//--- display the dataset
   string console;
   for(uint i = 0; i<predictor_names.Size(); i++)
      console+=StringFormat(" %12s ",predictor_names[i]);
   console+=" NextBar Returns (Target) ";
   Print(console);
   for(ulong i = 0; i<feature_matrix.Rows(); i++)
     {
      console = "";
      for(ulong j = 0; j<feature_matrix.Cols(); j++)
         console += StringFormat(" %12.6lf ",feature_matrix[i][j]);
      console+=StringFormat(" %12.6lf ",indicator[indicator.Size()-1][i]);
      Print(console);
     }
//---
   Cmrmr mstep(NumReplications,MaxPredictors,ChiSquareThreshold,VerboseOutPut);
//---
   if(!mstep.StepWise(feature_matrix,indicator[indicator.Size()-1]))
      return;
//---
   Print(" Final predictor labels ");
   ulong variables[];
   if(mstep.GetSelectedVars(variables))
     {
      for(uint i = 0; i<variables.Size(); i++)
         Print(predictor_names[variables[i]]);
     }
   return;
  }
//+------------------------------------------------------------------+
Ниже приведен фрагмент набора данных индикаторов, собранных из терминала MetaTrader 5, а также целевая переменная в последнем столбце. Всего существует 12 потенциальных предикторов (индикаторов).
MFI_2         MFI_4         MFI_6          MA_2          MA_4          MA_6       BEARS_2       BEARS_4       BEARS_6       BULLS_2       BULLS_4       BULLS_6  NextBar Returns(Target)
0.000000     53.151442     46.921608   7213.654000   7279.552750   7260.007333    -76.371267   -101.867797   -107.113146     87.265733     61.769203     56.523854     -0.003564
0.000000     26.316595     34.451328   7180.962000   7244.558000   7255.420333    -41.795089    -70.889078    -83.462247     42.344911     13.250922      0.677753     -0.032447
0.000000      0.000000     36.720977   7053.740000   7133.697000   7204.281833   -127.731696   -210.813447   -251.244462    153.948304     70.866553     30.435538      0.054562

Ниже приведены результаты анализа предиктора с использованием параметров скрипта по умолчанию. Сначала посмотрим на таблицу значений взаимной информации. Здесь мы видим, что некоторые индикаторы имеют нулевую взаимную информацию с целью. Индикатор скользящей средней с длительностью периода равной 6, обладает наибольшей взаимной информацией по отношению к цели.
PS          Variable         MI
ND                 5       0.0308
LG                 4       0.0293
MJ                 3       0.0279
GM                 6       0.0227
JP                 9       0.0182
IS                 1       0.0165
MF                 8       0.0135
JI                 7       0.0126
HL                 0       0.0000
JO                 2       0.0000
GS                10       0.0000
HD                11       0.0000

Естественно, MA_6 - это первый отбор, сделанный алгоритмом. Поскольку первый отбор сделан, будет интересно посмотреть, есть ли какие-либо другие потенциальные предикторы, которые делятся с ней (MA_6) информацией. Это можно сделать, изучив приведенные ниже значения избыточности.

ME   Predictors so far   Relevance   Redundancy   Criterion
GH                 5       0.0308       0.0000       0.0308
II   Additional candidates, in order of decreasing relevance minus redundancy
FE          Variable     Relevance   Redundancy   Criterion
LE                 0       0.0000       0.0095      -0.0095
ED                 1       0.0165       0.0869      -0.0704
FD                 2       0.0000       0.1149      -0.1149
PE                11       0.0000       0.2768      -0.2768
ID                 8       0.0135       0.3251      -0.3116
NS                 7       0.0126       0.3492      -0.3366
CR                10       0.0000       0.3484      -0.3484
ES                 6       0.0227       0.4030      -0.3802
IS                 9       0.0182       0.4285      -0.4103
CR                 3       0.0279       2.5363      -2.5084
DR                 4       0.0293       3.0096      -2.9803
Следующим отбором для алгоритма будет MFI_2, поскольку он обладает наименьшей избыточностью среди всех оставшихся потенциальных предикторов, несмотря на нулевую релевантность к целевой переменной. Также обратите внимание, что предикторы в индексах столбцов 3 и 4 (MA_2 и MA_4 соответственно) имеют самые высокие уровни избыточности с MA_6. Это понятно, поскольку это один и тот же индикатор, рассчитанный с меньшими длинами окон. Ниже приведен предиктор, установленный после еще двух отборов, оба из которых также имеют нулевую релевантность к целевой переменной. Следует отметить главный предиктор в наборе кандидатов, которые не были отобраны. Алгоритм начинает рассматривать релевантные предикторы из-за разбавляющего эффекта, который последние два отбора оказали на отобранные предикторы в целом.
PQ   Predictors so far   Relevance   Redundancy   Criterion
FL                 5       0.0308       0.0000       0.0308
JK                 0       0.0000       0.0095      -0.0095
DK                 2       0.0000       0.1796      -0.1796
EE  Additional candidates, in order of decreasing relevance minus redundancy
JH         Variable     Relevance   Redundancy   Criterion
CH                6       0.0227       0.1469      -0.1241
PH                9       0.0182       0.1465      -0.1283
GI               10       0.0000       0.2024      -0.2024
PG                7       0.0126       0.2133      -0.2007
PF               11       0.0000       0.2308      -0.2308
GG                8       0.0135       0.2664      -0.2529
QG                1       0.0165       0.4679      -0.4514
KF                3       0.0279       0.8840      -0.8561
LF                4       0.0293       1.0372      -1.0079
Мы завершаем наш комментарий к результатам, забегая вперед и показывая окончательный набор предикторов, выбранных алгоритмом.
OQ  Final predictors ||  Relevance ||  Redundancy || Criterion  ||  Solo pval || Group pval
HN                5       0.0308       0.0000       0.0308       0.010       0.010
DJ                0       0.0000       0.0095      -0.0095       1.000       0.010
JG                2       0.0000       0.1796      -0.1796       1.000       0.010
HD                6       0.0227       0.1620      -0.1393       0.010       0.010
FQ                9       0.0182       0.2023      -0.1842       0.010       0.010
DL               11       0.0000       0.2798      -0.2798       1.000       0.010
KJ                1       0.0165       0.2932      -0.2767       0.010       0.010
OG                7       0.0126       0.3298      -0.3172       0.010       0.010
OE               10       0.0000       0.4151      -0.4151       1.000       0.010
JP                8       0.0135       0.4585      -0.4450       0.010       0.010
RN   Final predictor labels
NO  MA_6
DE  MFI_2
NN  MFI_6
KP  BEARS_2
DJ  BULLS_2
FL  BULLS_6
PQ  MFI_4
MK  BEARS_4
FM  BULLS_4
OF  BEARS_6


Заключение

В настоящей статье рассмотрено применение взаимной информации при отборе признаков. Основное внимание уделяется алгоритму MRMR (максимальная зависимость, максимальная релевантность, минимальная избыточность), предложенному авторами Пенгом (Peng), Лонгом (Long) и Дином (Ding). Мы начали с обсуждения оценки взаимной информации для непрерывных переменных, подчеркнув ее способность фиксировать сложные нелинейные зависимости между признаками и целевой переменной. В ходе нашего исследования алгоритма MRMR мы продемонстрировали, как он эффективно уравновешивает конкурирующие задачи по максимизации релевантности целевой переменной при минимизации избыточности уже отобранных признаков.

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

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

Имя файла
Описание
MQL5/include/np.mqh
Заголовочный файл с матричными и векторными служебными функциями.
MQL5/include/mutualinfo.mqh
Заголовочный файл, содержащий определение класса Capm, который реализует адаптивный метод разбиения для оценки взаимной информации непрерывных переменных. И определение класса Cmrmr, реализующего поэтапный отбор признаков на основе взаимной информации.
MQL5/scripts/RelevanceMinusRedundancy.mq5
Скрипт, демонстрирующий использование класса Cmrmr с помощью синтетических данных
MQL5/scripts/StepWiseFeatureSelectionByMutualInformation.mq5
Другой сценарий также демонстрирует применение класса Cmrmr на более реалистичном наборе данных.

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

Прикрепленные файлы |
np.mqh (74.16 KB)
mutualinfo.mqh (26.74 KB)
Mql5.zip (19.35 KB)
Подробная информация о торговле на основе объема: Выход за рамки графиков OHLC Подробная информация о торговле на основе объема: Выход за рамки графиков OHLC
Алгоритмическая торговая система, сочетающая анализ объема с методами машинного обучения, в частности с нейронными сетями LSTM. В отличие от традиционных торговых подходов, которые в первую очередь фокусируются на движении цен, эта система делает упор на паттернах объема и их производных для прогнозирования движений рынка. Методология включает в себя три основных компонента: анализ производных от объема (первые и вторые производные), прогнозы LSTM для паттернов объема и традиционные технические индикаторы.
Трейдинг с экономическим календарем MQL5 (Часть 2): Создание новостной панели Трейдинг с экономическим календарем MQL5 (Часть 2): Создание новостной панели
В этой статье мы создадим практичную новостную панель с использованием экономического календаря MQL5 для улучшения нашей торговой стратегии. Начнем с проектирования макета, уделив особое внимание ключевым элементам, таким как названия событий, важность и время, а затем перейдем к настройке в MQL5. Наконец, мы внедрим систему сортировки для отображения только самых актуальных новостей, предоставляя трейдерам быстрый доступ к важным экономическим событиям.
Автоматизация торговых стратегий с помощью MQL5 (Часть 1): Система Profitunity (Торговый хаос Билла Вильямса) Автоматизация торговых стратегий с помощью MQL5 (Часть 1): Система Profitunity (Торговый хаос Билла Вильямса)
В данной статье мы исследуем систему Profitunity авторства Билла Вильямса, подробно разобрав ее ключевые составляющие и уникальный подход к торговле в хаотичных условиях рынка. Мы продемонстрируем читателям реализацию системы на языке программирования MQL5, делая акцент на автоматизации ключевых индикаторов и сигналов для входа/выхода. Наконец, мы протестируем и оптимизируем стратегию, детально анализируя ее эффективность в различных рыночных сценариях.
Применение Grey-модели в техническом анализе финансовых временных рядов Применение Grey-модели в техническом анализе финансовых временных рядов
Данная статья посвящена изучению grey-модели — перспективного инструмента, способного расширить возможности трейдера. Мы рассмотрим некоторые варианты применения этой модели для технического анализа и построения торговых стратегий.