如何从内核密度估计中随机得出一个值?


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

有谁知道问题出在哪里吗?先感谢您。



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我不确定我对您的想法的理解是否正确。请帮助检查:首先从观察值中重新采样一个值;然后从内核中得出一个值,例如标准正态分布;最后,将它们加在一起?
emberbillow

Answers:


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独立同分布样本内核函数,同时sample吸引n来自数据替换样本x。“ +”运算符逐个分量地添加两个样本数组。


ķFķX=X1个X2Xñ

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

Xxi1/niYX+YxX

FX+ÿX=X+ÿX=一世=1个ñX+ÿXX=X一世X=X一世=一世=1个ñX一世+ÿX1个ñ=1个ñ一世=1个ñÿX-X一世=1个ñ一世=1个ñFķX-X一世=FX^;ķ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))

嗨@whuber,我想在论文中引用这个想法。您是否为此发表了一些论文?谢谢。
emberbillow 18-3-3的

2

您首先需要对CDF进行反相采样。CDF的逆称为分位数函数;它是从[0,1]到RV域的映射。然后,您将随机均匀RV采样为百分位数,并将其传递给分位数函数,以从该分布中获取随机样本。


2
这是困难的方法:请参阅我对问题的评论。
ub

2
@whuber好点。不必太着迷于程序方面,我假设在这种情况下我们必须与CDF合作。无疑,此类函数的内部结构采用内核平滑的密度,然后对其进行积分以获得CDF。到那时,使用逆变换采样可能会更好,更快。但是,您建议仅使用密度并直接从混合物中取样是更好的选择。
AdamO '18年

@AdamO谢谢您的回答。但是我的代码确实遵循您在这里所说的相同想法。我不知道为什么不能重现三峰模式。
emberbillow

@AdamO您的评论中的“内部”一词是否应为“间隔”?谢谢。
emberbillow

灰烬,“内部人”对我来说很有意义。这样的函数必须整合混合密度并构造一个逆函数:正如AdamO所暗示的那样,这是一个混乱的,数字复杂的过程,因此将被埋在函数中-它的“内部”。
ub

1

在这里,我也想按照whuber描述的想法发布Matlab代码,以帮助那些比R更熟悉Matlab的人。

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)是统一的。因此,您可以通过从均匀分布中获取随机数的逆cdf来获得X。我认为的问题是,在生成核密度时如何确定逆。
Michael R. Chernick

谢谢您的回答。我没有直接从CDF采样。该代码表明,我确实做了与逆变换采样相同的操作。p =兰特;%,此行将获得一个统一的随机数作为累积概率。[〜,idx] = sort(abs(cdf(:, 2)-p)); rndval(i,1)= cdf(idx(1),1);%这两条线将确定与累积概率相对应的分位数
emberbillow
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.