Zufällig, aber wiederholbar
Monte-Carlo-Simulation, Bootstrap und viele Tests brauchen Zufallszahlen, doch ein Ergebnis, das niemand reproduzieren kann, ist wertlos: Man kann es nicht debuggen, nicht zwei Modellversionen vergleichen und es keinem Kollegen zeigen. Die Zahlen eines Computers sind nicht wirklich zufällig. Ein Pseudozufallszahlengenerator ist ein deterministischer Algorithmus, der aus einem Anfangswert, dem Seed, eine lange Folge erzeugt, die sich wie Zufallsziehungen verhält. Gleicher Seed, gleiche Folge, jedes Mal und auf jeder Maschine.
import numpy as np
rng = np.random.default_rng(42) # a Generator created from the seed 42
print(rng.random(3).round(4).tolist()) # [0.774, 0.4389, 0.8586]
rng = np.random.default_rng(42) # the same seed again: the same numbers
print(rng.random(3).round(4).tolist()) # [0.774, 0.4389, 0.8586]
rng = np.random.default_rng(43) # another seed: other numbers
print(rng.random(3).round(4).tolist()) # [0.6523, 0.0438, 0.02]Nehmen Sie einen Generator, keinen globalen Zustand
Älterer Code ruft einmal np.random.seed(42) auf und dann irgendwo np.random.normal(...): Der gesamte Code teilt sich einen versteckten globalen Generator. Jede Bibliothek oder jede neue Zeile, die eine Zahl zieht, verschiebt die Folge aller anderen, sodass sich Ergebnisse aus Gründen ändern, die mit Ihrem Modell nichts zu tun haben. Der moderne Weg ist, mit np.random.default_rng(seed) ein Generator-Objekt zu erzeugen und es ausdrücklich weiterzureichen. Seine Methoden random, normal, choice, integers erledigen alle Ziehungen, und zwei Generatoren stören sich nie.
def simulate_returns(seed, n, mu, sigma):
rng = np.random.default_rng(seed) # a private generator, not the global one
return rng.normal(mu, sigma, size=n)
first = simulate_returns(7, 5, 0.0005, 0.01)
second = simulate_returns(7, 5, 0.0005, 0.01)
print(first.round(5).tolist()) # [0.00051, 0.00349, -0.00224, -0.00841, -0.00405]
print(np.array_equal(first, second)) # True repeatableResampling und unabhängige Ströme
Ein Bootstrap zieht die Historie neu, um zu sehen, wie stark eine Kennzahl hätte schwanken können: Man zieht so viele Renditen, wie man hat, mit Zurücklegen, mit rng.choice(returns, size=len(returns), replace=True). Manche Tage erscheinen zweimal, andere gar nicht. Laufen viele Simulationen parallel, ist es riskant, jedem Prozess die Seeds 1, 2, 3 zu geben, weil benachbarte Seeds nicht garantiert unabhängig sind. np.random.SeedSequence(seed).spawn(k) leitet k Kindfolgen ab, die sich nicht überlappen sollen; bauen Sie aus jeder ein default_rng(child).
data = np.array([1, 2, 3, 4, 5])
rng = np.random.default_rng(1)
print(rng.choice(data, size=len(data), replace=True).tolist()) # [3, 3, 4, 5, 1] 3 appears twice, 2 not at all
# Three independent streams from one seed, for three parallel simulations
children = np.random.SeedSequence(2026).spawn(3)
streams = [np.random.default_rng(child) for child in children]
print([stream.random(2).round(4).tolist() for stream in streams])
# [[0.3827, 0.0204], [0.6711, 0.158], [0.5508, 0.2339]]rng = np.random.default_rng(42)
print(rng.integers(1, 7, size=8).tolist()) # [1, 5, 4, 3, 3, 6, 1, 5] eight dice rolls, repeatable
rng = np.random.default_rng(42)
print(rng.uniform(-0.01, 0.01, 3).round(5).tolist()) # [0.00548, -0.00122, 0.00717]rng = np.random.default_rng(seed): Derselbe Seed liefert immer dieselben Zahlen; nutzen Sie die Methodenrandom,normal,choice,integers.- Reichen Sie den Generator weiter, statt den versteckten globalen Zustand
np.random.seedzu nutzen. choice(..., replace=True)ist ein Bootstrap;SeedSequence(seed).spawn(k)liefertkunabhängige Ströme; protokollieren Sie den Seed.
Als Nächstes: Ziehen aus Verteilungen, gleichverteilte Zufallszahlen in normale, exponentielle und diskrete Ziehungen umwandeln.
Lab: Was bewertet wird
- Mit
make_rngeinen wiederholbaren Generator erzeugen25 Pkt. - Mit
simulate_returnsreproduzierbare Renditen ziehen25 Pkt. - Mit
bootstrap_returnsdie Historie neu ziehen25 Pkt. - Mit
independent_streamseinen Seed in unabhängige Ströme aufteilen25 Pkt.