Como gerar amostras uniformemente aleatoriamente a partir de múltiplas variáveis ​​discretas sujeitas a restrições?

8

Eu gostaria de gerar um processo de Monte Carlo para encher uma urna com N bolas de cores I, C [i]. Cada cor C [i] possui um número mínimo e máximo de bolas que devem ser colocadas na urna.

Por exemplo, estou tentando colocar 100 bolas na urna e posso preenchê-la com quatro cores:

  • Vermelho - Mínimo 0, Máximo 100 # NB, o máximo real não pode ser realizado.
  • Azul - Mínimo 50, Máximo 100
  • Amarelo - Mínimo 0, Máximo 50
  • Verde - mínimo 25, máximo 75

Como posso gerar uma amostra N que garante distribuição uniforme entre os possíveis resultados?

Vi soluções para esse problema em que as bolas não têm mínimo ou máximo ou, alternativamente, têm os mesmos mínimos e máximos implícitos. Veja, por exemplo, esta discussão sobre um assunto um pouco diferente:

Gerar pesos uniformemente distribuídos que somam a unidade?

Mas estou tendo problemas para generalizar esta solução.

GPB
fonte
1
Por "distribuído aleatoriamente" você quer dizer uniformemente distribuído aleatoriamente?
whuber
1
Sua descrição não é clara. Você está buscando o tipo de amostragem aleatória que corresponde a uma hipergeometria multivariada ou algo mais?
Glen_b -Reinstala Monica
@ whuber - sim, uniformemente distribuído aleatoriamente. Esclarecido acima.
GPB
Um amostrador do tipo Gibbs fará o trabalho muito bem, mesmo em versões muito maiores desse problema.
whuber

Respostas:

3

Deixe denotar o número de bolas da cor . Além disso, e denotam o número mínimo e máximo de bolas da cor , respectivamente. Queremos amostrar uniformemente aleatoriamente, sujeito às seguintes restrições:C i m i M i C i ( n 1 , , n I )niCimiMiCi(n1,,nI)

  1. miniMi
  2. i=1Ini=N

Primeiro, você pode remover as restrições do limite inferior (por exemplo, ) escolhendo bolas de cor inicialmente. Isso modifica as duas restrições da seguinte maneira:m i C iminimiCi

  1. 0nibi=Mimi
  2. i=1Ini=B=Ni=1Imi

Deixe denotar a distribuição uniforme em que estamos interessados. Podemos usar regras de cadeia e programação dinâmica para obter amostras de eficiência. Primeiro, usamos a regra da cadeia para escrever seguinte maneira: onde é a distribuição marginal de . Observe queP P P ( n 1 , , n IB , b 1 : I )P(n1,,nIB,b1:I)PP P(

P(n1,,nIB,b1:I)=P(nIB,b1:I)P(n1,,nI1nI,B,b1:I)=P(nIB,b1:I)P(n1,,nI1BnI,b1:I1)(1)
n I P ( n I | B , b 1 : I ) n I B -P(nI|B,b1:I)=n1,,nI1P(n1,,nI|B,b1:I)nIP(nI|B,b1:I)é uma distribuição discreta e pode ser computada eficientemente usando programação dinâmica. Observe também que o segundo termo em (1) pode ser calculado recursivamente. Nós provar na primeira rodada da recursão, atualizar o número total de bolas para e recurse para amostra na próxima rodada.nIn I - 1BnInI1

A seguir, é apresentada uma implementação da idéia no Matlab. A complexidade do algoritmo é onde . O código usa s gerados aleatoriamente em cada execução. Como resultado, alguns dos casos de teste gerados podem não ter uma solução válida. Nesse caso, o código imprime uma mensagem de aviso.O(I×B×K)m iK=maxi=1Ibimi

global dpm b

I = 5; % number of colors
N = 300; % total number of balls

m = randi(50, 1, I)-1; % minimum number of balls from each from each color
M = 99*ones(1, I); % maximum number of balls from each color

% print original constraints
print_constraints(I, N, m, M, 'original constraints');

% remove the lower bound constraints
b = M - m;
B = N - sum(m);
m = zeros(size(m));

% print transformed constraints
print_constraints(I, B, zeros(1, I), b, 'transformed constraints');

% initialize the dynamic programming matrix (dpm)
% if dpm(i, n) <> -1, it denotes the value of the following marginal probability
% \sum_{k=1}^{i-1} P(n_1, ..., n_i |
dpm = -ones(I, B);

% sample the number of balls of each color, one at a time, using chain rule
running_B = B;  % we change the value of "running_B" on the fly, as we sample balls of different colors
for i = I : -1 : 1
    % compute marginal distribution P(n_i)

    % instead of P(n_i) we compute q(n_i) which is un-normalized.
    q_ni = zeros(1, b(i) + 1); % possibilities for ni are 0, 1, ..., b(i)
    for ni = 0 : b(i)
        q_ni(ni+1) = dpfunc(i-1, running_B-ni);
    end
    if(sum(q_ni) == 0)
        fprintf('Impossible!!! constraints can not be satisfied!\n');
        return;
    end
    P_ni = q_ni / sum(q_ni);
    ni = discretesample(P_ni, 1) - 1;
    fprintf('n_%d=%d\n', i, ni);
    running_B = running_B - ni;
end

onde a função print_constraints é

function [] = print_constraints(I, N, m, M, note)
    fprintf('\n------ %s ------ \n', note);
    fprintf('%d <= n_%d <= %d\n', [m; [1:I]; M]);
    fprintf('========================\n');
    fprintf('sum_{i=1}^%d n_i = %d\n', I, N);
end

e a função dpfunc executa o cálculo da programação dinâmica da seguinte maneira:

function res = dpfunc(i, n)
    global dpm b

    % check boundary cases
    if(n == 0)
        res = 1;
        return;
    end
    if(i == 0) % gets here only if n <> 0
        res = 0;
        return;
    end

    if(n < 0)
        res = 0;
        return;
    end

    if(dpm(i, n) == -1) % if <> -1, it has been compute before, so, just use it!
        % compute the value of dpm(i, n) = \sum_{n_1, ..., n_i} valid(n, n_1, ..., n_i)
        % where "valid" return 1 if \sum_{j=1}^i n_i = n and 0 <= n_i <= b_i, for all i
        % and 0 otherwise.
        dpm(i, n) = 0;
        for ni = 0 : b(i)
            dpm(i, n) = dpm(i, n) + dpfunc(i-1, n-ni);
        end
    end
    res = dpm(i, n);
end

e, finalmente, a função de amostra discreta (p, 1) extrai uma amostra aleatória da distribuição discreta . Você pode encontrar uma implementação dessa função aqui .p

Sobi
fonte
1
Você poderia explicar por que você acha que a distribuição marginal é multinomial?
whuber
Eu quis dizer distribuição categórica / discreta, obrigado por identificá-la. Acabei de corrigir na minha resposta!
Sobi 01/12/2015
1
@sobi - O que significa a linha de código: q_ni (ni + 1) = dpfunc (i-1, running_B texto forte -ni)? Existe algum problema de formatação?
GPB
@ GPP: não tenho certeza de quão forte o texto chegou lá! Tem que ser removido. Obrigado por detectar isso; fixo! O código demora um pouco para ser executado (alguns segundos) porque possui muitos loops, instruções if e chamadas de funções recursivas, que são particularmente lentas no Matlab; portanto, uma implementação C ++ seria muito mais rápida!
Sobi 03/12
@Sobi - Eu estou codificação em Python, vou compartilhar com você quando terminar ....
GPB
2

Vamos considerar uma generalização desse problema. Existem latas de tinta de cores distintas e bolas. Pode pode conter até bolas. Você deseja gerar configurações de bolas nas latas com pelo menos bolas em can para cada , cada configuração com igual probabilidade.m = 4 n ( 0 ) = 100 i a ( 0 ) i = ( 100 , 100 , 50 , 75 ) b i = ( 0 , 50 , 0 , 25 ) i im=4m=4n(0)=100iai(0)=(100,100,50,75)bi=(0,50,0,25)ii

Essas configurações estão em correspondência individual com as configurações obtidas após a remoção de bolas do can , limitando bolas restantes para no máximo por lata. Portanto, apenas e deixarei ajustá-los depois (colocando bolas volta no can para cada ). i N = N ( 0 ) - Σ i b i = 100 - ( 0 + 50 + 0 + 25 ) = 25 um i = um ( 0 ) i - b i = ( 100 , 50 , 50 , 50 )biin=n(0)ibi=100(0+50+0+25)=25ai=ai(0)bi=(100,50,50,50) i ibiii

Para contar essas configurações, corrija todos os índices, com exceção de dois, digamos e . Suponhamos que existem bolas já no pode para cada diferindo e . Isso deixa bolas. Condicional em que o outro bolas são, estes são distribuídos uniformemente dentro de latas e . As configurações possíveis são em número (ver as observações), variando de colocar o maior número de bolas pode emj s k k k i j s i + s j n - ( s i + s j ) i jijskkkijsi+sjn(si+sj)ij1+min(ai+ajsisj,si+sj)iquanto possível durante todo o tempo de colocar o maior número de bolas em lata quanto possível.j

Se desejar, você pode contar o número total de configurações aplicando esse argumento recursivamente às latas restantes . No entanto, para obter amostras , nem precisamos saber essa contagem. Tudo o que precisamos fazer é visitar repetidamente todos os possíveis pares não ordenados de latas e alterar aleatoriamente (e uniformemente) a distribuição de bolas nessas duas latas. Esta é uma cadeia de Markov com uma distribuição de probabilidade limitadora que é uniforme em todos os estados possíveis (como é facilmente mostrado usando métodos padrão). Portanto, basta iniciar em qualquerm2{i,j}estado, execute a cadeia por tempo suficiente para alcançar a distribuição limitadora e depois acompanhe os estados visitados por este procedimento. Como de costume, para evitar correlação serial, essa sequência de estados deve ser "reduzida", pulando-os (ou revisitados aleatoriamente). O desbaste por um fator de cerca da metade do número de latas tende a funcionar bem, porque depois disso muitas etapas, em média, cada lata pode ser afetada, produzindo uma configuração genuinamente nova.

Esse algoritmo custa esforço para gerar cada configuração aleatória em média. Embora existam outros algoritmos , este possui a vantagem de não precisar fazer os cálculos combinatórios antecipadamente.O(m)O(m)


Como exemplo, vamos resolver uma situação menor manualmente. Deixe que e , por exemplo. Existem 15 configurações válidas, que podem ser escritas como cadeias de números de ocupação. Por exemplo, coloca duas bolas na segunda lata e uma bola na quarta lata. Emulando o argumento, vamos considerar a ocupação total das duas primeiras latas. Quando são bolas, nenhuma bola é deixada para as duas últimas latas. Isso dá aos estadosa=(4,3,2,1)n=30201s1+s2=3

30**, 21**, 12**, 03**

onde **representa todos os números de ocupação possíveis para as duas últimas latas: a saber 00,. Quando , os estados sãos1+s2=2

20**, 11**, 02**

onde agora **pode ser um 10ou outro 01. Isso dá mais estados. Quando , os estados são3×2=6s1+s2=1

10**, 01**

onde agora **pode estar 20, 11mas não 02 (devido ao limite de uma bola na última lata). Isso dá mais estados. Finalmente, quando , todas as bolas estão nas duas últimas latas, que devem estar cheias até o limite de e . Os estados igualmente prováveis são, portanto,2×2=4s1+s2=0214+6+4+1=15

3000, 2100, 1200, 0300; 2010, 2001, 1110, 1101, 0210, 0201; 1020, 1011, 0120, 0111; 0021.

Usando o código abaixo, uma sequência de dessas configurações foi gerada e reduzida a cada terceira, criando configurações dos estados. Suas frequências eram as seguintes:10,009333715

State: 3000 2100 1200 0300 2010 1110 0210 1020 0120 2001 1101 0201 1011 0111 0021 
Count:  202  227  232  218  216  208  238  227  237  209  239  222  243  211  208 

A teste de uniformidade dá uma valor de , ( graus de liberdade): o que é belo acordo com a hipótese de que este procedimento produz estados igualmente prováveis.χ 2 11,2χ2χ211.2p=0.6714


Este Rcódigo está configurado para lidar com a situação na pergunta. Mude ae ntrabalhe com outras situações. Defina Ncomo grande o suficiente para gerar o número de realizações necessárias após o desbaste .

Esse código engana um pouco, alternando sistematicamente todos os pares . Se você quiser ser rigoroso sobre o funcionamento da cadeia de Markov, gerar , e de forma aleatória, tal como consta no código comentado. (i,j)ijij

#
# Gibbs-like sampler.
#
# `a` is an array of maximum numbers of balls of each type.  Its values should
#     all be integers greater than zero.
# `n` is the total number of balls.
#------------------------------------------------------------------------------#
g <- function(j, state, a) {
  #
  # `state` contains the occupancy numbers.
  # `a`     is the array of maximum occupancy numbers.
  # `j`     is a pair of indexes into `a` to "rotate".
  #
  k <- sum(state[j]) # Total occupancy.
  x <- floor(runif(1, max(0, k - a[j[2]]), min(k, a[j[1]]) + 1))
  state[j] <- c(x, k-x)
  return(state)
}
#
# Set up the problem.
#
a <- c(100, 50, 50, 50)
n <- 25

# a <- 4:1
# n <- 3
#
# Initialize the state.
#
state <- round(n * a / sum(a))
i <- 1
while (sum(state) < n) {
  if (state[i] < a[i]) state[i] <- state[i] + 1
  i <- i+1
}
while (sum(state) > n) {
  i <- i-1
  if (state[i] > 0) state[i] <- state[i] - 1
}
#
# Conduct a sequence of random changes.
#
set.seed(17)
N <- 1e5
sim <- matrix(state, ncol=1)
u <- ceiling(N / choose(length(state), 2) / 2)
i <- rep(rep(1:length(state), each=length(state)-1), u)
j <- rep(rep(length(state):1, length(state)-1), u)
ij <- rbind(i, j)
#
# Alternatively, generate `ij` randomly:
#   i <- sample.int(length(state), N, replace=TRUE)
#   j <- sample.int(length(state)-1, N, replace=TRUE)
#   ij <- rbind(i, ((i+j-1) %% length(state))+1)
#
sim <- cbind(sim, apply(ij, 2, function(j) {state <<- g(j, state, a); state}))
rownames(sim) <- paste("Can", 1:nrow(sim))
#
# Thin them for use.  Each column is a state.
#
thin <- function(x, stride=1, start=1) {
  i <- round(seq(start, ncol(x), by=stride))
  x[, i]
}
#
# Make a scatterplot of the results, to illustrate.
#
par(mfrow=c(1,1))
s <- thin(sim, stride=max(1, N/1e4))
pairs(t(s) + runif(length(s), -1/2, 1/2), cex=1/2, col="#00000005", pch=16)
whuber
fonte
muito obrigado ... Estou processando esta e a outra resposta hoje à noite e espero reverter amanhã.
GPB
1
Você pode explicar por que o número de maneiras possíveis de distribuir bolas si + sj nas latas iej é 1 + ai + aj-si-sj? Por exemplo, se ai = 0, existe apenas uma maneira possível (ou seja, colocar todas as bolas si + sj no can j). Mas, de acordo com sua fórmula, deve haver 1 + 0 + aj + 0-sj = 1 + aj-sj maneiras possíveis, e aj-sj pode ser escolhido para que o número seja arbitrariamente maior que 1. Além disso, você pode explicar por que o tempo de execução do algoritmo é O (m)?
Sobi 02/12/2015
1
@Sobi Existem espaços disponíveis e bolas para colocar neles. As configurações correspondem ao layout de slots em uma linha com um divisor entre eles (para separar as duas latas) e ao preenchimento de uma sequência contígua de desses slots que inclui ou é adjacente ao divisor. Isso significa que quando , o valor é apenas , portanto, a quantidade correta é . Eu incorporei essa mudança nesta resposta. Obrigado por apontar isto! O algoritmo, como encapsulado na função , é obviamente . ai+ajsi+sjai+ajsi+sjsi+sj<ai+ajsi+sj+1min(ai+ajsisj,si+sj)+1gO(m)
whuber
1
Considere . A nova fórmula fornece mas existem apenas 2 maneiras possíveis. Eu acho que a seguinte fórmula deve funcionar: . A primeira segunda quantidade / é o número máximo / mínimo de esferas que pode possa ter. min ( 1 + 3 - 2 , 2 ) + 1 = 3 min ( um i , s i + s j ) - max ( 0 , s i + s j - a j ) + 1 iai=1,aj=3,si+sj=2min(1+32,2)+1=3min(ai,si+sj)max(0,si+sjaj)+1i
Sobi 02/12/2015
1
Sim, acredito que o tempo de mistura é mas não importa se é . Com iterações suficientes (que temos ao contemplar resultados assintóticos), a contribuição unitária da mistura é , que é desprezível. No entanto, é porque cria um novo estado cada vez que é executado e simplesmente emitir um estado é uma operação . O desbaste também não prejudica o resultado. Uma maneira de diminuir é criar um grande número de realizações e reordená-las (talvez movendo-se ciclicamente por elas com um grande passo), o que exige apenas esforço. O ( f ( m ) ) N O ( f ( m ) / N ) O ( m ) O ( m ) N O ( N )O(m)O(f(m))NO(f(m)/N)gO(m)O(m)NO(N)
whuber