如今机器学习甚是流行,不抓紧学习就要被时代淘汰了。以下的内容大多来自《机器学习实战》和《机器学习》这两本书中,一本偏向实战、一本偏向理论。
K-近邻(k-Nearest Neighbor,简称kNN)算法是《机器学习实战》中的第一个算法,比较简单易懂,本文中的内容更多取自于《机器学习实战》。
k-近邻算法简述。
在《机器学习中》的简述(也不算是定义,就是对kNN的一种描述)如下:
给定测试样本,基于某种距离度量找出训练集中与其最靠近的k个训练样本,然后基于这k个“邻居”的信息来进行预测,通常,在分类任务中可以使用“投票法”,即选择这k个样本中出现最多的类别标记作为预测结果;在回归任务中可以使用“平均法”,即将这k个样本的实值输出标记的平均值作为预测结果;还可基于距离远近进行加权平均或加权投票,距离越近的样本权重越大。
很显然,在这本书中的定义比较全了,即指出了kNN在分类中的使用,也提到了在回归中的使用,相比之下《机器学习实战》中的定义就较为简单,仅仅是描述了分类的部分,以下是《机器学习实战》中对kNN的工作原理的描述:
存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中的每一数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征值与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据及中前k个最相似的数据,这就是k-近邻算法中的k的出处,通常k是不大于20的整数。最后,选择k个最现实数据中出现次数最多的分类,作为新数据的分类。
划重点:
- 训练样本集。
- 分类标签。
- 样本特征。
- 最相似(最接近)。
每一个样本根据样本的特征不同,将其划分为不同的标签分类,预测新数据的时候就是根据这些特征值在样本数据中找到最相似的一个分类标签。
这两点就是对kNN的描述,这里主要针对的是kNN在分类中的使用,下面使用一个电影分类的故事来介绍kNN。
电影分类与kNN。
1. 电影分类介绍:
众所周知,电影可以按照题材分类,然而题材本身是如何定义的?由谁来判定某部电影属于哪个题材?也就是说同一题材的电影具有哪些公共特征?这些都是在进行电影分类时必须要考虑的问题。
没有哪个电影人会说自己制作的电影和以前的某部电影类似,但我们确实知道每部电影在风格上的确可能会和同题材的电影相近。
那么动作片具有哪些共有特征,使得动作片之间非常类似,而与爱情片存在着明显的差别呢?
动作片中也会存在接吻镜头,爱情片中也会存在打斗场景,我们不能单纯依靠是否存在打斗或者亲吻来判断影片的类型。但爱情片中的亲吻镜头更多,动作片中的打斗场景也更频繁,基于此类场景在某部电影中出现的次数可以用来进行电影分类。
2.电影分类用kNN描述。
如果用kNN算法来表示的话,已经发布的电影就是训练样本集,而且这些已经发布的电影已经分好类别了,比如大火的《战狼》就是动作片,比较刺激的《釜山行》也能算的上是动作片;而比较甜蜜的《怦然心动》和龙母演的《遇见你之前(Me Before You)》就属于爱情片,其中动作片和爱情片就是两种分类标签,打斗场景和亲吻场景就是它们的特征值。
我们就是根据电影中的打斗场景和亲吻场景的次数将上述的电影进行了分类,很明显动作片的打斗场景比较多,而且要比爱情片多,但是也并非没有出现过亲吻场景,只是比较少而已;相反,爱情片中的亲吻场景也会比较多,肯定比动作片中的多,不然为啥叫爱情片,其中可能也会出现部分的打斗场景,但也是比较少。
如果我们想将刚刚上映的《芳华》进行分类的话,肯定也是根据其中的打斗场景和亲吻场景的次数多少来分类的。
假设上述电影中的打斗场景和亲吻场景的次数如下(大概猜测的,其中的《芳华》本人还没有看):
电影名称 | 打斗镜头 | 亲吻镜头 | 电影类型 |
---|---|---|---|
《怦然心动》 | 3 | 104 | 爱情片 |
《遇见你之前》 | 2 | 100 | 爱情片 |
《战狼》 | 101 | 10 | 动作片 |
《釜山行》 | 98 | 2 | 动作片 |
《芳华》 | 18 | 90 | 未知 |
再次申明:《芳华》中的数据内容均为猜测。
其中与上述的kNN对应的内容:
- 训练样本集:就是已经分好类别的四个电影《怦然心动》、《遇见你之前》、《战狼》、《釜山行》。
- 分类标签:动作片和爱情片。
- 特征值:打斗次数和亲吻次数。
这三个很好理解,那么“最相似”是怎么回事呢?我们需要根据已知的东西找出与《芳华》最相似的类别,而这个类别就是《芳华》的电影分类。
3.如何找到最相似(最接近)。
既然我们要将新数据的每个特征值与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。那么最相似是怎么计算的呢?我们怎么计算《芳华》与上面已知电影的相似度呢?
实际上我们就是根据特征值来计算的。
简单的可以将上述的电影用一个x和y的二维坐标系表示出来,x表示电影中的打斗次数,y表示电影中的亲吻次数:
x表示打斗次数,y表示亲吻次数,这两个就是两种特征值,从图中也能明显的看到《芳华》更靠近《怦然心动》和《遇见你之前》,而这两部都是属于爱情片,物以类聚,人以群分,大致心里就有数了,《芳华》应该是属于一部爱情片的,我们现在需要做的是计算出来。
“最相似”的说法可能比较模糊,其实也可以说为“最接近”,我们既然能够看出来《芳华》更接近《怦然心动》和《遇见你之前》,我们是否能计算出来呢?答案是肯定的,而且是简单的,使用一个简单的距离公式就可以了,更官方的说法就是:欧几里得度量(euclidean metric)(也称欧氏距离):
这个就比较熟悉了,我们可以使用这个公式,在坐标系中根据x和y的值求出《芳华》距离其他已知的四部电影的距离:
电影名称 | 与《芳华》的距离 |
---|---|
《怦然心动》 | 20.5 |
《遇见你之前》 | 18.7 |
《战狼》 | 115.3 |
《釜山行》 | 118.9 |
比如计算《怦然心动》与《芳华的》距离就是:√[(3-18)^2 + (104-90)^2] = √421 = 20.5。
这样我们就得到了《芳华》与其他已知电影的距离,这个距离就是我们“最相似”的划分依据。我们按照距离递增排序,可以找到k个距离《芳华》最近的电影,也可以说这k个电影与《芳华》最相似,假设k=3,则三个最靠近《芳华》的电影为:《遇见你之前》、《怦然心动》、《战狼》,而这三个电影的标签分别是:爱情片、爱情片、动作片。
这里出现了两种标签,这是因为我们的样本集的缘故,如果样本集中再多一个爱情片很显然算出来的结果前三个都会是爱情片了。
出现了两种标签怎么办?我们就可以使用最开始提到的“投票法”,对结果标签进行投票,而投票结果就是:爱情片两票,动作片一片,用k-v表示:(爱情片,2)、(动作片,1)。那么我们就取票数最多的那个:爱情片。
这样我们就找到了《芳华》的类别:爱情片。
kNN的一般流程。
上述的内容已经介绍了kNN的工作原理,就是根据数据之间的距离远近,来将数据进行分类的。上面电影分类例子其中使用了两个特征值进行计算,使用的公式就是欧氏距离,当然如果是多个特征值的计算方法也是这样的。
k-近邻算法的一般流程:
- 收集数据:可以使用任何方法。
- 准备数据:距离计算所需要的数值,最好是结构化的数据格式。
- 分析数据:可以使用任何方法。
- 训练算法:此步骤不适用于k-近邻算法。
- 测试算法:计算错误率。
- 使用算法:首先需要输入样本数据和结构化的输出结果,然后运行k-近邻算法判定输入数据分别属于哪个分类,最后应用对计算出的分类执行后续的处理。
用Python实现kNN。
说了这么多的文字,下面就用Python来实现kNN算法。
需要掌握的技能:
- Python基本语法。
- numpy库的基本使用。
- 欧氏距离计算。
import numpy as np
import operator
def createDataSet():
"""
测试用的数据集
:return:
"""
group = np.array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
labels = ['A', 'A', 'B', 'B']
return group, labels
def classify0(inX, dataSet, labels, k):
"""
KNN 分类器
:param inX: 需要分类的数据
:param dataSet: 数据集
:param labels: 标签
:param k: k个最相似的数据
:return: 返回分类结果
"""
# 计算数据集共有多少行
dataSetSize = dataSet.shape[0]
# 将传入进来的inX,扩增到与整个数据集相等矩阵,并与原矩阵做差,得到与各个点之间的坐标距离
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
# 计算与各个点之间距离的平方
sqDiffMat = diffMat ** 2
# 在轴为1(这里也就是行)的方向上求和,这里计算得到的就是x方和y方的和
sqDistances = sqDiffMat.sum(axis=1)
# 开方得到距离
distances = sqDistances ** 0.5
# 将距离按照从小到大的顺序排列,最后得到下标
sortedDistIndicies = distances.argsort()
# 用来存放标签
classCount = {}
# 选择距离最小的k个数值
for i in range(k):
# 选择对应标签
voteIlabel = labels[sortedDistIndicies[i]]
# 将对应标签加一
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
# 进行排序,按照值进行排序,并且从大到小(逆序)排列
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
# 返回第一个元素,这里打印了一下结果
print(sortedClassCount)
return sortedClassCount[0][0]
if __name__ == '__main__':
group, labels = createDataSet()
print(classify0([0, 0], group, labels, 3))
classify0这个函数实现了kNN算法。createDataSet函数创建了一个简单的数据集,用来测试kNN算法。
测试运行结果:
至此,我们已经成功的实现了kNN算法,当我们想要使用kNN算法的时候,只需要将其导入,然后调用classify0,传入对应的参数即可。
总结。
k-近邻算法是比较简单的一个算法,使用一个简单的欧氏距离公式,计算出新数据和各个样本数据集的距离,然后找到最接近的k个样本数据,得到这k个样本数据对应的分类标签,然后在这些分类标签中使用投票法找到最高的那个标签,这个标签作为新数据的标签。
k-近邻算法的优缺点如下:
- 优点:精度高、对异常值不敏感、无数据输入假定。
- 缺点:计算复杂度高、空间复杂度高。
- 适用数据范围:数值型和标称型。
总的来说,无论是思想还是计算过程都是比较简单的(起码在本书中的表现如此),作为机器学习的入门算法,能够快速的实现出来,增加了学习信心。