
“告诉我你的朋友是谁,我来告诉你你是谁?”
k近邻分类器的概念是很难简单描述的。这是一句古老的谚语,可以在许多语言和文化中找到。圣经中也有这句话:“ 与智慧人同行的人是有智慧的,与愚人同行的人是有害的 。”(箴言13:20)
这意味着K近邻的概念是我们日常生活和评判的一部分:想象你遇到一群人,他们都非常年轻、时尚、喜欢运动。他们谈论的是没有和他们在一起的朋友本。那么,你对本的想象是什么?是的,你可以想象他年轻、时尚、爱玩。
如果你知道本住在一个人们投保守票的社区里,平均年收入超过20万美元?他的邻居每年赚的钱都超过30万美元?你觉得本怎么样?最可能的是,你不认为他是一个失败者,你可能怀疑他也是一个保守派?
最近邻居分类背后的原理是找到一个预定义的数字,即距离新样本最近的训练样本的“k”,这个样本必须被分类。新示例的标签将从这些邻居中定义。K近邻有一个用户定义的固定常数,用于确定邻居的数量。也有基于辐射的邻居学习算法,基于点的局部密度有不同数量的邻居,所有的样本都在一个固定的半径内。一般来说,距离可以是任何度量标准:标准欧几里得距离是最常见的选择。基于邻居的方法被称为非一般化的机器学习方法, 因为它们只是“记住”它的所有训练数据 。分类可以通过(标签)未知的样本的最近邻居的多数投票来计算。
k-NN算法是所有机器学习算法中最简单的一种,但是尽管它很简单,但它在大量的分类和回归问题(例如字符识别或图像分析)中都相当成功。
现在让我们从数学上讲一下 与其他分类方法相比,k-近邻分类器(k-NN)直接用于学习样本,而不是创建规则。
最近邻算法:
给定一组类别{c1,c2,…cn},也称为类,例如{“male”,“female”}。还有一个学习集 LS,由带标签的实例组成。
分类的任务是将一个类别或类分配给一个任意的实例。如果实例o是LS的元素,则将使用实例的标签。
现在,我们来看看o不在LS中的情况:
o与所有LS实例进行比较。距离度量用于比较。我们确定o的k个最近邻,即距离最小的项。k是用户定义的常数和正整数,通常很小。
最常见的LS类将被分配给实例o。如果k = 1,那么对象将被简单地分配给最近邻居的类。
k近邻分类器的算法是所有机器学习算法中最简单的一种。k-NN是一种基于实例的学习,或者说是惰性学习,当我们进行实际分类时,函数只在局部近似,所有的计算都被执行。
从头开始KNN 准备数据集在开始编写最近邻居分类器之前,我们需要考虑数据,即learnset。我们将使用sklearn模块的数据集提供的“iris”数据集。
数据集由三种iris中的每种50个样本组成
iris setosa,
iris virginica和
iris versicolor。
每个样本测量了四个特征:萼片和花瓣的长度和宽度,单位为厘米。
import numpy as np from sklearn import datasets iris = datasets.load_iris() iris_data = iris.data iris_label = iris.target print(iris_data[0], iris_data[79], iris_data[100]) print(iris_label[0], iris_label[79], iris_label[100]) [5.1 3.5 1.4 0.2] [5.7 2.6 3.5 1. ] [6.3 3.3 6. 2.5] 0 1 2我们从上面的集合创建一个learnset。我们使用np.random的permutation函数随机分割数据。
np.random.seed(42) indices = np.random.permutation(len(iris_data)) n_training_samples = 12 learnset_data = iris_data[indices[:-n_training_samples]] learnset_label = iris_label[indices[:-n_training_samples]] testset_data = iris_data[indices[-n_training_samples:]] testset_label = iris_label[indices[-n_training_samples:]] print(learnset_data[:4], learnset_label[:4]) print(testset_data[:4], testset_label[:4]) [[6.1 2.8 4.7 1.2] [5.7 3.8 1.7 0.3] [7.7 2.6 6.9 2.3] [6. 2.9 4.5 1.5]] [1 0 2 1] [[5.7 2.8 4.1 1.3] [6.5 3. 5.5 1.8] [6.3 2.3 4.4 1.3] [6.4 2.9 4.3 1.3]] [1 2 1 1]下面的代码只需要可视化我们的learnset的数据。我们的数据由每个iris项的4个值组成,因此我们将通过将第三和第四个值相加,将数据减少到3个值。这样,我们就可以在三维空间中描绘数据:
import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D X = [] for iclass in range(3): X.append([[], [], []]) for i in range(len(learnset_data)): if learnset_label[i] == iclass: X[iclass][0].append(learnset_data[i][0]) X[iclass][1].append(learnset_data[i][1]) X[iclass][2].append(sum(learnset_data[i][2:])) colours = ("r", "g", "y") fig = plt.figure() ax = fig.add_subplot(111, projection='3d') for iclass in range(3): ax.scatter(X[iclass][0], X[iclass][1], X[iclass][2], c=colours[iclass]) plt.show()
确定邻居
为了确定两个实例之间的相似性,我们需要一个距离函数。在我们的例子中, 欧几里得距离 是理想的:
def distance(instance1, instance2): instance1 = np.array(instance1) instance2 = np.array(instance2) return np.linalg.norm(instance1 - instance2) print(distance([3, 5], [1, 1])) print(distance(learnset_data[3], learnset_data[44]))函数' get_neighbour '返回一个带有'k'近邻的列表,它与实例'test_instance'最接近:
我们将使用iris样本对函数进行测试:
for i in range(5): neighbors = get_neighbors(learnset_data, learnset_label, testset_data[i], 3, distance_param=distance) print(i, testset_data[i], testset_label[i], neighbors) 投票选出一个结果。我们现在写一个投票函数。这个函数使用集合中的类'Counter'来计算实例列表中类的数量。这个实例列表当然是邻居。函数“投票”返回最普通的类:
from collections import Counter def vote(neighbors1): class_counter = Counter() for neighbor in neighbors1: class_counter[neighbor[2]] += 1 return class_counter.most_common(1)[0][0]我们将对我们的训练样本进行“投票”测试:
for i in range(n_training_samples): neighbors = get_neighbors(learnset_data, learnset_label, testset_data[i], 3, distance_param=distance) print("index: ", i, "result of vote: ", vote(neighbors), ", label: ", testset_label[i], ", data: ", testset_data[i])我们可以看到,这些预测与标记的结果相对应,但对于索引8的项目则不同。 'vote_prob'是类似于'vote'的函数,但返回类名和该类的概率:
def vote_prob(neighbors1): class_counter = Counter() for neighbor in neighbors1: class_counter[neighbor[2]] += 1 labels, votes = zip(*class_counter.most_common()) winner = class_counter.most_common(1)[0][0] votes4winner = class_counter.most_common(1)[0][1] return winner, votes4winner/sum(votes) for i in range(n_training_samples): neighbors = get_neighbors(learnset_data, learnset_label, testset_data[i], 5, distance_param=distance) print("index: ", i, ", vote_prob: ", vote_prob(neighbors), ", label: ", testset_label[i], ", data: ", testset_data[i]) 加权最近邻分类器我们只研究k项目附近的一个未知对UO和多数投票。在我们前面的示例中,使用多数投票已经显示出了相当高效的结果,但这并没有考虑以下推理: 邻居越远,就越“偏离”“真实”的结果 。换句话说,我们可以信任最接近的邻居,而不是远的邻居。假设,我们有未知项目UO的11个邻居。最近的五个邻居属于a类,其余的六个都属于b类。应该给UO分配什么类?之前的方法是B,因为我们有6比5的投票支持B,另一方面,最接近的5个都是a,这应该更重要。
为了实现这个策略,我们可以通过以下方式给邻居分配权重:实例的最近邻居的权值为1/1,第二个最近的邻居的权值为1/2,然后最远的邻居的权值为1/k。
这意味着我们用谐波级数作为权重:

我们在以下函数中实现这一点:
def vote_harmonic_weights(neighbors, all_results=True): class_counter = Counter() number_of_neighbors = len(neighbors) for index in range(number_of_neighbors): class_counter[neighbors[index][2]] += 1/(index+1) labels, votes = zip(*class_counter.most_common()) winner = class_counter.most_common(1)[0][0] votes4winner = class_counter.most_common(1)[0][1] if all_results: total = sum(class_counter.values(), 0.0) for key in class_counter: class_counter[key] /= total return winner, class_counter.most_common() else: return winner, votes4winner / sum(votes) for i in range(n_training_samples): neighbors = get_neighbors(learnset_data, learnset_label, testset_data[i], 6, distance_param=distance) print("index: ", i, ", result of vote: ", vote_harmonic_weights(neighbors, all_results=True))原文链接: https://www.python-course.eu/k_nearest_neighbor_classifier.php
版权声明:作者保留权利,严禁修改,转载请注明原文链接。
数据人网是数据人学习、交流和分享的平台http://shujuren.org 。专注于从数据中学习到有用知识。 平台的理念:人人投稿,知识共享;人人分析,洞见驱动;智慧聚合,普惠人人。 您在数据人网平台,可以1)学习数据知识;2)创建数据博客;3)认识数据朋友;4)寻找数据工作;5)找到其它与数据相关的干货。 我们努力坚持做原创,聚合和分享优质的省时的数据知识! 我们都是数据人,数据是有价值的,坚定不移地实现从数据到商业价值的转换!