<audio id="audio" title="24丨KNN（上）：如何根据打斗和接吻次数来划分电影类型？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4c/22/4c6cb0d4b941254d064f275d496e1122.mp3"></audio>

今天我来带你进行KNN的学习，KNN的英文叫K-Nearest Neighbor，应该算是数据挖掘算法中最简单的一种。

我们先用一个例子体会下。

假设，我们想对电影的类型进行分类，统计了电影中打斗次数、接吻次数，当然还有其他的指标也可以被统计到，如下表所示。

<img src="https://static001.geekbang.org/resource/image/6d/87/6dac3a9961e69aa86d80de32bdc00987.png" alt=""><br>
我们很容易理解《战狼》《红海行动》《碟中谍6》是动作片，《前任3》《春娇救志明》《泰坦尼克号》是爱情片，但是有没有一种方法让机器也可以掌握这个分类的规则，当有一部新电影的时候，也可以对它的类型自动分类呢？

我们可以把打斗次数看成X轴，接吻次数看成Y轴，然后在二维的坐标轴上，对这几部电影进行标记，如下图所示。对于未知的电影A，坐标为(x,y)，我们需要看下离电影A最近的都有哪些电影，这些电影中的大多数属于哪个分类，那么电影A就属于哪个分类。实际操作中，我们还需要确定一个K值，也就是我们要观察离电影A最近的电影有多少个。

<img src="https://static001.geekbang.org/resource/image/fa/cc/fa0aa02dae219b21de5984371950c3cc.png" alt="">

## KNN的工作原理

“近朱者赤，近墨者黑”可以说是KNN的工作原理。整个计算过程分为三步：

<li>
计算待分类物体与其他物体之间的距离；
</li>
<li>
统计距离最近的K个邻居；
</li>
<li>
对于K个最近的邻居，它们属于哪个分类最多，待分类物体就属于哪一类。
</li>

**K值如何选择**

你能看出整个KNN的分类过程，K值的选择还是很重要的。那么问题来了，K值选择多少是适合的呢？

如果 K 值比较小，就相当于未分类物体与它的邻居非常接近才行。这样产生的一个问题就是，如果邻居点是个噪声点，那么未分类物体的分类也会产生误差，这样KNN分类就会产生过拟合。

如果K值比较大，相当于距离过远的点也会对未知物体的分类产生影响，虽然这种情况的好处是鲁棒性强，但是不足也很明显，会产生欠拟合情况，也就是没有把未分类物体真正分类出来。

所以K值应该是个实践出来的结果，并不是我们事先而定的。在工程上，我们一般采用交叉验证的方式选取 K 值。

交叉验证的思路就是，把样本集中的大部分样本作为训练集，剩余的小部分样本用于预测，来验证分类模型的准确性。所以在KNN算法中，我们一般会把K值选取在较小的范围内，同时在验证集上准确率最高的那一个最终确定作为K值。

**距离如何计算**

在KNN算法中，还有一个重要的计算就是关于距离的度量。两个样本点之间的距离代表了这两个样本之间的相似度。距离越大，差异性越大；距离越小，相似度越大。

关于距离的计算方式有下面五种方式：

<li>
欧氏距离；
</li>
<li>
曼哈顿距离；
</li>
<li>
闵可夫斯基距离；
</li>
<li>
切比雪夫距离；
</li>
<li>
余弦距离。
</li>

其中前三种距离是KNN中最常用的距离，我给你分别讲解下。

**欧氏距离**是我们最常用的距离公式，也叫做欧几里得距离。在二维空间中，两点的欧式距离就是：

<img src="https://static001.geekbang.org/resource/image/f8/80/f8d4fe58ec9580a4ffad5cee263b1b80.png" alt=""><br>
同理，我们也可以求得两点在n维空间中的距离：

<img src="https://static001.geekbang.org/resource/image/40/6a/40efe7cb4a2571e55438b55f8d37366a.png" alt=""><br>
**曼哈顿距离**在几何空间中用的比较多。以下图为例，绿色的直线代表两点之间的欧式距离，而红色和黄色的线为两点的曼哈顿距离。所以曼哈顿距离等于两个点在坐标系上绝对轴距总和。用公式表示就是：

<img src="https://static001.geekbang.org/resource/image/bd/aa/bda520e8ee34ea19df8dbad3da85faaa.png" alt="">

<img src="https://static001.geekbang.org/resource/image/dd/43/dd19ca4f0be3f60b526e9ea0b7d13543.jpg" alt=""><br>
**闵可夫斯基距离**不是一个距离，而是一组距离的定义。对于n维空间中的两个点 x(x1,x2,…,xn) 和 y(y1,y2,…,yn) ， x 和 y 两点之间的闵可夫斯基距离为：

<img src="https://static001.geekbang.org/resource/image/4d/c5/4d614c3d6722c02e4ea03cb1e6653dc5.png" alt=""><br>
其中p代表空间的维数，当p=1时，就是曼哈顿距离；当p=2时，就是欧氏距离；当p→∞时，就是切比雪夫距离。

**那么切比雪夫距离**怎么计算呢？二个点之间的切比雪夫距离就是这两个点坐标数值差的绝对值的最大值，用数学表示就是：max(|x1-y1|,|x2-y2|)。

**余弦距离**实际上计算的是两个向量的夹角，是在方向上计算两者之间的差异，对绝对数值不敏感。在兴趣相关性比较上，角度关系比距离的绝对值更重要，因此余弦距离可以用于衡量用户对内容兴趣的区分度。比如我们用搜索引擎搜索某个关键词，它还会给你推荐其他的相关搜索，这些推荐的关键词就是采用余弦距离计算得出的。

## KD树

其实从上文你也能看出来，KNN的计算过程是大量计算样本点之间的距离。为了减少计算距离次数，提升KNN的搜索效率，人们提出了KD树（K-Dimensional的缩写）。KD树是对数据点在K维空间中划分的一种数据结构。在KD树的构造中，每个节点都是k维数值点的二叉树。既然是二叉树，就可以采用二叉树的增删改查操作，这样就大大提升了搜索效率。

在这里，我们不需要对KD树的数学原理了解太多，你只需要知道它是一个二叉树的数据结构，方便存储K维空间的数据就可以了。而且在sklearn中，我们直接可以调用KD树，很方便。

## 用KNN做回归

KNN不仅可以做分类，还可以做回归。首先讲下什么是回归。在开头电影这个案例中，如果想要对未知电影进行类型划分，这是一个分类问题。首先看一下要分类的未知电影，离它最近的K部电影大多数属于哪个分类，这部电影就属于哪个分类。

如果是一部新电影，已知它是爱情片，想要知道它的打斗次数、接吻次数可能是多少，这就是一个回归问题。

那么KNN如何做回归呢？

对于一个新电影X，我们要预测它的某个属性值，比如打斗次数，具体特征属性和数值如下所示。此时，我们会先计算待测点（新电影X）到已知点的距离，选择距离最近的K个点。假设K=3，此时最近的3个点（电影）分别是《战狼》，《红海行动》和《碟中谍6》，那么它的打斗次数就是这3个点的该属性值的平均值，即(100+95+105)/3=100次。

<img src="https://static001.geekbang.org/resource/image/35/16/35dc8cc7d781c94b0fbaa0b53c01f716.png" alt="">

## 总结

今天我给你讲了KNN的原理，以及KNN中的几个关键因素。比如针对K值的选择，我们一般采用交叉验证的方式得出。针对样本点之间的距离的定义，常用的有5种表达方式，你也可以自己来定义两个样本之间的距离公式。不同的定义，适用的场景不同。比如在搜索关键词推荐中，余弦距离是更为常用的。

另外你也可以用KNN进行回归，通过K个邻居对新的点的属性进行值的预测。

KNN的理论简单直接，针对KNN中的搜索也有相应的KD树这个数据结构。KNN的理论成熟，可以应用到线性和非线性的分类问题中，也可以用于回归分析。

不过KNN需要计算测试点与样本点之间的距离，当数据量大的时候，计算量是非常庞大的，需要大量的存储空间和计算时间。另外如果样本分类不均衡，比如有些分类的样本非常少，那么该类别的分类准确率就会低很多。

当然在实际工作中，我们需要考虑到各种可能存在的情况，比如针对某类样本少的情况，可以增加该类别的权重。

同样KNN也可以用于推荐算法，虽然现在很多推荐系统的算法会使用TD-IDF、协同过滤、Apriori算法，不过针对数据量不大的情况下，采用KNN作为推荐算法也是可行的。

<img src="https://static001.geekbang.org/resource/image/d6/0f/d67073bef9247e1ca7a58ae7869f390f.png" alt=""><br>
最后我给你留几道思考题吧，KNN的算法原理和工作流程是怎么样的？KNN中的K值又是如何选择的？

## 上一篇文章思考题的代码

我在上篇文章里留了一道思考题，你可以在[GitHub](http://github.com/cystanford/breast_cancer_data)上看到我写的关于这道题的代码（完整代码和文章案例代码差别不大），供你借鉴。

<img src="https://static001.geekbang.org/resource/image/fa/44/fa09558150152cdb250e715ae9047544.png" alt="">

欢迎你在评论区与我分享你的答案，也欢迎点击“请朋友读”，把这篇文章分享给你的朋友或者同事。


