TensorFlow 1.x 深度学习秘籍:6~10

原文:TensorFlow 1.x Deep Learning Cookbook

协议:CC BY-NC-SA 4.0

译者:飞龙

本文来自【ApacheCN 深度学习 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。

不要担心自己的形象,只关心如何实现目标。——《原则》,生活原则 2.3.c

六、循环神经网络

在本章中,我们将介绍一些涵盖以下主题的秘籍:

  • 神经机器翻译-训练 seq2seq RNN
  • 神经机器翻译-推理 seq2seq RNN
  • 您只需要关注-seq2seq RNN 的另一个示例
  • 通过 RNN 学习写作莎士比亚
  • 学习使用 RNN 预测未来的比特币价值
  • 多对一和多对多 RNN 示例

介绍

在本章中,我们将讨论循环神经网络RNN)如何在保持顺序顺序重要的领域中用于深度学习。 我们的注意力将主要集中在文本分析和自然语言处理NLP)上,但我们还将看到用于预测比特币价值的序列示例。

通过采用基于时间序列的模型,可以描述许多实时情况。 例如,如果您考虑编写文档,则单词的顺序很重要,而当前单词肯定取决于先前的单词。 如果我们仍然专注于文本编写,很明显单词中的下一个字符取决于前一个字符(例如quick brown字符的下一个字母很有可能将会是字母fox),如下图所示。 关键思想是在给定当前上下文的情况下生成下一个字符的分布,然后从该分布中采样以生成下一个候选字符:

The quick brown fox句子进行预测的例子

一个简单的变体是存储多个预测,因此创建一棵可能的扩展树,如下图所示:

The quick brown fox句子的预测树的示例

但是,基于序列的模型可以在大量其他域中使用。 在音乐中,乐曲中的下一个音符肯定取决于前一个音符,而在视频中,电影中的下一个帧必定与前一帧有关。 此外,在某些情况下,当前的视频帧,单词,字符或音符不仅取决于前一个,而且还取决于后一个。

可以使用 RNN 描述基于时间序列的模型,其中对于给定输入X[i],时间为i,产生输出Y[i],将时间[0,i-1]的以前状态的记忆反馈到网络。 反馈先前状态的想法由循环循环描述,如下图所示:

反馈示例

循环关系可以方便地通过展开网络来表示,如下图所示:

展开循环单元的例子

最简单的 RNN 单元由简单的 tanh 函数(双曲正切函数)组成,如下图所示:

个简单的 tanh 单元的例子

梯度消失和爆炸

训练 RNN 十分困难,因为存在两个稳定性问题。 由于反馈回路的缘故,梯度可能会迅速发散到无穷大,或者它可能会迅速发散到 0。在两种情况下,如下图所示,网络将停止学习任何有用的东西。 可以使用基于梯度修剪的相对简单的解决方案来解决梯度爆炸的问题。 梯度消失的问题更难解决,它涉及更复杂的 RNN 基本单元的定义,例如长短期记忆LSTM)或门控循环单元GRU)。 让我们首先讨论梯度爆炸和梯度裁剪:

梯度示例

梯度裁剪包括对梯度施加最大值,以使其无法无限增长。 下图所示的简单解决方案为梯度爆炸问题提供了简单的解决方案

梯度裁剪的例子

解决梯度消失的问题需要一种更复杂的内存模型,该模型可以选择性地忘记先前的状态,只记住真正重要的状态。 考虑下图,输入以[0,1]中的概率p写入存储器M中,并乘以加权输入。

以类似的方式,以[0,1]中的概率p读取输出,将其乘以加权输出。 还有一种可能性用来决定要记住或忘记的事情:

存储单元的一个例子

长短期记忆(LSTM)

LSTM 网络可以控制何时让输入进入神经元,何时记住在上一个时间步中学到的内容以及何时让输出传递到下一个时间戳。 所有这些决定都是自调整的,并且仅基于输入。 乍一看,LSTM 看起来很难理解,但事实并非如此。 让我们用下图来说明它是如何工作的:

LSTM 单元的一个例子

首先,我们需要一个逻辑函数σ(请参见第 2 章,“回归”)来计算介于 0 和 1 之间的值,并控制哪些信息流过 LSTM 门。 请记住,逻辑函数是可微的,因此允许反向传播。 然后,我们需要一个运算符,它采用两个相同维的矩阵并生成另一个矩阵,其中每个元素ij是原始两个矩阵的元素ij的乘积。 同样,我们需要一个运算符,它采用两个相同维度的矩阵并生成另一个矩阵,其中每个元素ij是原始两个矩阵的元素ij之和。 使用这些基本块,我们考虑时间i处的输入X[i],并将其与上一步中的输出Y[i-1]并置。

方程f[t] = σ(W[f] · [y[i-1], x[t]] + b[f])实现了控制激活门的逻辑回归,并用于确定应从先前候选值C[i-1]获取多少信息。 传递给下一个候选值C[i](此处W[f]b[f]矩阵和用于逻辑回归的偏差)。如果 Sigmoid 输出为 1,则表示不要忘记先前的单元格状态C[i-1];如果输出 0, 这将意味着忘记先前的单元状态C[i-1](0, 1)中的任何数字都将表示要传递的信息量。

然后我们有两个方程:s[i] = σ(W[s] · [Y[i-1], x[i]] + b[s]),用于通过控制由当前单元产生的多少信息(Ĉ[i] = tanh(W [C] · [Y[i-1], X[i] + b[c]))应该通过运算符添加到下一个候选值C[i]中,根据上图中表示的方案。

为了实现与运算符所讨论的内容,我们需要另一个方程,其中进行实际的加法+和乘法*C[i] = f[t] * C[i-1] + s[i] * Ĉ[i]

最后,我们需要确定当前单元格的哪一部分应发送到Y[i]输出。 这很简单:我们再进行一次逻辑回归方程,然后通过运算来控制应使用哪一部分候选值输出。 在这里,有一点值得关注,使用 tanh 函数将输出压缩为[-1, 1]。 最新的步骤由以下公式描述:

现在,我了解到这看起来像很多数学运算,但有两个好消息。 首先,如果您了解我们想要实现的目标,那么数学部分并不是那么困难。 其次,您可以将 LSTM 单元用作标准 RNN 单元的黑盒替代,并立即获得解决梯度消失问题的好处。 因此,您实际上不需要了解所有数学知识。 您只需从库中获取 TensorFlow LSTM 实现并使用它即可。

门控循环单元(GRU)和窥孔 LSTM

近年来提出了许多 LSTM 单元的变体。 其中两个真的很受欢迎。 窥孔 LSTM 允许栅极层查看单元状态,如下图虚线所示,而门控循环单元GRU)将隐藏状态和单元状态和合并为一个单一的信息渠道。

同样,GRU 和 Peephole LSTM 都可以用作标准 RNN 单元的黑盒插件,而无需了解基础数学。 这两个单元都可用于解决梯度消失的问题,并可用于构建深度神经网络:

标准 LSTM,PeepHole LSTM 和 GRU 的示例

向量序列的运算

使 RNN 真正强大的是能够对向量序列进行操作的能力,其中 RNN 的输入和/或 RNN 的输出都可以是序列。 下图很好地表示了这一点,其中最左边的示例是传统的(非循环)网络,其后是带有输出序列的 RNN,然后是带有输入序列的 RNN,再是带有序列的 RNN 在不同步序列的输入和输出中,然后是在序列同步的输入和输出中具有序列的 RNN:

RNN 序列的一个例子

机器翻译是输入和输出中不同步序列的一个示例:网络将输入文本作为序列读取,在读取全文之后,会输出目标语言

视频分类是输入和输出中同步序列的示例:视频输入是帧序列,并且对于每个帧,输出中都提供了分类标签。

如果您想了解有关 RNN 有趣应用的更多信息,则必须阅读 Andrej Karpathy 发布的博客。 他训练了网络,以莎士比亚的风格撰写论文(用 Karpathy 的话说:几乎不能从实际的莎士比亚中识别出这些样本),撰写有关虚构主题的现实 Wikipedia 文章,撰写关于愚蠢和不现实问题的现实定理证明(用 Karpathy 的话:更多的幻觉代数几何),并写出现实的 Linux 代码片段(用 Karpathy 的话:他首先建模逐个字符地列举 GNU 许可证,其中包括一些示例,然后生成一些宏,然后深入研究代码)。

以下示例摘自这个页面

用 RNN 生成的文本示例

神经机器翻译 – 训练 seq2seq RNN

序列到序列(seq2seq)是 RNN 的一种特殊类型,已成功应用于神经机器翻译,文本摘要和语音识别中。 在本秘籍中,我们将讨论如何实现神经机器翻译,其结果与 Google 神经机器翻译系统。 关键思想是输入整个文本序列,理解整个含义,然后将翻译输出为另一个序列。 读取整个序列的想法与以前的架构大不相同,在先前的架构中,将一组固定的单词从一种源语言翻译成目标语言。

本节的灵感来自 Minh-Thang Luong 的 2016 年博士学位论文《神经机器翻译》。 第一个关键概念是编码器-解码器架构的存在,其中编码器将源句子转换为代表含义的向量。 然后,此向量通过解码器以产生翻译。 编码器和解码器都是 RNN,它们可以捕获语言中的长期依赖关系,例如性别协议和语法结构,而无需先验地了解它们,并且不需要跨语言进行 1:1 映射。 这是一种强大的功能,可实现非常流畅的翻译:

编解码器的示例

让我们看一个 RNN 的示例,该语句将She loves cute cats翻译成Elle Aime les chat Mignons

有两种 RNN:一种充当编码器,另一种充当解码器。 源句She loves cute cats后跟一个分隔符-目标句是Elle aime les chats mignons。 这两个连接的句子在输入中提供给编码器进行训练,并且解码器将生成目标目标。 当然,我们需要像这样的多个示例来获得良好的训练:

NMT 序列模型的示例

现在,我们可以拥有许多 RNN 变体。 让我们看看其中的一些:

  • RNN 可以是单向或双向的。 后者将捕捉双方的长期关系。
  • RNN 可以具有多个隐藏层。 选择是关于优化的问题:一方面,更深的网络可以学到更多;另一方面,更深的网络可以学到更多。 另一方面,可能需要很长的时间来训练并且可能会过头。
  • RNN 可以具有一个嵌入层,该层将单词映射到一个嵌入空间中,在该空间中相似的单词恰好被映射得非常近。
  • RNNs 可以使用简单的或者循环的单元,或 LSTM,或窥视孔 LSTM,或越冬。

仍然参考博士学位论文《神经机器翻译》,我们可以使用嵌入层来将输入语句放入嵌入空间。 然后,有两个 RNN 粘在一起——源语言的编码器和目标语言的解码器。 如您所见,存在多个隐藏层,并且有两个流程:前馈垂直方向连接这些隐藏层,水平方向是将知识从上一步转移到下一层的循环部分:

神经机器翻译的例子

在本秘籍中,我们使用 NMT(神经机器翻译),这是一个可在 TensorFlow 顶部在线获得的翻译演示包。

准备

NMT 可在这个页面上找到,并且代码在 GitHub 上。

操作步骤

我们按以下步骤进行:

  1. 从 GitHub 克隆 NMT:
git clone https://github.com/tensorflow/nmt/
  1. 下载训练数据集。 在这种情况下,我们将使用训练集将越南语翻译为英语。 其他数据集可从这里获取其他语言,例如德语和捷克语:
nmt/scripts/download_iwslt15.sh /tmp/nmt_data
  1. 考虑这里,我们将定义第一个嵌入层。 嵌入层接受输入,词汇量 V 和输出嵌入空间的所需大小。 词汇量使得仅考虑 V 中最频繁的单词进行嵌入,而所有其他单词都映射到一个常见的未知项。 在我们的例子中,输入是主要时间的,这意味着最大时间是第一个输入参数
# Embedding
 embedding_encoder = variable_scope.get_variable(
 "embedding_encoder", [src_vocab_size, embedding_size], ...)
 # Look up embedding:
 # encoder_inputs: [max_time, batch_size]
 # encoder_emb_inp: [max_time, batch_size, embedding_size]
 encoder_emb_inp = embedding_ops.embedding_lookup(
 embedding_encoder, encoder_inputs)
  1. 仍然参考这里,我们定义了一个简单的编码器,它使用tf.nn.rnn_cell.BasicLSTMCell(num_units)作为基本 RNN 单元。 这非常简单,但是要注意,给定基本的 RNN 单元,我们使用tf.nn.dynamic_rnn创建 RNN:
# Build RNN cell
 encoder_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units)

 # Run Dynamic RNN
 # encoder_outpus: [max_time, batch_size, num_units]
 # encoder_state: [batch_size, num_units]
 encoder_outputs, encoder_state = tf.nn.dynamic_rnn(
 encoder_cell, encoder_emb_inp,
 sequence_length=source_sequence_length, time_major=True)
  1. 之后,我们需要定义解码器。 因此,第一件事是拥有一个带有tf.nn.rnn_cell.BasicLSTMCell的基本 RNN 单元,然后将其用于创建一个基本采样解码器tf.contrib.seq2seq.BasicDecoder,该基本采样解码器将用于与解码器tf.contrib.seq2seq.dynamic_decode进行动态解码:
# Build RNN cell
 decoder_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units)
# Helper
 helper = tf.contrib.seq2seq.TrainingHelper(
 decoder_emb_inp, decoder_lengths, time_major=True)
 # Decoder
 decoder = tf.contrib.seq2seq.BasicDecoder(
 decoder_cell, helper, encoder_state,
 output_layer=projection_layer)
 # Dynamic decoding
 outputs, _ = tf.contrib.seq2seq.dynamic_decode(decoder, ...)
 logits = outputs.rnn_output
  1. 网络的最后一个阶段是 softmax 密集阶段,用于将顶部隐藏状态转换为对率向量:
projection_layer = layers_core.Dense(
 tgt_vocab_size, use_bias=False)
  1. 当然,我们需要定义交叉熵函数和训练阶段使用的损失:
crossent = tf.nn.sparse_softmax_cross_entropy_with_logits(
 labels=decoder_outputs, logits=logits)
 train_loss = (tf.reduce_sum(crossent * target_weights) /
 batch_size)
  1. 下一步是定义反向传播所需的步骤,并使用适当的优化器(在本例中为 Adam)。 请注意,梯度已被裁剪,Adam 使用预定义的学习率:
# Calculate and clip gradients
 params = tf.trainable_variables()
 gradients = tf.gradients(train_loss, params)
 clipped_gradients, _ = tf.clip_by_global_norm(
 gradients, max_gradient_norm)
# Optimization
 optimizer = tf.train.AdamOptimizer(learning_rate)
 update_step = optimizer.apply_gradients(
 zip(clipped_gradients, params))
  1. 现在,我们可以运行代码并了解不同的执行步骤。 首先,创建训练图。 然后,训练迭代开始。 用于评估的度量标准是双语评估研究BLEU)。 此度量标准是评估已从一种自然语言机器翻译成另一种自然语言的文本质量的标准。 质量被认为是机器与人工输出之间的对应关系。 如您所见,该值随时间增长:
python -m nmt.nmt --src=vi --tgt=en --vocab_prefix=/tmp/nmt_data/vocab --train_prefix=/tmp/nmt_data/train --dev_prefix=/tmp/nmt_data/tst2012 --test_prefix=/tmp/nmt_data/tst2013 --out_dir=/tmp/nmt_model --num_train_steps=12000 --steps_per_stats=100 --num_layers=2 --num_units=128 --dropout=0.2 --metrics=bleu
# Job id 0
[...]
# creating train graph ...
num_layers = 2, num_residual_layers=0
cell 0 LSTM, forget_bias=1 DropoutWrapper, dropout=0.2 DeviceWrapper, device=/gpu:0
cell 1 LSTM, forget_bias=1 DropoutWrapper, dropout=0.2 DeviceWrapper, device=/gpu:0
cell 0 LSTM, forget_bias=1 DropoutWrapper, dropout=0.2 DeviceWrapper, device=/gpu:0
cell 1 LSTM, forget_bias=1 DropoutWrapper, dropout=0.2 DeviceWrapper, device=/gpu:0
start_decay_step=0, learning_rate=1, decay_steps 10000,decay_factor 0.98
[...]
# Start step 0, lr 1, Thu Sep 21 12:57:18 2017
# Init train iterator, skipping 0 elements
global step 100 lr 1 step-time 1.65s wps 3.42K ppl 1931.59 bleu 0.00
global step 200 lr 1 step-time 1.56s wps 3.59K ppl 690.66 bleu 0.00
[...]
global step 9100 lr 1 step-time 1.52s wps 3.69K ppl 39.73 bleu 4.89
global step 9200 lr 1 step-time 1.52s wps 3.72K ppl 40.47 bleu 4.89
global step 9300 lr 1 step-time 1.55s wps 3.62K ppl 40.59 bleu 4.89
[...]
# External evaluation, global step 9000
decoding to output /tmp/nmt_model/output_dev.
done, num sentences 1553, time 17s, Thu Sep 21 17:32:49 2017.
bleu dev: 4.9
saving hparams to /tmp/nmt_model/hparams
# External evaluation, global step 9000
decoding to output /tmp/nmt_model/output_test.
done, num sentences 1268, time 15s, Thu Sep 21 17:33:06 2017.
bleu test: 3.9
saving hparams to /tmp/nmt_model/hparams
[...]
global step 9700 lr 1 step-time 1.52s wps 3.71K ppl 38.01 bleu 4.89

工作原理

所有上述代码已在这个页面中定义。 关键思想是将两个 RNN 打包在一起。 第一个是编码器,它在嵌入空间中工作,非常紧密地映射相似的单词。 编码器理解训练示例的含义,并产生张量作为输出。 然后只需将编码器的最后一个隐藏层连接到解码器的初始层,即可将该张量传递给解码器。 注意力学习是由于我们基于与labels=decoder_outputs的交叉熵的损失函数而发生的。

该代码学习如何翻译,并通过 BLEU 度量标准通过迭代跟踪进度,如下图所示:

Tensorboard 中的 BLEU 指标示例

神经机器翻译 – 用 seq2seq RNN 推理

在此秘籍中,我们使用先前秘籍的结果将源语言转换为目标语言。 这个想法非常简单:给源语句提供两个组合的 RNN(编码器+解码器)作为输入。 句子一结束,解码器将产生对率值,我们贪婪地产生与最大值关联的单词。 例如,从解码器产生单词moi作为第一个令牌,因为该单词具有最大对率值。 之后,会产生单词suis,依此类推:

具有概率的 NM 序列模型的示例

使用解码器的输出有多种策略:

  • 贪婪:产生对应最大对率的字
  • 采样:通过对产生的对率进行采样来产生单词
  • 集束搜索:一个以上的预测,因此创建了可能的扩展树

操作步骤

我们按以下步骤进行:

  1. 定义用于对解码器进行采样的贪婪策略。 这很容易,因为我们可以使用tf.contrib.seq2seq.GreedyEmbeddingHelper中定义的库。 由于我们不知道目标句子的确切长度,因此我们将启发式方法限制为最大长度为源句子长度的两倍:
# Helper
 helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(
 embedding_decoder,
 tf.fill([batch_size], tgt_sos_id), tgt_eos_id)

 # Decoder
 decoder = tf.contrib.seq2seq.BasicDecoder(
 decoder_cell, helper, encoder_state,
 output_layer=projection_layer)
 # Dynamic decoding
 outputs, _ = tf.contrib.seq2seq.dynamic_decode(
 decoder, maximum_iterations=maximum_iterations)
 translations = outputs.sample_id
maximum_iterations = tf.round(tf.reduce_max(source_sequence_length) * 2)
  1. 现在,我们可以运行网络,输入一个从未见过的句子(inference_input_file=/tmp/my_infer_file),然后让网络翻译结果(inference_output_file=/tmp/nmt_model/output_infer):
python -m nmt.nmt \
 --out_dir=/tmp/nmt_model \
 --inference_input_file=/tmp/my_infer_file.vi \
 --inference_output_file=/tmp/nmt_model/output_infer

工作原理

将两个 RNN 打包在一起,以形成编码器-解码器 RNN 网络。 解码器产生对率,然后将其贪婪地转换为目标语言的单词。 例如,此处显示了从越南语到英语的自动翻译:

  • 用英语输入的句子:小时候,我认为朝鲜是世界上最好的国家,我经常唱歌&。 我们没有什么可嫉妒的。
  • 翻译成英语的输出句子:当我非常好时,我将去了解最重要的事情,而我不确定该说些什么。

您只需要注意力 – seq2seq RNN 的另一个示例

在本秘籍中,我们介绍了注意力方法(Dzmitry Bahdanau,Kyunghyun Cho 和 Yoshua Bengio,ICLR 2015),这是神经网络翻译的最新解决方案。 ,它包括在编码器和解码器 RNN 之间添加其他连接。 实际上,仅将解码器与编码器的最新层连接会带来信息瓶颈,并且不一定允许通过先前的编码器层获取的信息通过。 下图说明了采用的解决方案:

NMT 注意力模型的示例

需要考虑三个方面:

  • 首先,将当前目标隐藏状态与所有先前的源状态一起使用以得出注意力权重,该注意力权重用于或多或少地关注序列中先前看到的标记
  • 其次,创建上下文向量以汇总注意力权重的结果
  • 第三,将上下文向量与当前目标隐藏状态组合以获得注意力向量

操作步骤

我们按以下步骤进行:

  1. 使用库tf.contrib.seq2seq.LuongAttention定义注意力机制,该库实现了 Minh-Thang Luong,Hieu Pham 和 Christopher D. Manning(2015 年)在《基于注意力的神经机器翻译有效方法》中定义的注意力模型:
# attention_states: [batch_size, max_time, num_units]
 attention_states = tf.transpose(encoder_outputs, [1, 0, 2])

 # Create an attention mechanism
 attention_mechanism = tf.contrib.seq2seq.LuongAttention(
 num_units, attention_states,
 memory_sequence_length=source_sequence_length)
  1. 通过注意力包装器,将定义的注意力机制用作解码器单元周围的包装器:
decoder_cell = tf.contrib.seq2seq.AttentionWrapper(
 decoder_cell, attention_mechanism,
 attention_layer_size=num_units)
  1. 运行代码以查看结果。 我们立即注意到,注意力机制在 BLEU 得分方面产生了显着改善:
python -m nmt.nmt \
> --attention=scaled_luong \
> --src=vi --tgt=en \
> --vocab_prefix=/tmp/nmt_data/vocab \
> --train_prefix=/tmp/nmt_data/train \
> --dev_prefix=/tmp/nmt_data/tst2012 \
> --test_prefix=/tmp/nmt_data/tst2013 \
> --out_dir=/tmp/nmt_attention_model \
> --num_train_steps=12000 \
> --steps_per_stats=100 \
> --num_layers=2 \
> --num_units=128 \
> --dropout=0.2 \
> --metrics=bleu
[...]
# Start step 0, lr 1, Fri Sep 22 22:49:12 2017
# Init train iterator, skipping 0 elements
global step 100 lr 1 step-time 1.71s wps 3.23K ppl 15193.44 bleu 0.00
[...]
# Final, step 12000 lr 0.98 step-time 1.67 wps 3.37K ppl 14.64, dev ppl 14.01, dev bleu 15.9, test ppl 12.58, test bleu 17.5, Sat Sep 23 04:35:42 2017
# Done training!, time 20790s, Sat Sep 23 04:35:42 2017.
# Start evaluating saved best models.
[..]
loaded infer model parameters from /tmp/nmt_attention_model/best_bleu/translate.ckpt-12000, time 0.06s
# 608
src: nhưng bạn biết điều gì không ?
ref: But you know what ?
nmt: But what do you know ?
[...]
# Best bleu, step 12000 step-time 1.67 wps 3.37K, dev ppl 14.01, dev bleu 15.9, test ppl 12.58, test bleu 17.5, Sat Sep 23 04:36:35 2017

工作原理

注意是一种机制,该机制使用由编码器 RNN 的内部状态获取的信息,并将该信息与解码器的最终状态进行组合。 关键思想是,通过这种方式,有可能或多或少地关注源序列中的某些标记。 下图显示了 BLEU 得分,引起了关注。

我们注意到,相对于我们第一个秘籍中未使用任何注意力的图表而言,它具有明显的优势:

Tensorboard 中注意力的 BLEU 指标示例

更多

值得记住的是 seq2seq 不仅可以用于机器翻译。 让我们看一些例子:

  • Lukasz Kaiser 在作为外语的语法中,使用 seq2seq 模型来构建选区解析器。 选区分析树将文本分为多个子短语。 树中的非终结符是短语的类型,终结符是句子中的单词,并且边缘未标记。
  • seq2seq 的另一个应用是 SyntaxNet,又名 Parsey McParserFace(语法分析器),它是许多 NLU 系统中的关键第一组件。 给定一个句子作为输入,它将使用描述单词的句法特征的词性POS)标签标记每个单词,并确定句子中单词之间的句法关系,在依存关系分析树中表示。 这些句法关系与所讨论句子的潜在含义直接相关。

下图使我们对该概念有了一个很好的了解:

SyntaxNet 的一个例子

通过 RNN 学习写作莎士比亚

在本秘籍中,我们将学习如何生成与威廉·莎士比亚(William Shakespeare)相似的文本。 关键思想很简单:我们将莎士比亚写的真实文本作为输入,并将其作为输入 RNN 的输入,该 RNN 将学习序列。 然后将这种学习用于生成新文本,该文本看起来像最伟大的作家用英语撰写的文本。

为了简单起见,我们将使用框架 TFLearn,它在 TensorFlow 上运行。 此示例是标准分发版的一部分,可从以下位置获得。开发的模型是 RNN 字符级语言模型,其中考虑的序列是字符序列而不是单词序列。

操作步骤

我们按以下步骤进行:

  1. 使用pip安装 TFLearn:
pip install -I tflearn
  1. 导入许多有用的模块并下载一个由莎士比亚撰写的文本示例。 在这种情况下,我们使用这个页面中提供的一种:
import os
import pickle
from six.moves import urllib
import tflearn
from tflearn.data_utils import *
path = "shakespeare_input.txt"
char_idx_file = 'char_idx.pickle'
if not os.path.isfile(path): urllib.request.urlretrieve("https://raw.githubusercontent.com/tflearn/tflearn.github.io/master/resources/shakespeare_input.txt", path)
  1. 使用string_to_semi_redundant_sequences()将输入的文本转换为向量,并返回解析的序列和目标以及相关的字典,该函数将返回一个元组(输入,目标,字典):
maxlen = 25
char_idx = None
if os.path.isfile(char_idx_file):
print('Loading previous char_idx')
char_idx = pickle.load(open(char_idx_file, 'rb'))
X, Y, char_idx = \
textfile_to_semi_redundant_sequences(path, seq_maxlen=maxlen, redun_step=3,
pre_defined_char_idx=char_idx)
pickle.dump(char_idx, open(char_idx_file,'wb'))
  1. 定义一个由三个 LSTM 组成的 RNN,每个 LSTM 都有 512 个节点,并返回完整序列,而不是仅返回最后一个序列输出。 请注意,我们使用掉线模块连接 LSTM 模块的可能性为 50%。 最后一层是密集层,其应用 softmax 的长度等于字典大小。 损失函数为categorical_crossentropy,优化器为 Adam:
g = tflearn.input_data([None, maxlen, len(char_idx)])
g = tflearn.lstm(g, 512, return_seq=True)
g = tflearn.dropout(g, 0.5)
g = tflearn.lstm(g, 512, return_seq=True)
g = tflearn.dropout(g, 0.5)
g = tflearn.lstm(g, 512)
g = tflearn.dropout(g, 0.5)
g = tflearn.fully_connected(g, len(char_idx), activation='softmax')
g = tflearn.regression(g, optimizer='adam', loss='categorical_crossentropy',
learning_rate=0.001)
  1. 给定步骤 4 中定义的网络,我们现在可以使用库flearn.models.generator.SequenceGeneratornetworkdictionary=char_idx, seq_maxlen=maxleclip_gradients=5.0, checkpoint_path='model_shakespeare')生成序列:
m = tflearn.SequenceGenerator(g, dictionary=char_idx,
seq_maxlen=maxlen,
clip_gradients=5.0,
checkpoint_path='model_shakespeare')
  1. 对于 50 次迭代,我们从输入文本中获取随机序列,然后生成一个新文本。 温度正在控制所创建序列的新颖性; 温度接近 0 看起来像用于训练的样本,而温度越高,新颖性越强:
for i in range(50):
seed = random_sequence_from_textfile(path, maxlen)
m.fit(X, Y, validation_set=0.1, batch_size=128,
n_epoch=1, run_id='shakespeare')
print("-- TESTING...")
print("-- Test with temperature of 1.0 --")
print(m.generate(600, temperature=1.0, seq_seed=seed))
print("-- Test with temperature of 0.5 --")
print(m.generate(600, temperature=0.5, seq_seed=seed))

工作原理

当新的未知或被遗忘的艺术品要归功于作者时,有著名的学者将其与作者的其他作品进行比较。 学者们要做的是在著名作品的文本序列中找到共同的模式,希望在未知作品中找到相似的模式。

这种方法的工作方式相似:RNN 了解莎士比亚作品中最特殊的模式是什么,然后将这些模式用于生成新的,从未见过的文本,这些文本很好地代表了最伟大的英语作者的风格。

让我们看一些执行示例:

python shakespeare.py
Loading previous char_idx
Vectorizing text...
Text total length: 4,573,338
Distinct chars : 67
Total sequences : 1,524,438
---------------------------------
Run id: shakespeare
Log directory: /tmp/tflearn_logs/

第一次迭代

在这里,网络正在学习一些基本结构,包括需要建立有关虚构字符(DIASURYONTHRNTLGIPRMARARILEN)的对话。 但是,英语仍然很差,很多单词不是真正的英语:

---------------------------------
Training samples: 1371994
Validation samples: 152444
--
Training Step: 10719 | total loss: 2.22092 | time: 22082.057s
| Adam | epoch: 001 | loss: 2.22092 | val_loss: 2.12443 -- iter: 1371994/1371994
-- TESTING...
-- Test with temperature of 1.0 --
'st thou, malice?
If thou caseghough memet oud mame meard'ke. Afs weke wteak, Dy ny wold' as to of my tho gtroy ard has seve, hor then that wordith gole hie, succ, caight fom?
DIA:
A gruos ceen, I peey
by my
Wiouse rat Sebine would.
waw-this afeean.
SURYONT:
Teeve nourterong a oultoncime bucice'is furtutun
Ame my sorivass; a mut my peant?
Am:
Fe, that lercom ther the nome, me, paatuy corns wrazen meas ghomn'ge const pheale,
As yered math thy vans:
I im foat worepoug and thit mije woml!
HRNTLGIPRMAR:
I'd derfomquesf thiy of doed ilasghele hanckol, my corire-hougangle!
Kiguw troll! you eelerd tham my fom Inow lith a
-- Test with temperature of 0.5 --
'st thou, malice?
If thou prall sit I har, with and the sortafe the nothint of the fore the fir with with the ceme at the ind the couther hit yet of the sonsee in solles and that not of hear fore the hath bur.
ARILEN:
More you a to the mare me peod sore,
And fore string the reouck and and fer to the so has the theat end the dore; of mall the sist he the bot courd wite be the thoule the to nenge ape and this not the the ball bool me the some that dears,
The be to the thes the let the with the thear tould fame boors and not to not the deane fere the womour hit muth so thand the e meentt my to the treers and woth and wi

经过几次迭代

在这里,网络开始学习对话的正确结构,并且使用Well, there shall the things to need the offer to our heartThere is not that be so then to the death To make the body and all the mind这样的句子,书面英语看起来更正确:

---------------------------------
Training samples: 1371994
Validation samples: 152444
--
Training Step: 64314 | total loss: 1.44823 | time: 21842.362s
| Adam | epoch: 006 | loss: 1.44823 | val_loss: 1.40140 -- iter: 1371994/1371994
--
-- Test with temperature of 0.5 --
in this kind.
THESEUS:
There is not that be so then to the death
To make the body and all the mind.
BENEDICK:
Well, there shall the things to need the offer to our heart,
To not are he with him: I have see the hands are to true of him that I am not,
The whom in some the fortunes,
Which she were better not to do him?
KING HENRY VI:
I have some a starter, and and seen the more to be the boy, and be such a plock and love so say, and I will be his entire,
And when my masters are a good virtues,
That see the crown of our worse,
This made a called grace to hear him and an ass,
And the provest and stand,

更多

博客文章循环神经网络的不合理有效性描述了一组引人入胜的示例 RNN 字符级语言模型,包括以下内容:

  • 莎士比亚文本生成类似于此示例
  • Wikipedia 文本生成类似于此示例,但是基于不同的训练文本
  • 代数几何(LaTex)文本生成类似于此示例,但基于不同的训练文本
  • Linux 源代码文本的生成与此示例相似,但是基于不同的训练文本
  • 婴儿命名文本的生成与此示例类似,但是基于不同的训练文本

学习使用 RNN 预测未来的比特币价值

在本秘籍中,我们将学习如何使用 RNN 预测未来的比特币价值。 关键思想是,过去观察到的值的时间顺序可以很好地预测未来的值。 对于此秘籍,我们将使用 MIT 许可下的这个页面上提供的代码。 给定时间间隔的比特币值通过 API 从这里下载。 这是 API 文档的一部分:

We offer historical data from our Bitcoin Price Index through the following endpoint: https://api.coindesk.com/v1/bpi/historical/close.json By default, this will return the previous 31 days' worth of data. This endpoint accepts the following optional parameters: ?index=[USD/CNY]The index to return data for. Defaults to USD. ?currency=<VALUE>The currency to return the data in, specified in ISO 4217 format. Defaults to USD. ?start=<VALUE>&end=<VALUE> Allows data to be returned for a specific date range. Must be listed as a pair of start and end parameters, with dates supplied in the YYYY-MM-DD format, e.g. 2013-09-01 for September 1st, 2013. ?for=yesterday Specifying this will return a single value for the previous day. Overrides the start/end parameter. Sample Request: https://api.coindesk.com/v1/bpi/historical/close.json?start=2013-09-01&end=2013-09-05 Sample JSON Response: {"bpi":{"2013-09-01":128.2597,"2013-09-02":127.3648,"2013-09-03":127.5915,"2013-09-04":120.5738,"2013-09-05":120.5333},"disclaimer":"This data was produced from the CoinDesk Bitcoin Price Index. BPI value data returned as USD.","time":{"updated":"Sep 6, 2013 00:03:00 UTC","updatedISO":"2013-09-06T00:03:00+00:00"}}

操作步骤

这是我们进行秘籍的方法:

  1. 克隆以下 GitHub 存储库。 这是一个鼓励用户尝试使用 seq2seq 神经网络架构的项目:
git clone https://github.com/guillaume-chevalier/seq2seq-signal-prediction.git
  1. 给定前面的存储库,请考虑以下函数,这些函数可加载和标准化 USD 或 EUR 比特币值的比特币历史数据。 这些特征在dataset.py中定义。 训练和测试数据根据 80/20 规则分开。 因此,测试数据的 20% 是最新的历史比特币值。 每个示例在特征轴/维度中包含 40 个 USD 数据点,然后包含 EUR 数据。 根据平均值和标准差对数据进行归一化。 函数generate_x_y_data_v4生成大小为batch_size的训练数据(分别是测试数据)的随机样本:
def loadCurrency(curr, window_size):
   """
   Return the historical data for the USD or EUR bitcoin value. Is done with an web API call.
   curr = "USD" | "EUR"
   """
   # For more info on the URL call, it is inspired by :
   # https://github.com/Levino/coindesk-api-node
   r = requests.get(
       "http://api.coindesk.com/v1/bpi/historical/close.json?start=2010-07-17&end=2017-03-03&currency={}".format(
           curr
       )
   )
   data = r.json()
   time_to_values = sorted(data["bpi"].items())
   values = [val for key, val in time_to_values]
   kept_values = values[1000:]
   X = []
   Y = []
   for i in range(len(kept_values) - window_size * 2):
       X.append(kept_values[i:i + window_size])
       Y.append(kept_values[i + window_size:i + window_size * 2])
   # To be able to concat on inner dimension later on:
   X = np.expand_dims(X, axis=2)
   Y = np.expand_dims(Y, axis=2)
   return X, Y
def normalize(X, Y=None):
   """
   Normalise X and Y according to the mean and standard
deviation of the X values only.
   """
   # # It would be possible to normalize with last rather than mean, such as:
   # lasts = np.expand_dims(X[:, -1, :], axis=1)
   # assert (lasts[:, :] == X[:, -1, :]).all(), "{}, {}, {}. {}".format(lasts[:, :].shape, X[:, -1, :].shape, lasts[:, :], X[:, -1, :])
   mean = np.expand_dims(np.average(X, axis=1) + 0.00001, axis=1)
   stddev = np.expand_dims(np.std(X, axis=1) + 0.00001, axis=1)
   # print (mean.shape, stddev.shape)
   # print (X.shape, Y.shape)
   X = X - mean
   X = X / (2.5 * stddev)
   if Y is not None:
       assert Y.shape == X.shape, (Y.shape, X.shape)
       Y = Y - mean
       Y = Y / (2.5 * stddev)
       return X, Y
   return X

def fetch_batch_size_random(X, Y, batch_size):
   """
   Returns randomly an aligned batch_size of X and Y among all examples.
   The external dimension of X and Y must be the batch size
(eg: 1 column = 1 example).
   X and Y can be N-dimensional.
   """
   assert X.shape == Y.shape, (X.shape, Y.shape)
   idxes = np.random.randint(X.shape[0], size=batch_size)
   X_out = np.array(X[idxes]).transpose((1, 0, 2))
   Y_out = np.array(Y[idxes]).transpose((1, 0, 2))
   return X_out, Y_out
X_train = []
Y_train = []
X_test = []
Y_test = []

def generate_x_y_data_v4(isTrain, batch_size):
   """
   Return financial data for the bitcoin.
   Features are USD and EUR, in the internal dimension.
   We normalize X and Y data according to the X only to not
   spoil the predictions we ask for.
   For every window (window or seq_length), Y is the prediction following X.
   Train and test data are separated according to the 80/20
rule.
   Therefore, the 20 percent of the test data are the most
   recent historical bitcoin values. Every example in X contains
   40 points of USD and then EUR data in the feature axis/dimension.
   It is to be noted that the returned X and Y has the same shape
   and are in a tuple.
   """
   # 40 pas values for encoder, 40 after for decoder's predictions.
   seq_length = 40
   global Y_train
   global X_train
   global X_test
   global Y_test
   # First load, with memoization:
   if len(Y_test) == 0:
       # API call:
       X_usd, Y_usd = loadCurrency("USD",
window_size=seq_length)
       X_eur, Y_eur = loadCurrency("EUR",
window_size=seq_length)
       # All data, aligned:
       X = np.concatenate((X_usd, X_eur), axis=2)
       Y = np.concatenate((Y_usd, Y_eur), axis=2)
       X, Y = normalize(X, Y)
       # Split 80-20:
       X_train = X[:int(len(X) * 0.8)]
       Y_train = Y[:int(len(Y) * 0.8)]
       X_test = X[int(len(X) * 0.8):]
       Y_test = Y[int(len(Y) * 0.8):]
   if isTrain:
       return fetch_batch_size_random(X_train, Y_train, batch_size)
   else:
       return fetch_batch_size_random(X_test,  Y_test,  batch_size)
  1. 生成训练,验证和测试数据,并定义许多超参数,例如batch_sizehidden_dim(RNN 中隐藏的神经元的数量)和layers_stacked_count(栈式循环单元的数量)。 此外,定义一些参数以微调优化器,例如优化器的学习率,迭代次数,用于优化器模拟退火的lr_decay,优化器的动量以及避免过拟合的 L2 正则化。 请注意,GitHub 存储库具有默认的batch_size = 5nb_iters = 150,但使用batch_size = 1000nb_iters = 100000获得了更好的结果:
from datasets import generate_x_y_data_v4
generate_x_y_data = generate_x_y_data_v4
import tensorflow as tf  
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
sample_x, sample_y = generate_x_y_data(isTrain=True, batch_size=3)
print("Dimensions of the dataset for 3 X and 3 Y training
examples : ")
print(sample_x.shape)
print(sample_y.shape)
print("(seq_length, batch_size, output_dim)")
print sample_x, sample_y
# Internal neural network parameters
seq_length = sample_x.shape[0]  # Time series will have the same past and future (to be predicted) lenght.
batch_size = 5  # Low value used for live demo purposes - 100 and 1000 would be possible too, crank that up!
output_dim = input_dim = sample_x.shape[-1]  # Output dimension (e.g.: multiple signals at once, tied in time)
hidden_dim = 12  # Count of hidden neurons in the recurrent units.
layers_stacked_count = 2  # Number of stacked recurrent cells, on the neural depth axis.
# Optmizer:
learning_rate = 0.007  # Small lr helps not to diverge during training.
nb_iters = 150  # How many times we perform a training step (therefore how many times we show a batch).
lr_decay = 0.92  # default: 0.9 . Simulated annealing.
momentum = 0.5  # default: 0.0 . Momentum technique in weights update
lambda_l2_reg = 0.003  # L2 regularization of weights - avoids overfitting
  1. 将网络定义为由基本 GRU 单元组成的编码器/解码器。 该网络由layers_stacked_count=2 RNN 组成,我们将使用 TensorBoard 可视化该网络。 请注意,hidden_dim = 12是循环单元中的隐藏神经元:
tf.nn.seq2seq = tf.contrib.legacy_seq2seq
tf.nn.rnn_cell = tf.contrib.rnn
tf.nn.rnn_cell.GRUCell = tf.contrib.rnn.GRUCell
tf.reset_default_graph()
# sess.close()
sess = tf.InteractiveSession()
with tf.variable_scope('Seq2seq'):
   # Encoder: inputs
   enc_inp = [
       tf.placeholder(tf.float32, shape=(None, input_dim), name="inp_{}".format(t))
          for t in range(seq_length)
   ]
   # Decoder: expected outputs
   expected_sparse_output = [
       tf.placeholder(tf.float32, shape=(None, output_dim), name="expected_sparse_output_".format(t))
         for t in range(seq_length)
   ]

   # Give a "GO" token to the decoder.
   # You might want to revise what is the appended value "+ enc_inp[:-1]".
   dec_inp = [ tf.zeros_like(enc_inp[0], dtype=np.float32, name="GO") ] + enc_inp[:-1]
   # Create a `layers_stacked_count` of stacked RNNs (GRU cells here).
   cells = []
   for i in range(layers_stacked_count):
       with tf.variable_scope('RNN_{}'.format(i)):
           cells.append(tf.nn.rnn_cell.GRUCell(hidden_dim))
           # cells.append(tf.nn.rnn_cell.BasicLSTMCell(...))
   cell = tf.nn.rnn_cell.MultiRNNCell(cells)   
   # For reshaping the input and output dimensions of the seq2seq RNN:
   w_in = tf.Variable(tf.random_normal([input_dim, hidden_dim]))
   b_in = tf.Variable(tf.random_normal([hidden_dim], mean=1.0))
   w_out = tf.Variable(tf.random_normal([hidden_dim, output_dim]))
   b_out = tf.Variable(tf.random_normal([output_dim]))   
reshaped_inputs = [tf.nn.relu(tf.matmul(i, w_in) + b_in) for i in enc_inp]   
# Here, the encoder and the decoder uses the same cell, HOWEVER,
   # the weights aren't shared among the encoder and decoder, we have two
   # sets of weights created under the hood according to that function's def.

   dec_outputs, dec_memory = tf.nn.seq2seq.basic_rnn_seq2seq(
       enc_inp,
       dec_inp,
       cell
   )   

output_scale_factor = tf.Variable(1.0, name="Output_ScaleFactor")
   # Final outputs: with linear rescaling similar to batch norm,
   # but without the "norm" part of batch normalization hehe.
   reshaped_outputs = [output_scale_factor*(tf.matmul(i, w_out) + b_out) for i in dec_outputs]  
   # Merge all the summaries and write them out to /tmp/bitcoin_logs (by default)
   merged = tf.summary.merge_all()
   train_writer = tf.summary.FileWriter('/tmp/bitcoin_logs',                                     sess.graph)
  1. 现在让我们运行 TensorBoard 并可视化由 RNN 编码器和 RNN 解码器组成的网络:
tensorboard --logdir=/tmp/bitcoin_logs

以下是代码流程:

Tensorboard 中的比特币价值预测代码示例

  1. 现在让我们将损失函数定义为具有正则化的 L2 损失,以避免过拟合并获得更好的泛化。 选择的优化器是 RMSprop,其值为learning_rate,衰减和动量,如步骤 3 所定义:
# Training loss and optimizer
with tf.variable_scope('Loss'):
   # L2 loss
   output_loss = 0
   for _y, _Y in zip(reshaped_outputs, expected_sparse_output):
       output_loss += tf.reduce_mean(tf.nn.l2_loss(_y - _Y))       
  # L2 regularization (to avoid overfitting and to have a  better generalization capacity)
   reg_loss = 0
   for tf_var in tf.trainable_variables():
       if not ("Bias" in tf_var.name or "Output_" in tf_var.name):
           reg_loss += tf.reduce_mean(tf.nn.l2_loss(tf_var))

   loss = output_loss + lambda_l2_reg * reg_loss

with tf.variable_scope('Optimizer'):
   optimizer = tf.train.RMSPropOptimizer(learning_rate, decay=lr_decay, momentum=momentum)
   train_op = optimizer.minimize(loss)
  1. 通过生成训练数据并在数据集中的batch_size示例上运行优化器来为批量训练做准备。 同样,通过从数据集中的batch_size示例生成测试数据来准备测试。 训练针对nb_iters+1迭代进行,每十个迭代中的一个用于测试结果:
def train_batch(batch_size):
   """
   Training step that optimizes the weights
   provided some batch_size X and Y examples from the dataset.
   """
   X, Y = generate_x_y_data(isTrain=True, batch_size=batch_size)
   feed_dict = {enc_inp[t]: X[t] for t in range(len(enc_inp))}
   feed_dict.update({expected_sparse_output[t]: Y[t] for t in range(len(expected_sparse_output))})
   _, loss_t = sess.run([train_op, loss], feed_dict)
   return loss_t

def test_batch(batch_size):
   """
   Test step, does NOT optimizes. Weights are frozen by not
   doing sess.run on the train_op.
   """
   X, Y = generate_x_y_data(isTrain=False, batch_size=batch_size)
   feed_dict = {enc_inp[t]: X[t] for t in range(len(enc_inp))}
   feed_dict.update({expected_sparse_output[t]: Y[t] for t in range(len(expected_sparse_output))})
   loss_t = sess.run([loss], feed_dict)
   return loss_t[0]

# Training
train_losses = []
test_losses = []
sess.run(tf.global_variables_initializer())

for t in range(nb_iters+1):
   train_loss = train_batch(batch_size)
   train_losses.append(train_loss)   
   if t % 10 == 0:
       # Tester
       test_loss = test_batch(batch_size)
       test_losses.append(test_loss)
       print("Step {}/{}, train loss: {}, \tTEST loss: {}".format(t, nb_iters, train_loss, test_loss))
print("Fin. train loss: {}, \tTEST loss: {}".format(train_loss, test_loss))
  1. 可视化n_predictions结果。 我们将以黄色形象化nb_predictions = 5预测,以x形象化蓝色的实际值ix。 请注意,预测从直方图中的最后一个蓝点开始,从视觉上,您可以观察到,即使这个简单的模型也相当准确:
# Test
nb_predictions = 5
print("Let's visualize {} predictions with our signals:".format(nb_predictions))
X, Y = generate_x_y_data(isTrain=False, batch_size=nb_predictions)
feed_dict = {enc_inp[t]: X[t] for t in range(seq_length)}
outputs = np.array(sess.run([reshaped_outputs], feed_dict)[0])
for j in range(nb_predictions):
   plt.figure(figsize=(12, 3))   
   for k in range(output_dim):
       past = X[:,j,k]
       expected = Y[:,j,k]
       pred = outputs[:,j,k]       
       label1 = "Seen (past) values" if k==0 else "_nolegend_"
       label2 = "True future values" if k==0 else "_nolegend_"
       label3 = "Predictions" if k==0 else "_nolegend_"
       plt.plot(range(len(past)), past, "o--b", label=label1)
       plt.plot(range(len(past), len(expected)+len(past)), expected, "x--b", label=label2)
       plt.plot(range(len(past), len(pred)+len(past)), pred, "o--y", label=label3)   
   plt.legend(loc='best')
   plt.title("Predictions v.s. true values")
   plt.show()

我们得到的结果如下:

比特币价值预测的一个例子

工作原理

带有 GRU 基本单元的编码器-解码器层堆叠 RNN 用于预测比特币值。 RNN 非常擅长学习序列,即使使用基于 2 层和 12 个 GRU 单元的简单模型,比特币的预测确实相当准确。 当然,此预测代码并非鼓励您投资比特币,而只是讨论深度学习方法。 而且,需要更多的实验来验证我们是否存在数据过拟合的情况。

更多

预测股市价值是一个不错的 RNN 应用,并且有许多方便的包,例如:

多对一和多对多 RNN 示例

在本秘籍中,我们通过提供 RNN 映射的各种示例来总结与 RNN 讨论过的内容。 为了简单起见,我们将采用 Keras 并演示如何编写一对一,一对多,多对一和多对多映射,如下图所示:

RNN 序列的一个例子

操作步骤

我们按以下步骤进行:

  1. 如果要创建一对一映射,则这不是 RNN,而是密集层。 假设已经定义了一个模型,并且您想添加一个密集网络。 然后可以在 Keras 中轻松实现:
model = Sequential()
model.add(Dense(output_size, input_shape=input_shape))
  1. 如果要创建一对多选项,可以使用RepeatVector(...)实现。 请注意,return_sequences是一个布尔值,用于决定是返回输出序列中的最后一个输出还是完整序列:
model = Sequential()
model.add(RepeatVector(number_of_times,input_shape=input_shape)) 
model.add(LSTM(output_size, return_sequences=True))
  1. 如果要创建多对一选项,则可以使用以下 LSTM 代码段实现:
model = Sequential()
model.add(LSTM(1, input_shape=(timesteps, data_dim)))
  1. 如果要创建多对多选项,当输入和输出的长度与循环步数匹配时,可以使用以下 LSTM 代码段来实现:
model = Sequential() 
model.add(LSTM(1, input_shape=(timesteps, data_dim), return_sequences=True))

工作原理

Keras 使您可以轻松编写各种形状的 RNN,包括一对一,一对多,多对一和多对多映射。 上面的示例说明了用 Keras 实现它们有多么容易。

七、无监督学习

到目前为止,我们在本书中涵盖的所有模型都是基于监督学习范式的。 训练数据集包括输入和该输入的所需标签。 相反,本章重点介绍无监督的学习范式。 本章将包括以下主题:

  • 主成分分析
  • K 均值聚类
  • 自组织图
  • 受限玻尔兹曼机
  • 使用 RBM 的推荐系统
  • 用于情感检测的 DBN

介绍

在机器学习中,存在三种不同的学习范式:监督学习,无监督学习和强化学习。

监督学习(也称为与老师一起学习)中,向网络提供输入和各自所需的输出。 例如,在 MNIST 数据集中,手写数字的每个图像都有一个标签,表示与之关联的数字值。

强化学习(也称为与批评家学习)中,没有为网络提供所需的输出; 相反,环境会提供奖励或惩罚方面的反馈。 当其输出正确时,环境奖励网络,而当输出不正确时,环境对其进行惩罚。

无监督学习(也称为无老师学习)中,没有向网络提供有关其输出的信息。 网络接收输入,但是既不提供期望的输出,也不提供来自环境的奖励; 网络自己学习输入的隐藏结构。 无监督学习非常有用,因为正常情况下可用的数据没有标签。 它可以用于模式识别,特征提取,数据聚类和降维等任务。 在本章和下一章中,您将学习基于无监督学习的不同机器学习和 NN 技术。

主成分分析

主成分分析PCA)是用于降维的最流行的多元统计技术。 它分析了由几个因变量组成的训练数据,这些因变量通常是相互关联的,并以一组称为主成分的新正交变量的形式从训练数据中提取重要信息。 我们可以使用两种方法执行 PCA – 特征值分解奇异值分解SVD)。

准备

PCA 将n维输入数据还原为r维输入数据,其中r < n。 简单来说,PCA 涉及平移原点并执行轴的旋转,以使其中一个轴(主轴)与数据点的差异最小。 通过执行此变换,然后以高方差落下(删除)正交轴,可以从原始数据集中获得降维数据集。 在这里,我们采用 SVD 方法降低 PCA 尺寸。 考虑Xn维数据,具有p个点X[p,n]。 任何实数(p × n)矩阵都可以分解为:

X = U ∑ V^T

在这里, UV是正交矩阵(即U · U^T = V^T · V = E),大小分别为p × nn × n是大小为n × n的对角矩阵。 接下来,将矩阵切成r列,得到∑[r]; 使用UV,我们找到了降维数据点Y[r]

Y[r] = U ∑[r]

此处提供的代码已从以下 GitHub 链接进行改编

操作步骤

我们按以下步骤进行操作:

  1. 导入所需的模块。 我们肯定会使用 TensorFlow; 我们还需要numpy进行一些基本矩阵计算,并需要matplotlibmpl_toolkitseaborn进行绘图:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import seaborn as sns
%matplotlib inline
  1. 我们加载数据集-我们将使用我们最喜欢的 MNIST 数据集:
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/")
  1. 我们定义了一个TF_PCA类,它将实现我们的所有工作。 该类的初始化如下:
def __init__(self, data,  dtype=tf.float32):
        self._data = data
        self._dtype = dtype
        self._graph = None
        self._X = None
        self._u = None
        self._singular_values = None
        self._sigma = None)
  1. 给定输入数据的 SVD 用fit方法计算。 该方法定义了计算图,并执行该计算图以计算奇异值和正交矩阵U。需要self.data来输入占位符self._Xtf.svd以降序返回形状[..., p]ssingular_values)。 我们使用tf.diag将其转换为对角矩阵:
def fit(self):
        self._graph = tf.Graph()
        with self._graph.as_default():
            self._X = tf.placeholder(self._dtype, shape=self._data.shape)
            # Perform SVD
            singular_values, u, _ = tf.svd(self._X)
            # Create sigma matrix
            sigma = tf.diag(singular_values)
        with tf.Session(graph=self._graph) as session:
            self._u, self._singular_values, self._sigma = session.run([u, singular_values, sigma], feed_dict={self._X: self._data})
  1. 现在我们有了sigma矩阵,正交U矩阵和奇异值,我们通过定义reduce方法来计算降维数据。 该方法需要两个输入参数之一n_dimensionskeep_infon_dimensions参数表示我们要保留在降维数据集中的维数。 另一方面,keep_info参数确定我们要保留的信息的百分比(值为 0.8 表示我们要保留 80% 的原始数据)。 该方法创建一个切片 Sigma 矩阵的图,并计算降维数据集Y[r]
def reduce(self, n_dimensions=None, keep_info=None):
        if keep_info:
            # Normalize singular values
            normalized_singular_values = self._singular_values / sum(self._singular_values)
            # information per dimension
            info = np.cumsum(normalized_singular_values)            # Get the first index which is above the given information threshold
           it = iter(idx for idx, value in enumerate(info) if value >= keep_info)
            n_dimensions = next(it) + 1 
       with self.graph.as_default():
            # Cut out the relevant part from sigma
            sigma = tf.slice(self._sigma, [0, 0], [self._data.shape[1], n_dimensions])
            # PCA
            pca = tf.matmul(self._u, sigma)

        with tf.Session(graph=self._graph) as session:
            return session.run(pca, feed_dict={self._X: self._data})
  1. 我们的TF_PCA类已准备就绪。 现在,我们将使用它来将 MNIST 数据从尺寸为 784(28 x 28)的每个输入减少为尺寸为 3 的每个点的新数据。这里,我们仅保留了 10% 的信息以便于查看,但是通常需要保留大约 80% 的信息:
tf_pca.fit()
pca = tf_pca.reduce(keep_info=0.1)  # The reduced dimensions dependent upon the % of information
print('original data shape', mnist.train.images.shape)
print('reduced data shape', pca.shape) 

以下是以下代码的输出:

  1. 现在,让我们在三维空间中绘制 55,000 个数据点:
Set = sns.color_palette("Set2", 10)
color_mapping = {key:value for (key,value) in enumerate(Set)}
colors = list(map(lambda x: color_mapping[x], mnist.train.labels))
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(pca[:, 0], pca[:, 1],pca[:, 2], c=colors)

工作原理

前面的代码对 MNIST 图像执行降维。 每个原始图像的尺寸为28 x 28; 使用 PCA 方法,我们可以将其减小到较小的尺寸。 通常,对于图像数据,降维是必要的。 之所以如此,是因为图像很大,并且包含大量的冗余数据。

更多

TensorFlow 提供了一种称为嵌入的技术,该技术是将对象映射到向量中。 TensorBoard 的嵌入式投影仪允许我们以交互方式可视化模型中的嵌入。 嵌入式投影仪提供了三种降低尺寸的方法:PCA,t-SNE 和自定义。 我们可以使用 TensorBoard 的嵌入投影器实现与上一个类似的结果。 我们需要从tensorflow.contrib.tensorboard.plugins导入projector类,以从tensorflow.contrib.tensorboard.plugins导入projector进行相同的操作。 我们可以通过三个简单的步骤来做到这一点:

  1. 加载要探索其嵌入的数据:
mnist = input_data.read_data_sets('MNIST_data')
images = tf.Variable(mnist.test.images, name='images')
  1. 创建一个metadata文件((metadata文件是制表符分隔的.tsv文件):
with open(metadata, 'w') as metadata_file:
    for row in mnist.test.labels:
        metadata_file.write('%d\n' % row)
  1. 将嵌入内容保存在所需的Log_DIR中:
with tf.Session() as sess:
    saver = tf.train.Saver([images])

    sess.run(images.initializer)
    saver.save(sess, os.path.join(LOG_DIR, 'images.ckpt'))

    config = projector.ProjectorConfig()
    # One can add multiple embeddings.
    embedding = config.embeddings.add()
    embedding.tensor_name = images.name
    # Link this tensor to its metadata file (e.g. labels).
    embedding.metadata_path = metadata
    # Saves a config file that TensorBoard will read during startup.
    projector.visualize_embeddings(tf.summary.FileWriter(LOG_DIR), config)

嵌入已准备就绪,现在可以使用 TensorBoard 看到。 通过 CLI tensorboard --logdir=log启动 TensorBoard,在 Web 浏览器中打开 TensorBoard,然后转到EMBEDDINGS选项卡。 这是使用 PCA 的 TensorBoard 投影,前三个主要成分为轴:

另见

K 均值聚类

顾名思义,K 均值聚类是一种对数据进行聚类的技术,即将数据划分为指定数量的数据点。 这是一种无监督的学习技术。 它通过识别给定数据中的模式来工作。 还记得哈利波特成名的分拣帽子吗? 书中的工作是聚类-将新生(未标记)的学生分成四个不同的类:格兰芬多,拉文克劳,赫奇帕奇和斯莱特林。

人类非常擅长将对象分组在一起。 聚类算法试图为计算机提供类似的功能。 有许多可用的聚类技术,例如“层次”,“贝叶斯”或“局部”。 K 均值聚类属于部分聚类; 它将数据划分为k簇。 每个簇都有一个中心,称为重心。 簇数k必须由用户指定。

K 均值算法以以下方式工作:

  1. 随机选择k个数据点作为初始质心(集群中心)
  2. 将每个数据点分配给最接近的质心; 可以找到接近度的不同方法,最常见的是欧几里得距离
  3. 使用当前簇成员资格重新计算质心,以使平方和的距离减小
  4. 重复最后两个步骤,直到达到收敛

准备

我们将使用 TensorFlow KmeansClustering估计器类来实现 K 均值。 它在这个链接中定义。它创建一个模型来运行 K 均值和推理。 根据 TensorFlow 文档,一旦创建了KmeansClustering类对象,就可以使用以下__init__方法实例化该对象:

__init__(
num_clusters,
model_dir=None,
initial_clusters=RANDOM_INIT,
distance_metric=SQUARED_EUCLIDEAN_DISTANCE,
random_seed=0,
use_mini_batch=True,
mini_batch_steps_per_iteration=1,
kmeans_plus_plus_num_retries=2,
relative_tolerance=None,
config=None
)

TensorFlow 文档对这些参数的定义如下:

Args:
num_clusters: The number of clusters to train.
model_dir: The directory to save the model results and log files.
initial_clusters: Specifies how to initialize the clusters for training. See clustering_ops.kmeans for the possible values.
distance_metric: The distance metric used for clustering. See clustering_ops.kmeans for the possible values.
random_seed: Python integer. Seed for PRNG used to initialize centers.
use_mini_batch: If true, use the mini-batch k-means algorithm. Or else assume full batch.
mini_batch_steps_per_iteration: The number of steps after which the updated cluster centers are synced back to a master copy. See clustering_ops.py for more details.
kmeans_plus_plus_num_retries: For each point that is sampled during kmeans++ initialization, this parameter specifies the number of additional points to draw from the current distribution before selecting the best. If a negative value is specified, a heuristic is used to sample O(log(num_to_sample)) additional points.
relative_tolerance: A relative tolerance of change in the loss between iterations. Stops learning if the loss changes less than this amount. Note that this may not work correctly if use_mini_batch=True.
config: See Estimator.

TensorFlow 支持欧几里得距离和余弦距离作为质心的量度。 TensorFlow KmeansClustering提供了各种与KmeansClustering对象进行交互的方法。 在本秘籍中,我们将使用fit()clusters()predict_clusters_idx()方法:

fit(
 x=None,
 y=None,
 input_fn=None,
 steps=None,
 batch_size=None,
 monitors=None,
 max_steps=None
)

根据 TensorFlow 文档,对于KmeansClustering估计器,我们需要向fit()提供input_fn()cluster方法返回聚类中心,predict_cluster_idx方法返回预测的聚类索引。

操作步骤

这是我们进行秘籍的方法:

  1. 和以前一样,我们从加载必要的模块开始。 我们将像往常一样需要 TensorFlow,NumPy 和 Matplotlib。 在本秘籍中,我们使用的是鸢尾花数据集,该数据集包含三个类别,每个类别有 50 个实例,其中每个类别都代表一种鸢尾花植物。 我们可以从这里下载数据作为.csv文件,也可以使用 sklearn 的数据集模块(scikit-learn) 做任务:
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
# dataset Iris
from sklearn import datasets

%matplotlib inline
  1. 我们加载数据集:
# import some data to play with
iris = datasets.load_iris()
x = iris.data[:, :2] # we only take the first two features.
y = iris.target
  1. 让我们看看该数据集的外观:
# original data without clustering
plt.scatter(hw_frame[:,0], hw_frame[:,1])
plt.xlabel('Sepia Length')
plt.ylabel('Sepia Width')

以下是以下代码的输出:

  1. 我们可以看到在数据中没有明显可见的聚类。 现在我们定义input_fn,它将用于提供fit方法。 我们的输入函数返回一个 TensorFlow 常数,该常数被分配了x的值和形状,并且类型为float
def input_fn():
 return tf.constant(np.array(x), tf.float32, x.shape),None
  1. 现在我们使用KmeansClustering类; 在这里,我们已经知道类的数量为 3,因此我们将num_clusters=3设置为。 通常,我们不知道集群的数量。 在这种情况下,常用的方法是肘部法则
kmeans = tf.contrib.learn.KMeansClustering(num_clusters=3, relative_tolerance=0.0001, random_seed=2) 
kmeans.fit(input_fn=input_fn)
  1. 我们使用clusters()方法找到聚类,并使用predict_cluster_idx()方法为每个输入点分配聚类索引:
clusters = kmeans.clusters()
assignments = list(kmeans.predict_cluster_idex(input_fn=input_fn))
  1. 现在让我们可视化由 K 均值创建的聚类。 为此,我们创建一个包装器函数ScatterPlot,该函数将XY值以及每个数据点的簇和簇索引一起使用:
def ScatterPlot(X, Y, assignments=None, centers=None):
 if assignments is None:
 assignments = [0] * len(X)
 fig = plt.figure(figsize=(14,8))
 cmap = ListedColormap(['red', 'green', 'blue'])
 plt.scatter(X, Y, c=assignments, cmap=cmap)
 if centers is not None:
 plt.scatter(centers[:, 0], centers[:, 1], c=range(len(centers)), 
 marker='+', s=400, cmap=cmap) 
 plt.xlabel('Sepia Length')
 plt.ylabel('Sepia Width')

我们用它来绘制我们的clusters

ScatterPlot(x[:,0], x[:,1], assignments, clusters)

情节如下:

+标记是三个簇的质心。

工作原理

前面的秘籍使用 TensorFlow 的 K 均值聚类估计器将给定数据聚类为聚类。 在这里,由于我们知道集群的数量,我们决定保留num_clusters=3,但是在大多数情况下,如果使用未标记的数据,则永远无法确定存在多少集群。 可以使用弯头法确定最佳簇数。 该方法基于以下原则:我们应选择能减少平方误差和SSE)距离的簇数。 如果k是簇数,则随着k增加,SSE 减少,SSE = 0; 当k等于数据点数时,每个点都是其自己的簇。 我们想要一个k较低的值,以使 SSE 也较低。 在 TensorFlow 中,我们可以使用KmeansClustering类中定义的score()方法找到 SSE; 该方法将距离的总和返回到最近的聚类:

sum_distances = kmeans.score(input_fn=input_fn, steps=100)

对于鸢尾数据,如果我们针对不同的k值绘制 SSE,则可以看到对于k = 3而言,SSE 的方差最高; 之后,它开始减小,因此肘点为k = 3

更多

K 均值聚类非常流行,因为它快速,简单且健壮。 它还有一些缺点:最大的缺点是用户必须指定簇的数量。 其次,该算法不能保证全局最优。 第三,它对异常值非常敏感。

另见

自组织图

自组织映射SOM),有时也称为 Kohonen 网络胜者通吃单元WTU),是一种非常特殊的神经网络,受人脑的独特特征驱动。 在我们的大脑中,不同的感觉输入以拓扑有序的方式表示。 与其他神经网络不同,神经元并非都通过权重相互连接,而是会影响彼此的学习。 SOM 的最重要方面是神经元以拓扑方式表示学习的输入。

在 SOM 中,神经元通常放置在(1D 或 2D)晶格的节点上。 更大的尺寸也是可能的,但实际上很少使用。 晶格中的每个神经元都通过权重矩阵连接到所有输入单元。 在这里,您可以看到一个具有3 x 4(12 个神经元)和七个输入的 SOM。 为了清楚起见,仅显示将所有输入连接到一个神经元的权重向量。 在这种情况下,每个神经元将具有七个元素,从而形成大小为(12 x 7)的组合权重矩阵:

SOM 通过竞争性学习来学习。 可以将其视为 PCA 的非线性概括,因此,像 PCA 一样,可以用于降维。

准备

为了实现 SOM,让我们首先了解它是如何工作的。 第一步,将网络的权重初始化为某个随机值,或者通过从输入中获取随机样本进行初始化。 占据晶格中空间的每个神经元将被分配特定的位置。 现在,作为输入出现,与输入距离最小的神经元被宣布为 Winner(WTU)。 这是通过测量所有神经元的权重向量(W)和输入向量(X)之间的距离来完成的:

在此,d[j]是神经元j的权重与输入X的距离。 最小d值的神经元是赢家。

接下来,以一种方式调整获胜神经元及其相邻神经元的权重,以确保如果下次出现相同的输入,则相同的神经元将成为获胜者。 为了确定哪些相邻神经元需要修改,网络使用邻域函数Λ(r); 通常,选择高斯墨西哥帽函数作为邻域函数。 邻域函数在数学上表示如下:

在这里,σ是神经元的时间依赖性半径,d是其与获胜神经元的距离:

邻域函数的另一个重要属性是其半径随时间减小。 结果,一开始,许多相邻神经元的权重被修改,但是随着网络的学习,最终在学习过程中,一些神经元的权重(有时只有一个或没有)被修改。 权重变化由以下公式给出:

dW = η * Λ(X - W)

我们继续所有输入的过程,并重复给定的迭代次数。 随着迭代的进行,我们将学习率和半径减小一个取决于迭代次数的因素。

操作步骤

我们按以下步骤进行:

  1. 与往常一样,我们从导入必要的模块开始:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
  1. 接下来,我们声明一个类 WTU,它将执行所有任务。 用m x n 2D SOM 格的大小,dim输入数据中的维数以及迭代总数来实例化该类:
def __init__(self, m, n, dim, num_iterations, eta = 0.5, sigma = None):
    """
    m x n : The dimension of 2D lattice in which neurons are     arranged
    dim : Dimension of input training data
    num_iterations: Total number of training iterations
    eta : Learning rate
    sigma: The radius of neighbourhood function.
    """
    self._m = m
    self._n = n
    self._neighbourhood = []
    self._topography = []
    self._num_iterations = int(num_iterations) 
    self._learned = False
  1. __init__本身中,我们定义了计算图和会话。
  2. 如果网络未提供任何sigma值,它将采用默认值,该值通常是 SOM 晶格最大尺寸的一半:
if sigma is None:
    sigma = max(m,n)/2.0 # Constant radius
else:
    sigma = float(sigma)
  1. 接下来,在图中,我们声明权重矩阵的变量,输入的占位符以及计算获胜者并更新其及其邻居权重的计算步骤。 由于 SOM 提供了地形图,因此我们还添加了操作以获得神经元的地形位置:
self._graph = tf.Graph()

# Build Computation Graph of SOM
 with self._graph.as_default():
# Weight Matrix and the topography of neurons
    self._W = tf.Variable(tf.random_normal([m*n, dim], seed = 0))
    self._topography = tf.constant(np.array(list(self._neuron_location(m, n))))

    # Placeholders for training data
    self._X = tf.placeholder('float', [dim])

    # Placeholder to keep track of number of iterations
    self._iter = tf.placeholder('float')

    # Finding the Winner and its location
    d = tf.sqrt(tf.reduce_sum(tf.pow(self._W - tf.stack([self._X 
          for i in range(m*n)]),2),1))
    self.WTU_idx = tf.argmin(d,0)
    slice_start = tf.pad(tf.reshape(self.WTU_idx, [1]),np.array([[0,1]]))
    self.WTU_loc = tf.reshape(tf.slice(self._topography, slice_start,             [1,2]), [2])
    # Change learning rate and radius as a function of iterations
    learning_rate = 1 - self._iter/self._num_iterations
    _eta_new = eta * learning_rate
    _sigma_new = sigma * learning_rate

    # Calculating Neighbourhood function
    distance_square = tf.reduce_sum(tf.pow(tf.subtract(
    self._topography, tf.stack([self.WTU_loc for i in range(m * n)])), 2), 1)
    neighbourhood_func = tf.exp(tf.negative(tf.div(tf.cast(
distance_square, "float32"), tf.pow(_sigma_new, 2))))

    # multiply learning rate with neighbourhood func
    eta_into_Gamma = tf.multiply(_eta_new, neighbourhood_func)

    # Shape it so that it can be multiplied to calculate dW
    weight_multiplier = tf.stack([tf.tile(tf.slice(
eta_into_Gamma, np.array([i]), np.array([1])), [dim])
for i in range(m * n)])
    delta_W = tf.multiply(weight_multiplier,
tf.subtract(tf.stack([self._X for i in range(m * n)]),self._W))
    new_W = self._W + delta_W
    self._training = tf.assign(self._W,new_W)

   # Initialize All variables
   init = tf.global_variables_initializer()
   self._sess = tf.Session()
   self._sess.run(init)
  1. 我们为该类定义一个fit方法,该方法执行在该类的默认图中声明的训练操作。 该方法还计算质心网格:
def fit(self, X):
 """
 Function to carry out training
 """
 for i in range(self._num_iterations):
   for x in X:
       self._sess.run(self._training, feed_dict= {self._X:x, self._iter: i})

 # Store a centroid grid for easy retreival
 centroid_grid = [[] for i in range(self._m)]
 self._Wts = list(self._sess.run(self._W))
 self._locations = list(self._sess.run(self._topography))
 for i, loc in enumerate(self._locations):
      centroid_grid[loc[0]].append(self._Wts[i])
 self._centroid_grid = centroid_grid

 self._learned = True
  1. 我们定义一个函数来确定获胜神经元在 2D 晶格中的索引和位置:
def winner(self, x):
    idx = self._sess.run([self.WTU_idx,self.WTU_loc], feed_dict = {self._X:x})
    return idx
  1. 我们定义一些更多的辅助函数,以执行晶格中神经元的 2D 映射并将输入向量映射到 2D 晶格中的相关神经元:
def _neuron_location(self,m,n):
    """
    Function to generate the 2D lattice of neurons
    """
    for i in range(m):
       for j in range(n):
           yield np.array([i,j])

def get_centroids(self):
    """
    Function to return a list of 'm' lists, with each inner     list containing the 'n' corresponding centroid locations     as 1-D NumPy arrays.
    """
    if not self._learned:
       raise ValueError("SOM not trained yet")
    return self._centroid_grid
def map_vects(self, X):
    """
    Function to map each input vector to the relevant neuron         in the lattice
    """
    if not self._learned:
        raise ValueError("SOM not trained yet")
    to_return = []
    for vect in X:
       min_index = min([i for i in range(len(self._Wts))],
       key=lambda x: np.linalg.norm(vect -
self._Wts[x]))
       to_return.append(self._locations[min_index])
return to_return
  1. 现在我们的 WTU 类已经准备好,我们从.csv文件中读取数据并对其进行规范化:
def normalize(df):
    result = df.copy()
    for feature_name in df.columns:
       max_value = df[feature_name].max()
       min_value = df[feature_name].min()
       result[feature_name] = (df[feature_name] - min_value) / (max_value - min_value)
    return result

# Reading input data from file
import pandas as pd
df = pd.read_csv('colors.csv') # The last column of data file is a label
data = normalize(df[['R', 'G', 'B']]).values
name = df['Color-Name'].values
n_dim = len(df.columns) - 1

# Data for Training
colors = data
color_names = name
  1. 最后,我们使用我们的类执行降维并将其布置在美丽的地形图中:
som = WTU(30, 30, n_dim, 400, sigma=10.0)
som.fit(colors)

# Get output grid
image_grid = som.get_centroids()

# Map colours to their closest neurons
mapped = som.map_vects(colors)

# Plot
plt.imshow(image_grid)
plt.title('Color Grid SOM')
for i, m in enumerate(mapped):
     plt.text(m[1], m[0], color_names[i], ha='center', va='center', bbox=dict(facecolor='white', alpha=0.5, lw=0))

情节如下:

工作原理

SOM 在计算上很昂贵,因此对于非常大的数据集并没有真正的用处。 尽管如此,它们仍然易于理解,并且可以很好地找到输入数据之间的相似性。 因此,它们已被用于图像分割和确定 NLP 中的单词相似度图。

另见

受限玻尔兹曼机

受限玻尔兹曼机RBM)是两层神经网络,第一层称为可见层,第二层称为隐藏层。 它们被称为浅层神经网络,因为它们只有两层深。 它们最初是由 1986 年由保罗·斯莫伦斯基(Paul Smolensky)提出的(他称其为 Harmony Networks),后来由 Geoffrey Hinton 提出,于 2006 年提出了对比发散CD)作为训练他们的方法。 可见层中的所有神经元都与隐藏层中的所有神经元相连,但是存在限制-同一层中没有神经元可以连接。 所有神经元本质上都是二进制的:

资料来源:Qwertyus 自己的作品,CC BY-SA 3.0

RBM 可用于降维,特征提取和协作过滤。 RBM 中的训练可分为三个部分:前进,后退和比较。

准备

让我们看看制作 RBM 所需的表达式:

正向传递:可见单元(V)上的信息通过权重(W)和偏差(c)传递给隐藏的对象单元(h[0])。 隐藏单元是否可以触发取决于随机概率(σ是随机概率):

p(h[i]|v[0]) = σ(V^T · W + c)[i]

向后传递:隐藏的单元表示(h[0])然后通过相同的权重W但不同的偏置c传递回可见单元,它们在其中重构输入。 再次,对输入进行采样:

p(v[i]|h[0]) = σ(W^T · h[0] + b)[i]*

将这两个遍重复 k 步或直到达到收敛。 根据研究人员的说法,k = 1给出了很好的结果,因此我们将保持k = 1

可见向量V和隐藏向量的联合构型具有如下能量:

自由能还与每个可见向量V相关,为与具有V的所有构型具有相同概率的单个配置所需的能量:

使用对比度发散目标函数,即Mean(F(Voriginal))- Mean(F(Vreconstructed)),权重的变化由此给出:

在此,η是学习率。 对于偏差bc存在相似的表达式。

操作步骤

我们按以下步骤进行:

  1. 导入模块:
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
%matplotlib inline
  1. 声明 RBM 类,它将完成主要任务。 __init__将建立完整的图,正向和反向传递以及目标函数; 我们将使用 TensorFlow 内置的优化器来更新权重和偏差:
class RBM(object):
    def __init__(self, m, n):
        """
        m: Number of neurons in visible layer
        n: number of neurons in hidden layer
        """
        self._m = m
        self._n = n
        # Create the Computational graph
        # Weights and biases
        self._W = tf.Variable(tf.random_normal(shape=(self._m,self._n)))
        self._c = tf.Variable(np.zeros(self._n).astype(np.float32)) #bias for hidden layer
        self._b = tf.Variable(np.zeros(self._m).astype(np.float32)) #bias for Visible layer
         # Placeholder for inputs
        self._X = tf.placeholder('float', [None, self._m])
         # Forward Pass
        _h = tf.nn.sigmoid(tf.matmul(self._X, self._W) + self._c)
        self.h = tf.nn.relu(tf.sign(_h -         tf.random_uniform(tf.shape(_h))))
        #Backward pass
        _v = tf.nn.sigmoid(tf.matmul(self.h, tf.transpose(self._W)) + self._b)
        self.V = tf.nn.relu(tf.sign(_v - tf.random_uniform(tf.shape(_v))))
        # Objective Function
        objective = tf.reduce_mean(self.free_energy(self._X)) - tf.reduce_mean(
self.free_energy(self.V))
        self._train_op = tf.train.GradientDescentOptimizer(1e-3).minimize(objective)
        # Cross entropy cost
        reconstructed_input = self.one_pass(self._X)
        self.cost = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(
labels=self._X, logits=reconstructed_input))
  1. 我们在RBM类中定义fit()方法。 在__init__中声明了所有操作后,训练只是在会话中调用train_op。 我们使用批量训练:
 def fit(self, X, epochs = 1, batch_size = 100):
        N, D = X.shape
        num_batches = N // batch_size

        obj = []
        for i in range(epochs):
            #X = shuffle(X)
            for j in range(num_batches):
                batch = X[j * batch_size: (j * batch_size + batch_size)]
                _, ob = self.session.run([self._train_op,self.cost ], feed_dict={self._X: batch})
                if j % 10 == 0:
                    print('training epoch {0} cost {1}'.format(j,ob)) 
                obj.append(ob)
        return obj
  1. 还有其他辅助函数可计算对率误差并从网络返回重建的图像:
def set_session(self, session):
    self.session = session

def free_energy(self, V):
    b = tf.reshape(self._b, (self._m, 1))
    term_1 = -tf.matmul(V,b)
    term_1 = tf.reshape(term_1, (-1,))
    term_2 = -tf.reduce_sum(tf.nn.softplus(tf.matmul(V,self._W) +
        self._c))
    return term_1 + term_2

def one_pass(self, X):
    h = tf.nn.sigmoid(tf.matmul(X, self._W) + self._c)
    return tf.matmul(h, tf.transpose(self._W)) + self._b

def reconstruct(self,X):
    x = tf.nn.sigmoid(self.one_pass(X))
    return self.session.run(x, feed_dict={self._X: X})

  1. 我们加载 MNIST 数据集:
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
  1. 接下来,我们在 MNIST 数据集上训练RBM
Xtrain = trX.astype(np.float32)
Xtest = teX.astype(np.float32)
_, m = Xtrain.shape
rbm = RBM(m, 100)
#Initialize all variables
init = tf.global_variables_initializer()
with tf.Session() as sess:
    sess.run(init)
    rbm.set_session(sess)
    err = rbm.fit(Xtrain)
    out = rbm.reconstruct(Xest[0:100])  # Let us reconstruct Test Data
  1. 不同周期的函数误差:

工作原理

由于其具有重建图像的能力,RBM 可用于从现有数据中生成更多数据。 通过制作一个小的助手绘图代码,我们可以看到原始和重建的 MNIST 图像:

row, col = 2, 8
idx = np.random.randint(0, 100, row * col // 2)
f, axarr = plt.subplots(row, col, sharex=True, sharey=True, figsize=(20,4))
for fig, row in zip([Xtest_noisy,out], axarr):
    for i,ax in zip(idx,row):
        ax.imshow(fig[i].reshape((28, 28)), cmap='Greys_r')
        ax.get_xaxis().set_visible(False)
        ax.get_yaxis().set_visible(False)

我们得到的结果如下:

另见

使用 RBM 的推荐系统

网上零售商广泛使用推荐系统向客户推荐产品。 例如,亚马逊会告诉您购买此商品的其他客户对什么感兴趣,或者 Netflix 根据您所观看的内容以及有相同兴趣的其他 Netflix 用户所观看的内容推荐电视连续剧和电影。 这些推荐器系统在协作筛选的基础上工作。 在协作过滤中,系统根据用户的过去行为来构建模型。 我们将使用上一个秘籍中的 RBM 构建一个使用协作过滤来推荐电影的推荐器系统。 这项工作中的一个重要挑战是,大多数用户不会对所有产品/电影进行评分,因此大多数数据都将丢失。 如果有 M 个产品和 N 个用户,则我们需要构建一个数组N x M,其中包含用户的已知等级并将所有未知值设为零。

准备

为了使用协作过滤创建推荐系统,我们需要修改数据。 作为说明,我们将使用来自这里的电影数据集。 数据由两个.dat文件组成:movies.datratings.datmovies.dat文件包含 3 列:3883 个电影的 MovieID,Title 和 Genre。 ratings.dat文件包含四列:UserID,MovieID,Rating 和 Time。 我们需要合并这两个数据文件,以便能够构建一个数组,其中对于每个用户,我们对所有 3,883 部电影都有一个评分。 问题在于用户通常不会对所有电影进行评级,因此我们仅对某些电影进行非零(标准化)评级。 其余部分设为零,因此不会对隐藏层有所贡献。

操作步骤

  1. 我们将使用在先前秘籍中创建的RBM类。 让我们定义我们的 RBM 网络; 可见单元的数量将是电影的数量,在我们的示例中为 3883(movies_df是包含movies.dat文件中的数据的数据帧):
m = len(movies_df)  # Number of visible units
n = 20  # Number of Hidden units
recommender = rbm.RBM(m,n)
  1. 我们使用 Pandas 合并和groupby命令创建了一个列表trX,该列表包含大约 1,000 个用户的规范化电影评分。 列表的大小为1000 x 3883。我们使用它来训练我们的 RBM:
Xtrain = np.array(trX) 
init = tf.global_variables_initializer()
with tf.Session() as sess:
 sess.run(init)
 recommender.set_session(sess)
 err = recommender.fit(Xtrain, epochs=10)
  1. 每个周期的跨逻辑误差减少:

  1. 网络现已接受训练; 我们使用它为索引为 150 的随机用户(可能是任何现有用户)获得推荐:
user_index = 150
x = np.array([Xtrain[user_index, :]])
init = tf.global_variables_initializer()
with tf.Session() as sess:
 sess.run(init)
 recommender.set_session(sess)
 out = recommender.reconstruct(x.astype(np.float32))
  1. 结果与现有数据帧合并,我们可以看到该用户的推荐分数:

更多

杰弗里·欣顿(Geoffrey Hinton)教授领导的多伦多大学团队赢得了 Netflix 最佳协作过滤竞赛的冠军,该协作过滤使用 RBM 来预测电影的用户收视率。 可以从他们的论文中获取其工作的详细信息

一个 RBM 的隐藏单元的输出可以馈送到另一个 RBM 的可见单元,可以重复此过程以形成 RBM 的栈。 这导致栈式 RBM 。 假定不存在其他堆叠式 RBM,则对其进行独立训练。 大量栈式 RBM 构成了深度信念网络(DBN)。 可以使用有监督或无监督的训练来训练 DBN。 您将在下一个秘籍中了解有关它们的更多信息。

用于情感检测的 DBN

在本秘籍中,我们将学习如何首先堆叠 RBM 来制作 DBN,然后训练它来检测情感。 秘籍中有趣的部分是我们采用了两种不同的学习范例:首先,我们使用无监督学习对 RBM 进行了预训练,最后,我们有了一个 MLP 层,该层是使用监督学习进行了训练的。

准备

我们使用已经在秘籍受限玻尔兹曼机中创建的 RBM 类,只需进行一次更改即可,现在无需在训练后重建图像。 取而代之的是,我们栈式 RBM 将仅将数据转发至 DBN 的最后一个 MLP 层。 这是通过从类中删除reconstruct()函数并将其替换为rbm_output()函数来实现的:

def rbm_output(self,X):
    x = tf.nn.sigmoid(tf.matmul(X, self._W) + self._c)
    return self.session.run(x, feed_dict={self._X: X})

对于数据,我们考虑了 Kaggle 面部表情识别数据,该数据可从这里获得。 此处给出的数据描述为:

数据由48 x 48像素的面部灰度图像组成。 面部已自动注册,因此面部或多或少居中,并且在每个图像中占据大约相同的空间量。 任务是根据面部表情中显示的情感将每个面孔分类为七个类别之一(0 为愤怒,1 恶心,2 为恐惧,3 为快乐,4 为悲伤,5 为惊奇,6 为中性) 。

train.csv包含两列,“情感”和“像素”。 “情感”列包含图像中存在的情感的数字代码,范围从 0 到 6(含)。 “像素”列包含每个图像用引号引起来的字符串。 该字符串的内容是按行主要顺序分隔的像素值。 test.csv仅包含“像素”列,您的任务是预测情感列。

训练集包含 28,709 个示例。 用于排行榜的公共测试集包含 3,589 个示例。 最终测试集用于确定比赛的获胜者,另外还有 3,589 个示例。

该数据集由 Pierre-Luc Carrier 和 Aaron Courville 进行,是正在进行的研究项目的一部分。 他们为研讨会的组织者提供了他们数据集的初步版本,供比赛使用。

完整的数据合而为一。 名为fer2013.csvcsv文件。 我们从中分离出训练,验证和测试数据:

data = pd.read_csv('data/fer2013.csv')
tr_data = data[data.Usage == "Training"]
test_data = data[data.Usage == "PublicTest"]
mask = np.random.rand(len(tr_data)) < 0.8
train_data = tr_data[mask]
val_data = tr_data[~mask]

我们将需要预处理数据,即将像素和情感标签分开。 为此,我们制作了两个函数dense_to_one_hot (),它对标签执行了单热编码。 第二个函数是preprocess_data(),它将单个像素分离为一个数组。 在这两个函数的帮助下,我们生成了训练,验证和测试数据集的输入特征和标签:

def dense_to_one_hot(labels_dense, num_classes):
     num_labels = labels_dense.shape[0]
     index_offset = np.arange(num_labels) * num_classes
     labels_one_hot = np.zeros((num_labels, num_classes))
     labels_one_hot.flat[index_offset + labels_dense.ravel()] = 1
     return labels_one_hot
def preprocess_data(dataframe):
     pixels_values = dataframe.pixels.str.split(" ").tolist()
     pixels_values = pd.DataFrame(pixels_values, dtype=int)
     images = pixels_values.values
     images = images.astype(np.float32)
     images = np.multiply(images, 1.0/255.0)
     labels_flat = dataframe["emotion"].values.ravel()
     labels_count = np.unique(labels_flat).shape[0]
     labels = dense_to_one_hot(labels_flat, labels_count)
     labels = labels.astype(np.uint8)
     return images, labels

使用前面代码中定义的函数,我们以训练所需的格式获取数据。 基于本文针对 MNIST 提到的相似原理,我们构建了情感检测 DBN

操作步骤

我们按以下步骤进行:

  1. 我们需要导入标准模块 TensorFlow,NumPy 和 Pandas,以读取.csv文件和 Matplolib:
import tensorflow as tf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
  1. 训练,验证和测试数据是使用辅助函数获得的:
X_train, Y_train = preprocess_data(train_data)
X_val, Y_val = preprocess_data(val_data)
X_test, Y_test = preprocess_data(test_data)
  1. 让我们来探讨一下我们的数据。 我们绘制平均图像并找到每个训练,验证和测试数据集中的图像数量:
# Explore Data
mean_image = X_train.mean(axis=0)
std_image = np.std(X_train, axis=0)
print("Training Data set has {} images".format(len(X_train)))
print("Validation Data set has {} images".format(len(X_val)))
print("Test Data set has {} images".format(len(X_test)))
plt.imshow(mean_image.reshape(48,48), cmap='gray')

我们得到的结果如下:

  1. 我们还会看到训练样本中的图像及其各自的标签:
classes = ['angry','disgust','fear','happy','sad','surprise','neutral']
num_classes = len(classes)
samples_per_class = 7
for y,cls in enumerate(classes):
     idxs = np.flatnonzero(np.argmax(Y_train, axis =1) == y)
     idxs = np.random.choice(idxs, samples_per_class, replace=False)
     for i, idx in enumerate(idxs):
         plt_idx = i * num_classes + y + 1
         plt.subplot(samples_per_class, num_classes, plt_idx)
         plt.imshow(X_train[idx].reshape(48,48), cmap='gray') #pixel height and width
         plt.axis('off')
         if i == 0:
             plt.title(cls)
plt.show()

情节如下:

  1. 接下来,我们定义 RBM 栈; 每个 RBM 都将先前 RBM 的输出作为其输入:
RBM_hidden_sizes = [1500, 700, 400] #create 4 layers of RBM with size 1500, 700, 400 and 100
#Set input as training data
inpX = X_train
#Create list to hold our RBMs
rbm_list = []
#Size of inputs is the number of inputs in the training set
input_size = inpX.shape[1]
#For each RBM we want to generate
for i, size in enumerate(RBM_hidden_sizes):
     print ('RBM: ',i,' ',input_size,'->', size)
     rbm_list.append(RBM(input_size, size))
     input_size = size

这将生成三个 RBM:第一个 RBM 具有 2304(48×48)个输入和 1500 个隐藏单元,第二个 RBM 具有 1500 个输入和 700 个隐藏单元,最后第三个 RBM 具有 700 个输入和 400 个隐藏单元。

  1. 我们逐一训练每个 RBM。 该技术也称为贪婪训练。 在原始论文中,用于在 MNIST 上训练每个 RBM 的周期数是 30,因此在这里,增加周期也应会改善网络的表现:
# Greedy wise training of RBMs
init = tf.global_variables_initializer()
for rbm in rbm_list:
     print ('New RBM:')
     #Train a new one
     with tf.Session() as sess:
         sess.run(init)
         rbm.set_session(sess)
         err = rbm.fit(inpX, 5)
         inpX_n = rbm.rbm_output(inpX)
         print(inpX_n.shape)
         inpX = inpX_n
  1. 我们定义一个DBN类。 在类中,我们用三层 RBM 和另外两层 MLP 构建完整的 DBN。 从预训练的 RBM 中加载 RBM 层的权重。 我们还声明了训练和预测 DBN 的方法; 为了进行微调,网络尝试最小化均方损失函数:
class DBN(object):

     def __init__(self, sizes, X, Y, eta = 0.001, momentum = 0.0, epochs = 10, batch_size = 100):
         #Initialize hyperparameters
         self._sizes = sizes
         print(self._sizes)
         self._sizes.append(1000) # size of the first FC layer
         self._X = X
         self._Y = Y
         self.N = len(X)
         self.w_list = []
         self.c_list = []
         self._learning_rate = eta
         self._momentum = momentum
         self._epochs = epochs
         self._batchsize = batch_size
         input_size = X.shape[1]

         #initialization loop
         for size in self._sizes + [Y.shape[1]]:
             #Define upper limit for the uniform distribution range
             max_range = 4 * math.sqrt(6\. / (input_size + size))

             #Initialize weights through a random uniform distribution
             self.w_list.append(
             np.random.uniform( -max_range, max_range, [input_size,         size]).astype(np.float32))

             #Initialize bias as zeroes
             self.c_list.append(np.zeros([size], np.float32))
             input_size = size

         # Build DBN
         #Create placeholders for input, weights, biases, output
         self._a = [None] * (len(self._sizes) + 2)
         self._w = [None] * (len(self._sizes) + 1)
         self._c = [None] * (len(self._sizes) + 1)
         self._a[0] = tf.placeholder("float", [None, self._X.shape[1]])
         self.y = tf.placeholder("float", [None, self._Y.shape[1]])

         #Define variables and activation function
         for i in range(len(self._sizes) + 1):
             self._w[i] = tf.Variable(self.w_list[i])
             self._c[i] = tf.Variable(self.c_list[i])
         for i in range(1, len(self._sizes) + 2):
             self._a[i] = tf.nn.sigmoid(tf.matmul(self._a[i - 1], self._w[i - 1]) + self._c[i - 1])

         #Define the cost function
         cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=self.y, logits= self._a[-1]))
         #cost = tf.reduce_mean(tf.square(self._a[-1] - self.y))

         #Define the training operation (Momentum Optimizer minimizing the Cost function)
         self.train_op = tf.train.AdamOptimizer(learning_rate=self._learning_rate).minimize(cost)

         #Prediction operation
         self.predict_op = tf.argmax(self._a[-1], 1)

     #load data from rbm
     def load_from_rbms(self, dbn_sizes,rbm_list):
         #Check if expected sizes are correct
         assert len(dbn_sizes) == len(self._sizes)

         for i in range(len(self._sizes)):
             #Check if for each RBN the expected sizes are correct
             assert dbn_sizes[i] == self._sizes[i]

         #If everything is correct, bring over the weights and biases
         for i in range(len(self._sizes)-1):
             self.w_list[i] = rbm_list[i]._W
             self.c_list[i] = rbm_list[i]._c

     def set_session(self, session):
         self.session = session

    #Training method
     def train(self, val_x, val_y):
         #For each epoch
         num_batches = self.N // self._batchsize

         batch_size = self._batchsize
         for i in range(self._epochs):
             #For each step
             for j in range(num_batches):
                 batch = self._X[j * batch_size: (j * batch_size + batch_size)]
                 batch_label = self._Y[j * batch_size: (j * batch_size + batch_size)]

                 self.session.run(self.train_op, feed_dict={self._a[0]: batch, self.y: batch_label})

                 for j in range(len(self._sizes) + 1):
                     #Retrieve weights and biases
                     self.w_list[j] = sess.run(self._w[j])
                     self.c_list[j] = sess.run(self._c[j])

             train_acc = np.mean(np.argmax(self._Y, axis=1) ==
 self.session.run(self.predict_op, feed_dict={self._a[0]: self._X, self.y: self._Y}))

             val_acc = np.mean(np.argmax(val_y, axis=1) ==
 self.session.run(self.predict_op, feed_dict={self._a[0]: val_x, self.y: val_y}))
             print (" epoch " + str(i) + "/" + str(self._epochs) + " Training Accuracy: " +  str(train_acc) + " Validation Accuracy: " + str(val_acc))

     def predict(self, X):
         return self.session.run(self.predict_op, feed_dict={self._a[0]: X})
  1. 现在,我们训练实例化DBN对象并对其进行训练。 并预测测试数据的标签:
nNet = DBN(RBM_hidden_sizes, X_train, Y_train, epochs = 80)
with tf.Session() as sess:
     #Initialize Variables
     sess.run(tf.global_variables_initializer())
     nNet.set_session(sess)
     nNet.load_from_rbms(RBM_hidden_sizes,rbm_list)
     nNet.train(X_val, Y_val)
     y_pred = nNet.predict(X_test)

工作原理

RBM 使用无监督学习来学习模型的隐藏表示/特征,然后对与预训练 RBM 一起添加的全连接层进行微调。

这里的精度在很大程度上取决于图像表示。 在前面的秘籍中,我们没有使用图像处理,仅使用了 0 到 1 之间缩放的灰度图像。但是,如果我们按照以下论文所述添加图像处理,则会进一步提高精度。 因此,我们在preprocess_data函数中将每个图像乘以 100.0/255.0,然后将以下几行代码添加到主代码中:

std_image = np.std(X_train, axis=0)
X_train = np.divide(np.subtract(X_train,mean_image), std_image)
X_val = np.divide(np.subtract(X_val,mean_image), std_image)
X_test = np.divide(np.subtract(X_test,mean_image), std_image)

更多

在前面的示例中,没有进行预处理,这三个数据集的准确率大约为 40%。 但是,当我们添加预处理时,训练数据的准确率将提高到 90%,但是对于验证和测试,我们仍然可以获得约 45% 的准确率。

可以引入许多更改来改善结果。 首先,我们在秘籍中使用的数据集是只有 22,000 张图像的 Kaggle 数据集。 如果观察这些图像,则会发现仅过滤面部的步骤会改善结果。 如下文所述,另一种策略是增加隐藏层的大小而不是减小它们的大小

在识别情感方面确实非常成功的另一个更改是使用面部关键点而不是整个面部训练

使用前面的秘籍,您可以尝试这些更改并探索表现如何提高。 愿 GPU 力量与您同在!

八、自编码器

自编码器是前馈,非循环神经网络,可通过无监督学习来学习。 他们具有学习数据的紧凑表示的固有能力。 它们是深度信念网络的中心,可在图像重建,聚类,机器翻译等领域找到应用。 在本章中,您将学习和实现自编码器的不同变体,并最终学习如何堆叠自编码器。 本章包括以下主题:

  • 普通自编码器
  • 稀疏自编码器
  • 去噪自编码器
  • 卷积自编码器
  • 栈式自编码器

介绍

自编码器,也称为空竹网络自动关联器,最初由 Hinton 和 PDP 小组于 1980 年代提出。 它们是前馈网络,没有任何反馈,并且它们是通过无监督学习来学习的。 像第 3 章的多人感知机,神经网络感知机一样,它们使用反向传播算法进行学习,但有一个主要区别-目标与输入相同。

我们可以认为自编码器由两个级联网络组成-第一个网络是编码器,它接受输入x,然后使用变换h将其编码为编码信号y

y = h(x)

第二网络使用编码信号y作为其输入,并执行另一个变换f以获得重构信号r

r = f(y) = f(h(x))

我们将误差e定义为原始输入x与重构信号r之间的差,e = x - r。然后,网络通过减少均方误差MSE)进行学习,并且像 MLP 一样,该误差会传播回隐藏层。 下图显示了自编码器,其中编码器和解码器分别突出显示。 自编码器可以具有权重分配,也就是说,解码器和编码器的权重只是彼此的换位,这可以在训练参数数量较少时帮助网络更快地学习,但同时会降低编码器的自由度。 网络。 它们与第 7 章“无监督学习”的 RBM 非常相似,但有一个很大的区别-自编码器中神经元的状态是确定性的,而在 RBM 中,神经元是概率性的:

根据隐藏层的大小,自编码器分为不完整(隐藏层的神经元少于输入层)或过完整(隐藏层的神经元多于输入层)。 。 根据对损失施加的限制/约束,我们有多种类型的自编码器:稀疏自编码器,降噪自编码器和卷积自编码器。 在本章中,您将了解自编码器中的这些变体,并使用 TensorFlow 实现它们。

自编码器的明显应用之一是在降维领域[2]。 结果表明,与 PCA 相比,自编码器产生了更好的结果。 自编码器还可以用于特征提取[3],文档检索[2],分类和异常检测。

另见

  • Rumelhart, David E., Geoffrey E. Hinton, and Ronald J. Williams. Learning internal representations by error propagation. No. ICS-8506. California Univ San Diego La Jolla Inst for Cognitive Science, 1985. (http://www.cs.toronto.edu/~fritz/absps/pdp8.pdf)

  • Hinton, Geoffrey E., and Ruslan R. Salakhutdinov. Reducing the dimensionality of data with neural networks, science 313.5786 (2006): 504-507. (https://pdfs.semanticscholar.org/7d76/b71b700846901ac4ac119403aa737a285e36.pdf)

  • Masci, Jonathan, et al. Stacked convolutional auto-encoders for hierarchical feature extraction. Artificial Neural Networks and Machine Learning–ICANN 2011 (2011): 52-59. (https://www.researchgate.net/profile/Jonathan_Masci/publication/221078713_Stacked_Convolutional_Auto-Encoders_for_Hierarchical_Feature_Extraction/links/0deec518b9c6ed4634000000/Stacked-Convolutional-Auto-Encoders-for-Hierarchical-Feature-Extraction.pdf)

  • Japkowicz, Nathalie, Catherine Myers, and Mark Gluck. A novelty detection approach to classification. IJCAI. Vol. 1. 1995. (http://www.ijcai.org/Proceedings/95-1/Papers/068.pdf)

普通自编码器

Hinton 提出的普通自编码器仅包含一个隐藏层。 隐藏层中神经元的数量少于输入(或输出)层中神经元的数量。 这导致对网络中信息流产生瓶颈效应,因此我们可以将隐藏层视为瓶颈层,从而限制了要存储的信息。 自编码器中的学习包括在隐藏层上开发输入信号的紧凑表示,以便输出层可以忠实地再现原始输入:

具有单个隐藏层的自编码器

准备

此秘籍将使用自编码器进行图像重建; 我们将在 MNIST 数据库上训练自编码器,并将其用于重建测试图像。

操作步骤

我们按以下步骤进行:

  1. 与往常一样,第一步是导入所有必需的模块:
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
%matplotlib inline
  1. 接下来,我们从 TensorFlow 示例中获取 MNIST 数据-这里要注意的重要一点是,标签不是一次性编码的,仅仅是因为我们没有使用标签来训练网络。 自编码器通过无监督学习来学习:
mnist = input_data.read_data_sets("MNIST_data/")
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
  1. 接下来,我们声明一个类AutoEncoder; 该类具有init方法来为自编码器初始化权重,偏差和占位符。 我们还可以使用init方法构建完整的图。 该类还具有用于encoderdecoder,设置会话(set_session)和fit的方法。 我们在此处构建的自编码器使用简单的 MSE 作为loss函数,我们尝试使用AdamOptimizer对其进行优化:
class AutoEncoder(object):
def __init__(self, m, n, eta = 0.01):
"""
m: Number of neurons in input/output layer
n: number of neurons in hidden layer
"""
self._m = m
self._n = n
self.learning_rate = eta

# Create the Computational graph

# Weights and biases
self._W1 = tf.Variable(tf.random_normal(shape=(self._m,self._n)))
self._W2 = tf.Variable(tf.random_normal(shape=(self._n,self._m)))
self._b1 = tf.Variable(np.zeros(self._n).astype(np.float32)) #bias for hidden layer
self._b2 = tf.Variable(np.zeros(self._m).astype(np.float32)) #bias for output layer

# Placeholder for inputs
self._X = tf.placeholder('float', [None, self._m])

self.y = self.encoder(self._X)
self.r = self.decoder(self.y)
error = self._X - self.r

self._loss = tf.reduce_mean(tf.pow(error, 2))
self._opt = tf.train.AdamOptimizer(self.learning_rate).minimize(self._loss)

def encoder(self, x):
h = tf.matmul(x, self._W1) + self._b1
return tf.nn.sigmoid(h)

def decoder(self, x):
h = tf.matmul(x, self._W2) + self._b2
return tf.nn.sigmoid(h)

def set_session(self, session):
self.session = session

def reduced_dimension(self, x):
h = self.encoder(x)
return self.session.run(h, feed_dict={self._X: x})

def reconstruct(self,x):
h = self.encoder(x)
r = self.decoder(h)
return self.session.run(r, feed_dict={self._X: x})

def fit(self, X, epochs = 1, batch_size = 100):
N, D = X.shape
num_batches = N // batch_size

obj = []
for i in range(epochs):
#X = shuffle(X)
for j in range(num_batches):
    batch = X[j * batch_size: (j * batch_size + batch_size)]
    _, ob = self.session.run([self._opt,self._loss], feed_dict={self._X: batch})
    if j % 100 == 0 and i % 100 == 0:
        print('training epoch {0} batch {2} cost {1}'.format(i,ob, j)) 
obj.append(ob)
return obj

为了能够在训练后使用自编码器,我们还定义了两个工具函数:reduced_dimension提供编码器网络的输出,reconstruct重构最终图像。

  1. 我们将输入数据转换为float进行训练,初始化所有变量,然后开始计算会话。 在计算中,我们目前仅测试自编码器的重构能力:
Xtrain = trX.astype(np.float32)
Xtest = teX.astype(np.float32)
_, m = Xtrain.shape

autoEncoder = AutoEncoder(m, 256)

#Initialize all variables
init = tf.global_variables_initializer()
with tf.Session() as sess:
    sess.run(init)
    autoEncoder.set_session(sess)
    err = autoEncoder.fit(Xtrain, epochs=10)
    out = autoEncoder.reconstruct(Xtest[0:100])
  1. 我们可以通过绘制误差与周期的关系图来验证我们的网络在训练时是否确实优化了 MSE。 为了获得良好的训练,应该使用epochs来减少误差:
plt.plot(err)
plt.xlabel('epochs')
plt.ylabel('cost')

该图如下所示:

我们可以看到,随着网络的学习,损耗/成本正在降低,到我们达到 5,000 个周期时,损耗/成本几乎在一条线上振荡。 这意味着进一步增加周期将是无用的。 如果现在要改善训练,则应该更改超参数,例如学习率,批量大小和使用的优化程序。

  1. 现在让我们看一下重建的图像。 在这里,您可以同时看到由我们的自编码器生成的原始图像和重建图像:
# Plotting original and reconstructed images
row, col = 2, 8
idx = np.random.randint(0, 100, row * col // 2)
f, axarr = plt.subplots(row, col, sharex=True, sharey=True, figsize=(20,4))
for fig, row in zip([Xtest,out], axarr):
    for i,ax in zip(idx,row):
        ax.imshow(fig[i].reshape((28, 28)), cmap='Greys_r')
        ax.get_xaxis().set_visible(False)
        ax.get_yaxis().set_visible(False)

我们得到以下结果:

工作原理

有趣的是,在前面的代码中,我们将输入的尺寸从 784 减少到 256,并且我们的网络仍可以重建原始图像。 让我们比较一下具有相同隐藏层尺寸的 RBM(第 7 章,无监督学习)的表现:

我们可以看到,自编码器重建的图像比 RBM 重建的图像更清晰。 原因是,在自编码器中,还有其他权重(从隐藏层到解码器输出层的权重)需要训练,因此保留了学习知识。 随着自编码器了解更多,即使两者都将信息压缩到相同的尺寸,它的表现也比 RBM 更好。

更多

诸如 PCA 之类的自编码器可以用于降维,但是 PCA 仅可以表示线性变换,但是我们可以在自编码器中使用非线性激活函数,从而在编码中引入非线性。 这是从 Hinton 论文复制的结果,该结果使用神经网络降低了数据的维数。 该结果将 PCA(A)的结果与栈式 RBM 作为具有 784-1000-500-250-2 架构的自编码器的结果进行了比较:

正如我们稍后将看到的,当使用栈式自编码器制作自编码器时,每个自编码器最初都经过单独的预训练,然后对整个网络进行微调以获得更好的表现。

稀疏自编码器

我们在前面的秘籍中看到的自编码器的工作方式更像是一个身份网络-它们只是重构输入。 重点是在像素级别重建图像,唯一的限制是瓶颈层中的单元数; 有趣的是,像素级重建不能确保网络将从数据集中学习抽象特征。 通过添加更多约束,我们可以确保网络从数据集中学习抽象特征。

在稀疏自编码器中,将稀疏惩罚项添加到重构误差中,以确保在任何给定时间触发瓶颈层中较少的单元。 如果m是输入模式的总数,那么我们可以定义一个数量ρ_hat(您可以在 Andrew Ng 的讲座中检查数学细节),它测量每个隐藏层单元的净活动(平均触发多少次)。 基本思想是放置一个约束ρ_hat,使其等于稀疏性参数ρ。这导致损失函数中添加了稀疏性的正则项,因此现在loss函数如下:

loss = Mean squared error + Regularization for sparsity parameter

如果ρ_hat偏离ρ,则此正则化项将对网络造成不利影响;做到这一点的一种标准方法是使用ρρ_hat之间的 Kullback-LeiberKL)差异。

准备

在开始秘籍之前,让我们进一步探讨 KL 的差异,D[KL]。 它是两个分布之间差异的非对称度量,在我们的情况下为ρρ_hat。当ρρ_hat相等时,则为零,否则,当ρ_hatρ分叉时,它单调增加。在数学上,它表示为:

这是固定ρ = 0.3D[KL]的图,我们可以看到当ρ_hat = 0.3时,D[KL] = 0;否则在两端单调增长:

操作步骤

我们按以下步骤进行:

  1. 我们导入必要的模块:
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
%matplotlib inline
  1. 从 TensorFlow 示例中加载 MNIST 数据集:
mnist = input_data.read_data_sets("MNIST_data/")
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
  1. 定义SparseAutoEncoder类,它与前面的秘籍中的AutoEncoder类非常相似,除了引入了 KL 散度损失之外:
def kl_div(self, rho, rho_hat):
 term2_num = tf.constant(1.)- rho
 term2_den = tf.constant(1.) - rho_hat
 kl = self.logfunc(rho,rho_hat) + self.logfunc(term2_num, term2_den)
 return kl

 def logfunc(self, x1, x2):
 return tf.multiply( x1, tf.log(tf.div(x1,x2)))

我们将 KL 约束添加到损失中,如下所示:

alpha = 7.5e-5
kl_div_loss = tf.reduce_sum(self.kl_div(0.02, tf.reduce_mean(self.y,0)))
loss = self._loss + alpha * kl_div_loss

在此,alpha是赋予稀疏性约束的权重。 该类的完整代码如下:

class SparseAutoEncoder(object):
 def __init__(self, m, n, eta = 0.01):
 """
 m: Number of neurons in input/output layer
 n: number of neurons in hidden layer
 """
 self._m = m
 self._n = n
 self.learning_rate = eta

 # Create the Computational graph

 # Weights and biases
 self._W1 = tf.Variable(tf.random_normal(shape=(self._m,self._n)))
 self._W2 = tf.Variable(tf.random_normal(shape=(self._n,self._m)))
 self._b1 = tf.Variable(np.zeros(self._n).astype(np.float32)) #bias for hidden layer
 self._b2 = tf.Variable(np.zeros(self._m).astype(np.float32)) #bias for output layer

 # Placeholder for inputs
 self._X = tf.placeholder('float', [None, self._m])

 self.y = self.encoder(self._X)
 self.r = self.decoder(self.y)
 error = self._X - self.r

 self._loss = tf.reduce_mean(tf.pow(error, 2))
 alpha = 7.5e-5
 kl_div_loss = tf.reduce_sum(self.kl_div(0.02,   tf.reduce_mean(self.y,0)))
 loss = self._loss + alpha * kl_div_loss 
 self._opt = tf.train.AdamOptimizer(self.learning_rate).minimize(loss)

 def encoder(self, x):
 h = tf.matmul(x, self._W1) + self._b1
 return tf.nn.sigmoid(h)

 def decoder(self, x):
 h = tf.matmul(x, self._W2) + self._b2
 return tf.nn.sigmoid(h)

 def set_session(self, session):
 self.session = session

 def reduced_dimension(self, x):
 h = self.encoder(x)
 return self.session.run(h, feed_dict={self._X: x})

 def reconstruct(self,x):
 h = self.encoder(x)
 r = self.decoder(h)
 return self.session.run(r, feed_dict={self._X: x})

 def kl_div(self, rho, rho_hat):
 term2_num = tf.constant(1.)- rho
 term2_den = tf.constant(1.) - rho_hat
 kl = self.logfunc(rho,rho_hat) + self.logfunc(term2_num, term2_den)
 return kl

 def logfunc(self, x1, x2):
 return tf.multiply( x1, tf.log(tf.div(x1,x2)))

 def fit(self, X, epochs = 1, batch_size = 100):
 N, D = X.shape
 num_batches = N // batch_size

 obj = []
 for i in range(epochs):
     #X = shuffle(X)
     for j in range(num_batches):
         batch = X[j * batch_size: (j * batch_size + batch_size)]
         _, ob = self.session.run([self._opt,self._loss], feed_dict={self._X: batch})
         if j % 100 == 0:
             print('training epoch {0} batch {2} cost {1}'.format(i,ob, j)) 
obj.append(ob)
 return obj
  1. 接下来,我们声明SparseAutoEncoder类的对象,对训练数据进行拟合,并计算重建的图像:
Xtrain = trX.astype(np.float32)
Xtest = teX.astype(np.float32)
_, m = Xtrain.shape
sae = SparseAutoEncoder(m, 256)
#Initialize all variables
init = tf.global_variables_initializer()
with tf.Session() as sess:
 sess.run(init)
 sae.set_session(sess)
 err = sae.fit(Xtrain, epochs=10)
 out = sae.reconstruct(Xtest[0:100])
  1. 让我们看看随着网络学习,均方重构损失的变化:
plt.plot(err)
plt.xlabel('epochs')
plt.ylabel('Reconstruction Loss (MSE)')

情节如下:

  1. 让我们看一下重建的图像:
# Plotting original and reconstructed images
row, col = 2, 8
idx = np.random.randint(0, 100, row * col // 2)
f, axarr = plt.subplots(row, col, sharex=True, sharey=True, figsize=(20,4))
for fig, row in zip([Xtest,out], axarr):
    for i,ax in zip(idx,row):
        ax.imshow(fig[i].reshape((28, 28)), cmap='Greys_r')
        ax.get_xaxis().set_visible(False)
        ax.get_yaxis().set_visible(False)

我们得到以下结果:

工作原理

您必须已经注意到,稀疏自编码器的主要代码与普通自编码器的主要代码完全相同,这是因为稀疏自编码器只有一个主要变化-增加了 KL 发散损耗以确保稀疏性。 隐藏的(瓶颈)层。 但是,如果比较这两个重构,则可以发现即使在隐藏层中具有相同数量的单元,稀疏自编码器也比标准编码器好得多:

训练 MNIST 数据集的原始自编码器后的重建损失为 0.022,而稀疏自编码器则为 0.006。 因此,添加约束会迫使网络学习数据的隐藏表示。

更多

输入的紧凑表示形式以权重存储; 让我们可视化网络学习到的权重。 这分别是标准自编码器和稀疏自编码器的编码器层的权重。 我们可以看到,在标准自编码器中,许多隐藏单元的权重非常大,表明它们工作过度:

另见

去噪自编码器

我们在前两个秘籍中探讨过的两个自编码器是未完成的自编码器的示例,因为与输入(输出)层相比,它们中的隐藏层具有较低的尺寸。 去噪自编码器属于过完整自编码器的类别,因为当隐藏层的尺寸大于输入层的尺寸时,它会更好地工作。

去噪自编码器从损坏的(嘈杂的)输入中学习; 它为编码器网络提供噪声输入,然后将来自解码器的重建图像与原始输入进行比较。 这个想法是,这将帮助网络学习如何去噪输入。 它不再只是按像素进行比较,而是为了进行去噪,还将学习相邻像素的信息。

准备

去噪自编码器还将具有 KL 散度惩罚项; 它在两个主要方面与先前秘籍的稀疏自编码器有所不同。 首先,n_hidden > m瓶颈层中的隐藏单元数大于输入层m中的单元数n_hidden > m。 其次,编码器的输入已损坏。 为了在 TensorFlow 中做到这一点,我们添加了invalid函数,这给输入增加了噪音:

def corruption(x, noise_factor = 0.3): #corruption of the input
    noisy_imgs = x + noise_factor * np.random.randn(*x.shape)
    noisy_imgs = np.clip(noisy_imgs, 0., 1.)
    return noisy_imgs

操作步骤

  1. 像往常一样,第一步是导入必要的模块-TensorFlow,numpy 来操纵输入数据,matplotlib 来进行绘制,等等:
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
import math
%matplotlib inline
  1. 从 TensorFlow 示例中加载数据。 在本章的所有秘籍中,我们都使用标准的 MNIST 数据库进行说明,以便为您提供不同自编码器之间的基准。
mnist = input_data.read_data_sets("MNIST_data/")
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
  1. 接下来,我们定义此秘籍的主要组成部分DenoisingAutoEncoder类。 该类与我们在前面的秘籍中创建的SparseAutoEncoder类非常相似。 在这里,我们为噪点图像添加了一个占位符; 该噪声输入被馈送到编码器。 现在,当输入的是噪点图像时,重构误差就是原始清晰图像与解码器输出之间的差。 我们在此保留稀疏惩罚条款。 因此,拟合函数将原始图像和噪声图像都作为其参数。
class DenoisingAutoEncoder(object):
def __init__(self, m, n, eta = 0.01):
"""
m: Number of neurons in input/output layer
n: number of neurons in hidden layer
"""
self._m = m
self._n = n
self.learning_rate = eta

# Create the Computational graph

# Weights and biases
self._W1 = tf.Variable(tf.random_normal(shape=(self._m,self._n)))
self._W2 = tf.Variable(tf.random_normal(shape=(self._n,self._m)))
self._b1 = tf.Variable(np.zeros(self._n).astype(np.float32)) #bias for hidden layer
self._b2 = tf.Variable(np.zeros(self._m).astype(np.float32)) #bias for output layer

# Placeholder for inputs
self._X = tf.placeholder('float', [None, self._m])

self._X_noisy = tf.placeholder('float', [None, self._m])

self.y = self.encoder(self._X_noisy)
self.r = self.decoder(self.y)
error = self._X - self.r

self._loss = tf.reduce_mean(tf.pow(error, 2))
#self._loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels =self._X, logits = self.r))
alpha = 0.05
kl_div_loss = tf.reduce_sum(self.kl_div(0.02, tf.reduce_mean(self.y,0)))
loss = self._loss + alpha * kl_div_loss 
self._opt = tf.train.AdamOptimizer(self.learning_rate).minimize(loss)

def encoder(self, x):
h = tf.matmul(x, self._W1) + self._b1
return tf.nn.sigmoid(h)

def decoder(self, x):
h = tf.matmul(x, self._W2) + self._b2
return tf.nn.sigmoid(h)

def set_session(self, session):
self.session = session

def reconstruct(self,x):
h = self.encoder(x)
r = self.decoder(h)
return self.session.run(r, feed_dict={self._X: x})

def kl_div(self, rho, rho_hat):
term2_num = tf.constant(1.)- rho
term2_den = tf.constant(1.) - rho_hat
kl = self.logfunc(rho,rho_hat) + self.logfunc(term2_num, term2_den)
return kl

def logfunc(self, x1, x2):
return tf.multiply( x1, tf.log(tf.div(x1,x2)))

def corrupt(self,x):
return x * tf.cast(tf.random_uniform(shape=tf.shape(x), minval=0,maxval=2),tf.float32)

def getWeights(self):
return self.session.run([self._W1, self._W2,self._b1, self._b2])

def fit(self, X, Xorg, epochs = 1, batch_size = 100):
N, D = X.shape
num_batches = N // batch_size

obj = []
for i in range(epochs):
#X = shuffle(X)
for j in range(num_batches):
batch = X[j * batch_size: (j * batch_size + batch_size)]
batchO = Xorg[j * batch_size: (j * batch_size + batch_size)]
_, ob = self.session.run([self._opt,self._loss], feed_dict={self._X: batchO, self._X_noisy: batch})
if j % 100 == 0:
print('training epoch {0} batch {2} cost {1}'.format(i,ob, j)) 
obj.append(ob)
return obj

也可以向自编码器对象添加噪声。 在这种情况下,您将使用类self._X_noisy = self.corrupt(self._X) * 0.3 + self._X * (1 - 0.3)中定义的损坏方法,并且fit方法也将更改为以下内容:

def fit(self, X, epochs = 1, batch_size = 100):
        N, D = X.shape
        num_batches = N // batch_size

        obj = []
        for i in range(epochs):
            #X = shuffle(X)
            for j in range(num_batches):
                batch = X[j * batch_size: (j * batch_size + batch_size)]
                _, ob = self.session.run([self._opt,self._loss], feed_dict={self._X: batch})
                if j % 100 == 0:
                    print('training epoch {0} batch {2} cost {1}'.format(i,ob, j)) 
                obj.append(ob)
        return obj
  1. 现在,我们使用前面定义的损坏函数来生成一个嘈杂的图像并将其提供给会话:
n_hidden = 800
Xtrain = trX.astype(np.float32)
Xtrain_noisy = corruption(Xtrain).astype(np.float32)
Xtest = teX.astype(np.float32)
#noise = Xtest * np.random.randint(0, 2, Xtest.shape).astype(np.float32)
Xtest_noisy = corruption(Xtest).astype(np.float32) #Xtest * (1-0.3)+ noise *(0.3)
_, m = Xtrain.shape

dae = DenoisingAutoEncoder(m, n_hidden)

#Initialize all variables
init = tf.global_variables_initializer()
with tf.Session() as sess:
 sess.run(init)
 dae.set_session(sess)
 err = dae.fit(Xtrain_noisy, Xtrain, epochs=10)
 out = dae.reconstruct(Xtest_noisy[0:100])
 W1, W2, b1, b2 = dae.getWeights()
 red = dae.reduced_dimension(Xtrain)
  1. 随着网络的学习,重建损失减少:
plt.plot(err)
plt.xlabel('epochs')
plt.ylabel('Reconstruction Loss (MSE)')

情节如下:

  1. 当来自测试数据集的嘈杂图像呈现给训练网络时,重建图像如下:
# Plotting original and reconstructed images
row, col = 2, 8
idx = np.random.randint(0, 100, row * col // 2)
f, axarr = plt.subplots(row, col, sharex=True, sharey=True, figsize=(20,4))
for fig, row in zip([Xtest_noisy,out], axarr):
 for i,ax in zip(idx,row):
 ax.imshow(fig[i].reshape((28, 28)), cmap='Greys_r')
 ax.get_xaxis().set_visible(False)
 ax.get_yaxis().set_visible(False)

我们得到以下结果:

另见

卷积自编码器

研究人员发现卷积神经网络CNN)与图像效果最佳,因为它们可以提取隐藏在图像中的空间信息。 因此,很自然地假设,如果编码器和解码器网络由 CNN 组成,它将比其余的自编码器更好地工作,因此我们有了卷积自编码器CAE)。 在第 4 章“卷积神经网络”中,说明了卷积和最大池化的过程,我们将以此为基础来了解卷积自编码器的工作原理。

CAE 是其中编码器和解码器均为 CNN 网络的一种 CAE。 编码器的卷积网络学习将输入编码为一组信号,然后解码器 CNN 尝试从中重建输入。 它们充当通用特征提取器,并学习从输入捕获特征所需的最佳过滤器。

准备

从第 4 章“卷积神经网络”中,您了解到,随着添加卷积层,传递到下一层的信息在空间范围上会减少,但是在自编码器中,重建的图像应该有输入图像的相同的大小和深度。 这意味着解码器应以某种方式对图像进行大小调整和卷积以重建原始图像。 与卷积一起增加空间范围的一种方法是借助转置的卷积层。 通过tf.nn.conv2d_transpose可以轻松地在 TensorFlow 中实现这些功能,但是发现转置的卷积层会在最终图像中产生伪像。 奥古斯都·奥德纳(Augustus Odena)等。 [1]在他们的工作中表明,可以通过使用最近邻或双线性插值(上采样)再加上卷积层来调整层的大小来避免这些伪像。 他们通过tf.image.resize_images实现了最近邻插值,取得了最佳结果; 我们将在此处采用相同的方法。

操作步骤

  1. 与往常一样,第一步包括必要的模块:
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
import math
%matplotlib inline
  1. 加载输入数据:
mnist = input_data.read_data_sets("MNIST_data/")
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
  1. 定义网络参数。 在这里,我们还计算每个最大池层的输出的空间尺寸。 我们需要以下信息来对解码器网络中的图像进行升采样:
# Network Parameters
h_in, w_in = 28, 28 # Image size height and width
k = 3 # Kernel size
p = 2 # pool
s = 2 # Strides in maxpool
filters = {1:32,2:32,3:16}
activation_fn=tf.nn.relu
# Change in dimensions of image after each MaxPool
h_l2, w_l2 = int(np.ceil(float(h_in)/float(s))) , int(np.ceil(float(w_in)/float(s))) # Height and width: second encoder/decoder layer
h_l3, w_l3 = int(np.ceil(float(h_l2)/float(s))) , int(np.ceil(float(w_l2)/float(s))) # Height and width: third encoder/decoder layer
  1. 为输入(嘈杂的图像)和目标(对应的清晰图像)创建占位符:
X_noisy = tf.placeholder(tf.float32, (None, h_in, w_in, 1), name='inputs')
X = tf.placeholder(tf.float32, (None, h_in, w_in, 1), name='targets')
  1. 建立编码器和解码器网络:
### Encoder
conv1 = tf.layers.conv2d(X_noisy, filters[1], (k,k), padding='same', activation=activation_fn)
# Output size h_in x w_in x filters[1]
maxpool1 = tf.layers.max_pooling2d(conv1, (p,p), (s,s), padding='same')
# Output size h_l2 x w_l2 x filters[1] 
conv2 = tf.layers.conv2d(maxpool1, filters[2], (k,k), padding='same', activation=activation_fn)
# Output size h_l2 x w_l2 x filters[2] 
maxpool2 = tf.layers.max_pooling2d(conv2,(p,p), (s,s), padding='same')
# Output size h_l3 x w_l3 x filters[2] 
conv3 = tf.layers.conv2d(maxpool2,filters[3], (k,k), padding='same', activation=activation_fn)
# Output size h_l3 x w_l3 x filters[3]
encoded = tf.layers.max_pooling2d(conv3, (p,p), (s,s), padding='same')
# Output size h_l3/s x w_l3/s x filters[3] Now 4x4x16

### Decoder
upsample1 = tf.image.resize_nearest_neighbor(encoded, (h_l3,w_l3))
# Output size h_l3 x w_l3 x filters[3]
conv4 = tf.layers.conv2d(upsample1, filters[3], (k,k), padding='same', activation=activation_fn)
# Output size h_l3 x w_l3 x filters[3]
upsample2 = tf.image.resize_nearest_neighbor(conv4, (h_l2,w_l2))
# Output size h_l2 x w_l2 x filters[3] 
conv5 = tf.layers.conv2d(upsample2, filters[2], (k,k), padding='same', activation=activation_fn)
# Output size h_l2 x w_l2 x filters[2] 
upsample3 = tf.image.resize_nearest_neighbor(conv5, (h_in,w_in))
# Output size h_in x w_in x filters[2]
conv6 = tf.layers.conv2d(upsample3, filters[1], (k,k), padding='same', activation=activation_fn)
# Output size h_in x w_in x filters[1]

logits = tf.layers.conv2d(conv6, 1, (k,k) , padding='same', activation=None)

# Output size h_in x w_in x 1
decoded = tf.nn.sigmoid(logits, name='decoded')

loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=X, logits=logits)
cost = tf.reduce_mean(loss)
opt = tf.train.AdamOptimizer(0.001).minimize(cost)
  1. 启动会话:
sess = tf.Session()
  1. 将模型拟合给定输入:
epochs = 10
batch_size = 100
# Set's how much noise we're adding to the MNIST images
noise_factor = 0.5
sess.run(tf.global_variables_initializer())
err = []
for i in range(epochs):
 for ii in range(mnist.train.num_examples//batch_size):
 batch = mnist.train.next_batch(batch_size)
 # Get images from the batch
 imgs = batch[0].reshape((-1, h_in, w_in, 1))

 # Add random noise to the input images
 noisy_imgs = imgs + noise_factor * np.random.randn(*imgs.shape)
 # Clip the images to be between 0 and 1
 noisy_imgs = np.clip(noisy_imgs, 0., 1.)

 # Noisy images as inputs, original images as targets
 batch_cost, _ = sess.run([cost, opt], feed_dict={X_noisy: noisy_imgs,X: imgs})
 err.append(batch_cost)
 if ii%100 == 0:
 print("Epoch: {0}/{1}... Training loss {2}".format(i, epochs, batch_cost))
  1. 网络学习到的误差如下:
plt.plot(err)
plt.xlabel('epochs')
plt.ylabel('Cross Entropy Loss')

绘图如下:

  1. 最后,让我们看一下重建的图像:
fig, axes = plt.subplots(rows=2, cols=10, sharex=True, sharey=True, figsize=(20,4))
in_imgs = mnist.test.images[:10]
noisy_imgs = in_imgs + noise_factor * np.random.randn(*in_imgs.shape)
noisy_imgs = np.clip(noisy_imgs, 0., 1.)
reconstructed = sess.run(decoded, feed_dict={X_noisy: noisy_imgs.reshape((10, 28, 28, 1))})
for images, row in zip([noisy_imgs, reconstructed], axes):
 for img, ax in zip(images, row):
 ax.imshow(img.reshape((28, 28)), cmap='Greys_r')
 ax.get_xaxis().set_visible(False)
 ax.get_yaxis().set_visible(False)

这是前面代码的输出:

  1. 关闭会话:
sess.close()

工作原理

前面的 CAE 是降噪 CAE,与仅由一个瓶颈层组成的简单降噪自编码器相比,我们可以看到它在降噪图像方面更好。

更多

研究人员已将 CAE 用于语义分割。 有趣的读物是 Badrinayanan 等人在 2015 年发表的论文 Segnet:一种用于图像分割的深度卷积编码器-解码器架构。 该网络使用 VGG16 的卷积层作为其编码器网络,并包含一层解码器,每个解码器对应一个解码器层次作为其解码器网络。 解码器使用从相应的编码器接收的最大池索引,并对输入特征图执行非线性上采样。 本文的链接在本秘籍的另请参见部分以及 GitHub 链接中给出。

另见

  1. https://distill.pub/2016/deconv-checkerboard/
  2. https://pgaleone.eu/neural-networks/2016/11/24/convolutional-autoencoders/
  3. https://arxiv.org/pdf/1511.00561.pdf
  4. https://github.com/arahusky/Tensorflow-Segmentation

栈式自编码器

到目前为止,涵盖的自编码器(CAE 除外)仅由单层编码器和单层解码器组成。 但是,我们可能在编码器和解码器网络中具有多层; 使用更深的编码器和解码器网络可以使自编码器表示复杂的特征。 这样获得的结构称为栈式自编码器(深度自编码器); 由一个编码器提取的特征将作为输入传递到下一个编码器。 可以将栈式自编码器作为一个整体网络进行训练,以最大程度地减少重构误差,或者可以首先使用您先前学习的无监督方法对每个单独的编码器/解码器网络进行预训练,然后对整个网络进行微调。 已经指出,通过预训练,也称为贪婪分层训练,效果更好。

准备

在秘籍中,我们将使用贪婪分层方法来训练栈式自编码器; 为了简化任务,我们将使用共享权重,因此相应的编码器/解码器权重将相互转换。

操作步骤

我们按以下步骤进行:

  1. 第一步是导入所有必要的模块:
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
%matplotlib inline
  1. 加载数据集:
mnist = input_data.read_data_sets("MNIST_data/")
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
  1. 接下来,我们定义类StackedAutoencoder__init__类方法包含一个列表,该列表包含从第一个输入自编码器和学习率开始的每个自编码器中的许多神经元。 由于每一层的输入和输出都有不同的尺寸,因此我们选择字典数据结构来表示每一层的权重,偏差和输入:
class StackedAutoEncoder(object):
 def __init__(self, list1, eta = 0.02):
 """
 list1: [input_dimension, hidden_layer_1, ....,hidden_layer_n]
 """
 N = len(list1)-1
 self._m = list1[0]
 self.learning_rate = eta

 # Create the Computational graph
 self._W = {}
 self._b = {}
 self._X = {}
 self._X['0'] = tf.placeholder('float', [None, list1[0]])

 for i in range(N):
 layer = '{0}'.format(i+1)
 print('AutoEncoder Layer {0}: {1} --> {2}'.format(layer, list1[i], list1[i+1]))
 self._W['E' + layer] = tf.Variable(tf.random_normal(shape=(list1[i], list1[i+1])),name='WtsEncoder'+layer)
 self._b['E'+ layer] = tf.Variable(np.zeros(list1[i+1]).astype(np.float32),name='BiasEncoder'+layer)
 self._X[layer] = tf.placeholder('float', [None, list1[i+1]])
 self._W['D' + layer] = tf.transpose(self._W['E' + layer]) # Shared weights
 self._b['D' + layer] = tf.Variable(np.zeros(list1[i]).astype(np.float32),name='BiasDecoder' + layer)

 # Placeholder for inputs
 self._X_noisy = tf.placeholder('float', [None, self._m])
  1. 我们建立一个计算图来定义每个自编码器的优化参数,同时进行预训练。 当先前的自编码器的编码器的输出为其输入时,它涉及为每个自编码器定义重建损耗。 为此,我们定义类方法pretrainone_pass,它们分别为每个栈式自编码器返回训练操作器和编码器的输出:
 self.train_ops = {}
 self.out = {}

 for i in range(N):
 layer = '{0}'.format(i+1)
 prev_layer = '{0}'.format(i)
 opt = self.pretrain(self._X[prev_layer], layer)
 self.train_ops[layer] = opt
 self.out[layer] = self.one_pass(self._X[prev_layer], self._W['E'+layer], self._b['E'+layer], self._b['D'+layer])
  1. 我们建立计算图以对整个栈式自编码器进行微调。 为此,我们使用类方法encoderdecoder
self.y = self.encoder(self._X_noisy,N) #Encoder output 
self.r = self.decoder(self.y,N) # Decoder ouput

optimizer = tf.train.AdamOptimizer(self.learning_rate) 
error = self._X['0'] - self.r # Reconstruction Error

self._loss = tf.reduce_mean(tf.pow(error, 2))
self._opt = optimizer.minimize(self._loss)
  1. 最后,我们定义类方法fit,以执行每个自编码器的分批预训练,然后进行微调。 在进行预训练时,我们使用未损坏的输入,而对于微调,我们使用损坏的输入。 这使我们能够使用栈式自编码器甚至从嘈杂的输入中进行重构:
def fit(self, Xtrain, Xtr_noisy, layers, epochs = 1, batch_size = 100):
 N, D = Xtrain.shape
 num_batches = N // batch_size
 X_noisy = {}
 X = {}
 X_noisy ['0'] = Xtr_noisy
 X['0'] = Xtrain

 for i in range(layers):
 Xin = X[str(i)]
 print('Pretraining Layer ', i+1)
 for e in range(5):
 for j in range(num_batches):
 batch = Xin[j * batch_size: (j * batch_size + batch_size)]
 self.session.run(self.train_ops[str(i+1)], feed_dict= {self._X[str(i)]: batch})
 print('Pretraining Finished')
 X[str(i+1)] = self.session.run(self.out[str(i+1)], feed_dict = {self._X[str(i)]: Xin})

 obj = []
 for i in range(epochs):
 for j in range(num_batches):
 batch = Xtrain[j * batch_size: (j * batch_size + batch_size)]
 batch_noisy = Xtr_noisy[j * batch_size: (j * batch_size + batch_size)]
 _, ob = self.session.run([self._opt,self._loss], feed_dict={self._X['0']: batch, self._X_noisy: batch_noisy})
 if j % 100 == 0 :
 print('training epoch {0} batch {2} cost {1}'.format(i,ob, j)) 
 obj.append(ob)
 return obj
  1. 不同的类方法如下:
def encoder(self, X, N):
 x = X
 for i in range(N):
 layer = '{0}'.format(i+1)
 hiddenE = tf.nn.sigmoid(tf.matmul(x, self._W['E'+layer]) + self._b['E'+layer])
 x = hiddenE
 return x

def decoder(self, X, N):
 x = X
 for i in range(N,0,-1):
 layer = '{0}'.format(i)
 hiddenD = tf.nn.sigmoid(tf.matmul(x, self._W['D'+layer]) + self._b['D'+layer])
 x = hiddenD
 return x

def set_session(self, session):
 self.session = session

def reconstruct(self,x, n_layers):
 h = self.encoder(x, n_layers)
 r = self.decoder(h, n_layers)
 return self.session.run(r, feed_dict={self._X['0']: x})

def pretrain(self, X, layer ):
 y = tf.nn.sigmoid(tf.matmul(X, self._W['E'+layer]) + self._b['E'+layer])
 r =tf.nn.sigmoid(tf.matmul(y, self._W['D'+layer]) + self._b['D'+layer])

 # Objective Function
 error = X - r # Reconstruction Error
  loss = tf.reduce_mean(tf.pow(error, 2))
 opt = tf.train.AdamOptimizer(.001).minimize(loss, var_list = 
 [self._W['E'+layer],self._b['E'+layer],self._b['D'+layer]])
  return opt

def one_pass(self, X, W, b, c):
 h = tf.nn.sigmoid(tf.matmul(X, W) + b)
 return h
  1. 我们使用降噪自编码器秘籍中定义的破坏函数来破坏图像,最后创建一个StackAutoencoder并对其进行训练:
Xtrain = trX.astype(np.float32)
Xtrain_noisy = corruption(Xtrain).astype(np.float32)
Xtest = teX.astype(np.float32)
Xtest_noisy = corruption(Xtest).astype(np.float32) 
_, m = Xtrain.shape

list1 = [m, 500, 50] # List with number of neurons in Each hidden layer, starting from input layer
n_layers = len(list1)-1
autoEncoder = StackedAutoEncoder(list1)

#Initialize all variables
init = tf.global_variables_initializer()

with tf.Session() as sess:
 sess.run(init)
 autoEncoder.set_session(sess)
 err = autoEncoder.fit(Xtrain, Xtrain_noisy, n_layers, epochs=30)
 out = autoEncoder.reconstruct(Xtest_noisy[0:100],n_layers)
  1. 这里给出了随着堆叠自编码器的微调,重建误差与历时的关系。 您可以看到,由于进行了预训练,我们已经从非常低的重建损失开始了:
plt.plot(err)
plt.xlabel('epochs')
plt.ylabel('Fine Tuning Reconstruction Error')

情节如下:

  1. 现在让我们检查网络的表现。 当网络中出现嘈杂的测试图像时,这是去噪后的手写图像:

工作原理

在栈式自编码器上进行的实验表明,应以较低的学习率值进行预训练。 这样可以确保在微调期间具有更好的收敛性和表现。

更多

整章都是关于自编码器的,尽管目前它们仅用于降维和信息检索,但它们引起了很多兴趣。 首先,因为它们不受监督,其次,因为它们可以与 FCN 一起使用。 它们可以帮助我们应对维度的诅咒。 研究人员已经证明,它们也可以用于分类和异常检测。

另见

九、强化学习

本章介绍强化学习RL)-学习最少,但最有前途的学习范例。 本章包括以下主题:

  • 了解 OpenAI Gym
  • 实现神经网络智能体来玩吃豆人
  • 用 Q 学习平衡推车
  • 深度 Q 网络和 Atari 游戏
  • 用策略梯度玩 Pong 游戏

介绍

2016 年 3 月,由 Google DeepMind 制作的程序 AlphaGo 以 4 比 1 击败了世界上最好的围棋选手,十八届世界冠军李·塞多尔(Lee Sedol)。 ,具有:

208,168,199,381,979,984,699,478,633,344,862,770,286,522,
453,884,530,548,425,639,456,820,927,419,612,738,015,378,
525,648,451,698,519,643,907,259,916,015,628,128,546,089,
888,314,427, 129,715,319,317,557,736,620,397,247,064,840,935

可能的法律委员会职位。 玩和赢得围棋无法通过简单的蛮力完成。 它需要技巧,创造力,以及专业围棋选手所说的直觉。

AlphaGo 在基于 RL 算法的深度神经网络与最先进的树搜索算法相结合的帮助下实现了这一非凡的成就。 本章介绍 RL 和我们用于执行 RL 的一些算法。

因此,出现的第一个问题是什么是 RL,它与我们在前几章中探讨的有监督和无监督学习有何不同?

拥有宠物的任何人都知道,训练宠物的最佳策略是奖励其期望的行为,并惩罚其不良行为。 RL 也称为与批评者进行的学习,它是一种学习范例,其中智能体以相同的方式进行学习。 这里的智能体对应我们的网络(程序); 它可以执行一组动作a),这会导致环境的状态s)发生变化。 ,则智能体会感知其是否获得奖励或惩罚。

例如,在狗的情况下,狗是我们的智能体,狗的自愿肌肉运动是动作,地面是环境。 狗给我们骨头作为奖励,从而感觉到我们对其动作的反应:

改编自强化学习:萨顿(Sutton)和巴托(Barto)的引言即使我们的大脑在前脑底部有一组称为“基底神经节”的皮层下核,根据神经科学,它们负责选择动作,即帮助 我们决定在任何给定时间执行几个可能的动作中的哪一个。

智能体的目的是使报酬最大化并减少惩罚。 做出此决策涉及各种挑战,最重要的挑战是如何最大化未来的回报,也称为临时得分分配问题。 智能体根据某些策略(π)决定其操作; 智能体根据其与环境的交互来学习此策略(π)。 有各种策略学习算法; 我们将在本章中探索其中的一些。 智能体通过反复试验的过程来推断最优策略(π*),并且要学习最优策略,智能体需要与之交互的环境; 我们将使用提供不同环境的 OpenAI Gym。

在这里,我们仅对 RL 中涉及的基本概念进行了回顾; 我们假设您熟悉马尔可夫概念的决策过程,折现因子和值函数(状态值和动作值)。

在本章以及随后的秘籍中,我们将一集定义为游戏的一次运行,例如,解决一个数独游戏。 通常,智能体会播放许多剧集以学习一种最佳策略,该策略可使奖励最大化。

看到 RL 特工在没有任何游戏隐性知识的情况下,如何在这些游戏中学会玩游戏,不仅玩游戏,甚至击败人类,真是太神奇了。

了解 OpenAI Gym

我们将使用 OpenAI Gym 为我们的智能体提供一个环境。 OpenAI Gym 是一个开源工具包,用于开发和比较 RL 算法。 它包含各种模拟环境,可用于训练智能体和开发新的 RL 算法。

准备

首先要做的是安装 OpenAI Gym; 使用pip install gym可以完成最少的安装。 OpenAI 运动场提供了多种环境,例如 Atari,棋盘游戏以及 2D 或 3D 物理引擎。 最小安装可在 Windows 上运行,并且仅支持基本环境-算法,toy_textclassic_control-但如果您要探索其他环境,则它们将需要更多的依赖项。 OSX 和 Ubuntu 支持完整版本。 可以在 OpenAI Gym 的 GitHub 链接上阅读详细说明。

操作步骤

让我们从秘籍开始:

  1. OpenAI Gym 提供的核心接口是统一环境接口。 智能体可以使用三种基本方法与环境进行交互,即重置,逐步和渲染。 reset方法重置环境并返回观察值。 step方法将环境步进一个时间步,并返回观察,奖励,完成和信息。 render方法呈现一帧环境,例如弹出一个窗口。
  2. 要使用 OpenAI Gym,您需要先将其导入:
import gym
  1. 接下来,我们创建我们的第一个环境:
env_name = 'Breakout-v3'
env = gym.make(env_name)
  1. 我们使用reset方法启动环境:
obs = env.reset()
  1. 让我们检查一下环境的形状:
print(obs.shape)
  1. 可以使用命令actions = env.action_space检查可能的操作数量。 从此结果可以看出,对于 Breakout-v4,我们有四个可能的动作:NoOpFireLeftRight。 可以通过调用env.action_space.n命令获得操作总数。
  2. 让我们定义一个具有随机策略的智能体。 智能体会随机选择四个可能的动作中的任何一个:
def random_policy(n):
 action = np.random.randint(0,n)
 return action
  1. 接下来,我们使用obs, reward, done, info = env.step(action)允许我们的随机智能体播放 1,000 步:
for step in range(1000): # 1000 steps max
 action = random_policy(env.action_space.n)
 obs, reward, done, info = env.step(action)
 env.render()
 if done:
     img = env.render(mode='rgb_array')
     plt.imshow(img)
     plt.show()
     print("The game is over in {} steps".format(step))
     break

obs告诉智能体程序环境是什么样的; 对于我们的环境,它对应于大小为210 x 160 x 3的 RGB 图像。在每个步骤中,智能体将获得 0 或 1 奖励,根据 OpenAI Gym Wiki,其reward[-inf, inf]。 游戏结束后,环境将done返回为Trueinfo可用于调试,但智能体不使用。 env.render()命令弹出一个窗口,显示环境的当前状态。 当包含此命令时,您可以通过弹出窗口查看座席如何尝试玩耍和学习。 最好在座席接受训练时对此进行评论,以节省时间。

  1. 最后,关闭环境:
env.close()

工作原理

前面的代码实现了一个随机智能体; 智能体会随机选择以下四个动作之一:

要观察的另一件重要事情是,在这种环境中,动作空间是离散的,而观察空间是盒子类型的。 OpenAI 中关于空间(动作/观察)的术语“离散”和“框”是指允许的值。 离散空间允许固定范围的非负数,在我们的情况下为(0,1,2,3)。 另一方面,盒子空间表示一个n维盒子,因此对于吃豆人来说,任何有效观察结果都是210×160×3数字的数组。

更多

OpenAI Gym 由许多不同的环境组成,其活跃的贡献社区在其中添加了许多环境。 要获取所有现有环境的列表,可以运行以下简单代码

from gym import envs
env_ids = [spec.id for spec in envs.registry.all()]
print("Total Number of environments are", len(env_ids))
for env_id in sorted(env_ids):
    print(env_id)

目前,OpenAI Gym 共有 777 个环境。 这是吃豆人使用与之前相同的随机智能体的图像:

另见

  • 可以从这里获取有关不同环境的详细信息。
  • 这个链接中为某些环境维护了 Wiki 页面
  • 可以从这个链接获得有关安装说明和依赖项的详细信息。

实现神经网络智能体来玩吃豆人

让我们首先构建一个简单的神经网络智能体来玩“吃豆人”游戏。 我们将创建具有一组随机权重和偏差的智能体。 然后,这些特工将尝试玩游戏; 我们选择能够发挥最长平均持续时间的特工,并假设他们是最佳策略。

准备

此秘籍中的智能体没有学习任何策略; 他们根据初始权重(固定策略)做出决策。 智能体根据神经网络给出的概率来选择动作。 每个智能体做出的决定仅基于对环境的当前观察。

我们通过完全连接的神经网络来实现。 NN 的输入由环境的观察空间决定; 输出神经元的数量由可能的离散动作的数量决定。 吃豆人游戏有九种可能的离散动作-无操作,右转,左转,上转,向下转,左移,右移,上移和下移-因此我们的 NN 具有九个输出神经元。

操作步骤

让我们从秘籍开始:

  1. 与往常一样,第一步是导入模块。 在这种情况下,除了通常的模块之外,我们还将导入gym,以便我们可以使用它提供的不同环境:
import gym
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
  1. 接下来,我们创建一个RlAgent类。 该类由三种方法组成-__init__方法初始化 NN 大小并创建计算图。 在这里,我们使用了 TensorFlow 函数tf.multinomial来决定采取的可能措施。 该函数根据我们网络的 9 个输出神经元的 Sigmoid 值返回操作。 这样可以确保网络根据概率选择最终操作。 predict方法返回由 NN 预测的动作。 get_weights方法可帮助我们获得获胜者智能体的权重和偏置:
class RlAgent(object):
 def __init__(self,m,n,ini=False,W=None, b=None ):
     self._graph = tf.Graph()
     with self._graph.as_default():
     self._X = tf.placeholder(tf.float32,shape=(1,m))
     if ini==False:
         self.W = tf.Variable(tf.random_normal([m,n]), trainable=False)
         self.bias =         tf.Variable(tf.random_normal([1,n]),trainable=False)
     else:
         self.W = W
         self.bias = b
     out = tf.nn.sigmoid(tf.matmul(self._X,self.W)+ self.bias)
     self._result = tf.multinomial(out,1)
     init = tf.global_variables_initializer()

    self._sess = tf.Session()
    self._sess.run(init)

    def predict(self, X):
         action = self._sess.run(self._result, feed_dict= {self._X: X})
         return action

    def get_weights(self):
         W, b = self._sess.run([self.W, self.bias])
         return W, b
  1. 我们定义了一些辅助函数来玩一个完整的游戏play_one_episode
def play_one_episode(env, agent):
    obs = env.reset()
    img_pre = preprocess_image(obs)
     done = False
     t = 0
    while not done and t < 10000:
         env.render()  # This can be commented to speed up 
         t += 1
         action = agent.predict(img_pre)
         #print(t,action)
         obs, reward, done, info = env.step(action)
         img_pre = preprocess_image(obs)
         if done:
             break
    return t
  1. play_multiple_episodes函数创建智能体的一个实例,并使用该智能体进行许多游戏,并返回其平均游戏时间:
def play_multiple_episodes(env, T,ini=False, W=None, b=None):
    episode_lengths = np.empty(T)
    obs = env.reset()
    img_pre = preprocess_image(obs)
    if ini== False:
        agent = RlAgent(img_pre.shape[1],env.action_space.n)
    else:
        agent = RlAgent(img_pre.shape[1],env.action_space.n,ini, W, b)
    for i in range(T):
        episode_lengths[i] = play_one_episode(env, agent)
    avg_length = episode_lengths.mean()
    print("avg length:", avg_length)
    if ini == False:
        W, b = agent.get_weights()
    return avg_length, W, b
  1. random_search函数调用play_multiple_episodes; 每次调用play_multiple_episodes时,都会使用一组新的随机权重和偏差来实例化新智能体。 这些随机创建的 NN 智能体之一将胜过其他智能体,这将是我们最终选择的智能体:
def random_search(env):
    episode_lengths = []
    best = 0
    for t in range(10):
        print("Agent {} reporting".format(t))
        avg_length, wts, bias = play_multiple_episodes(env, 10)
        episode_lengths.append(avg_length)
        if avg_length > best:
            best_wt = wts
            best_bias = bias
            best = avg_length
    return episode_lengths, best_wt, best_bias
  1. 每次执行步骤时,环境都会返回一个观察场。 该观察具有三个颜色通道。 为了将其馈送到 NN,需要对观测值进行预处理,目前,我们唯一要做的预处理是将其转换为灰度,增加对比度并将其整形为行向量:
def preprocess_image(img):
    img = img.mean(axis =2) # to grayscale
    img[img==150] = 0  # Bring about a better contrast
    img = (img - 128)/128 - 1 # Normalize image from -1 to 1
    m,n = img.shape
    return img.reshape(1,m*n)
  1. NN 智能体一一实例化,然后选择最佳智能体。 为了提高计算效率,我们目前仅搜索 10 个智能体,每个智能体玩 10 场比赛。 玩得最长的游戏被认为是最好的:
if __name__ == '__main__':
    env_name = 'Breakout-v0'
    #env_name = 'MsPacman-v0'
    env = gym.make(env_name)
    episode_lengths, W, b = random_search(env)
    plt.plot(episode_lengths)
    plt.show()
    print("Final Run with best Agent")
    play_multiple_episodes(env,10, ini=True, W=W, b=b)

结果如下:

我们可以看到我们的随机智能体也可以平均 615.5 的长度玩游戏。 不错!

用 Q 学习平衡推车

如导言所述,我们有一个由状态ss ∈ S,其中S是所有可能状态的集合)描述的环境,可以执行动作aa ∈ A,其中A由所有可能的动作组成),从而导致主体从一种状态移动到另一种状态 。 智能体因其行为而受到奖励,智能体的目标是使奖励最大化。 在 Q 学习中,智能体通过计算最大化报酬的状态-动作组合的数量(R)来学习要采取的动作(策略,π)。 在选择行动时,智能体不仅要考虑现在的奖励,而且要考虑未来的折扣。

Q: S × A → R

智能体以Q的任意初始值开头,并且随着智能体选择动作a并获得奖励r,它会更新状态s(取决于过去状态s和动作a)和Q值:

Q(s, a) = (1 - α) · Q(s, a) + α(r + γ · max[a']Q(s', a'))

在此,α是学习率,γ是折扣因子。 第一项保留Q的旧值,第二项提供Q值的改进估计值(它包括当前奖励和未来行动的折现奖励)。 当结果状态不理想时,这将降低Q值,从而确保智能体在下次遇到此状态时不会选择相同的动作。 类似地,当期望结果状态时,相应的Q值将增加。

Q 学习的最简单实现涉及维护和更新状态-作用值查找表; 表的大小将为N×M,其中N是所有可能状态的数量,M是所有可能动作的数量。 对于大多数环境,此表将很大。 表越大,搜索所需的时间就越多,并且存储表所需的内存也就越多,因此这不是可行的解决方案。 在本秘籍中,我们将使用 Q 学习的 NN 实现。 在此,将神经网络用作函数逼近器来预测值函数(Q)。 NN 具有等于可能动作数的输出节点,并且它们的输出表示相应动作的值函数。

准备

我们将训练一个线性神经网络来解决'CartPole-v0'环境。 目的是平衡手推车上的杆。 观测状态由四个连续值参数组成:推车位置[-2.4, 2.4],推车速度[-∞, ∞],极角[~-41.8º, ~41.8º]和极限速度[-∞, ∞]。 可以通过向左或向右推推车来实现平衡,因此动作空间由两个可能的动作组成。 您可以看到CartPole-v0环境空间:

现在,对于 Q 学习,我们需要找到一种量化连续值观测状态的方法。 这是使用类FeatureTransform实现的; 该类首先生成 20,000 个观察空间示例的随机样本。 随机生成的观察空间示例使用 scikit StandardScaler类进行了标准化。 然后以不同的方差使用 scikit 的RBFSampler来覆盖观察空间的不同部分。 FeatureTransformer类由随机观察空间示例实例化,该示例用于使用fit_transform函数方法训练RBFSampler

后来,使用transform方法将连续观察空间转换为这种特征化表示:

class FeatureTransformer:
 def __init__(self, env):
   obs_examples = np.random.random((20000, 4))
   print(obs_examples.shape)
   scaler = StandardScaler()
   scaler.fit(obs_examples)

   # Used to converte a state to a featurizes represenation.
   # We use RBF kernels with different variances to cover different parts of the space
   featurizer = FeatureUnion([
       ("cart_position", RBFSampler(gamma=0.02, n_components=500)),
       ("cart_velocity", RBFSampler(gamma=1.0, n_components=500)),
       ("pole_angle", RBFSampler(gamma=0.5, n_components=500)),
       ("pole_velocity", RBFSampler(gamma=0.1, n_components=500))
       ])
    feature_examples =          featurizer.fit_transform(scaler.transform(obs_examples))
    print(feature_examples.shape)

    self.dimensions = feature_examples.shape[1]
    self.scaler = scaler
    self.featurizer = featurizer

def transform(self, observations):
    scaled = self.scaler.transform(observations)
    return self.featurizer.transform(scaled)

操作步骤

我们按以下步骤进行:

  1. 第一步是导入必要的模块。 这次,除了我们通常的 TensorFlow,Numpy 和 Matplotlib,我们还将从 scikit 导入 Gym 和一些类:
import numpy as np
import tensorflow as tf
import gym
import matplotlib.pyplot as plt
from sklearn.pipeline import FeatureUnion
from sklearn.preprocessing import StandardScaler
from sklearn.kernel_approximation import RBFSampler
  1. 在 Q 学习中,我们使用 NN 作为函数逼近器来估计值函数。 我们定义一个线性NeuralNetwork类; NN 将把变换后的观测空间作为输入并预测估计的Q值。 由于我们有两个可能的动作,因此我们需要两个不同的神经网络对象来获取预测的状态动作值。 该类包括训练单个 NN 和预测输出的方法:
class NeuralNetwork:
 def __init__(self, D):
     eta = 0.1
     self.W = tf.Variable(tf.random_normal(shape=(D, 1)), name='w')
     self.X = tf.placeholder(tf.float32, shape=(None, D), name='X')
     self.Y = tf.placeholder(tf.float32, shape=(None,), name='Y')

     # make prediction and cost
     Y_hat = tf.reshape(tf.matmul(self.X, self.W), [-1])
     err = self.Y - Y_hat
     cost = tf.reduce_sum(tf.pow(err,2))

    # ops we want to call later
    self.train_op = tf.train.GradientDescentOptimizer(eta).minimize(cost)
    self.predict_op = Y_hat

    # start the session and initialize params
    init = tf.global_variables_initializer()
    self.session = tf.Session()
    self.session.run(init)

def train(self, X, Y):
    self.session.run(self.train_op, feed_dict={self.X: X, self.Y: Y})

def predict(self, X):
    return self.session.run(self.predict_op, feed_dict={self.X: X})
  1. 下一个重要的类是Agent类,它使用NeuralNetwork类来创建学习智能体。 实例化类创建具有两个线性 NN 的智能体,每个线性 NN 具有 2,000 个输入神经元和 1 个输出神经元。 (从本质上讲,这意味着该智能体具有 2 个神经元,每个神经元具有 2,000 个输入,因为 NN 的输入层不执行任何处理)。 Agent类具有定义为预测两个 NN 的输出并更新两个 NN 权重的方法。 此处的智能体在训练阶段使用 Epsilon 贪婪策略进行探索。 在每个步骤中,智能体程序会根据 epsilon(eps)的值选择具有最高Q值的操作或随机操作。 epsilon 在训练过程中经过退火处理,因此,最初,智能体会采取许多随机动作(探索),但随着训练的进行,会采取具有最大Q值的动作(探索)。 这称为探索与开发的权衡取舍:我们允许智能体在被利用的操作过程中探索随机操作,这使智能体可以尝试新的随机操作并从中学习:
class Agent:
 def __init__(self, env, feature_transformer):
 self.env = env
 self.agent = []
 self.feature_transformer = feature_transformer
 for i in range(env.action_space.n):
 model = NeuralNetwork(feature_transformer.dimensions)
 self.agent.append(model)

def predict(self, s):
 X = self.feature_transformer.transform([s])
 return np.array([m.predict(X)[0] for m in self.agent])

def update(self, s, a, G):
 X = self.feature_transformer.transform([s])
 self.agent[a].train(X, [G])

def sample_action(self, s, eps):
 if np.random.random() < eps:
     return self.env.action_space.sample()
 else:
     return np.argmax(self.predict(s))
  1. 接下来,我们定义一个函数来播放一集; 它类似于我们先前使用的play_one函数,但现在我们使用 Q 学习来更新智能体的权重。 我们首先使用env.reset()重置环境,然后开始游戏,直到完成游戏为止(并进行了最大迭代以确保程序结束)。 像以前一样,智能体为当前观察状态选择一个动作,并在环境上执行该动作(env.step(action))。 现在的区别在于,根据先前状态和采取操作后的状态,使用G = r + γ · max[a']Q(s', a')更新 NN 权重,以便它可以预测与某个动作相对应的准确期望值。 为了获得更好的稳定性,我们修改了奖励-杆位下降时,座席会获得 -400 的奖励,否则,每一步都会获得 +1 的奖励:
def play_one(env, model, eps, gamma):
 obs = env.reset()
 done = False
 totalreward = 0
 iters = 0
 while not done and iters < 2000:
 action = model.sample_action(obs, eps)
 prev_obs = obs
 obs, reward, done, info = env.step(action)
 env.render()   # Can comment it to speed up.

if done:
 reward = -400

# update the model
 next = model.predict(obs)
 assert(len(next.shape) == 1)
 G = reward + gamma*np.max(next)
 model.update(prev_obs, action, G)

if reward == 1:
 totalreward += reward
iters += 1
  1. 现在所有函数和类均已就绪,我们定义了智能体和环境(在本例中为'CartPole-v0')。 该智能体总共播放 1000 集,并通过使用值函数与环境交互来学习:
if __name__ == '__main__':
    env_name = 'CartPole-v0'
    env = gym.make(env_name)
    ft = FeatureTransformer(env)
    agent = Agent(env, ft)
    gamma = 0.97

    N = 1000
    totalrewards = np.empty(N)
    running_avg = np.empty(N)
    for n in range(N):
        eps = 1.0 / np.sqrt(n + 1)
        totalreward = play_one(env, agent, eps, gamma)
        totalrewards[n] = totalreward
        running_avg[n] = totalrewards[max(0, n - 100):(n + 1)].mean()
        if n % 100 == 0:
            print("episode: {0}, total reward: {1} eps: {2} avg reward (last 100): {3}".format(n, totalreward, eps,
                                                                                               running_avg[n]), )

    print("avg reward for last 100 episodes:", totalrewards[-100:].mean())
    print("total steps:", totalrewards.sum())

    plt.plot(totalrewards)
    plt.xlabel('episodes')
    plt.ylabel('Total Rewards')
    plt.show()

    plt.plot(running_avg)

    plt.xlabel('episodes')
    plt.ylabel('Running Average')
    plt.show()
    env.close()

  1. 以下是智能体通过游戏获悉的总奖励和移动平均奖励的图。 根据 Cart-Pole Wiki 的说法,奖励 200 分表示该特工在接受 1,000 次训练后赢得了该剧集。 我们的特工播放 100 集时平均获得 195.7 的平均奖励,这是一项了不起的壮举:

更多

可以使用相同的逻辑为 OpenAI 的其他环境创建智能体。 但是,对于诸如 Breakout 或 Pac-Man 之类的 Atari 游戏,观察空间并不只是由四个数字组成的数组。 相反,它非常大(210×160 = 33,600 像素,具有 3 个 RGB 值); 如果没有某种形式的量化,则这种简单的 NN 可能的状态是无限的,并且不会产生良好的结果。 我们将在深度 Q 学习秘籍中使用 CNN 解决此问题。

另见

尽管有很多有关 Q 学习的 Web 链接,但一些有用的链接如下:

深度 Q 网络和 Atari 游戏

深度 Q 网络DQN)是 Q 学习与卷积神经网络CNN)的结合。 由 Mnih 等人在 2013 年提出。 CNN 网络具有提取空间信息的能力,因此能够从原始像素数据中学习成功的控制策略。 我们已经在第 4 章,“卷积神经网络”中使用了 CNN,因此我们直接从这里开始。

此秘籍基于 DeepMind 的原始 DQN 论文《使用深度强化学习玩 Atari》。 在本文中,他们使用了一种称为“经验重放”的概念,该概念涉及随机采样先前的游戏动作(状态,动作奖励,下一状态)。位于前脑底部的核称为“基底神经节”, 根据神经科学,它们负责选择动作,即帮助我们确定在任何给定时间执行几种可能动作中的哪一种。

准备

如先前的秘籍所述,“用 Q 学习平衡 CartPole”,对于像《吃豆人》或 Breakout 之类的 Atari 游戏,我们需要预处理观察状态空间,该状态空间由 33,600 个像素组成,具有 3 个 RGB 值。 这些像素中的每个像素都可以采用 0 到 255 之间的任何值。我们的preprocess函数应该能够量化像素的可能值,同时减少观察状态空间。

我们利用 Scipy 的imresize函数对图像进行下采样。 以下函数preprocess在将图像馈送到 DQN 之前:

def preprocess(img):
    img_temp = img[31:195]  # Choose the important area of the image
    img_temp = img_temp.mean(axis=2)  # Convert to Grayscale#
    # Downsample image using nearest neighbour interpolation
    img_temp = imresize(img_temp, size=(IM_SIZE, IM_SIZE), interp='nearest')
    return img_temp

IM_SIZE是一个全局参数-在代码中,我们将其值为 80。该函数具有描述每个过程的注释。 在这里,您可以看到预处理前后的观察空间:

要注意的另一重要事项是,当前的观察空间不能完全显示游戏情况。 例如,参见上图,您无法确定桨叶是向左还是向右移动。 因此,为了完全理解游戏的当前状态,我们需要考虑动作和观察的顺序。 在秘籍中,我们考虑了四个动作和观察序列,以确定当前情况并训练智能体。 这是借助state_update函数完成的,该函数将当前的观察状态附加到先前的状态,从而生成一系列状态:

def update_state(state, obs):
    obs_small = preprocess(obs)
    return np.append(state[1:], np.expand_dims(obs_small, 0), axis=0)

最后,为了解决训练时的稳定性问题,我们使用了target_network的概念,它是 DQN 的副本,但更新频率不高。 我们使用目标网络来生成 DQN 网络的目标值函数,而 DQN 在每个步骤/片段都进行更新,并且target_network在固定间隔后进行更新(与 DQN 相同)。 由于所有更新都在 TensorFlow 会话中进行,因此我们使用名称范围来区分target_network和 DQN 网络。

操作步骤

  1. 我们导入必要的模块。 我们正在使用sys模块的stdout.flush()来帮助我们强制 Python 刷新标准输出(在我们的情况下为计算机监视器)中的数据。 random模块用于从经验重放缓冲区(我们存储过去经验的缓冲区)中获取随机样本。 datetime模块用于跟踪训练时间:
import gym
import sys
import random
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from datetime import datetime
from scipy.misc import imresize
  1. 我们定义训练的超参数; 您可以通过更改它们进行试验。 这些参数定义了经验重放缓冲区的最小和最大大小以及更新目标网络之后的情节数量:
MAX_EXPERIENCES = 500000
MIN_EXPERIENCES = 50000
TARGET_UPDATE_PERIOD = 10000
IM_SIZE = 80
K = 4
  1. 定义了类DQN; 其构造器使用tf.contrib.layers.conv2d函数构建 CNN 网络,并定义成本和训练操作:
class DQN:
    def __init__(self, K, scope, save_path= 'models/atari.ckpt'):

        self.K = K
        self.scope = scope
        self.save_path = save_path

        with tf.variable_scope(scope):

            # inputs and targets
            self.X = tf.placeholder(tf.float32, shape=(None, 4, IM_SIZE, IM_SIZE), name='X')

            # tensorflow convolution needs the order to be:
            # (num_samples, height, width, "color")
            # so we need to tranpose later
            self.G = tf.placeholder(tf.float32, shape=(None,), name='G')
            self.actions = tf.placeholder(tf.int32, shape=(None,), name='actions')

            # calculate output and cost
            # convolutional layers
            Z = self.X / 255.0
            Z = tf.transpose(Z, [0, 2, 3, 1])
            cnn1 = tf.contrib.layers.conv2d(Z, 32, 8, 4, activation_fn=tf.nn.relu)
            cnn2 = tf.contrib.layers.conv2d(cnn1, 64, 4, 2, activation_fn=tf.nn.relu)
            cnn3 = tf.contrib.layers.conv2d(cnn2, 64, 3, 1, activation_fn=tf.nn.relu)

            # fully connected layers
            fc0 = tf.contrib.layers.flatten(cnn3)
            fc1 = tf.contrib.layers.fully_connected(fc0, 512)

            # final output layer
            self.predict_op = tf.contrib.layers.fully_connected(fc1, K)

            selected_action_values = tf.reduce_sum(self.predict_op * tf.one_hot(self.actions, K),
                reduction_indices=[1]
            )

            self.cost = tf.reduce_mean(tf.square(self.G - selected_action_values))
            self.train_op = tf.train.RMSPropOptimizer(0.00025, 0.99, 0.0, 1e-6).minimize(self.cost)
  1. 该类具有设置会话set_session(),预测动作值函数predict(),更新网络update()以及使用 Epsilon 贪婪算法sample_action()选择动作的方法:
def set_session(self, session):
    self.session = session

def predict(self, states):
    return self.session.run(self.predict_op, feed_dict={self.X: states})

def update(self, states, actions, targets):
    c, _ = self.session.run(
        [self.cost, self.train_op],
        feed_dict={
            self.X: states,
            self.G: targets,
            self.actions: actions
        }
    )
    return c

def sample_action(self, x, eps):
    """Implements epsilon greedy algorithm"""
    if np.random.random() < eps:
        return np.random.choice(self.K)
    else:
        return np.argmax(self.predict([x])[0])
  1. 我们还定义了加载和保存网络的方法,因为训练可能会花费一些时间:
def load(self):
    self.saver = tf.train.Saver(tf.global_variables())
    load_was_success = True
    try:
        save_dir = '/'.join(self.save_path.split('/')[:-1])
        ckpt = tf.train.get_checkpoint_state(save_dir)
        load_path = ckpt.model_checkpoint_path
        self.saver.restore(self.session, load_path)
    except:
        print("no saved model to load. starting new session")
        load_was_success = False
    else:
        print("loaded model: {}".format(load_path))
        saver = tf.train.Saver(tf.global_variables())
        episode_number = int(load_path.split('-')[-1])

def save(self, n):
    self.saver.save(self.session, self.save_path, global_step=n)
    print("SAVED MODEL #{}".format(n))
  1. 将主 DQN 网络的参数复制到目标网络的方法如下:
def copy_from(self, other):
    mine = [t for t in tf.trainable_variables() if t.name.startswith(self.scope)]
    mine = sorted(mine, key=lambda v: v.name)
    others = [t for t in tf.trainable_variables() if t.name.startswith(other.scope)]
    others = sorted(others, key=lambda v: v.name)

    ops = []
    for p, q in zip(mine, others):
        actual = self.session.run(q)
        op = p.assign(actual)
        ops.append(op)

    self.session.run(ops)
  1. 我们定义一个函数learn(),它预测值函数并更新原始 DQN 网络:
def learn(model, target_model, experience_replay_buffer, gamma, batch_size):
    # Sample experiences
    samples = random.sample(experience_replay_buffer, batch_size)
    states, actions, rewards, next_states, dones = map(np.array, zip(*samples))

    # Calculate targets
    next_Qs = target_model.predict(next_states)
    next_Q = np.amax(next_Qs, axis=1)
    targets = rewards + np.invert(dones).astype(np.float32) * gamma * next_Q

    # Update model
    loss = model.update(states, actions, targets)
    return loss
  1. 既然我们已经在主代码中定义了所有特征,我们就可以使用它们来构建和训练 DQN 网络以玩 Atari 游戏。 该代码经过了很好的注释,并且是对先前 Q 学习代码的扩展,并增加了经验重放缓冲区,因此您应该不难理解它:
if __name__ == '__main__':
    # hyperparameters
    gamma = 0.99
    batch_sz = 32
    num_episodes = 500
    total_t = 0
    experience_replay_buffer = []
    episode_rewards = np.zeros(num_episodes)
    last_100_avgs = []

    # epsilon for Epsilon Greedy Algorithm
    epsilon = 1.0
    epsilon_min = 0.1
    epsilon_change = (epsilon - epsilon_min) / 500000

    # Create Atari Environment
    env = gym.envs.make("Breakout-v0")

    # Create original and target  Networks
    model = DQN(K=K, gamma=gamma, scope="model")
    target_model = DQN(K=K, gamma=gamma, scope="target_model")

    with tf.Session() as sess:
        model.set_session(sess)
        target_model.set_session(sess)
        sess.run(tf.global_variables_initializer())
        model.load()

        print("Filling experience replay buffer...")
        obs = env.reset()
        obs_small = preprocess(obs)
        state = np.stack([obs_small] * 4, axis=0)

        # Fill experience replay buffer
        for i in range(MIN_EXPERIENCES):

            action = np.random.randint(0,K)
            obs, reward, done, _ = env.step(action)
            next_state = update_state(state, obs)

            experience_replay_buffer.append((state, action, reward, next_state, done))

            if done:
                obs = env.reset()
                obs_small = preprocess(obs)
                state = np.stack([obs_small] * 4, axis=0)

            else:
                state = next_state

        # Play a number of episodes and learn
        for i in range(num_episodes):
            t0 = datetime.now()

            # Reset the environment
            obs = env.reset()
            obs_small = preprocess(obs)
            state = np.stack([obs_small] * 4, axis=0)
            assert (state.shape == (4, 80, 80))
            loss = None

            total_time_training = 0
            num_steps_in_episode = 0
            episode_reward = 0

            done = False
            while not done:

                # Update target network
                if total_t % TARGET_UPDATE_PERIOD == 0:
                    target_model.copy_from(model)
                    print("Copied model parameters to target network. total_t = %s, period = %s" % (
                        total_t, TARGET_UPDATE_PERIOD))

                # Take action
                action = model.sample_action(state, epsilon)
                obs, reward, done, _ = env.step(action)
                obs_small = preprocess(obs)
                next_state = np.append(state[1:], np.expand_dims(obs_small, 0), axis=0)

                episode_reward += reward

                # Remove oldest experience if replay buffer is full
                if len(experience_replay_buffer) == MAX_EXPERIENCES:
                    experience_replay_buffer.pop(0)

                # Save the recent experience
                experience_replay_buffer.append((state, action, reward, next_state, done))

                # Train the model and keep measure of time
                t0_2 = datetime.now()
                loss = learn(model, target_model, experience_replay_buffer, gamma, batch_sz)
                dt = datetime.now() - t0_2

                total_time_training += dt.total_seconds()
                num_steps_in_episode += 1

                state = next_state
                total_t += 1

                epsilon = max(epsilon - epsilon_change, epsilon_min)

            duration = datetime.now() - t0

            episode_rewards[i] = episode_reward
            time_per_step = total_time_training / num_steps_in_episode

            last_100_avg = episode_rewards[max(0, i - 100):i + 1].mean()
            last_100_avgs.append(last_100_avg)
            print("Episode:", i,"Duration:", duration, "Num steps:", num_steps_in_episode,
                  "Reward:", episode_reward, "Training time per step:", "%.3f" % time_per_step,
                  "Avg Reward (Last 100):", "%.3f" % last_100_avg,"Epsilon:", "%.3f" % epsilon)

            if i % 50 == 0:
                model.save(i)
            sys.stdout.flush()

    #Plots
    plt.plot(last_100_avgs)
    plt.xlabel('episodes')
    plt.ylabel('Average Rewards')
    plt.show()
    env.close()

从上图中我们可以看到,特工通过训练获得了更高的报酬,并且通过每 100 集的平均报酬图可以清楚地看到情况:

这只是在训练的前 500 集之后; 为了获得更好的效果,您将需要训练更长的时间,约 10,000 集。

更多

训练智能体需要花费很多时间,这既浪费时间又消耗内存。 OpenAI Gym 提供了一个包装器来将游戏另存为视频,因此,除了使用渲染之外,您还可以使用包装器来保存视频并随后监视智能体的学习方式。 AI 工程师和发烧友可以上传这些视频以显示结果。 为此,我们需要首先导入包装器,然后创建环境,最后使用监视器。 默认情况下,它将存储 1、8、27、64 等视频,然后每第 1000 集(带有完美立方体的情节编号)存储; 默认情况下,每项训练都保存在一个文件夹中。 为此要添加的代码如下:

import gym
from gym import wrappers
env = gym.make('Breakout-v0)
env = wrappers.Monitor(env, '/save-path')

如果您想在下一个训练中使用相同的文件夹,可以将force=True添加到监视器。

另见

  • Mnih, Volodymyr, and others, Playing Atari with deep reinforcement learning, arXiv preprint arXiv:1312.5602 (2013) (https://arxiv.org/pdf/1312.5602.pdf)
  • Mnih, Volodymyr, et al. Human-level control through deep reinforcement learning, Nature 518.7540 (2015): 529-533
  • 玩 Atari 的 DQN 的一个很酷的实现

使用策略梯度玩 Pong 游戏

到目前为止,策略梯度是最常用的 RL 算法之一。 研究表明,经过适当调优后,它们的表现要优于 DQN,同时不会遭受过多的内存和计算缺点。 与 Q 学习不同,策略梯度使用参数化策略,该策略可以选择操作而不咨询值函数。 在策略梯度中,我们讨论了表现度量η(θ[p]); 目标是最大程度地提高表现,因此根据梯度上升算法更新 NN 的权重。 但是,TensorFlow 没有maximum优化器,因此我们使用表现梯度的负值-∇η(θ[p])并将其最小化。

准备

Pong 的游戏是一个两人游戏,目标是将球弹回另一位玩家。 智能体可以上下移动操纵杆(是的,是标准NoOp)。 OpenAI 环境中的一名玩家是一位体面的 AI 玩家,他知道如何很好地玩游戏。 我们的目标是使用策略梯度来训练第二个智能体。 我们的经纪人精通所玩的每款游戏。 虽然代码已构建为只能运行 500 集,但我们应该添加一条规定以将智能体状态保存在指定的检查点,并且在下一次运行时,首先加载上一个检查点。 为此,我们首先声明一个保护程序,然后使用 TensorFlow saver.save方法保存当前的网络状态(检查点),最后从最后保存的检查点加载网络。 为完成本秘籍的部分,在“操作步骤”一节中定义的以下PolicyNetwork类方法可以执行此工作:

def load(self):
    self.saver = tf.train.Saver(tf.global_variables())
    load_was_success = True  # yes, I'm being optimistic
    try:
        save_dir = '/'.join(self.save_path.split('/')[:-1])
        ckpt = tf.train.get_checkpoint_state(save_dir)
        load_path = ckpt.model_checkpoint_path
        self.saver.restore(self.session, load_path)
    except:
        print("no saved model to load. starting new session")
        load_was_success = False
    else:
        print("loaded model: {}".format(load_path))
        saver = tf.train.Saver(tf.global_variables())
        episode_number = int(load_path.split('-')[-1])

为了每 50 集保存一次模型,我们使用以下方法:

def save(self):
    self.saver.save(self.session, self.save_path, global_step=n)
    print("SAVED MODEL #{}".format(n))

操作步骤

  1. 此秘籍的代码基于 Andrej Karpathy 博客,并且其中一部分已由 Sam Greydanus 的代码进行了改编。
  2. 我们有通常的导入:
import numpy as np
import gym
import matplotlib.pyplot as plt
import tensorflow as tf
  1. 我们定义我们的PolicyNetwork类。 在类构建期间,还将初始化模型超参数。 __init__方法定义输入状态self.tf_x的占位符; 预测作用,self.tf.y; 相应的奖励,self.tf_epr; 网络权重; 并预测行动值,训练和更新。 您可以看到该类构造还启动了一个交互式 TensorFlow 会话:
class PolicyNetwork(object):
    def __init__(self, N_SIZE, h=200, gamma=0.99, eta=1e-3, decay=0.99, save_path = 'models2/pong.ckpt' ):

        self.gamma = gamma
        self.save_path = save_path
        # Placeholders for passing state....
        self.tf_x = tf.placeholder(dtype=tf.float32, shape=[None, N_SIZE * N_SIZE], name="tf_x")
        self.tf_y = tf.placeholder(dtype=tf.float32, shape=[None, n_actions], name="tf_y")
        self.tf_epr = tf.placeholder(dtype=tf.float32, shape=[None, 1], name="tf_epr")

        # Weights
        xavier_l1 = tf.truncated_normal_initializer(mean=0, stddev=1\. / N_SIZE, dtype=tf.float32)
        self.W1 = tf.get_variable("W1", [N_SIZE * N_SIZE, h], initializer=xavier_l1)
        xavier_l2 = tf.truncated_normal_initializer(mean=0, stddev=1\. / np.sqrt(h), dtype=tf.float32)
        self.W2 = tf.get_variable("W2", [h, n_actions], initializer=xavier_l2)

        # Build Computation
        # tf reward processing (need tf_discounted_epr for policy gradient wizardry)
        tf_discounted_epr = self.tf_discount_rewards(self.tf_epr)
        tf_mean, tf_variance = tf.nn.moments(tf_discounted_epr, [0], shift=None, name="reward_moments")
        tf_discounted_epr -= tf_mean
        tf_discounted_epr /= tf.sqrt(tf_variance + 1e-6)

        # Define Optimizer, compute and apply gradients
        self.tf_aprob = self.tf_policy_forward(self.tf_x)
        loss = tf.nn.l2_loss(self.tf_y - self.tf_aprob)
        optimizer = tf.train.RMSPropOptimizer(eta, decay=decay)
        tf_grads = optimizer.compute_gradients(loss, var_list=tf.trainable_variables(), grad_loss=tf_discounted_epr)
        self.train_op = optimizer.apply_gradients(tf_grads)

        # Initialize Variables
        init = tf.global_variables_initializer()

        self.session = tf.InteractiveSession()
        self.session.run(init)
        self.load()
  1. 我们定义了一种计算折现奖励的方法。 这确保智能体不仅考虑当前奖励,而且考虑未来奖励。 任意时间t的折现奖励为R[t] = ∑γ[k]r[t + k],其中总和超过k∈[0, ∞],并且γ是贴现因子,值在 0 到 1 之间。在我们的代码中,我们使用了gamma = 0.99
def tf_discount_rewards(self, tf_r):  # tf_r ~ [game_steps,1]
    discount_f = lambda a, v: a * self.gamma + v;
    tf_r_reverse = tf.scan(discount_f, tf.reverse(tf_r, [0]))
    tf_discounted_r = tf.reverse(tf_r_reverse, [0])
    return tf_discounted_r
  1. 在给定输入观察状态的情况下,我们定义了tf_policy_forward方法来提供将桨向上移动的概率。 我们使用两层神经网络实现它。 网络获取处理过的游戏状态图像,并生成一个数字,表示将球拍向上移动的可能性。 在 TensorFlow 中,由于仅在 TensorFlow 会话中计算网络图,因此我们定义了另一种方法predict_UP来计算概率:
def tf_policy_forward(self, x): #x ~ [1,D]
     h = tf.matmul(x, self.W1)
     h = tf.nn.relu(h)
     logp = tf.matmul(h, self.W2)
     p = tf.nn.softmax(logp)
     return p

def predict_UP(self,x):
    feed = {self.tf_x: np.reshape(x, (1, -1))}
    aprob = self.session.run(self.tf_aprob, feed);
    return aprob
  1. PolicyNetwork智能体使用update方法更新权重:
def update(self, feed):
    return self.session.run(self.train_op, feed)
  1. 我们定义一个辅助函数来预处理观察状态空间:
# downsampling
def preprocess(I):
    """ prepro 210x160x3 uint8 frame into 6400 (80x80) 1D float vector """
    I = I[35:195] # crop
    I = I[::2,::2,0] # downsample by factor of 2
    I[I == 144] = 0  # erase background (background type 1)
    I[I == 109] = 0  # erase background (background type 2)
    I[I != 0] = 1    # everything else (paddles, ball) just set to 1
    return I.astype(np.float).ravel()

  1. 其余的很简单-我们创建一个游戏环境,定义要持有的数组(状态,动作,奖励,状态),并使智能体学习大量情节(休息或连续不断),这完全取决于您的计算能力和资源)。 这里要注意的重要一点是,智能体没有按动作步骤学习。 相反,智能体使用一个情节的完整(状态,动作,奖励,状态)集来纠正其策略。 这可能会占用大量内存:
if __name__ == '__main__':
    # Create Game Environment
    env_name = "Pong-v0"
    env = gym.make(env_name)
    env = wrappers.Monitor(env, '/tmp/pong', force=True)
    n_actions = env.action_space.n  # Number of possible actions
    # Initializing Game and State(t-1), action, reward, state(t)
    xs, rs, ys = [], [], []
    obs = env.reset()
    prev_x = None

    running_reward = None
    running_rewards = []
    reward_sum = 0
    n = 0
    done = False
    n_size = 80
    num_episodes = 500

    #Create Agent
    agent = PolicyNetwork(n_size)

    # training loop
    while not done and n< num_episodes:
        # Preprocess the observation
        cur_x = preprocess(obs)
        x = cur_x - prev_x if prev_x is not None else np.zeros(n_size*n_size)
        prev_x = cur_x

        #Predict the action
        aprob = agent.predict_UP(x) ; aprob = aprob[0,:]

        action = np.random.choice(n_actions, p=aprob)
        #print(action)
        label = np.zeros_like(aprob) ; label[action] = 1

        # Step the environment and get new measurements
        obs, reward, done, info = env.step(action)
        env.render()
        reward_sum += reward

        # record game history
        xs.append(x) ; ys.append(label) ; rs.append(reward)

        if done:
            # update running reward
            running_reward = reward_sum if running_reward is None else running_reward * 0.99 + reward_sum * 0.01
            running_rewards.append(running_reward)
            feed = {agent.tf_x: np.vstack(xs), agent.tf_epr: np.vstack(rs), agent.tf_y: np.vstack(ys)}
            agent.update(feed)
            # print progress console
            if n % 10 == 0:
                print ('ep {}: reward: {}, mean reward: {:3f}'.format(n, reward_sum, running_reward))
            else:
                print ('\tep {}: reward: {}'.format(n, reward_sum))

            # Start next episode and save model
            xs, rs, ys = [], [], []
            obs = env.reset()
            n += 1 # the Next Episode

            reward_sum = 0
            if n % 50 == 0:
                agent.save()
            done = False

    plt.plot(running_rewards)
    plt.xlabel('episodes')
    plt.ylabel('Running Averge')
    plt.show()
    env.close()

下图显示了智能体在前 500 个情节中学习时的平均运行奖励:

工作原理

权重使用 Xavier 初始化进行了初始化,这确保了我们的权重既不会太大也不会太小。 两种情况都阻碍了网络的学习。 在 Xavier 初始化中,为权重分配一个具有零均值和特定方差2/(nin+nout)的值,其中ninnout该层的输入和输出数。 要了解有关 Xavier 初始化的更多信息,请参阅 Glorot 和 Bengio 在 2009 年发表的论文。 有关详细信息,请参见“另见”部分。

更多

看到智能体第一次学习演奏的任何人都会对此感到惊讶-看起来很像人。 最初的举动总是很笨拙。 缓慢地,坐席会学习走哪条路,尽管速度很慢并且经常会错过球。 但是,随着学习的继续,智能体将成为专家。

但这与我们很不一样。 一旦学会玩游戏,我们便可以在其他任何类似情况下轻松使用该知识。 RL 智能体将无法执行此操作-即使是简单的事情(例如更改环境空间的大小)也会将其恢复为零。 迁移学习是研究人员正在研究的一种技术,它可以帮助主体在另一环境空间中的一个环境中使用它所学到的知识,也许有一天可以为真正的人工智能奠定基础。

AlphaGo Zero

最近,DeepMind 发表了有关 AlphaGo Zero(AlphaGo 的最新版本)的文章。 根据他们发布的结果,AlphaGo Zero 甚至更强大,并且是历史上最强大的围棋选手。 AlphaGo 从表格状态开始,即从空白状态开始,并且仅使用棋盘状态和与其对抗的游戏来调整神经网络并预测正确的动作。

AlphaGo Zero 使用深层神经网络,该网络将原始板表示形式(当前和历史)作为输入,并输出移动概率和值。 因此,该神经网络结合了策略网络和值网络的作用。 该网络是通过自玩游戏进行训练的,这与以前的 AlphaGo 版本不同(它们是使用监督学习进行训练的)。 在每个位置上,由神经网络指导执行蒙特卡洛树搜索(MCTS)。 通过使用 MCTS 播放每个动作的自演强化学习算法来训练神经网络。

最初,神经网络的权重是随机初始化的。 在每个迭代步骤中,都会生成许多自玩游戏。 在每个时间步,使用神经网络的先前迭代对可能的策略执行 MCTS 搜索,然后通过对搜索概率进行采样来进行移动。 重复此过程直到该特定游戏终止。 存储游戏状态,采取的策略以及游戏每个时间步骤的奖励。 并行地,根据自播放的先前迭代的所有时间步长之间均匀采样的数据训练神经网络。 调整神经网络的权重,以最小化预测值和自赢者之间的误差,并使神经网络移动概率与搜索概率的相似性最大化。

在配备 4 个 TPU 的单台机器上仅进行了 3 天的训练,AlphaGo Zero 以 100-0 击败 AlphaGo。 AlphaGo Zero 完全基于 RL。 可以在 2017 年 10 月发表于《自然》上的论文《掌握无人掌握的围棋游戏》中阅读其实现的详细信息。

另见

十、移动计算

在本章中,我们将讨论在移动设备上使用深度学习的问题,并为以下内容提供一些方法:

  • 安装适用于 macOS 和 Android 的 TensorFlow Mobile
  • 玩转 TensorFlow 和 Android 示例
  • 为 MacOS 和 iPhone 安装 TensorFlow Mobile
  • 为移动设备优化 TensorFlow 图
  • 转换移动设备的 TensorFlow 图

介绍

在本节中,我们将介绍移动深度学习的一些用例。 这与台式机或云深度学习的情况大不相同,在台式机或云深度学习中,GPU 和电力通常可用。 实际上,在移动设备上,保存电池非常重要,并且 GPU 经常不可用。 但是,深度学习在许多情况下可能非常有用。 让我们回顾一下:

  • 图像识别:现代手机具有功能强大的摄像头,用户热衷于尝试对图像和图片产生效果。 通常,了解图片中的内容也很重要,并且有多种适用于此的预训练模型,如专用于 CNN 的章节所述。 这里给出了用于图像识别的模型的一个很好的例子

  • 对象定位:识别运动对象是一项关键操作,对于视频和图像处理是必需的。 例如,可以想象如果在图像中识别出多个人,那么相机将使用多个对焦点。 这里提供了对象本地化示例的集合

  • 光学字符识别:在许多活动(例如文本分类和推荐)中,识别手写字符都是至关重要的。 深度学习可以为开展这些活动提供根本帮助。 在专用于 CNN 的章节中,我们研究了 MNIST 识别的一些示例。 关于 MNIST 的信息也可以在这个页面中找到。

  • 语音识别:语音识别是访问现代电话的常用界面。 因此,深度学习用于识别语音和口头命令。 在过去的几年中,这方面的进展令人印象深刻。

  • 翻译:处理多种语言是现代多元文化世界的一部分。 手机在各种语言之间进行即时翻译的准确率越来越高,深度学习帮助打破了障碍,而这在几年前是无法想象的。 在专门针对 RNN 的一章中,我们研究了一些机器翻译示例。

  • 手势识别:电话开始使用手势作为接收命令的界面。 当然,有一些模型。

  • 压缩:压缩是手机的关键方面。 可以想象,在通过网络发送图像或视频之前减少空间是有益的。 同样,在本地存储在设备上之前压缩数据可能会很方便。 在所有这些情况下,深度学习都可以提供帮助。 使用 RNNS 进行压缩的模型位于这里

TensorFlow,移动和云

如上所述,电话通常没有 GPU,因此节省电池电量非常重要。 为了减轻成本,需要将许多昂贵的计算卸载到云中。 当然,要折衷考虑各种因素,包括在移动设备上执行深度学习模型的成本,将数据移至云的成本,用于此传输的电池成本以及云计算的成本。 没有单一的解决方案,最佳策略取决于您的具体情况。

安装适用于 macOS 和 Android 的 TensorFlow Mobile

在本秘籍中,我们将学习如何为移动环境设置 TensorFlow。 我的环境是 macOS,我为 Android 开发。 但是,在以下秘籍中将描述其他配置。

准备

我们将使用 Android Studio,这是适用于 Google Android 操作系统的官方集成开发环境IDE)。

操作步骤

我们继续按以下步骤安装适用于 macOS 和 Android 的 TensorFlow mobile:

  1. 这里安装 Android Studio。

  2. 创建一个新的项目名称AndroidExampleTensorflow,如以下屏幕截图所示:

在 AndroidStudio 中创建 TensorFlow 移动应用的示例,第一步如下图所示:选择电话和表格选项:

在 AndroidStudio 中创建 TensorFlow 移动应用的示例,第二步并选择一个空活动,如下图所示:

在 AndroidStudio 中创建 TensorFlow 移动应用的示例,第三步然后自定义MainActivity,如下图所示:

在 AndroidStudio 中创建 TensorFlow 移动应用的示例,第四步称为“基础神经节”,根据神经科学,它负责选择动作,即帮助我们确定在任何给定时间执行几个可能动作中的哪个。

  1. 将以下行插入build.gradle应用中,如以下代码所示:
// added for automatically connect to TensorFlow via maven
repositories {
jcenter()
maven {
url 'https://google.bintray.com/TensorFlow'
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:26.+'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
// added for automatically compile TensorFlow
compile 'org.TensorFlow:TensorFlow-android:+'
testCompile 'junit:junit:4.12'
}

以下屏幕截图显示了插入的代码:

  1. 运行项目并获得结果:

使用 AndroidStudio 进行编译的示例,其中显示了连接的设备。

在 AndroidStudio 中创建 TensorFlow 移动应用的示例。 一个简单的Hello World应用

工作原理

使用 Android Studio 设置 Android TensorFlow 非常简单。 您只需要在应用的build.gradle文件中添加一些配置行,Android Studio 就会代表您执行所有操作。

更多

如果要直接从 TensorFlow 源构建,则需要安装 Bazel 和 TensorFlow。 Bazel 是一个快速,可扩展,多语言和可扩展的构建系统。 Google 内部使用了构建工具 Blaze,并将 Blaze 工具的开源部分称为 Bazel。 名称是 Blaze 的字谜。

此页面将指导您完成该过程

如果您正在运行 macOS,则过程非常简单:

  1. 按照这个页面上的说明安装 Bazel。 对于 macOS,我们将使用 Homebrew:
/usr/bin/ruby -e "$(curl -fsSL \
 https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install bazel
bazel version
brew upgrade bazel
  1. 从 GitHub 克隆 TensorFlow 发行版。
git clone https://github.com/TensorFlow/TensorFlow.git

玩转 TensorFlow 和 Android 示例

在本秘籍中,我们将考虑 TensorFlow 发行版中提供的标准 Android 示例并将其安装在我们的移动设备上。

准备

TensorFlow 移动 Android 应用可在 GitHub 上的以下地址获得。 2017 年 10 月,该页面包含以下示例:

  • TF 分类:使用 Google Inception 模型实时对相机帧进行分类,并在相机图像上以重叠显示顶部结果。
  • TF 检测:演示使用 TensorFlow 对象检测 API 训练的 SSD-Mobilenet 模型。 这是在现代卷积目标检测器的速度/精度折衷中引入的,以实时定位和跟踪摄像机预览中的目标(来自 80 个类别)。
  • TF 风格化:使用基于艺术风格的学习表示的模型将相机预览图像重新设置为许多不同艺术风格的图像。
  • TF 语音:运行在音频训练教程中构建的简单语音识别模型。 监听一小部分单词,并在识别它们时在 UI 中突出显示它们。

操作步骤

我们按以下步骤进行:

  1. 安装包的最佳方法是使用每晚创建的预构建 APK。 将浏览器指向这里并下载TensorFlow_demo.apk,如以下屏幕截图所示:

  1. 在您的设备上安装应用。 在我的示例中,我将使用 Android Studio 中可用的 Pixel XL 仿真设备。 这是直接从 Android Studio 内部模拟的终端设备。 命令adb devices列出所有连接的设备。 在这种情况下,我有一个 Pixel XL 模拟器,可以安装TensorFlow_demo apk
adb devices
List of devices attached
emulator-5554 device
adb install -r TensorFlow_demo.apk

安装后,仿真器将具有一组新的 TensorFlow 应用可供使用,如下图所示。

  1. 运行您喜欢的应用。 例如,以下图像是 TF Stylize 的示例,用于通过 Transfer Learning 将相机预览图像重新设置为多种不同艺术风格的图像:

下图是 TF 语音的示例(请记住为仿真器激活麦克风):

工作原理

如果您使用夜间构建演示和adb工具在设备上安装 APK,则安装 Android 的 TensorFlow 示例非常容易。

为 MacOS 和 iPhone 安装 TensorFlow Mobile

在本秘籍中,我们将学习如何在移动环境中设置 TensorFlow。 我的环境是 macOS,这里的想法是为 iOS 和 iPhone 开发。

准备

我们将使用 Xcode 开发环境和 CocoaPods 来预安装 TensorFlow。 我将假定您的环境中已经安装了 Xcode。 如果没有,请从这里下载。

操作步骤

我们将按照以下步骤进行操作:

  1. 使用以下命令安装 CocoaPods
sudo gem install cocoapods
pod setup
Setting up CocoaPods master repo
$ /usr/local/git/current/bin/git clone https://github.com/CocoaPods/Specs.git master --progress
Cloning into 'master'...
remote: Counting objects: 1602077, done.
remote: Compressing objects: 100% (243/243), done.
remote: Total 1602077 (delta 125), reused 172 (delta 74), pack-reused 1601747
Receiving objects: 100% (1602077/1602077), 432.12 MiB | 1.83 MiB/s, done.
Resolving deltas: 100% (849517/849517), done.
Checking out files: 100% (188907/188907), done.
  1. 使用 CocoaPods 安装 TensorFlow 发行版:
cd TensorFlow/TensorFlow/examples/ios/benchmark
pod install
Analyzing dependencies
Downloading dependencies
Installing TensorFlow-experimental (1.1.1)
Generating Pods project
Integrating client project
[!] Please close any current Xcode sessions and use `tf_benchmark_example.xcworkspace` for this project from now on.
Sending stats
Pod installation complete! There is 1 dependency from the Podfile and 1 total pod installed.
  1. 从 Inception v1 下载一些样本数据。 将标签和图文件提取到simplecamera文件夹内的数据文件夹中:
mkdir -p ~/graphs
 curl -o ~/graphs/inception5h.zip \
 https://storage.googleapis.com/download.TensorFlow.org/models/inception5h.zip \
 && unzip ~/graphs/inception5h.zip -d ~/graphs/inception5h
 cp ~/graphs/inception5h/* TensorFlow/examples/ios/benchmark/data/
 cp ~/graphs/inception5h/* TensorFlow/examples/ios/camera/data/
 cp ~/graphs/inception5h/* TensorFlow/examples/ios/simple/data/
  1. 从中下载用作测试的图像并将其复制到基准目录:

https://upload.wikimedia.org/wikipedia/commons/5/55/Grace_Hopper.jpg

cp grace_hopper.jpg ../../benchmark/data/

Grace Hopper 的图片

  1. 打开以前使用的示例项目。 以下命令将打开已经可用的 TensorFlow 的 Xcode 之后,运行编译,如以下代码和图像所示:
open tf_benchmark_example.xcworkspace

  1. 在 iPhone 模拟器中查看结果。 根据 Inception v1 类别,将步骤 4 中使用的图像识别为军服的图像:

用于 Tensorflow 计算的 Iphone 应用示例

工作原理

Xcode 和 CocoaPods 用于编译 TensorFlow 应用,该应用用于对不同 Inception 类别中的图像进行分类。 结果使用 iPhone 模拟器可视化。

更多

您可以直接在应用中使用 TensorFlow。 可在此处获得更多信息

为移动设备优化 TensorFlow 图

在本秘籍中,我们将考虑不同的选项来优化在移动设备上运行的 TensorFlow 代码。 从减小模型的大小到量化,分析了不同的选项。

准备

我们将使用 Bazel 构建 TensorFlow 的不同组件。 因此,第一步是确保同时安装了 Bazel 和 TensorFlow。

操作步骤

我们按以下步骤进行优化:

  1. 这里安装 Android Studio。

  2. 按照这个页面上的说明安装 Bazel。 对于 macOS,我们将使用 Homebrew:

/usr/bin/ruby -e "$(curl -fsSL \
 https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install bazel
bazel version
brew upgrade bazel
  1. 从 GitHub 克隆 TensorFlow 发行版:
git clone https://github.com/TensorFlow/TensorFlow.git
  1. 构建一个图转换器,汇总一下图本身:
cd ~/TensorFlow/
bazel build TensorFlow/tools/graph_transforms:summarize_graph
[2,326 / 2,531] Compiling TensorFlow/core/kernels/cwise_op_greater.cc
INFO: From Linking TensorFlow/tools/graph_transforms/summarize_graph:
clang: warning: argument unused during compilation: '-pthread' [-Wunused-command-line-argument]
Target //TensorFlow/tools/graph_transforms:summarize_graph up-to-date:
bazel-bin/TensorFlow/tools/graph_transforms/summarize_graph
INFO: Elapsed time: 1521.260s, Critical Path: 103.87s
  1. 下载 TensorFlow 图以用作示例。 在这种情况下,我们将使用 Inception v1 TensorFlow 图:
mkdir -p ~/graphs
 curl -o ~/graphs/inception5h.zip \
 https://storage.googleapis.com/download.TensorFlow.org/models/inception5h.zip \
 && unzip ~/graphs/inception5h.zip -d ~/graphs/inception5h
  1. 汇总 Inception 图并注意常参数的数量:1,346 万。 它们每个都存储有 32 位浮点数,这非常昂贵:
bazel-bin/TensorFlow/tools/graph_transforms/summarize_graph --in_graph=/Users/gulli/graphs/TensorFlow_inception_graph.pb
Found 1 possible inputs: (name=input, type=float(1), shape=[])
No variables spotted.
Found 3 possible outputs: (name=output, op=Identity) (name=output1, op=Identity) (name=output2, op=Identity)
Found 13462015 (13.46M) const parameters, 0 (0) variable parameters, and 0 control_edges
370 nodes assigned to device '/cpu:0'Op types used: 142 Const, 64 BiasAdd, 61 Relu, 59 Conv2D, 13 MaxPool, 9 Concat, 5 Reshape, 5 MatMul, 3 Softmax, 3 Identity, 3 AvgPool, 2 LRN, 1 Placeholder
To use with TensorFlow/tools/benchmark:benchmark_model try these arguments:
bazel run TensorFlow/tools/benchmark:benchmark_model -- --graph=/Users/gulli/graphs/TensorFlow_inception_graph.pb --show_flops --input_layer=input --input_layer_type=float --input_layer_shape= --output_layer=output,output1,output2
  1. 编译该工具以将常量操作截断至 8 位:
bazel build TensorFlow/tools/graph_transforms:transform_graph
INFO: From Linking TensorFlow/tools/graph_transforms/transform_graph:
clang: warning: argument unused during compilation: '-pthread' [-Wunused-command-line-argument]
Target //TensorFlow/tools/graph_transforms:transform_graph up-to-date:
bazel-bin/TensorFlow/tools/graph_transforms/transform_graph
INFO: Elapsed time: 294.421s, Critical Path: 28.83s
  1. 运行该工具以量化 Inception V1 图:
bazel-bin/TensorFlow/tools/graph_transforms/transform_graph --in_graph=/Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb --out_graph=/tmp/TensorFlow_inception_quantized.pb --inputs='Mul:0' --outputs='softmax:0' --transforms='quantize_weights'
2017-10-15 18:56:01.192498: I TensorFlow/tools/graph_transforms/transform_graph.cc:264] Applying quantize_weights
  1. 比较两个模型:
ls -lah /Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb
-rw-r----- 1 gulli 5001 51M Nov 19 2015 /Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb
ls -lah /tmp/TensorFlow_inception_quantized.pb
-rw-r--r-- 1 gulli wheel 13M Oct 15 18:56 /tmp/TensorFlow_inception_quantized.pb

工作原理

量化通过将常量操作从 32 位缩减为 8 位来帮助减小模型的大小。 通常,该模型不会遭受表现的显着降低。 但是,这必须根据具体情况进行验证。

为移动设备分析 TensorFlow 图

在本秘籍中,我们将考虑不同的选项来优化 TensorFlow 代码以在移动设备上运行。 从减小模型的大小到量化,分析了不同的选项。

准备

我们将使用 Bazel 构建 TensorFlow 的不同组件。 因此,第一步是确保同时安装了 Bazel 和 TensorFlow。

操作步骤

我们进行如下分析:

  1. 这里安装 Android Studio。

  2. 按照这个页面上的说明安装 Bazel。 对于 macOS,我们将使用 Homebrew:

/usr/bin/ruby -e "$(curl -fsSL \
 https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install bazel
bazel version
brew upgrade bazel
  1. 从 GitHub 克隆 TensorFlow 发行版:
git clone https://github.com/TensorFlow/TensorFlow.git
  1. 构建图变压器,该图变压器对图本身进行配置:
cd ~/TensorFlow/
bazel build -c opt TensorFlow/tools/benchmark:benchmark_model
INFO: Found 1 target...
Target //TensorFlow/tools/benchmark:benchmark_model up-to-date:
bazel-bin/TensorFlow/tools/benchmark/benchmark_model
INFO: Elapsed time: 0.493s, Critical Path: 0.01s
  1. 通过在桌面上运行以下命令来对模型进行基准测试:
bazel-bin/TensorFlow/tools/benchmark/benchmark_model --graph=/Users/gulli/graphs/TensorFlow_inception_graph.pb --show_run_order=false --show_time=false --show_memory=false --show_summary=true --show_flops=true
Graph: [/Users/gulli/graphs/TensorFlow_inception_graph.pb]
Input layers: [input:0]
Input shapes: [1,224,224,3]
Input types: [float]
Output layers: [output:0]
Num runs: [1000]
Inter-inference delay (seconds): [-1.0]
Inter-benchmark delay (seconds): [-1.0]
Num threads: [-1]
Benchmark name: []
Output prefix: []
Show sizes: [0]
Warmup runs: [2]
Loading TensorFlow.
Got config, 0 devices
Running benchmark for max 2 iterations, max -1 seconds without detailed stat logging, with -1s sleep between inferences
count=2 first=279182 curr=41827 min=41827 max=279182 avg=160504 std=118677
Running benchmark for max 1000 iterations, max 10 seconds without detailed stat logging, with -1s sleep between inferences
count=259 first=39945 curr=44189 min=36539 max=51743 avg=38651.1 std=1886
Running benchmark for max 1000 iterations, max 10 seconds with detailed stat logging, with -1s sleep between inferences
count=241 first=40794 curr=39178 min=37634 max=153345 avg=41644.8 std=8092
Average inference timings in us: Warmup: 160504, no stats: 38651, with stats: 41644

  1. 通过在运行 64 位 ARM 处理器的目标 android 设备上运行以下命令来对模型进行基准测试。 请注意,以下命令将初始图推送到设备上并运行可在其中执行基准测试的外壳程序:
bazel build -c opt --config=android_arm64 \ TensorFlow/tools/benchmark:benchmark_model
adb push bazel-bin/TensorFlow/tools/benchmark/benchmark_model \ /data/local/tmp
adb push /tmp/TensorFlow_inception_graph.pb /data/local/tmp/
adb push ~gulli/graphs/inception5h/TensorFlow_inception_graph.pb /data/local/tmp/
/Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb: 1 file pushed. 83.2 MB/s (53884595 bytes in 0.618s)
adb shell
generic_x86:/ $
/data/local/tmp/benchmark_model --graph=/data/local/tmp/TensorFlow_inception_graph.pb --show_run_order=false --show_time=false --show_memory=false --show_summary=true

工作原理

正如预期的那样,该模型在 Conv2D 操作上花费了大量时间。 总体而言,这大约占我台式机平均时间的 77.5%。 如果在移动设备上运行此程序,那么花时间执行神经网络中的每一层并确保它们处于受控状态至关重要。 要考虑的另一个方面是内存占用。 在这种情况下,桌面执行约为 10 Mb。

转换移动设备的 TensorFlow 图

在本秘籍中,我们将学习如何转换 TensorFlow 图,以便删除所有仅训练节点。 这将减小图的大小,使其更适合于移动设备。

What is a graph transform tool? According to https://github.com/tensorflow/tensorflow/blob/master/tensorflow/tools/graph_transforms/README.md “When you have finished training a model and want to deploy it in production, you’ll often want to modify it to better run in its final environment. For example if you’re targeting a phone you might want to shrink the file size by quantizing the weights, or optimize away batch normalization or other training-only features. The Graph Transform framework offers a suite of tools for modifying computational graphs, and a framework to make it easy to write your own modifications”.

准备

我们将使用 Bazel 构建 TensorFlow 的不同组件。 因此,第一步是确保同时安装了 Bazel 和 TensorFlow。

操作步骤

这是我们如何转换 TensorFlow 的方法:

  1. 这里安装 Android Studio。

  2. 按照这个页面上的说明安装 Bazel。 对于 macOS,我们将使用 Homebrew:

/usr/bin/ruby -e "$(curl -fsSL \
 https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install bazel
bazel version
brew upgrade bazel
  1. 从 GitHub 克隆 TensorFlow 发行版:
git clone https://github.com/TensorFlow/TensorFlow.git
  1. 构建一个图转换器,它汇总了图本身:
bazel run TensorFlow/tools/graph_transforms:summarize_graph -- --in_graph=/Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb
WARNING: /Users/gulli/TensorFlow/TensorFlow/core/BUILD:1783:1: in includes attribute of cc_library rule //TensorFlow/core:framework_headers_lib: '../../external/nsync/public' resolves to 'external/nsync/public' not below the relative path of its package 'TensorFlow/core'. This will be an error in the future. Since this rule was created by the macro 'cc_header_only_library', the error might have been caused by the macro implementation in /Users/gulli/TensorFlow/TensorFlow/TensorFlow.bzl:1054:30.
INFO: Found 1 target...
Target //TensorFlow/tools/graph_transforms:summarize_graph up-to-date:
bazel-bin/TensorFlow/tools/graph_transforms/summarize_graph
INFO: Elapsed time: 0.395s, Critical Path: 0.01s
INFO: Running command line: bazel-bin/TensorFlow/tools/graph_transforms/summarize_graph '--in_graph=/Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb'
Found 1 possible inputs: (name=input, type=float(1), shape=[])
No variables spotted.
Found 3 possible outputs: (name=output, op=Identity) (name=output1, op=Identity) (name=output2, op=Identity)
Found 13462015 (13.46M) const parameters, 0 (0) variable parameters, and 0 control_edges
370 nodes assigned to device '/cpu:0'Op types used: 142 Const, 64 BiasAdd, 61 Relu, 59 Conv2D, 13 MaxPool, 9 Concat, 5 Reshape, 5 MatMul, 3 Softmax, 3 Identity, 3 AvgPool, 2 LRN, 1 Placeholder
To use with TensorFlow/tools/benchmark:benchmark_model try these arguments:
bazel run TensorFlow/tools/benchmark:benchmark_model -- --graph=/Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb --show_flops --input_layer=input --input_layer_type=float --input_layer_shape= --output_layer=output,output1,output2
  1. 剥去用于训练的所有节点,当在移动设备上使用图进行推理时,不需要这些节点:
bazel run TensorFlow/tools/graph_transforms:transform_graph -- --in_graph=/Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb --out_graph=/tmp/optimized_inception_graph.pb --transforms="strip_unused_nodes fold_constants(ignore_errors=true) fold_batch_norms fold_old_batch_norms"
WARNING: /Users/gulli/TensorFlow/TensorFlow/core/BUILD:1783:1: in includes attribute of cc_library rule //TensorFlow/core:framework_headers_lib: '../../external/nsync/public' resolves to 'external/nsync/public' not below the relative path of its package 'TensorFlow/core'. This will be an error in the future. Since this rule was created by the macro 'cc_header_only_library', the error might have been caused by the macro implementation in /Users/gulli/TensorFlow/TensorFlow/TensorFlow.bzl:1054:30.
INFO: Found 1 target...
Target //TensorFlow/tools/graph_transforms:transform_graph up-to-date:
bazel-bin/TensorFlow/tools/graph_transforms/transform_graph
INFO: Elapsed time: 0.578s, Critical Path: 0.01s
INFO: Running command line: bazel-bin/TensorFlow/tools/graph_transforms/transform_graph '--in_graph=/Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb' '--out_graph=/tmp/optimized_inception_graph.pb' '--transforms=strip_unused_nodes fold_constants(ignore_errors=true) fold_batch_norms fold_old_batch_norms'
2017-10-15 22:26:59.357129: I TensorFlow/tools/graph_transforms/transform_graph.cc:264] Applying strip_unused_nodes
2017-10-15 22:26:59.367997: I TensorFlow/tools/graph_transforms/transform_graph.cc:264] Applying fold_constants
2017-10-15 22:26:59.387800: I TensorFlow/core/platform/cpu_feature_guard.cc:137] Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.2 AVX AVX2 FMA
2017-10-15 22:26:59.388676: E TensorFlow/tools/graph_transforms/transform_graph.cc:279] fold_constants: Ignoring error Must specify at least one target to fetch or execute.
2017-10-15 22:26:59.388695: I TensorFlow/tools/graph_transforms/transform_graph.cc:264] Applying fold_batch_norms
2017-10-15 22:26:59.388721: I TensorFlow/tools/graph_transforms/transform_graph.cc:264] Applying fold_old_batch_norms

工作原理

为了创建可以在设备上加载的更轻的模型,我们使用了图变换工具应用的strip_unused_nodes规则删除了所有不需要的节点。 该操作将删除用于学习的所有操作,并保留用于推理的操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
In this book, you will learn how to efficiently use TensorFlow, Google's open source framework for deep learning. You will implement different deep learning networks such as Convolutional Neural Networks (CNNs), Recurrent Neural Networks (RNNs), Deep Q-learning Networks (DQNs), and Generative Adversarial Networks (GANs) with easy to follow independent recipes. Take the next step in implementing various common and not-so-common neural networks with Tensorflow 1.x About This Book Skill up and implement tricky neural networks using Google's TensorFlow 1.x An easy-to-follow guide that lets you explore reinforcement learning, GANs, autoencoders, multilayer perceptrons and more. Hands-on recipes to work with Tensorflow on desktop, mobile, and cloud environment Who This Book Is For This book is intended for data analysts, data scientists, machine learning practitioners and deep learning enthusiasts who want to perform deep learning tasks on a regular basis and are looking for a handy guide they can refer to. People who are slightly familiar with neural networks, and now want to gain expertise in working with different types of neural networks and datasets, will find this book quite useful. What You Will Learn Install TensorFlow and use it for CPU and GPU operations Implement DNNs and apply them to solve different AI-driven problems. Leverage different data sets such as MNIST, CIFAR-10, and Youtube8m with TensorFlow and learn how to access and use them in your code. Use TensorBoard to understand neural network architectures, optimize the learning process, and peek inside the neural network black box. Use different regression techniques for prediction and classification problems Build single and multilayer perceptrons in TensorFlow Implement CNN and RNN in TensorFlow, and use it to solve real-world use cases. Learn how restricted Boltzmann Machines can be used to recommend movies. Understand the implementation of Autoencoders and deep belief networks, and use them for emotion detection. Master the different reinforcement learning methods to implement game playing agents. GANs and their implementation using TensorFlow. In Detail Deep neural networks (DNNs) have achieved a lot of success in the field of computer vision, speech recognition, and natural language processing. The entire world is filled with excitement about how deep networks are revolutionizing artificial intelligence. This exciting recipe-based guide will take you from the realm of DNN theory to implementing them practically to solve the real-life problems in artificial intelligence domain. In this book, you will learn how to efficiently use TensorFlow, Google's open source framework for deep learning. You will implement different deep learning networks such as Convolutional Neural Networks (CNNs), Recurrent Neural Networks (RNNs), Deep Q-learnin... Read more...

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交