KDE算法解析
核密度估计(Kernel Density Estimation, KDE)算法通过样本估计这些样本所属的概率密度函数,是non-parametric方法,也就是在进行估计时无需假设分布的具体形式。本文只讨论单变量(univariate)。
数学表达
给定\(n\)个样本\(x_i \in \mathcal{R}\),通过KDE算法估计\(x\)处的概率密度为
\]
其中,核函数\(K\)和带宽(bandwidth)\(h\)正是KDE算法的两个核心参数。
通过公式(1)易得,如果有\(m\)个\(x\)值要估计的概率密度,那么计算时间复杂度是\(O(m\times n)\)。
Binning
Binning技术是通过将相邻样本分箱到一个box中。分箱之前,我们需要对所有的\(n\)个样本计算核密度函数值;分箱之后,我们只需要对每个box计算一次即可,然后用它来代表所有被分到这个box内的样本。
根据[1],通过binning计算可分为以下三步:
通过将原始数据分配给相邻网格点来对数据进行分箱以获得网格计数。网格计数可以被认为表示其对应网格点附近的数据量。
Bin the data by assigning the raw data to neighboring grid points to obtain grid counts. A grid count can be thought of as representing the amount of data in the neighborhood of its corresponding grid point.计算所需的内核权重。网格点等距意味着不同核权重的数量相对较少。
Compute the required kernel weights. The fact that the grid points are equally spaced means that the number of distinct kernel weights is comparatively small.结合网格计数和核权重以获得核估计的近似值。这本质上涉及一系列离散卷积。
Combine the grid counts and the kernel weights to obtain the approximation to the kernel estimate. This essentially involves a series of discrete convolutions.
步骤1涉及划分多少个box以及如何将样本对应到box中,对计算速度和精度都非常关键。两种常见的划分方式是
Simple Binning: 将样本对应的权重划分到距离到离其最近的grid中。
If a data point at y has surrounding grid points at x and z, then simple binning involves assigning a unit mass to the grid point closest to y.Linear Binning: 将样本对应的权重按比例划分到其左右相邻的两个grid中。注意给左端点x和右端点z的权重是和它到两个端点的距离“反过来”的,也就是给x的权重是\((z-y)/(z-x)\),而给右端点z的权重是\((y-x)/(z-x)\)。
Linear binning assigns a mass of (z - y)/(z - x) to the grid point at x, and (y - x)/(z - x) to the grid point at z.
这里所说的样本权重就是每个样本是否重要性相同,可以联系python代码中sklearn.neighbors.KernelDensity.fit(sample_weight)参数,默认情况下,每个样本地位相同,其权重都为1(或者说都是1/n)。
根据[2],我们可以给Simple Binning一种形式化定义:定义box宽度为\(\delta\)的等距网格(其实也就是对应要划分多少个box),令第\(j\)个bin的中点表示为\(t_j\),其包含\(n_j\)个样本。因此有
\]
\]
不难发现,在上述定义中,内核权重对应为\(n_j\)(或者说\(n_j/nh\)),也就是每个box从所有样本中分到的权重是多少。
卷积近似
在binning的第3步中,我们提到结合网格计数和核权重本质上是在做卷积运算,在许多文献中我们也经常可以发现为了加速KDE算法(或解决高维KDE问题)会提到进行卷积近似操作。
首先明确卷积的概念,参考维基百科的定义,卷积是通过两个函数\(f\)和\(g\)生成第三个函数的一种数学算子,设\(f(t)\)和\(g(t)\)是实数\(\mathcal{R}\)上的两个可积函数,定理两者的卷积为如下特定形式的积分变换:
\]
将公式(3)和公式(4)起来,就不难建立起卷积近似在KDE中的作用了。
Linear Binning + Deriche Approximation
2021年的一篇short paper[3],表明the combination of linear binning and a recursive filter approximation by Deriche可以实现both fast and highly accurate的效果。
Luckily, 作者在Github上公布了开源代码;Unluckily, 代码语言是Javascript;于是我将其翻译为Python版本并公布在Github上。
然而,对于其效果我持怀疑态度,通过运行density1d.py-main,100个高斯分布采样点拟合得到的概率密度函数图像为


而且,更为关键的问题是无法该代码无法基于样本生成点x处的概率密度?即,在没有人为指定的情况下,代码会默认将[data.min()-pad×bandwidth, data.max()+pad×bandwidth]作为考虑范围,然后将这个区间划分为bins个区间,即\((x_0,x_1),(x_1,x_2),...,(x_{\text{bins}-1}, x_\text{bins})\),那么该方法生成的就是\(x_0,x_1,...\)这些点的概率密度,但如果我希望生成任意一个位置\(x, x\ne x_i\)处的概率密度,目前并没有实现。
sklearn.neighbors.KernelDensity
在sklearn库中有KDE算法的实现,而且其考虑采用KD-tree(默认)或Ball-tree来进行加速。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import KernelDensity
# 生成一些示例数据
np.random.seed(0)
data = np.random.normal(loc=0, scale=1, size=1000)
# 创建一个KernelDensity对象
kde = KernelDensity(bandwidth=0.5, kernel='gaussian')
# 用示例数据拟合KDE模型
kde.fit(data[:, None])
# 生成一些测试点来评估KDE模型
x = np.linspace(-5, 5, 1000)
x = x[:, None]
# 使用KDE模型评估密度
log_density = kde.score_samples(x)
# 绘制原始数据和KDE估计的密度曲线
plt.figure(figsize=(10, 6))
plt.hist(data, bins=30, density=True, alpha=0.5, color='blue')
plt.plot(x, np.exp(log_density), color='red', lw=2)
plt.title('Kernel Density Estimation (KDE)')
plt.xlabel('Value')
plt.ylabel('Density')
plt.show()
这是非常简单的一个例子,简单到甚至我之前没有思考为什么要先kde.fit()才能评估密度kde.score_sample()。通过公式(1),我们可以发现只要清楚指定的核函数\(K\)和带宽(bandwidth)\(h\),那么就可以计算出任意一点的概率密度,而这两个参数都是在创建KDE对象时就指定好的。
起初,我以为kde.fit()是在训练参数\(h\),甚至有那么一瞬间让我觉得困扰我们如何指定超参数\(h\)的值根本不是问题,因为它只是个初始值,而在fit之后会训练到最好的参数。这种思路明显是错的。因为缺乏ground_truth,因此模型本身就缺乏训练\(h\)的能力,这也是为什么\(h\)被称作超参数更合适而非参数。
既然核函数和带宽都是指定好的,那么如果我们不fit直接去评估密度呢?
import numpy as np
from sklearn.neighbors import KernelDensity
# 生成一些示例数据
np.random.seed(0)
data = np.random.normal(loc=0, scale=1, size=1000)
# 创建一个KernelDensity对象
kde = KernelDensity(bandwidth=0.5, kernel='gaussian')
# 生成一些测试点来评估KDE模型
x = np.linspace(-5, 5, 1000)
x = x[:, None]
# 使用KDE模型评估密度
log_density = kde.score_samples(x)
Traceback (most recent call last):
File "D:\python\fast_kde\main.py", line 17, in <module>
log_density = kde.score_samples(x)
^^^^^^^^^^^^^^^^^^^^
File "F:\anaconda3\Lib\site-packages\sklearn\neighbors\_kde.py", line 261, in score_samples
check_is_fitted(self)
File "F:\anaconda3\Lib\site-packages\sklearn\utils\validation.py", line 1462, in check_is_fitted
raise NotFittedError(msg % {"name": type(estimator).__name__})
sklearn.exceptions.NotFittedError: This KernelDensity instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator.
看起来,sklearn要求在调用kde.score_samples()必须要kde.fit()。
通过阅读源代码,kde.fit()没有改变任何KDE的参数,但构建了内置对象self.tree_,这个就是我们前面提到的KD-tree或Ball-tree,而在进行评估时也是基于这个tree进行的。我的理解是,KD-tree维护样本之间的距离信息,在评估一个新的数据\(x\)处的概率密度时,和它距离太远的样本就不计算了?
References
[1] @article{wand1994fast,
title = {Fast Computation of Multivariate Kernel Estimators},
author = {Wand, M. P.},
year = {1994},
journal = {Journal of Computational and Graphical Statistics},
volume = {3},
number = {4},
pages = {433},
doi = {10.2307/1390904}
}
[2] @article{wand1994fast,
title = {Fast Computation of Multivariate Kernel Estimators},
author = {Wand, M. P.},
year = {1994},
journal = {Journal of Computational and Graphical Statistics},
volume = {3},
number = {4},
pages = {433},
doi = {10.2307/1390904}
}
[3] @inproceedings{heer2021fast,
title = {Fast & Accurate Gaussian Kernel Density Estimation},
booktitle = {2021 IEEE Visualization Conference (VIS)},
author = {Heer, Jeffrey},
year = {2021},
pages = {11--15},
publisher = {IEEE},
address = {New Orleans, LA, USA},
doi = {10.1109/VIS49827.2021.9623323},
isbn = {978-1-66543-335-8},
langid = {english}
}
KDE算法解析的更多相关文章
- 地理围栏算法解析(Geo-fencing)
地理围栏算法解析 http://www.cnblogs.com/LBSer/p/4471742.html 地理围栏(Geo-fencing)是LBS的一种应用,就是用一个虚拟的栅栏围出一个虚拟地理边界 ...
- KMP串匹配算法解析与优化
朴素串匹配算法说明 串匹配算法最常用的情形是从一篇文档中查找指定文本.需要查找的文本叫做模式串,需要从中查找模式串的串暂且叫做查找串吧. 为了更好理解KMP算法,我们先这样看待一下朴素匹配算法吧.朴素 ...
- Peterson算法与Dekker算法解析
进来Bear正在学习巩固并行的基础知识,所以写下这篇基础的有关并行算法的文章. 在讲述两个算法之前,需要明确一些概念性的问题, Race Condition(竞争条件),Situations lik ...
- python常见排序算法解析
python——常见排序算法解析 算法是程序员的灵魂. 下面的博文是我整理的感觉还不错的算法实现 原理的理解是最重要的,我会常回来看看,并坚持每天刷leetcode 本篇主要实现九(八)大排序算法 ...
- Java虚拟机对象存活标记及垃圾收集算法解析
一.对象存活标记 1. 引用计数算法 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加1:当引用失效时,计数器就减1:任何时刻计数器都为0的对象就是不可能再被使用的. 引用计数算法(Re ...
- JVM垃圾回收算法解析
JVM垃圾回收算法解析 标记-清除算法 该算法为最基础的算法.它分为标记和清除两个阶段,首先标记出需要回收的对象,在标记结束后,统一回收.该算法存在两个问题:一是效率问题,标记和清除过程效率都不太高, ...
- DeepFM算法解析及Python实现
1. DeepFM算法的提出 由于DeepFM算法有效的结合了因子分解机与神经网络在特征学习中的优点:同时提取到低阶组合特征与高阶组合特征,所以越来越被广泛使用. 在DeepFM中,FM算法负责对一阶 ...
- GBDT+LR算法解析及Python实现
1. GBDT + LR 是什么 本质上GBDT+LR是一种具有stacking思想的二分类器模型,所以可以用来解决二分类问题.这个方法出自于Facebook 2014年的论文 Practical L ...
- 最长上升子序列(LIS)n2 nlogn算法解析
题目描述 给定一个数列,包含N个整数,求这个序列的最长上升子序列. 例如 2 5 3 4 1 7 6 最长上升子序列为 4. 1.O(n2)算法解析 看到这个题,大家的直觉肯定都是要用动态规划来做,那 ...
- Android逆向之旅---Android中锁屏密码算法解析以及破解方案
一.前言 最近玩王者荣耀,下载了一个辅助样本,结果被锁机了,当然破解它很简单,这个后面会详细分析这个样本,但是因为这个样本引发出的欲望就是解析Android中锁屏密码算法,然后用一种高效的方式制作锁机 ...
随机推荐
- Linux中echo的作用
Linux echo命令 echo命令是一个内置在Bash中的shell,通常用于shell脚本中以显示消息或输出其他命令的结果. echo命令的语法如下: 1 echo [-neE] [ARGUME ...
- vivo 短视频体验与成本优化实践
作者:来自 vivo 互联网短视频研发团队 本文根据蔡创业.马运杰老师在"2023 vivo开发者大会"现场演讲内容整理而成. 在线点播场景,播放体验提升与成本优化是同等重要的两件 ...
- python调用namp.py进行扫描,调用go编译的so文件
#!/usr/bin/env python # -*- coding: utf-8 -*- import json import os import platform from ctypes impo ...
- win32-ReadProcessMemory在x86和x64下运行
#include <iostream> #include <Windows.h> #include <winternl.h> #include <tchar. ...
- Docker进阶之02-Swarm集群入门实践
Docker集群概述 Docker集群有2种方案: 1.在Docker Engine 1.12之前的集群模式被称为经典集群,这是通过API代理系统实现的集群,目前已经不再维护. 2.自Docker E ...
- vue实现导出word文档(含多张图片)
vue实现导出word文档(含多张图片) 转自: https://www.pudn.com/news/62e1e14e55398e076bea2d2f.html
- React同级组件传值
在React中同级组件本身是没有任何关联的,要想有联系只能通过共同的父组件传值,一个子组件将数据传递到父组件中,父组件接收值再传入另一个子组件中 <!DOCTYPE html> ...
- 终端SSH远程连接CentOS报错:-bash: warning: setlocale: LC_CTYPE: cannot change locale (UTF-8): No such file or directory
终端SSH远程连接CentOS时,报以下错误提示: -bash: warning: setlocale: LC_CTYPE: cannot change locale (UTF-8): No such ...
- mySQL清除数据表数据/删除表
一.sql清空表数据的三种方式: 1.truncate – 删除所有数据,保留表结构,不能撤销还原,速度快 2.delete – 是逐行删除,不适合大量数据删除,速度极慢 3.drop – 删除表,表 ...
- 使用 maven 的 `wagon-maven-plugin`插件 快速部署 到不同的 环境
profile 在pom文件中配置 开发和测试环境的 profile信息, <profiles> <profile> <!-- 开发环境 --> <id> ...