Lineær regression i Standard ML Hans Hüttel 1. november 2001 Indhold 1 Hvad denne note handler om 2 2 Hvor bruger man lineær regression? 2 3 Problemanalyse 3 3.1 Den matematiske teori...................... 3 3.2 Krav til program......................... 4 3.3 Interface.............................. 4 4 Programmering 5 4.1 Hvordan skal data repræsenteres i programmet?........ 5 4.1.1 Repræsentation af data.................. 5 4.1.2 Repræsentation af resultat................ 5 4.2 Statistiske funktioner....................... 6 4.2.1 Hjælpefunktioner..................... 6 4.2.2 Middelværdi........................ 7 4.2.3 SSD............................ 7 4.2.4 En meddelelse fra ML-systemet............. 8 4.3 Beregning af bedste linie..................... 8 5 Test 9 e-mail: hans@cs.auc.dk. Denne note er naturligvis skrevet med dokumentbehandlingssystemet LA TE X. 1
1 Hvad denne note handler om I denne note skal vi se hvordan man kan implementere lineær regression i Standard ML. Denne note giver et første eksempel på hvordan man i ingeniørsammenhænge kan drage stor nytte af programmering til problemløsning. Samtidig giver denne note et realistisk eksempel på anvendelse af begreberne fra kapitlerne 3, 4 og 5 i [HR99]. I en senere note følger vi op på dette eksempel og viser hvordan man kan lave et program der læser data til en regressionsanalyse fra en l og skriver resultaterne ud til plotteprogrammet gnuplot. Herved bliver det muligt at bruge Standard ML til at generere illustrationer af sammenhænge i data. 2 Hvor bruger man lineær regression? Lineær regression er en statistisk metode der tillader os at nde den bedste rette linie givet en mængde af datapunkter. Lineær regression dukker op i ingeniørvidenskaberne i mange sammenhænge. Herunder er nogle eksempler. I kemi dukker lineær regression hyppigt op. Man kan ønske at undersøge om der er en lineær sammenhæng mellem to datasæt. Eller man har behov for kalibrere måleapparatur således at måleapparaturet viser en lineær sammenhæng mellem f.eks. koncentrationen af en analyt og en tilført værdi. I mekanisk fysik er mange sammenhænge lineære eller tilnærmelsesvist lineære. Et velkendt eksempel er Hookes lov. Denne giver en sammenhæng mellem kraften virkende på et elastisk objekt og dette objekts bøjning. Et eksempel kunne være en bjælke der ligger vandret. Hvis x er nedbøjningen af bjælken, når man belaster den med kraften F, siger Hooke's lov at F = b x b er en konstant, der kaldes blødheden. At blødheden er konstant betyder, at den ikke afhænger af nedbøjning eller kraft. En velkendt problemstilling vil være at bestemme et skøn over bjælkens blødhed b ud fra en række målinger af F og x. Hertil bruger man lineær regression. I kredsløbsteori er mange sammenhænge lineære eller tilnærmelsesvist lineære eller tilnærmelsesvist lineære.. Et velkendt eksempel er Ohms lov, der siger at sammenhængen mellem spændingsforskel U, modstand R og strømstyrke I er 2
U = R I En velkendt problemstilling vil være at bestemme et skøn over modstanden R ud fra en række målinger af U og I. Hertil bruger man lineær regression. 3 Problemanalyse Inden programmeringsarbejdet skal vi analysere vores problem og opstille krav til det program vi skal konstruere. 3.1 Den matematiske teori Lad os antage at vi har målt to størrelser X og Y n gange. Dette har givet os datasættene (x 1,..., x n ) og (y 1,..., y n ) henholdsvis. Vi antager at der er en lineær sammenhæng mellem X-værdierne og Y - værdierne, så X = my + b Vi vil nu estimere (nde de bedste bud på) hældningskoecienten m og skæringen b med y-aksen. Middelværdierne af X og Y er givet ved X = Y = ni x i n ni y i Ud fra middelværdierne skal vi nu beregne covariansen Var(X, Y ) og variansen Var(X) af X og variansen Var(Y ) af Y. Disse er givet ved n Var(X, Y ) = Var(X) = Var(Y ) = n (x i X)(y i Y ) i=1 n (x i X) 2 i=1 n (y i Y ) 2 i=1 3
Bemærk at varians og covarians ligner hinanden; de er begge en sum af produkter af forskelle. Hvis vi denerer størrelsen SSD ( Sums of Squares of Deviations) SSD(X 1, X 2, d 1, d 2 ) = n i+1 kan vi udtrykke varians og covarians som henholdsvis (x 1i d 1 )(x 2i d 2 ) (1) Var(X, Y ) = SSD(X, Y, X, Y ) (2) Var(X) = SSD(X, X, X, X) (3) Var(Y ) = SSD(Y, Y, Y, Y ) (4) (5) Estimaterne m og b er givet ved formlerne m = Var(X, Y ) (6) Var(X) b = Y mx (7) 3.2 Krav til program Vores program skal ud fra en given mængde af datapunkter kunne beregne m og b, Var(X) og Var(Y ) ved brug af formlerne (1)-(7). Datapunkterne skal være angivet i en liste. 3.3 Interface Vi ønsker at denere to nye typer, dataliste, der skal være typen af vore data, og resultat, der skal være typen af resultatet af en regressionsanalyse. En dataliste skal være en liste af par af real-værdier, mens et resultat skal være en record der rummer de interessante værdier som regressionsanalysen beregner. Vi vender tilbage til disse typer i afsnit 4. Vi ønsker at lave en funktion bedste_linie der, givet en dataliste, giver os et resultat af type resultat. Desuden er vi interesseret i en funktion middelv der, givet en liste af værdier, giver os middelværdien af værdierne i listen. Og endelig er vi interesseret i en liste der, givet to lister af værdier for X 1 og X 2 samt afvigelser d 1 og d 2, beregner SSD(X 1, X 2, d 1, d 2 ). Disse funktioner er angivet i interface-denitionen i Tabel 1. 4
Specikation type dataliste = (real * real) list type resultat = { hkoeff : real, skaering : real} middelv : real list -> real ssd : real list * real list * real * real -> real bedste_linie : dataliste -> resultat Kommentar Type af en liste af data Type af resultat Beregning af middelværdi Beregning af SSD Beregning af bedste linie Tabel 1: Interface Funktionerne i vores interface er de funktioner som en bruger af vores program skal kunne benytte. Desuden får vi brug for nogle hjælpefunktioner som brugeren ikke skal kunne se; disse skal ikke være med i vores interface. 4 Programmering Nu kan vi gå over til at skrive programtekst. 4.1 Hvordan skal data repræsenteres i programmet? Vores program skal bestå af en samling af funktioner der opererer på data. Først er vi derfor nødt til at nde ud af hvordan vi vil beskrive data i SML. 4.1.1 Repræsentation af data Et datapunkt skal være et par af kommatal; vores datasæt er en liste af datapunkter. Derfor giver følgende typeerklæringer god mening: type datapunkt = real * real; type dataliste = datapunkt list; Vores liste kunne f.eks. være denne val minliste = [(1.2,2.4),(1.3,2.6),(3.2,6.7)] : 4.1.2 Repræsentation af resultat Vi ønsker at regressionsanalysen skal give os værdier for m og b, Var(X) og Var(Y ). Disse værdier kunne vi samle i et par eller i en record. Vi vælger at sige at typen af resultatet skal være en record. Felterne i en record er nemlig 5
navngivet og det gør det nemmere for os at huske hvilken værdi der er m og hvilken der er b når vi bliver præsenteret for resultatet af regressionsanalysen. Vi erklærer derfor denne type: type resultat = { hkoeff : real, skaering : real } Når vi kalder vores funktion der nder bedste linie skal vi derfor forvente at få et svar som f.eks. {hkoeff = 2.0, skaering = 0.0, x_varians = 9.48, y_varians = 37.92} : {hkoeff : real, skaering : real, x_varians : real, y_varians : real} 4.2 Statistiske funktioner Vi skal nu se hvordan man kan beregne værdierne deneret i (1)(7). Vores data repræsenterer vi som lister og de statistiske funktioner opererer således på lister. Vi tager derfor udgangspunkt i [HR99, kapitel 5]. 4.2.1 Hjælpefunktioner Vi får brug for nogle hjælpefunktioner. Fra en liste af par til et par af lister Vi repræsenterer data som en liste af par. Men når vi skal beregne middelværdier og SSD-værdier er det en fordel at kunne splitte listen op så vi kun en liste af første-komponenter eller en liste af anden-komponenter. Hertil kan vi bruge en funktion der allerede ndes, nemlig funktionen unzip fra [HR99, afsnit 5.6.4]. Denne funktion ndes i biblioteket ListPair som vi derfor skal huske at hente ind. unzip har typen val unzip = fn : ('a * 'b) list -> 'a list * 'b list og er en polymorf funktion som beskrevet i [HR99, afsnit 5.4.1]. unzip virker nemlig på vilkårlige lister af par. At summere værdierne i en liste Når vi skal beregne middelværdi får vi brug for at summere værdierne i en liste. Dette gør vi med funktionen sum_liste der kan deneres således: fun sum_liste [] = 0.0 sum_liste (x::l) = x + sum_liste l; 6
Denne funktion er rekursiv; summen af en tom liste er 0, 0 mens summen af en liste der ikke er tom er første element plus summen af resten af listen. Hvad er typen af sum_liste? Den er val sum_liste = fn : real list -> real Det var vigtigt at vi skrev 0.0 i denition. Hvis vi havde skrevet fun sum_liste [] = 0 sum_liste (x::l) = x + sum_liste l; ville vi få val sum_liste = fn : int list -> int da 0 har typen int. Dette ville give os en typefejl hvis vi ville anvende funktionen på en liste af type real list. 4.2.2 Middelværdi Ved hjælp af funktionen sum_liste kan vi nu let beregne middelværdi af en liste af real-værdier ved brug af (1). For at nde n skal vi bruge længdefunktionen length som giver os længden af en liste. Da length: 'a list -> int er længden et heltal, men vi skal dividere med en værdi af type real. En værdi af type int kan konverteres til en værdi af type real med funktionen real : int -> real. Vi får derfor fun middelv l = (sum_liste l)/(real(length l)); middelv har typen val middelv = fn : real list -> real 4.2.3 SSD SSD(X, Y, d 1, d 2 ) kan beregnes med denne rekursive funktion: fun ssd ([],[], d1, d2) = 0.0 ssd ((x::lx),(y::ly),d1,d2) = (x-d1)*(y-d2) + ssd (lx,ly,d1,d2); ssd baner sig vej gennem parret af datalister på følgende vis: Hvis de to datalister begge er tomme, giver ssd værdien 0, 0. Ellers beregner ssd produktafvigelsen (x d 1 )(y d 2 ) for de første værdier x og y og lægger den fundne værdi til resultatet af at nde ssd for de to lister med de resterende data. 7
4.2.4 En meddelelse fra ML-systemet ML-systemet giver følgende meddelelse når det ser denitionen af ssd: Warning: pattern matching is not exhaustive Denne meddelelse skyldes at der ikke er en klausul for alle re mulige udseender af de to lister med data, men kun de to mønstre som er interessante. Denne meddelelse betyder ikke at der er noget galt, blot at funktionen ssd ikke kan kaldes med f.eks. to lister hvor kun den ene er tom som f.eks. ssd([],[1.3,2.4]) Der er nemlig ikke noget mønster i denition for dette tilfælde. Men det er ikke et problem i denne sammenhæng. Vi anvender nemlig kun ssd på par af lister hvor de to lister er lige lange i det rekursive kald af ssd i anden klausul kalder vi ssd med halerne, der jo er lister der begge er 1 element kortere. Hvis vi derfor kalder ssd med et par af lige lange lister vil vi til sidst kunne anvende første klausul i denitionen. ssd har typen val ssd = fn : real list * real list * real * real -> real 4.3 Beregning af bedste linie Vi kan nu denere funktionen bedste_linie der, givet en liste af par, udregner hældningskoecient m og skæring med y-akse. I funktionens denition er det en fordel at have nogle mellemregninger. Her er let-konstruktionen meget nyttig. Selve mellemregningerne anvender funktionerne middelv og ssd som i (2), (3) og (4). fun bedste_linie l = let (* lister af x- og y-vaerdier beregnes *) val (lx,ly) = ListPair.unzip l (* middelvaerdi af x-vaerdier *) val mx = middelv lx (* middelvaerdi af y-vaerdier *) val my = middelv ly (* varians af x-vaerdier *) val xvarians = ssd (lx,lx,mx,mx) (* varians af y-vaerdier *) val yvarians = ssd (ly,ly,my,my) 8
(* covarians *) val covarians = ssd (lx,ly,mx,my) (* haeldning *) val m = covarians/xvarians (* skaering *) val b = my - m*mx in { hkoeff= m, skaering=b } end; Typen af bedste_linie er som interface-denitionen krævede det val bedste_linie = fn : (real * real) list -> {hkoeff : real, skaering : real} 5 Test Også i dette tilfælde er det vigtigt at teste programmet systematisk. Her er den vigtige test testen af funktionerne middelv, ssd og bedste_linie. Da middelv også anvender sum_liste skal vi også teste denne funktion. Vi skal specielt teste hver klausul i sum_liste og ssd, da begge disse funktioner er rekursive og deneret ved et antal klausuler. For bedste_linie skal vi teste funktionen med et kendt eksempel på lineær regression fra litteraturen og sammenligne de fundne værdier med litteraturens. Litteratur [HR99] Michael R. Hansen og Hans Rischel. Introduction to Programming Using Standard ML, Addison-Wesley 1999. 9