Finjusteringen skulle begynne. I stedet gikk to dager med å finne seks feil i data jeg allerede hadde verifisert.
Det de har til felles er verdt mer enn hver av dem: ingen av dem produserte en feilmelding. De produserte tall. Endelige tall, i riktig enhet, som varierte glatt og oppførte seg fysisk rimelig. De var bare gale.
Duggpunktet som lå femti grader for lavt
MEPS-arkivet gir relativ fuktighet. Modellen vil ha duggpunkt. Konverteringen er Magnus-formelen, og den venter fuktighet i prosent. Kilden gir en brøk.
En fuktighet på 0,79 ble altså lest som 0,79 prosent. Duggpunktet som ble skrevet ligger rundt 225 K der det skulle stått 277.
Se på hva resultatet av det ser ut som. Det er i kelvin. Det er endelig i hvert eneste punkt. Det varierer glatt over feltet. Det ligger under temperaturen, slik et duggpunkt må. Alt som kan sjekkes med en rimelighetsbetraktning, består det.
Det ble funnet ved å regne baklengs: hvilken fuktighet svarer det lagrede duggpunktet til? Svaret var 0,869 prosent, over hele Norge, i oktober. Det er ikke lavt. Det er umulig.
Magnus-formelen lar seg invertere, og feilen er en konstant på det ene stedet som betyr noe, så reparasjonen krevde ingen ny nedlasting. Men de tre årsdatasettene hadde et annet problem: der hadde etterbehandlingen aldri kjørt i det hele tatt.
Sømmen mellom de to halvdelene
Modellen ser ett rutenett som er satt sammen av to. MEPS dekker Norden med 2,5 km, en global analyse dekker resten.
MEPS lagrer nedbør i kg/m², som er millimeter. ECMWF lagrer den i meter. Oppskriften døper feltet om og konverterer det ikke, så de to halvdelene sto en faktor tusen fra hverandre, med sømmen midt gjennom området oppgaven handler om.
Verre: normaliseringen regner ut ett gjennomsnitt og ett standardavvik for hele feltet. Én statistikk for to enheter.
Det pinlige er at rangeringen av ekstremvær hadde oppdaget dette for flere uker siden. Den har en funksjon som gjetter enheten ut fra størrelsesorden, med en kommentar som forklarer nøyaktig hvorfor de to halvdelene er forskjellige. Den jobbet rundt problemet så godt at ingen trakk konklusjonen.
Statistikken som ble liggende igjen
Etterbehandlingen endret dataene og lot middel, standardavvik, minimum og maksimum stå og beskrive verdiene som pleide å ligge der.
Normaliseringen leser de tabellene. Ikke dataene. Et datasett kunne altså bli riktig konvertert og likevel normalisert som om det ikke var det, uten noe på skjermen som sa fra.
Denne feilen avslørte den forrige. Duggpunktsmaksimumet sto fortsatt på nøyaktig 1 i filene som faktisk var konvertert, og motsetningen mellom «konvertert» og «maksimum 1» var det som gjorde at begge kom fram samtidig.
Navnene som ikke fulgte med
Oppskriften døper x_wind_10m om til 10u mens feltet ennå bærer METs størrelse, og etterbehandlingen konverterer verdiene. Omdøpingen endrer navnet på kolonnen. Den endrer ikke metadataene ved siden av.
I tre år med data sa metadataene fortsatt x_wind_10m om kolonnen som het 10u. 89 av 98 variabler var i den tilstanden.
Ingenting merket det. Dataveien går etter kolonnenavn, så hver sjekk, hver statistikk og hver figur leste riktig kolonne hele veien. Tapsskalererne i anemoi går etter metadata, og de er det eneste i hele kjeden som gjør det. De stoppet treningen med en gang.
Testen som målte noe annet enn den trodde
Vindrotasjonen er den ene konverteringen ingenting annet kan bekrefte. En rotasjon bevarer vindstyrken eksakt, så hver statistikk er identisk før og etter, og varselfilene bærer styrke framfor komponenter.
Sjekken jeg hadde skrevet sammenliknet vindretningen med trykkgradienten og ventet omtrent nitti grader. Den fikk mellom 54 og 64 på de tre årene, og med en terskel på 35 kalte den det bestått på to av dem og strøket på det tredje.
Den kunne ikke svare på spørsmålet den ble stilt. Vinkelen bærer riktignok rotasjonen i hvert punkt, men medianen av rotasjonen over Norden er rundt to grader, fordi rutenettet dreier én vei vest for midtmeridianen og motsatt vei øst for den. Å rotere eller la være flytter medianen de to gradene, og tretti grader friksjon ligger oppå. Testen målte friksjon og leste det som rotasjon.
Variasjonen svarer der gjennomsnittet ikke kan. Rotasjonsfeltet svinger over 65 grader fra den ene kanten til den andre, mens friksjonsleddet er tilnærmet konstant. Å trekke rotasjonsfeltet fra den observerte vinkelen klemmer spredningen sammen hvis vinden er rotert, blåser den opp hvis den ikke er det, og blåser den enda mer opp hvis den er rotert feil vei. Tre kandidater, minst spredning vinner, og signalet er 65 grader i stedet for to.
Den nye testen bygger de tre tilfellene med kjent historie og krever at den navngir hvert av dem, før den får lov til å uttale seg om ekte data. Fortegnet på en rotasjon er lett å snu, og en test som trygt melder feil svar er verre enn ingen test.
Svaret ble at alle tre årene er riktig rotert. Det mest overbevisende var ikke marginen, men asymmetrien: hvis vinden ikke var rotert, ville de to gale kandidatene ligget omtrent like langt over den riktige. De lå 40,5 og 48,1. Det mønsteret oppstår bare når historien faktisk er en riktig rotasjon.
Tre stasjoner av 1120
Til slutt, på observasjonssiden. Nedbør hentes fra Frost som timessummer. Temperatur og vind er øyeblikksverdier, og mange stasjoner rapporterer dem hvert tiende minutt.
Tidsaksen min var unionen av alle tidsstempler som dukket opp. En eneste stasjon med timinuttersoppløsning la dermed til fem tomme kolonner for hver stasjon som rapporterte hver time, og de timesbaserte så fem sjettedeler fraværende ut.
Ingenting feilet. Filen ble skrevet. Den var større enn nedbørsfilen, noe som så ut som mer data. Det var først da koblingen mellom måler og gitterpunkt rapporterte hvor mange stasjoner som overlevde hvert kutt at tallet kom fram: 3 brukbare av 1120.
Hva som faktisk fanget dem
Ikke grundighet, og ikke flere sjekker av samme slag. Jeg hadde allerede skrevet mange sjekker, og de hadde alle bestått.
Hver eneste av disse ble tatt av et tall som ikke kunne være sant:
| feilen | tallet som avslørte den |
|---|---|
| duggpunkt som fuktighet i prosent | 0,869 % fuktighet over hele landet |
| nedbør i to enheter | faktor 588 mellom halvdelene |
| statistikk som ikke fulgte dataene | maksimum 1,00001 i en kolonne i kelvin |
| omdøping uten metadata | 89 av 98 variabler het noe annet |
| observasjoner på ti minutter | 3 brukbare stasjoner av 1120 |
Ingen av de tallene krever domenekunnskap for å forkaste. De krever bare at noe faktisk regner dem ut og skriver dem på skjermen.
Det er forskjellen jeg tar med meg. En sjekk som spør «ser dette rimelig ut» fanger ingenting her, fordi alt dette så rimelig ut. En sjekk som spør «er dette i det hele tatt mulig» fanget alle sammen.
Og den billigste av dem alle kom til slutt: et skript som bygger hele treningsoppsettet på CPU og stopper rett før første batch. Det tar fjorten sekunder og fanger alt som avgjøres før dataene i det hele tatt leses. De siste to dagene har jeg brukt opptil seks døgn i GPU-kø for å oppdage en manglende nøkkel i en konfigurasjonsfil, flere ganger. Det trengte aldri et grafikkort.
