1 Star 6 Fork 4

OpenDocCN / apachecn-ml-zh

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
12.md 41.25 KB
一键复制 编辑 原始数据 按行查看 历史
布客飞龙 提交于 2022-01-02 01:27 . 2022-01-02 01:27:33

十二、计算机视觉

图像分析和计算机视觉在工业和科学应用中一直很重要。随着具有强大摄像头和互联网连接的手机的普及,现在图像越来越多地由消费者生成。因此,有机会利用计算机视觉在新的环境中提供更好的用户体验。

在本章中,我们将了解如何将您在本书其余部分中学习的几种技术应用于这种特定类型的数据。特别是,我们将学习如何使用mahotas计算机视觉包从图像中提取特征。然后,这些特征可以用作我们在其他章节中研究的相同分类方法的输入。我们将把这些技术应用于公开的照片数据集。我们还将看到如何使用相同的特征来寻找相似的图像。我们还将学习如何使用本地功能。这些是相对通用的,并且在许多任务中获得非常好的结果(尽管它们具有较高的计算成本)。

最后,在本章的最后,我们将使用 Tensorflow 基于现有数据集生成新图像。特别是,在本章中,我们将执行以下操作:

  • 了解如何将图像表示为 NumPy 数组并对其进行操作
  • 了解如何将图像表示为一组小特征,以便在这种数据类型上使用标准分类和聚类方法
  • 学习如何使用视觉单词来生成另一种类型的特征
  • 了解如何生成与现有图像相似的新图像

介绍图像处理

从计算机的角度来看,图像是像素值的大矩形阵列。我们的目标是处理一个或多个图像,并为我们的应用程序做出决定。

根据设置,它可能是一个分类问题,一个聚类问题,或者我们在书中看到的任何其他问题类别。

第一步是从磁盘加载图像,图像通常以特定于图像的格式存储,如巴布亚新几内亚或 JPEG,前者是无损压缩格式,后者是有损压缩,是针对照片的视觉评估而优化的。然后,我们可能希望对图像进行预处理(例如,针对光照变化对图像进行归一化)。

加载和显示图像

为了操纵图像,我们将使用一个名为mahotas的包。您可以通过蟒蛇获取mahotas,并在httpT4T6】上阅读其手册://maho tas . read docs .T8】io。Mahotas 是一个开源包(它拥有麻省理工学院的许可,因此可以在任何项目中使用),由本书的作者之一开发。它基于 NumPy。因此,您迄今为止获得的 NumPy 知识可以用于图像处理。还有其他的图像包,比如scikit-image(skipage)n 维图像(ndi image)模块在 SciPy 中,以及 OpenCV 的 Python 绑定。所有这些都与 NumPy 数组一起工作,因此您甚至可以混合和匹配来自不同包的功能来构建一个组合管道。

我们首先用mh缩写导入mahotas,我们将在本章中使用该缩写,如下所示:

import mahotas as mh 

现在,我们可以使用imread加载一个图像文件,如下所示:

image = mh.imread('scene00.jpg') 

scene00.jpg文件(该文件包含在本书配套代码库中可用的数据集中)是高度h和宽度w的彩色图像;图像将是一系列形状(h, w, 3)。第一维是高度,第二维是宽度,第三维是红/绿/蓝。其他系统将宽度放在第一维,但这是所有基于 NumPy 的包使用的约定。数组的类型通常是np.uint8(一个无符号的 8 位整数)。这些是您的相机拍摄的图像,您的显示器可以完全显示。

科学和技术应用中使用的一些专用设备可以以更高的位分辨率(即对亮度的微小变化更敏感)拍摄图像。12 或 16 位在这类设备中很常见。Mahotas 可以处理所有这些类型,包括浮点图像。在许多计算中,即使原始数据由无符号整数组成,为了简化舍入和溢出问题的处理,转换为浮点数也是有利的。

Mahotas can use a variety of different input/output backends. Unfortunately, none of them can load all image formats that exist (there are hundreds, with several variations of each). However, the loading of PNG and JPEG images is supported by all of them. We will focus on these common formats and refer you to the mahotas documentation on how to read uncommon formats.

我们可以使用matplotlib在屏幕上显示图像,我们已经使用过几次的plotting库如下:

from matplotlib import pyplot as plt 
fig,ax = plt.subplots() 
ax.imshow(image) 

如下面的截图所示,这段代码使用第一个维度是高度,第二个维度是宽度的约定来显示图像。它也能正确处理彩色图像。当使用 Python 进行数值计算时,我们受益于整个生态系统的良好合作:mahotas与 NumPy 数组一起工作,这些数组可以用 matplotlib 显示。稍后,我们将从图像中计算特征,以便与 scikit-learn 一起使用:

阈值化

我们从一些简单的图像处理操作开始这一章。这些不会使用机器学习,但目标是证明图像可以作为数组进行操作。当我们引入新特性时,这将很有用。

阈值化是一个非常简单的操作:我们将某个阈值以上的所有像素值转换为1,将其以下的所有像素值转换为0(或者使用布尔运算,将其转换为TrueFalse)。阈值处理中的重要问题是选择一个好的值作为阈值限制。Mahotas 实现了一些从图像中选择阈值的方法。我们将使用一种以其发明者命名的方法Otsu。第一个必要的步骤是将图像转换为灰度,在mahotas.colors子模块中rgb2gray

除了rgb2gray,我们还可以通过调用image.mean(2)来获得红色、绿色和蓝色通道的平均值。然而,结果会不一样,因为rgb2gray对不同的颜色使用不同的权重来给出主观上更令人愉悦的结果。我们的眼睛对三种基本颜色不太敏感:

image = mh.colors.rgb2grey(image, dtype=np.uint8) 
fig,ax = plt.subplots() 
ax.imshow(image) # Display the image 

默认情况下,matplotlib 会将此单通道图像显示为假彩色图像,使用红色表示高值,蓝色表示低值。对于自然图像,灰度更合适。您可以通过以下方式选择它:

plt.gray() 

现在图像以灰度显示。请注意,只有解释和显示像素值的方式发生了变化,图像数据未被触及。我们可以通过计算阈值来继续我们的处理。阈值化是两类聚类的一种形式,对于这一任务有几种方法。Otsu就是这样一种thresholding方法,它试图找到两组紧凑的像素组,高于阈值和低于阈值的像素组:

thresh = mh.thresholding.otsu(image)
print('Otsu threshold is {}.'.format(thresh))
Otsu threshold is 138. 
fig,ax = plt.subplots()
ax.imshow(image > thresh)

该方法应用于上一张图像时,发现threshold138,将地面与上方天空分开,如下图截图所示:

高斯模糊

模糊你的图像可能看起来很奇怪,但它通常有助于减少噪音,这有助于进一步的处理。有了mahotas,就只是一个函数调用:

im16 = mh.gaussian_filter(image, 16) 

请注意,我们没有将灰度图像转换为无符号整数;我们只是按原样使用了浮点结果。gaussian_filter函数的第二个参数是过滤器的大小(过滤器的标准偏差)。较大的值会导致更多的模糊,如下图所示:

我们可以使用前面的截图和带有Otsuthreshold(使用前面的代码)。现在,边界更加平滑,没有锯齿边缘,如下图所示:

聚焦中心

最后一个例子向您展示了如何将 NumPy 运算符与一点点过滤混合在一起,以获得一个有趣的结果。我们从森林中一条小路的照片开始:

im = mh.imread('forest') 

要分割红色、绿色和蓝色通道,我们使用以下代码。NumPy transpose方法改变多维数组中轴的顺序:

r,g,b = im.transpose(2,0,1) 

现在,我们分别过滤这三个通道,并用mh.as_rgb从其中构建一个合成图像。该函数采用三个二维数组,执行对比度拉伸,使每个数组成为一个 8 位整数数组,然后堆叠它们,返回一个彩色 RGB 图像:

r24 = mh.gaussian_filter(r, 24.) 
g24 = mh.gaussian_filter(g, 24.) 
b24 = mh.gaussian_filter(b, 24.) 
im24 = mh.as_rgb(r24, g24, b24) 

现在,我们将两幅图像从中心向边缘混合。首先,我们需要构建一个权重数组W,它将在每个像素包含一个归一化值,即它到中心的距离:

h, w = r.shape # height and width 
Y, X = np.mgrid[:h,:w] 

我们使用了np.mgrid对象,该对象返回大小为(h, w)的数组,其值分别对应于 yx 坐标。接下来的步骤如下:

Y = Y - h/2\. # center at h/2 
Y = Y / Y.max() # normalize to -1 .. +1 

X = X - w/2\. 
X = X / X.max() 

我们现在使用高斯函数给中心区域一个高值:

C = np.exp(-2.*(X**2+ Y**2)) 

# Normalize again to 0..1 
C = C - C.min() 
C = C / C.ptp() 
C = C[:,:,None] # This adds a dummy third dimension to C 

请注意,所有这些操作都是使用 NumPy 数组执行的,而不是某些mahotas特定的方法:

ringed = mh.stretch(im*C + (1-C)*im24) 

最后,我们可以将两幅图像合并,使中心清晰聚焦,边缘更加柔和:

基本图像分类

我们将从专为本书收集的一个小数据集开始。它有三类:建筑物、自然场景(风景)和文字图片。每个类别有 30 张图片,它们都是用手机摄像头拍摄的,构图很少。这些图片类似于那些没有经过摄影训练的用户上传到现代网站的图片。该数据集可在伴随代码库中找到。在本章的后面,我们将看到一个更大的数据集,其中有更多的图像和更难分类的类别。

对图像进行分类时,我们从一个大的矩形数字数组(像素值)开始。如今,数百万像素很常见。我们可以尝试将所有这些数字作为特征输入到学习算法中。这不是一个很好的主意,除非你有很多数据。这是因为每个像素(甚至每个小像素组)与最终结果的关系非常间接。此外,拥有数百万像素,但仅作为少量示例图像,会导致非常困难的统计学习问题。这是我们在第三章回归中讨论的 P 大于 N 类型问题的一种极端形式。相反,解决较小问题的好方法是从图像中计算特征,并使用这些特征进行分类。

我们之前用了一个场景类的例子。以下是文本和构建类的示例:

从图像计算特征

借助mahotas,从图像中计算特征非常容易。有一个名为mahotas.features的子模块,可以使用特征计算功能。

一组常用的纹理特征是 Haralick 集。和许多图像处理方法一样,这个名字是为了纪念它的发明者。这些特征是基于纹理的;它们区分平滑图像和有图案的图像,以及不同的图案。有了mahotas,计算它们非常容易,如下所示:

haralick_features = mh.features.haralick(image) 
haralick_features_mean = np.mean(haralick_features, axis=0) 
haralick_features_all = np.ravel(haralick_features) 

mh.features.haralick函数返回一个 4×13 的数组。第一维指的是计算要素的四个可能方向(垂直、水平、对角线和反对角线)。如果我们对方向不特别感兴趣,我们可以使用所有方向的平均值(在前面的代码中显示为haralick_features_mean)。否则,我们可以单独使用所有功能(使用haralick_features_all)。这个决定应该由数据集的属性来决定。在我们的例子中,我们认为水平和垂直方向应该分开。因此,我们将使用haralick_features_all

mahotas中还实现了一些其他的特性集。线性二进制模式是另一种基于纹理的特征集,它对光照变化非常鲁棒。还有其他类型的功能,包括本地功能,我们将在本章后面讨论。

有了这些特性,我们使用标准的分类方法,如logistic regression,如下所示:

from glob import glob
images = glob('SimpleImageDataset/*.jpg')
features = []
labels = []
for im in images:
    labels.append(im[:-len('00.jpg')])
    im = mh.imread(im)
    im = mh.colors.rgb2gray(im, dtype=np.uint8)
    features.append(mh.features.haralick(im).ravel())

features = np.array(features)
labels = np.array(labels)

这三个类有非常不同的纹理。建筑物有尖锐的边缘和颜色相似的大块(像素值很少完全相同,但变化很小)。文本由许多清晰的明暗过渡组成,白色的海洋中有黑色的小区域。自然场景具有更平滑的变化和类似分形的过渡。因此,基于纹理的分类器有望做得很好。

我们将使用逻辑回归分类器作为分类器,对特征进行预处理,如下所示:

from sklearn.pipeline import Pipeline 
from sklearn.preprocessing import StandardScaler 
from sklearn.linear_model import LogisticRegression 
clf = Pipeline([('preproc', StandardScaler()), 
                    ('classifier', LogisticRegression())]) 

由于我们的数据集很小,我们可以使用省去回归,如下所示:

from sklearn import model_selection 
cv = model_selection.LeaveOneOut() 
scores = model_selection.cross_val_score(clf, features, labels, cv=cv) 
print('Accuracy: {:.1%}'.format(scores.mean())) 
Accuracy: 81.1% 

81%对这三个班来说还不错(随机猜测相当于 33%)。写自己的特色可以做得更好。

写自己的特色

一个功能没有什么神奇的。这只是一个我们从图像中计算出来的数字。文献中已经定义了几个特征集。这些通常还有一个额外的优势,即它们被设计和研究成对许多不重要的因素不变。例如,线性二进制模式对于将所有像素值乘以一个数或向所有这些值添加一个常数是完全不变的。这使得该特征集对光照变化具有鲁棒性。

然而,您的特定用例也有可能受益于一些特别设计的特性。

mahotas没有附带的一个简单类型的特征是颜色直方图。幸运的是,这个特性很容易实现。颜色直方图将颜色空间划分为一组面元,然后计算每个面元中有多少像素。

图像采用 RGB 格式,即每个像素有三个值:R 代表红色,G 代表绿色,B 代表蓝色。由于这些组件中的每一个都是 8 位值,因此总共有 1700 万种不同的颜色。我们将通过将颜色分组到箱中,将这个数字减少到只有 64 种颜色。我们将编写一个函数来封装这个算法,如下所示:

def chist(im): 

为了绑定颜色,我们首先将图像除以64,如下舍入像素值:

im = im // 64 

这使得像素值的范围从零到三,这给了我们总共64种不同的颜色。

按照以下步骤分离红色、绿色和蓝色通道:

r,g,b = im.transpose((2,0,1)) 
pixels = 1 * r + 4 * b + 16 * g 
hist = np.bincount(pixels.ravel(), minlength=64) 
hist = hist.astype(float) 

转换为对数刻度,如下面的代码片段所示。严格来说,这不是必需的,但是有利于更好的特性。我们使用np.log1p,它计算对数(h+1) 。这可确保零值保持为零值(数学上,零的对数未定义,如果您尝试计算,NumPy 会打印警告):

hist = np.log1p(hist) 
return hist 

我们可以修改前面的处理代码,以便非常容易地使用我们编写的函数:

features = [] 
for im in images: 
    image = mh.imread(im) 
    features.append(chist(im)) 

使用我们之前使用的相同交叉验证代码,我们获得了 90%的准确性。然而,最好的结果来自于结合所有的特性,我们可以实现如下:

features = [] 
for im in images: 
    imcolor = mh.imread(im) 
    im = mh.colors.rgb2gray(imcolor, dtype=np.uint8) 
    features.append(np.concatenate([ 
          mh.features.haralick(im).ravel(), 
          chist(imcolor), 
      ])) 

通过使用所有这些特性,我们获得了百分之95.6的准确率,如下面的代码片段所示:

scores = model_selection.cross_val_score( 
    clf, features, labels, cv=cv) 
print('Accuracy: {:.1%}'.format(scores.mean())) 
Accuracy: 95.6% 

这完美地说明了好的算法是最简单的。您始终可以使用 scikit-learn 中最先进的分类实现。真正的秘密和附加值往往来自功能设计和工程。这就是数据集知识的价值所在。

使用特征查找相似图像

通过相对少量的特征来表示图像的基本概念可以不仅仅用于分类。例如,我们还可以使用它来查找与给定查询图像相似的图像(就像我们之前对文本文档所做的那样)。

我们将计算与以前相同的特征,但有一个重要的区别:我们将忽略图片的边界区域。原因是,由于构图的业余性质,画面的边缘往往包含不相关的元素。当在整个图像上计算特征时,这些元素被考虑在内。通过简单地忽略它们,我们得到了稍微好一点的特性。在有监督的例子中,这并不重要,因为学习算法将学习哪些特征信息更丰富,并相应地对它们进行加权。当以无监督的方式工作时,我们需要更加小心,以确保我们的特征捕捉到数据的重要元素。这在如下循环中实现:

features = [] 
for im in images: 
    imcolor = mh.imread(im) 
    # ignore everything in the 200 pixels closest to the borders 
    imcolor = imcolor[200:-200, 200:-200] 
    im = mh.colors.rgb2gray(imcolor, dtype=np.uint8) 
    features.append(np.concatenate([ 
          mh.features.haralick(im).ravel(), 
          chist(imcolor), 
      ])) 

我们现在对特征进行归一化,并计算距离矩阵,如下所示:

sc = StandardScaler() 
features = sc.fit_transform(features) 
from scipy.spatial import distance 
dists = distance.squareform(distance.pdist(features)) 

我们将只绘制数据的子集(每 10 个元素),这样查询将在顶部,返回的最近邻居在底部,如下面的代码所示:

fig, axes = plt.subplots(2, 9) 
for ci,i in enumerate(range(0,90,10)): 
    query = images[i] 
    dists_query = dists[i] 
    closest = dists_query.argsort() 
    # closest[0] is same as the query image, so pick next closest 
    closest = closest[1] 
    result = images[closest] 
    query = mh.imread(query) 
    result = mh.imread(result) 
    axes[0, ci].imshow(query) 
    axes[1, ci].imshow(result) 

结果显示在下面的截图中(顶部图像是查询图像,底部图像是返回的结果):

很明显,该系统并不完美,但可以找到至少在视觉上与查询相似的图像。除了一种情况之外,找到的图像都来自与查询相同的类。

分类较难的数据集

之前的数据集是一个易于使用纹理特征进行分类的数据集。事实上,从商业角度来看,许多有趣的问题都相对容易。然而,有时我们可能会面临一个更棘手的问题,需要更好、更现代的技术来获得好的结果。

我们现在将测试一个公共数据集,它具有相同的结构:几张照片被分成少量的类。课程有动物、汽车、交通和自然景观。

与我们之前讨论的三类问题相比,这些类更难区分。自然场景、建筑和文本具有非常不同的纹理。然而,在这个数据集中,纹理和颜色不是图像类的清晰标记。以下是动物类的一个例子:

这是汽车课的另一个例子:

两个对象都是在自然背景下,对象内部有大而平滑的区域。这是一个比以前的数据集更难的问题,因此我们需要使用更高级的方法。第一个改进是使用稍微强大一点的分类器。scikit-learn 提供的逻辑回归是一种惩罚形式的逻辑回归,它包含一个可调参数C。默认情况下,C = 1.0,但这可能不是最佳选择。我们可以使用网格搜索为这个参数找到一个好的值,如下所示:

from sklearn.grid_search import GridSearchCV 
C_range = 10.0 ** np.arange(-4, 3) 
grid = GridSearchCV(LogisticRegression(), param_grid={'C' : C_range}) 
clf = Pipeline([('preproc', StandardScaler()), 
               ('classifier', grid)]) 

数据在数据集中不是以随机的顺序组织的:相似的图像靠得很近。因此,我们使用一个交叉验证计划来考虑被打乱的数据,这样每个文件夹都有一个更具代表性的训练集,如下面的代码所示:

cv = model_selection.KFold(n_splits=5, 
                     shuffle=True, random_state=123) 
scores = model_selection.cross_val_score( 
   clf, ifeatures, labels, cv=cv) 
print('Accuracy: {:.1%}'.format(scores.mean())) 
Accuracy: 73.4% 

这对于四个类来说还不错,但是我们现在将看看我们是否可以通过使用一组不同的特性做得更好。事实上,我们将看到,我们需要将这些特性与其他方法结合起来,以获得最佳的可能结果。

局部特征表示

与我们之前使用的特征不同,局部特征是在图像的一个小区域上计算的。Mahotas 支持名为加速鲁棒特征 ( SURF )的计算类型特征。这些特征被设计为对旋转或照明变化具有鲁棒性(也就是说,它们仅在照明变化时轻微改变它们的值)。

使用这些功能时,我们必须决定在哪里计算它们。通常使用三种可能性:

  • 随便地
  • 在网格中
  • 检测图像的感兴趣区域(一种称为关键点检测或兴趣点检测的技术)

所有这些都是有效的,并将在适当的情况下产生良好的结果。Mahotas 支持这三种方法。如果您有理由预期您的兴趣点将对应于图像中的重要区域,则使用兴趣点检测效果最佳。

我们将使用interest point方法。用mahotas计算特征很简单:导入正确的子模块,调用surf.surf函数如下:

descriptors = surf.surf(im, descriptor_only=True) 

descriptors_only=True标志意味着我们只对局部特征本身感兴趣,而对它们的像素位置、大小或方向不感兴趣(单词descriptor经常用来指代这些局部特征)。或者,我们可以使用dense sampling方法,使用如下的surf.dense功能:

from mahotas.features import surf 
descriptors = surf.dense(im, spacing=16) 

这将返回在相距 24 像素的点上计算的描述符的值。由于点的位置是固定的,所以关于兴趣点的元信息不是很有趣,并且默认不返回。在任一情况下,结果(描述符)都是 nx 64 数组,其中 n 是采样的点数。点数取决于图像的大小、内容以及传递给函数的参数。在这个例子中,我们使用默认设置,每个图像获得几百个描述符。

我们不能将这些描述符直接输入到支持向量机、逻辑回归机或类似的分类系统中。为了使用来自图像的描述符,有几种解决方案。我们可以对它们进行平均,但是这样做的结果不是很好,因为它们会丢弃所有特定于位置的信息。在这种情况下,我们将只有另一个基于边缘测量的全局特征集。

我们这里要用到的解决方案就是包字模型。它于 2004 年首次出版,但这是一个明显的后知后觉的想法;实现起来非常简单,取得了很好的效果。

在处理图像的时候说可能会显得很奇怪。如果你认为你没有书面文字,很容易区分,而是口头音频,这可能更容易理解。现在,每次说一个单词,听起来都会略有不同,不同的说话人会有自己的发音。因此,一个单词的波形不会在每次说的时候都一样。然而,通过在这些波形上使用聚类,我们可以希望恢复大部分结构,以便给定单词的所有实例都在同一聚类中。即使过程不完美(也不会完美),我们仍然可以谈论将波形分组为单词。

我们对图像数据执行相同的操作:我们将所有图像中看起来相似的区域聚集在一起,并将这些视觉单词称为

The number of words used does not usually have a big impact on the final performance of the algorithm. Naturally, if the number is extremely small (10 or 20, when you have a few thousand images), then the overall system will not perform well. Similarly, if you have too many words (many more than the number of images, for example), the system will also not perform well. However, in between these two extremes, there is often a very large plateau, where you can choose the number of words without a big impact on the result. As a rule of thumb, using a value such as 256, 512, or 1,024 if you have many images should give you a good result.

我们将从计算以下特征开始:

alldescriptors = [] 
for im in images: 
    im = mh.imread(im, as_grey=True) 
    im = im.astype(np.uint8) 
    alldescriptors.append((surf.surf(im, descriptor_only=True)) 
# get all descriptors into a single array 
concatenated = np.concatenate(alldescriptors) 

现在,我们使用 k-means 聚类来获得质心。我们可以使用所有的描述符,但是我们将使用一个更小的样本来提高速度。我们有几百万个描述符,全部使用它们并没有错。然而,这将需要更多的计算,几乎没有额外的好处。采样和聚类如以下代码所示:

# use only every 64th vector 
concatenated = concatenated[::64] 
from sklearn.cluster import KMeans 
k = 256 
km = KMeans(k) 
km.fit(concatenated) 

完成后(需要一段时间),km对象包含质心信息。我们现在回到描述符,构建如下特征向量:

sfeatures = [] 
for d in alldescriptors: 
    c = km.predict(d) 
    sfeatures.append(np.bincount(c, minlength=256)) 
# build single array and convert to float 
sfeatures = np.array(sfeatures, dtype=float) 

这个循环的最终结果是sfeatures[fi, fj]是图像fi包含元素fj的次数。用np.histogram函数可以更快地计算出同样的结果,但是要让参数恰到好处有点棘手。我们将结果转换为浮点,因为我们不需要整数算术(及其舍入语义)。

结果是,每个图像现在由相同大小的单个特征阵列表示(在我们的例子中,簇的数量是 256)。因此,我们可以使用如下标准分类方法:

scores = model_selection.cross_val_score( 
   clf, sfeatures, labels, cv=cv) 
print('Accuracy: {:.1%}'.format(scores.mean())) 
Accuracy: 62.4% 

这比以前更糟糕了!我们一无所获吗?

事实上,我们有,因为我们可以将所有特征组合在一起以获得百分之76.7的准确度,如下所示:

allfeatures = np.hstack([ifeatures, sfeatures]) scores = model_selection.cross_val_score( clf, allfeatures, labels, cv=cv) print('Accuracy: {:.1%}'.format(scores.mean())) 
Accuracy: 76.7% 

这是我们拥有的最好的结果,比任何单一的特征集都好。这是由于局部 SURF 特征足够不同,以向我们之前拥有的全局图像特征添加新信息,并改善组合结果。

敌对网络下的图像生成

生成性对抗网络 ( GANs )是一种新的、潮流的网络类型。他们的主要吸引力是生殖方面。这意味着我们可以训练一个网络来生成一个类似于引用的新数据样本。

几年前,研究人员使用深度信念网络 ( DBN )来完成这项任务,它由一个可见层和一组内部层组成,最终会反复出现。训练这样的网络相当困难,所以人们考虑新的架构。

进入我们的 GAN。我们如何训练网络来生成类似于参考的样本?首先,我们需要设计一个发电机网络。通常,我们需要一组随机变量,这些变量将被输入到一组密集的conv2d_transpose层中。后者与conv2d层相反,从看起来像卷积输出的输入到看起来像卷积输入的输出。

现在,为了训练这个网络,我们使用对抗性部分。诀窍是训练另一个网络,即鉴别器,以检测样本是真实样本还是生成样本。一次迭代将训练鉴别器以增强其鉴别能力,之后的迭代将训练生成器以获得更接近真实图像的图像。

让我们尝试生成逼真的手写数字;我们将重用我们以前的 CNN 分类器的部分内容。我们需要在那里添加一个生成器,并改变图层,以考虑到将生成我们的图像的额外随机输入。

让我们从一些辅助函数开始:我们的cost函数的匹配辅助函数,以及将新生成的样本写入磁盘并在训练期间显示的函数。我们还为批处理规范化创建了自己的层,以简化底层计算:

import tensorflow as tf
import numpy as np

def match(logits, labels):
    logits = tf.clip_by_value(logits, 1e-7, 1\. - 1e-7)
    return tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(
        logits=logits, labels=labels))

We use the non-sparse version of the cost function helper we used before, softmax_cross_entropy_with_logits_v2. We could have used the sparse version as well, but at the cost of additional code. As there is only one output value, this is simple to handle.

让我们解释批处理规范化层。我们计算输入张量的平均值和标准差。然后,我们对这个矩阵进行归一化(这样,矩阵的平均值为0,标准差为1)。我们明确处理了 2D 矩阵和 4D 矩阵,因为我们需要明确处理坐标轴的差异:

def batchnormalize(X, eps=1e-8, g=None, b=None):
    if X.get_shape().ndims == 4:
        mean = tf.reduce_mean(X, [0,1,2])
        std = tf.reduce_mean( tf.square(X-mean), [0,1,2] )
        X = (X-mean) / tf.sqrt(std+eps)

        if g is not None and b is not None:
            g = tf.reshape(g, [1,1,1,-1])
            b = tf.reshape(b, [1,1,1,-1])
            X = X*g + b

    elif X.get_shape().ndims == 2:
        mean = tf.reduce_mean(X, 0)
        std = tf.reduce_mean(tf.square(X-mean), 0)
        X = (X-mean) / tf.sqrt(std+eps)

        if g is not None and b is not None:
            g = tf.reshape(g, [1,-1])
            b = tf.reshape(b, [1,-1])
            X = X*g + b

    else:
        raise NotImplementedError

    return X

def save_visualization(X, nh_nw, save_path='./sample.jpg'):
    from imageio import imwrite
    from matplotlib import pyplot as plt
    h,w = X.shape[1], X.shape[2]
    img = np.zeros((h * nh_nw[0], w * nh_nw[1], 3))

    for n,x in enumerate(X):
        j = n // nh_nw[1]
        i = n % nh_nw[1]
        img[j*h:j*h+h, i*w:i*w+w, :] = x

    img = img.astype(np.uint8)
    imwrite(save_path, img)
    plt.imshow(img)
    plt.show()

As we are using the sigmoid mapping function for probabilities, we need to remove values 0 and 1 from the mapping (as they map from infinity). We do that by adding 1e-7 to 0 and subtracting it from 1.

我们现在可以创建我们的类,创建我们的模型。构造器将有额外的新参数,类的数量Y,以及随机状态的大小Z:

class DCGAN():
    def __init__(
            self,
            image_shape=[28,28,1],
            dim_z=100,
            dim_y=10,
            dim_W1=1024,
            dim_W2=128,
            dim_W3=64,
            dim_channel=1,
            ):

        self.image_shape = image_shape
        self.dim_z = dim_z
        self.dim_y = dim_y

        self.dim_W1 = dim_W1
        self.dim_W2 = dim_W2
        self.dim_W3 = dim_W3
        self.dim_channel = dim_channel

现在,这是我们为生成器创建的新的特殊层,它将创建良好的图像——我们之前谈到的卷积转置层:

def create_conv2d_transpose(self, input, filters, kernel_size, name, with_batch_norm):
        layer = tf.layers.conv2d_transpose(
                    inputs=input,
                    filters=filters,
                    kernel_size=kernel_size,
                    strides=[2,2],
                    name="Conv2d_transpose_" + name,
                    padding="SAME")
        if with_batch_norm:
            layer = batchnormalize(layer)
            layer = tf.nn.relu(layer, name="RELU_" + name)
        return layer

我们的鉴别器应该返回真实图像的01之间的概率。为了实现这一点并允许生成器创建各种类型的图像,我们将图像以及它们在每一层上的类驱动到鉴别器中:

def discriminate(self, image, Y, reuse=False):
        with tf.variable_scope('discriminate', reuse=reuse):
            Y = tf.one_hot(Y, dim_y)
            yb = tf.reshape(Y, tf.stack([-1, 1, 1, self.dim_y]))
            image = tf.concat(axis=3, values=
                [image, yb*tf.ones([1, 28, 28, self.dim_y])])

            h1 = self.create_conv2d(image, self.dim_W3, 5, "Lay-er1", True)
            h1 = tf.concat(axis=3, values=
                [h1, yb*tf.ones([1, 14, 14, self.dim_y])])

            h2 = self.create_conv2d(h1, self.dim_W2, 5, "Layer2", True)
            h2 = tf.reshape(h2, tf.stack([-1, 7*7*128]))
            h2 = tf.concat(axis=1, values=[h2, Y])

            h3 = self.create_dense(h2, self.dim_W1, "Layer3", True)
            h3 = tf.concat(axis=1, values=[h3, Y])

            h4 = self.create_dense(h3, 1, "Layer4", True)
            return h4

正如我们之前所说的,生成器做相反的事情,从我们的类变量和随机状态到最终生成的值在01之间的图像:

def generate(self, Z, Y, reuse=False):
        with tf.variable_scope('generate', reuse=reuse):

            Y = tf.one_hot(Y, dim_y)
            yb = tf.reshape(Y, tf.stack([-1, 1, 1, self.dim_y]))
            Z = tf.concat(axis=1, values=[Z,Y])
            h1 = self.create_dense(Z, self.dim_W1, "Layer1", False)
            h1 = tf.concat(axis=1, values=[h1, Y])
            h2 = self.create_dense(h1, self.dim_W2*7*7, "Layer2", False)
            h2 = tf.reshape(h2, tf.stack([-1,7,7,self.dim_W2]))
            h2 = tf.concat(axis=3, values=
                [h2, yb*tf.ones([1, 7, 7, self.dim_y])])

            h3 = self.create_conv2d_transpose(h2, self.dim_W3, 5, "Layer3", True)
            h3 = tf.concat(axis=3, values=
                [h3, yb*tf.ones([1, 14,14,self.dim_y])] )

            h4 = self.create_conv2d_transpose(
                h3, self.dim_channel, 7, "Layer4", False)
            x = tf.nn.sigmoid(h4)
            return x

现在是组装零件的时候了。我们为生成器以及真实图像输入创建占位符。然后我们创建我们的图像生成器(我们将使用它来显示我们生成的图像),然后创建我们的鉴别器。诀窍就在这里。我们同时创造了两个。一个用于实像,应返回1;另一个将被输入生成的图像并返回0。由于两者共享相同的权重,我们为第二个鉴别器传递重用标志。

我们试图为鉴别器步骤优化的成本是两个鉴别器和生成器的差异之和。如前所述,我们优化了生成器,以在鉴别器上获得一个1:

def build_model(self):
    Z = tf.placeholder(tf.float32, [None, self.dim_z])
    Y = tf.placeholder(tf.int64, [None])

    image_real = tf.placeholder(tf.float32, [None]+self.image_shape)
    image_gen = self.generate(Z, Y)

    raw_real = self.discriminate(image_real, Y, False)
    raw_gen = self.discriminate(image_gen, Y, True)

    discrim_cost_real = match(raw_real, tf.ones_like(raw_real))
    discrim_cost_gen = match(raw_gen, tf.zeros_like(raw_gen))
    discrim_cost = discrim_cost_real + discrim_cost_gen

    gen_cost = match( raw_gen, tf.ones_like(raw_gen) )

    return Z, Y, is_training, image_real, image_gen, dis-crim_cost, gen_cost

我们现在可以开始用超参数建立图表:

n_epochs = 10
learning_rate = 0.0002
batch_size = 1024
image_shape = [28,28,1]
dim_z = 10
dim_y = 10
dim_W1 = 1024
dim_W2 = 128
dim_W3 = 64
dim_channel = 1

visualize_dim=196
step = 200

和以前一样,我们阅读了 MNIST 数据集:

from sklearn.datasets import fetch_mldata
mnist = fetch_mldata('MNIST original')
mnist.data.shape = (-1, 28, 28)
mnist.data = mnist.data.astype(np.float32).reshape( [-1, 28, 28, 1]) / 255.
mnist.num_examples = len(mnist.data)

我们创建我们的图表。我们将变量分成两个列表(因为它们有一个前缀),每个列表都有一个 Adam 优化器。我们还为样本创建变量,以检查我们的生成器是否已经开始生成可识别的图像:

from sklearn.datasets import fetch_mldata
mnist = fetch_mldata('MNIST original')
mnist.data.shape = (-1, 28, 28)
mnist.data = mnist.data.astype(np.float32).reshape( [-1, 28, 28, 1]) / 255.
mnist.num_examples = len(mnist.data)
tf.reset_default_graph()
dcgan_model = DCGAN(
        image_shape=image_shape,
        dim_z=dim_z,
        dim_W1=dim_W1,
        dim_W2=dim_W2,
        dim_W3=dim_W3,
        )
Z_tf, Y_tf, iimage_tf, image_tf_sample, d_cost_tf, g_cost_tf, = dcgan_model.build_model()
discrim_vars = list(filter(lambda x: x.name.startswith('discriminate'),
    tf.trainable_variables()))
gen_vars = list(filter(lambda x: x.name.startswith('generate'), tf.trainable_variables()))

train_op_discrim = tf.train.AdamOptimizer(learning_rate, beta1=0.5).minimize(
    d_cost_tf, var_list=discrim_vars)
train_op_gen = tf.train.AdamOptimizer(learning_rate, beta1=0.5).minimize(
    g_cost_tf, var_list=gen_vars)

Z_np_sample = np.random.uniform(-1, 1, size=(visualize_dim,dim_z))
Y_np_sample = np.random.randint(10, size=[visualize_dim])

在我们的会话中,我们首先使用 Tensorflow 将标签转换为单向编码,然后使用与之前网络相同的模式。我们为每批生成的图像生成随机数,然后按照计划优化鉴别器和生成器:

with tf.Session() as sess:
    mnist.target = tf.one_hot(mnist.target.astype(np.int8), dim_y).eval()
    Y_np_sample = tf.one_hot(Y_np_sample, dim_y).eval()

    sess.run(tf.global_variables_initializer())
    for epoch in range(n_epochs):
        permut = np.random.permutation(mnist.num_examples)
        trX = mnist.data[permut]
        trY = mnist.target[permut]
        Z = np.random.uniform(-1, 1, size=[mnist.num_examples, dim_z]).astype(np.float32)

        print("epoch: %i" % epoch)
        for j in range(0, mnist.num_examples, batch_size):
            if j % step == 0:
                print(" batch: %i" % j)

            batch = permut[j:j+batch_size]

            Xs = trX[batch]
            Ys = trY[batch]
            Zs = Z[batch]

            sess.run(train_op_discrim,
                    feed_dict={
                        Z_tf:Zs,
                        Y_tf:Ys,
                        image_tf:Xs,
                        })

            sess.run(train_op_gen,
                    feed_dict={
                        Z_tf:Zs,
                        Y_tf:Ys,
                        })

            if j % step == 0:
                generated_samples = sess.run(
                        image_tf_sample,
                        feed_dict={
                            Z_tf:Z_np_sample,
                            Y_tf:Y_np_sample,
                            })
                generated_samples = generated_samples * 255
                save_visualization(generated_samples, (7,28),
                    save_path='./sample_%03d_%04d.jpg' %
                        (epoch, j / step))
epoch: 0
 batch: 0


epoch: 3
 batch: 0

epoch: 9
 batch: 64000

我们可以看到非常早期的类似手指的形状。它们的进化方式非常有趣。它们从光滑到松脆再到嘈杂(对于大量的时代)。这是可以理解的,因为这些网络没有融合。因为它们是对抗性的,每次一个人学会了对另一个人有效的技巧,另一个人就会反击。例如,如果图像不够平滑,鉴别器可以对这个差异进行鉴别,如果生成器生成平滑图像,则鉴别器将继续处理其他差异。问题是发电机会忘记过去已知的把戏,所以没有办法停在有意义的点上!

摘要

我们学习了在机器学习环境中处理图像的经典的基于特征的方法;通过将一百万个像素转换成几个数字特征,我们能够直接使用逻辑回归分类器。我们在其他章节中学到的所有技术突然变得直接适用于图像问题。我们看到了一个使用图像特征在数据集中查找相似图像的例子。

我们还学习了如何使用单词包模型中的局部特征进行分类。这是一种非常现代的计算机视觉方法,并取得了良好的效果,同时对于图像的许多不相关方面(例如照明,甚至同一图像中的不均匀照明)足够鲁棒。我们还将聚类作为分类中一个有用的中间步骤,而不是目的本身。

我们关注的是mahotas,这是 Python 中主要的计算机视觉库之一。还有一些同样得到很好的维护。Skimage 在精神上是相似的,但是有一套不同的特征。OpenCV 是一个非常好的带有 Python 接口的 C++库。所有这些都可以与 NumPy 数组一起工作,您可以混合和匹配不同库中的函数来构建复杂的计算机视觉管道。

我们还尝试了一种用 Tensorflow(可用于非图像域)生成类似图像的新方法,目前流行的网络类型名为 GAN。

第 13 章强化学习中,我们将探讨强化学习这一深度学习的热门话题。我们将看到如何让神经网络在不告诉它任何事情的情况下学习一套规则。

1
https://gitee.com/OpenDocCN/apachecn-ml-zh.git
git@gitee.com:OpenDocCN/apachecn-ml-zh.git
OpenDocCN
apachecn-ml-zh
apachecn-ml-zh
master

搜索帮助