K-近邻法——近朱者赤,近墨者黑

本文参考:

1)机器学习.周志华

2)集体智慧编程

3)https://cuijiahua.com/blog/2017/11/ml_1_knn.html 崔家华. 

K-近邻法(kNN)

       在利用多种不同属性(比如价格)对数值型数据进行预测时,贝叶斯分类器、决策树、支持向量机都不是最佳的算法。

       k近邻法(k-nearest neighbor, k-NN)是1967年由Cover T和Hart P提出的一种基本分类与回归方法。

      其工作机制非常简单:给定测试样本,基于某种距离度量找出训练集中与其最靠近的k 个训练样本,然后基于这k 个“邻居” 的信息来进行预测。通常,在分类任务中可使用“投票法”,即选择这k 个样本的实值输出标记的平均值作为预测结果;还可以基于距离远近进行加权平均或加权投票,距离越近的样本权重越大

1、邻近数

过少过多均不好

2、定义相似度

寻找一种衡量两个样本之间相似程度的方法。

欧几里得距离算法

æºå¨å­¦ä¹ å®ææç¨ï¼ä¸ï¼ï¼K-è¿é»ï¼KNNï¼ç®æ³ï¼å²è¯çº§å¹²è´§é¿æï¼

#欧几里得距离算法
def euclidean(v1,v2):
    d=0.0
    for i in range(len(v1)):
        d+=(v1[i]-v2[i])**2
    return math.sqrt(d)

3、k-近邻算法步骤

  1. 计算已知类别数据集中的点与当前点之间的距离;
  2. 按照距离递增次序排序;
  3. 选取与当前点距离最小的k个点;
  4. 确定前k个点所在类别的出现频率;
  5. 返回前k个点所出现频率最高的类别作为当前点的预测分类。

 4、Python3代码实现

æºå¨å­¦ä¹ å®ææç¨ï¼ä¸ï¼ï¼K-è¿é»ï¼KNNï¼ç®æ³ï¼å²è¯çº§å¹²è´§é¿æï¼

import numpy as np
import operator


def createDataSet():
    group = np.array([[1, 101], [5, 89], [108, 5], [115, 8]])
    labels = ['爱情片', '爱情片', '动作片', '动作片']
    return group, labels


def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet  # 把数组沿各个方向复制
    sqDiffMat = diffMat ** 2  # 数组的每个元素各自平方
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances ** 0.5
    sortedDistIndices = distances.argsort()  # 将x中的元素从小到大排列,输出其对应的index(索引)
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndices[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1  # get(key,default=None)返回指定键的值,如果值不在字典中返回默认值
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)  # items()
    return sortedClassCount[0][0]


if __name__ == '__main__':
    group, labels = createDataSet()
    test = [101, 20]
    test_class = classify0(test, group, labels, 3)
    print(test_class)

输出:  

       KNN没有显式的训练过程!它是“懒惰学习”(lazy learning)的著名代表,此类学习技术在训练阶段仅仅是把样本保存起来,训练时间开销为0,待收到测试样本后再进行处理;相应的,那些在训练阶段就对样本进行学习处理的方法,称为“急切学习” (eager learning)。

5、k-近邻算法实战之约会网站配对效果判定

       海伦女士一直使用在线约会网站寻找适合自己的约会对象。尽管约会网站会推荐不同的任选,但她并不是喜欢每一个人。经过一番总结,她发现自己交往过的人可以进行如下分类:

  1. 不喜欢的人
  2. 魅力一般的人
  3. 极具魅力的人

        海伦收集约会数据已经有了一段时间,她把这些数据存放在文本文件datingTestSet.txt中,每个样本数据占据一行,总共有1000行。 

算法步骤:

  1. 收集数据:可以使用爬虫进行数据的收集,也可以使用第三方提供的免费或收费的数据。一般来讲,数据放在txt文本文件中,按照一定的格式进行存储,便于解析及处理。
  2. 准备数据:使用Python解析、预处理数据。
  3. 分析数据:可以使用很多方法对数据进行分析,例如使用Matplotlib将数据可视化。
  4. 测试算法:计算错误率。
  5. 使用算法:错误率在可接受范围内,就可以运行k-近邻算法进行分类。

1)准备数据,数据解析

      datingTestSet数据集已经下载好,放在百度云中 https://pan.baidu.com/s/1-eintcAD0pEj45oFep5SXQ  密码:31us

40920   8.326976   0.953952   largeDoses
14488  7.153469   1.673904   smallDoses
26052  1.441871   0.805124   didntLike
75136  13.147394  0.428964   didntLike
38344  1.669788   0.134296   didntLike
72993  10.141740  1.032955   didntLike
35948  6.830792   1.213192   largeDoses
42666  13.276369  0.543880   largeDoses
67497  8.631577   0.749278   didntLike
35483  12.273169  1.508053   largeDoses
50242  3.723498   0.831917   didntLike
63275  8.385879   1.669485   didntLike
5569   4.875435   0.728658   smallDoses
51052  4.680098   0.625224   didntLike
77372  15.299570  0.331351   didntLike
......

列1:每年获得的飞行常客里程数

列2:玩视频游戏所消耗时间百分比

列3:每周消费的冰淇淋公升数

import numpy as np


def file_matrix(filename):
    fr = open(filename)
    array0Lines = fr.readlines()  # ???
    numberOfLines = len(array0Lines)
    returnMat = np.zeros((numberOfLines, 3))
    classLabelVector = []
    index = 0
    for line in array0Lines:
        line = line.strip()  # s.strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
        listFromLine = line.split('\t')
        returnMat[index, :] = listFromLine[0:3]  # ???
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return returnMat, classLabelVector


if __name__ == '__main__':
    filename = 'datingTestSet.txt'
    datingDataMat, datingLabels = file_matrix(filename)
    print(datingDataMat)
    print(datingLabels)

result: 

[[4.0920000e+04 8.3269760e+00 9.5395200e-01]
 [1.4488000e+04 7.1534690e+00 1.6739040e+00]
 [2.6052000e+04 1.4418710e+00 8.0512400e-01]
 ...
 [2.6575000e+04 1.0650102e+01 8.6662700e-01]
 [4.8111000e+04 9.1345280e+00 7.2804500e-01]
 [4.3757000e+04 7.8826010e+00 1.3324460e+00]]

 [3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3, 2, 1, 2, 3, 2, 3, 2, 3, 2, 1, 3, 1, 3, 1, 2, 1, 1, 2, 3, 3, 1, 2, 3, 3, 3, 1, 1, 1, 1, 2, 2, 1, 3, 2, 2, 2, 2, 3, 1, 2, 1, 2, 2, 2, 2, 2, 3, 2, 3, 1, 2, 3, 2, 2, 1, 3, 1, 1, 3, 3, 1, 2, 3, 1, 3, 1, 2, 2, 1, 1, 3, 3, 1, 2, 1, 3, 3, 2, 1, 1, 3, 1, 2, 3, 3, 2, 3, 3, 1, 2, 3, 2, 1, 3, 1, 2, 1, 1, 2, 3, 2, 3, 2, 3, 2, 1, 3, 3, 3, 1, 3, 2, 2, 3, 1, 3, 3, 3, 1, 3, 1, 1, 3, 3, 2, 3, 3, 1, 2, 3, 2, 2, 3, 3, 3, 1, 2, 2, 1, 1, 3, 2, 3, 3, 1, 2, 1, 3, 1, 2, 3, 2, 3, 1, 1, 1, 3, 2, 3, 1, 3, 2, 1, 3, 2, 2, 3, 2, 3, 2, 1, 1, 3, 1, 3, 2, 2, 2, 3, 2, 2, 1, 2, 2, 3, 1, 3, 3, 2, 1, 1, 1, 2, 1, 3, 3, 3, 3, 2, 1, 1, 1, 2, 3, 2, 1, 3, 1, 3, 2, 2, 3, 1, 3, 1, 1, 2, 1, 2, 2, 1, 3, 1, 3, 2, 3, 1, 2, 3, 1, 1, 1, 1, 2, 3, 2, 2, 3, 1, 2, 1, 1, 1, 3, 3, 2, 1, 1, 1, 2, 2, 3, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 2, 2, 3, 2, 3, 3, 3, 3, 1, 2, 3, 1, 1, 1, 3, 1, 3, 2, 2, 1, 3, 1, 3, 2, 2, 1, 2, 2, 3, 1, 3, 2, 1, 1, 3, 3, 2, 3, 3, 2, 3, 1, 3, 1, 3, 3, 1, 3, 2, 1, 3, 1, 3, 2, 1, 2, 2, 1, 3, 1, 1, 3, 3, 2, 2, 3, 1, 2, 3, 3, 2, 2, 1, 1, 1, 1, 3, 2, 1, 1, 3, 2, 1, 1, 3, 3, 3, 2, 3, 2, 1, 1, 1, 1, 1, 3, 2, 2, 1, 2, 1, 3, 2, 1, 3, 2, 1, 3, 1, 1, 3, 3, 3, 3, 2, 1, 1, 2, 1, 3, 3, 2, 1, 2, 3, 2, 1, 2, 2, 2, 1, 1, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 3, 1, 1, 2, 2, 1, 2, 2, 2, 3, 1, 1, 1, 3, 1, 3, 1, 3, 3, 1, 1, 1, 3, 2, 3, 3, 2, 2, 1, 1, 1, 2, 1, 2, 2, 3, 3, 3, 1, 1, 3, 3, 2, 3, 3, 2, 3, 3, 3, 2, 3, 3, 1, 2, 3, 2, 1, 1, 1, 1, 3, 3, 3, 3, 2, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, 2, 3, 2, 1, 2, 2, 2, 3, 2, 1, 3, 2, 3, 2, 3, 2, 1, 1, 2, 3, 1, 3, 3, 3, 1, 2, 1, 2, 2, 1, 2, 2, 2, 2, 2, 3, 2, 1, 3, 3, 2, 2, 2, 3, 1, 2, 1, 1, 3, 2, 3, 2, 3, 2, 3, 3, 2, 2, 1, 3, 1, 2, 1, 3, 1, 1, 1, 3, 1, 1, 3, 3, 2, 2, 1, 3, 1, 1, 3, 2, 3, 1, 1, 3, 1, 3, 3, 1, 2, 3, 1, 3, 1, 1, 2, 1, 3, 1, 1, 1, 1, 2, 1, 3, 1, 2, 1, 3, 1, 3, 1, 1, 2, 2, 2, 3, 2, 2, 1, 2, 3, 3, 2, 3, 3, 3, 2, 3, 3, 1, 3, 2, 3, 2, 1, 2, 1, 1, 1, 2, 3, 2, 2, 1, 2, 2, 1, 3, 1, 3, 3, 3, 2, 2, 3, 3, 1, 2, 2, 2, 3, 1, 2, 1, 3, 1, 2, 3, 1, 1, 1, 2, 2, 3, 1, 3, 1, 1, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 2, 2, 2, 3, 1, 3, 1, 2, 3, 2, 2, 3, 1, 2, 3, 2, 3, 1, 2, 2, 3, 1, 1, 1, 2, 2, 1, 1, 2, 1, 2, 1, 2, 3, 2, 1, 3, 3, 3, 1, 1, 3, 1, 2, 3, 3, 2, 2, 2, 1, 2, 3, 2, 2, 3, 2, 2, 2, 3, 3, 2, 1, 3, 2, 1, 3, 3, 1, 2, 3, 2, 1, 3, 3, 3, 1, 2, 2, 2, 3, 2, 3, 3, 1, 2, 1, 1, 2, 1, 3, 1, 2, 2, 1, 3, 2, 1, 3, 3, 2, 2, 2, 1, 2, 2, 1, 3, 1, 3, 1, 3, 3, 1, 1, 2, 3, 2, 2, 3, 1, 1, 1, 1, 3, 2, 2, 1, 3, 1, 2, 3, 1, 3, 1, 3, 1, 1, 3, 2, 3, 1, 1, 3, 3, 3, 3, 1, 3, 2, 2, 1, 1, 3, 3, 2, 2, 2, 1, 2, 1, 2, 1, 3, 2, 1, 2, 2, 3, 1, 2, 2, 2, 3, 2, 1, 2, 1, 2, 3, 3, 2, 3, 1, 1, 3, 3, 1, 2, 2, 2, 2, 2, 2, 1, 3, 3, 3, 3, 3, 1, 1, 3, 2, 1, 2, 1, 2, 2, 3, 2, 2, 2, 3, 1, 2, 1, 2, 2, 1, 1, 2, 3, 3, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 1, 3, 3, 2, 3, 2, 3, 3, 2, 2, 1, 1, 1, 3, 3, 1, 1, 1, 3, 3, 2, 1, 2, 1, 1, 2, 2, 1, 1, 1, 3, 1, 1, 2, 3, 2, 2, 1, 3, 1, 2, 3, 1, 2, 2, 2, 2, 3, 2, 3, 3, 1, 2, 1, 2, 3, 1, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 1, 3, 3, 3] 

2)数据可视化

添加showdatas

def showdatas(datingDataMat, datingLabels):
    font = FontProperties(fname=r'c:\windows\fonts\simsun.ttc', size=14)
    fig, axs = plt.subplots(nrows=2, ncols=2, sharex=False, sharey=False, figsize=(13, 8))
    numberOfLabels = len(datingLabels)
    LabelsColors = []
    for i in datingLabels:
        if i == 1:
            LabelsColors.append('black')
        if i == 2:
            LabelsColors.append('orange')
        if i == 3:
            LabelsColors.append('red')
    axs[0][0].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 1], color=LabelsColors, s=15, alpha=.5)
    axs0_title_text = axs[0][0].set_title(u'每年获得的飞行常客里程数与玩视频游戏所消耗时间占比', FontProperties=font)
    axs0_xlabel_text = axs[0][0].set_xlabel(u'每年获得的飞行常客里程数', FontProperties=font)
    axs0_ylabel_text = axs[0][0].set_ylabel(u'玩视频游戏所消耗时间占比', FontProperties=font)
    plt.setp(axs0_title_text, size=9, weight='bold', color='red')
    plt.setp(axs0_xlabel_text, size=9, weight='bold', color='black')
    plt.setp(axs0_ylabel_text, size=9, weight='bold', color='black')

    axs[0][1].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
    axs1_title_text = axs[0][1].set_title(u'每年获得的飞行常客里程数与每周消费的冰淇凌公升数', FontProperties=font)
    axs1_xlabel_text = axs[0][1].set_xlabel(u'每年获得的飞行常客里程数', FontProperties=font)
    axs1_ylabel_text = axs[0][1].set_ylabel(u'每周消费的冰淇凌公升数', FontProperties=font)
    plt.setp(axs1_title_text, size=9, weight='bold', color='red')
    plt.setp(axs1_xlabel_text, size=9, weight='bold', color='black')
    plt.setp(axs1_ylabel_text, size=9, weight='bold', color='black')

    axs[1][0].scatter(x=datingDataMat[:, 1], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
    axs2_title_text = axs[1][0].set_title(u'玩视频游戏所消耗时间占比与每周消费的冰淇凌公升数', FontProperties=font)
    axs2_xlabel_text = axs[1][0].set_xlabel(u'玩视频游戏所消耗时间占比', FontProperties=font)
    axs2_ylabel_text = axs[1][0].set_ylabel(u'每周消费的冰淇凌公升数', FontProperties=font)
    plt.setp(axs2_title_text, size=9, weight='bold', color='red')
    plt.setp(axs2_xlabel_text, size=9, weight='bold', color='black')
    plt.setp(axs2_ylabel_text, size=9, weight='bold', color='black')

    didntLike = mlines.Line2D([], [], color='black', marker='.', markersize=6, label='didntLike')
    smallDoses = mlines.Line2D([], [], color='orange', marker='.', markersize=6, label='smallDoses')
    largeDoses = mlines.Line2D([], [], color='red', marker='.', markersize=6, label='largeDoses')

    axs[0][0].legend(handles=[didntLike, smallDoses, largeDoses])
    axs[0][1].legend(handles=[didntLike, smallDoses, largeDoses])
    axs[1][0].legend(handles=[didntLike, smallDoses, largeDoses])

    plt.show()
if __name__ == '__main__':
    filename = 'datingTestSet.txt'
    datingDataMat, datingLabels = file_matrix(filename)
    # print(datingDataMat)
    # print(datingLabels)
    showdatas(datingDataMat, datingLabels)

 3)数据归一化

如果想要计算样本3和样本4之间的距离,可以使用欧拉公式计算。

机器学习实战教程(一):K-近邻(KNN)算法(史诗级干货长文)

计算方法: 

æºå¨å­¦ä¹ å®ææç¨ï¼ä¸ï¼ï¼K-è¿é»ï¼KNNï¼ç®æ³ï¼å²è¯çº§å¹²è´§é¿æï¼

       很容易发现,上面方程中数字差值最大的属性对计算结果的影响最大,也就是说,每年获取的飞行常客里程数对于计算结果的影响将远远大于其他两个特征-玩视频游戏所耗时间占比和每周消费冰淇淋公斤数的影响。而产生这种现象的唯一原因,仅仅是因为飞行常客里程数远大于其他特征值。在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化

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

添加autoNorm 

def autoNorm(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = np.zeros(np.shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - np.tile(minVals, (m, 1))
    normDataSet = normDataSet / np.tile(ranges, (m, 1))
    return normDataSet,ranges,minVals

对缩放结果进行优化

       大多数时候我们所面对的数据集都不是自己构造的,而且我们也未必知道,到底哪些变量是不重要的,而哪些变量又对计算结果有着重大的影响。理论上我们可以尝试大量不同的组合,直到发现一个足够好的结果为止,不过也许有数以百计的变量须要考查,并且这项工作可能会非常地乏味。利用模拟退火以及遗传算法等优化算法地一个好处在于,我们很快就能发觉哪些变量是重要地,并且其重要程度有多大。

不对称分布 

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 4)验证分类器

       通常我们只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。10%的测试数据应该是随机选择的。

添加代码:

def datingClassTest():
    filename = 'datingTestSet.txt'
    datingDataMat, datingLabels = file_matrix(filename)
    hoRatio = 0.10
    # 数据归一化,返回归一化的矩阵,数据范围,数据最小值
    normMat, ranges, minVals = autoNorm(datingDataMat)
    m = normMat.shape[0]
    # 取所有数据的10%
    numTestVecs = int(m * hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        classifierResult = knn01.classify0(normMat[i, :], normMat[numTestVecs:m, :],
                                           datingLabels[numTestVecs:m], 4)
        print('分类结果: %d\t 真实类别:%d' % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print('错误率:%f%%' % (errorCount / float(numTestVecs) * 100))


if __name__ == '__main__':
    datingClassTest()

result: 

 

       错误率是4%,这是一个想当不错的结果。我们可以改变函数 datingClassTest内变量hoRatio和分类器k的值,检测错误率是否随着变量值的变化而增加。依赖于分类算法、数据集和程序设置,分类器的输出结果可能有很大的不同。我试了一下改变hoRatio和分类器k的值,目前这两个参数错误率是最小的。 

6)构建完整可用系统

      给海伦一个小段程序,通过该程序海伦会在约会网站上找到某个人并输入他的信息。程序会给出她对男方喜欢程度的预测值。

添加代码

def classPerson():
    resultList = ['讨厌', '有些喜欢', '非常喜欢']
    precentTats = float(input("玩视频游戏所耗时间百分比:"))
    ffMiles = float(input("每年获得的飞行常客里程数:"))
    iceCream = float(input("每周消费的冰激淋公升数:"))
    filename = 'datingTestSet.txt'
    datingDataMat, datingLabels = file_matrix(filename)
    # 训练集归一化
    normMat, ranges, minVals = autoNorm(datingDataMat)
    # 生成NumPy数组,测试集
    inArr = np.array([ffMiles, precentTats, iceCream])
    # 测试集归一化
    norminArr = (inArr - minVals) / ranges
    classifierResult = knn01.classify0(norminArr, normMat, datingLabels, 3)
    print("你可能%s这个人" % (resultList[classifierResult - 1]))
if __name__ == '__main__':
    classPerson()

6、k-近邻算法实战之sklearn手写数字识别 

 sklearn.neighbors模块实现了k-近邻算法。

 Parameters

 

n_neighbors : int, optional (default = 5)

Number of neighbors to use by default for kneighbors queries.

weights : str or callable, optional (default = ‘uniform’)

weight function used in prediction. Possible values:

  • ‘uniform’ : uniform weights. All points in each neighborhood are weighted equally.
  • ‘distance’ : weight points by the inverse of their distance. in this case, closer neighbors of a query point will have a greater influence than neighbors which are further away.
  • [callable] : a user-defined function which accepts an array of distances, and returns an array of the same shape containing the weights.

algorithm : {‘auto’, ‘ball_tree’, ‘kd_tree’, ‘brute’}, optional

Algorithm used to compute the nearest neighbors:

  • ‘ball_tree’ will use BallTree
  • ‘kd_tree’ will use KDTree
  • ‘brute’ will use a brute-force search.
  • ‘auto’ will attempt to decide the most appropriate algorithm based on the values passed to fitmethod.

Note: fitting on sparse input will override the setting of this parameter, using brute force.

leaf_size : int, optional (default = 30)

Leaf size passed to BallTree or KDTree. This can affect the speed of the construction and query, as well as the memory required to store the tree. The optimal value depends on the nature of the problem.

p : integer, optional (default = 2)

Power parameter for the Minkowski metric. When p = 1, this is equivalent to using manhattan_distance (l1), and euclidean_distance (l2) for p = 2. For arbitrary p, minkowski_distance (l_p) is used.

metric : string or callable, default ‘minkowski’

the distance metric to use for the tree. The default metric is minkowski, and with p=2 is equivalent to the standard Euclidean metric. See the documentation of the DistanceMetric class for a list of available metrics.

metric_params : dict, optional (default = None)

Additional keyword arguments for the metric function.

n_jobs : int or None, optional (default=None)

The number of parallel jobs to run for neighbors search. None means 1 unless in a joblib.parallel_backend context. -1 means using all processors. See Glossary for more details. Doesn’t affect fit method.

  • n_neighbors:默认为5,就是k-NN的k的值,选取最近的k个点。
  • weights:默认是uniform,参数可以是uniform、distance,也可以是用户自己定义的函数。uniform是均等的权重,就说所有的邻近点的权重都是相等的。distance是不均等的权重,距离近的点比距离远的点的影响大。用户自定义的函数,接收距离的数组,返回一组维数相同的权重。
  • algorithm:快速k近邻搜索算法,默认参数为auto,可以理解为算法自己决定合适的搜索算法。除此之外,用户也可以自己指定搜索算法ball_tree、kd_tree、brute方法进行搜索,brute是蛮力搜索,也就是线性扫描,当训练集很大时,计算非常耗时。kd_tree,构造kd树存储数据以便对其进行快速检索的树形数据结构,kd树也就是数据结构中的二叉树。以中值切分构造的树,每个结点是一个超矩形,在维数小于20时效率高。ball tree是为了克服kd树高纬失效而发明的,其构造过程是以质心C和半径r分割样本空间,每个节点是一个超球体。
  • leaf_size:默认是30,这个是构造的kd树和ball树的大小。这个值的设置会影响树构建的速度和搜索速度,同样也影响着存储树所需的内存大小。需要根据问题的性质选择最优的大小。
  • metric:用于距离度量,默认度量是minkowski,也就是p=2的欧氏距离(欧几里德度量)
  • p:距离度量公式。这个参数默认为2,也就是默认使用欧式距离公式进行距离度量。也可以设置为1,使用曼哈顿距离公式进行距离度量
  • metric_params:距离公式的其他关键参数,这个可以不管,使用默认的None即可。
  • n_jobs:并行处理设置。默认为1,临近点搜索并行工作数。如果为-1,那么CPU的所有cores都用于并行工作。

 Methods

fit(X, y)Fit the model using X as training data and y as target values
get_params([deep])Get parameters for this estimator.
kneighbors([X, n_neighbors, return_distance])Finds the K-neighbors of a point.
kneighbors_graph([X, n_neighbors, mode])Computes the (weighted) graph of k-Neighbors for points in X
predict(X)Predict the class labels for the provided data
predict_proba(X)Return probability estimates for the test data X.
score(X, y[, sample_weight])Returns the mean accuracy on the given test data and labels.
set_params(**params)Set the parameters of this estimator.

       在安装sklearn之前,需要安装两个库,即numpy+mkl和scipy。不要使用pip3直接进行安装,因为pip3默安装的是numpy,而不是numpy+mkl。第三方库下载地址:http://www.lfd.uci.edu/~gohlke/pythonlibs/

import numpy as np
import operator
from os import listdir
from sklearn.neighbors import KNeighborsClassifier as kNN


def imgvector(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 returnVect


def handwritingClassTest():
    hwLabels = []
    trainingFileList = listdir('trainingDigits')  # ???
    m = len(trainingFileList)
    trainingMat = np.zeros((m, 1024))
    for i in range(m):
        fileNameStr = trainingFileList[i]
        classNumber = int(fileNameStr.split('_')[0])
        hwLabels.append(classNumber)
        trainingMat[i, :] = imgvector('trainDigits/%s' % (fileNameStr))
    # *****************************************************************************
    neigh = kNN(n_neighbors=3, algorithm='auto')
    neigh.fit(trainingMat, hwLabels)
    # *****************************************************************************
    testFileList = listdir('testDigits')
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        classNumber = int(fileNameStr.split('_')[0])
        vectorUnderTest = imgvector('testDigits/%s' % (fileNameStr))
        classifierResult = neigh.predict(vectorUnderTest)
        print('分类返回结果为%d\t真实结果为%d' % (classifierResult, classNumber))
        if (classifierResult != classNumber):
            errorCount += 1.0
    print('总共错了%d个数据\n错误率为%f%%' % (errorCount, errorCount / mTest * 100))


if __name__ == '__main__':
    handwritingClassTest()

       上述代码使用的algorithm参数是auto,更改algorithm参数为brute,使用暴力搜索,你会发现,运行时间变长了,变为10s+。更改n_neighbors参数,你会发现,不同的值,检测精度也是不同的。自己可以尝试更改这些参数的设置,加深对其函数的理解。

7、分析kNN

       对1NN在二分类问题上的性能做一个简单的讨论。给定测试样本x,若其最近邻样本为z,则最近邻分类器出错的概率就是x与z类别标记不同的概率,即

                                                                           P(err)=1-\sum_{c\epsilon y}^{ }P(c|x)P(c|z)

       假设样本独立同分布,且对任意x和任意小正数\delta ,在x 附近\delta距离范围内总能找到一个训练样本;换言之,对任意测试样本,总能在任意近的范围内找到训练样本z。

       令 c^*=argmax_{c\epsilon y}P(c|x) 表示贝叶斯最优分类器的结果

                                                                                P(err)=1-\sum_{c\epsilon y}^{ }P(c|x)P(c|z)

                                                                                              \simeq 1-\sum_{c\epsilon y}^{ }P^{2}(c|x)

                                                                                              \leq 1-P^2(c^*|x)

                                                                                              =(1+P(c^*|x))(1-P(c^*|x))

                                                                                              \leq 2*(1-P(c^*|x))

        于是我们得到了有点令人惊讶的结论:最近邻分类器虽简单,但它的泛化错误率不超过贝叶斯最优分类器的错误率的两倍

        这个结论基于一个重要假设:任意测试样本x附近任意小的 \delta 距离范围内总能找到一个训练样本,即训练样本的采样密度足够大,或称为“密采样”。若\delta =0.001,仅考虑单个属性,则仅需1000个样本点平均分布在归一化后的属性取值范围内,即可使得任意测试样本在其附近0.001距离范围内总能找到一个训练样本,此时最近邻分类器的错误率不超过贝叶斯最优分类器的错误率的两倍。这仅是属性维数为1的情形,若要求样本满足密采样条件,则至少需 (10^3)^{20}=10^{60} 个样本。高维空间会给距离计算带来很大的麻烦,例如当维数很高时甚至连计算内积都不再容易。

       在高维情形下出现的数据样本稀疏、距离计算困难等问题,被称为“维数灾难”(curse of dimensioonality)。缓解维数灾难的一个重要途径是降维,亦称“维数约简”(subspace)。为什么能进行降维?这是因为在很多时候,人们观测或收集到的数据样本虽高维的,但与学习任务密切相关的也许仅是某个低维分布,即高维空间中的一个低维“嵌入”

       流形学习(Manifold Learning)是机器学习中一大类算法的统称,而“多维缩放”(Multiple Dimensional Scalin,简称MDS)就是其中非常经典的一种方法。当n个对象(object)中各对对象之间的相似性(或距离)给定时,确定这些对象在低维空间中的表示,并使其尽可能与原先的相似性(或距离)“大体匹配”,使得由降维所引起的任何变形达到最小

       MDS内容丰富、方法较多。按相似性(距离)数据测量尺度的不同MDS可分为:度量MDS和非度量MDS。当利用原始相似性(距离)的实际数值为间隔尺度和比率尺度时称为度量MDS(metric MDS)。

       假定原始高维数据样本的距离矩阵为D,则在低维下的距离矩阵为Z,我们可以用优化算法选取初始点,用梯度下降法求最佳逼近,使得||D-Z||最小。

算法具体内容
通过输入的高维点集(I 个点),可以得到距离矩阵:

降维后的点集:x_i,i=1,2,...,I

我们要想办法,使得降维后的点生成的距离矩阵和高维点距离矩阵尽量相同。即,

                                                                                        \left \| x_i-x_j \right \|\approx \delta _{i,j}

构造降维后点集的矩阵

定义矩阵T

                                                                                          T=XX^T
其中,

                                                                                          t_{i,j}=x_ix_j 

如果对X进行去均值化的话,就有 
                                                                                   \sum_jx_j=\sum_ix_i=0 

对于距离矩阵--------------------------------------------------------------------------------------------------

                                                                 \delta^2_{ij}=(x_i-x_j)^T(x_i-x_j)=x^2_i+x^2_j-2x_i^Tx_j

                                                                                t_{ij}=-\frac{1}{2}(\delta^2_{ij}-x^2_i-x^2_j)

                                                             \sum_{j}^{ }\delta^2_{ij}=nx^2_i+\sum_{j}x^2_j-2x_i\sum_jx_j=nx^2_i+\sum_jx^2_j

                                                             \sum_{i}^{ }\delta^2_{ij}=nx^2_j+\sum_{i}x^2_i-2x_j\sum_ix_i=nx^2_j+\sum_ix^2_i

                                                                           \sum_{ij}\delta^2_{ij}=n\sum_ix^2_i+n\sum_jx^2_j


联立以上各式, 可以求得矩阵T。 
注意到,                                                                          

                                                                                         T=XX^T

对T进行特征分解, 

                                                                                        T=U\Lambda U^T     
即可得到X,也就是降维后的点集。 
                                                                                       X=U\sqrt {\Lambda} 

 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------

8、评价k-最邻近算法

优点

  • 简单好用,容易理解,精度高,理论成熟,既可以用来做分类也可以用来做回归;
  • 可用于数值型数据和离散型数据;
  • 训练时间复杂度为O(n);无数据输入假定;
  • 对异常值不敏感

缺点

  • 计算复杂性高;空间复杂性高;
  • 样本不平衡问题(即有些类别的样本数量很多,而其它样本的数量很少);
  • 一般数值很大的时候不用这个,计算量太大。但是单个样本又不能太少,否则容易发生误分。
  • 最大的缺点是无法给出数据的内在含义。

加权kNN

       实现加权kNN算法的代码与普通的kNN函数在执行过程上是相同的,函数首先获得经过排序的距离值,然后取距离最近的k个元素。与普通kNN相比,加权kNN算法最重要的区别在于,它并不是对这些元素简单的求平均,它求的是加权平均。加权平均的结果是通过将每一项的值乘以对应权重,然后将所得结果累加得到的。待求出总和以后,再将其除以所有权重之和。

1、为邻近分配权重

1)反函数

       该函数最为简单的一种形式是返回距离的倒数,不过有时候,完全一样或非常接近的商品,会使权重值变得非常之大,甚至是无穷大,基于这样的原因,有必要在对距离求倒数之前先加上一个小小的常量.

def inverseweight(dist,num=1.0,const=0.1):
    return num/(dist+const)

       缺陷在于它会为近邻项赋以很大的权重,而稍远一点的项,其权重则会‘衰减’得很快。这种情况也许正是我们所期望的,但有的时候,这也会使算法对噪声变的更加敏感

2)减法函数

def subtractweight(dist,const=1.0):
    if dist>const:
        return 0
    else:
        return const-dist

 

       该函数克服了反函数对近邻项权重分配过大的潜在问题,但是由于权重值最终会跌至0,因此,我们有可能找不到距离足够近的项,将其视作近邻,即:对于某些项,算法根本就无法作出预测。

3)高斯函数——‘钟型曲线’

#高斯函数
def gaussian(dist,sigma=10.0):
    return math.e**(-dist**2/(2*sigma**2))

       该函数在距离为0的时候所得的权重值为1,并且权重值会随着距离的增加而减少。不过,和剑法函数不同的是,这里的权重始终不会跌至0,因此,该方法总是可以作出预测的。高斯函数的代码有些复杂,而且,其执行速度不会像其他两个函数那样快。 

 

       对于一个特定的训练集而言,选择参数只须做一次即可,但随着训练集内容的增长,偶尔还须对其进行再次的更新。 

 

 

 

 

                   

 

 

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值