目录
1.项目背景
2.流程步骤
3.代码部分
3.1导入可能需要用的包
3.2准备数据:从文本文件中解析数据
3.3分析数据:用Matplotlib创建散点图
3.4准备数据:数据归一化
3.5 测试算法:作为完整程序验证分类器
【关于K值的选择】
3.6使用算法:构建完整可用系统
4.总结
文章来源地址https://www.toymoban.com/news/detail-500313.html
关于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行。样本主要包含以下三个特征:
- 家庭人均年收入
- 使用手机价格
- 每月外出吃饭次数
创建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值就是最优值。 文章来源:https://www.toymoban.com/news/detail-500313.html
到了这里,关于机器学习——KNN算法实例的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!