机器学习:KNN(K 近邻)分类算法

kNN 算法不需要经过算法训练,属于懒惰学习,需要训练的属于急切学习
kNN 是最简单的分类算法

优点:精度高、对异常值不敏感、无数据输入假定
缺点:计算复杂度高、空间复杂度高

工作原理:

  • 输入没有标签的新数据后,将新数据的每个特征与样本集中每个数据对应的特征进行比较,然后算法提取样本集中 k 个特征最相似的数据(最近邻)的分类标签
  • 通过距离矩阵计算相似度(距离):比如 (1, 2, 0, 1) 与 (7, 6, 9, 4) 的距离
      \(\small d = \sqrt{(7-1)^{2} + (6-2)^{2} + (9-0)^{2} + (4-1)^{2}}\)
  • 通常 k 是不大于 20 的整数,选择 k 个最相似数据中出现次数最多的分类,作为新数据的分类
# coding=utf-8
import numpy as np
import operator as op


# KNN 分类算法
def classify(inX, dataSet, labels, k):
    """
    inX     - 要预测的特征值矩阵,是一个 (1, n) 的 numpy 矩阵,n 是特征的数量
    dataSet - 样本的特征值矩阵,是一个 (r, n) 的 numpy 矩阵,r 是样本数量,n 是特征的数量
    labels  - 样本的标签,是一个长度为 r 的列表
    k       - 取最近的 k 个样本
    """

    # 取样本的数量
    dataSetSize = dataSet.shape[0]

    # tile 将 inX 按 (dataSetSize, 1) 平铺,结果是 (r, n) 矩阵,矩阵的每列都是 inX,然后和样本矩阵相减
    # 得到 diffMat 是 inX 的每个特征值和所有样本的差
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet

    # sum(axis=1) 指按第二个下标求和,即
    #     sqDiffMat[0][0] + sqDiffMat[0][1] + sqDiffMat[0][2] + ...... + sqDiffMat[0][n-1]
    #     sqDiffMat[1][0] + sqDiffMat[1][1] + sqDiffMat[0][2] + ...... + sqDiffMat[1][n-1]
    # 得到一个 (r, 1) 矩阵
    # 再开根,得到 inX 和每个样本的距离
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5

    # 对距离按从小到大排序,并返回索引值,即 distances[sortedDistIndex[0]] 是 distances 的最小值
    sortedDistIndex = distances.argsort()

    classCount = {}
    # 取距离最近的 k 个样本
    for i in range(k):
        # 取样本的标签值
        votedLabel = labels[sortedDistIndex[i]]
        # 统计每个标签值的数量
        classCount[votedLabel] = classCount.get(votedLabel, 0) + 1

    # classCount.iteritems 得到为迭代器
    # op.itemgetter(1) 取每个数据的第二个域的值,即 label 的次数
    # 从大到小排序
    sortedClassCount = sorted(classCount.iteritems(), key=op.itemgetter(1), reverse=True)

    # 返回距离最近的 k 个样本中出现次数最多的标签值
    return sortedClassCount[0][0]


# 从文件中取样本数据
def readDataFromFile(filename, featureNumber):
    """
    文件的每一行是一条数据,最后一列是标签,其余列是特征值,共 fieldNumber 个特征
    """
    numberOfLines = 0
    with open(filename) as f:
        # 统计行数,数据量太大时 len(f.readlines()) 可能无法工作,所以采用循环的方式
        while True:
            tempBuffer = f.read(8192 * 1024)
            if not tempBuffer:
                numberOfLines = numberOfLines + 1
                break
            numberOfLines += tempBuffer.count('\n')

    # 特征值矩阵
    featureMat = np.zeros((numberOfLines, featureNumber))
    # 标签列表
    classLabelVector = []

    with open(filename) as f:
        index = 0
        while True:
            line = f.readline()
            if line:
                line = line.strip()
                fields = line.split('\t')
                featureMat[index, :] = fields[0:featureNumber]
                classLabelVector.append(int(fields[featureNumber]))
                index += 1
            else:
                break

    return featureMat, classLabelVector


# 归一化数据
def normalize(dataSet):
    # dataSet.min(0) 按第一个下标求最小值,即求
    #   min(dataSet[0][0], dataSet[1][0], dataSet[2][0], ..., dataSet[m][0])
    #   min(dataSet[0][1], dataSet[1][1], dataSet[2][1], ..., dataSet[m][1])
    # 结果是求每列,即每个特征值的最大和最小值
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)

    # 最大值和最小值的差
    ranges = maxVals - minVals

    # np.tile(minVals, (m, 1)) 复制 m 个 minVals,方便矩阵的计算
    # 归一化 newValue = (oldValue - min)/(max - min) 将所有数据转换到 (0, 1)
    m = dataSet.shape[0]
    normDataSet = dataSet - np.tile(minVals, (m, 1))
    normDataSet = normDataSet/np.tile(ranges, (m, 1))

    return normDataSet


# 测试
def classifyTest():
    # 10% 的数据作测试集,90% 的数据作样本集
    ratio = 0.10

    # 读数据
    featureNumber = 10
    featureMat, classLabelVector = readDataFromFile('kNN.txt', featureNumber)

    # 归一化
    normDataSet = normalize(featureMat)

    # 数据量
    m = normDataSet.shape[0]

    # 测试集大小
    numTest = int(m * ratio)

    errorCount = 0
    for i in range(numTest):
        result = classify(normDataSet[i, :], normDataSet[numTest:m, :], classLabelVector[numTest:], featureNumber)
        if result != classLabelVector[i]:
            errorCount += 1

        print "predict result: %d, real answer is: %d" % (result, classLabelVector[i])

    print "error count: %d, total: %d" % (errorCount, numTest)
    print "the total error rate is: %f" % (errorCount / float(numTest))




猜你喜欢

转载自www.cnblogs.com/moonlight-lin/p/12297625.html