一 图像识别背景知识
1.1 图像识别任务描述
图像识别的应用场景特别多,比如人脸识别、物体识别、监控视频里的异常检测、无人驾驶等,其核心技术都来自于图像识别。
目前对于图像识别任务,最有效的方法是使用深度学习,比如卷积神经网络。但实际上,我们也可以通过其他简单的算法如KNN来识别图片,只不过比起卷积神经网络效果会差一些。
图像本身是一个非结构化的数据,而且非结构化数据本身是不能直接用来做模型输入的。
1.2 图像的读取以及表示
那什么是非结构化数据呢? 简单来讲,文本、图片、声音、视频这些都属于非结构化数据,需要做进一步的处理。相反, 结构化的数据指的是存放在数据库里的年龄,身高等这种信息。
对于图像来说,此过程相对简单。一般可以通过Python自带的库来读取图片,并把图片数据存放在矩阵(Matrix)或者张量(Tensor)里。
图片是由像素来构成的,比如256×256或者128×128。两个值分别代表长宽上的像素。这个值越大图片就会越清晰。另外,对于彩色的图片,一个像素点一般由三维数组来构成,分别代表的是R,G,B三种颜色。除了RGB,其实还有其他常用的色彩空间。如果使用RGB来表示每一个像素点,一个大小为128×128像素的图片实际大小为128×128×3,是一个三维张量的形式。
import matplotlib.pyplot as plt
# 读取图片的数据,存放到img
img = plt.imread("/home/anaconda/data/RGZNXLY/ch3/sample.jpg")
print(img.shape)
plt.imshow(img)
(347, 335, 3)
1.3 降维及图片的特征
图像识别工作中需要考虑的点:
- 从不同视角拍摄同一个物体
- 主要的物体被部分覆盖
- 图片的亮度
- 同一个物体可以有不同的形状
问题:假如我们直接读取图片里的数据,然后再通过KNN算法来识别图片,那这种方案是否能考虑到上面提到的几点呢?
很显然是不可以的。
KNN模型的核心是计算距离。假设我们把图片转换成数据,之后无非就是计算两个矩阵/张量之间的距离罢了,这很明显不会考虑到以上几个问题,这也是为什么KNN这种方式得出来的效果并不是特别好的主要原因。
常用的图片特征:
- 颜色特征(最常用的是颜色直方图(color histogram))
- SIFT特征(一个局部的特征,它会试图去寻找图片中的拐点这类的关键点,然后再通过一系列的处理最终得到一个SIFT向量)
- HOG(通过计算和统计图像局部区域的梯度方向直方图来构建特征)
由于HOG是在图像的局部方格单元上操作,所以它对图像几何的和光学的形变都能保持很好的不变性。
对于一个中小型图片,它的大小一般大于256×256×3。如果把它转换成向量,其实维度的大小已经几十万了。这会导致消耗非常大的计算资源,所以一般情况下我们都会尝试对图片做一些降维操作。其实特征提取过程我们自然地可以理解为是降维过程。
除了特征提取之外,我们还有一种常用的降维工具叫做PCA(Principal Component Analysis), 它是一种无监督的学习方法,可以把高维的向量映射到低维的空间里。它的核心思路是对数据做线性的变换,然后在空间里选择信息量最大的Top K维度作为新的特征值。
二 搭建基于Knn的图像识别系统
在这个项目里, 需要完成一个图像识别的任务,主要使用的模型是KNN算法。使用的数据集是cifar-10,是图像识别领域最为经典的数据集之一。具体的数据可以从以下的链接下载: http://www.cs.toronto.edu/~kriz/cifar.html, 下载之后把是数据集解压在当前的工程的根目录下。
该项目主要做了如下几个方面的工作:
- 读取图片文件、展示图片、并做部分采样。采样的原因主要是为了节省训练的时间,因为我们知道KNN的搜索复杂度为O(N),何况图片也属于高维的数据,这也会增加搜索效率。
- 使用KNN算法识别图片。在这里,需要使用K折交叉验证来选择最适合的超参数。
- 使用PCA技术先给图片做降维,然后在使用KNN来识别图片。 另外,使用PCA把图片降维到2维度空间里,这时候相当于我们得到了每一个图片在(x,y)轴上的坐标。之后把采样过的一些图片在二维空间里根据每个坐标点来展示一下。
- 给每一个图片抽取两个经典的特征,color historgram和HOG, 抽取完之后再通过KNN来训练。
- 基于第四步,换成神经网络模型去识别。
在机器学习的上下文中,超参数是在开始学习过程之前设置的参数。
超参数的一些示例:
- 树的数量或树的深度
- 矩阵分解中潜在因素的数量
- 学习率(多种模式)
- 深层神经网络隐藏层数
- k均值聚类中的簇数
2.1 文件的读取、可视化、以及采样
# 文件的读取,我们直接通过给定的`load_CIFAR10`模块读取数据。
from load_data import load_CIFAR10 # load_data为用于读取数据的脚本文件,不必要担心如何写读取的过程。
import numpy as np
import matplotlib.pyplot as plt
cifar10_dir = 'cifar-10-batches-py' # 定义文件夹的路径
# 清空变量,防止重复导入多次。
try:
del X_train, y_train
del X_test, y_test
print('清除之前导入过的变量...Done!')
except:
pass
# 读取文件,并把数据保存到训练集和测试集合。
X_train, y_train, X_test, y_test = load_CIFAR10(cifar10_dir)
# 先来查看一下每个变量的大小,确保没有任何错误!X_train和X_test的大小应该为 N*W*H*3
# N: 样本个数, W: 样本宽度 H: 样本高度, 3: RGB颜色。 y_train和y_test为图片的标签。
print ("训练数据和测试数据:", X_train.shape, y_train.shape, X_test.shape, y_test.shape)
print ("标签的种类: ", np.unique(y_train)) # 查看标签的个数以及标签种类,预计10个类别。
训练数据和测试数据: (50000, 32, 32, 3) (50000,) (10000, 32, 32, 3) (10000,)
标签的种类: [0 1 2 3 4 5 6 7 8 9]
从训练数据抽取50个样本(每一个类别中随机抽取5个样本),并做可视化
classes = ['plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
num_classes = len(classes) # 样本种类的个数
samples_per_class = 5 # 每一个类随机选择5个样本
plt.figure(figsize=(10,6))
for col in range(num_classes):
# 随机抽取第col类的5个样本
i_class_samples = X_train[np.random.choice(np.argwhere(y_train == col).ravel(),samples_per_class,replace=False)] # 不放回采样
# 循环绘制第col类的这5个样本图片
for row in range(samples_per_class):
# 定位当前画板的对应位置子图
plt.subplot(samples_per_class, num_classes, (num_classes * row + col + 1))
# 在当前位置子图上绘制对应的样本图片
plt.imshow(i_class_samples[row]/255)
# 给各个类别的第一个图片加上标题
if row == 0:
plt.title(classes[col])
plt.axis("off") # 不显示坐标轴
plt.savefig('./图片1')
检查样本是否均衡
# 统计并展示每一个类别出现的次数
from collections import Counter
# 使用Counter统计各个类别下标出现次数
count_result = Counter(y_train)
# 对统计结果按类别下标排序,并拆分为横坐标与纵坐标序列
count_plot_result = list(zip(*sorted(count_result.items())))
count_plot_result
[(0, 1, 2, 3, 4, 5, 6, 7, 8, 9),
(5000, 5000, 5000,<