一.工作原理
kNN算法是监督学习的一种,首先要有样本集(包含特征与目标变量),然后再输入没有标签只有特征的新数据,其次算出新数据与每个样本集的距离(所以kNN算法的特征都要为数据类型或标称型),这里的距离计算函数可以是欧氏距离、余弦距离、汉明距离、曼哈顿距离等,选出前k个最近距离的样本,最后定义新数据的目标变量是前k个样本中出现频率最高的目标变量。
把工作原理转化为伪代码的思路:
- 有一个未知分类的数据A,我们只知道A的特征值[x1, y1, z1], 我想知道A的分类是什么
- 如果要通过A的特征值来判断它的分类,前提条件一定是要有一批和A相同特征且已经知道分类的样本,这里我们取最简情况,样本有两个一个为B[x2, y2, z2]分类为“要下雨”,C[x3, y3, z3]分类为“不下雨”
- 按kNN算法的思路,我算出A与B,C的距离,A离B近那么A的分类就是“要下雨”,反之则A是“不下雨”,这样就成功得到A的分类,不过这只是最简的情况,下面我们还要考虑一些复杂的情况
- 问题1:如果有很多个样本,而且我们也没办法保证样本中没有异常值。 假如现在样本有五个分别为B(要下雨),C(不下雨),D(要下雨),E(不下雨),F(要下雨),其中F是异常值,假设测试样本A的真实分类是不下雨,因为F是异常值,导致F与A的距离最近,如果我们就简单地选距离最近的,就容易造成错误,所以这时候就要用到kNN算法中的K了,我们选取前K个最近距离的样本,然后再选出前K个中分类数最多的那个分类。比如,K取3,然后前3个中有1个要下雨,2个不下雨,那么我们就认为A是不下雨。
- 问题2:如果特征值x的取值范围大大超过y和z,会有什么影响? 例如:x的取值范围在[0,100],而y和z的取值范围只有[0,1],那么这样就会造成在算距离的时候特征x对距离的值影响非常大,这就要用到归一化的手段了,具体下面第六点有讲。
- 问题3:如果特征值是标称类的怎么办? 例如特征值z是指颜色,最简单的办法就是比较测试样本与训练样本的颜色是否相同,相同差值就为0,不相同差值就为1
二.代码思路
- 计算已知类别数据集中的点与当前点之间的距离;
- 按照距离递增次序排序;
- 选取与当前点距离最小的k个点;
- 确定前k个点所在类别的出现频率;
- 返回前k个点出现频率最高的类别作为当前点的预测分类
三.伪代码
输入参数
- 测试样本 :是一个数组,存放当前要测试的样本
- 训练样本 :是一个二维的矩阵,存放了很多条训练样本记录
- 目标变量集:训练样本集对应的目标变量
分类器算法(测试样本,训练样本, 目标变量集,k):
用线性运算求出训练样本每个记录与测试样本的距离
对距离进行排序
for i in range(k):
算出前k个分类变量中每个分类变量的数目
返回数目最多的那个变量
四.python最简代码
写在kNN.py文件中,样本集和测试样本都存放在numpy的array对象中,因为array对象可以用线性代数运算
def classify0(in_data, feature_group, label, k):
"""
k近邻算法的分类器
@param in_data:没有目标变量的新数据
@param feature_group:只有特征的样本数据集
@param label:训练样本数据集对应的目标变量
@param k:
@return:
"""
dim_len = feature_group.shape[0] # 查看列方向的维度
sub_data = np.tile(in_data, (dim_len, 1)) - feature_group # tile:从不同维度扩展列表,1代表第二维不扩展
sq_sub_data = sub_data ** 2 # 对列表中的每个元素求平方
sum_sq_sub_data = sq_sub_data.sum(axis=1) # 以行方向求和
geom_distance = sum_sq_sub_data ** 0.5
index_sorted_gd = geom_distance.argsort() # 返回数组排序后的索引值
label_freq = {}
for i in range(k):
label_value = label[index_sorted_gd[i]]
label_freq[label_value] = label_freq.get(label_value, 0) + 1
sort_class_label = sorted(iter(label_freq.items()), key=operator.itemgetter(1), reverse=True) # 排序
return sort_class_label[0][0]
五.示例
六.归一化
由于不同的特征,其总体的值大小不一样,这样有些特征占的权重就会非常大,所以我们要把所有的特征都归一化,这样每个特征的影响都一样
ef normalization(data_set):
"""
核心公式 (x-min)/(max-min), 记得用线性代数的思维去处理矩阵
@param data_set:存放特征的数据集,不包含目标变量
@return:归一化的特征数据集,后面两个返回变量可有可无,没什么影响
"""
max_data = data_set.max(0) # 找出每列的最小值
min_data = data_set.min(0) # 找出每列的最大值
range_data = max_data - min_data
norm_data = np.zeros(np.shape(data_set))
d1 = data_set.shape[0]
norm_data = data_set - np.tile(min_data, (d1, 1)) # min_data本身就三列,所以列不用扩展
norm_data = norm_data/np.tile(range_data, (d1, 1))
return norm_data, d1, min_data