Maskinlæring i kjemi*#
Læringsutbytte
Etter å ha arbeidet med dette temaet, skal du kunne:
forklare forskjellen mellom regelbasert programmering og maskinlæring
identifisere observasjoner, egenskaper og målvariabel i et kjemisk datasett
dele data i trenings- og testsett
trene og bruke en enkel klassifikasjonsmodell
evaluere modellen med testdata, treffsikkerhet og forvekslingsmatrise
forklare overtilpasning, datalekkasje og noen begrensninger ved modellen
I vanlige programmer spesifiserer vi reglene datamaskinen skal følge. I maskinlæring gir vi i stedet datamaskinen eksempler og lar en algoritme finne mønstre som kan brukes på nye data.
Maskinlæring
Maskinlæring er metoder der en algoritme lærer mønstre fra eksempler og bruker mønstrene til å lage prediksjoner for nye data.
Maskinlæring kan blant annet brukes til å:
klassifisere spektre
forutsi stoffegenskaper fra molekylære beskrivelser
oppdage avvik i prosessdata
tolke bilder og kromatogrammer
lage modeller for sammenhenger med mange variabler
En maskinlæringsmodell er ikke det samme som en generativ KI som ChatGPT. Modellen i dette kapitlet lærer en avgrenset sammenheng i et tabulært datasett. En språkmodell er trent på store tekstmengder og genererer tekst. Begge bygger på data og statistiske mønstre, men oppgavene, modellene og feilkildene er svært forskjellige.
Hvorfor er kapitlet merket med stjerne?
Maskinlæring bygger videre på datahåndtering, statistikk og modellvurdering. Hovedmålet her er ikke å lære mange algoritmer, men å forstå arbeidsflyten og stille kritiske spørsmål til resultatet.
Case: klassifisering av flammeemisjon#
Natrium-, litium- og kaliumioner gir karakteristisk emisjon ved ulike bølgelengder. Vi bruker et lite syntetisk datasett med signalintensiteter rundt 589, 671 og 766 nm. Målet er å klassifisere ionet som Na, Li eller K.
Datasettet er konstruert for undervisning. Det representerer ikke et ferdig validert analyseinstrument, men etterlikner en situasjon der forskjellige ioner gir sterke signaler i forskjellige spektralområder.
import pandas as pd
data = pd.read_csv("data/flame_emission.csv")
print(data.head())
print(data.shape)
print(data["ion"].value_counts())
Hver rad er én prøve.
Egenskaper og målvariabel
Egenskapene (features) er kolonnene modellen får bruke som informasjon. Målvariabelen er verdien eller klassen modellen skal lære å forutsi. Her er emisjonsintensitetene egenskaper, mens ion er målvariabelen.
Steg 1: Utforske dataene#
Før vi lager en modell, må vi undersøke datasettet:
print(data.info())
print(data.isna().sum())
print(data.groupby("ion").mean(numeric_only=True))
Vi bør blant annet spørre:
Finnes det manglende verdier?
Har alle klasser omtrent like mange observasjoner?
Er enhetene og målebetingelsene sammenliknbare?
Er noen av egenskapene direkte avledet fra målvariabelen?
Er observasjonene uavhengige?
Vi kan visualisere to av emisjonssignalene:
import matplotlib.pyplot as plt
for ion, group in data.groupby("ion"):
plt.scatter(group["emission_589_nm"], group["emission_671_nm"], label=ion)
plt.xlabel("Emission intensity at 589 nm")
plt.ylabel("Emission intensity at 671 nm")
plt.legend(title="Ion")
plt.tight_layout()
plt.show()
En slik figur kan vise om klassene allerede er tydelig separert. Dersom problemet er enkelt å løse visuelt, trenger vi ikke en kompleks modell.
Steg 2: Velge egenskaper og målvariabel#
features = data[[
"emission_589_nm",
"emission_671_nm",
"emission_766_nm",
]]
labels = data["ion"]
Variabelen features inneholder informasjonen modellen får bruke. labels inneholder fasiten under trening og evaluering.
Vi bruker ikke sample_id som egenskap. Prøvenavnet identifiserer raden, men har ingen kjemisk sammenheng med ionet. Dersom prøve-ID-en kodet ionet direkte, ville modellen kunne lære navnemønsteret i stedet for spektrene. Det er et eksempel på datalekkasje.
Steg 3: Dele i trenings- og testsett#
Modellen må evalueres på data den ikke har brukt til trening:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
features,
labels,
test_size=0.30,
random_state=42,
stratify=labels)
test_size=0.30legger 30 prosent av observasjonene i testsettet.random_state=42gjør oppdelingen reproduserbar.stratify=labelsforsøker å bevare klassefordelingen i begge settene.
Testsettet må legges til side før modellvalg og tilpasning. Dersom vi gjentatte ganger endrer modellen for å få bedre resultat på det samme testsettet, begynner testsettet i praksis å påvirke treningen.
Treningsdata og testdata
Treningssettet brukes til å lære modellen. Testsettet brukes til en mer uavhengig vurdering av hvor godt den generaliserer til nye observasjoner.
Steg 4: En enkel referansemodell#
Før vi vurderer en maskinlæringsmodell, bør vi ha et enkelt sammenlikningsgrunnlag. En referansemodell kan alltid velge den vanligste klassen:
most_common_class = y_train.mode()[0]
baseline_predictions = [most_common_class] * len(y_test)
I et balansert datasett med tre klasser vil en slik modell ofte treffe omtrent en tredjedel. En avansert modell som ikke gjør det bedre enn dette, har liten verdi.
Steg 5: Trene et beslutningstre#
Et beslutningstre deler datasettet ved hjelp av en serie vilkår. Det passer godt pedagogisk fordi vi kan visualisere reglene:
from sklearn.tree import DecisionTreeClassifier
model = DecisionTreeClassifier(
max_depth=3,
random_state=42)
model.fit(X_train, y_train)
Under treningen velger algoritmen terskler som skiller klassene best i treningsdataene. max_depth=3 begrenser hvor komplisert treet kan bli.
Steg 6: Evaluere modellen#
from sklearn.metrics import accuracy_score
predictions = model.predict(X_test)
accuracy = accuracy_score(y_test, predictions)
print(f"Test accuracy: {accuracy:.3f}")
Treffsikkerheten er andelen testprøver som klassifiseres riktig. Den er lett å forstå, men kan være misvisende dersom klassene er svært ulikt fordelt.
En forvekslingsmatrise viser hvilke klasser som blandes sammen:
import matplotlib.pyplot as plt
from sklearn.metrics import ConfusionMatrixDisplay
ConfusionMatrixDisplay.from_predictions(y_test, predictions)
plt.title("Flame-emission classification")
plt.tight_layout()
plt.show()
Radene representerer de sanne klassene, mens kolonnene representerer modellens prediksjoner. Diagonalen viser riktige klassifikasjoner.
Du kan kjøre hele arbeidsflyten og visualisere beslutningstreet i editoren nedenfor.
Underveisoppgave
Endre
random_state. Hvor mye endres testresultatet?Endre
max_depthtil 1 og deretter tilNone.Fjern én av bølgelengdene fra egenskapene. Hvilke ioner blir vanskeligere å skille?
Er høy treffsikkerhet overraskende for dette konstruerte datasettet?
Løsningsforslag
Klassene er laget slik at hvert ion har et sterkt signal ved én karakteristisk bølgelengde. Problemet er derfor ganske enkelt. Høy testnøyaktighet viser at modellen finner dette mønsteret, men sier lite om hvordan den ville fungert med interferenser, blandinger, bakgrunnssignal eller instrumentdrift.
Prediksjon av en ukjent prøve#
Når modellen er trent, kan den brukes på en ny prøve med de samme egenskapene og enhetene:
unknown_sample = pd.DataFrame({
"emission_589_nm": [90],
"emission_671_nm": [10],
"emission_766_nm": [12],
})
predicted_ion = model.predict(unknown_sample)
print(predicted_ion[0])
En prediksjon er bare meningsfull dersom den nye prøven er sammenliknbar med treningsdataene. Modellen er ikke trent på blandinger, andre ioner eller signaler utenfor området i datasettet. Den vil likevel alltid velge én av de tre kjente klassene.
Et viktig problem
En klassifikasjonsmodell kan gi et tydelig svar også når prøven ligger langt utenfor det den har sett før. At programmet returnerer Na, betyr ikke at prøven med sikkerhet inneholder natrium.
Overtilpasning#
Overtilpasning
Overtilpasning betyr at modellen lærer detaljer og tilfeldigheter i treningsdataene så godt at den fungerer dårligere på nye data.
Vi kan sammenlikne resultatet på trenings- og testsettet:
train_accuracy = model.score(X_train, y_train)
test_accuracy = model.score(X_test, y_test)
print(f"Training accuracy: {train_accuracy:.3f}")
print(f"Test accuracy: {test_accuracy:.3f}")
Svært høy treningsnøyaktighet og betydelig lavere testnøyaktighet kan være et tegn på overtilpasning. I små datasett kan resultatet også variere mye med akkurat hvilke observasjoner som havner i testsettet.
Kryssvalidering#
Ved kryssvalidering deles treningsdataene på flere måter. Modellen trenes og valideres flere ganger:
from sklearn.model_selection import cross_val_score
scores = cross_val_score(model, features, labels, cv=5)
print(scores)
print(f"Mean cross-validation accuracy: {scores.mean():.3f}")
print(f"SD: {scores.std(ddof=1):.3f}")
Kryssvalidering gir et mer stabilt bilde enn én tilfeldig oppdeling, men gjør ikke datasettet større eller mer representativt. Dersom alle dataene kommer fra samme instrument og samme dag, tester vi ikke nødvendigvis hvordan modellen fungerer på et annet instrument eller en seinere prøveserie.
Datalekkasje#
Datalekkasje
Datalekkasje oppstår når modellen får informasjon som ikke ville vært tilgjengelig ved en reell prediksjon, eller når informasjon fra testsettet påvirker treningen.
Eksempler:
Prøvenavnet inneholder fasiten.
Replikater av samme fysiske prøve fordeles mellom trening og test.
Hele datasettet standardiseres før det deles.
Manglende verdier fylles ved hjelp av statistikk beregnet fra både trening og test.
Testsettet brukes gjentatte ganger til å velge modell.
I kjemiske data kan tekniske replikater være svært like. Dersom ett replikat brukes til trening og et annet til test, kan testresultatet bli kunstig godt. Da bør oppdelingen skje på prøvenivå, batchnivå eller instrumentnivå, avhengig av hva modellen skal generalisere til.
Modellen lærer datasettet, ikke kjemien automatisk#
Beslutningstreet kan finne sammenhenger mellom emisjonsintensiteter og ioneklasser. Det betyr ikke at modellen forstår elektronoverganger eller atomspektre. Den statistiske sammenhengen får kjemisk mening fordi vi kjenner hvordan datasettet ble laget og hvorfor de valgte bølgelengdene er relevante.
Før en modell brukes i praksis, bør vi blant annet undersøke:
representativitet og prøvetakingsmetode
interfererende stoffer og blandinger
instrument- og batchvariasjon
klassebalanse
måleusikkerhet
hvilke feil som er mest alvorlige
ytelse på helt uavhengige data
Oppgaver#
Oppgave 5.1
Forklar med egne ord forskjellen mellom et vanlig program med eksplisitte vilkår og et beslutningstre som lærer tersklene fra data.
Oppgave 5.2
Les flame_emission.csv. Identifiser observasjoner, egenskaper, målvariabel og kolonner som ikke bør brukes til trening.
Oppgave 5.3
Visualiser alle par av emisjonsintensiteter. Hvilke to bølgelengder skiller best mellom Na, Li og K? Er den tredje egenskapen overflødig?
Oppgave 5.4
Del datasettet uten stratify. Kjør programmet med flere random_state-verdier og undersøk klassefordelingen i trenings- og testsettet.
Oppgave 5.5
Beregn treffsikkerheten til en modell som alltid velger den vanligste klassen. Hvorfor må maskinlæringsmodellen sammenliknes med en slik referanse?
Oppgave 5.6
Tren beslutningstrær med max_depth fra 1 til 10. Plott trenings- og testnøyaktighet som funksjon av tre-dybden. Se etter tegn på undertilpasning og overtilpasning.
Oppgave 5.7
Lag en forvekslingsmatrise og beskriv hvilke feil modellen gjør. Hvorfor kan samme treffsikkerhet skjule svært forskjellige typer feil?
Oppgave 5.8
Lag en kunstig prøve med høy intensitet ved både 589 og 766 nm. Hva predikerer modellen? Hvorfor bør svaret tolkes forsiktig?
Oppgave 5.9
Beskriv tre former for datalekkasje som kan oppstå dersom hver kjemisk prøve er målt fem ganger. Foreslå en bedre oppdeling av dataene.
Oppgave 5.10 – kritisk vurdering
Skriv en kort valideringsplan for en modell som skal klassifisere ioner i virkelige prøver. Planen skal inkludere prøvetyper, interferenser, uavhengige testdata, relevante ytelsesmål og situasjoner der modellen ikke bør gi et automatisk svar.