data.tableのR速度
特定のパフォーマンスの問題があります。可能であれば、より一般的に拡張したいと思います。
環境:
私はGooglecolabで、defaultdictを使用して状態とアクションを値に関連付けるQラーニングエージェントのPythonコードサンプルを試してみました。
self._qvalues = defaultdict(lambda: defaultdict(lambda: 0))
return self._qvalues[state][action]
専門家ではありませんが、私の理解では、値を返すか、キーが見つからない場合は加算して0を返します。
私はこれの一部をRで適応さ
せています。問題は、状態と値の組み合わせがいくつあるかわからないことです。技術的には、推測する状態の数を知る必要はありません。
最初に私は、間違った道を行きrbind
のdata.frame
Sと非常に遅かったという。
次に、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
ので、問題はコンテナの選択にあると思います。
質問:
- data.tableの使用は間違っており、Pythonの実装を改善して一致させるために修正できますか?
- 寸法が大きくなりすぎると問題になる可能性のあるすべての状態/アクションの組み合わせを宣言することを回避するための別の設計は可能ですか?
- ハッシュパッケージについて見てきましたが、それは進むべき道ですか?
ポインタをありがとう!
更新:
すべての入力に感謝します。だから私がしたことはあなたの提案を使って私の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
と、ボトルネックは完全になくなりました。
回答
data.table
は非常に大きなデータテーブルでルックアップと操作を行うのに高速ですが、Python辞書のように行を1つずつ追加するのは高速ではありません。明らかにあなたが望むものではない行を追加するたびに、テーブル全体をコピーすることになると思います。
環境(ハッシュマップのようなもの)を使用するか、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
使用すると、使用されている場合でもコードが高速になります