K近邻算法介绍及numpy实现

K近邻是机器学习中最基础最简单的算法了,没有之一!
最近在复习机器学习、深度学习的算法,从头好好过一遍吧!

1.K近邻分类算法描述

输入:(1)训练样本集D,包括dataX, dataY (2)观测样本x
输出:观测样本x所属的类别y.
step0. 训练集D的输入部分预处理,并记录预处理的使用参数
step1. 指定距离度量,并选择K值
step2. 训练集D内找到预处理的样本x的前K个近邻
step3. 结合指定的决策规则,对x的类别y进行预测

2.三个基本要素

k近邻算法:计算已知类别数据集中的点与当前样本点之间的距离,根据前k个距离最小的样本投票决定目标样本的类别。
由此可知,K近邻算法的三个基本要素为:距离度量、超参数K的确定、决策规则(注:超参数即需要人为给定的参数,这里K一般不超过20)。

典型的距离度量方式与规范化预处理

距离度量方式

计算样本点之间距离的方式:

  1. L p L_{p} Lp距离 (闵可夫斯基距离)
    L p ( x a , x b ) = ( ∑ i = 1 d ∣ x a ( i ) − x b ( i ) ∣ p ) 1 p L_{p}(x_{a}, x_{b}) = \left ( \sum_{i=1}^{d}\left |x_{a}^{\left ( i \right )} - x_{b}^{\left ( i \right )} \right |^{p} \right )^{\frac{1}{p}} Lp(xa,xb)=(i=1dxa(i)xb(i)p)p1,其中,p>=1
  2. 欧式距离 ,也叫欧几里得距离,当p=2时
    d ( x a , x b ) = ∑ i = 1 d ( x a ( i ) − x b ( i ) ) 2 d(x_{a}, x_{b}) = \sqrt{\sum_{i=1}^{d} (x_{a}^{\left ( i \right )}-x_{b}^{\left ( i \right )})^{2}} d(xa,xb)=i=1d(xa(i)xb(i))2
  3. 曼哈顿距离 ,也叫绝对值距离,当p=1时
    L 1 ( x a , x b ) = ∑ i = 1 d ∣ x a ( i ) − x b ( i ) ∣ L_{1}(x_{a}, x_{b}) = \sum_{i=1}^{d}\left |x_{a}^{\left ( i \right )} - x_{b}^{\left ( i \right )} \right | L1(xa,xb)=i=1dxa(i)xb(i)
  4. 切比雪夫距离 ,也叫切氏距离、拉格朗日距离,当p= ∞ \infty
    L ∝ ( x a , x b ) = m a x i ∣ x a ( i ) − x b ( i ) ∣ L_{\propto }(x_{a}, x_{b}) = \underset{i}{max}\left | {x_{a}}^{\left ( i \right )}-x_{b}^{\left ( i \right )} \right | L(xa,xb)=imaxxa(i)xb(i)
规范化预处理

规范化预处理的必要性:
样本的各特征间量纲的不同,固定量纲下采用不同量纲单位,会导致不同数量级的特征取值,所以在距离度量时,可能产生很大的问题:
大数量级、大动态范围的特征与小数量级、小动态范围的特征同时参与距离度量:起主导作用的前者可能淹没后者特征变化,起主导作用的特征所含的类鉴别信息不一定明显。

  1. z-score归一化(推荐)
    x k ′ = x k − μ k σ k x_{k}{}' = \frac{x_{k} - \mu _{k}}{\sigma _{k}} xk=σkxkμk
  2. min-max归一化 (线性映射)
    x k ′ = x k − x k m i n x k m a x − x k m i n x_{k}{}' = \frac{x_{k} - x_{kmin}}{x_{kmax} - x_{kmin}} xk=xkmaxxkminxkxkmin

超参数K值的选取

m折交叉验证+分类问题评价指标

step1训练集随机打乱,均分成m等份,每一份的训练样本数目 N m \frac{N}{m} mN
step2:对于每个备选K值,
2-1. for i = 1, … , m do
拿出第i份作为验证集,其余m-1份作为估计集,利用估计集,对验证集的每个样本进行类别预测,得验证集预测错误率 E r r i ( K ) Err_{i}\left ( K \right ) Erri(K)
2-2. 估计对应于该备选K值的平均错误率 μ E r r ( K ) \mu _{Err\left ( K \right )} μErr(K)、标准差 σ E r r ( K ) \sigma _{Err\left ( K \right )} σErr(K)
μ E r r ( K ) = 1 m ⋅ ∑ i = 1 m E r r i ( K ) \mu _{Err\left ( K \right )} = \frac{1}{m} \cdot \sum_{i=1}^{m} Err_{i}\left ( K \right ) μErr(K)=m1i=1mErri(K)
σ E r r ( K ) = 1 m ⋅ ∑ i = 1 m ( E r r i ( K ) − μ E r r ( K ) ) 2 \sigma _{Err\left ( K \right )} = \sqrt{\frac{1}{m}\cdot \sum_{i=1}^{m}\left ( Err_{i}\left ( K \right )-\mu _{Err\left ( K \right )} \right )^{2}} σErr(K)=m1i=1m(Erri(K)μErr(K))2
表示为 μ E r r ( K ) ± σ E r r ( K ) \mu _{Err\left ( K \right )}\pm \sigma _{Err\left ( K \right )} μErr(K)±σErr(K)
step3:选择最低的 μ E r r ( K ) \mu _{Err\left ( K \right )} μErr(K)对应的K值作为最终选择结果,若同时多个K值对应最小的 μ E r r ( K ) \mu _{Err\left ( K \right )} μErr(K),则选择其中最小的 σ E r r ( K ) \sigma _{Err\left ( K \right )} σErr(K)对应的K值作为最终选择结果。

决策规则

  1. 胜者为王 (多数表决)——传统的K近邻决策方式
    等权投票,相当于每个样本投票的权重均为1。
    观测x的K个近邻 N K ( x ) = N K , 1 ( x ) ∪ N K , 2 ( x ) ∪ . . . ∪ N K , C ( x ) N_{K}\left ( x \right ) = N_{K,1}(x)\cup N_{K,2}(x)\cup...\cup N_{K,C}(x) NK(x)=NK,1(x)NK,2(x)...NK,C(x)
    来自第j类的近邻数: k j = ∑ x i ∈ N K ( x ) I ( y i = j ) = ∣ N K , j ( x ) ∣ k_{j} = \sum_{x_{i\in N_{K}(x)}}^{}I(y_{i} = j) = \left | N_{K, j}(x) \right | kj=xiNK(x)I(yi=j)=NK,j(x)
    K = ∑ j = 1 C k j \sum_{j = 1}^{C}k_{j} j=1Ckj
    决策规则:若 k l k_{l} kl = m a x j = 1 , 2 , . . . , C k j \underset{j=1,2,...,C}{max}k_{j} j=1,2,...,Cmaxkj,则将x决策为第l类。
    特殊情况:有多个类别同时达到票数最多。
    处理方法: 选择最近邻样本对应的类别;随机选择其中一个类别;选择距离均值向量最近的样本对应的类别
  2. 加权投票
    N K ( x ) N_{K}(x) NK(x)中K个训练样本类别标号 a 1 a_{1} a1, …, a K a_{K} aK,K个训练样本关于x的距离为 δ a 1 ≤ δ a 2 ≤ . . . ≤ δ a K \delta _{a_{1}} \leq \delta _{a_{2}}\leq ...\leq \delta _{a_{K}} δa1δa2...δaK
    统计第j类的决策权重
    W j = ∑ i = 1 K w ( δ a i ) I ( a i = j ) , j = 1 , . . . , C W_{j} = \sum_{i=1}^{K}w(\delta {a_{i}})I(a_{i}=j),j = 1, ..., C Wj=i=1Kw(δai)I(ai=j),j=1,...,C
    类别决策:若 W l = m a x j = 1 , 2 , . . . , C W j W_{l} = \underset{j=1,2,...,C}{max}W_{j} Wl=j=1,2,...,CmaxWj,则将x决策为第l类。
    投票权重计算方式
    A. w ( δ a i ) = 1 ( δ a i ) 2 w(\delta _{a_{i}}) = \frac{1}{(\delta _{a_{i}})^{2}} w(δai)=(δai)21
    B. w ( δ a i ) = e − ( δ a i ) 2 w(\delta _{a_{i}}) = e^{-(\delta _{a_{i}})^{2}} w(δai)=e(δai)2
    C. w ( δ a i ) = 1 δ a i w(\delta _{a_{i}}) = \frac{1}{\delta _{a_{i}}} w(δai)=δai1
    各近邻的归一化权重 w ( δ a i ) = w ( δ a i ) ∑ j = 1 K w ( δ a j ) , i = 1 , . . . , K w(\delta _{a_{i}}) = \frac{w(\delta _{a_{i}})}{\sum_{j=1}^{K}w(\delta _{a_{j}})},i=1, ..., K w(δai)=j=1Kw(δaj)w(δai),i=1,...,K

3.Python语言算法实现

import numpy as np
import math
import operator
from sklearn import datasets
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import train_test_split

def normalization(dataX):
    '''z-score归一化
    Parameters:
        dataX: 输入特征向量
    Returns:
        dataX1: 归一化之后的dataX
        mu: 均值向量
        sigma: 标准差向量
    '''
    mu = dataX.mean(0)
    sigma = dataX.std(0)
    dataX1 = (dataX - mu) / sigma
    return dataX1, mu, sigma

def cross_validation(trainX, trainY, ks, m = 10):
    '''m折交叉验证
    Parameters:
        trainX, trainY: 都是np.array类型,训练集
        ks: 备选K值
        m: 超参数
    Returns:
        k: 最终选择的K
    '''
    skf = StratifiedKFold(n_splits = m, shuffle=True, random_state=0) # 创建一个StratifiedKFold对象,分层 m折 随机打乱
    accs = []
    for train_ids, test_ids in skf.split(newTrainX, trainY):
        # 固定样本划分下
        estimationX = newTrainX[train_ids] # 估计集特征
        validationX = newTrainX[test_ids] # 验证集特征
        estimationY = trainY[train_ids] # 估计集标签
        validationY = trainY[test_ids] # 验证集标签
        n = estimationX.shape[0]
        l = [] # 存放各个K值对应的错误率
        for k in ks:
            num = 0
            for index, x in enumerate(validationX):
                y_hat = knn(x, estimationX, estimationY, k)
                if y_hat != validationY[index]:
                    num += 1
            acc = num / n
            l.append(acc)
        accs.append(l)
    accs = np.array(accs)
    mu = accs.mean(0) # 不同K值对应的平均错误率
    sigma = accs.std(0) # 不同K值对应的标准差
    v = mu.min() # 最小的错误率
    i = mu.argmin() # 最小错误率对应的索引
    k = ks[i] # 最小平均错误率对应的K值作为最终选择的K
    for index, value in enumerate(mu):
        if(value == v):
            if(sigma[index] < sigma[i]):
                k = ks[index] # 如果多个K取得最小mu,则选择其中标准差小的对应的K
    return k

def knn(x, trainX, trainY, k):
    '''K近邻
    parameters:
        x: 输入样本
        dataSet: 数据集特征矩阵
        labels: 数据集类别标签
        ks: 备选K值
    
    returns:
        y_hat: x预测类别
    '''
    # step1: 计算已知类别样本点与当前样本点之间的距离
    sqDiffMat = (trainX - x) ** 2
    sqDistance = sqDiffMat.sum(axis=1)
    distance = sqDistance ** 0.5
    # step2: 按距离从小到大排序
    sortedDistIndices = distance.argsort()
    # step3: 前k个样本投票决策
    classCount = dict()
    # step3-1: 等权投票
    for i in range(k):
        label = trainY[sortedDistIndices[i]]
        classCount[label] = classCount.get(label, 0) + 1
    # step3-2: 加权投票
#     for i in range(k):
#         position = sortedDistIndices[i]
#         label = labels[position]
#         d = distance[position]
#         classCount[label] = classCount.get(label, 0) + 1 / (d ** 2)
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    y_hat = sortedClassCount[0][0]
    return y_hat

def load_data():
    iris = datasets.load_iris()
    print(iris.keys())
    n_samples, n_features = iris.data.shape
    print('-----------------------数据集描述-----------------------')
    print("样本数,特征数:", n_samples, n_features)
    print("第一条样本:", iris.data[0])
    print("标签形状:", iris.target.shape)
    print("标签:", iris.target)
    print("标签名称:", iris.target_names)
    print("特征名称:", iris.feature_names)
    dataSet = iris.data
    labels = iris.target
    return dataSet, labels

if __name__ == '__main__':
    # 1.获取数据集
    dataSet, labels = load_data() # 注意不能在dataSet上做标准化预处理
    # 2.划分数据集
    trainX, testX, trainY, testY = train_test_split(dataSet, labels, test_size=0.3, random_state=0)
    # 3.标准化预处理
    newTrainX, mu, sigma = normalization(trainX)
    newTestX = (testX - mu) / sigma
    # 4.选择合适的K值
    ks = [1, 3, 5, 7, 9, 11, 13]
    k = cross_validation(newTrainX, trainY, ks)
    print('-----------------------模型结果-----------------------')
    print('最合适的K值为:', k)
    # 5.计算模型正确率
    n = testX.shape[0]
    cnt = 0
    for i, x in enumerate(newTestX):
        y_hat = knn(x, newTrainX, trainY, k)
        if (y_hat == testY[i]):
            cnt += 1
    print("模型在测试集上的预测正确率:", cnt / n)

代码执行结果:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值