data.tableのR速度

Aug 19 2020

特定のパフォーマンスの問題があります。可能であれば、より一般的に拡張したいと思います。

環境:

私はGooglecolabで、defaultdictを使用して状態とアクションを値に関連付けるQラーニングエージェントのPythonコードサンプルを試してみました。

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

専門家ではありませんが、私の理解では、値を返すか、キーが見つからない場合は加算して0を返します。

私はこれの一部をRで適応さ
せています。問題は、状態と値の組み合わせがいくつあるかわからないことです。技術的には、推測する状態の数を知る必要はありません。
最初に私は、間違った道を行きrbinddata.frameSと非常に遅かったという。
次に、Rオブジェクトをdata.frame(state, action, value = NA_real)。に置き換えました。それは動作しますが、それでも非常に遅いです。もう1つの問題は、data.frameオブジェクトの最大サイズがあり、将来問題になる可能性があることです。
それから私は自分data.frameをにチャンドしました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秒で実行しますが、私のコードは28秒で100x100アクセスを実行します。バイトコンパイルによって2秒の改善が得られましたが、それでも残念です。

を使用するとprofvis、ほとんどの時間が次の2つの呼び出しで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. ハッシュパッケージについて見てきましたが、それは進むべき道ですか?

ポインタをありがとう!

更新:

すべての入力に感謝します。だから私がしたことはあなたの提案を使って私のdata.tableへの3つのアクセスを置き換えることでした:

#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秒に短縮され、大幅に改善されましたが、Pythonのdefaultdict実装と比較すると依然として非常に低速です。

私は次の
ことに気づきました:バッチでの作業:関数の呼び出しは前の呼び出しに依存するため、私はできないと思います。
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するcollections::dictと、ボトルネックは完全になくなりました。

回答

2 pseudospin Aug 19 2020 at 02:45

data.tableは非常に大きなデータテーブルでルックアップと操作を行うのに高速ですが、Python辞書のように行を1つずつ追加するのは高速ではありません。明らかにあなたが望むものではない行を追加するたびに、テーブル全体をコピーすることになると思います。

環境(ハッシュマップのようなもの)を使用するか、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使用すると、使用されている場合でもコードが高速になります