问题描述

  • 说明:
    KNN算法的核心是要获取k个近邻,衡量的指标通常用的是欧氏距离。
  • 步骤
    (1)获取到所有训练样本的特征向量矩阵
    (2)利用待测样本的特征向量,与所有训练样本的特征向量计算欧式距离
    (3)对得到的距离进行排序,根据指定的k获取前k个距离最近的索引(即训练样本原本在特征矩阵中的索引)
    (4)根据获取到的索引来获得该训练样本的标签
    (5)统计前k个近邻的标签出现的次数,返回出现次数最多的标签,并将其作为待测样本的预测标签

代码示例

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
 @Author   : Sword
 @Date     : 2020/5/28
 @Time     : 下午 14:22
 @Version  : v1.0
 @File     : knn_new.py 
 @Describe :
"""
import numpy as np
import operator


def create_dataset():
    # 创建4个样本,特征矩阵为 4 * 2
    group = np.array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
    labels = ['A', 'A', 'B', 'B']
    return group, labels


def classify(test_x, dataset, labels, k):
    """
    :param test_x: 待测样本的特征向量
    :param dataset: 训练样本集的特征矩阵
    :param labels: 训练样本的标签
    :param k: 前k个最近邻
    """
    dataset_size = dataset.shape[0]

    # 计算待测样本test_x与每个训练样本的距离
    # 计算每一维特征的差值,diff_mat中每个元素表示(x1 - x2)
    # 然后对diff_mat中的每个元素求平方,再按行累加再开根号得到与当前
    # 样本的距离
    diff_mat = np.tile(test_x, (dataset_size, 1)) - dataset
    distance_mat = (diff_mat ** 2).sum(axis=1) ** 0.5

    # 根据距离排序,返回索引值
    sorted_dist_index = distance_mat.argsort()
    class_count = {}
    for i in range(k):
        # 根据距离对比返回的前k个近邻获取其索引值
        # 然后根据索引值获取该近邻的标签
        # 并利用字典来统计k个近邻的标签出现次数
        vote_label = labels[sorted_dist_index[i]]
        class_count[vote_label] = class_count.get(vote_label, 0) + 1

    # 将字典按照值进行排序,返回一个(label, count)元组,然后返回第一个元组对应的标签
    # 第一个元组存放着出现次数最多的标签
    sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
    return sorted_class_count[0][0]


def file_2_matrix(path):
    """
    根据文件路径将文件信息存储为特征向量矩阵和标签列表
    文件内容的格式为: 特征1  特征2   特征3   标签
    大小为:(1000, 3)
    """
    with open(path, 'r') as f:
        lines = f.readlines()

        # 声明一个全零矩阵,用于存放特征值
        # 首先获取该矩阵的大小,总共3列特征
        size = len(lines)
        feature_matrix = np.zeros((size, 3))

        # 声明一个列表,存放标签
        label_list = []

        # 按行读取数据,每一行的前3维是特征,最后一维是标签
        # 声明一个变量来记录当前是第几行
        index = 0
        for line in lines:
            value = line.strip().split('\t')
            # 取出前3维的特征,存放到特征矩阵中
            feature_matrix[index, :] = value[0:3]
            # 取出最后一维的标签,存放到标签列表中
            label_list.append(int(value[-1]))

            # 存完一行(即表示存了一个样本)就增加1
            index += 1
        return feature_matrix, label_list


def normalize(dataset):
    """
    将特征值转化为0-1区间内的值
    new_value = (value - min) / (max - min)
    """

    # 获取特征矩阵中每一列的最小值和最大值
    # 0表示获取每一列的最小值
    # 1表示获取每一行的最小值
    # 因为对象是特征矩阵(1000, 3), 所以返回的min_value和max_value的大小为(1, 3)
    min_value = dataset.min(0)
    max_value = dataset.max(0)
    range = max_value - min_value

    # 将特征矩阵的每个元素都按照公式进行转化
    matirx_size = dataset.shape[0]
    # 矩阵中每个元素 - min_value
    feature_matirx = dataset - np.tile(min_value, (matirx_size, 1))
    # 矩阵中每个元素 / (max - min)  ==> 分母相当于range
    feature_matirx = feature_matirx / np.tile(range, (matirx_size, 1))
    return feature_matirx


def predict(test_size=0.1):
    """
    对测试样本进行测试,测试样本从数据集中的第一个开始选取,直到满足条件为止
    这样方便将后面的所有样本作为训练数据
    :param test_size: 测试样本的比例
    """
    # 根据文件路径获取训练数据和标签
    # 并对特征值进行转换
    file_path = './data/datingTestSet.txt'
    train_data, train_label = file_2_matrix(file_path)
    train_data = normalize(train_data)

    # 获取训练数据的数量以及测试样本的数量
    train_data_num = train_data.shape[0]
    test_num = int(test_size * train_data_num)

    # 声明一个变量用于记录分类错误的样本数
    error_count = 0

    # 根据测试样本的数量进行循环
    # 每次取出一个样本进行测试
    for i in range(test_num):
        predict_result = classify(train_data[i, :], train_data[test_num:train_data_num, :],
                                  train_label[test_num:train_data_num], 3)
        print("the classifier came back with:%d, the real answer is:%d" % (predict_result, train_label[i]))
        if predict_result != train_label[i]:
            error_count += 1
    accuracy = 100. * (1 - (error_count / test_num))
    print("test accuracy is:%.2f%%" % accuracy)


if __name__ == '__main__':
    file_path = './data/datingTestSet.txt'
    predict()