做者:Philipp Muens算法
翻译:老齐bash
与本文相关的图书推荐:《数据准备和特征工程》(电子工业出版社天猫旗舰店有售)微信
本文的代码,均发布到百度AI Studio的在线平台中,关注微信公众号「老齐教室」,并回复:#真实姓名+手机号+‘案例’#
,申请加入含有苯问案例的《机器学习案例》课程,获得包含本案例在内的更多机器学习案例。注意: 回复信息中(1)必须以#
开始和结尾(2)必须是真实姓名和手机号。markdown
K近邻(简称K-NN或KNN)是一种简单而优雅的机器学习算法,用于根据现有数据对不可见的数据进行分类。该算法的优势是不须要传统的训练阶段。若是存在分类问题和标记数据,则能够利用现有的已分类数据,预测任何不可见的数据类别。app
让咱们仔细看看核心思想背后相关的数学知识和将这些转化为代码的过程。机器学习
想象一下,咱们邀请了100个养狗的人带着他们的狗过来作一个咱们想作的统计实验。每只参与实验的狗是咱们感兴趣的4个不一样犬种中的1个。在这些狗及其主人的配合下,咱们测量每只狗的3种不一样属性:函数
测量完成后,咱们将测量值标准化,使其在000到111之间。oop
在收集了每只狗的数据后,咱们获得了100个测量值,每一个测量值都标有相应的狗品种。学习
下面是一个例子:测试
为了更好地理解数据,最好把它标出来。因为咱们收集了3种不一样的测量数据(重量、高度和警戒性),所以能够将全部100个数据点投影到三维空间中,并根据其标签为每一个数据点上色(例如,把“Podenco”的标签涂上棕色)。
不幸的是,咱们在试图绘制此数据时遇到问题,由于咱们忘了标注其中的一个测量数据。咱们确实有狗的重量,高度和警觉性,但因为某种缘由,咱们忘记写下这只狗的品种。
既然咱们已经有其余狗的测量数据,有没有可能推测出这只狗的品种呢?咱们仍然能够将未标记的数据添加到现有三维空间中,全部其余的彩色数据点都在这个空间里。但咱们该怎么给这个推测的数据点上色呢?
一个可能的解决方案是查看问题数据点周围的5个邻居,看看它们是什么颜色的。若是这些数据点中的大多数标记为“Podenco”,那么咱们的测量数据极可能也是从Podenco中获取的。
这正是K-NN算法(k近邻算法)的做用。该算法根据一个不可见数据点的K近邻和这些K近邻的绝大多数类型,来预测该数据点的类。让咱们从数学的角度来仔细研究一下这个问题。
为了经过K-NN对数据进行分类,咱们只须要实现两个概念。
如上所述,该算法经过查看K个最近邻和它们各自的大多数类来对数据进行分类。
所以咱们须要实现两个函数:距离函数和投票函数。前者用于计算两点之间距离的,后者返回给定的任意标签列表中最多见的标签。
考虑到“最近邻”的概念,咱们须要计算“待分类”数据点与全部其余数据点之间的距离,以找到距离最近的点。
有几个不一样的距离函数。对于咱们的实现,将使用欧几里德距离,由于它计算简单,能够很容易地扩展到多维。
用数学符号表示以下:
让咱们经过一个例子来解释这个公式。假设有两个向量和
,二者之间的欧氏距离计算以下:
将其转化为代码的结果以下:
def distance(x: List[float], y: List[float]) -> float: assert len(x) == len(y) interim_res: float = 0 for i, _ in enumerate(x): interim_res += (x[i] - y[i]) ** 2 return sqrt(interim_res) assert distance([1, 2, 3, 4], [5, 6, 7, 8]) == 8 复制代码
太好了。咱们刚刚实现了第一个构建:一个欧氏距离函数。
接下来咱们须要实现投票函数。投票函数接受一个标签列表做为输入,并返回该列表的“最多见”标签。虽然这听起来很容易实现,但咱们应该后退一步,考虑可能遇到的潜在的极端状况。
其中一种状况是,咱们有两个或多个“最多见”标签:
# Do we return `a` or `b`? labels: List[str] = ['a', 'a', 'b', 'b', 'c'] 复制代码
对于这些场景,咱们须要实现一个决策机制。
有几种方法能够解决这个问题。一种解决办法多是随机挑选一个标签。然而,在咱们的例子中,咱们不该该孤立地考虑投票函数,由于咱们知道:距离函数和投票函数共同来肯定对未分类数据的预测。
咱们能够利用这一事实。假设咱们的投票函数输入了一个标签列表,这个列表是按距离从近到远排序的。有了这一条件,就很容易打破平局。咱们须要作的就是递归地删除列表中的最后一个条目(也就是最远的条目),直到只有一个标签明显胜出。
下面根据以上的标签示例演示此过程:
# Do we return `a` or `b`? labels: List[str] = ['a', 'a', 'b', 'b', 'c'] # Remove one entry. We're still unsure if we should return `a` or `b` labels: List[str] = ['a', 'a', 'b', 'b'] # Remove another entry. Now it's clear that `a` is the "winner" labels: List[str] = ['a', 'a', 'b'] 复制代码
咱们把这个算法转换成一个函数,而且称之为majority_vote
:
def majority_vote(labels: List[str]) -> str: counted: Counter = Counter(labels) winner: List[str] = [] max_num: int = 0 most_common: List[Tuple[str, int]] for most_common in counted.most_common(): label: str = most_common[0] num: int = most_common[1] if num < max_num: break max_num = num winner.append(label) if len(winner) > 1: return majority_vote(labels[:-1]) return winner[0] assert majority_vote(['a', 'b', 'b', 'c']) == 'b' assert majority_vote(['a', 'b', 'b', 'a']) == 'b' assert majority_vote(['a', 'a', 'b', 'b', 'c']) == 'a' 复制代码
测试代表,咱们的majority_vote
函数可以可靠地处理上述极端状况(边缘状况)。
既然咱们已经研究并编写了两个函数,如今是时候把它们结合起来了。咱们即将实现的knn函数会输入带标签的数据列表、一个新的度量值(咱们要分类的数据点)和一个参数k。参数k决定了:在经过majority_vote
函数投票给新标签时,咱们要考虑多少个邻居。
knn算法的首要任务是计算新数据点和全部其余现有数据点之间的距离。以后,咱们须要从最近到最远的距离排序,并提取数据点标签。而后截断此有序列表,使其仅包含k个最近的数据点标签。最后一步是将此列表传递给投票函数,该函数用于计算预测标签。
将所述步骤转换为代码,将产生如下knn函数:
def knn(labeled_data: List[LabeledData], new_measurement, k: int = 5) -> Prediction: class Distance(NamedTuple): label: str distance: float distances: List[Distance] = [Distance(data.label, distance(new_measurement, data.measurements)) for data in labeled_data] distances = sorted(distances, key=attrgetter('distance')) labels = [distance.label for distance in distances][:k] label: str = majority_vote(labels) return Prediction(label, new_measurement) 复制代码
就是这样。这就是从头开始实现的k近邻算法!
如今是时候看看咱们的自制k-NN实现效果是否像宣传的那样了。为了测试咱们编写的代码,咱们将使用臭名昭著的鸢尾花数据集。
该数据集由三种不一样的鸢尾花的50个样本组成:
对每个样品,收集了4种不一样的测量数据:萼片的宽度和长度以及花瓣的宽度和长度。
下面是数据集中的一个示例行,其中前4个数字是萼片长度、萼片宽度、花瓣长度、花瓣宽度,最后一个字符串表示这些测量数据的标签。
6.9,3.1,5.1,2.3,Iris-virginica
复制代码
探索这些数据的最好方法是可视化。不幸的是,很难绘制和检查四维数据。然而,咱们能够选择两个特征(如花瓣长度和花瓣宽度)并绘制二维散点图。
fig = px.scatter(x=xs, y=ys, color=text, hover_name=text, labels={'x': 'Petal Length', 'y': 'Petal Width'}) fig.show() 复制代码
咱们能够清楚地看到数据点的分类状况,每一个类别的数据点有着相同的颜色,所以具备相同的标签。
如今假设咱们有一个新的、未标记的数据点:
new_measurement: List[float] = [7, 3, 4.8, 1.5] 复制代码
将这个数据点添加到现有的散点图,结果以下:
fig = px.scatter(x=xs, y=ys, color=text, hover_name=text, labels={'x': 'Petal Length', 'y': 'Petal Width'}) fig.add_annotation( go.layout.Annotation( x=new_measurement[petal_length_idx], y=new_measurement[petal_width_idx], text="The measurement we want to classify") ) fig.update_annotations(dict( xref="x", yref="y", showarrow=True, arrowhead=7, ax=0, ay=-40, borderwidth=2, borderpad=4, bgcolor="#c3c3c3" )) fig.show() 复制代码
即便咱们只是在二维中绘制花瓣的长度和宽度,新的测量值彷佛也可能来自“变色鸢尾”。
让咱们用knn函数获得一个明确的答案:
knn(labeled_data, new_measurement, 5)
复制代码
果真,咱们获得的结果代表,咱们正在处理一个“变色鸢尾”:
Prediction(label='Iris-versicolor', measurements=[7, 3, 4.8, 1.5]) 复制代码
k近邻分类算法是一种很是强大的分类算法,它能够根据已有标签的数据来标记缺失标签的数据。k-NNs的主要思想是:利用新的“待分类”数据点的K个最近邻来“投票”选出它应有的标签。
所以,咱们须要两个核心函数来实现k-NN。第一个函数计算两个数据点之间的距离,以便找到最近的邻居。第二个函数执行多数投票,以即可以决定哪一个标签在给定的邻域中最多见。
同时使用这两个函数可使k-NN发挥积极做用,而且能够可靠地标记未显示的数据点。
我但愿这篇文章是有帮助的,它揭开了k近邻算法的内部工做原理的神秘面纱。
原文连接:philippmuens.com/k-nearest-n…
搜索技术问答的公众号:老齐教室
在公众号中回复:老齐,可查看全部文章、书籍、课程。
以为好看,就点赞转发