机器学习之KNN算法原理及Python实现方法详解


Posted in Python onJuly 09, 2018

本文实例讲述了机器学习之KNN算法原理及Python实现方法。分享给大家供大家参考,具体如下:

文中代码出自《机器学习实战》CH02,可参考本站:

KNN算法介绍

KNN是一种监督学习算法,通过计算新数据与训练数据特征值之间的距离,然后选取K(K>=1)个距离最近的邻居进行分类判(投票法)或者回归。若K=1,新数据被简单分配给其近邻的类。

KNN算法实现过程

(1)选择一种距离计算方式, 通过数据所有的特征计算新数据与已知类别数据集中的数据点的距离;

(2)按照距离递增次序进行排序,选取与当前距离最小的k个点;

(3)对于离散分类,返回k个点出现频率最多的类别作预测分类;对于回归则返回k个点的加权值作为预测值;

算法关键

(1)数据的所有特征都要做可比较的量化

若是数据特征中存在非数值的类型,必须采取手段将其量化为数值。例如样本特征中包含颜色,可通过将颜色转换为灰度值来实现距离计算。

(2)样本特征要做归一化处理

样本有多个参数,每一个参数都有自己的定义域和取值范围,他们对距离计算的影响不一样,如取值较大的影响力会盖过取值较小的参数。所以样本参数必须做一些scale处理,最简单的方式就是所有特征的数值都采取归一化处置。

(3)需要一个距离函数以计算两个样本之间的距离

距离的定义:欧氏距离、余弦距离、汉明距离、曼哈顿距离等,一般选欧氏距离作为距离度量,但是这是只适用于连续变量。在文本分类这种非连续变量情况下,汉明距离可以用来作为度量。通常情况下,如果运用一些特殊的算法来计算度量的话,K近邻分类精度可显著提高,如运用大边缘最近邻法或者近邻成分分析法。

(4)确定K的值

K值选的太大易引起欠拟合,太小容易过拟合。交叉验证确定K值。

KNN分类

分类算法常采用多数表决决定。一个缺点是出现频率较多的样本将会主导测试点的预测结果。解决这个缺点的方法之一是在进行分类时将K个邻居到测试点的距离考虑进去。若样本到测试点距离d,则选1/d为该邻居的权重,统计k个邻居所有类标签的权重和,值最大的就是新数据点的预测类标签。

KNN回归

KNN回归是取K个邻居类标签值得加权作为新数据点的预测值。

优缺点

(1)KNN算法的优点

  • 1.简单、有效。
  • 2.重新训练的代价较低(类别体系的变化和训练集的变化,在Web环境和电子商务应用中是很常见的)。
  • 3.计算时间和空间线性于训练集的规模(在一些场合不算太大)。
  • 4.由于KNN方法主要靠周围有限的邻近的样本,而不是靠判别类域的方法来确定所属类别的,因此对于类域的交叉或重叠较多的待分样本集来说,KNN方法较其他方法更为适合。
  • 5.该算法比较适用于样本容量比较大的类域的自动分类,而那些样本容量较小的类域采用这种算法比较容易产生误分。

(2)KNN算法缺点

  • 1.KNN算法是懒散学习方法(lazy learning,基本上不学习),一些积极学习的算法要快很多。
  • 2.类别评分不是规格化的(不像概率评分)(???)。
  • 3.输出的可解释性不强,例如决策树的可解释性较强。
  • 4.该算法在分类时有个主要的不足是,当样本不平衡时,如一个类的样本容量很大,而其他类样本容量很小时,有可能导致当输入一个新样本时,该样本的K个邻居中大容量类的样本占多数。该算法只计算最近的邻居样本,某一类的样本数量很大,那么或者这类样本并不接近目标样本,或者这类样本很靠近目标样本。无论怎样,数量并不能影响运行结果。可以采用权值的方法(和该样本距离小的邻居权值大)来改进。
  • 5.计算量较大。目前常用的解决方法是事先对已知样本点进行剪辑,事先去除对分类作用不大的样本。

KNN实现

import numpy as np
import operator
import matplotlib
import matplotlib.pyplot as plt
from os import listdir
def Create_DataSet():
 group = np.array([[1.0, 1.1],[1.0,1.0],[0,0],[0,0.1]])
 labels = ['A','A','B','B']
 return group,labels

函数Create_DataSet创建一个数据集,坐标轴左下角分类为B,右上角分类为A。

下面函数classify0,计算向量inX与数据集中各点的距离,计算n_estimators个近邻中label频率最高的分类号并返回作为向量inX的分类号。

def classify0(inX, dataSet, labels, n_estimators=3):
 dataSetSize = dataSet.shape[0]
 #print 'in classify0,dataSetSize = \n',dataSetSize
 #转变向量inx格式为datasize行,1列;并计算与dataset元素距离
 diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
 #计算欧氏距离((x0-x1)^2 + (y0-y1)^2 )^(1/2)
 sqDiffMat = diffMat**2 #diffMat每个元素取平方
 sqDistances = sqDiffMat.sum(axis=1)
 distances = sqDistances**0.5
 #排序,将值从小到大排列,返回索引
 sortedDistIndicies = distances.argsort()
 #print 'in classify0,sortedDistIndicies:\n',sortedDistIndicies
 #求与距离最近的k个点的label统计
 classCount={}
 for i in range(n_estimators):
  voteIlabel = labels[sortedDistIndicies[i]] #获取对应label号
  classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
 #对字典排序,按value值降序排列
 sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
 #print 'sortedClassCount[0][0]:\n',sortedClassCount[0][0]
 return sortedClassCount[0][0]

dataSet.shape()函数用于获取矩阵dataSet的大小,shape[0]返回对应行数,shape[1]返回对应列数。

因为需要对每列属性做距离运算,所以需要将输入inX转换为和dataSet相同行数和列数的矩阵,因为inX列数与dataSet中每个元素列数相同,所以需要将其行数进行扩展,np.tile(inX, (dataSetSize,1))将inX行数拓展为dataSetSize行,1表示纵向复制1次,即列不变。

距离公式采用欧式距离计算,得到的距离值为一维列表,分别对应dataSet中每个元素和inX的距离。distances.argsort() 将距离按从小到大排列,并返回索引。例如distance = [0.1,0.5,0.3],distance.argsort()返回[1,3,2] 。返回索引是为了找到对应的label值,并进行统计。

下面for循环用于建立字典并统计前n_estimators个label的个数。key对应label,key_value对应个数。

operator.itemgetter函数,operator模块提供的itemgetter函数用于获取对象的哪些维的数据,参数为一些序号,即需要获取的数据在对象中的序号;例如a = [1,2,3] ,定义函数b=operator.itemgetter(1),获取对象的第1个域的值,则 b(a)=2;若定义函数b,获取对象的第1个域和第0个的值b=operator.itemgetter(1,0),则b(a)=(2, 1) 。注意operator.itemgetter函数获取的不是值,而是定义了一个函数,通过该函数作用到对象上才能获取值;

sorted函数:Python内置的排序函数sorted可以对list或者iterator进行排序;第一个参数iterable指定要排序的list或者iterable,第二个参数指定排序时进行比较的函数,可以指定一个函数或者lambda函数。例如students为类对象的list,每个成员有三个域,用sorted进行比较时可以自己定cmp函数,例如这里要通过比较第三个数据成员来排序,students = [(‘john', ‘A', 15), (‘jane', ‘B', 12), (‘dave', ‘B', 10)],sorted(students, key=lambda student : student[2]),key为函数,指定取待排序元素的哪一项进行排序,key指定的lambda函数功能是去元素student的第三个域(student[2]),因此sorted排序时会以students所有元素的第三个域来进行排序;也可以这么写sorted(students, key=operator.itemgetter(2)) ,sorted函数也可以进行多级排序,例如要根据第二个域和第三个域进行排序;sorted(students, key=operator.itemgetter(1,2))即先跟句第二个域排序,再根据第三个域排序;第三个参数reverse是一个bool变量,表示升序还是降序排列,默认为false升序排列,定义为True时将按降序排列。

此处sort函数用于对字典进行排序。按key_value降序排列,即对应label个数从大到小排列。返回值为列表,列表元素为元组,元组第一个元素对应label,第二个元素对应label个数。sortedClassCount[0][0]即返回label次数最多的类标号,为inX的label。

下面测试一个简单的向量:

group,labels = Create_DataSet()
sortedClassCount = classify0([0,0.5],group,labels,3)

输出为

sortedClassCount:[(‘B', 2), (‘A', 1)]
sortedClassCount[0][0]:
B

下面函数file2matrix用于从txt中读取原始数据并转化为矩阵。

test.txt格式为

40920 8.326976 0.953952 largeDoses
14488 7.153469 1.673904 smallDoses
26052 1.441871 0.805124 didntLike
75136 13.147394 0.428964 didntLike
……

最后一列为label,值为largeDoses、smallDoses或didntLike。每行元素用\t隔开。转换后label分别对应3、2、1。

转换函数如下:

def file2matrix(filename):
 fr = open(filename)
 numberOfLines = len(fr.readlines())
 print 'in file2matrix,numberOfLines:\n',numberOfLines
 returnMat = np.zeros((numberOfLines,3))
 classLabelVector = []
 fr = open(filename)
 index = 0
 for line in fr.readlines(): #遍历每一行
  line = line.strip() #strip用于删除字符,此处删除空白字符,回车
  listFromLine = line.split('\t') #获取每行的元素列表,元素用\t分开
  returnMat[index,:] = listFromLine[0:3]#取前3个元素,对应属性集
  if(listFromLine[-1] == 'largeDoses'):#有什么有效的方法 将属性值和类标号分开,相互对应
   classLabelVector.append(3)
  elif(listFromLine[-1] == 'smallDoses'):
   classLabelVector.append(2)
  elif(listFromLine[-1] == 'didntLike'):
   classLabelVector.append(1)
  elif(listFromLine[-1] == 3):
   classLabelVector.append(3)
  elif(listFromLine[-1] == 2):
   classLabelVector.append(2)
  elif(listFromLine[-1] == 1):
   classLabelVector.append(1)
  index += 1
 #print 'returnMat = ',returnMat
 #print 'classLabelVector = ',classLabelVector
 return returnMat,classLabelVector #得到属性集和类标号

首先打开文件并获取行数,建立一个相同大小的空矩阵,用于存储转换后的属性集,并新建一个一维列表,用于存放类标号。fr.readlines()读取所有行,得到一个行列表,列表元素为每行内容;readline只读取1行,获取该行元素的列表。
上述函数即返回属性集矩阵和类标号列表。

因为属性值差距较大,为了减少值太大的属性对值小的属性的影响,分类之前还需要进行归一化。归一化方程为(datain-min_val) / (max_val - min_val),输出值都介于0-1。

def autoNorm(dataSet):
 minVals = dataSet.min(0) #获取每列最大值与最小值,(0)指定列,而不是行
 print 'in autoNorm,minVals:',minVals
 maxVals = dataSet.max(0)
 print 'in autoNorm,maxVals:',maxVals
 ranges = maxVals - minVals
 print 'in autoNorm,ranges:',ranges
 normDataSet = np.zeros(np.shape(dataSet))
 m = dataSet.shape[0] #获取行数
 #特征值矩阵为1000x3,minVals值为1x3,使用tile函数扩展为相同大小的矩阵
 #np.tile(minVals, (m,1))矩阵minval,横向复制m次,纵向复制1次
 normDataSet = dataSet - np.tile(minVals, (m,1)) # (data - minval)/(maxval - minval)
 normDataSet = normDataSet/np.tile(ranges, (m,1)) #element wise divide
 print 'in autoNorm,normDataSet = ',normDataSet
 return normDataSet, ranges, minVals

返回归一化以后的属性集。即可进行距离运算并分类。

下面函数即对文件中所有输入的行向量属性进行分类

def datingClassTest(n_estimators=3):
 hoRatio = 0.50
 #(1)读取文件
 datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
 #(2)归一化
 normMat, ranges, minVals = autoNorm(datingDataMat)
 m = normMat.shape[0]
 numTestVecs = int(m*hoRatio)
 errorCount = 0.0
 for i in range(numTestVecs):
  classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],n_estimators=n_estimators)
  if (classifierResult != datingLabels[i]): errorCount += 1.0
 print "in datingClassTest,the total error rate is: %f" % (errorCount/float(numTestVecs))
 print 'in datingClassTest,errorCount:',errorCount

将测试文件分为数据集和用于测试的向量2部分。前一半用于测试,后一半作为数据集,并定义errorCount用于统计出错个数。经过归一化以后的数据集和验证通过for循环计算分类结果,并与实际结果进行对比,得到总出错数和出错率。

执行该函数,结果显示:

in datingClassTest,the total error rate is: 0.064000
in datingClassTest,errorCount: 32.0

希望本文所述对大家Python程序设计有所帮助。

Python 相关文章推荐
Python中实现参数类型检查的简单方法
Apr 21 Python
python读写二进制文件的方法
May 09 Python
python开发环境PyScripter中文乱码问题解决方案
Sep 11 Python
Python读写zip压缩文件的方法
Aug 29 Python
Python3.4学习笔记之常用操作符,条件分支和循环用法示例
Mar 01 Python
django rest framework 实现用户登录认证详解
Jul 29 Python
python实现tail -f 功能
Jan 17 Python
python opencv 实现对图像边缘扩充
Jan 19 Python
Python单例模式的四种创建方式实例解析
Mar 04 Python
django rest framework serializers序列化实例
May 13 Python
秀!学妹看见都惊呆的Python小招数!【详细语言特性使用技巧】
Apr 27 Python
Python使用pandas导入csv文件内容的示例代码
Dec 24 Python
Python实现基于KNN算法的笔迹识别功能详解
Jul 09 #Python
Python 16进制与中文相互转换的实现方法
Jul 09 #Python
python 文件转成16进制数组的实例
Jul 09 #Python
使用Python读取二进制文件的实例讲解
Jul 09 #Python
Python实现随机漫步功能
Jul 09 #Python
Python2包含中文报错的解决方法
Jul 09 #Python
对numpy数据写入文件的方法讲解
Jul 09 #Python
You might like
php pcntl_fork和pcntl_fork 的用法
2009/04/13 PHP
php 生成随机验证码图片代码
2010/02/08 PHP
关于PHP堆栈与列队的学习
2013/06/21 PHP
PHP 面向对象程序设计(oop)学习笔记(一) - 抽象类、对象接口、instanceof 和契约式编程
2014/06/12 PHP
CI使用Tank Auth转移数据库导致密码用户错误的解决办法
2014/06/12 PHP
Yii2中使用asset压缩js,css文件的方法
2016/11/24 PHP
PHP抽象类基本用法示例
2018/12/28 PHP
php封装的pdo数据库操作工具类与用法示例
2019/05/08 PHP
JS下高效拼装字符串的几种方法比较与测试代码
2010/04/15 Javascript
JQuery选中checkbox方法代码实例(全选、反选、全不选)
2015/04/27 Javascript
详谈JS中实现种子随机数及作用
2016/07/19 Javascript
使用ionic切换页面卡顿的解决方法
2016/12/16 Javascript
100多个基础常用JS函数和语法集合大全
2017/02/16 Javascript
nodejs入门教程一:概念与用法简介
2017/04/24 NodeJs
javascript 数据存储的常用函数总结
2017/06/01 Javascript
JavaScript中附件预览功能实现详解(推荐)
2017/08/15 Javascript
js 两个日期比较相差多少天的实例
2017/10/19 Javascript
javascript Function函数理解与实战
2017/12/01 Javascript
js自定义trim函数实现删除两端空格功能
2018/02/09 Javascript
jQuery实现菜单的显示和隐藏功能示例
2018/07/24 jQuery
基于vue-cli 路由 实现类似tab切换效果(vue 2.0)
2019/05/08 Javascript
vue使用混入定义全局变量、函数、筛选器的实例代码
2019/07/29 Javascript
JavaScript中0、空字符串、'0'是true还是false的知识点分享
2019/09/16 Javascript
解决vuex数据异步造成初始化的时候没值报错问题
2019/11/13 Javascript
浅谈实现在线预览PDF的几种解决办法
2020/08/10 Javascript
Python中的defaultdict模块和namedtuple模块的简单入门指南
2015/04/01 Python
Python中的hypot()方法使用简介
2015/05/18 Python
Python实现将DOC文档转换为PDF的方法
2015/07/25 Python
总结Python编程中函数的使用要点
2016/03/20 Python
python魔法方法-自定义序列详解
2016/07/21 Python
css3实现冲击波效果的示例代码
2018/01/11 HTML / CSS
前台接待岗位职责
2013/12/03 职场文书
工程部岗位职责范本
2015/04/11 职场文书
签订劳动合同通知书
2015/04/16 职场文书
2016年入党心得体会范文
2016/01/23 职场文书
销售会议开幕词
2016/03/04 职场文书