ฉันจะดึงค่าแบบสุ่มจากการประมาณความหนาแน่นของเคอร์เนลได้อย่างไร


10

ฉันมีข้อสังเกตบางอย่างและฉันต้องการจำลองการสุ่มตัวอย่างตามข้อสังเกตเหล่านี้ ที่นี่ฉันพิจารณารูปแบบที่ไม่ใช่พารามิเตอร์โดยเฉพาะฉันใช้เคอร์เนลที่ราบเรียบเพื่อประเมิน CDF จากการสังเกตที่ จำกัด จากนั้นฉันวาดค่าที่สุ่มจาก CDF ที่ได้รับต่อไปนี้เป็นรหัสของฉัน (ความคิดคือการสุ่มสะสม ความน่าจะเป็นโดยใช้การแจกแจงแบบสม่ำเสมอและหาค่าผกผันของ CDF เทียบกับค่าความน่าจะเป็น)

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)

ดังที่แสดงในรหัสฉันใช้ตัวอย่างสังเคราะห์เพื่อทดสอบขั้นตอนของฉัน แต่ผลลัพธ์ไม่เป็นที่น่าพอใจดังแสดงโดยตัวเลขสองตัวด้านล่าง (ตัวแรกคือการสังเกตแบบจำลองและตัวเลขที่สองแสดงฮิสโตแกรมจาก CDF โดยประมาณ) :

รูปที่ 1 รูปที่ 2

มีใครบ้างที่รู้ว่าปัญหาอยู่ที่ไหน? ขอบคุณล่วงหน้า.


บานพับการสุ่มตัวอย่างการแปลงผกผันในการใช้Inverse CDF en.wikipedia.org/wiki/Inverse_transform_sampling
Sycorax พูดว่า Reinstate Monica

1
ตัวประมาณความหนาแน่นของเคอร์เนลของคุณสร้างการแจกแจงที่เป็นตำแหน่งผสมของการกระจายเคอร์เนลดังนั้นสิ่งที่คุณต้องวาดค่าจากการประมาณความหนาแน่นของเคอร์เนลคือ (1) ดึงค่าจากความหนาแน่นของเคอร์เนลและ (2) เลือกหนึ่งใน จุดข้อมูลสุ่มและเพิ่มมูลค่าให้กับผลลัพธ์ของ (1) การพยายามคว่ำ KDE โดยตรงจะมีประสิทธิภาพน้อยกว่ามาก
whuber

@Sycorax แต่ฉันทำตามขั้นตอนการสุ่มตัวอย่างการแปลงผกผันตามที่อธิบายไว้ใน Wiki โปรดดูรหัส: p = rand; [~, idx] = sort (abs (cdf (:, 2) - p)); rndval (i, 1) = cdf (idx (1), 1);
emberbillow

@whuber ฉันไม่แน่ใจว่าความเข้าใจในความคิดของคุณถูกต้องหรือไม่ กรุณาช่วยตรวจสอบ: ก่อน resample ค่าจากการสังเกต; แล้วดึงค่าจากเคอร์เนลพูดการแจกแจงแบบปกติมาตรฐาน ในที่สุดเพิ่มเข้าด้วยกันไหม
emberbillow

คำตอบ:


12

ประมาณความหนาแน่นเคอร์เนล (KDE) ผลิตการกระจายที่เป็นส่วนผสมที่ตั้งของการกระจายเคอร์เนลดังนั้นเพื่อดึงค่าจากการประมาณการความหนาแน่นเคอร์เนลสิ่งที่คุณต้องทำคือ (1) วาดค่าจากความหนาแน่นเคอร์เนลแล้ว (2) เลือกหนึ่งในจุดข้อมูลที่สุ่มและเพิ่มมูลค่าให้กับผลลัพธ์ของ (1)

นี่คือผลลัพธ์ของขั้นตอนนี้นำไปใช้กับชุดข้อมูลเช่นเดียวกับในคำถาม

รูป

ฮิสโตแกรมทางซ้ายแสดงตัวอย่าง สำหรับการอ้างอิงเส้นโค้งสีดำพล็อตความหนาแน่นที่ตัวอย่างถูกวาด เส้นโค้งสีแดงพล็อต KDE ของตัวอย่าง (ใช้แบนด์วิดท์แบบแคบ) (ไม่ใช่ปัญหาหรือแม้แต่คาดไม่ถึงเลยว่ายอดเขาสีแดงนั้นสั้นกว่ายอดเขาสีดำ: KDE กระจายสิ่งต่าง ๆ ออกไปดังนั้นยอดเขาจะต่ำลงเพื่อชดเชย)

ฮิสโตแกรมด้านขวาแสดงตัวอย่าง (ขนาดเท่ากัน) จาก KDE เส้นโค้งสีดำและสีแดงเหมือนเมื่อก่อน

เห็นได้ชัดว่าขั้นตอนที่ใช้ในการสุ่มตัวอย่างจากงานความหนาแน่น มันยังเร็วมาก: การRใช้งานด้านล่างสร้างค่าหลายล้านต่อวินาทีจาก KDE ใด ๆ ฉันให้ความเห็นเป็นอย่างมากเพื่อช่วยในการย้ายไปยัง Python หรือภาษาอื่น ๆ อัลกอริทึมการสุ่มตัวอย่างตัวเองถูกนำมาใช้ในฟังก์ชั่นที่rdensมีเส้น

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

rkernelดึงnตัวอย่าง IID จากฟังก์ชันเคอร์เนลในขณะที่sampleดึงตัวอย่างด้วยการเปลี่ยนจากข้อมูลn xตัวดำเนินการ "+" เพิ่มสองตัวอย่างอาร์เรย์ของส่วนประกอบตัวอย่างตามส่วนประกอบ


KFKx=(x1,x2,...,xn)

Fx^;K(x)=1nΣผม=1nFK(x-xผม).

Xxผม1/nผมYX+YxX

FX+Y(x)=ราคา(X+Yx)=Σผม=1nราคา(X+Yx|X=xผม)ราคา(X=xผม)=Σผม=1nราคา(xผม+Yx)1n=1nΣผม=1nราคา(Yx-xผม)=1nΣผม=1nFK(x-xผม)=Fx^;K(x),

ตามที่อ้างสิทธิ์


#
# 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))

สวัสดี @ คนรอบข้างฉันต้องการอ้างอิงความคิดนี้ในบทความของฉัน คุณมีเอกสารที่ตีพิมพ์สำหรับเรื่องนี้หรือไม่? ขอบคุณ.
emberbillow

2

คุณสุ่มตัวอย่างจาก CDF ก่อนโดยทำการย้อนกลับ CDF ผกผันเรียกว่าฟังก์ชัน quantile มันเป็นแผนที่จาก [0,1] ถึงโดเมนของ RV จากนั้นคุณสุ่มตัวอย่าง RVs เครื่องแบบแบบสุ่มเป็นเปอร์เซ็นไทล์และส่งผ่านไปยังฟังก์ชันควอไทล์เพื่อรับตัวอย่างสุ่มจากการแจกแจงนั้น


2
นี่เป็นวิธีที่ยาก: ดูความคิดเห็นของฉันต่อคำถาม
whuber

2
@ จุดสีดำที่ดี โดยไม่ถูกมองในแง่มุมของการเขียนโปรแกรมมากเกินไปฉันคิดว่าเราต้องทำงานกับ CDF ในกรณีนี้ ไม่ต้องสงสัยเลย internals ไปเช่นฟังก์ชั่นใช้เคอร์เนลเรียบหนาแน่นและแล้วบูรณาการที่จะได้รับ CDF ณ จุดนั้นมันน่าจะดีกว่าและเร็วกว่าที่จะใช้การสุ่มตัวอย่างการแปลงผกผัน อย่างไรก็ตามคำแนะนำของคุณเพียงใช้ความหนาแน่นและตัวอย่างตรงจากส่วนผสมจะดีกว่า
AdamO

@AdamO ขอบคุณสำหรับคำตอบของคุณ แต่รหัสของฉันเป็นไปตามแนวคิดเดียวกันกับที่คุณพูดที่นี่ ฉันไม่รู้ว่าทำไมรูปแบบไตรโมดัลจึงไม่สามารถทำซ้ำได้
emberbillow

@AdamO ที่นี่ไม่ว่าคำว่า "internals" ในความคิดเห็นของคุณควรเป็น "ช่วงเวลา" หรือไม่? ขอบคุณ.
emberbillow

เอ็มเบอร์ "internals" เข้ากับฉันได้ดีมาก ฟังก์ชั่นดังกล่าวต้องรวมความหนาแน่นของส่วนผสมเข้าด้วยกันและสร้างสิ่งผกผัน: นั่นเป็นกระบวนการที่ยุ่งและซับซ้อนเป็นตัวเลขตามคำแนะนำของ AdamO และจะถูกฝังอยู่ภายในฟังก์ชัน - ซึ่งเป็น "internals"
whuber

1

ที่นี่ฉันยังต้องการโพสต์รหัส Matlab ตามความคิดที่อธิบายโดย whuber เพื่อช่วยให้ผู้ที่คุ้นเคยกับ Matlab มากกว่า 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;

ต่อไปนี้คือผลลัพธ์: ผล

โปรดบอกฉันหากใครพบปัญหาใด ๆ กับความเข้าใจของฉันและรหัส ขอบคุณ.


1
นอกจากนี้ฉันพบว่ารหัสของฉันในคำถามนั้นถูกต้อง การสังเกตว่ารูปแบบที่ไม่สามารถทำซ้ำได้นั้นส่วนใหญ่เกิดจากการเลือกใช้แบนด์วิดท์
emberbillow

0

ฉันไม่ได้รับขั้นตอนการจัดทำดัชนีของคุณอย่างเต็มที่จาก ICDF ฉันคิดว่าคุณวาดจาก CDF ไม่ใช่สิ่งที่ตรงกันข้าม นี่คือการดำเนินการของฉัน:

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
หากคุณมี cdf F เป็นจริงที่ F (X) เหมือนกัน ดังนั้นคุณจะได้ X ด้วยการหา cdf ผกผันของตัวเลขสุ่มจากการแจกแจงแบบเดียวกัน ปัญหาที่ฉันคิดว่าเป็นวิธีการกำหนดค่าผกผันเมื่อคุณสร้างความหนาแน่นเคอร์เนล
Michael R. Chernick

ขอบคุณสำหรับคำตอบ. ฉันไม่ได้ตัวอย่างโดยตรงจาก CDF รหัสแสดงให้เห็นว่าฉันทำอย่างเดียวกันกับการสุ่มตัวอย่างการแปลงผกผัน p = rand; % สายนี้ได้รับตัวเลขสุ่มสม่ำเสมอความน่าจะเป็นสะสม [~, idx] = sort (abs (cdf (:, 2) - p)); rndval (i, 1) = cdf (idx (1), 1);% ทั้งสองบรรทัดนี้จะกำหนดควอนไทล์ที่สอดคล้องกับความน่าจะเป็นสะสม
emberbillow
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.