k-近邻算法(k-nearest neighbor)

文章目录

  • 1.原理
  • 2.距离计算
  • 3.算法过程
  • 4.K值得选择
  • 5.优缺点
  • 6.k近邻的实现:kd树
  • 7.代码
    • 7.1.原理的实现
    • 7.2.sklearn的实现
  • 8.代码和数据
  • 9.参考

1.原理

给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的k个实例,这k个实例的多数实例属于某个类别,就把这个输入实例分为这个类。

算法:

输入:训练数据T= ( x ( 1 ) , y ( 1 ) ) , ( x ( 2 ) , y ( 2 ) ) , . . . , ( x ( N ) , y ( N ) ) (x^{(1)},y^{(1)}),(x^{(2)},y^{(2)}),...,(x^{(N)},y^{(N)}) (x(1),y(1)),(x(2),y(2)),...,(x(N),y(N)),其中,,i=1,2,3…N,测试实例x。
输出:测试实例x所属的类别y
(1)根究给定的距离度量,在训练集T中找出与x最邻近的k个点。
(2)计算这k个点的各个类别的样本数,选取样本数最多的类别y。
在这里插入图片描述
I为指示函数, y i = c i y_i=c_i yi=ci时I为1,否则为0.

重要的是K值的选取和点距离的计算
和k-means的差别:就是画一个圈,KNN是让进来圈子里的人变成自己人,Kmeans是让原本在圈内的人归成一类人。

2.距离计算

特征空间中两个实例点的距离是两个实例点相似程度的放映。
x ( i ) = ( x 1 ( i ) , y 1 ( i ) ) , ( x 2 ( i ) , y 2 ( i ) ) , . . . , ( x n ( i ) , y n ( i ) ) x^{(i)}=(x^{(i)}_1,y^{(i)}_1),(x_2^{(i)},y_2^{(i)}),...,(x_n^{(i)},y_n^{(i)}) x(i)=(x1(i),y1(i)),(x2(i),y2(i)),...,(xn(i),yn(i))
x ( j ) = ( x 1 ( j ) , y 1 ( j ) ) , ( x 2 ( j ) , y 2 ( j ) ) , . . . , ( x n ( j ) , y n ( j ) ) x^{(j)}=(x^{(j)}_1,y^{(j)}_1),(x_2^{(j)},y_2^{(j)}),...,(x_n^{(j)},y_n^{(j)}) x(j)=(x1(j),y1(j)),(x2(j),y2(j)),...,(xn(j),yn(j))
x ( i ) x^{(i)} x(i) x ( j ) x^{(j)} x(j)的距离定义为:
在这里插入图片描述

  • 当p=2时,称为欧氏距离(Euclidean distance),即
    在这里插入图片描述
  • 当p=1时,称为曼哈顿距离(Manhattan distance),即
    在这里插入图片描述
  • 当p=无穷大时,它是各个坐标距离的最大值,即

在这里插入图片描述

二维空间两个点的欧式距离计算公式如下:
在这里插入图片描述
这个高中应该就有接触到的了,其实就是计算(x1,y1)和(x2,y2)的距离。拓展到多维空间,则公式变成这样:

在这里插入图片描述
KNN算法最简单粗暴的就是将预测点与所有点距离进行计算,然后保存并排序,选出前面K个值看看哪些类别比较多。但其实也可以通过一些数据结构来辅助,比如最大堆,这里就不多做介绍,有兴趣可以百度最大堆相关数据结构的知识。

3.算法过程

图中绿色的点就是我们要预测的那个点,假设K=3。那么KNN算法就会找到与它距离最近的三个点(这里用圆圈把它圈起来了),看看哪种类别多一些,比如这个例子中是蓝色三角形多一些,新来的绿色点就归类到蓝三角了。
k-近邻算法(k-nearest neighbor)_第1张图片

但是,当K=5的时候,判定就变成不一样了。这次变成红圆多一些,所以新来的绿点被归类成红圆。从这个例子中,我们就能看得出K的取值是很重要的。

k-近邻算法(k-nearest neighbor)_第2张图片

4.K值得选择

k值的选择会对k近邻法的结果产生重大影响。

  • 如果选择较小的k值,就相当于用较小的邻域中的训练实例进行预测,“学习”的近似误差(approximation error)会减小,只有与输入实例较近的(相似的)训练实例才会对预测结果起作用。但缺点是“学习”的估计误差(estimation error)会増大,预测结果会对近邻的实例点非常敏感。如果邻近的实例点恰巧是噪声,预测就会出错。换句话说,A值的减小就意味着整体模型变得复杂,容易发生过拟合

  • 如果选择较大的k值,就相当于用较大邻域中的训练实例进行预测。其优点是可以减少学习的估计误差。但缺点是学习的近似误差会增大。这时与输入实例较远的(不相似的)训练实例也会对预测起作用,使预测发生错误。k值的増大就意味着整体的模型变得简单。

  • 如果k = N,那么无论输入实例是什么,都将简单地预测它属于在训练实例
中最多的类别。这时,模型过于简单,完全忽略训练实例中的大量有用信息,是不可取的。

在应用中,k值一般取一个比较小的数值。通常釆用交叉验证法来选取最优的k值。或是逐一计算检测。

估计误差:度量预测结果与最优结果的相近程度
近似误差:度量预测结果与最优误差之间的相近程度

5.优缺点

优点:精度高、对异常值不敏感、 数据输入假定。
缺点:计算复杂度高、空间复杂度高。
适用数据范围:数值型和标称型。

6.k近邻的实现:kd树

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

7.代码

7.1.原理的实现

# coding: utf-8
# Author: shelley
# 2020/5/916:05
from numpy import *
import numpy as np
import operator
from os import listdir


def classify0(inX, dataSet, labels, k):
    """
    knn分类算法
    :param inX: (1,3)
    :param dataSet: (n,3)
    :param labels: (n,3)
    :param k: int, k个相似度样本最大的样本,这些样本中相同标签最大的为测试样本的标签
    :return:测试样本的标签
    """
    dataSetSize = dataSet.shape[0]  # 样本个数
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet  # 测试样本-训练样本
    sqDiffMat = diffMat ** 2  # 平方差
    sqDistances = sqDiffMat.sum(axis=1)  # 将每一行的3个特征进行相加 sum(0)列相加,sum(1)行相加
    distances = sqDistances ** 0.5  # 开方
    sortedDistIndicies = distances.argsort()  # argsort函数返回的是distances值从小到大的--索引值
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]


def createDataSet():
    group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
    labels = ['A', 'A', 'B', 'B']
    return group, labels


def file2matrix(filename):
    """
    读取文件的数据
    :param filename: 文件名
    :return: 特征矩阵[fea1,fea2,fea3](n*3),标签[label](n*1)
    """
    fr = open(filename)
    numberOfLines = len(fr.readlines())  # 获得文件的所有文本行
    returnMat = zeros((numberOfLines, 3))  # 定义存储数据的矩阵
    classLabelVector = []  # 定义存储标签的矩阵
    fr = open(filename)
    index = 0  # 表示第几个样本
    for line in fr.readlines():
        line = line.strip()  # 去掉每一行首尾的空白符,例如'\n','\r','\t',' '
        listFromLine = line.split('\t')  # 每一行根据\t进行切片
        returnMat[index, :] = listFromLine[0:3]  # 前三列是特征
        classLabelVector.append(int(listFromLine[-1]))  # 最后一列是标签
        index += 1
    return returnMat, classLabelVector


def autoNorm(dataSet):
    """
    数据归一化
    :param dataSet: 特征矩阵[fea1,fea2,fea3](n*3)
    :return:归一化的特征矩阵,特征取值范围(1,3),特征最小值(1,3)
    """
    minVals = dataSet.min(0)  # (1,3) 每一列的最小值,即一个特征中所有样本的最小值
    maxVals = dataSet.max(0)  # (1,3) dataSet.min(1),每一行的最小值
    ranges = maxVals - minVals  # 每个特征的取值范围
    m = dataSet.shape[0]  # 样本个数
    #  原始值减去最小值(x-xmin)
    normDataSet = dataSet - np.tile(minVals, (m, 1))  # (n,3)复制n个minVals,
    # 差值处以最大值和最小值的差值(x-xmin)/(xmax-xmin)
    normDataSet = normDataSet / np.tile(ranges, (m, 1))  # element wise divide
    return normDataSet, ranges, minVals


def datingClassTest():
    """
    dating数据分类
    :return:
    """
    hoRatio = 0.1  # 保留10%作为测试集
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')  # 加载数据
    normMat, ranges, minVals = autoNorm(datingDataMat)  # 数据归一化,返回归一化数据结果,数据范围,最小值
    m = normMat.shape[0]  # 样本个数
    numTestVecs = int(m * hoRatio)  # 测试样本个数
    errorCount = 0.0
    for i in range(numTestVecs):  # 对每个样本进行测试
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
        print("knn获得的标签: %d, 真实标签: %d" % (classifierResult, datingLabels[i]))

        if (classifierResult != datingLabels[i]): errorCount += 1.0
    print ("错误率: %f %%" % (errorCount / float(numTestVecs)*100))

    print(errorCount)


def img2vector(filename):
    """

    :param filename:  文件名,有32行,一行32个字符
    :return:  文件里的数据,(1,1024)
    """
    returnVect = zeros((1, 1024))  # 定义(1,1024)形状的变量,32*32=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])  # 每行数据顺序存储在returnVect
    return returnVect


def handwritingClassTest():
    """
    手写数字分类
    :return:
    """
    hwLabels = []
    trainingFileList = listdir('trainingDigits')  # 获得trainingDigits下所有文件的路径
    m = len(trainingFileList)  # 文件个数
    trainingMat = zeros((m, 1024))  # 样本个数*特征维度
    for i in range(m):
        fileNameStr = trainingFileList[i]  # 获得其中一个文件名testDigits\0_0.txt
        fileStr = fileNameStr.split('.')[0]  # 去掉 .txt
        classNumStr = int(fileStr.split('_')[0])  # 按_进行切分,第一个字符串是类别
        hwLabels.append(classNumStr)
        trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)  # 获得特征矩阵
    testFileList = listdir('testDigits')  # 读取测试数据
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]  # take off .txt
        classNumStr = int(fileStr.split('_')[0])
        vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print("分类结果为: %d, 真实结果为: %d" % (classifierResult, classNumStr))

        if (classifierResult != classNumStr): errorCount += 1.0
    print("\n分类错误的个数: %d" % errorCount)

    print("\n错误率: %f" % (errorCount / float(mTest)))


if __name__ == '__main__':
    # dating数据分类
    # datingClassTest()
    # 手写数字分类
    handwritingClassTest()

7.2.sklearn的实现

KNeighborsClassifier(n_neighbors = 5,
weights=‘uniform’,
algorithm = ‘’,
leaf_size = ‘30’,
p = 2,
metric = ‘minkowski’,
metric_params = None,
n_jobs = None
)

(1)n_neighbors:这个值就是指 KNN 中的 “K”了。前面说到过,通过调整 K 值,算法会有不同的效果。
(2)weights(权重):最普遍的 KNN 算法无论距离如何,权重都一样,但有时候我们想搞点特殊化,比如距离更近的点让它更加重要。这时候就需要 weight 这个参数了,这个参数有三个可选参数的值,决定了如何分配权重。参数选项如下:
• ‘uniform’:不管远近权重都一样,就是最普通的 KNN 算法的形式。
• ‘distance’:权重和距离成反比,距离预测目标越近具有越高的权重。
• 自定义函数:自定义一个函数,根据输入的坐标值返回对应的权重,达到自定义权重的目的。
(3)algorithm:在 sklearn 中,要构建 KNN 模型有三种构建方式,1. 暴力法,就是直接计算距离存储比较的那种放松。2. 使用 kd 树构建 KNN 模型 3. 使用球树构建。 其中暴力法适合数据较小的方式,否则效率会比较低。如果数据量比较大一般会选择用 KD 树构建 KNN 模型,而当 KD 树也比较慢的时候,则可以试试球树来构建 KNN。参数选项如下:
• ‘brute’ :蛮力实现
• ‘kd_tree’:KD 树实现 KNN
• ‘ball_tree’:球树实现 KNN
• ‘auto’: 默认参数,自动选择合适的方法构建模型
不过当数据较小或比较稀疏时,无论选择哪个最后都会使用 ‘brute’
(4)leaf_size:如果是选择蛮力实现,那么这个值是可以忽略的,当使用KD树或球树,它就是是停止建子树的叶子节点数量的阈值。默认30,但如果数据量增多这个参数需要增大,否则速度过慢不说,还容易过拟合。
(5)p:和metric结合使用的,当metric参数是"minkowski"的时候,p=1为曼哈顿距离, p=2为欧式距离。默认为p=2。
(6)metric:指定距离度量方法,一般都是使用欧式距离。
• ‘euclidean’ :欧式距离
• ‘manhattan’:曼哈顿距离
• ‘chebyshev’:切比雪夫距离
• ‘minkowski’: 闵可夫斯基距离,默认参数
(7)n_jobs:指定多少个CPU进行运算,默认是-1,也就是全部都算。

cross_val_score(estimator,
X,
y=None,
groups=None,
scoring=None,
cv=None,
n_jobs=1,
verbose=0,
fit_params=None,
pre_dispatch=‘2*n_jobs’)

(1)estimator : 分类对象,并有fit函数的实现,用来训练数。
(2)X:用来训练的数据
(3)y : 目标数据
(4)groups : array-like形状 (n_samples,)组标签,用来把数据分成训练和测试集
(5)scoring : 计分的方式
(6)cv : 将数据分成的分数

# coding: utf-8
# Author: shelley
# 2020/5/1116:40

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import ListedColormap

from sklearn.datasets import load_iris
from sklearn.model_selection import cross_val_score
from sklearn.neighbors import KNeighborsClassifier
from sklearn import neighbors

iris = load_iris()
x = iris.data
y = iris.target

# 确定k的值
k_range = range(1,31)
k_error = []
for k in k_range:
    knn = KNeighborsClassifier(n_neighbors=k)
    scores = cross_val_score(knn, x, y, cv=6, scoring='accuracy')
    k_error.append(1-scores.mean())
plt.plot(k_range, k_error)
plt.xlabel('value of k for knn')
plt.ylabel('error')
plt.show()

n_neighbors = 11
h = .02  # 网格中的步长

# 创建彩色的图
cmap_light = ListedColormap(['#FFAAAA', '#AAFFAA', '#AAAAFF'])
cmap_bold = ListedColormap(['#FF0000', '#00FF00', '#0000FF'])

# weights,两种权重参数下KNN的效果图
for weights in ['uniform', 'distance']:
    # 创建了一个knn分类器的实例,并拟合数据。
    clf = neighbors.KNeighborsClassifier(n_neighbors, weights=weights)
    clf.fit(x, y)

    # 绘制决策边界。为此,我们将为每个分配一个颜色
    # 来绘制网格中的点 [x_min, x_max]x[y_min, y_max].
    x_min, x_max = x[:, 0].min() - 1, x[:, 0].max() + 1
    y_min, y_max = x[:, 1].min() - 1, x[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                         np.arange(y_min, y_max, h))
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])

    # 将结果放入一个彩色图中
    Z = Z.reshape(xx.shape)
    plt.figure()
    plt.pcolormesh(xx, yy, Z, cmap=cmap_light)

    # 绘制训练点
    plt.scatter(x[:, 0], x[:, 1], c=y, cmap=cmap_bold)
    plt.xlim(xx.min(), xx.max())
    plt.ylim(yy.min(), yy.max())
    plt.title("3-Class classification (k = %i, weights = '%s')"
              % (n_neighbors, weights))

plt.show()

8.代码和数据

链接:https://pan.baidu.com/s/1QbeG8g_9JhVwJRhERViTMg
提取码:80su

9.参考

机器学习实战
一些博客,如有侵权,请联系。

你可能感兴趣的:(机器学习,机器学习)