摘要
本文主要介绍k近邻,以及k近邻在数据集上的应用。
目录
一、k-近邻算法原理
1.1 k-近邻算法介绍
K近邻分类算法(k-Nearest Neighbor,KNN),是一个理论上比较成熟的方法,也是最简单的机器学习算法之一。
K近邻算法,即是给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的K个实例(也就是上面所说的K个邻居), 这K个实例的多数属于某个类,就把该输入实例分类到这个类中。
1.2 计算步骤
(1)计算测试对象与训练集中所有对象的距离,可以是欧式距离、余弦距离等,比较常用的是较为简单的欧式距离;
(2)找出上步计算的距离中最近的K个对象,作为测试对象的邻居;
(3)找出K个对象中出现频率最高的对象,其所属的类别就是该测试对象所属的类别。
1.3 数学表达:
输入:输入训练集
其中,
是样本的特征向量,
是样本的类别,
是一个特征空间,包含输入的样本
过程:
(1)根据给定的距离度量,在训练集
中找出与
最邻近的k个点,包含这k个点的x的邻域记作
,这里不要纠结邻域是什么,简单来说,邻域就是x点组成的集合域
(2)在
中根据分类决策规则(如多数表决)决定
的类别
。
输出: 所属的类别
1.4 算法的三要素:
1.4.1 k的选择:
k的取值对结果会有很大的影响。
k值设置过小会降低分类精度;若设置过大,且测试样本属于训练集中包含数据较少的类,则会增加噪声,降低分类效果。
通常,k值的设定采用交叉检验的方式(以kK=1为基准)
经验规则:k一般低于训练样本数的平方根。
1.4.2 距离度量
在KNN算法中,常用的距离有三种,分别为曼哈顿距离、欧式距离和闵可夫斯基距离。
1.4.3 分类决策规则
KNN做分类预测时,一般是选择多数表决法,即训练集里和预测的样本特征最近的K个样本,预测为里面有最多类别数的类别。而KNN做回归时,一般是选择平均法,即最近的K个样本的样本输出的平均值作为回归预测值。由于两者区别不大,虽然本文主要是讲解KNN的分类方法,但思想对KNN的回归方法也适用。由于scikit-learn里只使用了蛮力实现(brute-force),KD树实现(KDTree)和球树(BallTree)实现,本文只讨论这几种算法的实现原理。
二、数据集说明
2.1 数据的获取
自2001 年以来,世界睡眠医学协会每年将3 月21 日定为世界睡眠日,以引起人们对睡眠的重要性以及睡眠质量的关注。一天的精神状态取决于前一晚的睡眠质量,高睡眠质量自然保证了人们第二天精力充沛。但据统计,中国成年人失眠率高达38.2%,青少年失眠率也在上升。总的来说,如果入睡时间超过30 分钟,就属于失眠的范畴,所以我们相信很多参与者也存在失眠的情况。长期失眠会使人整天感到疲劳,精力不足、注意力不集中、工作学习效率低下。严重的失眠甚至会导致植物神经功能紊乱,导致体内各种系统的不平衡以及各种问题。
影响失眠的因素很多,一般可以分为客观因素和主观因素两大类。客观因素是环境变化、在睡觉前喝茶或咖啡等;而主观因素一般是生活压力、情绪失落、精神兴奋等精神因素。然而,青年人在成长和发展的过程中,由于学习和工作压力极易疲劳。因此,他们必须特别注意卧床休息,以确保身体健康。
本文针对睡眠质量进行了一些研究。本文数据数据集源自2017年Asia and Pacific Mathematical Contest in Modeling,并经过处理而来,数据含2732个样本,显示精神质Psychoticism、神经质Nervousness、可靠性Reliability和性格Character等指标与睡眠质量Sleep quality的关系(“1”代表睡眠好,“2”代表睡眠差)。
部分数据截图:
图1
2.2 基本假设与指标的选取
2.2.1 基本假设
假设1:数据真实可靠。
假设2:男女群体中发生睡眠质量的概率相等。
假设3:年龄对于睡眠质量的差异性可以忽略。
2.2.2 指标的选取
可知,数据的序号Number、来源Source本身和睡眠质量并无关系。
基于以上假设,年龄、性别对于睡眠质量影响可以忽略。于是,选取的指标为:精神质、神经质、可靠性和性格。
三、数据的训练与测试
基于精神质、神经质、可靠性和性格这4个指标,我们应用k-近邻对数据集进行训练和测试。其中,训练数据与测试数据的比例为6:4,选取k=5,程序运行结果如下图所示:
图2
从图2中看出,训练数据与测试数据的比例为6:4,选取k=5,测试的正确率为93.413%,错误率仅为6.587% 。
这表明,对于用精神质、神经质、可靠性和性格这4个指标来分类睡眠质量的好坏,使用k-近邻算法是可行的,正确率超过90%。
四、进一步探索
我们在不同的训练数据与测试数据的比例和k的不同选值下,求得测试结果的正确率如图所示:
图3
从图中可以看出,在相同的训练数据与测试数据的比例下,k的取值越大,正确率一般也越大。
同时可以看出,在k取定值时,训练数据与测试数据的比例在7:3时的正确率比6:4和8:2的低,但是,在这三个比例下,正确率均超过90%,表明k-近邻算法在区别睡眠质量好坏时是具有一定的可行性。
本文只考虑精神质、神经质、可靠性和性格这4个指标,没考虑性别和年龄的影响。基于本文可以以此作为发展点,继续探索性别、年龄、精神质、神经质、可靠性和性格这6个指标对于睡眠质量的关系,并使用k-近邻算法或其改进算法进行进一步探索。
五、小结
优点:精度高,对异常值不敏感,无数据输入假定。
缺点:计算复杂度高,空间复杂度高。
适用数据范围:数值型和标称型。
六、参考文献
[1]周志华.机器学习[M].北京:清华大学出版社,2016.
[2]Peter Harrington.机器学习实战[M].北京:人民邮电出版社,2013.
[3]韩家炜等.数据挖掘概念与技术[M].北京:机械工业出版社,2012.
七、附录
《机器学习实战》的代码,其代码的资源网址为:
https://www.manning.com/books/machine-learning-in-action
其中,kNN.py文件为:
from numpy import *
import operator
from os import listdir
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0]
diffMat = tile(inX, (dataSetSize,1)) - dataSet
sqDiffMat = diffMat**2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances**0.5
sortedDistIndicies = distances.argsort()
classCount={}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
def file2matrix(filename):
w=4
fr = open(filename)
numberOfLines = len(fr.readlines()) #get the number of lines in the file
returnMat = zeros((numberOfLines,w)) #prepare matrix to return
classLabelVector = [] #prepare labels return
fr = open(filename)
index = 0
for line in fr.readlines():
line = line.strip()
listFromLine = line.split('\t')
returnMat[index,:] = listFromLine[0:w]
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat,classLabelVector
def autoNorm(dataSet):
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = zeros(shape(dataSet))
m = dataSet.shape[0]
normDataSet = dataSet - tile(minVals, (m,1))
normDataSet = normDataSet/tile(ranges, (m,1)) #element wise divide
return normDataSet, ranges, minVals
def datingClassTest( hoRatio, filename, k ):
#hoRatio = #测试数据的比例
datingDataMat,datingLabels = file2matrix( filename ) #load data setfrom file
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], k )
#print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]) )
if (classifierResult != datingLabels[i]):
errorCount += 1.0
#print(numTestVecs)#"测试数据:"+str(m)
print("正确率 : %3.4f %%" % (100-100.0*errorCount/float(numTestVecs)) )
print("错误率 : %3.4f %%" % (100.0*errorCount/float(numTestVecs)) )