机器学习-KNN算法

一、KNN算法原理与简单实现

1、KNN算法的概念

K最近邻(k-Nearest Neighbor,KNN)分类算法,是最简单的机器学习算法之一,涉及高等数学知识近乎为0,虽然它简单,但效果很好,是入门机器学习的首选算法。但很多教程只是一笔带过,在这里通过该算法,我们可以学习到在机器学习中所涉及的其他知识点和需要注意的地方。

KNN实现鸢尾花分类 

鸢尾花数据集介绍 

鸢尾花数据集记录了三类花以及它们的四种属性。(四种属性:花萼长度,花萼宽度,花瓣长度,花瓣宽度;3种标签:Setosa,versicolor,virginica)。我们的目标是当输入一个测试数据时通过KNN算法获得预测结果。 

 

数据可视化 

​ 我们可以提取鸢尾花的任意两个特征作为二维空间的坐标点进行可视化,来观察每个类别的属性分布范围。 

 

import matplotlib.pyplot as plt 
import numpy as np 
import tensorflow as tf 
import pandas as pd

plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False

TRAIN_URL = r'http://download.tensorflow.org/data/iris_training.csv'
train_path = tf.keras.utils.get_file(TRAIN_URL.split('/')[-1],TRAIN_URL)


names = ['Sepal length','Sepal width','Petal length','Petal width','Species']
df_iris = pd.read_csv(train_path,header=0,names=names)
iris_data = df_iris.values

plt.figure(figsize=(15,15),dpi=60)
for i in range(4):
    for j in range(4):
        plt.subplot(4,4,i*4+j+1)
        if i==0:
            plt.title(names[j])
        if j==0:
            plt.ylabel(names[i])
        if i == j:
            plt.text(0.3,0.4,names[i],fontsize = 15)
            continue
        
        plt.scatter(iris_data[:,j],iris_data[:,i],c= iris_data[:,-1],cmap='brg')
        

plt.tight_layout(rect=[0,0,1,0.9])
plt.suptitle('鸢尾花数据集\nBule->Setosa | Red->Versicolor | Green->Virginica', fontsize = 20)
plt.show()

 

 knn实现

 

import numpy as np
import pandas as pd
import math
from collections import Counter

import matplotlib.pyplot as plt

# 读取数据集
def Data():
    iris=pd.read_csv('iris.csv')
    return iris

# 划分数据集
def Datasets(iris):
    index=np.random.permutation(len(iris))
    index=index[0:15]
    Test = iris.take(index)
    Train = iris.drop(index)
    datasets = [Test, Train]
    
    return datasets

# KNN算法
def KNN(Train, Test, GT, k):
    Train_num = Train.shape[0]
    tests = np.tile(Test, (Train_num, 1)) - Train
    distance = (tests ** 2) ** 0.5
    result = distance.sum(axis=1)
    results = result.argsort()
    label = []
    for i in range(k):
        label.append(GT[results[i]])
    return label

def cross_define_K(Train, Test, GT):
    precision = []

    for k in range(1,50):
        #print(k)
        true = 0
        for i in Test:
            Test1 = [i[0],i[1],i[2],i[3]]
            result = KNN(Train,Test1,GT,k)
            collection = Counter(result)
            result = collection.most_common(1)
            if result[0][0] == i[4]:
                true += 1
        success = true / len(Test)
        precision.append(success)

    k1 = range(1,50)
    plt.plot(k1,precision,label='line1',color='g',marker='.',markerfacecolor='pink',markersize=10)
    plt.xlabel('K')
    plt.ylabel('Precision')
    plt.title('KNN')
    plt.legend()
    plt.show()


if __name__ == "__main__":
    # 读取iris数据集
    iris = Data()
    # 对数据集进行划分(训练集,测试集)
    datasets = Datasets(iris)

    print(datasets[0])

    # 设置KNN的k值
    k = 3
    
    # 将训练集的GT隐去
    Train = datasets[1].drop(columns=['class']).values

    # 读取训练集的GT
    GT = datasets[1]['class'].values
    
    # 读取测试集
    Test = datasets[0].values

    cross_define_K(Train,Test,GT)
    
    true = 0
    for i in Test:
        Test = [i[0],i[1],i[2],i[3]]
        result = KNN(Train,Test,GT,k)
        
        # KNN返回的是测试数据与训练数据相近的n个预测值
        collection = Counter(result)
        result = collection.most_common(1)
        #print(result[0][0])

        # 选取其中出现最多的结果进行验证
        if result[0][0] == i[4]:
            true += 1
    
    success = true/len(datasets[0])
    print('success:\n',success)



在之前的鸢尾花数据集中,我们只将2种花的150个样本的前2个特征在二维特征空间中表示,如下图 

  • 那么当来了一个新的数据(如下图中绿色的点),我们如何判断它最可能属于哪种花呢
  • 2、KNN算法的原理

  • 我们先取一个k值(即KNN中的"K"),在这里我们先根据经验假设取得了最优值k=3。K近邻算法做的事情就是对于每个新的点,我们计算出距离它最近的前k个点,然后这k个点进行投票,在这里k=3,如下图所示
  • 这个例子中,蓝色:红色为2:1
  • 因此该新的绿色数据点更有可能属于蓝色类别的花
  • 即KNN算法就是通过各样本之间的相似程度(样本空间中的距离)作出判断,因此只考虑1个样本是不具有说服力的,通常我们考虑k为多个

  • 这里K近邻解决的就是前面讲到的分类问题,它也可以解决回归问题

  • 3、KNN算法的简单实现

  • 经过上面的分析我们可以得出该算法大致思路,即判断新来的数据点与其他所有数据的距离,距离最近的点的类别即可能为该新点的类别
  • 这里模拟了十组数据,每组数据横坐标代表已患肿瘤天数,纵坐标代表对应肿瘤大小,依次对应标记数据:0代表良性肿瘤用绿点表示,1代表恶性肿瘤红点表示
  • 这里所用到的数学公式是大家初高中就学习的求两点(x1, y1)与(x2, y2)间距离公式:

    欧氏距离公式
  • 假设现在来了一个新的病人数据(2.5, 2.2)对应图中蓝色点,绘制散点图后我们可以很容易发现其属于红色即恶性肿瘤一类,那么接下来让我们用代码实现吧
  • 通过预测结果得出:由于该点所属类别很可能为1(恶性肿瘤),因此很遗憾,该病人很可能已经是癌症晚期
  • 二、Scikit-learn中KNN算法的封装

  • 我们将之前自己实现的KNN算法继续完善,加入了一些参数变量判断,并将其封装进了函数方法
  • import numpy as np
    from math import sqrt
    from collections import Counter
    
    '''
    k:kNN中的k,判断多少个最近的数据
    xTrain:待训练的特征数据
    yTrain:待训练的Label标记数据
    x:新的待预测判断的数据
    return 返回预测结果
    '''
    def kNNClassify(k, xTrain, yTrain, x):
    
        # k需大于1且小于训练集样本数量
        print(k)
        print(xTrain.shape[0])
        if 1 <= k <= xTrain.shape[0]:
            print("无效的K值")
            return
        # 训练数据特征需与Label一一对应数量相同
        if xTrain.shape[0] != yTrain.shape[0]:
            print("训练数据特征与Label数量不同")
            return
        # 训练数据特征维度需与待预测的数据相同
        if xTrain.shape[0] != x.shape[0]:
            print("训练数据特征维度与待预测的数据不同")
            return
    
        distances = [sqrt(np.sum((xTrain - x)**2)) for xTrain in xTrain]
        nearest = np.argsort(distances)
        topKY = [yTrain[i] for i in nearest[:k]]
        votes = Counter(topKY)
    
        return votes.most_common(1)[0][0]

  • 使用魔法命令加载该文件,运行函数得到一致的预测结果
  • 小结

  • 通过该KNN机器学习算法的学习,对比之前的机器学习流程图,我们可以知道:
    • 对于监督学习,训练数据集既要有特征也包括了相应的标签(如刷高考题,既给你题目也给你答案)
    • 训练模型这个过程更专业的叫法英文中为fit,通常译为拟合;最后得出预测结果即为predict
    • 由于KNN算法的特殊性,我们未得到一个训练出的模型,因为KNN是一个不需要训练过程的算法,没有模型,但其训练数据集即可被看做为模型
    • 1、Scikit-learn中KNN算法的使用

    • 再次提醒学会查询帮助文档及官方文档
    • 如上就是使用Scikit-learn中的算法相当标准的步骤,总结为如下四步:
      • 加载相应算法
      • 创建实例(传入相应参数)
      • Fit拟合
      • Predict预测得出结果

2、模仿Scikit-learn重构自己实现的kNN算法 

import numpy as np
from math import sqrt
from collections import Counter

class kNNClassifier:
    # 定义构造函数
    def __init__(self, k):

        assert k>=1, "无效的K值"
        self.k = k
        # 加上_定义为protected变量
        self._xTrain = None
        self._yTrain = None

    # 定义fit方法
    def fit(self, xTrain, yTrain):

        self._xTrain = xTrain
        self._yTrain = yTrain
        return self

    # 定义predict方法
    def predict(self, xPredict):

        assert self._xTrain is not None and self._yTrain is not None, "请先进行Fit"
        assert xPredict.shape[1] == self._xTrain.shape[1], "特征数量与训练数据集不一致(列数不一致)"
        
        yPredict = [self._predict(x) for x in xPredict]
        return np.array(yPredict)

    def _predict(self, x):

        assert x.shape[0] == self._xTrain.shape[1], "特征数量与训练数据集不一致(列数不一致)"

        distances = [sqrt(np.sum((xTrain - x)**2)) for xTrain in self._xTrain]
        nearest = np.argsort(distances)
        topKY = [self._yTrain[i] for i in nearest[:self.k]]
        votes = Counter(topKY)

        return votes.most_common(1)[0][0]

    def __repr__(self):
        return "kNN(k=%d)" % self.k

  • 当然,Scikit-learn中底层实现的kNN算法没这么简单,为提升性能做了很多优化。

三、如何判断算法的好坏与性能 

  • 问题:将所有真实数据通过训练得出的模型如果很差怎么办?
  • 解决办法:训练与测试数据集分离

 

推荐学习资源

 参考:

Github地址:https://github.com/Exrick/Machine-Learning

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值