TensorFlow2-深度学习项目-二-

99 阅读1小时+

TensorFlow2 深度学习项目(二)

原文:Deep Learning Projects Using TensorFlow 2

协议:CC BY-NC-SA 4.0

五、音乐生成

每个人都喜欢音乐。不管我们的心情或偏好如何,总有一首歌适合我们所有人。考虑到这一点,在本章中,我们将使用一种叫做门控递归单元(GRU)的神经网络来生成一首歌曲。别担心,你不需要成为一名专业的音乐家来使用深度学习生成音乐。

GRU 概述

GRUs 旨在解决标准循环神经网络(RNN)带来的消失梯度问题。当梯度变得越来越小时,消失梯度问题出现在深度神经网络中,这阻止了权重改变其值。

为了解决梯度消失的问题,GRUs 使用了一个更新门和一个重置门。这些门是决定什么信息应该被传递到输出的两个向量。他们可以被训练储存很久以前的信息,而不会随着时间的推移而抹去。他们还可以删除与预测无关的信息。

与 RNN 之前拥有的四个节点的简单神经网络不同,GRU 拥有一个包含多种操作的单元。GRU 将信息向前传递到几个时间段,以便影响未来的时间段。换句话说,该值会在内存中存储一段时间。在一个临界点,它被取出并与当前状态一起使用,以便在将来的某个日期进行更新。GRU 网络涉及的参数更少,这使得它们的训练速度更快。网络学会如何使用门来保护它的记忆,这样它就能够在更长的时间内进行长期预测。类似于传统的 RNNs,GRU 网络在每个时间步产生一个输出,这个输出用于使用梯度下降来训练网络。

GRU 网络的应用包括:

  • 复调音乐造型

  • 语音信号建模

  • 手写识别

  • 语音识别

  • 人类基因组的研究

  • 股票市场分析

GRU 是如何工作的

GRU 是循环神经网络的门控版本。隐藏状态用于传输信息。GRU 是直观的,因为它能够确定将多少过去的信息传递给未来,而无需显式编程。与 LSTM 不同,GRU 只有两个门:

  • 更新门:更新门帮助模型确定有多少过去的信息(来自以前的时间步骤)需要传递给未来。这真的很强大,因为该模型可以决定复制过去的所有信息,并消除梯度消失问题的风险。

  • 重置门:本质上,该门用于从模型中决定要忘记多少过去的信息。

图 5-1 显示了一个 GRU 的内部工作方式,这里Rg(t)是复位门,Ug(t)是更新门。

img/488562_1_En_5_Fig1_HTML.jpg

图 5-1

在 GRU 里面

起重机实习

让我们来看看 GRU 每个阶段的过程。记住,GRU 的输入是三维数组的形式。

第一阶段

电流存储器的功能非常像一个门,但它集成在复位门中,用于在输入中引入一些非线性,并使输入为零均值。此外,它减少了先前信息对当前信息的影响,而当前信息会传递到未来。请遵循以下步骤:

  1. 引入新的存储内容,使用复位门存储过去的相关信息。输入nt乘以权重WHs(t-1)Rgt

  2. 计算复位门RgtRgt * Hs(t-1) *之间的哈达玛乘积。*这将决定从之前的时间步骤中删除什么。

  3. 根据输入到矢量中的输入,Rgt矢量被赋予一个接近于01的值。

  4. 将第 1 步和第 2 步的结果相加。

  5. 应用非线性激活功能tanh。数学表示如下:

    Hst' = tanh(W【Rg】t* Hs(t-1),n t )

第二阶段

本质上,该模型使用重置门来决定忘记多少过去的信息。它类似于 LSTM 循环单元中输入门和遗忘门的组合。总误差由所有时间步的误差总和给出。类似地,该值可以计算为每个时间步长的梯度总和。请遵循以下步骤:

  1. 将输入的nt乘以权重Wrg,将Hst-1乘以Wrg

  2. 计算步骤 1 中的乘积之和。

  3. 对结果应用 Sigmoid。

模型可以学习将向量 Rg t 设置为接近01。如果向量Rgt接近0,当前内容的大部分将被忽略,因为它与我们的预测无关。同时,由于在这个时间步Rgt将接近0,因此1-Rgt将接近1,从而允许保留大部分过去的信息。

数学表示如下:

  • Rg t =乙状结肠(Wrg【Hst-1,n t )
第 3 阶段

更新门是模型可以决定从过去复制所有信息并消除消失梯度问题的风险的地方。请遵循以下步骤:

  1. 将输入的nt乘以权重Wug,将Hst-1乘以Wug

  2. 计算步骤 1 中的乘积之和。

  3. 步骤 3:对结果应用 Sigmoid。

模型可以学习将向量 Ug t 设置为接近01。如果向量Ugt接近0,当前内容的大部分将被忽略,因为它与我们的预测无关。同时,由于Ugt在这个时间步将接近0,(1-Ugt)将接近1,允许保留大部分过去的信息。

数学表示如下:

  • Ug t =乙状结肠(W ug Hs t-1 ,n t )
第四阶段

当前时间步长的存储器是 GRU 的最后一级。网络计算保存当前单元信息的向量Hst,并将其传递给网络。

为此,更新门确定从当前存储器内容Hst收集什么,以及从先前步骤Hs(t-1)收集什么。这是按如下方式完成的:

  1. 将逐元素乘法应用于更新门UgtHs(t-1 )

  2. (1-Rg t )Hst应用逐元素乘法。

  3. 将第 1 步和第 2 步的结果相加。

    ' Hst=(1-Rgt)(Hst-1)+Ugt Hst'

格鲁层

为了设计有效的 GRU,我们可以利用以下类型的层:

  • **嵌入层:**用于为进来的词创建词向量。它位于输入层和 GRU 层之间。嵌入层的输出是 GRU 层的输入。

  • **GRU 层:**循环神经网络层,将序列作为输入,可以返回序列或矩阵。

  • **丢弃层:**一种正则化技术,包括在训练期间的每次更新时将输入单元的一部分设置为 0,以防止过拟合。

  • **密集层:**全连接层,其中每个输入节点连接到每个输出节点。

  • **激活层:**决定 GRU 使用哪个激活函数来计算节点的输出。

Note

keras.layers.CuDNNGRU提供由 cuDNN 支持的快速 GRU 实施。NVIDIA CUDA 深度神经网络库(cuDNN)是一个 GPU 加速的深度神经网络图元库。cuDNN 为标准例程提供了高度优化的实现,例如前向和后向卷积、池化、规范化和激活层。

比较 GRU 和 LSTM

乍一看,LSTMs 和 GRUs 似乎非常相似。但是,需要注意两者之间的细微差别:

  • GRU 只有两个大门,而 LSTM 有三个。

  • LSTMs 控制内存内容(单元状态)的公开,而 gru 将整个单元状态公开给网络中的其他单元。LSTM 单元具有独立的输入和遗忘门,而 GRU 通过其复位门同时执行这两种操作。

  • LSTM 给了我们最大的控制,因此,更好的结果。它确实带来了更多的复杂性和更高的运营成本。

与 LSTM 相比,GRU 使用的训练参数更少,因此使用的内存更少,执行速度更快,训练速度更快,而 LSTM 在使用较长序列的数据集上更准确。

你现在知道什么是 GRU 以及它与 LSTM 有什么不同了。是时候看看如何将 GRU 变成音乐 DJ 了。

项目描述

在这个项目中,我们将创建一个 GRU,它将根据给定的音符序列生成音乐。一旦模型被训练,当一组随机的音符被提供作为输入时,它应该能够预测接下来的音符序列。数据集由 MIDI 文件组成,我们从中提取音乐成分作为模型的特征。GRU 模型需要以下两个对象来生成音乐:

  • **音符对象:**包含关于音高、八度和偏移的信息。

  • **和弦对象:**一组音符的容器。

然后使用一键编码的过程将音符和和弦对象转换成整数向量。这使得模型能够学习各种音乐作品中的模式。然后,该预测将从整数向量重新转换为音符,并保存为 MIDI 文件。目标是获得一个听起来尽可能与原曲相似的 MIDI 文件。

![img/488562_1_En_5_Fig2_HTML.jpg

图 5-2

GRU 音乐项目工作流程

关于数据集

名称: 13 万个 MIDI 文件集合

流派包括:流行音乐、古典音乐(钢琴/小提琴/吉他)、电子音乐、电子游戏和电影/电视主题

来源: www.reddit.com/r/datasets/comments/3akhxy/the_largest_midi_collection_on_the_internet/

**创作人:**迷笛侠

在你开始创作音乐之前,你需要理解一些术语和概念。

重要术语和概念

在开始这个项目之前,这里有一些你应该知道的方便的术语和概念。

img/488562_1_En_5_Fig3_HTML.jpg

图 5-3

一键编码工作流程

  • **注:**表示音乐声音的符号。它用乐谱表示声音的音高和音长。

  • **和弦:**同时演奏的两个或两个以上音符的组合。

  • **音高:**声音的频率,或者说高低。用字母 A 到 g 表示。

  • **偏移:**音符在乐曲中的位置。

  • **八度音程:**占据两个音之间(包括两个音在内)间隔的一系列八个音,其中一个音的振动频率是另一个音的两倍或一半。

  • **流:**music 21 对象的基本容器;在 Music21 中,对象可以基于从该容器的开始的偏移来按时间排序和/或放置。

  • MIDI 文件:(读作“mid-ee”):乐器数字接口文件的缩写。它有两个扩展--。MID 或. MIDI。它不包含实际的音频,只包含诸如音高、和弦、音符等信息。

  • **分类交叉熵:**用于单标签分类的损失函数。这是指只有一个类别适用于每个数据点。

  • **RMSprop 优化器:**类似于梯度下降算法,但是有动量。它将振动限制在垂直方向。

  • **One-hot 编码:**将分类变量转换为整数或 0 和 1 的向量的过程。例如,在图 5-3 中,我们有一个包含元素 A、B、C 和 A 的数据集。目前它们是字符。然而,我们的模型只能接受数值。因此,我们使用一键编码将字符转换为向量,每个向量有一行三列来表示每个类别。对于本例,映射是最后一列表示类别 A,中间一列表示类别 B,第一列表示类别 c。因此,1 用于指示变量属于哪个类别。

最后,你已经准备好开始这个项目了。让我们从准备这个项目的包开始。

必需的库

让我们看看这个项目所需库的列表。

  • 操作系统(内置 Python)

  • Os.path(内置 Python)

  • 随机(内置 Python 2 及更高版本)

  • Shutil(内置 Python 库)

  • NumPy(安装说明见第一章)

  • 熊猫(安装说明见第一章)

  • Matplotlib(安装说明见第一章)

  • TensorFlow(安装说明见第一章)

  • Keras(安装说明见第一章)

  • Music21(本章中的安装说明)

看起来我们需要做的就是安装 Music21 和 Random2。让我们现在做那件事。

安装说明

在第一章,我们安装了每个项目所需的标准库。这些是这个特定项目中使用的附加库的安装说明。

使用画中画

为了确保我们可以安装这些库,而不用考虑我们的系统,我们将使用 Python 包 PIP。

  1. 在终端中使用以下命令安装 Music21。

  2. 在终端中使用以下命令检查 Music21 的安装。

Pip3 install Music21

  1. 在终端中使用以下命令安装 Random2。
Pip3 show Music21

  1. 在终端中使用以下命令检查 Random2 的安装。
Pip3 install random2

img/488562_1_En_5_Fig4_HTML.jpg

图 5-4

MuseScore 官方网站

  1. 安装 MuseScore。因为我们正在处理 MIDI 文件,我们需要软件来打开和查看这些文件。前往官网 https://musescore.org/en (见图 5-4 )。MuseScore 是开源免费的。该网站会自动检测您使用的系统,并推荐合适的 MuseScore 版本。只需点击免费下载按钮。
Pip3 show random2

此后,根据您使用的操作系统,说明会有所不同。

使用 Windows

在 Windows 中,请按照下列步骤操作:

  1. 找到下载文件的位置,双击安装程序开始安装。

  2. 单击每个屏幕上的下一步按钮,直到出现安装按钮。点击此按钮,MuseScore 将被安装。

  3. 一旦弹出完成按钮,就意味着 MuseScore 已经成功安装。退出安装程序。

  4. 进入开始菜单,选择所有程序➤ MuseScore,启动 MuseScore。

使用 macOS

在 macOS 中,请按照下列步骤操作:

  1. 找到文件(通常在Downloads文件夹中)并运行它。将出现 MuseScore 图标。

  2. 将图标拖至Applications文件夹。它可能会询问管理员密码;输入密码,然后点按“鉴定”。安装将开始。

  3. 要运行 MuseScore,导航至Applications文件夹并点击 MuseScore 图标。

使用 Linux

在 Linux 中,请遵循以下步骤:

  1. 在终端类型中:

  2. 然后按回车键。

  3. 出现提示时,按 Y,然后再次返回。将安装 MuseScore。

  4. MuseScore 可以通过application菜单打开,或者通过在终端中键入musescore并按回车键打开。

sudo apt-get install musescore

安装故障排除

以下是一些可能会出现但很容易修复的常见错误:

  • 确保所有安装都是最新的。

Note

random2提供了 Python 2.7 的随机模块的 Python 3 移植版本。它也被移植到 Python 2.6 中。

  • 尽量不要手动安装任何东西。请改用 pip 安装程序。

  • 如果你得到一个“不在 sudoers 文件中”的错误,可以通过编辑文件来修复。您将需要在终端中su到根目录,然后运行visudo并搜索看起来像root ALL=(ALL) ALL的一行。复制那一行,用用户名替换root。然后保存文件。或者,您可以在“用户”偏好设置面板中将用户设置为管理员。然后,您将拥有管理员权限。

  • 在 Windows 中,在使用 pip 之前,必须将 Python 路径添加到环境变量中。

这些是你可能遇到的最常见的问题。为了获得更多的帮助,有一个谷歌小组,用户可以在那里发布关于 Music21 的问题。该组的链接可以在本章末尾的“参考资料”部分找到。

GRU 建筑

要使用 GRU 生成音乐,您需要一个具有正确层数和正确顺序的模型。图 5-5 显示了我们将用于这个项目的模型。

img/488562_1_En_5_Fig5_HTML.jpg

图 5-5

该项目的 GRU 建筑

让我们来看看 GRU 的“蓝图”。该模型将由四层组成:

  • GRU 层 - 3

  • 脱落层- 3

  • 密集层- 2

  • 激活层- 1

GRU 层学习音乐作品中的模式。它们利用复位门和更新门来提取模式并“记忆”它。dropout 层完全按照它所暗示的那样工作。通过“丢弃”模型学习的部分模式,我们避免了过拟合模型。最后,密集层是一个完全连通的层,可以提高模型的准确性。

  • Activation function: 我们将使用 Softmax 函数,因为它非常适合于音乐作品中组件的识别和分类。

  • **损失函数:**由于我们是把这个项目当做一个分类问题来处理,分类交叉熵损失函数会给我们最好的结果。它是专门为分类而设计的。

  • 优化器:我们使用 RMSprop 优化器,因为它有伪曲率信息。此外,它可以处理随机目标。这两个特性对于小批量学习很重要,这是我们的模型使用的过程。

Note

嵌入层通常用于文本分析。所以我们不会在模型中使用它。

程序

您终于准备好实现 GRU 了。让我们打开一个新的 Jupyter 笔记本并开始工作。

第一步。导入库

为此项目导入必要的库。

import tensorflow as tf
from music21 import converter, instrument, note, chord,stream
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import os
import random
from keras.layers.core import Dense, Activation, Flatten
from keras.layers import GRU,Convolution1D,Convolution2D, Flatten, Dropout, Dense
from keras import utils as np_utils
from tensorflow.keras import layers

设置文件路径,以便 Jupyter 笔记本可以访问该数据集。使用os.chdir命令并输入文件路径。

os.chdir("//Users//vinitasilaparasetty//Downloads//Project 1- GRU//Input//music_files")#enter the file path from your system.

声明将用于特征提取的变量:

musical_note = []
offset = []
instrumentlist=[]

第二步。加载数据

创建filenames变量,该变量使用随机模块随机选择并加载 MIDI 文件。我们可以设置随机选择的文件数量;在这种情况下,我们选择了5。创建musiclist变量,加载数据集中所有的 MIDI 文件。

filenames = random.sample(os.listdir('D:\\Proj\\vinita\\Project 1- GRU\Input\\music_files\\'), 5) #Only 5 files are taken at random
musiclist = os.listdir('D:\\Proj\\vinita\\Project 1- GRU\Input\\music_files\\')

第三步。特征抽出

因为音符的最重要部分可以使用音高的字符串符号重新创建,所以使用音符对象的字符串符号来附加每个音符对象的音高。请遵循以下步骤:

  1. 使用string_midi = converter.parse(r1)解析注释并初始化parsednotes = None变量。

  2. 使用parts = instrument.partitionByInstrument(string_midi)检测仪器类型,并使用instrumentlist.append(parts.parts[0].getInstrument().instrumentName)更新仪器计数器。

  3. 使用if parts: parsednotes = parts.parts[0].recurse()提取尖锐音符。

  4. 使用else: parsednotes = string_midi.flat.notes提取平音符。

  5. 使用for element in parsednotes: offset.append(element.offset)提取偏移量。

  6. 使用if isinstance(element, note.Note):musical_note.append(str(element.pitch))开始向musical_note[ ]数组添加音符。

  7. 用一个.来分隔每个音符:elif isinstance(element, chord.Chord):musical_note.append('.'.join(str(n) for n in element.normalOrder))

for file in filenames:
   matching = [s for s in musiclist if file.split('_')[0] in s]
   print(matching)
   r1 = matching[random.randint(1, len(matching))]
   string_midi = converter.parse(r1)
   parsednotes = None
   parts = instrument.partitionByInstrument(string_midi)
   instrumentlist.append(parts.parts[0].getInstrument().instrumentName)
   if parts: # file has instrument parts
       parsednotes = parts.parts[0].recurse()
   else: # file has flat notes
       parsednotes = string_midi.flat.notes
   for element in parsednotes: #detect offsets
       offset.append(element.offset)
       if isinstance(element, note.Note):
           musical_note.append(str(element.pitch))
       elif isinstance(element, chord.Chord):
           musical_note.append('.'.join(str(n) for n in element.normalOrder))

第四步。探索性数据分析

使用以下方法计算 MIDI 文件中独特乐器的数量:

pd.Series(instrumentlist).value_counts()
pd.Series(instrumentlist).value_counts()

使用以下方法计算音符和和弦的数量:

pd.Series(musical_note).value_counts()
pd.Series(musical_note).value_counts()

使用以下命令可视化 MIDI 文件中的偏移:

 offset = [float(item) for item in offset]
offset = [float(item) for item in offset]
plt.plot(offset)
plt.show() # this shows that offset is normally started from 0 for each musical file

输出应该类似于图 5-6 。

img/488562_1_En_5_Fig6_HTML.jpg

图 5-6

生成偏移的散点图

第五步。数据准备(输入)

使用变量sequence_length = 100变量将前 100 个音符序列作为输入。

现在我们来整理一下笔记:

  1. musical_note[ ]数组中,按升序使用pitchcategory = sorted(set(item for item in musical_note))

  2. 然后通过将 MIDI 文件中的音符映射到整数来使用一个热编码。

  3. 使用以下内容创建一个字典来保存映射的注释:

  4. 通过声明model_input_original = []变量来创建模型的输入。

  5. 使用model_output = []收集模型的输出。

  6. 将作为输入序列输入的音符分开:

    for i in range(0, len(musical_note) - sequence_length, 1):
    sequence_in = musical_note[i:i + sequence_length]
    
    
  7. 将预期输出中的注释分开:

    sequence_out = musical_note[i + sequence_length]
    
    
  8. 将输入序列添加到字典中:

    model_input_original.append([note_encoding[char] for char in sequence_in])
    
    
  9. 添加音符的输出序列:

    model_output.append(note_encoding[sequence_out])
    
    
note_encoding = dict((note, number) for number, note in enumerate(pitchcategory))

MIDI 文件的组成模式存储在n_patterns = len(model_input_original)变量中。现在我们已经有了输入,是时候对它进行整形,以便它可以在模型上使用。使用下面的行来改变数据的形状。

model_input = np.reshape(model_input_original, (n_patterns, sequence_length, 1))

在重塑数据之后,是时候对其进行标准化,以获得准确的结果,避免在训练过程中出现问题。

sequence_length = 100

# Arranging notes and chords in ascending order
pitchcategory = sorted(set(item for item in musical_note))

# One hot encoding
note_encoding = dict((note, number) for number, note in enumerate(pitchcategory))
model_input_original = []
model_output = []

# Prepare input and output data for model
for i in range(0, len(musical_note) - sequence_length, 1):
   sequence_in = musical_note[i:i + sequence_length]
   sequence_out = musical_note[i + sequence_length]
   model_input_original.append([note_encoding[char] for char in sequence_in])
   model_output.append(note_encoding[sequence_out])

n_patterns = len(model_input_original)

# converting data for compatibility with GRU
model_input = np.reshape(model_input_original, (n_patterns, sequence_length, 1))

# standardizing model input data
model_output = np_utils.to_categorical(model_output)
Len_Notes = model_output.shape[1]
model_input = model_input / float(Len_Notes)

第六步。构建模型

您现在已经准备好创建 GRU 模型了。请遵循以下步骤:

  1. 使用model_GRU = tf.keras.models.Sequential()初始化模型。

  2. 使用model_GRU.add(layers.GRU(16,input_shape=(model_input.shape[1], model_input.shape[2]),return_sequences=True))添加第一个 GRU 层。该层在数据经过处理后返回值。

  3. 使用model_GRU.add(layers.Dropout(0.3))添加脱落层。

  4. 这是其次是第二个 GRU 层使用model_GRU.add(layers.GRU(64, return_sequences=True))

  5. 使用model_GRU.add(layers.Dropout(0.3))添加第二个脱落层。

  6. 使用model_GRU.add(layers.GRU(64))添加最后的 GRU 层。

  7. 使用model_GRU.add(layers.Dense(16))添加第一个密集层。

  8. 使用model_GRU.add(layers.Dropout(0.3))添加最终的脱落层。

  9. 使用model_GRU.add(layers.Dense(Len_Notes)),你已经为最终的密集层做好了准备。

  10. 现在你只需要添加最后一层,也就是激活层。使用model_GRU.add(layers.Activation('softmax'))将 Softmax 设置为激活功能。

  11. 要完成模型的设置,使用model_GRU.compile(loss='categorical_crossentropy', optimizer="rmsprop")添加编译特性。在这里,您将损失函数设置为分类交叉熵,将优化器设置为rmsprop

  12. 现在使用model_GRU.fit(model_input, model_output, epochs=30, batch_size=64)为模型设置时期和批量大小。对于第一次运行,您将从 30 个时期和 6 个批量开始。这可以在以后更改以改进模型。

  13. 为了确保模型的架构是正确的,您可以使用model_GRU.summary()来获得模型结构的概述。

model_GRU = tf.keras.models.Sequential()
model_GRU.add(layers.GRU(16,input_shape=(model_input.shape[1], model_input.shape[2]),return_sequences=True))
model_GRU.add(layers.Dropout(0.3))
model_GRU.add(layers.GRU(64, return_sequences=True))
model_GRU.add(layers.Dropout(0.3))
model_GRU.add(layers.GRU(64))
model_GRU.add(layers.Dense(16))
model_GRU.add(layers.Dropout(0.3))
model_GRU.add(layers.Dense(Len_Notes))
model_GRU.add(layers.Activation('softmax'))
model_GRU.compile(loss='categorical_crossentropy', optimizer="rmsprop")
model_GRU.summary() #Displays model architecture

第七步。训练模型

按照以下步骤训练模型:

  1. 使用int_to_note = dict((number, note) for number, note in enumerate(pitchcategory))为笔记初始化一个新的字典。

  2. 使用pattern = model_input_original[0]初始化一个新数组来检测 MIDI 文件中的模式。

  3. 使用prediction_output = [ ]初始化一个新数组来存储预测的音符。

# initializing data for model prediction
int_to_note = dict((number, note) for number, note in enumerate(pitchcategory))
pattern = model_input_original[0]
prediction_output = []
model_GRU.fit(model_input, model_output, epochs=30, batch_size=64)

第八步。预报

通过使用 for note_index in range(500)并遵循以下步骤,您可以预测接下来的 500 个音符:

  1. 必须使用prediction_input = np.reshape(pattern, (1, len(pattern), 1))对预测输入进行整形,以与 GRU 兼容。

  2. 需要使用prediction_input = prediction_input / float(Len_Notes)对输入进行标准化。

  3. 接下来,使用prediction_GRU = model_GRU.predict(prediction_input, verbose=0)获得预测音符。

  4. 使用index_GRU = np.argmax(prediction_GRU)找到频率最高的音符。

  5. 使用以下方法将整数重新转换为相应的音符:

  6. 使用以下命令将注释添加到数组中:

index = index_GRU
  result = int_to_note[index]

    prediction_output.append(result)
    pattern = np.append(pattern,index)
    pattern = pattern[1:len(pattern)]

# generate 500 notes

for note_index in range(500):
   prediction_input = np.reshape(pattern, (1, len(pattern), 1))
   prediction_input = prediction_input / float(Len_Notes)
   prediction_GRU = model_GRU.predict(prediction_input, verbose=0)
   index_GRU = np.argmax(prediction_GRU)
   index = index_GRU
   result = int_to_note[index]
   prediction_output.append(result)
   pattern = np.append(pattern,index)
   pattern = pattern[1:len(pattern)]

第九步。数据准备(偏移)

按照以下步骤准备偏移:

  1. 使用offlen = len(offset)找到偏移的长度。

  2. 现在使用DifferentialOffset = (max(offset)-min(offset))/len(offset)找到偏移的微分。

  3. 创建使用offset2 = offset.copy()收集的偏移的副本。

  4. 使用output_notes = [ ]初始化输出音符数组,并使用i = 0将计数器设置为零。

  5. 现在使用offset = [ ]初始化偏移量数组,并使用initial = 0将计数器设置为零。

  6. 使用以下公式开始计算偏移量:

    for i in range(len(offset2)):
        offset.append(initial)
        initial  = initial+DifferentialOffset
    
    
  7. 现在是时候检测音符和和弦了。使用i=0重置计数器,确保计数器为零。

  8. 使用以下内容搜索句号,以检测每个音符开始的时间:

    for pattern in prediction_output:
        if ('.' in pattern) or pattern.isdigit():
    
    
  9. 使用以下方法将和弦从弦中分离出来,并将其添加到音符数组中:

    notes_in_chord = pattern.split('.')
        notes = [ ]
    
    
  10. 使用以下方法检测音符并将其分开:

```py
for check_note in notes_in_chord:
     gen_note = note.Note(int(check_note))
     gen_note.storedInstrument = instrument.Guitar()
     notes.append(gen_note)
     gen_chord = chord.Chord(notes)

```

11. 使用以下方法检测偏移:

```py
gen_chord.offset = offset[i]
  output_notes.append(gen_chord)

```

12. 为了防止异常,请使用以下代码:

```py
else:
        gen_note = note.Note(pattern)
        gen_note.offset = offset[i]
        gen_note.storedInstrument = instrument.Guitar()
        output_notes.append(gen_note)
    i=i+1

```

```py
# prepare notes , chords and offset separately
offlen = len(offset)
DifferentialOffset = (max(offset)-min(offset))/len(offset)
offset2 = offset.copy()
output_notes = []
i = 0
offset = []
initial = 0
for i in range(len(offset2)):
   offset.append(initial)
   initial  = initial+DifferentialOffset
# Differentiate notes and chords
i=0
for pattern in prediction_output:
   if ('.' in pattern) or pattern.isdigit():
       notes_in_chord = pattern.split('.')
       notes = []
       for check_note in notes_in_chord:
           gen_note = note.Note(int(check_note))
           gen_note.storedInstrument = instrument.Guitar()
           notes.append(gen_note)
       gen_chord = chord.Chord(notes)
       gen_chord.offset = offset[i]
       output_notes.append(gen_chord)
   else:
       gen_note = note.Note(pattern)
       gen_note.offset = offset[i]
       gen_note.storedInstrument = instrument.Guitar()

       output_notes.append(gen_note)
   i=i+1

```

第十步。将输出存储为 MIDI 文件

按照以下步骤将输出存储为 MIDI 文件:

  1. 使用os.chdir('D:\\Proj\\vinita\\Project 1- GRU\Output\\')指定存储 MIDI 文件的文件路径。

  2. 使用midi_stream = stream.Stream(output_notes) #create stream创建带有输出的流。

  3. 使用下面的代码创建一个 MIDI 文件:

midi_stream.write('midi', fp='GRU_output.mid') #create MIDI file using stream

os.chdir('D:\\Proj\\vinita\\Project 1- GRU\Output\\') #Specify file path to store the MIDI file.
midi_stream = stream.Stream(output_notes) #create stream
midi_stream.write('midi', fp='GRU_output.mid') #create MIDI file using stream

我们现在可以使用 MuseScore 查看 MIDI 文件(参见图 5-7 )。将原始音乐作品与生成的输出进行比较,看看 GRU 的表现如何。

img/488562_1_En_5_Fig7_HTML.jpg

图 5-7

使用 MuseScore 查看生成的输出

进一步测试

这里有一些想法可以尝试,并从这个项目中学到更多:

  • 尝试增加和减少 GRU 中的层数。

  • 尝试增加下降图层。

  • 尝试增加和减少历元的数量,以查看其对模型结果的影响。

解决纷争

以下是您可能遇到的一些常见错误,这些错误很容易修复:

  • 当您开始定型模型时,可能会出现以下错误:

    WARNING: Logging before flag parsing goes to stderr.

    不要担心这个。不会影响你的节目。

  • 如果您使层有状态,请记住指定大小。

  • 确保所有路径文件都是正确的。

  • 数据量相当大,可能会耗尽 RAM。若要避免此问题,请减少定型集中的文件数量。

  • 当测试其他数据集时,如果识别乐器有问题,请确保数据集包含在单个乐器上演奏的音乐片段。

  • 使用.show('midi')在 Jupyter 笔记本中直接播放 MIDI 文件。

摘要

以下是你在本章中学到的所有内容的回顾。

  • GRU 能够学习长期依赖,而不是香草 RNN。

  • 在反向传播过程中,梯度随着通过神经网络向后移动而减小。这就是所谓的消失梯度问题。

  • gru 的架构比 LSTMs 简单,训练速度也更快。

  • GRUs 的应用包括复调音乐建模、语音信号建模、手写识别、语音识别、人类基因组研究和股票市场分析。

  • GRU 有两个门:复位门和更新门。

  • GRU 中的不同图层包括嵌入图层、GRU 图层、下降图层和密集图层。

  • 我们可以将 RNN 层设置为有状态的,这意味着为一批中的样本计算的状态将在下一批中作为样本的初始状态重用。

  • GRU 的输入是三维数组的形式。第一个维度表示批量大小,第二个维度表示我们输入到序列中的时间步长数,第三个维度表示一个输入序列中的单元数。

  • 电流存储器的功能非常像一个门,但它集成在复位门中,用于在输入中引入一些非线性,并使输入为零均值。此外,它减少了以前的信息对传递到未来的当前信息的影响。

  • 模型使用重置门来决定要忘记多少过去的信息。它类似于 LSTM 循环单元中输入门和遗忘门的组合。

  • 更新门是模型可以决定从过去复制所有信息并消除消失梯度问题的风险的地方。

  • 当前时间步长的存储器是 GRU 的最后一级。网络计算 Hs t ,这是一个向量,它保存当前单元的信息,并将其传递给网络。

  • 分类交叉熵是用于单标签分类的损失函数。这是指每个数据点仅适用一个类别。

  • RMSprop 优化器类似于梯度下降算法,但具有动量。它限制垂直方向的振动。

  • 一键编码是一个过程,通过该过程,分类变量被转换成可以提供给 ML 算法的形式,以在预测中做得更好。

参考

本章中使用的参考资料如下:

资源

以下是对你的项目有帮助的附加材料的链接。

进一步阅读

有兴趣了解本章中涉及的一些主题吗?这里有一些很棒的链接可以查看:

六、图像上色

图像上色是将颜色添加到原始黑白图像的过程。这意味着艺术家需要计划配色方案,然后花时间费力地手动填充颜色。目前选择的工具是 Photoshop 或类似工具。一张照片可能需要一个月的时间来上色。在这一章中,我们将实现一个简单的卷积神经网络(CNN)模型来理解图像着色是如何工作的。

CNN 模型的灵感来源于人类的视觉。人类的眼睛扫描一个物体,大脑很快地获取该物体的独特特征,以便“识别”它。CNN 模仿这种扫描图像并识别图像的不同特征来识别它的行为。这使它成为影像数据集的理想选择。我们先来看看人类的视觉是如何工作的。然后我们将它与 CNN 模型的工作方式进行比较。

人类视觉评论

图 6-1 展示了人类视觉的全过程,需要大脑和眼睛同时协同工作。

虽然视觉始于眼睛,但对我们所见的解释发生在大脑中,即初级视觉皮层。当我们看到一个物体时,我们眼中的光感受器通过视神经向初级视觉皮层发送信号,在那里处理输入。

我们能够认出我们生活中看到的所有物体和人。大脑中神经元和连接的复杂层次结构在记忆和标记物体的过程中起着重要作用。

就像一个孩子学习识别物体一样,我们需要对数百万张带标签的图片引入一种算法,然后它才能概括输入,并对它从未见过的图像做出预测。计算机将物体以数字的形式形象化。每幅图像都可以用一组二维数字来表示,称为像素。

在视觉中,单个感觉神经元的感受域是视网膜上的特定区域,其中某些东西会激活神经元。每个感觉神经元细胞都有相似的感受野,它们的感受野是重叠的。

等级观念在大脑中起着重要作用。信息以连续的顺序存储在模式序列中。位于大脑最外层的新皮层,分层存储信息。它储存在皮层柱中,或新皮层中统一组织的神经元群中。

img/488562_1_En_6_Fig1_HTML.jpg

图 6-1

人类视觉

你现在应该对人类视觉的工作原理有了基本的了解。让我们看看如何通过使用 CNN 模拟人类视觉,使机器能够“识别”物体。

计算机视觉评论

计算机视觉是一个跨学科的科学领域,旨在使机器能够像人类一样看待世界,并以类似的方式感知世界。它寻求自动完成人类视觉系统可以完成的任务,主要使用 CNN。

CNN 的架构类似于人类大脑中神经元的连接模式,并受到视觉皮层组织的启发。单个神经元只在视野的一个有限区域对刺激做出反应,这个区域被称为感受野。这些区域的集合重叠覆盖了整个可视区域。

卷积神经网络(CNN)是一种深度学习算法,可以接受输入图像,为图像中的各个方面/对象分配重要性(可学习的权重和偏差),并能够区分彼此。

卷积有三个优点:

  • 稀疏相互作用

  • 参数共享

  • 等变表示

CNN 可以通过应用相关的过滤器成功地捕捉图像中的空间和时间依赖性。由于所涉及的参数数量的减少和权重的可重用性,该架构对图像数据集执行更好的拟合。换句话说,网络可以被训练来理解图像的复杂度。

CNN 的作用是将图像简化成一种更容易处理的形式,而不丢失对做出好的预测至关重要的特征。当我们希望设计一个不仅擅长学习特征,而且可扩展到大规模数据集的架构时,这一点非常重要。

CNN 的应用包括:

  • 图像和视频识别

  • 图像分析和分类

  • 媒体娱乐

  • 推荐系统

  • 自然语言处理

现在您已经知道了 CNN 是什么,以及它在现实生活中的应用,让我们来看看 CNN 内部的过程。

CNN 是如何工作的

CNN 的隐藏层通常由输入层、卷积层、汇集层和全连接层组成(见图 6-2 )。每一层都通过可微函数将一个体积转换成另一个体积。

img/488562_1_En_6_Fig2_HTML.jpg

图 6-2

CNN 的标准架构

输入层

当计算机看到一幅图像(以一幅图像作为输入)时,它将看到一个像素值数组。根据图像的分辨率和大小,它将看到 R x C x 3 的张量,其中 R 表示行,C 表示列,3 表示 RGB 值。这些数字中的每一个都被赋予一个从 0 到 255 的值,该值描述了该点的像素强度。这些数字是计算机仅有的输入。

卷积层:内核

卷积层对两个信号进行操作:

  • 一维的

  • 二维(它接受两幅图像作为输入,产生第三幅图像作为输出)

数学上,卷积层可以表示如下:

\mathbf{\mathsf{f}}\kern0.5em \left[\mathbf{\mathsf{n}}\right]\kern0.5em =\left(\mathbf{\mathsf{i}}\kern0.5em \mathbf{\mathsf{x}}\kern0.5em \mathbf{\mathsf{k}}\right)\kern0.5em \left[\mathbf{\mathsf{n}}\right]=\sum \limits_{a=-\infty}^{a=\infty}\mathbf{\mathsf{i}}\kern0.5em \left[\mathbf{\mathsf{a}}\right]\kern0.5em \mathbf{\mathsf{k}}\kern0.5em \left[\mathbf{\mathsf{a}}+\mathbf{\mathsf{n}}\right]

特征图:f

输入:i

内核:k

在卷积层的第一部分中执行卷积运算所涉及的元素被称为内核/滤波器。卷积运算的目的是提取低层特征,如边缘、颜色、梯度方向等。,来自输入图像。

想象一下,我们在一个黑暗的房间里,只用一个手电筒就可以观察周围的环境。手电筒让我们一次只能看到房间的一小部分。

CNN 以类似的方式工作。手电筒被称为滤光器,手电筒覆盖的照明区域被称为感受野。

过滤器以一定的步幅值向右移动,直到解析完整的宽度。然后,它以相同的步幅值向下滑动或卷积到图像的开头(左侧),并重复该过程,直到遍历整个图像。

在图像具有多个通道(例如,RGB)的情况下,内核具有与输入图像相同的深度。将所有结果与偏差相加,以产生挤压的单深度通道卷积特征输出。

该操作有两种结果:

  • **有效填充:**与输入相比,卷积特征的维数减少。

  • **相同填充:**维度要么增加,要么保持不变。

从第二卷积层向前,输入是从第一层产生的激活图。因此,输入的每一层基本上都描述了原始图像中某些低级特征出现的位置。现在,当我们在其上应用一组过滤器时(通过第二个卷积层),输出将是代表更高级特征的激活。当我们通过网络和更多的卷积层时,我们会得到代表越来越复杂功能的激活图。随着我们深入网络,过滤器开始有越来越大的感受域,这意味着它们能够考虑来自原始输入量的更大区域的信息。(换句话说,它们对更大的像素空间区域更敏感。)

Note

滤镜的深度必须与图像的深度相同。

上采样层

上采样层是一个简单的层,没有权重,会使输入的维度加倍。在传统卷积层之后,它可以用于生成模型。

DepthwiseConv2D

深度方向可分离卷积仅执行深度方向空间卷积的第一步(分别作用于每个输入通道)。

汇集层

汇集是一个基于样本的离散化过程。目标是对输入表示(图像、隐藏层输出矩阵等)进行下采样。),因此减少了它的维数,并允许对装仓的子区域中包含的特征进行假设。

与卷积层类似,汇集层负责减小卷积要素的空间大小。它也称为缩减像素采样层。这是为了降低处理数据所需的计算能力。通过降维,参数或权重的数量减少了 75%,从而降低了计算成本。此外,它对于提取旋转和位置不变的主要特征是有用的,因此保持了有效训练模型的过程。

池化的工作方式是在特征图上放置一个较小的矩阵,并在该框中选取最大值。矩阵在整个特征图中从左到右移动,每次选取某个值。然后这些值形成一个新的矩阵,称为汇集特征图。

共有三种类型的池:

  • Max pooling: 返回内核覆盖的图像部分的最大值。

  • 最大池也作为噪音抑制剂。它完全丢弃有噪声的激活,并且在降维的同时执行去噪。最大池比平均池表现好得多。Max pooling 通过选择最大值同时减小图像的大小来保留主要特征。这有助于减少过拟合。

  • Min pooling: 选择批次的最小像素值。

  • Average pooling: 返回内核覆盖的图像部分的所有值的平均值。平均池简单地执行维数减少作为噪声抑制机制。

全连接层

添加全连接层是学习由卷积层的输出表示的高级特征的非线性组合的(通常)廉价方式。这一步由输入层、全连接层和输出层组成。完全连接层类似于人工神经网络中的隐藏层,但在这种情况下,它是完全连接的。输出层是我们获得预测类的地方。信息通过网络传递,计算预测误差。然后,误差通过系统反向传播,以改进预测。它计算类分数并输出大小等于类数量的一维数组。

现在您已经了解了 CNN 是如何工作的,您已经准备好将它应用到项目中了。

项目描述

在这个项目中,我们收集了一组彩色图像。这是我们的基准,以便我们知道我们希望我们的模型达到什么结果。然后我们将这些彩色图像转换成灰度。我们将灰度图像分成训练集和测试集。然后,我们将训练集图像及其相应的彩色图像输入到我们的模型(VGG-16 和 CNN 的组合)中,以“学习”图像是如何着色的。然后,为了测试这个模型,我们给它输入一个灰度图像。它自己给这些图像添加颜色。目标是让模型添加颜色,使其看起来令人信服,并尽可能接近原始图像。流程图见图 6-3 。

img/488562_1_En_6_Fig3_HTML.jpg

图 6-3

图像上色流程图

关于数据集

名称: Colornet

来源: www.floydhub.com/emilwallner/datasets/colornet

**创建者:**埃米尔·沃纳

重要术语

彩色空间

我们需要知道的第一件重要的事情是使用了 Lab 色彩空间。原因很简单:在 RGB 等色彩空间中,我们需要学习三个不同的通道,而在 Lab 中,我们只需要学习两个。通道 L 表示亮度,其值介于 0(暗)和 100(亮)之间。通道 a 和 b 分别是红绿和蓝黄范围之间的轴位置。

Lab 编码的图像有一个灰度层。然后,它将三个颜色层合二为一。这意味着我们可以在最终预测中使用原始灰度图像。那么,我们只有两个渠道可以预测。

使用 Lab 颜色空间的一个很好的原因是它可以保持光强度值的分离。黑白图片可以被视为 L 通道,模型在进行预测时不必学习如何保持正确的光强(如果使用 RGB,则必须这样做)。该模型将只学习如何给图像着色,让它专注于重要的事情。

该模型输出 AB 值,然后可以将其应用于黑白图像以获得彩色版本。

真彩色值从-128 到 128,这是 Lab 色彩空间中的默认间隔。将它们除以 128,它们也落在-1 比 1 的区间内。这使我们能够比较预测的误差。

在计算出最终误差后,网络更新滤波器以减少总误差。网络保持在这个循环中,直到误差尽可能低。1.0/255 表示我们使用的是 24 位 RGB 颜色空间。这意味着我们对每个颜色通道使用 0-255 个数字。这是颜色的标准尺寸,产生 1670 万种颜色组合。

图像上色

黑白图像可以用像素网格来表示。每个像素都有一个与其亮度相对应的值。值的范围是 0-255,其中 0 表示黑色,255 表示白色。单通道中的值 0 表示该层中没有颜色。如果所有颜色通道的值都为 0,则图像像素为黑色。

彩色图像由三层组成:

  • 红色层

  • 绿色层

  • 蓝色层

图层不仅决定颜色,还决定亮度。例如,为了获得白色,我们需要每种颜色的平均分布。因此,彩色图像使用这三层对颜色和对比度进行编码。

对于上色任务,网络需要找到将灰度图像与彩色图像联系起来的特征。我们正在寻找将灰度值网格与三种颜色网格联系起来的特征。

我们有一个输入的灰度层,我们想预测两个颜色层,Lab 中的 ab 。为了创建最终的彩色图像,我们将包括用于输入的 L/灰度图像,从而创建一个 Lab 图像。

为了将一层变成两层,我们使用卷积滤波器。每个滤镜决定了我们在图片中看到的内容。他们可以突出显示或删除某些内容,以从图片中提取信息。网络可以从一个过滤器创建一个新的图像,或者将几个过滤器组合成一个图像。

对于卷积神经网络,每个滤波器都会自动调整,以帮助实现预期的结果。我们将从堆叠数百个过滤器开始,然后将它们缩小到两层,即 ab 层。

  • 输入是代表黑白图像的网格。

  • 它输出两个带有颜色值的网格。

  • 在输入和输出值之间,我们创建过滤器将它们连接在一起,这是一个卷积神经网络。

当我们训练网络时,我们使用彩色图像。我们将 RGB 颜色转换到 Lab 颜色空间。黑白层是我们的输入,两个彩色层是输出。

我们有黑白输入,或过滤器,以及来自神经网络的预测。

我们将预测值映射到相同区间内的真实值。这样,我们可以比较值。间隔从-1 到 1。为了映射预测值,我们使用了一个tanh激活函数。对于我们给tanh函数的任何值,它将返回-1 到 1。

我们的神经网络发现了将灰度图像与其彩色版本联系起来的特征。

过程是这样的:

  1. 首先,我们寻找简单的图案:一条对角线,全黑像素,等等。

  2. 我们在每个方块中寻找相同的图案,并移除不匹配的像素。

  3. 如果我们再次扫描图像,我们会看到我们已经检测到的相同的小图案。为了更好地理解图像,我们将图像尺寸缩小了一半。

  4. 我们仍然只有一个 3×3 的过滤器来扫描每张图像。但是通过将新的 9 个像素与较低级别的过滤器相结合,我们可以检测到更复杂的图案。

  5. 一个像素组合可能形成一个半圆、一个小点或一条线。同样,我们从图像中反复提取相同的模式。这一次,我们生成 128 个新的过滤图像。

如前所述,我们从低级特征开始,比如边。离输出更近的图层组合成图案,再组合成细节,最终转化成人脸。

神经网络以试错的方式运行。它首先对每个像素进行随机预测。基于每个像素的误差,它通过网络反向工作以改进特征提取。

它开始针对产生最大误差的情况进行调整。在这种情况下,是要不要上色以及定位不同的对象。然后它把所有的物体都涂成棕色。它是与所有其他颜色最相似的颜色,因此产生的误差最小。

与其他视觉网络的主要区别在于像素位置的重要性。给网络着色时,图像大小或比例在整个网络中保持不变。在其他网络中,图像越接近最终图层就越失真。

分类网络中的最大池层增加了信息密度,但也扭曲了图像。它只重视信息,而不重视图像的布局。当给网络着色时,我们使用步长 2,将宽度和高度减半。这也增加了信息密度,但不会扭曲图像。

另外两个区别是对图层进行上采样和保持图像比例。分类网络只关心最终的分类。因此,当图像在网络中传输时,它们会不断降低图像的大小和质量。着色网络保持图像比例。这是通过添加白色填充来实现的。否则,每个卷积层都会切割图像。这是用*padding='same'*参数完成的。为了将图像的大小加倍,着色网络使用了上采样层。

填充本质上使得由滤波器核产生的特征图与原始图像具有相同的大小。这对于深度 CNN 非常有用,因为我们不希望输出减少,这样我们在网络的末端只剩下一个 2×2 的区域来预测我们的结果。

Note

人类只能感知 200-1000 万种颜色,所以用更大的颜色空间没有太大意义。

VGG-16

VGG-16 是一种卷积神经网络(CNN)架构,被认为是一种优秀的视觉模型架构(见图 6-4 )。该模型具有带 3x3 过滤器和跨度为 1 的卷积层。它始终使用相同的填充和最大池层(2x2 过滤器和步幅为 2 的)。在整个架构中,它始终遵循卷积层和最大池层的这种安排。最后,它有两个 fc(全连接层),后跟一个 Softmax 优化功能。这个网络是一个相当大的网络,它有大约 1.38 亿个参数。

该模型在 ImageNet 中取得了 92.7%的前五名测试准确率,ImageNet 是一个包含属于 1,000 个类别的超过 1,400 万张图像的数据集。它优于 AlexNet,因为它用多个 3×3 内核大小的滤波器一个接一个地替换了大内核大小的滤波器(第一和第二卷积层中分别为 11 和 5 个)。它在 ImageNet 数据库中的超过一百万张图像上进行了训练。该网络有 16 层,可以将图像分为 1000 种对象类别,如键盘、鼠标、铅笔和许多动物。

img/488562_1_En_6_Fig4_HTML.jpg

图 6-4

VGG-16 体系结构

MAPE 损失函数

平均绝对百分比误差(MAPE),也称为平均绝对百分比偏差(MAPD),是统计学中预测方法预测准确性的一种度量。

你现在应该对这个项目有了一个清晰的了解,并且已经学习了一些新的术语,所以让我们继续。

必需的库

对于这个项目,我们将使用您在本书第一章中安装的基本库。然而,我们还需要一些额外的库。此项目需要以下库:

  • 操作系统(内置 Python2 和更高版本)

  • NumPy(安装说明见第一章)

  • 熊猫(安装说明见第一章)

  • Matplotlib(安装说明见第一章)

  • TensorFlow(安装说明见第一章)

  • Keras(安装说明见第一章)

  • PIL(安装说明在本章中)

  • 数学(内置 Python2 和更高版本)

  • 随机(内置 Python2 和更高版本)

  • cv2(安装说明在本章中)

  • Scikit-Image(安装说明在本章中)

安装说明

在第一章,我们安装了每个项目所需的标准库。这些是这个特定项目中使用的附加库的安装说明。为了确保我们可以安装这些库而不用考虑我们的系统,我们将使用名为 PIP 的 Python 包。

安装 PIL

PIL 是 Python 图像库,最初由 Fredrik Lundh 及其贡献者开发。这个库可以用来处理图像。自 2009 年以来,PIL 没有任何发展。所以建议你用枕头代替。

Pillow 是 PIL 的一个分支,由 Alex Clark 和贡献者创建和维护。它以 PIL 电码为基础,然后演变成更好的 PIL 版本。它增加了对打开、操作和保存许多不同图像文件格式的支持。它的很多功能都和最初的 PIL 一样。

Note

截至本书出版时,根据官方网站,PIL 图书馆的状态如下:“当前的免费版本是 PIL 1.1.7。此版本支持 Python 1.5.2 和更新版本,包括 2.5 和 2.6。3 的版本。x 稍后会发布。”Python3 用户可以安装一个名为 Pillow 的分叉版本。

在终端中使用以下命令安装 Pillow。

Pip3 install Pillow

然后在终端中使用以下命令来检查安装。

Pip3 show pillow

PIL 故障排除
  • 或者,您也可以从官方网站 http://www.pythonware.com/products/pil/ 下载,手动安装 PIL。

  • 卸载任何过时版本的 PIL。

  • Pillow 及以后版本不支持import image命令。用from PIL import image代替。

  • 枕头 2.1.0 及以后版本不支持import _imaging。用from PIL.Image import core as _imaging代替。

安装 CV2

在终端中使用以下命令安装 CV2:

Pip3 install opencv

然后在终端中使用以下命令检查 CV2 的安装:

Pip3 show cv2

CV2 故障排除
  • opencv有四个不同的包,你应该只选择其中一个。不要在同一环境中安装多个不同的软件包。
安装 Scikit-Image

在终端中使用以下命令安装scikit-image

Pip3 install scikit-image

然后在终端中使用以下命令检查scikit-image的安装。

Pip3 show scikit-image

Scikit 故障排除-图像
  • 确保 PIP 已升级。

  • 确保您有一个可用的最新 C 编译器。

  • 如果在系统上直接安装时出现错误,请尝试使用虚拟环境。

  • 有时候skimage会给出一个假的错误。要禁用来自skimage的错误和警告,导出值为0False的环境变量SKIMAGE_TEST_STRICT_WARNINGS,并运行如下测试:

 export SKIMAGE_TEST_STRICT_WARNINGS=False
pytest --pyargs skimage

现在,您应该已经拥有了这个项目所需的所有库。让我们把重点放在我们想要在这个项目中使用的层的类型和每种类型的层的数量上。

CNN+VGG-16 架构

要使用 CNN+VGG-16 对灰度图像进行着色,我们需要首先加载 VGG-16 模型,这是 TensorFlow 2.0 环境的一部分。这省去了我们从头构建它的麻烦。然后,我们开始构建 CNN,并插入 VGG-16 作为 CNN 的第二层。CNN 的第一层对输入的图像进行整形,这样它们就可以很容易地输入到 VGG-16 中。从那里,输入继续通过 CNN。图 6-5 显示了我们将用于本项目的模型。

img/488562_1_En_6_Fig5_HTML.jpg

图 6-5

本项目 CNN+VGG-16 的架构

让我们来看看 CNN+VGG-16 的“蓝图”,更好地了解这两种模式是如何结合的。我们的模型将包括以下内容:

  • 输入层- 1

  • 密集层- 2

  • 向上采样 2D - 6

  • DepthwiseConv2D - 3

  • 激活层- 3

  • 辍学层- 3

  • 平均池 2D - 1

我们将使用一个 Keras 输入图层将数据重塑为三个通道:亮度(黑到白)、a(绿到红)和 b(蓝到黄)。密集层是完全连接的,并确保模型中使用了图像中的所有值。

Upsampling2D 层只是将输入的维度加倍。它在输出中重复行和列(由输入提供)。该层没有参数或模型权重,因为它不学习任何东西;它只是加倍了输入。默认情况下,UpSampling2D 将使每个输入维度加倍。此外,默认情况下,UpSampling2D 图层将使用最近邻算法来填充新的行和列。这对于我们的模型是理想的,这样有价值的图像数据在被模型处理时不会丢失。

DepthwiseConv2D 层执行深度方向可分离卷积,这只是深度方向空间卷积的第一步(它分别作用于每个输入通道)。

dropout 层会删除训练过程中获得的一些值,这对于避免过拟合模型至关重要。

AveragePooling2D 层通过将输入划分为矩形池区域并计算每个区域的平均值来执行下采样。

对于激活函数,由于其梯度的不饱和性,我们将使用 ReLU,这大大加快了随机梯度下降的收敛速度。

对于损失函数,我们将使用 MAPE,如“重要术语”一节所述。由于其尺度无关性和可解释性的优势,它可以帮助我们确定我们的模型工作得有多好。

Adam 优化器是大多数项目使用的标准优化器,包括这个项目。

我们已经规划好了我们的架构。剩下的就是实现模型了。

程序

按照以下步骤构建这个项目。

第一步。导入库

我们通过导入必要的库来开始这个项目。

# Importing Libraries
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.keras.layers import Dense, Dropout, Input, InputLayer, Conv2D,UpSampling2D,DepthwiseConv2D
from tensorflow.keras.layers import Flatten,MaxPooling2D,Conv2DTranspose, AveragePooling2D
from tensorflow.keras.applications.vgg16  import VGG16
from tensorflow.keras.models import Model,Sequential
from tensorflow.keras.optimizers import Adam
from tensorflow.keras import layers
from tensorflow.keras.preprocessing.image import img_to_array,load_img
from PIL import Image
from tensorflow.keras.utils import plot_model
from math import ceil
import random
import cv2
from skimage import io, color

设置文件路径,以便 Jupyter 笔记本可以访问数据集。使用os.path命令并输入文件路径。

os.path=("Macintosh HD/Users/vinitasilaparasetty□/Downloads/")

第二步。将图像转换为灰度

然后我们将所有的图像转换成灰度,这样我们就有了一组彩色图像和它们相应的灰度版本。

  • random.sample()是 Python 中random模块的内置函数,返回从序列中选择的特定长度的项目列表。它用于无替换的随机抽样。

  • cv2.cvtColor(rgb, cv2.COLOR_BGR2LAB)是 CV2 中的一个函数,用于将颜色空间从 RGB 转换到 LAB。

  • cv2.split(lab_image)将图像分割成三个通道。

Note

cv2.split()使用更多的计算时间。NumPy 索引是一个很好的选择,但是结果可能不太准确。

rootdir = os.getcwd()
filenames = random.sample(os.listdir('D:\\Proj\\vinita\\colornet\\'), 500)
lspace=[]
abspace=[]
for file in filenames:
   rgb = io.imread(file)
   lab_image = cv2.cvtColor(rgb, cv2.COLOR_BGR2LAB) #convert colors space from RGB to LAB
   l_channel,a_channel,b_channel = cv2.split(lab_image)
   lspace.append(l_channel)
   replot_lab=np.zeros((256, 256, 2))
   replot_lab[:,:,0] = a_channel
   replot_lab[:,:,1] = b_channel
   abspace.append(replot_lab)
   transfer = cv2.merge([l_channel, a_channel, b_channel])
   transfer = cv2.cvtColor(transfer.astype("uint8"), cv2.COLOR_LAB2BGR)

lspace=np.asarray(lspace) #convert to array
abspace=np.asarray(abspace) #convert to array

第三步。加载数据

lspace加载为X,将abspace加载为Ylspace表示图像的亮度,abspaceab通道的组合值,它们位于红绿和蓝黄范围之间的轴上。

X = np.load("lspace100.npy")
Y = np.load("abspace100.npy")

第四步。构建模型

我们准备创建 CNN+VGG-16。使用下面的代码。

model6 = VGG16(weights='imagenet',include_top=False,input_shape=(256, 256, 3))
model = Sequential()
model.add(InputLayer(input_shape=(X.shape[1], X.shape[2], 1)))
model.add(layers.Dense(units=3))
model.add(Model(inputs=model6.inputs, outputs=model6.layers[-10].output))
model.add(UpSampling2D((2, 2)))
model.add(UpSampling2D((2, 2)))
model.add(DepthwiseConv2D(32, (2, 2), activation="tanh", padding="same"))
model.add(UpSampling2D((2, 2)))
model.add(DepthwiseConv2D(32, (2, 2), activation="tanh", padding="same"))
model.add(layers.ReLU(0.3))
model.add(layers.Dropout(0.4))
model.add(UpSampling2D((2, 2)))
model.add(UpSampling2D((2, 2)))
model.add(DepthwiseConv2D(2, (2, 2), activation="tanh", padding="same"))
model.add(layers.ReLU(0.3))
model.add(layers.Dropout(0.2))
model.add(UpSampling2D((2, 2)))
model.add(layers.ReLU(0.3))
model.add(layers.Dropout(0.2))
model.add(AveragePooling2D(pool_size = (2, 2)))
model.add(layers.Dense(units=2))
print(model.summary())

第五步。设置模型参数

为了完成 CNN 的架构,我们通过创建一个函数来设置优化器和损失函数,如下所示:

def adam_optimizer():
   return Adam(lr=0.001, beta_1=0.99, beta_2=0.999)
model.compile(loss='mape', optimizer=adam_optimizer())

第六步。数据准备

在将数据输入模型之前,我们需要对其进行整形,以便使用以下代码将值输入适当的通道:

X=((X.reshape(X.shape[0],X.shape[1],X.shape[2],1)))
X=(X-255)/255
Y=(Y-255)/255

trainsize= ceil(0.8 * X.shape[0])
testsize= ceil(0.2 * X.shape[0])+1

train_inp=X[:trainsize,]
test_inp=X[testsize:,]

train_out=Y[:trainsize,]
test_out=Y[testsize:,]

第七步。训练模型

我们现在可以训练模型:

model.fit(x=train_inp, y=train_out, batch_size=10, epochs=5)

第八步。获得预测

现在我们的模型已经训练好了,它已经学会了如何给图像添加颜色,使它看起来尽可能自然。让我们通过使用以下代码将新的灰度图像输入模型来检查结果:

train_pred = model.predict(train_inp)
test_pred = model.predict(test_inp)

train_random=random.randint(1,trainsize)
test_random=random.randint(1,testsize)

check=np.interp(train_pred, (train_pred.min(), train_pred.max()), (0,255))
check1=np.interp(test_pred, (test_pred.min(), test_pred.max()), (0,255))

l_channel=test_inp[20]*255
a_channel=check1[20,:,:,0]
b_channel=check1[20,:,:,1]

transfer = cv2.merge([l_channel, a_channel, b_channel])
transfer = cv2.cvtColor(transfer.astype("uint8"), cv2.COLOR_LAB2BGR)

第九步。查看结果

让我们来看看我们的图像着色模型的结果。

plt.imshow(transfer)

需要考虑的几点:

  • 我们在这个项目中使用的数据集只有彩色图像。我们需要将图像转换为灰度,以便得到一组彩色图像和一组灰度图像。

  • 大多数在线数据集只包含彩色图像,因此我们必须手动将图像转换为灰度,然后才能使用它们来训练模型。

  • 因为大多数训练数据都非常相似,所以网络很难区分不同的对象。它会调整不同的棕色调,但无法生成更细微的颜色。

解决纷争

以下是您可能会遇到的一些常见错误,这些错误很容易修复:

  • 如果 PIL 安装给您带来了麻烦,请尝试卸载它,然后使用以下命令进行升级:

  • VGG 模型相当大,需要相当大的内存,所以使用有大量内存的系统或者使用云。

  • 如果显示此警告,您可以忽略它,因为它不会影响程序:

pip uninstall PIL
pip install PIL —upgrade

WARNING:tensorflow:From /usr/local/lib/python3.6
/dist-packages/tensorflow/python/framework
/op_def_library.py:263: colocate_with
(from tensorflow.python.framework.ops) is
deprecated and will be removed in a future version.
Instructions for updating: Collocations handled
automatically by placer.

进一步测试

这里有一些想法可以尝试,并从这个项目中学到更多:

  • 尝试单独使用 VGG-16,看看结果有什么不同。

  • 试着只用彩色图像训练模型,看看模型表现如何。

  • 尝试其他色彩空间,看看结果如何变化。

  • 尝试最小/最大池,而不是平均池。

  • 移除上采样层,看看它如何影响结果。

  • 移除脱落层,查看结果有何不同。

摘要

下面是你在本章中学到的所有内容的快速回顾。

  • 计算机将物体以数字的形式形象化。每幅图像都可以用一组二维数字来表示,称为像素。

  • CNN 可以通过应用相关的过滤器成功地捕捉图像中的空间和时间依赖性。

  • 滤镜的深度必须与图像的深度相同。

  • 有三种类型的池:最小池,最大池,平均池。

  • 对于 RGB,我们需要学习三个不同的通道,而在实验室中,我们只需要学习两个。

  • Lab 颜色空间保持光强度值分开。真彩色值从-128 到 128,这是 Lab 色彩空间中的默认间隔。将它们除以 128,它们也落在-1 比 1 的区间内。

  • 通道 L 表示亮度,其值介于 0(暗)和 100(亮)之间。

  • 像素值范围为 0 - 255,其中 0 表示黑色,255 表示白色。

  • 平均绝对百分比误差(MAPE),也称为平均绝对百分比偏差(MAPD),是统计学中预测方法预测准确性的一种度量。

参考

本章中使用的参考资料如下:

进一步阅读

有兴趣了解本章中涉及的一些主题吗?这里有一些很棒的链接可以查看:

七、图像去模糊

在前一章中,我们讨论了图像着色,这是使用 Photoshop 等工具完成的。现在让我们来谈谈 Photoshop 通常用于的另一项任务,但我们可以使用神经网络来自动化这项任务。在这一章中,我们将讨论图像去模糊。我们将在这个项目中使用一个与 VGG-16 结合的生成性对抗网络(GAN)。首先,我们将了解什么是 GAN 以及它是如何工作的。然后,我们将仔细看看什么是图像去模糊。

什么是甘?

生成对抗网络(GANs)是一类强大的神经网络,与无监督学习一起使用。gan 由两个相互竞争的神经网络模型组成,能够分析、捕获和复制数据集中的变化。

GANs 由两种成分组合而成。一个称为生成器,它生成新的数据实例,而另一个称为鉴别器,评估实例的真实性。换句话说,鉴别器决定它检查的每个数据实例是否属于实际的训练数据集。

即使在原始数据中引入少量噪声,主流神经网络也经常会对事物进行错误分类。这是因为大多数模型从有限的数据中学习,这使它们容易过拟合。此外,输入和输出之间的映射几乎是线性的。各种类别之间的分离边界似乎是线性的,但实际上,它们是由线性组成的,即使特征空间中某个点的微小变化也可能导致数据错误分类。

gan 的类型

一些常用的方法如下:

  • **香草甘:**这是最简单的一种甘。这里,生成器和鉴别器是简单的多层感知器。算法真的很简单;它试图使用随机梯度下降来优化数学方程。

  • **条件 GAN(CGAN):**CGAN 可以描述为一种深度学习方法,其中一些条件参数被放置到位。将一个附加参数 y 添加到生成器中,以生成相应的数据。标签也被用作鉴别器的输入,以帮助区分真实数据和伪造生成的数据。

  • **深度卷积 GAN(DCGAN):**DCGAN 是 GAN 最成功的实现之一。它由神经网络代替多层感知器组成。ConvNets 的实现没有最大池。它们被卷积步幅所取代。此外,这些层没有完全连接。

  • 拉普拉斯金字塔 GAN (LAPGAN): 拉普拉斯金字塔是一种线性可逆图像表示,由一组带通图像组成,间隔一个倍频程,加上一个低频残差。这种方法使用多个生成器和鉴别器网络以及不同级别的拉普拉斯金字塔。主要使用这种方法是因为它可以产生非常高质量的图像。在金字塔的每一层对图像进行下采样,然后在每一层反向放大,图像从条件 GAN 获取一些噪声,直到达到其原始大小。

  • **超分辨率 GAN(SRGAN):**SRGAN 是一种设计 GAN 的方法,其中深度神经网络与对抗网络一起使用,以产生更高分辨率的图像。它有助于优化放大原始低分辨率图像以增强其细节,同时最大限度地减少误差。

gan 的应用包括:

  • 生成图像数据集的示例

  • 生成人脸照片

  • 生成逼真的照片

  • 生成卡通人物

  • 图像到图像的翻译

  • 文本到图像的翻译

  • 语义图像到照片的翻译

  • 人脸正面视图生成

现在,您应该了解了什么是 GAN,以及它在现实生活中的应用。让我们看看 GAN 内部的过程。

GAN 是如何工作的

gan 可分为两个主要部分,发生器(第一部分)和鉴别器(第二部分)(见图 7-1 )。

img/488562_1_En_7_Fig1_HTML.jpg

图 7-1

氮化镓的组成部分

生成模型

生成模型根据概率模型描述了数据是如何生成的。它捕捉数据的分布,并以这样一种方式进行训练,即它试图最大化鉴别器出错的概率。

生成器模型将固定长度的随机向量作为输入,并在域中生成样本。该向量从高斯分布中随机抽取,并且该向量用于生成过程的种子。在训练之后,这个多维向量空间中的点将对应于问题域中的点,形成数据分布的压缩表示。

这个向量空间被称为潜在空间,或者由潜在变量组成的向量空间。潜在变量,或隐藏变量,是那些对一个领域很重要但不能直接观察到的变量。

在 GANs 的情况下,生成器模型将意义应用于所选潜在空间中的点,使得从潜在空间中提取的新点可以被提供给生成器模型作为输入,并用于生成新的不同的输出示例。在训练之后,生成器模型被保留并用于生成新的样本。见图 7-2 。

img/488562_1_En_7_Fig2_HTML.jpg

图 7-2

发电机过程的流程图

发电机内的过程

鉴别器空闲时,发电机被训练。G(z)给出了与实际输入相同的形状。例如,如果 10*10 的图像是真实输入,那么 G(z)产生相同的形状,但是我们希望我们的生成器最大化伪数据,并使用最大化真实数据同时最小化伪数据的鉴别器。

  • max v(di)= en ~ rdata(n)[logd(n)]+ey ~ gy(y)[log(1-d(g(z)]]

    G 损失 = log (1-Di(G(y)))

其中:

G 损耗 =发电机损耗

Di =鉴别器

G(y) =发电机的输出

y =噪声矢量

步骤 1: 发电机 Ge 根据其噪声计算损耗,作为 Ge 损耗。

**第二步:**反向传播

  • V (Di,Ge)= En ~ Rdata(n)【logD(n)】+ Ey ~ G(y)【log(1-Di(G(y)))】

其中:

Ge =发电机

Di =鉴别器

n =来自 Rdata(n)的训练样本

D(n) =鉴频器的输出

G(y) =发电机的输出

y =噪声矢量

一旦我们得到这两个损耗,我们计算关于它们的参数的梯度,并通过它们的网络独立地反向传播,以从损耗中学习(调整关于损耗的参数)。)

这种方法重复几个时期,然后手动检查准确性。如果看起来可以接受,那么就停止训练;否则,它将被允许继续运行几个纪元。

鉴别器模型

鉴别器是一个正常的分类模型。鉴别器的目标是估计接收到的样本来自训练数据而不是来自生成器的概率。鉴别器模型从域中取一个例子作为输入(真实的或生成的),并预测一个真实或虚假的二元类标签(生成的样本)。见图 7-3 。

真实的例子来自训练数据集。生成的示例由生成器模型输出。在训练过程之后,鉴别器模型被丢弃,因为我们对生成器感兴趣。

img/488562_1_En_7_Fig3_HTML.jpg

图 7-3

鉴别器过程的流程图

Note

发生器和鉴别器都经过各自的反馈回路。

鉴别者试图使其报酬 V(Di,Ge)最小化,而生产者试图使其损失最大化。

鉴别器内的过程

鉴别器在发电机空闲时进行训练。在此阶段,网络仅向前传播,不进行向后传播。在这个阶段,鉴别器在假生成的数据以及真实数据上被训练,以查看它是否能够正确地预测它们。

在鉴别器被生成器生成的假数据训练后,我们可以得到它的预测,并使用结果来训练生成器。这导致了一个比前一个状态更好的结果,所以我们可以尝试欺骗鉴别器。它以介于0(代表真实)和1(代表虚假)之间的数字的形式返回概率。正如我们在下面的公式中看到的,D(x)和 D(G(z))给出了一个介于 0 和 1 之间的分数:

  • Min V Ge Ey ~ py(y)【log(1-Di(Ge(y)))】

  • DL =日志(di(n)) =日志(1-D(G)))

  • DL = DL 实数 + DL = >日志(D(n)) +日志(1-D(G(y)))

其中:

Di =鉴别器

n =来自 Rdata(n)的训练样本

D(n) =鉴频器的输出

G(y) =发电机的输出

y =噪声矢量

DL 真实 =真实样本的损失

DL fake =丢失虚假/生成的数据

步骤 1: 我们从随机分布中取出一些噪声,并将其馈送给 Ge 生成器,以产生伪 n(标签 y=0) → (n,y)输入-标签对。

**第二步:**我们取这个假对和真对 n(标号 y =1)交替馈给 Di 鉴别器。

**第三步:*Di 鉴别器是一个二元分类神经网络,所以它运行两次。*它计算假 n 和真 n 的损失,并将它们合并为最终损失,Di 损失。

训练 GAN 的技巧:

  • 训练鉴别器时,保持发生器值不变。训练发生器时,保持鉴别器不变。每个人都应该针对静态的对手进行训练。这使得生成器能够更好地读取它必须学习的梯度。

  • GAN 的每一边都可以压倒另一边。如果鉴别器太好,它将返回非常接近01的值,发生器将难以读取梯度。如果生成器太好,它将持续利用鉴别器中的弱点,导致假阴性。这可以通过母语英语教师各自的学习速度来缓解。

项目描述

在这个项目中,我们将训练一个 GAN 接受模糊的照片作为输入,消除模糊,并返回清晰的,消除障碍的图像作为输出。甘需要很长时间来训练。因此,在这个项目中,我们将我们的定制 GAN 与预训练的 VGG-16 模型相结合。这将节省计算资源和时间,并给我们更好的结果。该过程如图 7-4 所示。

img/488562_1_En_7_Fig4_HTML.jpg

图 7-4

图像去模糊流程图

关于数据集

名称: CERTH 图像模糊数据集

内容:

训练集:

  • 630 张无失真图像(原件)

  • 220 张自然模糊的图像(在拍照时模糊)

  • 150 张人工模糊图像(使用软件模糊)

由“自然模糊”集和“人工模糊”集组成的评估集。

自然模糊集:

  • 589 个不失真的图像

  • 411 自然模糊的图像

人造模糊集:

  • 30 幅不失真的图像

  • 450 张人工模糊的图像

来源: https://mklab.iti.gr/results/certh-image-blur-dataset/

创建者: E. Mavridaki 和 V. Mezaris

重要术语和概念

图像去模糊

图像去模糊是去除图像模糊的过程。模糊通常是由散焦像差、运动模糊和高斯模糊等引起的。

当一个像素的值受到相邻像素的影响时,就会出现图像模糊。

散焦

散焦是一种模糊,因为主要元素不仅出现在活动的像素上,还出现在相邻像素上。这可能是由于焦距调整不当或缺少对焦元件造成的。

运动模糊

运动模糊也是一种模糊,因为相同的信号与物体一样落在不同的接收器单元上,或者物体正在运动。

目标是从模糊的图像中恢复清晰的图像。

盘旋

在数学上,这个过程表示如下:

  • BI = SI*BK

其中 BI 是模糊的输入图像。我们需要找到清晰的图像(SI)和模糊的内核(BK)。当我们把它们相乘时,叫做卷积。我们说 SI 与 BK 卷积生成模糊图像(BI ),其中 BK 是模糊。

模糊(BK)通常被建模为点扩展函数,并与假设的清晰图像(SI)卷积以获得模糊图像(BI),其中清晰图像(SI)和点扩展函数(BK)都是未知的。

这是一个逆问题的例子。在几乎所有的情况下,模糊图像中没有足够的信息来唯一地确定似乎合理的原始图像,这使得它成为一个不适定的问题。此外,模糊图像包含额外的噪声,这使得确定原始图像的任务变得复杂。这通常通过使用正则化项来试图消除不合理的解决方案来解决。

反褶积

图像去模糊问题可以分成两个不同的问题:

  • **盲解卷积:**这包括恢复点扩散函数(PSF)。在多图像 PSF 估计方法中,通过图像序列跟踪对象,或者通过使用多个模糊图像或一个模糊噪声图像对,问题在数学上被约束为变得越来越不病态。在单幅图像的点扩散函数估计中,物体的模糊边缘代表了局部运动信息的来源。在全局水平上,将整个图像的梯度与已知的一般估计进行比较可以帮助推断 PSF。

  • **非盲解卷积:**这包括使用已知的 PSF 恢复初始估计。非盲解卷积方法解决了最小化附加噪声在用已知 PSF 去模糊中的巨大影响、消除源自近似 PSF 估计的伪像以及截断改变的图像中的数据的问题。

    注意un bur 是不正确的技术术语。去模糊是正确的技术术语。

GAN 架构

cov1层的输入是固定大小的 224x224 RGB 图像。图像通过一堆卷积( conv )层,在这里使用的过滤器具有 3×3 的非常小的感受野(这是捕捉左/右、上/下和中心概念所需的最小尺寸)。

在其中一种配置中,它还利用 1×1 卷积滤波器,可视为输入通道的线性变换(后跟非线性)。卷积步距固定为一个像素;conv 图层输入的空间填充使得空间分辨率在卷积后保持不变(即,3×3 conv 图层的填充为一个像素)。

空间池由五个最大池图层执行,这五个图层位于一些 conv 图层之后(并非所有 conv 图层都遵循最大池)。最大池化在 2×2 像素窗口上执行,步长为 2。

三个全连接(FC)层跟随一个卷积层堆栈(在不同的架构中具有不同的深度)。前两层各有 4096 个通道,第三层执行 1000 路 ILSVRC 分类,因此包含 1000 个通道(每个类别一个通道)。

最后一层是 Softmax 层。全连接层的配置在所有网络中都是相同的。所有隐藏层都配备了整流(ReLU)非线性。

必需的库

对于这个项目,我们将使用您在本书第一章中安装的基本库。以下是该项目所需的所有库的列表:

  • NumPy(安装说明见第一章)

  • 操作系统(内置 Python 2 及更高版本)

  • 熊猫(安装说明见第一章)

  • Matplotlib(安装说明见第一章)

  • Keras(安装说明见第一章)

  • TensorFlow(安装说明见第一章)

  • PIL(安装说明见第六章)

  • 随机(内置 Python 2 及更高版本)

  • 数学(内置 Python 2 及更高版本)

看起来我们已经拥有了这个项目所需要的所有库。让我们把重点放在我们想要在这个项目中使用的层的类型和每种类型的层的数量上。

GAN 架构

让我们来看看 GAN 的“蓝图”。我们的模型将由以下部分组成,如图 7-5 所示。

发电机

  • 卷积 2D 层:4

  • 脱落层:2

  • 上采样 2D : 1

  • 致密层:1

  • 激活层:2

  • 激活功能:泄漏 ReLU

  • 损失函数:二元交叉熵

鉴别器

  • 卷积 2D 层:4

  • 卷积 2D 转置:1

  • 最大池 2D: 1

  • 脱落层:3

  • 致密层:4

  • 激活层:3

  • 激活功能:泄漏 ReLU

  • 损失函数:二元交叉熵

img/488562_1_En_7_Fig5_HTML.jpg

图 7-5

GAN 模型

程序

本节概述了用于构建这个项目的步骤和代码。

第一步。导入库

通过导入必要的库来开始项目。

import numpy as np
import os
import pandas as pd
import matplotlib.pyplot as plt
import keras
import tensorflow as tf
from tensorflow.keras.layers import Dense, Dropout, Input, InputLayer, Conv2D,UpSampling2D , Flatten,MaxPooling2D,Conv2DTranspose
from tensorflow.keras.models import Model,Sequential
from tensorflow.keras.optimizers import Adam
from tensorflow.keras import layers
from PIL import Image
import random
from math import ceil

设置文件路径,以便 Jupyter 笔记本可以访问数据集。使用os.chdir命令并输入文件路径。

os.chdir('CERTH_ImageBlurDataset') #enter file path of dataset
os.curdir   #enter dataset directory

现在,我们将查看数据集的内容:

os.listdir()    #list contents of current directory

以下是输出:

 ['Artificially-Blurred', 'Naturally-Blurred', 'Undistorted']

从输出中,您可以看到数据集中有三个文件夹。

Note

为了简单起见,我们将人工模糊和自然模糊的图像称为“假的”,而未失真的图像称为“真实的”

第二步。数据集准备

使用init_size=100一次取 100 个文件。然后使用folders=os.listdir()开始浏览数据集的内容。

init_size=100
folders=os.listdir()

然后声明将用于对数据集排序的变量

filelist=[]#Keeps track of files.
fake_data=[]#stores the distorted images.
real_data=[]#stores the undistorted image.

创建for循环来挑选假图像。im变量一个接一个地打开每个图像。然后,为了方便和统一,图像被调整为 50x50。请记住,模型不能直接处理图像。因此,您需要将图像转换为数组。现在,使用缩小滤波器进一步简化阵列。然后将数组添加到fake_data,如下所示。

for i in folders[0:3]:
   files=os.listdir(i)
   for j in files:
       im = Image.open(i+'\\'+j) # opening each image
       width = 50   #setting width of image
       height = 50 #setting height of image
       im5 = im.resize((width, height), Image.ANTIALIAS)   #resizing image
       x=np.asarray(im5)  #convert image to array
       x =(x-x.mean())/255.0# best down-sizing filter
       fake_data.append(x)

创建for循环来整理真实图像。这个过程类似于前面的代码块,除了这一次,您使用的是“真实的”图像。

for i in folders[2:]:
   files1=os.listdir(i)
   for j in files1:
       im = Image.open(i+'\\'+j)
       width = 50
       height = 50
       im5 = im.resize((width, height), Image.LANCZOS)
       x=np.asarray(im5)
       x =(x-x.mean())/255.0# best down-sizing filter
       real_data.append(x)

第三步。探索性数据分析

让我们先来看看“假”图像。通过设置interpolation='nearest',如果显示分辨率与图像分辨率不同(这是最常见的情况),它只显示图像,而不尝试在像素之间进行插值。这将导致图像中的像素显示为多个像素的正方形。

fake_data= np.asarray(fake_data)     #convert image to array
plt.imshow(fake_data[70], interpolation="nearest")

对于输出,您将得到第 70 个位置的图像,如图 7-6 所示。

Note

图 7-6 和 7-7 可能与您在运行项目时看到的不同。不要担心;这只是由于各种因素,如你正在工作的系统。

img/488562_1_En_7_Fig6_HTML.jpg

图 7-6

第 70 个位置的假图像

现在,您可以查看“真实”图像:

real_data= np.asarray(real_data)   #convert image to array
plt.imshow(real_data[50], interpolation="nearest")

对于输出,您将获得“真实”图像,它位于第 50 个位置,如图 7-7 所示。

img/488562_1_En_7_Fig7_HTML.jpg

图 7-7

第 50 个位置的真实图像

第四步。构建模型

接下来,为 Adam 优化器定义一个带有参数的函数。学习率(lr)beta_1beta_2都设置为默认值。记住beta_1beta_2必须在01之间。

Note

因为我们没有将amsgrad设置为FALSE,所以我们使用 Adam 优化器的 AMSGrad 变体。

def adam_optimizer():
   return Adam(lr=0.001, beta_1=0.9, beta_2=0.999)

使用create_generator()功能构建 GAN 的发生器。

def create_generator():
   generator=tf.keras.models.Sequential()
   generator.add(InputLayer(input_shape=(50,100,100)))

   generator.add(Conv2D(32, (2, 2), activation="tanh", padding="same", strides=2))
   generator.add(layers.LeakyReLU(0.6))
   generator.add(layers.Dropout(0.4))

   generator.add(Conv2D(32, (2, 2), activation="tanh", padding="same"))
   generator.add(layers.LeakyReLU(0.3))
   generator.add(layers.Dropout(0.2))

   generator.add(Conv2D(32, (3, 3), activation="tanh", padding="same"))
   generator.add(UpSampling2D((2, 1)))
   generator.add(Conv2D(3, (5, 5), activation="tanh", padding="same"))

   generator.add(layers.Dense(units=3, activation="tanh"))
   generator.compile(loss='binary_crossentropy', optimizer=adam_optimizer())
   return generator

创建生成器并查看摘要,以确保所有层都已定义。

g=create_generator()
g.summary()

以下是输出结果:

_______________________________________________________________
Layer (type)                 Output Shape              Param #
===============================================================
conv2d_8 (Conv2D)            (None, 25, 50, 32)        12832
_______________________________________________________________
leaky_re_lu_5 (LeakyReLU)    (None, 25, 50, 32)        0
_______________________________________________________________
dropout_5 (Dropout)          (None, 25, 50, 32)        0
_______________________________________________________________
conv2d_9 (Conv2D)            (None, 25, 50, 32)        4128
_______________________________________________________________
leaky_re_lu_6 (LeakyReLU)    (None, 25, 50, 32)        0
_______________________________________________________________
dropout_6 (Dropout)          (None, 25, 50, 32)        0
_______________________________________________________________
conv2d_10 (Conv2D)           (None, 25, 50, 32)        9248
_______________________________________________________________
up_sampling2d_1 (UpSampling2 (None, 50, 50, 32)        0
_______________________________________________________________
conv2d_11 (Conv2D)           (None, 50, 50, 3)         2403
_______________________________________________________________
dense_4 (Dense)              (None, 50, 50, 3)         12
===============================================================
Total params: 28,623
Trainable params: 28,623
Non-trainable params: 0
_______________________________________________________________

生成器的摘要应该是这样的。

接下来,使用create_discriminator()功能构建 GAN 的鉴频器。

def create_discriminator():
   discriminator=tf.keras.models.Sequential()
   discriminator.add(InputLayer(input_shape=(50,50,3)))

   discriminator.add(Conv2D(10, (2, 2), activation="tanh", padding="same", strides=2))
   discriminator.add(layers.Dense(units=100))
   discriminator.add(layers.LeakyReLU(0.2))
   discriminator.add(layers.Dropout(0.3))

   discriminator.add(Conv2D(10, (3, 3), activation="tanh", padding="same", strides=2))
   discriminator.add(layers.Dense(units=50))
   discriminator.add(layers.LeakyReLU(0.2))
   discriminator.add(layers.Dropout(0.3))

   discriminator.add(Conv2D(10, (3, 3), activation="tanh", padding="same", strides=2))
   discriminator.add(layers.Dense(units=25))
   discriminator.add(layers.LeakyReLU(0.2))
   discriminator.add(layers.Dropout(0.3))

   discriminator.add(Conv2D(1, (4, 4), activation="sigmoid", padding="same", strides=3))
   #discriminator.add(Conv2D(1, (4, 4), activation="tanh", padding="same"))
   discriminator.add(MaxPooling2D(pool_size = (2, 3)))
   #discriminator.add(Conv2DTranspose(1, (2,2), strides=(2,2)))
   discriminator.add(Flatten())
   discriminator.compile(loss='binary_crossentropy', optimizer=adam_optimizer())
   return discriminator

创建鉴别器并查看摘要,以确保所有层都已定义。

d =create_discriminator()
d.summary()

以下是输出结果:

_______________________________________________________________
Layer (type)                 Output Shape              Param #
===============================================================
conv2d_12 (Conv2D)           (None, 25, 25, 10)        130
_______________________________________________________________
dense_5 (Dense)              (None, 25, 25, 100)       1100
_______________________________________________________________
leaky_re_lu_7 (LeakyReLU)    (None, 25, 25, 100)       0
_______________________________________________________________
dropout_7 (Dropout)          (None, 25, 25, 100)       0
_______________________________________________________________
conv2d_13 (Conv2D)           (None, 13, 13, 10)        9010
_______________________________________________________________
dense_6 (Dense)              (None, 13, 13, 50)        550
_______________________________________________________________
leaky_re_lu_8 (LeakyReLU)    (None, 13, 13, 50)        0
_______________________________________________________________
dropout_8 (Dropout)          (None, 13, 13, 50)        0
_______________________________________________________________
conv2d_14 (Conv2D)           (None, 7, 7, 10)          4510
_______________________________________________________________
dense_7 (Dense)              (None, 7, 7, 25)          275
_______________________________________________________________
leaky_re_lu_9 (LeakyReLU)    (None, 7, 7, 25)          0
_______________________________________________________________
dropout_9 (Dropout)          (None, 7, 7, 25)          0
_______________________________________________________________
conv2d_15 (Conv2D)           (None, 3, 3, 1)           401
_______________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 1, 1, 1)           0
_______________________________________________________________
flatten_1 (Flatten)          (None, 1)                 0
===============================================================
Total params: 15,976
Trainable params: 15,976
Non-trainable params: 0
_______________________________________________________________

鉴别器的摘要应该是这样的。

现在使用create_gan(discriminator, generator)功能组合 VGG-16。

def create_gan(discriminator, generator):
   d.trainable=False     #This enables us to treat the model as a combination of our custom GAN and the VGG16
   gan_input = Input(shape=(None,100,100))   #set the input shape.
   x = g(gan_input)
   gan_output= d(x)
   gan= Model(inputs=gan_input, outputs=gan_output)
   gan.compile(loss='binary_crossentropy', optimizer="adam")
   return gan

使用create_gan(d,g)创建 GAN 并查看摘要,以确保所有参数均已定义。

gan = create_gan(d,g)
gan.summary()  #view the structure to ensure it is correct.

以下是输出:

_______________________________________________________________
Layer (type)                 Output Shape              Param #
===============================================================
input_7 (InputLayer)         (None, None, 100, 100)    0
_______________________________________________________________
sequential_2 (Sequential)    multiple                  28623
_______________________________________________________________
sequential_3 (Sequential)    multiple                  15976
===============================================================
Total params: 44,599
Trainable params: 28,623
Non-trainable params: 15,976
_______________________________________________________________
0
1

GAN 的摘要应该是这样的。

第五步。输入准备

这里需要将i计数器初始化为0,将名为epoch_num的纪元号初始化为1,将名为batches的批号初始化为2

然后,在for循环中,您随机抽取数据样本,并使用tf.cast()将其转换为类型float32

i=0     #set counter
epoch_num=1   #set number of epochs
batches=2     #set number of batches
for epoch in range(epoch_num):
   i=i+1
   for index in range(batches):
       # [Batch Preparation]
       print(index)
       noise= np.random.normal(0,1, [batches,50,100,100])
       noise = tf.cast(noise, tf.float32)

现在,您需要使用生成器为模型生成假输入。例如,您可以使用np.ones(gen_images.shape[0])生成一个只包含一个数组的伪输出:

# Generate fake inputs
gen_images = g.predict(x=noise,steps=10)
y_gen = np.ones(gen_images.shape[0])

使用np.random.randint()创建一组新的随机选择的伪造和真实图像:

ran_real_image =real_data[np.random.randint(low=0,high=real_data.shape[0],size=batches)] #get random set of real images

使用以下选项选择随机实像:

ran_fake_image =real_data[np.random.randint(low=0,high=real_data.shape[0],size=batches)]#get random set of fake images

构建不同批次的真假数据。使用np.concatenate([ran_real_image, ran_fake_image])将随机生成的假图像和真图像组合成一组新图像。

#Construct different batches of  real and fake data
X= np.concatenate([ran_real_image, ran_fake_image])
y_combined=np.zeros(2*batches)
y_combined[:batches]=0.9

d.trainable=True   #Train only the custom GAN.
d.train_on_batch(X, y_combined)

noise= np.random.randint(0,1, [batches,50,100,100])
noise = tf.cast(noise, tf.float32)
y_gen = np.ones(batches)
d.trainable=False
gan.train_on_batch(noise, y_gen)
noise= np.random.randint(0,1, [batches,50,100,100])
noise = tf.cast(noise, tf.float32)
gen_images = g.predict(x=noise,steps=10)
gen_images = gen_images

第六步。查看图像

一旦模型训练完毕,您就可以查看图像了。我们将以 20x20 的尺寸查看它们,并将插值设置为nearest。由于我们不需要轴来查看图像,我们将使用plt.axis('off')将其设置为off

dim=(20,20)
figsize=(20,20)
plt.figure(figsize=figsize)
for i in range(gen_images.shape[0]):
    plt.subplot(dim[0], dim[1], i+1)
    plt.imshow(gen_images[i]*256, interpolation="nearest")
    plt.axis('off')
    plt.tight_layout()
    os.chdir('output')
    plt.savefig('actual'+str(i)+'.png')

第七步。保存结果

现在,首先使用os.chdir('output')设置文件路径,将结果直接保存到系统中。然后使用result.save('actual.png')命名图像文件。

# Send Output to folder
result = Image.fromarray((gen_images[5]*256).astype(np.uint8))   #obtain array form of image.
os.chdir('output')    #specify the file path to save the image.
result.save('actual.png')      #save the image
a=(gen_images[5]*255.0).astype(np.uint8)   #Unsigned Integers of 8 bits. A uint8 data type contains all whole numbers from 0 to 255.

解决纷争

这里有一些快速解决你在这个项目中可能遇到的问题的方法。

  • 确保在正确的时间将模型设置为trainable

  • 如果结果不令人满意,请在训练前尝试洗牌。

  • 批量过大会降低模型的泛化能力。尝试使用小批量。

  • 由于我们正在使用 VGG-16,这是一个相关的模型,请确保您在训练时使用与模型相同的规范化和预处理。

  • 过多的调整会导致网络严重不足。减少正则化,例如丢失、批量范数、权重/偏差 L2 正则化等。

  • 在开始做出有意义的预测之前,网络可能需要更多的时间来训练。如果损失在稳步减少,让它多训练一些。

进一步测试

这里有一些想法可以尝试,并从这个项目中学到更多:

  • 试着移除预训练的 VGG-16 模型,看看仅使用定制 GAN 的结果如何。(警告:GANs 需要很长的训练时间,大约 24 小时以上。因此,只有当您的系统能够处理训练时间时,才测试这一点。)

  • 在生成器中添加/移除层,并查看这如何影响结果。

  • 在鉴别器中添加/移除层,并查看这会如何影响结果。

  • 改变时代。

  • 尝试只使用 VGG-16,看看结果如何比较 GAN。

摘要

下面是你在本章中学到的所有内容的快速回顾。

  • 生成对抗网络(GANs)是一类强大的神经网络,用于无监督学习

  • GANs 由两部分组成——一个发生器和一个鉴别器。生成器生成新的数据实例(通常是图像)。鉴别器评估图像的真实性。

  • 主流神经网络的输入和输出之间的映射几乎是线性的。

  • 香草甘真的很简单;它试图使用随机梯度下降来优化数学方程。

  • 条件 GAN 可以被描述为一种深度学习方法,其中一些条件参数,例如附加参数 y 和标签被放置到位。

  • 深度卷积 GAN 是 GAN 最成功的实现之一。它由神经网络代替多层感知器组成。它不使用最大池,并且各层没有完全连接。

  • 拉普拉斯金字塔 GAN 是一种线性图像表示,由一组相隔一个倍频程的带通图像和一个低频残差组成。

  • 超分辨率 GAN 是一种深度神经网络,与对抗网络一起使用,以产生更高分辨率的图像。

  • GANs 用于生成图像数据集的示例、生成人脸的照片、生成逼真的照片、生成卡通人物、图像到图像的翻译、文本到图像的翻译、语义图像到照片的翻译以及面部正面视图的生成。

  • 生成模型根据概率模型描述了数据是如何生成的。它捕捉数据的分布,并以这样一种方式进行训练,即它试图最大化鉴别器出错的概率。

  • 鉴别器是一个正常的分类模型。鉴别器模型从域中取一个例子作为输入(真实的或生成的),并预测真实或虚假的二进制类别标签(生成的样本)。

  • 训练鉴别器时,保持发生器值不变;并且在训练生成器时,保持鉴别器不变。每个人都应该针对静态的对手进行训练。这使得生成器能够更好地读取它必须学习的梯度。

参考

本章中使用的参考资料如下:

进一步阅读

有兴趣了解本章中涉及的一些主题吗?这里有一些很棒的链接可以查看: