统计学习方法笔记(李航)———第三章(k近邻法)

k 近邻法 (k-NN) 是一种基于实例的学习方法,没法转化为对参数空间的搜索问题(参数最优化 问题)。它的特色是对特征空间进行搜索。除了k近邻法,本章还对如下几个问题进行较深刻的讨
论:html

  • 切比雪夫距离 L ∞ ( x i , x j ) L_{\infty}\left(x_{i}, x_{j}\right) L(xi,xj) 的计算
  • “近似偏差” 与“估计偏差" 的含义
  • k-d树搜索算法图解

1、算法

输入:训练集 T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , … , ( x N , y N ) } , x i ∈ X ⊆ R n T=\left\{\left(x_{1}, y_{1}\right),\left(x_{2}, y_{2}\right), \ldots,\left(x_{N}, y_{N}\right)\right\}, \quad x_{i} \in X \subseteq R^{n} T={ (x1,y1),(x2,y2),,(xN,yN)},xiXRn 为实例特征向 y i ∈ Y = { c 1 , c 2 , … , c K } y_{i} \in Y=\left\{c_{1}, c_{2}, \ldots, c_{K}\right\} yiY={ c1,c2,,cK} 为实例的类别, i = 1 , 2 , … , N \quad i=1,2, \ldots, N i=1,2,,Npython

输出:实例 x x x 所属的类 y y yweb

设在给定距离度量下,涵盖最近k个点的邻域为 N K ( x ) N_{K}(x) NK(x)
y = arg ⁡ max ⁡ c j ∑ x i ∈ N K ( x ) I ( y i = c j ) , i = 1 , 2 , … , K y=\arg \max _{c_{j}} \sum_{x_{i} \in N_{K}(x)} I\left(y_{i}=c_{j}\right), \quad i=1,2, \ldots, K y=argcjmaxxiNK(x)I(yi=cj),i=1,2,,K算法

其中示性函数 I ( x ) = { 1 , x  为真  0 , x  为假 I(x)=\left\{\begin{array}{l}1, x \text { 为真 }\\ 0, x \text { 为假}\end{array}\right. I(x)={ 1,x 为真 0,x 为假svg

寻找使得函数 ∑ x i ∈ N K ( x ) I ( y i = c j ) \sum_{x_{i} \in N_{K}(x)} I\left(y_{i}=c_{j}\right) xiNK(x)I(yi=cj) 取得最大值的变量 c j , c_{j}, cj, 也就是说, 看看距离 x i x_{i} xi 最近的k个点里面哪一类别最多,以此做为输出。函数

2、模型

根据模型的分类, k-NN模型属于非几率模型。学习

观察 y = arg ⁡ max ⁡ c j ∑ x i ∈ N K ( x ) I ( y i = c j ) y=\arg \max _{c_{j}} \sum_{x_{i} \in N_{K}(x)} I\left(y_{i}=c_{j}\right) y=argmaxcjxiNK(x)I(yi=cj) 可发现它与感知机不一样的之处, 做为决策函数, 它并不须要任何未知参数(感知机须要肯定W和b),直接从训练集的数据获得输出。优化

1. 距离度量

k-NN的基本思想是,特征空间中的距离反映了两个点的类似程度, 所以 “距离" 是做出分类判断 的基本依据。向量空间 R n R^{n} Rn 的距离有多种度量方式:ui

(1) 不一样距离度量spa

通常形式是闵可夫斯基距离( L p L_{p} Lp 范数):

L p ( x i , x j ) = ( ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ p ) 1 / p L_{p}\left(x_{i}, x_{j}\right)=\left(\sum_{l=1}^{n}\left|x_{i}^{(l)}-x_{j}^{(l)}\right|^{p}\right)^{1 / p} Lp(xi,xj)=(l=1nxi(l)xj(l)p)1/p

当p=1时, 称为曼哈顿距离( L 1 L_{1} L1 范数):

L 1 ( x i , x j ) = ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ L_{1}\left(x_{i}, x_{j}\right)=\sum_{l=1}^{n}\left|x_{i}^{(l)}-x_{j}^{(l)}\right| L1(xi,xj)=l=1nxi(l)xj(l)

当p=2时,称为欧几里得距离( L 2 L_{2} L2 范数),也就是最经常使用的距离::

L 2 ( x i , x j ) = ( ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ 2 ) 1 2 L_{2}\left(x_{i}, x_{j}\right)=\left(\sum_{l=1}^{n}\left|x_{i}^{(l)}-x_{j}^{(l)}\right|^{2}\right)^{\frac{1}{2}} L2(xi,xj)=(l=1nxi(l)xj(l)2)21

import math
from itertools import combinations

def L(x, y, p=2):
    # x1 = [1, 1], x2 = [5,1]
    if len(x) == len(y) and len(x) > 1:
        sum = 0
        for i in range(len(x)):
            sum += math.pow(abs(x[i] - y[i]), p)
        return math.pow(sum, 1 / p)
    else:
        return 0

下图表示平面上A、B两点之间不一样的距离:
在这里插入图片描述

  • L 1 L_{1} L1 只容许沿着坐标轴方向前进, 就像曼哈顿街区的出租车走过的距离
  • L 2 L_{2} L2 两点之间直线最短, 经过勾股定理计算斜边的距离
  • L ∞ L_{\infty} L只剩下一个维度, 即最大的份量方向上的距离

可见p取值越大,两点之间的距离越短。

(2) 问题:为何切比雪夫距离 L ∞ ( x i , x j ) = max ⁡ l ∣ x i ( l ) − x j ( l ) ∣ ? L_{\infty}\left(x_{i}, x_{j}\right)=\max _{l}\left|x_{i}^{(l)}-x_{j}^{(l)}\right| ? L(xi,xj)=maxlxi(l)xj(l)?

其实这个问题等价于:为何 ∥ x ∥ ∞ = max ⁡ ∣ x ( l ) ∣ , \|x\|_{\infty}=\max \left|x^{(l)}\right|, x=maxx(l), R n R^{n} Rn 空间中的向量 x , x, x, 它的切比雪 夫长度等于它的最大份量方向上的长度。

证实: 设 x = ( x ( 1 ) , x ( 2 ) , … x ( n ) ) T ∈ X ⊆ R n x=\left(x^{(1)}, x^{(2)}, \ldots x^{(n)}\right)^{T} \in X \subseteq R^{n} x=(x(1),x(2),x(n))TXRn

不妨设 ∣ x ( 1 ) ∣ = max ⁡ { ∣ x ( 1 ) ∣ , ∣ x ( 2 ) , … , ∣ x ( n ) ∣ ∣ } , \left|x^{(1)}\right|=\max \left\{\left|x^{(1)}\right|,\left|x^{(2)}, \ldots,\right| x^{(n)}||\right\}, x(1)=max{ x(1),x(2),,x(n)}, ∣ x ( l ) ∣ ≤ ∣ x ( 1 ) ∣ \left|x^{(l)}\right| \leq\left|x^{(1)}\right| x(l)x(1)

注意:最大份量的长度惟一, 但最大份量可能并不惟 一,$设有 x ( 1 ) , x ( 2 ) , … x ( k ) x^{(1)}, x^{(2)}, \ldots x^{(k)} x(1),x(2),x(k) 等K个份量的 长度都等于 ∣ x ( 1 ) ∣ \left|x^{(1)}\right| x(1)

∣ x ( l ) ∣ = ∣ x ( 1 ) ∣ , lim ⁡ p → ∞ ( ∣ x ( l ) ∣ ∣ x ( 1 ) ∣ ) p = 1 , \left|x^{(l)}\right|=\left|x^{(1)}\right|, \quad \lim _{p \rightarrow \infty}\left(\frac{\left|x^{(l)}\right|}{\left|x^{(1)}\right|}\right)^{p}=1, x(l)=x(1),limp(x(1)x(l))p=1, x ( l ) x^{(l)} x(l) x ( 1 ) , x ( 2 ) , … x ( k ) x^{(1)}, x^{(2)}, \ldots x^{(k)} x(1),x(2),x(k)

∣ x ( l ) ∣ < ∣ x ( 1 ) ∣ , lim ⁡ p → ∞ ( ∣ x ( l ) ∣ ∣ x ( 1 ) ∣ ) p = 0 , \left|x^{(l)}\right|<\left|x^{(1)}\right|, \quad \lim _{p \rightarrow \infty}\left(\frac{\left|x^{(l)}\right|}{\left|x^{(1)}\right|}\right)^{p}=0, x(l)<x(1),limp(x(1)x(l))p=0, x ( l ) x^{(l)} x(l) 为非最大长度份量时

计算 x x x 的切比雪夫长度:

∥ x ∥ ∞ = lim ⁡ p → ∞ ( ∑ l = 1 n ∣ x ( l ) ∣ p ) 1 p = lim ⁡ p → ∞ ( ∣ x ( 1 ) ∣ p ∑ l = 1 n ∣ x ( l ) ∣ p ∣ x ( 1 ) ∣ p ) 1 p \|x\|_{\infty}=\lim _{p \rightarrow \infty}\left(\sum_{l=1}^{n}\left|x^{(l)}\right|^{p}\right)^{\frac{1}{p}}=\lim _{p \rightarrow \infty}\left(\left|x^{(1)}\right|^{p} \sum_{l=1}^{n} \frac{\left|x^{(l)}\right|^{p}}{\left|x^{(1)}\right|^{p}}\right)^{\frac{1}{p}} x=limp(l=1nx(l)p)p1=limp(x(1)pl=1nx(1)px(l)p)p1

因为已知 lim ⁡ p → ∞ ( ∣ x ( l ) ∣ ∣ x ( 1 ) ∣ ) p \lim _{p \rightarrow \infty}\left(\frac{\left|x^{(l)}\right|}{\left|x^{(1)}\right|}\right)^{p} limp(x(1)x(l))p 等于0或1,且有k个份量结果为1, 因此

lim ⁡ p → ∞ ∑ l = 1 n ( ∣ x ( l ) ∣ ∣ x ( 1 ) ∣ ) p = ( 1 + 1 + … + 1 + 0 + 0 + … . 0 ) = k \lim _{p \rightarrow \infty} \sum_{l=1}^{n}\left(\frac{\left|x^{(l)}\right|}{\left|x^{(1)}\right|}\right)^{p}=(1+1+\ldots+1+0+0+\ldots .0)=k limpl=1n(x(1)x(l))p=(1+1++1+0+0+.0)=k

所以
∥ x ∥ ∞ = lim ⁡ p → ∞ ( ∣ x ( 1 ) ∣ p ∑ l = 1 n ∣ x ( l ) ∣ p ∣ x ( 1 ) ∣ p ) 1 p = lim ⁡ p → ∞ ( ∣ x ( 1 ) ∣ p k ) 1 p = lim ⁡ p → ∞ ∣ x ( 1 ) ∣ k 1 p = ∣ x ( 1 ) ∣ \|x\|_{\infty}=\lim _{p \rightarrow \infty}\left(\left|x^{(1)}\right|^{p} \sum_{l=1}^{n} \frac{\left|x^{(l)}\right|^{p}}{\left|x^{(1)}\right|^{p}}\right)^{\frac{1}{p}}=\lim _{p \rightarrow \infty}\left(\left|x^{(1)}\right|^{p} k\right)^{\frac{1}{p}}=\lim _{p \rightarrow \infty}\left|x^{(1)}\right| k^{\frac{1}{p}}=\left|x^{(1)}\right| x=plim(x(1)pl=1nx(1)px(l)p)p1=plim(x(1)pk)p1=plimx(1)kp1=x(1)
∥ x ∥ ∞ = max ⁡ ∣ x ( l ) ∣ , \|x\|_{\infty}=\max \left|x^{(l)}\right|, x=maxx(l), 得证。

以上证实参考

(3) 平面上的圆

L p = 1 L_{p}=1 Lp=1 在平面上的图像:
在这里插入图片描述
若是圆形的定义是 “到某一点距离相等的曲线围成的图形" ,那么在不一样的距离度量下,圆形的形 状能够彻底不一样。为什么 L 1 L_{1} L1 正则化在平面上的等高线为同心正方形, 不就很容易理解吗?

  1. k值选择

李航老师书中引入了“近似偏差”和“估计偏差”两个概念,但没有给出具体定义。

这里简单总结一下:
I [ f ^ H , l ] − I [ f 0 ] = ( I [ f ^ H , l ] − I [ f H ] ) + ( I [ f H ] − I [ f 0 ] ) I\left[\hat{f}_{H, l}\right]-I\left[f_{0}\right]=\left(I\left[\hat{f}_{H, l}\right]-I\left[f_{H}\right]\right)+\left(I\left[f_{H}\right]-I\left[f_{0}\right]\right) I[f^H,l]I[f0]=(I[f^H,l]I[fH])+(I[fH]I[f0])
右侧两项分别是 “估计偏差” 和 “近似偏差”

  • 估计偏差:训练集与无限数据集获得的模型的差距
  • 近似偏差:限制假设空间与无限制假设空间获得的模型的差距
    • k值越小 ⇔ \Leftrightarrow 单个样本影响越大 ⇔ \Leftrightarrow 模型越复杂 ⇔ \Leftrightarrow 假设空间越大 ⇒ \Rightarrow 近似偏差越小 (估计偏差 越大),容易过拟合;
    • k值越大 ⇔ \Leftrightarrow 单个样本影响越小 ⇔ \Leftrightarrow 模型越简单 ⇔ \Leftrightarrow 假设空间越小 ⇒ \Rightarrow 近似偏差越大(估计偏差 越小),容易欠拟合。

通常经过交叉验证来肯定最优的 k 值。

  1. 决策规则

k 近邻的决策规则就是 “多数表决" ,即少数服从多数, 用数学表达式表示为

1 k ∑ x i ∈ N k ( x ) I ( y i ≠ c j ) = 1 − 1 k ∑ x i ∈ N k ( x ) I ( y i = c j ) \frac{1}{k} \sum_{x_{i} \in N_{k}(x)} I\left(y_{i} \neq c_{j}\right)=1-\frac{1}{k} \sum_{x_{i} \in N_{k}(x)} I\left(y_{i}=c_{j}\right) k1xiNk(x)I(yi=cj)=1k1xiNk(x)I(yi=cj)

等号左侧为误分类率,要是误分类率最小,就要使得 ∑ x i ∈ N k ( x ) I ( y i = c j ) \sum_{x_{i} \in N_{k}(x)} I\left(y_{i}=c_{j}\right) xiNk(x)I(yi=cj) 最大, 即选择集合 N k ( x ) N_{k}(x) Nk(x) 中最多的一类。

3、kd树

参见本篇文章:(必定看)
kd树介绍(KNN算法引出)

4、习题

3.1

参照图3.1,在二维空间中给出实例点,画出kk为1和2时的kk近邻法构成的空间划分,并对其进行比较,体会kk值选择与模型复杂度及预测准确率的关系。

%matplotlib inline
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap 

data = np.array([[5, 12, 1],
                [6, 21, 0],
                [14, 5, 0],
                [16, 10, 0],
                [13, 19, 0],
                [13, 32, 1],
                [17, 27, 1],
                [18, 24, 1],
                [20, 20, 0],
                [23, 14, 1],
                [23, 25, 1],
                [23, 31, 1],
                [26, 8, 0],
                [30, 17, 1],
                [30, 26, 1],
                [34, 8, 0],
                [34, 19, 1],
                [37, 28, 1]])
X_train = data[:, 0:2]
y_train = data[:, 2]

models = (KNeighborsClassifier(n_neighbors=1, n_jobs=-1),
          KNeighborsClassifier(n_neighbors=2, n_jobs=-1))
models = (clf.fit(X_train, y_train) for clf in models)

titles = ('K Neighbors with k=1',
          'K Neighbors with k=2')

fig = plt.figure(figsize=(15, 5))
plt.subplots_adjust(wspace=0.4, hspace=0.4)

X0, X1 = X_train[:, 0], X_train[:, 1]

x_min, x_max = X0.min() - 1, X0.max() + 1
y_min, y_max = X1.min() - 1, X1.max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.2),
                         np.arange(y_min, y_max, 0.2))

for clf, title, ax in zip(models, titles, fig.subplots(1, 2).flatten()):    
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape) 
    colors = ('red', 'green', 'lightgreen', 'gray', 'cyan')  
    cmap = ListedColormap(colors[:len(np.unique(Z))])  
    ax.contourf(xx, yy, Z, cmap=cmap, alpha=0.5)

    ax.scatter(X0, X1, c=y_train, s=50, edgecolors='k', cmap=cmap, alpha=0.5)
    ax.set_title(title)

plt.show()

在这里插入图片描述
3.2
利用例题3.2构造的kd树求点x = ( 3 , 4.5 ) T =(3,4.5)^{\mathrm{T}} =(3,4.5)T 的最近邻点。

import numpy as np
from sklearn.neighbors import KDTree

train_data = np.array([(2, 3), (5, 4), (9, 6), (4, 7), (8, 1), (7, 2)])
tree = KDTree(train_data, leaf_size=2)
dist, ind = tree.query(np.array([(3, 4.5)]), k=1)
x1 = train_data[ind[0]][0][0]
x2 = train_data[ind[0]][0][1]

print("x点的最近邻点是({0}, {1})".format(x1, x2))
#x点的最近邻点是(2, 3)

3.3
解答:
算法:用kd树的kk近邻搜索
输入:已构造的kd树;目标点xx;
输出:xx的最近邻

  1. 在kdkd树中找出包含目标点xx的叶结点:从根结点出发,递归地向下访问树。若目标点xx当前维的坐标小于切分点的坐标,则移动到左子结点,不然移动到右子结点,直到子结点为叶结点为止;
  2. 若是“当前kk近邻点集”元素数量小于kk或者叶节点距离小于“当前kk近邻点集”中最远点距离,那么将叶节点插入“当前k近邻点集”;
  3. 递归地向上回退,在每一个结点进行如下操做:
    (a)若是“当前kk近邻点集”元素数量小于kk或者当前节点距离小于“当前kk近邻点集”中最远点距离,那么将该节点插入“当前kk近邻点集”。
    (b)检查另外一子结点对应的区域是否与以目标点为球心、以目标点与于“当前kk近邻点集”中最远点间的距离为半径的超球体相交。若是相交,可能在另外一个子结点对应的区域内存在距目标点更近的点,移动到另外一个子结点,接着,递归地进行最近邻搜索;若是不相交,向上回退;
  4. 当回退到根结点时,搜索结束,最后的“当前kk近邻点集”即为xx的最近邻点。
# 构建kd树,搜索待预测点所属区域
from collections import namedtuple
import numpy as np


# 创建节点类
class Node(namedtuple("Node", "location left_child right_child")):
    def __repr__(self):
        return str(tuple(self))


# kd tree类
class KdTree():
    def __init__(self, k=1):
        self.k = k
        self.kdtree = None

    # 构建kd tree
    def _fit(self, X, depth=0):
        try:
            k = self.k
        except IndexError as e:
            return None
        # 这里能够展开,经过方差选择axis
        axis = depth % k
        X = X[X[:, axis].argsort()]
        median = X.shape[0] // 2
        try:
            X[median]
        except IndexError:
            return None
        return Node(
            location=X[median],
            left_child=self._fit(X[:median], depth + 1),
            right_child=self._fit(X[median + 1:], depth + 1)
        )

    def _search(self, point, tree=None, depth=0, best=None):
        if tree is None:
            return best
        k = self.k
        # 更新 branch
        if point[0][depth % k] < tree.location[depth % k]:
            next_branch = tree.left_child
        else:
            next_branch = tree.right_child
        if not next_branch is None:
            best = next_branch.location
        return self._search(point, tree=next_branch, depth=depth + 1, best=best)

    def fit(self, X):
        self.kdtree = self._fit(X)
        return self.kdtree

    def predict(self, X):
        res = self._search(X, self.kdtree)
        return res
KNN = KdTree()
X_train = np.array([[2, 3],
                    [5, 4],
                    [9, 6],
                    [4, 7],
                    [8, 1],
                    [7, 2]])
KNN.fit(X_train)
X_new = np.array([[3, 4.5]])
res = KNN.predict(X_new)

x1 = res[0]
x2 = res[1]

print("x点的最近邻点是({0}, {1})".format(x1, x2))
#x点的最近邻点是(2, 3)