六、循环神经网络
在本章中,我们将介绍一些涵盖以下主题的秘籍:
在本章中,我们将讨论循环神经网络(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 单元的一个例子
首先,我们需要一个逻辑函数σ(请参见第 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 实现并使用它即可。
近年来提出了许多 LSTM 单元的变体。 其中两个真的很受欢迎。 窥孔 LSTM 允许栅极层查看单元状态,如下图虚线所示,而门控循环单元(GRU)将隐藏状态和单元状态和合并为一个单一的信息渠道。
同样,GRU 和 Peephole LSTM 都可以用作标准 RNN 单元的黑盒插件,而无需了解基础数学。 这两个单元都可用于解决梯度消失的问题,并可用于构建深度神经网络:
标准 LSTM,PeepHole LSTM 和 GRU 的示例
使 RNN 真正强大的是能够对向量序列进行操作的能力,其中 RNN 的输入和/或 RNN 的输出都可以是序列。 下图很好地表示了这一点,其中最左边的示例是传统的(非循环)网络,其后是带有输出序列的 RNN,然后是带有输入序列的 RNN,再是带有序列的 RNN 在不同步序列的输入和输出中,然后是在序列同步的输入和输出中具有序列的 RNN:
机器翻译是输入和输出中不同步序列的一个示例:网络将输入文本作为序列读取,在读取全文之后,会输出目标语言。
视频分类是输入和输出中同步序列的示例:视频输入是帧序列,并且对于每个帧,输出中都提供了分类标签。
如果您想了解有关 RNN 有趣应用的更多信息,则必须阅读 Andrej Karpathy 发布的博客。 他训练了网络,以莎士比亚的风格撰写论文(用 Karpathy 的话说:几乎不能从实际的莎士比亚中识别出这些样本),撰写有关虚构主题的现实 Wikipedia 文章,撰写关于愚蠢和不现实问题的现实定理证明(用 Karpathy 的话:更多的幻觉代数几何),并写出现实的 Linux 代码片段(用 Karpathy 的话:他首先建模逐个字符地列举 GNU 许可证,其中包括一些示例,然后生成一些宏,然后深入研究代码)。
以下示例摘自这个页面:
用 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
。 这两个连接的句子在输入中提供给编码器进行训练,并且解码器将生成目标目标。 当然,我们需要像这样的多个示例来获得良好的训练:
现在,我们可以拥有许多 RNN 变体。 让我们看看其中的一些:
仍然参考博士学位论文《神经机器翻译》,我们可以使用嵌入层来将输入语句放入嵌入空间。 然后,有两个 RNN 粘在一起——源语言的编码器和目标语言的解码器。 如您所见,存在多个隐藏层,并且有两个流程:前馈垂直方向连接这些隐藏层,水平方向是将知识从上一步转移到下一层的循环部分:
在本秘籍中,我们使用 NMT(神经机器翻译),这是一个可在 TensorFlow 顶部在线获得的翻译演示包。
NMT 可在这个页面上找到,并且代码在 GitHub 上。
我们按以下步骤进行:
git clone https://github.com/tensorflow/nmt/
nmt/scripts/download_iwslt15.sh /tmp/nmt_data
# 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)
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)
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
projection_layer = layers_core.Dense(
tgt_vocab_size, use_bias=False)
crossent = tf.nn.sparse_softmax_cross_entropy_with_logits(
labels=decoder_outputs, logits=logits)
train_loss = (tf.reduce_sum(crossent * target_weights) /
batch_size)
# 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))
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 指标示例
在此秘籍中,我们使用先前秘籍的结果将源语言转换为目标语言。 这个想法非常简单:给源语句提供两个组合的 RNN(编码器+解码器)作为输入。 句子一结束,解码器将产生对率值,我们贪婪地产生与最大值关联的单词。 例如,从解码器产生单词moi
作为第一个令牌,因为该单词具有最大对率值。 之后,会产生单词suis
,依此类推:
使用解码器的输出有多种策略:
我们按以下步骤进行:
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)
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 网络。 解码器产生对率,然后将其贪婪地转换为目标语言的单词。 例如,此处显示了从越南语到英语的自动翻译:
在本秘籍中,我们介绍了注意力方法(Dzmitry Bahdanau,Kyunghyun Cho 和 Yoshua Bengio,ICLR 2015),这是神经网络翻译的最新解决方案。 ,它包括在编码器和解码器 RNN 之间添加其他连接。 实际上,仅将解码器与编码器的最新层连接会带来信息瓶颈,并且不一定允许通过先前的编码器层获取的信息通过。 下图说明了采用的解决方案:
需要考虑三个方面:
我们按以下步骤进行:
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)
decoder_cell = tf.contrib.seq2seq.AttentionWrapper(
decoder_cell, attention_mechanism,
attention_layer_size=num_units)
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 不仅可以用于机器翻译。 让我们看一些例子:
下图使我们对该概念有了一个很好的了解:
在本秘籍中,我们将学习如何生成与威廉·莎士比亚(William Shakespeare)相似的文本。 关键思想很简单:我们将莎士比亚写的真实文本作为输入,并将其作为输入 RNN 的输入,该 RNN 将学习序列。 然后将这种学习用于生成新文本,该文本看起来像最伟大的作家用英语撰写的文本。
为了简单起见,我们将使用框架 TFLearn,它在 TensorFlow 上运行。 此示例是标准分发版的一部分,可从以下位置获得。开发的模型是 RNN 字符级语言模型,其中考虑的序列是字符序列而不是单词序列。
我们按以下步骤进行:
pip
安装 TFLearn:pip install -I tflearn
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)
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'))
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)
flearn.models.generator.SequenceGenerator
(network
,dictionary=char_idx, seq_maxlen=maxle
和clip_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')
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/
在这里,网络正在学习一些基本结构,包括需要建立有关虚构字符(DIA
,SURYONT
,HRNTLGIPRMAR
和ARILEN
)的对话。 但是,英语仍然很差,很多单词不是真正的英语:
---------------------------------
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 heart
和There 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 字符级语言模型,包括以下内容:
在本秘籍中,我们将学习如何使用 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"}}
这是我们进行秘籍的方法:
git clone https://github.com/guillaume-chevalier/seq2seq-signal-prediction.git
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¤cy={}".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)
batch_size
,hidden_dim
(RNN 中隐藏的神经元的数量)和layers_stacked_count
(栈式循环单元的数量)。 此外,定义一些参数以微调优化器,例如优化器的学习率,迭代次数,用于优化器模拟退火的lr_decay
,优化器的动量以及避免过拟合的 L2 正则化。 请注意,GitHub 存储库具有默认的batch_size = 5
和nb_iters = 150
,但使用batch_size = 1000
和nb_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
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)
tensorboard --logdir=/tmp/bitcoin_logs
以下是代码流程:
Tensorboard 中的比特币价值预测代码示例
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)
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))
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 讨论过的内容。 为了简单起见,我们将采用 Keras 并演示如何编写一对一,一对多,多对一和多对多映射,如下图所示:
我们按以下步骤进行:
model = Sequential()
model.add(Dense(output_size, input_shape=input_shape))
RepeatVector(...)
实现。 请注意,return_sequences
是一个布尔值,用于决定是返回输出序列中的最后一个输出还是完整序列:model = Sequential()
model.add(RepeatVector(number_of_times,input_shape=input_shape))
model.add(LSTM(output_size, return_sequences=True))
model = Sequential()
model.add(LSTM(1, input_shape=(timesteps, data_dim)))
model = Sequential()
model.add(LSTM(1, input_shape=(timesteps, data_dim), return_sequences=True))
Keras 使您可以轻松编写各种形状的 RNN,包括一对一,一对多,多对一和多对多映射。 上面的示例说明了用 Keras 实现它们有多么容易。
到目前为止,我们在本书中涵盖的所有模型都是基于监督学习范式的。 训练数据集包括输入和该输入的所需标签。 相反,本章重点介绍无监督的学习范式。 本章将包括以下主题:
在机器学习中,存在三种不同的学习范式:监督学习,无监督学习和强化学习。
在监督学习(也称为与老师一起学习)中,向网络提供输入和各自所需的输出。 例如,在 MNIST 数据集中,手写数字的每个图像都有一个标签,表示与之关联的数字值。
在强化学习(也称为与批评家学习)中,没有为网络提供所需的输出; 相反,环境会提供奖励或惩罚方面的反馈。 当其输出正确时,环境奖励网络,而当输出不正确时,环境对其进行惩罚。
在无监督学习(也称为无老师学习)中,没有向网络提供有关其输出的信息。 网络接收输入,但是既不提供期望的输出,也不提供来自环境的奖励; 网络自己学习输入的隐藏结构。 无监督学习非常有用,因为正常情况下可用的数据没有标签。 它可以用于模式识别,特征提取,数据聚类和降维等任务。 在本章和下一章中,您将学习基于无监督学习的不同机器学习和 NN 技术。
主成分分析(PCA)是用于降维的最流行的多元统计技术。 它分析了由几个因变量组成的训练数据,这些因变量通常是相互关联的,并以一组称为主成分的新正交变量的形式从训练数据中提取重要信息。 我们可以使用两种方法执行 PCA – 特征值分解或奇异值分解(SVD)。
PCA 将n
维输入数据还原为r
维输入数据,其中r < n
。 简单来说,PCA 涉及平移原点并执行轴的旋转,以使其中一个轴(主轴)与数据点的差异最小。 通过执行此变换,然后以高方差落下(删除)正交轴,可以从原始数据集中获得降维数据集。 在这里,我们采用 SVD 方法降低 PCA 尺寸。 考虑X
,n
维数据,具有p
个点X[p,n]
。 任何实数(p × n
)矩阵都可以分解为:
X = U ∑ V^T
在这里, U
和V
是正交矩阵(即U · U^T = V^T · V = E
),大小分别为p × n
和n × n
。 ∑
是大小为n × n
的对角矩阵。 接下来,将∑
矩阵切成r
列,得到∑[r]
; 使用U
和V
,我们找到了降维数据点Y[r]
:
Y[r] = U ∑[r]
我们按以下步骤进行操作:
numpy
进行一些基本矩阵计算,并需要matplotlib
,mpl_toolkit
和seaborn
进行绘图: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
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/")
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)
fit
方法计算。 该方法定义了计算图,并执行该计算图以计算奇异值和正交矩阵U
。需要self.data
来输入占位符self._X
。 tf.svd
以降序返回形状[..., p]
的s
(singular_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})
sigma
矩阵,正交U
矩阵和奇异值,我们通过定义reduce
方法来计算降维数据。 该方法需要两个输入参数之一n_dimensions
或keep_info
。 n_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})
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)
以下是以下代码的输出:
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
进行相同的操作。 我们可以通过三个简单的步骤来做到这一点:
mnist = input_data.read_data_sets('MNIST_data')
images = tf.Variable(mnist.test.images, name='images')
metadata
文件((metadata
文件是制表符分隔的.tsv
文件):with open(metadata, 'w') as metadata_file:
for row in mnist.test.labels:
metadata_file.write('%d\n' % row)
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
个数据点作为初始质心(集群中心)我们将使用 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
方法返回预测的聚类索引。
这是我们进行秘籍的方法:
.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
# import some data to play with
iris = datasets.load_iris()
x = iris.data[:, :2] # we only take the first two features.
y = iris.target
# original data without clustering
plt.scatter(hw_frame[:,0], hw_frame[:,1])
plt.xlabel('Sepia Length')
plt.ylabel('Sepia Width')
以下是以下代码的输出:
input_fn
,它将用于提供fit
方法。 我们的输入函数返回一个 TensorFlow 常数,该常数被分配了x
的值和形状,并且类型为float
:def input_fn():
return tf.constant(np.array(x), tf.float32, x.shape),None
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)
clusters()
方法找到聚类,并使用predict_cluster_idx()
方法为每个输入点分配聚类索引:clusters = kmeans.clusters()
assignments = list(kmeans.predict_cluster_idex(input_fn=input_fn))
ScatterPlot
,该函数将X
和Y
值以及每个数据点的簇和簇索引一起使用: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 均值聚类非常流行,因为它快速,简单且健壮。 它还有一些缺点:最大的缺点是用户必须指定簇的数量。 其次,该算法不能保证全局最优。 第三,它对异常值非常敏感。
Kanungo, Tapas, et al. An efficient k-means clustering algorithm: Analysis and implementation. IEEE transactions on pattern analysis and machine intelligence 24.7 (2002): 881-892.
Ortega, Joaquín Pérez, et al. Research issues on k-means algorithm: An experimental trial using matlab. CEUR Workshop Proceedings: Semantic Web and New Technologies.
Chen, Ke. On coresets for k-median and k-means clustering in metric and euclidean spaces and their applications. SIAM Journal on Computing 39.3 (2009): 923-947.
自组织映射(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)
我们继续所有输入的过程,并重复给定的迭代次数。 随着迭代的进行,我们将学习率和半径减小一个取决于迭代次数的因素。
我们按以下步骤进行:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
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
__init__
本身中,我们定义了计算图和会话。sigma
值,它将采用默认值,该值通常是 SOM 晶格最大尺寸的一半:if sigma is None:
sigma = max(m,n)/2.0 # Constant radius
else:
sigma = float(sigma)
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)
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
def winner(self, x):
idx = self._sess.run([self.WTU_idx,self.WTU_loc], feed_dict = {self._X:x})
return idx
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
.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
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))
,权重的变化由此给出:
在此,η
是学习率。 对于偏差b
和c
存在相似的表达式。
我们按以下步骤进行:
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
%matplotlib inline
__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))
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
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})
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
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
由于其具有重建图像的能力,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)
我们得到的结果如下:
Smolensky, Paul. Information processing in dynamical systems: Foundations of harmony theory. No. CU-CS-321-86. COLORADO UNIV AT BOULDER DEPT OF COMPUTER SCIENCE, 1986.
http://stanford.edu/~jlmcc/papers/PDP/Volume 1/Chap6_PDP86.pdf
Salakhutdinov, Ruslan, Andriy Mnih, and Geoffrey Hinton. Restricted Boltzmann machines for collaborative filtering. Proceedings of the 24th international conference on Machine learning. ACM, 2007.
http://machinelearning.wustl.edu/mlpapers/paper_files/icml2007_SalakhutdinovMH07.pdf
Hinton, Geoffrey. A practical guide to training restricted Boltzmann machines. Momentum 9.1 (2010): 926.
http://www.csri.utoronto.ca/~hinton/absps/guideTR.pdf)
网上零售商广泛使用推荐系统向客户推荐产品。 例如,亚马逊会告诉您购买此商品的其他客户对什么感兴趣,或者 Netflix 根据您所观看的内容以及有相同兴趣的其他 Netflix 用户所观看的内容推荐电视连续剧和电影。 这些推荐器系统在协作筛选的基础上工作。 在协作过滤中,系统根据用户的过去行为来构建模型。 我们将使用上一个秘籍中的 RBM 构建一个使用协作过滤来推荐电影的推荐器系统。 这项工作中的一个重要挑战是,大多数用户不会对所有产品/电影进行评分,因此大多数数据都将丢失。 如果有 M 个产品和 N 个用户,则我们需要构建一个数组N x M
,其中包含用户的已知等级并将所有未知值设为零。
为了使用协作过滤创建推荐系统,我们需要修改数据。 作为说明,我们将使用来自这里的电影数据集。 数据由两个.dat
文件组成:movies.dat
和ratings.dat
。 movies.dat
文件包含 3 列:3883 个电影的 MovieID,Title 和 Genre。 ratings.dat
文件包含四列:UserID,MovieID,Rating 和 Time。 我们需要合并这两个数据文件,以便能够构建一个数组,其中对于每个用户,我们对所有 3,883 部电影都有一个评分。 问题在于用户通常不会对所有电影进行评级,因此我们仅对某些电影进行非零(标准化)评级。 其余部分设为零,因此不会对隐藏层有所贡献。
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)
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)
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))
杰弗里·欣顿(Geoffrey Hinton)教授领导的多伦多大学团队赢得了 Netflix 最佳协作过滤竞赛的冠军,该协作过滤使用 RBM 来预测电影的用户收视率。 可以从他们的论文中获取其工作的详细信息。
一个 RBM 的隐藏单元的输出可以馈送到另一个 RBM 的可见单元,可以重复此过程以形成 RBM 的栈。 这导致栈式 RBM 。 假定不存在其他堆叠式 RBM,则对其进行独立训练。 大量栈式 RBM 构成了深度信念网络(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.csv
的csv
文件。 我们从中分离出训练,验证和测试数据:
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。
我们按以下步骤进行:
.csv
文件和 Matplolib:import tensorflow as tf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
X_train, Y_train = preprocess_data(train_data)
X_val, Y_val = preprocess_data(val_data)
X_test, Y_test = preprocess_data(test_data)
# 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')
我们得到的结果如下:
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()
情节如下:
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 个隐藏单元。
# 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
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})
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 数据库上训练自编码器,并将其用于重建测试图像。
我们按以下步骤进行:
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
%matplotlib inline
mnist = input_data.read_data_sets("MNIST_data/")
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
AutoEncoder
; 该类具有init
方法来为自编码器初始化权重,偏差和占位符。 我们还可以使用init
方法构建完整的图。 该类还具有用于encoder
,decoder
,设置会话(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
重构最终图像。
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])
epochs
来减少误差:plt.plot(err)
plt.xlabel('epochs')
plt.ylabel('cost')
该图如下所示:
我们可以看到,随着网络的学习,损耗/成本正在降低,到我们达到 5,000 个周期时,损耗/成本几乎在一条线上振荡。 这意味着进一步增加周期将是无用的。 如果现在要改善训练,则应该更改超参数,例如学习率,批量大小和使用的优化程序。
# 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-Leiber (KL)差异。
在开始秘籍之前,让我们进一步探讨 KL 的差异,D[KL]
。 它是两个分布之间差异的非对称度量,在我们的情况下为ρ
和ρ_hat
。当ρ
和ρ_hat
相等时,则为零,否则,当ρ_hat
与ρ
分叉时,它单调增加。在数学上,它表示为:
这是固定ρ = 0.3
时D[KL]
的图,我们可以看到当ρ_hat = 0.3
时,D[KL] = 0
;否则在两端单调增长:
我们按以下步骤进行:
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
%matplotlib inline
mnist = input_data.read_data_sets("MNIST_data/")
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
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
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])
plt.plot(err)
plt.xlabel('epochs')
plt.ylabel('Reconstruction Loss (MSE)')
情节如下:
# 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
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
mnist = input_data.read_data_sets("MNIST_data/")
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
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
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)
plt.plot(err)
plt.xlabel('epochs')
plt.ylabel('Reconstruction Loss (MSE)')
情节如下:
# 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
实现了最近邻插值,取得了最佳结果; 我们将在此处采用相同的方法。
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
mnist = input_data.read_data_sets("MNIST_data/")
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
# 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
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')
### 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)
sess = tf.Session()
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))
plt.plot(err)
plt.xlabel('epochs')
plt.ylabel('Cross Entropy Loss')
绘图如下:
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)
这是前面代码的输出:
sess.close()
前面的 CAE 是降噪 CAE,与仅由一个瓶颈层组成的简单降噪自编码器相比,我们可以看到它在降噪图像方面更好。
研究人员已将 CAE 用于语义分割。 有趣的读物是 Badrinayanan 等人在 2015 年发表的论文 Segnet:一种用于图像分割的深度卷积编码器-解码器架构。 该网络使用 VGG16 的卷积层作为其编码器网络,并包含一层解码器,每个解码器对应一个解码器层次作为其解码器网络。 解码器使用从相应的编码器接收的最大池索引,并对输入特征图执行非线性上采样。 本文的链接在本秘籍的另请参见部分以及 GitHub 链接中给出。
到目前为止,涵盖的自编码器(CAE 除外)仅由单层编码器和单层解码器组成。 但是,我们可能在编码器和解码器网络中具有多层; 使用更深的编码器和解码器网络可以使自编码器表示复杂的特征。 这样获得的结构称为栈式自编码器(深度自编码器); 由一个编码器提取的特征将作为输入传递到下一个编码器。 可以将栈式自编码器作为一个整体网络进行训练,以最大程度地减少重构误差,或者可以首先使用您先前学习的无监督方法对每个单独的编码器/解码器网络进行预训练,然后对整个网络进行微调。 已经指出,通过预训练,也称为贪婪分层训练,效果更好。
在秘籍中,我们将使用贪婪分层方法来训练栈式自编码器; 为了简化任务,我们将使用共享权重,因此相应的编码器/解码器权重将相互转换。
我们按以下步骤进行:
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
%matplotlib inline
mnist = input_data.read_data_sets("MNIST_data/")
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
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])
pretrain
和one_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])
encoder
和decoder
: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)
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
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
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)
plt.plot(err)
plt.xlabel('epochs')
plt.ylabel('Fine Tuning Reconstruction Error')
情节如下:
在栈式自编码器上进行的实验表明,应以较低的学习率值进行预训练。 这样可以确保在微调期间具有更好的收敛性和表现。
整章都是关于自编码器的,尽管目前它们仅用于降维和信息检索,但它们引起了很多兴趣。 首先,因为它们不受监督,其次,因为它们可以与 FCN 一起使用。 它们可以帮助我们应对维度的诅咒。 研究人员已经证明,它们也可以用于分类和异常检测。
Schwenk, Holger. "The diabolo classifier." Neural Computation 10.8 (1998): 2175-2200.
Sakurada, Mayu, and Takehisa Yairi. "Anomaly detection using autoencoders with nonlinear dimensionality reduction." Proceedings of the MLSDA 2014 2nd Workshop on Machine Learning for Sensory Data Analysis. ACM, 2014.
本章介绍强化学习(RL)-学习最少,但最有前途的学习范例。 本章包括以下主题:
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 是一个开源工具包,用于开发和比较 RL 算法。 它包含各种模拟环境,可用于训练智能体和开发新的 RL 算法。
首先要做的是安装 OpenAI Gym; 使用pip install gym
可以完成最少的安装。 OpenAI 运动场提供了多种环境,例如 Atari,棋盘游戏以及 2D 或 3D 物理引擎。 最小安装可在 Windows 上运行,并且仅支持基本环境-算法,toy_text
和classic_control
-但如果您要探索其他环境,则它们将需要更多的依赖项。 OSX 和 Ubuntu 支持完整版本。 可以在 OpenAI Gym 的 GitHub 链接上阅读详细说明。
让我们从秘籍开始:
reset
方法重置环境并返回观察值。 step
方法将环境步进一个时间步,并返回观察,奖励,完成和信息。 render
方法呈现一帧环境,例如弹出一个窗口。import gym
env_name = 'Breakout-v3'
env = gym.make(env_name)
reset
方法启动环境:obs = env.reset()
print(obs.shape)
actions = env.action_space
检查可能的操作数量。 从此结果可以看出,对于 Breakout-v4,我们有四个可能的动作:NoOp
,Fire
,Left
和Right
。 可以通过调用env.action_space.n
命令获得操作总数。def random_policy(n):
action = np.random.randint(0,n)
return action
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
返回为True
。 info
可用于调试,但智能体不使用。 env.render()
命令弹出一个窗口,显示环境的当前状态。 当包含此命令时,您可以通过弹出窗口查看座席如何尝试玩耍和学习。 最好在座席接受训练时对此进行评论,以节省时间。
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 个环境。 这是吃豆人使用与之前相同的随机智能体的图像:
让我们首先构建一个简单的神经网络智能体来玩“吃豆人”游戏。 我们将创建具有一组随机权重和偏差的智能体。 然后,这些特工将尝试玩游戏; 我们选择能够发挥最长平均持续时间的特工,并假设他们是最佳策略。
此秘籍中的智能体没有学习任何策略; 他们根据初始权重(固定策略)做出决策。 智能体根据神经网络给出的概率来选择动作。 每个智能体做出的决定仅基于对环境的当前观察。
我们通过完全连接的神经网络来实现。 NN 的输入由环境的观察空间决定; 输出神经元的数量由可能的离散动作的数量决定。 吃豆人游戏有九种可能的离散动作-无操作,右转,左转,上转,向下转,左移,右移,上移和下移-因此我们的 NN 具有九个输出神经元。
让我们从秘籍开始:
gym
,以便我们可以使用它提供的不同环境:import gym
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
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
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
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
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
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)
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 的长度玩游戏。 不错!
如导言所述,我们有一个由状态s
(s ∈ S
,其中S
是所有可能状态的集合)描述的环境,可以执行动作a
(a ∈ 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)
我们按以下步骤进行:
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
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})
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))
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
'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()
可以使用相同的逻辑为 OpenAI 的其他环境创建智能体。 但是,对于诸如 Breakout 或 Pac-Man 之类的 Atari 游戏,观察空间并不只是由四个数字组成的数组。 相反,它非常大(210×160 = 33,600 像素,具有 3 个 RGB 值); 如果没有某种形式的量化,则这种简单的 NN 可能的状态是无限的,并且不会产生良好的结果。 我们将在深度 Q 学习秘籍中使用 CNN 解决此问题。
尽管有很多有关 Q 学习的 Web 链接,但一些有用的链接如下:
深度 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 网络。
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
MAX_EXPERIENCES = 500000
MIN_EXPERIENCES = 50000
TARGET_UPDATE_PERIOD = 10000
IM_SIZE = 80
K = 4
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)
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])
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))
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)
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
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
到目前为止,策略梯度是最常用的 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))
import numpy as np
import gym
import matplotlib.pyplot as plt
import tensorflow as tf
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()
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
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
PolicyNetwork
智能体使用update
方法更新权重:def update(self, feed):
return self.session.run(self.train_op, feed)
# 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()
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)
的值,其中nin
和nout
该层的输入和输出数。 要了解有关 Xavier 初始化的更多信息,请参阅 Glorot 和 Bengio 在 2009 年发表的论文。 有关详细信息,请参见“另见”部分。
看到智能体第一次学习演奏的任何人都会对此感到惊讶-看起来很像人。 最初的举动总是很笨拙。 缓慢地,坐席会学习走哪条路,尽管速度很慢并且经常会错过球。 但是,随着学习的继续,智能体将成为专家。
但这与我们很不一样。 一旦学会玩游戏,我们便可以在其他任何类似情况下轻松使用该知识。 RL 智能体将无法执行此操作-即使是简单的事情(例如更改环境空间的大小)也会将其恢复为零。 迁移学习是研究人员正在研究的一种技术,它可以帮助主体在另一环境空间中的一个环境中使用它所学到的知识,也许有一天可以为真正的人工智能奠定基础。
最近,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 月发表于《自然》上的论文《掌握无人掌握的围棋游戏》中阅读其实现的详细信息。
Xavier Glorot and Yoshua Bengio, Understanding the difficulty of training deep feedforward neural networks, Proceedings of the Thirteenth International Conference on Artificial Intelligence and Statistics, 2010, http://proceedings.mlr.press/v9/glorot10a/glorot10a.pdf
在本章中,我们将讨论在移动设备上使用深度学习的问题,并为以下内容提供一些方法:
在本节中,我们将介绍移动深度学习的一些用例。 这与台式机或云深度学习的情况大不相同,在台式机或云深度学习中,GPU 和电力通常可用。 实际上,在移动设备上,保存电池非常重要,并且 GPU 经常不可用。 但是,深度学习在许多情况下可能非常有用。 让我们回顾一下:
如上所述,电话通常没有 GPU,因此节省电池电量非常重要。 为了减轻成本,需要将许多昂贵的计算卸载到云中。 当然,要折衷考虑各种因素,包括在移动设备上执行深度学习模型的成本,将数据移至云的成本,用于此传输的电池成本以及云计算的成本。 没有单一的解决方案,最佳策略取决于您的具体情况。
在本秘籍中,我们将学习如何为移动环境设置 TensorFlow。 我的环境是 macOS,我为 Android 开发。 但是,在以下秘籍中将描述其他配置。
我们将使用 Android Studio,这是适用于 Google Android 操作系统的官方集成开发环境(IDE)。
我们继续按以下步骤安装适用于 macOS 和 Android 的 TensorFlow mobile:
AndroidExampleTensorflow
,如以下屏幕截图所示:
在 AndroidStudio 中创建 TensorFlow 移动应用的示例,第一步如下图所示:选择电话和表格选项:
在 AndroidStudio 中创建 TensorFlow 移动应用的示例,第二步并选择一个空活动,如下图所示:
在 AndroidStudio 中创建 TensorFlow 移动应用的示例,第三步然后自定义MainActivity
,如下图所示:
在 AndroidStudio 中创建 TensorFlow 移动应用的示例,第四步称为“基础神经节”,根据神经科学,它负责选择动作,即帮助我们确定在任何给定时间执行几个可能动作中的哪个。
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'
}
以下屏幕截图显示了插入的代码:
使用 AndroidStudio 进行编译的示例,其中显示了连接的设备。
在 AndroidStudio 中创建 TensorFlow 移动应用的示例。 一个简单的Hello World
应用
使用 Android Studio 设置 Android TensorFlow 非常简单。 您只需要在应用的build.gradle
文件中添加一些配置行,Android Studio 就会代表您执行所有操作。
如果要直接从 TensorFlow 源构建,则需要安装 Bazel 和 TensorFlow。 Bazel 是一个快速,可扩展,多语言和可扩展的构建系统。 Google 内部使用了构建工具 Blaze,并将 Blaze 工具的开源部分称为 Bazel。 名称是 Blaze 的字谜。
如果您正在运行 macOS,则过程非常简单:
/usr/bin/ruby -e "$(curl -fsSL \
https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install bazel
bazel version
brew upgrade bazel
git clone https://github.com/TensorFlow/TensorFlow.git
在本秘籍中,我们将考虑 TensorFlow 发行版中提供的标准 Android 示例并将其安装在我们的移动设备上。
TensorFlow 移动 Android 应用可在 GitHub 上的以下地址获得。 2017 年 10 月,该页面包含以下示例:
我们按以下步骤进行:
TensorFlow_demo.apk
,如以下屏幕截图所示:adb devices
列出所有连接的设备。 在这种情况下,我有一个 Pixel XL 模拟器,可以安装TensorFlow_demo apk
。adb devices
List of devices attached
emulator-5554 device
adb install -r TensorFlow_demo.apk
安装后,仿真器将具有一组新的 TensorFlow 应用可供使用,如下图所示。
下图是 TF 语音的示例(请记住为仿真器激活麦克风):
如果您使用夜间构建演示和adb
工具在设备上安装 APK,则安装 Android 的 TensorFlow 示例非常容易。
在本秘籍中,我们将学习如何在移动环境中设置 TensorFlow。 我的环境是 macOS,这里的想法是为 iOS 和 iPhone 开发。
我们将使用 Xcode 开发环境和 CocoaPods 来预安装 TensorFlow。 我将假定您的环境中已经安装了 Xcode。 如果没有,请从这里下载。
我们将按照以下步骤进行操作:
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.
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.
simple
和camera
文件夹内的数据文件夹中: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/
https://upload.wikimedia.org/wikipedia/commons/5/55/Grace_Hopper.jpg
cp grace_hopper.jpg ../../benchmark/data/
Grace Hopper 的图片
open tf_benchmark_example.xcworkspace
用于 Tensorflow 计算的 Iphone 应用示例
Xcode 和 CocoaPods 用于编译 TensorFlow 应用,该应用用于对不同 Inception 类别中的图像进行分类。 结果使用 iPhone 模拟器可视化。
您可以直接在应用中使用 TensorFlow。 可在此处获得更多信息。
在本秘籍中,我们将考虑不同的选项来优化在移动设备上运行的 TensorFlow 代码。 从减小模型的大小到量化,分析了不同的选项。
我们将使用 Bazel 构建 TensorFlow 的不同组件。 因此,第一步是确保同时安装了 Bazel 和 TensorFlow。
我们按以下步骤进行优化:
/usr/bin/ruby -e "$(curl -fsSL \
https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install bazel
bazel version
brew upgrade bazel
git clone https://github.com/TensorFlow/TensorFlow.git
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
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
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
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
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
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 代码以在移动设备上运行。 从减小模型的大小到量化,分析了不同的选项。
我们将使用 Bazel 构建 TensorFlow 的不同组件。 因此,第一步是确保同时安装了 Bazel 和 TensorFlow。
我们进行如下分析:
/usr/bin/ruby -e "$(curl -fsSL \
https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install bazel
bazel version
brew upgrade bazel
git clone https://github.com/TensorFlow/TensorFlow.git
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
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
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 图,以便删除所有仅训练节点。 这将减小图的大小,使其更适合于移动设备。
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 的方法:
/usr/bin/ruby -e "$(curl -fsSL \
https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install bazel
bazel version
brew upgrade bazel
git clone https://github.com/TensorFlow/TensorFlow.git
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
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
规则删除了所有不需要的节点。 该操作将删除用于学习的所有操作,并保留用于推理的操作。