Optionen für Caching / Memoization / Hashing in R.

72

Ich versuche, einen einfachen Weg zu finden, um so etwas wie Perls Hash-Funktionen in R zu verwenden (im Wesentlichen Caching), da ich sowohl Hashing im Perl-Stil als auch meine eigene Memoisierung von Berechnungen schreiben wollte. Andere haben mich jedoch bis zum Anschlag geschlagen und haben Pakete zum Auswendiglernen. Je mehr ich grabe, desto mehr finde ich zB memoiseund R.cache, aber Unterschiede sind nicht ohne weiteres klar. Außerdem ist nicht klar, wie man sonst Hashes im Perl-Stil (oder Wörterbücher im Python-Stil) erhalten und eigene Memoisierungen schreiben kann, außer das hashPaket zu verwenden, das die beiden Memoisierungspakete nicht zu untermauern scheint.

Da ich keine Informationen zu CRAN oder anderswo finden kann, um zwischen den Optionen zu unterscheiden, sollte dies möglicherweise eine Community-Wiki-Frage zu SO sein: Welche Optionen gibt es für das Speichern und Zwischenspeichern in R und welche Unterschiede gibt es?


Als Vergleichsbasis finden Sie hier eine Liste der Optionen, die ich gefunden habe. Außerdem scheint es mir, dass alle vom Hashing abhängen, daher werde ich auch die Hashing-Optionen beachten. Die Schlüssel- / Wertspeicherung ist etwas verwandt, eröffnet jedoch eine große Anzahl von Würmern in Bezug auf DB-Systeme (z. B. BerkeleyDB, Redis, MemcacheDB und viele andere ).

Es sieht so aus, als wären die Optionen:

Hashing

  • Digest - Bietet Hashing für beliebige R-Objekte.

Auswendiglernen

  • memoise - ein sehr einfaches Werkzeug zum Speichern von Funktionen.
  • R.cache - bietet mehr Funktionen zum Auswendiglernen, obwohl einigen Funktionen anscheinend Beispiele fehlen.

Caching

  • Hash - Bietet Caching-Funktionen, die Perls Hashes und Python-Wörterbüchern ähneln.

Schlüssel- / Wertspeicherung

Dies sind grundlegende Optionen für die externe Speicherung von R-Objekten.

Checkpointing

Andere

  • Base R unterstützt: benannte Vektoren und Listen, Zeilen- und Spaltennamen von Datenrahmen und Namen von Elementen in Umgebungen. Es scheint mir, dass die Verwendung einer Liste ein bisschen kludge ist. (Es gibt auch pairlist, aber es ist veraltet .)
  • Das Paket data.table unterstützt die schnelle Suche nach Elementen in einer Datentabelle.

Anwendungsfall

Obwohl ich hauptsächlich daran interessiert bin, die Optionen zu kennen, ergeben sich zwei grundlegende Anwendungsfälle:

  1. Caching: Einfaches Zählen von Strings. [Hinweis: Dies ist nicht für NLP, sondern für den allgemeinen Gebrauch, daher sind NLP-Bibliotheken übertrieben. Tabellen sind unzureichend, weil ich lieber nicht warten möchte, bis der gesamte Satz von Zeichenfolgen in den Speicher geladen ist. Perl-artige Hashes sind auf dem richtigen Nutzen.]
  2. Auswendiglernen monströser Berechnungen.

Diese entstehen wirklich, weil ich mich mit der Profilerstellung von Sloooooow-Code beschäftige und wirklich nur einfache Zeichenfolgen zählen möchte, um zu sehen, ob ich einige Berechnungen durch Memoisierung beschleunigen kann. Wenn ich in der Lage bin, die Eingabewerte zu hashen, auch wenn ich mich nicht merke, kann ich sehen, ob das Speichern hilfreich ist.


Hinweis 1: In der CRAN-Aufgabenansicht für reproduzierbare Forschung sind einige der Pakete ( cacherund R.cache) aufgeführt, die Verwendungsoptionen werden jedoch nicht näher erläutert.

Hinweis 2: Um anderen bei der Suche nach verwandtem Code zu helfen, finden Sie hier einige Hinweise zu einigen Autoren oder Paketen. Einige der Autoren verwenden SO. :) :)

  • Dirk Eddelbuettel: digest- Viele andere Pakete hängen davon ab.
  • Roger Peng: cacher, filehash, stashR- diese Adresse unterschiedliche Probleme auf unterschiedliche Weise; Weitere Pakete finden Sie auf Rogers Website .
  • Christopher Brown: hash- Scheint ein nützliches Paket zu sein, aber die Links zu ODG sind leider nicht verfügbar.
  • Henrik Bengtsson: R.cache& Hadley Wickham: memoise- Es ist noch nicht klar, wann ein Paket dem anderen vorzuziehen ist.

Hinweis 3: Einige Personen verwenden Memoise / Memoization, andere verwenden Memoize / Memoization. Nur eine Notiz, wenn Sie herum suchen. Henrik benutzt "z" und Hadley benutzt "s".

Iterator
quelle
Es wäre wahrscheinlich gut, ein oder zwei echte Anwendungsfälle hinzuzufügen, damit die Methoden verglichen werden können ...
Tommy
@ Tommy: Danke, das mache ich!
Iterator
Verwirrt über Ihre Kommentare zu: Umgebungen. Wenn Sie eine neue Umgebung erstellen, wird diese gehasht. ?environmentzB env.profile(new.env())$size # [1] 29
IRTFM
@ DWin: Du bist richtig. Ich erwähne es nur als Option für eine Hash-Fähigkeit.
Iterator
2
Dieser Beitrag des Autors von 'R in a Nutshell' enthält Geschwindigkeitstests verschiedener Optionen zum Nachschlagen von Objekten, einschließlich deren Platzierung in einer Umgebung (in der bei der Suche Hash-Namen verwendet werden). Broadcast.oreilly.com/2010/03/lookup- Performance-in-r.html . Ich weiß nicht, ob es für Sie nützlich ist, dachte aber, ich würde es für jeden anderen, der mitkommt, an diesen Beitrag anheften.
Josh O'Brien

Antworten:

11

Ich hatte kein Glück damit, memoiseweil es too deep recursiveProbleme mit einer Funktion eines Pakets gab, mit dem ich es versucht hatte. Mit hatte R.cacheich besseres Glück. Es folgt ein kommentierter Code, den ich aus der R.cacheDokumentation übernommen habe. Der Code zeigt verschiedene Optionen für das Caching.

# Workaround to avoid question when loading R.cache library
dir.create(path="~/.Rcache", showWarnings=F) 
library("R.cache")
setCacheRootPath(path="./.Rcache") # Create .Rcache at current working dir
# In case we need the cache path, but not used in this example.
cache.root = getCacheRootPath() 
simulate <- function(mean, sd) {
    # 1. Try to load cached data, if already generated
    key <- list(mean, sd)
    data <- loadCache(key)
    if (!is.null(data)) {
        cat("Loaded cached data\n")
        return(data);
    }
    # 2. If not available, generate it.
    cat("Generating data from scratch...")
    data <- rnorm(1000, mean=mean, sd=sd)
    Sys.sleep(1) # Emulate slow algorithm
    cat("ok\n")
    saveCache(data, key=key, comment="simulate()")
    data;
}
data <- simulate(2.3, 3.0)
data <- simulate(2.3, 3.5)
a = 2.3
b = 3.0
data <- simulate(a, b) # Will load cached data, params are checked by value
# Clean up
file.remove(findCache(key=list(2.3,3.0)))
file.remove(findCache(key=list(2.3,3.5)))

simulate2 <- function(mean, sd) {
    data <- rnorm(1000, mean=mean, sd=sd)
    Sys.sleep(1) # Emulate slow algorithm
    cat("Done generating data from scratch\n")
    data;
}
# Easy step to memoize a function
# aslo possible to resassign function name.
This would work with any functions from external packages. 
mzs <- addMemoization(simulate2)

data <- mzs(2.3, 3.0)
data <- mzs(2.3, 3.5)
data <- mzs(2.3, 3.0) # Will load cached data
# aslo possible to resassign function name.
# but different memoizations of the same 
# function will return the same cache result
# if input params are the same
simulate2 <- addMemoization(simulate2)
data <- simulate2(2.3, 3.0)

# If the expression being evaluated depends on
# "input" objects, then these must be be specified
# explicitly as "key" objects.
for (ii in 1:2) {
    for (kk in 1:3) {
        cat(sprintf("Iteration #%d:\n", kk))
        res <- evalWithMemoization({
            cat("Evaluating expression...")
            a <- kk
            Sys.sleep(1)
            cat("done\n")
            a
        }, key=list(kk=kk))
        # expressions inside 'res' are skipped on the repeated run
        print(res)
        # Sanity checks
        stopifnot(a == kk)
        # Clean up
        rm(a)
    } # for (kk ...)
} # for (ii ...)
Biocyberman
quelle
9

Für das einfache Zählen von Zeichenfolgen (und nicht das Verwenden tableoder Ähnliches) scheint eine Multiset- Datenstruktur eine gute Anpassung zu sein. Das environmentObjekt kann verwendet werden, um dies zu emulieren.

# Define the insert function for a multiset
msetInsert <- function(mset, s) {
    if (exists(s, mset, inherits=FALSE)) {
        mset[[s]] <- mset[[s]] + 1L
    } else {
        mset[[s]] <- 1L 
    }
}

# First we generate a bunch of strings
n <- 1e5L  # Total number of strings
nus <- 1e3L  # Number of unique strings
ustrs <- paste("Str", seq_len(nus))

set.seed(42)
strs <- sample(ustrs, n, replace=TRUE)


# Now we use an environment as our multiset    
mset <- new.env(TRUE, emptyenv()) # Ensure hashing is enabled

# ...and insert the strings one by one...
for (s in strs) {
    msetInsert(mset, s)
}

# Now we should have nus unique strings in the multiset    
identical(nus, length(mset))

# And the names should be correct
identical(sort(ustrs), sort(names(as.list(mset))))

# ...And an example of getting the count for a specific string
mset[["Str 3"]] # "Str 3" instance count (97)
Tommy
quelle
4

Bezogen auf @biocyperman Lösung . R.cache verfügt über eine Wrapping-Funktion, um das Laden, Speichern und Auswerten des Caches zu vermeiden. Siehe die geänderte Funktion:

R.cache bietet einen Wrapper zum Laden, Auswerten und Speichern. Sie können Ihren Code folgendermaßen vereinfachen:

simulate <- function(mean, sd) {
key <- list(mean, sd)
data <- evalWithMemoization(key = key, expr = {
    cat("Generating data from scratch...")
    data <- rnorm(1000, mean=mean, sd=sd)
    Sys.sleep(1) # Emulate slow algorithm
    cat("ok\n")
    data})
}
user890739
quelle