统计学习方法第三章
统计学习方法-第三章
KNN算法原理及其实现
数据集基本结构
该数据结构用于描绘伪代码,在平面空间的二分类问题
索引 | 列索引1 | 列索引2 | 标签 |
---|---|---|---|
行索引1 | x 11 x_{11} x11 | x 12 x_{12} x12 | y 1 = 1 y_{1}=1 y1=1 |
行索引2 | x 21 x_{21} x21 | x 22 x_{22} x22 | y 2 = 1 y_{2}=1 y2=1 |
行索引3 | x 31 x_{31} x31 | x 31 x_{31} x31 | y 3 = 0 y_{3}=0 y3=0 |
行索引4 | x 41 x_{41} x41 | x 41 x_{41} x41 | y 4 = 0 y_{4}=0 y4=0 |
基本原理
在待预测点周围找寻 k k k个与之相邻的距离最小的点,根据点的标签来进行投票表决,投票表决的目的是使经验风险最小化。
距离
n维空间的向量为
x
i
=
(
x
i
(
1
)
,
x
i
(
2
)
,
…
,
x
i
(
l
)
)
x_i = (x_i^{(1)},x_i^{(2)},\dots ,x_i^{(l)})
xi=(xi(1),xi(2),…,xi(l)),距离定义为:
L
p
(
x
i
,
x
j
)
=
(
∑
l
=
1
n
∣
x
i
(
l
)
−
x
j
(
l
)
∣
p
)
1
/
p
L_p(x_i,x_j) = (\sum_{l = 1}^{n}|x_i^{(l)}-x_j^{(l)}|^p)^{1/p}
Lp(xi,xj)=(l=1∑n∣xi(l)−xj(l)∣p)1/p
其中
1
≤
p
1\leq p
1≤p,当
p
=
2
p = 2
p=2时,为欧氏距离,当
p
=
1
p = 1
p=1时,为曼哈顿距离,当
k
→
∞
k \rightarrow \infty
k→∞时,为最大坐标轴距离
k k k的取值
k k k的取值对结果有巨大影响: k k k值减小意味着会有过拟合的风险,增大估计误差,如果邻近模型是噪声,则会发生预测错误。 k k k值太大会增加近似误差,如果 k k k值等于了样本数目,则会造成模型完全没有意义。
k-d树及树的操作
kd树的基本思想在于加快搜索判别速度,通过提前放置数据,来达到目的。注意平衡的k-d树算法未必是最优的,由于算法步骤对于数据集(对于高维数据集采用相同的操作):
a
=
{
(
x
1
,
y
1
,
z
1
)
,
(
x
2
,
y
2
,
z
2
)
,
…
(
x
n
,
y
n
,
z
n
)
,
}
a = \left \{ (x_1,y_1,z_1),(x_2,y_2,z_2), \dots (x_n,y_n,z_n), \right \}
a={(x1,y1,z1),(x2,y2,z2),…(xn,yn,zn),}
- 对x维度按照 ( x 1 , x 2 , … , x n ) (x_1,x_2,\dots ,x_n) (x1,x2,…,xn)进行排序,找出中间点。记为第一个结点,进行划分两个数据集,即左子树和右子树
- 对左子树和右子树的y维度按照 ( y 1 , y 2 , … , y n ) (y_1,y_2,\dots ,y_n) (y1,y2,…,yn)进行排序,找出中间点,进行划分,得到对应的左子树和右子树
- 对得到的数据集进行z维度按照 ( z 1 , z 2 , … , z n ) (z_1,z_2,\dots ,z_n) (z1,z2,…,zn)进行排序,找出中间点,进行划分,得到对应的左子树和右子树
- 重复以上步骤,直到没有点可以被划分为止。
寻找k-d树d维最小坐标值点算法
- 若当前节点的切分维度是d,在其左子树进行搜索,如果左子树的第一个节点比其父节点小,则替换为。若无左子树,当前节点即是最小坐标值节点。
- 若当前节点的切分维度不是d,需在其左子树与右子树分别进行递归搜索。
伪代码,采用递归函数:
def 找维度(树,树的深度,d维度,最小向量(属性为向量)=当前树的根节点):
当前维度 = 树的深度 % 总维度
if 树 is 最后一个节点:
最小向量 = 该节点
return 最小向量
if 当前树的第一个节点向量的d维度 < 最小向量的d维度最小值(上次递归求得的):
最小向量 = 当前向量
if 当前维度 == d维度:
return 找维度(左子树,树的深度+1,d维度,最小值)
else :
左子树搜索 = 找维度(左子树,树的深度+1,d维度,最小值)
右子树搜索 = 找维度(右子树,树的深度+1,d维度,最小值)
if 左子树搜索>右子树搜索:
return 左子树搜索
else :
return 右子树搜索
新增节点
简单新增:从根节点出发,若待插入节点在当前节点切分维度的坐标值小于当前节点在该维度的坐标值时,在其左子树插入;若大于等于当前节点在该维度的坐标值时,在其右子树插入。递归遍历,直至叶子节点。
复杂新增:对树进行重构。
一般情况在增加许多点影响树的平衡性才对树进行重构
伪代码(简单新增):
def 新增节点(树,点,树的深度):
当前维度 = 树的深度 % 总维度
if 点[当前维度] < 当前树的根节点的[当前维度]:
if 当前点没有左子树:
将该点添加到树
return 新的树
else:
新增节点(左子树,点,树的深度+1)
else :
if 当前点没有右子树:
将该点添加到树
return 新的树
else:
新增节点(右子树,点,树的深度+1)
新增节点
最简单的方法是将待删节点的所有子节点组成一个新的集合,然后对其进行重新构建。将构建好的子树挂载到被删节点即可。但事实上此法过于复杂,可以考虑优化算法,在此不做赘述。
KNN基于k-d树的算法二分类(习题3.3)
记正例为1,反利为-1,设按照点的维度顺序分类
# 当前维度右移的操作可以通过控制点的索引进行
def findtree(树,当前维度 = 1,点):
if 当前树只有一个节点:
return 该节点,搜索路径
if 点[当前维度]<树的当前节点[当前维度]:
findtree(树,当前维度>>1,点)
更新搜索路径
else:
findtree(树,当前维度>>1,点)
更新搜索路径
#寻找真正的最近点
def find_real(树,点):
当前最近点,搜索路径 = findtree(树,当前维度 = 1,点)
当前点的父节点 = 当前最近点.father
#比较大小
if 当前点的父节点的距离 < 当前最近点的距离
当前最近点 = 当前点的父节点
当前最近距离 = 当前点的父节点的距离
#更新节点,计算超平面距离
超平面距离 = (当前点父节点搜索路径-点)
if 当前最近距离<超平面距离:
#当前点为最优点
return 最优点
else:
if 点[当前维度]<树的当前节点[当前维度]:
子树当前最近点,子树当前最近距离 = find_real(树,点)
else:
子树当前最近点,子树当前最近距离 = find_real(树,点)
if 子树当前最近距离<当前最近距离:
更新点,更新距离
return 当前最近点,当前最近距离
def 找最近邻(树,点,邻居数目k,标签结果=0):
if k=0:
if 标签结果>0:
return 正例
else :
return 反例
最近点标签 = find_real(树,点)
标签结果 = 标签结果+最近点标签
新树 = 树-找到的节点 #这个地方对树操作不影响找最近点
找最近邻(新树,点,邻居数目k-1,标签结果)
没咋理清楚逻辑,再看看吧。