目录
前言
k近邻法(k-nearest neighbor, k-NN)是1967年由Cover T和Hart P提出的一种基本分类与回归方法。
KNN是监督学习分类算法之一,
对于监督学习而言,其训练集已经给出了标签信息(label),监督学习就是对已经带有标签的训练集进行学习,从而对新样本(测试集)进行分类,给出其标签。
而与之相对的非监督学习,则是没有给出训练集的标签,也就是说训练集的标签信息是未知的,需要对无标签的样本(训练集)进行学习,进而总结出这些样本数据的内在联系或者类型结构,为进一步的数据分析提供依据。对于无监督学习,应用最多的是聚类算法,具体有K-means聚类、DBSCAN聚类等等。
总而言之,监督学习和非监督学习的主要区别在于:监督学习的数据既有特征又有标签,而非监督学习的数据中只有特征而没有标签。
一、KNN基本思路
1.1 文字描述
首先给定一个样本训练集,其中训练集中的每个数据都存在对应的标签(这里对应了KNN是监督学习分类算法之一),也就是说我们知道每个样本数据的实际分类情况。在输入未知标签的新数据时,KNN算法要做的就是:将新数据的每个特征(feature)与样本数据集中的特征进行比较,提取出前K个与新数据最相似的数据,这K个数据中出现标签占比最大,也就是出现次数最多的分类,就是新数据的分类(标签信息)。这里的K通常取20以内的整数。
简单地说,就是提取出前K个与新数据最相似的数据,其中出现次数最多的分类即是新数据分类。
1.2 示例讲解
如下图所示,这里有一组关于电影镜头的样本数据:
电影类型 | 打戏 | 吻戏 |
爱情片 | 2 | 9 |
1 | 10 | |
动作片 | 8 | 2 |
9 | 0 |
将样本数据(训练集)绘制成图像如下图所示,其中红色圆点代表爱情片,蓝色圆点代表动作片,绿色倒三角为未知分类的测试集。
由图像我们可以大致推断坐标为(8,1)的电影属于动作片,因为它离那两个动作片的圆点更近。这是我们可以用肉眼观察到的,但是中间坐标为(5,6)的电影我们用肉眼观察不出来。那KNN近邻算法又是如何判断的呢?其实已经很显然了,就是距离度量。
在该电影分类示例中有两个特征(feature),分别是打戏数量和吻戏数量。这也就对应了二维实数向量空间,计算两点距离公式:
根据公式,我们可以算出:
电影(8,1)距离训练集样本数据距离分别为
电影(8,1)到 爱情片(1,10)的距离:11.4
电影(8,1)到 爱情片(2,9)的距离:10
电影(8,1)到 动作片(8,2)的距离:1
电影(8,1)到 动作片(9,0)的距离:1.41
假设我们的K取2,其中动作片占比100%,因此电影(8,1)属于动作片。这里K值的选取有时候会影响分类的判断,当K=4时,我们会发现动作片和爱情片各占50%,这是由于训练集数量太少的原因。需要适当扩大训练集,降低偶然性。
同理,我们可以算出电影(5,6)距离训练集样本数据距离分别为:
电影(5,6)到 爱情片(1,10)的距离:5.66
电影(5,6)到 爱情片(2,9)的距离:4.24
电影(5,6)到 动作片(8,2)的距离:5
电影(5,6)到 动作片(9,0)的距离:7.21
假设我们的K取3,其中爱情片占比66.7%,因此电影(8,1)属于爱情片。
上面这个示例是具有两个特征,当我们的训练集有两个以上的特征时,就要使用欧式距离(欧几里得度量):
二、算法介绍
2.1 算法步骤
1. 计算已知类别的训练集中的点与当前样本集中的数据点之间的距离;
2. 将这些点按距离递增次序排列;
3. 选出距离短的前K个点;
4. 计算前K个点中各个分类出现的频率;
5. 将出现频率最高的类别作为样本点的分类。
三、代码实现
3.1 引入库
import numpy as np
from scipy.spatial import distance
#Counter()函数来自Python的collections库,用于计算列表中元素的频次。
from collections import Counter
3.2 准备数据集
def create():
group = np.array([[2,9],[1,10],[0,7],[3,10],[8,2],[9,4],[6,1],[9,0]])
labels = ['爱情片','爱情片','爱情片','爱情片','动作片','动作片','动作片','动作片']
return group,labels
if __name__ == '__main__':
group,labels = create()
print(group)
print(labels)
这里为了降低偶然性,新增加了一些数据,运行结果如下:
3.3 计算两点距离
接下来我们创建一个函数来计算样本点和训练集各点之间的距离:
def euclidean_distance(x1, x2):
return distance.euclidean(x1, x2)
3.4 实现KNN算法
def knn(X_train, y_train, x_test, k):
distances = [] #用于存储样本与训练集之间的距离
targets = [] #用于存储训练集的标签(分类)
#通过循环遍历所有的训练样本,计算x_test与X_train[i]之间的欧氏距离
#并将距离和对应的索引(i)存储在distances列表中
for i in range(len(X_train)):
distances.append([euclidean_distance(x_test, X_train[i]), i])
#进行排序
distances = sorted(distances)
for i in range(k):
index = distances[i][1]
targets.append(y_train[index])
return Counter(targets).most_common(1)[0][0]
其中各参数含义为:
X_train(训练集特征向量)
y_train(训练集标签)
x_test(测试样本特征向量)
k(选择最近邻的数目)
3.5 测试用例
X_train, y_train = create()
x_test = np.array([5, 5])
k = 3
predicted_label = knn(X_train, y_train, x_test, k)
print(predicted_label)
3.6 完整代码
import numpy as np
from scipy.spatial import distance
from collections import Counter
def create():
group = np.array([[2,9],[1,10],[0,7],[3,10],[8,2],[9,4],[6,1],[9,0]])
labels = ['爱情片','爱情片','爱情片','爱情片','动作片','动作片','动作片','动作片']
return group, labels
def euclidean_distance(x1, x2):
return distance.euclidean(x1, x2)
def knn(X_train, y_train, x_test, k):
distances = []
targets = []
for i in range(len(X_train)):
distances.append([euclidean_distance(x_test, X_train[i]), i])
distances = sorted(distances)
for i in range(k):
index = distances[i][1]
targets.append(y_train[index])
return Counter(targets).most_common(1)[0][0]
X_train, y_train = create()
x_test = np.array([5, 5])
k = 3
predicted_label = knn(X_train, y_train, x_test, k)
print(predicted_label)
运行结果:电影(5,5)属于
四、总结
4.1 KNN算法优缺点
优点
- 简单好用,容易理解,精度高,理论成熟,既可以用来做分类也可以用来做回归;
- 训练时间复杂度为O(n);无数据输入假定;
- 对异常值不敏感
缺点
- 计算复杂性高;空间复杂性高;
- 样本不平衡问题(即有些类别的样本数量很多,而其它样本的数量很少);
- 最大的缺点是无法给出数据的内在含义。
- 计算量大、对噪声和异常值敏感以及可能发生维度灾难等。
应用场合:
- 适用于小型数据集和低维特征空间。
- 可用于分类和回归问题。
- 用于推荐系统、图像识别、模式识别等领域。
优化:
针对KNN算法的一些缺陷,可以采用一些策略进行优化。例如,选择合适的k值可以平衡分类器的泛化能力和对噪声的敏感性;使用权重代替简单的投票可以改进分类器的性能;采用核技巧可以将低维空间中的非线性问题映射到高维空间中进行线性分类等。
以上就是今天要讲的内容,本文仅仅简单介绍了KNN近邻算法基本思路、代码实现还总结了KNN算法的优缺点,上面的示例是比较简单的二维特征,大家可以尝试更高维特征、更复杂的例子。