Come campionare una distribuzione multinomiale troncata?


9

Ho bisogno di un algoritmo per campionare una distribuzione multinomiale troncata. Questo è,

x1Zp1x1pkxkx1!xk!

dove è una costante di normalizzazione, ha componenti positivi e . Considero solo i valori di nell'intervallo .x k x i = n x axbZxkxi=nxaxb

Come posso campionare questa distribuzione multinomiale troncata?

Nota: consultare Wikipedia per un algoritmo per campionare una distribuzione multinomiale non troncata. C'è un modo per adattare questo algoritmo a una distribuzione troncata?

Versione uniforme: una versione più semplice del problema è prendere tutti i uguali, . Se è possibile progettare un algoritmo per campionare almeno la distribuzione troncata in questo caso, si prega di pubblicarlo. Sebbene non sia la risposta generale, ciò mi aiuterebbe a risolvere altri problemi pratici al momento.p i = 1 / kpipi=1/k

Risposte:


9

Se ti capisco correttamente, vuoi campionare i valori dalla distribuzione multinomiale con probabilità tali che , tuttavia vuoi che la distribuzione venga troncata così per tutti x_i .p 1 , , p k i x i = n ax1,,xkp1,,pkixi=nx iaixibixi

Vedo tre soluzioni (né eleganti come nel caso non troncato):

  1. Accept-rifiuto. Campione da multinomio non troncato, accettare il campione se si adatta ai limiti del troncamento, altrimenti rifiutare e ripetere il processo. È veloce, ma può essere molto inefficiente.
rtrmnomReject <- function(R, n, p, a, b) {
  x <- t(rmultinom(R, n, p))
  x[apply(a <= x & x <= b, 1, all) & rowSums(x) == n, ]
}
  1. Simulazione diretta. Fai un esempio di moda che assomigli al processo di generazione dei dati, ovvero campiona un singolo marmo da un'urna casuale e ripeti questo processo fino a quando non hai campionato marmi in totale, ma mentre distribuisci il numero totale di marmi da una determinata urna ( è già uguale a ) quindi smettere di disegnare da tale urna. L'ho implementato in uno script di seguito.x i b inxibi
# single draw from truncated multinomial with a,b truncation points
rtrmnomDirect <- function(n, p, a, b) {
  k <- length(p)

  repeat {
    pp <- p         # reset pp
    x <- numeric(k) # reset x
    repeat {
      if (sum(x<b) == 1) { # if only a single category is left
        x[x<b] <- x[x<b] + n-sum(x) # fill this category with reminder
        break
      }
      i <- sample.int(k, 1, prob = pp) # sample x[i]
      x[i] <- x[i] + 1  
      if (x[i] == b[i]) pp[i] <- 0 # if x[i] is filled do
      # not sample from it
      if (sum(x) == n) break    # if we picked n, stop
    }
    if (all(x >= a)) break # if all x>=a sample is valid
    # otherwise reject
  }

  return(x)
}
  1. Algoritmo Metropolis. Infine, il terzo e più efficace approccio sarebbe quello di utilizzare l' algoritmo Metropolis . L'algoritmo viene inizializzato utilizzando la simulazione diretta (ma può essere inizializzato in modo diverso) per disegnare il primo campione . Nei passaggi seguenti in modo iterativo: il valore della proposta viene accettato come con probabilità , altrimenti viene preso il valore è il luogo, dove. Come proposta ho usato la funzione che accetta il valore e capovolge casualmente da 0 al numero di casi e lo sposta in un'altra categoria.X1y=q(Xi1)Xif(y)/f(Xi1)Xi1f(x)ipixi/xi!qXi1step
# draw R values
# 'step' parameter defines magnitude of jumps
# for Meteropolis algorithm
# 'init' is a vector of values to start with
rtrmnomMetrop <- function(R, n, p, a, b,
                          step = 1,
                          init = rtrmnomDirect(n, p, a, b)) {

  k <- length(p)
  if (length(a)==1) a <- rep(a, k)
  if (length(b)==1) b <- rep(b, k)

  # approximate target log-density
  lp <- log(p)
  lf <- function(x) {
    if(any(x < a) || any(x > b) || sum(x) != n)
      return(-Inf)
    sum(lp*x - lfactorial(x))
  }

  step <- max(2, step+1)

  # proposal function
  q <- function(x) {
    idx <- sample.int(k, 2)
    u <- sample.int(step, 1)-1
    x[idx] <- x[idx] + c(-u, u)
    x
  }

  tmp <- init
  x <- matrix(nrow = R, ncol = k)
  ar <- 0

  for (i in 1:R) {
    proposal <- q(tmp)
    prob <- exp(lf(proposal) - lf(tmp))
    if (runif(1) < prob) {
      tmp <- proposal
      ar <- ar + 1
    }
    x[i,] <- tmp
  }

  structure(x, acceptance.rate = ar/R, step = step-1)
}

L'algoritmo inizia da e quindi si aggira tra le diverse regioni di distribuzione. È ovviamente più veloce di quelli precedenti, ma è necessario ricordare che se lo si usasse per campionare un numero limitato di casi, si potrebbe finire con disegni vicini l'uno all'altro. Un altro problema è che devi decidere in merito alle dimensioni, ovvero a quali grandi salti dovrebbe fare l'algoritmo: troppo piccolo può portare a muoversi lentamente, troppo grande può portare a fare troppe proposte non valide e respingerle. Di seguito puoi vedere un esempio del suo utilizzo. Sui grafici è possibile visualizzare: densità marginali nella prima riga, grafici di traccia nella seconda riga e grafici che mostrano i salti successivi per coppie di variabili.X1step

n <- 500
a <- 50
b <- 125
p <- c(1,5,2,4,3)/15
k <- length(p)
x <- rtrmnomMetrop(1e4, n, p, a, b, step = 15)

cmb <- combn(1:k, 2)

par.def <- par(mfrow=c(4,5), mar = c(2,2,2,2))
for (i in 1:k)
  hist(x[,i], main = paste0("X",i))
for (i in 1:k)
  plot(x[,i], main = paste0("X",i), type = "l", col = "lightblue")
for (i in 1:ncol(cmb))
  plot(jitter(x[,cmb[1,i]]), jitter(x[,cmb[2,i]]),
       type = "l", main = paste(paste0("X", cmb[,i]), collapse = ":"),
       col = "gray")
par(par.def)

inserisci qui la descrizione dell'immagine

Il problema con il campionamento da questa distribuzione è che descrive una strategia di campionamento molto inefficiente in generale. Immagina che e , e 's siano vicini a 's, in tal caso desideri campionare categorie con diverse probabilità, ma ti aspetti simili frequenze alla fine. In casi estremi, immagina una distribuzione a due categorie dove e ,a 1 = = a k b 1 = b k a i b i p 1pp1pka1==akb1=bkaibip1p2a1a2b1b2, in tal caso ti aspetti che accada qualcosa di molto raro (un esempio di tale distribuzione nella vita reale sarebbe il ricercatore che ripete il campionamento fino a quando non trova il campione coerente con la sua ipotesi, quindi ha più a che fare con il tradimento che con il campionamento casuale) .

La distribuzione è molto meno problematica se la si definisce come Rukhin (2007, 2008) in cui si campionano casi in ciascuna categoria, vale a dire campionati proporzionalmente a quelli di .p inpipi


Rukhin, AL (2007). Statistiche dell'ordine normale e somme di variabili casuali geometriche nei problemi di allocazione del trattamento. Lettere statistiche e probabilità, 77 (12), 1312-1321.

Rukhin, AL (2008). Arresto delle regole nei problemi di allocazione bilanciata: distribuzioni esatte e asintotiche. Analisi sequenziale, 27 (3), 277-292.


Il rifiuto di campioni non validi potrebbe essere troppo lento. Probabilmente è più semplice fare una traduzione, , . In questo modo hai solo il limite superiore, di cui preoccuparti. Quindi è possibile rimuovere la riga in cui si rifiuta il campione se viene violata la (si possono concepire i valori di dove questo rifiuto sarebbe molto inefficiente) m = n - i a i y ib i - a i x a ayi=xiaim=niaiyibiaixaa
becko,

@becko se confronti tale approccio con quello descritto da me vedrai che offrono soluzioni diverse .
Tim

Non capisco come possano essere diversi? Tutto quello che ho fatto è stato un cambiamento di variabili.
becko,

@becko il tuo punto di partenza è tutto qui x[i] >= a. Immagina di aver lanciato una moneta distorta con probabilità di testa = 0,9. Getti la moneta fino ad ottenere almeno 10 teste e 10 code. Al punto di arresto avresti in media molte più teste che code. Iniziare da x[1] = ... = x[k] = asignifica che si ignora il fatto che i punti di partenza di ciascuno di essi x[i]sono diversi a causa di quelli diversi p[i].
Tim

Vedo il tuo punto. L'unica cosa che non mi piace della tua soluzione è che penso che potrebbe essere molto inefficiente per particolari scelte dei parametri.
becko,

1

Ecco il mio sforzo nel tentativo di tradurre il codice R di Tim in Python. Dato che ho trascorso un po 'di tempo a comprendere questo problema e ho codificato gli algoritmi in Python, ho pensato di condividerli qui nel caso le persone fossero interessate.

  1. Algoritmo Accept-Reject :
def sample_truncated_multinomial_accept_reject(k, pVec, a, b):
    x = list(np.random.multinomial(k, pVec, size=1)[0])
    h = [x[i] >= a[i] and x[i] <= b[i] for i in range(len(x))]
    while sum(h) < len(h):
        x = list(np.random.multinomial(k, pVec, size=1)[0])
        h = [x[i] >= a[i] and x[i] <= b[i] for i in range(len(x))]
    return x
  1. Simulazione diretta
def truncated_multinomial_direct_sampling_from_urn(k, pVec, a, b):
    n = len(pVec)
    while True:
        pp = pVec 
        x = [0 for _ in range(n)] 
        while True:
            if sum([x[h] < b[h] for h in range(n)])==1:
                indx = [h for h in range(n) if x[h] < b[h]][0]
                x[indx] = k - sum(x)
                break
            i = np.random.choice(n, 1, p=pp)[0]
            x[i] += 1
            if x[i] == b[i]:
                pp = [pp[j]/(1-pp[i]) for j in range(n)]
                pp[i] = 0 
            if sum(x) == k:
                break  
        if sum([x[h] < a[h] for h in range(n)]) == 0:
            break 
    return x 
  1. Algoritmo Metropolis
def compute_log_function(x, pVec, a, b):
    x_less_a = sum([x[i] < a[i] for i in range(len(pVec))])
    x_more_a = sum([x[i] > b[i] for i in range(len(pVec))])
    if x_less_a or x_more_a or sum(x) != k:
        return float("-inf")
    return np.sum(np.log(pVec)*x - np.array([math.lgamma(h+1) for h in x]))
def sampling_distribution(original, pVec, a, b, step):
    x = copy.deepcopy(original) 
    idx = np.random.choice(len(x), 2, replace=False)
    u = np.random.choice(step, 1)[0]
    x[idx[0]] -= u
    x[idx[1]] += u
    x_less_a = sum([x[i] < a[i] for i in range(len(pVec))])
    x_more_a = sum([x[i] > b[i] for i in range(len(pVec))])
    while x_less_a or x_more_a or sum(x) != k:
        x = copy.deepcopy(original)  
        idx = np.random.choice(len(x), 2, replace=False)
        u = np.random.choice(step, 1)[0]
        x[idx[0]] -= u
        x[idx[1]] += u
        x_less_a = sum([x[i] < a[i] for i in range(len(pVec))])
        x_more_a = sum([x[i] > b[i] for i in range(len(pVec))])
    return x 
def sample_truncated_multinomial_metropolis_hasting(k, pVec, a, b, iters, step=1):
    tmp=sample_truncated_multinomial_accept_reject(k, pVec, a, b)[0]
    step = max(2, step)
    for i in range(iters):
        proposal = sampling_distribution(tmp, pVec, a, b, step)
        if compute_log_function(proposal, pVec, a, b) == float("-inf"):
            continue             
        prob = np.exp(np.array(compute_log_function(proposal, pVec, a, b)) -\
                      np.array(compute_log_function(tmp, pVec, a, b)))
        if np.random.uniform() < prob:
            tmp = proposal 
        step -= 1 
    return tmp

Per un'implementazione completa di questo codice, consultare il mio repository Github all'indirizzo

https://github.com/mohsenkarimzadeh/sampling

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.