R prędkość danych. Tabela

Aug 19 2020

Mam konkretny problem z wydajnością, który chciałbym omówić bardziej ogólnie, jeśli to możliwe.

Kontekst:

Bawiłem się w Google Colab z przykładowym kodem Pythona dla agenta Q-Learning, który kojarzy stan i akcję z wartością za pomocą defaultdict:

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

Nie jest ekspertem, ale rozumiem, że zwraca wartość lub dodaje i zwraca 0, jeśli klucz nie zostanie znaleziony.

adaptuję część tego w R.,
problem polega na tym, że nie wiem, ile mam kombinacji stanów / wartości, a technicznie rzecz biorąc, nie powinienem wiedzieć, ile stanów wydaje mi się.
Na pierwszy pojechałem w niewłaściwy sposób, przy czym rbindod data.frames, a że był bardzo powolny.
Następnie zamieniłem mój obiekt R na data.frame(state, action, value = NA_real). działa, ale nadal jest bardzo powolny. innym problemem jest to, że mój obiekt data.frame ma maksymalny rozmiar, który może być problematyczny w przyszłości.
potem zmieniłem mój data.framena a data.table, który dał mi najgorsze wyniki, a potem w końcu zindeksowałem go według (stan, akcja).

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

Problem:

Porównując googlecolab / python z moją lokalną implementacją R, Google wykonuje dostęp 1000x10e4 do obiektu w powiedzmy 15 sekund, podczas gdy mój kod wykonuje dostęp 100x100 w 28 sekund. Uzyskałem 2-sekundowe ulepszenia dzięki kompilacji bajtów, ale to wciąż szkoda.

Używając profvis, widzę, że większość czasu spędza na uzyskiwaniu dostępu do data.table na tych dwóch wywołaniach:

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

Naprawdę nie wiem, co ma Google, ale mój komputer to bestia. Widziałem również, że niektóre testy porównawcze data.tablebyły szybsze niż pandas, więc przypuszczam, że problem leży w moim wyborze kontenera.

Pytania:

  1. czy moje użycie data.table jest niewłaściwe i czy można to naprawić, aby ulepszyć i dopasować implementację języka Python?
  2. Czy jest możliwy inny projekt, aby uniknąć deklarowania wszystkich kombinacji stanu / działań, które mogłyby stanowić problem, gdyby wymiary stały się zbyt duże?
  3. widziałem pakiet hash, czy to jest droga?

Wielkie dzięki za każdą wskazówkę!

AKTUALIZACJA:

dzięki za cały wkład. Więc to, co zrobiłem, to zastąpienie 3 dostępu do moich danych.table, korzystając z twoich sugestii:

#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]

skróciło to czas działania z 33 do 21 sekund, co jest ogromną poprawą, ale nadal jest bardzo powolne w porównaniu z defaultdictimplementacją Pythona .

Zauważyłem, co następuje:
praca wsadowa: nie sądzę, żebym mógł to zrobić, ponieważ wywołanie funkcji zależy od poprzedniego wywołania.
peudospin> Widzę, że jesteś zaskoczony, że uzyskanie jest czasochłonne. ja też, ale to właśnie stwierdza profvis:

a tutaj kod funkcji jako odniesienie:

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)
})

W tym momencie, jeśli nie będzie już sugestii, stwierdzę, że data.table jest po prostu zbyt wolna dla tego rodzaju zadań i powinienem przyjrzeć się użyciu pliku envlub a collections. (zgodnie z sugestią: R szybkie wyszukiwanie pojedynczego elementu z listy vs data.table vs hash )

WNIOSEK:

Wymieniłem data.tablena a collections::dicti wąskie gardło całkowicie zniknęło.

Odpowiedzi

2 pseudospin Aug 19 2020 at 02:45

data.tablejest szybki do wyszukiwania i manipulacji w bardzo dużych tabelach danych, ale nie będzie szybki w dodawaniu wierszy jeden po drugim, jak w słownikach Pythona. Spodziewałbym się, że będzie to kopiowanie całej tabeli za każdym razem, gdy dodasz wiersz, który najwyraźniej nie jest tym, czego chcesz.

Możesz spróbować użyć środowisk (które są czymś w rodzaju hashmap) lub jeśli naprawdę chcesz to zrobić w R, możesz potrzebować specjalistycznego pakietu, tutaj jest link do odpowiedzi z kilkoma opcjami.

AbdessabourMtk Aug 19 2020 at 04:53

Reper

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)

jasne jest, że użycie seqzajmuje więcej czasu niż wywołanie 1:nb. plus użycie produktu kartezjańskiego sprawia, że ​​kod jest szybszy, nawet gdy 1:nbjest używany