カーネル密度推定からランダムに値を取得するにはどうすればよいですか?


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)

コードに示すように、合成例を使用して手順をテストしましたが、以下の2つの図に示されているように、結果は満足のいくものではありません(最初の図はシミュレートされた観測値、2番目の図は推定CDFから得られたヒストグラムを示しています)。 :

図1 図2

問題がどこにあるか知っている人はいますか?前もって感謝します。


変換サンプリングは、 CDFの使用にかかっています。en.wikipedia.org/wiki/Inverse_transform_sampling
Sycoraxによると

1
カーネル密度推定器は、カーネル分布のロケーション混合である分布を生成します。したがって、カーネル密度推定から値を引き出すために必要なのは、(1)カーネル密度から値を引き、次に(2)のいずれかを個別に選択することです。データはランダムにポイントし、その値を(1)の結果に追加します。KDEを直接反転させようとすると、効率が大幅に低下します。
whuber

@Sycoraxしかし、私は確かにWikiで説明されている逆変換サンプリング手順に従います。コードをご覧ください:p = rand; [〜、idx] = sort(abs(cdf(:, 2)-p)); rndval(i、1)= cdf(idx(1)、1);
emberbillow 2018年

@whuberあなたの考えに対する私の理解が正しいかどうかわかりません。チェックを助けてください:最初に観測から値をリサンプリングします。そして、カーネルから値を引き出します。たとえば、標準正規分布です。最後に、それらを一緒に追加しますか?
emberbillow 2018年

回答:


12

カーネル密度推定(KDE)は、カーネル分布のロケーション混合である分布を生成するため、カーネル密度推定から値を引き出すために必要なのは、(1)カーネル密度から値を引き、次に(2)です。ランダムにデータポイントの1つを個別に選択し、その値を(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。「+」演算子は、サンプルの2つの配列をコンポーネントごとに追加します。


正しさの正式なデモンストレーションを希望する人のために、ここで提供します。LET CDFとカーネル分布表し、データがであるものとする。カーネル推定の定義により、KDEのCDFはF K x = x 1x 2x nKFKx=(x1,x2,,xn)

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

上記のレシピは、データの経験的分布からを引き出す(つまり、各について確率で値を達成する)、カーネル分布からランダム変数個別に引き、それらを合計することを示しています。分布関数がKDE の分布関数であることを証明する必要があります。定義から始めて、それがどこにつながるかを見てみましょう。ましょ任意の実数とします。条件付けはx i 1 / n i Y X + Y x XXxi1/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),

主張通り。


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

2

まず、CDFを反転してサンプリングします。逆CDFは分位関数と呼ばれます。これは、[0,1]からRVのドメインへのマッピングです。次に、ランダムな均一RVをパーセンタイルとしてサンプリングし、それらを分位関数に渡して、その分布からランダムなサンプルを取得します。


2
これは難しい方法です。質問に対する私のコメントを参照してください。
whuber

2
@whuber良い点。プログラムの側面にあまり夢中になることなく、この場合はCDFを使用する必要があると想定していました。間違いなく、このような関数の内部はカーネル平滑化密度を取り、それを統合してCDFを取得します。その時点で、逆変換サンプリングを使用する方がおそらくより適切で高速です。ただし、密度とサンプルを混合物から直接使用することをお勧めします。
AdamO

@AdamO回答ありがとうございます。しかし、私のコードは確かに、ここで言ったのと同じ考えに従います。なぜトライモーダルなパターンが再現できないのかわかりません。
emberbillow 2018年

@AdamOここで、コメント内の「内部」という単語を「間隔」にする必要があるかどうか。ありがとうございました。
emberbillow 2018年

エンバー、「内部」は私には完全に理にかなっています。そのような関数は、混合密度を統合して逆を構築する必要があります。これは、AdamOが示唆するように、面倒で数値的に複雑なプロセスであるため、関数内に埋め込まれます(その「内部」)。
whuber

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 2018年

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)が均一であることはttrueです。したがって、一様分布から乱数の逆累積分布関数を取得してXを取得します。私が考える問題は、カーネル密度を生成しているときに逆を決定する方法です。
マイケルR.チェニック2018年

お返事ありがとうございます。CDFから直接サンプリングしませんでした。コードは、私が実際に逆変換サンプリングと同じことをしたことを示しています。p = rand; %この線は、累積確率として一様な乱数を取得します。[〜、idx] = sort(abs(cdf(:, 2)-p)); rndval(i、1)= cdf(idx(1)、1);%これらの2行は、累積確率に対応する分位を決定するためのものです
emberbillow
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.