ZIP - Скрытая марковская модель r Stan

Я пытаюсь настроить марковскую модель с нулевым завышением Пуассона, скрытой от Стэна. Для Poisson-HMM на прошлом форуме эта настройка была показана. см. ссылку.

В то время как для настройки ZIP с классическая теория хорошо документирована кодом и моделью.

введите здесь описание изображения


ziphsmm
library(ziphsmm)
set.seed(123)
prior_init <- c(0.5,0.5)
emit_init <- c(20,6)
zero_init <- c(0.5,0)
tpm <- matrix(c(0.9, 0.1, 0.2, 0.8),2,2,byrow=TRUE)
result <- hmmsim(n=100,M=2,prior=prior_init, tpm_parm=tpm,emit_parm=emit_init,zeroprop=zero_init)
y <- result$series
serie <- data.frame(y = result$series, m = result$state)

fit1 <-  fasthmmfit(y,x=NULL,ntimes=NULL,M=2,prior_init,tpm,
                    emit_init,0.5, hessian=FALSE,method="BFGS", 
                    control=list(trace=1))
fit1
$prior
            [,1]
[1,] 0.997497445
[2,] 0.002502555

$tpm
          [,1]       [,2]
[1,] 0.9264945 0.07350553
[2,] 0.3303533 0.66964673

$zeroprop
[1] 0.6342182

$emit
          [,1]
[1,] 20.384688
[2,]  7.365498

$working_parm
[1] -5.9879373 -2.5340475  0.7065877  0.5503559  3.0147840  1.9968067

$negloglik
[1] 208.823
Stan
library(rstan)

ZIPHMM <- 'data {
    int<lower=0> N;
    int<lower=0> y[N];
    int<lower=1> m;
  }

parameters {
    real<lower=0, upper=1> theta; //
    positive_ordered[m] lambda; //
    simplex[m] Gamma[m]; // tpm
  }

model {
  vector[m] log_Gamma_tr[m];
  vector[m] lp;
  vector[m] lp_p1;

  // priors
  lambda ~ gamma(0.1,0.01);
  theta ~ beta(0.05, 0.05);

  // transposing tpm and taking the log of each entry
  for(i in 1:m)
    for(j in 1:m)
      log_Gamma_tr[j, i] = log(Gamma[i, j]);

  lp = rep_vector(-log(m), m); // 

    for(n in 1:N) {
      for(j in 1:m){
        if (y[n] == 0)
          lp_p1[j] = log_sum_exp(log_Gamma_tr[j] + lp) +
                     log_sum_exp(bernoulli_lpmf(1 | theta),
                     bernoulli_lpmf(0 | theta) + poisson_lpmf(y[n] | lambda[j]));
        else
          lp_p1[j] = log_sum_exp(log_Gamma_tr[j] + lp) + 
                     bernoulli_lpmf(0 | theta) + 
                     poisson_lpmf(y[n] | lambda[j]);
                   }
      lp = lp_p1;
                  }
    target += log_sum_exp(lp);
}'
mod_ZIP <- stan(model_code = ZIPHMM, data=list(N=length(y), y=y, m=2), iter=1000, chains=1)
print(mod_ZIP,digits_summary = 3)
               mean se_mean    sd     2.5%      25%      50%      75%    97.5% n_eff  Rhat
theta         0.518   0.002 0.052    0.417    0.484    0.518    0.554    0.621   568 0.998
lambda[1]     7.620   0.039 0.787    6.190    7.038    7.619    8.194    9.132   404 1.005
lambda[2]    20.544   0.039 0.957   18.861   19.891   20.500   21.189   22.611   614 1.005
Gamma[1,1]    0.664   0.004 0.094    0.473    0.604    0.669    0.730    0.841   541 0.998
Gamma[1,2]    0.336   0.004 0.094    0.159    0.270    0.331    0.396    0.527   541 0.998
Gamma[2,1]    0.163   0.003 0.066    0.057    0.114    0.159    0.201    0.312   522 0.999
Gamma[2,2]    0.837   0.003 0.066    0.688    0.799    0.841    0.886    0.943   522 0.999
lp__       -222.870   0.133 1.683 -227.154 -223.760 -222.469 -221.691 -220.689   161 0.999
True values
real = list(tpm = tpm, 
     zeroprop = nrow(serie[serie$m == 1 & serie$y == 0, ]) / nrow(serie[serie$m == 1,]),
     emit = t(t(tapply(serie$y[serie$y != 0],serie$m[serie$y != 0], mean))))
real
$tpm
     [,1] [,2]
[1,]  0.9  0.1
[2,]  0.2  0.8

$zeroprop
[1] 0.6341463

$emit
       [,1]
1 20.433333
2  7.277778

Оценки дают довольно странно, чтобы кто-то мог помочь мне понять, что я делаю неправильно. Как мы видим, оценки stan zeroprop = 0,518, тогда как реальное значение равно 0,634, с другой стороны, значения t.p.m. в стане они довольно далеки и средние значения лямбда1 = 7,62 и лямбда2 = 20,54, хотя они достаточно приближены, дали в другом порядке реальные 20,43 и 7,27. Я думаю, что делаю какую-то ошибку в определении модели в Стэне, но я не знаю, какую.


person Rafael Díaz    schedule 17.02.2019    source источник
comment
Я разверну свой вопрос, доля нулей дает довольно большое расстояние с rStan theta = 0,518 и реальным 0,634, то же самое для значений матрицы перехода. Также среднее значений lambda1 = 7,62 и lambda2 = 20,54, а действительные lambda1 = 20,43 и lambda2 = 7,27. То есть они пересекаются.   -  person Rafael Díaz    schedule 19.02.2019
comment
Несоответствие для theta похоже на то, что оно возникает из-за того, что fasthmmfit налагает нулевую инфляцию только на первое состояние, тогда как способ, которым вы закодировали модель Стэна, применяет нулевую инфляцию на обоих состояниях. Априор, который вы навязываете theta, является бимодальным, что обычно трудно подобрать. Вы пробовали плоскую приору на theta и навязывали ее только одному штату?   -  person merv    schedule 19.02.2019
comment
Как я могу изменить код, чтобы ввести тета-значение только в первом состоянии? Я не знаю, как это сделать, я ценю вашу помощь.   -  person Rafael Díaz    schedule 19.02.2019
comment
[zeroprop] реальное значение равно 0,634 Исходя из исходных параметров HMM, реальное значение zeroprop равно 0,5. Вам нужна модель, которая лучше всего восстанавливает истинные параметры HMM, или вам нужна точная повторная реализация этой оценки MAP на основе SGD, но с использованием Стэна? Если последнее, то rstan::optimizing используется для оценок MAP. Кроме того, вам, вероятно, не следует инициализировать fasthmmfit с истинными параметрами, если вы хотите получить реалистичную характеристику его поведения.   -  person merv    schedule 20.02.2019


Ответы (1)


Хотя я не знаком с внутренней работой алгоритма подгонки ZIP-HMM, есть некоторые очевидные различия в том, что вы реализовали в модели Стэна, и в том, как описывает себя алгоритм оптимизации ZIP-HMM. Рассмотрение этих вопросов кажется достаточным для получения аналогичных результатов.

Различия между моделями

Начальная вероятность состояния

Значения, которые оценивает ZIP-HMM, в частности fit1$prior, указывают на то, что он включает в себя возможность узнать вероятность для начального состояния. Однако в модели Стэна это фиксированное значение 1:1.

lp = rep_vector(-log(m), m);

Это следует изменить, чтобы позволить модели оценить начальное состояние.

Приоры по параметрам (опционально)

Модель Стэна имеет неплоские априорные значения для lambda и theta, но, по-видимому, ZIP-HMM не взвешивает полученные значения. Если кто-то хотел более реалистично имитировать ZIP-HMM, то плоские приоры были бы лучше. Однако возможность иметь неплоские априорные значения в Стэне на самом деле дает возможность разработать более хорошо настроенную модель, чем это достижимо с помощью стандартных алгоритмов вывода HMM.

Нулевая инфляция в штате 1

Из документации метода fasthmmfit

Алгоритм быстрого градиентного спуска/стохастического градиентного спуска для изучения параметров в специализированной скрытой марковской модели с нулевым раздуванием, где нулевое раздувание происходит только в состоянии 1. [курсив добавлен]

Модель Стэна предполагает нулевую инфляцию во всех штатах. Вероятно, поэтому оценочное значение theta занижено по сравнению с оценкой ZIP-HMM MAP.

Государственный заказ

При оценке дискретных скрытых состояний или кластеров в Stan можно использовать упорядоченный вектор в качестве трюка, чтобы устранение проблем с переключением ярлыков. Здесь это эффективно достигается с помощью

positive_ordered[m] lambda;

Однако, поскольку ZIP-HMM имеет нулевую инфляцию только в первом состоянии, правильная реализация этого поведения в Стэне требует предварительного знания того, каков ранг lambda для «первого» состояния. Это кажется очень проблематичным для обобщения этого кода. А пока давайте просто двигаться вперед, предполагая, что мы всегда можем каким-то образом восстановить эту информацию. В этом конкретном случае мы предположим, что состояние 1 в HMM имеет более высокое значение lambda и, следовательно, будет состоянием 2 в модели Стэна.

Обновленная модель Стэна

Включение вышеуказанных изменений в модель должно быть чем-то вроде

Стэн Модель

data {
  int<lower=0> N;    // length of chain
  int<lower=0> y[N]; // emissions
  int<lower=1> m;    // num states
}

parameters {
  simplex[m] start_pos;         // initial pos probs
  real<lower=0, upper=1> theta; // zero-inflation parameter
  positive_ordered[m] lambda;   // emission poisson params
  simplex[m] Gamma[m];          // transition prob matrix
}

model {
  vector[m] log_Gamma_tr[m];
  vector[m] lp;
  vector[m] lp_p1;

  // transposing tpm and taking the log of each entry
  for (i in 1:m) {
    for (j in 1:m) { 
      log_Gamma_tr[j, i] = log(Gamma[i, j]);
    }
  }

  // initial position log-lik
  lp = log(start_pos);

  for (n in 1:N) {
    for (j in 1:m) {
      // log-lik for state
      lp_p1[j] = log_sum_exp(log_Gamma_tr[j] + lp);

      // log-lik for emission
      if (j == 2) { // assuming only state 2 has zero-inflation
        if (y[n] == 0) {
          lp_p1[j] += log_mix(theta, 0, poisson_lpmf(0 | lambda[j]));
        } else {
          lp_p1[j] += log1m(theta) + poisson_lpmf(y[n] | lambda[j]);
        }
      } else {
        lp_p1[j] += poisson_lpmf(y[n] | lambda[j]);
      }
    }
    lp = lp_p1; // log-lik for next position
  }
  target += log_sum_exp(lp);
}

Оценка карты

Загружая приведенное выше как строковую переменную code.ZIPHMM, мы сначала компилируем ее и запускаем оценку MAP (поскольку оценка MAP будет вести себя больше всего как алгоритм подбора HMM):

model.ZIPHMM <- stan_model(model_code=code.ZIPHMM)

// note the use of some initialization on the params,
// otherwise it can occasionally converge to strange extrema
map.ZIPHMM <- optimizing(model.ZIPHMM, algorithm="BFGS",
                         data=list(N=length(y), y=y, m=2),
                         init=list(theta=0.5, lambda=c(5,10)))

Изучение предполагаемых параметров

> map.ZIPHMM$par
start_pos[1] start_pos[2]        
9.872279e-07 9.999990e-01 

theta    
6.342449e-01 

lambda[1]    lambda[2]   
7.370525e+00 2.038363e+01 

Gamma[1,1]   Gamma[2,1]   Gamma[1,2]   Gamma[2,2] 
6.700871e-01 7.253215e-02 3.299129e-01 9.274678e-01

показывает, что они точно отражают значения, которые вывел fasthmmfit, за исключением того, что государственные порядки поменялись местами.

Выборка апостериорной

Эта модель также может быть запущена с MCMC для получения полного апостериорного,

samples.ZIPHMM <- stan(model_code = code.ZIPHMM, 
                       data=list(N=length(y), y=y, m=2), 
                       iter=2000, chains=4)

который хорошо сэмплирует и дает аналогичные результаты (и без каких-либо инициализаций параметров)

> samples.ZIPHMM
Inference for Stan model: b29a2b7e93b53c78767aa4b0c11b62a0.
4 chains, each with iter=2000; warmup=1000; thin=1; 
post-warmup draws per chain=1000, total post-warmup draws=4000.

                mean se_mean   sd    2.5%     25%     50%     75%   97.5% n_eff Rhat
start_pos[1]    0.45    0.00 0.29    0.02    0.20    0.43    0.69    0.97  6072    1
start_pos[2]    0.55    0.00 0.29    0.03    0.31    0.57    0.80    0.98  6072    1
theta           0.63    0.00 0.05    0.53    0.60    0.63    0.67    0.73  5710    1
lambda[1]       7.53    0.01 0.72    6.23    7.02    7.49    8.00    9.08  4036    1
lambda[2]      20.47    0.01 0.87   18.83   19.87   20.45   21.03   22.24  5964    1
Gamma[1,1]      0.65    0.00 0.11    0.43    0.57    0.65    0.72    0.84  5664    1
Gamma[1,2]      0.35    0.00 0.11    0.16    0.28    0.35    0.43    0.57  5664    1
Gamma[2,1]      0.08    0.00 0.03    0.03    0.06    0.08    0.10    0.16  5605    1
Gamma[2,2]      0.92    0.00 0.03    0.84    0.90    0.92    0.94    0.97  5605    1
lp__         -214.76    0.04 1.83 -219.21 -215.70 -214.43 -213.43 -212.25  1863    1
person merv    schedule 20.02.2019
comment
Большое спасибо за помощь и время, потраченное на проработку кода, это впечатляет. Видимо, это то, что ему было нужно. У меня только два вопроса. 1. Можно ли изменить порядок состояний в Stan, чтобы он совпадал как в ziphsmm? так как в модели с тремя состояниями порядок матрицы перехода вероятностей был бы немного запутанным. 2. Если код j = 2 опущен, предполагается, что инфляция есть во всех состояниях, и поэтому будет создана более реалистичная модель? - person Rafael Díaz; 21.02.2019
comment
@RafaelDíaz 1) Вы можете добавить преобразованный параметр в модель, где вы переупорядочиваете вектор lambda. 2) Чтобы сделать все раздутым, оставьте только блок кода под оператором if (j==2) {...}, но удалите условное. - person merv; 21.02.2019
comment
Мой друг, большое спасибо, ты спас мне жизнь, это было то, что я искал, и я считаю, что ты заслужил награду. - person Rafael Díaz; 22.02.2019