机器学习(一):k-近邻算法(实践篇)

在这里插入图片描述
对于KNN算法,我分成了基础篇,进阶篇,实践篇。本篇是我们的实践篇,对于KNN原理没有过多的讲解,如果是刚接触到K-近邻算法的话,建议可以先从机器学习(一):K-近邻算法(基础篇)学习。欢迎大家学习愉快。

实践基础内容:

  1. 理解 KNN 算法的思想。
  2. 使用 Python 实现 KNN 算法 。
  3. 使用 UCI 上面的 Iris 数据集进行算法测试。
  4. 记录测试结果

实践进阶内容:

  1. 自行优化 KNN 算法并进行代码测试。

所用数据集:

  1. 网址:http://archive.ics.uci.edu/ml/index.php
  2. Iris数据集

第一步:对数据进行分析:

下面代码画出了iris花的不同属性之间的影响程度:

# -*- coding:utf-8 -*-
import math
import warnings
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
import matplotlib.patches as mpatchs

warnings.filterwarnings('ignore')   #忽略警告
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

def loadDataSet(filename):
    """
    函数说明:从文件中下载数据,并将分离除连续型变量和标签变量
    :parameter:
            data - Iris数据集
            attributes - 鸢尾花的属性
            type - 鸢尾花的类别
            sl-花萼长度 , sw-花萼宽度, pl-花瓣长度, pw-花瓣宽度
    :return:
    """
    iris_data = pd.read_csv(filename)   #打开文件
    iris_data = pd.DataFrame(data=np.array(iris_data), columns=['sl', 'sw', 'pl', 'pw', 'type'], index=range(149))   #给数据集添加列名,方便后面的操作
    attributes = iris_data[['sl', 'sw', 'pl', 'pw']]   #分离出花的属性
    iris_data['type'] = iris_data['type'].apply(lambda x: x.split('-')[1])  # 最后类别一列,感觉前面的'Iris-'有点多余即把class这一列的数据按'-'进行切分取切分后的第二个数据
    labels = iris_data['type']            #分理出花的类别
    return attributes, labels

def showdatas(attributes, datinglabels):
    """
    函数说明:画出花的属性两两之间的关系图
    :parameter:
            attributes - 花的属性
            datinglabels - 花的类别
    :return:none
    """
    fig, axs = plt.subplots(nrows=3, ncols=2, figsize=(20, 8))   #定义一个32列的画布
    LabelsColors = []    #建立一个颜色标签列表
    for i in datinglabels:   #遍历花的类型
        if i == 'setosa':    #setosa类型的花画成黑色的点
            LabelsColors.append('black')
        if i == 'versicolor':   #versicolor类型的花画成橙色的点
            LabelsColors.append('orange')
        if i == 'virginica':    #virginica类型的花画成红色的点
            LabelsColors.append('red')

    #在画板第一行第一列的位置绘制花萼长度和花萼宽度之间的关系
    axs[0][0].scatter(x=attributes['sl'], y=attributes['sw'], color=LabelsColors, s=15, alpha=.5) #x轴为花萼长度,y轴为花萼宽度, 点大小为15, 透明度为0.5
    axs0_title_text = axs[0][0].set_title(u'花萼长度和花萼宽度')     #设置title
    axs0_xlabel_text = axs[0][0].set_xlabel(u'花萼长度')            #设置x轴的标签
    axs0_ylabel_text = axs[0][0].set_ylabel(u'花萼宽度')            #设置y轴的标签
    plt.setp(axs0_title_text, size=9, weight='bold', color='red')
    plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black')

    #在画板第一行第二列的位置绘制花萼长度和花瓣长度之间的关系
    axs[0][1].scatter(x=attributes['sl'], y=attributes['pl'], color=LabelsColors, s=15, alpha=.5) #x轴为花萼长度,y轴为花瓣长度,点的大小为15, 透明度为0.5
    axs1_title_text = axs[0][1].set_title(u'花萼长度和花瓣长度')    #设立title
    axs1_xlabel_text = axs[0][1].set_xlabel(u'花萼长度')           #设置x轴标签
    axs1_ylabel_text = axs[0][1].set_ylabel(u'花瓣长度')           #设置y轴标签
    plt.setp(axs1_title_text, size=9, weight='bold', color='red')
    plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black')

    #在画板第二行第一列的位置绘制花萼长度与花瓣宽度之间的关系
    axs[1][0].scatter(x=attributes['sl'], y=attributes['pw'], color=LabelsColors, s=15, alpha=.5) #x轴为花萼长度,y轴为花瓣长度,点的大小为15, 透明度为0.5
    axs2_title_text = axs[1][0].set_title(u'花萼长度和花瓣宽度')    #设立title
    axs2_xlabel_text = axs[1][0].set_xlabel(u'花萼长度')           #设立x轴标签
    axs2_ylabel_text = axs[1][0].set_ylabel(u'花瓣宽度')          #设立y轴标签
    plt.setp(axs2_title_text, size=9, weight='bold', color='red')
    plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black')

    #在画板第二行第二列的位置上绘制花萼宽度与花瓣长度之间的关系
    axs[1][1].scatter(x=attributes['sw'], y=attributes['pl'], color=LabelsColors, s=15, alpha=.5)  #x轴为花萼宽度,y轴为花瓣长度
    axs3_title_text = axs[1][1].set_title(u'花萼宽度和花瓣长度')    #设立title
    axs3_xlabel_text = axs[1][1].set_xlabel(u'花萼宽度')          #设立x轴标签
    axs3_ylabel_text = axs[1][1].set_ylabel(u'花瓣长度')          #设立y轴标签
    plt.setp(axs3_title_text, size=9, weight='bold', color='red')
    plt.setp(axs3_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs3_ylabel_text, size=7, weight='bold', color='black')

    #在画板第三行第一列的位置绘制花萼宽度与花瓣宽度之间的关系
    axs[2][0].scatter(x=attributes['sw'], y=attributes['pw'], color=LabelsColors, s=15, alpha=.5)  #x轴为花萼宽度,y轴为花瓣宽度
    axs4_title_text = axs[2][0].set_title(u'花萼宽度和花瓣宽度')   #设立title
    axs4_xlabel_text = axs[2][0].set_xlabel(u'花萼宽度')          #设立x轴坐标
    axs4_ylabel_text = axs[2][0].set_ylabel(u'花瓣宽度')          #设立y轴坐标
    plt.setp(axs4_title_text, size=9, weight='bold', color='red')
    plt.setp(axs4_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs4_ylabel_text, size=7, weight='bold', color='black')

    #在画板第三行第二列的位置绘制花瓣长度和花瓣宽度之间的关系
    axs[2][1].scatter(x=attributes['pl'], y=attributes['pw'], color=LabelsColors, s=15, alpha=.5)  #x轴花瓣长度,y轴为花瓣宽度
    axs5_title_text = axs[2][1].set_title(u'花瓣长度和花瓣宽度')
    axs5_xlabel_text = axs[2][1].set_xlabel(u'花瓣长度')
    axs5_ylabel_text = axs[2][1].set_ylabel(u'花瓣宽度')
    plt.setp(axs5_title_text, size=9, weight='bold', color='red')
    plt.setp(axs5_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs5_ylabel_text, size=7, weight='bold', color='black')

    #设置图例
    setosa = mlines.Line2D([], [], color='black', marker='.', markersize=6, label='setosa')   #设置setosa的图例为黑色的点,大小为6
    versicolor = mlines.Line2D([], [], color='orange', marker='.', markersize=6, label='versicolor')   #设置yersicolor的图例为橙色的点,大小为6
    virginica = mlines.Line2D([], [], color='red', marker='.', markersize=6, label='virginica')  #设置virginica的图例为红色的点,大小为6

    axs[0][0].legend(handles=[setosa,versicolor,virginica])   #对每一个图形设置图例
    axs[0][1].legend(handles=[setosa,versicolor,virginica])
    axs[1][0].legend(handles=[setosa,versicolor,virginica])
    axs[1][1].legend(handles=[setosa, versicolor, virginica])
    axs[2][0].legend(handles=[setosa, versicolor, virginica])
    axs[2][1].legend(handles=[setosa, versicolor, virginica])
    #绘制图形
    plt.show()

def autoNorm(attributes):
    """
    函数说明: 对数据进行归一化
    :parameter
            attributes - 特征矩阵
    :return:  nonormAttributes - 归一化后的矩阵
    """
    attributes = attributes.values  #将DataFrame类型转变为array类型
    minVal = attributes.min()      #找出数据中的最小值
    maxVal = attributes.max()      #找出数据中的最大值
    range = maxVal - minVal        #数据范围
    normAttributes = np.zeros(np.shape(attributes))  #初始化归一化数据
    m = attributes.shape[0]     #获取数据的行数
    normAttributes = attributes - np.tile(minVal, (m, 1))  #创建一个全是最小值得数组
    normAttributes = normAttributes / np.tile(range, (m, 1))  #创建一个全是范围值得数组
    return normAttributes   #返回归一化后的数据

def colcPer(attributes):
    """
    函数说明:计算每个特征之间的Person相关系数
    :parameter:
        attributes - 特征值
    :return: none
    """
    attributes = attributes.astype('float32')  #将attributes数据类型更改成float32
    colcPerAttributes = np.corrcoef(attributes, rowvar=0)   #以每一列计算相关系数
    colcPerAttributes = pd.DataFrame(data=colcPerAttributes, columns=['sl', 'sw', 'pl', 'pw'], index=['sl', 'sw', 'pl', 'pw'])  # 给数据集添加列名,方便观察
    print(colcPerAttributes)   #输出相关系数矩阵

if __name__ == '__main__':
    df = "iris.data"      #文件路径
    attributes, labels = loadDataSet(df)   #得到特征矩阵和标签变量
    showdatas(attributes, labels)      #输出相关的散点图
    normAttributes = autoNorm(attributes)  #进行归一化处理
    colcPer(normAttributes)    #计算相关系数

看看结果图:
在这里插入图片描述
这样看可能不太清楚,如果有信心的同学可以尝试一幅幅地画出来。由图可以看出,黑黄红代表着三种不同的品种,可以看出来,三种颜色的黑点都聚在了一起。说明,不同的花的品种两辆属性之间有着较大的差别,可以很好地使用分类算法。

使用KNN算法

代码如下:

# -*- coding:utf-8 -*-
import warnings
import operator
import numpy as np
import pandas as pd
from time import time
import matplotlib.pyplot as plt

warnings.filterwarnings('ignore')   #忽略警告
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

def loadDataSet(filename):
    """
    函数说明:从文件中下载数据,并将分离除连续型变量和标签变量
    :parameter:
            data - Iris数据集
            attributes - 鸢尾花的属性
            type - 鸢尾花的类别
            sl-花萼长度 , sw-花萼宽度, pl-花瓣长度, pw-花瓣宽度
    :return:
    """
    iris_data = pd.read_csv(filename)   #打开文件
    iris_data = pd.DataFrame(data=np.array(iris_data), columns=['sl', 'sw', 'pl', 'pw', 'type'], index=range(149))   #给数据集添加列名,方便后面的操作
    attributes = iris_data[['sl', 'sw', 'pl', 'pw']]   #分离出花的属性
    iris_data['type'] = iris_data['type'].apply(lambda x: x.split('-')[1])  # 最后类别一列,感觉前面的'Iris-'有点多余即把class这一列的数据按'-'进行切分取切分后的第二个数据
    labels = iris_data['type']     #分理出花的类别
    attriLabels = []      #建立一个标签列表
    for label in labels:        #为了更方便操作,将三中不同的类型分别设为123
        if label == 'setosa':    #如果类别为setosa的话,设为1
            attriLabels.append(1)
        elif label == 'versicolor':  #如果是versicolor的时候设为2
            attriLabels.append(2)
        elif label == 'virginica':  #如果是virginica的时候设为3
            attriLabels.append(3)
    return attributes, attriLabels

def autoNorm(attributes):
    """
    函数说明: 对数据进行归一化
    :parameter
            attributes - 特征矩阵
    :return:  nonormAttributes - 归一化后的矩阵
    """
    attributes = attributes.values  #将DataFrame类型转变为array类型
    minVal = attributes.min()      #找出数据中的最小值
    maxVal = attributes.max()      #找出数据中的最大值
    ranges = maxVal - minVal        #数据范围
    normAttributes = np.zeros(np.shape(attributes))  #初始化归一化数据
    m = attributes.shape[0]     #获取数据的行数
    normAttributes = attributes - np.tile(minVal, (m, 1))  #创建一个全是最小值得数组
    normAttributes = normAttributes / np.tile(ranges, (m, 1))  #创建一个全是范围值得数组
    return normAttributes, ranges, minVal   #返回归一化后的数据

def classify0(inX, dataSet, labels, k):
    """
    函数说明:kNN算法分类器
    :param inX: 用于分类的数据(测试集)
    :param dataSet: 用于训练的数据(训练集)
    :param labels:   花的种类(分类标签)
    :param k:   选取距离最小的k个点
    :return: sortedClassCount[0][0] - 分类结果
    """
    #返回数据的行数
    dataSetSize = dataSet.shape[0]
    #将输入的测试集在行向方向复制一次,列向方向复制dataSetSize次,再相减
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    #将相减后的数据平方
    sqDiffMat = diffMat**2
    #将平方后的数据横向相加
    sqDistances = sqDiffMat.sum(axis=1)
    #开平方,得到测试集到每个训练集的距离
    distances = sqDistances**0.5
    #返回distances中元素从小到大排序后的索引值
    sortedDistIndices = distances.argsort()
    #计数类别次数的字典
    classCount = {}
    for i in range(k): #遍历循环取出k个训练集
        #取出前k个标签
        voteIlabel = labels[sortedDistIndices[i]]
        #计算类别次数
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    #将值按字典的值进行降序排序
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    #返回次数最多的类别
    return sortedClassCount[0][0]

def classifyPerson():
    """
    函数说明:通过输入花的四个特征属性,进行分类输出
    :return:
    """
    #输出结果列表
    resultList = ['setosa', 'versicolor', 'virginica']
    #输入四个特征
    sl = float(input("请输入花萼长度:"))
    sw = float(input("请输入花萼宽度:"))
    pl = float(input("请输入花瓣长度:"))
    pw = float(input("请输入花瓣宽度:"))
    filename = "iris.data"
    #打开文件,得到特征数组和标签数组
    attributes, labels = loadDataSet(filename)
    #对特征变量进行归一化处理,得到归一化后的数据,和数据范围和最小值
    norAttributes, range, minVal = autoNorm(attributes)
    #生成训练集
    inArr = np.array([sl, sw, pl, pw])
    #测试集归一化
    norminArr = (inArr - minVal) / range
    #得到分类结果
    classifierReslut = classify0(norminArr, norAttributes, labels, 7)
    print("该花的类型可能为%s" % (resultList[classifierReslut-1]))

def datingClassTest():
    """
    函数说明:分类器测试器
    :return: none
    """
    #打开文件名
    # filename = "iris.data"
    filename = "bezdekIris.data"
    #打开文件,得到特征数组,和标签数组
    attributes, labels = loadDataSet(filename)
    #输出每个类别的个数
    # for i in set(labels):
    #     print(i, "的个数为", labels.count(i))
    #取数据的程度
    hoRatio = 0.3
    #数据归一化,得到归一化后的矩阵,数据范围,数据最小值
    norAttributes, ranges, minVal = autoNorm(attributes)
    #由于attributes里面的数据集中类别排序太正规了,对测试算法具有较大的影响,所以随机打乱数据的下标排序
    norAttributes_indexes = np.random.permutation(len(norAttributes))
    #得到归一化后数据的行数
    m = norAttributes.shape[0]
    #取百分之hoRatio的数据
    numTestVecs = int(m * hoRatio)
    #取norAttributes的前百分之三十的下标
    test_indexes = norAttributes_indexes[:numTestVecs]
    #取后百分之70的下标
    train_indexes = norAttributes_indexes[numTestVecs:]
    #得到训练集
    x_train = np.array(norAttributes)[train_indexes]
    #得到训练集的标签
    y_train = np.array(labels)[train_indexes]
    #得到测试集
    x_test = np.array(norAttributes)[test_indexes]
    #得到测试集的标签
    y_test = np.array(labels)[test_indexes]
    #得到最优参数k
    k = adjustK(x_test, x_train, y_test, y_train, numTestVecs)
    #计算错误个数
    errorCount = 0.0
    #遍历numTestVecs次
    for i in range(numTestVecs):
        #前numTestVecs个数据为测试集,后m-numTestVecs为训练集
        classifierResult = classify0(x_test[i,:], x_train, y_train, k)
        #输出预测结果和分类结果
        print("分类结果:%d \t 真实类别:%d" % (classifierResult, y_test[i]))
        #如果不相等,就错误个数加一
        if classifierResult != y_test[i]:
            errorCount += 1.0
    #输出错误率
    print("错误率: %f%%" % (errorCount/float(numTestVecs)*100))

def adjustK(x_test, x_train, y_test, y_train, nunTestVecs):
    """
    函数说明:获取最优参数k
    :param x_test: 测试集的特征值
    :param x_train: 训练集的特征值
    :param y_test: 测试集的标签值
    :param y_train: 训练集的标签值
    :param nunTestVecs: 训练集的个数
    :return: k - 最优参数k
    """
    #定义最优的正确率
    best_correctRate = 0.0
    #最优的k值
    best_k = 0
    #让k在18取值
    for k in range(1,9):
        #正确的个数要放在循环内
        correctCount = 0.0
        #循环多次求最优解
        for i in range(nunTestVecs):
            #得到测试集的预测值
            y_predict = classify0(x_test[i,:], x_train, y_train, k)
            #当预测值和真实值相等的时候
            if y_predict == y_test[i]:
                correctCount += 1.0  #正确个数加一
        #求出正确率
        correctRate = correctCount / float(nunTestVecs)
        print("k=", k, "正确率为:", correctRate)
        #判断当前正确率与前面出现的正确率的大小
        if correctRate > best_correctRate:
            best_correctRate = correctRate  #将最大值赋值给最优值
            best_k = k
    print("正确率最大时k的取值为:", best_k)  #输出最优参数k
    return best_k

if __name__ == "__main__":
    time1 = time()
    # datingClassTest() #测试数据集
    classifyPerson()   # 使用数据集
    time2 = time()
    time = time2 - time1
    print(f"耗时:{time}秒")

在下面,我们使用测试数据集来测试一下算法的错误率:
在这里插入图片描述
会发现算法对于这个数据的错误率保持在4%以下。说明这个算法起到了很好的分类效果,代表我们成功了。
ok,再来,我们对这个算法进行使用,将测试数据集注释掉。我们看一下结果:
在这里插入图片描述
很漂亮,我们可以成功的使用该算法。到这里,我们的实践算成功了。

在上面的算法中,我进行了k值得调整优化,大家可以看一看adjustk()这个函数,这个函数主要得作用是:让k从0到9取多个值,取出正确率最大的k值,并进行调用,这样能使我们的算法性能达到最优,不过有个很明显的缺点:时间复杂度过大。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值