Скачать MetaTrader 5

Глубокие нейросети (Часть II). Разработка и выбор предикторов

31 июля 2017, 10:15
Vladimir Perervenko
7
2 411

Содержание


Введение

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

1. Разработка признаков

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

Технологически процесс может быть разделен на два этапа:

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

1.1. Трансформация признаков


1.1.1. Преобразование


Что такое преобразование переменной?

В моделировании данных преобразование заключается в замене переменной функцией. Например, замена переменной x на квадратный / кубический корень или логарифм x — это трансформация (или преобразование). Иными словами, трансформация/преобразование — это процесс, который изменяет распределение или связь переменной с другими.

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

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

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

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

  • Когда нужно преобразовать непрерывную переменную в дискретную. Метод такого преобразования — дискретизация.

Каковы общие методы преобразования переменной?


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

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

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

  3. Дискретизация/Бининг: используется для категоризации переменных. Выполняется на исходных значениях, процентилях или частотах. Решение о методе категоризации основывается на том, какова природа данных. Мы можем проводить совместные сегментирования взаимозависимых переменных.

Любая трансформация данных приводит к изменению распределения переменных. Рассмотрим это на примерах двух методов преобразования.

Две проблемы нашего исходного набора данных — выбросы и правая асимметрия. Удаление выбросов мы уже рассмотрели. Сейчас сначала попробуем удалить/уменьшить асимметрию, а после этого уберем выбросы.

Метод 1.

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

evalq({x.ln <- apply(x, 2, function(x) log2(x + 1))
       sk.ln <- skewness(x.ln)},
      env)
 > env$sk.ln
               ftlm      stlm      rbci      pcci   v.fatl
Skewness -0.2715663 -2.660613 -4.484301 0.4267873 1.253008
          v.satl   v.rftl     v.rstl    v.ftlm     v.stlm
Skewness 1.83489 2.065224 -0.0343451 -15.62414 0.01529019
            v.pcci
Skewness 0.1811206

Три переменных — stlm, rbci и v.ftlm — получили сильную левую асимметрию, три переменных — v.fatl, v.satl и v.rftl — остались с сильным правым скосом, у остальных переменных асимметрия выровнялась. Уберем и импутируем выбросы из этого набора и посмотрим, какая асимметрия и распределение переменных у нас получится:

evalq({
  foreach(i = 1:ncol(x.ln), .combine = "cbind") %do% {
    remove_outliers(x.ln[ ,i])
  } -> x.ln.out
  colnames(x.ln.out) <- colnames(x.ln)
  },  
env)
evalq({
  foreach(i = 1:ncol(x.ln), .combine = "cbind") %do% {
    capping_outliers(x.ln[ ,i])
  } -> x.ln.cap
  colnames(x.ln.cap) <- colnames(x.ln)
},  
env)
evalq({
  sk.ln.out <- skewness(x.ln.out) 
  sk.ln.cap <- skewness(x.ln.cap)
}, 
env)
> env$sk.ln.out
              ftlm       stlm       rbci        pcci
Skewness -0.119055 -0.3549119 -0.1099921 -0.01476384
              v.fatl      v.satl      v.rftl     v.rstl
Skewness -0.02896319 -0.03634833 -0.06259749 -0.2120127
              v.ftlm      v.stlm      v.pcci
Skewness -0.05819699 -0.01661317 -0.05420077
> env$sk.ln.cap
               ftlm       stlm       rbci        pcci
Skewness -0.1814781 -0.4582045 -0.1658855 -0.02849945
              v.fatl      v.satl     v.rftl     v.rstl
Skewness -0.04336238 -0.04400781 -0.0692754 -0.2269408
              v.ftlm      v.stlm      v.pcci
Skewness -0.06184128 -0.02856397 -0.06258243

В обоих наборах (x.out и x.cap) данные почти симметричны. Распределение продемонстрировано на рисунках ниже.

par(mfrow = c(2,2))
boxplot(env$x.ln, 
              main = "x.ln with outliers",
              xlab = "")
boxplot(env$x.ln.out, 
              main = "x.ln.out without outliers",
              xlab = "")
boxplot(env$x.ln.cap, 
              main = "x.ln.cap with imputed outliers",
              xlab = "")
par(mfrow = c(1,1))

x.lnx.ln.out

Рис.1. Логтрансформированные данные с выбросами и без

x.ln.cap

Рис.2. Логтрансформированные данные с импутированными выбросами

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

Преобразуем x.ln.cap датафрейм и посмотрим вариацию и ковариацию набора:

 evalq(x.ln.cap %>% tbl_df() %>% 
        cbind(Data = dataSetClean$Data, .,
              Class = dataSetClean$Class) -> 
        dataSetLnCap, 
      env) 

Построим графики:

require(GGally)
evalq(ggpairs(dataSetLnCap, columns = 2:7, 
              mapping = aes(color = Class),
              title = "PredLnCap1"), 
      env)
evalq(ggpairs(dataSetLnCap, columns = 8:13, 
              mapping = aes(color = Class),
              title = "PredLnCap2"), 
      env)

LnCap1

Рис.3. Параметры логтрансформированных данных, часть 1

LnCap2

Рис. 4. Параметры логтрансформированных данных, часть 2

Метод 2. 

Трансформируем данные с помощью функции sin(2*pi*x), уберем и импутируем выбросы и посмотрим на асимметрию, распределение выбросов и ковариацию трансформированных переменных на графиках.

evalq({x.sin <- apply(x, 2, function(x) sin(2*pi*x))
      sk.sin <- skewness(x.sin)
      },
env)
#----------
evalq({
  foreach(i = 1:ncol(x.sin), .combine = "cbind") %do% {
    remove_outliers(x.sin[ ,i])
  } -> x.sin.out
  colnames(x.sin.out) <- colnames(x.sin)
},  
env)
#-----------------
evalq({
  foreach(i = 1:ncol(x.sin), .combine = "cbind") %do% {
    capping_outliers(x.sin[ ,i])
  } -> x.sin.cap
  colnames(x.sin.cap) <- colnames(x.sin)
},  
env)
#-----------
evalq({
  sk.sin.out <- skewness(x.sin.out) 
  sk.sin.cap <- skewness(x.sin.cap)
}, 
env) 

Какова асимметрия в этих трансформированных наборах?

env$sk.sin
                ftlm        stlm        rbci         pcci
Skewness -0.02536085 -0.04234074 -0.00587189 0.0009679463
             v.fatl    v.satl     v.rftl      v.rstl
Skewness 0.03280465 0.5217757 0.05611136 -0.02825112
             v.ftlm     v.stlm     v.pcci
Skewness 0.04923953 -0.2123434 0.01738377
> env$sk.sin.out
                ftlm        stlm        rbci       pcci
Skewness -0.02536085 -0.04234074 -0.00587189 0.03532892
             v.fatl      v.satl      v.rftl      v.rstl
Skewness 0.00360966 -0.02380975 -0.05336561 -0.02825112
               v.ftlm     v.stlm       v.pcci
Skewness 0.0009366441 0.01835948 0.0008843329
> env$sk.sin.cap
                ftlm        stlm        rbci       pcci
Skewness -0.02536085 -0.04234074 -0.00587189 0.03283132
              v.fatl      v.satl      v.rftl      v.rstl
Skewness 0.007588308 -0.02424707 -0.04106469 -0.02825112
              v.ftlm      v.stlm      v.pcci
Skewness 0.007003051 0.009237835 0.002101687

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

par(mfrow = c(2, 2))
boxplot(env$x.sin, main = "x.sin with outlier")
abline(h = 0, col = 2)
boxplot(env$x.sin.out, main = "x.sin.out without outlier")
abline(h = 0, col = 2)
boxplot(env$x.sin.cap, main = "x.sin.cap with capping outlier")
abline(h = 0, col = 2)
par(mfrow = c(1, 1))

x.Sin

Рис.5. Набор данных, трансформированный функцией sin()

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

Посмотрим, как NA распределены в переменных после удаления выбросов.

require(VIM)
evalq(a <- aggr(x.sin.out), env)

SinMissAggr

Рис.6. Распределение NA в наборе

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

> print(env$a)

 Missings in variables:
 Variable Count
     pcci   256
   v.fatl   317
   v.satl   289
   v.rftl   406
   v.ftlm   215
   v.stlm   194
   v.pcci   201

Как распределены NA в переменных?

 par(mfrow = c(3, 4))
evalq(
  foreach(i = 1:ncol(x.sin.out)) %do% {
    barMiss(x.sin.out, pos = i, only.miss = TRUE, 
            main = "x.sin.out without outlier")
  }, env
)
par(mfrow = c(1, 1))

SinMissBar

Рис.7. Распределение NA в переменных

Синим цветом показаны наблюдаемые значения переменной, красным — количество NA других переменных в различных диапазонах значений текущей. Бар справа — вклад NA текущей переменной в общее количество NA всех переменных.

В заключение посмотрим вариацию и ковариацию трансформированного набора с импутированными выбросами.

#---------------
evalq(x.sin.cap %>% tbl_df() %>% 
        cbind(Data = dataSetClean$Data, .,
              Class = dataSetClean$Class) -> 
        dataSetSinCap, 
      env) 
require(GGally)
evalq(ggpairs(dataSetSinCap, columns = 2:7, 
              mapping = aes(color = Class),
              title = "dataSetSinCap1 with capping outlier "), 
      env)
evalq(ggpairs(dataSetSinCap, columns = 8:13, 
              mapping = aes(color = Class),
              title = "dataSetSinCap2 with capping outlier"), 
      env)
#---------------------------

SinCap1

Рис.8. Параметры трансформированных sin() данных, часть 1

SinCap2

Рис.9. Параметры трансформированных sin() данных, часть 2


1.1.2. Нормализация


Поскольку мы готовим данные для нейросети, нужно привести переменные в диапазон { -1..+1 }. Для этого используем функцию preProcess()::caret и method = “spatialSign”. Как вариант, перед нормализацией можно центрировать и шкалировать данные. Поскольку это довольно тривиальный процесс, мы не будем останавливаться на нем подробно.

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

Для использования в дальнейших экспериментах разделим наш набор, полученный в предыдущих вычислениях (dataSet без удаления высокоррелированных ) на train/test/val и приведем в диапазон (-1,+1) без стандартизации.

При нормировании со стандартизацией нужно помнить, что при определении параметров нормализации (mean/median, sd/mad) мы определяем и параметры импутации выбросов. В дальнейшем они будут использованы на train/val/test . Ранее в статье мы уже написали 2 функции: prep.outlier() и treatOutlier(), которые предназначены именно для этих целей.

Последовательность выполнения операций:

  1. Определение параметров выбросов в train
  2. Удаление выбросов в train
  3. Определение параметров стандартизации в train
  4. Импутируем выбросы в train/val/test
  5. Нормализация train/val/test.

Этот вариант в статье мы рассматривать не будем, вы можете ознакомиться с ним самостоятельно.

Разделяем набор на train/val/test:

 evalq(
{
  train = 1:2000
  val = 2001:3000
  test = 3001:4000
  DT <- list()
  list(clean = data.frame(dataSet) %>% na.omit(), 
       train = clean[train, ], 
       val = clean[val, ], 
       test = clean[test, ]) -> DT
}, env) 

Определим параметры нормализации для набора train и нормализуем наборы train/test/val:

 require(foreach)
evalq(
{
 preProcess(DT$train, method = "spatialSign") -> preproc 
 list(train = predict(preproc, DT$train), 
        val = predict(preproc, DT$val),
        test = predict(preproc, DT$test)
       ) -> DTn
}, 
env) 

Посмотрим суммарную статистику набора train:

 > table.Stats(env$DTn$train %>% tk_xts())
Using column `Data` for date_var.
                     ftlm      stlm      rbci      pcci
Observations    2000.0000 2000.0000 2000.0000 2000.0000
NAs                0.0000    0.0000    0.0000    0.0000
Minimum           -0.5909   -0.7624   -0.6114   -0.8086
Quartile 1        -0.2054   -0.2157   -0.2203   -0.2110
Median             0.0145    0.0246    0.0147    0.0068
Arithmetic Mean    0.0070    0.0190    0.0085    0.0028
Geometric Mean    -0.0316   -0.0396   -0.0332   -0.0438
Quartile 3         0.2139    0.2462    0.2341    0.2277
Maximum            0.6314    0.8047    0.7573    0.7539
SE Mean            0.0060    0.0073    0.0063    0.0065
LCL Mean (0.95)   -0.0047    0.0047   -0.0037   -0.0100
UCL Mean (0.95)    0.0188    0.0333    0.0208    0.0155
Variance           0.0719    0.1058    0.0784    0.0848
Stdev              0.2682    0.3252    0.2800    0.2912
Skewness          -0.0762   -0.0221   -0.0169   -0.0272
Kurtosis          -0.8759   -0.6688   -0.8782   -0.7090
                   v.fatl    v.satl    v.rftl    v.rstl
Observations    2000.0000 2000.0000 2000.0000 2000.0000
NAs                0.0000    0.0000    0.0000    0.0000
Minimum           -0.5160   -0.5943   -0.6037   -0.7591
Quartile 1        -0.2134   -0.2195   -0.1988   -0.2321
Median             0.0015    0.0301    0.0230    0.0277
Arithmetic Mean    0.0032    0.0151    0.0118    0.0177
Geometric Mean    -0.0323   -0.0267   -0.0289   -0.0429
Quartile 3         0.2210    0.2467    0.2233    0.2657
Maximum            0.5093    0.5893    0.6714    0.7346
SE Mean            0.0058    0.0063    0.0062    0.0074
LCL Mean (0.95)   -0.0082    0.0028   -0.0003    0.0033
UCL Mean (0.95)    0.0146    0.0274    0.0238    0.0321
Variance           0.0675    0.0783    0.0757    0.1083
Stdev              0.2599    0.2798    0.2751    0.3291
Skewness          -0.0119   -0.0956   -0.0648   -0.0562
Kurtosis          -1.0788   -1.0359   -0.7957   -0.7275
                   v.ftlm    v.stlm    v.rbci    v.pcci
Observations    2000.0000 2000.0000 2000.0000 2000.0000
NAs                0.0000    0.0000    0.0000    0.0000
Minimum           -0.5627   -0.6279   -0.5925   -0.7860
Quartile 1        -0.2215   -0.2363   -0.2245   -0.2256
Median            -0.0018    0.0092   -0.0015   -0.0054
Arithmetic Mean   -0.0037    0.0036   -0.0037    0.0013
Geometric Mean    -0.0426   -0.0411   -0.0433   -0.0537
Quartile 3         0.2165    0.2372    0.2180    0.2276
Maximum            0.5577    0.6322    0.5740    0.9051
SE Mean            0.0061    0.0065    0.0061    0.0070
LCL Mean (0.95)   -0.0155   -0.0091   -0.0157   -0.0124
UCL Mean (0.95)    0.0082    0.0163    0.0082    0.0150
Variance           0.0732    0.0836    0.0742    0.0975
Stdev              0.2706    0.2892    0.2724    0.3123
Skewness           0.0106   -0.0004   -0.0014    0.0232
Kurtosis          -1.0040   -1.0083   -1.0043   -0.4159

Из таблицы мы видим, что переменные симметричны и имеют очень близкие параметры.

Посмотрим на распределение переменных в наборах train/val/test:

 boxplot(env$DTn$train %>% 
          dplyr::select(-c(Data, Class)),
        horizontal = T, main = "Train")
abline(v = 0, col = 2)
boxplot(env$DTn$test %>% 
          dplyr::select(-c(Data, Class)),
        horizontal = T, main = "Test")
abline(v = 0, col = 2)
boxplot(env$DTn$val %>% 
          dplyr::select(-c(Data, Class)),
        horizontal = T, main = "Val")
abline(v = 0, col = 2) 

DTn

Рис.10. Распределение переменных в наборах train/val/test после нормализации

Распределение переменных во всех наборах практически одинаково. Дополнительно нужно рассмотреть корреляцию и ковариацию переменных в наборе train:

require(GGally)
evalq(ggpairs(DTn$train, columns = 2:7, 
              mapping = aes(color = Class),
              title = "DTn$train1 "), 
      env)
evalq(ggpairs(DTn$train, columns = 8:14, 
              mapping = aes(color = Class),
              title = "DTn$train2"), 
      env)

DTn$train1

Рис.11. Вариация и ковариация набора 1 train

DTn$train2

Рис.12. Вариация и ковариация набора 2 train

Высококоррелированных данных у нас нет, распределение компактное и без выбросов, данные хорошо разделяемые. На первый взгляд предварительно видны две проблемных переменных — stlm и v.rstl. В дальнейшем, когда мы будем оценивать релевантность предикторов, мы уточним это предварительное мнение. Сейчас мы можем посмотреть корреляцию этих предикторов к целевой:

> funModeling::correlation_table(env$DTn$train %>% tbl_df %>%
+                    select(-Data), str_target = 'Class')
   Variable Class
1     Class  1.00
2    v.fatl  0.38
3      ftlm  0.34
4      rbci  0.28
5    v.rbci  0.28
6    v.satl  0.27
7      pcci  0.24
8    v.ftlm  0.22
9    v.stlm  0.22
10   v.rftl  0.18
11   v.pcci  0.08
12     stlm  0.03
13   v.rstl -0.01
Видим, что указанные переменные находятся внизу таблицы с очень низкими показателями. Под вопросом также релевантность переменной v.pcci. Посмотрим ближе переменную v.fatl во всех наборах train/val/test:
require(ggvis)
evalq(
  DTn$train %>% ggvis(~v.fatl, fill = ~Class) %>% 
  group_by(Class) %>%  layer_densities() %>% 
  add_legend("fill", title = "DTn$train$v.fatl"),
  env)
evalq(
  DTn$val %>% ggvis(~v.fatl, fill = ~Class) %>% 
    group_by(Class) %>%  layer_densities() %>% 
    add_legend("fill", title = "DTn$val$v.fatl"),
  env)
evalq(
  DTn$test %>% ggvis(~v.fatl, fill = ~Class) %>% 
    group_by(Class) %>%  layer_densities() %>% 
    add_legend("fill", title = "DTn$test$v.fatl"),
  env) 

Train$vfatl

Рис.13. Распределение переменной v.fatl в наборе train после нормализации

Valid$vfatl

Рис.14. Распределение переменной v.fatl в наборе valid после нормализации

Test$vfatl

Рис.15. Распределение переменной v.fatl в наборе test после нормализации

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


1.1.3. Дискретизация


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

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

Первая группа методов практически полностью охватывается функцией cut2()::Hmisc. Можно разделить выборку на заранее заданное количество участков, с указанием конкретных границ, поквартильно, с указанием минимального количества примеров в каждом участке, на равночастотные участки.

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

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

Проведем очистку набора (без удаления высококоррелированных), разделим на train/val/test в соотношении 2000/1000/1000.

require(discretization)
require(caret)
require(pipeR)
evalq(
  {
    dataSet %>%
    preProcess(.,
               method = c("zv", "nzv", "conditionalX")) %>%
    predict(., dataSet) %>%
    na.omit -> dataSetClean
    train = 1:2000
    val = 2001:3000
    test = 3001:4000
    DT <- list()
    list(train = dataSetClean[train, ], 
         val = dataSetClean[val, ], 
         test = dataSetClean[test, ]) -> DT
  }, 
  env)

Будем использовать функцию mdlp()::discretization, которая описывает дискретизацию с использованием принципа минимальной длины описания. Эта функция дискретизирует непрерывные атрибуты матрицы данных с использованием критерия энтропии с минимальной длиной описания в качестве правила остановки.

evalq(
  pipeline({
    DT$train
    select(-Data)
    as.data.frame()
    mdlp()}) -> mdlp.train, envir = env)

Функция возвращает лист с двумя слотами: это cutp — датафрейм с точками разделения для каждой переменной и Disc.data — датафрейм с размеченными переменными.

> env$mdlp.train%>%str()
List of 2
 $ cutp     :List of 12
  ..$ : num [1:2] -0.0534 0.0278
  ..$ : chr "All"
  ..$ : num -0.0166
  ..$ : num [1:2] -0.0205 0.0493
  ..$ : num [1:3] -0.0519 -0.0055 0.019
  ..$ : num 0.000865
  ..$ : num -0.00909
  ..$ : chr "All"
  ..$ : num 0.0176
  ..$ : num [1:2] -0.011 0.0257
  ..$ : num [1:3] -0.03612 0.00385 0.03988
  ..$ : chr "All"
 $ Disc.data:'data.frame':      2000 obs. of  13 variables:
  ..$ ftlm  : int [1:2000] 3 3 3 3 3 2 1 1 1 1 ...
  ..$ stlm  : int [1:2000] 1 1 1 1 1 1 1 1 1 1 ...
  ..$ rbci  : int [1:2000] 2 2 2 2 2 2 1 1 1 1 ...
  ..$ pcci  : int [1:2000] 2 2 1 2 2 1 1 2 3 2 ...
  ..$ v.fatl: int [1:2000] 4 4 3 4 3 1 1 2 3 2 ...
  ..$ v.satl: int [1:2000] 1 1 1 2 2 1 1 1 1 1 ...
  ..$ v.rftl: int [1:2000] 1 2 2 2 2 2 2 2 1 1 ...
  ..$ v.rstl: int [1:2000] 1 1 1 1 1 1 1 1 1 1 ...
  ..$ v.ftlm: int [1:2000] 2 2 1 1 1 1 1 1 2 1 ...
  ..$ v.stlm: int [1:2000] 1 1 1 2 2 1 1 1 1 1 ...
  ..$ v.rbci: int [1:2000] 4 4 3 3 2 1 1 2 3 2 ...
  ..$ v.pcci: int [1:2000] 1 1 1 1 1 1 1 1 1 1 ...
  ..$ Class : Factor w/ 2 levels "-1","1": 2 2 2 2 2 1 1 1 1 1 ...

Что мы можем увидеть в первом слоте?

У нас есть три неразмеченных переменных, значения которых никак не связаны с целевой. Это 2,8 и 12 (stlm, v.rstl, v.pcci). Их можно удалить без потери качества набора. Кстати, эти же переменные мы определили как нерелевантные и в предыдущем разделе.

4 переменных разделены на два класса, 3 переменных — на 3 класса и две переменных — на 4 класса.

Разметим наши наборы val/test на сегменты, используя точки разделения, полученные на train. Для этого уберем из набора train переменные, которые остались неразмеченными, и сохраним набор в датафрейм train.d. Затем, используя функцию findInterval(), разметим набор test/val точками разделения, полученными ранее.

evalq(
  {
    mdlp.train$cutp %>% 
    lapply(., function(x) is.numeric(x)) %>%
    unlist -> idx   # bool
    #----train-----------------
    mdlp.train$Disc.data[ ,idx] -> train.d
    #---test------------
    DT$test %>% 
      select(-c(Data, Class)) %>%
      as.data.frame() -> test.d
  
    foreach(i = 1:length(idx), .combine = 'cbind') %do% {
      if (idx[i]) {findInterval(test.d[ ,i], 
                   vec = mdlp.train$cutp[[i]],
                   rightmost.closed = FALSE, 
                   all.inside = F,
                   left.open = F)}
    } %>% as.data.frame() %>% add(1) %>%
      cbind(., DT$test$Class) -> test.d
    colnames(test.d) <- colnames(train.d)
    #-----val-----------------
    DT$val %>% 
      select(-c(Data, Class)) %>%
      as.data.frame() -> val.d
    foreach(i = 1:length(idx), .combine = 'cbind') %do% {
      if (idx[i]) {findInterval(val.d[ ,i], 
                                vec = mdlp.train$cutp[[i]],
                                rightmost.closed = FALSE, 
                                all.inside = F,
                                left.open = F)}
    } %>% as.data.frame() %>% add(1) %>%
      cbind(., DT$val$Class) -> val.d
    colnames(val.d) <- colnames(train.d)
  },env
)

Как выглядят эти наборы?

> env$train.d %>% head()
  ftlm rbci pcci v.fatl v.satl v.rftl v.ftlm v.stlm v.rbci Class
1    3    2    2      4      1      1      2      1      4     1
2    3    2    2      4      1      2      2      1      4     1
3    3    2    1      3      1      2      1      1      3     1
4    3    2    2      4      2      2      1      2      3     1
5    3    2    2      3      2      2      1      2      2     1
6    2    2    1      1      1      2      1      1      1    -1
> env$test.d %>% head()
  ftlm rbci pcci v.fatl v.satl v.rftl v.ftlm v.stlm v.rbci Class
1    1    1    1      2      1      1      1      1      2    -1
2    1    1    3      3      1      1      2      2      3    -1
3    1    1    2      2      1      1      1      2      2    -1
4    2    1    2      3      1      1      2      2      3     1
5    2    2    2      3      1      1      1      2      3     1
6    2    2    2      4      1      1      2      2      3     1
> env$val.d %>% head()
  ftlm rbci pcci v.fatl v.satl v.rftl v.ftlm v.stlm v.rbci Class
1    2    2    2      2      2      2      1      2      2     1
2    2    2    2      2      2      2      1      2      2     1
3    2    2    2      3      2      2      1      2      2     1
4    2    2    2      4      2      2      2      2      3     1
5    2    2    2      3      2      2      1      2      2     1
6    2    2    2      3      2      2      2      2      2     1

> env$train.d$v.fatl %>% table()
.
  1   2   3   4 
211 693 519 577 
> env$test.d$v.fatl %>% table()
.
  1   2   3   4 
 49 376 313 262 
> env$val.d$v.fatl %>% table()
.
  1   2   3   4 
 68 379 295 258 

Дальнейшее использование наборов с дискретными данными зависит от используемой модели. Если это нейросеть, то нужно будет превратить предикторы в dummy-переменные. Насколько хорошо разделены классы этими переменными? Насколько хорошо они коррелируют с целевой? Визуализируем эти зависимости с помощью cross-plot()::funModeling. Cross_plot показывает, как входная переменная коррелирует с целевой переменной, получая коэффициенты правдоподобия для каждого диапазона каждого входа.

Рассмотрим три переменных — v.fatl, ftlm и v.satl, разделенных на 4, 3 и 2 диапазона соответственно. Построим графики:

evalq(
  cross_plot(data = train.d, 
             str_input = c("v.fatl", "ftlm", "v.satl"), 
             str_target = "Class", 
             auto_binning = F,
             plot_type = "both"), #'quantity' 'percentual'
  env
  )

Discret 1

Рис.16. Cross-plot  переменной v.fatl/Class

Discret 2

Рис.17. Cross-plot  переменной ftlm/Class

Discret 3

Рис.18. Cross-plot переменной v.satl/Class

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

Можно просто (неоптимально) разделить предикторы на равные участки и посмотреть, как в таком случае они будут коррелировать с целевой. Возьмем три предыдущих переменных и две плохих (stlm, v.rstl) из набора train (недискретизованного). Разделим их на 10 равночастотных участков и посмотрим на cross-plot их с целевой:

evalq(
  cross_plot(
      DT$train  %>% select(-Data) %>%
      select(c(v.satl, ftlm, v.fatl, stlm, v.rstl, Class)) %>%
      as.data.frame(), 
      str_input = Cs(v.satl, ftlm, v.fatl, stlm, v.rstl), 
      str_target = "Class", 
      auto_binning = T,
      plot_type = "both"), #'quantity' 'percentual'
  env
)

Отрисуем пять графиков этих переменных:

Discret 4

Рис.19. Cross-plot переменной v.satl (10 участков) vs Class

Discret 5

Рис.20. Cross-plot переменной ftlml (10 участков) vs Class

Discret 6

Рис.21. Cross-plot переменной v.fatl (10 участков) vs Class

discret 8

Рис.22. Cross-plot переменной stlm (10 участков) vs Class

Discret 9

Рис.23. Cross-plot переменной v.rstl (10 участков) vs Class

Как видно из этих рисунков, и при разделении на 10 дискретных равночастотных участков переменные v.fatl, ftlm и v.satl имеют хорошо выраженный порог разделения уровней целевой. Становится понятно, почему две других переменных (stlm, v.rstl) нерелевантны. Кстати, это эффективный способ определения важности предикторов. Мы еще поговорим об этом ниже.

В заключение посмотрим, как входная переменная коррелирует с целевой переменной, сравнивая байесовским способом posterior conversion rate к целевой переменной. Полезно сравнивать категориальные ценности, которые не имеют внутреннего порядка. Для этого будем использовать функцию bayes_plot::funModeling. Возьмем переменные v.fatl, ftlm и v.satl с наборов train.d, val.d и test.d.

#------BayesTrain-------------------
evalq(
  {
    bayesian_plot(train.d, input = "v.fatl", 
                  target = "Class", 
                  title = "Bayesian comparison train$v.fatl/Class",
                  plot_all = F, extra_above = 5, 
                  extra_under = 5)
  },env
)
evalq(
  {
    bayesian_plot(train.d, input = "ftlm", 
                  target = "Class", 
                  title = "Bayesian comparison train$ftlm/Class",
                  plot_all = F, extra_above = 5, 
                  extra_under = 5)
  },env
)
evalq(
  {
    bayesian_plot(train.d, input = "v.satl", 
                  target = "Class", 
                  title = "Bayesian comparison train$v.satl/Class",
                  plot_all = F, extra_above = 5, 
                  extra_under = 5)
  },env
)
#------------BayesTest------------------------
evalq(
  {
    bayesian_plot(test.d, input = "v.fatl", 
                  target = "Class", 
                  title = "Bayesian comparison test$v.fatl/Class",
                  plot_all = F, extra_above = 5, 
                  extra_under = 5)
  },env
)
evalq(
  {
    bayesian_plot(test.d, input = "ftlm", 
                  target = "Class", 
                  title = "Bayesian comparison test$ftlm/Class",
                  plot_all = F, extra_above = 5, 
                  extra_under = 5)
  },env
)
evalq(
  {
    bayesian_plot(test.d, input = "v.satl", 
                  target = "Class", 
                  title = "Bayesian comparison test$v.satl/Class",
                  plot_all = F, extra_above = 5, 
                  extra_under = 5)
  },env
)
#-------------BayesVal---------------------------------
evalq(
  {
    bayesian_plot(val.d, input = "v.fatl", 
                  target = "Class", 
                  title = "Bayesian comparison val$v.fatl/Class",
                  plot_all = F, extra_above = 5, 
                  extra_under = 5)
  },env
)
evalq(
  {
    bayesian_plot(val.d, input = "ftlm", 
                  target = "Class", 
                  title = "Bayesian comparison val$ftlm/Class",
                  plot_all = F, extra_above = 5, 
                  extra_under = 5)
  },env
)
evalq(
  {
    bayesian_plot(val.d, input = "v.satl", 
                  target = "Class", 
                  title = "Bayesian comparison val$v.satl/Class",
                  plot_all = F, extra_above = 5, 
                  extra_under = 5)
  },env
)
#------------------------------------------

BayesCorrTrain

Рис.24. Байесовское сравнение переменных с целевой в наборе train

BayesCorrVal

Рис.25. Байесовское сравнение переменных с целевой в наборе val

BayesCorrTest

Рис.26. Байесовское сравнение переменных с целевой в наборе test

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

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

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


1.2. Создание новых признаков

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

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

Создание фиктивных переменных — еще один популярный способ работы с переменными. Чаще всего фиктивные переменные применяются при преобразовании категориальных переменных в числовые. Категориальная переменная может принимать значения 0 и 1. Мы можем создать фиктивные переменные для более чем двух классов категориальных переменных с N или N-1 переменных.

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

  1. Использовать значения даты и времени как переменные. Можно создавать новые переменные, учитывая различия в датах и времени.
  2. Создать новые соотношения и пропорции: вместо того, чтобы просто держать прошлые входы и выходы в наборе данных, мы можем занести в набор их соотношения, которые могут иметь большое значение.
  3. Применить стандартные преобразования. Глядя на колебания и участки переменной вместе с выходом, мы можем увидеть, улучшится ли корреляция после применения базовых преобразований. Наиболее часто используемые преобразования включают log, exp, квадратичную и тригонометрическую вариации.
  4. Проверить переменные на сезонность и создать модель за нужный период (неделя, месяц, сессия и т.п.).

Интуитивно понятно, что в понедельник поведение рынка не такое, как в среду или четверг. То есть, день недели — важный признак. Не менее важно, в каком периоде суток находится рынок (азиатская, европейская или американская сессии). Как мы можем определить эти признаки?

Используем возможности пакета timekit. Центральная функция пакета — tk_augment_timeseries_signature(), которая добавляет к временным меткам нашего начального набора pr целый ряд временных данных, которые могут быть полезны и как дополнительные признаки, и как параметры группировки. Какие это данные?

Index значение индекса, который был разложен
Index.num числовое значение индекса в секундах. База “1970-01-01 00:00:00”
diff разница в секундах от предыдущего числового значения индекса
Year год, компонента индекса
half половина составляющей индекса
quarter квартал, составляющая индекса
month месяц, составляющая индекса с основанием 1
month.xts месяц, составляющая индекс с базой 0, так как реализовано в xts
month.lbl метка месяца как упорядоченный фактор, начинающийся с января и заканчивающийся декабрем
day день, составляющая индекса
hour час, составляющая индекса
minute минута, составляющая индекса
second секунда, составляющая индекса
hour12 часовая компонента в 12-часовой шкале
am.pm утро (am) = 1, после полудня (pm) = 2
wday день недели с основанием 1. Воскресенье = 1, суббота = 7
wday.xts день недели с основанием 0, как реализовано в xts. Воскресенье = 0, суббота = 6
wday.lbl метка дня недели как упорядоченный фактор, начиная с воскресенья и заканчивая субботой
mday день месяца
qday день квартала
yday день года.
mweek неделя месяца
week номер недели в году (начало с воскресенья)
week.iso номер недели года по ISO (начало с понедельника)
week2 модуль для двухнедельной частоты
week3 модуль для трехнедельной частоты
week4 модуль для quad-недельной частоты

Возьмем начальный набор данных pr, усилим его функцией tk_augment_timeseries_signature(), добавим к начальному набору переменные mday, wday.lbl, hour, удалим неопределенные данные (NA) и сгруппируем данные по дням недели.

evalq(
  {
    tk_augment_timeseries_signature(pr) %>%
    select(c(mday, wday.lbl,  hour)) %>% 
    cbind(pr, .) -> pr.augm
    pr.compl <- pr.augm[complete.cases(pr.augm), ]
    pr.nest <- pr.compl %>% group_by(wday.lbl) %>% nest() 
  },
  env)
> str(env$pr.augm)
'data.frame':   8000 obs. of  33 variables:
 $ Data    : POSIXct, format: "2017-01-10 11:00:00" ...
 $ Open    : num  123 123 123 123 123 ...
 $ High    : num  123 123 123 123 123 ...
 $ Low     : num  123 123 123 123 123 ...
 $ Close   : num  123 123 123 123 123 ...
 $ Vol     : num  3830 3360 3220 3241 3071 ...
 ..................................................
 $ zigz    : num  123 123 123 123 123 ...
 $ dz      : num  NA -0.0162 -0.0162 -0.0162 -0.0162 ...
 $ sig     : num  NA -1 -1 -1 -1 -1 -1 -1 -1 -1 ...
 $ mday    : int  10 10 10 10 10 10 10 10 10 10 ...
 $ wday.lbl: Ord.factor w/ 7 levels "Sunday"<"Monday"<..: 3 3 3 3 3 3 3 3 3 3 ...
 $ hour    : int  11 11 11 11 12 12 12 12 13 13 ...

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

require(lubridate)
evalq({pr %>% mutate(.,
                     wday = wday(Data), #label = TRUE, abbr = TRUE),
                     day = day(Data),
                     hour = hour(Data)) %>%
    filter(wday != "Sat") -> pr1
  pr1.nest <- pr1 %>% na.omit %>% 
    group_by(wday) %>% nest()}, 
  env
)
#-------
str(env$pr1)
'data.frame':   7924 obs. of  33 variables:
 $ Data  : POSIXct, format: "2017-01-10 11:00:00" ...
 $ Open  : num  123 123 123 123 123 ...
 $ High  : num  123 123 123 123 123 ...
 $ Low   : num  123 123 123 123 123 ...
 $ Close : num  123 123 123 123 123 ...
 $ Vol   : num  3830 3360 3220 3241 3071 ...
 ..........................................
 $ zigz  : num  123 123 123 123 123 ...
 $ dz    : num  NA -0.0162 -0.0162 -0.0162 -0.0162 ...
 $ sig   : num  NA -1 -1 -1 -1 -1 -1 -1 -1 -1 ...
 $ wday  : int  3 3 3 3 3 3 3 3 3 3 ...
 $ day   : int  10 10 10 10 10 10 10 10 10 10 ...
 $ hour  : int  11 11 11 11 12 12 12 12 13 13 ...

Сгруппированные по дням недели данные выглядят так (воскресенье = 1, понедельник = 2 и т.д.):

> env$pr1.nest
# A tibble: 5 × 2
   wday                  data
  <int>                <list>
1     4 <tibble [1,593 Ч 32]>
2     5 <tibble [1,632 Ч 32]>
3     6 <tibble [1,624 Ч 32]>
4     2 <tibble [1,448 Ч 32]>
5     3 <tibble [1,536 Ч 32]>

Из данных в основном наборе pr можно дополнительно использовать переменные dL, dH на последних 3 барах.

2. Выбор предикторов

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

2.1. Визуальная оценка

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

Соберем в один набор log-трансформированные, sin-трансформированные, добавим tanh-преобразованные и нормализованные данные и оценим зависимость целевой и предикторов в этих наборах.

Последовательность обработки первичного набора (приведена на ниже показанном рисунке) следующая: сырые данные dataSet очищаем (без удаления высококоррелированных) и, разделив на train/val/test, получаем набор DT. Дальше по каждому виду трансформации выполним действия по схеме. Соберем все в один скрипт:

Preprocess

Рис.27. Структурная схема препроцессинга

Очистим набор, разделим на train/val/test и уберем ненужные данные:

#----Clean---------------------
require(caret)
require(pipeR)
evalq(
  {
    train = 1:2000
    val = 2001:3000
    test = 3001:4000
    DT <- list()
    dataSet %>%
      preProcess(., method = c("zv", "nzv", "conditionalX")) %>%
      predict(., dataSet) %>%
      na.omit -> dataSetClean
    list(train = dataSetClean[train, ], 
         val = dataSetClean[val, ], 
         test = dataSetClean[test, ]) -> DT
    rm(dataSetClean, train, val, test)
  }, 
  env)

Обработаем выбросы во всех наборах:

#------outlier-------------
evalq({
# определим новый лист для результата
  DTcap <- list()
# пройдемся по трем наборам
  foreach(i = 1:3) %do% {
    DT[[i]] %>% 
# уберем колонки c(Data, Class)
      select(-c(Data, Class)) %>%
# преобразуем в  data.frame и сохраним во временную переменную x
      as.data.frame() -> x
    if (i == 1) {
# при первом входе определеим параметры выбросов
      foreach(i = 1:ncol(x), .combine = "cbind") %do% {
        prep.outlier(x[ ,i]) %>% unlist()
      } -> pre.outl
      colnames(pre.outl) <- colnames(x)
    } 
# заменим выбросы на 5/95 % и сохраним результат в x.cap
    foreach(i = 1:ncol(x), .combine = "cbind") %do% {
      stopifnot(exists("pre.outl", envir = env))
      lower = pre.outl['lower.25%', i] 
      upper = pre.outl['upper.75%', i]
      med = pre.outl['med', i]
      cap1 = pre.outl['cap1.5%', i] 
      cap2 = pre.outl['cap2.95%', i] 
      treatOutlier(x = x[ ,i], impute = T, fill = T, 
                   lower = lower, upper = upper, 
                   med = med, cap1 = cap1, cap2 = cap2) 
      } %>% as.data.frame() -> x.cap
    colnames(x.cap) <- colnames(x)
    return(x.cap)
  } -> Dtcap
#уберем ненужные переменные
  rm(lower, upper, med, cap1, cap2, x.cap, x)
}, env)

Трансформируем переменные во всех наборах Dtcap без выбросов функцией log(x+1). Получим DTLn лист с тремя наборами логарифмированных переменных.

#------logtrans------------
evalq({
  DTLn <- list()
  foreach(i = 1:3) %do% {
    DTcap[[i]] %>% 
      apply(., 2, function(x) log2(x + 1)) %>%
      as.data.frame() %>%
      cbind(., Class = DT[[i]]$Class)
  } -> DTLn
},
env)

Трансформируем переменные во всех наборах Dtcap без выбросов функцией sin(2*pi*x). Получим DTSin лист с тремя наборами sin-трансформированных переменных.

#------sintrans--------------
evalq({
  DTSin <- list()
  foreach(i = 1:3) %do% {
    DTcap[[i]] %>% 
      apply(., 2, function(x) sin(2*pi*x)) %>%
      as.data.frame() %>%
      cbind(., Class = DT[[i]]$Class)
  } -> DTSin
},
env)

Трансформируем переменные во всех наборах Dtcap без выбросов функцией tanh(x). Получим DTTanh лист с тремя наборами tanh-трансформированных переменных.

#------tanhTrans----------
evalq({
  DTTanh <- list()
  foreach(i = 1:3) %do% {
    DTcap[[i]] %>% 
      apply(., 2, function(x) tanh(x)) %>%
      as.data.frame() %>%
      cbind(., Class = DT[[i]]$Class)
  } -> DTTanh
},
env)

Нормализуем наборы DT, DTLn, DTSin, DTTanh.

#------normalize-----------
evalq(
  {
# определяем параметры нормализации
    preProcess(DT$train, method = "spatialSign") -> preproc 
    list(train = predict(preproc, DT$train), 
         val = predict(preproc, DT$val),
         test = predict(preproc, DT$test)
 ) -> DTn
  }, 
  env) 
#--ln---
evalq(
  {
    preProcess(DTLn[[1]], method = "spatialSign") -> preprocLn 
    list(train = predict(preprocLn, DTLn[[1]]), 
         val = predict(preprocLn, DTLn[[2]]),
         test = predict(preprocLn, DTLn[[3]])
    ) -> DTLn.n
  }, 
  env)
#---sin---
evalq(
  {
    preProcess(DTSin[[1]], method = "spatialSign") ->  preprocSin 
    list(train = predict(preprocSin, DTSin[[1]]), 
         val = predict(preprocSin, DTSin[[2]]),
         test = predict(preprocSin, DTSin[[3]])
    ) -> DTSin.n
  }, 
  env)
#-----tanh-----------------
evalq(
  {
    preProcess(DTTanh[[1]], method = "spatialSign") -> preprocTanh 
    list(train = predict(preprocTanh, DTTanh[[1]]), 
         val = predict(preprocTanh, DTTanh[[2]]),
         test = predict(preprocTanh, DTTanh[[3]])
    ) -> DTTanh.n
  }, 
  env)

Дискретизируем наш набор DT с помощью функции mdlp::discretization.

##------discretize----------
#--------preCut---------------------
# определяем параметры дискретизации (cutpoint)
require(pipeR)
require(discretization)
evalq(
  #require(pipeR) 
# занимает некоторое время !
  pipeline({
    DT$train
    select(-Data)
    as.data.frame()
    mdlp() 
  }) -> mdlp.train, 
  env)
#-------cut_opt----------
evalq(
  {
    DTd <- list()
    mdlp.train$cutp %>% 
# определяем колонки которые нужно дискретизировать
      lapply(., function(x) is.numeric(x)) %>%
      unlist -> idx   # bool
    #----train-----------------
    mdlp.train$Disc.data[ ,idx] -> DTd$train 
    #---test------------
    DT$test %>% 
      select(-c(Data, Class)) %>%
      as.data.frame() -> test.d
# переразметим данные в соответствии с расчитанными диапазонами    
    foreach(i = 1:length(idx), .combine = 'cbind') %do% {
      if (idx[i]) {
        findInterval(test.d[ ,i], 
        vec = mdlp.train$cutp[[i]],
        rightmost.closed = FALSE, 
        all.inside = F,
        left.open = F)
        }
    } %>% as.data.frame() %>% add(1) %>%
      cbind(., DT$test$Class) -> DTd$test
    colnames(DTd$test) <- colnames(DTd$train)
    #-----val-----------------
    DT$val %>% 
      select(-c(Data, Class)) %>%
      as.data.frame() -> val.d
# переразметим данные в соответствии с расчитанными диапазонами  
    foreach(i = 1:length(idx), .combine = 'cbind') %do% {
      if (idx[i]) {
        findInterval(val.d[ ,i], 
        vec = mdlp.train$cutp[[i]],
        rightmost.closed = FALSE, 
        all.inside = F,
        left.open = F)
        }
    } %>% as.data.frame() %>% add(1) %>%
      cbind(., DT$val$Class) -> DTd$val 
    colnames(DTd$val) <- colnames(DTd$train)
# подчистим за собой
    rm(test.d, val.d)
  }, 
  env
)

Напомним, какие переменные у нас в начальном наборе DT$train:

require(funModeling)
plot_num(env$DT$train %>% select(-Data), bins = 20)

FSelect 1

Рис.28. Распределение переменных набора DT$train

Используем возможности пакета smbinning для определения релевантных предикторов в поднаборах train всех нормализованных наборов, полученных нами ранее (Dtn, DTLn.n, DTSin.n и DTTanh.n). Поскольку целевая в этом пакете должна быть числовой со значениями (0, 1) напишем функцию для необходимых преобразований.

#--------------------------------
require(smbinning)
targ.int <- function(x){
  x %>% tbl_df() %>%
  mutate(Cl = (as.numeric(Class) - 1) %>%
           as.integer()) %>%
  select(-Class) %>% as.data.frame()
}

Кроме того, пакет не любит переменные с точкой в имени. Напишем функцию, которая переименует все переменные с точкой в переменные с нижней чертой.

renamepr <- function(X){
  X %<>% rename(v_fatl = v.fatl,
               v_satl = v.satl,
               v_rftl = v.rftl,
               v_rstl = v.rstl,
               v_ftlm = v.ftlm,
               v_stlm = v.stlm,
               v_rbci = v.rbci,
               v_pcci = v.pcci)
  return(X)
}

Вычислим и отрисуем графики релевантных предикторов.

par(mfrow = c(2,2))
#--Ln--------------
evalq({
  df <- renamepr(DTLn.n[[1]]) %>% targ.int
  sumivt.ln.n = smbinning.sumiv(df = df, y = 'Cl')
  smbinning.sumiv.plot(sumivt.ln.n, cex = 0.7)
  rm(df)
}, 
env)
#---Sin-----------------
evalq({
  df <- renamepr(DTSin.n[[1]]) %>% targ.int
  sumivt.sin.n = smbinning.sumiv(df = df, y = 'Cl')
  smbinning.sumiv.plot(sumivt.sin.n, cex = 0.7)
  rm(df)
  }, 
env)
#---norm-------------
evalq({
  df <- renamepr(DTn[[1]]) %>% targ.int
  sumivt.n = smbinning.sumiv(df = df, y = 'Cl')
  smbinning.sumiv.plot(sumivt.n, cex = 0.7)
  rm(df)
  }, 
env)
#-----Tanh----------------
evalq({
  df <- renamepr(DTTanh.n[[1]]) %>% targ.int
  sumivt.tanh.n = smbinning.sumiv(df = df, y = 'Cl')
  smbinning.sumiv.plot(sumivt.tanh.n, cex = 0.7)
  rm(df)
  }, 
env)
par(mfrow = c(1,1))

FSelect 2

Рис.29. Важность предикторов в поднаборе train нормализованных наборов

Во всех наборах сильны пять предикторов — v_fatl, ftlm, v_satl, rbci, v_rbci, правда, в различном порядке. Средняя сила у четырех предикторов — pcci, v_ftlm, v_stlm, v_rftl. Слабые — v_pcci и stlm. Можно посмотреть по каждому набору распределение переменных в порядке их значимости:

env$sumivt.ln.n
     Char     IV               Process
5  v_fatl 0.6823    Numeric binning OK
1    ftlm 0.4926    Numeric binning OK
6  v_satl 0.3737    Numeric binning OK
3    rbci 0.3551    Numeric binning OK
11 v_rbci 0.3424    Numeric binning OK
10 v_stlm 0.2591    Numeric binning OK
4    pcci 0.2440    Numeric binning OK
9  v_ftlm 0.2023    Numeric binning OK
7  v_rftl 0.1442    Numeric binning OK
12 v_pcci 0.0222    Numeric binning OK
2    stlm     NA No significant splits
8  v_rstl     NA No significant splits

Последние три переменных можно спокойно отбросить. Т.е., останутся пять сильных и четыре средних. Определим имена лучших переменных (IV > 0.1).

evalq(sumivt.sin.n$Char[sumivt.sin.n$IV > 0.1] %>% 
        na.omit %>% as.character() -> best.sin.n, 
      env)
> env$best.sin.n
[1] "v_fatl" "ftlm"   "rbci"   "v_rbci" "v_satl" "pcci"  
[7] "v_ftlm" "v_stlm" "v_rftl"

Посмотрим более подробно переменные v_fatl и ftlm.

evalq({
    df <- renamepr(DTTanh.n[[1]]) %>% targ.int
    x = 'v_fatl'
    y = 'Cl'
    res <- smbinning(df = df, 
                        y = y,
                        x = x) 
  #res$ivtable # Tabulation and Information Value
  #res$iv # Information value
  #res$bands # Bins or bands
  #res$ctree  # Decision tree from partykit
  par(mfrow = c(2,2))
  sub = paste0(x, "  vs  ", y) #rbci vs Cl"
  boxplot(df[[x]]~df[[y]],
          horizontal = TRUE, 
          frame = FALSE, col = "lightblue",
          main = "Distribution")
  mtext(sub,3) #ftlm
  smbinning.plot(res, option = "dist",
                 sub = sub) #"pcci vs Cl")
  smbinning.plot(res, option = "goodrate", #"badrate"
                 sub = sub) #"pcci vs Cl")
  smbinning.plot(res, option = "WoE",
                 sub = sub) #"pcci vs Cl")
  par(mfrow = c(1, 1))
}, env)

FSelect 3

Рис.30. Связь диапазонов переменной v_fatl с целевой Cl

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

> env$res$cuts
[1] -0.3722 -0.0433  0.1482

Проведем аналогичный расчет для переменной ftlm и построим графики:

FSelect 4

Рис.31. Связь диапазонов переменной ftlm c целевой Cl

Точки разделения диапазонов:

> env$res$cuts
[1] -0.2084 -0.0150  0.2216

Имея точки разделения, мы можем дискретизировать переменные в наших наборах и посмотреть, насколько отличаются:

  • важные переменные, ранее определенные с помощью функции mdlp::discretization от переменных, определенных с помощью функции smbinning::smbinning;
  • разбиение переменных на диапазоны.

У нас уже есть один набор с данными, дискретизированными функцией mdlp::discretization DTd. Проделаем то же самое, но с помощью функции smbinning::smbinning и только train поднабором.

Определим параметры дискретизации:

evalq({
  res <- list()
  DT$train %>% renamepr() %>% targ.int() -> df
  x <- colnames(df)
  y <- "Cl"
  foreach(i = 1:(ncol(df) - 1)) %do% {
    smbinning(df, y = y, x = x[i])
  } -> res
  res %>% lapply(., function(x) x[1] %>% is.list) %>%
    unlist -> idx
}, env) 

Дискретизируем поднабор DT$train:

evalq({
  DT1.d <- list()
  DT$train %>% renamepr() %>% 
    targ.int() %>% select(-Cl) -> train
  foreach(i = 1:length(idx), .combine = 'cbind') %do% {
    if (idx[i]) {
      findInterval(train[ ,i], 
                   vec = res[[i]]$cuts,
                   rightmost.closed = FALSE, 
                   all.inside = F,
                   left.open = F)
    }
  } %>% as.data.frame() %>% add(1) %>%
    cbind(., DT$train$Class) -> DT1.d$train
  colnames(DT1.d$train) <- colnames(train)[idx] %>%
    c(., 'Class')
},
env)

Определим лучшие переменные с информационной значимостью больше 0.1, упорядоченные по возрастанию:

evalq({
  DT$train %>% renamepr() %>% targ.int() -> df
  sumivt.dt1.d = smbinning.sumiv(df = df, y = 'Cl')
  sumivt.dt1.d$Char[sumivt.dt1.d$IV > 0.1] %>% 
    na.omit %>% as.character() -> best.dt1.d
  rm(df)
}, 
env)

Отрисуем график разбиения переменных в наборе DTd$train:

require(funModeling)
plot_num(env$DTd$train)

FSelect 5

Рис.32. Переменные набора DT$ train, дискретизированные функцией mdlp

График набора DT1.d со всеми переменными и с лучшими приведен ниже.

plot_num(env$DT1.d$train)

FSelect 6

Рис.33. Переменные набора DT1 d$train, дискретизированные функцией smbinning (все)

plot_num(env$DT1.d$train[ ,env$best.dt1.d])

FSelect 7

Рис.34. Переменные набора DT1.d$train, дискретизированные функцией smbinning (лучшие упорядоченные по возрастанию информационной значимости)

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

2.2. Аналитическая оценка

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

Используем пакет varbvs. В одноименной функции реализованы быстрые алгоритмы для установки байесовских моделей выбора переменных и вычисления коэффициентов Байеса, в которых результат (или целевая) моделируется с использованием линейной регрессии или логистической регрессии. Алгоритмы основаны на вариационных приближениях, описанных в "Масштабируемом вариационном выводе для выбора байесовских переменных в регрессии и его точности в исследованиях генетических ассоциаций" (P. Carbonetto and M. Stephens, Bayesian Analysis 7, 2012, pages 73-108). Это программное обеспечение было применено к большим наборам данных с более чем миллионом переменных и с тысячами образцов.

Функция varbvs() принимает на вход матрицу и целевую — численный вектор (0, 1). Возьмем наш набор с нормализованными данными DTTanh.n$train и проверим, какие предикторы будут определяться как важные по этому методу.

require(varbvs)
evalq({
  train <- DTTanh.n$train %>% targ.int() %>%  as.matrix()
  fit <- varbvs(X = train[ ,-ncol(train)] , 
                Z = NULL,
                y = train[ ,ncol(train)] %>% as.vector(),
                "binomial", 
                logodds = seq(-2,-0.5,0.1),
                optimize.eta = T,
                initialize.params = T,
                verbose = T, nr = 100
                )
  print(summary(fit))
}, env)

Welcome to           --       *                              *               
VARBVS version 2.0.3 --       |              |               |               
large-scale Bayesian --       ||           | |    |          || |     |   |  
variable selection   -- |     || | |    |  | ||  ||        |||| ||    |   || 
****************************************************************************
Copyright (C) 2012-2017 Peter Carbonetto.
See http://www.gnu.org/licenses/gpl.html for the full license.
Fitting variational approximation for Bayesian variable selection model.
family:     binomial   num. hyperparameter settings: 16 
samples:    2000       convergence tolerance         1.0e-04
variables:  12         iid variable selection prior: yes 
covariates: 0          fit prior var. of coefs (sa): yes 
intercept:  yes        fit approx. factors (eta):    yes 
Finding best initialization for 16 combinations of hyperparameters.
-iteration-   variational    max.   incl variance params
outer inner   lower bound  change   vars   sigma      sa
 0016 00018 -1.204193e+03 6.1e-05 0003.3      NA 3.3e+00
Computing marginal likelihood for 16 combinations of hyperparameters.
-iteration-   variational    max.   incl variance params
outer inner   lower bound  change   vars   sigma      sa
 0016 00002 -1.204193e+03 3.2e-05 0003.3      NA 3.3e+00
Summary of fitted Bayesian variable selection model:
family:     binomial   num. hyperparameter settings: 16
samples:    2000       iid variable selection prior: yes
variables:  12         fit prior var. of coefs (sa): yes
covariates: 1          fit approx. factors (eta):    yes
maximum log-likelihood lower bound: -1204.1931
Hyperparameters: 
        estimate Pr>0.95             candidate values
sa          3.49 [3.25,3.6]          NA--NA
logodds    -0.75 [-1.30,-0.50]       (-2.00)--(-0.50)
Selected variables by probability cutoff:
>0.10 >0.25 >0.50 >0.75 >0.90 >0.95 
    3     3     3     3     3     3 
Top 5 variables by inclusion probability:
  index variable   prob PVE coef*  Pr(coef.>0.95)
1     1     ftlm 1.0000  NA 2.442 [+2.104,+2.900]
2     4     pcci 1.0000  NA 2.088 [+1.763,+2.391]
3     3     rbci 0.9558  NA 0.709 [+0.369,+1.051]
4    10   v.stlm 0.0356  NA 0.197 [-0.137,+0.529]
5     6   v.satl 0.0325  NA 0.185 [-0.136,+0.501]
*See help(varbvs) about interpreting coefficients in logistic regression.

Как видим, определены пять лучших предикторов (ftlm, pcci, rbci, v.stlm, v.satl). Они входят в девятку лучших, которую мы определили раньше, но в другом порядке и с другими весами значимости. Поскольку модель у нас уже есть, давайте проверим, какое качество результата мы получим на валидационном и тестовом наборе.

Валидационный набор:

#-----------------
evalq({
  val <- DTTanh.n$val %>% targ.int() %>%
    as.matrix()
  y = val[ ,ncol(val)] %>% as.vector()
  pr <- predict(fit, X = val[ ,-ncol(val)] , 
                Z = NULL)
  
}, env)
cm.val <- confusionMatrix(table(env$y, env$pr))
> cm.val
Confusion Matrix and Statistics

   
      0   1
  0 347 204
  1 137 312
                                          
               Accuracy : 0.659           
                 95% CI : (0.6287, 0.6884)
    No Information Rate : 0.516           
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.3202          
 Mcnemar's Test P-Value : 0.0003514       
                                          
            Sensitivity : 0.7169          
            Specificity : 0.6047          
         Pos Pred Value : 0.6298          
         Neg Pred Value : 0.6949          
             Prevalence : 0.4840          
         Detection Rate : 0.3470          
   Detection Prevalence : 0.5510          
      Balanced Accuracy : 0.6608          
                                          
       'Positive' Class : 0  

Результат, прямо скажем, не ахти. Тестовый набор:

evalq({
  test <- DTTanh.n$test %>% targ.int() %>%
    as.matrix()
  y = test[ ,ncol(test)] %>% as.vector()
  pr <- predict(fit, X = test[ ,-ncol(test)] , 
                Z = NULL)
  
}, env)
cm.test <- confusionMatrix(table(env$y, env$pr))
> cm.test
Confusion Matrix and Statistics

   
      0   1
  0 270 140
  1 186 404
                                        
               Accuracy : 0.674         
                 95% CI : (0.644, 0.703)
    No Information Rate : 0.544         
    P-Value [Acc > NIR] : < 2e-16       
                                        
                  Kappa : 0.3375        
 Mcnemar's Test P-Value : 0.01269       
                                        
            Sensitivity : 0.5921        
            Specificity : 0.7426        
         Pos Pred Value : 0.6585        
         Neg Pred Value : 0.6847        
             Prevalence : 0.4560        
         Detection Rate : 0.2700        
   Detection Prevalence : 0.4100        
      Balanced Accuracy : 0.6674        
                                        
       'Positive' Class : 0  

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

Итак: лучшие по версии varbvs — ftlm, pcci, rbci, v.stlm, v.satl


2.3. Нейросеть

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

Используем пакет FCNN4R, который предоставляет интерфейс для программ ядра из библиотеки FCNN C ++. FCNN основан на совершенно новом представлении искусственной нейронной сети, которое предполагает эффективность, модульность и расширяемость. FCNN4R обеспечивает стандартное обучение (backpropagation, Rprop, имитация отжига, стохастический градиент) и алгоритмы обрезки (minimum magnitude, Optimal Brain Surgeon), но прежде всего я вижу в ней эффективный вычислительный движок.

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

Сети могут быть экспортированы в функции C, чтобы интегрировать их в практически любое программное решение.

Создаем полносвязную нейросеть с двумя скрытыми слоями. Количество нейронов в каждом слое: входной = 12 (количество предикторов), выходной = 1. Инициируем нейроны случайными весами в диапазоне +/- 0.17. Установим функции активации в каждом слое нейросети (кроме входного) = c("tanh", "tanh", "sigmoid"). Подготовим наборы train/val/test.

Ниже представлен скрипт, выполняющий эту последовательность действий.

require(FCNN4R)
evalq({
mlp_net(layers = c(12, 8, 5, 1), name = "n.tanh") %>%
  mlp_rnd_weights(a = 0.17) %>% 
  mlp_set_activation(layer = c(2, 3, 4), 
  activation = c("tanh", "tanh", "sigmoid"), #"threshold", "sym_threshold",
                                            #"linear", "sigmoid", "sym_sigmoid",
                                            #"tanh", "sigmoid_approx",
                                            #"sym_sigmoid_approx"), 
                 slope = 0) -> Ntanh #show() 
#-------
train <- DTTanh.n$train %>% targ.int() %>% as.matrix()
test <- DTTanh.n$test %>% targ.int() %>%  as.matrix()
val <- DTTanh.n$val %>% targ.int() %>% as.matrix()
}, env)

Из предлагаемых методов обучения будем использовать rprop. Зададим константы: tol — уровень ошибки, при достижении которого нужно остановить обучение, max_ep — количество эпох, после выполнения которых нужно остановить обучение, l2reg — коэффициент регуляризации. Обучим модель с этими параметрами, посмотрим визуально, какую сеть и ошибку обучения мы получили.

evalq({
  tol <- 1e-1
  max_ep = 1000
  l2reg = 0.0001
net_rp <- mlp_teach_rprop(Ntanh, 
                          input = train[ ,-ncol(train)], 
                          output = train[ ,ncol(train)] %>% as.matrix(),
                          tol_level = tol, 
                          max_epochs = max_ep, 
                          l2reg = l2reg,
                          u = 1.2, d = 0.5, 
                          gmax = 50, gmin = 1e-06, 
                          report_freq = 100)
}, env)
plot(env$net_rp$mse, t = "l", 
     main = paste0("max_epochs =", env$max_ep, " l2reg = ", env$l2reg))

NN1

Рис.35. Ошибка обучения нейросети

evalq(mlp_plot(net_rp$net, FALSE), envir = env)

NN2

Рис.36. Структура нейросети

Обрезка (Prune)

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

evalq({
  tol <- 1e-1
  max_ep = 1000
  l2reg = 0.0001
  mlp_prune_mag(net_rp$net, 
                input = train[ ,-ncol(train)], 
                output = train[ ,ncol(train)] %>% as.matrix(),
                tol_level = tol,  
                max_reteach_epochs = max_ep, 
                report = FALSE,
                plots = TRUE) -> net_rp_prune
  
}, env)

NN3

Рис.37. Обрезанная нейросеть

Мы видим, что при конкретной структуре нейросети, начальной инициализации, функциях активации и ошибке обучения мы вполне можем обойтись сетью структуры (12, 2, 1, 1). Какие предикторы при этом выбрала нейросеть?

evalq(
  best <- train %>% tbl_df %>%  select(c(1,5,7,8,10,12)) %>% colnames(),
           env)
env$best
[1] "ftlm"   "v.fatl" "v.rftl" "v.rstl" "v.stlm"
[6] "v.pcci"

Двух переменных — v.rstl и v.pcci — нет в составе лучших 9, определенных ранее.

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

Экспериментируйте!

Заключение

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

Приложение

1. Скрипты FeatureTransformation.R, FeatureSelect.R, FeatureSelect_analitic.R  FeatureSelect_NN.R и снимок с результатами работы скриптов первой части статьи Part_1.RData можно скачать с Git /Part_II.


elibrarius
elibrarius | 23 окт 2017 в 19:11
Vladimir Perervenko:

1. Алгоритм работает именно так. С одним исключением : отсеиваются нейроны во всех скрытых слоях.

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

Преимущества? Убираем все лишнее, что порождает ложную классификацию. Так утверждают разработчики.

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

Удачи

1) Если связи от входов к внутр. нейронам отсутствуют, то и сами входы можно отключить.

2) Меня смущает многократно превышающие затраты времени, чем если просто обучить полную модель по п.1 Если результат тот же, то зачем тратить столько времени?

Могу предположить, что отсеянные предикторы будут игнорироваться в будущем при переобучении/дообучении и экономия времени будет именно тогда. Но ведь и важность предикторов со временем может поменяться.

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

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


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

Vladimir Perervenko
Vladimir Perervenko | 23 окт 2017 в 20:24
elibrarius:

1) Если связи от входов к внутр. нейронам отсутствуют, то и сами входы можно отключить.

2) Меня смущает многократно превышающие затраты времени, чем если просто обучить полную модель по п.1 Если результат тот же, то зачем тратить столько времени?

Могу предположить, что отсеянные предикторы будут игнорироваться в будущем при переобучении/дообучении и экономия времени будет именно тогда. Но ведь и важность предикторов со временем может поменяться.

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

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


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

Посмотрите пакет и описание функции. Я не разбирался глубоко. Но в нескольких моделях (например H2O) именно так определяют важность предикторов. Я просто проверил и мне не показался этот способ надежным.

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

Это единственно правильный путь. ИМХО

Удачи

Удачи

elibrarius
elibrarius | 23 окт 2017 в 20:41
Vladimir Perervenko:

Посмотрите пакет и описание функции. Я не разбирался глубоко. Но в нескольких моделях (например H2O) именно так определяют важность предикторов. Я просто проверил и мне не показался этот способ надежным.

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

Это единственно правильный путь. ИМХО

Удачи

Удачи

Спасибо!
elibrarius
elibrarius | 30 окт 2017 в 20:11

Не лучше ли данные о часе и дне вводить в НС не одним предиктором, а отдельными по числу часов и дней?

Если одним, то вес/значимость понедельника (1) и вторника (2) будет отличаться на 100%, а четверга (4) и пятницы(5) на 20%.  С часами 1,2 и 22,23 еще сильнее отличие. А переход от 5 к 1  или 23 к 1 - вообще будет огромным скачком в весе.

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

5 и 24 лишних предикторов - многовато. Но т.к. последовательность дней и часов циклична, то их можно перевести в угол на окружности и поступить по аналогии с обычными углами: "Разумнее выглядит подача в качестве входных данных синуса и косинуса этого угла." Т.е. будет по 2 предиктора на часы и на дни. Идея взята отсюда http://megaobuchalka.ru/9/5905.html
Vladimir Perervenko
Vladimir Perervenko | 31 окт 2017 в 09:21
elibrarius:

Не лучше ли данные о часе и дне вводить в НС не одним предиктором, а отдельными по числу часов и дней?

Если одним, то вес/значимость понедельника (1) и вторника (2) будет отличаться на 100%, а четверга (4) и пятницы(5) на 20%.  С часами 1,2 и 22,23 еще сильнее отличие. А переход от 5 к 1  или 23 к 1 - вообще будет огромным скачком в весе.

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

5 и 24 лишних предикторов - многовато. Но т.к. последовательность дней и часов циклична, то их можно перевести в угол на окружности и поступить по аналогии с обычными углами: "Разумнее выглядит подача в качестве входных данных синуса и косинуса этого угла." Т.е. будет по 2 предиктора на часы и на дни. Идея взята отсюда http://megaobuchalka.ru/9/5905.html

Час суток и день(недели, месяца, года) являются номинальными переменными, не числовыми. Можно говорить только о том упорядоченны они или нет. Поэтому за предложение спасибо, но не принимается. 

Использовать эти переменные как числовые ? Можно экспериментировать, но я в эту сторону не смотрю. Если у Вас есть какие то результаты, поделитесь.

Удачи

TradeObjects: Автоматизация торговли на основе графических объектов в MetaTrader TradeObjects: Автоматизация торговли на основе графических объектов в MetaTrader

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

Глубокие нейросети (Часть I). Подготовка данных Глубокие нейросети (Часть I). Подготовка данных

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

Графические интерфейсы XI: Интеграция графической стандартной библиотеки (build 16) Графические интерфейсы XI: Интеграция графической стандартной библиотеки (build 16)

Недавно разработчики MQL5 представили новую версию графической библиотеки для создания научных графиков (класс CGraphic). В этом обновлении нашей библиотеки будет представлена версия с новым элементом для создания графиков. Теперь визуализировать данные различных типов будет ещё проще.

Универсальный торговый эксперт: Индикатор CUnIndicator и работа с отложенными ордерами (часть 9) Универсальный торговый эксперт: Индикатор CUnIndicator и работа с отложенными ордерами (часть 9)

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