2013-02-13 2 views
10

Ich mag die Zufallszahl-Verteilungen aus der C++ 11-Standardbibliothek mit einfachen Funktionen wickeln, dass die Parameter der Verteilung und einen Generator Instanz als Argument. Zum Beispiel:Sollte ich Reset() auf meine zufällige C++ - Verteilung aufrufen, um den versteckten Status zu löschen?

double normal(double mean, double sd, std::mt19937_64& generator) 
{ 
    static std::normal_distribution<double> dist; 
    return dist(generator, std::normal_distribution<double>::param_type(mean, sd)); 
} 

Ich mag jeden versteckten Zustand innerhalb des Verteilungsobjekts vermeiden, so dass jeder Aufruf dieser Wrapper-Funktion auf den angegebenen Argumenten hängt nur. (Potentiell jeder Aufruf dieser Funktion könnte einen anderen Generator Beispiel nehmen.) Idealerweise würde ich die Verteilung Instanz static const machen dies zu gewährleisten; Die operator() der Distribution ist jedoch keine konstante Funktion, daher ist dies nicht möglich.

Meine Frage ist dies: Um sicherzustellen, dass es keinen versteckten Zustand innerhalb der Verteilung gibt, ist es 1) notwendig und 2) ausreichend, um reset() auf der Verteilung jedes Mal aufzurufen? Zum Beispiel:

double normal(double mean, double sd, std::mt19937_64& generator) 
{ 
    static std::normal_distribution<double> dist; 
    dist.reset(); 
    return dist(generator, std::normal_distribution<double>::param_type(mean, sd)); 
} 

(Insgesamt bin ich verwirrt über den Zweck der reset() Funktion für die Zufallsverteilungen ... Ich verstehe, warum der Generator manchmal Reset/reseeded sein müssten, aber warum sollte die Verteilungsobjekt müssen zurückgesetzt werden?)

+0

Danke für die sehr hilfreiche Antworten und Kommentare alle! –

+0

Ich denke, ich werde meine Gesamtstrategie nach dem Lesen der Antworten ändern: Ich werde entweder meinen Generator und die Verteilung binden (was der beabsichtigte Einsatz zu sein scheint, anstatt mehreren Generatoren die Verwendung des gleichen Verteilungsobjekts zu erlauben), oder einfach benutze die Generatoren der std lib, ignoriere aber die Verteilungsfunktionen (nachdem du herausgefunden hast, dass die Distributionen nicht unbedingt portabel sind: http://stackoverflow.com/questions/14840901/is-the-random-library-in-c11-portable, also selbst mit dem gleichen Seed, kann ich verschiedene Sequenzen von zB Normalverteilungsproben auf verschiedenen Plattformen erhalten). –

+0

Es scheint hier zwei Anwendungsfälle zu geben: 1) Bindung eines Motors und Verteilung (die beabsichtigte Verwendung) und 2) Verwendung mehrerer Engines mit einer Verteilung (was ich hier mache). Ich denke, ich bevorzuge es, die Verteilungen als einfache zustandslose Funktionen zu betrachten: Sie geben ihnen eine Engine und sie geben eine Stichprobe zurück. Aber ich verstehe die Notwendigkeit, sie als Objekte zu behandeln, damit sie Werte für Effizienz speichern können. Es wäre schön gewesen, wenn der Standard für jede Distribution sowohl eine zustandslose Funktion, um nur ein * Sample zu erhalten, als auch ein Objekt zum Erzeugen/Cachen * mehrerer * Samples zur Verfügung stellen würde. –

Antwort

8

um sicherzustellen, dass es keine versteckten Zustand innerhalb der Verteilung ist, ist es 1) notwendig

Ja.

und 2) ausreichend, um reset() auf die Verteilung jedes Mal aufzurufen?

Ja.

Wahrscheinlich möchten Sie dies jedoch nicht tun. Zumindest nicht bei jedem einzelnen Anruf. Das std::normal_distribution ist das Poster-Kind, das es Verteilungen ermöglicht, den Status beizubehalten. Zum Beispiel wird eine beliebte Implementierung der Box-Muller transformation verwenden zwei Zufallszahlen auf einmal zu berechnen, sondern geben Sie zurück nur einer von ihnen, der andere für die nächste Zeit zu sparen Sie anrufen. Durch den Aufruf von reset() vor dem nächsten Aufruf würde die Verteilung dieses bereits gültige Ergebnis wegwerfen und die Effizienz des Algorithmus halbieren.

+1

Whoa, also was passiert, wenn ich zwei Generatorinstanzen habe, gen1 und gen2, und eine std :: normal_distribution Instanz normdist, und ich rufe normdist (gen1) gefolgt von normdist (gen2) auf. Dann wird der zweite Anruf von gen1 und nicht gen2 abhängen? Ist das wahr? –

+1

@TylerStreeter: Dies gilt für die libC++ - Implementierung.Tabelle 118, die das Verhalten beim Aufrufen einer Verteilung mit einem Urng angibt, gibt das Verhalten nur für "aufeinanderfolgende Aufrufe mit dem ** gleichen ** Objekt' g "an." Aufeinanderfolgende Anrufe mit unterschiedlichen Urngs werden nicht angegeben. –

+0

Ok, das ist sehr hilfreich (und verwirrend!) Darüber hinaus spezifiziert der Standard für die Operatorversion (gen, params) das Verhalten nur für "aufeinanderfolgende Aufrufe mit den gleichen Objekten g und p". Wenn ich also zwei Generatoren (gen1 und gen2) und zwei Sätze normaler params (p1 und p2) habe und ich normdist (gen1, p1) nenne, gefolgt von normdist (gen2, p2), dann kann der zweite Aufruf * von gen1 abhängen und p1 und nicht gen2 und p2 überhaupt. Nicht das was ich erwartet habe! –

2

Einige Distributionen haben einen internen Status. Wenn Sie die Funktion der Distribution stören, indem Sie sie ständig zurücksetzen, erhalten Sie keine korrekt verteilten Ergebnisse. Dies ist so, als ob Sie vor jedem Anruf an srand() anrufen würden: rand().

+1

Sind Sie sicher, dass dies die Verteilung beeinflusst und nicht nur die Effizienz (siehe @ HowardHinnants Antwort)? Ich bin mir nicht sicher, ob deine srand() Analogie korrekt ist (aber korrigiere mich, wenn ich falsch liege). Es scheint mir, dass der interne Status der Distribution nur verwendet werden sollte, um die Effizienz zu verbessern, aber das Zurücksetzen sollte nicht die Verteilung beeinflussen (wie das Zurücksetzen des Samens würde). –

+0

@TylerStreeter: Obwohl nur die Effizienz in der libC++ - Implementierung beeinflusst wird, behaupte ich nicht, dass dies ein portables Ergebnis ist. Ich bezweifle, dass die Designer oder Entwickler dieser Bibliothek jemals daran gedacht haben, dass jemand vor jedem Anruf eine Distribution "zurücksetzen" möchte. –

+0

@HowardHinnant: Also sehen Sie ein potenzielles Problem mit diesem Anwendungsfall auch: http://StackOverflow.com/Questions/8433421/Sould-Ikeep-the-Random-Distribution-Object-Instance-Or-Can- i-always-recreate-i wo, anstatt ständig Reset aufzurufen, wird das Verteilungsobjekt ständig neu erstellt? –

1

reset() Aufruf auf einem Vertriebsobjekt d hat folgende Wirkung:

Anschließende Verwendungen von d hängen nicht auf Werte von jedem Motor erzeugte vor dem Reset aufgerufen wird.

(ein Motor ist kurz gesagt ein Generator, der gesät werden kann).

Mit anderen Worten, es löscht alle "zwischengespeicherten" Zufallsdaten, die das Verteilungsobjekt gespeichert hat und die von der Ausgabe abhängen, die es zuvor von einer Engine gezogen hat.

Also, wenn Sie das tun möchten, dann sollten Sie reset() anrufen. Der Hauptgrund, an den ich denken könnte, dass Sie das tun würden, ist, wenn Sie Ihren Motor mit einem bekannten Wert mit der Absicht säen, wiederholbare pseudozufällige Resultate zu produzieren. Wenn die Ergebnisse von Ihrem Verteilungsobjekt auf auch basierend auf diesem Seed wiederholbar sein sollen, müssen Sie das Verteilungsobjekt zurücksetzen (oder ein neues erstellen). Ein weiterer Grund, den ich mir vorstellen kann, ist, dass Sie das Generatorobjekt defensiv sezieren, weil Sie befürchten, dass ein Angreifer partielle Kenntnisse über seinen internen Zustand erlangen könnte (wie zum Beispiel Fortuna). Zur Vereinfachung können Sie sich vorstellen, dass die Qualität/Sicherheit der Daten des Generators im Laufe der Zeit abnimmt und durch erneutes Nachstellen wiederhergestellt wird. Da ein Verteilungsobjekt beliebige Datenmengen von dem Generator zwischenspeichern kann, wird es eine willkürliche Verzögerung zwischen dem Erhöhen der Qualität/Sicherheit der Ausgabe des Generators und dem Erhöhen der Qualität/Sicherheit der Ausgabe des Verteilungsobjekts geben. Der Aufruf von reset auf dem Verteilungsobjekt vermeidet diese Verzögerung. Aber ich werde nicht schwören, dass diese letztere Verwendung korrekt ist, weil sie in die Bereiche kommt, in denen ich es vorziehe, nicht mein eigenes Urteil darüber zu fällen, was sicher ist, wenn ich mich auf Expertenarbeit eines Experten verlassen kann :-)

In Bezug auf Ihren Code insbesondere - wenn Sie nicht möchten, dass die Ausgabe von der vorherigen Verwendung desselben dist Objekts mit verschiedenen Generatorobjekten abhängig ist, dann wäre der Aufruf reset() der Weg, dies zu tun. Aber ich denke, es ist unwahrscheinlich, dass das Aufrufen von reset für ein Verteilungsobjekt und dann das Verwenden von neuen Parametern billiger ist als das Erstellen eines neuen Verteilungsobjekts mit diesen Parametern. Mit einem lokalen static-Objekt scheint mir also Ihre Funktion nicht threadsicher zu sein, ohne Nutzen: Sie könnten jedes Mal ein neues Verteilungsobjekt erstellen und der Code wäre wahrscheinlich nicht schlechter. Es gibt Gründe für das Design im Standard, und es wird erwartet, dass Sie ein Verteilungsobjekt wiederholt mit demselben Generator verwenden. Die Funktion, die Sie geschrieben haben, indem Sie das Verteilungsobjekt aus der Schnittstelle herausschneiden, verwirft die Vorteile dieses Teils des Designs im Standard.

+0

Der Hauptzweck von 'reset' ist es, die Dinge wieder in einen bekannten Zustand zu versetzen, so dass Sie eine vorherige Sequenz reproduzieren oder fortsetzen können, wo Sie aufgehört haben. –

Verwandte Themen