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:
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:
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:
set.seed(NULL)
(in .onLoad()
) .globals$ownSeed
) 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?
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:
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 .
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:
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:
#if defined
Blöcke) 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!
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.
Daher würde ich mit dem zweiten Ansatz fortfahren, wenn ID-Kollisionen nicht erwünscht sind.
Tags und Links r random random-seed