Come posso trarre un valore in modo casuale da una stima della densità del kernel?


10

Ho alcune osservazioni e voglio imitare il campionamento basato su queste osservazioni. Qui considero un modello non parametrico, in particolare, uso il smoothing del kernel per stimare un CDF dalle osservazioni limitate, quindi disegno valori casuali dal CDF ottenuto. Il seguente è il mio codice (l'idea è di ottenere casualmente un cumulativo probabilità usando una distribuzione uniforme e prendere l'inverso del CDF rispetto al valore di probabilità)

x = [randn(100, 1); rand(100, 1)+4; rand(100, 1)+8];
[f, xi] = ksdensity(x, 'Function', 'cdf', 'NUmPoints', 300);
cdf = [xi', f'];
nbsamp = 100;
rndval = zeros(nbsamp, 1);
for i = 1:nbsamp
    p = rand;
   [~, idx] = sort(abs(cdf(:, 2) - p));
   rndval(i, 1) = cdf(idx(1), 1);
end
figure(1);
hist(x, 40)
figure(2);
hist(rndval, 40)

Come mostrato nel codice, ho usato un esempio sintetico per testare la mia procedura, ma il risultato è insoddisfacente, come illustrato dalle due figure sottostanti (il primo è per le osservazioni simulate e la seconda figura mostra l'istogramma tratto dalla CDF stimata) :

Figura 1 figura 2

C'è qualcuno che sa dov'è il problema? Grazie in anticipo.


Cerniere di campionamento per trasformazioni inverse che utilizzano il CDF inverso . en.wikipedia.org/wiki/Inverse_transform_sampling
Sycorax dice Reinstate Monica

1
Lo stimatore della densità del kernel produce una distribuzione che è una miscela di ubicazione della distribuzione del kernel, quindi tutto ciò che serve per trarre un valore dalla stima della densità del kernel è (1) disegnare un valore dalla densità del kernel e quindi (2) selezionare indipendentemente uno dei i dati puntano a caso e aggiungono il suo valore al risultato di (1). Tentare di invertire KDE direttamente sarà molto meno efficiente.
whuber

@Sycorax Ma seguo davvero la procedura di campionamento della trasformazione inversa come descritto in Wiki. Si prega di consultare il codice: p = rand; [~, idx] = sort (abs (cdf (:, 2) - p)); rndval (i, 1) = cdf (idx (1), 1);
emberbillow,

@whuber Non sono sicuro che la mia comprensione della tua idea sia corretta o meno. Per favore aiutatemi a controllare: prima ricampionate un valore dalle osservazioni; e quindi trarre un valore dal kernel, diciamo distribuzione normale standard; infine, aggiungerli insieme?
emberbillow,

Risposte:


12

Uno stimatore della densità del kernel (KDE) produce una distribuzione che è una miscela di ubicazione della distribuzione del kernel, quindi per trarre un valore dalla stima della densità del kernel tutto ciò che devi fare è (1) trarre un valore dalla densità del kernel e quindi (2) seleziona in modo indipendente uno dei punti dati in modo casuale e aggiungi il suo valore al risultato di (1).

Ecco il risultato di questa procedura applicata a un set di dati come quello nella domanda.

figura

L'istogramma a sinistra raffigura il campione. Per riferimento, la curva nera traccia la densità da cui è stato estratto il campione. La curva rossa traccia il KDE del campione (usando una larghezza di banda stretta). (Non è un problema, o addirittura inaspettato, che i picchi rossi siano più corti dei picchi neri: il KDE diffonde le cose, quindi i picchi si abbasseranno per compensare.)

L'istogramma a destra raffigura un campione (della stessa dimensione) dal KDE. Le curve nere e rosse sono le stesse di prima.

Evidentemente, la procedura utilizzata per campionare dalla densità funziona. È anche estremamente veloce: l' Rimplementazione di seguito genera milioni di valori al secondo da qualsiasi KDE. L'ho commentato pesantemente per aiutare il porting su Python o altre lingue. L'algoritmo di campionamento stesso è implementato nella funzione rdenscon le linee

rkernel <- function(n) rnorm(n, sd=width) 
sample(x, n, replace=TRUE) + rkernel(n)  

rkerneldisegna i ncampioni dalla funzione del kernel mentre sampledisegna i ncampioni con la sostituzione dai dati x. L'operatore "+" aggiunge i due array di campioni componente per componente.


KFKX=(X1,X2,...,Xn)

FX^;K(X)=1nΣio=1nFK(X-Xio).

XXio1/nioYX+YXX

FX+Y(X)=Pr(X+YX)=Σio=1nPr(X+YX|X=Xio)Pr(X=Xio)=Σio=1nPr(Xio+YX)1n=1nΣio=1nPr(YX-Xio)=1nΣio=1nFK(X-Xio)=FX^;K(X),

come affermato.


#
# Define a function to sample from the density.
# This one implements only a Gaussian kernel.
#
rdens <- function(n, density=z, data=x, kernel="gaussian") {
  width <- z$bw                              # Kernel width
  rkernel <- function(n) rnorm(n, sd=width)  # Kernel sampler
  sample(x, n, replace=TRUE) + rkernel(n)    # Here's the entire algorithm
}
#
# Create data.
# `dx` is the density function, used later for plotting.
#
n <- 100
set.seed(17)
x <- c(rnorm(n), rnorm(n, 4, 1/4), rnorm(n, 8, 1/4))
dx <- function(x) (dnorm(x) + dnorm(x, 4, 1/4) + dnorm(x, 8, 1/4))/3
#
# Compute a kernel density estimate.
# It returns a kernel width in $bw as well as $x and $y vectors for plotting.
#
z <- density(x, bw=0.15, kernel="gaussian")
#
# Sample from the KDE.
#
system.time(y <- rdens(3*n, z, x)) # Millions per second
#
# Plot the sample.
#
h.density <- hist(y, breaks=60, plot=FALSE)
#
# Plot the KDE for comparison.
#
h.sample <- hist(x, breaks=h.density$breaks, plot=FALSE)
#
# Display the plots side by side.
#
histograms <- list(Sample=h.sample, Density=h.density)
y.max <- max(h.density$density) * 1.25
par(mfrow=c(1,2))
for (s in names(histograms)) {
  h <- histograms[[s]]
  plot(h, freq=FALSE, ylim=c(0, y.max), col="#f0f0f0", border="Gray",
       main=paste("Histogram of", s))
  curve(dx(x), add=TRUE, col="Black", lwd=2, n=501) # Underlying distribution
  lines(z$x, z$y, col="Red", lwd=2)                 # KDE of data

}
par(mfrow=c(1,1))

Ciao @whuber, voglio citare questa idea nel mio documento. Hai alcuni articoli che sono stati pubblicati per questo? Grazie.
emberbillow

2

Prima campionare dal CDF capovolgendolo. Il CDF inverso è chiamato funzione quantile; è una mappatura da [0,1] al dominio del camper. Quindi campionate i camper uniformi casuali come percentili e li passate alla funzione quantile per ottenere un campione casuale da quella distribuzione.


2
Questo è il modo più difficile: vedi il mio commento alla domanda.
whuber

2
@whuber buon punto. Senza essere troppo invischiato negli aspetti programmatici, stavo assumendo che in questo caso dovessimo lavorare con un CDF. Senza dubbio gli interni di una tale funzione prendono una densità levigata del kernel e quindi la integrano per ottenere un CDF. A quel punto è probabilmente meglio e più veloce usare il campionamento di trasformazioni inverse. Tuttavia, il tuo suggerimento di utilizzare solo la densità e il campione direttamente dalla miscela è migliore.
AdamO,

@AdamO Grazie per la tua risposta. Ma il mio codice segue davvero la stessa idea che hai detto qui. Non so perché i modelli trimodali non possano essere riprodotti.
emberbillow

@AdamO Qui se la parola "internals" nel tuo commento dovrebbe essere "intervalli"? Grazie.
emberbillow,

Ember, "internals" ha perfettamente senso per me. Tale funzione deve integrare la densità della miscela e costruire un contrario: è un processo disordinato e numericamente complicato come suggerisce AdamO, e quindi sarebbe sepolto all'interno della funzione - i suoi "interni".
whuber

1

Qui, voglio anche pubblicare il codice Matlab seguendo l'idea descritta da whuber, per aiutare coloro che hanno più familiarità con Matlab di R.

x = exprnd(3, [300, 1]);
[~, ~, bw] = ksdensity(x, 'kernel', 'normal', 'NUmPoints', 800);

k = 0.25; % control the uncertainty of generated values, the larger the k the greater the uncertainty
mstd = bw*k;
rkernel = mstd*randn(300, 1);
sampleobs = randsample(x, 300, true);
simobs = sampleobs(:) + rkernel(:);

figure(1);
subplot(1,2,1);
hist(x, 50);title('Original sample');
subplot(1,2,2);
hist(simobs, 50);title('Simulated sample');
axis tight;

Il seguente è il risultato: risultati

Per favore dimmi se qualcuno trova qualche problema con la mia comprensione e il codice. Grazie.


1
Inoltre, ho scoperto che il mio codice nella domanda è giusto. L'osservazione che il pattern non può essere riprodotto è in gran parte dovuta alla scelta della larghezza di banda.
emberbillow

0

Senza guardare troppo da vicino alla tua implementazione, non riesco completamente a trarre la tua procedura di indicizzazione dall'ICCDF. Penso che tu disegni dal CDF, non è inverso. Ecco la mia implementazione:

import sys
sys.path.insert(0, './../../../Python/helpers')
import numpy as np
import scipy.stats as stats
from sklearn.neighbors import KernelDensity

def rugplot(axis,x,color='b',label='draws',shape='+',alpha=1):
    axis.plot(x,np.ones(x.shape)*0,'b'+shape,ms=20,label=label,c=color,alpha=alpha);
    #axis.set_ylim([0,max(axis.get_ylim())])

def PDF(x):
    return 0.5*(stats.norm.pdf(x,loc=6,scale=1)+ stats.norm.pdf(x,loc=18,scale=1));

def CDF(x,PDF):
    temp = np.linspace(-10,x,100)
    pdf = PDF(temp);
    return np.trapz(pdf,temp);

def iCDF(p,x,cdf):
    return np.interp(p,cdf,x);

res = 1000;
X = np.linspace(0,24,res);
P = np.linspace(0,1,res)
pdf  = np.array([PDF(x) for x in X]);#attention dont do [ for x in x] because it overrides original x value
cdf  = np.array([CDF(x,PDF) for x in X]);
icdf = [iCDF(p,X,cdf) for p in P];

#draw pdf and cdf
f,(ax1,ax2) = plt.subplots(1,2,figsize=(18,4.5));
ax1.plot(X,pdf, '.-',label = 'pdf');
ax1.plot(X,cdf, '.-',label = 'cdf');
ax1.legend();
ax1.set_title('PDF & CDF')

#draw inverse cdf
ax2.plot(cdf,X,'.-',label  = 'inverse by swapping axis');
ax2.plot(P,icdf,'.-',label = 'inverse computed');
ax2.legend();
ax2.set_title('inverse CDF');

#draw from custom distribution
N = 100;
p_uniform = np.random.uniform(size=N)
x_data  = np.array([iCDF(p,X,cdf) for p in p_uniform]);

#visualize draws
a = plt.figure(figsize=(20,8)).gca();
rugplot(a,x_data);

#histogram
h = np.histogram(x_data,bins=24);
a.hist(x_data,bins=h[1],alpha=0.5,normed=True);

2
Se hai un cdf F è vero che F (X) è uniforme. Quindi ottieni X prendendo il cdf inverso di un numero casuale da una distribuzione uniforme. Il problema penso sia come determinare l'inverso quando si sta producendo una densità del kernel.
Michael R. Chernick,

La ringrazio per la risposta. Non ho provato direttamente dal CDF. Il codice mostra che ho effettivamente fatto la stessa cosa del campionamento della trasformazione inversa. p = rand; % questa riga ottiene un numero casuale uniforme come probabilità cumulativa. [~, idx] = sort (abs (cdf (:, 2) - p)); rndval (i, 1) = cdf (idx (1), 1);% queste due righe servono per determinare il quantile corrispondente alla probabilità cumulativa
emberbillow
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.