Kecepatan R data.table

Aug 19 2020

Saya memiliki masalah kinerja tertentu, yang ingin saya sampaikan secara lebih umum jika memungkinkan.

Konteks:

Saya telah bermain-main di google colab dengan contoh kode python untuk agen Q-Learning, yang mengaitkan keadaan dan tindakan ke nilai menggunakan defaultdict:

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

Bukan ahli tetapi pemahaman saya adalah mengembalikan nilai atau menambah dan mengembalikan 0 jika kunci tidak ditemukan.

Saya mengadaptasi bagian dari ini di R.
Masalahnya adalah saya tidak tahu berapa banyak kombinasi status / nilai yang saya miliki, dan secara teknis saya tidak tahu berapa banyak status yang saya kira.
Awalnya saya salah jalan, dengan rbinddari data.frames dan itu sangat lambat.
Saya kemudian mengganti objek R saya dengan a data.frame(state, action, value = NA_real). itu berhasil tetapi masih sangat lambat. Masalah lainnya adalah objek data.frame saya memiliki ukuran maksimum yang mungkin bermasalah di masa mendatang.
lalu saya menggantungkan saya data.frameke a data.table, yang memberi saya kinerja terburuk, lalu saya akhirnya mengindeksnya dengan (status, tindakan).

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

Masalah:

Membandingkan googlecolab / python vs implementasi R lokal saya, google melakukan akses 1000x10e4 ke objek dalam katakanlah 15 detik, sementara kode saya melakukan akses 100x100 dalam 28 detik. Saya mendapat peningkatan 2 dengan kompilasi byte tetapi itu masih terlalu buruk.

Dengan menggunakan profvis, saya melihat sebagian besar waktu dihabiskan untuk mengakses data.table pada dua panggilan ini:

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

Saya tidak begitu tahu apa yang dimiliki google, tetapi desktop saya adalah binatang buas. Saya juga melihat beberapa tolok ukur yang menyatakan data.tablelebih cepat dari pandas, jadi saya kira masalahnya terletak pada pilihan wadah saya.

Pertanyaan:

  1. apakah penggunaan data.table saya salah dan dapat diperbaiki untuk meningkatkan dan mencocokkan implementasi python?
  2. adakah desain lain yang mungkin untuk menghindari deklarasi semua kombinasi keadaan / tindakan yang dapat menjadi masalah jika dimensinya menjadi terlalu besar?
  3. Saya telah melihat tentang paket hash, apakah ini cara yang tepat?

Terima kasih banyak untuk petunjuk apa pun!

MEMPERBARUI:

terima kasih atas semua masukannya. Jadi yang saya lakukan adalah mengganti 3 akses ke data.table saya menggunakan saran Anda:

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

ini menurunkan runtime dari 33 detik menjadi 21 detik yang merupakan peningkatan besar, tetapi itu masih sangat lambat dibandingkan dengan defaultdictimplementasi python .

Saya mencatat hal berikut:
bekerja dalam batch: Saya rasa saya tidak dapat melakukan karena panggilan ke fungsi tergantung pada panggilan sebelumnya.
peudospin> Saya melihat Anda terkejut mendapatkan memakan waktu. begitu juga saya tapi itulah yang dinyatakan profvis:

dan di sini kode fungsinya sebagai referensi:

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

Pada titik ini, jika tidak ada lagi saran, saya akan menyimpulkan data.table terlalu lambat untuk tugas semacam ini, dan saya harus mempertimbangkan untuk menggunakan envatau collections. (seperti yang disarankan di sana: R pencarian item tunggal cepat dari daftar vs data.table vs hash )

KESIMPULAN:

Saya mengganti data.tableuntuk a collections::dictdan kemacetannya benar-benar hilang.

Jawaban

2 pseudospin Aug 19 2020 at 02:45

data.tablecepat untuk melakukan pencarian dan manipulasi dalam tabel data yang sangat besar, tetapi tidak akan cepat menambahkan baris satu per satu seperti kamus python. Saya berharap itu akan menyalin seluruh tabel setiap kali Anda menambahkan baris yang jelas bukan yang Anda inginkan.

Anda dapat mencoba menggunakan lingkungan (yang mirip dengan hashmap), atau jika Anda benar-benar ingin melakukan ini di R, Anda mungkin memerlukan paket spesialis, berikut tautan ke jawaban dengan beberapa opsi.

AbdessabourMtk Aug 19 2020 at 04:53

Tolok ukur

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)

jelas penggunaan seqmenghabiskan lebih banyak waktu daripada memanggil 1:nb. ditambah menggunakan produk kartesian membuat kode lebih cepat bahkan saat 1:nbdigunakan