R скорость data.table
У меня есть конкретная проблема с производительностью, которую я хочу расширить, если возможно.
Контекст:
Я играл в google colab с образцом кода python для агента Q-Learning, который связывает состояние и действие со значением с помощью defaultdict:
self._qvalues = defaultdict(lambda: defaultdict(lambda: 0))
return self._qvalues[state][action]
Не эксперт, но я понимаю, что он возвращает значение или добавляет и возвращает 0, если ключ не найден.
Я адаптирую часть этого в R.
Проблема в том, что я не знаю, сколько у меня комбинаций состояния / значений, и технически я не должен знать, сколько состояний я предполагаю.
Сначала я пошел неправильным путем, с rbind
of data.frame
s, и это было очень медленно.
Затем я заменил свой объект 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
, поэтому я полагаю, что проблема заключается в моем выборе контейнера.
Вопросов:
- Я неправильно использую data.table и могу ли это исправить для улучшения и соответствия реализации python?
- возможен ли другой дизайн, позволяющий избежать объявления всех комбинаций состояния / действий, которые могут стать проблемой, если размеры станут слишком большими?
- Я видел о хеш-пакете, можно ли это сделать?
Большое спасибо за любой указатель!
ОБНОВИТЬ:
спасибо за вклад. Итак, я заменил 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.table
a на a, collections::dict
и узкое место полностью исчезло.
Ответы
data.table
быстро выполняет поиск и манипуляции с очень большими таблицами данных, но не может быстро добавлять строки одну за другой, как словари Python. Я ожидал, что он будет копировать всю таблицу каждый раз, когда вы добавляете строку, которая явно не то, что вам нужно.
Вы можете либо попробовать использовать среды (которые похожи на хэш-карту), либо, если вы действительно хотите сделать это в R, вам может понадобиться специальный пакет, вот ссылка на ответ с несколькими вариантами.
Контрольный показатель
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
он используется