【机器学习与实现】K近邻算法


一、KNN算法简介

想一想:下面图片中只有三种豆,有三个豆是未知的种类,如何判定他们的种类?

在这里插入图片描述
1968年,Cover和Hart提出了最初的近邻算法。

(一)KNN算法包括三个步骤

  1. 算距离:给定测试样本,计算它与训练集中的每个样本的距离;
  2. 找邻居:圈定距离最近的k个训练样本,作为测试样本的近邻;
  3. 做分类:根据这k个近邻归属的主要类别,来对测试样本分类。

k值的选择距离度量分类决策规则是KNN的3个基本要素。

(二)超参数K的影响

在这里插入图片描述
K值取3时,判断绿色点的类别为蓝色; K值取5时,判断绿色点的类别为红色为了能得到较优的K值,可以采用交叉验证和网格搜索的办法分别尝试不同K值下的分类准确性。

在这里插入图片描述
当增大k值时,一般错误率会先降低,因为有周围更多的样本可以借鉴了。但当K值更大的时候,错误率会更高。这也很好理解,比如说你一共就35个样本,当你K增大到30的时候,KNN基本上就没意义了。

在这里插入图片描述
左图为k选取不同值时对鸢尾花分类影响。当k逐步增大时,局部噪音样本对边界的影响逐渐减小,边界形状趋于平滑。较大的k是会抑制噪声的影响,但是使得分类界限不明显。举个极端例子,如果选取k值为训练样本数量,即k=n,采用等权重投票,这种情况不管查询点q在任何位置,预测结果仅有一个(分类结果只能是样本数最多的那一类,kNN这一本来与空间位置相关的算法因此失去了作用)。这种训练得到的模型过于简化,忽略样本数据中有价值的信息。

二、距离度量

1、欧氏距离(Euclidean Distance)

n n n 维空间点 a ( x 11 , x 12 , . . . , x 1 n ) a(x_{11},x_{12},...,x_{1n}) a(x11,x12,...,x1n) b ( x 21 , x 22 , . . . , x 2 n ) b(x_{21},x_{22},...,x_{2n}) b(x21,x22,...,x2n) 间的欧氏距离(两个 n n n维向量): d 12 = ∑ k = 1 n ( x 1 k − x 2 k ) 2 d_{12}=\sqrt{\sum_{k=1}^n(x_{1k}-x_{2k})^2} d12=k=1n(x1kx2k)2

2、曼哈顿距离(Manhattan Distance)

曼哈顿距离(Manhattan Distance)是指两点在南北方向上的距离加上在东西方向上的距离。

n n n 维空间点 a ( x 11 , x 12 , . . . , x 1 n ) a(x_{11},x_{12},...,x_{1n}) a(x11,x12,...,x1n) b ( x 21 , x 22 , . . . , x 2 n ) b(x_{21},x_{22},... ,x_{2n}) b(x21,x22,...,x2n) 的曼哈顿距离为 d 12 = ∑ k = 1 n ∣ x 1 k − x 2 k ∣ d_{12}=\sum_{k=1}^n|x_{1k}-x_{2k}| d12=k=1nx1kx2k

在这里插入图片描述
图中两点间的绿线代表的是欧式距离 。红线,蓝线和黄线代表的都是曼哈顿距离,由此可见在两点间曼哈顿距离相等的情况下,线路有多种情况。

3、余弦距离(Cosine Distance)

两个 n n n 维样本点 a ( x 11 , x 12 , . . . , x 1 n ) a(x_{11},x_{12},...,x_{1n}) a(x11,x12,...,x1n) b ( x 21 , x 22 , . . . , x 2 n ) b(x_{21},x_{22},...,x_{2n}) b(x21,x22,...,x2n) 的夹角余弦为: c o s ( θ ) = a ⋅ b ∣ a ∣ ∣ b ∣ cos(\theta)=\frac{a\cdot b}{|a||b|} cos(θ)=a∣∣bab c o s ( θ ) = ∑ n k = 1 x 1 k x 2 k ∑ n k = 1 x 1 k 2 ∑ n k = 1 x 2 k 2 cos(\theta)=\frac{\sum_n^{k=1}x_{1k}x_{2k}}{\sqrt{\sum_n^{k=1}x_{1k}^2\sum_n^{k=1}x_{2k}^2}} cos(θ)=nk=1x1k2nk=1x2k2 nk=1x1kx2k

4、切比雪夫距离(Chebyshev Disatance)

在这里插入图片描述
切比雪夫距离(Chebyshev Disatance)是指二个点之间的距离定义是其各坐标数值差绝对值的最大值。
D C h e b y s h e v ( p , q ) = max ⁡ i ( ∣ p i − q i ∣ ) D_{Chebyshev}(p,q)=\max_{i}(|p_i-q_i|) DChebyshev(p,q)=imax(piqi)

这也等于以下 L p L_p Lp 度量的极限: lim ⁡ k → ∞ ( ∑ i = 1 n ∣ p i − q i ∣ k ) 1 k \lim_{k\to \infty}\left(\sum_{i=1}^n|p_i-q_i|^k\right)^{\frac{1}{k}} klim(i=1npiqik)k1 因此切比雪夫距离也称为 L ∞ L_{\infty} L 度量(无穷范数)。

国际象棋棋盘上二个位置间的切比雪夫距离是指王要从一个位子移至另一个位子需要走的步数。由于王可以往斜前或斜后方向移动一格,因此可以较有效率的到达目的的格子。上图是棋盘上所有位置距 f 6 f6 f6 位置的切比雪夫距离。

5、闵可夫斯基距离(Minkowski Distance)

闵氏距离的定义:两个 n n n 维变量 a ( x 11 , x 12 , . . . , x 1 n ) a(x_{11},x_{12},...,x_{1n}) a(x11,x12,...,x1n) b ( x 21 , x 22 , . . . , x 2 n ) b(x_{21},x_{22},...,x_{2n}) b(x21,x22,...,x2n) 间的闵可夫斯基距离定义为: d 12 = ∑ k = 1 n ∣ x 1 k − x 2 k ∣ p p d_{12}=\sqrt[p]{\sum_{k=1}^n\left|x_{1k}-x_{2k}\right|^p} d12=pk=1nx1kx2kp 其中 p p p 是一个变参数:

  • p = 1 p=1 p=1 时,就是曼哈顿距离;
  • p = 2 p=2 p=2 时,就是欧氏距离;
  • p → ∞ p→\infty p 时,就是切比雪夫距离。

KNN算法默认使用闵可夫斯基距离。

三、邻近点的搜索算法

  KNN算法需要在中搜索与x最临近的k个点,最直接的方法是逐个计算x与中所有点的距离,并排序选择最小的k个点,即线性扫描。
  当训练数据集很大时,计算非常耗时,以至于不可行。实际应用中常用的是kd-tree (k-dimension tree) 和ball-tree这两种方法。ball-tree是对kd-tree的改进,在数据维度大于20时,kd-tree性能急剧下降,而ball-tree在高维数据情况下具有更好的性能。
  kd-tree以空间换时间,利用训练样本集中的样本点,沿各维度依次对k维空间进行划分,建立二叉树,利用分治思想大大提高算法搜索效率。

样本集不平衡时的影响:

观察下面的例子,对于位置样本X,通过KNN算法,显然可以得到X应属于红点,但对于位置样本Y,通过KNN算法我们似乎得到了Y应属于蓝点的结论,而这个结论直观来看并没有说服力。

在这里插入图片描述
由上面的例子可见:该算法在分类时有个重要的不足是,当样本不平衡时,即:一个类的样本容量很大,而其他类样本容量很小时,很有可能导致当输入一个未知样本时,该样本的K个邻居中大数量类的样本占多数。

解决方法:采用权值的方法来改进。和该样本距离小的邻居权值大,和该样本距离大的邻居权值则相对较小,避免因一个类别的样本过多而导致误判。

四、KNN算法的特点

优点:

  1. KNN 理论简单,容易实现;
  2. 既可以用来做分类也可以用来做回归,还可以用于非线性分类;
  3. 新数据可以直接加入数据集而不必进行重新训练;
  4. 对离群点不敏感。

缺点:

  1. 样本不平衡问题(即有些类别的样本数量很多,而其它样本的数量很少)效果差;
  2. 不适合高维数据,对于样本容量大的数据集计算量比较大(体现在距离计算上);
  3. KNN 每一次分类都会重新进行一次全局运算;
  4. K值不好确定;
  5. 各属性的权重相同,影响准确率。

五、KNN常用的参数及其说明

class sklearn.neighbors.KNeighborsClassifier(n_neighbors=5, weights='uniform',algorithm='auto',
leaf_size=30, p=2, metric='minkowski', metric_params=None,n_jobs=1,**kwargs)
参数名称说明
n_neighbors接收int。表示近邻点的个数,即K值。默认为5。
weights接收str或callable,可选参数有“uniform”和“distance”。表示近邻点的权重,“uniform”表示所有的邻近点权重相等;“distance”表示距离近的点比距离远的点的权重大。默认为“uniform”。
algorithm接收str,可选参数有“auto”“ball_tree”“kd_tree”和“brute”。表示搜索近邻点的算法。默认为“auto”,即自动选择。
leaf_size接收int。表示kd树和ball树算法的叶尺寸,它会影响树构建的速度和搜索速度,以及存储树所需的内存大小。默认为30。
p接收int。表示距离度量公式,1是曼哈顿距离公式;2是欧式距离。默认为2。
metric接收str或callable。表示树算法的距离矩阵。默认为“minkowski”。
metric_params接收dict。表示metric参数中接收的自定义函数的参数。默认为None。
n_jobs接收int。表示并行运算的(CPU)数量,默认为1,-1则是使用全部的CPU。

六、分类算法的性能度量

(一)混淆矩阵及相关概念

在这里插入图片描述
在这里插入图片描述
说明:
(1)结论中的正例、反例以预测值为准,而前面的定语真、假以真实值为准;
(2)预测值与真实值一致,则定语为真,预测值与真实值不一致则定语为假;
(3)主对角线表示预测值与真实值相符,副对角线表示预测值与真实值不符。

相关概念:

  • 准确率ACCuracy:全部预测中,预测正确的比例(即主对角线的占比)
  • 查准率Precision :预测的正例中,预测正确(真正例)的比例
  • 查全率或找回率Recall:真实正例中,预测正确(真正例)的比例

准确率 A C C = T P + T N T P + T N + F N + F P 准确率ACC=\frac{TP+TN}{TP+TN+FN+FP} 准确率ACC=TP+TN+FN+FPTP+TN 查准率 P = T P T P + F P 查准率P=\frac{TP}{TP+FP} 查准率P=TP+FPTP 查全率 R = T P T P + F N 查全率R=\frac{TP}{TP+FN} 查全率R=TP+FNTP

(二)F1_score和其他度量指标

y_true = [1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0]
y_pred = [1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0]

在这里插入图片描述
  查准率和查全率是一对矛盾的度量,一般来说,查准率高时,查全率往往会偏低,查全率高时,查准率往往会偏低。而F1-Score指标综合了Precision与Recall的产出的结果,是F1是基于查准率与查重率的调和平均。F1-Score的取值范围从0到1,1代表模型的输出最好0代表模型的输出结果最差

准确率 A C C = 2 + 3 2 + 4 + 3 + 3 = 5 12 准确率ACC=\frac{2+3}{2+4+3+3}=\frac{5}{12} 准确率ACC=2+4+3+32+3=125 查准率 P r e c i s i o n = 3 4 + 3 = 3 7 查准率Precision=\frac{3}{4+3}=\frac{3}{7} 查准率Precision=4+33=73 查全率 R e c a l l = 3 3 + 3 = 3 6 查全率Recall=\frac{3}{3+3}=\frac{3}{6} 查全率Recall=3+33=63 1 F 1 _ s c o r e = 1 2 ⋅ ( 1 P + 1 R ) ⇒ F 1 _ s c o r e = 2 ⋅ P ⋅ R P + R \frac{1}{F1\_score}=\frac{1}{2}\cdot(\frac{1}{P}+\frac{1}{R})\Rightarrow F1\_score=\frac{2\cdot P\cdot R}{P+R} F1_score1=21(P1+R1)F1_score=P+R2PR F 1 _ s c o r e = 2 × 3 7 × 3 6 3 7 + 3 6 = 6 13 F1\_score=\frac{2×\frac{3}{7}×\frac{3}{6}}{\frac{3}{7}+\frac{3}{6}}=\frac{6}{13} F1_score=73+632×73×63=136

此外,还有P-R曲线、ROC曲线、 AUC值等分类性能的指标。

  • 37
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
K近邻算法是一种基于实例的机器学习算法,它的主要思想是通过计算样本之间的距离来确定新样本的类别。下面是一个简单的C++实现: ```c++ #include <iostream> #include <vector> #include <algorithm> #include <cmath> using namespace std; // 定义一个结构体表示样本 struct Sample { vector<double> features; // 样本的特征向量 int label; // 样本的类别 }; // 计算两个样本之间的欧几里得距离 double distance(const Sample& s1, const Sample& s2) { double sum = 0.0; for (int i = 0; i < s1.features.size(); i++) { sum += pow(s1.features[i] - s2.features[i], 2); } return sqrt(sum); } // K近邻算法 int knn(const vector<Sample>& samples, const Sample& query, int k) { // 计算所有样本与查询样本之间的距离 vector<pair<double, int>> distances; // 存储距离和样本下标的pair for (int i = 0; i < samples.size(); i++) { double d = distance(samples[i], query); distances.push_back(make_pair(d, i)); } // 按距离从小到大排序 sort(distances.begin(), distances.end()); // 统计前k个样本中出现最多的类别 vector<int> counts(k, 0); // 存储每个类别出现的次数 for (int i = 0; i < k; i++) { int label = samples[distances[i].second].label; counts[label]++; } // 返回出现次数最多的类别 int max_count = 0; int max_label = -1; for (int i = 0; i < counts.size(); i++) { if (counts[i] > max_count) { max_count = counts[i]; max_label = i; } } return max_label; } int main() { // 构造样本数据 vector<Sample> samples = { {{1.0, 2.0}, 0}, {{2.0, 1.0}, 0}, {{4.0, 5.0}, 1}, {{5.0, 4.0}, 1} }; // 构造查询样本 Sample query = {{3.0, 3.0}, -1}; // 进行K近邻分类 int k = 3; int label = knn(samples, query, k); // 输出结果 cout << "query sample belongs to class " << label << endl; return 0; } ``` 这个实现中,我们首先定义了一个`Sample`结构体表示样本,其中包含了样本的特征向量和类别。然后,我们定义了一个`distance`函数来计算两个样本之间的欧几里得距离。接着,我们实现了一个`knn`函数来进行K近邻分类,其中,我们首先计算所有样本与查询样本之间的距离,并按距离从小到大排序。然后,我们统计前k个样本中出现最多的类别,并返回出现次数最多的类别。最后,我们在`main`函数中构造了样本数据和查询样本,并调用`knn`函数进行分类。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Francek Chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值