Yerel Ağırlıklı Öğrenme

Yerel Ağırlıklı Öğrenme

1 Yerel Ağırlıklı Öğrenme

Üzerinde tahmin yapılacak olan noktaya query (sorgu) noktası diyelim. Burada amaç, query noktasına yakın olan bölgelere ağırlık vererek, uzak olan bölgelerin ağırlığını azaltmaktır. Bu şekilde her query notkası için farklı bir ağırlıklı model kurulur ve daha esnek yapıları tespit etmek mümkün olur. Yerel ağırlıklı regresyon ve yerel ağırlıklı sınıflama olmak üzere iki türü vardır.

1.1 Ağırlıklı öğrenme

Yerel ağırlıklı öğrenmeyi anayabilmek için öncelikle ağırlıklı öğrenmenin ne olduğunu anlayalım. Burada ağırlık ile kastedilen, örnek ağırlığıdır. Her bir veri setinin ağırlığı, diğerlerinin ağırlığına kıyasla ne kadar önem sarfettiğini belirtir. Ağırlıklar dikkate alınmadığında, tüm örneklere ait ağırlıkların birbirine eşit olduğunu varsayarız. Bahsi geçen ağırlıklara göre parametre tahminleri ve amaç fonksiyonu ağırlıklı bir şekilde hesaplanır. Modeller ağırlıklı parametrelere göre şekillenir.

\(X\) açıklayıcı değişkenlerin matrisi, \(Y\) bağımlı değişken ve \(\beta\) katsayılar vektörü olsun. \(\epsilon =Y - \hat{Y}=Y-X\hat{\beta}\) hatayı ifade etsin. O halde ağırlıklı hata kareler (Weighted sum of squared errors, WSSE)

\[ WSSE = \sum_{i=1}^n W\epsilon ^2 \]

şeklindedir. \(\hat{\beta}\) parametre tahminleri, türevler sıfıra eşitlenerek hesaplanabilir. Süreç, ağırlıksız en küçük kareler yöntemi ile aynıdır.

\[ \mathbf{\hat{\beta}} = (\mathbf{X}^{\intercal}\mathbf{W}\mathbf{X})^{-1}\mathbf{X}^{\intercal}\mathbf{W}\mathbf{Y} \]

eşitliği ile ağırlıklı katsayılar hesaplanabilir.

Bir örnek basit regresyon modeli kurarak bunu gösterelim. Önce veri simülasyonu:

set.seed(1)
n <- 100
x <- rnorm(n = n, mean = 5, sd = 1.5)
eps <- rnorm(n = n, mean = 0, sd = 2)

y <- 3 + 2*x + eps
## güzel renkler için
library(ggplot2)
## Registered S3 methods overwritten by 'tibble':
##   method     from  
##   format.tbl pillar
##   print.tbl  pillar
library(ggsci)

ggplot() +
  geom_point(mapping = aes(x = x, y = y), shape = 1) +
  theme_bw()

Ağırlıksız ve ağırlıklı modelleri kuralım. Ağırlıkları da kafama göre oluştruacağım.

## Ağırlıksız model
model_lm_agirliksiz <- lm(y~x)
## katsayılar
katsayilar_lm_agirliksiz <- model_lm_agirliksiz$coefficients
print(katsayilar_lm_agirliksiz)
## (Intercept)           x 
##    2.931684    1.998586
## Ağırlıklar. Kafama göre bir ağırlık patterni oluşturacağım.
w <- rep(1, n)
w[(y/x)>median(y/x)] <- w[(y/x)>median(y/x)]*runif(n = sum((y/x)>median(y/x)), min = 0, max = 0.1)
w[y>15] <- w[y>15]*runif(n = sum(y>15), min = 2, max = 3)
w[y<8] <- w[y<8]*runif(n = sum(y<8), min = 0, max = 0.2)

## Ağırlıklı model
model_lm_agirlikli <- lm(y~x, weights = w)

## katsayılar
katsayilar_lm_agirlikli <- model_lm_agirlikli$coefficients
print(katsayilar_lm_agirlikli)
## (Intercept)           x 
##   0.5811147   2.2348140
## katsayıları elle hesaplayalım
X <- as.matrix(cbind(1,x))

## ağırlıksız katsayılar
katsayilar_elle_agirliksiz <- solve(t(X)%*%X)%*%t(X)%*%y

## ağırlıklı katsayılar
katsayilar_elle_agirlikli <- solve(t(X)%*%(matrix(rep(w,2),ncol = 2)*X))%*%t(X)%*%(w*y)

## sonuçları karşılaştıralım
print(data.frame(katsayilar_lm_agirliksiz,
           katsayilar_elle_agirliksiz,
           katsayilar_lm_agirlikli,
           katsayilar_elle_agirlikli))
##             katsayilar_lm_agirliksiz katsayilar_elle_agirliksiz
## (Intercept)                 2.931684                   2.931684
## x                           1.998586                   1.998586
##             katsayilar_lm_agirlikli katsayilar_elle_agirlikli
## (Intercept)               0.5811147                 0.5811147
## x                         2.2348140                 2.2348140

Elde ettiğimiz modellere ait eğrileri ve örnek ağırlıklarını bir grafikle gösterelim.

res <- 1000
grid <- seq(min(x) - 1, max(x) + 1, length.out = res)
tahmin_lm_agirliksiz <- as.matrix(cbind(1,grid))%*%model_lm_agirliksiz$coefficients
tahmin_lm_agirlikli <- as.matrix(cbind(1,grid))%*%model_lm_agirlikli$coefficients

tbl <- data.frame(yontem = rep(c("Ağırlıksız lm", "Ağırlıklı lm"), each = res),
                  tahmin = c(tahmin_lm_agirliksiz, tahmin_lm_agirlikli),
                  grid = rep(grid, 2))

ggplot() +
  geom_point(mapping = aes(x = x, y = y, size = w), shape = 1, show.legend = FALSE) +
  geom_line(mapping = aes(x = tbl$grid, tbl$tahmin, color = tbl$yontem)) +
  scale_color_d3(name = "Yöntem") +
  theme_bw()

Diyelim ki en küçük kareler doğrusal regresyonundan farlı bir regresyon türü kullanmak istiyoruz. Elimizde teorik çözümü ve ağırlıkları yerleştirebilmemiz için hazır bir fonksiyon da yok. Burada bootstrap yönteminden faydalanarak bir nevi hile yapabiliriz. En basit bootstrap, yerine koymalı örneklemeye karşılık gelmektedir. Bu örnekleme sürecinde, ağırlıklara göre olasılıklarla örneklem çekme uygulanarak elde edilen veri setlerinde modeller kurulur. Yeterli bir sayıda elde edilen model katsayılarının ortalaması, bize katsayı tahminini verir. Elimizdeki veri seti üzerinde bunu gösterelim. Önce elde ettiğimiz ağırlıklı katsayıları, ağırlıklı bootstrap ile tahmin edelim. Daha sonra e1071 paketinde svm fonksiyonu ile doğrusal bir destek vektör modeli kuralım. Bu fonksiyon, hali hazırda örnek ağırlıklarını kabul etmese de, bu fonksiyonu kullanarak ağırlıklı destek vektör modeli kurabiliriz. Başlayalım.

n_boot <- 10000
coefs <- matrix(NA, nrow = n_boot, ncol = 2)

for (i in 1:n_boot) {
  ## bootsrap aşaması
  index <- sample(1:n, size = n, replace = TRUE, prob = w)
  x_boot <- x[index]
  y_boot <- y[index]
  
  coefs[i,] <- lm(y_boot~x_boot)$coefficients
  ### coefs[i] <- solve(t(cbind(1,x_boot))%*%cbind(1,x_boot))%*%t(cbind(1,x_boot))%*%y
}

katsayilar_lm_agirlikli_bootsrap <- colMeans(coefs)

## katsayiları karşılaştıralım
print(data.frame(katsayilar_lm_agirlikli,
                 katsayilar_lm_agirlikli_bootsrap))
##             katsayilar_lm_agirlikli katsayilar_lm_agirlikli_bootsrap
## (Intercept)               0.5811147                        0.5911279
## x                         2.2348140                        2.2329383

Görüldüğü gibi katsayılar birbirine çok yakın. Aynı işlemi destek vektör makineleri için uygulayalım.

library(e1071)
n_boot <- 10000
coefs <- matrix(NA, nrow = n_boot, ncol = 2)

model_svm_agirliksiz <- svm(y~x, kernel = "linear", scale = FALSE)
katsayilar_svm_agirliksiz <- coef(model_svm_agirliksiz)

for (i in 1:n_boot) {
  ## bootsrap aşaması
  index <- sample(1:n, size = n, replace = TRUE, prob = w)
  x_boot <- x[index]
  y_boot <- y[index]
  
  coefs[i,] <- coef(svm(y_boot~x_boot, kernel = "linear", scale = FALSE))
}

katsayilar_svm_agirlikli_bootsrap <- colMeans(coefs)

## katsayiları karşılaştıralım
print(data.frame(katsayilar_svm_agirliksiz,
                 katsayilar_svm_agirlikli_bootsrap))
##             katsayilar_svm_agirliksiz katsayilar_svm_agirlikli_bootsrap
## (Intercept)                  2.161523                       -0.07622334
## x                            2.099636                        2.38518737
tahmin_svm_agirliksiz <- as.matrix(cbind(1,grid))%*%katsayilar_svm_agirliksiz
tahmin_svm_agirlikli <- as.matrix(cbind(1,grid))%*%katsayilar_svm_agirlikli_bootsrap

tbl <- data.frame(yontem = rep(c("Ağırlıksız svm", "Ağırlıklı svm"), each = res),
                  tahmin = c(tahmin_svm_agirliksiz, tahmin_svm_agirlikli),
                  grid = rep(grid, 2))

ggplot() +
  geom_point(mapping = aes(x = x, y = y, size = w), shape = 1, show.legend = FALSE) +
  geom_line(mapping = aes(x = tbl$grid, tbl$tahmin, color = tbl$yontem)) +
  scale_color_d3(name = "Yöntem") +
  theme_bw()

Bootstrap yöntemi ile hem regresyon hem sınıflama yöntemlerini, ağırlıklı şekilde elde etmek mümkündür. Prosedür tamamen aynıdır. Tabi hesaplama yoluyla bootstrapa gerek kalmadna ağırlıklı olarak hesaplanabilen sınıflama yöntemleri de mevcuttur. Bunlara örnek olarak naive bayesi verebiliriz. Naive Bayes sınıflayıcısından daha önce Bayes Sınıflayıcı İskeleti sayfasında söz etmiştim. Naive Bayes yöntemini özetleyecek olursak,

\[ \begin{alignedat}{2} P(Y|\textbf{X})=\frac{P(Y)P(\textbf{X}|Y)}{P(\textbf{X})}\\ \end{alignedat} \] koşullu olasılığını hesaplar. Burada \(Y\) sınıf değişkeni, \(\textbf{X}\) ise açıklayıcı değişkenler matrisidir. Hangi sınıfın koşullu olasılığı daha büyükse o sınıf tahmini yapılır. Bu nedenle \(P(\textbf{X})\) tüm sınıflar için aynı olduğundan dolayı ihmal edilir. \(P(Y)\) kısmı önsel olarak adlandırılır. En basit haliyle

\[ P(Y=S_{j})=\frac{n_j}{n}, j= 1,...,k \] şeklinde hesaplanır. Burada \(k\) sınıf sayısıdır. \(P(\textbf{X}|Y)\) kısmı ise olabilirlik olarak adlandırılır. Değişkenlerin birbirleriyle bağımsızlığı varsayımından

\[ P(\textbf{X}|Y=S_{k})=P(X_{1}|Y=S_{k})\times P(X_{2}|Y=S_{k}) \times \cdots \times P(X_{p}|Y=S_{k}) \] \[ P(\textbf{X}|Y=S_{k})= \prod_{j=1}^{p} P(X_{j}|Y=S_{k}) \] şeklinde hesaplanır. Ağırlıklı naif bayes için yapılacaklar çok basittir. Aynı şekilde koşullu olasılıktan \[ \begin{alignedat}{2} P(Y|\textbf{X},W)=\frac{P(Y|W)P(\textbf{X}|Y,W)}{P(\textbf{X}|W)}\\ \end{alignedat} \] Önsel,

\[ P(Y=S_{j}|W)=\frac{\sum_i^{n_j} w_j}{\sum_i^{n} w} \] Olabilirlik, \[ P(\textbf{X}|Y=S_{k},W)= \prod_{j=1}^{p} P(X_{j}|Y=S_{k},W) \] şeklinde hesaplanır. \(W\) ağırlıklarının hepsi birbirine eşit ise, ağırlıkların etkisi yoktur ve yöntem ağırlıksız naive bayes yöntemine dönüşür. Ağırlıklı naive bayes için R’da gerekli kodlamaları oluşturursak, ağırlıksız için de oluşturmuş oluruz. Sürekli değişkenler için her bir değişkenin olabilirliğe katkısını normal dağılım yoğunlukları üzerinden hesaplayacağız. Farklı yöntemler için Bayes Sınıflayıcı İskeleti sayfasını ziyaret ediniz. Naive Bayes fonksiyonunu hızlıca oluşturalım:

weighted_naive_bayes <- function(x_train, y_train, w = NULL){
  n_train <- nrow(x_train)
  p <- ncol(x_train)

  if (is.null(w)) {
    w <- rep(1, n_train)
  }
  ### ağırlıkların toplamı n yapacak şekilde dönüştürdük. Çok küçük veya çok büyük girilen ağırlıkların yaratacağı problemlerden kurtulmak için yaptık. Aslında sonucu değiştirmez.
  w <- w*n_train/sum(w)
  
  class_names <- unique(y)
  k_classes <- length(class_names)
  
  n_train <- nrow(x_train)
  n_classes <- sapply(class_names, function(m) sum(y_train == m))
  
  ### önseller
  priors <- sapply(class_names, function(m) sum(w[y_train == m])/n_train)

  x_classes <- lapply(class_names, function(m) x_train[y_train == m,])
  w_classes <- lapply(class_names, function(m) w[y_train == m])
  
  ### ağırlıklı ortalamalar
  means <- lapply(1:k_classes, function(m2) sapply(1:p, function(m) {
    ww <- w_classes[[m2]]/sum(w_classes[[m2]])*n_classes[m2]
    ms <- Hmisc::wtd.mean(x = x_classes[[m2]][,m], na.rm = TRUE, weights = ww)
    return(ms)
  }))
  
  ### ağırlıklı standart sapmaklar
  stds <- lapply(1:k_classes, function(m2) sapply(1:p, function(m) {
    ww <- w_classes[[m2]]/sum(w_classes[[m2]])*n_classes[m2]
    vars <- Hmisc::wtd.var(x = x_classes[[m2]][,m], na.rm = TRUE, weights = ww)
    return(sqrt(vars))
  }))
  
  return(list(n_train = n_train,
              p = p,
              x_classes = x_classes,
              n_classes = n_classes,
              k_classes = k_classes,
              priors = priors,
              class_names = class_names,
              means = means,
              stds = stds))
}

predict_weighted_naive_bayes <- function(object, newdata, type = "prob"){
  n_train <- object$n_train
  p <- object$p
  x_classes <- object$x_classes
  n_classes <- object$n_classes
  k_classes <- object$k_classes
  priors <- object$priors
  class_names <- object$class_names
  means <- object$means
  stds <- object$stds
  
  x_test <- newdata
  n_test <- nrow(x_test)
  
  ## yoğunluklar
  densities <- lapply(1:k_classes, function(m) sapply(1:p, function(m2) { 
    d <- dnorm(x_test[,m2], mean = means[[m]][m2], sd = stds[[m]][m2])
    d[is.infinite(d)] <- .Machine$double.xmax
    d[d == 0] <- 1e-20
    return(d)
    }))
  
  ## olabilirlikler
  likelihoods <- sapply(1:k_classes, function(m) apply(densities[[m]], 1, prod))
  ## sonsallar
  posteriors <- sapply(1:k_classes, function(m) apply(cbind(priors[m], likelihoods[,m]), 1, prod))
  
  
  ### Inf ve her iki tarafta 0 değeri alan durumlar için düzeltme
  posteriors <- t(apply(posteriors, 1, function(m) {
    if(all(m == 0)){
      runif(k_classes, min = 0, max = 1)
    } else{
      m
    }
  }))
  posteriors[is.infinite(posteriors)] <- .Machine$double.xmax
  
  ### sonsalların toplamını 1'e eşitleyelim. 
  posteriors <- posteriors/apply(posteriors, 1, sum)
  
  colnames(posteriors) <- class_names
  
  if (type == "prob") {
    return(posteriors)
  }
  if (type == "pred") {
    predictions <- apply(posteriors, 1, function(m) class_names[which.max(m)])
    return(predictions)
  }
}

Yerel ağırlıklı sınıflama kısmında, burada oluşturduğumuz ağırlıklı bayes sınıflayıcısından faydalanacağız.

1.2 Yerel Ağırlıklı Regresyon

Yerel ağırlıklı regresyonda, her bir tahmin noktası için farklı ağırlıklı regresyon modeli kurulur. Burada amaç, tahmin noktasıya yakın örnekler için yüksek ağırlık, uzak örnekler için düşük ağırlıklar vermektir. \(X_q\) bizim tahmin yapacağımız nokta olsun. Ağırlıklar

\[ W_i=e^{-0.5(\frac{X_i - X_q}{\lambda})^2} \] şeklinde hesaplanabilir. Bu ağırlıklar, yakın noktalar için büyük, uzak noktalar için küçük değer alır. \(\lambda\) değeri düzeltme katsayısıdır. Kullanıcının belirlediği bir sayıdır. Farklı değerlere göre farklı eğriler oluşturacaktır. Her bir \(X_q\) değeri için farklı \(W\) ağırlıkları ile modeller kurulacaktır. Böylece doğrusal olmayan yapıları yakalamak mümkün olacaktır. Bunu gösterebilmek için örnek bir veri seti simüle edelim.

n <- 100
x <- seq(-2*pi,2*pi,length.out = n)
err <- rnorm(n = n, mean = 0, sd = 0.5)
y <- 2*cos(x) + err

ggplot() +
  geom_point(mapping = aes(x = x, y = y), shape = 1) +
  theme_bw()

Bu veri seti için yerel ağırlıklı regresyon yöntemi ile \(X=-5\) noktası için inceleme yaparak tahminlerde bulunalım.

x_query <- -5

### ağırlık hesaplama fonksiyonu
agirlik_hesapla <- function(x_selected, x_train, lambda){
    exp(-0.5*((x_selected - x_train)/lambda)^2)
}

w <- agirlik_hesapla(x_selected = x_query, x_train = x, lambda = 1)
### ağırlıklara ait grafik
ggplot() +
  geom_line(mapping = aes(x = x, y = w)) +
  geom_vline(xintercept = x_query, color = "red") +
  theme_bw()

Yorumlar