一.工作原理

kNN算法是监督学习的一种,首先要有样本集(包含特征与目标变量),然后再输入没有标签只有特征的新数据,其次算出新数据与每个样本集的距离(所以kNN算法的特征都要为数据类型或标称型),这里的距离计算函数可以是欧氏距离、余弦距离、汉明距离、曼哈顿距离等,选出前k个最近距离的样本,最后定义新数据的目标变量是前k个样本中出现频率最高的目标变量。

把工作原理转化为伪代码的思路:

  1. 有一个未知分类的数据A,我们只知道A的特征值[x1, y1, z1], 我想知道A的分类是什么
  2. 如果要通过A的特征值来判断它的分类,前提条件一定是要有一批和A相同特征且已经知道分类的样本,这里我们取最简情况,样本有两个一个为B[x2, y2, z2]分类为“要下雨”,C[x3, y3, z3]分类为“不下雨”
  3. 按kNN算法的思路,我算出A与B,C的距离,A离B近那么A的分类就是“要下雨”,反之则A是“不下雨”,这样就成功得到A的分类,不过这只是最简的情况,下面我们还要考虑一些复杂的情况
  4. 问题1:如果有很多个样本,而且我们也没办法保证样本中没有异常值。 假如现在样本有五个分别为B(要下雨),C(不下雨),D(要下雨),E(不下雨),F(要下雨),其中F是异常值,假设测试样本A的真实分类是不下雨,因为F是异常值,导致F与A的距离最近,如果我们就简单地选距离最近的,就容易造成错误,所以这时候就要用到kNN算法中的K了,我们选取前K个最近距离的样本,然后再选出前K个中分类数最多的那个分类。比如,K取3,然后前3个中有1个要下雨,2个不下雨,那么我们就认为A是不下雨。
  5. 问题2:如果特征值x的取值范围大大超过y和z,会有什么影响? 例如:x的取值范围在[0,100],而y和z的取值范围只有[0,1],那么这样就会造成在算距离的时候特征x对距离的值影响非常大,这就要用到归一化的手段了,具体下面第六点有讲。
  6. 问题3:如果特征值是标称类的怎么办? 例如特征值z是指颜色,最简单的办法就是比较测试样本与训练样本的颜色是否相同,相同差值就为0,不相同差值就为1

二.代码思路

  1. 计算已知类别数据集中的点与当前点之间的距离;
  2. 按照距离递增次序排序;
  3. 选取与当前点距离最小的k个点;
  4. 确定前k个点所在类别的出现频率;
  5. 返回前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]

五.示例

Python代码转换伪代码工具 python生成伪代码_Python代码转换伪代码工具

六.归一化

由于不同的特征,其总体的值大小不一样,这样有些特征占的权重就会非常大,所以我们要把所有的特征都归一化,这样每个特征的影响都一样

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