K近邻算法实现--KNN

一. KNN概述

k-近邻(kNN, k-NearestNeighbor)算法是一种基本分类与回归方法,我们这里只讨论分类问题中的 k-近邻算法。

k 近邻算法的输入为实例的特征向量,对应于特征空间的点;输出为实例的类别,可以取多类。k 近邻算法假设给定一个训练数据集,其中的实例类别已定。分类时,对新的实例,根据其 k 个最近邻的训练实例的类别,通过多数表决等方式进行预测。因此,k近邻算法不具有显式的学习过程。

k 近邻算法实际上利用训练数据集对特征向量空间进行划分,并作为其分类的“模型”。 k值的选择、距离度量以及分类决策规则是k近邻算法的三个基本要素。

 

二. KNN原理

  1. 假设有一个带有标签的样本数据集(训练样本集)
  2. 输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较。

            a. 计算新数据与样本数据集中每个样本的距离

             b. 对求得的所有距离进行排序(从小到大,距离越近表示越相似)

             c.  取前 k (k 一般小于等于 20 )个样本数据对应的标签

   3.求 k 个数据中出现次数最多的分类标签作为新数据的类别

通俗理解:给定训练集,对于某个新输入的样本,计算该待分类样本距离训练集所有样本的距离,取距离最近的K个样本呢,统计他们的类别,K个样本的最多数属于哪个类,哪个类将被判定为新样本的类别

 

三. 项目案例

1.项目概述

海伦使用约会网站寻找约会对象。经过一段时间之后,她发现曾交往过三种类型的人:

  • 不喜欢的人
  • 魅力一般的人
  • 极具魅力的人

现在她收集到了一些约会网站未曾记录的数据信息,这更有助于匹配对象的归类

2.收集数据

海伦把这些约会对象的数据存放在文本文件 datingTestSet2.txt 中,总共有 1000 行。海伦约会的对象主要包含以下 3 种特征:

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

文本文件数据格式如下:

                                     

3. 思路分析

     a. 导入数据,可以通过pandas或使用.readlines()

     b. 可视化(非必要):可以观察数据的分布规则,查看哪些特征具有可分性,是否不存在类别不平衡问题

     c.  数据归一化:归一化特征值,消除特征之间量级不同导致的影响,其次程序运行收敛更快, 归一化在-1--+1之间是统计的                                  坐标分布

     d.  分类(计算距离所有已知类别样本的距离,距离排序,取前K个样本,统计K个样本的类别,返回频次最高的类别)

 

四. 代码分析

import os
import matplotlib.pyplot as plt
import operator
import math
from mpl_toolkits.mplot3d import Axes3D

 

1. 导入数据

filepath ='D:\code\code_test\KNN\海伦约会\datingTestSet2.txt'   # data的绝对路径
pdData = pd.read_csv(filepath, header=None, names=['Fly_distance', 'Game_time','Ice_weight' , 'label'], sep='\s+')

print(pdData.head(7))     # 打印前 7 条数据

# 划分数据集
k = 50
# 划分数据(0-899) 和测试数据(900-999)
norm_train_dataSet = normData.iloc[0:900, 0:3]
#print(norm_train_dataSet)
train_dataSet_labels = pdData.iloc[0:900, 3]
#print(train_dataSet_labels)

norm_test_dataSet = normData.iloc[900:1000, 0:3]
#print(norm_test_dataSet)
test_dataSet_labels = pdData.iloc[900:1000, 3]
#print(test_dataSet_labels)

2. 可视化数据

def plot_show(pdData,model):
    if model == '2D':    # 二维 散点图
        label_1 = pdData[pdData['label'] == 1]
        label_2 = pdData[pdData['label'] == 2]
        label_3 = pdData[pdData['label'] == 3]

        fig, ax = plt.subplots(figsize=(10, 5))  # 画图大小
        ax.scatter(label_1['Fly_distance'], label_1['Game_time'], s=30, c='b', marker='o', label='1')
        ax.scatter(label_2['Fly_distance'], label_2['Game_time'], s=30, c='r', marker='o', label='2')
        ax.scatter(label_3['Fly_distance'], label_3['Game_time'], s=30, c='g', marker='o', label='3')
        ax.legend()
        ax.set_xlabel('Fly_distance')
        ax.set_ylabel('Game_time')
        plt.show()

    if model == '3D':
        # 可视化--Matplotlib画三维
        label_1 = pdData[pdData['label'] == 1]
        label_2 = pdData[pdData['label'] == 2]
        label_3 = pdData[pdData['label'] == 3]
        x_1 = label_1.loc[:, ['Fly_distance']]
        x_2 = label_2.loc[:, ['Fly_distance']]
        x_3 = label_3.loc[:, ['Fly_distance']]

        y_1 = label_1.loc[:, ['Game_time']]
        y_2 = label_2.loc[:, ['Game_time']]
        y_3 = label_3.loc[:, ['Game_time']]

        z_1 = label_1.loc[:, ['Ice_weight']]
        z_2 = label_2.loc[:, ['Ice_weight']]
        z_3 = label_3.loc[:, ['Ice_weight']]

        fig = plt.figure()
        ax = Axes3D(fig)
        ax.scatter(x_1, y_1, z_1, c='r', label='label_1')
        ax.scatter(x_2, y_2, z_2, c='b', label='label_2')
        ax.scatter(x_3, y_3, z_3, c='y', label='label_3')

        # 添加坐标轴(顺序是Z, Y, X)
        ax.set_zlabel('Z', fontdict={'size': 15, 'color': 'red'})
        ax.set_ylabel('Y', fontdict={'size': 15, 'color': 'red'})
        ax.set_xlabel('X', fontdict={'size': 15, 'color': 'red'})
        plt.show()

3. 归一化数据

def autoNorm(pdData,method):
    dataSet = pdData.iloc[:, 0:3]

    if method == 'linear':
        # 计算每种属性的最大值、最小值、范围
        minVals = dataSet.min(0)
        maxVals = dataSet.max(0)

        # 极差
        ranges = maxVals - minVals
        normDataSet = np.zeros(dtype=float, shape=(dataSet.shape))
        m = dataSet.shape[0]
        # 生成与最小值之差组成的矩阵
        normDataSet = dataSet - np.tile(minVals, (m, 1))  #####np.tile(a, (2,3)) 将a中内容重复3次,两行3*a列的数据:#a=[4,5,6] #print(np.tile(a,(4,3)))#############
        # 将最小值之差除以max-min组成矩阵
        normDataSet = normDataSet / np.tile(ranges, (m, 1))
        return normDataSet

    #if method == 'log':
    '''
        对数函数转换,表达式如下:  
        y = log10(x)  
        说明:以10为底的对数函数转换。
        
    '''

    # if method == 'arccot'
    '''
        反余切函数转换
        y=arctan(x)*2/PI  
    '''

4. 类别预测(计算距离、距离排序、统计最近的K个样本的类别、返回出现频次最高的类别作为预测结果)

def class_predict(inx, norm_train_dataSet, labels, k):
    # 距离度量 度量公式为欧氏距离
    diffMat = np.tile(inx, (norm_train_dataSet.shape[0],1)) - norm_train_dataSet
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances ** 0.5         # 得到传进来的一个样本inx 到norm_train_dataSet 中所有点的距离

    # 将距离排序:从小到大
    sortedDistIndicies = distances.argsort()        
    # 选取前K个最短距离, 取该K个样本中类别出现次数最多的类别作为 预测 label
    classCount={}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)        
    return sortedClassCount[0][0]

5. 测试

每次调用class_predict传入一个待分类样本,得到其预测的类别,循环test_num次后,统计得到所有测试样本中分类正确的样本个数,返回准确率

def class_test(norm_train_dataSet,train_dataSet_labels, norm_test_dataSet, test_dataSet_labels, k):
    pre_right_count = 0
    # print(norm_test_dataSet.shape[0])
    test_num = norm_test_dataSet.shape[0]
    for i in range(test_num):
        inx = norm_test_dataSet.iloc[i, :]
        labels = test_dataSet_labels.iloc[i]
        pre_label = class_predict(inx, norm_train_dataSet, train_dataSet_labels, k)
        if labels == pre_label:
            pre_right_count += 1

    acc = pre_right_count / test_num

    return acc

五.完整代码

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
import operator
import math
from mpl_toolkits.mplot3d import Axes3D


#  可视化函数
def plot_show(pdData,model):
    if model == '2D':    # 二维 散点图
        label_1 = pdData[pdData['label'] == 1]
        label_2 = pdData[pdData['label'] == 2]
        label_3 = pdData[pdData['label'] == 3]

        fig, ax = plt.subplots(figsize=(10, 5))  # 画图大小
        ax.scatter(label_1['Fly_distance'], label_1['Game_time'], s=30, c='b', marker='o', label='1')
        ax.scatter(label_2['Fly_distance'], label_2['Game_time'], s=30, c='r', marker='o', label='2')
        ax.scatter(label_3['Fly_distance'], label_3['Game_time'], s=30, c='g', marker='o', label='3')
        ax.legend()
        ax.set_xlabel('Fly_distance')
        ax.set_ylabel('Game_time')
        plt.show()

    if model == '3D':
        # 可视化--Matplotlib画三维
        label_1 = pdData[pdData['label'] == 1]
        label_2 = pdData[pdData['label'] == 2]
        label_3 = pdData[pdData['label'] == 3]
        x_1 = label_1.loc[:, ['Fly_distance']]
        x_2 = label_2.loc[:, ['Fly_distance']]
        x_3 = label_3.loc[:, ['Fly_distance']]

        y_1 = label_1.loc[:, ['Game_time']]
        y_2 = label_2.loc[:, ['Game_time']]
        y_3 = label_3.loc[:, ['Game_time']]

        z_1 = label_1.loc[:, ['Ice_weight']]
        z_2 = label_2.loc[:, ['Ice_weight']]
        z_3 = label_3.loc[:, ['Ice_weight']]

        fig = plt.figure()
        ax = Axes3D(fig)
        ax.scatter(x_1, y_1, z_1, c='r', label='label_1')
        ax.scatter(x_2, y_2, z_2, c='b', label='label_2')
        ax.scatter(x_3, y_3, z_3, c='y', label='label_3')

        # 添加坐标轴(顺序是Z, Y, X)
        ax.set_zlabel('Z', fontdict={'size': 15, 'color': 'red'})
        ax.set_ylabel('Y', fontdict={'size': 15, 'color': 'red'})
        ax.set_xlabel('X', fontdict={'size': 15, 'color': 'red'})
        plt.show()



# 归一化函数
def autoNorm(pdData, method):
     """
        Desc:
            归一化特征值,消除特征之间量级不同导致的影响,其次程序运行收敛更快, 归一化在-1--+1之间是统计的坐标分布。
        parameter:
            dataSet: 数据集
        return:
            归一化后的数据集 normDataSet. ranges和minVals即最小值与范围,并没有用到

        归一化公式:
            Y = (X-Xmin)/(Xmax-Xmin)
            其中的 min 和 max 分别是数据集中的最小特征值和最大特征值。该函数可以自动将数字特征值转化为0到1的区间。
     """

     dataSet = pdData.iloc[:, 0:3]

     if method == 'linear':
         # 计算每种属性的最大值、最小值、范围
         minVals = dataSet.min(0)
         maxVals = dataSet.max(0)

         # 极差
         ranges = maxVals - minVals
         normDataSet = np.zeros(dtype=float, shape=(dataSet.shape))
         m = dataSet.shape[0]
         # 生成与最小值之差组成的矩阵
         normDataSet = dataSet - np.tile(minVals, (
         m, 1))  #####np.tile(a, (2,3)) 将a中内容重复3次,两行3*a列的数据:#a=[4,5,6] #print(np.tile(a,(4,3)))#############
         # 将最小值之差除以max-min组成矩阵
         normDataSet = normDataSet / np.tile(ranges, (m, 1))
         return normDataSet

     # if method == 'log':
     '''
         对数函数转换,表达式如下:  
         y = log10(x)  
         说明:以10为底的对数函数转换。
 
     '''

     # if method == 'arcsin'
     '''
         反余切函数转换
         y=arctan(x)*2/PI  
     '''
filepath ='D:\code\code_test\自己手动搭建\KNN\海伦约会\datingTestSet2.txt'
pdData = pd.read_csv(filepath, header=None, names=['Fly_distance', 'Game_time', 'Ice_weight' , 'label'], sep='\s+')    # 正则匹配 sep='\s+',间隔读取数据
#print(pdData.head(7))     # 打印前 7 条数据

# 可视化数据
plot_show(pdData, '3D')
plot_show(pdData, '2D')

normData = autoNorm(pdData,'linear')

# KNN算法伪代码
'''
    对于每一个在数据集中的数据点:
    计算目标的数据点(需要分类的数据点)与该数据点的距离
    将距离排序:从小到大                     * 三要素之一: 距离度量
    选取前K个最短距离                        * 三要素之二: k的取值
    选取这K个中最多的分类类别                * 三要素之三:分类决策
    返回该类别来作为目标数据点的预测值
'''

k = 50
# 划分数据(0-899) 和测试数据(900-999)
norm_train_dataSet = normData.iloc[0:900, 0:3]
#print(norm_train_dataSet)
train_dataSet_labels = pdData.iloc[0:900, 3]
#print(train_dataSet_labels)

norm_test_dataSet = normData.iloc[900:1000, 0:3]
#print(norm_test_dataSet)
test_dataSet_labels = pdData.iloc[900:1000, 3]
#print(test_dataSet_labels)
######################################################

# 分类: 返回预测的 label
def class_predict(inx, norm_train_dataSet, labels, k):
    # 距离度量 度量公式为欧氏距离
    diffMat = np.tile(inx, (norm_train_dataSet.shape[0],1)) - norm_train_dataSet
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances ** 0.5         # 得到传进来的一个样本inx 到norm_train_dataSet 中所有点的距离

    # 将距离排序:从小到大
    sortedDistIndicies = distances.argsort()
    # 选取前K个最短距离, 取该K个样本中类别出现次数最多的类别作为 预测 label
    classCount={}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

'''
inx = norm_test_dataSet.iloc[0,:]   # 取第一个样本值inx
end=class_test(inx, norm_train_dataSet, train_dataSet_labels, 10)
print(end)
'''

#  传入测试数据,逐个送入class_predict函数进行预测,统计准确率
def class_test(norm_train_dataSet,train_dataSet_labels, norm_test_dataSet, test_dataSet_labels, k):
    pre_right_count = 0
    # print(norm_test_dataSet.shape[0])
    test_num = norm_test_dataSet.shape[0]
    for i in range(test_num):
        inx = norm_test_dataSet.iloc[i, :]
        labels = test_dataSet_labels.iloc[i]
        pre_label = class_predict(inx, norm_train_dataSet, train_dataSet_labels, k)
        if labels == pre_label:
            pre_right_count += 1
    acc = pre_right_count / test_num
    return acc

acc = class_test(norm_train_dataSet,train_dataSet_labels, norm_test_dataSet, test_dataSet_labels, k)
print(acc)












写在最后:欢迎批评指正,共同学习进步!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值