Generativ musikkmodellering ved hjelp av Recurrent Neural Networks (RNN) krever en kompleks, men presis representasjon av musikkens struktur. I tilfeller hvor dataene er avledet fra MIDI-filer, kan sekvensiell musikk representeres gjennom hendelser som note-on, note-off, tidsforskyvninger, velocity (styrke) og kontrollendringer – alt dette for å fange opp både notenes start og slutt, dynamikken i uttrykket, pedalbruk og andre utførelsesaspekter. Dette gir et omfattende vokabular bestående av hundrevis av distinkte tokens som sammen representerer de temporale og uttrykksmessige dimensjonene ved et musikkstykke.

Performance RNN-modellen er trent på Yamaha e-Piano Competition-datasettet, som består av ca. 1 400 MIDI-filer spilt inn av klassisk skolerte pianister. Disse opptakene er rike på rubato og dynamisk variasjon, og dermed egnet for modellering av menneskelig uttrykk. Tokeniseringen følger et MIDI-lignende system hvor hver hendelse kodes som en én-hot-vektor på 413 dimensjoner, som inkluderer note-on, note-off, velocity og tidsforskyvning. Dette skaper en presis og håndterbar representasjon for nevrale nettverk.

Læringsprosessen baseres på teacher forcing, hvor modellen trenes på 30-sekunders segmenter og mates med korrekte neste steg fra treningsdataene. Dette fremskynder konvergensen og hjelper modellen å lære riktige sekvenser, men innskrenker samtidig modellens evne til å lære variasjoner som ikke forekommer eksplisitt i datasettet. Denne formen for rigid læring begrenser modellens evne til kreativ utfoldelse og innovasjon – den tenderer heller mot regelstyrt stilreproduksjon enn improvisatorisk uttrykk.

Den underliggende arkitekturen består av tre lag med Long Short-Term Memory-enheter (LSTM), hver med 512 celler. Treningen bruker log-tap (kategorisk kryssentropi) som tapfunksjon for å effektivisere tilbakepropagering. Ved generering benyttes beam search med en grenfaktor på 2 og minnekapasitet på 8 samtidige sekvenser. Dette gir modellen evnen til å evaluere flere kandidatsekvenser og velge de med høyest samlet sannsynlighet. For å unngå lav entropi og repetisjon benyttes stokastisk beam search med temperaturjustering, noe som gir en kontrollert grad av tilfeldighet i utvalget av sekvenser.

Et sentralt problem i generativ musikkmodellering er mangelen på kontroll over generert innhold. Derfor er conditioning – innføring av eksterne kontrollsignaler – essensielt. Dette kan gjøres ved å gi modellen ekstra input som beskriver komponist, epoke, geografisk opprinnelse, tonalitet, tempo og musikalsk form. Slike kontrollparametere kan trekkes fra metadata som tittel, stilart eller analyse av stykket. En spesielt effektiv conditioning-strategi er relativ posisjon i verket, som gir informasjon om hvor i stykket et segment er hentet fra – innledning, klimaks eller avslutning – og lar modellen reflektere strukturelle karakteristikker i genereringen.

Det finnes imidlertid utfordringer knyttet til sparsitet i treningsdata for slike kontrollvariabler. Dette gjør at modellen lett overtrener på begrensede mønstre. Samtidig argumenteres det for at en viss grad av overfitting kan være akseptabel i kreative sammenhenger, fordi det kan føre til stilistiske lån eller "sitat" som likner menneskelige musikkpraksiser. Slik kan modellen produsere sekvenser som bærer tydelige stiltrekk fra en bestemt komponist eller epoke, noe som i kunstnerisk forstand kan oppfattes som autentisk snarere enn mekanisk.

Det er viktig å forstå at selv om en modell som Performance RNN kan reprodusere uttrykk, stil og struktur, mangler den en ekte intensjon eller musikalitet i menneskelig forstand. Dynamikk og rytmisk fleksibilitet blir innkodet som tokens og beregnet via sannsynlighetsfordelin

Hvordan fungerer Transformer-modellen og dens applikasjoner?

Transformer-modellen har revolusjonert feltet for naturlig språkbehandling, spesielt når det gjelder oppgaver som maskinoversettelse, tekstgenerering og forståelse. Hovedidéen bak Transformer-modellen er at den bruker oppmerksomhet (attention) på en helt ny måte, og dermed muliggjør mer effektive og fleksible løsninger enn tidligere modeller som RNN og LSTM. Den grunnleggende byggesteinen i Transformer er oppmerksomhetsmekanismen, som tillater modellen å fokusere på ulike deler av inngangssekvensen samtidig, i motsetning til de sekvensielle prosessene som preget tidligere modeller.

I Transformer-modellen benyttes det en mekanisme kalt cross-attention, der det er to sekvenser som samhandler med hverandre: en spørring (query) og et sett med nøkler og verdier (keys og values). Spørringen er relatert til utgangssekvensen, mens nøklene og verdiene hentes fra inngangssekvensen. Ved å beregne dot-produktet mellom spørring og nøkkel oppnår man en vektet sum, som representerer en form for oppmerksomhet som "krysser" inngangssekvensen med utgangssekvensen. Denne mekanismen gjør det mulig for modellen å fange langsiktige avhengigheter i sekvenser på en svært effektiv måte.

Posisjonell embedding er et annet viktig element i Transformer-modellen. Det fungerer som en måte å gi modellen informasjon om rekkefølgen på elementene i sekvensen, noe som er viktig for å opprettholde den nødvendige konteksten. Uten posisjonell embedding ville modellen ikke kunne skille mellom forskjellige elementer i en sekvens, og dermed miste den essensielle informasjonen om rekkefølgen. Posisjonelle embedding fungerer ved å tildele hvert element i sekvensen en vektor som representerer dets posisjon, og deretter legges denne vektoren til den vanlige token-embedding-en.

For å konstruere Transformer-modellen begynner man med å vektorize inngangssekvensen og legge til de posisjonelle embeddingene. Deretter blir denne komplekse representasjonen matet inn i Transformer Encoder, som bearbeider sekvensen. Dekoderen tar både den kodede sekvensen og tidligere utgangsverdier (kalt "decoder inputs"), og genererer den endelige prediksjonen ved å bruke en ny runde med oppmerksomhet og sammenslåing av inngangs- og utgangssekvenser. Resultatet er et utgangselement som har størrelse som det ønskede vokabularet.

Det finnes flere varianter av Transformer-modellen, avhengig av bruksområdet. Den originale Transformer-modellen er et fullt encoder-decoder system, brukt for oppgaver som maskinoversettelse. BERT (Bidirectional Encoder Representations from Transformers) er en encoder-only modell, designet for oppgaver som setningsforståelse og tekstklassifisering, der målet er å generere en god representasjon av inngangsteksten. GPT (Generative Pretrained Transformer), derimot, er en autoregressiv modell som benytter seg av en dekoderstruktur for oppgaver som språkmodellering og tekstgenerering. GPT er kjent for sin evne til å utføre flere oppgaver uten spesifikk trening på dem, et fenomen som kalles "zero-shot" læring.

BERT og GPT er eksempler på hvordan Transformer-modellen kan tilpasses til forskjellige typer problemer. BERT bruker maskert språkmodellering for å forutsi manglende tokens i en setning, og lærer å forstå konteksten ved å se på begge sider av et gitt token. GPT, derimot, bruker en autoregressiv tilnærming, der den genererer tekst ved å forutsi neste token i en sekvens, basert på konteksten fra venstre side.

En av de største fordelene med Transformer-modeller er deres evne til å generalisere til forskjellige oppgaver. Etter å ha blitt forhåndstrent på en stor mengde tekstdata, kan modellene fintune spesifikke oppgaver, som oversettelse, tekstgenerering eller klassifisering. En særlig fordel med GPT-2 og senere modeller er deres evne til å lære oppgaver "zero-shot", noe som betyr at de kan håndtere nye oppgaver uten eksplisitt opplæring på disse, bare ved å gi en passende prompt.

Dette åpner for spennende muligheter, spesielt i situasjoner der dataene er begrensede, eller når man trenger modeller som kan håndtere en rekke forskjellige oppgaver uten behov for å trene på hver enkelt oppgave. Transformer-modellene kan trenes på tvers av ulike typer data og kan håndtere mange forskjellige modaliteter, fra tekst til bilder og til og med multimodale oppgaver som kombinerer begge.

Det er også viktig å merke seg at selv om Transformer-modellene er ekstremt kraftige, kommer de med noen utfordringer. De er kjent for deres store beregningsmessige kostnader, spesielt når det gjelder minnebruk og prosesseringstid, ettersom modellen må håndtere hele sekvensen samtidig. Det er derfor fortsatt viktig å finne effektive metoder for optimalisering, spesielt når det gjelder implementering av slike modeller i produksjon.

Hvordan kan vi forklare beslutninger i nevrale nettverk ved hjelp av additive funksjonsattribusjonsmetoder?

Nevrale nettverk har oppnådd stor suksess innen mange områder, men deres komplekse og ofte uforståelige struktur gjør dem til «black-box»-modeller. Dette representerer en utfordring, spesielt når slike modeller brukes i kritiske applikasjoner som medisinsk diagnostikk eller kredittvurdering, hvor det er nødvendig å forstå og kunne forklare modellens beslutninger. I stedet for å modellere problemet eksplisitt, trenes nevrale nettverk gjennom store mengder data, og modellens parametere justeres for å oppnå optimal ytelse, noe som fører til at den underliggende logikken ofte forblir uklar for brukeren.

Det essensielle i forklarbar kunstig intelligens (Explainable AI) er ikke nødvendigvis å forstå de eksakte interne mekanismene, men å kunne identifisere hvilke inputfunksjoner som har påvirket modellens beslutning. Ved å kunne knytte utdata tilbake til bestemte egenskaper ved inngangsdataene, øker tilliten til modellens prediksjoner betydelig. Additive funksjonsattribusjonsmetoder tar sikte på å forenkle komplekse modeller ved å tilnærme dem med en lineær forklaringsmodell som gjør det mulig å rangere funksjonenes betydning.

En av de mest anvendte tilnærmingene i denne kategorien bygger på Shapley-verdier, som stammer fra kooperativ spillteori. Her oppfattes inputfunksjonene som spillere i et spill, hvor modellens utfall representerer «gevinsten» som skal fordeles rettferdig. Forklaringsmodellen uttrykkes som en lineær kombinasjon av funksjoner hvor hver funksjons bidrag, eller attributt, kvantifiseres gjennom en Shapley-verdi. Disse verdiene reflekterer den gjennomsnittlige marginale bidraget til en funksjon over alle mulige kombinasjoner av funksjonssett (koalisjoner).

I praksis betyr dette at for hver enkelt prediksjon evalueres effekten av tilstedeværelsen eller fraværet av hver funksjon, noe som krever at modellen simulerer scenarier med delvis manglende input. Dette er nødvendig fordi modellen vanligvis ikke er trent på slike delvise data. De resulterende forklaringene gir innsikt i hvordan spesifikke funksjoner bidrar til utfallet, noe som kan være avgjørende for å identifisere viktige egenskaper, feilkilder eller bias i modellen.

Metodikken er også nyttig for å debugge og forbedre robuste prediksjoner, samt for å utlede enklere regler fra komplekse modeller. For å estimere forklaringsmodellen benyttes ofte sampling- eller kjernebaserte regresjonsmetoder for å håndtere kompleksiteten i beregningen av Shapley-verdier.

Det er viktig å forstå at denne typen forklaring ikke avklarer alle sider ved modellens funksjon, men gir et verktøy for å tolke og validere beslutningene på et meningsfylt nivå. Slike forklaringer fungerer best når de kombineres med en dypere forståelse av data og kontekst, og når man anerkjenner at kompleksiteten i nevrale nettverk kan medføre at enkelte interne prosesser forblir utilgjengelige for direkte tolkning.

Hvordan fungerer klassifisering med nevrale nettverk i PyTorch?

Når vi løser et klassifiseringsproblem med nevrale nettverk, er målet at nettverket skal lære seg å gjenkjenne ulike klasser basert på treningsdata. For eksempel, i et problem med ti klasser, vil nettverket produsere en ti-dimensjonal utgangsvektor hvor hvert element representerer sannsynligheten for at input tilhører en bestemt klasse. Denne vektoren kalles ofte for logitene. Gitt et input xx, for eksempel et bilde som er omgjort til en vektor, vil nettverket gi et output z=(z1,...,z10)R10z = (z_1, ..., z_{10}) \in \mathbb{R}^{10}.

For å tolke disse outputverdiene som sannsynligheter, transformeres de ved hjelp av softmax-funksjonen. Softmax omformer vektoren zz til en sannsynlighetsfordeling z~=(z~1,...,z~10)\tilde{z} = (\tilde{z}_1, ..., \tilde{z}_{10}), hvor hver komponent z~i\tilde{z}_i er definert som

z~i=exp(zi)j=110exp(zj),\tilde{z}_i = \frac{\exp(z_i)}{\sum_{j=1}^{10} \exp(z_j)},

som garanterer at summen av alle sannsynligheter blir 1. På denne måten kan man si at z~i\tilde{z}_i er sannsynligheten for at input tilhører klasse ii.

Den riktige klassen for et gitt input kan representeres som en såkalt one-hot-vektor yR10y \in \mathbb{R}^{10}, hvor én komponent er 1 (den korrekte klassen) og resten er 0. Dette gjør at det er enkelt å sammenligne nettverkets prediksjon med den faktiske klassen. Forskjellen mellom prediksjon og sann klasse måles ved hjelp av kryssentropi-tapet (cross-entropy loss), som for et datapunkt xx kan uttrykkes som

ECE(x)=i=110yilog(z~i).E_{CE}(x) = -\sum_{i=1}^{10} y_i \log(\tilde{z}_i).

Siden yy er en one-hot-vektor, reduseres summen til bare ett ledd som svarer til den korrekte klassen. Kryssentropi-tapet gir en intuitiv måte å måle hvor godt sannsynlighetsfordelingen til nettverket samsvarer med den riktige klassen.

I PyTorch trenger man ikke å beregne softmax manuelt når man bruker funksjonen torch.nn.CrossEntropyLoss. Denne funksjonen tar imot logitene direkte og kombinerer softmax og kryssentropi i en numerisk stabil implementasjon.

For å bygge nevrale nettverk i PyTorch brukes ofte klasser som arver fra nn.Module. For eksempel kan man definere et enkelt fullt tilkoblet nettverk med tre lag:

python
class NeuralNetwork(nn.Module):
def __init__(self): super(NeuralNetwork, self).__init__() self.flatten = nn.Flatten() self.fcn = nn.Sequential( nn.Linear(28*28, 512), nn.ReLU(), nn.Linear(512, 512), nn.ReLU(), nn.Linear(512, 10) ) def forward(self, x): x = self.flatten(x) logits = self.fcn(x) return logits

Dette nettverket tar inn 28x28 piksler flate bilder, bearbeider dem gjennom to skjulte lag med ReLU-aktivering, og gir et output på 10 logiter, ett for hver klasse.

For effektiv trening kan man dra nytte av GPU-akselerasjon via CUDA dersom tilgjengelig, og man kan laste data i minibatcher med DataLoader. Under treningen brukes typisk en løkke hvor man for hver batch beregner prediksjoner, tap, og utfører backpropagation for å oppdatere vektene i nettverket. Optimizer som SGD (stochastic gradient descent) justerer parametrene basert på læringsrate.

Ved evaluering av modellen beregnes tap og nøyaktighet over testsettet uten at gradienter kalkuleres. Nøyaktigheten måles som andelen riktige prediksjoner, hvor prediksjonen regnes som klassen med høyest logitverdi (argmax).

Læringsprosessen kan forbedres ved bruk av avanserte teknikker som læringsrate-planlegging, for eksempel med 1-cycle scheduler, som dynamisk justerer læringsraten gjennom trenings-epoker for å oppnå bedre generalisering og raskere konvergens.

Moderne rammeverk som PyTorch bygger på automatisk differensiering (autograd), som effektivt kalkulerer gradienter ved bruk av kjerneregelen. Dette muliggjør rask og pålitelig trening av komplekse modeller.

Det er viktig å forstå at nevrale nettverk lærer ved å justere vekter slik at tapet minimeres, noe som krever god balanse mellom modellens kompleksitet, treningsdata og optimaliseringsstrategi. Overfitting kan unngås med passende reguleringsteknikker, og valg av arkitektur og hyperparametre har stor innvirkning på resultatet.

Hvordan virker tilbakepropagering og hva skjer egentlig med aktiveringene i nevrale nettverk?

Kjederegelen fra kalkulus gir en fundamental struktur for å håndtere avhengighetene mellom variabler i sammensatte funksjoner. I sammenheng med nevrale nettverk er denne regelen selve matematiske ryggraden bak tilbakepropagering: prosessen som gjør det mulig å oppdatere vektene i nettverket for å minimere feil. Dersom vi har en sammensetning av funksjoner som y=g(x)y = g(x) og z=f(y)z = f(y), og ønsker å finne dzdx\frac{dz}{dx}, anvendes kjederegelen til å sekvensielt differensiere de indre funksjonene: dzdx=f(g(x))g(x)\frac{dz}{dx} = f'(g(x)) \cdot g'(x).

Dette prinsippet generaliseres enkelt til vektorer. Gitt vektorfunksjoner y=g(x)y = g(x) og z=f(y)z = f(y), kan vi uttrykke den totale gradienten som xz=Jyz\nabla_x z = J \cdot \nabla_y z, der JJ er Jacobimatrisen til gg med hensyn til xx. Det essensielle her er at vi bruker transponerte Jacobimatriser, hvilket er kritisk for riktig dimensjonsmatching i tilbakepropageringen.

I et enkelt nevralt nettverk, hvor input xx multipliseres med vekter w1w_1 og w2w_2 for å produsere mellomledd z1=w1xz_1 = w_1x og z2=w2xz_2 = w_2x, følges disse av en ikke-lineær aktiveringsfunksjon σ\sigma, og de resulterende aktiveringene h1=σ(z1)h_1 = \sigma(z_1) og h2=σ(z2)h_2 = \sigma(z_2) summeres til en endelig output y=h1+h2y = h_1 + h_2. For å finne hvordan yy påvirkes av en bestemt vekt som w1w_1, følger man kjeden bakover: yh1z1w1y \to h_1 \to z_1 \to w_1, og multipliserer de partielle deriverte: yw1=yh1h1z1z1w1=1σ(z1)x\frac{\partial y}{\partial w_1} = \frac{\partial y}{\partial h_1} \cdot \frac{\partial h_1}{\partial z_1} \cdot \frac{\partial z_1}{\partial w_1} = 1 \cdot \sigma'(z_1) \cdot x.

For input-derivater, som yx\frac{\partial y}{\partial x}, må man ta hensyn til begge veier fra yy til xx, via h1h_1 og h2h_2. Bidragene fra hver vei summeres: σ(z1)w1+σ(z2)w2\sigma'(z_1) w_1 + \sigma'(z_2) w_2. Denne strukturen gjør det mulig å effektivt implementere læring i store nettverk, ved å systematisk propagere feil tilbake gjennom lagene.

Moderne automatiske derivasjonssystemer implementerer dette med to hovedstrategier: symbol-til-tall-derivasjon og symbol-til-symbol-derivasjon. I det første tilfellet, typisk brukt av PyTorch, bygges grafen dynamisk under foroverpasset, og deriverte beregnes med faktiske numeriske verdier. I det andre tilfellet, anvendt i tidlige versjoner av TensorFlow, konstrueres en symbolsk graf på forhånd, og tilbakepropagering skjer ved hjelp av symbolsk manipulering. Valget av strategi påvirker fleksibiliteten og transparensen i modelleringen.

Når det gjelder klassifikasjon, er derivasjonene for tapfunksjoner sentrale. For binær klassifikasjon med logistisk sigmoid-output yi=11+eaiy_i = \frac{1}{1 + e^{ -a_i}}, og krysstapsfunksjon

ECE=i=1N(tilogyi+(1ti)log(1yi)),\text{ECE} = -\sum_{i=1}^N \left( t_i \log y_i + (1 - t_i) \log (1 - y_i) \right),

er den partielle deriverte av tapet med hensyn til logit aia_i gitt ved ECEai=yiti\frac{\partial \text{ECE}}{\partial a_i} = y_i - t_i. Dette resultatet er ikke bare en analytisk forenkling, men gir også et direkte mål på feilen – differansen mellom predikert sannsynlighet og det sanne merket – og det er dette som danner utgangspunktet for gradienten som tilbakepropageres gjennom nettverket.

En annen sentral utfordring under trening av nevrale nettverk er det som kalles intern kovariatforskyvning: når distribusjonen av aktiveringer i skjulte lag endres som følge av oppdateringene i vektene. Dette kan føre til ustabilitet i treningen, særlig i dype arkitekturer. For å motvirke dette, ble batchnormalisering introdusert. Prosedyren innebærer sentrering og skalering av aktiveringene over en mini-batch:

x^i=xiμBσB2+ε,yi=γx^i+β,\hat{x}_i = \frac{x_i - \mu_B}{\sqrt{\sigma_B^2 + \varepsilon}}, \quad y_i = \gamma \hat{x}_i + \beta,