算法原理
kNN算法的核心思想是用距离最近的k个样本数据的分类来代表目标数据的分类。
其原理具体地讲,存在一个训练样本集,这个数据训练样本的数据集合中的每个样本都包含数据的特征和目标变量(即分类值),输入新的不含目标变量的数据,将该数据的特征与训练样本集中每一个样本进行比较,找到最相似的k个数据,这k个数据出席那次数最多的分类,即输入的具有特征值的数据的分类。
例如,训练样本集中包含一系列数据,这个数据包括样本空间位置(特征)和分类信息(即目标变量,属于蓝色三角形还是红色正方形),要对中心的绿色数据的分类。运用kNN算法思想,距离最近的k个样本的分类来代表测试数据的分类,那么:
当k=1时,距离最近的1个样本在实线内,具有1个红色正方形**,因此将它归为红色正方形。
当k=5时,距离最近的5个样本在实线内,具有3个蓝色三角和2个红色正方形**,因此将它归为蓝色三角形。
knn三要素
训练集少且种类少的时候算法有效,训练集大的时候要使用KD树和球树的方法建立模型。
距离度量
距离衡量的方法有多种,目的都是搜索最近邻,最常用的是欧氏距离。
k值的选择
k值决定了最后确定类别时所参考的样本数量,k值过大则训练误差增大,选取的临近点中包含错误种类的可能性增大,导致结果不准确;k值过小则泛化误差增大,模型容易受到噪声干扰,容易过度拟合。因此k值的选取非常重要,一般先选取一个很小的值,再使用交叉验证法确定最优的k值,一般都低于训练样本的平方根。
分类决策规则
1.多数表决法
多数表决法类似于投票的过程,也就是在 K 个邻居中选择类别最多的种类作为测试样本的类别。
2.加权表决法
根据距离的远近,对近邻的投票进行加权,距离越近则权重越大,通过权重计算结果最大值的类为测试样本的类别。
算法流程
计算距离:
给出训练集数据和待测样本数据,计算待测样本离训练集中每个样本的距离
计算距离的方法有很多,常用的有欧氏距离、曼哈顿距离、切比雪夫距离、闵可夫斯基距离和余弦距离等,其中对于数字类型的样本来说最常用的是欧式距离,文本分类时最常用余弦距离。
找k个最近样本:
给定k值,根据求出的距离选定距离最近的k个样本
利用交叉检验确定最优的k值,即将数据临时分为训练集和测试集,从k=1开始测试,最大不能超过样本数据的平方根,从中选出效果最好的k值。
定类别:
根据最近的k个样本的类别,确定待测样本的类别
一般有两种方法确定类别:第一,类别定为k个样本中出现次数最多的类别;第二,距离加权法,在k个样本中,权重为距离平方的倒数,每个点都赋值为1,对相同种类的点进行加权求和,最后值最大的种类为待测样本的种类。
python代码及分析
"""
"""
import numpy as np
from matplotlib import pyplot as plt
#计算距离公式
def d_man(x, y):
d = np.sum(np.abs(x - y))#曼哈顿距离公式
return d
def d_euc(x, y):
d = np.sqrt(np.sum(np.square(x - y)))#欧式距离公式
return d
#sorted函数按照key值进行排序,key参数的值为一个函数,此函数只有一个参数且返回一个值用来进行比较
#reverse参数接受False 或者True 表示是否逆序;class_count.items()返回可遍历的(键, 值) 元组数组。
import operator
def majority_voting(class_count):
sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)#itemgetter(a)获得第_a_个值,默认从小到大
return sorted_class_count
#生成示例数据
def create_data():
features = np.array(
[[2.88, 3.05], [3.1, 2.45], [3.05, 2.8], [2.9, 2.7], [2.75, 3.4],
[3.23, 2.9], [3.2, 3.75], [3.5, 2.9], [3.65, 3.6], [3.35, 3.3]]) #np.array构建二维数组且无需指针
labels = ['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B']
return features, labels #返回features,labels
def knn_classify(test_data, train_data, labels, k):
distances = np.array([]) # 创建一个空的数组存放距离
for each_data in train_data: # 使用欧式距离计算数据相似度
d = d_euc(test_data, each_data)
distances = np.append(distances, d) #append() 用于在列表末尾添加新的对象。
print(f"距离相似度distances={distances}")
sorted_distance_index = distances.argsort() # 获取按距离大小排序后的索引(argsort提取索引)
sorted_distance = np.sort(distances)
print(f"查看sorted_distance_index{sorted_distance_index}")
print(f"查看sorted_distance{sorted_distance}")
r = (sorted_distance[k]+sorted_distance[k-1])/2 # 计算圆的半径
#选择距离最近的k个值
class_count = {}
for i in range(k):
vote_label = labels[sorted_distance_index[i]] #根据索引找到距离对应的类别
class_count[vote_label] = class_count.get(vote_label, 0) + 1
#排序
final_label = majority_voting(class_count)
return final_label, r #返回值
#生成训练样本
features, labels =create_data()
test_data = np.array([3.18, 3.15])
final_label, r = knn_classify(test_data, features, labels, 5)
print(final_label)
print(r)
#可视化
#极坐标方式表示圆
def circle(r, a, b): # 为了画出圆,这里采用极坐标的方式对圆进行表示 :x=r*cosθ,y=r*sinθ。
theta = np.arange(0, 2*np.pi, 0.01)
x = a+r * np.cos(theta)
y = b+r * np.sin(theta)
return x, y
k_circle_x, k_circle_y = circle(r, 3.18, 3.15)
plt.figure(figsize=(5, 5))
plt.xlim((2.4, 3.8)) #x轴范围
plt.ylim((2.4, 3.8)) #y轴范围
x_feature = list(map(lambda x: x[0], features)) # 返回每个数据的x特征值
y_feature = list(map(lambda y: y[1], features))
plt.scatter(x_feature[:5], y_feature[:5], c="b") # 在画布上绘画出"A"类标签的数据点
plt.scatter(x_feature[5:], y_feature[5:], c="g")
plt.scatter([3.18], [3.15], c="r", marker="x") # 待测试点的坐标为 [3.18,3.15]
plt.plot(k_circle_x, k_circle_y)