Isolieren Sie die Zufälligkeit einer lokalen Umgebung vom globalen R-Prozess

8

Wir können set.seed() verwenden, um eine zufällige Saat in R zu setzen, und dies hat einen globalen Effekt. Hier ist ein minimales Beispiel, um mein Ziel zu veranschaulichen:

%Vor%

Grundsätzlich möchte ich in der Lage sein, den Effekt des globalen Zufallssamens (dh .Random.seed ) in einer lokalen Umgebung wie einer R-Funktion zu vermeiden, so dass ich eine Art von Zufälligkeit erreichen kann, über die der Benutzer verfügt keine Kontrolle. Zum Beispiel, auch wenn der Benutzer set.seed() hat, wird er jedes Mal, wenn er diese Funktion aufruft, verschiedene Ausgaben erhalten.

Jetzt gibt es zwei Implementierungen. Der erste hängt von set.seed(NULL) ab, damit R den Zufallssamen jedes Mal neu initialisiert, wenn ich einige Zufallszahlen erhalten möchte:

%Vor%

Sie sehen, dass ich verschiedene ID-Strings bekomme, auch wenn ich den Seed auf 0 setze und der globale Zufallszahlenstrom immer noch reproduzierbar ist:

%Vor%

Die zweite Implementierung finden Sie hier auf Github. Es ist komplizierter und die Grundidee ist:

  • initialisiert den zufälligen Startwert während des Paketstarts mit set.seed(NULL) (in .onLoad() )
  • speichert den Zufallssatz in einer separaten Umgebung ( .globals$ownSeed )
  • jedes Mal, wenn wir Zufallszahlen erzeugen wollen:
    1. Ordnen Sie den lokalen Samen dem globalen Zufallssamen
    2. zu
    3. generieren Zufallszahlen
    4. weisen Sie den neuen globalen Startwert (der sich aufgrund von Schritt 2 geändert hat) dem lokalen Startwert
    5. zu
    6. stellt den globalen Seed auf seinen ursprünglichen Wert zurück

Nun ist meine Frage, ob die beiden Ansätze in der Theorie gleich sind. Die Zufälligkeit des ersten Ansatzes beruht auf der aktuellen Zeit und Prozess-ID, wenn createUniqueId() aufgerufen wird, und der zweite Ansatz scheint auf die Zeit und die Prozess-ID zu beruhen, wenn das Paket geladen wird. Ist es für den ersten Ansatz möglich, dass zwei Aufrufe von createUniqueId() genau gleichzeitig im selben R-Prozess stattfinden, sodass sie dieselbe ID-Zeichenfolge zurückgeben?

Aktualisieren

In der Antwort unten lieferte Robert Krzyzanowski einige empirische Belege dafür, dass set.seed(NULL) zu schweren ID-Kollisionen führen kann. Ich habe dafür eine einfache Visualisierung gemacht:

%Vor%

Wenn die Linie den oberen Rand des Diagramms erreicht, bedeutet dies, dass ein doppelter Wert generiert wird. Beachten Sie jedoch, dass diese Duplikate nicht nacheinander kommen, d. H.% Co_de% ist normalerweise any(x[-1] == x[-n]) . Möglicherweise gibt es ein Muster für die Duplizierung in Verbindung mit der Systemzeit. Ich bin nicht in der Lage, weiter zu forschen, weil ich nicht verstehe, wie der zeitbasierte Zufallssamen funktioniert, aber Sie können die relevanten Teile des C-Quellcodes sehen hier und hier .

    
Yihui Xie 15.04.2014, 17:52
quelle

2 Antworten

7

Ich dachte, es wäre schön, nur einen unabhängigen RNG in Ihrer Funktion zu haben, der nicht vom globalen Samen betroffen ist, aber seinen eigenen Samen haben würde. Es stellt sich heraus, dass randtoolbox diese Funktionalität bietet:

%Vor%

Die obersten und untersten Zeilen werden vom Seed beeinflusst, während die mittleren "wirklich zufällig" sind.

Darauf basiert die Implementierung Ihrer Funktion:

%Vor%

Bearbeiten

Um zu verstehen, warum wir doppelte Schlüssel erhalten, sollten wir uns ansehen, was passiert, wenn wir set.seed(NULL) aufrufen. Alle RNG-bezogenen Code ist in C geschrieben, also sollten wir direkt zu svn.r- project.org/R/trunk/src/main/RNG.c und beziehen sich auf die Funktion do_setseed . Wenn seed = NULL , dann wird eindeutig TimeToSeed aufgerufen. Es gibt einen Kommentar, der besagt, dass es sich in datetime.c befinden sollte, aber es kann in svn.r-project.org/R/trunk/src/main/times.c .

Das Navigieren durch die R-Quelle kann schwierig sein, daher füge ich die Funktion hier ein:

%Vor%

Jedes Mal, wenn wir set.seed(NULL) aufrufen, führt R folgende Schritte aus:

  1. Nimmt die aktuelle Zeit in Sekunden und Nanosekunden (wenn möglich, Plattformabhängigkeit hier in #if defined Blöcke)
  2. Wendet die Bitverschiebung auf Nanosekunden an und das Bit "xor" ergibt sich mit Sekunden
  3. Wendet die Bit-Verschiebung auf pid und bit an, xoriert sie mit dem vorherigen Ergebnis
  4. Setzt das Ergebnis als neuen Seed

Nun, es ist fast offensichtlich, dass wir doppelte Werte erhalten, wenn die resultierenden Seeds kollidieren. Meine Vermutung ist, dass dies passiert, wenn zwei Anrufe innerhalb von 1 Sekunde liegen, so dass tv_sec konstant ist. Um das zu bestätigen, führe ich eine Verzögerung ein:

%Vor%

Was verwirrend ist, ist, dass selbst die Verzögerung im Vergleich zu Nanosekunden beträchtlich ist, wir trotzdem Kollisionen bekommen! Lass es uns weiter graben, ich schrieb einen "Debugging-Emulator" für den Seed:

%Vor%

Das ist wirklich verwirrend: Nanosekunden sind alle einzigartig, und das garantiert keine Eindeutigkeit des endgültigen Samens. Um diesen Effekt zu demonstrieren, hier eine der Duplikate:

%Vor%

Die letzte Anmerkung: Die binäre Darstellung dieser beiden Zahlen und die Verschiebung ist

%Vor%

Also, das ist wirklich eine unerwünschte Kollision.

Nun, nach all dem ist die endgültige Schlussfolgerung: set.seed(NULL) ist anfällig für hohe Belastung und garantiert nicht die Abwesenheit von Kollisionen bei mehreren aufeinanderfolgenden Aufrufen!

    
tonytonov 18.04.2014, 07:39
quelle
4

Für den ersten Ansatz scheint es tatsächlich möglich zu sein, dass zwei Aufrufe von createUniqueId() genau zur selben Zeit im selben R-Prozess passieren und dieselbe ID-Zeichenkette zurückgeben.

%Vor%

Daher würde ich mit dem zweiten Ansatz fortfahren, wenn ID-Kollisionen nicht erwünscht sind.

    
Robert Krzyzanowski 16.04.2014 00:41
quelle

Tags und Links