Bagaimana saya bisa mengambil nilai secara acak dari perkiraan kepadatan kernel?

10

Saya memiliki beberapa pengamatan, dan saya ingin meniru sampel berdasarkan pengamatan ini. Di sini saya mempertimbangkan model non-parametrik, khususnya, saya menggunakan kernel smoothing untuk memperkirakan CDF dari pengamatan terbatas. Kemudian saya menggambar nilai secara acak dari CDF yang diperoleh. Berikut ini adalah kode saya, (idenya adalah mendapatkan kumulatif secara acak probabilitas menggunakan distribusi seragam, dan ambil kebalikan dari CDF sehubungan dengan nilai probabilitas)

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)

Seperti yang ditunjukkan dalam kode, saya menggunakan contoh sintetis untuk menguji prosedur saya, tetapi hasilnya tidak memuaskan, seperti yang diilustrasikan oleh dua gambar di bawah ini (yang pertama adalah untuk pengamatan simulasi, dan gambar kedua menunjukkan histogram yang diambil dari perkiraan CDF) :

Gambar 1 Gambar 2

Adakah yang tahu di mana masalahnya? Terima kasih sebelumnya.

barabillow
sumber
Pengambilan sampel transformasi terbalik bergantung pada penggunaan CDF terbalik . en.wikipedia.org/wiki/Inverse_transform_sampling
Sycorax mengatakan Reinstate Monica
1
Penaksir kerapatan kernel Anda menghasilkan distribusi yang merupakan campuran lokasi dari distribusi kernel, sehingga yang Anda butuhkan untuk mengambil nilai dari estimasi kerapatan kernel adalah (1) menarik nilai dari kerapatan kernel dan kemudian (2) secara mandiri memilih salah satu dari data menunjuk secara acak dan menambahkan nilainya ke hasil (1). Mencoba membalikkan KDE secara langsung akan jauh lebih efisien.
whuber
@ Scorax Tapi saya memang mengikuti prosedur sampling transformasi terbalik seperti yang dijelaskan dalam Wiki. Silakan lihat kode: p = rand; [~, idx] = sort (abs (cdf (:, 2) - p)); rndval (i, 1) = cdf (idx (1), 1);
emberbillow
@whuber Saya tidak yakin apakah pemahaman saya tentang ide Anda benar atau tidak. Tolong bantu memeriksa: resample pertama nilai dari pengamatan; dan kemudian menarik nilai dari kernel, katakanlah distribusi normal standar; akhirnya, tambahkan bersama?
emberbillow

Jawaban:

12

Kira-kira penduga kepadatan kernel (KDE) menghasilkan distribusi yang merupakan campuran lokasi dari distribusi kernel, sehingga untuk menggambar nilai dari estimasi kepadatan kernel yang Anda butuhkan adalah (1) mengambil nilai dari kepadatan kernel dan kemudian (2) pilih salah satu titik data secara acak dan tambahkan nilainya ke hasil (1) secara independen.

Berikut adalah hasil dari prosedur ini yang diterapkan pada dataset seperti yang ada di pertanyaan.

Angka

Histogram di sebelah kiri menggambarkan sampel. Untuk referensi, kurva hitam memplot kepadatan dari mana sampel diambil. Kurva merah memplot KDE sampel (menggunakan bandwidth sempit). (Ini bukan masalah, atau bahkan tidak terduga, bahwa puncak merah lebih pendek dari puncak hitam: KDE menyebarkan berbagai hal, sehingga puncak akan menjadi lebih rendah untuk mengimbangi.)

Histogram di sebelah kanan menggambarkan sampel (dengan ukuran yang sama) dari KDE. Kurva hitam dan merah sama dengan sebelumnya.

Terbukti, prosedur yang digunakan untuk sampel dari kepadatan bekerja. Ini juga sangat cepat: Rimplementasi di bawah ini menghasilkan jutaan nilai per detik dari setiap KDE. Saya telah banyak berkomentar untuk membantu dalam porting ke Python atau bahasa lain. Algoritma sampling itu sendiri diimplementasikan dalam fungsi rdensdengan garis

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

rkernelmengambil nsampel iid dari fungsi kernel sementara samplemengambil nsampel dengan penggantian dari data x. Operator "+" menambahkan dua larik komponen sampel dengan komponen.


Bagi mereka yang menginginkan demonstrasi formal tentang kebenaran, saya menawarkannya di sini. Biarkan mewakili distribusi kernel dengan CDF dan biarkan datanya . Dengan definisi estimasi kernel, CDF dari KDE adalahKFKx=(x1,x2,,xn)

Fx^;K(x)=1ni=1nFK(xxi).

Resep sebelumnya mengatakan untuk menggambar dari distribusi empiris data (yaitu, ia mencapai nilai dengan probabilitas untuk setiap ), menggambar secara mandiri variabel acak dari distribusi kernel, dan menjumlahkannya. Saya berutang bukti bahwa fungsi distribusi adalah fungsi KDE. Mari kita mulai dengan definisi dan melihat ke mana ia mengarah. Biarkan menjadi bilangan real. Pengkondisian pada memberiXxi1/niYX+YxX

FX+Y(x)=Pr(X+Yx)=i=1nPr(X+YxX=xi)Pr(X=xi)=i=1nPr(xi+Yx)1n=1ni=1nPr(Yxxi)=1ni=1nFK(xxi)=Fx^;K(x),

seperti yang diklaim.


#
# 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))
whuber
sumber
Hai @whuber, saya ingin mengutip ide ini di koran saya. Apakah Anda memiliki beberapa makalah yang telah diterbitkan untuk ini? Terima kasih.
emberbillow
2

Anda mengambil sampel dari CDF terlebih dahulu dengan membalikkannya. CDF terbalik disebut fungsi kuantil; ini adalah pemetaan dari [0,1] ke domain RV. Anda kemudian sampel RV seragam acak sebagai persentil dan meneruskannya ke fungsi kuantil untuk mendapatkan sampel acak dari distribusi itu.

AdamO
sumber
2
Ini cara yang sulit: lihat komentar saya untuk pertanyaan.
whuber
2
@whuber poin bagus. Tanpa terlalu terlibat dalam aspek program, saya berasumsi kita harus bekerja dengan CDF dalam hal ini. Tidak ada keraguan internal untuk fungsi seperti mengambil kernel merapikan kepadatan dan kemudian mengintegrasikannya untuk mendapatkan CDF a. Pada titik itu mungkin lebih baik dan lebih cepat untuk menggunakan sampling transformasi terbalik. Namun, saran Anda untuk hanya menggunakan kerapatan dan sampel langsung dari campuran lebih baik.
AdamO
@ Adamo Terima kasih atas jawaban Anda. Tetapi kode saya memang mengikuti ide yang sama seperti yang Anda katakan di sini. Saya tidak tahu mengapa pola tri-modal tidak dapat direproduksi.
emberbillow
@ AdamO Di sini apakah kata "internal" dalam komentar Anda harus "interval"? Terima kasih.
emberbillow
Ember, "internal" sangat masuk akal bagi saya. Fungsi semacam itu harus mengintegrasikan kerapatan campuran dan membuat inversi: itu adalah proses yang berantakan dan rumit secara numerik seperti yang diisyaratkan AdamO, dan karenanya akan dimakamkan di dalam fungsi - "internalnya".
whuber
1

Di sini, saya juga ingin memposting kode Matlab mengikuti ide yang dijelaskan oleh whuber, untuk membantu mereka yang lebih akrab dengan Matlab daripada 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;

Berikut ini hasilnya: hasil

Tolong beritahu saya jika ada yang menemukan masalah dengan pemahaman dan kode saya. Terima kasih.

barabillow
sumber
1
Selain itu, saya menemukan bahwa kode saya dalam pertanyaan itu benar. Pengamatan bahwa pola tersebut tidak dapat direproduksi sebagian besar karena pilihan bandwidth.
emberbillow
0

Tanpa melihat terlalu dekat dengan implementasi Anda, saya tidak sepenuhnya mendapatkan prosedur pengindeksan Anda untuk menarik dari ICDF. Saya pikir Anda menarik dari CDF, bukan kebalikannya. Inilah implementasi saya:

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);
Jan
sumber
2
Jika Anda memiliki cdf F, maka benar bahwa F (X) seragam. Jadi Anda mendapatkan X dengan mengambil cdf terbalik dari nomor acak dari distribusi yang seragam. Masalahnya saya pikir adalah bagaimana menentukan kebalikan ketika Anda menghasilkan kepadatan kernel.
Michael R. Chernick
Terima kasih atas jawaban Anda. Saya tidak mencicipi langsung dari CDF. Kode menunjukkan bahwa saya memang melakukan hal yang sama dengan sampling transformasi terbalik. p = rand; % baris ini mendapat angka acak seragam sebagai probabilitas kumulatif. [~, idx] = sort (abs (cdf (:, 2) - p)); rndval (i, 1) = cdf (idx (1), 1);% kedua baris ini adalah untuk menentukan kuantil yang sesuai dengan probabilitas kumulatif
emberbillow