机器学习算法笔记之1:kNN算法

一、k近邻算法

1、概述

k近邻(k-NearestNeighbor,简称kNN)算法是一种常见的监督学习算法。其工作机制可概括为:给定测试样本,基于某种距离度量找出训练集中与其距离最近的k个训练样本,通常k是不大于20的整数。然后基于这k个“邻居”的类别信息来进行预测,通常使用投票法,即选择这k个样本中出现最多的类别来标记测试样本,在回归任务中可使用“平均法”,即将这k个训练样本标记的平均值作为预测结果,还可以基于距离进行加权平均或加权投票,距离样本最近的权重最大。

k近邻算法的三要素:k值选择、距离度量和分类决策规则。

优点:精度高、对异常值不敏感、无数据输入假定。

缺点:计算复杂度高、空间复杂度高,训练模型依赖训练集数据且不可丢弃。

适用数据范围:数值型和标称型。

k-NearestNeighbor分类器存在以下不足:

分类器必须记住所有训练数据并将其存储起来,以便于未来测试数据用于比较。这在存储空间上是低效的,数据集的大小很容易就以GB计。

对一个测试图像进行分类需要和所有训练图像作比较,算法计算资源耗费高。

2、算法代码实现

伪代码,对未知类别属性的数据集中的每个点依次执行以下操作:

(1)计算已知类别数据中的点与当前点之间的距离;

(2)按照距离递增次序排序;

(3)选取与当前点距离最小的k个点;

(4)确定前k个点所在类别的出现频率;

(5)返回前k个点出现频率最高的类别作为当前点的预测分类。

程序清单2-1 k-近邻算法

def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0] #获取dataSet的第一维度的大小
    diffMat = tile(inX, (dataSetSize,1)) - dataSet #将inX在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]]
        #依次查询cclassCount中是否有该key,有则将取出value再+1,没有则返回添加该key并置value为0,再+1
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 #统计得到各个标签的个数
    #按照对象的第1域位置的值进行降序排序,即获得得票最高的标签
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

准备数据:归一化数据

在处理不同取值范围的特征值时,我们通常采用的方法是将特征数值归一化,如将取值范围处理为0到1或者-1到1之间。

newValue= (oldValue – min) / (max – min)

其中min和max分别是数据集中的最小特征值和最大特征值。最然改变数值取值范围增加了分类器的复杂度,但这是为了得到准确结果所必需的。

程序清单归一化特征值

defautoNorm(dataSet):
    minVals = dataSet.min(0) #参数0是从列中选取最小值
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - tile(minVals,(m,1)) #多用于矩阵之间的对应计算,在行方向扩展m次,在列方向扩展1次
    normDataSet = normDataSet/tile(ranges,(m,1))   #element wise divide
returnnormDataSet, ranges, minVals

二、低维嵌入

在高维情形下出现的数据样本稀疏、距离计算困难等问题,是所有机器学习方法共同面临的严重障碍,被称为“维数灾难”(curse ofdimensionality)。缓解维数灾难的一个重要途径是降维(dimensionreduction),亦称维数约简,即通过某种数学变换将原始高维属性空间转变为一个低维子空间(subspace)。人们观测或收集到的数据样本虽是高维的,但与学习任务密切相关的也许是某个低维分布,即高维空间中的一个低维“嵌入”(embedding)。

若要求原始空间中样本之间的距离在低维空间中得以保持,即得到多维缩放(MultipleDimensional Scaling, MDS)。

一般来说,欲获得低维子空间,最简单的是对原始高维空间进行线性变换,基于线性变换来进行降维的方法称为线性降维方法。

三、主成分分析

该部分请参见本博客另一篇笔记的第十四章内容:

http://blog.csdn.net/marsjhao/article/details/61914386

四、核化线性降维

在不少现实任务中,需要非线性映射才能找到恰当的低维嵌入。非线性降维的一种常用方法是基于核技巧对线性降维进行“核化”(kernelized),例如核主成分分析KPCA。

五、实际应用kNN

如果你希望将k-NN分类器用到实处(最好别用到图像上,若是仅仅作为练手还可以接受),那么可以按照以下流程:

1.预处理你的数据:对你数据中的特征进行归一化(normalize),让其具有零平均值(zeromean)和单位方差(unit variance)。在后面的小节我们会讨论这些细节。本小节不讨论,是因为图像中的像素都是同质的,不会表现出较大的差异分布,也就不需要标准化处理了。

2.如果数据是高维数据,考虑使用降维方法,比如PCA(wiki refCS229refblog ref)或随机投影

3.将数据随机分入训练集和验证集。按照一般规律,70%-90% 数据作为训练集。这个比例根据算法中有多少超参数,以及这些超参数对于算法的预期影响来决定。如果需要预测的超参数很多,那么就应该使用更大的验证集来有效地估计它们。如果担心验证集数量不够,那么就尝试交叉验证方法。如果计算资源足够,使用交叉验证总是更加安全的(份数越多,效果越好,也更耗费计算资源)。

4.在验证集上调优,尝试足够多的k值,尝试L1和L2两种范数计算方式。

5.如果分类器跑得太慢,尝试使用ApproximateNearest Neighbor库(比如FLANN)来加速这个过程,其代价是降低一些准确率。

6.对最优的超参数做记录。记录最优参数后,是否应该让使用最优参数的算法在完整的训练集上运行并再次训练呢?因为如果把验证集重新放回到训练集中(自然训练集的数据量就又变大了),有可能最优参数又会有所变化。在实践中,不要这样做。千万不要在最终的分类器中使用验证集数据,这样做会破坏对于最优参数的估计。直接使用测试集来测试用最优参数设置好的最优模型,得到测试集数据的分类准确率,并以此作为你的kNN分类器在该数据上的性能表现。

六、kNN在Scikit-learn上得实现

import numpy as np
import matplotlib.pyplot as plt
from sklearn import neighbors, datasets, cross_validation

def load_classification_data():
    digits = datasets.load_digits()
    X_train = digits.data
    y_train = digits.target
    return cross_validation.train_test_split(X_train, y_train, test_size=0.25, 
                                             random_state = 0, stratify=y_train)

def test_KNeighborsClassifier(*data):
    X_train, X_test, y_train, y_test = data
    clf = neighbors.KNeighborsClassifier(n_neighbors=5, weights = 'distance')
    clf.fit(X_train, y_train)
    print("Training Score:%f" %clf.score(X_train, y_train))
    print("Testing Score:%f" %clf.score(X_test, y_test))

def test_KNeighborsClassifier_k_w(*data):
    X_train,X_test,y_train,y_test=data
    Ks=np.linspace(1,y_train.size,num=100,endpoint=False,dtype='int')
    weights=['uniform','distance']

    fig=plt.figure()
    ax=fig.add_subplot(1,1,1)
    ### 绘制不同 weights 下, 预测得分随 n_neighbors 的曲线
    for weight in weights:
        training_scores=[]
        testing_scores=[]
        for K in Ks:
            clf=neighbors.KNeighborsClassifier(weights=weight,n_neighbors=K)
            clf.fit(X_train,y_train)
            testing_scores.append(clf.score(X_test,y_test))
            training_scores.append(clf.score(X_train,y_train))
        ax.plot(Ks,testing_scores,label="testing score:weight=%s"%weight)
        ax.plot(Ks,training_scores,label="training score:weight=%s"%weight)
    ax.legend(loc='best')
    ax.set_xlabel("K")
    ax.set_ylabel("score")
    ax.set_ylim(0,1.05)
    ax.set_title("KNeighborsClassifier")
    plt.show()
    
X_train, X_test, y_train, y_test = load_classification_data()
test_KNeighborsClassifier(X_train, X_test, y_train, y_test)
test_KNeighborsClassifier_k_w(X_train, X_test, y_train, y_test)
不同weight参数下和不同k值下的准确率变化情况:

机器学习算法笔记之1:kNN算法_第1张图片

你可能感兴趣的:(机器学习/深度学习)