Manual Regression
손수 회귀곡선을 그리는 방법
genData=function(n=1000) {
#n <- 1000
x <- runif(n, -3, 3)
e <- rnorm(n, 0, 1.1)
y <- -0.3*x^2 + 1.4*x+ e
return(data.frame(x=x, y=y))
}
dat <- genData(n=1000)
plot(y~x, data=dat)
사람들은 여러 가지 ML/AI가 뭔가 신비스로운 힘이 있다고 생각한다. 하지만 적어도 지도 학습(supervised learnign)의 회귀(regression)는 (간단하게 정리하면) 데이터의 크기와 적절한 사전 지식의 결합으로 설명할 수 있다.
예를 들어 위의 데이터를 보자. \(x\) 가 주어졌을 때, \(y\) 를 예측하려고 한다면, 가장 손쉬운 방법은 데이터에서 \(x\) 주위의 \(y\) 의 대표값을 계산하는 것이다.
예를 들어 \(x=1\) 에서 \(y\) 를 예측하고자 한다면, \(0 < x < 1\) 인 데이터에 대해 \(y\) 의 평균값을 제시하는 방법을 사용할 수 있다.
predict2 <- function(dat, x, binsize=1) {
predy <- rep(NA, length(x))
for (i in seq_along(x)) {
x0 <- x[i]
xs <- dat[, "x"]
ys <- dat[, "y"]
ys <- ys[xs > x0-binsize & xs < x0+binsize]
predy[i] <- mean(ys, na.rm=TRUE)
}
return(predy)
}
xs <- seq(-3,3,0.1)
datGG <- data.frame(x=xs, y=predict2(dat, x=xs, binsize=1))
ggplot(dat, aes(x=x, y=y)) +
geom_point(fill='grey70', col='grey20', alpha=0.2) +
geom_line(data=datGG, aes(x=x, y=y), size=1.1)
결과는 위와 같다. 물론 사람이 직접 계산하려면 시간이 많이 걸리겠지만, 시간만 있다면 손수 계산할 수 있다.
이때 \(x = 1\) 의 예측값을 구하기 위해서 사용하는 데이터의 \(x\) 범위와 \(y\) 예측값을 구할 때 사용하는 방법(평균, 중앙값, 그 밖의 함수들)에 따라 온갖 회귀 모형(Kernel Regression 등)이 만들어 질 수 있지만 핵심은 비슷하다.
예를 들어 \(x=1\) 의 예측값을 구하기 위해 \(-1 < x < 3\) 를 사용하거나,
datGG <- data.frame(x=xs, y=predict2(dat, x=xs, binsize=2))
ggplot(dat, aes(x=x, y=y)) +
geom_point(fill='grey70', col='grey20', alpha=0.2) +
geom_line(data=datGG, aes(x=x, y=y), size=1.1)
\(0.5 < x < 1.5\) 를 사용할 수 있다.
datGG <- data.frame(x=xs, y=predict2(dat, x=xs, binsize=0.5))
ggplot(dat, aes(x=x, y=y)) +
geom_point(fill='grey70', col='grey20', alpha=0.2) +
geom_line(data=datGG, aes(x=x, y=y), size=1.1)
(이 결과를 보면 손쉽게 \(y\) 예측값을 구하기 위해 사용하는 \(x\) 의 범위를 \(f(x)\) 의 기울기에 따라 조절해야겠다는 생각이 떠오르지 않는가?)
이렇게 보면, 대부분의 AI/ML은 어처구니없이 간단하다(물론 개념적으로 간단하다는 얘기이다.) (이런 이유로 Pearl은 대부분의 ML/AI가 단순히 Curve Fitting일 뿐이라고 비하한다. 심리학자들이 fMRI 연구를 뇌 사진 찍기에 불과하다고 비아냥거리던 것과 비슷한 듯?)
이 방법은 여러 입력 변수에 대해서도 적용 가능하다. 물론 변수의 갯수가 늘어날 수록 데이터의 갯수는 굉장히 늘어나기 때문에 사전 지식을 활용하게 된다. 이때 사전지식은 보통 모형(혹은 베이지안에서 사전 분포)으로 나타난다.
입력변수일 때 예시.
genData2=function(n=1000) {
#n <- 1000
x1 <- runif(n, -3, 3)
x2 <- runif(n, -3, 3)
e <- rnorm(n, 0, 1.1)
y <- -0.3*x1^2 + 2*sin(x2)*x1 + 1.4*x1+ e
return(data.frame(x1=x1, x2=x2, y=y))
}
library(plot3D)
dat <- genData2(n=1000)
scatter3D(x=dat$x1, y=dat$x2, z=dat$y)
#install.packages('plot3Drgl')
library(plot3Drgl)
#scatter3Drgl(x=dat$x1, y=dat$x2, z=dat$y)
만약 두 변수 \(x_1\) , \(x_2\) 로 \(y\) 를 예측한다면, 예측하고자 하는 \(x_1\) , \(x_2\) 주변 데이터에서 \(y\) 의 대표값을 구하면 된다. 예를 들어, \(x_1 = 1\) , \(x_2 = 1\) 일때 \(y\) 를 예측한다면, \(0 < x_1 < 2\) 이고 \(0 < x_2 < 2\) 인 데이터에 대해 \(y\) 를 평균하는 방법을 생각해 볼 수 있다.
predict3 <- function(dat, x1, x2, binsize=1) {
stopifnot(length(x1)==length(x2))
predy <- rep(NA, length(x1))
for (i in seq_along(x1)) {
x10 <- x1[i]
x20 <- x2[i]
x1s <- dat[, "x1"]
x2s <- dat[, "x2"]
ys <- dat[, "y"]
ys <- ys[x1s > x10-binsize & x1s < x10+binsize &
x2s > x20-binsize & x2s < x20+binsize]
predy[i] <- mean(ys, na.rm=TRUE)
}
return(predy)
}
datp <- expand.grid(x1=seq(-3,3,0.1), x2=seq(-3,3,0.1))
datp$y <- predict3(dat, x1=datp$x1, x2=datp$x2, binsize=1)
scatter3D(x=datp$x1, y=datp$x2, z=datp$y)
#scatter3Drgl(x=datp$x1, y=datp$x2, z=datp$y)
datp$y <- predict3(dat, x1=datp$x1, x2=datp$x2, binsize=2)
scatter3D(x=datp$x1, y=datp$x2, z=datp$y)
#scatter3Drgl(x=datp$x1, y=datp$x2, z=datp$y)
불확실성
하지만 이런 단순한 방법의 또 한가지 문제는 이 결과가 얼마나 정확한지 알 수 없다는 점이다.
예를 들어 100개의 데이터를 사용해서 회귀선을 구성할 수도 있고, 1000개의 데이터를 써서 회귀선을 구성할 수도 있다. 하지만 이 둘이 정확성에서, 또는 신빙성에서 얼마나 차이가 나는지, 사람들은 알기 힘들다. 단지 직관적으로 추측할 수 있을 뿐이다. (하지만 사람마다 이 추측이 다르다는 문제가 있다.)
dat <- genData(n=100)
xs <- seq(-3,3,0.1)
datGG <- data.frame(x=xs, y=predict2(dat, x=xs, binsize=1))
ggplot(dat, aes(x=x, y=y)) +
geom_point(col='grey20', alpha=0.2) +
geom_line(data=datGG, aes(x=x, y=y), size=1.1)
dat <- genData(n=10000)
xs <- seq(-3,3,0.1)
datGG <- data.frame(x=xs, y=predict2(dat, x=xs, binsize=1))
ggplot(dat, aes(x=x, y=y)) +
geom_point(col='grey20', alpha=0.2) +
geom_line(data=datGG, aes(x=x, y=y), size=1.1)
(이 결과를 보니 \(y\) 예측값을 나타내기 위해 사용하는 \(x\) 의 범위를 표본크기에 따라 적절히 조절해야겠다는 아이디어가 떠오르지 않는가? 표본크기가 작을 수록, 그리고 사용된 \(x\) 의 범위가 좁을 수록 분산이 커지니, 둘 중 하나는 늘려야 적당한 분산에 적당한 편향을 얻을 수 있다.)
불확실성을 대처하는 방법
확률모형은 이런 불확실성을 기본적인 확률 법칙을 사용하여 계산 가능하게 해준다. 확률모형과 확률법칙을 통해 우리가 관심이 있는 대상(예. 예측 함수)에 대한 불확실성(확률)을 계산할 수 있다.
dat <- genData(n=100)
library(visreg) # install.packages('visreg')
fitLM <- lm(y~x+I(x^2), data=dat)
visreg(fitLM, 'x')
dat <- genData(n=20)
library(visreg) # install.packages('visreg')
fitLM <- lm(y~x+I(x^2), data=dat)
visreg(fitLM, 'x')
위의 그래프(특히 신뢰구간)는 만약 모형이 정확하다면, 단지 20개의 자료만으로도 \(f(x)\) 를 꽤나 정확하게 구성할 수 있음을 보여준다.[2]
[2]: 편의상 간단하게 말하자면, \(f(x)\) 가 \(a x^2 + bx+c\) 이고, 오차가 정규분포를 따르고, 등분산이고, 오차자기상관이 없으며… 물론 정확성의 기준도 정해야 겠지만…
글을 쓰고 보니, 의사결정나무와 같은 알고리즘적 방법을 까는 글이 된 듯 하지만, 의사결정나무도 CV(Cross Validation; 교차검증)나, 붓스트랩을 사용하여, 최종모형의 불확실성을 측정할 수 있다.
Leave a comment