knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
library(dmtr) library(purrr) library(dplyr) library(stringr)
data(binaryclass2, package = "dmtr")
knitr::kable( binaryclass2, align = c('r', 'r', 'r', 'r'), caption = '판별분석 학습표본 데이터' )
각 객체는 변수벡터 $\mathbf{x} \in \mathbb{R}^p$와 범주 $y \in {1, 2}$로 이루어진다고 하자. 아래는 변수 $\mathbf{x}$의 기대치와 분산-공분산행렬(varinace-covariance matrix)을 나타낸다.
\begin{eqnarray} \boldsymbol\mu_1 = E(\mathbf{x} | y = 1)\ \boldsymbol\mu_2 = E(\mathbf{x} | y = 2)\ \boldsymbol\Sigma = Var(\mathbf{x} | y = 1) = Var(\mathbf{x} | y = 2) \end{eqnarray}
다음과 같이 변수들의 선형조합으로 새로운 변수 $z$를 형성하는 함수를 피셔 판별함수(Fisher's discriminant function)라 한다.
\begin{equation} z = \mathbf{w}^\top \mathbf{x} (#eq:fisher-discriminant-function) \end{equation}
여기서 계수벡터 $\mathbf{w} \in \mathbb{R}^p$는 통상 아래와 같이 변수 $z$의 범주간 평균 차이 대 변수 $z$의 분산의 비율을 최대화하는 것으로 결정한다.
\begin{equation} {\arg!\min}_{\mathbf{w}} \frac{\mathbf{w}^\top ( \boldsymbol\mu_1 - \boldsymbol\mu_2 )}{\mathbf{w}^\top \boldsymbol\Sigma \mathbf{w}} (#eq:fisher-discriminant-function-coef) \end{equation}
위 식 \@ref(eq:fisher-discriminant-function-coef)의 해는
\begin{equation} \mathbf{w} \propto \boldsymbol\Sigma^{-1}(\boldsymbol\mu_1 - \boldsymbol\mu_2) \end{equation}
의 조건을 만족하며, 편의상 비례상수를 1로 두면 아래와 같은 해가 얻어진다.
\begin{equation} \mathbf{w} = \boldsymbol\Sigma^{-1}(\boldsymbol\mu_1 - \boldsymbol\mu_2) (#eq:fisher-discriminant-function-coef-sol) \end{equation}
실제 모집단의 평균 및 분산을 알지 못하는 경우, 학습표본으로부터 $\boldsymbol\mu_1, \boldsymbol\mu_2, \boldsymbol\Sigma$의 추정치를 얻어 식 \@ref(eq:fisher-discriminant-function-coef-sol)에 대입하는 방식으로 판별계수를 추정한다. 자세한 내용은 교재 [@jun2012datamining] 참조.
Table \@ref(tab:fisher-data-print)에 주어진 학습표본을 이용하여 피셔 판별함수를 구해보도록 하자. 우선 각 범주별 관측수 $n_1, n_2$, 평균벡터 $\hat{\boldsymbol\mu}_1, \hat{\boldsymbol\mu}_2$, 그리고 범주별 표본 분산-공분산행렬 $\mathbf{S}_1, \mathbf{S}_2$를 다음과 같이 함수 group_summary()
를 사용하여 구한다.
summary_within_group <- group_summary(binaryclass2, class, c(x1, x2)) print(summary_within_group)
위에서 얻은 결과를 이용하여 합동 분산-공분산행렬을 아래와 같이 추정한다.
\begin{equation} \hat{\boldsymbol\Sigma} = \mathbf{S}_p = \frac{(n_1 - 1)\mathbf{S}_1 + (n_2 - 1)\mathbf{S}_2}{n_1 + n_2 - 2} \end{equation}
sigma_hat <- pooled_variance(binaryclass2, class, c(x1, x2)) print(sigma_hat)
위에서 구한 추정치들을 이용하여 아래와 같이 판별함수 계수 추정치 $\hat{\mathbf{w}}$를 구한다. \begin{equation} \hat{\mathbf{w}} = \hat{\boldsymbol\Sigma}^{-1}(\hat{\boldsymbol\mu}_1 - \hat{\boldsymbol\mu}_2) \end{equation}
mu_hat <- map(summary_within_group, ~ .[["mean"]]) w_hat <- solve(sigma_hat) %*% as.matrix(mu_hat[[1]] - mu_hat[[2]], ncol = 1L) %>% as.vector() print(w_hat)
위 피셔 판별함수를 구하는 과정을 하나의 함수 fisher_ld()
로 구현하였다.
w_hat <- fisher_ld(binaryclass2, class, c(x1, x2)) print(w_hat)
피셔 판별함수에 따른 분류 경계값은 학습표본에 대한 판별함수값의 평균으로 아래와 같이 구할 수 있다.
\begin{equation} \bar{z} = \frac{1}{N} \sum_i^N \hat{\mathbf{w}}^\top \mathbf{x}_i \end{equation}
z_mean <- binaryclass2 %>% select(x1, x2) %>% summarize_all(mean) %>% `*`(w_hat) %>% sum() print(z_mean)
위 분류 경계값을 구하는 과정을 함수 fisher_ld_threshold()
로 구현하였다.
z_mean <- fisher_ld_threshold(binaryclass2, class, c(x1, x2)) print(z_mean)
위 결과를 통해, 분류규칙은 다음과 같이 주어진다.
class_level <- attr(summary_within_group, "group") prediction_df <- binaryclass2 %>% mutate( z = w_hat[1] * x1 + w_hat[2] * x2, .pred_class = factor( if_else(z >= z_mean, class_level[1], class_level[2]), levels = class_level ) )
위 분류 과정을 함수 fisher_ld_prediction()
을 이용하여 아래와 같이 수행할 수 있다.
prediction_df <- fisher_ld_prediction( .w = w_hat, .z = z_mean, .newdata = binaryclass2, .xvar = c(x1, x2), .levels = attr(w_hat, "group") )
분류 결과는 아래와 같다.
knitr::kable( prediction_df, align = c('r', 'r', 'r', 'r', 'r', 'r'), caption = '학습표본에 대한 피셔 분류 결과' )
위 결과 객체 r prediction_df %>% filter(class != .pred_class) %>% pull(id) %>% paste(collapse = ", ")
가 오분류된다.
다음과 같이 객체가 각 범주에 속할 사전확률과 각 범주 내에서의 분류변수의 확률밀도함수에 대한 기호를 정의한다.
이 때 통상적으로 $\mathbf{x}$는 다변량 정규분포를 따르는 것으로 가정하여 아래와 같이 평균벡터 $\boldsymbol\mu_k$와 분산-공분산행렬 $\boldsymbol\Sigma$로 확률밀도함수를 정의할 수 있다. 이 때 분산-공분산행렬 $\boldsymbol\Sigma$는 모든 범주에 대해 동일하다고 가정한다.
\begin{equation} f_k(\mathbf{x}) = \frac{1}{(2\pi)^{p/2}|\boldsymbol\Sigma|^{1/2}} \exp { -\frac{1}{2} \left(\mathbf{x} - \boldsymbol\mu_k\right)^\top \boldsymbol\Sigma^{-1} \left(\mathbf{x} - \boldsymbol\mu_k\right) } (#eq:mv-gaussian-dist) \end{equation}
각 범주에 대한 판별함수를
\begin{equation} u_k(\mathbf{x}) = \boldsymbol\mu_k^\top \boldsymbol\Sigma^{-1}\mathbf{x} - \frac{1}{2} \boldsymbol\mu_k^\top \boldsymbol\Sigma^{-1} \boldsymbol\mu_k + \ln \pi_k (#eq:lda-discriminant-function) \end{equation}
로 정의한다. 위 판별함수를 함수 ld_fun()
로 구현하였다.
f <- ld_fun(binaryclass2, class, c(x1, x2))
위 함수를 이용하여 학습표본에 대해 판별함수값을 다음과 같이 얻을 수 있다.
u_df <- map_dfc(f, ~ binaryclass2 %>% select(x1, x2) %>% transmute(u = apply(., 1, .x)) ) names(u_df) <- str_c(".score", attr(f, "group"), sep = "_") score_df <- bind_cols(binaryclass2, u_df)
knitr::kable( score_df, align = c('r', 'r', 'r', 'r', 'r', 'r'), digits = 2L, caption = '학습표본에 대한 LDA 적용 결과: 판별함수값' )
베이즈 정리(Bayes's theorem)에 따라 변수 $\mathbf{x}$값이 주어졌을 때 범주 $k$에 속할 사후확률(posterior)은 아래와 같이 구할 수 있다.
\begin{equation} P(y = k \, | \, \mathbf{x}) = \frac{\pi_k f_k(\mathbf{x})}{f(\mathbf{x})} (#eq:lda-posterior) \end{equation}
p_df <- u_df %>% mutate_all(exp) %>% mutate_all(function(x) x / rowSums(.)) names(p_df) <- str_c(".pred", attr(f, "group"), sep = "_") posterior_df <- bind_cols(binaryclass2, p_df)
knitr::kable( posterior_df, align = c('r', 'r', 'r', 'r', 'r', 'r'), digits = 2L, caption = '학습표본에 대한 LDA 적용 결과: 사후확률값' )
최종적으로, 각 범주에 대한 사후확률이 높은 쪽으로 범주를 추정한다.
\begin{equation} \hat{y} = \begin{cases} 1, & \text{if } P(y = 1 \, | \, \mathbf{x}) \ge P(y = 2 \, | \, \mathbf{x})\ 2, & \text{otherwise} \end{cases} (#eq:lda-posterior-rule) \end{equation}
yhat_df <- tibble( .pred_class = attr(f, "group")[apply(p_df, 1, which.max)] ) prediction_df <- bind_cols(posterior_df, yhat_df)
knitr::kable( prediction_df, align = c('r', 'r', 'r', 'r', 'r', 'r', 'r'), digits = 2L, caption = '학습표본에 대한 LDA 적용 결과: 사후확률값 및 추정범주' )
위와 같이 새 데이터에 대해 판별함수값, 사후확률 및 범주추정을 수행하는 함수를 predict_da()
로 구현하였다.
predict_da(f, .new_data = binaryclass2, c(x1, x2), .include_score = TRUE, .include_posterior = TRUE, .include_class = TRUE)
각 범주의 확률밀도함수는 아래와 같이 다변량 정규분포로 정의된다.
\begin{equation} f_k(\mathbf{x}) = \frac{1}{(2\pi)^{p/2}|\boldsymbol\Sigma_k|^{1/2}} \exp { -\frac{1}{2} \left(\mathbf{x} - \boldsymbol\mu_k\right)^\top \boldsymbol\Sigma_k^{-1} \left(\mathbf{x} - \boldsymbol\mu_k\right) } (#eq:qda-mv-gaussian-dist) \end{equation}
위 식 \@ref(eq:qda-mv-gaussian-dist)이 선형판별함수에서 사용한 식 \@ref(eq:mv-gaussian-dist)과 다른 부분은 분산-공분산분포 $\boldsymbol\Sigma_k$가 범주 $k$에 대해 각각 정의된다는 점이다.
이 경우 각 범주에 대한 판별함수는 아래와 같이 정의된다.
\begin{equation} u_k(\mathbf{x}) = - \frac{1}{2} (\mathbf{x} - \boldsymbol\mu_k)^\top \boldsymbol\Sigma_k^{-1} (\mathbf{x} - \boldsymbol\mu_k) - \frac{1}{2} \ln \left| \boldsymbol\Sigma_k \right| + \ln \pi_k (#eq:qda-discriminant-function) \end{equation}
위 판별함수를 데이터로부터 얻는 함수를 qd_fun()
로 구현하였다.
f <- qd_fun(binaryclass2, class, c(x1, x2))
이후, 선형판별함수에서와 같이 함수 predict_df()
를 이용하여 판별함수값, 사후확률 및 범주추정값을 얻을 수 있다.
prediction_df <- predict_da(f, .new_data = binaryclass2, c(x1, x2), .include_score = TRUE, .include_posterior = TRUE, .include_class = TRUE) prediction_df <- bind_cols(binaryclass2, prediction_df)
knitr::kable( prediction_df, align = c('r', 'r', 'r', 'r', 'r', 'r', 'r', 'r', 'r'), digits = 2L, caption = '학습표본에 대한 QDA 적용 결과: 판별함수값, 사후확률 및 추정범주' )
3개의 범주를 지닌 붓꽃(iris) 데이터에 대해 판별분석을 수행하고자 한다.
data(iris90, package = "dmtr")
함수 ld_fun()
을 이용하여 선형 판별함수를 구하고, predict_da()
를 이용하여 판별함수 값을 구하고 범주를 추정한다.
f <- ld_fun(iris90, class, x1:x4) new_data <- tibble::tribble( ~x1, ~x2, ~x3, ~x4, ~class, 5.1, 3.5, 1.4, 0.2, "setosa", 7.0, 3.2, 4.7, 1.4, "versicolor", 6.3, 3.3, 6.0, 2.5, "virginica" ) %>% mutate( class = factor(class, levels = c("setosa", "versicolor", "virginica")) ) predict_df <- predict_da(f, .new_data = new_data, .xvar = x1:x4, .include_score = TRUE) predict_df <- bind_cols(new_data, predict_df)
knitr::kable( predict_df, align = rep('r', ncol(prediction_df)), digits = 2L, caption = '붗꽃 표본에 대한 LDA 적용 결과: 판별함수값 및 추정범주' )
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.