k近邻算法是非常自然的一种分类方法,即选取k个与输入数据距离最近的数据集中的点,这k个点的多数属于哪个类,就把输入数据归类为哪个类。而这其中只要数据集,距离度量,k值,决策方法有关,下面我们就从这几个方面进行分析。(这几周太懒了,没有坚持,以后一定经常更新)
距离度量
定义
这一小节我们把所有的统计学习方法中实例进行声明:对于输入的特征向量 x ∈ R n , x\in R^n, x∈Rn,代表特征空间是n维的,其中对于一个给定的实例 x i = ( x i ( 1 ) , x i ( 2 ) , x i ( 3 ) . . . , x i ( l ) . . . , x i ( n ) ) ⊺ x_i=(x_i^{(1)},x_i^{(2)},x_i^{(3)}...,x_i^{(l)}...,x_i^{(n)})^\intercal xi=(xi(1),xi(2),xi(3)...,xi(l)...,xi(n))⊺.
LP距离
L p ( x i , x j ) = ( ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ p ) 1 p ; p ≥ 1 L_p(x_i,x_j)=\bigg(\sum_{l=1}^n|x_i^{(l)}-x_j^{(l)}|^p\bigg)^{\frac{1}{p}};p\ge1 Lp(xi,xj)=(l=1∑n∣xi(l)−xj(l)∣p)p1;p≥1
欧式距离
就是LP距离中
p
=
2
p=2
p=2的情况:
L
(
x
i
,
x
j
)
=
(
∑
l
=
1
n
∣
x
i
(
l
)
−
x
j
(
l
)
∣
2
)
1
2
L(x_i,x_j)=\bigg(\sum_{l=1}^n|x_i^{(l)}-x_j^{(l)}|^2\bigg)^{\frac{1}{2}}
L(xi,xj)=(l=1∑n∣xi(l)−xj(l)∣2)21
曼哈顿距离
就是LP距离中
p
=
1
p=1
p=1的情况:
L
(
x
i
,
x
j
)
=
∑
l
=
1
n
∣
x
i
(
l
)
−
x
j
(
l
)
∣
L(x_i,x_j)=\sum_{l=1}^n|x_i^{(l)}-x_j^{(l)}|
L(xi,xj)=l=1∑n∣xi(l)−xj(l)∣
切比雪夫距离
就是LP距离中
p
=
∞
p=\infty
p=∞的情况:
L
(
x
i
,
x
j
)
=
m
a
x
l
∣
x
i
(
l
)
−
x
j
(
l
)
∣
L(x_i,x_j)=\underset{l}{\mathrm max}|x_i^{(l)}-x_j^{(l)}|
L(xi,xj)=lmax∣xi(l)−xj(l)∣
k值
很自然的,选择k值对整个模型是至关重要的,但是并不好确定。因为k值太小,则会导致输入点仅和输入点最近的实例点非常敏感,也就是发生过拟合。而k值太大,则会导致太过于照顾全局,而分类不准,也就是发生欠拟合。
决策方法
k近邻一般采用多数表决规则。
KD Tree
背景
k近邻的方法中最为关键的一步是寻找k个近邻的数据点,如果全量搜索工作量会很大,效率也是难以忍受的,因此KD Tree这种数据结构被提出用于方便寻找k近邻的搜索过程。
约定和说明
- 划分点都为指定维度的中位数,这样得到的是平衡KD Tree
- 平衡的KD Tree并不意味着搜索性能最高
- 注意KD Tree中的k指的是每个数据的维度为k,和感知机一节的 n n n等效,但为了保持叫法我们还是叫做KD Tree,但k近邻指的是个数。两个k的含义并不相同。
构建方法
KD Tree的基本思想就是依次在数据的k个维度上进行切分,直至不能切分为止,切分的标准就是尽量使得切分后的空间内实例点个数相等。
举一个例子感受一下,有以下数据:
T
=
{
(
1
,
2
,
2
)
,
(
2
,
3
,
4
)
,
(
3
,
2
,
4
)
,
(
4
,
3
,
6
)
,
(
6
,
4
,
7
)
,
(
3
,
5
,
2
)
}
T=\{(1,2,2),(2,3,4),(3,2,4),(4,3,6),(6,4,7),(3,5,2)\}
T={(1,2,2),(2,3,4),(3,2,4),(4,3,6),(6,4,7),(3,5,2)}
至此基于训练集 T T T的KD Tree就构建完成了。
搜索方法
以搜索最近邻的点为例,k近邻和最近邻类似
- 从上而下叶节点确定:从根节点开始,依次比较不同维度的数寻找到目标点所在的叶节点。
- 认定当前叶节点为最近邻点,然后计算距离成为最近邻距离,往上回溯至父节点1,判断目标点是否与父节点1的距离是否小于最近邻距离,如果小于,将该父节点1作为最近邻点,如果大于,判断目标点为中心,最近邻为半径的超球体是否与该父节点1所在的轴相交,如果相交,则对父节点1另一侧的空间进行搜索,如果不相交,则向上回退至父节点2。直至回到根节点。
算法复杂度为 l o g ( N ) log(N) log(N)
习题解答
3.1在二维空间中给出实例点,画出k=1和k=2时的k近邻法构成的特征划分。
回过头看图3.1,发现有些问题,在这里提出来,希望大家轻拍,这个图是有问题的,对于k=1的时候,交点应该偏好于三个相对近邻点的外心,因此不会形成砖块一样超出的形状,如下图红圈圈出的位置。(**可能是距离度量不是欧式距离? **)
为了验证我的想法,花了十分种简单写了段代码,用来复现图中的工作:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(233)
train_x = np.array([[i for i in range(1000)] for j in range(1000)]).reshape(-1)
train_y = np.array([[j for i in range(1000)] for j in range(1000)]).reshape(-1)
test_x = np.random.rand(10)*1000
test_y = np.random.rand(10)*1000
label = []
for i in range(len(train_x)):
distance = float('inf')
for j in range(len(test_x)):
temp = (test_y[j] - train_y[i]) ** 2 + (test_x[j] - train_x[i]) ** 2
if temp < distance:
distance = temp
l = j
label.append(l)
plt.figure(figsize=(10,10))
plt.scatter(train_x,train_y,c=label)
plt.scatter(test_x,test_y,c='red')
结果显示为:
除了欧式距离,我还依次尝试了曼哈顿距离和切比雪夫距离:
但没有一种是李航老师书中那种划分方式,因此我十分困惑,希望有读者知道原因的不吝赐教。
好了,回到正题:
k=1上面已经说了一部分,当k=2时,情况就有些复杂,如果两个最近邻点不属于同一类,此时我们将其划分为第三类也就是待决策类。为了更清楚说明,对于k=2的时候,我们将10个点分为2类,采用欧式距离进行计算。
k=2,对上面代码稍微修改
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(233)
train_x = np.array([[i for i in range(1000)] for j in range(1000)]).reshape(-1)
train_y = np.array([[j for i in range(1000)] for j in range(1000)]).reshape(-1)
test_x = np.random.rand(10)*1000
test_y = np.random.rand(10)*1000
test_label = [0]*5+[1]*5
label = []
for i in range(len(train_x)):
distance = []
for j in range(len(test_x)):
temp = (test_y[j] - train_y[i]) ** 2 + (test_x[j] - train_x[i]) ** 2
distance.append(temp)
l_1,l_2 = np.argsort(distance)[:2]
if test_label[l_1] == test_label[l_2]:
label.append(test_label[l_1])
else:
label.append(-1)
plt.figure(figsize=(10,10))
plt.scatter(train_x,train_y,c=label)
plt.scatter(test_x[:5],test_y[:5],c='red',marker='x',label=0)
plt.scatter(test_x[5:],test_y[5:],c='red',label=1)
plt.legend()
注意:紫色为待定的区域
k=1时,修改label标注选择即可:
# l_1,l_2 = np.argsort(distance)[:2]
# if test_label[l_1] == test_label[l_2]:
# label.append(test_label[l_1])
# else:
# label.append(-1)
##上面的代码改为
l_1,l_2 = np.argsort(distance)[:2]
label.append(test_label[l_1])
可以明显看出,两者差别还是非常大的,基本上k=2是k=1的子区域,相当于k
2将k=1时的置信度更加提高了,也就是只有最近的两个点都表决为同一类时才能确定为此类。
3.2 搜索路径为:
(4,7),(5,4),(2,3),(7,2)
3.3 参照算法3.3,写出输出为x的k近邻的算法
待补充