本章概要

分类是数据分析中非常重要的方法,是对已有数据进行学习,得到一个分类函数或构
造出一个分类模型(即通常所说的分类器(Clasifier))。

分类函数或模型能够将数据样本对应某个给定的类别,完成数据的类别预测。分类
器是机器学习算法中对数据样本进行分类的方法的统称,包含决策树、SVM 、逻辑回归、
朴素贝叶斯、神经网络等算法。

本章主要介绍K近邻分类算法的原理、算法的核心要素,并以K近邻算法的实现为
例,对算法的数据获取、数据预处理、模型实现以及性能评价做了整体介绍。

学习目标

当完成本章的学习后,要求: 

(1)了解KNN分类算法的基本概念。
(2)熟悉KNN算法的核心要素。
(3)熟悉距离的度量方法。
(4)掌握使用KNN算法解决实际分类问题。
1 
KNN 
分类

5.
分类是使用已知类别的数据样本,训练出分类器,使其能够对未知样本进行分类。分视频讲解
类算法是最为常用的机器学习算法之一,属于监督学习算法。

KNN分类(K-Nearest-NeighborsClasification)算法,又叫K近邻算法。它是概念
极其简单,而效果又很优秀的分类算法,于1967年由CoverT和HartP提出。KNN分


140
类算法的核心思想是,如果一个样本在特征空间中的k个最相似(即特征空间中最邻近) 
的样本中的大多数属于某一个类别,则该样本也属于这个类别。

如图5.1所示,假设已经获取一些动物的特征,且已知这些动物的类别。现在需要识
别一个新动物,判断它是哪类动物。


图5.

1 KNN 分类示意图

首先找到与这个物体最接近的k个动物。假设k=3,则可以找到两只猫和一只狗。
由于找到的结果中大多数是猫,则把这个新动物划分为猫类。
KNN 没有专门的学习过程,是基于数据实例的一种学习方法。从上面的描述中不
难看出,KNN 方法有以下三个核心要素。

1.k值
k值也就是选择几个和新动物相邻的已知动物。如果k取值太小,好处是近似误
差会减小,只有特征与这个新动物很相似的才对预测新动物的类别起作用。但同时预
测结果对近邻的样本点非常敏感,仅由非常近的训练样本决定预测结果。因此会使模
型变得复杂,容易过拟合。如果k值太大,学习的近似误差会增大,导致分类模糊,即
欠拟合。

下面举例看k值对预测结果的影响。对图5.当k=分类

2中的动物进行分类,3时, 
结果为“猫∶狗=2∶1”,所以属于猫;当k=6时,表决结果为“猫∶狗∶熊猫=2∶3∶1”, 
所以判断目标动物为狗。

那么k值到底怎么选取呢? 这就涉及距离的度量问题。

2. 
距离的度量
距离决定了哪些是邻居哪些不是。度量距离有很多种方法,不同的距离所确定的近
邻点不同。平面上比较常用的是欧式距离。此外,还有曼哈顿距离、余弦距离、球面距离
等。例如,图5.对于新的点nw(3) ct

3中的四个点为训练样本点, e3,进行预测。其中,a1、
cat2、dog1、dog2、dog3为训练数据,new为测试数据。


141
图5.

2 不同k值对结果的影响示意图


图5.

3 KNN 中距离的度量

通过坐标计算new(3,3)到各个点的欧氏距离,根据二维平面上的欧式距离公式

2

ρ= (x2-x1)2-y1)5.4所示。
2+(y(1) 

可以得到距离如图5.

3. 
分类决策规则
分类结果的确定往往采用多数表决原则,即由输入实例的k个最邻近的训练实例中
的多数类决定输入实例的类别。
KNN 算法是一个简单高效的分类算法,可用于多个类别的分类,还可以用于回归。


1 42 
图5.4 欧式距离计算结果
5.2 初识KNN———鸢尾花分类
本节使用KNN 算法对SKlearn的鸢尾花数据集进行分类。
鸢尾花数据集:鸢尾花(iris)是单子叶百合目花卉,鸢尾花数据集最初由科学家
Anderson测量收集而来。1936年因用于公开发表的Fisher线性判别分析的示例,在机
器学习领域广为人知。
数据集中的鸢尾花数据主要收集自加拿大加斯帕半岛,是一份经典数据集。鸢尾花
数据集共收集了三类鸢尾花,即Setosa山鸢尾花、Versicolour杂色鸢尾花和Virginica弗
吉尼亚鸢尾花,每类鸢尾花有50条记录,共150条数据。数据集包括4个属性特征,分别
是花瓣长度、花瓣宽度、花萼长度和花萼宽度。
在对鸢尾花数据集进行操作之前,先对数据进行详细观察。SKlearn中的iris数据集
有5个key,分别如下。
(1)target_names:分类名称,包括setosa、versicolor和virginica类。
(2)data:特征数据值。
(3)target:分类(150个)。
(4)DESCR:数据集的简介。
(5)feature_names:特征名称。
1.查看数据
【例5.1】 对鸢尾花iris数据集进行调用,查看数据的各方面特征。 
from sklearn.datasets import load_iris 
iris_dataset = load_iris() 
#下面是查看数据的各项属性
视频讲解

1 43 
print("数据集的Keys:\n",iris_dataset.keys()) #查看数据集的keys 
print("特征名:\n",iris_dataset['feature_names']) #查看数据集的特征名称
print("数据类型:\n",type(iris_dataset['data'])) #查看数据类型
print("数据维度:\n",iris_dataset['data'].shape) #查看数据的结构
print("前五条数据:\n{}".format(iris_dataset['data'][:5])) #查看前5 条数据
#查看分类信息
print("标记名:\n",iris_dataset['target_names']) 
print("标记类型:\n",type(iris_dataset['target'])) 
print("标记维度:\n",iris_dataset['target'].shape) 
print("标记值:\n",iris_dataset['target']) 
#查看数据集的简介
print('数据集简介:\n',iris_dataset['DESCR'][:20] + "\n.......") 
#数据集简介前20 个字符
2.数据集拆分
对鸢尾花数据集进行训练集和测试集的拆分操作,可以使用train_test_split()函数。
train_test_split()函数属于sklearn.model_selection类中的交叉验证功能,能随机地将样
本数据集合拆分成训练集和测试集。其格式为: 
X_train,X_test, y_train, y_test = 
cross_validation.train_test_split(train_data, train_target, test_size= 0.4, 
random_state=0)

1 44 
【例5.2】 对iris数据集进行拆分,并查看拆分结果。 
from sklearn.datasets import load_iris 
from sklearn.model_selection import train_test_split 
iris_dataset = load_iris() 
X_train, X_test, y_train, y_test = train_test_split( iris_dataset['data'], iris 
_dataset['target'], random_state=2) 
print("X_train",X_train) 
print("y_train",y_train) 
print("X_test",X_test) 
print("y_test",y_test) 
print("X_train shape: {}".format(X_train.shape)) 
print("X_test shape: {}".format(X_test.shape)) 
运行结果中,X_train和X_test的维度分别为: 
3.使用散点矩阵查看数据特征关系
在数据分析中,同时观察一组变量的多个散点图是很有意义的,这也被称为散点图矩
阵。创建这样的图表工作量巨大,可以使用scatter_matrix()函数。scatter_matrix()函
数是Pandas提供的一个能从DataFrame创建散点图矩阵的函数。
函数格式: 
scatter_matrix(frame, alpha= 0.5, c,figsize= None, ax= None, diagonal= 'hist', 
marker='.', density_kwds=None,hist_kwds=None, range_padding=0.05,**kwds) 
主要参数如下。
frame:PandasDataFrame对象。
alpha:图像透明度,一般取(0,1)的小数。
figsize:以英寸为单位的图像大小,一般以元组(width,height)形式设置。
diagonal:必须且只能在{‘hist’,‘kde’}中选择一个,‘hist’表示直方图(Histogram 
Plot),‘kde’表示核密度估计(KernelDensityEstimation)。该参数是scatter_matrix() 
函数的关键参数。
marker:Matplotlib可用的标记类型,如‘.’‘,’‘o’等。
【例5.3】 对例5.2的数据结果,使用scatter_matrix()显示训练集与测试集。
可以在例5.2的基础上添加如下语句。 
import pandas as pd 
iris_dataframe = pd.DataFrame(X_train, columns=iris_dataset.feature_names) 
#创建一个scatter matrix,颜色值来自y_train 
pd.plotting.scatter _ matrix (iris _ dataframe, c = y _ train, figsize = (15, 15), 
marker='o', hist_kwds={'bins': 20}, s=60, alpha=.8)

145 

运行结果如图5.5所示。可以看到,散点矩阵图呈对称结构,除对角上的密度函数图
之外,其他子图分别显示了不同特征列之间的关联关系。例如,petallength与petal 
width之间近似成线性关系,说明这对特征关联性很强。相反,有些特征列之间的散布状
态比较杂乱,基本无规律可循,说明特征间的关联性不强。

因此,在训练模型时,要优先选择关联明显的特征对进行学习。


图5.s数据集的特征散点矩阵图

5iri

4.建立KNN 
模型
初步对数据集了解后,选取合适的模型并对模型进行初始化。然后对数据集进行分
类学习,得到训练好的模型。

在Python中,实现KNN方法使用的是KNeighborsClasifier类,KNeighborsClasifier 
类属于Scikitlearn的neighbors包。

KNeighborsClasifier使用很简单,核心操作包括以下三步。


1 46 
(1)创建KNeighborsClassifier对象,并进行初始化。
基本格式: 
sklearn.neighbors.KNeighborsClassifier(n _neighbors = 5, weights = 'uniform ', 
algorithm='auto', leaf_size=30,p=2, metric='minkowski', metric_params=None, 
n_jobs=None,**kwargs) 
主要参数如下。
n_neighbors:int型,可选,默认值是5,代表KNN 中的近邻数量k值。
weights:计算距离时使用的权重,默认值是“uniform”,表示平等权重。也可以取值
“distance”,则表示按照距离的远近设置不同权重。还可以自主设计加权方式,并以函数
形式调用。
metric:距离的计算,默认值是“minkowski”。当p=2,metric=‘minkowski’时,使
用的是欧式距离。p=1,metric=‘minkowski’时为曼哈顿距离。
(2)调用fit()方法,对数据集进行训练。
函数格式: 
fit(X, y) 
说明:以X为训练集,以y为测试集对模型进行训练。
(3)调用predict()函数,对测试集进行预测。
函数格式: 
predict(X) 
说明:根据给定的数据预测其所属的类别标签。
【例5.4】 使用KNN 对鸢尾花iris数据集进行分类的完整代码实现。 
from sklearn import datasets 
from sklearn.neighbors import KNeighborsClassifier 
from sklearn.model_selection import train_test_split 
#导入鸢尾花数据并查看数据特征
iris = datasets.load_iris() 
print('数据集结构:',iris.data.shape) 
#获取属性
iris_X = iris.data 
#获取类别
iris_y = iris.target 
#划分成测试集和训练集
iris_train_X,iris_test_X,iris_train_y,iris_test_y= train_test_split(iris_X, 
iris_y,test_size=0.2, random_state=0) 
#分类器初始化
knn = KNeighborsClassifier() 
#对训练集进行训练
knn.fit(iris_train_X, iris_train_y) 
视频讲解

1 47 
#对测试集数据的鸢尾花类型进行预测
predict_result = knn.predict(iris_test_X) 
print('测试集大小:',iris_test_X.shape) 
print('真实结果:',iris_test_y) 
print('预测结果:',predict_result) 
#显示预测精确率
print('预测精确率:',knn.score(iris_test_X, iris_test_y)) 
程序运行结果如下。
从结果可以看出,拆分的测试集中有30个样本,其中有一个判断错误,总体精确率约
96.7%,精度较高。主要原因在于数据集中的数据比较好,数据辨识度较高。
也可以将KNN 用于图像等分类场合,通过对目标图像进行归类,能够解决类似图像
识别等问题。
5.3 KNN 手写数字识别
图像识别是模式识别研究中一个重要的领域,通过对图像进行分析和理解,识别出不
同模式的目标对象。图像识别包括文字识别、图像识别与物体识别。文字识别是常见的
图像识别问题,目的是分析并识别图片中包含的文字。
文字识别中难度较高的是手写文字识别,因为手写体与印刷体相比,个人风格迥异、
图片大小不一。手写数字识别的目标相对简单,是从图像中识别出数字0~9,经常用于
自动邮件分拣等生产领域。在机器学习中,有时将识别问题转换为分类问题。
本实验使用的数据集修改自“手写数字光学识别数据集”①,共保留了1600张图片。
通过拆分,其中1068张作为训练集,其余的532张为测试集。图片为长宽都是32px的二
值图,为方便处理,将图片预存为文本文件(过程省略,参考3.6.3节)。
【例5.5】 使用KNN 方法实现手写数字识别。
本例的素材文件夹为HWdigits,子目录trainSet下存放训练数据,子目录testSet存
放测试数据。数据为文本文件形式,每个文件表示一个手写数字。
在对文件系统进行操作时,可以使用模块os提供的listdir()方法。listdir()方法返
回指定文件夹下的文件/文件夹列表,格式为os.listdir(path),字符型参数path指明目标
路径。operator模块中的itemgetter()函数用于获取对象的某个维度的数据,参数为
序号。
① 来源:http://archive.ics.uci.edu/ml/datasets,Alpaydin与Kaynak提供,1998-07-01发布。
视频讲解

1 48 
#coding=utf-8 
import numpy as np 
from os import listdir 
def loadDataSet(): #加载数据集 
#获取训练数据集 
print("1.Loading trainSet...") 
trainFileList = listdir('HWdigits/trainSet') 
trainNum = len(trainFileList) 
trainX = np.zeros((trainNum, 32*32)) 
trainY = [] 
for i in range(trainNum): 
trainFile = trainFileList[i] 
#将训练数据集向量化 
trainX[i, :] = img2vector('HWdigits/trainSet/%s' % trainFile,32,32) 
label = int(trainFile.split('_')[0]) #读取文件名的第一位作为标记 
trainY.append(label) 
#获取测试数据集 
print("2.Loading testSet...") 
testFileList = listdir('HWdigits/testSet') 
testNum = len(testFileList) 
testX = np.zeros((testNum, 32*32)) 
testY = [] 
for i in range(testNum): 
testFile = testFileList[i] 
#将测试数据集向量化 
testX[i, :] = img2vector('HWdigits/testSet/%s' % testFile,32,32) 
label = int(testFile.split('_')[0]) #读取文件名的第一位作为标记 
testY.append(label) 
return trainX, trainY, testX, testY 
def img2vector(filename,h,w): #将32*32 的文本转换为向量 
imgVector = np.zeros((1, h * w)) 
fileIn = open(filename) 
for row in range(h): 
lineStr = fileIn.readline() 
for col in range(w): 
imgVector[0, row * 32 + col] = int(lineStr[col]) 
return imgVector 
def myKNN(testDigit, trainX, trainY, k): 
numSamples = trainX.shape[0] #shape[0]代表行,每行一个图片,得到样本个数 
#1.计算欧式距离