监督学习算法-k近邻

k近邻(k-Nearest Neighbor Method, k-NN)

k-NN是一种搜索最近邻的算法,当输入一个未知的数据时,该算法根据邻近的k个已知数据来对该数据进行预测。

k-NN算法原理
k-NN分类
  • 计算测试数据与各个训练数据之间的距离
  • 按照距离的递增关系进行排序
  • 选取距离最小的k个点
  • 确定k个点所在类别的出现频率
  • 返回前k个点中出现次数多的类别作为预测结果(单一kNN的预测结果就是那个邻居点的标签)

如下图分别展示了1个近邻和3个近邻的预测结果
在这里插入图片描述

接下来,再让我们看一下,对于二维数据集,具有不同个邻居情况的决策边界
在这里插入图片描述

从上图中我们可以发现,随着邻居个数越来越多,决策边界也越来越光滑。更光滑的边界对应更简单的模型。换句话说,使用更少的邻居对应更高的模型复杂度(如上图中的左侧),而使用更多的邻居对应更低的模型复杂度(如上图中的右侧)。

k-NN回归
  • 计算测试数据与各个训练数据之间的距离
  • 按照距离的递增关系进行排序
  • 选取距离最小的k个点
  • 确定k个点所在的目标值(如工资)
  • 返回前k个点的目标值的平均值作为预测结果(单一kNN的预测结果就是那个邻居点的目标值)

如下图分别展示了1个近邻和3个近邻的预测结果
在这里插入图片描述

同样,让我们对kNN回归进行分析,在这里,我们选用一维数据集,并可以查看所有特征取值(即所有数据点)所对应的预测结果。
在这里插入图片描述

从上图中,可以看出,仅使用单一邻居时,训练集中的每个点都对预测结果有显著影响,这将导致预测结果非常不稳定,当考虑更多的邻居之后,预测结果变得更加平滑,但对训练数据的拟合也不太好。

k-NN算法特点
  • 逐个数据进行学习
  • 一般要计算与所有数据的距离,所以预测计算费时
  • 与k相关,预测性能还算可以
距离计算

在距离计算时,有很多方法,通常采用的是连接两点的直线的长度。我们首先以二维为例(两个点 x 1 ( x 1 1 , x 1 2 ) , x 2 ( x 2 1 , x 2 2 ) x_{1}(x^{1}_{1},x^{2}_{1}), x_{2}(x^{1}_{2},x^{2}_{2}) x1(x11,x12),x2(x21,x22)),之后在推广到多维的情况,

  • 曼哈顿距离
    二维: d ( x 1 , x 2 ) = ∣ x 1 1 − x 2 1 ∣ + ∣ x 1 2 − x 2 2 ∣ d(x_{1},x_{2}) = |x^{1}_{1}-x^{1}_{2}|+|x^{2}_{1}-x^{2}_{2}| d(x1,x2)=x11x21+x12x22
    n维: d ( x i , x j ) = ∑ l = 1 n ( x i ( l ) − x j ( l ) ) d(x_{i},x_{j}) = \sum\limits_{l=1}^{n}{(x_{i}^{(l)}-x_{j}^{(l)})} d(xi,xj)=l=1n(xi(l)xj(l))

  • 欧式距离
    二维: d ( x 1 , x 2 ) = ( x 1 1 − x 2 1 ) 2 + ( x 1 2 − x 2 2 ) 2 d(x_{1},x_{2}) = \sqrt{(x^{1}_{1}-x^{1}_{2})^{2}+(x^{2}_{1}-x^{2}_{2})^{2}} d(x1,x2)=(x11x21)2+(x12x22)2
    n维: d ( x i , x j ) = ∑ l = 1 n ( x i ( l ) − x j ( l ) ) 2 d(x_{i},x_{j}) = \sqrt{\sum\limits_{l=1}^{n}{(x_{i}^{(l)}-x_{j}^{(l)})^{2}}} d(xi,xj)=l=1n(xi(l)xj(l))2

将上述两个距离公式推广到一般情况,则可以得到Minkowski距离
d ( x i , x j ) = ( ∑ l = 1 n ( x i ( l ) − x j ( l ) ) p ) 1 p d(x_{i},x_{j}) = ({\sum\limits_{l=1}^{n}{(x_{i}^{(l)}-x_{j}^{(l)})^{p}}})^{\frac{1}{p}} d(xi,xj)=(l=1n(xi(l)xj(l))p)p1
p = 1 p=1 p=1时,即为曼哈顿距离;当 p = 2 p=2 p=2时,即为欧式距离。

我们以2维数据编写计算距离程序,假设a和b是表示点的坐标的向量,其欧式距离为:

def euclidean_distance(a, b):
    return np.sqrt(sum(x - y) ** 2 for (x, y) in zip(a, b))

scikit-learnKNeighborsClassifier、KNeighborsRegressor默认使用欧式距离,其在许多情况下的效果都很好。但是,有时也会采用马哈拉诺比斯距离,马氏距离不仅考虑某类别数据群的平均远近,而且还考虑数据的分散方向。

标准化、归一化

在某些数据中,某个特征可能会比另外一个特征大得多,这将导致模型无法正常学习,所以为了整合特征间的尺度,需对数据进行标准化或归一化处理。

  • 标准化
    标准化是将数据按比例缩放,使之落入一个特定区间,其计算公式为
    z i = x i − μ σ z_{i} = \frac{x_{i}-\mu}{\sigma} zi=σxiμ
    其中 z i z_{i} zi为标准化后的数据,均值 μ = 1 N ∑ i = 1 N x i \mu = \frac{1}{N} {\sum\limits_{i=1}^{N}{x_{i}}} μ=N1i=1Nxi,标准差 σ = 1 N ∑ i = 1 N ( x i − μ ) 2 \sigma = \sqrt{ \frac{1}{N} \sum\limits_{i=1}^{N}{ (x_{i}-\mu)^{2} } } σ=N1i=1N(xiμ)2 。经过处理的数据的均值为0,标准差为1。

  • 归一化
    对原始数据的线性变换,将数据值映射到[0, 1]之间,其计算公式为
    z i = x i − min ⁡ i = 1 , . . . , N { x i } max ⁡ i = 1 , . . . , N { x i } − min ⁡ i = 1 , . . . , N { x i } z_{i} = \frac{x_{i} - \min\limits_{i=1,...,N}{\{x_{i}\}}}{ \max\limits_{i=1,...,N}{\{x_{i}\}} - \min\limits_{i=1,...,N}{\{x_{i}\}}} zi=i=1,...,Nmax{xi}i=1,...,Nmin{xi}xii=1,...,Nmin{xi}

k-NN算法实现

这里,我们根据k-NN算法原理来对其进行实现(不使用sklearn库)。我们选用书中所设计的数据集,并将其划分为训练集和测试集两个部分,同时进行归一化处理,完整代码如下:

import mglearn
import numpy as np
from collections import Counter

X, y = mglearn.datasets.make_forge() # 选用的别人设计好的数据集
# 数据集形式为:
# (array([[ 9.96346605, 4.59676542],
# ...,
# [11.563957 , 1.3389402 ]]),
# array([1, 0, ..., 1, 0]))

def creatData(X, y):
 # 将所有数据划分为训练集和测试集
    X_train, X_test = X[:20], X[20:]
    y_train, y_test = y[:20], y[20:]
    return X_train, X_test, y_train, y_test # array类型的数据

def normalized(X_data):
    # 对数据进行归一化处理
    X_data_min = X_data.min(0) # 计算0轴上的最小值
    X_data_max = X_data.max(0) # 计算0轴上的最大值
    ranges = X_data_max - X_data_min # 计算最大值和最小值的差
    normData = np.zeros(np.shape(X_data)) # 构建一个全零数组来存放归一化后的数据
    m = X_data.shape[0] # 数据集的行数
    normData = (X_data - np.tile(X_data_min, (m, 1))) / np.tile(ranges, (m, 1))
    return normData

def k_neighbors_class(X_train, X_test, y_train, k):
 # 将训练数据和测试数据归一化处理
    X_train = normalized(X_train)
    X_test = normalized(X_test)
    results = []
    train_row = X_train.shape[0]
    # 判断每个测试点的所属类别
    for test_node in X_test:
        # 计算每个测试点与训练集中点的欧式距离
        diff = (np.tile(test_node, (train_row, 1)) - X_train) ** 2
        dist = diff.sum(axis = 1) ** 0.5 # 按1轴方向相加, 并开根号
        # 对获取的所有距离进行排序
        sorteddist = np.argsort(dist) # 返回排序后的元素的下标
        class_list = [y_train[sorteddist[i]] for i in range(k)] # 将k个邻居的所有类别加入到一个列表当中
        class_label = Counter(class_list).most_common(1)[0][0] # 将出现次数最多的类别作为该点的预测结果,在这里我们并没有考虑个数相等的情况
        results.append(class_label)
    return results

if __name__ == '__main__':
    X_train, X_test, y_train, y_test = creatData(X, y)
    print('原测试集标签:', y_test)
    k = int(input('请输入邻居个数:'))
    results = k_neighbors_class(X_train, X_test, y_train, k)
    print('预测结果:',results)

我们分别输入不同的邻居个数来进行判断:

原测试集标签: [0 0 0 0 1 0]
请输入邻居个数:1
预测结果: [0, 0, 0, 0, 1, 0]
原测试集标签: [0 0 0 0 1 0]
请输入邻居个数:5
预测结果: [0, 0, 0, 0, 1, 1]

从上述结果可以发现,当选用1个邻居时,我们的预测精度可以达到100%,而当选用5个邻居时,83.3%,因此在实际中使用k-NN算法时,需选择合适的邻居数量。上述程序实现的是k-NN分类,k-NN回归方法与其类似。

好好学习,天天向上!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值