k近邻算法

来源:互联网 发布:网络延长器是什么 编辑:程序博客网 时间:2024/05/22 08:20

对在漫长人生中读过的书而言,人类的记忆力是有限的。如果想在读完每一本书后都有扎实的收获,就需要有技巧地写读书笔记,并养成长期的习惯。——《如何有效阅读一本书:超实用笔记读书法 ([日]奥野宣之)》

前言

  文章为《机器学习实战》摘录笔记。

概述

  简单地说,k近邻算法采用测量不同特征值之间的距离方法进行分类。
  k近邻算法是分类数据最简单最有效的算法。
  k近邻算法必须保存全部数据集,如果训练数据集的很大,必须使用大量的存储空间。此外,由于必须对数据集中的每个数据计算距离值,实际使用时可能非常耗时。K近邻算法的另一个缺陷是它无法给出任何数据的基础结构信息,因此我们也无法知晓平均实例样本和典型实例样本具有什么特征。

k-近邻算法
优点:精度高、对异常值不敏感、无数据输入假定。
缺点:计算复杂度高、空间复杂度高。 适用数据范围:数值型和标称型。

概念

  一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。

理论依据

  在KNN中,通过计算对象间的距离来作为各个对象之间的非相似指标,避免了对象之间的匹配距离,在这里距离一般使用的是欧式距离或者是曼哈顿距离:
  这里写图片描述

示例解释

  假如有一部未看过的电影,如何确定它是爱情片还是动作片呢?
  比如,我们通过以下的表格可以绘制出下面的坐标图:
这里写图片描述
这里写图片描述
  现在我们得到了样本集中所有电影与未知电影的距离,按照距离递增排序,可以找到k个距离最近的电影。假定k=3,则三个最靠近的电影依次是He’s Not Really into DudesBeautiful WomanCalifornia Man。k近邻算法按照距离最近的三部电影的类型,决定未知电影的类型,而这三部电影全是爱情片,因此我们判定未知电影是爱情片

KNN分类算法

对未知类别属性的数据集中的每个点依次执行以下操作:
1. 计算已知类别数据集中的点与当前点之间的距离;
2. 按照距离递增次序排序;
3. 选取与当前点距离最小的k个点;
4. 确定前k个点所在类别的出现频率;
5. 返回前k个点出现频率最高的类别作为当前点的预测分类。

import operatordef classify0(in_x, data_set, labels, k):    """    分类器    :param in_x:用于分类的输入向量(列表)    :param data_set:训练样本集    :param labels:标签向量    :param k:距离样本最近的k个邻居    :return:    """    # numpy方法    # shape表示各个维度大小的元组    # shape[0]表示在0维度上元组的大小    data_set_size = data_set.shape[0]    # numpy方法    # 在对应维度上重复复制in_x,并去除样本集    diff_mat = tile(in_x, (data_set_size, 1)) - data_set    """    平方之后求和,求和完之后开方    """    # 对集合进行平方处理    sq_diff_mat = diff_mat ** 2    # sum(axis=1)表示对行的元素进行累加。    sq_distances = sq_diff_mat.sum(axis=1)    # 对结果集进行开方    distances = sq_distances ** 0.5    # 对结果集升序排序之后返回对数组的索引    sorted_dist_indicies = distances.argsort()    """    统计    """    # 新建一个字典,用于保存数据    class_count = {}    for i in range(k):        vote_label = labels[sorted_dist_indicies[i]]        # 如果vote_label不存在,就取默认值0        class_count[vote_label] = class_count.get(vote_label, 0) + 1    sorted_class_count = sorted(class_count.items(),                                key=operator.itemgetter(1),                                reverse=True)    # 返回出现次数最多的分类    return sorted_class_count[0][0]

使用k近邻算法改进约会网站的配对效果

收集数据:提供文本文件。
准备数据:使用Python解析文本文件。
分析数据:使用Matplotlib画二维扩散图,并对数据进行归一化处理,让数据都在[0,1]范围内,方便进行矩阵运算。
训练算法:此步骤不适用于k近邻算法。(本案例不包含训练部分)
测试算法:使用部分数据作为测试样本。
测试样本和非测试样本的区别在于:测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误。
使用算法:产生简单的命令行程序,然后海伦可以输入一些特征数据以判断对方是否为自己喜欢的类型。

  datingTestSet.txt 文件中有1000行的约会数据,样本主要包括以下3种特征:

  • 每年获得的飞行常客里程数
  • 玩视频游戏所耗时间百分比
  • 每周消费的冰淇淋公升数

from os import listdirfrom numpy import *def file_matrix(filename):    """    使用Python解析文本文件。    该函数的输入为文件名字符串,输出为训练样本矩阵和类标签向量。    :param filename:文件名    :return:训练样本矩阵和类标签向量。    """    fr = open(filename)    # 获取文件的行数    number_of_lines = len(fr.readlines())    # 创建返回的Numpy矩阵    return_mat = zeros((number_of_lines, 3))    # 准备返回的标签向量    class_label_vector = []    fr = open(filename)    index = 0    for line in fr.readlines():        line = line.strip()  # 去除多余的回车字符        list_from_line = line.split('\t')  # 使用\t字符作为分隔符将数据拆分成一个列表        return_mat[index, :] = list_from_line[0:3]        # 需要注意的是,我们必须明确地通知解释器,告诉它列表中存储的元素值为整型,        # 否则Python语言会将这些元素当作字符串处理。        class_label_vector.append(int(list_from_line[-1]))        index += 1    return return_mat, class_label_vectordef auto_norm(data_set):    """    对数据进行归一化处理,让数据都在[0,1]范围内,方便进行矩阵运算。    归一化特征值(将数据处理为0~1范围内)    :param data_set:数据集合    :return:    """    # 每列中的最小值    min_val = data_set.min(0)    # 每列中的最大值    max_val = data_set.max(0)    # 取值范围    ranges = max_val - min_val    # data中的行的数量    m = data_set.shape[0]    # 下面两行的逻辑:newValue = (oldValue-min)/(max-min)    norm_data_set = data_set - tile(min_val, (m, 1))    # Numpy库中tile()函数将变量内容复制成输入矩阵同样大小的矩阵    norm_data_set = norm_data_set / tile(ranges, (m, 1))  # 特征值相除    return norm_data_set, ranges, min_valdef dating_class_test():    """    使用部分数据作为测试样本。    :return: 无    """    # 使用10%的数据进行测试    ho_ratio = 0.90    # 从文件中读取数据集合    dating_data_mat, dating_label = file_matrix('datingTestSet2.txt')    # 将数据进行归一化处理。    # 由于数据的大小不统一,所以需要进行归一化处理    norm_mat, ranges, minVal = auto_norm(dating_data_mat)    # 获取归一化后一维数组的数量    m = norm_mat.shape[0]    # 仅仅拿出m * ho_ratio%的数据进行分类    num_test_vec = int(m * ho_ratio)    # 错误的数据的数量    error_count = 0.0    for i in range(num_test_vec):        # 分类        classifier_result = classify0(norm_mat[i, :],  # 输入向量                                      norm_mat[num_test_vec:m, :],  # 样本数量                                      dating_label[num_test_vec:m],  # 标签                                      3)        # print("the classifier came back with: %d, the real answer is: %d" %        #       (classifier_result, dating_label[i]))        if classifier_result != dating_label[i]:            error_count += 1.0    print("the total error rate is: %d%%" % (error_count / float(num_test_vec) * 100))    print("the sum is %d and the error count is: %d" % (m, error_count))def classify_person():    result_list = ['可能性为0', '小几率', '大概率']    percent_tats = float(input("问题1:玩游戏事件所占的百分比?"))    fly_miles = float(input("问题2:每年获得的飞行常客里程数?"))    ice_cream = float(input("问题3:每周消费的冰淇淋公升数?"))    dating_data_mat, dating_labels = file_matrix('datingTestSet2.txt')    norm_mat, ranges, min_val = auto_norm(dating_data_mat)    in_arr = array([fly_miles, percent_tats, ice_cream])    classifierResult = classify0((in_arr - min_val) / ranges,  # 输入向量                                 norm_mat,  # 样本数量                                 dating_labels,  # 标签                                 3)    print("You will probably like this person: ", result_list[classifierResult - 1])

手写识别系统

收集数据:提供文本文件。
准备数据:编写函数classify0(),将图像格式转换为分类器使用的list格式。
分析数据:在Python命令提示符中检查数据,确保它符合要求。(若是需要,必须进行归一化处理,详见auto_norm函数)
训练算法:此步骤不适用于k近邻算法。(本案例不包含训练部分。)
测试算法:编写函数使用提供的部分数据集作为测试样本,测试样本与非测试样本的区别在于测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误。
使用算法:本例没有完成此步骤,若你感兴趣可以构建完整的应用程序,从图像中提取数字,并完成数字识别,美国的邮件分拣系统就是一个实际运行的类似系统。

from os import listdirdef img_to_vector(filename):    """    将图片转换为一个向量    :param filename:图片文件名    :return: 返回图片向量    """    return_vec = zeros((1, 1024))    fr = open(filename)    for i in range(32):        line_str = fr.readline()        for j in range(32):            return_vec[0, 32 * i + j] = int(line_str[j])    return return_vecdef handwriting_class_test():    """    手写数字识别系统的测试代码    :return:无    """    hw_labels = []    # 将digits/trainingDigits目录下的文件全部存到一个列表中    training_file_list = listdir('digits/trainingDigits')    # 获取文件的数量    m = len(training_file_list)    # 训练矩阵    training_mat = zeros((m, 1024))    # 分类    # 由于文本中的值已经在0和1之间了,所以我们不需要归一化处理了。    for i in range(m):        # 文件名        file_name = training_file_list[i]        file = file_name.split('.')[0]  # take off .txt        # 类别        class_num = int(file.split('_')[0])        # 将类别添加到标签列表中        hw_labels.append(class_num)        training_mat[i, :] = img_to_vector('digits/trainingDigits/%s' % file_name)    # 测试样本    test_file_list = listdir('digits/testDigits')    # 错误率    error_count = 0.0    mTest = len(test_file_list)    for i in range(mTest):        file_name = test_file_list[i]        # 将.txt去掉,仅仅需要的是第一个部分        file = file_name.split('.')[0]        # 第一个部分中的_前面的数字是文本内显示的内容        # 第二个是编号        class_num = int(file.split('_')[0])        # 图像文本转向量        vector_under_test = img_to_vector('digits/testDigits/%s' % file_name)        # 分类        classifier_result = classify0(vector_under_test, training_mat, hw_labels, 3)        # print("the classifier came back with: %d, the real answer is: %d" %        #       (classifier_result, class_num))        if classifier_result != class_num: error_count += 1.0    print("\n the total number of errors is: %d" % error_count)    print("\n the total error rate is: %f" % (error_count / float(mTest)))

  实际使用这个算法时,算法的执行效率并不高。因为算法需要为每个测试向量做2000次距离计算,每个距离计算包括了1024个维度浮点运算,总计要执行900次,此外,我们还需要为测试向量准备2MB的存储空间。是否存在一种算法减少存储空间和计算时间的开销呢?k决策树就是k近邻算法的优化版,可以节省大量的计算开销。