¿Cómo generar muestras uniformemente al azar a partir de múltiples variables discretas sujetas a restricciones?

8

Me gustaría generar un proceso de Monte Carlo para llenar una urna con N bolas de colores I, C [i]. Cada color C [i] tiene un número mínimo y máximo de bolas que deben colocarse en la urna.

Por ejemplo, estoy tratando de poner 100 bolas en la urna y puedo llenarla con cuatro colores:

  • Rojo - Mínimo 0, Máximo 100 # NB, no se puede alcanzar el máximo real.
  • Azul: mínimo 50, máximo 100
  • Amarillo: mínimo 0, máximo 50
  • Verde: mínimo 25, máximo 75

¿Cómo puedo generar N muestras que se distribuyan uniformemente entre los posibles resultados?

He visto soluciones a este problema donde las bolas no tienen mínimo o máximo, o alternativamente, tienen los mismos mínimos y máximos implícitos. Vea, por ejemplo, esta discusión sobre un tema ligeramente diferente:

¿Generar pesos distribuidos uniformemente que sumen a la unidad?

Pero tengo problemas para generalizar esta solución.

GPB
fuente
1
¿Por "distribuido aleatoriamente" quiere decir distribuido uniformemente aleatoriamente?
whuber
1
Tu descripción no es muy clara. ¿Busca el tipo de muestreo aleatorio que corresponde a un hipergeométrico multivariado, o algo más?
Glen_b -Reinstale a Monica el
@whuber: sí, uniformemente distribuido al azar. Aclarado arriba.
GPB
Una muestra tipo Gibbs hará el trabajo bien incluso para versiones mucho más grandes de este problema.
whuber

Respuestas:

3

Dejar ni denotar la cantidad de bolas de color Ci. Además, dejami y Mi denota el número mínimo y máximo de bolas de color Ci, respectivamente. Queremos probar(n1,,nI) uniformemente al azar sujeto a las siguientes restricciones:

  1. miniMi
  2. i=1Ini=N

En primer lugar, puede eliminar las restricciones de límite inferior (es decir, mini) eligiendo mi bolas de color Ciinicialmente. Esto modifica las dos restricciones de la siguiente manera:

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

Dejar P(n1,,nIB,b1:I)denotan la distribución uniforme que nos interesa. Podemos usar reglas de cadena y programación dinámica para tomar muestras dePeficientemente. Primero, usamos la regla de la cadena para escribirP como sigue:

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)
dónde P(nI|B,b1:I)=n1,,nI1P(n1,,nI|B,b1:I) es la distribución marginal de nI. Tenga en cuenta queP(nI|B,b1:I)es una distribución discreta y se puede calcular de manera eficiente mediante programación dinámica. Además, tenga en cuenta que el segundo término en (1) se puede calcular de forma recursiva. Nosotros muestranI en la primera ronda de la recursividad, actualice el número total de bolas a BnI y recurse a la muestra nI1 en la siguiente ronda

La siguiente es una implementación de Matlab de la idea. La complejidad del algoritmo esO(I×B×K) dónde K=maxi=1Ibi. El código utiliza s generados aleatoriamente en cada ejecución. Como resultado, algunos de los casos de prueba generados pueden no tener una solución válida, en cuyo caso el código imprime un mensaje de advertencia.mi

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

donde la función print_constraints es

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

y la función dpfunc realiza el cálculo de programación dinámica de la siguiente manera:

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

y finalmente, la función muestra discreta (p, 1) extrae una muestra aleatoria de la distribución discretap. Puede encontrar una implementación de esta función aquí .

Sobi
fuente
1
¿Podría explicar por qué cree que la distribución marginal es multinomial?
whuber
Me refería a una distribución categórica / discreta, gracias por detectarlo. ¡Acabo de corregirlo en mi respuesta!
Sobi
1
@sobi - ¿Qué se entiende por la línea de código: q_ni (ni + 1) = dpfunc (i-1, running_B texto fuerte -ni)? ¿Hay algún problema de formato?
GPB
@GPB: ¡no estoy seguro de cuán fuerte llegó el texto ! Tiene que ser eliminado. Gracias por ver esto; ¡fijo! El código tarda un tiempo en ejecutarse (un par de segundos) porque tiene muchos bucles, sentencias if y llamadas a funciones recursivas, que son particularmente lentas en Matlab, por lo que una implementación de C ++ se ejecutaría mucho más rápido.
Sobi
@Sobi - Estoy codificando en Python, lo compartiré contigo cuando termine ...
GPB
2

Consideremos una generalización de este problema. Existenm=4 latas de pintura de m=4 colores distintos y n(0)=100pelotas. latai puede aguantar hasta ai(0)=(100,100,50,75)pelotas. Desea generar configuraciones de bolas en las latas que tienen al menosbi=(0,50,0,25) bolas en lata i para cada i, cada configuración con igual probabilidad.

Dichas configuraciones están en correspondencia uno a uno con las configuraciones obtenidas después de eliminar bi bolas de lata i, limitando el n=n(0)ibi=100(0+50+0+25)=25 bolas restantes a lo sumo ai=ai(0)bi=(100,50,50,50)por lata. Por lo tanto, solo generaré estos y te dejaré ajustarlos después (poniendobi bolas de vuelta en lata i para cada i)

Para contar estas configuraciones, arregle todos menos dos de los índices, digamos i y j. Supongamos que haysk bolas ya en lata k para cada k diferente de i y j. Eso dejasi+sjpelotas. Condicional a donde el otron(si+sj)las bolas son, estas se distribuyen uniformemente dentro de latasi y j. Las configuraciones posibles son1+min(ai+ajsisj,si+sj) en número (ver los comentarios), que van desde colocar tantas bolas en la lata i como sea posible a través de la colocación de tantas bolas en la lata j como sea posible.

Si lo desea, puede contar el número total de configuraciones aplicando este argumento de forma recursiva al resto m2latas Sin embargo, para obtener muestras ni siquiera necesitamos saber este recuento. Todo lo que necesitamos hacer es visitar repetidamente todos los pares desordenados posibles{i,j}de latas y al azar (y de manera uniforme) cambian la distribución de las bolas dentro de esas dos latas. Esta es una cadena de Markov con una distribución de probabilidad limitante que es uniforme en todos los estados posibles (como se muestra fácilmente usando métodos estándar). Por lo tanto, es suficiente comenzar en cualquier estado, ejecutar la cadena el tiempo suficiente para alcanzar la distribución limitante y luego realizar un seguimiento de los estados visitados por este procedimiento. Como de costumbre, para evitar la correlación en serie, esta secuencia de estados debe "diluirse" saltando a través de ellos (o revisada al azar). El adelgazamiento por un factor de aproximadamente la mitad del número de latas tiende a funcionar bien, porque después de eso, en promedio, muchos pasos se han visto afectados, produciendo una configuración realmente nueva.

Este algoritmo cuesta O(m)esfuerzo para generar cada configuración aleatoria en promedio. Aunque otroO(m) existen algoritmos, este tiene la ventaja de no necesitar hacer cálculos combinatorios de antemano.


Como ejemplo, resolvamos una situación más pequeña manualmente. Dejara=(4,3,2,1) y n=3, por ejemplo. Hay 15 configuraciones válidas, que pueden escribirse como cadenas de números de ocupación. Por ejemplo, 0201coloca dos bolas en la segunda lata y una bola en la cuarta lata. Emulando el argumento, consideremos la ocupación total de las dos primeras latas. Cuando eso ess1+s2=3bolas, no quedan bolas para las dos últimas latas. Eso le da a los estados

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

donde **representa a todos los posibles números de ocupación para las dos últimas latas: a saber, 00. Cuandos1+s2=2, los estados son

20**, 11**, 02**

donde ahora **puede ser cualquiera 10o 01. Eso da3×2=6más estados Cuandos1+s2=1, los estados son

10**, 01**

donde ahora **puede estar 20, 11pero no 02 (debido al límite de una bola en la última lata). Eso da2×2=4más estados Finalmente cuandos1+s2=0, todas las bolas están en las dos últimas latas, que deben estar llenas hasta el límite de 2 y 1. los4+6+4+1=15 estados igualmente probables por lo tanto son

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

Usando el siguiente código, una secuencia de 10,009 tales configuraciones fueron generadas y reducidas a cada tercio, creando 3337 configuraciones de la 15estados. Sus frecuencias fueron las siguientes:

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 

UNA χ2 prueba de uniformidad da un χ2 valor de 11.2, p=0.67 (14 grados de libertad): es un hermoso acuerdo con la hipótesis de que este procedimiento produce estados igualmente probables.


Este Rcódigo está configurado para manejar la situación en la pregunta. Cambiar ay ntrabajar con otras situaciones. Establezca Nque sea lo suficientemente grande como para generar la cantidad de realizaciones que necesita después del adelgazamiento .

Este código engaña un poco al recorrer sistemáticamente todos (i,j)pares. Si quieres ser estrictos sobre la ejecución de la cadena de Markov, generar i, jy ijal azar, como se da en el código comentado.

#
# 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
fuente
muchas gracias ... Estoy procesando esta y la otra respuesta esta noche y espero volver mañana.
GPB
1
¿Puede explicar por qué la cantidad de formas posibles de distribuir bolas si + sj en las latas i y j es 1 + ai + aj − si − sj? Por ejemplo, si ai = 0, solo hay una forma posible (es poner todas las bolas si + sj en can j). Pero, de acuerdo con su fórmula, debe haber 1 + 0 + aj + 0-sj = 1 + aj-sj posibles, y se puede elegir aj-sj para que el número sea arbitrariamente mayor que 1. Además, ¿puede explicarlo? ¿Por qué el tiempo de ejecución del algoritmo es O (m)?
Sobi
1
@Sobi Hay ai+aj espacios disponibles y si+sjbolas para poner en ellos. Las configuraciones corresponden a la disposiciónai+ajranuras en una fila con un divisor entre ellas (para separar las dos latas) y llenar una secuencia contigua desi+sjde esas ranuras que incluye o está adyacente al divisor. Eso significa que cuandosi+sj<ai+aj, el valor es solo si+sj+1, entonces la cantidad correcta es min(ai+ajsisj,si+sj)+1. He incorporado ese cambio en esta respuesta. ¡Gracias por señalar esto! El algoritmo, como encapsulado en función g, es obviamenteO(m).
whuber
1
Considerar ai=1,aj=3,si+sj=2. La nueva fórmula damin(1+32,2)+1=3pero solo hay 2 formas posibles. Creo que la siguiente fórmula debería funcionar:min(ai,si+sj)max(0,si+sjaj)+1. La primera / segunda cantidad es el número máximo / mínimo de bolas que puedenipuede tener.
Sobi
1
Sí, creo que el tiempo de mezcla es O(m)- pero no importa si es O(f(m)). Con suficientes iteracionesN (que tenemos cuando contemplamos resultados asintóticos) la contribución unitaria de la mezcla es O(f(m)/N), que es insignificante. Sin embargo, gesO(m) porque crea un nuevo estado cada vez que se ejecuta y simplemente generar un estado es un O(m)operación. El adelgazamiento tampoco perjudica el resultado. Una forma de adelgazar es crear un gran númeroN de realizaciones y reordenarlas (quizás moviéndose a través de ellas cíclicamente con un gran paso), lo que solo requiere O(N)esfuerzo.
whuber