maanantai 25. huhtikuuta 2016

Machine learning: Blending

Esittelen nyt tehokkaan koneoppimisen menetelmän, jolla mallien ennusteita voidaan parantaan. Menetelmää kutsutaan nimellä "Blending". Ideana on yhdistää usean mallin ennusteet ja näin saavuttaa parempi ennustustarkkuus. Voimme siis yhdistää esim. logistisesta regressiosta ja random forestista saamamme ennusteet ja näin saavuttaa paremmat ennusteet. Blending toimii parhaiten, jos eri mallit pystyvät edes kohtalaisiin ennusteisiin (AUC>0.5) ja niiden välinen korrelaatio ei ole suuri. Jos yhdistämme kaksi mallia, jotka korreloivat vahvasti, niin emme saa uutta tietoa, vaan uusi ennuste vastaa lähes kokonaan yksittäisen mallin ennustetta.  Mikäli mallit eivät korreloi vahvasti, niin parhaimmillaan yhden mallin oikeat ennusteet dominoivat, kun toinen malli on väärässä ja näin saadaan aikaan parempia ennusteita. Blending ei aina paranna mallien ennusteita, mutta se ei kuitenkaan yleensä huononna ennusteita verrattuna yksittäisiin malleihin, joten sitä kannattaa kokeilla. Lisätietoa löytyy mm. täältä

Esimerkki, kun blending parantaa ennustustarkkuutta: Logistisen mallin ennusteet ovat kolmen lainanhakijan maksuhäiriöiden todennäköisyyksistä seuraavat:

Hakija1: 0.09, Hakija2: 0.05, Hakija3: 0.045.

Vastaavat random forestista saadut todennäköisyydet ovat:

Hakija1: 0.08, Hakija2: 0.09,  Hakija3: 0.1

Kuvitellaan, että todellisuudessa Hakijat 1 ja 3 saivat maksuhäiriön. Logistinen malli ennusti siis hakijat maksuhäiriön todennäköisyyden mukaisesti suuruusjärjestykseen seuraavasti: Hakija1, Hakija2, Hakija3. Random forestilla vastaava järjestys oli: Hakija3, Hakija2, Hakija1. Oikea järjestys olisi ollut Hakija1, Hakija3, Hakija2, eli hakijoiden 1 ja 3 olisi pitänyt saada suuremmat maksuhäiriön todennäköisyydet kuin toisen hakijan. Yhdistämällä mallit seuraavasti saamme optimaalisen mallin: 0.5* Logistisen mallin ennusteet + 0.5* Random forestin ennusteet. Nyt hakijoiden maksuhäiriöiden todennäköisyydet ovat: Hakija1: 0.085, Hakija2: 0.07, Hakija3: 0.0725. Saimme yhdistämällä mallin, joka on parempi kuin yksittäiset mallit.

Kokeilen seuraavaksi kolmen eri mallin yhdistämistä. Muodostan mallit jo tutusta luottodatasta (GermanCreditData). Malleina käytän logistista regressiota, random forestia ja support vector machinea.

R-koodi:

data <- read.delim(file.choose(), header=FALSE,sep="") # Avataan data ja nimetään muuttujat
names(data) <- c("check_acc", "duration", "history", "purpose", "amount", "sav_acc", "employment", "install_rate", "ptatus", "other_debtor", "resid", "property","age", "other_install", "housing", "other_credits", "job", "num_depend", "teleph", "foreign", "default")
data$install_rate <- as.factor(data$install_rate)
data$resid <- as.factor(data$resid)
data$other_credits <- as.factor(data$other_credits)
data$num_depend <- as.factor(data$num_depend)
data$default <- as.factor(ifelse(data$default==1,"0","1"))
data$amount <- as.factor(ifelse(trainingdata$amount<=2500,"2500",ifelse(data$amount<=5000,'2600-5000','5000+')))
data$duration <- as.factor(ifelse(trainingdata$duration <= 12,"1-12",ifelse(datadata$duration <=24,"13-24","24+")))

data$age <- as.factor(ifelse(testdata$age <= 30,"0-30",ifelse(data$age <=40,"31-40","40+")))
d <- sort(sample(nrow(data),0.6*nrow(data)))  # Muodostetaan training- ja testdata
trainingdata <- data[d,]
testdata <- data[-d,]
model1 <- glm(default~check_acc+duration+history+purpose+  # Tehdään logistinen malli
amount+
sav_acc+
employment+
install_rate+
ptatus+
other_debtor+
resid+
property+
age+
other_install+
housing+
other_credits+
job+
num_depend+
teleph+
foreign,family=binomial(link=logit),data=trainingdata) 
testdata$log <- predict(model1, testdata,type="response") # Muodostetaan mallin ennusteet
m1.scores <- prediction(testdata$log, testdata$default)
performance(m1.scores,"auc") # AUC 0.773

library(randomForest)  # Muodostetaan random forest -malli
model2 <- randomForest(default~check_acc+duration+history+purpose+
amount+
sav_acc+
employment+
install_rate+
ptatus+
other_debtor+
resid+
property+
age+
other_install+
housing+
other_credits+
job+
num_depend+
teleph+
foreign, ntree=1000, data=trainingdata)
testdata$rf <- predict(model2, testdata,type="prob")[,2]  # Muodostetaan mallin ennusteet
m2.scores <- prediction(testdata$rf, testdata$default)
performance(m2.scores,"auc") # AUC 0.794

library(kernlab)
model3 <- ksvm(default~check_acc+duration+history+purpose+ # SVM-malli
amount+
sav_acc+
employment+
install_rate+
ptatus+
other_debtor+
resid+
property+
age+
other_install+
housing+
other_credits+
job+
num_depend+
teleph+
foreign, data=trainingdata,
cross=5,type='eps-svr' )
svm <- predict(mode13, testdata,type="response") # Muodostetaan mallin ennusteet
logsvm <- glm(testdata$default~svm,family=binomial) #Huom. SVM ei anna todennäköisyyksiä suoraan, joten ne ratkaistaan erikseen logistisella mallilla.
testdata$svm <- predict(logsvm, testdata,type="response")
m3.scores <- prediction(testdata$svm, testdata$default)
performance(m3.scores,"auc") # AUC 0.778


Random forest ennusti parhaiten testidataan (AUC: 0.794). Kokeillaan parantaako mallien keskiarvottaminen ennusteita.

R-koodi:

preyhd <- (testdata$m1.log+testdata$rf+testdata$svm)/3
m4.scores <- prediction(preyhd, testdata$default)
performance(m4.scores,"auc") # AUC 0.796

Mallien yhdistäminen paransi hieman AUC-arvoa. Tehtävänä on seuraavaksi löytää optimaaliset painot ennusteille, eli voimme esim. painottaa random forestin ennusteita enemmän. Painojen löytämiseen voi käyttää esim. meta-luokittelijaa, jolloin puhutaan ns. stacking:sta. Tällöin käytämme esim. logistista regressiomallia apuna oikeiden painojen löytämiseen. Idea on laittaa mallien ennusteet mallin selittäviksi tekijöiksi ja muodostaa näiden kertoimista painot.

Esittelen nyt menetelmän, jolla testidatan AUC-arvo voidaan maksimoida. Täytyy tietysti muistaa, ettei tälle testidatalle optimaalisen AUC:n antavat painot välttämättä toimi parhaiten uudelle datalle. Saamme kuitenkin arvokasta tietoa oikeiden painojen suunnasta.


install.packages("Metrics") 
library("Metrics")
cols <- c("m1.log","rf","svm")
fn.opt.pred <- function(pars, data) {
    pars.m <- matrix(rep(pars,each=nrow(data)),nrow=nrow(data))
    rowSums(data*pars.m)
}
fn.opt <- function(pars) {
    -auc(testdata$default, fn.opt.pred(pars, testdata[,cols]))
}
pars <- rep(1/length(cols),length(cols))
opt.result <- optim(pars, fn.opt, control = list(trace = T))

test.pred <- fn.opt.pred(opt.result$par, testdata[,cols])


print(opt.result$par)

# Saadaan optimaaliset painot: 0.3890623 0.8060392 0.1767558

Näillä painoilla saamme AUC-arvoksi 0.799. On hyvä huomata, että random forest saa suurimman painon. Random forest oli alunperin malleista ennusteissaan paras, joten parhaalle mallille on luontevaa antaa suurin paino malleja yhdistellessä.  

Ei kommentteja:

Lähetä kommentti