看啥推荐读物
专栏名称: Kervin_Chan
目录
相关文章推荐
张小北  ·  转发微博-20240515173801·  3 天前  
张小北  ·  祖传 56 ...·  4 天前  
张小北  ·  转发微博-20240514134504·  5 天前  
今天看啥  ›  专栏  ›  Kervin_Chan

计算机视觉—kNN识别手写数字(9)

Kervin_Chan  · 掘金  ·  · 2018-06-05 02:02

计算机视觉—kNN识别手写数字(9)

一、MNIST手写数字介绍

1、获取样本

手写数字的MNIST数据库可从此页面获得,其中包含60,000个示例的训练集以及10,000个示例的测试集。它是NIST提供的更大集合的子集。这些数字已经过尺寸标准化并以固定尺寸的图像为中心。

下载链接:http://yann.lecun.com/exdb/mnist/

样本包含了四个部分:

训练集图片: train-images-idx3-ubyte.gz
(9.9 MB, 解压后 47 MB, 包含 60,000 个样本)

训练集图片对应的标签: train-labels-idx1-ubyte.gz
(29 KB, 解压后 60 KB, 包含 60,000 个标签)

Test set images: t10k-images-idx3-ubyte.gz
(1.6 MB, 解压后 7.8 MB, 包含 10,000 个样本)

Test set labels: t10k-labels-idx1-ubyte.gz
(5KB, 解压后 10 KB, 包含 10,000 个标签)

2、MNIST解析

MNIST是深度学习的经典入门demo,他是由6万张训练图片和1万张测试图片构成的,每张图片都是28*28大小(如下图),而且都是黑白色构成(这里的黑色是一个0-1的浮点数,黑色越深表示数值越靠近1),这些图片是采集的不同的人手写从0到9的数字。TensorFlow将这个数据集和相关操作封装到了库中,下面我们来一步步解读深度学习MNIST的过程。

上图就是4张MNIST图片。这些图片并不是传统意义上的png或者jpg格式的图片,因为png或者jpg的图片格式,会带有很多干扰信息(如:数据块,图片头,图片尾,长度等等),这些图片会被处理成很简易的二维数组,如图:

可以看到,矩阵中有值的地方构成的图形,跟左边的图形很相似。之所以这样做,是为了让模型更简单清晰。特征更明显。

二、kNN原理

1、kNN算法概述

  kNN算法的核心思想是如果一个样本在特征空间中的k个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性。该方法在确定分类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。

2、kNN算法介绍

  最简单最初级的分类器是将全部的训练数据所对应的类别都记录下来,当测试对象的属性和某个训练对象的属性完全匹配时,便可以对其进行分类。但是怎么可能所有测试对象都会找到与之完全匹配的训练对象呢,其次就是存在一个测试对象同时与多个训练对象匹配,导致一个训练对象被分到了多个类的问题,基于这些问题呢,就产生了kNN。

  kNN是通过测量不同特征值之间的距离进行分类。它的的思路是:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。K通常是不大于20的整数。kNN算法中,所选择的邻居都是已经正确分类的对象。该方法在定类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。

下面通过一个简单的例子说明一下:

  如下图,绿色圆要被决定赋予哪个类,是红色三角形还是蓝色四方形?如果K=3,由于红色三角形所占比例为2/3,绿色圆将被赋予红色三角形那个类,如果K=5,由于蓝色四方形比例为3/5,因此绿色圆被赋予蓝色四方形类。

由此也说明了kNN算法的结果很大程度取决于K的选择。

  在kNN中,通过计算对象间距离来作为各个对象之间的非相似性指标,避免了对象之间的匹配问题,在这里距离一般使用欧氏距离或曼哈顿距离:

  同时,kNN通过依据k个对象中占优的类别进行决策,而不是单一的对象类别决策。这两点就是kNN算法的优势。

接下来对kNN算法的思想总结一下:

  就是在训练集中数据和标签已知的情况下,输入测试数据,将测试数据的特征与训练集中对应的特征进行相互比较,找到训练集中与之最为相似的前K个数据,则该测试数据对应的类别就是K个数据中出现次数最多的那个分类,其算法的描述为:

1)计算测试数据与各个训练数据之间的距离;

2)按照距离的递增关系进行排序;

3)选取距离最小的K个点;

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

5)返回前K个点中出现频率最高的类别作为测试数据的预测分类。

原文链接:https://www.cnblogs.com/sxron/p/5451923.html

三、实例讲解

1、加载MNIST数据

TensorFlow已经准备了一个脚本来自动下载和导入MNIST数据集。它会自动创建一个'MNIST_data'的目录来存储数据。

import tensorflow as tf
import numpy as np
import random 
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data',one_hot=True)
# one_hot介绍 :https://blog.csdn.net/lanhaier0591/article/details/78702558

这里,mnist是一个轻量级的类。它以Numpy数组的形式存储着训练、校验和测试数据集。同时提供了一个函数,用于在迭代中获得minibatch,后面我们将会用到。

2、设置属性

trainNum = 60000
# 训练图片总数
testNum = 10000
# 测试图片总数
trainSize = 500
# 训练的时候使用的图片数量
testSize = 5
# 测试的时候使用的图片数量
k = 4
# 距离最小的K个图片

3、数据分解

我们前面说过,每张图片都是28 * 28 = 784像素,所以在testData.shape= (5, 784)中,5代表图片个数,784代表每张图片的像素。

那testLabel.shape= (5, 10),代表什么?

先看看:

  • testLabel= [[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
    [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
    [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
    [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
    [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]]

testLabel中第1个标签[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.] 为数字6,和testData的第1个数据相对应。 以此类推,第2个数据的标签是0,第3个数据的标签是4,第4个数据的标签是0,第5个数据的标签是6。

trainIndex = np.random.choice(trainNum,trainSize,replace=False)
# 在trainNum数据中,生成trainSize个不重复的随机数 
testIndex = np.random.choice(testNum,testSize,replace=False)

trainData = mnist.train.images[trainIndex]
# 训练数据集的图片是 mnist.train.images
trainLabel = mnist.train.labels[trainIndex]
# 训练数据集的标签是 mnist.train.labels
testData = mnist.test.images[testIndex]
# 测试数据集的图片是 mnist.test.images
testLabel = mnist.test.labels[testIndex]
# 测试数据集的标签是 mnist.test.labels
print('trainData.shape=',trainData.shape)
print('trainLabel.shape=',trainLabel.shape)
print('testData.shape=',testData.shape)
print('testLabel.shape=',testLabel.shape)
print('testLabel=',testLabel)

结果:

trainData.shape= (500, 784)
trainLabel.shape= (500, 10)
testData.shape= (5, 784)
testLabel.shape= (5, 10)
testLabel= [[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]]

4、数据训练

(1)设置变量

tf.placeholder(dtype, shape=None, name=None)
此函数可以理解为形参,用于定义过程,在执行的时候再赋具体的值 参数:

  • dtype:数据类型。常用的是tf.float32,tf.float64等数值类型
  • shape:数据形状。默认是None,就是一维值,也可以是多维,比如[2,3], [None, 3]表示列是3,行不定
  • name:名称。
trainDataInput = tf.placeholder(shape=[None,784],dtype=tf.float32)
trainLabelInput = tf.placeholder(shape=[None,10],dtype=tf.float32)
testDataInput = tf.placeholder(shape=[None,784],dtype=tf.float32)
testLabelInput = tf.placeholder(shape=[None,10],dtype=tf.float32)

(2)计算kNN的距离

使用曼哈顿距离:

f1 = tf.expand_dims(testDataInput,1) 
# expand_dim()来增加维度
f2 = tf.subtract(trainDataInput,f1)
# subtract()相减,得到一个三维数据
f3 = tf.reduce_sum(tf.abs(f2),reduction_indices=2)
# tf.abs()求数据绝对值
# tf.reduce_sum()完成数据累加,把数据放到f3中
with tf.Session() as sess:
    p1 = sess.run(f1,feed_dict={testDataInput:testData[0:5]})
    print('p1=',p1.shape)
    # p1= (5, 1, 784)
    p2 = sess.run(f2,feed_dict={trainDataInput:trainData,testDataInput:testData[0:5]})
    print('p2=',p2.shape)
    # p2= (5, 500, 784) 
    p3 = sess.run(f3,feed_dict={trainDataInput:trainData,testDataInput:testData[0:5]})
    print('p3=',p3.shape)
    # p3= (5, 500)
    print('p3[0,0]=',p3[0,0]) 
    # p3[0,0]= 132.96472,表示第一张测试图片和第一张训练图片的距离是132.96472

结果:

p1= (5, 1, 784)
p2= (5, 500, 784)
p3= (5, 500)
p3[0,0]= 132.96472

###(3)选取距离最小的K个图片

tf.nn.top_k(input, k, name=None)
这个函数的作用是返回 input 中每行最大的 k 个数,并且返回它们所在位置的索引。

输入参数:

  • input: 一个张量,数据类型必须是以下之一:float32、float64、int32、int64、uint8、int16、int8。数据维度是 batch_size 乘上 x 个类别。
  • k: 一个整型,必须 >= 1。在每行中,查找最大的 k 个值。
  • name: 为这个操作取个名字。

输出参数:

  • 一个元组 Tensor ,数据元素是 (values, indices),具体如下:
  • values: 一个张量,数据类型和 input 相同。数据维度是 batch_size 乘上 k 个最大值。
  • indices: 一个张量,数据类型是 int32 。每个最大值在 input 中的索引位置。
f4 = tf.negative(f3)
# tf.negative(x,name=None),取负运算(f4 =-f3 = -132.96472)
f5,f6 = tf.nn.top_k(f4,k=4) 
# f5,选取f4最大的四个值,即f3最小的四个值
# f6,这四个值对应的索引
with tf.Session() as sess:
    p4 = sess.run(f4,feed_dict={trainDataInput:trainData,testDataInput:testData[0:5]})
    print('p4=',p4.shape)
    print('p4[0,0]=',p4[0,0])
    p5,p6 = sess.run((f5,f6),feed_dict={trainDataInput:trainData,testDataInput:testData[0:5]})
    # p5= (5, 4),每一张测试图片(共5张),分别对应4张最近训练图片,共20张
    print('p5=',p5.shape)
    print('p6=',p6.shape)
    print('p5',p5)
    print('p6',p6)

结果:

p4= (5, 500)
p4[0,0]= -132.96472
p5= (5, 4)
p6= (5, 4)
p5= [[-54.49804  -54.87059  -55.690197 -59.97647 ]
     [-49.09412  -64.74118  -68.22353  -68.76863 ]
     [-65.36079  -69.278435 -72.60785  -74.84314 ]
     [-75.46667  -78.19216  -78.36864  -80.44706 ]
     [-42.478436 -61.517654 -62.36863  -63.42353 ]]
p6= [[150 167 493 226]
     [402 268 279 164]
     [300  97  78 237]
     [387 164 268 311]
     [258 107 226 207]]

(3)确定K个图片在类型出现的频率

tf.gather()
根据索引从参数轴上收集切片。 索引必须是任何维度的整数张量 (通常为 0-D 或 1-D)。生成输出张量该张量的形状为:params.shape[:axis] + indices.shape + params.shape[axis + 1:]

输入参数:

  • params:一个张量。这个张量是用来收集数值的。该张量的秩必须至少是 axis + 1。
  • indices:一个张量。必须是以下类型之一:int32,int64。索引张量必须在 [0, params.shape[axis]) 范围内。
  • axis:一个张量。必须是以下类型之一:int32,int64。在参数轴从中收集索引。默认为第一个维度。支持负索引。
  • name:操作的名称(可选)。

输出参数:

  • 该函数返回一个张量。与参数具有相同的类型。参数值从索引给定的索引中收集而来,并且形状为:params.shape[:axis] + indices.shape + params.shape[axis + 1:]。
f7 = tf.gather(trainLabelInput,f6)
# 根据索引找到对应的标签值
f8 = tf.reduce_sum(f7,reduction_indices=1)
# 累加维度1的数值
f9 = tf.argmax(f8,dimension=1)
# 返回的是f8中的最大值的索引号
with tf.Session() as sess:
    p7 = sess.run(f7,feed_dict={trainDataInput:trainData,testDataInput:testData[0:5],trainLabelInput:trainLabel})
    print('p7=',p7.shape)
    print('p7[]',p7)
    p8 = sess.run(f8,feed_dict={trainDataInput:trainData,testDataInput:testData[0:5],trainLabelInput:trainLabel})
    print('p8=',p8.shape)
    print('p8[]=',p8)
    p9 = sess.run(f9,feed_dict={trainDataInput:trainData,testDataInput:testData[0:5],trainLabelInput:trainLabel})
    print('p9=',p9.shape)
    print('p9[]=',p9)

结果:

p7= (5, 4, 10)
p7[]= [[[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
  [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
  [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
  [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]]

 [[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
  [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
  [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
  [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]

 [[0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
  [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
  [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
  [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]]

 [[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
  [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
  [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
  [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]

 [[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
  [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
  [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
  [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]]
p8= (5, 10)
p8[]= [[0. 0. 0. 0. 0. 0. 4. 0. 0. 0.]
 [4. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 4. 0. 0. 0. 0. 0. 0.]
 [4. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 3. 0. 0. 1.]]
p9= (5,)
p9[]= [6 0 3 0 6]

(5)检验结果

with tf.Session() as sess:
 p10 = np.argmax(testLabel[0:5],axis=1)
 # p9=p10,代表正确
    print('p10[]=',p10)
j = 0
for i in range(0,5):
    if p10[i] == p9[i]:
        j = j+1
print('ac=',j*100/5)
# 正确率

结果:

p10[]= [6 0 3 0 6]
ac= 100.0



原文地址:访问原文地址
快照地址: 访问文章快照