用 OpenCV 绘图
原文:https://pyimagesearch.com/2021/01/27/drawing-with-opencv/
在本教程中,你将学习如何使用 OpenCV 的基本绘图功能。您将学习如何使用 OpenCV 来绘制:
- 线
- 长方形
- 环
您还将学习如何使用 OpenCV 在图像和用 NumPy 初始化的空白/空数组上绘图。
学习如何使用 OpenCV 的基本绘图功能, 继续阅读。
用 OpenCV 绘图
在本教程的第一部分,我们将简要回顾 OpenCV 的绘图功能。
然后,我们将配置我们的开发环境,并检查我们的项目目录结构。
完成审查后,我们将继续实现两个 Python 脚本:
basic_drawing.py
image_drawing.py
这些脚本将帮助您理解如何使用 OpenCV 执行基本的绘图功能。
本指南结束时,您将了解如何使用 OpenCV 绘制直线、圆和矩形。
OpenCV 中的绘图功能
OpenCV 有许多绘图函数,您可以使用它们来绘制各种形状,包括不规则形状的多边形,但是您将看到的三个最常见的 OpenCV 绘图函数是:
cv2.line
:在图像上画一条线,从指定的 (x,y) 坐标开始,到另一个 (x,y) 坐标结束cv2.circle
:在由中心 (x,y) 坐标和提供的半径指定的图像上画一个圆cv2.rectangle
:在由左上角和右下角指定的图像上绘制一个矩形 (x,y)-坐标
今天我们将讨论这三个绘图功能。
但是,值得注意的是,还有更高级的 OpenCV 绘图函数,包括:
cv2.ellipse
:在图像上画一个椭圆cv2.polylines
:绘制一组 (x,y) 坐标指定的多边形轮廓cv2.fillPoly
:绘制多边形,但不是绘制轮廓,而是填充多边形cv2.arrowedLine
:画一个箭头,从起点 (x,y) 坐标指向终点 (x,y) 坐标
这些 OpenCV 绘图函数不太常用,但仍然值得注意。我们偶尔会在 PyImageSearch 博客上使用它们。
配置您的开发环境
为了遵循本指南,您需要在您的系统上安装 OpenCV 库。
幸运的是,OpenCV 可以通过 pip 安装:
$ pip install opencv-contrib-python
如果你需要帮助为 OpenCV 配置开发环境,我强烈推荐阅读我的 pip 安装 OpenCV 指南——它将在几分钟内让你的系统启动并运行。
在配置开发环境时遇到了问题?
Figure 1: Having trouble configuring your development environment? Want access to pre-configured Jupyter Notebooks running on Google Colab? Be sure to join PyImageSearch Plus — your system will be up and running with this tutorial in a matter of minutes.
说了这么多,你是:
- 时间紧迫?
- 了解你雇主的行政锁定系统?
- 想要跳过与命令行、包管理器和虚拟环境斗争的麻烦吗?
- 准备好在您的 Windows、macOS 或 Linux 系统上运行代码了吗?
那今天就加入 PyImageSearch 加吧!
获得本教程的 Jupyter 笔记本和其他 PyImageSearch 指南,这些指南是 预先配置的 **,可以在您的网络浏览器中运行在 Google Colab 的生态系统上!**无需安装。
最棒的是,这些 Jupyter 笔记本可以在 Windows、macOS 和 Linux 上运行!
项目结构
让我们从查看 OpenCV 绘图项目的项目目录结构开始:
$ tree . --dirsfirst
.
├── adrian.png
├── basic_drawing.py
└── image_drawing.py
0 directories, 3 files
我们今天要复习两个 Python 脚本:
- 初始化一个空的 NumPy 数组,并利用 OpenCV 绘制线条、圆形和矩形
image_drawing.py
:从磁盘加载adrian.png
,然后在图像上绘制(而不是空的/空白的 NumPy 数组画布)。
我们现在准备开始了!
用 OpenCV 实现基本绘图功能
在我们绘制实际图像之前,让我们首先学习如何初始化一个空的 NumPy 数组/图像并在其上绘制。
打开项目目录结构中的basic_drawing.py
文件,让我们开始工作。
# import the necessary packages
import numpy as np
import cv2
# initialize our canvas as a 300x300 pixel image with 3 channels
# (Red, Green, and Blue) with a black background
canvas = np.zeros((300, 300, 3), dtype="uint8")
第 2 行和第 3 行导入我们将要使用的包。
作为一种快捷方式,我们将为numpy
创建一个别名为np
。您将在所有利用 NumPy 的 PyImageSearch 教程中看到这种约定(事实上,您在 Python 社区中也会经常看到这种约定!)
我们还将导入cv2
,因此我们可以访问 OpenCV 库。
初始化我们的图像是在线 7 上处理的。我们使用np.zeros
方法构造一个 300 行 300 列的 NumPy 数组,得到一个 300 x 300 像素的图像。我们还为 3 个通道分配空间——红色、绿色和蓝色各一个。顾名思义,np.zeros
方法用初始值零填充数组中的每个元素。
其次,需要注意的是np.zeros
方法的第二个参数:数据类型dtype
。
因为我们用范围*【0,255】*内的像素来表示我们的图像,所以我们必须使用 8 位无符号整数,或uint8
。我们可以使用许多其他的数据类型(常见的包括 32 位整数和 32 位和 64 位浮点数),但是对于本课中的大多数例子,我们将主要使用uint8
。
现在我们已经初始化了画布,我们可以开始绘制了:
# draw a green line from the top-left corner of our canvas to the
# bottom-right
green = (0, 255, 0)
cv2.line(canvas, (0, 0), (300, 300), green)
cv2.imshow("Canvas", canvas)
cv2.waitKey(0)
# draw a 3 pixel thick red line from the top-right corner to the
# bottom-left
red = (0, 0, 255)
cv2.line(canvas, (300, 0), (0, 300), red, 3)
cv2.imshow("Canvas", canvas)
cv2.waitKey(0)
我们在第 11 行做的第一件事是定义一个用于表示颜色“绿色”的元组然后,我们从图像左上角的点 (0,0)(T4)到图像右下角的点 (300,300) (在第 12 行处)画一条绿线。****
为了画线,我们使用了cv2.line
方法:
- 这个方法的第一个参数是我们将要绘制的图像。在这种情况下,是我们的
canvas
。 - **第二个参数是该行的起点。**我们选择从图像左上角的开始我们的行,在点 (0,0)——同样,记住 Python 语言是零索引的。
- 我们还需要为该行提供一个终点(第三个参数)。我们定义我们的终点为 (300,300) ,图像右下角的。**
- 最后一个参数是我们的线的颜色(在本例中是绿色)。第 13 和 14 行显示我们的图像,然后等待按键(见图 2 )。
**
Figure 2: Drawing lines with OpenCV.
如你所见,使用cv2.line
函数非常简单!但是在cv2.line
方法中还有一个重要的参数需要考虑:厚度。
在**第 18-21 行,我们将红色定义为一个元组(同样是 BGR 格式,而不是 RGB 格式)。然后我们从图像右上角的到左下角的画一条红线。该方法的最后一个参数控制线条的粗细——我们决定将粗细设为 3 个像素。我们再次显示我们的图像并等待按键:
Figure 3: Drawing multiple lines with OpenCV.
画一条线很简单。现在我们可以继续画矩形了。查看下面的代码了解更多细节:
# draw a green 50x50 pixel square, starting at 10x10 and ending at 60x60
cv2.rectangle(canvas, (10, 10), (60, 60), green)
cv2.imshow("Canvas", canvas)
cv2.waitKey(0)
# draw another rectangle, this one red with 5 pixel thickness
cv2.rectangle(canvas, (50, 200), (200, 225), red, 5)
cv2.imshow("Canvas", canvas)
cv2.waitKey(0)
# draw a final rectangle (blue and filled in )
blue = (255, 0, 0)
cv2.rectangle(canvas, (200, 50), (225, 125), blue, -1)
cv2.imshow("Canvas", canvas)
cv2.waitKey(0)
在的第 24 行,我们使用了cv2.rectangle
的方法。这个方法的签名与上面的cv2.line
方法相同,但是无论如何让我们研究一下每个参数:
- **第一个参数是我们要在其上绘制矩形的图像。**我们想利用我们的
canvas
,所以我们把它传递到方法中。 - 第二个参数是我们矩形的起始位置 (x,y)—这里,我们从点 (10,10) 开始矩形。
- 然后,我们必须为矩形提供一个结束点 (x,y) 。我们决定在 (60,60) 处结束我们的矩形,定义一个 50 x 50 像素的区域(花一秒钟让自己相信最终的矩形是 50 x 50 )。
- **最后,最后一个参数是我们要绘制的矩形的颜色。**这里,我们画一个绿色的长方形。
正如我们可以控制线条的粗细一样,我们也可以控制矩形的粗细。第 29 行提供了厚度参数。这里,我们画一个 5 像素厚的红色矩形,从点 (50,200) 开始,到 (200,225)结束。
至此,我们只画出了一个长方形的轮廓。我们如何画一个“完全填充”的矩形?
简单。我们只是为厚度参数传递了一个负值。
演示了如何绘制一个纯色的矩形。我们画一个蓝色的矩形,从 (200,50) 开始,到 (225,125) 结束。通过指定-1
(或者使用cv2.FILLED
关键字)作为厚度,我们的矩形被绘制为纯蓝色。
图 4 显示绘制我们的线条和矩形的完整输出:
Figure 4: Using OpenCV to draw lines and rectangles.
如您所见,输出与我们的代码相匹配。我们可以从左上角到右下角画一条绿线,然后从右上角到左下角画一条粗红线。
我们还能够画出一个绿色矩形 T1,一个稍微 T2 粗一点的红色矩形 T3,以及一个完全填充的蓝色矩形 T4。
这很好,但是圆圈呢?
怎样才能用 OpenCV 画圆?
画圆和画矩形一样简单,但函数参数略有不同:
# re-initialize our canvas as an empty array, then compute the
# center (x, y)-coordinates of the canvas
canvas = np.zeros((300, 300, 3), dtype="uint8")
(centerX, centerY) = (canvas.shape[1] // 2, canvas.shape[0] // 2)
white = (255, 255, 255)
# loop over increasing radii, from 25 pixels to 150 pixels in 25
# pixel increments
for r in range(0, 175, 25):
# draw a white circle with the current radius size
cv2.circle(canvas, (centerX, centerY), r, white)
# show our work of art
cv2.imshow("Canvas", canvas)
cv2.waitKey(0)
在**第 41 行,**我们将画布重新初始化为空白:
Figure 5: Re-initializing our canvas as a blank image.
第 42 行计算两个变量:centerX
和centerY
。这两个变量代表图像中心的 (x,y)-坐标。
我们通过检查 NumPy 数组的形状来计算中心,然后除以 2:
- 图像的高度可以在
canvas.shape[0]
(行数)中找到 - 宽度在
canvas.shape[1]
(列数)中找到
最后,行 43 定义了一个白色像素(即,红色、绿色和蓝色分量的每一个的桶都是“满的”)。
现在,让我们画一些圆圈!
在**第 45 行,**我们循环了几个半径值,从0
开始,到150
结束,每一步增加25
。range
功能是独占;因此,我们指定停止值为175
而不是150
。
要亲自演示这一点,请打开 Python shell,并执行以下代码:
$ python
>>> list(range(0, 175, 25))
[0, 25, 50, 75, 100, 125, 150]
注意range
的输出如何在150
停止,并且不包括175
。
第 49 行处理圆的实际绘制:
- 第一个参数是我们的
canvas
,我们想要在上面画圆的图像。 - 然后我们需要提供一个点,我们将围绕这个点画一个圆。我们传入一个元组
(centerX, centerY)
,这样我们的圆就会以图像的中心为中心。 - 第三个参数是我们想要画的圆的半径
r
。 - 最后,我们传入圆圈的颜色:在本例中,是白色。
第 52 行和第 53 行然后显示我们的图像并等待按键:
Figure 6: Drawing a bullseye with OpenCV.
查看图 6 ,你会看到我们已经画了一个简单的靶心!图像正中心的“点”是以半径 0 绘制的。较大的圆是从我们的for
循环中以不断增加的半径尺寸画出的。
不算太坏。但是我们还能做什么呢?
让我们画一些抽象画:
# re-initialize our canvas once again
canvas = np.zeros((300, 300, 3), dtype="uint8")
# let's draw 25 random circles
for i in range(0, 25):
# randomly generate a radius size between 5 and 200, generate a
# random color, and then pick a random point on our canvas where
# the circle will be drawn
radius = np.random.randint(5, high=200)
color = np.random.randint(0, high=256, size=(3,)).tolist()
pt = np.random.randint(0, high=300, size=(2,))
# draw our random circle on the canvas
cv2.circle(canvas, tuple(pt), radius, color, -1)
# display our masterpiece to our screen
cv2.imshow("Canvas", canvas)
cv2.waitKey(0)
我们的代码从第 59 行的开始,有更多的循环。这一次,我们不是在我们的半径大小上循环——相反,我们将绘制25
随机圆,通过np.random.randint
函数利用 NumPy 的随机数功能。
要画一个随机的圆,我们需要生成三个值:圆的radius
、圆的color
和pt
—将要画圆的地方的 (x,y)—坐标。
我们在第 63 行的*上生成一个*【5,200】范围内的radius
值。这个值控制我们的圆有多大。
在第 64 行,我们随机生成一个color
。我们知道,RGB 像素的颜色由范围*【0,255】*内的三个值组成。为了得到三个随机整数而不是一个整数,我们传递关键字参数size=(3,)
,指示 NumPy 返回一个包含三个数字的列表。
最后,我们需要一个 (x,y)-中心点来画我们的圆。我们将再次使用 NumPy 的np.random.randint
函数在*【0,300】*范围内生成一个点。
然后在第 68 行上画出我们的圆,使用我们随机生成的radius
、color
和pt
。注意我们是如何使用厚度-1
的,所以我们的圆被绘制成纯色,而不仅仅是轮廓。
第 71 行和第 72 行展示了我们的杰作,你可以在图 7 中看到:
Figure 7: Drawing multiple circles with OpenCV.
注意每个圆在画布上有不同的大小、颜色和位置。
OpenCV 基本绘图结果
要执行我们的基本绘图脚本,请确保访问 “下载” 部分以检索源代码和示例图像。
从那里,您可以执行以下命令:
$ python basic_drawing.py
您的输出应该与前一部分的输出相同。
使用 OpenCV 在图像上绘图
到目前为止,我们只探索了在空白画布上绘制形状。但是如果我们想在一个现有的图像上画形状呢?
原来,在现有图像上绘制形状的代码是 完全相同的 就好像我们在 NumPy 生成的空白画布上绘图一样。
为了演示这一点,让我们看一些代码:
# import the necessary packages
import argparse
import cv2
# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", type=str, default="adrian.png",
help="path to the input image")
args = vars(ap.parse_args())
第 2 行和第 3 行导入我们需要的 Python 包,而第 6-9 行解析我们的命令行参数。
我们只需要一个参数--image
,它是我们在磁盘上的输入图像的路径。默认情况下,我们将--image
命令行参数设置为指向项目目录结构中的adrian.png
图像。
# load the input image from disk
image = cv2.imread(args["image"])
# draw a circle around my face, two filled in circles covering my
# eyes, and a rectangle over top of my mouth
cv2.circle(image, (168, 188), 90, (0, 0, 255), 2)
cv2.circle(image, (150, 164), 10, (0, 0, 255), -1)
cv2.circle(image, (192, 174), 10, (0, 0, 255), -1)
cv2.rectangle(image, (134, 200), (186, 218), (0, 0, 255), -1)
# show the output image
cv2.imshow("Output", image)
cv2.waitKey(0)
第 12 行从磁盘加载我们的--image
。从那里,我们继续:
- 在我的头(线 16 )周围画一个空的圆(不是填充)
- 画两个填充的圆圈遮住我的眼睛(第 17 行和第 18 行
- 在我的嘴上画一个矩形
我们的最终输出image
,然后显示在我们的屏幕上。
OpenCV 图像绘制结果
让我们看看如何使用 OpenCV 在图像上绘制,而不是使用 NumPy 生成的“空白画布”。
首先访问本指南的 “下载” 部分,检索源代码和示例图像。
然后,您可以执行以下命令:
$ python image_drawing.py
Figure 8: Drawing shapes on an image with OpenCV.
在这里,你可以看到我们在我的脸周围画了一个圆形,在我的眼睛上画了两个圆形,在我的嘴巴上画了一个矩形。
事实上,在从磁盘加载的图像和空白数字数组上绘制形状没有区别。只要我们的图像/画布可以表示为一个 NumPy 数组,OpenCV 也会在其上绘制。**
总结
在本教程中,您学习了如何使用 OpenCV 绘图。具体来说,您学习了如何使用 OpenCV 来绘制:
- 线
- 环
- 长方形
使用cv2.line
功能绘制线条。我们用 OpenCV 的cv2.circle
函数画圆,用cv2.rectangle
方法画矩形。
OpenCV 中还存在其他绘图功能。但是,这些是您最常使用的功能。
要下载这篇文章的源代码(并在未来教程在 PyImageSearch 上发布时得到通知), 只需在下面的表格中输入您的电子邮件地址!**
利用 OpenCV 进行睡意检测
原文:https://pyimagesearch.com/2017/05/08/drowsiness-detection-opencv/
https://www.youtube.com/embed/Q23K7G1gJgY?feature=oembed
使用 Keras 调谐器和 TensorFlow 轻松调整超参数
原文:https://pyimagesearch.com/2021/06/07/easy-hyperparameter-tuning-with-keras-tuner-and-tensorflow/
在本教程中,您将学习如何使用 Keras Tuner 软件包通过 Keras 和 TensorFlow 轻松进行超参数调谐。
本教程是我们关于超参数调优的四部分系列的第四部分:
- 使用 scikit-learn 和 Python 进行超参数调优的介绍(本系列的第一篇教程)
- 【网格搜索超参数调优】用 scikit-learn(GridSearchCV)(教程来自两周前)
- 用 scikit-learn、Keras 和 TensorFlow 进行深度学习的超参数调优(上周的帖子)
- 使用 Keras 调谐器和 TensorFlow 轻松调整超参数(今天的帖子)
上周我们学习了如何使用 scikit-learn 与 Keras 和 TensorFlow 进行交互,以执行随机交叉验证的超参数搜索。
然而,还有更先进的超参数调整算法,包括贝叶斯超参数优化和超带,这是对传统随机超参数搜索的适应和改进。
贝叶斯优化和 Hyperband 都在 keras 调谐器包中实现。正如我们将看到的,在您自己的深度学习脚本中使用 Keras Tuner 就像单个导入和单个类实例化一样简单——从那里开始,它就像训练您的神经网络一样简单!
除了易于使用,你会发现 Keras 调谐器:
- 集成到您现有的深度学习培训管道中,只需最少的代码更改
- 实现新的超参数调整算法
- 可以用最少的努力提高准确性
要了解如何使用 Keras Tuner 调整超参数, 继续阅读。
使用 Keras 调谐器和 TensorFlow 轻松调谐超参数
在本教程的第一部分,我们将讨论 Keras Tuner 包,包括它如何帮助用最少的代码自动调整模型的超参数。
然后,我们将配置我们的开发环境,并检查我们的项目目录结构。
我们今天要回顾几个 Python 脚本,包括:
- 我们的配置文件
- 模型架构定义(我们将调整超参数,包括 CONV 层中的过滤器数量、学习速率等。)
- 绘制我们训练历史的实用程序
- 一个驱动程序脚本,它将所有部分粘合在一起,并允许我们测试各种超参数优化算法,包括贝叶斯优化、超波段和传统的随机搜索
我们将讨论我们的结果来结束本教程。
什么是 Keras Tuner,它如何帮助我们自动调整超参数?
上周,您学习了如何使用 scikit-learn 的超参数搜索功能来调整基本前馈神经网络的超参数(包括批量大小、要训练的时期数、学习速率和给定层中的节点数)。
虽然这种方法工作得很好(并且给了我们一个很好的准确性提升),但是代码并不一定“漂亮”
更重要的是,它使我们不容易调整模型架构的“内部”参数(例如,CONV 层中过滤器的数量、步幅大小、池的大小、辍学率等。).
像 keras tuner 这样的库使得以一种有机的方式将超参数优化实现到我们的训练脚本中变得非常简单:
- 当我们实现我们的模型架构时,我们定义我们想要为给定的参数搜索什么范围(例如,我们的第一 CONV 层中的过滤器的数量,第二 CONV 层中的过滤器的数量,等等)。)
- 然后我们定义一个
Hyperband
、RandomSearch
或BayesianOptimization
的实例 - keras tuner 包负责剩下的工作,运行多次试验,直到我们收敛到最佳的超参数集
这听起来可能很复杂,但是一旦你深入研究了代码,这就很容易了。
此外,如果您有兴趣了解更多关于 Hyperband 算法的信息,请务必阅读李等人的 2018 年出版物, Hyperband:一种基于 Bandit 的超参数优化新方法 。
要了解关于贝叶斯超参数优化的更多信息,请参考多伦多大学教授兼研究员罗杰·格罗斯的幻灯片。
配置您的开发环境
要遵循本指南,您需要安装 TensorFlow、OpenCV、scikit-learn 和 Keras Tuner。
所有这些软件包都是 pip 可安装的:
$ pip install tensorflow # use "tensorflow-gpu" if you have a GPU
$ pip install opencv-contrib-python
$ pip install scikit-learn
$ pip install keras-tuner
此外,这两个指南提供了在您的计算机上安装 Keras 和 TensorFlow 的更多详细信息、帮助和提示:
这两个教程都有助于在一个方便的 Python 虚拟环境中为您的系统配置这篇博客文章所需的所有软件。
在配置开发环境时遇到了问题?
说了这么多,你是:
- 时间紧迫?
- 了解你雇主的行政锁定系统?
- 想要跳过与命令行、包管理器和虚拟环境斗争的麻烦吗?
- 准备好在您的 Windows、macOS 或 Linux 系统上运行代码***?***
*那今天就加入 PyImageSearch 大学吧!
获得本教程的 Jupyter 笔记本和其他 PyImageSearch 指南,这些指南是 预先配置的 **,可以在您的网络浏览器中运行在 Google Colab 的生态系统上!**无需安装。
最棒的是,这些 Jupyter 笔记本可以在 Windows、macOS 和 Linux 上运行!
项目结构
在我们可以使用 Keras Tuner 来调优我们的 Keras/TensorFlow 模型的超参数之前,让我们首先回顾一下我们的项目目录结构。
通过访问本教程的 【下载】 部分来检索源代码。
从那里,您将看到以下目录结构:
$ tree . --dirsfirst --filelimit 10
.
├── output
│ ├── bayesian [12 entries exceeds filelimit, not opening dir]
│ ├── hyperband [79 entries exceeds filelimit, not opening dir]
│ └── random [12 entries exceeds filelimit, not opening dir]
│ ├── bayesian_plot.png
│ ├── hyperband_plot.png
│ └── random_plot.png
├── pyimagesearch
│ ├── __init__.py
│ ├── config.py
│ ├── model.py
│ └── utils.py
└── train.py
2 directories, 8 files
在pyimagesearch
模块中,我们有三个 Python 脚本:
config.py
:包含重要的配置选项,例如输出路径目录、输入图像尺寸以及数据集中唯一类标签的数量model.py
:包含build_model
函数,负责实例化我们模型架构的一个实例;该函数设置将要调整的超参数以及每个超参数的适当取值范围utils.py
:实现save_plot
,一个助手/便利函数,生成训练历史图
train.py
脚本使用pyimagesearch
模块中的每个实现来执行三种类型的超参数搜索:
- 超波段
- 随意
- 贝叶斯优化
每个实验的结果都保存在output
目录中。为每个实验使用专用输出目录的主要好处是,您可以启动、停止和恢复超参数调整实验。这一点尤其重要,因为超参数调整可能需要相当长的时间。
创建我们的配置文件
在使用 Keras Tuner 优化超参数之前,我们首先需要创建一个配置文件来存储重要的变量。
打开项目目录结构中的config.py
文件,插入以下代码:
# define the path to our output directory
OUTPUT_PATH = "output"
# initialize the input shape and number of classes
INPUT_SHAPE = (28, 28, 1)
NUM_CLASSES = 10
第 2 行定义了我们的输出目录路径(即存储训练历史图和超参数调整实验日志的位置)。
在这里,我们定义了数据集中图像的输入空间维度以及唯一类标签的总数(第 5 行和第 6 行)。
下面我们定义我们的训练变量:
# define the total number of epochs to train, batch size, and the
# early stopping patience
EPOCHS = 50
BS = 32
EARLY_STOPPING_PATIENCE = 5
对于每个实验,我们将允许我们的模型训练最大的50
个时期。我们将在每个实验中使用批量32
。
对于没有显示有希望迹象的短路实验,我们定义了一个早期停止耐心5
,这意味着如果我们的精度在5
个时期后没有提高,我们将终止训练过程,并继续进行下一组超参数。
调整超参数是一个计算量非常大的过程。如果我们可以通过取消表现不佳的实验来减少需要进行的实验数量,我们就可以节省大量的时间。
实现我们的绘图助手功能
在为我们的模型找到最佳超参数后,我们将希望在这些超参数上训练模型,并绘制我们的训练历史(包括训练集和验证集的损失和准确性)。
为了简化这个过程,我们可以在utils.py
文件中定义一个save_plot
助手函数。
现在打开这个文件,让我们来看看:
# set the matplotlib backend so figures can be saved in the background
import matplotlib
matplotlib.use("Agg")
# import the necessary package
import matplotlib.pyplot as plt
def save_plot(H, path):
# plot the training loss and accuracy
plt.style.use("ggplot")
plt.figure()
plt.plot(H.history["loss"], label="train_loss")
plt.plot(H.history["val_loss"], label="val_loss")
plt.plot(H.history["accuracy"], label="train_acc")
plt.plot(H.history["val_accuracy"], label="val_acc")
plt.title("Training Loss and Accuracy")
plt.xlabel("Epoch #")
plt.ylabel("Loss/Accuracy")
plt.legend()
plt.savefig(path)
save_plot
函数需要我们传入两个变量:通过调用model.fit
获得的训练历史H
和输出图的path
。
然后,我们绘制训练损失、验证损失、训练准确度和验证准确度。
结果图保存到输出path
。
创建我们的 CNN
可以说,本教程最重要的部分是定义我们的 CNN 架构,也就是说,因为这是我们设置想要调谐的超参数的地方。
打开pyimagesearch
模块内的model.py
文件,让我们看看发生了什么:
# import the necessary packages
from . import config
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import BatchNormalization
from tensorflow.keras.layers import Conv2D
from tensorflow.keras.layers import MaxPooling2D
from tensorflow.keras.layers import Activation
from tensorflow.keras.layers import Flatten
from tensorflow.keras.layers import Dropout
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import Adam
2-11 行导入我们需要的包。请注意我们是如何导入我们在本指南前面创建的config
文件的。
如果您以前使用 Keras 和 TensorFlow 创建过 CNN,那么这些导入的其余部分对您来说应该很熟悉。如果没有,建议你看我的 Keras 教程,连同我的书 用 Python 进行计算机视觉的深度学习 。
现在让我们构建我们的模型:
def build_model(hp):
# initialize the model along with the input shape and channel
# dimension
model = Sequential()
inputShape = config.INPUT_SHAPE
chanDim = -1
# first CONV => RELU => POOL layer set
model.add(Conv2D(
hp.Int("conv_1", min_value=32, max_value=96, step=32),
(3, 3), padding="same", input_shape=inputShape))
model.add(Activation("relu"))
model.add(BatchNormalization(axis=chanDim))
model.add(MaxPooling2D(pool_size=(2, 2)))
build_model
函数接受单个对象hp
,这是我们从 Keras Tuner 得到的超参数调整对象。在本教程的后面,我们将在我们的驱动脚本train.py
中创建hp
。
第 16-18 行初始化我们的model
,获取我们数据集中输入图像的空间维度,并设置通道排序(假设“通道最后”)。
从那里,行 21-26 定义了我们的第一个 conv =>=>池层集,最重要的一行是行 22。
这里,我们定义了第一个要搜索的超参数——conv 层中过滤器的数量。
由于 CONV 层中滤镜的数量是一个整数,我们使用hp.Int
创建一个整数超参数对象。
超参数被命名为conv_1
,可以接受范围*【32,96】*内的值,步长为32
。这意味着conv_1
的有效值是32, 64, 96
。
我们的超参数调谐器将自动为这个 CONV 层选择最大化精度的最佳值。
同样,我们对第二个 CONV => RELU = >池层集做同样的事情:
# second CONV => RELU => POOL layer set
model.add(Conv2D(
hp.Int("conv_2", min_value=64, max_value=128, step=32),
(3, 3), padding="same"))
model.add(Activation("relu"))
model.add(BatchNormalization(axis=chanDim))
model.add(MaxPooling2D(pool_size=(2, 2)))
对于我们的第二个 CONV 层,我们允许在*【64,128】范围内学习更多的过滤器。*步长为32
,这意味着我们将测试64, 96, 128
的值。
我们将对完全连接的节点数量做类似的事情:
# first (and only) set of FC => RELU layers
model.add(Flatten())
model.add(Dense(hp.Int("dense_units", min_value=256,
max_value=768, step=256)))
model.add(Activation("relu"))
model.add(BatchNormalization())
model.add(Dropout(0.5))
# softmax classifier
model.add(Dense(config.NUM_CLASSES))
model.add(Activation("softmax"))
第 38 行和第 39 行定义了我们的 FC 层。我们希望调整这一层中的节点数量。我们指定最少256
和最多768
个节点,允许一步256
。
我们的下一个代码块使用了hp.Choice
函数:
# initialize the learning rate choices and optimizer
lr = hp.Choice("learning_rate",
values=[1e-1, 1e-2, 1e-3])
opt = Adam(learning_rate=lr)
# compile the model
model.compile(optimizer=opt, loss="categorical_crossentropy",
metrics=["accuracy"])
# return the model
return model
对于我们的学习率,我们希望看到1e-1
、1e-2
和1e-3
中哪一个表现最好。使用hp.Choice
将允许我们的超参数调谐器选择最佳学习率。
最后,我们编译模型并将其返回给调用函数。
使用 Keras 调谐器实现超参数调谐
让我们把所有的部分放在一起,学习如何使用 Keras Tuner 库来调整 Keras/TensorFlow 超参数。
打开项目目录结构中的train.py
文件,让我们开始吧:
# import the necessary packages
from pyimagesearch import config
from pyimagesearch.model import build_model
from pyimagesearch import utils
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.datasets import fashion_mnist
from tensorflow.keras.utils import to_categorical
from tensorflow.keras import backend as K
from sklearn.metrics import classification_report
import kerastuner as kt
import numpy as np
import argparse
import cv2
第 2-13 行导入我们需要的 Python 包。值得注意的进口包括:
config
:我们的配置文件build_model
:接受一个超参数调整对象,该对象选择各种值来测试 CONV 滤波器、FC 节点和学习率——生成的模型被构建并返回给调用函数utils
:用于绘制我们的训练历史EarlyStopping
:一个 Keras/TensorFlow 回调,用于缩短运行不佳的超参数调整实验fashion_mnist
:我们将在时尚 MNIST 数据集上训练我们的模特kerastuner
:用于实现超参数调谐的 Keras 调谐器包
接下来是我们的命令行参数:
# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-t", "--tuner", required=True, type=str,
choices=["hyperband", "random", "bayesian"],
help="type of hyperparameter tuner we'll be using")
ap.add_argument("-p", "--plot", required=True,
help="path to output accuracy/loss plot")
args = vars(ap.parse_args())
我们有两个命令行参数要解析:
- 我们将使用的超参数优化器的类型
- 输出训练历史图的路径
在那里,从磁盘加载时尚 MNIST 数据集:
# load the Fashion MNIST dataset
print("[INFO] loading Fashion MNIST...")
((trainX, trainY), (testX, testY)) = fashion_mnist.load_data()
# add a channel dimension to the dataset
trainX = trainX.reshape((trainX.shape[0], 28, 28, 1))
testX = testX.reshape((testX.shape[0], 28, 28, 1))
# scale data to the range of [0, 1]
trainX = trainX.astype("float32") / 255.0
testX = testX.astype("float32") / 255.0
# one-hot encode the training and testing labels
trainY = to_categorical(trainY, 10)
testY = to_categorical(testY, 10)
# initialize the label names
labelNames = ["top", "trouser", "pullover", "dress", "coat",
"sandal", "shirt", "sneaker", "bag", "ankle boot"]
第 26 行装载时装 MNIST,预分割成训练和测试集。
然后,我们向数据集添加一个通道维度(行 29 和 30 ),将像素强度从范围*【0,255】缩放到【0,1】*(行 33 和 34 ),然后对标签进行一次性编码(行 37 和 38 )。
正如在这个脚本的导入部分提到的,我们将使用EarlyStopping
来缩短运行不佳的超参数试验:
# initialize an early stopping callback to prevent the model from
# overfitting/spending too much time training with minimal gains
es = EarlyStopping(
monitor="val_loss",
patience=config.EARLY_STOPPING_PATIENCE,
restore_best_weights=True)
我们会监控验证损失。如果验证损失在EARLY_STOPPING_PATIENCE
总时期后未能显著改善,那么我们将终止试验并继续下一个试验。
请记住,调整超参数是一个极其计算量巨大的过程,因此,如果我们能够取消表现不佳的试验,我们就可以节省大量时间。
下一步是初始化我们的超参数优化器:
# check if we will be using the hyperband tuner
if args["tuner"] == "hyperband":
# instantiate the hyperband tuner object
print("[INFO] instantiating a hyperband tuner object...")
tuner = kt.Hyperband(
build_model,
objective="val_accuracy",
max_epochs=config.EPOCHS,
factor=3,
seed=42,
directory=config.OUTPUT_PATH,
project_name=args["tuner"])
第 52-62 行处理我们是否希望使用 Hyperband 调谐器。Hyperband 调谐器是随机搜索与“自适应资源分配和提前停止”的结合它实质上是李等人的论文, Hyperband:一种新的基于 Bandit 的超参数优化方法的实现。
如果我们提供一个值random
作为我们的--tuner
命令行参数,那么我们将使用一个基本的随机超参数搜索:
# check if we will be using the random search tuner
elif args["tuner"] == "random":
# instantiate the random search tuner object
print("[INFO] instantiating a random search tuner object...")
tuner = kt.RandomSearch(
build_model,
objective="val_accuracy",
max_trials=10,
seed=42,
directory=config.OUTPUT_PATH,
project_name=args["tuner"])
否则,我们将假设我们正在使用贝叶斯优化:
# otherwise, we will be using the bayesian optimization tuner
else:
# instantiate the bayesian optimization tuner object
print("[INFO] instantiating a bayesian optimization tuner object...")
tuner = kt.BayesianOptimization(
build_model,
objective="val_accuracy",
max_trials=10,
seed=42,
directory=config.OUTPUT_PATH,
project_name=args["tuner"])
一旦我们的超参数调谐器被实例化,我们可以搜索空间:
# perform the hyperparameter search
print("[INFO] performing hyperparameter search...")
tuner.search(
x=trainX, y=trainY,
validation_data=(testX, testY),
batch_size=config.BS,
callbacks=[es],
epochs=config.EPOCHS
)
# grab the best hyperparameters
bestHP = tuner.get_best_hyperparameters(num_trials=1)[0]
print("[INFO] optimal number of filters in conv_1 layer: {}".format(
bestHP.get("conv_1")))
print("[INFO] optimal number of filters in conv_2 layer: {}".format(
bestHP.get("conv_2")))
print("[INFO] optimal number of units in dense layer: {}".format(
bestHP.get("dense_units")))
print("[INFO] optimal learning rate: {:.4f}".format(
bestHP.get("learning_rate")))
第 90-96 行开始超参数调整过程。
调谐过程完成后,我们获得最佳超参数(行 99 )并在终端上显示最佳:
- 第一个 CONV 图层中的滤镜数量
- 第二 CONV 层中的过滤器数量
- 全连接层中的节点数
- 最佳学习率
一旦我们有了最好的超参数,我们需要基于它们实例化一个新的model
:
# build the best model and train it
print("[INFO] training the best model...")
model = tuner.hypermodel.build(bestHP)
H = model.fit(x=trainX, y=trainY,
validation_data=(testX, testY), batch_size=config.BS,
epochs=config.EPOCHS, callbacks=[es], verbose=1)
# evaluate the network
print("[INFO] evaluating network...")
predictions = model.predict(x=testX, batch_size=32)
print(classification_report(testY.argmax(axis=1),
predictions.argmax(axis=1), target_names=labelNames))
# generate the training loss/accuracy plot
utils.save_plot(H, args["plot"])
第 111 行负责用我们最好的超参数建立模型。
在行 112-114 上对model.fit
的调用在最佳超参数上训练我们的模型。
训练完成后,我们对测试集进行全面评估(第 118-120 行)。
最后,使用我们的save_plot
实用函数将生成的训练历史图保存到磁盘。
超波段超参数调谐
让我们看看应用 Keras 调谐器的超波段优化器的结果。
通过访问本教程的 【下载】 部分来检索源代码。
从那里,打开一个终端并执行以下命令:
$ time python train.py --tuner hyperband --plot output/hyperband_plot.png
[INFO] loading Fashion MNIST...
[INFO] instantiating a hyperband tuner object..."
[INFO] performing hyperparameter search...
Search: Running Trial #1
Hyperparameter |Value |Best Value So Far
conv_1 |96 |?
conv_2 |96 |?
dense_units |512 |?
learning_rate |0.1 |?
Epoch 1/2
1875/1875 [==============================] - 119s 63ms/step - loss: 3.2580 - accuracy: 0.6568 - val_loss: 3.9679 - val_accuracy: 0.7852
Epoch 2/2
1875/1875 [==============================] - 79s 42ms/step - loss: 3.5280 - accuracy: 0.7710 - val_loss: 2.5392 - val_accuracy: 0.8167
Trial 1 Complete [00h 03m 18s]
val_accuracy: 0.8166999816894531
Best val_accuracy So Far: 0.8285999894142151
Total elapsed time: 00h 03m 18s
Keras 调谐器包通过运行几个“试验”来工作在这里,我们可以看到,在第一次试验中,我们将对第一个 CONV 层使用96
过滤器,对第二个 CONV 层使用96
过滤器,对全连接层使用总共512
个节点,学习速率为0.1
。
随着我们试验的结束,Best Value So Far
栏将会更新,以反映找到的最佳超参数。
但是,请注意,我们仅针对总共两个时期训练该模型——这是由于我们的EarlyStopping
停止标准。如果我们的验证准确性没有提高一定的量,我们将缩短训练过程,以避免花费太多时间探索不会显著提高我们准确性的超参数。
由此可见,在第一次试验结束时,我们坐在 的准确率为 82%。
现在让我们跳到最后的审判:
Search: Running Trial #76
Hyperparameter |Value |Best Value So Far
conv_1 |32 |64
conv_2 |64 |128
dense_units |768 |512
learning_rate |0.01 |0.001
Epoch 1/17
1875/1875 [==============================] - 41s 22ms/step - loss: 0.8586 - accuracy: 0.7624 - val_loss: 0.4307 - val_accuracy: 0.8587
...
Epoch 17/17
1875/1875 [==============================] - 40s 21ms/step - loss: 0.2248 - accuracy: 0.9220 - val_loss: 0.3391 - val_accuracy: 0.9089
Trial 76 Complete [00h 11m 29s]
val_accuracy: 0.9146000146865845
Best val_accuracy So Far: 0.9289000034332275
Total elapsed time: 06h 34m 56s
目前发现的最好的验证准确率是92%。
Hyperband 完成运行后,我们会看到终端上显示的最佳参数:
[INFO] optimal number of filters in conv_1 layer: 64
[INFO] optimal number of filters in conv_2 layer: 128
[INFO] optimal number of units in dense layer: 512
[INFO] optimal learning rate: 0.0010
对于我们的第一个 CONV 层,我们看到64
过滤器是最好的。网络中的下一个 CONV 层喜欢128
层——这并不是一个完全令人惊讶的发现。通常,随着我们深入 CNN,随着体积大小的空间维度减少,过滤器的数量增加。
AlexNet、VGGNet、ResNet 和几乎所有其他流行的 CNN 架构都有这种类型的模式。
最终的 FC 层有512
个节点,而我们的最优学习速率是1e-3
。
现在让我们用这些超参数来训练 CNN:
[INFO] training the best model...
Epoch 1/50
1875/1875 [==============================] - 69s 36ms/step - loss: 0.5655 - accuracy: 0.8089 - val_loss: 0.3147 - val_accuracy: 0.8873
...
Epoch 11/50
1875/1875 [==============================] - 67s 36ms/step - loss: 0.1163 - accuracy: 0.9578 - val_loss: 0.3201 - val_accuracy: 0.9088
[INFO] evaluating network...
precision recall f1-score support
top 0.83 0.92 0.87 1000
trouser 0.99 0.99 0.99 1000
pullover 0.83 0.92 0.87 1000
dress 0.93 0.93 0.93 1000
coat 0.90 0.83 0.87 1000
sandal 0.99 0.98 0.99 1000
shirt 0.82 0.70 0.76 1000
sneaker 0.94 0.99 0.96 1000
bag 0.99 0.98 0.99 1000
ankle boot 0.99 0.95 0.97 1000
accuracy 0.92 10000
macro avg 0.92 0.92 0.92 10000
weighted avg 0.92 0.92 0.92 10000
real 407m28.169s
user 2617m43.104s
sys 51m46.604s
在我们最好的超参数上训练 50 个时期后,我们在我们的验证集上获得了 92%的准确度。
在我的 3 GHz 英特尔至强 W 处理器上,总的超参数搜索和训练时间为
6.7 hours. Using a GPU would reduce the training time considerably.
随机搜索超参数调谐
现在让我们来看一个普通的随机搜索。
同样,请务必访问本教程的 “下载” 部分,以检索源代码和示例图像。
在那里,您可以执行以下命令:
$ time python train.py --tuner random --plot output/random_plot.png
[INFO] loading Fashion MNIST...
[INFO] instantiating a random search tuner object...
[INFO] performing hyperparameter search...
Search: Running Trial #1
Hyperparameter |Value |Best Value So Far
conv_1 |64 |?
conv_2 |64 |?
dense_units |512 |?
learning_rate |0.01 |?
Epoch 1/50
1875/1875 [==============================] - 51s 27ms/step - loss: 0.7210 - accuracy: 0.7758 - val_loss: 0.4748 - val_accuracy: 0.8668
...
Epoch 14/50
1875/1875 [==============================] - 49s 26ms/step - loss: 0.2180 - accuracy: 0.9254 - val_loss: 0.3021 - val_accuracy: 0.9037
Trial 1 Complete [00h 12m 08s]
val_accuracy: 0.9139999747276306
Best val_accuracy So Far: 0.9139999747276306
Total elapsed time: 00h 12m 08s
在第一次试验结束时,我们获得了
91% accuracy on our validation set with 64
filters for the first CONV layer, 64
filters for the second CONV layer, a total of 512
nodes in the FC layer, and a learning rate of 1e-2
.
到第 10 次试验时,我们的准确率有所提高,但没有使用 Hyperband 时的进步大:
Search: Running Trial #10
Hyperparameter |Value |Best Value So Far
conv_1 |96 |96
conv_2 |64 |64
dense_units |512 |512
learning_rate |0.1 |0.001
Epoch 1/50
1875/1875 [==============================] - 64s 34ms/step - loss: 3.8573 - accuracy: 0.6515 - val_loss: 1.3178 - val_accuracy: 0.7907
...
Epoch 6/50
1875/1875 [==============================] - 63s 34ms/step - loss: 4.2424 - accuracy: 0.8176 - val_loss: 622.4448 - val_accuracy: 0.8295
Trial 10 Complete [00h 06m 20s]
val_accuracy: 0.8640999794006348
Total elapsed time: 01h 47m 02s
Best val_accuracy So Far: 0.9240000247955322
Total elapsed time: 01h 47m 02s
我们现在到了
92% accuracy. Still, the good news is that we’ve only spent 1h47m exploring the hyperparameter space (as opposed to 6h30m from the Hyperband trials).
下面我们可以看到随机搜索找到的最佳超参数:
[INFO] optimal number of filters in conv_1 layer: 96
[INFO] optimal number of filters in conv_2 layer: 64
[INFO] optimal number of units in dense layer: 512
[INFO] optimal learning rate: 0.0010
我们随机搜索的输出与超波段调谐的输出略有不同。第一层 CONV 有96
滤镜,第二层有64
(Hyperband 分别有64
和128
)。
也就是说,随机搜索和 Hyperband 都同意 FC 层中的512
节点和1e-3
的学习速率。
经过培训后,我们达到了与 Hyperband 大致相同的验证准确度:
[INFO] training the best model...
Epoch 1/50
1875/1875 [==============================] - 64s 34ms/step - loss: 0.5682 - accuracy: 0.8157 - val_loss: 0.3227 - val_accuracy: 0.8861
...
Epoch 13/50
1875/1875 [==============================] - 63s 34ms/step - loss: 0.1066 - accuracy: 0.9611 - val_loss: 0.2636 - val_accuracy: 0.9251
[INFO] evaluating network...
precision recall f1-score support
top 0.85 0.91 0.88 1000
trouser 0.99 0.98 0.99 1000
pullover 0.88 0.89 0.88 1000
dress 0.94 0.90 0.92 1000
coat 0.82 0.93 0.87 1000
sandal 0.97 0.99 0.98 1000
shirt 0.82 0.69 0.75 1000
sneaker 0.96 0.95 0.96 1000
bag 0.99 0.99 0.99 1000
ankle boot 0.97 0.96 0.97 1000
accuracy 0.92 10000
macro avg 0.92 0.92 0.92 10000
weighted avg 0.92 0.92 0.92 10000
real 120m52.354s
user 771m17.324s
sys 15m10.248s
虽然 92%的准确率与 Hyperband 基本相同,一次随机搜索将我们的超参数搜索时间缩短了 3 倍,这本身就是一个 巨大的 改进。
使用贝叶斯优化的超参数调整
让我们看看贝叶斯优化性能如何与超波段和随机搜索进行比较。
请务必访问本教程的 “下载” 部分来检索源代码。
从这里开始,让我们尝试一下贝叶斯超参数优化:
$ time python train.py --tuner bayesian --plot output/bayesian_plot.png
[INFO] loading Fashion MNIST...
[INFO] instantiating a bayesian optimization tuner object...
[INFO] performing hyperparameter search...
Search: Running Trial #1
Hyperparameter |Value |Best Value So Far
conv_1 |64 |?
conv_2 |64 |?
dense_units |512 |?
learning_rate |0.01 |?
Epoch 1/50
1875/1875 [==============================] - 143s 76ms/step - loss: 0.7434 - accuracy: 0.7723 - val_loss: 0.5290 - val_accuracy: 0.8095
...
Epoch 12/50
1875/1875 [==============================] - 50s 27ms/step - loss: 0.2210 - accuracy: 0.9223 - val_loss: 0.4138 - val_accuracy: 0.8693
Trial 1 Complete [00h 11m 45s]
val_accuracy: 0.9136999845504761
Best val_accuracy So Far: 0.9136999845504761
Total elapsed time: 00h 11m 45s
在我们的第一次试验中,我们击中了
91% accuracy.
在最后的试验中,我们略微提高了精确度:
Search: Running Trial #10
Hyperparameter |Value |Best Value So Far
conv_1 |64 |32
conv_2 |96 |96
dense_units |768 |768
learning_rate |0.001 |0.001
Epoch 1/50
1875/1875 [==============================] - 64s 34ms/step - loss: 0.5743 - accuracy: 0.8140 - val_loss: 0.3341 - val_accuracy: 0.8791
...
Epoch 16/50
1875/1875 [==============================] - 62s 33ms/step - loss: 0.0757 - accuracy: 0.9721 - val_loss: 0.3104 - val_accuracy: 0.9211
Trial 10 Complete [00h 16m 41s]
val_accuracy: 0.9251999855041504
Best val_accuracy So Far: 0.9283000230789185
Total elapsed time: 01h 47m 01s
我们现在获得
92% accuracy.
通过贝叶斯优化找到的最佳超参数如下:
[INFO] optimal number of filters in conv_1 layer: 32
[INFO] optimal number of filters in conv_2 layer: 96
[INFO] optimal number of units in dense layer: 768
[INFO] optimal learning rate: 0.0010
以下列表对超参数进行了细分:
- 我们的第一个 CONV 层有
32
个节点(相对于超波段的64
和随机的96
) - 第二 CONV 层有
96
个节点(超波段选择128
和随机搜索64
- 完全连接的层有
768
个节点(超波段和随机搜索都选择了512
- 我们的学习率是
1e-3
(这三个超参数优化器都同意)
现在让我们在这些超参数上训练我们的网络:
[INFO] training the best model...
Epoch 1/50
1875/1875 [==============================] - 49s 26ms/step - loss: 0.5764 - accuracy: 0.8164 - val_loss: 0.3823 - val_accuracy: 0.8779
...
Epoch 14/50
1875/1875 [==============================] - 47s 25ms/step - loss: 0.0915 - accuracy: 0.9665 - val_loss: 0.2669 - val_accuracy: 0.9214
[INFO] evaluating network...
precision recall f1-score support
top 0.82 0.93 0.87 1000
trouser 1.00 0.99 0.99 1000
pullover 0.86 0.92 0.89 1000
dress 0.93 0.91 0.92 1000
coat 0.90 0.86 0.88 1000
sandal 0.99 0.99 0.99 1000
shirt 0.81 0.72 0.77 1000
sneaker 0.96 0.98 0.97 1000
bag 0.99 0.98 0.99 1000
ankle boot 0.98 0.96 0.97 1000
accuracy 0.92 10000
macro avg 0.93 0.92 0.92 10000
weighted avg 0.93 0.92 0.92 10000
real 118m11.916s
user 740m56.388s
sys 18m2.676s
这里的精确度有所提高。我们现在处于 93%的准确率使用贝叶斯优化(超波段和随机搜索都有报道
92% accuracy).
我们如何解释这些结果?
现在让我们花点时间来讨论这些结果。既然贝叶斯优化返回了最高的精度,这是否意味着您应该总是使用贝叶斯超参数优化?
不,不一定。
相反,我建议对每个超参数优化器进行一些试验,这样您就可以了解跨几种算法的超参数的“一致程度”。如果所有三个超参数调谐器都报告了相似的超参数,那么你可以有理由相信你找到了最佳的。
说到这里,下表列出了每个优化器的超参数结果:
虽然在 CONV 滤波器的数量和 FC 节点的数量上有一些分歧, 三者都同意 1e-3 是最佳学习速率。
这告诉我们什么?
假设其他超参数有变化,但所有三个优化器的学习率是相同的,我们可以得出结论,学习率对准确性有最大的影响。其他参数没有简单地获得正确的学习率重要。
总结
在本教程中,您学习了如何使用 Keras Tuner 和 TensorFlow 轻松调整神经网络超参数。
Keras Tuner 软件包通过以下方式使您的模型超参数的调整变得非常简单:
- 只需要一次进口
- 允许您在您的模型架构中定义值和范围
- 直接与 Keras 和 TensorFlow 接口
- 实施最先进的超参数优化器
*当训练你自己的神经网络时,我建议你至少花一些时间来调整你的超参数,因为你很可能能够在精度上从 1-2%的提升(低端)到 25%的提升(高端)。同样,这取决于你项目的具体情况。
要下载这篇文章的源代码(并在未来教程在 PyImageSearch 上发布时得到通知), 只需在下面的表格中输入您的电子邮件地址!**
增强型超分辨率生成对抗网络
目录
**上周我们学习了超分辨率甘斯。他们在实现超分辨率图像的更好清晰度方面做得非常好。但是,在超分辨率领域,甘斯的路走到尽头了吗?
深度学习的一个共同主题是,成长永不停止。因此,我们转向增强的超分辨率 gan。顾名思义,它对原来的 SRGAN 架构进行了许多更新,极大地提高了性能和可视化。
在本教程中,您将学习如何使用 tensorflow 实现 ESRGAN。
本课是关于 GANs 201 的 4 部分系列中的第 2 部分:
要了解如何实现 ESRGAN ,请继续阅读。*
前言
GANs 同时训练两个神经网络:鉴别器和生成器。生成器创建假图像,而鉴别器判断它们是真的还是假的。
SRGANs 将这一思想应用于图像超分辨率领域。发生器产生超分辨率图像,鉴别器判断真假。
增强型超分辨率甘斯
建立在 SRGANs 领导的基础上,ESRGAN 的主要目标是引入模型修改,从而提高训练效率并降低复杂性。
SRGANs 的简要概述:
- 将低分辨率图像作为输入提供给生成器,并将超分辨率图像作为输出。
- 将这些预测通过鉴别器,得到真实或虚假的品牌。
- 使用 VGG 网添加感知损失(像素方面)来增加我们预测的假图像的清晰度。
但是 ESRGANs 带来了哪些更新呢?
首先,发电机采取了一些主要措施来确保性能的提高:
-
**删除批量标准化层:**对 SRGAN 架构的简要回顾将显示批量标准化层在整个生成器架构中被广泛使用。由于提高了性能和降低了计算复杂性,ESRGANs 完全放弃了 BN 层的使用。
-
Residual in Residual Dense Block:标准残差块的升级,这种特殊的结构允许一个块中的所有层输出传递给后续层,如图图 1 所示。这里的直觉是,模型可以访问许多可供选择的特性,并确定其相关性。此外,ESRGAN 使用残差缩放来缩小残差输出,以防止不稳定。
-
对于鉴频器,主要增加的是相对论损耗。它估计真实图像比假预测图像更真实的概率**。自然地,将它作为损失函数加入使得模型能够克服相对论损失。**
-
感知损失有一点改变,使损失基于激活功能之前而不是激活功能之后的特征,如上周 SRGAN 论文所示。
-
总损失现在是 GAN 损失、感知损失以及地面真实高分辨率和预测图像之间的像素距离的组合。
这些添加有助于显著改善结果。在我们的实现中,我们忠实于论文,并对传统的 SRGAN 进行了这些更新,以提高超分辨率结果。
ESRGAN 背后的核心理念不仅是提高结果,而且使流程更加高效。因此,本文并没有全面谴责批量规范化的使用。尽管如此,它指出,刮 BN 层的使用将有利于我们的特定任务,即使在最小的像素的相似性是必要的。
配置您的开发环境
要遵循这个指南,您需要在您的系统上安装 OpenCV 库。
幸运的是,OpenCV 可以通过 pip 安装:
$ pip install opencv-contrib-python
如果您需要帮助配置 OpenCV 的开发环境,我们强烈推荐阅读我们的 pip 安装 OpenCV 指南——它将在几分钟内让您启动并运行。
在配置开发环境时遇到了问题?
说了这么多,你是:
- 时间紧迫?
- 了解你雇主的行政锁定系统?
- 想要跳过与命令行、包管理器和虚拟环境斗争的麻烦吗?
- 准备好在您的 Windows、macOS 或 Linux 系统上运行代码***?***
*那今天就加入 PyImageSearch 大学吧!
获得本教程的 Jupyter 笔记本和其他 PyImageSearch 指南,这些指南是 预先配置的 **,可以在您的网络浏览器中运行在 Google Colab 的生态系统上!**无需安装。
最棒的是,这些 Jupyter 笔记本可以在 Windows、macOS 和 Linux 上运行!
项目结构
我们首先需要回顾我们的项目目录结构。
首先访问本教程的 “下载” 部分,检索源代码和示例图像。
从这里,看一下目录结构:
!tree .
.
├── create_tfrecords.py
├── inference.py
├── outputs
├── pyimagesearch
│ ├── config.py
│ ├── data_preprocess.py
│ ├── __init__.py
│ ├── losses.py
│ ├── esrgan.py
│ ├── esrgan_training.py
│ ├── utils.py
│ └── vgg.py
└── train_esrgan.py
2 directories, 11 files
在pyimagesearch
目录中,我们有:
config.py
:包含完整项目的端到端配置管道。data_preprocess.py
:包含有助于数据处理的功能。__init__.py
:使目录像 python 包一样运行。losses.py
:初始化训练 ESRGAN 所需的损耗。esrgan.py
:包含 ESRGAN 架构。esrgan_training.py
:包含运行 ESRGAN 训练的训练类。utils.py
:包含附加实用程序vgg.py
:初始化用于感知损失计算的 VGG 模型。
在根目录中,我们有:
create_tfrecords.py
:从我们将使用的数据集创建TFRecords
。inference.py
:使用训练好的模型进行推理。train_srgan.py
:使用esrgan.py
和esrgan_training.py
脚本执行 ESRGAN 训练。
配置先决条件
位于pyimagesearch
目录中的config.py
脚本包含了整个项目所需的几个参数和路径。将配置变量分开是一个很好的编码实践。为此,让我们转向config.py
剧本。
# import the necessary packages
import os
# name of the TFDS dataset we will be using
DATASET = "div2k/bicubic_x4"
# define the shard size and batch size
SHARD_SIZE = 256
TRAIN_BATCH_SIZE = 64
INFER_BATCH_SIZE = 8
# dataset specs
HR_SHAPE = [96, 96, 3]
LR_SHAPE = [24, 24, 3]
SCALING_FACTOR = 4
我们首先在第 5 行引用我们项目的数据集。
TFRecords
的SHARD_SIZE
定义在线 8 上。随后是第 9 行和第 10 行上的TRAIN_BATCH_SIZE
和INFER_BATCH_SIZE
定义。
我们的高分辨率输出图像将具有尺寸96 x 96 x 3
,而我们的输入低分辨率图像将具有尺寸24 x 24 x 3
( 行 13 和 14 )。相应地,在线 15 上SCALING_FACTOR
被设置为4
。
# GAN model specs
FEATURE_MAPS = 64
RESIDUAL_BLOCKS = 16
LEAKY_ALPHA = 0.2
DISC_BLOCKS = 4
RESIDUAL_SCALAR = 0.2
# training specs
PRETRAIN_LR = 1e-4
FINETUNE_LR = 3e-5
PRETRAIN_EPOCHS = 1500
FINETUNE_EPOCHS = 1000
STEPS_PER_EPOCH = 10
# define the path to the dataset
BASE_DATA_PATH = "dataset"
DIV2K_PATH = os.path.join(BASE_DATA_PATH, "div2k")
正如我们对 SRGAN 所做的那样,该架构由剩余网络组成。首先,我们设置在Conv2D
层使用的滤镜数量(第 18 行)。在第 19 行上,我们定义了剩余块的数量。我们的ReLU
功能的alpha
参数设置在第 20 行的上。
鉴别器架构将基于DISC_BLOCKS
( 第 21 行)的值实现自动化。现在,我们为RESIDUAL_SCALAR
定义一个值,这将有助于我们将剩余块输出缩放到各个级别,并保持训练过程稳定(第 22 行)。
现在,重复一下我们的 SRGAN 参数(学习率、时期等。)在第 25-29 行完成。我们将对我们的 GAN 进行预训练,然后对其进行全面训练以进行比较。出于这个原因,我们为预训练的 GAN 和完全训练的 GAN 定义了学习率和时期。
设置BASE_DATA_PATH
来定义存储数据集的。DIV2K_PATH
引用了DIV2K
数据集(第 32 和 33 行)。 div2k
数据集非常适合辅助图像超分辨率研究,因为它包含各种高分辨率图像。
# define the path to the tfrecords for GPU training
GPU_BASE_TFR_PATH = "tfrecord"
GPU_DIV2K_TFR_TRAIN_PATH = os.path.join(GPU_BASE_TFR_PATH, "train")
GPU_DIV2K_TFR_TEST_PATH = os.path.join(GPU_BASE_TFR_PATH, "test")
# define the path to the tfrecords for TPU training
TPU_BASE_TFR_PATH = "gs://<PATH_TO_GCS_BUCKET>/tfrecord"
TPU_DIV2K_TFR_TRAIN_PATH = os.path.join(TPU_BASE_TFR_PATH, "train")
TPU_DIV2K_TFR_TEST_PATH = os.path.join(TPU_BASE_TFR_PATH, "test")
# path to our base output directory
BASE_OUTPUT_PATH = "outputs"
# GPU training ESRGAN model paths
GPU_PRETRAINED_GENERATOR_MODEL = os.path.join(BASE_OUTPUT_PATH,
"models", "pretrained_generator")
GPU_GENERATOR_MODEL = os.path.join(BASE_OUTPUT_PATH, "models",
"generator")
# TPU training ESRGAN model paths
TPU_OUTPUT_PATH = "gs://<PATH_TO_GCS_BUCKET>/outputs"
TPU_PRETRAINED_GENERATOR_MODEL = os.path.join(TPU_OUTPUT_PATH,
"models", "pretrained_generator")
TPU_GENERATOR_MODEL = os.path.join(TPU_OUTPUT_PATH, "models",
"generator")
# define the path to the inferred images and to the grid image
BASE_IMAGE_PATH = os.path.join(BASE_OUTPUT_PATH, "images")
GRID_IMAGE_PATH = os.path.join(BASE_OUTPUT_PATH, "grid.png")
因此,为了比较训练效率,我们将在 TPU 和 GPU 上训练 GAN。为此,我们必须为 GPU 训练和 TPU 训练分别创建引用数据和输出的路径。
在的第 36-38 行,我们为 GPU 训练定义了TFRecords
。在第 41-43 行,我们为 TPU 训练定义了TFRecords
。
现在,我们在线 46 上定义基本输出路径。接下来是经过 GPU 训练的 ESRGAN 发电机模型的参考路径(第 49-52 行)。我们对 TPU 培训的 ESRGAN 发电机模型做同样的事情(线 55-59 )。
完成所有设置后,剩下的唯一任务是引用推断图像的路径(行 62 和 63 )。
实现数据处理实用程序
训练 GANs 需要大量的计算能力和数据。为了确保我们有足够的数据,我们将采用几种数据扩充技术。让我们看看位于pyimagesearch
目录中的data_preprocess.py
脚本中的那些。
# import the necessary packages
from tensorflow.io import FixedLenFeature
from tensorflow.io import parse_single_example
from tensorflow.io import parse_tensor
from tensorflow.image import flip_left_right
from tensorflow.image import rot90
import tensorflow as tf
# define AUTOTUNE object
AUTO = tf.data.AUTOTUNE
def random_crop(lrImage, hrImage, hrCropSize=96, scale=4):
# calculate the low resolution image crop size and image shape
lrCropSize = hrCropSize // scale
lrImageShape = tf.shape(lrImage)[:2]
# calculate the low resolution image width and height offsets
lrW = tf.random.uniform(shape=(),
maxval=lrImageShape[1] - lrCropSize + 1, dtype=tf.int32)
lrH = tf.random.uniform(shape=(),
maxval=lrImageShape[0] - lrCropSize + 1, dtype=tf.int32)
# calculate the high resolution image width and height
hrW = lrW * scale
hrH = lrH * scale
# crop the low and high resolution images
lrImageCropped = tf.slice(lrImage, [lrH, lrW, 0],
[(lrCropSize), (lrCropSize), 3])
hrImageCropped = tf.slice(hrImage, [hrH, hrW, 0],
[(hrCropSize), (hrCropSize), 3])
# return the cropped low and high resolution images
return (lrImageCropped, hrImageCropped)
考虑到我们将在这个项目中使用的 TensorFlow 包装器的数量,为空间优化定义一个tf.data.AUTOTUNE
对象是一个好主意。
我们定义的第一个数据增强函数是random_crop
( 第 12 行)。它接受以下参数:
lrImage
:低分辨率图像。hrImage
:高分辨率图像。hrCropSize
:用于低分辨率裁剪计算的高分辨率裁剪尺寸。scale
:我们用来计算低分辨率裁剪的因子。
然后计算左右宽度和高度偏移(第 18-21 行)。
为了计算相应的高分辨率值,我们简单地将低分辨率值乘以比例因子(行 24 和 25 )。
使用这些值,我们裁剪出低分辨率图像及其对应的高分辨率图像,并返回它们(第 28-34 行)
def get_center_crop(lrImage, hrImage, hrCropSize=96, scale=4):
# calculate the low resolution image crop size and image shape
lrCropSize = hrCropSize // scale
lrImageShape = tf.shape(lrImage)[:2]
# calculate the low resolution image width and height
lrW = lrImageShape[1] // 2
lrH = lrImageShape[0] // 2
# calculate the high resolution image width and height
hrW = lrW * scale
hrH = lrH * scale
# crop the low and high resolution images
lrImageCropped = tf.slice(lrImage, [lrH - (lrCropSize // 2),
lrW - (lrCropSize // 2), 0], [lrCropSize, lrCropSize, 3])
hrImageCropped = tf.slice(hrImage, [hrH - (hrCropSize // 2),
hrW - (hrCropSize // 2), 0], [hrCropSize, hrCropSize, 3])
# return the cropped low and high resolution images
return (lrImageCropped, hrImageCropped)
数据扩充实用程序中的下一行是get_center_crop
( 第 36 行),它接受以下参数:
lrImage
:低分辨率图像。hrImage
:高分辨率图像。hrCropSize
:用于低分辨率裁剪计算的高分辨率裁剪尺寸。scale
:我们用来计算低分辨率裁剪的因子。
就像我们为之前的函数创建裁剪大小值一样,我们在第 38 行和第 39 行得到 lr 裁剪大小值和图像形状。
现在,要获得中心像素坐标,我们只需将低分辨率形状除以2
( 第 42 行和第 43 行)。
为了获得相应的高分辨率中心点,将 lr 中心点乘以比例因子(第 46 和 47 行)。
def random_flip(lrImage, hrImage):
# calculate a random chance for flip
flipProb = tf.random.uniform(shape=(), maxval=1)
(lrImage, hrImage) = tf.cond(flipProb < 0.5,
lambda: (lrImage, hrImage),
lambda: (flip_left_right(lrImage), flip_left_right(hrImage)))
# return the randomly flipped low and high resolution images
return (lrImage, hrImage)
我们有random_flip
功能来翻转行 58 上的图像。它接受低分辨率和高分辨率图像作为其参数。
基于使用tf.random.uniform
的翻转概率值,我们翻转我们的图像并返回它们(第 60-66 行)。
def random_rotate(lrImage, hrImage):
# randomly generate the number of 90 degree rotations
n = tf.random.uniform(shape=(), maxval=4, dtype=tf.int32)
# rotate the low and high resolution images
lrImage = rot90(lrImage, n)
hrImage = rot90(hrImage, n)
# return the randomly rotated images
return (lrImage, hrImage)
在行 68 上,我们有另一个数据扩充函数叫做random_rotate
,它接受低分辨率图像和高分辨率图像作为它的参数。
变量n
生成一个值,这个值稍后将帮助应用于我们的图像集的旋转量(第 70-77 行)。
def read_train_example(example):
# get the feature template and parse a single image according to
# the feature template
feature = {
"lr": FixedLenFeature([], tf.string),
"hr": FixedLenFeature([], tf.string),
}
example = parse_single_example(example, feature)
# parse the low and high resolution images
lrImage = parse_tensor(example["lr"], out_type=tf.uint8)
hrImage = parse_tensor(example["hr"], out_type=tf.uint8)
# perform data augmentation
(lrImage, hrImage) = random_crop(lrImage, hrImage)
(lrImage, hrImage) = random_flip(lrImage, hrImage)
(lrImage, hrImage) = random_rotate(lrImage, hrImage)
# reshape the low and high resolution images
lrImage = tf.reshape(lrImage, (24, 24, 3))
hrImage = tf.reshape(hrImage, (96, 96, 3))
# return the low and high resolution images
return (lrImage, hrImage)
数据扩充功能完成后,我们可以转到第 79 行上的图像读取功能read_train_example
。该函数在单个图像集(低分辨率和相应的高分辨率图像)中运行
在第行第 82-90 行,我们创建一个 lr,hr 特征模板,并基于它解析示例集。
现在,我们在 lr-hr 集合上应用数据扩充函数(第 93-95 行)。然后,我们将 lr-hr 图像重塑回它们需要的尺寸**(第 98-102 行**)。
def read_test_example(example):
# get the feature template and parse a single image according to
# the feature template
feature = {
"lr": FixedLenFeature([], tf.string),
"hr": FixedLenFeature([], tf.string),
}
example = parse_single_example(example, feature)
# parse the low and high resolution images
lrImage = parse_tensor(example["lr"], out_type=tf.uint8)
hrImage = parse_tensor(example["hr"], out_type=tf.uint8)
# center crop both low and high resolution image
(lrImage, hrImage) = get_center_crop(lrImage, hrImage)
# reshape the low and high resolution images
lrImage = tf.reshape(lrImage, (24, 24, 3))
hrImage = tf.reshape(hrImage, (96, 96, 3))
# return the low and high resolution images
return (lrImage, hrImage)
我们为推断图像创建一个类似于read_train_example
的函数,称为read_test_example
,它接受一个 lr-hr 图像集(行 104 )。除了数据扩充过程(第 107-125 行)之外,重复前面函数中所做的一切。
def load_dataset(filenames, batchSize, train=False):
# get the TFRecords from the filenames
dataset = tf.data.TFRecordDataset(filenames,
num_parallel_reads=AUTO)
# check if this is the training dataset
if train:
# read the training examples
dataset = dataset.map(read_train_example,
num_parallel_calls=AUTO)
# otherwise, we are working with the test dataset
else:
# read the test examples
dataset = dataset.map(read_test_example,
num_parallel_calls=AUTO)
# batch and prefetch the data
dataset = (dataset
.shuffle(batchSize)
.batch(batchSize)
.repeat()
.prefetch(AUTO)
)
# return the dataset
return dataset
现在,总结一下,我们在行 127 上有了load_dataset
函数。它接受文件名、批量大小和一个布尔变量,指示模式是训练还是推理。
在第 129 行和第 130 行上,我们从提供的文件名中得到TFRecords
。如果模式设置为 train,我们将read_train_example
函数映射到数据集。这样,它里面的所有条目都通过read_train_example
函数传递(第 133-136 行)。
如果模式是推理,我们将read_test_example
函数移至数据集(第 138-141 行)。
随着我们的数据集的创建,它现在被批处理、混洗并设置为自动预取(第 144-152 行)。
实现 ESRGAN 架构
我们的下一个目的地是位于pyimagesearch
目录中的esrgan.py
脚本。该脚本包含完整的 ESRGAN 架构。我们已经讨论了 ESRGAN 带来的变化,现在让我们一个一个地来看一下。
# import the necessary packages
from tensorflow.keras.layers import BatchNormalization
from tensorflow.keras.layers import GlobalAvgPool2D
from tensorflow.keras.layers.experimental.preprocessing import Rescaling
from tensorflow.keras.layers import LeakyReLU
from tensorflow.keras.layers import Lambda
from tensorflow.keras.layers import Conv2D
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Add
from tensorflow.nn import depth_to_space
from tensorflow.keras import Model
from tensorflow.keras import Input
class ESRGAN(object):
@staticmethod
def generator(scalingFactor, featureMaps, residualBlocks,
leakyAlpha, residualScalar):
# initialize the input layer
inputs = Input((None, None, 3))
xIn = Rescaling(scale=1.0/255, offset=0.0)(inputs)
# pass the input through CONV => LeakyReLU block
xIn = Conv2D(featureMaps, 9, padding="same")(xIn)
xIn = LeakyReLU(leakyAlpha)(xIn)
为了简化工作流程,最好将 ESRGAN 定义为类模板(第 14 行)。
首先,我们算出发电机。第 16 行上的函数generator
作为我们的生成器定义,并接受以下参数:
scalingFactor
:输出图像缩放的决定因素。featureMaps
:卷积滤波器的数量。residualBlocks
:添加到架构中的剩余块数。leakyAlpha
:决定我们漏ReLU
函数阈值的因子residualScalar
:保持残差块的输出成比例的值,使得训练稳定。
输入被初始化,像素被缩放到0
和1
的范围(第 19 行和第 20 行)。
处理后的输入然后通过一个Conv2D
层,接着是一个LeakyReLU
激活功能(行 23 和 24 )。这些层的参数已经在config.py
中定义过了。
# construct the residual in residual block
x = Conv2D(featureMaps, 3, padding="same")(xIn)
x1 = LeakyReLU(leakyAlpha)(x)
x1 = Add()([xIn, x1])
x = Conv2D(featureMaps, 3, padding="same")(x1)
x2 = LeakyReLU(leakyAlpha)(x)
x2 = Add()([x1, x2])
x = Conv2D(featureMaps, 3, padding="same")(x2)
x3 = LeakyReLU(leakyAlpha)(x)
x3 = Add()([x2, x3])
x = Conv2D(featureMaps, 3, padding="same")(x3)
x4 = LeakyReLU(leakyAlpha)(x)
x4 = Add()([x3, x4])
x4 = Conv2D(featureMaps, 3, padding="same")(x4)
xSkip = Add()([xIn, x4])
# scale the residual outputs with a scalar between [0,1]
xSkip = Lambda(lambda x: x * residualScalar)(xSkip)
正如我们之前提到的,ESRGAN 在残差块中使用残差。因此,接下来定义基块。
我们开始添加一个Conv2D
和一个LeakyReLU
层。由于块的性质,这种层组合的输出x1
然后被添加到初始输入x
。这被重复三次,在连接初始输入xIn
和块输出x4
( 行 27-40 )的跳跃连接之前添加最后的Conv2D
层。
现在,这和原来的论文有点偏离,所有的层都是相互连接的。互连的目的是确保模型在每一步都可以访问前面的特征。基于我们今天使用的任务和数据集,我们的方法足以给出我们想要的结果。
添加跳过连接后,使用线 43 上的residualScalar
变量缩放输出。
# create a number of residual in residual blocks
for blockId in range(residualBlocks-1):
x = Conv2D(featureMaps, 3, padding="same")(xSkip)
x1 = LeakyReLU(leakyAlpha)(x)
x1 = Add()([xSkip, x1])
x = Conv2D(featureMaps, 3, padding="same")(x1)
x2 = LeakyReLU(leakyAlpha)(x)
x2 = Add()([x1, x2])
x = Conv2D(featureMaps, 3, padding="same")(x2)
x3 = LeakyReLU(leakyAlpha)(x)
x3 = Add()([x2, x3])
x = Conv2D(featureMaps, 3, padding="same")(x3)
x4 = LeakyReLU(leakyAlpha)(x)
x4 = Add()([x3, x4])
x4 = Conv2D(featureMaps, 3, padding="same")(x4)
xSkip = Add()([xSkip, x4])
xSkip = Lambda(lambda x: x * residualScalar)(xSkip)
现在,块重复是使用for
循环的自动化。根据指定的剩余块数量,将添加块层(第 46-61 行)。
# process the residual output with a conv kernel
x = Conv2D(featureMaps, 3, padding="same")(xSkip)
x = Add()([xIn, x])
# upscale the image with pixel shuffle
x = Conv2D(featureMaps * (scalingFactor // 2), 3,
padding="same")(x)
x = tf.nn.depth_to_space(x, 2)
x = LeakyReLU(leakyAlpha)(x)
# upscale the image with pixel shuffle
x = Conv2D(featureMaps, 3, padding="same")(x)
x = tf.nn.depth_to_space(x, 2)
x = LeakyReLU(leakyAlpha)(x)
# get the output layer
x = Conv2D(3, 9, padding="same", activation="tanh")(x)
output = Rescaling(scale=127.5, offset=127.5)(x)
# create the generator model
generator = Model(inputs, output)
# return the generator model
return generator
最终的剩余输出在行 64 和 65 上加上另一个Conv2D
层。
现在,升级过程在线 68 开始,这里scalingFactor
变量开始起作用。随后是depth_to_space
效用函数,通过相应地均匀减小通道尺寸来增加featureMaps
的高度和宽度(行 70 )。添加一个LeakyReLU
激活功能来完成这个特定的层组合(行 71 )。
在行 73-76 上重复相同的一组层。输出层是通过将featureMaps
传递给另一个Conv2D
层来实现的。注意这个卷积层有一个tanh
激活函数,它将你的输入缩放到-1
和1
的范围。
因此,像素被重新调整到 0 到 255 的范围内。(第 79 行和第 80 行)。
随着线 83 上发电机的初始化,我们的 ESRGAN 发电机侧要求完成。
@staticmethod
def discriminator(featureMaps, leakyAlpha, discBlocks):
# initialize the input layer and process it with conv kernel
inputs = Input((None, None, 3))
x = Rescaling(scale=1.0/127.5, offset=-1)(inputs)
x = Conv2D(featureMaps, 3, padding="same")(x)
x = LeakyReLU(leakyAlpha)(x)
# pass the output from previous layer through a CONV => BN =>
# LeakyReLU block
x = Conv2D(featureMaps, 3, padding="same")(x)
x = BatchNormalization()(x)
x = LeakyReLU(leakyAlpha)(x)
正如我们所知,鉴别器的目标是接受图像作为输入,并输出一个单一的值,它表示图像是真的还是假的。
第 89 行上的discriminator
函数由以下参数定义:
featureMaps
:图层Conv2D
的滤镜数量。leakyAlpha
:激活功能LeakyReLU
所需的参数。discBlocks
:我们在架构中需要的鉴别器块的数量。
鉴频器的输入被初始化,像素被缩放到-1
和1
的范围(行 91 和 92 )。
该架构从一个Conv2D
层开始,接着是一个LeakyReLU
激活层(行 93 和 94 )。
尽管我们已经为生成器放弃了批处理规范化层,但我们将把它们用于鉴别器。下一组图层是一个Conv
→ BN
→ LeakyReLU
的组合(第 98-100 行)。
# create a downsample conv kernel config
downConvConf = {
"strides": 2,
"padding": "same",
}
# create a number of discriminator blocks
for i in range(1, discBlocks):
# first CONV => BN => LeakyReLU block
x = Conv2D(featureMaps * (2 ** i), 3, **downConvConf)(x)
x = BatchNormalization()(x)
x = LeakyReLU(leakyAlpha)(x)
# second CONV => BN => LeakyReLU block
x = Conv2D(featureMaps * (2 ** i), 3, padding="same")(x)
x = BatchNormalization()(x)
x = LeakyReLU(leakyAlpha)(x)
在第行第 103-106 行,我们创建一个下采样卷积模板配置。然后在线 109-118 上的自动鉴别器模块中使用。
# process the feature maps with global average pooling
x = GlobalAvgPool2D()(x)
x = LeakyReLU(leakyAlpha)(x)
# final FC layer with sigmoid activation function
x = Dense(1, activation="sigmoid")(x)
# create the discriminator model
discriminator = Model(inputs, x)
# return the discriminator model
return discriminator
特征地图然后通过GlobalAvgPool2D
层和另一个LeakyReLU
激活层,之后最终的密集层给出我们的输出(第 121-125 行)。
鉴别器对象被初始化并在行 128-131 返回,鉴别器功能结束。
为 ESRGAN 建立训练管道
架构完成后,是时候转到位于pyimagesearch
目录中的esrgan_training.py
脚本了。
# import the necessary packages
from tensorflow.keras import Model
from tensorflow import concat
from tensorflow import zeros
from tensorflow import ones
from tensorflow import GradientTape
from tensorflow.keras.activations import sigmoid
from tensorflow.math import reduce_mean
import tensorflow as tf
class ESRGANTraining(Model):
def __init__(self, generator, discriminator, vgg, batchSize):
# initialize the generator, discriminator, vgg model, and
# the global batch size
super().__init__()
self.generator = generator
self.discriminator = discriminator
self.vgg = vgg
self.batchSize = batchSize
为了使事情变得更简单,完整的培训模块被打包在第 11 行上定义的类中。
自然地,第一个函数变成了__init__
,它接受发生器模型、鉴别器模型、VGG 模型和批量规格(第 12 行)。
在这个函数中,我们为参数创建相应的类变量(第 16-19 行)。这些变量将在后面用于类函数。
def compile(self, gOptimizer, dOptimizer, bceLoss, mseLoss):
super().compile()
# initialize the optimizers for the generator
# and discriminator
self.gOptimizer = gOptimizer
self.dOptimizer = dOptimizer
# initialize the loss functions
self.bceLoss = bceLoss
self.mseLoss = mseLoss
第 21 行上的compile
函数接收生成器和鉴别器优化器、二元交叉熵损失函数和均方损失函数。
该函数初始化发生器和鉴别器的优化器和损失函数(第 25-30 行)。
def train_step(self, images):
# grab the low and high resolution images
(lrImages, hrImages) = images
lrImages = tf.cast(lrImages, tf.float32)
hrImages = tf.cast(hrImages, tf.float32)
# generate super resolution images
srImages = self.generator(lrImages)
# combine them with real images
combinedImages = concat([srImages, hrImages], axis=0)
# assemble labels discriminating real from fake images where
# label 0 is for predicted images and 1 is for original high
# resolution images
labels = concat(
[zeros((self.batchSize, 1)), ones((self.batchSize, 1))],
axis=0)
现在是我们定义培训程序的时候了。这在行 32 上定义的函数train_step
中完成。这个函数接受图像作为它的参数。
我们将图像集解包成相应的低分辨率和高分辨率图像,并将它们转换成float32
数据类型(第 34-36 行)。
在行 39 处,我们从生成器中得到一批假的超分辨率图像。这些与真实的超分辨率图像连接,并且相应地创建标签(行 47-49 )。
# train the discriminator with relativistic error
with GradientTape() as tape:
# get the raw predictions and divide them into
# raw fake and raw real predictions
rawPreds = self.discriminator(combinedImages)
rawFake = rawPreds[:self.batchSize]
rawReal = rawPreds[self.batchSize:]
# process the relative raw error and pass it through the
# sigmoid activation function
predFake = sigmoid(rawFake - reduce_mean(rawReal))
predReal = sigmoid(rawReal - reduce_mean(rawFake))
# concat the predictions and calculate the discriminator
# loss
predictions = concat([predFake, predReal], axis=0)
dLoss = self.bceLoss(labels, predictions)
# compute the gradients
grads = tape.gradient(dLoss,
self.discriminator.trainable_variables)
# optimize the discriminator weights according to the
# gradients computed
self.dOptimizer.apply_gradients(
zip(grads, self.discriminator.trainable_variables)
)
# generate misleading labels
misleadingLabels = ones((self.batchSize, 1))
首先,我们将定义鉴别器训练。开始一个GradientTape
,我们从我们的鉴别器得到对组合图像集的预测(第 52-55 行)。我们从这些预测中分离出假图像预测和真实图像预测,并得到两者的相对论误差。然后这些值通过一个 sigmoid 函数得到我们的最终输出值(第 56-62 行)。
预测再次被连接,并且通过使预测通过二元交叉熵损失来计算鉴别器损失(行 66 和 67 )。
利用损失值,计算梯度,并相应地改变鉴别器权重(第 70-77 行)。
鉴别器训练结束后,我们现在生成生成器训练所需的误导标签(第 80 行)。
# train the generator (note that we should *not* update
# the weights of the discriminator)
with GradientTape() as tape:
# generate fake images
fakeImages = self.generator(lrImages)
# calculate predictions
rawPreds = self.discriminator(fakeImages)
realPreds = self.discriminator(hrImages)
relativisticPreds = rawPreds - reduce_mean(realPreds)
predictions = sigmoid(relativisticPreds)
# compute the discriminator predictions on the fake images
# todo: try with logits
#gLoss = self.bceLoss(misleadingLabels, predictions)
gLoss = self.bceLoss(misleadingLabels, predictions)
# compute the pixel loss
pixelLoss = self.mseLoss(hrImages, fakeImages)
# compute the normalized vgg outputs
srVGG = tf.keras.applications.vgg19.preprocess_input(
fakeImages)
srVGG = self.vgg(srVGG) / 12.75
hrVGG = tf.keras.applications.vgg19.preprocess_input(
hrImages)
hrVGG = self.vgg(hrVGG) / 12.75
# compute the perceptual loss
percLoss = self.mseLoss(hrVGG, srVGG)
# compute the total GAN loss
gTotalLoss = 5e-3 * gLoss + percLoss + 1e-2 * pixelLoss
# compute the gradients
grads = tape.gradient(gTotalLoss,
self.generator.trainable_variables)
# optimize the generator weights according to the gradients
# calculated
self.gOptimizer.apply_gradients(zip(grads,
self.generator.trainable_variables)
)
# return the generator and discriminator losses
return {
"dLoss": dLoss, "gTotalLoss": gTotalLoss,
"gLoss": gLoss, "percLoss": percLoss, "pixelLoss": pixelLoss}
我们再次为生成器初始化一个GradientTape
,并使用生成器生成假的超分辨率图像(第 84-86 行)。
计算伪超分辨率图像和真实超分辨率图像的预测,并计算相对论误差(行 89-92 )。
预测被馈送到二进制交叉熵损失函数,同时使用均方误差损失函数计算像素损失(行 97-100 )。
接下来,我们计算 VGG 输出和感知损失(行 103-111 )。有了所有可用的损耗值,我们使用行 114 上的等式计算 GAN 总损耗。
计算并应用发电机梯度(第 117-128 行)。
创建效用函数辅助甘训练
我们使用了一些实用程序脚本来帮助我们的培训。第一个是保存我们在训练中使用的损失的脚本。为此,让我们转到pyimagesearch
目录中的losses.py
脚本。
# import necessary packages
from tensorflow.keras.losses import MeanSquaredError
from tensorflow.keras.losses import BinaryCrossentropy
from tensorflow.keras.losses import Reduction
from tensorflow import reduce_mean
class Losses:
def __init__(self, numReplicas):
self.numReplicas = numReplicas
def bce_loss(self, real, pred):
# compute binary cross entropy loss without reduction
BCE = BinaryCrossentropy(reduction=Reduction.NONE)
loss = BCE(real, pred)
# compute reduced mean over the entire batch
loss = reduce_mean(loss) * (1\. / self.numReplicas)
# return reduced bce loss
return
__init__
函数定义了后续损失函数中使用的批量大小(第 8 行和第 9 行)。
损失被打包到第 7 行的一个类中。我们定义的第一个损失是线 11** 上的二元交叉熵损失。它接受真实标签和预测标签。**
二进制交叉熵损失对象定义在行 13 ,损失计算在行 14 。然后在整批中调整损耗(第 17 行)。
def mse_loss(self, real, pred):
# compute mean squared error loss without reduction
MSE = MeanSquaredError(reduction=Reduction.NONE)
loss = MSE(real, pred)
# compute reduced mean over the entire batch
loss = reduce_mean(loss) * (1\. / self.numReplicas)
# return reduced mse loss
return loss
下一个损失是在行 22 上定义的均方误差函数。一个均方误差损失对象被初始化,随后是整个批次的损失计算(第 24-28 行)。
我们的losses.py
脚本到此结束。我们接下来进入utils.py
脚本,它将帮助我们更好地评估 GAN 生成的图像。为此,接下来让我们进入utils.py
剧本。
# import the necessary packages
from . import config
from matplotlib.pyplot import subplots
from matplotlib.pyplot import savefig
from matplotlib.pyplot import title
from matplotlib.pyplot import xticks
from matplotlib.pyplot import yticks
from matplotlib.pyplot import show
from tensorflow.keras.preprocessing.image import array_to_img
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
import os
# the following code snippet has been taken from:
# https://keras.io/examples/vision/super_resolution_sub_pixel
def zoom_into_images(image, imageTitle):
# create a new figure with a default 111 subplot.
(fig, ax) = subplots()
im = ax.imshow(array_to_img(image[::-1]), origin="lower")
title(imageTitle)
# zoom-factor: 2.0, location: upper-left
axins = zoomed_inset_axes(ax, 2, loc=2)
axins.imshow(array_to_img(image[::-1]), origin="lower")
# specify the limits.
(x1, x2, y1, y2) = 20, 40, 20, 40
# apply the x-limits.
axins.set_xlim(x1, x2)
# apply the y-limits.
axins.set_ylim(y1, y2)
# remove the xticks and yticks
yticks(visible=False)
xticks(visible=False)
# make the line.
mark_inset(ax, axins, loc1=1, loc2=3, fc="none", ec="blue")
# build the image path and save it to disk
imagePath = os.path.join(config.BASE_IMAGE_PATH,
f"{
imageTitle}.png")
savefig(imagePath)
# show the image
show()
这个脚本在第 16 行的处包含一个名为zoom_into_images
的函数,它接受图像和图像标题作为参数。
首先定义支线剧情,绘制图像(第 18 行和第 19 行)。在第 21-24 行,我们放大图像的左上区域,并再次绘制该部分。
该图的界限设置在第 27-31 行的上。现在,我们移除 x 轴和 y 轴上的记号,并在原始绘图上插入线条(第 34-38 行)。
绘制完图像后,我们保存图像并结束该功能(第 41-43 行)。
我们最后的实用程序脚本是vgg.py
脚本,它初始化了我们感知损失的 VGG 模型。
# import the necessary packages
from tensorflow.keras.applications import VGG19
from tensorflow.keras import Model
class VGG:
@staticmethod
def build():
# initialize the pre-trained VGG19 model
vgg = VGG19(input_shape=(None, None, 3), weights="imagenet",
include_top=False)
# slicing the VGG19 model till layer #20
model = Model(vgg.input, vgg.layers[20].output)
# return the sliced VGG19 model
return model
我们在5 号线为 VGG 模型创建一个类。该函数包含一个名为build
的单一函数,它简单地初始化一个预训练的 VGG-19 架构,并返回一个切片到第 20 层的 VGG 模型(第 7-16 行)。这就结束了vgg.py
脚本。
训练 ESR gan
现在我们所有的积木都准备好了。我们只需要按照正确的顺序来执行它们,以便进行适当的 GAN 训练。为了实现这一点,我们进入train_esrgan.py
脚本。
# USAGE
# python train_esrgan.py --device gpu
# python train_esrgan.py --device tpu
# import tensorflow and fix the random seed for better reproducibility
import tensorflow as tf
tf.random.set_seed(42)
# import the necessary packages
from pyimagesearch.data_preprocess import load_dataset
from pyimagesearch.esrgan_training import ESRGANTraining
from pyimagesearch.esrgan import ESRGAN
from pyimagesearch.losses import Losses
from pyimagesearch.vgg import VGG
from pyimagesearch import config
from tensorflow import distribute
from tensorflow.config import experimental_connect_to_cluster
from tensorflow.tpu.experimental import initialize_tpu_system
from tensorflow.keras.optimizers import Adam
from tensorflow.io.gfile import glob
import argparse
import sys
import os
# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("--device", required=True, default="gpu",
choices=["gpu", "tpu"], type=str,
help="device to use for training (gpu or tpu)")
args = vars(ap.parse_args())
这里的第一个任务是定义一个参数解析器,以便用户可以选择是使用 TPU 还是 GPU 来完成 GAN 训练(第 26-30 行)。正如我们已经提到的,我们已经使用 TPU 和 GPU 训练了 GAN 来评估效率。
# check if we are using TPU, if so, initialize the TPU strategy
if args["device"] == "tpu":
# initialize the TPUs
tpu = distribute.cluster_resolver.TPUClusterResolver()
experimental_connect_to_cluster(tpu)
initialize_tpu_system(tpu)
strategy = distribute.TPUStrategy(tpu)
# ensure the user has entered a valid gcs bucket path
if config.TPU_BASE_TFR_PATH == "gs://<PATH_TO_GCS_BUCKET>/tfrecord":
print("[INFO] not a valid GCS Bucket path...")
sys.exit(0)
# set the train TFRecords, pretrained generator, and final
# generator model paths to be used for TPU training
tfrTrainPath = config.TPU_DIV2K_TFR_TRAIN_PATH
pretrainedGenPath = config.TPU_PRETRAINED_GENERATOR_MODEL
genPath = config.TPU_GENERATOR_MODEL
# otherwise, we are using multi/single GPU so initialize the mirrored
# strategy
elif args["device"] == "gpu":
# define the multi-gpu strategy
strategy = distribute.MirroredStrategy()
# set the train TFRecords, pretrained generator, and final
# generator model paths to be used for GPU training
tfrTrainPath = config.GPU_DIV2K_TFR_TRAIN_PATH
pretrainedGenPath = config.GPU_PRETRAINED_GENERATOR_MODEL
genPath = config.GPU_GENERATOR_MODEL
# else, invalid argument was provided as input
else:
# exit the program
print("[INFO] please enter a valid device argument...")
sys.exit(0)
# display the number of accelerators
print(f"[INFO] number of accelerators: {
strategy.num_replicas_in_sync}...")
根据设备选择,我们必须初始化策略。首先,我们探索 TPU 选择的案例(第 33 行)。
为了恰当地利用 TPU 的能力,我们初始化了一个TPUClusterResolver
来有效地利用资源。接下来,TPU 策略被初始化(第 35-43 行)。
定义了到 TPU 训练数据、预训练发生器和完全训练发生器的TFRecords
路径(第 47-49 行)。
现在探讨第二种设备选择,即 GPU。对于 GPU,使用 GPU 镜像策略(第 55 行),并定义 GPU 特定的TFRecords
路径、预训练的生成器路径和完全训练的生成器路径(第 59-61 行)。
如果给出了任何其他选择,脚本会自己退出(第 64-67 行)。
# grab train TFRecord filenames
print("[INFO] grabbing the train TFRecords...")
trainTfr = glob(tfrTrainPath +"/*.tfrec")
# build the div2k datasets from the TFRecords
print("[INFO] creating train and test dataset...")
trainDs = load_dataset(filenames=trainTfr, train=True,
batchSize=config.TRAIN_BATCH_SIZE * strategy.num_replicas_in_sync)
# call the strategy scope context manager
with strategy.scope():
# initialize our losses class object
losses = Losses(numReplicas=strategy.num_replicas_in_sync)
# initialize the generator, and compile it with Adam optimizer and
# MSE loss
generator = ESRGAN.generator(
scalingFactor=config.SCALING_FACTOR,
featureMaps=config.FEATURE_MAPS,
residualBlocks=config.RESIDUAL_BLOCKS,
leakyAlpha=config.LEAKY_ALPHA,
residualScalar=config.RESIDUAL_SCALAR)
generator.compile(optimizer=Adam(learning_rate=config.PRETRAIN_LR),
loss=losses.mse_loss)
# pretraining the generator
print("[INFO] pretraining ESRGAN generator ...")
generator.fit(trainDs, epochs=config.PRETRAIN_EPOCHS,
steps_per_epoch=config.STEPS_PER_EPOCH)
我们获取TFRecords
文件,然后使用load_dataset
函数(第 74-79 行)创建一个训练数据集。
首先,我们将初始化预训练的生成器。为此,我们首先调用策略范围上下文管理器来初始化第 82-95 行上的损失和生成器。接着在线 99 和 100 上训练发电机。
# check whether output model directory exists, if it doesn't, then
# create it
if args["device"] == "gpu" and not os.path.exists(config.BASE_OUTPUT_PATH):
os.makedirs(config.BASE_OUTPUT_PATH)
# save the pretrained generator
print("[INFO] saving the pretrained generator...")
generator.save(pretrainedGenPath)
# call the strategy scope context manager
with strategy.scope():
# initialize our losses class object
losses = Losses(numReplicas=strategy.num_replicas_in_sync)
# initialize the vgg network (for perceptual loss) and discriminator
# network
vgg = VGG.build()
discriminator = ESRGAN.discriminator(
featureMaps=config.FEATURE_MAPS,
leakyAlpha=config.LEAKY_ALPHA,
discBlocks=config.DISC_BLOCKS)
# build the ESRGAN model and compile it
esrgan = ESRGANTraining(
generator=generator,
discriminator=discriminator,
vgg=vgg,
batchSize=config.TRAIN_BATCH_SIZE)
esrgan.compile(
dOptimizer=Adam(learning_rate=config.FINETUNE_LR),
gOptimizer=Adam(learning_rate=config.FINETUNE_LR),
bceLoss=losses.bce_loss,
mseLoss=losses.mse_loss,
)
# train the ESRGAN model
print("[INFO] training ESRGAN...")
esrgan.fit(trainDs, epochs=config.FINETUNE_EPOCHS,
steps_per_epoch=config.STEPS_PER_EPOCH)
# save the ESRGAN generator
print("[INFO] saving ESRGAN generator to {}..."
.format(genPath))
esrgan.generator.save(genPath)
如果设备被设置为 GPU,基本输出模型目录被初始化(如果还没有完成的话)(行 104 和 105 )。预训练的发电机然后被保存到指定的路径(行 109 )。
现在我们来看看训练有素的 ESRGAN。我们再次初始化策略范围上下文管理器,并初始化一个 loss 对象(行 112-114 )。
感知损失所需的 VGG 模型被初始化,随后是 ESRGAN ( 行 118-129 )。然后用所需的优化器和损耗编译 es rgan(第 130-135 行)。
这里的最后一步是用训练数据拟合 ESRGAN,并让它训练(行 139 和 140 )。
一旦完成,被训练的重量被保存在线 145 上的预定路径中。
为 ESRGAN 构建推理脚本
随着我们的 ESRGAN 培训的完成,我们现在可以评估我们的 ESRGAN 在结果方面表现如何。为此,让我们看看位于核心目录中的inference.py
脚本。
# USAGE
# python inference.py --device gpu
# python inference.py --device tpu
# import the necessary packages
from pyimagesearch.data_preprocess import load_dataset
from pyimagesearch.utils import zoom_into_images
from pyimagesearch import config
from tensorflow import distribute
from tensorflow.config import experimental_connect_to_cluster
from tensorflow.tpu.experimental import initialize_tpu_system
from tensorflow.keras.models import load_model
from tensorflow.keras.preprocessing.image import array_to_img
from tensorflow.io.gfile import glob
from matplotlib.pyplot import subplots
import argparse
import sys
import os
# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("--device", required=True, default="gpu",
choices=["gpu", "tpu"], type=str,
help="device to use for training (gpu or tpu)")
args = vars(ap.parse_args())
根据我们用于训练的设备,我们需要提供相同的设备来初始化模型并相应地加载所需的权重。为此,我们构建了一个参数解析器,它接受用户的设备选择(第 21-25 行)。
# check if we are using TPU, if so, initialize the strategy
# accordingly
if args["device"] == "tpu":
tpu = distribute.cluster_resolver.TPUClusterResolver()
experimental_connect_to_cluster(tpu)
initialize_tpu_system(tpu)
strategy = distribute.TPUStrategy(tpu)
# set the train TFRecords, pretrained generator, and final
# generator model paths to be used for TPU training
tfrTestPath = config.TPU_DIV2K_TFR_TEST_PATH
pretrainedGenPath = config.TPU_PRETRAINED_GENERATOR_MODEL
genPath = config.TPU_GENERATOR_MODEL
# otherwise, we are using multi/single GPU so initialize the mirrored
# strategy
elif args["device"] == "gpu":
# define the multi-gpu strategy
strategy = distribute.MirroredStrategy()
# set the train TFRecords, pretrained generator, and final
# generator model paths to be used for GPU training
tfrTestPath = config.GPU_DIV2K_TFR_TEST_PATH
pretrainedGenPath = config.GPU_PRETRAINED_GENERATOR_MODEL
genPath = config.GPU_GENERATOR_MODEL
# else, invalid argument was provided as input
else:
# exit the program
print("[INFO] please enter a valid device argument...")
sys.exit(0)
根据用户输入的选择,我们必须建立处理数据的策略。
第一个设备选择(TPU)是通过初始化TPUClusterResolver
、策略和特定于 TPU 的输出路径来探索的,其方式与我们为训练脚本所做的相同(第 29-33 行)。
对于第二个选择(GPU),我们重复与训练脚本相同的过程(第 43-51 行)。
如果给出了任何其他输入,脚本会自行退出(第 54-57 行)。
# get the dataset
print("[INFO] loading the test dataset...")
testTfr = glob(tfrTestPath + "/*.tfrec")
testDs = load_dataset(testTfr, config.INFER_BATCH_SIZE, train=False)
# get the first batch of testing images
(lrImage, hrImage) = next(iter(testDs))
# call the strategy scope context manager
with strategy.scope():
# load the ESRGAN trained models
print("[INFO] loading the pre-trained and fully trained ESRGAN model...")
esrganPreGen = load_model(pretrainedGenPath, compile=False)
esrganGen = load_model(genPath, compile=False)
# predict using ESRGAN
print("[INFO] making predictions with pre-trained and fully trained ESRGAN model...")
esrganPreGenPred = esrganPreGen.predict(lrImage)
esrganGenPred = esrganGen.predict(lrImage)
出于测试目的,我们在行 62 上创建一个测试数据集。使用next(iter())
,我们可以抓取一批图像集,我们在行 65 上将其解包。
接下来,预训练的 GAN 和完全训练的 ESRGAN 被初始化并加载到线 71 和 72 上。然后低分辨率图像通过这些 gan 进行预测(线 76 和 77 )。
# plot the respective predictions
print("[INFO] plotting the ESRGAN predictions...")
(fig, axes) = subplots(nrows=config.INFER_BATCH_SIZE, ncols=4,
figsize=(50, 50))
# plot the predicted images from low res to high res
for (ax, lowRes, esrPreIm, esrGanIm, highRes) in zip(axes, lrImage,
esrganPreGenPred, esrganGenPred, hrImage):
# plot the low resolution image
ax[0].imshow(array_to_img(lowRes))
ax[0].set_title("Low Resolution Image")
# plot the pretrained ESRGAN image
ax[1].imshow(array_to_img(esrPreIm))
ax[1].set_title("ESRGAN Pretrained")
# plot the ESRGAN image
ax[2].imshow(array_to_img(esrGanIm))
ax[2].set_title("ESRGAN")
# plot the high resolution image
ax[3].imshow(array_to_img(highRes))
ax[3].set_title("High Resolution Image")
# check whether output image directory exists, if it doesn't, then
# create it
if not os.path.exists(config.BASE_IMAGE_PATH):
os.makedirs(config.BASE_IMAGE_PATH)
# serialize the results to disk
print("[INFO] saving the ESRGAN predictions to disk...")
fig.savefig(config.GRID_IMAGE_PATH)
# plot the zoomed in images
zoom_into_images(esrganPreGenPred[0], "ESRGAN Pretrained")
zoom_into_images(esrganGenPred[0], "ESRGAN")
为了可视化我们的结果,在第 81 行和第 82 行初始化子情节。然后,我们对批处理进行循环,并绘制低分辨率图像、预训练 GAN 输出、ESRGAN 输出和实际高分辨率图像进行比较(第 85-101 行)。
的可视化效果
**图 3 和图 4 分别向我们展示了预训练的 ESRGAN 和完全训练的 ESRGAN 的最终预测图像。
这两个模型的输出在视觉上是无法区分的。然而,结果比上周的 SRGAN 输出要好,即使 ESRGAN 被训练的时期更少。
放大的补丁显示了 ESRGAN 实现的像素化信息的复杂清晰度,证明用于 SRGAN 增强的增强配方工作得相当好。
汇总
根据他们的结果,SRGANs 已经给人留下了深刻的印象。它优于现有的几种超分辨率算法。在其基础上,提出增强配方是整个深度学习社区非常赞赏的事情。
这些添加是经过深思熟虑的,结果一目了然。我们的 ESRGAN 取得了很大的成绩,尽管被训练了很少的时代。这非常符合 ESRGAN 优先关注效率的动机。
GANs 一直给我们留下深刻的印象,直到今天,新的域名都在使用 GANs。但是在今天的项目中,我们讨论了可以用来改善最终结果的方法。
引用信息
Chakraborty,D. “增强的超分辨率生成对抗网络(ESRGAN), PyImageSearch ,P. Chugh,A. R. Gosthipaty,S. Huot,K. Kidriavsteva,R. Raha,A. Thanki 编辑。,2022 年,【https://pyimg.co/jt2cb
@incollection{
Chakraborty_2022_ESRGAN,
author = {
Devjyoti Chakraborty},
title = {
Enhanced Super-Resolution Generative Adversarial Networks {
(ESRGAN)}},
booktitle = {
PyImageSearch},
editor = {
Puneet Chugh and Aritra Roy Gosthipaty and Susan Huot and Kseniia Kidriavsteva and Ritwik Raha and Abhishek Thanki},
year = {
2022},
note = {
https://pyimg.co/jt2cb},
}
要下载这篇文章的源代码(并在未来教程在 PyImageSearch 上发布时得到通知),只需在下面的表格中输入您的电子邮件地址!********