机器学习——KNN算法实例

目录

1.项目背景

2.流程步骤 

3.代码部分

3.1导入可能需要用的包 

3.2准备数据:从文本文件中解析数据

 3.3分析数据:用Matplotlib创建散点图

 3.4准备数据:数据归一化

3.5 测试算法:作为完整程序验证分类器

 【关于K值的选择】

3.6使用算法:构建完整可用系统

4.总结 

 


关于KNN算法的简单理解在我的上一篇博客机器学习——K-近邻算法_装进了牛奶箱中的博客-CSDN博客

1.项目背景

近期集美大学在进行贫困生评定工作,先根据学生的家庭人均年收入,使用手机价格以及每月外出吃饭次数简单判断该学生是否贫困。根据以上三个特征,借助KNN分类,将学生家庭情况分为贫困,普通,富裕三类。

2.流程步骤 

(1)收集数据:使用Excel表格列出数据,并将其另存为.txt文件。

(2)准备数据:使用python解析文本文件。

(3)分析数据:使用Matplotlib画二维扩散图。

(4)训练算法:此步骤不适用于k近邻算法。

(5)测试算法:选定文本文件中的部分数据作为测试样本。

测试样本与非测试样本的区别:测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误。

(6) 使用算法:产生简单的命令行程序,输入某位学生的家庭人均年收入,使用手机价格以及每月外出吃饭次数,然后系统可以判断该学生的家庭情况。

3.代码部分

3.1导入可能需要用的包 

import cv2
import pandas as pd
import numpy as np
from numpy import *
from matplotlib.font_manager import FontProperties
import matplotlib
import matplotlib.lines as mlines
import matplotlib.pyplot as plt
%matplotlib inline
from scipy.interpolate import interp1d
import operator
import matplotlib as mpl

 有些包可能用不到,我在解决matplotlib不能显示中文的问题时,尝试多种解决办法导入了许多可能用不到的包

3.2准备数据:从文本文件中解析数据

学生个人情况数据集存放在KNNdata1.txt中,每个样本数据占据一行,总共有1101行。样本主要包含以下三个特征:

  1. 家庭人均年收入
  2. 使用手机价格
  3. 每月外出吃饭次数 

创建fileMatrix函数,将输入为文件名字符串,输出为训练样本矩阵和类标签向量

def fileMatrix(filename):
    file = open(filename) #打开文件
    arrayOLines = file.readlines() #读取文件所有内容
    numberOfLines = len(arrayOLines) #得到文件行数
    returnMat = zeros((numberOfLines, 3)) #返回给定形状和类型的新数组,用0填充
    classLabelVector = []#返回的分类标签向量
    index = 0 #行的索引值
    for line in arrayOLines:
        line = line.strip()#用于移除字符串头尾指定的字符,默认删除空白符(包括'\n','\t','\r',' ')
        listFromLine = line.split('\t')#通过指定分隔符对字符串进行切片,返回分割后的字符串列表
        returnMat[index,:] = listFromLine[0:3]#将数据前三列提取出来,存放到returnMat的numpy矩阵中
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    return returnMat,classLabelVector

运行结果 

 

 

 3.3分析数据:用Matplotlib创建散点图

def showdatas(datingDataMat, datingLabels):
    #设置汉字格式
    # sans-serif就是无衬线字体,是一种通用字体族。
    mpl.rcParams['font.sans-serif'] = ['Songti SC']  # 用来正常显示中文标签
    mpl.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
    #将fig画布分隔成2行2列,不共享x轴和y轴,fig画布的大小为(13,8)
    #当nrow=2,nclos=2时,代表fig画布被分为四个区域,axs[0][0]表示第一行第一个区域
    fig, axs = plt.subplots(nrows=2, ncols=2,sharex=False, sharey=False, figsize=(13,9))
 
    LabelsColors = []
    for i in datingLabels:
        if i == 0:
            LabelsColors.append('green')
        if i == 1:
            LabelsColors.append('red')
        if i == 2:
            LabelsColors.append('blue')
 
    #画出散点图,以datingDataMat矩阵的第一(家庭人均年收入)、第二列(使用手机价格)数据画散点数据,散点大小为15,透明度为0.5
    axs[0][0].scatter(x=datingDataMat[:,0], y=datingDataMat[:,1], color=LabelsColors,s=15, alpha=.5)
    #设置标题,x轴label,y轴label
    axs0_title_text = axs[0][0].set_title('家庭人均年收入与使用手机价格')
    axs0_xlabel_text = axs[0][0].set_xlabel('家庭人均年收入')
    axs0_ylabel_text = axs[0][0].set_ylabel('使用手机价格')
    plt.setp(axs0_title_text, size=12, weight='bold', color='red')
    plt.setp(axs0_xlabel_text, size=10, weight='bold', color='black')
    plt.setp(axs0_ylabel_text, size=10, weight='bold', color='black')
 
    #画出散点图,以datingDataMat矩阵的第一(家庭人均年收入)、第三列(每月外出吃饭次数)数据画散点数据,散点大小为15,透明度为0.5
    axs[0][1].scatter(x=datingDataMat[:,0], y=datingDataMat[:,2], color=LabelsColors,s=15, alpha=.5)
    #设置标题,x轴label,y轴label
    axs1_title_text = axs[0][1].set_title('家庭人均年收入与每月外出吃饭次数',)
    axs1_xlabel_text = axs[0][1].set_xlabel('家庭人均年收入')
    axs1_ylabel_text = axs[0][1].set_ylabel('每月外出吃饭次数')
    plt.setp(axs1_title_text, size=12, weight='bold', color='red')
    plt.setp(axs1_xlabel_text, size=10, weight='bold', color='black')
    plt.setp(axs1_ylabel_text, size=10, weight='bold', color='black')
 
    #画出散点图,以datingDataMat矩阵的第二(使用手机价格)、第三列(每月外出吃饭次数)数据画散点数据,散点大小为15,透明度为0.5
    axs[1][0].scatter(x=datingDataMat[:,1], y=datingDataMat[:,2], color=LabelsColors,s=15, alpha=.5)
    #设置标题,x轴label,y轴label
    axs2_title_text = axs[1][0].set_title('使用手机价格与每月外出吃饭次数')
    axs2_xlabel_text = axs[1][0].set_xlabel('使用手机价格')
    axs2_ylabel_text = axs[1][0].set_ylabel('每月外出吃饭次数')
    plt.setp(axs2_title_text, size=12, weight='bold', color='red')
    plt.setp(axs2_xlabel_text, size=10, weight='bold', color='black')
    plt.setp(axs2_ylabel_text, size=10, weight='bold', color='black')
 
    #设置图例
    impoverished = mlines.Line2D([], [], color='green', marker='.', markersize=6, label='贫困')
    ordinary = mlines.Line2D([], [], color='red', marker='.',markersize=6, label='普通')
    affluent = mlines.Line2D([], [], color='blue', marker='.',markersize=6, label='富裕')
    #添加图例
    axs[0][0].legend(handles=[impoverished,ordinary,affluent])
    axs[0][1].legend(handles=[impoverished,ordinary,affluent])
    axs[1][0].legend(handles=[impoverished,ordinary,affluent])
    #显示图片
    plt.show()

datingDataMat, datingLabels = fileMatrix('KNNdata1.txt')
showdatas(datingDataMat,datingLabels)
 

 遇到的问题matplotlib不能显示中文

解决办法macos或windows中 matplotlib中文显示(matplotlib字体常见使用)_吨吨不打野的博客-CSDN博客_mac matplotlib 显示中文

不同系统的字体可能不一样,windows大多用simhei,macOS用Songti SC 或STFangsong,一定一定要根据自己的系统查找解决方法 

import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

plt.rcParams['font.sans-serif']=['Songti SC'] #用来正常显示中文标签
# 或者是下面这个,宋体和仿宋字体,都可以用。
plt.rcParams['font.sans-serif']=['STFangsong'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

运行结果 

 3.4准备数据:数据归一化

在处理不同取值范围的特征值时,我们通常采用的方法是将数值归一化,创建autoNorm函数,自动的将数字特征值转化为0到1的区间

使用 newValue = (oldValue - min) / (max - min) 将任意取值范围的特征值转化为0到1区间内的值

def autoNorm(dataSet):
    #获得每列数据的最小值和最大值
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals #最大值和最小值范围
    normDataSet = zeros(shape(dataSet)) 
    m = dataSet.shape[0] #返回dataset的行数
    normDataSet = dataSet - tile(minVals, (m, 1))  #原始值减去最小值
    normDataSet = normDataSet/tile(ranges, (m, 1)) #除以最大和最小值的差,得到归一化数据
    return normDataSet, ranges, minVals #返回归一化数据结果,数据范围,最小值

datingDatMat, datingLabels = fileMatrix('KNNdata1.txt')
normData, ranges, minVals = autoNorm(datingDatMat)
print('normData')
print(normData)
print('ranges')
print(ranges)
print('minVals')
print(minVals)

运行结果

3.5 测试算法:作为完整程序验证分类器

提供已有数据的80%作为训练样本来训练分类器,而其余的20%数据去测试分类器,检测分类算法的正确率。

def datingClassTest():
    hoRatio = 0.2 #20%的测试数据
    datingDatMat, datingLabels = fileMatrix('KNNdata1.txt')  #从文件读数据
    normMat, ranges, minVals = autoNorm(datingDatMat)              #数据的归一化
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio)                                   #测试数据数量
    errorCount = 0.0                                               #错误数量统计
    for i in range(numTestVecs):
        classifierResult = classify(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 1)
        print("分类器返回的结果是:%d,真实结果是:%d"%(classifierResult, datingLabels[i]))
        if(classifierResult != datingLabels[i]):
            errorCount += 1.0
    print('分类器处理约会数据集的错误率是:%f'%(errorCount/float(numTestVecs)))

 这里还调用了分类器函数classify

#KNN算法分类器

#inX 用于分类的数据(测试集)

#dataSet 用于训练的数据(训练集)

#labes 训练数据的分类标签

#k KNN算法的参数,选择距离最小的k个点

#sortedClassCount[0][0] 分类结果

def classify(inX, dataSet, labels, k):
    #dataSetSize是训练样本集数量
    dataSetSize = dataSet.shape[0]
 
    #距离计算——欧式距离公式
    #tile函数,把inX变成能与dataSet相减的二维数组
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat ** 2
    #axis=1是列相加求和,即得到(x1-x2)^2+(y1-y2)^2的值
    sqDistances = sqDiffMat.sum(axis = 1)     
    distances = sqDistances ** 0.5
 
    #按照距离递增次序排序,返回下标
    sortedDistIndicies = distances.argsort()
 
    #选择距离最小的k个点
    classCount = {}
    for i in range(k):
        voteILabel = labels[sortedDistIndicies[i]]
        classCount[voteILabel] = classCount.get(voteILabel,0) + 1
 
    #按照字典里的关键字的值排序,reverse=True降序排序
    sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)
    
    #返回类别最多的标签
    return sortedClassCount[0][0]

 运行结果

当k=1,hoRatio=0.2时,分类器处理数据集的错误率是2.3%

这是一个还算不错的结果,但是我们可以通过改变数据集和训练集的比例以及k的值,来检测错误率随着变量值的变化而变大或变小

 当k=1,hoRatio=0.1时,分类器处理数据集的错误率是0.9%

 当k=1,hoRatio=0.3时,分类器处理数据集的错误率是2.4% 

  当k=3,hoRatio=0.2时,分类器处理数据集的错误率接近0

 

 当k=7,hoRatio=0.2时,分类器处理数据集的错误率是1.4% 

 

 

 【关于K值的选择】

 李航博士《统计学说明方法》

1) 选择较小的K值,就相当于用较小的领域中的训练实例进行预测,“学习”近似误差会减小,只有与输入实例较近或相似的训练实例才会对预测结果起作用,与此同时带来的问题是“学习”的估计误差会增大,换句话说,K值的减小就意味着整体模型变得复杂,容易发生过拟合;

2) 选择较大的K值,就相当于用较大领域中的训练实例进行预测,其优点是可以减少学习的估计误差,但缺点是学习的近似误差会增大。这时候,与输入实例较远(不相似的)训练实例也会对预测器作用,使预测发生错误,且K值的增大就意味着整体的模型变得简单。

3) K=N(N为训练样本个数),则完全不足取,因为此时无论输入实例是什么,都只是简单的预测它属于在训练实例中最多的类,模型过于简单,忽略了训练实例中大量有用信息。

在实际应用中,K值一般取一个比较小的数值,例如采用交叉验证法(简单来说,就是把训练数据在分成两组:训练集和验证集)来选择最优的K值。对这个简单的分类器进行泛化,用核方法把这个线性模型扩展到非线性的情况,具体方法是把低维数据集映射到高维特征空间。

看了很多博客k值的选择他们大多用的是交叉验证,不过我没有很看懂,不太会用,所以在k值的选择问题上,我是用最简单的办法:一个一个数字试,发现在k=3时错误率最小

K值过小,容易受到异常点的影响,出现过拟合的问题;K值过大,容易受到样本均衡的影响,出现欠拟合的问题。

K值一般选择奇数3,5,7

因为我的数据是自己编造的,异常数据较少,所以实际上错误率随着k值的改变变化的较少,这个例子不能很好的说明k值的选择问题,只能体会精神

3.6使用算法:构建完整可用系统

使用分类器评定学生家庭情况,通过输入学生个人情况,程序会给出该学生的家庭情况

def classifyPerson():
    resultList = ['贫困', '普通', '富裕']
    precentTats = float(input('家庭人均年收入:'))           #用户输入三个特征
    ffMiles = float(input('使用手机价格:')) 
    iceCream = float(input('每月外出吃饭次数:'))
    datingDatMat, datingLabels = fileMatrix('KNNdata.txt')    #文件数据读入
    normMat, ranges, minVals = autoNorm(datingDatMat)               
    inArr = array([precentTats, ffMiles, iceCream])                  #生成测试集                
    norminArr = (inArr-minVals)/ranges                               #数据归一化
    classifierResult = classify(norminArr, normMat, datingLabels, 3) #分类器分类
    print('这位学生的家庭情况可能是%s'%(resultList[classifierResult]))

 运行结果

 

 

 

4.总结 

通过改变函数datingClassTest内变量hoRatio和变量k的值,发现hoRatio的值越小,错误率越小;在hoRatio值不变的情况下,随着k值的变化,错误率会先变小后变大,而使得错误率最小的那个k值就是最优值。 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值