数据分析(四)从K近邻算法入门

K最近邻(k-Nearest Neighbor,KNN)分类算法,是一个理论上比较成熟的方法,也是最简单的机器学习算法之一。该方法的思路是:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。

举例来说,我们要根据三角形更像什么(跟哪种图形离得更近),预测三角形的类别。我们找到三个离它最近的邻居:两个菱形和一个圆。菱形的数量多于圆,因此我们预测三角形的类别为菱形。
这里写图片描述

KNN的优点是

1. 简单,易于理解,易于实现,无需估计参数,无需训练;
2. 适合对稀有事件进行分类;
3. 特别适合于多分类问题(multi-modal,对象具有多个类别标签), kNN比SVM的表现要好

缺点也很明显

1. 当样本不平衡时,如一个类的样本容量很大,而其他类样本容量很小时,有可能导致当输入一个新样本时,该样本的K个邻居中大容量类的样本占多数。 该算法只计算“最近的”邻居样本,某一类的样本数量很大,那么或者这类样本并不接近目标样本,或者这类样本很靠近目标样本。无论怎样,数量并不能影响运行结果。

2. 该方法的另一个不足之处是计算量较大,因为对每一个待分类的文本都要计算它到全体已知样本的距离,才能求得它的K个最近邻点。

3. 可理解性差,无法给出像决策树那样的规则。

我们以手写识别为例,用KNN算法来预测数字的正确率。KNN算法通常需要求两个目标之间的距离,一般情况下都是欧氏距离,就是两个点坐标之间的直线距离。有些情况也会用到曼哈顿距离、余弦距离。这里我们将数字的二位数组转变为一维向量,距离就是向量之间的距离,也就是欧氏距离。

我们用到的数据集来自机器学习实战,有2000个例子,共10个数字,每个数字有大约200个样本。每个例子的名称的第一个数字就是它表示的数字,它是由32*32的矩阵表示,大概是这样:

这里写图片描述

代码和数据放在github上: https://github.com/ColinFred/KNN

首先,写一个函数,将每一个32*32的矩阵变成1*24的一维向量。

import numpy as np
from os import listdir

def img2vector(filename):
    """
    将图片转化为向量形式。即将二维数组变为一维数组
    """
    returnVect = np.zeros((1, 1024))
    fr = open(filename)
    for i in range(32):
        lineStr = fr.readline()
        for j in range(32):
            returnVect[0, 32 * i + j] = int(lineStr[j])
    return returnVectdef img2vector(filename):

然后,将所有的数据都做这样的处理,并且从名称中提取出它表示的数字作为标签

def handwritingClassTest():
    """
    数据预处理
    """
    hwLabels = []
    trainingFileList = listdir('trainingDigits')  # 加载训练集
    m = len(trainingFileList)
    trainingMat = np.zeros((m, 1024))  # 用来存放训练集各个向量
    for i in range(m):
        fileNameStr = trainingFileList[i]  # 文件名称
        fileStr = fileNameStr.split('.')[0]  # 将 .txt 字符串剔除
        classNumStr = int(fileStr.split('_')[0])  # 提取出这个文件所代表的数字
        hwLabels.append(classNumStr)  # 将数字放入标签集合中
        trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)  # 将向量形式的数字文件存入trainingMat中

    return trainingMat, hwLabels  # 返回值是训练集的属性和标签

将所有的数据随机划分为两组,一组作为训练集,一组作为测试集

from sklearn.model_selection import train_test_split  # 随机划分训练集和测试集

X, y = handwritingClassTest()
x_train, x_test, y_train, y_test = train_test_split(X, y, random_state=0)

print("training dataset: {0}".format(x_train.shape[0]))
print("testing dataset: {0}".format(x_test.shape[0]))
print("each sample has {0} features".format(x_train.shape[1]))
# training dataset: 1450
# testing dataset: 484
# each sample has 1024 features

函数:sklearn.model_selection.train_test_split
随机划分训练集和测试集

train_test_split是交叉验证中常用的函数,功能是从样本中随机的按比例选取train data和testdata,形式为:
X_train, X_test, y_train, y_test = cross_validation.train_test_split(train_data, train_target, test_size=0.4, random_state=0)

参数解释:
train_data:所要划分的样本特征集
train_target:所要划分的样本结果
test_size:样本占比,如果是整数的话就是样本的数量
random_state:是随机数的种子。

利用sklearn中的 KNeighborsClassifier 分类器对训练集进行训练,并用测试集测试其准确率。

from sklearn.neighbors import KNeighborsClassifier  # 引入k近邻算法

estimator = KNeighborsClassifier()  # 目前都使用默认参数
estimator.fit(x_train, y_train)  # 开始训练
y_predicted = estimator.predict(x_test)  # 开始预测
accuracy = np.mean(y_test == y_predicted) * 100  # 准确率
print("the accuracy is {0:.1f}%".format(accuracy))
# the accuracy is 96.7%

我们把数据集分为训练集和测试集,用训练集训练算法,在测试集上评估效果。倘若碰巧走运,测试集很简单,我们就会觉得算法表现很出色。反之,我们可能会怀疑算法很糟糕。也许由于我们一时不走运,就把一个其实很不错的算法给无情抛弃了,这岂不是很可惜。

交叉检验能解决上述一次性测试所带来的问题。既然只切一次有问题,那就多切几次,多进行几次实验。每次切分时,都要保证这次得到的训练集和测试集与上次不一样,还要确保每条数据都只能用来测试一次。算法描述如下。

1.  将整个大数据集分为几个部分。

2.  对于每一部分执行以下操作:
  2.1 将其中一部分作为当前测试集
  2.2 用剩余部分训练算法
  2.3 在当前测试集上测试算法

3. 记录每次得分及平均得分。

4.  在上述过程中,每条数据只能在测试集中出现一次,以减少(但不能完全规避)运气成分

为了避免一次性测试的运气问题,我们引入交叉检验

from sklearn.model_selection import cross_val_score

scores = cross_val_score(estimator, X, y, scoring='accuracy')
average_accuacy = np.mean(scores) * 100
print("the average accuracy is {0:.1f}%".format(average_accuacy))
# the average accuracy is 94.7%

函数:sklearn.cross_validation.cross_val_score

调用形式是:sklearn.cross_validation.cross_val_score(estimator, X, y=None, scoring=None, cv=None,n_jobs=1, verbose=0, fit_params=None, pre_dispatch=’2*n_jobs’)

返回值就是对于每次不同的的划分raw data时,在test data上得到的分类的准确率。

参数解释:
estimator: 是不同的分类器,可以是任何的分类器。比如支持向量机分类器:estimator = svm.SVC(kernel=’linear’, C=1)
cv: 代表不同的cross validation的方法。如果cv是一个int值,并且如果提供了rawtarget参数,那么就代表使用StratifiedKFold分类方式;如果cv是一个int值,并且没有提供rawtarget参数,那么就代表使用KFold分类方式;也可以给定它一个CV迭代策略生成器,指定不同的CV方法。
scoring: 默认Nnoe,准确率的算法,可以通过score_func参数指定;如果不指定的话,是用estimator默认自带的准确率算法。

近邻算法有多个参数,最重要的是选取多少个近邻作为预测依据。 scikit-learn管这个参数叫n_neighbors。

我们看看n_neighbors对算法准确率的影响。我们对它从1-20逐个取值,用图表来显示结果。

avg_scores = []
all_scores = []
parameter_values = list(range(1, 21))
for n_neighbors in parameter_values:
    estimator = KNeighborsClassifier(n_neighbors=n_neighbors)
    scores = cross_val_score(estimator, X, y, scoring='accuracy')
    avg_scores.append(np.mean(scores))
    all_scores.append(scores)

# 使用图来分析一下n_neighbors的不同取值与分类正确率的关系
from matplotlib import pyplot as plt

plt.figure(figsize=(32, 20))
plt.plot(parameter_values, avg_scores, '-o', linewidth=5, markersize=24)
plt.show()

这里写图片描述

可以看到,当n_neighbors=3的时候,KNN的准确率最高。当n_neighbors=2的时候,会出现很明显的偏差。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值