上上一章已经学习了感知机模型、策略和算法,感知机对于分类任务有着其优点,但是该模型是在具有强假设的条件下——训练数据集必须是线性可分的,但是如果数据集是呈现无规则的分布,那么此时如果要做分类任务,还可以考虑k近邻(KNN),这是一种基本的分类和回归方法,既可以做简单的二分类也可以做复杂的多分类任务,还可以做回归任务。

KNN模型

KNN模型实际上对应于对特征空间的划分,虽然没有具体的数学抽象语言描述,但是仍然存在其三要素:距离度量、K值的选择、分类决策规则

距离度量

\[设特征空间\chi是n维实数向量空间R^n,x_i,x_j \in \chi,x_i=(x_i^{(1)},x_i^{(2)},x_i^{(3)}...,x_i^{(n)})^T,\\ x_j=(x_j^{(1)},x_j^{(2)},x_j^{(3)}...,x_j^{(n)})^T,x_i,x_j的距离可定义为:\\ L_P(x_i,x_j)=(\sum^n_{l=1}|x_i^{(l)}-x_j^{(l)}|^p)^{\frac{1}{p}}\\ 一般地,当p=1时,L_1(x_i,x_j)=(\sum^n_{l=1}|x_i^{(l)}-x_j^{(l)}|),称为曼哈顿距离;\\ 当p=2时,L_2(x_i,x_j)=(\sum^n_{l=1}|x_i^{(l)}-x_j^{(l)}|^2)^{\frac{1}{2}},其实形式上也是L2范数,称为欧式距离,平常使用的比较多;\\ 当p=\infty,它是各个坐标距离的最大值,即为:L_{\infty}(x_i,x_j)=max|x_i^{(l)}-x_j^{(l)}| \]

K值的选择

除了距离度量外,还有K值的选择对KNN算法的结果也会产生重大影响。

  • 如果选择较小的k值,就相当于用较小的领域中的训练实例进行预测,“学习”的近似误差会减小,只有与输入实例较近的实例才会对预测结果起到作用,但缺点就是学习的估计误差就会增大,预测结果就会近邻的实例点非常敏感;
  • 如果选择较大的值,学习的误差估计会减小,但是与此同时,近似误差就会增大,这时会出现对于距离比较远的实例点起不到预测作用,使得预测结果错误。

分类决策规则

KNN中的决策规则通常就是“投票选举”——少数服从多数的方式。

如果损失函数是0-1损失函数,那么分类函数就是:

\[f:R^n \rightarrow {\{c_1,c_2,...,c_k}\} \]

对于相邻k个训练实例点构成集合N,误分类率是:

\[\frac{1}{k}\sum_{x_i\in N_k(x)}I(y_i \neq c_j )=1-\frac{1}{k}\sum_{x_i\in N_k(x)}I(y_i = c_j ) \]

要使误分类率最小,那么就是要求正确的概率最大,所以少数服从多数的规则正好可以满足经验风险最小化。

KNN算法

算法描述

\[输入:训练数据集:T=\{(x_1,y_1),(x_2,y_2),(x_3,y_3)...,(x_N,y_N)\},其中x_i \in \chi \subseteq R^n为实例的特征向量,\\ y_i \in y=\{c_1,c_2,...,c_K\}为实例得类别,i=1,2,...,;实例特征向量x;\\ 输出:实例x所属的类别y。\\ (1)根据给定的距离向量,在训练集T中找出与x最近邻的k个点,包含k个点的x的领域记作N_k(x);\\ (2)在N_k(x)中根据分类决策规则决定x的类别y:y=argmax\sum_{x_i\in N_k(x)}I(y_i = c_j ),i,j=1,2,..,N。 \]

实现KNN时,主要是考虑的问题时如何对训练数据进行快速K近邻搜索,如果特征空间的维数大或者训练数据容量大时,那么数据存储就是一个大问题。KNN最简单的实现方法是线性扫描,这时当数据集很大,计算就非常地耗时。为了提高这种搜索效率,使用特殊地结构进行存储训练数据——kd树(kd tree)。kd树是一种对k维空间中的点进行存储以便于对其进行快速搜索的树形数据结构。实质上,kd树是一种二叉树,表示对k维空间的一个划分

代码实现

自编程实现

class KNN:
    """
    使用自编程实现KNN算法
    @author cecilia
    """
    def __init__(self,X_train,y_train,k=3):
        # 所需参数初始化
        self.k=k   # 所取k值
        self.X_train=X_train
        self.y_train=y_train

    def predict(self,X_new):
        # 计算欧氏距离
        dist_list=[(np.linalg.norm(X_new-self.X_train[i],ord=2),self.y_train[i])
                   for i in range(self.X_train.shape[0])]
        #[(d0,-1),(d1,1)...]
        # 对所有距离进行排序
        dist_list.sort(key=lambda x: x[0])
        # 取前k个最小距离对应的类别(也就是y值)
        y_list=[dist_list[i][-1] for i in range(self.k)]
        # [-1,1,1,-1...]
        # 对上述k个点的分类进行统计
        y_count=Counter(y_list).most_common()
        # [(-1, 3), (1, 2)]
        return y_count[0][0]

def main():
  	# 初始化数据
    X_train=np.array([[5,4],
                      [9,6],
                      [4,7],
                      [2,3],
                      [8,1],
                      [7,2]])
    y_train=np.array([1,1,1,-1,-1,-1])
    # 测试数据
    X_new = np.array([[5, 3]])
    # 不同的k(取奇数)对分类结果的影响
    for k in range(1,6,2):
        #构建KNN实例
        clf=KNN(X_train,y_train,k=k)
        #对测试数据进行分类预测
        y_predict=clf.predict(X_new)
        print("k={},class label is:{}".format(k,y_predict))

Sklearn库

from sklearn.neighbors import KNeighborsClassifier

def sklearn_knn():
    """
    使用sklearn库实现KNN算法
    @author cecilia
    """
    X_train=np.array([[5,4],
                      [9,6],
                      [4,7],
                      [2,3],
                      [8,1],
                      [7,2]])
    y_train=np.array([1,1,1,-1,-1,-1])
    # 待预测数据
    X_new = np.array([[5, 3]])
    # 不同k值对结果的影响
    for k in range(1,6,2):
        # 构建实例
        clf = KNeighborsClassifier(n_neighbors=k,n_jobs=-1)
        # 选择合适算法
        clf.fit(X_train, y_train)
        # print(clf.kneighbors(X_new))
        # 预测
        y_predict=clf.predict(X_new)
        #print(clf.predict_proba(X_new))
        print("accuracy:{:.0%}".format(clf.score([[5,3]],[[1]])))
        print("k={},label lcass is:{}".format(k,y_predict))

结果显示:

java knn回归模型 knn回归算法_数据集

思考

KNN算法模型的复杂度主要是体现在哪儿?什么情况下会造成过拟合?

k临近算法的模型复杂度体现在k值上;k值较小容易造成过拟合,k值较大容易造成欠拟合。