机器学习之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爬虫常用的模块分析
Aug 29 Python
Django admin实现图书管理系统菜鸟级教程完整实例
Dec 12 Python
Python实现将照片变成卡通图片的方法【基于opencv】
Jan 17 Python
Python实现合并同一个文件夹下所有PDF文件的方法示例
Apr 28 Python
Python for循环生成列表的实例
Jun 15 Python
python程序控制NAO机器人行走
Apr 29 Python
详解Python中的测试工具
Jun 09 Python
Python异常继承关系和自定义异常实现代码实例
Feb 20 Python
Python数据结构dict常用操作代码实例
Mar 12 Python
关于python tushare Tkinter构建的简单股票可视化查询系统(Beta v0.13)
Oct 19 Python
用python读取xlsx文件
Dec 17 Python
浅谈Python numpy创建空数组的问题
May 25 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 array_push 数组函数
2009/12/26 PHP
php 获取SWF动画截图示例代码
2014/02/10 PHP
php中执行系统命令的方法
2015/03/21 PHP
php如何实现只替换一次或N次
2015/10/29 PHP
支持汉转拼和拼音分词的PHP中文工具类ChineseUtil
2018/02/23 PHP
让回调函数 showResponse 也带上参数的代码
2007/08/13 Javascript
js判断变量是否空值的代码
2008/10/26 Javascript
Javascript 调试利器 Firebug使用详解六
2009/07/05 Javascript
struts2+jquery+json实现异步加载数据(自写)
2013/06/24 Javascript
jQuery中复合属性选择器用法实例
2014/12/31 Javascript
45个JavaScript编程注意事项、技巧大全
2015/02/11 Javascript
jquery ztree异步搜索(搜叶子)实践
2016/02/25 Javascript
BootStrap中
2016/12/10 Javascript
JS前端加密算法示例
2016/12/22 Javascript
微信小程序 省市区选择器实例详解(附源码下载)
2017/01/05 Javascript
详解vuelidate 对于vueJs2.0的验证解决方案
2017/03/09 Javascript
JS实现键值对遍历json数组功能示例
2018/05/30 Javascript
vue2.0中set添加属性后视图不能更新的解决办法
2019/02/22 Javascript
js中的深浅拷贝问题简析
2019/05/10 Javascript
JavaScript遍历数组和对象的元素简单操作示例
2019/07/09 Javascript
python 合并文件的具体实例
2013/08/08 Python
浅谈Python中函数的参数传递
2016/06/21 Python
python装饰器初探(推荐)
2016/07/21 Python
python的unittest测试类代码实例
2017/12/07 Python
python 3.6 +pyMysql 操作mysql数据库(实例讲解)
2017/12/20 Python
pandas 条件搜索返回列表的方法
2018/10/30 Python
基于SpringBoot构造器注入循环依赖及解决方式
2020/04/26 Python
Python3中对json格式数据的分析处理
2021/01/28 Python
HTML5+CSS3实现无插件拖拽上传图片(支持预览与批量)
2017/01/05 HTML / CSS
安全教育心得体会
2013/12/29 职场文书
行政专员岗位职责说明书
2014/07/30 职场文书
查摆问题自我剖析材料
2014/08/18 职场文书
企业三严三实学习心得体会
2014/10/13 职场文书
导游词之南京夫子庙
2019/12/09 职场文书
MySQL系列之三 基础篇
2021/07/02 MySQL
为什么MySQL不建议使用SELECT *
2022/04/03 MySQL