机器学习实战笔记(三):使用k-近邻算法的手写识别系统(Python3 实现)

完整代码及数据地址:https://github.com/cqulun123/Machine-Learning-in-Action

0 使用k-近邻算法的手写识别系统的步骤

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

1 准备数据:将图像转换为测试向量

       我们使用目录trainingDigits中的数据训练分类器,使用目录testDigits中的数据测试分类器的效果。,具体数据下载见本文给出的github地址。为了使用前面两个例子的分类器,我们必须将图像格式化处理为一个向量。我们将把一个32×32的二进制图像矩阵转换为1×1024的向量 。具体处理代码如下:

def img_to_vector(filename):
    """
    将图像转换为向量:该函数创建1×1024的NumPy数组,然后打开给定的文件,
    循环读出文件的前32行,并将每行的头32个字符值存储在NumPy数组中,最后返回数组。
    """
    # 创建1×1024的NumPy数组
    return_vect = np.zeros((1, 1024))
    # 打开给定的文件名
    fr = open(filename)
    # 循环读出文件的前32行
    for i in range(32):
        line_str = fr.readline()
        # 将每行的头32个字符值存储在NumPy数组
        for j in range(32):
            return_vect[0, 32 * i + j] = int(line_str[j])
    # 返回数组
    return return_vect

2 测试算法:使用 k-近邻算法识别手写数字

       上节我们已经将数据处理成分类器可以识别的格式,本节我们将这些数据输入到分类器,检测分类器的执行效果。具体处理代码如下:

def handwriting_class_test():
    """
    测试手写数字识别系统的kNN分类器
    """
    # 创建测试集标签
    hw_labels = []
    # 加载训练数据
    training_file_list = listdir('trainingDigits')
    # 获取文件夹下文件的个数
    m = len(training_file_list)
    # 创建一个m行1024列的训练矩阵,该矩阵的每行数据存储一个图像
    training_mat = np.zeros((m, 1024))
    # 从文件名中解析出分类数字,如文件9_45.txt的分类是9,它是数字9的第45个实例
    for i in range(m):
        # 获取文件名
        file_name_str = training_file_list[i]
        # 去掉 .txt
        file_str = file_name_str.split('.')[0]
        # 获取分类数字
        class_num_str = int(file_str.split('_')[0])
        # 将获取到的分类数字添加到标签向量中
        hw_labels.append(class_num_str)
        # 将每一个文件的1x1024数据存储到训练矩阵中
        training_mat[i, :] = img_to_vector('trainingDigits/%s' % file_name_str)
    # 加载测试数据
    test_file_list = listdir('testDigits')
    # 错误计数
    error_count = 0.0
    # 测试数据的个数
    m_test = len(test_file_list)
    # 从测试数据文件名中解析出分类数字
    for i in range(m_test):
        # 获取文件名
        file_name_str = test_file_list[i]
        # 去掉 .txt
        file_str = file_name_str.split('.')[0]
        # 获取分类数字
        class_num_str = int(file_str.split('_')[0])
        # 获取测试集的1x1024向量,用于训练
        vector_under_test = img_to_vector('testDigits/%s' % file_name_str)
        # 返回分类结果
        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_str))
        if (classifier_result != class_num_str): error_count += 1.0
    # 输出错误个数
    print("\nthe total number of errors is: %d" % error_count)
    # 输出错误率
    print("\nthe total error rate is: %f" % (error_count / float(m_test)))

测试该函数的输出结果 :

if __name__ == '__main__':
    handwriting_class_tst()

       k - 近邻算法识别手写数字数据集,错误率为 1.3% 。改变变量 k 的值、修改函数 handwriting_class_test 随机选取训练样本、改变训练样本的数目,都会对 k - 近邻算法的错误率产生影响,感兴趣的话可以改变这些变量值,观察错误率的变化。
完整代码:
# encoding: utf-8
"""
@author:max bay 
@version: python 3.6
@time: 2018/5/6 0:21
"""

import numpy as np
import operator
from os import listdir


def classify0(input_data, data_set, labels_set, k):
    """
    函数作用:使用k-近邻算法将每组数据划分到某个类中
    :param input_data:用于分类的输入数据(测试集)
    :param data_set:输入的训练样本集
    :param labels_set:训练样本标签
    :param k:用于选择最近邻居的数目,即kNN算法参数,选择距离最小的k个点
    :return:返回分类结果
    """
    # data_set.shape[0]返回训练样本集的行数
    data_set_size = data_set.shape[0]
    # 在列方向上重复input_data,1次,行方向上重复input_data,data_set_size次
    diff_mat = np.tile(input_data, (data_set_size, 1)) - data_set
    # diff_mat:输入样本与每个训练样本的差值,然后对其每个x和y的差值进行平方运算
    sq_diff_mat = diff_mat ** 2
    # 按行进行累加,axis=1表示按行。
    sq_distances = sq_diff_mat.sum(axis=1)
    # 开方运算,求出距离
    distances = sq_distances ** 0.5
    # 返回distances中元素从小到大排序后的索引值
    sorted_dist_indices = distances.argsort()
    # 定一个字典:统计类别次数
    class_count = {}

    for i in range(k):
        # 取出前k个元素的类别
        vote_index_label = labels_set[sorted_dist_indices[i]]
        # 统计类别次数
        class_count[vote_index_label] = class_count.get(vote_index_label, 0) + 1
        # 把分类结果进行降序排序,然后返回得票数最多的分类结果
        sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
        return sorted_class_count[0][0]


def file_to_matrix(filename):
    """
     函数作用:从文件中读入训练数据,并存储为矩阵
    :param filename:文件名字符串
    :return:训练样本矩阵和类标签向量
    """
    # 打开文件
    fr = open(filename)
    # 读取文件内容
    array_lines = fr.readlines()
    # 得到文件行数
    number_of_lines = len(array_lines)
    # 返回解析后的数据
    return_mat = np.zeros((number_of_lines, 3))
    # 定义类标签向量
    class_label_vector = []
    # 行索引值
    index = 0
    for line in array_lines:
        # 去掉 回车符号
        line = line.strip()
        # 用\t分割每行数据
        list_from_line = line.split('\t')
        # 选取前3个元素,将它们存储到特征矩阵中
        return_mat[index, :] = list_from_line[0:3]
        # 把该样本对应的标签放至标签向量,顺序与样本集对应。
        class_label_vector.append(int(list_from_line[-1]))
        index += 1
    return return_mat, class_label_vector


def auto_norm(data_set):
    """
    该函数可以自动将数字特征值转化为0到1的区间,即归一化训练数据
    """
    # 获取数据集中每列的最小数值
    min_vals = data_set.min(0)
    # 获取数据集中每列的最大数值
    max_vals = data_set.max(0)
    # 最大值与最小的差值
    ranges = max_vals - min_vals
    # 创建一个全0矩阵,用于存放归一化后的数据
    norm_data_set = np.zeros(np.shape(data_set))
    # 返回data_set的行数
    m = data_set.shape[0]
    # 原始数据值减去最小值
    norm_data_set = data_set - np.tile(min_vals, (m, 1))
    # 除以最大和最小值的差值,得到归一化数据
    norm_data_set = norm_data_set / np.tile(ranges, (m, 1))
    # 返回归一化数据,最大值与最小的差值,每列的最小数值
    return norm_data_set, ranges, min_vals


def img_to_vector(filename):
    """
    将图像转换为向量:该函数创建1×1024的NumPy数组,然后打开给定的文件,
    循环读出文件的前32行,并将每行的头32个字符值存储在NumPy数组中,最后返回数组。
    """
    # 创建1×1024的NumPy数组
    return_vect = np.zeros((1, 1024))
    # 打开给定的文件名
    fr = open(filename)
    # 循环读出文件的前32行
    for i in range(32):
        line_str = fr.readline()
        # 将每行的头32个字符值存储在NumPy数组
        for j in range(32):
            return_vect[0, 32 * i + j] = int(line_str[j])
    # 返回数组
    return return_vect


def handwriting_class_test():
    """
    测试手写数字识别系统的kNN分类器
    """
    # 创建测试集标签
    hw_labels = []
    # 加载训练数据
    training_file_list = listdir('trainingDigits')
    # 获取文件夹下文件的个数
    m = len(training_file_list)
    # 创建一个m行1024列的训练矩阵,该矩阵的每行数据存储一个图像
    training_mat = np.zeros((m, 1024))
    # 从文件名中解析出分类数字,如文件9_45.txt的分类是9,它是数字9的第45个实例
    for i in range(m):
        # 获取文件名
        file_name_str = training_file_list[i]
        # 去掉 .txt
        file_str = file_name_str.split('.')[0]
        # 获取分类数字
        class_num_str = int(file_str.split('_')[0])
        # 将获取到的分类数字添加到标签向量中
        hw_labels.append(class_num_str)
        # 将每一个文件的1x1024数据存储到训练矩阵中
        training_mat[i, :] = img_to_vector('trainingDigits/%s' % file_name_str)
    # 加载测试数据
    test_file_list = listdir('testDigits')
    # 错误计数
    error_count = 0.0
    # 测试数据的个数
    m_test = len(test_file_list)
    # 从测试数据文件名中解析出分类数字
    for i in range(m_test):
        # 获取文件名
        file_name_str = test_file_list[i]
        # 去掉 .txt
        file_str = file_name_str.split('.')[0]
        # 获取分类数字
        class_num_str = int(file_str.split('_')[0])
        # 获取测试集的1x1024向量,用于训练
        vector_under_test = img_to_vector('testDigits/%s' % file_name_str)
        # 返回分类结果
        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_str))
        if (classifier_result != class_num_str): error_count += 1.0
    # 输出错误个数
    print("\nthe total number of errors is: %d" % error_count)
    # 输出错误率
    print("\nthe total error rate is: %f" % (error_count / float(m_test)))


if __name__ == '__main__':
    handwriting_class_test()

小结

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

4 参考资料

[1] 机器学习实战

[2] 机器学习实战笔记(Python实现)-02-k近邻算法(kNN)

[3] Python3《机器学习实战》学习笔记(一):k-近邻算法(史诗级干货长文)



猜你喜欢

转载自blog.csdn.net/cqulun123/article/details/80219788