2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RとStanで実装するディリクレ過程混合モデル

Posted at

はじめに

前回扱った有限混合モデルでは、クラスター数が恣意的に決定されたり、パラメータの識別性が弱かったりといったデメリットがあった。そこで、今回は特にクラスター数の恣意性を克服すべく、ディリクレ過程モデルを扱う。

一般にディリクレ過程によるモデルはノンパラメトリックな手法として知られるが、まずノンパラメトリックな手法を概観する。

以下のパラメトリックな設定を考える。

$$y_i \in \mathcal Y$$
$$y_i|F \sim iid ~ F$$
$$F \in \mathcal F^*,\quad where \quad \mathcal F^* = N(y|\mu, \tau^2)$$

パラメトリックな設定では、$\mathcal F^*$は$\mathcal F=\lbrace \mathcal Y上のすべての分布\rbrace$に比べて小さいことがわかる。

したがって、ノンパラメトリックな設定では$\mathcal F$の部分集合のうち、より大きな(柔軟な)ものを考える。そのアプローチの一つが、確率過程によるものである。

出力のベクトルを$y=(y_1, ,\cdots, y_n)$とし、以下の階層的なディリクレ過程混合モデルを考える。

$$y_i \sim k(\theta_i) \quad \cdots①$$
$$\theta_i \sim P\quad \cdots②$$
$$P \sim DP(\alpha, P_0)\quad \cdots③$$

ただし、$k(\theta_i)$はパラメトリックな密度関数。$\alpha$は集中度パラメータで、$P_0$が$P$に「収縮」する程度を示す。

①,②,③の表記は$y_i|P$を$f(y|P)$からサンプリングするのと同義である。

$$y_i \sim ~iid~ f(y|P)$$

$P(\cdot)$は$P_0(\theta)$から生成されたアトム$\theta_h$の重み付き和として表現でき、これを棒折り過程という。

$$P=\sum_{h=1}^{\infty}\pi_h \delta_{\theta_h}$$

ただし、$\pi_h = v_h\Pi_{l<h}(1-v_h)$, $v_h = Beta(1,\alpha)$, $\theta_h \sim P_0.$

一般に、$\pi_1 = v_1$, $\pi_i = v_i\Pi_{j=1}^{l-1}(1-v_j)$である。

データの生成

平均がそれぞれ2,5,10のポアソン分布に従うサンプルを200個ずつ発生させ、分布を確認する。

set.seed(12345)

lambdas <- c(2, 5, 10)
n_per_group <- 200

df <- tibble::tibble(
  group = rep(paste0("lambda=", lambdas), each = n_per_group),
  y = c(
    rpois(n_per_group, lambdas[1]),
    rpois(n_per_group, lambdas[2]),
    rpois(n_per_group, lambdas[3])
  )
)


df |>
  ggplot2::ggplot(ggplot2::aes(x = y, fill = group)) +
  ggplot2::geom_histogram(binwidth = 1, alpha = 0.6, position = "identity") +
  ggplot2::facet_wrap(~ group, scales = "free_y") +
  ggplot2::theme_minimal() +
  ggplot2::labs(x = "y", y = "Count")

image.png

データの生成過程

$y_i \in \mathbb{N}, i=1,\cdots, n$でサンプルを表す。

棒折り過程によって、$y_i$が従う分布を表現する。

$$y_i \sim \sum_{c=1}^C \pi_c \, \text{Poisson}(y_i \mid \lambda_c), \quad \lambda_c = e^{\phi_c}.$$
ただし、$C$はクラスターの最大数。

また、以下で棒折り過程を表現する。

$$breaks_c \sim \text{Beta}(1, intensity), \quad \pi_c = breaks_c \prod_{j < c} (1 - breaks_j).$$
$$intensity \sim gamma(1,1).$$

Stanファイルの実装

data {
  int<lower=0> sample_type;
  int<lower=1> cluster_type; 
  array[sample_type] int<lower=0> outcome;  
  int<lower=0> outcome_max;
}

parameters {
  real<lower=0> intensity;
  vector[cluster_type] phi;
  vector<lower=0, upper=1>[cluster_type - 1] breaks;
  real<lower=0> sigma_phi;
}

transformed parameters {
  simplex[cluster_type] pi;

  {
    real sum = 0;
    pi[1] = breaks[1];
    sum = pi[1];
    for (c in 2:(cluster_type - 1)) {
      pi[c] = (1 - sum) * breaks[c];
      sum += pi[c];
    }
    pi[cluster_type] = 1 - sum;
  }
}

model {
  for (n in 1:sample_type) {
    vector[cluster_type] case_vector;
    for (c in 1:cluster_type) {
      case_vector[c] = bernoulli_lpmf(1 | pi[c])
              + poisson_log_lpmf(outcome[n] | phi[c]);
    }
    target += log_sum_exp(case_vector);
  }
  phi ~ normal(0, sigma_phi);
  breaks ~ beta(1, intensity);
  intensity ~ gamma(1, 1);
  
  sigma_phi ~ gamma(0.1, 0.1);
}

generated quantities {
  vector[outcome_max + 1] density;
  matrix[outcome_max + 1, cluster_type] cluster_level_density;

  for (y in 0:outcome_max) {
    for (c in 1:cluster_type) {
      real lambda = exp(phi[c]);
      cluster_level_density[y + 1, c] = pi[c] * lambda^y * exp(-lambda) / tgamma(y + 1);
    }
    density[y + 1] = sum(cluster_level_density[y + 1, ]);
  }
}

パラメータの推定

クラスターの最大数$C$は10とした。

stan_data <- list(
  sample_type = nrow(df),
  cluster_type = 10,
  outcome = df$y,
  outcome_max = max(df$y)
)
stan_model <- rstan::stan_model("dirichlet_process_mixture.stan")
vb_fit <- rstan::vb(stan_model,
                    data = stan_data,
                    output_samples = 1000, 
                    iter = 10000,         
                    tol_rel_obj = 0.01)

各クラスターが選ばれる確率(つまり、$\pi_c$)を可視化する。

pi_summary <- rstan::summary(vb_fit, pars = "pi")$summary

pi_est <- pi_summary |>
  tibble::as_tibble() |>
  tibble::rownames_to_column("variable") |>
  dplyr::mutate(index = stringr::str_extract(variable, "[0-9]+") |>
                  as.numeric() |> 
                  as.factor())

ggplot2::ggplot(pi_est) +
  ggplot2::geom_point(ggplot2::aes(x = index, y = mean)) +
  ggplot2::geom_segment(ggplot2::aes(x = index, xend = index, y = `2.5%`, yend = `97.5%`)) +
  ggplot2::labs(x = "Cluster", y = "Probability")

image.png

最初の3つのクラスターでデータのほとんどを説明していることがわかる。

また、生成したサンプルの密度と事後分布による密度を比較する。

density_summary <- rstan::summary(vb_fit, pars = "density", probs = c(0.05, 0.95))$summary

density <- density_summary |>
  tibble::as.tibble() |>
  tibble::rownames_to_column("variable") |>
  dplyr::mutate(
    Y = stringr::str_extract(variable, "[0-9]+") |>
      as.numeric() - 1,
    mean = `mean`,
    q5 = `5%`,
    q95 = `95%`
  )

df_data <- tibble::tibble(Y = df$y)

ggplot2::ggplot() +
  ggplot2::geom_bar(data = df_data, ggplot2::aes(x = Y, y = ggplot2::after_stat(prop)),
           fill = "grey70", colour = "black") +
  ggplot2::geom_point(data = density, ggplot2::aes(x = Y, y = mean),
             colour = "red", size = 1.5) +
  ggplot2::geom_segment(data = density,
                        ggplot2::aes(x = Y, xend = Y, y = q5, yend = q95),
               colour = "red") +
  ggplot2::labs(x = "Y", y = "Density / Proportion") +
  ggplot2::theme_minimal()

image.png

最後に、サンプルの分布と実際にクラスタリングに使われた分布を可視化する。

density_draws <- as.data.frame(vb_fit, pars = "density")
cluster_level_density_draws <- as.data.frame(vb_fit, pars = "cluster_level_density")

density_mean <- colMeans(density_draws)
cluster_level_density_mean <- colMeans(cluster_level_density_draws)

outcome_max <- length(dens_mean) - 1
C <- ncol(dens_c_draws) / (outcome_max + 1)  

density_long <- tibble::tibble(
  y = rep(0:outcome_max, C),
  component = rep(1:C, each = outcome_max + 1),
  prob = cluster_level_density_mean
) |>
  dplyr::mutate(component = factor(component, labels = paste0("group", 1:C)))

density_df <- tibble::tibble(
  y = 0:outcome_max,
  prob = density_mean
)

ggplot2::ggplot() +
  ggplot2::geom_col(data = density_df, ggplot2::aes(x = y, y = prob), 
           fill = "grey70", alpha = 0.6, width = 0.9) +
  ggplot2::geom_line(data = density_long, ggplot2::aes(x = y, y = prob, color = component), size = 1) +
  ggplot2::theme_minimal() +
  ggplot2::labs(x = "Count", 
                y = "Probability"
  )

image.png

平均がそれぞれ2,5,10のポアソン分布に従うサンプルを発生させていたが、事後密度の推定においては平均が1,3,9あたりのポアソン分布が使われているように見える。

注意:ディリクレ過程では、データ数が大きくなるにつれて推定されるクラスター数が増え、ひとつのデータポイントからなるクラスターがたくさん生成されるとともに、各クラスターのサイズがどんどん小さくなるとされる。

真のデータ生成過程がクラスター数の有限混合モデルであるときには、ディリクレ過程混合モデルで得られた推定値は一致性を持たない。(こちらのqiitaより引用。)

参考文献

https://ito4303.sakura.ne.jp/posts/2024-02-15-Poisson-Dirichlet-Stan/
https://qiita.com/gen_nospare/items/15c6a94d8e43121a686f

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?