KNN算法几乎是机器学习里最简单的算法了,简单到根本不需要训练直接拿来就可以使用的一种算法。KNN的核心思想是通过比较新数据点与已知类别训练集中数据点的距离,找出最近K个邻居,并根据多数类别决定新数据点的类别。KNN广泛应用于推荐系统、金融市场分析、市场细分等领域。在Python中,可使用scikit-learn库实现KNN算法,并通过数据预处理、参数调整等优化策略提高预测性能。
KNN 的核心思想非常直观:“一个样本的类别(或数值)取决于其最邻近的K个邻居的类别(或数值)。”
KNN算法是一种非参数、基于距离的分类方法,无需构建显式模型,而是直接依赖于训练数据进行预测。其主要工作流程如下:
好了,我想现在你应该对KNN算法有了基本的认识了。不过有几个问题还得明确一下:
先来说说如何确定K值。对于K值,从KNN算法的名称中,我们可以看出K值得重要性是毋庸置疑的。我们用下图的例子来说一说K值得样本分类的重要性:

图中所有圆点构成一个数据集,圆点颜色代表分类,那么,图中无色圆点划分到哪个类呢?
你看,最终的结果因K值而异,K值过大过小都会对数据的分类产生不同程度的影响。对于K值的确定,目前并没有专门的理论方案,一个较普遍的做法就是将数据集分为两部分,一部分用作训练集,一部分用作测试集,从K取一个较小值开始,逐步增加K值,最终去准确率最高的一个K值。通常把这种方法称为遍历搜索结合交叉验证。
我们以莺尾花案例为例子,看看如何选择最佳的K值。
from sklearn import datasets
from sklearn import neighbors
from sklearn.model_selection import cross_val_score
# 获取鸢尾花数据集
iris = datasets.load_iris()
for k in range(3, 16, 2):
# K近邻算法
knn = neighbors.KNeighborsClassifier(n_neighbors=k)
# 5折交叉验证
scores = cross_val_score(knn, iris.data, iris.target, cv=5)
print(f'K = {k}: the mean accuracy is {100 * scores.mean():.2f}%.')
运行结果:
K = 3: the mean accuracy is 96.67%.
K = 5: the mean accuracy is 97.33%.
K = 7: the mean accuracy is 98.00%.
K = 9: the mean accuracy is 97.33%.
K = 11: the mean accuracy is 98.00%.
K = 13: the mean accuracy is 97.33%.
K = 15: the mean accuracy is 96.67%.
我们发现K取7和11获得了最佳的准确率。因此我们可以尝试取K=7或者K=11。
总结一下,一般而言K取奇数并且要大于样本的维度,K既不能太小也不能过大,通常采用遍历搜索结合交叉验证方式寻找最佳K值。
关于距离度量,我们最熟悉的、使用最广泛的就是欧式距离了。对于维数据点和之间的欧氏距离定义为:

除了欧氏距离外,距离度量方法还有余弦距离、哈曼顿距离、切比雪夫距离等,但使用不多,不介绍了。
KNN算法要求样本的数量必须大于样本的维度,样本的范围越大越好,否则范围之外的点的预测值太单一。样本之间的分布越均匀越好,样本各个维度的数值以及样本的目标值的精度越高越好(既样本质量越高越好)。
KNN的主要优点有: - 理论成熟,思想简单,既可以用来做分类也可以用来做回归。 - 可用于非线性分类,和朴素贝叶斯之类的算法比,对数据没有假设,准确度高,对异常点不敏感。 - 由于KNN方法主要靠周围有限的邻近的样本,而不是靠判别类域的方法来确定所属类别的,因此对于类域的交叉或重叠较多的待分样本集来说,KNN方法较其他方法更为适合。 - 该算法比较适用于样本容量比较大的类域的自动分类,而那些样本容量较小的类域采用这种算法比较容易产生误分。
KNN的主要缺点有: - 计算量大,尤其是特征数非常多的时候。 - 样本不平衡的时候,对稀有类别的预测准确率低。 - 使用懒散学习方法,基本上不学习,导致预测时速度比起逻辑回归之类的算法慢。 - 相比决策树模型,KNN模型可解释性不强。
from numpy import *
import operator
# 给出训练数据以及类别
def createDataSet():
group = array([[1.0, 1.1],
[1.0, 1.0],
[0, 0],
[0, 0.1]])
labels = ["A", "A", "B", "B"]
return group, labels
# 通过KNN进行分类
def classify0(inX, dataSet, labels, k):
# 获取数据集的行数
dataSetSize = dataSet.shape[0]
# 计算欧氏距离
# 将 inX 扩展到 dataSize 行
diffMat = tile(inX, (dataSetSize, 1)) - dataSet
sqDiffMat = diffMat ** 2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances ** 0.5
sortedDistIndicies = distances.argsort()
# 选取最小的 k 个点
classCount = {}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
# 将 classCount 字典分解成元组,并按照第二个元素的次序对元组进行排序,此处的排序为逆序
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
if __name__ == '__main__':
group, labels = createDataSet()
inX = [0, 0]
classLabel = classify0(inX, group, labels, 3)
print("class is :",classLabel)
运行结果:
class is : B