《机器学习实战》萌新入门笔记 ① — — K近邻算法 趣味讲解和书本实例详细注释后代码

开坑前言:大一在读新生开启了自己的机器学习之旅,从接触到现在已经有快两个月了,现在回过头为夯实基础,从开始看起《机器学习实战》这本书,即使是最简单的KNN算法,详细看过作者用python实现的代码后还能感觉到不少收获。开这个坑希望能分享一些经验给到更多一同在学习这本书的朋友,共同进步。

若本文有漏洞或者希望交流的朋友可以和我私信,欢迎大家指正。

本系列笔记正式开始:

故事引入:
首先!有一只小牛在网上冲浪,它正在找伴侣,于是在一个约会网站上搜啊搜。

由于约会网站很是发达也有些缺点,这只小牛在这个网站上找到了1000个可叫来约会的对象,但是对于这些对象只知道三个信息,分别是身高,体重,颜值(满分为10分)。

于是小牛把这1000个对象的信息储存在一个表格里,小牛和这1000个对象逐个叫来约会……

过了N年,小牛和1000个对象一一约会完毕。每个对象小牛都给出了一个喜爱程度,1是不喜爱,2是一般般,3是特别喜爱。

约会完以后,约会网站又给小牛分配了1000个新对象,但是这个小牛不想再通过约会来知道这些对象是不是他喜爱的了,他想通过约会网站给出的下一个对象的三个信息,来预估自己喜不喜欢下一个对象(如果不喜欢或者一般就不去约会了 )。

小牛根据之前1000个老对象的三个信息,和约会得出的喜不喜欢,建立了一个三维坐标轴,这三维分别是身高 体重 颜值,通过这三个维度,点出了每个对象在三维坐标系中所在的位置。

而每个对象都有对应的喜欢程度,而喜欢程度都对应一个颜色,不喜欢的就是灰色,一般般的就是蓝色,特别喜欢的就是红色,小牛由此建立了一个装载了1000个老对象的三维坐标空间。

小牛把新对象一个个放进坐标系中进行比较,根据三个信息确定了他们在空间里的位置,计算了新对象和所有老对象在空间坐标系中的距离。

再找到离这个新对象最近的10个老对象,看看他们是什么颜色的,如果距离最近的老对象们大多是红色的,那么新对象一定也差不到哪去,小牛笃定这个新对象一定是值得约会的!如果最近的老对象们大多是黑色的,小牛笃定这个新对象肯定很差劲,于是就根本不打算和这个差劲的新对象约会。


是不是非常简单易懂呀,实际上就是对于训练集(老对象们)建立了一组三维空间坐标系,根据几何距离的关系来对测试样本(新对象)的类别做出判断。
而KNN的算法是在本故事的基础上将问题上升到了n维空间中,而故事里边寻找的距离新对象最近的老对象的数量,指代的就是KNN中的K,如果K=1,算法就叫最近邻,K=3,算法就叫3近邻。


这里将书本上算法实现流程码在下面仅供参考:

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

下面给出《机器学习实战》中,对于datingTestSet2.txt这一数据集实现绘制数据分析散点图和使用KNN算法预判类别功能的代码,(运行于版本最新的Python3.9):

这个代码需要结合书本来看,也就是说该代码对于有书的同学帮助较大。它是在原代码的基础上进行了优化和整合,将我注释的代码和源代码一一对应和比较更容易看懂~

这里给出各个函数对应的功能方便查阅:

  1. file_matrix 读入数据集
  2. classify0 实现KNN算法
  3. vasual_map 绘制数据分析图
  4. auto_norm 对数据进行归一化
  5. dating_class_test 划分测试集和训练集,检测准确率
  6. create_dataset 无视即可
  7. img_vector 列向量化手写图片
  8. handwriting_recognize 手写字KNN识别模块
from numpy import *
import operator
import matplotlib.pyplot as plt
import os

def file_matrix(filename):
    fr = open(filename)
    # open()函数用于打开一个文件,创建一个file对象,相关的方法才可以调用它进行读写
    array_in_line = fr.readlines()
    # file.readlines([size]) :返回包含size行的列表, size 未指定则返回全部行
    number_of_lines = len(array_in_line)
    # Python len() 方法返回对象(字符、列表、元组等)长度或项目个数。
    return_mat = zeros((number_of_lines, 3))
    # return_mat为len行,一行三元素的由零组成的数组,视为初始化样本数组
    class_label_vector = []
    index = 0
    for line in array_in_line:
        line = line.strip()
        # 去除该行的首尾空格
        list_from_line = line.split('\t')
        # \t为制表符,意为分割有空格分隔开的各元素
        return_mat[index, :] = list_from_line[0:3]
        # 注意,这里只取0 1 2个元素,不包括第三个元素,该数组即为读入的数据
        class_label_vector.append(int(list_from_line[-1]))
        # -1为最后一个元素,是每个样本的标签值
        # 注:必须有int注明为整数变量,否则将读入字符串型
        index += 1
    return return_mat, class_label_vector


def create_dataset():
    group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
    labels = ['A', 'A', 'B', 'B']
    return group, labels


def classify0(in_x, dataset, labels, kling):
    dataset_size = dataset.shape[0]
    # 获取训练集的大小
    diff_mat = tile(in_x, (dataset_size, 1)) - dataset
    # 将样本in_x按照dataset的大小纵向复制,(行,列),减去dataset代表各维度相减
    sq_diff_mat = diff_mat**2
    # 样本与测试集的各维度之差做平方
    sq_distances = sq_diff_mat.sum(axis=1)
    # axis=1代表跨行,axis=0代表跨列,这里表示计算样本与训练样本的各维度距离平方和
    distances = sq_distances**0.5
    # 计算样本与所有训练样本的距离
    sorted_dist_index = distances.argsort()
    # 返回对所有距离从小到大排序的索引值
    class_count = {}
    for i in range(kling):
        vote_label = labels[sorted_dist_index[i]]
        class_count[vote_label] = class_count.get(vote_label, 0) + 1
    # get(key, default=None)
    # 参数
    # key -- 字典中要查找的键。
    # default -- 如果指定键的值不存在时,返回该默认值。
    # 这里是选择距离最小的k个点
    sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
    # sorted是对是所有可迭代的对象进行排序操作,items(原iteritems)是将字典以列表的形式返回,itemgetter是返回对象第一个域的值,reverse=Ture是是数据
    return sorted_class_count[0][0]
    # 最后返回排序后的第一个点


def vasual_map(datamat, datalabels):
    fig = plt.figure()
    # 创建自定义图像
    ax = fig.add_subplot(1, 2, 1)
    ay = fig.add_subplot(1, 2, 2)
    # add_subplot(x,y,z) 表示将图像划分为x行y列,此子图占据从左到右从上到下的第z个位置
    ax.scatter(datamat[:, 1], datamat[:, 2], 15.0*array(datalabels), 15.0*array(datalabels))
    ay.scatter(datamat[:, 0], datamat[:, 1], 15.0*array(datalabels), 15.0*array(datalabels))
    # 使用数据矩阵的第二列第三列数据来构建ax子图像,并利用datalabels储存的不同标签,在散点图上绘制了色彩不等,尺寸不同的点
    plt.show()


def auto_norm(data_set):
    min_vals = data_set.min(0)
    max_vals = data_set.max(0)
    # min(0)返回该矩阵中每一列的最小值 min(1)返回该矩阵中每一行的最小值 最后返回的均是行向量的形式
    ranges = max_vals - min_vals
    norm_data_set = zeros(shape(data_set))
    # 初始化norm_data_set作为最终返回的处理过后的数据集
    m = data_set.shape[0]
    # shape[0]表示矩阵的行数 shape[1]表示矩阵的列数
    norm_data_set = data_set - tile(min_vals, (m, 1))
    norm_data_set = norm_data_set/tile(ranges, (m, 1))
    # tile(A,reps) 若reps为(a,b) 表示重构后的数组为min_vals的第一维上重复a次,在第二维上重复b次
    return norm_data_set, ranges, min_vals


def dating_class_test():
    test_ratio = 0.1
    # 选取的测试集的比例
    dating_data_mat, dating_labels = file_matrix('datingTestSet2.txt')
    # 读取数据集
    norm_mat, ranges, min_vals = auto_norm(dating_data_mat)
    # 将数据集进行归一化
    m = norm_mat.shape[0]
    # 取值数据集的行数,即样本的总数
    num_test_vecs = int(m*test_ratio)
    # 选取的测试集总数
    error_count = 0
    # 这个变量用于计量分类器判断错误的数量
    for i in range(num_test_vecs):
        classify_result = classify0(norm_mat[i, :], norm_mat[num_test_vecs:m, :], dating_labels[num_test_vecs:m], 3)
        if classify_result != dating_labels[i]:
            error_count += 1.0
        print("the classifier came back with: %d, the real answer is: %d" % (classify_result, dating_labels[i]))
    print("the total error rate is: %f" % (error_count/float(num_test_vecs)))
    # 输出比对结果(错误率)


def img_vector(filename):
    final_vector = zeros((1, 1024))
    # 初始化最终函数返回的矩阵
    fr = open(filename)
    # 打开文件
    for i in range(32):
        line_str = fr.readline()
        # 读取文件的第i行。每次读取一行后会记录,在下一次使用该语句使读取上一次的下一行。
        for j in range(32):
            final_vector[0, 32*i+j] = int(line_str[j])
            # 读取文件的第i行第j个字符
    return final_vector


def handwriting_recognize():
    hwlabels = []
    # 测试集的标识
    trainfilelist = os.listdir('trainingDigits')
    # trainfilelist为trainingDigits文件夹下每个元素的列表
    num_train = len(trainfilelist)
    # 该列表的总元素的数目
    training_mat = zeros((num_train, 1024))
    for i in range(num_train):
        filenamestr = trainfilelist[i]
        filestr = filenamestr.split('.')[0]
        # 读取文件的名字
        fileclassnum = filestr.split('_')[0]
        # 读取文件的标签
        hwlabels.append(fileclassnum)
        training_mat[i, :] = img_vector("trainingDigits/%s" % filenamestr)
        # 获取样本向量化后的列向量
    testfilelist = os.listdir('testDigits')
    error_count = 0.0
    num_test = len(testfilelist)
    for i in range(num_test):
        filenamestr = testfilelist[i]
        filestr = filenamestr.split('.')[0]
        test_label = filestr.split('_')[0]
        test_vector = img_vector("testDigits/%s" % filenamestr)
        classify_result = classify0(test_vector, training_mat, hwlabels, 3)
        if classify_result != test_label:
            error_count += 1
    print("the handwriting recognize total error rate is: %f" % (error_count/float(num_test)))


dating_class_test()
handwriting_recognize()



附上书上用代码绘制出的两个散点图:
在这里插入图片描述


后续的笔记很快就会更新!也请用这本书学习的同学持续关注我哦。


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值