R скорость data.table

Aug 19 2020

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

Контекст:

Я играл в google colab с образцом кода python для агента Q-Learning, который связывает состояние и действие со значением с помощью defaultdict:

self._qvalues = defaultdict(lambda: defaultdict(lambda: 0))
return self._qvalues[state][action]

Не эксперт, но я понимаю, что он возвращает значение или добавляет и возвращает 0, если ключ не найден.

Я адаптирую часть этого в R.
Проблема в том, что я не знаю, сколько у меня комбинаций состояния / значений, и технически я не должен знать, сколько состояний я предполагаю.
Сначала я пошел неправильным путем, с rbindof data.frames, и это было очень медленно.
Затем я заменил свой объект R на data.frame(state, action, value = NA_real). он работает, но все еще очень медленно. другая проблема - мой объект data.frame имеет максимальный размер, что может быть проблематичным в будущем.
затем я изменил свой data.frameна a data.table, что дало мне худшие результаты, затем я, наконец, проиндексировал его (состояние, действие).

qvalues <- data.table(qstate = rep(seq(nbstates), each = nbactions),
                        qaction = rep(seq(nbactions), times = nbstates),
                        qvalue = NA_real_,
                        stringsAsFactors =  FALSE)
setkey(qvalues, "qstate", "qaction")

Проблема:

Сравнивая googlecolab / python с моей локальной реализацией R, Google выполняет доступ 1000x10e4 к объекту, скажем, за 15 секунд, в то время как мой код выполняет доступ 100x100 за 28 секунд. Я получил улучшения на 2 секунды путем компиляции байтов, но это все еще плохо.

Используя profvis, я вижу, что большая часть времени тратится на доступ к таблице data.table на эти два вызова:

qval <- self$qvalues[J(state, action), nomatch = NA_real_]$qvalue
self$qvalues[J(state, action)]$qvalue <- value

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

Вопросов:

  1. Я неправильно использую data.table и могу ли это исправить для улучшения и соответствия реализации python?
  2. возможен ли другой дизайн, позволяющий избежать объявления всех комбинаций состояния / действий, которые могут стать проблемой, если размеры станут слишком большими?
  3. Я видел о хеш-пакете, можно ли это сделать?

Большое спасибо за любой указатель!

ОБНОВИТЬ:

спасибо за вклад. Итак, я заменил 3 доступа к моей таблице data.table, используя ваши предложения:

#self$qvalues[J(state, action)]$qvalue <- value
self$qvalues[J(state, action), qvalue := value] #self$qvalues[J(state, action),]$qvalue <- 0 self$qvalues[J(state, action), qvalue := 0]
#qval <- self$qvalues[J(state, action), nomatch = NA_real_]$qvalue
qval <- self$qvalues[J(state, action), nomatch = NA_real_, qvalue]

это снизило время выполнения с 33 до 21, что является значительным улучшением, но все еще очень медленным по сравнению с defaultdictреализацией на Python .

Я отметил следующее:
работа в пакетном режиме: я не думаю, что смогу, поскольку вызов функции зависит от предыдущего вызова.
peudospin> Я вижу, вы удивлены, что получение занимает много времени. Я тоже, но это то, что заявляет profvis:

и здесь код функции в качестве ссылки:

QAgent$set("public", "get_qvalue", function( state, action) {
  #qval <- self$qvalues[J(state, action), nomatch = NA_real_]$qvalue
  qval <- self$qvalues[J(state, action), nomatch = NA_real_, qvalue] if (is.na(qval)) { #self$qvalues[self$qvalues$qstate == state & self$qvalues$qaction == action,]$qvalue <- 0 #self$qvalues[J(state, action),]$qvalue <- 0 self$qvalues[J(state, action), qvalue := 0]
    return(0)
  }
  return(qval)
})

На этом этапе, если больше не будет предложений, я сделаю вывод, что таблица data.table слишком медленная для такого рода задач, и я должен изучить возможность использования envили collections. (как предложено там: быстрый поиск отдельного элемента R из списка vs data.table vs hash )

ЗАКЛЮЧЕНИЕ:

Я заменил data.tablea на a, collections::dictи узкое место полностью исчезло.

Ответы

2 pseudospin Aug 19 2020 at 02:45

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

Вы можете либо попробовать использовать среды (которые похожи на хэш-карту), либо, если вы действительно хотите сделать это в R, вам может понадобиться специальный пакет, вот ссылка на ответ с несколькими вариантами.

AbdessabourMtk Aug 19 2020 at 04:53

Контрольный показатель

library(data.table)
Sys.setenv('R_MAX_VSIZE'=32000000000) # add to the ram limit
setDTthreads(threads=0) # use maximum threads possible
nbstates <- 1e3
nbactions <- 1e5

cartesian <- function(nbstates,nbactions){
    x= data.table(qstate=1:nbactions)
    y= data.table(qaction=1:nbstates)
    k = NULL
    x = x[, c(k=1, .SD)]
    setkey(x, k)
    y = y[, c(k=1, .SD)]
    setkey(y, NULL)
    x[y, allow.cartesian=TRUE][, c("k", "qvalue") := list(NULL, NA_real_)][]
}

#comparing seq with `:`
(bench = microbenchmark::microbenchmark(
    1:1e9,
    seq(1e9),
    times=1000L
    ))
#> Unit: nanoseconds
#>        expr  min   lq     mean median   uq   max neval
#>     1:1e+09  120  143  176.264  156.0  201  5097  1000
#>  seq(1e+09) 3039 3165 3333.339 3242.5 3371 21648  1000
ggplot2::autoplot(bench)


(bench = microbenchmark::microbenchmark(
    "Cartesian product" = cartesian(nbstates,nbactions),
    "data.table assignement"=qvalues <- data.table(qstate = rep(seq(nbstates), each = nbactions),
                        qaction = rep(seq(nbactions), times = nbstates),
                        qvalue = NA_real_,
                        stringsAsFactors =  FALSE),
    times=100L))
#> Unit: seconds
#>                    expr      min       lq     mean   median       uq      max neval
#>       Cartesian product 3.181805 3.690535 4.093756 3.992223 4.306766 7.662306  100
#>  data.table assignement 5.207858 5.554164 5.965930 5.895183 6.279175 7.670521  100
#>      data.table  (1:nb) 5.006773 5.609738 5.828659  5.80034 5.979303 6.727074  100
#>  
#>  
ggplot2::autoplot(bench)

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