Esimerkki 1. Yksi normaalijakautunut muuttuja.
Henkilöllä X on 1000 transaktiota viimeisen vuoden aikana ja pankki haluaa asettaa hälytysrajat, jotta epäilyttävän suuret transaktiot voidaan tarkistaa manuaalisesti. Tehdään transaktioista histogrammi ja nähdään, että transaktiot ovat suunnilleen normaalisti jakautuneet. Normaalijakauma-oletuksen avulla tiedetään, että n. 0.15% tapahtumista asettuu plus kolmen keskihajonnan päähän keskiarvosta. Esimerkissä Cut-off-piste asettuu kohtaan 137 €, eli kaikki tämän ylittävät transaktiot hälyttäisivät.
a <- data.frame(transaktiot=c(rnorm(995, mean=70,sd=20),130,140,150))
a <- -min(a$transaktiot)+a
#Valitaan cut-off-piste kolmen keskihajonnan päästä keskiarvosta.
cutoff <- mean(a$transaktiot)+3*sd(a$transaktiot)
ggplot(data=a,aes(x=transaktiot))+
geom_histogram(binwidth=5, fill="blue") +
labs(title="1000 tilitapahtumaa (Cut-off-piste asetettu kolmen keskihajonnan päähän keskiarvosta)",x="Transaktion suuruus €",y="Määrä")+
theme(plot.title = element_text(hjust = 0.5))+
geom_vline(xintercept = cutoff, linetype="dotted",
color = "red", size=1.5)+
geom_segment(aes(x = 118, y = 76, xend = 130, yend = 73),size=1.5,
arrow = arrow(length = unit(0.5, "cm")))+
annotate(geom="text", x=113, y=79, label="Cut-off-piste",
color="red",size=6)

Esimerkki 2. Multivariate Gaussian: Useampi riippumaton normaalijakautunut muuttuja.
Normaalijakauma-oletuksen avulla voitaisiin lisätä mukaan myös muita muuttuja (esim. transaktion kellon aika) ja laskea eri yhdistelmille todennäköisyydet. Tällöin hälytys voitaisiin asettaa esim. samaan kohtaan "0.15%", eli vain tämän todennäköisyyden alittavat tapahtumat hälyttäisivät. Käytännössä laskenta tapahtuu niin, että estimoimme kaikille muuttujille keskiarvot sekä keskihajonnat ja saamme näin jokaiselle muuttujalle estimoitua tiheysfunktion (Probability density function). Tämän jälkeen voimme laskea jokaiselle muuttujan arvolle todennäköisyyden. Otetaan esimerkiksi tilanne, kun meillä on kaksi muuttujaa X1 ja X2 (Huom. teemme oletuksen muuttujien riippumattomuudesta).
X1:n keskiarvo on 5 ja keskihajonta 3.
X2:n keskiarvo on 2 ja keskihajonta 1.
Nyt voimme ratkaista, kuinka todennäköistä on saada näistä jakaumista esim. arvot X1=1 ja X2=4.
pX1 <- dnorm(1,mean=5,sd=3)
pX2 <- dnorm(4,mean=2,sd=1)
pX1*pX2
=0.29%
Näemme, että jos cut-off-pisteemme on 0.15% (0.29%>0.15%), niin kyseessä ei ole poikkeava havainto.
Katsotaan esimerkkiä vielä luomalla kuva, jossa on alkuperäisten datapisteiden lisäksi myös Contour-kuva, joka näyttää kohdat (isoviivat), joissa todennäköisyydet ovat samat. Esimerkiksi keskimmäinen isoviiva näyttää alueen, jossa todennäköisyys on korkein. Näemmekin, että tällä alueella todennäköisyydet ovat 4%:n tienoilla.
#Tehdään kaksi muuttujaa
X1 <- c(rnorm(20,mean=5,sd=3),11,15,16)
X2 <- c(rnorm(20,mean=2,sd=1),5,3,8)
data <- data.frame(X1,X2)
#Lasketaan todennäköisyydet eri yhdistelmille
data$probability <- dnorm(data$X1,mean=5,sd=3)*dnorm(data$X2,mean=2,sd=1)*100
#Luokitellaan alle 0.15% arvon saavat outliereiksi
data$class_ <- ifelse(data$probability < 0.15,"Outlier","Normaali")
#interpoloidaan arvoja Contour-kuvaa varten
ipdata <- with(data, interp(x=X1, y=X2, z=probability))
ipdata2 <- expand.grid(x=ipdata$x, y=ipdata$y)
ipdata2$z <- as.vector(ipdata$z)
head(ipdata2)
#https://stackoverflow.com/questions/19065290/r-ggplot-stat-contour-not-able-to-generate-contour-lines
head(ipdata2)
ggplot(data, aes(x=X1, y=X2))+
stat_contour(data=na.omit(ipdata2), binwidth=1, colour="red", aes(x=x, y=y, z=z))+
geom_point(aes(color=class_),size=3)+
geom_text(aes(label=paste(round(probability,2),"%")),hjust=0, vjust=1.5)+
labs(title="Normaalijakautuneet muuttujat ja Anomaly detection (Cut-off-piste 0.15%)",color="Luokittelu")+
theme(plot.title = element_text(hjust = 0.5),legend.title=element_text(size=15),legend.text=element_text(size=12))
Esimerkki 3. Multivariate Gaussian: Useampi keskenään korreloitunut muuttuja.
Mitä jos muuttujat korreloivat keskenään? Yllä oletettiin, että muuttujat ovat toisistaan täysin riippumattomia. Todellisuudessa esim. transaktion suuruus ja kellon aika voivat korreloida (esim. yöajan ostokset eroavat luultavasti iltapäivän ostoksista). Ottamalla korrelaation pystymme mallintamaan dataa generoivaa prosessia paremmin, kun muuttujat korreloivat keskenään.
Luodaan data, jossa muuttujat korreloivat keskenään ja lasketaan eri malleilla todennäköisyydet tapahtumille. Lasketaan todennäköisyydet ensin riippumattomuus-oletuksen mallille.
set.seed(1)
X1 <- c(rnorm(20,mean=5,sd=3),11,15,16)
X2 <- c(rnorm(20,mean=7,sd=1),8,9,10)
cor(X1,X2)
data <- data.frame(X1,X2)
#Lasketaan todennäköisyydet eri yhdistelmille
data$probability <- dnorm(data$X1,mean=mean(X1),sd=sd(X1))*dnorm(data$X2,mean=mean(X2),sd=sd(X2))*100
#Luokitellaan alle 0.15% arvon saavat outliereiksi
data$class_ <- ifelse(data$probability < 0.15,"Outlier","Normaali")
#interpoloidaan arvoja Contour-kuvaa varten
ipdata <- with(data, interp(x=X1, y=X2, z=probability))
ipdata2 <- expand.grid(x=ipdata$x, y=ipdata$y)
ipdata2$z <- as.vector(ipdata$z)
#Luodaan "Riippumattomuus-oletuksella"-kuva
plot1 <- ggplot(data, aes(x=X1, y=X2))+
stat_contour(data=na.omit(ipdata2), binwidth=1, colour="red", aes(x=x, y=y, z=z))+
geom_point(aes(color=class_),size=3)+
geom_smooth(method="lm",se=F)+
geom_text(aes(label=paste(round(probability,2),"%")),hjust=0, vjust=1.5)+
labs(title="Riippumattomuus-oletuksella",color="Luokittelu")+
theme(legend.position = "none",plot.title = element_text(hjust = 0.5),legend.title=element_text(size=15),legend.text=element_text(size=12))
Lasketaan ennusteet seuraavaksi ottamalla muuttujien välinen korrelaatio huomioon estimoidussa tiheysfunktiossa.
data2 <- data[,1:2]
centered <- as.matrix(data.frame(lapply(data2[,1:2],function(x) x-mean(x))))
sigma2=(var(Xval_centered))
#Lasketaan estimoidusta tiheysfunktiosta todennäköisyydet
library(MASS)
a=(2*pi)^(-ncol(Xval_centered)/2)*det(sigma2)^(-0.5)
b= exp(-0.5 *rowSums((Xval_centered%*%ginv(sigma2))*Xval_centered))
prediction=a*b
data2$probability <- prediction*100
data2$class_ <- ifelse(data2$probability <0.15,"Outlier","Normaali")
ipdataz <- with(data2, interp(x=X1, y=X2, z=probability))
ipdata2z <- expand.grid(x=ipdataz$x, y=ipdataz$y)
ipdata2z$z <- as.vector(ipdataz$z)
#Tehdään kuva
plot2 <- ggplot(data2, aes(x=X1, y=X2))+
stat_contour(data=na.omit(ipdata2z), binwidth=1, colour="red", aes(x=x, y=y, z=z))+
geom_point(aes(color=class_),size=3)+
geom_smooth(method="lm",se=F)+
geom_text(aes(label=paste(round(probability,2),"%")),hjust=0, vjust=1.5)+
labs(title="Korrelaatio huomioitu",color="Luokittelu")+
theme(plot.title = element_text(hjust = 0.5),legend.title=element_text(size=15),legend.text=element_text(size=12))+
geom_segment(aes(x = 14, y = 9.5, xend = 14.7, yend = 9.1),size=1.5,
arrow = arrow(length = unit(0.5, "cm")))+
annotate(geom="text", x=12.1, y=9.6, label="Todennäköisyys kasvanut",
color="red",size=4)
library(gridExtra)
grid.arrange(plot1, plot2, ncol=2,widths=c(2,2.5))
Alla olevasta kuvasta (vasen) näemme aiemmin esitellyn mallin, jossa oletettiin, että muuttujat ovat toisistaan riippumattomia. Oikealla on samalle datalle sovitetun korrelaation huomioivan tiheysfunktion arvot. Sininen viiva on regressioviiva, joka näyttää muuttujien välisen positiivisen korrelaation (r=0.41). Näemme kuinka oikeassa reunassa oleva piste muuttuukin oulierista normaaliksi korrelaation huomioimisen johdosta. Tämä johtuu siitä, että vaikka piste on kaukana massasta, niin silti se on regressiosuoran suuntainen. Näemme myös, että oikean puoleisessa kuvassa alareunan arvot eivät saa enää niin suuria todennäköisyyksiä.
Havainnollistetaan kahden mallin eroja vielä simuloimalla kaksi voimakkaasti korreloivaa muuttujaa (r=-0.71). Oikean puoleisessa kuvassa otetaan korrelaatio huomioon ja näemme, miten isoviivat ovat lähes kiinni kiinni havannoissa. Vasemman puolimmaisessa kuvassa korrelaatiota ei oteta huomioon, joten isoviivat muodstavat pyöreän alueen havaintojen keskustan päälle.
Alla vielä kuva oikenpuoleisesta tilanteesta. Näemme, että kun keskustasta lähdetään kasvattamaan X1:stä ja samalla pienentämään X2:sta, niin pysymme pidempään korkeimmalla todennäköisyys-alueella. Tämä johtuu korrelaation huomioimisesta. Jos emme huomioi korrelaatiota, niin vastaavassa tilanteessa todennäköisyys laskee jyrkemmin.
Kumpi malleista on parempi?
Vaikka korrelaation huomioiva malli voi kuvastaa paremmin dataa generoivaa prosessia, niin oleellista on miettiä, mikä "outlier" on tilanteessa. On siis tärkeää, että pystymme mittaamaan mallien toimivuutta käytännössä, jotta emme jää vain teoreettiselle pohjalle. Käytännössä voimme määritellä outlierit, kuten tunnetut luottotilin petostapahtumat ja laskea todennäköisyydet näille tapahtumille. Voimme esim. tehdä mallinnuksen täysin puhtaalla datalla, jossa ei ole outliereita ja sen jälkeen sovittaa mallin dataan, jossa on seassa outliereita. Tästä datasetistä voimme tarkastella performanssia ja valita sopivan cut-off-pisteen, jolla outlierit saada tehokkaimmin kiinni. Lopuksi voimme kokeilla mallia täysin uuteen dataan ja katsoa, miten se performoi. Tärkeää cut-off-pistettä on käyttää Precision-Recall-metriikoita, jotta huomioimme myös kustannukset, jotka syntyvät väärin outliereiksi luokitelluista tapahtumista.
Tässä esitellyt anomaly detection -mallit soveltuvat datalle, jotka ovat normaalijakautuneet. Vaikka alkuperäinen data ei olisikaan normaalijakautunut, niin transformaatiot voivat auttaa (esim. log). Jos data ei ole normaalijakautunutta, niin kannattaa kokeille tekniikoita, joissa normaalijakauma-oletusta ei ole mukana. Kirjoittelen näistä tekniikoista jatkossa lisää.