9.1 Inversjonsmetoden og numpy-verktøykassen
Fra $F^{-1}$ på papir til kjørbar numpy-funksjon: simulering fra en vilkårlig fordeling med inversjonsmetoden.
- Python/MC totalt i 8 av 9 sett siden des. 2023. Selve inversjonsmetoden — utlede analytisk og skrive/fullføre en numpy-funksjon — er testet i Des23, Aug24, Aug25 og Des25 (Des25 var en Gumbel-fordeling). I Des23 ble du dessuten bedt om å tolke gitt kode: hvilken fordeling genererer snutten, og hva estimerer den?
- Dette er sjanger N — sjanger N betyr rett og slett en oppgave der du skal lese, skrive eller fullføre programkode (oftest simulering). Prioritet: kunne (nivå 2), men reelt obligatorisk lesning, for koden dukker nå opp i nesten hvert sett.
Hvor poengene sitter: i at koden er komplett og kjørbar. Delvis kode gir bare delvis uttelling. Inversjonsmetoden står ikke i formelsamlingen (hjelpemiddelkode C har fordelingstabellene, ikke simuleringsteknikken) — den må kunnes aktivt: utled , løs , implementer.
ddof=1 i numpy).Sist du var her — de tre resultatene metoden hviler på (ferdig oppfrisket):
1. Fordelingsfunksjonen fra tettheten: , oppgitt komplett (0 under støtten, uttrykket inne, 1 over).
2. Kvantilen løser — løs likningen for , aldri . Den inverse funksjonen er nettopp «løs for ».
3. Halesannsynlighet: .
Matematisk trenger du å kunne integrere polynomer og potenser, løse en enkel likning for , og lese grunnleggende numpy. Ingen ny sannsynlighetsteori innføres her — bare et nytt verktøy for den du alt kan.
np.random.uniform). Inversjonsmetoden er oppskriften som gjør om det råstoffet til trekk fra hvilken som helst fordeling du kan skrive for — du «mater» de uniforme tallene inn i .Kapitlet går i fire løkker: inversjonsmetoden og beviset (løkke 1), et hale-eksempel fra bunnen (løkke 2), numpy-verktøykassa med de idiomene sensor ser etter (løkke 3), og kodelesing som egen ferdighet (løkke 4). Hver løkke går teori eksempel oppgave.
Samlet kjernetid ≈ 60 min. Naturlige pausepunkter er markert mellom løkkene. All kode i kapitlet er komplett og faktisk kjørt — du kan lime den rett inn og få tallene som står.
Løkke 1 — Inversjonsmetoden og beviset (~16 min)
nøyaktig fordelingsfunksjon .
Beviset i tre linjer. Vi regner ut fordelingsfunksjonen til direkte. Fordi er voksende, er det samme som :
Det siste likhetstegnet er selve nøkkelen: for er for , og her er . Dermed har fordelingsfunksjon — akkurat det vi ønsket.
Intuisjon: er «hvor stor andel av massen som ligger til venstre for ». Trekker vi en uniform andel og spør hvilken -verdi som har akkurat den andelen til venstre for seg (det er ), lander vi tett der massen er — altså med riktig fordeling.
Arbeidsflyten (tre steg)
Uansett fordeling følger inversjonsmetoden samme tre steg:
1. Utled . Fra tettheten : (teknikken fra kap. 2.4). Er oppgitt, hopp rett videre.
2. Løs for . Det gir den inverse . Dette er samme likning som kvantilregningen — bare med en generell i stedet for et tall.
3. Implementer. Trekk med np.random.uniform(size=n) og sett vektoren inn i uttrykket for . Ferdig funksjon.
Merk at steg 2 er hele det matematiske arbeidet — resten er én linje numpy.
Levetiden (i år) til en pumpe er eksponensialfordelt med forventning , altså for . Utled og skriv en Python-funksjon som trekker levetider med inversjonsmetoden.
Steg 2 — løs .
Så .
Steg 3 — implementer.
import numpy as np
def simuler_eksp(n, beta):
u = np.random.uniform(size=n) # råstoffet: uniform(0,1)
return -beta * np.log(1 - u) # F^{-1}(u), vektorisert
np.random.seed(0)
x = simuler_eksp(10**6, 4.0)
print(np.mean(x)) # ~ 4.0 = beta (E(X) = beta)
print(np.var(x, ddof=1)) # ~ 16.0 = beta**2 (Var(X) = beta^2)Kjørt gir dette og empirisk varians — nettopp og , som bekrefter at koden trekker riktig fordeling.
> Sensornotat: , ikke — det følger direkte av steg 2. (Siden og har samme fordeling, gir også riktig fordeling, men skriv så utledningen henger sammen.)
(Innstegsoppgave — bare gjengivelse.) Hva er råstoffet inversjonsmetoden starter fra, og hvilken numpy-funksjon lager det?
a) Hvilken fordeling har trekkene du starter med?
b) Hvilken kodelinje trekker slike tall?
En ventetid er eksponensialfordelt med forventning .
a) Skriv opp .
b) Utled .
c) Fullfør returlinjen i def simuler(n): u = np.random.uniform(size=n); return ___.
Løkke 2 — Et hale-eksempel fra bunnen (~14 min)
Den (skalerte) signifikante bølgehøyden i et havområde har tetthet for og 0 ellers. Utled og , skriv en simuleringsfunksjon, og forklar hva np.mean(simuler(10**6) > 2) estimerer.
Steg 2 — løs .
Så .
Steg 3 — implementer.
import numpy as np
def simuler_bolge(n):
u = np.random.uniform(size=n)
return (1 - u)**(-1/5) # F^{-1}(u)
np.random.seed(0)
x = simuler_bolge(10**6)
print(np.mean(x > 2)) # ~ 0.0313
print(np.mean(x)) # ~ 1.25Hva estimerer np.mean(simuler(10**6) > 2)? Uttrykket x > 2 er en vektor av True/False; np.mean av den er andelen trekk over 2 — altså et estimat på . Fasit analytisk: . Kjørt gir koden . ✓
(Til sammenligning er , som np.mean(x) bekrefter.)
> Sensornotat: np.mean av en betingelse = andel = sannsynlighetsestimat. Dette er broen til hele kap. 9.2.
En hale-tetthet er for og 0 ellers.
a) Utled .
b) Utled .
c) Skriv en komplett funksjon simuler(n) som trekker fra denne fordelingen.
d) Hva estimerer np.mean(simuler(10**6) > 2)? Regn også fasit analytisk.
Løkke 3 — numpy-verktøykassa (~16 min)
np.random.uniform(size=n) returnerer en vektor med uavhengige trekk fra uniform. Dette er startpunktet for inversjonsmetoden. Vil du ha uniform på et annet intervall, er det np.random.uniform(low, high, size=n), men i inversjonsmetoden er det alltid standardvarianten , siden det er den forventer.Én trekning gir hele vektoren på én gang — det er vektorisering: du slipper en løkke. All videre regning (som -beta*np.log(1-u)) skjer elementvis på hele vektoren samtidig.
np.random.normal(loc, scale, size=n) trekker fra normalfordelingen, der loc er forventningen og scale er standardavviket — ikke variansen. Dette er den vanligste numpy-fella i emnet: vi skriver med variansen som andre argument, men numpy vil ha standardavviket.Skal du simulere (altså varians , standardavvik ), skriver du np.random.normal(loc=100, scale=5, size=n) — send , ikke 25. Sender du scale=25, simulerer du i praksis : en fordeling med feil, altfor stor spredning.
np.var(x) deler som standard summen av kvadratavvik på (ddof=0). Men den forventningsrette empiriske variansen — utvalgsvariansen fra kap. 5.1 — deler på . I numpy får du den med np.var(x, ddof=1) (ddof = «delta degrees of freedom», trekker 1 fra ).Hvorfor ? Fordi vi bruker det estimerte gjennomsnittet i stedet for den (ukjente) sanne forventningen. Å dele på undervurderer da variansen systematisk; korreksjonen til gjør (forventningsrett — vist i kap. 5.1). Når du estimerer en varians fra simulerte data, bruk derfor ddof=1. (For gigantiske er forskjellen forsvinnende, men vanen skal sitte.)
Skriv en funksjon som simulerer trekk fra (varians 9), og bruk den til å estimere , forventningen og variansen. Pek på hvor scale-fella og ddof kommer inn.
import numpy as np
def simuler_normal(n):
return np.random.normal(loc=50, scale=3, size=n) # scale = SD = sqrt(9) = 3
np.random.seed(0)
x = simuler_normal(10**6)
print(np.mean(x > 56)) # ~ 0.0228 = P(X>56)
print(np.mean(x)) # ~ 50.0
print(np.var(x, ddof=1)) # ~ 9.0 (empirisk varians, delt paa n-1)- scale-fella: scale=3 (standardavviket), ikke scale=9 (variansen). Sender du 9, får du en fordeling med og alt blir galt.
- ddof: np.var(x, ddof=1) gir den forventningsrette variansen ().
- Fasit: . Koden kjørt gir . ✓
Funksjonsmønsteret går igjen overalt: def simuler(n): u = ...; return <uttrykk> — trekk råstoffet på én linje, transformer vektorisert på neste.
En dimensjon er (altså varians ).
a) Hva skal scale være i np.random.normal?
b) Skriv linjen som trekker slike verdier.
c) En medstudent skriver np.random.normal(loc=20, scale=0.04, size=n). Hvilken fordeling simulerer hen egentlig, og er spredningen for stor eller for liten?
Du har simulert en stor vektor x av observasjoner og vil estimere fordelingens forventning og varians.
a) Hvilken numpy-funksjon gir forventningsestimatet?
b) Hvordan skriver du variansestimatet slik at det er forventningsrett (som )?
c) Med én setning: hvorfor ddof=1 og ikke standard ddof=0?
Løkke 4 — Kodelesing som egen ferdighet (~14 min)
En egen sjanger N-ferdighet er å lese ferdig kode og svare på to spørsmål:
1. Hvilken fordeling genererer snutten? Se på transformasjonen av u. Mønsteret -beta*np.log(1-u) er inversjonsmetoden for eksponensial med forventning beta; (1-u)**(-1/k) er en hale-tetthet med ; np.random.normal(loc, scale, ...) er normal med forventning loc og standardavvik scale.
2. Hva estimeres? En np.mean(betingelse) estimerer en sannsynlighet ; np.mean(x) estimerer en forventning ; np.var(x, ddof=1) en varians.
Grepet er å lese baklengs: transformasjonen forteller fordelingen, og det ytterste kallet (mean av en betingelse vs. mean av vektoren) forteller hva tallet er et estimat på.
Forklar hva denne snutten gjør — hvilken fordeling genererer f, og hva estimerer siste linje?
import numpy as np
def f(n):
u = np.random.uniform(size=n)
return -3 * np.log(1 - u)
np.random.seed(0)
print(np.mean(f(10**6) > 5))-3 * np.log(1 - u) er nøyaktig inversjonsmetoden med . Så f(n) trekker verdier fra en eksponensialfordeling med forventning .Hva estimeres: f(10**6) > 5 er en boolsk vektor (er trekket over 5?), og np.mean av den er andelen over 5 — altså et estimat på .
Fasit: for eksponensial er . Kjørt gir koden . ✓
> Sensornotat: les baklengs — transformasjonen gir fordelingen (), og det ytterste np.mean(...>5) gir estimatet ().
Les koden:
def g(n):
u = np.random.uniform(size=n)
return (1 - u)**(-1/4)
print(np.mean(g(10**6)))
print(np.mean(g(10**6) > 3))a) Hvilken fordeling (fordelingsfunksjon ) genererer g?
b) Hva estimerer hver av de to print-linjene?
c) Regn den andre fasiten analytisk.
En materialstyrke (i passende enhet) har tetthet for og 0 ellers.
a) Bestem .
b) Utled og .
c) Skriv en komplett funksjon simuler(n) som trekker fra fordelingen med inversjonsmetoden.
d) Skriv én kodelinje som estimerer , og regn fasiten analytisk.
En kollega vil simulere (varians 4) og estimere , men koden gir rare tall:
def simuler(n):
return np.random.normal(loc=10, scale=4, size=n)
print(np.mean(8 < simuler(10**6) < 13))a) Pek på to feil i koden.
b) Skriv en korrekt versjon.
c) Hva blir fasiten for analytisk? (Bruk , .)
- Løse i stedet for . Inversjonsmetoden bruker fordelingsfunksjonen , ikke tettheten . Utled først (integrer ), og løs så .
- Bytte om loc og scale, eller sende variansen som scale. np.random.normal(loc, scale, ...) vil ha forventningen som loc og standardavviket som scale. For : send og , aldri variansen.
- Skrive en løkke der vektorisering er naturlig. Trekk hele u = np.random.uniform(size=n) på én gang og transformer vektorisert. En for-løkke over trekk er tregt og unødvendig.
- np.var uten ddof=1 for empirisk varians. Standard np.var deler på ; den forventningsrette utvalgsvariansen deler på (ddof=1).
- Kode som ikke kjører. Delvis eller syntaktisk feil kode gir bare delvis uttelling. Sjekk at parenteser, np.log, og elementvise betingelser (&, ikke kjedet <) er riktige.
- mot -slurv. Skriv i tråd med utledningen (selv om tilfeldigvis også gir riktig fordeling).
Begrepsbank
Kjernekortene for inversjonsmetoden og numpy-verktøykassa, samlet.
Flashcard-stoff — hopp trygt over ved førstegangslesing; tidsanslaget gjelder kjernestoffet.
Er , har fordelingsfunksjon . Metoden gjør uniforme tall om til trekk fra hvilken som helst fordeling du kan skrive for. Kjerneregelen: gå via (ikke ).
u = np.random.uniform(size=n), sett vektoren inn i . Steg 2 er hele det matematiske arbeidet.For eksponensial med forventning : , og . Kode: -beta * np.log(1 - u). Skriv (fra utledningen); gir samme fordeling, men er det riktige uttrykket.
For en hale-tetthet på er , og . Kode: (1 - u)**(-1/k). Halesannsynligheten er .
np.random.normal(loc, scale, size=n): loc , scale (standardavviket). For må du sende som scale, aldri variansen. Feil her ( scale=25) gir en fordeling med altfor stor spredning.np.mean(x) estimerer forventningen ; np.mean(betingelse) estimerer sannsynligheten (andelen True). Dette siste — sannsynlighet som gjennomsnittlig indikator — er grunnprinsippet for Monte Carlo (kap. 9.2).Empirisk (forventningsrett) varians deler på : np.var(x, ddof=1). Standard np.var(x) deler på . brukes fordi vi setter inn det estimerte ; korreksjonen gir (kap. 5.1).
def simuler(n): u = np.random.uniform(size=n); return <F-invers av u>. To linjer: trekk råstoffet, transformer vektorisert. Alt av simulering i boka følger dette skjelettet — bytt bare ut transformasjonen.Den hyppigste inversjonsfeilen: å løse tetthetslikningen i stedet for . Inversjonsmetoden bruker fordelingsfunksjonen. Integrer til først, løs så for . (Samme logikk som median-fella i kap. 2.4.)
For å tolke ferdig kode: transformasjonen av u gir fordelingen (-b*np.log(1-u) eksponensial ; (1-u)**(-1/k) hale med ), og det ytterste kallet gir estimatet (np.mean(x); np.mean(x>c)).
Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.
Skolesaga er en uavhengig læringsressurs og er ikke tilknyttet eller godkjent av Norges teknisk-naturvitenskapelige universitet. Dette er ikke offisielt studiemateriell. Les mer.