Improving Learning Results

Motivation

Wenn das gelernte Modell nicht die gewünschten Eigenschaften hat (sprich schlechte Vorhersagen macht), kann das mehrere Ursachen haben:

  • unpassende Modellarchitektur
  • Overfitting
  • Underfitting
  • es gibt keinen Zusammenhang in den Daten, der gelernt werden könnte
  • zu wenig Trainingsdaten, um die Zusammenhänge zwischen Input- und Outputdaten zu erlernen

Folgende Stellschrauben können angepasst werden:

  • mehr Trainingsdaten
  • mehr Merkmale
  • weniger Merkmale
  • höherer / niedriger Regularisierungsparameter
  • andere Modellarchitektur

Testdaten

Ein Teil der Daten wird nicht zum Training des Modells eingesetzt, sondern dient ausschließlich zum Testen des trainierten Modells. Hierdurch kann Overfitting erkannt und korrigiert werden. Man minimiert die Kostenfunktion auf den Trainingsdaten und misst anschließend die Kosten der Testdaten.

Im folgenden verwenden wir die Testdaten aus dem Online-Kurs, die bereits in Trainings-, Test- und Validierungsdaten aufgeteilt sind.

# Load data: The data is already split into train, test and validation set.
load(file="ex5data1.Rda") 

list2env(data, env = .GlobalEnv)
## <environment: R_GlobalEnv>
rm(data)


plot(y~X, xlab="change in water level",ylab="Water flowing out of the dam",col="red",pch=4,lwd=2)

theta <- c(1,1)
lambda <-0
cost <- costFunctionLinReg(ones(X),y,lambda)(theta)
grad <- gradientLinReg(ones(X),y,lambda)(theta)
theta <- trainLinReg(ones(X), y, lambda)

plot(y~X,
       col="red",pch=4,
       xlab="x",ylab="y",
       main = "linear fit",
       sub= paste("theta0=", round(theta[1],digits=3),", theta1=", round(theta[2],digits=3), ", cost=",round(cost,digits=3))
  ) 
  abline(theta[1],theta[2],col="blue",lwd=2)
  abline(h=0,col="gray",lwd=1)
  abline(v=0,col="gray",lwd=1)

Die Ausgangskosten liegen ohne Regularisierung bei 303.9515256, der Ausgangsgradient bei -15.3030157, 598.1674108.

Der Linear Fit ist natürlich sehr schlecht, da die Daten offenbar nicht linear sind. Im folgenden werden Diagnoseverfahren implementiert, um die Adäquatheit trainierter Modelle zu analysieren.

Lernkurven als diagnostische Mittel

Im folgenden werden die Lernkurven erstellt, die die Kosten der Trainingsdaten und der Validierungsdaten in Abhängigkeit von der Größe des Trainingsdatensatzes zeigen. Hierzu wird der Trainingsdatensatz schrittweise (ausgehend von einem einzigen Trainingsdatum) vergrößert. Für jeden Schritt wird der Trainingsfehler für die aktuelle Trainingsdatengröße und der Validierungsfehler auf den gesamten Validierungsdaten berechnet. Bei der Berechnung des Trainings- und Validierungsfehlers wird auf die Regularisierung verzichtet (\(\lambda=0\)).

lambda <- 0
m <- dim(X)[1]
lC <- learningCurve(ones(X),y,ones(Xval),yval,lambda)
plot(c(1:m,1:m),c(lC$errorTrain,lC$errorVal),type="n",xlab="number of training data", ylab="error",main="learning curve",sub="blue: training error, violet: validation error")
lines(1:m,lC$errorTrain,col="blue",type="l",lwd=2)
lines(1:m,lC$errorVal,col="violet",type="l",lwd=2)

Der hohe Fehler am Ende des Trainings weist auf ein hohes Maß an Underfitting hin (oder auf zu wenige Trainingsdaten).

Hinzunahme von polynomialen Merkmalen und Bestimmung des optimalen Grads

Wir versuchen zunächst die Daten mit einem Polyonom 8. Grades zu modellieren

d <- 8
lambda <- 0
Xpoly <- ones(polyFeatures(X,d))
fN <- featureNormalization(Xpoly)
Xpoly <- fN$nv
mu <- fN$mu
sigma <- fN$sigma

# plotting the polynomial fit
theta <- trainLinReg(Xpoly,y,lambda)
xvalues <- seq(min(X)-20, max(X)+20,0.05)
Xinput <- ones(polyFeatures(matrix(xvalues,ncol=1),d))
plot(xvalues,h(theta,normalizeFixed(Xinput,mu,sigma)),
     col="blue",lwd=1,type="l",
     xlab="change in water level",ylab="Water flowing out of the dam")
points(X,y,col="red",lwd=2,pch=4)

Xvalpoly <- ones(polyFeatures(Xval,d))
Xvalpoly <- normalizeFixed(Xvalpoly,mu,sigma)
lambda <- 0
m <- dim(X)[1]
lC <- learningCurve(Xpoly,y,Xvalpoly,yval,lambda)

plot(c(1:m,1:m),c(lC$errorTrain,lC$errorVal),type="n",xlab="number of training data", ylab="error",main="learning curve",sub="blue: training error, violet: validation error")
lines(1:m,lC$errorTrain,col="blue",type="l",lwd=2)
lines(1:m,lC$errorVal,col="violet",type="l",lwd=2)

Der Trainingsfehler ist nun von Anfang an sehr klein. Der Fehler auf den Validierungsdaten nähert sich dem Trainingsfehler zunächst an, steigt dann aber wieder. Dies weist auf Overfitting hin.

Mit Regularisierung lässt sich das Problem verringern:

lambda <- 1
# plotting the polynomial fit
theta <- trainLinReg(Xpoly,y,lambda)
xvalues <- seq(min(X)-20, max(X)+20,0.05)
Xinput <- ones(polyFeatures(matrix(xvalues,ncol=1),d))
plot(xvalues,h(theta,normalizeFixed(Xinput,mu,sigma)),
     col="blue",lwd=1,type="l",
     xlab="change in water level",ylab="Water flowing out of the dam")
points(X,y,col="red",lwd=2,pch=4)

Xvalpoly <- ones(polyFeatures(Xval,d))
Xvalpoly <- normalizeFixed(Xvalpoly,mu,sigma)
m <- dim(X)[1]
lC <- learningCurve(Xpoly,y,Xvalpoly,yval,lambda)

plot(c(1:m,1:m),c(lC$errorTrain,lC$errorVal),type="n",xlab="number of training data", ylab="error",main="learning curve",sub="blue: training error, violet: cross validation error")
lines(1:m,lC$errorTrain,col="blue",type="l",lwd=2)
lines(1:m,lC$errorVal,col="violet",type="l",lwd=2)

Der Regularisierungsparameter wurde hier auf lambda = 1 gesetzt.

Aber wie findet man einen geeigneten Lambda-Wert?

Wahl eines geeigneten Regularisierungsparameters mithilfe von Cross Validation

Wir plotten den Trainingsfehler und den Fehler der Validierungsdaten auf die unterschiedlichen Lambda-Werte:

lambda <- 0
lambdavec <- c(lambda)
theta <- trainLinReg(Xpoly,y,lambda)
trainCost <- costFunctionLinReg(Xpoly,y,0)(theta)
valCost <- costFunctionLinReg(Xvalpoly,yval,0)(theta)
trainvec <- c(trainCost)
valvec <- c(valCost)
lambda <- 0.001
j <- 25
for (i in 1:(j-1)){
  lambdavec <- c(lambdavec,lambda)
  theta <- trainLinReg(Xpoly,y,lambda)
  trainCost <- costFunctionLinReg(Xpoly,y,0)(theta)
  valCost <- costFunctionLinReg(Xvalpoly,yval,0)(theta)
  trainvec <- c(trainvec,trainCost)
  valvec <- c(valvec,valCost)
  lambda <- 1.7*lambda
}
plot(c(1:j,1:j),c(trainvec,valvec),type="n",xlab="lambda",ylab="error", sub="blue: training error, violet: cross validation error",main="Validation of lambda",xaxt="n")
axis(1,at=c(1:j),labels=round(lambdavec,digits=3),las=2)
lines(1:j,trainvec,col="blue",type="l",lwd=2)
lines(1:j,valvec,col="violet",type="l",lwd=2)

Wir sehen, dass der beste Wert für Lambda bei ca. 3 liegt. Für diesen Wert berechnen wir jetzt den Fehler auf den Testdaten und plotten zusätzlich den polynomialen Fit.

lambda <- 3
theta <- trainLinReg(Xpoly,y,lambda)
Xtestpoly <- normalizeFixed(ones(polyFeatures(Xtest,d)),mu,sigma)
testCost <- costFunctionLinReg(Xtestpoly,ytest,0)(theta)

Der Fehler für die Testdaten beläuft sich auf 3.858286.

d <- 8
Xpoly <- ones(polyFeatures(X,d))
fN <- featureNormalization(Xpoly)
Xpoly <- fN$nv
mu <- fN$mu
sigma <- fN$sigma
lambda <- 3

# plotting the polynomial fit
theta <- trainLinReg(Xpoly,y,lambda)
xvalues <- seq(min(X)-20, max(X)+20,0.05)
Xinput <- ones(polyFeatures(matrix(xvalues,ncol=1),d))
plot(xvalues,h(theta,normalizeFixed(Xinput,mu,sigma)),
     col="blue",lwd=1,type="l",
     xlab="change in water level",ylab="Water flowing out of the dam")
points(X,y,col="red",lwd=2,pch=4)

Wahl eines geeigneten Funktionsgrad mithilfe von Cross Validation

Statt den Regularisierungsfaktor anzupassen, kann man auch den Grad der polyonmialen Funktion anpassen.

dvec <- c()
trainvec <- c()
valvec <- c()
lambda <- 0
d <- 1
j <- 15
for (i in 1:j){
  fN <-  featureNormalization(ones(polyFeatures(X,d)))
  Xpoly <- fN$nv
  mu <- fN$mu
  sigma <- fN$sigma
  Xvalpoly <-  normalizeFixed(ones(polyFeatures(Xval,d)),mu,sigma)
  theta <- trainLinReg(Xpoly,y,lambda)
  trainCost <- costFunctionLinReg(Xpoly,y,0)(theta)
  valCost <- costFunctionLinReg(Xvalpoly,yval,0)(theta)
  dvec <- c(dvec,d)
  trainvec <- c(trainvec,trainCost)
  valvec <- c(valvec,valCost)
  d <- d+1
}
plot(c(1:j,1:j),c(trainvec,valvec),type="n",xlab="degree",ylab="error", sub="blue: training error, violet: cross validation error",main="Validation of degree",xaxt="n")
axis(1,at=c(1:j))
lines(1:j,trainvec,col="blue",type="l",lwd=2)
lines(1:j,valvec,col="violet",type="l",lwd=2)

Es zeigt sich, dass 3 ein recht guter Funktionsgrad ist.

d <- 3
Xpoly <- ones(polyFeatures(X,d))
fN <- featureNormalization(Xpoly)
Xpoly <- fN$nv
mu <- fN$mu
sigma <- fN$sigma

# plotting the polynomial fit
theta <- trainLinReg(Xpoly,y,lambda)
xvalues <- seq(min(X)-20, max(X)+20,0.05)
Xinput <- ones(polyFeatures(matrix(xvalues,ncol=1),d))
plot(xvalues,h(theta,normalizeFixed(Xinput,mu,sigma)),
     col="blue",lwd=1,type="l",
     xlab="change in water level",ylab="Water flowing out of the dam")
points(X,y,col="red",lwd=2,pch=4)

lambda <- 0
d <- 3
Xpoly <- ones(polyFeatures(X,d))
fN <- featureNormalization(Xpoly)
Xpoly <- fN$nv
mu <- fN$mu
sigma <- fN$sigma
theta <- trainLinReg(Xpoly,y,lambda)
Xtestpoly <- normalizeFixed(ones(polyFeatures(Xtest,d)),mu,sigma)
testCost <- costFunctionLinReg(Xtestpoly,ytest,0)(theta)

Der Fehler für die Testdaten beläuft sich auf 5.4958179.

Learning curves with iteratively selected examples

Für die Berechnung der Lernkurven wird das Modell zunächst nur über einen kleinen Teil der Trainingsdaten trainiert, der dann nach und nach ansteigt. Um die Schwankungen des Modells auszugleichen, die auf der zufälligen Wahl des kleinen Teildatensatzes beruhen, werden pro Teilstufe mehrere Durchläufe durchgeführt und die berechneten Fehlerdaten anschließend gemittelt.

Wir hatten 3 als einen optimalen Funktionsgrad ermittelt, wenn wir nicht regularisieren. Wir setzen den Funktionsgrad etwas höher an (\(d=5\)), da wir ihn durch Regularisierung ausgleichen können und bestimmen zunächst die Lernkurve ohne Regularisierung

d <- 5
lambda <- 0
Xpoly <- ones(polyFeatures(X,d))
fN <- featureNormalization(Xpoly)
Xpoly <- fN$nv
mu <- fN$mu
sigma <- fN$sigma

Xvalpoly <- ones(polyFeatures(Xval,d))
Xvalpoly <- normalizeFixed(Xvalpoly,mu,sigma)
lambda <- 0
m <- dim(X)[1]
lC <- learningCurveIter(Xpoly,y,Xvalpoly,yval,lambda,30)

plot(c(1:m,1:m),c(lC$errorTrain,lC$errorVal),type="n",xlab="number of training data", ylab="error",main="learning curve",sub="blue: training error, violet: validation error")
lines(1:m,lC$errorTrain,col="blue",type="l",lwd=2)
lines(1:m,lC$errorVal,col="violet",type="l",lwd=2)

Als nächstes ermitteln wir einen guten Lambda-Wert

d <- 5
lambdaInitial=0.001
lambdaFactor=1.7
lambdaSteps=25
lamCurve <- lambdaCurve(lambdaInitial,lambdaFactor,lambdaSteps,X,y,Xval,yval,d)
lambdavec <- lamCurve$lambdavec
trainvec <- lamCurve$trainvec
valvec <- lamCurve$valvec
plot(c(1:lambdaSteps,1:lambdaSteps),c(trainvec,valvec),type="n",xlab="lambda",ylab="error", sub="blue: training error, violet: cross validation error",main="Validation of lambda",xaxt="n")
axis(1,at=c(1:lambdaSteps),labels=round(lambdavec,digits=3),las=2)
lines(1:lambdaSteps,trainvec,col="blue",type="l",lwd=2)
lines(1:lambdaSteps,valvec,col="violet",type="l",lwd=2)

Der optimale lambda-Wert liegt bei 1.6837783.

Für diesen Wert betrachten wir nun den Linear Fit, den Testfehler und die Lernkurve:

d <- 5
lambda <- min(lambdavec)
poly <- polyPrepare(X,Xval,Xtest,d)
Xpoly <- poly$Xpoly
Xvalpoly <- poly$Xvalpoly
Xtestpoly <- poly$Xtestpoly

theta <- trainLinReg(Xpoly,y,lambda)
xvalues <- seq(min(X)-20, max(X)+20,0.05)
Xinput <- ones(polyFeatures(matrix(xvalues,ncol=1),d))
plot(xvalues,h(theta,normalizeFixed(Xinput,mu,sigma)),
     col="blue",lwd=1,type="l",
     xlab="change in water level",ylab="Water flowing out of the dam")
points(X,y,col="red",lwd=2,pch=4)

errortest <- costFunctionLinReg(Xtestpoly,ytest,0)(theta)
print(paste("\n Der Testfehler beträgt", errortest,"\n"))
## [1] "\n Der Testfehler beträgt 13.9727511689327 \n"
lC <- learningCurveIter(Xpoly,y,Xvalpoly,yval,lambda,30)

m <- dim(X)[1]
plot(c(1:m,1:m),c(lC$errorTrain,lC$errorVal),type="n",xlab="number of training data", ylab="error",main="learning curve",sub="blue: training error, violet: validation error")
lines(1:m,lC$errorTrain,col="blue",type="l",lwd=2)
lines(1:m,lC$errorVal,col="violet",type="l",lwd=2)