AI基础:KNN与K近邻距离度量说明、利用KNN手写字体识别分类实践

KNN k近邻

文章目录

  • KNN算法
  • K近邻中近邻的距离度量
    • 欧式距离
    • 标准化欧式距离
    • 曼哈顿距离
    • 汉明距离
    • 夹角余弦
    • 杰卡德相似系数
    • 皮尔逊系数
    • 切比雪夫距离
    • 闵可夫斯基距离
    • 马氏距离
    • 巴氏距离
    • 各种“距离”的应用场景
    • 距离函数之间的等价关系
  • K近邻中K值的选择
  • KNN最近邻分类算法的过程
  • 基于KNN的手写字体识别分类实践

KNN算法

何谓K近邻算法,即K-Nearest Neighbor algorithm,简称KNN算法,单从名字来猜想,可以简单粗暴的认为是:K个最近的邻居,当K=1时,算法便成了最近邻算法,即寻找最近的那个邻居。

用官方的话来说,所谓K近邻算法,即是给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的K个实例(也就是上面所说的K个邻居),这K个实例的多数属于某个类,就把该输入实例分类到这个类中。

AI基础:KNN与K近邻距离度量说明、利用KNN手写字体识别分类实践_第1张图片

AI基础:KNN与K近邻距离度量说明、利用KNN手写字体识别分类实践_第2张图片

  • 如上图所示,有两类不同的样本数据,分别用蓝色的小正方形和红色的小三角形表示,而图正中间的那个绿色的圆所标示的数据则是待分类的数据。也就是说,现在,我们不知道中间那个绿色的数据是从属于哪一类(蓝色小正方形or红色小三角形),KNN就是解决这个问题的。

  • 如果K=3,绿色圆点的最近的3个邻居是2个红色小三角形和1个蓝色小正方形,少数从属于多数,基于统计的方法,判定绿色的这个待分类点属于红色的三角形一类。

  • 如果K=5,绿色圆点的最近的5个邻居是2个红色三角形和3个蓝色的正方形,还是少数从属于多数,基于统计的方法,判定绿色的这个待分类点属于蓝色的正方形一类。

于此我们看到,当无法判定当前待分类点是从属于已知分类中的哪一类时,我们可以依据统计学的理论看它所处的位置特征,衡量它周围邻居的权重,而把它归为(或分配)到权重更大的那一类。这就是K近邻算法的核心思想。

K近邻中近邻的距离度量

我们看到,K近邻算法的核心在于找到实例点的邻居,这个时候,问题就接踵而至了,如何找到邻居,邻居的判定标准是什么,用什么来度量。这一系列问题便是下面要讲的距离度量表示法。

欧式距离

重要

最常见的两点之间或多点之间的距离表示法,又称之为欧几里得度量,它定义于欧几里得空间中,如
点 x = ( x 1 , . . . , x n ) 和 y = ( y 1 , . . . , y n ) 之 间 的 距 离 为 : d ( x , y ) = ( x 1 − y 1 ) 2    +    ( x 2 − y 2 ) 2 + . . + ( x n − y n ) 2 = Σ n i = 1 ( x i − y i ) 2 点x=(x_1,...,x_n)和y=(y_1,...,y_n)之间的距离为: \\ d\left( x,y \right) =\sqrt{\left( x_1-y_1 \right) ^2\,\,+\,\,\left( x_2-y_2 \right) ^2+..+\left( x_n-y_n \right) ^2}=\sqrt{\underset{i=1}{\overset{n}{\varSigma}}\left( x_i-y_i \right) ^2} x=(x1,...,xn)y=(y1,...,yn)d(x,y)=(x1y1)2+(x2y2)2+..+(xnyn)2 =i=1Σn(xiyi)2

  • 二维平面上两点a(x1,y1)与b(x2,y2)间的欧氏距离:

    d 12 = ( x 1 − x 2 ) 2    +    ( y 1 − y 2 ) 2 d_{12}=\sqrt{\left( x_1-x_2 \right) ^2\,\,+\,\,\left( y_1-y_2 \right) ^2} d12=(x1x2)2+(y1y2)2

  • 三维空间两点a(x1,y1,z1)与b(x2,y2,z2)间的欧氏距离:

    d 12 = ( x 1 − x 2 ) 2    +    ( y 1 − y 2 ) 2 +    ( z 1 − z 2 ) 2 d_{12}=\sqrt{\left( x_1-x_2 \right) ^2\,\,+\,\,\left( y_1-y_2 \right) ^2+\,\,\left( z_1-z_2 \right) ^2} d12=(x1x2)2+(y1y2)2+(z1z2)2

  • 两个n维向量a(x11,x12,…,x1n)与 b(x21,x22,…,x2n)间的欧氏距离:

    d 12 = Σ n k = 1 ( x 1 k − x 2 k ) 2 d_{12}=\sqrt{\underset{k=1}{\overset{n}{\varSigma}}\left( x_{1k}-x_{2k} \right) ^2} d12=k=1Σn(x1kx2k)2

    也可以用表示成向量运算的形式:

    d 12 = ( a − b ) ( a − b ) T d_{12}=\sqrt{\left( a-b \right) \left( a-b \right) ^T} d12=(ab)(ab)T

标准化欧式距离

重要

准化欧氏距离是针对简单欧氏距离的缺点而作的一种改进方案。标准欧氏距离的思路:既然数据各维分量的分布不一样,那先将各个分量都“标准化”到均值、方差相等。至于均值和方差标准化到多少,先复习点统计学知识。

假设样本集X的数学期望或均值(mean)为m,标准差(standard deviation,方差开根)为s,那么X的“标准化变量”X*表示为:(X-m)/s,而且标准化变量的数学期望为0,方差为1。
即,样本集的标准化过程(standardization)用公式描述就是:

X ∗ = X − m s X^*=\frac{X-m}{s} X=sXm

标准化后的值 = ( 标准d化前的值 - 分量的均值 ) /分量的标准差  
经过简单的推导就可以得到两个n维向量a(x11,x12,…,x1n)与 b(x21,x22,…,x2n)间的标准化欧氏距离的公式:
d 12 = Σ n k = 1 ( x 1 k − x 2 k s k ) 2 d_{12}=\sqrt{\underset{k=1}{\overset{n}{\varSigma}}\left( \frac{x_{1k}-x_{2k}}{s_k} \right) ^2} d12=k=1Σn(skx1kx2k)2

曼哈顿距离

重要

我们可以定义曼哈顿距离的正式意义为L1-距离或城市区块距离,也就是在欧几里得空间的固定直角坐标系上两点所形成的线段对轴产生的投影的距离总和。例如在平面上,坐标(x1, y1)的点P1与坐标(x2, y2)的点P2的曼哈顿距离为: ,要注意的是,曼哈顿距离依赖座标系统的转度,而非系统在座标轴上的平移或映射。

通俗来讲,想象你在曼哈顿要从一个十字路口开车到另外一个十字路口,驾驶距离是两点间的直线距离吗?显然不是,除非你能穿越大楼。而实际驾驶距离就是这个“曼哈顿距离”,此即曼哈顿距离名称的来源, 同时,曼哈顿距离也称为城市街区距离(City Block distance)。

  • 二维平面两点a(x1,y1)与b(x2,y2)间的曼哈顿距离

    d 12 = ∣ x 1 − x 2 ∣ +    ∣ y 1 − y 2 ∣ d_{12}=|x_1-x_2| +\,\,|y_1-y_2| d12=x1x2+y1y2

  • 两个n维向量a(x11,x12,…,x1n)与 b(x21,x22,…,x2n)间的曼哈顿距离

    d 12 = Σ n k = 1 ∣ x 1 k − x 2 k ∣ d_{12}=\underset{k=1}{\overset{n}{\varSigma}}|x_{1k}-x_{2k}| d12=k=1Σnx1kx2k

汉明距离

重要

两个等长字符串s1与s2之间的汉明距离定义为将其中一个变为另外一个所需要作的最小替换次数。例如字符串“1111”与“1001”之间的汉明距离为2。应用:信息编码(为了增强容错性,应使得编码间的最小汉明距离尽可能大)。

夹角余弦

重要

几何中夹角余弦可用来衡量两个向量方向的差异,机器学习中借用这一概念来衡量样本向量之间的差异。

  • 在二维空间中向量A(x1,y1)与向量B(x2,y2)的夹角余弦公式:

    cos ⁡ θ = x 1 x 2 + y 1 y 2 ( x 1 2 + y 1 2 ) ( x 2 2 + y 2 2 ) \cos \theta =\frac{x_1x_2+y_1y_2}{\sqrt{\left( {x_1}^2+{y_1}^2 \right)}\sqrt{\left( {x_2}^2+{y_2}^2 \right)}} cosθ=(x12+y12) (x22+y22) x1x2+y1y2

  • 两个n维样本点a(x11,x12,…,x1n)和b(x21,x22,…,x2n)的夹角余弦:

    cos ⁡ θ = a ⋅ b ∣ a ∣ ∣ b ∣ \cos \theta =\frac{a\cdot b}{|a||b|} cosθ=abab

夹角余弦取值范围为[-1,1]。夹角余弦越大表示两个向量的夹角越小,夹角余弦越小表示两向量的夹角越大。当两个向量的方向重合时夹角余弦取最大值1,当两个向量的方向完全相反夹角余弦取最小值-1。

杰卡德相似系数

重要

两个集合A和B的交集元素在A,B的并集中所占的比例,称为两个集合的杰卡德相似系数,用符号J(A,B)表示。杰卡德相似系数是衡量两个集合的相似度一种指标。
J ( A , B ) = ∣ A ∩ B ∣ ∣ A ∪ B ∣ J\left( A,B \right) =\frac{|A\cap B|}{|A\cup B|} J(A,B)=ABAB

与杰卡德相似系数相反的概念是杰卡德距离:
J δ ( A , B ) = 1 − J ( A , B ) = ∣ A ∪ B ∣ − ∣ A ∩ B ∣ ∣ A ∪ B ∣ J_{\delta}\left( A,B \right) =1-J\left( A,B \right) =\frac{|A\cup B|-|A\cap B|}{|A\cup B|} Jδ(A,B)=1J(A,B)=ABABAB

皮尔逊系数

重要

统计学中,皮尔逊积矩相关系数用于度量两个变量X和Y之间的相关(线性相关),其值介于-1与1之间。通常情况下通过以下取值范围判断变量的相关强度:
皮尔森相关系数等于两个变量的协方差除于两个变量的标准差

0.8-1.0     极强相关
0.6-0.8     强相关
0.4-0.6     中等程度相关
0.2-0.4     弱相关
0.0-0.2     极弱相关或无相关

切比雪夫距离

若二个向量或二个点p 、and q,其座标分别为Pi及qi,则两者之间的切比雪夫距离定义如下:

这也等于以下Lp度量的极值: ,因此切比雪夫距离也称为L∞度量。

以数学的观点来看,切比雪夫距离是由一致范数(uniform norm)(或称为上确界范数)所衍生的度量,也是超凸度量(injective metric space)的一种。

在平面几何中,若二点p及q的直角坐标系坐标为(x1,y1)及(x2,y2),则切比雪夫距离为:

玩过国际象棋的朋友或许知道,国王走一步能够移动到相邻的8个方格中的任意一个。那么国王从格子(x1,y1)走到格子(x2,y2)最少需要多少步?。你会发现最少步数总是max( | x2-x1 | , | y2-y1 | ) 步 。有一种类似的一种距离度量方法叫切比雪夫距离。

  • 二维平面两点a(x1,y1)与b(x2,y2)间的切比雪夫距离 :

  • 两个n维向量a(x11,x12,…,x1n)与 b(x21,x22,…,x2n)间的切比雪夫距离:

闵可夫斯基距离

(Minkowski Distance),闵氏距离不是一种距离,而是一组距离的定义。

两个n维变量a(x11,x12,…,x1n)与 b(x21,x22,…,x2n)间的闵可夫斯基距离定义为:

其中p是一个变参数。
当p=1时,就是曼哈顿距离
当p=2时,就是欧氏距离
当p→∞时,就是切比雪夫距离
根据变参数的不同,闵氏距离可以表示一类的距离。

马氏距离

有M个样本向量X1~Xm,协方差矩阵记为S,均值记为向量μ,则其中样本向量X到u的马氏距离表示为:

  • 若协方差矩阵是单位矩阵(各个样本向量之间独立同分布),则公式就成了,也就是欧氏距离了:

  • 若协方差矩阵是对角矩阵,公式变成了标准化欧氏距离。

马氏距离的优缺点:量纲无关,排除变量之间的相关性的干扰。

巴氏距离

在统计中,巴氏距离距离测量两个离散或连续概率分布的相似性。它与衡量两个统计样品或种群之间的重叠量的巴氏距离系数密切相关。巴氏距离距离和巴氏距离系数以20世纪30年代曾在印度统计研究所工作的一个统计学家A. Bhattacharya命名。同时,Bhattacharyya系数可以被用来确定两个样本被认为相对接近的,它是用来测量中的类分类的可分离性。

对于离散概率分布 p和q在同一域 X,它被定义为:

其中:

是Bhattacharyya系数。

各种“距离”的应用场景

  • 空间:欧氏距离,
  • 路径:曼哈顿距离,国际象棋国王:切比雪夫距离,
  • 以上三种的统一形式:闵可夫斯基距离,
  • 加权:标准化欧氏距离,
  • 排除量纲和依存:马氏距离,
  • 向量差距:夹角余弦,
  • 编码差别:汉明距离,
  • 集合近似度:杰卡德类似系数与距离,
  • 相关:相关系数与相关距离。

距离函数之间的等价关系

经过标准化后的欧氏距离和余弦相似度,等价于皮尔森相关系数

K近邻中K值的选择

  1. 如果选择较小的K值,就相当于用较小的领域中的训练实例进行预测,“学习”近似误差会减小,只有与输入实例较近或相似的训练实例才会对预测结果起作用,与此同时带来的问题是“学习”的估计误差会增大,换句话说,K值的减小就意味着整体模型变得复杂,容易发生过拟合;
  2. 如果选择较大的K值,就相当于用较大领域中的训练实例进行预测,其优点是可以减少学习的估计误差,但缺点是学习的近似误差会增大。这时候,与输入实例较远(不相似的)训练实例也会对预测器作用,使预测发生错误,且K值的增大就意味着整体的模型变得简单。
  3. K=N,则完全不足取,因为此时无论输入实例是什么,都只是简单的预测它属于在训练实例中最多的累,模型过于简单,忽略了训练实例中大量有用信息。

在实际应用中,K值一般取一个比较小的数值,例如采用交叉验证法(简单来说,就是一部分样本做训练集,一部分做测试集)来选择最优的K值。

KNN最近邻分类算法的过程

  1. 计算测试样本和训练样本中每个样本点的距离(常见的距离度量有欧式距离,马氏距离等);
  2. 对上面所有的距离值进行排序;
  3. 选前 k 个最小距离的样本;
  4. 根据这 k 个样本的标签进行投票,得到最后的分类类别;

基于KNN的手写字体识别分类实践

在jupyter notebook上测试

from numpy import *
from os import listdir
from collections import Counter

def img2vector(filename):
    """
    将图像数据转换为向量
    :param filename: 图片文件 因为我们的输入数据的图片格式是 32 * 32的。这个可以打开输入数据文件看看就知道了
    :return: 一维矩阵
    该函数将图像转换为向量:该函数创建 1 * 1024 的NumPy数组,然后打开给定的文件,
    循环读出文件的前32行,并将每行的头32个字符值存储在NumPy数组中,最后返回数组。
    """
    # 创建一个 1 行 1024列 的都为0的一维矩阵
    returnVect = zeros((1, 1024))
    # 读取文件
    fr = open(filename)
    # 行循环
    for i in range(32):
        # 读一行数据
        lineStr = fr.readline()
        # 列循环
        for j in range(32):
            # 将每个数据转成int类型后放到矩阵里
            returnVect[0, 32 * i + j] = int(lineStr[j])
    return returnVect

# KNN分类
def classify(inX, dataSet, labels, k):
    """
    inx[1,2,3]
    DS=[[1,2,3],[1,2,0]]
    inX: 用于分类的输入向量
    dataSet: 输入的训练样本集
    labels: 标签向量
    k: 选择最近邻居的数目
    注意:labels元素数目和dataSet行数相同;程序使用欧式距离公式.
    """
    # 1.计算距离
    # 获得数据集大小(shape(0),行数)
    dataSetSize = dataSet.shape[0]
    # tile表示将inX向量扩展成dataSetSize项,每项中对原数据集复制1次的数据集。例如,inx[1,2,3],tile(inx,(2,1))=[[1,2,3],[1,2,3]]
    # diffMat 得到每inX 与 原数据集的差集
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet
    # 取平方
    sqDiffMat = diffMat ** 2
    # 将矩阵的每一行相加
    sqDistances = sqDiffMat.sum(axis=1)
    # 开方
    distances = sqDistances ** 0.5
    # 根据距离排序从小到大的排序,返回对应的索引位置
    # argsort() 是将x中的元素从小到大排列,提取其对应的index(索引),然后输出到y。
    # x=np.array([1,4,3,-1,6,9])
    # y = x.argsort()
    # y=[3 0 2 1 4 5]
    sortedDistIndicies = distances.argsort()

    # 2. 选择距离最小的k个点
    # 初始化类别计数(计数个数相当于投票次数)
    classCount = {}
    for i in range(k):
        # 找到该样本的类型
        voteIlabel = labels[sortedDistIndicies[i]]
        # 对该类别进行计数累加
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    # 3. 排序并返回出现最多的那个类型 这里max方法是取dict类型的value最大值,并返回其对应的key
    maxClassCount = max(classCount, key=classCount.get)
    return maxClassCount

# 手写字体分类测试
def handwritingClassTest():
    # 1. 导入数据,准备好训练数据
    # hwLabels存储0~9对应的index位置, trainingMat存放的每个位置对应的图片向量
    hwLabels = []
    # load the training set
    trainingFileList = listdir('trainingDigits')  
    m = len(trainingFileList)
    trainingMat = zeros((m, 1024))
    # 对每个训练文件遍历
    for i in range(m):
        # 获取文件名(文件名长这样0_0.txt)
        fileNameStr = trainingFileList[i]
        # take off .txt
        fileStr = fileNameStr.split('.')[0]  
        # 获得文件对应类别
        classNumStr = int(fileStr.split('_')[0])
        # 类别存入标签组
        hwLabels.append(classNumStr)
        # 将 32*32的矩阵->1*1024的矩阵[i,:]表示第i行所有列
        trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)

    # 2. 导入测试数据进行测试
    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])
        # 将测试数据集的 32*32的矩阵->1*1024的一维矩阵
        vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
        # 分类
        classifierResult = classify(vectorUnderTest, trainingMat, hwLabels, 1)
        # 打印分类结果
        print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))
        if (classifierResult != classNumStr): errorCount += 1.0
    print("\nthe total number of errors is: %d" % errorCount)
    print("\nthe total error rate is: %f" % (errorCount / float(mTest)))
    
    
# 测试执行
handwritingClassTest()

输出

.....
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9

the total number of errors is: 13

the total error rate is: 0.013742

实际使用中,需要对不同的K进行测试,得到最佳的K

  • 测试文件数据说明

    测试文件名是例如0_0.txt、0_1.txt.。。9_0.txt,模式是 具体分类数字_该分类的第几个样本.txt

    其中一份文件数据如下

    00000000000000111000000000000000
    00000000000011111110100000000000
    00000000001111011111110000000000
    00000000001111011111111000000000
    00000000111111111111111000000000
    00000000111111111111111100000000
    00000001111110000000111100000000
    00000011111100000000111100000000
    00000011111100000000011110000000
    00000111111100000000011110000000
    00000011111111000000000111000000
    00000011111110000000000111000000
    00000011111100000000000111000000
    00000011111100000000000111000000
    00000001111000000000001111000000
    00000001111000000000001111000000
    00000001111000000000001111000000
    00000001111000000000001110000000
    00000001111000000000001110000000
    00000001110000000000011110000000
    00000001110000000000011110000000
    00000001110000000000111100000000
    00000011110000000000111100000000
    00000000111100000000111110000000
    00000000111100000000111100000000
    00000000111100000011111100000000
    00000000111110000111111000000000
    00000000011111111111111000000000
    00000000011111111111110000000000
    00000000001111111111000000000000
    00000000001111111111000000000000
    00000000000001101000000000000000
    
    

    若有兴趣需要完整测试数据,可以私聊联系我

OK,以上有问题请及时指出

你可能感兴趣的:(AI基础,KNN,K近邻,距离度量,手写字体识别,手写字体)