PyMC для непараметрической кластеризации: процесс Дирихле для оценки параметров гауссовой смеси не кластеризуется


10

Настройка проблемы

Одной из первых игрушечных проблем, к которой я хотел применить PyMC, является непараметрическая кластеризация: с учетом некоторых данных смоделируйте их как гауссову смесь и узнайте количество кластеров, а также среднее значение и ковариацию каждого кластера. Большая часть того, что я знаю об этом методе, взята из видео-лекций Майкла Джордана и Йи Уай Тех, примерно в 2007 году (до того, как редкость стала яростью), а также в последние пару дней, читая уроки докторов Фоннесбека и Э. Чена [fn1], [ fn2]. Но проблема хорошо изучена и имеет несколько надежных реализаций [fn3].

В этой игрушечной задаче я генерирую десять ничьих из одномерного гауссовского и сорок ничьих из . Как вы можете видеть ниже, я не тасовал ничьи, чтобы было легко определить, какие образцы взяты из какого компонента смеси.N(μ=0,σ=1)N(μ=4,σ=2)

Данные модели гауссовой смеси

Я моделирую каждый образец данных yi∼N(μzi,σzi) , для i=1,...,50 и где zi указывает кластер для этой i й точки данных: zi∈[1,...,NDP] . NDP здесь - длина используемого усеченного процесса Дирихле: для меня NDP=50 .

Расширяя инфраструктуру процесса Дирихле, каждый идентификатор кластера представляет собой случайную из категориальной случайной величины, функция вероятности которой задается с помощью ломающей конструкции: с для параметр концентрации . Разрыв палки создает N- -длинный вектор , который должен суммироваться с 1, сначала получая iid-бета-раздач, которые зависят от , см. [Fn1]. И поскольку я хотел бы, чтобы данные сообщали о моем незнании , я следую [fn1] и предполагаю .zizi∼Categorical(p)p∼Stick(α)αNDPpNDPααα∼Uniform(0.3,100)

Это указывает, как генерируется идентификатор кластера каждого образца данных. Каждый из кластеров имеет ассоциированное среднее значение и стандартное отклонение, и . Затем и .NDPμziσziμzi∼N(μ=0,σ=50)σzi∼Uniform(0,100)

(Ранее я бездумно следил за [fn1] и помещал гиперприор в , то есть а сам по себе был ничьей из нормальное распределение с фиксированными параметрами и из униформы. Но согласно https://stats.stackexchange.com/a/71932/31187 , мои данные не поддерживают этот тип иерархической гиперприоры.)μziμzi∼N(μ0,σ0)μ0σ0

Итак, моя модель:

yi∼N(μzi,σzi) где запускаю от 1 до 50 (количество выборок данных).i

zi∼Categorical(p) и может принимать значения от 0 до ; , -длинный вектор; и , скаляр. (Теперь я немного сожалею о том, что количество выборок данных было равно усеченной длине Дирихле, но надеюсь, что это понятно.)NDP−1=49p∼Stick(α)NDPα∼Uniform(0.3,100)

μzi∼N(μ=0,σ=50) и . Существует этих средних и стандартных отклонений (по одному для каждого из возможных кластеров.)σzi∼Uniform(0,100)NDPNDP

Вот графическая модель: имена являются именами переменных, см. Раздел кода ниже.

график

Постановка задачи

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

В настоящее время я инициализирую большинство случайных величин с фиксированными значениями. Переменные среднего и стандартного отклонения инициализируются до их ожидаемых значений (т. Е. 0 для нормальных, середина их поддержки для однородных). Я инициализирую все идентификаторы кластера равными 0. И я инициализирую параметр концентрации .ziα=5

С такими инициализациями 100 000 итераций MCMC просто не могут найти второй кластер. Первый элемент близок к 1, и почти все отрисовки для всех выборок данных одинаковы, около 3,5. Я показываю каждый сотый тираж здесь для первых двадцати выборок данных, то есть для :pμziiμzii=1,...,20

Средства с нулевым инициализированным идентификатором кластера

Напоминая, что первые десять выборок данных были из одного режима, а остальные - из другого, приведенный выше результат явно не в состоянии это уловить.

Если я разрешу случайную инициализацию идентификаторов кластеров, то получу более одного кластера, но кластер означает, что все они бродят по одному и тому же уровню 3.5:

Средства со случайно инициализированным идентификатором кластера

Это наводит меня на мысль, что это обычная проблема с MCMC, что он не может достичь другого режима апостериора, отличного от того, в котором он находится: вспомните, что эти разные результаты происходят после простого изменения инициализации идентификаторов кластера , а не их приоров или что-нибудь еще.zi

Я делаю какие-либо ошибки моделирования? Аналогичный вопрос: https://stackoverflow.com/q/19114790/500207 хочет использовать распределение Дирихле и подгонять трехэлементную гауссову смесь и сталкивается с несколько схожими проблемами. Должен ли я рассмотреть возможность создания полностью сопряженной модели и использования выборки Гиббса для такого рода кластеризации? (Я реализовал сэмплер Гиббса для случая параметрического распределения Дирихле, за исключением использования фиксированной концентрации , когда-то в тот день, и он работал хорошо, поэтому ожидайте, что PyMC сможет решить хотя бы эту проблему легко.)α

Приложение: код

import pymc
import numpy as np

### Data generation

# Means and standard deviations of the Gaussian mixture model. The inference
# engine doesn't know these.
means = [0, 4.0]
stdevs = [1, 2.0]

# Rather than randomizing between the mixands, just specify how many
# to draw from each. This makes it really easy to know which draws
# came from which mixands (the first N1 from the first, the rest from
# the secon). The inference engine doesn't know about N1 and N2, only Ndata
N1 = 10
N2 = 40
Ndata = N1+N2

# Seed both the data generator RNG  as well as the global seed (for PyMC)
RNGseed = 123
np.random.seed(RNGseed)

def generate_data(draws_per_mixand):
    """Draw samples from a two-element Gaussian mixture reproducibly.

    Input sequence indicates the number of draws from each mixand. Resulting
    draws are concantenated together.

    """
    RNG = np.random.RandomState(RNGseed)
    values = np.hstack([RNG.normal(means[i], stdevs[i], ndraws)
                        for (i,ndraws) in enumerate(draws_per_mixand)])
    return values

observed_data = generate_data([N1, N2])


### PyMC model setup, step 1: the Dirichlet process and stick-breaking

# Truncation level of the Dirichlet process
Ndp = 50

# "alpha", or the concentration of the stick-breaking construction. There exists
# some interplay between choice of Ndp and concentration: a high concentration
# value implies many clusters, in turn implying low values for the leading
# elements of the probability mass function built by stick-breaking. Since we
# enforce the resulting PMF to sum to one, the probability of the last cluster
# might be then be set artificially high. This may interfere with the Dirichlet
# process' clustering ability.
#
# An example: if Ndp===4, and concentration high enough, stick-breaking might
# yield p===[.1, .1, .1, .7], which isn't desireable. You want to initialize
# concentration so that the last element of the PMF is less than or not much
# more than the a few of the previous ones. So you'd want to initialize at a
# smaller concentration to get something more like, say, p===[.35, .3, .25, .1].
#
# A thought: maybe we can avoid this interdependency by, rather than setting the
# final value of the PMF vector, scale the entire PMF vector to sum to 1? FIXME,
# TODO.
concinit = 5.0
conclo = 0.3
conchi = 100.0
concentration = pymc.Uniform('concentration', lower=conclo, upper=conchi,
                             value=concinit)

# The stick-breaking construction: requires Ndp beta draws dependent on the
# concentration, before the probability mass function is actually constructed.
betas = pymc.Beta('betas', alpha=1, beta=concentration, size=Ndp)

@pymc.deterministic
def pmf(betas=betas):
    "Construct a probability mass function for the truncated Dirichlet process"
    # prod = lambda x: np.exp(np.sum(np.log(x))) # Slow but more accurate(?)
    prod = np.prod
    value = map(lambda (i,u): u * prod(1.0 - betas[:i]), enumerate(betas))
    value[-1] = 1.0 - sum(value[:-1]) # force value to sum to 1
    return value

# The cluster assignments: each data point's estimated cluster ID.
# Remove idinit to allow clusterid to be randomly initialized:
idinit = np.zeros(Ndata, dtype=np.int64)
clusterid = pymc.Categorical('clusterid', p=pmf, size=Ndata, value=idinit)

### PyMC model setup, step 2: clusters' means and stdevs

# An individual data sample is drawn from a Gaussian, whose mean and stdev is
# what we're seeking.

# Hyperprior on clusters' means
mu0_mean = 0.0
mu0_std = 50.0
mu0_prec = 1.0/mu0_std**2
mu0_init = np.zeros(Ndp)
clustermean = pymc.Normal('clustermean', mu=mu0_mean, tau=mu0_prec,
                          size=Ndp, value=mu0_init)

# The cluster's stdev
clustersig_lo = 0.0
clustersig_hi = 100.0
clustersig_init = 50*np.ones(Ndp) # Again, don't really care?
clustersig = pymc.Uniform('clustersig', lower=clustersig_lo,
                          upper=clustersig_hi, size=Ndp, value=clustersig_init)
clusterprec = clustersig ** -2

### PyMC model setup, step 3: data

# So now we have means and stdevs for each of the Ndp clusters. We also have a
# probability mass function over all clusters, and a cluster ID indicating which
# cluster a particular data sample belongs to.

@pymc.deterministic
def data_cluster_mean(clusterid=clusterid, clustermean=clustermean):
    "Converts Ndata cluster IDs and Ndp cluster means to Ndata means."
    return clustermean[clusterid]

@pymc.deterministic
def data_cluster_prec(clusterid=clusterid, clusterprec=clusterprec):
    "Converts Ndata cluster IDs and Ndp cluster precs to Ndata precs."
    return clusterprec[clusterid]

data = pymc.Normal('data', mu=data_cluster_mean, tau=data_cluster_prec,
                   observed=True, value=observed_data)

Ссылки

  1. fn1: http://nbviewer.ipython.org/urls/raw.github.com/fonnesbeck/Bios366/master/notebooks/Section5_2-Dirichlet-Processes.ipynb
  2. fn2: http://blog.echen.me/2012/03/20/infinite-mixture-models-with-nonparametric-bayes-and-the-dirichlet-process/
  3. fn3: http://scikit-learn.org/stable/auto_examples/mixture/plot_gmm.html#example-mixture-plot-gmm-py

Ваш приоритет по сравнению с отклонениями компонентов равен Uniform (0,100), что может вызвать серьезные проблемы. Только 2% от массы этого априора покрывают истинные отклонения 1 и 2. Ожидаемое отклонение ваших компонентов в соответствии с этим априором составляет 50, что является настолько широким гауссовым значением, что он может легко учитывать ваши данные с помощью одного компонента.
— Джерад

Читали ли вы эту главу книги « Вероятностное программирование и байесовская статистика для хакеров» ? У него есть пример, который может вам помочь!
— Тим

Это кажется немного кратким для ответа. Похоже, это больше комментарий. Не могли бы вы хотя бы обрисовать, какую информацию ОП получит, прочитав ее?
— Glen_b

@TimRich Да, я читал это, посещал занятия в аспирантуре и работал в промышленности, занимаясь прикладной статистикой;) Это вопрос, связанный с PyMC.
— Ахмед Фасих

1
Я бы не отказался от иерархического приора. Общеизвестно, что вы ставите себя в плохое положение, если ставите плоские приоры на компоненты смеси - особенно когда вы одновременно пытаетесь узнать количество кластеров. Это то, что вызывает странные всплески на ваших графиках трассировки, я думаю. Все громкие имена в NP-Байесе, кажется, устанавливают и либо используют оценки подключаемых модулей либо размещают информативный априор для этих компонентов, предназначенных для положить их на соответствующие весы. μZi∼N(μ0,σ0)μ0,σ0
— парень

Ответы:


4

Я не уверен, что кто-то еще рассматривает этот вопрос, но я поставил ваш вопрос на тему «Раджагс», чтобы проверить предложение Тома Гиббса о пробоотборе, в то же время включив в него понимание Гая о плоском значении до стандартного отклонения.

Эта проблема с игрушкой может быть трудной, потому что 10 и даже 40 точек данных недостаточно для оценки дисперсии без информативного предварительного анализа. Текущий предыдущий σzi∼Uniform (0,100) не является информативным. Это может объяснить, почему почти все ничьи µzi являются ожидаемым средним для двух распределений. Если это не сильно изменит ваш вопрос, я буду использовать 100 и 400 точек данных соответственно.

Я также не использовал процесс взлома флешек прямо в моем коде. Страница википедии о процессе dirichlet заставила меня думать, что p ~ Dir (a / k) будет в порядке.

Наконец, это только полупараметрическая реализация, поскольку она все еще занимает несколько кластеров k. Я не знаю, как сделать модель бесконечной смеси в рьягах.

Марковская цепь мю кластер 1

Марковская цепь мю кластер 2

library("rjags")

set1 <- rnorm(100, 0, 1)
set2 <- rnorm(400, 4, 1)
data <- c(set1, set2)

plot(data, type='l', col='blue', lwd=3,
     main='gaussian mixture model data',
     xlab='data sample #', ylab='data value')
points(data, col='blue')

cpd.model.str <- 'model {
  a ~ dunif(0.3, 100)
  for (i in 1:k){
    alpha[i] <- a/k
    mu[i] ~ dnorm(0.0, 0.001)
    sigma[i] ~ dunif(0, 100)
  }
  p[1:k] ~ ddirich(alpha[1:k])
  for (i in 1:n){
    z[i] ~ dcat(p)
    y[i] ~ dnorm(mu[z[i]], pow(sigma[z[i]], -2))
  }
}' 


cpd.model <- jags.model(textConnection(cpd.model.str),
                        data=list(y=data,
                                  n=length(data),
                                  k=5))
update(cpd.model, 1000)
chain <- coda.samples(model = cpd.model, n.iter = 1000,
                      variable.names = c('p', 'mu', 'sigma'))
rchain <- as.matrix(chain)
apply(rchain, 2, mean)

1
Что касается использования конечного числа кластеров , вы можете просто взять большим (и установить , как у вас) и получить модель, которая фактически идентична процессу Дирихле. Сходимость этого до процесса Дирихле до этого очень быстрая; для наблюдений что-то вроде должно быть более чем достаточно. Это также умно, при использовании , чтобы не использовать палки-ломать конструкцию, потому что не обнаружат обновление блока на ; то, что вы сделали здесь, лучше. KKαi=a/K500K=25JAGSJAGSp
— парень

1

Плохое микширование, которое вы видите, скорее всего, связано с тем, как PyMC рисует образцы. Как объяснено в разделе 5.8.1 документации PyMC, все элементы переменной массива обновляются вместе. В вашем случае это означает, что он попытается обновить весь clustermeanмассив за один шаг, и аналогично для clusterid. PyMC не делает выборку Гиббса; это делает Метрополис, где предложение выбирается с помощью простой эвристики. Это делает маловероятным предложение хорошего значения для всего массива.


Как только вы сказали: «Он попытается обновить весь массив за один шаг», я понял недостатки Метрополиса (в данном случае) по сравнению с Гиббсом. Есть ли что-то особенное в STAN или JAGS, которое могло бы позволить им добиться большего успеха в этом? В любом случае, я собираюсь потратить некоторое время на реализацию Gibbs в PyMC. Спасибо! (Я был фанатом твоей работы со времен LightSpeed, так что спасибо тебе!)
— Ахмед Фасих,

1
STAN не обрабатывает дискретные переменные, но JAGS стоит попробовать.
— Том Минка
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.