Machine-Learning-Mastery-LSTM-教程-二-

46 阅读1小时+

Machine Learning Mastery LSTM 教程(二)

原文:Machine Learning Mastery

协议:CC BY-NC-SA 4.0

如何在 Keras 中开发带有注意力的编解码器模型

原文: machinelearningmastery.com/encoder-decoder-attention-sequence-to-sequence-prediction-keras/

用于循环神经网络的编解码器架构被证明在诸如机器翻译和字幕生成之类的自然语言处理领域中的大量序列到序列预测问题上是强大的。

注意力是一种解决编解码器架构在长序列上的限制的机制,并且通常加速学习并且提升模型的技能而没有序列来排序预测问题。

在本教程中,您将了解如何使用 Keras 在 Python 中开发一个编解码器循环神经网络。

完成本教程后,您将了解:

  • 如何设计一个小的可配置问题来评估编解码器循环神经网络有无注意。
  • 如何设计和评估编解码器网络,有和没有注意序列预测问题。
  • 如何有力地比较编解码器网络的表现有没有注意。

让我们开始吧。

How to Develop an Encoder-Decoder Model with Attention for Sequence-to-Sequence Prediction in Keras

如何开发一个编解码器模型,注意 Keras 中的序列到序列预测 照片由 Angela 和 Andrew ,保留一些权利。

教程概述

本教程分为 6 个部分;他们是:

  1. 注意编解码器
  2. 注意力的测试问题
  3. 编解码器没有注意
  4. 自定义 Keras 注意层
  5. 注意编解码器
  6. 模型比较

Python 环境

本教程假定您已安装 Python 3 SciPy 环境。

您必须安装带有 TensorFlow 或 Theano 后端的 Keras(2.0 或更高版本)。

本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。

如果您需要有关环境的帮助,请参阅此帖子:

注意编解码器

用于循环神经网络的编解码器模型是用于序列到序列预测问题的架构。

它由两个子模型组成,顾名思义:

  • 编码器:编码器负责逐步执行输入时间步长并将整个序列编码为称为上下文向量的固定长度向量。
  • 解码器:解码器负责在从上下文向量读取时逐步执行输出时间步长。

该架构的问题在于长输入或输出序列的表现差。原因被认为是由于编码器使用的固定大小的内部表示。

注意是解决此限制的架构的扩展。它的工作原理是首先提供从编码器到解码器的更丰富的上下文和学习机制,其中解码器可以在预测输出序列中的每个时间步长时学习在更丰富的编码中注意的位置。

有关编解码器架构的更多关注,请参阅帖子:

注意力的测试问题

在我们开发注意力模型之前,我们将首先定义一个可设计的可扩展测试问题,我们可以用它来确定注意力是否提供任何好处。

在这个问题中,我们将生成随机整数序列作为输入和匹配输出序列,该输出序列由输入序列中的整数子集组成。

例如,输入序列可能是[1,6,2,7,3],预期的输出序列可能是序列[1,6]中的前两个随机整数。

我们将定义问题,使输入和输出序列长度相同,并根据需要用“0”值填充输出序列。

首先,我们需要一个函数来生成随机整数序列。我们将使用 Python randint()函数生成 0 和最大值之间的随机整数,并使用此范围作为问题的基数(例如,要素数或难度轴)。

下面的函数 generate_sequence() 将生成一个固定长度和指定基数的随机整数序列。

from random import randint

# generate a sequence of random integers
def generate_sequence(length, n_unique):
	return [randint(0, n_unique-1) for _ in range(length)]

# generate random sequence
sequence = generate_sequence(5, 50)
print(sequence)

运行此示例将生成一个包含 5 个时间步长的序列,其中序列中的每个值都是 0 到 49 之间的随机整数。

[43, 3, 28, 34, 33]

接下来,我们需要一个函数将单热编码的离散整数值转换成二进制向量。

如果使用 50 的基数,则每个整数将由 0 个值的 50 个元素向量和指定整数值的索引中的 1 表示。

下面的 one_hot_encode() 函数将对给定的整数序列进行热编码。

# one hot encode sequence
def one_hot_encode(sequence, n_unique):
	encoding = list()
	for value in sequence:
		vector = [0 for _ in range(n_unique)]
		vector[value] = 1
		encoding.append(vector)
	return array(encoding)

我们还需要能够解码编码序列。需要将预测从模型或编码的预期序列转换回我们可以读取和评估的整数序列。

下面的 one_hot_decode() 函数将单热编码序列解码回整数序列。

# decode a one hot encoded string
def one_hot_decode(encoded_seq):
	return [argmax(vector) for vector in encoded_seq]

我们可以在下面的例子中测试这些操作。

from random import randint
from numpy import array
from numpy import argmax

# generate a sequence of random integers
def generate_sequence(length, n_unique):
	return [randint(0, n_unique-1) for _ in range(length)]

# one hot encode sequence
def one_hot_encode(sequence, n_unique):
	encoding = list()
	for value in sequence:
		vector = [0 for _ in range(n_unique)]
		vector[value] = 1
		encoding.append(vector)
	return array(encoding)

# decode a one hot encoded string
def one_hot_decode(encoded_seq):
	return [argmax(vector) for vector in encoded_seq]

# generate random sequence
sequence = generate_sequence(5, 50)
print(sequence)
# one hot encode
encoded = one_hot_encode(sequence, 50)
print(encoded)
# decode
decoded = one_hot_decode(encoded)
print(decoded)

首先运行该示例打印随机生成的序列,然后打印单热编码版本,最后再打印解码序列。

[3, 18, 32, 11, 36]
[[0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0]]
[3, 18, 32, 11, 36]

最后,我们需要一个能够创建输入和输出序列对的函数来训练和评估模型。

下面命名为 get_pair() 的函数将返回一个输入和输出序列对,给定指定的输入长度,输出长度和基数。输入和输出序列的长度和输入序列的长度相同,但输出序列将作为输入序列的第一个n字符,并用零值填充到所需长度。

然后对整数序列进行编码,然后重新成形为循环神经网络所需的 3D 格式,其尺寸为:_ 样本 时间步长 _ 和 _ 特征 _。在这种情况下,样本总是 1,因为我们只生成一个输入 - 输出对,时间步长是输入序列长度,特征是每个时间步长的基数。

# prepare data for the LSTM
def get_pair(n_in, n_out, n_unique):
	# generate random sequence
	sequence_in = generate_sequence(n_in, n_unique)
	sequence_out = sequence_in[:n_out] + [0 for _ in range(n_in-n_out)]
	# one hot encode
	X = one_hot_encode(sequence_in, n_unique)
	y = one_hot_encode(sequence_out, n_unique)
	# reshape as 3D
	X = X.reshape((1, X.shape[0], X.shape[1]))
	y = y.reshape((1, y.shape[0], y.shape[1]))
	return X,y

我们可以将它们放在一起并演示数据准备代码。

from random import randint
from numpy import array
from numpy import argmax

# generate a sequence of random integers
def generate_sequence(length, n_unique):
	return [randint(0, n_unique-1) for _ in range(length)]

# one hot encode sequence
def one_hot_encode(sequence, n_unique):
	encoding = list()
	for value in sequence:
		vector = [0 for _ in range(n_unique)]
		vector[value] = 1
		encoding.append(vector)
	return array(encoding)

# decode a one hot encoded string
def one_hot_decode(encoded_seq):
	return [argmax(vector) for vector in encoded_seq]

# prepare data for the LSTM
def get_pair(n_in, n_out, n_unique):
	# generate random sequence
	sequence_in = generate_sequence(n_in, n_unique)
	sequence_out = sequence_in[:n_out] + [0 for _ in range(n_in-n_out)]
	# one hot encode
	X = one_hot_encode(sequence_in, n_unique)
	y = one_hot_encode(sequence_out, n_unique)
	# reshape as 3D
	X = X.reshape((1, X.shape[0], X.shape[1]))
	y = y.reshape((1, y.shape[0], y.shape[1]))
	return X,y

# generate random sequence
X, y = get_pair(5, 2, 50)
print(X.shape, y.shape)
print('X=%s, y=%s' % (one_hot_decode(X[0]), one_hot_decode(y[0])))

运行该示例生成单个输入 - 输出对并打印两个数组的形状。

然后以解码的形式打印生成的对,其中我们可以看到序列的前两个整数在输出序列中被再现,随后是零值的填充。

(1, 5, 50) (1, 5, 50)
X=[12, 20, 36, 40, 12], y=[12, 20, 0, 0, 0]

编解码器没有注意

在本节中,我们将在没有注意的情况下使用编解码器模型开发关于问题表现的基线。

我们将在 5 个时间步的输入和输出序列中修复问题定义,输出序列中输入序列的前 2 个元素和基数为 50。

# configure problem
n_features = 50
n_timesteps_in = 5
n_timesteps_out = 2

我们可以通过从编码器 LSTM 模型获得输出,在 Keras 中开发一个简单的编解码器模型,对输出序列中的时间步长重复 n 次,然后使用解码器来预测输出序列。

有关如何在 Keras 中定义编解码器架构的更多详细信息,请参阅帖子:

我们将使用相同数量的单位配置编码器和解码器,在本例中为 150.我们将使用梯度下降的有效 Adam 实现并优化分类交叉熵损失函数,因为该问题在技术上是一个多类别分类问题。

模型的配置是在经过一些试验和错误之后找到的,并未进行优化。

下面列出了 Keras 中编解码器架构的代码。

# define model
model = Sequential()
model.add(LSTM(150, input_shape=(n_timesteps_in, n_features)))
model.add(RepeatVector(n_timesteps_in))
model.add(LSTM(150, return_sequences=True))
model.add(TimeDistributed(Dense(n_features, activation='softmax')))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])

我们将在 5,000 个随机输入 - 输出对的整数序列上训练模型。

# train LSTM
for epoch in range(5000):
	# generate new random sequence
	X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
	# fit model for one epoch on this sequence
	model.fit(X, y, epochs=1, verbose=2)

一旦经过训练,我们将在 100 个新的随机生成的整数序列上评估模型,并且只在整个输出序列与预期值匹配时才标记预测正确。

# evaluate LSTM
total, correct = 100, 0
for _ in range(total):
	X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
	yhat = model.predict(X, verbose=0)
	if array_equal(one_hot_decode(y[0]), one_hot_decode(yhat[0])):
		correct += 1
print('Accuracy: %.2f%%' % (float(correct)/float(total)*100.0))

最后,我们将打印 10 个预期输出序列和模型预测序列的例子。

将所有这些放在一起,下面列出了完整的示例。

from random import randint
from numpy import array
from numpy import argmax
from numpy import array_equal
from keras.models import Sequential
from keras.layers import LSTM
from keras.layers import Dense
from keras.layers import TimeDistributed
from keras.layers import RepeatVector

# generate a sequence of random integers
def generate_sequence(length, n_unique):
	return [randint(0, n_unique-1) for _ in range(length)]

# one hot encode sequence
def one_hot_encode(sequence, n_unique):
	encoding = list()
	for value in sequence:
		vector = [0 for _ in range(n_unique)]
		vector[value] = 1
		encoding.append(vector)
	return array(encoding)

# decode a one hot encoded string
def one_hot_decode(encoded_seq):
	return [argmax(vector) for vector in encoded_seq]

# prepare data for the LSTM
def get_pair(n_in, n_out, cardinality):
	# generate random sequence
	sequence_in = generate_sequence(n_in, cardinality)
	sequence_out = sequence_in[:n_out] + [0 for _ in range(n_in-n_out)]
	# one hot encode
	X = one_hot_encode(sequence_in, cardinality)
	y = one_hot_encode(sequence_out, cardinality)
	# reshape as 3D
	X = X.reshape((1, X.shape[0], X.shape[1]))
	y = y.reshape((1, y.shape[0], y.shape[1]))
	return X,y

# configure problem
n_features = 50
n_timesteps_in = 5
n_timesteps_out = 2
# define model
model = Sequential()
model.add(LSTM(150, input_shape=(n_timesteps_in, n_features)))
model.add(RepeatVector(n_timesteps_in))
model.add(LSTM(150, return_sequences=True))
model.add(TimeDistributed(Dense(n_features, activation='softmax')))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])
# train LSTM
for epoch in range(5000):
	# generate new random sequence
	X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
	# fit model for one epoch on this sequence
	model.fit(X, y, epochs=1, verbose=2)
# evaluate LSTM
total, correct = 100, 0
for _ in range(total):
	X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
	yhat = model.predict(X, verbose=0)
	if array_equal(one_hot_decode(y[0]), one_hot_decode(yhat[0])):
		correct += 1
print('Accuracy: %.2f%%' % (float(correct)/float(total)*100.0))
# spot check some examples
for _ in range(10):
	X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
	yhat = model.predict(X, verbose=0)
	print('Expected:', one_hot_decode(y[0]), 'Predicted', one_hot_decode(yhat[0]))

运行此示例不会花费很长时间,可能需要几分钟的 CPU,不需要 GPU。

据报道该模型的准确率略低于 20%。鉴于神经网络的随机性,您的结果会有所不同;考虑运行几次示例并取平均值。

Accuracy: 19.00%

我们可以从样本输出中看到,模型确实在输出序列中得到一个数字,对于大多数或所有情况都是正确的,并且只与第二个数字斗争。正确预测所有零填充值。

Expected: [47, 0, 0, 0, 0] Predicted [47, 47, 0, 0, 0]
Expected: [43, 31, 0, 0, 0] Predicted [43, 31, 0, 0, 0]
Expected: [14, 22, 0, 0, 0] Predicted [14, 14, 0, 0, 0]
Expected: [39, 31, 0, 0, 0] Predicted [39, 39, 0, 0, 0]
Expected: [6, 4, 0, 0, 0] Predicted [6, 4, 0, 0, 0]
Expected: [47, 0, 0, 0, 0] Predicted [47, 47, 0, 0, 0]
Expected: [39, 33, 0, 0, 0] Predicted [39, 39, 0, 0, 0]
Expected: [23, 2, 0, 0, 0] Predicted [23, 23, 0, 0, 0]
Expected: [19, 28, 0, 0, 0] Predicted [19, 3, 0, 0, 0]
Expected: [32, 33, 0, 0, 0] Predicted [32, 32, 0, 0, 0]

自定义 Keras 注意层

现在我们需要关注编解码器模型。

在撰写本文时,Keras 没有内置于库中的注意力,但很快就会

在 Keras 正式提供关注之前,我们可以开发自己的实现或使用现有的第三方实现。

为了加快速度,让我们使用现有的第三方实现。

Zafarali Ahmed Datalogue 的实习生为 Keras 开发了一个自定义层,提供了关注支持,在一篇名为“如何可视化您的复发的帖子中提出 2017 年 Keras 中关注的神经网络和 GitHub 项目称为“ keras-attention ”。

自定义注意层称为AttentionDecoder,可在 GitHub 项目的 custom_recurrents.py 文件中找到。我们可以在项目的 GNU Affero 通用公共许可证 v3.0 许可证下重用此代码。

下面列出了自定义层的副本以确保完整性。将其复制并粘贴到当前工作目录中名为“attention_decoder.py”的新单独文件中。

import tensorflow as tf
from keras import backend as K
from keras import regularizers, constraints, initializers, activations
from keras.layers.recurrent import Recurrent, _time_distributed_dense
from keras.engine import InputSpec

tfPrint = lambda d, T: tf.Print(input_=T, data=[T, tf.shape(T)], message=d)

class AttentionDecoder(Recurrent):

    def __init__(self, units, output_dim,
                 activation='tanh',
                 return_probabilities=False,
                 name='AttentionDecoder',
                 kernel_initializer='glorot_uniform',
                 recurrent_initializer='orthogonal',
                 bias_initializer='zeros',
                 kernel_regularizer=None,
                 bias_regularizer=None,
                 activity_regularizer=None,
                 kernel_constraint=None,
                 bias_constraint=None,
                 **kwargs):
        """
        Implements an AttentionDecoder that takes in a sequence encoded by an
        encoder and outputs the decoded states
        :param units: dimension of the hidden state and the attention matrices
        :param output_dim: the number of labels in the output space

        references:
            Bahdanau, Dzmitry, Kyunghyun Cho, and Yoshua Bengio.
            "Neural machine translation by jointly learning to align and translate."
            arXiv preprint arXiv:1409.0473 (2014).
        """
        self.units = units
        self.output_dim = output_dim
        self.return_probabilities = return_probabilities
        self.activation = activations.get(activation)
        self.kernel_initializer = initializers.get(kernel_initializer)
        self.recurrent_initializer = initializers.get(recurrent_initializer)
        self.bias_initializer = initializers.get(bias_initializer)

        self.kernel_regularizer = regularizers.get(kernel_regularizer)
        self.recurrent_regularizer = regularizers.get(kernel_regularizer)
        self.bias_regularizer = regularizers.get(bias_regularizer)
        self.activity_regularizer = regularizers.get(activity_regularizer)

        self.kernel_constraint = constraints.get(kernel_constraint)
        self.recurrent_constraint = constraints.get(kernel_constraint)
        self.bias_constraint = constraints.get(bias_constraint)

        super(AttentionDecoder, self).__init__(**kwargs)
        self.name = name
        self.return_sequences = True  # must return sequences

    def build(self, input_shape):
        """
          See Appendix 2 of Bahdanau 2014, arXiv:1409.0473
          for model details that correspond to the matrices here.
        """

        self.batch_size, self.timesteps, self.input_dim = input_shape

        if self.stateful:
            super(AttentionDecoder, self).reset_states()

        self.states = [None, None]  # y, s

        """
            Matrices for creating the context vector
        """

        self.V_a = self.add_weight(shape=(self.units,),
                                   name='V_a',
                                   initializer=self.kernel_initializer,
                                   regularizer=self.kernel_regularizer,
                                   constraint=self.kernel_constraint)
        self.W_a = self.add_weight(shape=(self.units, self.units),
                                   name='W_a',
                                   initializer=self.kernel_initializer,
                                   regularizer=self.kernel_regularizer,
                                   constraint=self.kernel_constraint)
        self.U_a = self.add_weight(shape=(self.input_dim, self.units),
                                   name='U_a',
                                   initializer=self.kernel_initializer,
                                   regularizer=self.kernel_regularizer,
                                   constraint=self.kernel_constraint)
        self.b_a = self.add_weight(shape=(self.units,),
                                   name='b_a',
                                   initializer=self.bias_initializer,
                                   regularizer=self.bias_regularizer,
                                   constraint=self.bias_constraint)
        """
            Matrices for the r (reset) gate
        """
        self.C_r = self.add_weight(shape=(self.input_dim, self.units),
                                   name='C_r',
                                   initializer=self.recurrent_initializer,
                                   regularizer=self.recurrent_regularizer,
                                   constraint=self.recurrent_constraint)
        self.U_r = self.add_weight(shape=(self.units, self.units),
                                   name='U_r',
                                   initializer=self.recurrent_initializer,
                                   regularizer=self.recurrent_regularizer,
                                   constraint=self.recurrent_constraint)
        self.W_r = self.add_weight(shape=(self.output_dim, self.units),
                                   name='W_r',
                                   initializer=self.recurrent_initializer,
                                   regularizer=self.recurrent_regularizer,
                                   constraint=self.recurrent_constraint)
        self.b_r = self.add_weight(shape=(self.units, ),
                                   name='b_r',
                                   initializer=self.bias_initializer,
                                   regularizer=self.bias_regularizer,
                                   constraint=self.bias_constraint)

        """
            Matrices for the z (update) gate
        """
        self.C_z = self.add_weight(shape=(self.input_dim, self.units),
                                   name='C_z',
                                   initializer=self.recurrent_initializer,
                                   regularizer=self.recurrent_regularizer,
                                   constraint=self.recurrent_constraint)
        self.U_z = self.add_weight(shape=(self.units, self.units),
                                   name='U_z',
                                   initializer=self.recurrent_initializer,
                                   regularizer=self.recurrent_regularizer,
                                   constraint=self.recurrent_constraint)
        self.W_z = self.add_weight(shape=(self.output_dim, self.units),
                                   name='W_z',
                                   initializer=self.recurrent_initializer,
                                   regularizer=self.recurrent_regularizer,
                                   constraint=self.recurrent_constraint)
        self.b_z = self.add_weight(shape=(self.units, ),
                                   name='b_z',
                                   initializer=self.bias_initializer,
                                   regularizer=self.bias_regularizer,
                                   constraint=self.bias_constraint)
        """
            Matrices for the proposal
        """
        self.C_p = self.add_weight(shape=(self.input_dim, self.units),
                                   name='C_p',
                                   initializer=self.recurrent_initializer,
                                   regularizer=self.recurrent_regularizer,
                                   constraint=self.recurrent_constraint)
        self.U_p = self.add_weight(shape=(self.units, self.units),
                                   name='U_p',
                                   initializer=self.recurrent_initializer,
                                   regularizer=self.recurrent_regularizer,
                                   constraint=self.recurrent_constraint)
        self.W_p = self.add_weight(shape=(self.output_dim, self.units),
                                   name='W_p',
                                   initializer=self.recurrent_initializer,
                                   regularizer=self.recurrent_regularizer,
                                   constraint=self.recurrent_constraint)
        self.b_p = self.add_weight(shape=(self.units, ),
                                   name='b_p',
                                   initializer=self.bias_initializer,
                                   regularizer=self.bias_regularizer,
                                   constraint=self.bias_constraint)
        """
            Matrices for making the final prediction vector
        """
        self.C_o = self.add_weight(shape=(self.input_dim, self.output_dim),
                                   name='C_o',
                                   initializer=self.recurrent_initializer,
                                   regularizer=self.recurrent_regularizer,
                                   constraint=self.recurrent_constraint)
        self.U_o = self.add_weight(shape=(self.units, self.output_dim),
                                   name='U_o',
                                   initializer=self.recurrent_initializer,
                                   regularizer=self.recurrent_regularizer,
                                   constraint=self.recurrent_constraint)
        self.W_o = self.add_weight(shape=(self.output_dim, self.output_dim),
                                   name='W_o',
                                   initializer=self.recurrent_initializer,
                                   regularizer=self.recurrent_regularizer,
                                   constraint=self.recurrent_constraint)
        self.b_o = self.add_weight(shape=(self.output_dim, ),
                                   name='b_o',
                                   initializer=self.bias_initializer,
                                   regularizer=self.bias_regularizer,
                                   constraint=self.bias_constraint)

        # For creating the initial state:
        self.W_s = self.add_weight(shape=(self.input_dim, self.units),
                                   name='W_s',
                                   initializer=self.recurrent_initializer,
                                   regularizer=self.recurrent_regularizer,
                                   constraint=self.recurrent_constraint)

        self.input_spec = [
            InputSpec(shape=(self.batch_size, self.timesteps, self.input_dim))]
        self.built = True

    def call(self, x):
        # store the whole sequence so we can "attend" to it at each timestep
        self.x_seq = x

        # apply the a dense layer over the time dimension of the sequence
        # do it here because it doesn't depend on any previous steps
        # thefore we can save computation time:
        self._uxpb = _time_distributed_dense(self.x_seq, self.U_a, b=self.b_a,
                                             input_dim=self.input_dim,
                                             timesteps=self.timesteps,
                                             output_dim=self.units)

        return super(AttentionDecoder, self).call(x)

    def get_initial_state(self, inputs):
        # apply the matrix on the first time step to get the initial s0.
        s0 = activations.tanh(K.dot(inputs[:, 0], self.W_s))

        # from keras.layers.recurrent to initialize a vector of (batchsize,
        # output_dim)
        y0 = K.zeros_like(inputs)  # (samples, timesteps, input_dims)
        y0 = K.sum(y0, axis=(1, 2))  # (samples, )
        y0 = K.expand_dims(y0)  # (samples, 1)
        y0 = K.tile(y0, [1, self.output_dim])

        return [y0, s0]

    def step(self, x, states):

        ytm, stm = states

        # repeat the hidden state to the length of the sequence
        _stm = K.repeat(stm, self.timesteps)

        # now multiplty the weight matrix with the repeated hidden state
        _Wxstm = K.dot(_stm, self.W_a)

        # calculate the attention probabilities
        # this relates how much other timesteps contributed to this one.
        et = K.dot(activations.tanh(_Wxstm + self._uxpb),
                   K.expand_dims(self.V_a))
        at = K.exp(et)
        at_sum = K.sum(at, axis=1)
        at_sum_repeated = K.repeat(at_sum, self.timesteps)
        at /= at_sum_repeated  # vector of size (batchsize, timesteps, 1)

        # calculate the context vector
        context = K.squeeze(K.batch_dot(at, self.x_seq, axes=1), axis=1)
        # ~~~> calculate new hidden state
        # first calculate the "r" gate:

        rt = activations.sigmoid(
            K.dot(ytm, self.W_r)
            + K.dot(stm, self.U_r)
            + K.dot(context, self.C_r)
            + self.b_r)

        # now calculate the "z" gate
        zt = activations.sigmoid(
            K.dot(ytm, self.W_z)
            + K.dot(stm, self.U_z)
            + K.dot(context, self.C_z)
            + self.b_z)

        # calculate the proposal hidden state:
        s_tp = activations.tanh(
            K.dot(ytm, self.W_p)
            + K.dot((rt * stm), self.U_p)
            + K.dot(context, self.C_p)
            + self.b_p)

        # new hidden state:
        st = (1-zt)*stm + zt * s_tp

        yt = activations.softmax(
            K.dot(ytm, self.W_o)
            + K.dot(stm, self.U_o)
            + K.dot(context, self.C_o)
            + self.b_o)

        if self.return_probabilities:
            return at, [yt, st]
        else:
            return yt, [yt, st]

    def compute_output_shape(self, input_shape):
        """
            For Keras internal compatability checking
        """
        if self.return_probabilities:
            return (None, self.timesteps, self.timesteps)
        else:
            return (None, self.timesteps, self.output_dim)

    def get_config(self):
        """
            For rebuilding models on load time.
        """
        config = {
            'output_dim': self.output_dim,
            'units': self.units,
            'return_probabilities': self.return_probabilities
        }
        base_config = super(AttentionDecoder, self).get_config()
        return dict(list(base_config.items()) + list(config.items()))

我们可以通过以下方式导入项目中来使用此自定义层:

from attention_decoder import AttentionDecoder

如 Bahdanau 等人所述,该层实现了关注。在他们的论文“神经机器翻译中通过联合学习来对齐和翻译。”

该代码在原始帖子中得到了很好的解释,并与 LSTM 和注意力方程相关联。

该实现的限制是它必须输出与输入序列长度相同的序列,编解码器架构被设计为克服的特定限制。

重要的是,新层管理由第二 LSTM 执行的重复解码,以及由编解码器模型中的密集输出层执行的模型的 softmax 输出,而没有注意。这极大地简化了模型的代码。

值得注意的是,自定义层建立在 Keras 的 Recurrent 层上,在编写本文时,它被标记为遗留代码,并且可能会在某个时候从项目中删除。

编解码器注意

现在我们已经可以使用我们的注意力实现,我们可以开发一个编解码器模型,注意我们设计的序列预测问题。

具有关注层的模型定义如下。我们可以看到该层处理编解码器模型本身的一些机制,使得定义模型更简单。

# define model
model = Sequential()
model.add(LSTM(150, input_shape=(n_timesteps_in, n_features), return_sequences=True))
model.add(AttentionDecoder(150, n_features))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])

而已。示例的其余部分是相同的。

下面列出了完整的示例。

from random import randint
from numpy import array
from numpy import argmax
from numpy import array_equal
from keras.models import Sequential
from keras.layers import LSTM
from attention_decoder import AttentionDecoder

# generate a sequence of random integers
def generate_sequence(length, n_unique):
	return [randint(0, n_unique-1) for _ in range(length)]

# one hot encode sequence
def one_hot_encode(sequence, n_unique):
	encoding = list()
	for value in sequence:
		vector = [0 for _ in range(n_unique)]
		vector[value] = 1
		encoding.append(vector)
	return array(encoding)

# decode a one hot encoded string
def one_hot_decode(encoded_seq):
	return [argmax(vector) for vector in encoded_seq]

# prepare data for the LSTM
def get_pair(n_in, n_out, cardinality):
	# generate random sequence
	sequence_in = generate_sequence(n_in, cardinality)
	sequence_out = sequence_in[:n_out] + [0 for _ in range(n_in-n_out)]
	# one hot encode
	X = one_hot_encode(sequence_in, cardinality)
	y = one_hot_encode(sequence_out, cardinality)
	# reshape as 3D
	X = X.reshape((1, X.shape[0], X.shape[1]))
	y = y.reshape((1, y.shape[0], y.shape[1]))
	return X,y

# configure problem
n_features = 50
n_timesteps_in = 5
n_timesteps_out = 2

# define model
model = Sequential()
model.add(LSTM(150, input_shape=(n_timesteps_in, n_features), return_sequences=True))
model.add(AttentionDecoder(150, n_features))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])
# train LSTM
for epoch in range(5000):
	# generate new random sequence
	X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
	# fit model for one epoch on this sequence
	model.fit(X, y, epochs=1, verbose=2)
# evaluate LSTM
total, correct = 100, 0
for _ in range(total):
	X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
	yhat = model.predict(X, verbose=0)
	if array_equal(one_hot_decode(y[0]), one_hot_decode(yhat[0])):
		correct += 1
print('Accuracy: %.2f%%' % (float(correct)/float(total)*100.0))
# spot check some examples
for _ in range(10):
	X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
	yhat = model.predict(X, verbose=0)
	print('Expected:', one_hot_decode(y[0]), 'Predicted', one_hot_decode(yhat[0]))

运行该示例在 100 个随机生成的输入 - 输出对上打印模型的技能。在相同的资源和相同数量的训练下,关注的模型表现得更好。

鉴于神经网络的随机性,您的结果可能会有所不同。尝试运行几次示例。

Accuracy: 95.00%

通过抽查一些样本输出和预测序列,我们可以看到很少的错误,即使在前两个元素中存在零值的情况下也是如此。

Expected: [48, 47, 0, 0, 0] Predicted [48, 47, 0, 0, 0]
Expected: [7, 46, 0, 0, 0] Predicted [7, 46, 0, 0, 0]
Expected: [32, 30, 0, 0, 0] Predicted [32, 2, 0, 0, 0]
Expected: [3, 25, 0, 0, 0] Predicted [3, 25, 0, 0, 0]
Expected: [45, 4, 0, 0, 0] Predicted [45, 4, 0, 0, 0]
Expected: [49, 9, 0, 0, 0] Predicted [49, 9, 0, 0, 0]
Expected: [22, 23, 0, 0, 0] Predicted [22, 23, 0, 0, 0]
Expected: [29, 36, 0, 0, 0] Predicted [29, 36, 0, 0, 0]
Expected: [0, 29, 0, 0, 0] Predicted [0, 29, 0, 0, 0]
Expected: [11, 26, 0, 0, 0] Predicted [11, 26, 0, 0, 0]

模型比较

尽管我们从模型中获得了更好的结果,但是每个模型的单次运行都会报告结果。

在这种情况下,我们通过多次重复评估每个模型并报告这些运行的平均表现来寻求更稳健的发现。有关评估神经网络模型的这种强大方法的更多信息,请参阅帖子:

我们可以定义一个函数来创建每种类型的模型,如下所示。

# define the encoder-decoder model
def baseline_model(n_timesteps_in, n_features):
	model = Sequential()
	model.add(LSTM(150, input_shape=(n_timesteps_in, n_features)))
	model.add(RepeatVector(n_timesteps_in))
	model.add(LSTM(150, return_sequences=True))
	model.add(TimeDistributed(Dense(n_features, activation='softmax')))
	model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])
	return model

# define the encoder-decoder with attention model
def attention_model(n_timesteps_in, n_features):
	model = Sequential()
	model.add(LSTM(150, input_shape=(n_timesteps_in, n_features), return_sequences=True))
	model.add(AttentionDecoder(150, n_features))
	model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])
	return model

然后,我们可以定义一个函数来拟合和评估拟合模型的准确率并返回准确度分数。

# train and evaluate a model, return accuracy
def train_evaluate_model(model, n_timesteps_in, n_timesteps_out, n_features):
	# train LSTM
	for epoch in range(5000):
		# generate new random sequence
		X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
		# fit model for one epoch on this sequence
		model.fit(X, y, epochs=1, verbose=0)
	# evaluate LSTM
	total, correct = 100, 0
	for _ in range(total):
		X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
		yhat = model.predict(X, verbose=0)
		if array_equal(one_hot_decode(y[0]), one_hot_decode(yhat[0])):
			correct += 1
	return float(correct)/float(total)*100.0

综上所述,我们可以多次重复创建,训练和评估每种类型的模型,并报告重复的平均准确度。为了减少运行时间,我们将重复每次模型评估 10 次,但如果您有资源,则可以将此值增加到 30 或 100 次。

The complete example is listed below.

from random import randint
from numpy import array
from numpy import argmax
from numpy import array_equal
from keras.models import Sequential
from keras.layers import LSTM
from keras.layers import Dense
from keras.layers import TimeDistributed
from keras.layers import RepeatVector
from attention_decoder import AttentionDecoder

# generate a sequence of random integers
def generate_sequence(length, n_unique):
	return [randint(0, n_unique-1) for _ in range(length)]

# one hot encode sequence
def one_hot_encode(sequence, n_unique):
	encoding = list()
	for value in sequence:
		vector = [0 for _ in range(n_unique)]
		vector[value] = 1
		encoding.append(vector)
	return array(encoding)

# decode a one hot encoded string
def one_hot_decode(encoded_seq):
	return [argmax(vector) for vector in encoded_seq]

# prepare data for the LSTM
def get_pair(n_in, n_out, cardinality):
	# generate random sequence
	sequence_in = generate_sequence(n_in, cardinality)
	sequence_out = sequence_in[:n_out] + [0 for _ in range(n_in-n_out)]
	# one hot encode
	X = one_hot_encode(sequence_in, cardinality)
	y = one_hot_encode(sequence_out, cardinality)
	# reshape as 3D
	X = X.reshape((1, X.shape[0], X.shape[1]))
	y = y.reshape((1, y.shape[0], y.shape[1]))
	return X,y

# define the encoder-decoder model
def baseline_model(n_timesteps_in, n_features):
	model = Sequential()
	model.add(LSTM(150, input_shape=(n_timesteps_in, n_features)))
	model.add(RepeatVector(n_timesteps_in))
	model.add(LSTM(150, return_sequences=True))
	model.add(TimeDistributed(Dense(n_features, activation='softmax')))
	model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])
	return model

# define the encoder-decoder with attention model
def attention_model(n_timesteps_in, n_features):
	model = Sequential()
	model.add(LSTM(150, input_shape=(n_timesteps_in, n_features), return_sequences=True))
	model.add(AttentionDecoder(150, n_features))
	model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])
	return model

# train and evaluate a model, return accuracy
def train_evaluate_model(model, n_timesteps_in, n_timesteps_out, n_features):
	# train LSTM
	for epoch in range(5000):
		# generate new random sequence
		X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
		# fit model for one epoch on this sequence
		model.fit(X, y, epochs=1, verbose=0)
	# evaluate LSTM
	total, correct = 100, 0
	for _ in range(total):
		X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
		yhat = model.predict(X, verbose=0)
		if array_equal(one_hot_decode(y[0]), one_hot_decode(yhat[0])):
			correct += 1
	return float(correct)/float(total)*100.0

# configure problem
n_features = 50
n_timesteps_in = 5
n_timesteps_out = 2
n_repeats = 10
# evaluate encoder-decoder model
print('Encoder-Decoder Model')
results = list()
for _ in range(n_repeats):
	model = baseline_model(n_timesteps_in, n_features)
	accuracy = train_evaluate_model(model, n_timesteps_in, n_timesteps_out, n_features)
	results.append(accuracy)
	print(accuracy)
print('Mean Accuracy: %.2f%%' % (sum(results)/float(n_repeats)))
# evaluate encoder-decoder with attention model
print('Encoder-Decoder With Attention Model')
results = list()
for _ in range(n_repeats):
	model = attention_model(n_timesteps_in, n_features)
	accuracy = train_evaluate_model(model, n_timesteps_in, n_timesteps_out, n_features)
	results.append(accuracy)
	print(accuracy)
print('Mean Accuracy: %.2f%%' % (sum(results)/float(n_repeats)))

运行此示例将打印每个模型重复的准确率,以便您了解运行的进度。

Encoder-Decoder Model
20.0
23.0
23.0
18.0
28.000000000000004
28.999999999999996
23.0
26.0
21.0
20.0
Mean Accuracy: 23.10%

Encoder-Decoder With Attention Model
98.0
91.0
94.0
93.0
96.0
99.0
97.0
94.0
99.0
96.0
Mean Accuracy: 95.70%

我们可以看到,即使平均超过 10 次运行,注意模型仍然表现出比没有注意的编解码器模型更好的表现,23.10%对 95.70%。

此评估的一个很好的扩展是捕获每个模型的每个时期的模型损失,取平均值,并比较损失如何随着时间的推移而变化,无论是否受到关注。我希望这种追踪能够比非注意力模型更快,更快地显示出更好的技能,进一步突出了这种方法的好处。

进一步阅读

如果您希望深入了解,本节将提供有关该主题的更多资源。

摘要

在本教程中,您了解了如何使用 Keras 在 Python 中开发编解码器循环神经网络。

具体来说,你学到了:

  • 如何设计一个小的可配置问题来评估编解码器循环神经网络有无注意。
  • 如何设计和评估编解码器网络,有和没有注意序列预测问题。
  • 如何有力地比较编解码器网络的表现有没有注意。

你有任何问题吗? 在下面的评论中提出您的问题,我会尽力回答。

编解码器长短期记忆网络

原文: machinelearningmastery.com/encoder-decoder-long-short-term-memory-networks/

使用示例 Python 代码,轻松介绍

序列到序列预测的编解码器 LSTM。

编解码器 LSTM 是一种循环神经网络,旨在解决序列到序列问题,有时称为 seq2seq。

序列到序列预测问题具有挑战性,因为输入和输出序列中的项目数可以变化。例如,文本翻译和学习执行程序是 seq2seq 问题的例子。

在这篇文章中,您将发现用于序列到序列预测的编解码器 LSTM 架构。

完成这篇文章后,你会知道:

  • 序列到序列预测的挑战。
  • 编解码器架构以及它旨在解决的 LSTM 中的限制。
  • 如何使用 Keras 在 Python 中实现编解码器 LSTM 模型架构。

让我们开始吧。

Encoder-Decoder Long Short-Term Memory Networks

编解码器长短期记忆网络 照片来自 slashvee ,保留一些权利。

序列到序列预测问题

序列预测通常涉及预测实值序列中的下一个值或输出输入序列的类标签。

这通常被构造为一个输入时间步长到一个输出时间步长(例如一对一)或多个输入时间步长到一个输出时间步长(多对一)类型序列预测问题的序列。

存在更具挑战性的序列预测问题类型,其将序列作为输入并且需要序列预测作为输出。这些被称为序列到序列预测问题,或简称为 seq2seq。

使这些问题具有挑战性的一个建模问题是输入和输出序列的长度可能变化。假设存在多个输入时间步长和多个输出时间步长,则这种形式的问题被称为多对多类型序列预测问题。

编解码器 LSTM 架构

已证明非常有效的 seq2seq 预测问题的一种方法称为编解码器 LSTM。

该架构由两个模型组成:一个用于读取输入序列并将其编码为固定长度向量,第二个用于解码固定长度向量并输出预测序列。这些模型的使用使该架构的名称为 Encoder-Decoder LSTM,专门针对 seq2seq 问题而设计。

... RNN 编解码器,由两个循环神经网络(RNN)组成,充当编码器和解码器对。编码器将可变长度源序列映射到固定长度向量,并且解码器将向量表示映射回可变长度目标序列。

编解码器 LSTM 是为自然语言处理问题而开发的,它展示了最先进的表现,特别是在称为统计机器翻译的文本翻译领域。

该架构的创新之处在于在模型的核心使用固定大小的内部表示,读取输入序列并读取输出序列。因此,该方法可以称为序列嵌入。

在该架构的第一个应用之一中,英语到法语的翻译,编码的英语短语的内部表示是可视化的。这些图揭示了用于翻译任务的短语的定性有意义的学习结构。

所提出的 RNN 编解码器自然地生成短语的连续空间表示。 [...]从可视化中,很明显 RNN 编解码器捕获短语的语义和句法结构

Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation, 2014.

在翻译任务中,当输入序列被逆转时,发现模型更有效。此外,该模型甚至在很长的输入序列上也显示出有效性。

我们能够在长句子上做得好,因为我们颠倒了源句中的单词顺序,而不是训练和测试集中的目标句子。通过这样做,我们引入了许多短期依赖关系,使优化问题更加简单。 ......扭转源句中单词的简单技巧是这项工作的关键技术贡献之一

该方法还用于图像输入,其中卷积神经网络用作输入图像上的特征提取器,然后由解码器 LSTM 读取。

...我们建议遵循这个优雅的秘籍,用深度卷积神经网络(CNN)代替编码器 RNN。 [...]使用 CNN 作为图像编码器“很自然,首先将其预训练用于图像分类任务,并使用最后一个隐藏层作为生成句子的 RNN 解码器的输入

Encoder-Decoder LSTM Model Architecture

编解码器 LSTM 模型架构

编解码器 LSTM 的应用

下面的列表突出了编解码器 LSTM 架构的一些有趣应用。

  • 机器翻译,例如英语到法语的短语翻译。
  • 学习执行,例如计算小程序的结果。
  • 图像标题,例如生成图像的文本描述。
  • 会话建模,例如产生文本问题的答案。
  • 运动分类,例如从一系列手势生成一系列命令。

在 Keras 中实现编解码器 LSTM

编解码器 LSTM 可以直接在 Keras 深度学习库中实现。

我们可以认为该模型由两个关键部分组成:编码器和解码器。

首先,输入序列一次向网络显示一个编码字符。我们需要一个编码级别来学习输入序列中的步骤之间的关系,并开发这些关系的内部表示。

可以使用一个或多个 LSTM 层来实现编码器模型。此模型的输出是固定大小的向量,表示输入序列的内部表示。该层中的存储器单元数定义了该固定大小的向量的长度。

model = Sequential()
model.add(LSTM(..., input_shape=(...)))

解码器必须将输入序列的学习内部表示转换为正确的输出序列。

一个或多个 LSTM 层也可用于实现解码器模型。该模型从编码器模型的固定大小输出中读取。

与 Vanilla LSTM 一样,Dense 层用作网络的输出。通过将 Dense 层包装在 TimeDistributed 包装器中,可以使用相同的权重输出输出序列中的每个时间步长。

model.add(LSTM(..., return_sequences=True))
model.add(TimeDistributed(Dense(...)))

但是有一个问题。

我们必须将编码器连接到解码器,它们不适合。

也就是说,编码器将产生 2 维输出矩阵,其中长度由层中的存储器单元的数量来定义。解码器是 LSTM 层,其期望[样本,时间步长,特征]的 3D 输入,以便产生由问题定义的某个不同长度的解码序列。

如果您尝试将这些碎片强制在一起,则会出现错误,指示解码器的输出为 2D,并且需要向解码器输入 3D。

我们可以使用 RepeatVector 层来解决这个问题。该层简单地重复提供的 2D 输入多次以创建 3D 输出。

RepeatVector 层可以像适配器一样使用,以将网络的编码器和解码器部分组合在一起。我们可以将 RepeatVector 配置为对输出序列中的每个时间步重复一次固定长度向量。

model.add(RepeatVector(...))

把这些放在一起,我们有:

model = Sequential()
model.add(LSTM(..., input_shape=(...)))
model.add(RepeatVector(...))
model.add(LSTM(..., return_sequences=True))
model.add(TimeDistributed(Dense(...)))

总而言之,RepeatVector 用作适配器,以使编码器的固定大小的 2D 输出适合解码器所期望的不同长度和 3D 输入。 TimeDistributed 包装器允许为输出序列中的每个元素重用相同的输出层。

进一步阅读

如果您要深入了解,本节将提供有关该主题的更多资源。

文件

Keras API

帖子

摘要

在这篇文章中,您发现了用于序列到序列预测的编解码器 LSTM 架构

具体来说,你学到了:

  • 序列到序列预测的挑战。
  • 编解码器架构以及它旨在解决的 LSTM 中的限制。
  • 如何使用 Keras 在 Python 中实现编解码器 LSTM 模型架构。

你有任何问题吗? 在下面的评论中提出您的问题,我会尽力回答。

神经网络中梯度爆炸的温和介绍

原文: machinelearningmastery.com/exploding-gradients-in-neural-networks/

梯度爆炸是一个问题,其中大的误差梯度累积并导致在训练期间对神经网络模型权重的非常大的更新。

这会导致您的模型不稳定,无法从您的训练数据中学习。

在这篇文章中,您将发现深层人工神经网络梯度爆炸的问题。

完成这篇文章后,你会知道:

  • 爆炸的梯度是什么以及它们在训练过程中引起的问题。
  • 如何知道您的网络模型是否有梯度爆炸。
  • 如何解决网络中的梯度爆炸问题。

让我们开始吧。

  • 更新 Oct / 2018 :删除了 ReLU 作为解决方案的提及。

A Gentle Introduction to Exploding Gradients in Recurrent Neural Networks

回顾神经网络中梯度爆炸的温和介绍 Taro Taylor 的照片,保留一些权利。

什么是梯度爆炸?

误差梯度是在训练神经网络期间计算的方向和幅度,该神经网络用于以正确的方向和正确的量更新网络权重。

在深度网络或循环神经网络中,误差梯度可能在更新期间累积并导致非常大的梯度。这反过来又导致网络权重的大量更新,进而导致网络不稳定。在极端情况下,权重值可能会变得很大,以至于溢出并导致 NaN 值。

通过重复地将梯度乘以具有大于 1.0 的值的网络层,爆炸通过指数增长发生。

梯度爆炸有什么问题?

在深层多层 Perceptron 网络中,梯度爆炸可能导致网络不稳定,最多无法从训练数据中学习,最坏的情况是导致无法再更新的 NaN 权重值。

爆炸性的梯度会使学习变得不稳定。

在循环神经网络中,爆炸性梯度可能导致不稳定的网络无法从训练数据中学习,并且最多是无法通过长输入数据序列学习的网络。

......梯度爆炸问题是指训练期间梯度范数的大幅增加。这些事件是由于长期成分的爆炸造成的

你怎么知道你是否有爆炸的梯度?

有一些微妙的迹象表明您在网络训练期间可能会受到爆炸性梯度的影响,例如:

  • 该模型无法获得您的训练数据(例如损失不佳)。
  • 该模型不稳定,导致从更新到更新的损失发生很大变化。
  • 在训练期间模型损失归 NaN 所有。

如果你有这些类型的问题,你可以深入挖掘,看看你是否有梯度爆炸的问题。

有一些不太微妙的迹象可以用来确认你有爆炸的梯度。

  • 在训练期间,模型权重很快变得非常大。
  • 模型权重在训练期间达到 NaN 值。
  • 在训练期间,每个节点和层的误差梯度值始终高于 1.0。

如何修复梯度爆炸?

解决梯度爆炸的方法很多;本节列出了一些您可以使用的最佳实践方法。

1.重新设计网络模型

在深度神经网络中,可以通过重新设计网络以减少层数来解决梯度爆炸问题。

在训练网络时使用较小的批量大小也可能有一些好处。

在循环神经网络中,在训练期间通过较少的先前时间步骤进行更新,称为通过时间截断反向传播,可以减少梯度爆炸问题。

2.使用长短期记忆网络

在循环神经网络中,考虑到这种类型网络的训练中固有的不稳定性,例如,可以发生梯度爆炸。通过反向传播到时间,基本上将循环网络转换为深层多层感知机神经网络。

通过使用长短期记忆(LSTM)记忆单元和可能相关的门控型神经元结构,可以减少梯度爆炸。

采用 LSTM 存储器单元是用于序列预测的循环神经网络的新的最佳实践。

3.使用梯度剪辑

在具有大批量大小和具有非常长输入序列长度的 LSTM 的非常深的多层感知机网络中仍然可能发生梯度爆炸。

如果仍然出现梯度爆炸,您可以在网络训练期间检查并限制梯度的大小。

这称为梯度剪裁。

处理梯度爆炸有一个简单但非常有效的解决方案:如果它们的范数超过给定阈值,则剪切梯度。

具体地,如果误差梯度超过阈值,则针对阈值检查误差梯度的值并将其剪切或设置为该阈值。

在某种程度上,可以通过梯度限幅(在执行梯度下降步骤之前对梯度的值进行阈值处理)来减轻梯度爆炸问题。

在 Keras 深度学习库中,您可以通过在训练之前在优化器上设置clipnormclipvalue参数来使用梯度剪辑。

好的默认值是 clipnorm = 1.0clipvalue = 0.5

4.使用重量正规化

另一种方法,如果仍然出现梯度爆炸,则检查网络权重的大小,并对网络损失函数应用较大权重值的惩罚。

这被称为权重正则化,并且通常可以使用 L1(绝对权重)或 L2(平方权重)惩罚。

对复发权重使用 L1 或 L2 惩罚可以帮助梯度爆炸

On the difficulty of training recurrent neural networks, 2013.

在 Keras 深度学习库中,您可以通过在层上设置kernel_regularizer参数并使用L1L2正则化器来使用权重正则化。

进一步阅读

如果您希望深入了解,本节将提供有关该主题的更多资源。

图书

文件

用品

Keras API

摘要

在这篇文章中,您发现了在训练深度神经网络模型时梯度爆炸的问题。

具体来说,你学到了:

  • 爆炸的梯度是什么以及它们在训练过程中引起的问题。
  • 如何知道您的网络模型是否有梯度爆炸。
  • 如何解决网络中的梯度爆炸问题。

你有任何问题吗? 在下面的评论中提出您的问题,我会尽力回答。

沿时间反向传播的温和介绍

原文: machinelearningmastery.com/gentle-introduction-backpropagation-time/

Backpropagation Through Time 或 BPTT 是用于更新 LSTM 等循环神经网络中权重的训练算法。

为了有效地构建循环神经网络的序列预测问题,您必须对通过时间的反向传播正在做什么以及在训练网络时如何通过时间截断反向传播等可配置变量将影响技能,稳定性和速度有一个强有力的概念性理解。帖子,你会得到一个温和的介绍 Backpropagation 通过时间打算为从业者(没有方程!)。

在这篇文章中,您将获得针对从业者的 Backpropagation Through Time 的温和介绍(无方程!)。

阅读这篇文章后,你会知道:

  • 什么反向传播是时间以及它如何与多层感知机网络使用的反向传播训练算法相关。
  • 导致需要通过时间截断反向传播的动机,这是用于训练 LSTM 的深度学习中最广泛使用的变体。
  • 考虑如何配置截断反向传播时间以及研究和深度学习库中使用的规范配置的符号。

让我们开始吧。

A Gentle Introduction to Backpropagation Through Time

时间反向传播的温和介绍 Jocelyn Kinghorn 的照片,保留一些权利。

反向传播训练算法

反向传播指的是两件事:

  • 用于计算导数的数学方法和衍生链规则的应用。
  • 用于更新网络权重以最小化错误的训练算法。

我们在这里使用的是后一种对反向传播的理解。

反向传播训练算法的目标是修改神经网络的权重,以便与响应于相应输入的某些预期输出相比最小化网络输出的误差。

它是一种监督学习算法,允许根据所产生的特定错误纠正网络。

一般算法如下:

  1. 呈现训练输入模式并通过网络传播以获得输出。
  2. 将预测输出与预期输出进行比较并计算误差。
  3. 计算相对于网络权重的误差的导数。
  4. 调整权重以最小化错误。
  5. 重复。

有关 Backpropagation 的更多信息,请参阅帖子:

反向传播训练算法适用于训练固定大小的输入 - 输出对上的前馈神经网络,但是可能在时间上排序的序列数据呢?

通过时间反向传播

Backpropagation Through Time,或 BPTT,是将 Backpropagation 训练算法应用于应用于序列数据的循环神经网络,如时间序列。

循环神经网络每步显示一​​个输入并预测一个输出。

从概念上讲,BPTT 的工作方式是展开所有输入时间步长。每个时间步长有一个输入时间步长,一个网络副本和一个输出。然后计算每个时间步长的误差并累积。网络将回滚并更新权重。

在空间上,在给定问题的顺序依赖性的情况下,展开的循环神经网络的每个时间步长可被视为附加层,并且来自先前时间步的内部状态被视为随后时间步的输入。

我们可以总结算法如下:

  1. 向网络呈现输入和输出对的一系列时间步长。
  2. 展开网络,然后计算并累积每个时间步长的错误。
  3. 汇总网络并更新权重。
  4. 重复。

随着时间步长的增加,BPTT 在计算上可能是昂贵的。

如果输入序列由数千个时间步长组成,则这将是单个更新权重更新所需的导数的数量。这可能导致重量消失或爆炸(变为零或溢出)并使慢学习和模型技能嘈杂。

通过时间截断反向传播

截断反向传播通过时间,或 TBPTT,是用于循环神经网络的 BPTT 训练算法的修改版本,其中序列一次一步地处理并且周期性地(k1 时间步长)BPTT 更新被执行回固定数量的时间步长( k2 时间步长)。

Ilya Sutskever 在他的论文中明确指出:

截断反向传播可以说是训练 RNN 最实用的方法。

...

BPTT 的一个主要问题是单个参数更新的高成本,这使得无法使用大量迭代。

使用简单的方法可以降低成本,该方法将 1,000 个长序列分成 50 个序列(比方说),每个序列长度为 20,并将每个长度为 20 的序列视为单独的训练案例。这是一种合理的方法,可以在实践中很好地工作,但它对于跨越 20 多个步骤的时间依赖性是盲目的。

截断 BPTT 是一种密切相关的方法。它一次处理一个序列的序列,并且每 k1 个时间步,它运行 BPTT 达 k2 个时间步,因此如果 k2 很小,参数更新可以很便宜。因此,它的隐藏状态已暴露于许多时间步,因此可能包含有关远期过去的有用信息,这些信息将被机会性地利用。

We can summarize the algorithm as follows:

  1. 向网络呈现输入和输出对的 k1 个时间步长序列。
  2. 展开网络然后计算并累积 k2 个时间步长的错误。
  3. 汇总网络并更新权重。
  4. 重复

TBPTT 算法需要考虑两个参数:

  • k1 :更新之间的正向通过时间步数。通常,考虑到重量更新的执行频率,这会影响训练的速度或速度。
  • k2 :应用 BPTT 的时间步数。通常,它应该足够大以捕获问题中的时间结构以供网络学习。值太大会导致梯度消失。

为了更清楚:

...可以使用有界历史近似值,其中相关信息被保存了固定数量的时间步长,并且忘记了任何早于此的信息。通常,这应该被视为用于简化计算的启发式技术,但是,如下所述,它有时可以充当真实梯度的适当近似值,并且在权重被调整为网络的情况下也可能更合适。运行。让我们称这个算法在时间上被截断反向传播。如果 h 表示保存的先前时间步数,则该算法将表示为 BPTT(h)。

注意,在 BPTT(h)中,每次网络运行另外的时间步骤时,重新执行最近的 h 时间步骤的向后传递。为了概括这一点,可以考虑在执行下一个 BPTT 计算之前让网络运行 h0 个额外的时间步骤,其中 h0 <= h。

该算法的关键特征是直到时间步 t + h0 才执行下一个后向传递。在中间时间内,网络输入,网络状态和目标值的历史记录保存在历史缓冲区中,但不对该数据进行处理。我们用这个算法表示 BPTT(h; h0)。显然,BPTT(h)与 BPTT(h; 1)相同,BPTT(h; h)是符合消息的 BPTT 算法。

我们可以借用 Williams 和 Peng 的符号,并将不同的 TBPTT 配置称为 TBPTT(k1,k2)。

使用这种表示法,我们可以定义一些标准或常用方法:

注意,这里 n 指的是序列中的总时间步数:

  • TBPTT(n,n):在序列的所有时间步长(例如经典 BPTT)的序列末尾进行更新。
  • TBPTT(1,n):一次处理一个时间步,然后进行更新,覆盖到目前为止所见的所有时间步长(例如 Williams 和 Peng 的经典 TBPTT)。
  • TBPTT(k1,1):网络可能没有足够的时间背景来学习,严重依赖内部状态和输入。
  • TBPTT(k1,k2),其中 k1&lt; k2&lt; n :每个序列执行多次更新,这可以加速训练。
  • TBPTT(k1,k2),其中 k1 = k2 :一种常见配置,其中固定数量的时间步长用于前向和后向时间步(例如 10s 至 100s)。

我们可以看到所有配置都是 TBPTT(n,n)的变体,它基本上试图用可能更快的训练和更稳定的结果来近似相同的效果。

论文中报道的规范 TBPTT 可以被认为是 TBPTT(k1,k2),其中 k1 = k2 = h 并且 h <= n,并且其中所选择的参数小(几十到几百步)。

在像 TensorFlow 和 Keras 这样的库中,事物看起来很相似,并且 h 定义了准备好的数据的时间步长的向量化固定长度。

进一步阅读

本节提供了一些进一步阅读的资源。

图书

文件

用品

摘要

在这篇文章中,您发现了 Backpropagation Through Time 用于训练复现神经网络。

具体来说,你学到了:

  • 什么反向传播是时间以及它如何与多层感知机网络使用的反向传播训练算法相关。
  • 导致需要通过时间截断反向传播的动机,这是用于训练 LSTM 的深度学习中最广泛使用的变体。
  • 考虑如何配置截断反向传播时间以及研究和深度学习库中使用的规范配置的符号。

您对通过时间的反向传播有任何疑问吗? 在下面的评论中提出您的问题,我会尽力回答。

生成式长短期记忆网络的温和介绍

原文: machinelearningmastery.com/gentle-introduction-generative-long-short-term-memory-networks/

开发了用于序列预测的长短期记忆循环神经网络。

除了序列预测问题。 LSTM 也可以用作生成模型

在这篇文章中,您将了解 LSTM 如何用作生成模型。

完成这篇文章后,你会知道:

  • 关于生成模型,重点关注称为语言建模的文本的生成模型。
  • 使用 LSTM Generative 模型的应用示例。
  • 如何使用 LSTM 为生成模型建模文本的示例。

让我们开始吧。

Gentle Introduction to Generative Long Short-Term Memory Networks

生成长短期记忆网络的温和介绍 Fraser Mummery 的照片,保留一些权利。

生成模型

LSTM 可以用作生成模型。

给定大量序列数据,例如文本文档,可以设计 LSTM 模型以学习语料库的一般结构属性,并且当给定种子输入时,可以生成代表原始语料库的新序列。

开发用于概括文本语料库的模型的问题在自然语言处理领域被称为语言建模。语言模型可以在单词级别工作并学习文档中单词之间的概率关系,以便准确地完成句子并生成全新的句子。在最具挑战性的语言模型中,语言模型在角色级别工作,从角色序列中学习,并一次生成一个角色的新序列。

字符级语言建模的目标是预测序列中的下一个字符。

虽然更具挑战性,但字符级模型的附加灵活性允许生成新单词,添加标点符号以及生成文本数据中可能存在的任何其他结构。

...从序列生成的角度来看,一次预测一个字符更有趣,因为它允许网络发明新颖的单词和字符串。

语言建模是迄今为止生成 LSTM 最常研究的应用,可能是因为使用了可以量化和比较模型表现的标准数据集。这种方法已用于生成一系列有趣的语言建模问题的文本,例如:

  • 生成维基百科文章(包括标记)。
  • 生成莎士比亚等伟大作家的片段。
  • 生成技术手稿(包括标记)。
  • 生成计算机源代码。
  • 生成文章标题。

结果的质量各不相同;例如,标记或源代码可能需要手动干预才能呈现或编译。然而,结果令人印象深刻。

该方法还应用于不同的域,其中可获得大量现有序列信息,并且可以一次一步地生成新序列,例如:

  • 手写代。
  • 音乐一代。
  • 语音生成。

Example of LSTMs used in Automatic Handwriting Generation

用于自动手写生成的 LSTM 的示例。 取自“生成序列与循环神经网络”,2014 年。

生成 LSTM

生成型 LSTM 实际上不是架构,它更多地是关于 LSTM 预测模型学习内容以及模型如何使用的观点的变化。

我们可以想象使用任何 LSTM 架构作为生成模型。在这种情况下,我们将使用简单的 Vanilla LSTM。

Vanilla LSTM Architecture for Generative Models

用于生成模型的 Vanilla LSTM 架构

在字符级语言模型的情况下,所有可能字符的字母表是固定的。单热编码用于学习输入序列和预测输出序列。

使用一对一模型,其中针对每个输入时间步骤预测一步。这意味着输入序列可能需要专门处理以便被向量化或格式化以有效地训练监督模型。

例如,给定顺序:

"hello world"

需要构建数据集,例如:

'h' => 'e'
'e' => 'l'
'l' => 'l'
...

这可以按原样呈现为一个时间步骤样本的数据集,这可能对网络非常有限(例如,没有 BPTT)。

或者,它可以被向量化为固定长度的输入序列,用于多对一时间步长模型,例如:

['h', 'e', 'l'] => 'l'
['e', 'l', 'l'] => 'o'
['l', 'l', 'o'] => ' '
...

或者,一对多时间步长模型的固定长度输出序列:

'h' => ['e', 'l', 'l']
'e' => ['l', 'l', 'o']
'l' => ['l', 'o', ' ']
...

或者这些方法的一些变化。

注意,在做出预测时将需要相同的向量化表示,这意味着需要将预测的字符作为后续样本的输入来呈现。这在实现中可能非常笨拙。

网络的内部状态可能需要仔细管理,可能在输入序列中的选择位置(例如段落,页面或章节的末尾)而不是在每个输入序列的末尾重置。

进一步阅读

如果您要深入了解,本节将提供有关该主题的更多资源。

文件

帖子

摘要

在这篇文章中,您发现了使用 LSTM 作为生成模型。

具体来说,你学到了:

  • 关于生成模型,重点关注称为语言建模的文本的生成模型。
  • 使用 LSTM Generative 模型的应用示例。
  • 如何使用 LSTM 为生成模型建模文本的示例。

你有任何问题吗? 在下面的评论中提出您的问题,我会尽力回答。

专家对长短期记忆网络的简要介绍

原文: machinelearningmastery.com/gentle-introduction-long-short-term-memory-networks-experts/

长短期记忆(LSTM)网络是一种循环神经网络,能够学习序列预测问题中的顺序依赖性。

这是复杂问题域中所需的行为,如机器翻译,语音识别等。

LSTM 是一个复杂的深度学习领域。很难掌握 LSTM 是什么,以及双向和序列到序列等术语如何与场相关。

在这篇文章中,您将使用开发方法并将其应用于新的重要问题的研究科学家的话来深入了解 LSTM。

很少有人比制定它们的专家更清楚,更准确地阐明 LSTM 的承诺及其工作方式。

我们将使用专家的报价来探索 LSTM 领域的关键问题,如果您有兴趣,您将能够深入研究报价的原始论文。

A Gentle Introduction to Long Short-Term Memory Networks by the Experts

专家对长期短期记忆网络的简要介绍 Oran Viriyincy 的照片,保留一些权利。

循环神经网络的承诺

循环神经网络不同于传统的前馈神经网络。

增加复杂性的这种差异伴随着传统方法无法实现的新行为的承诺。

循环网络......具有可以表示上下文信息的内部状态。 ...... [他们]保留过去输入的信息一段时间不是先验固定的,而是取决于其权重和输入数据。

...

其输入不固定但构成输入序列的循环网络可用于将输入序列变换为输出序列,同时以灵活的方式考虑上下文信息。

本文定义了循环神经网络的 3 个基本要求:

  • 系统能够存储任意持续时间的信息。
  • 系统抵抗噪声(即输入的波动是随机的或与预测正确输出无关)。
  • 系统参数可以训练(在合理的时间内)。

本文还描述了用于演示循环神经网络的“最小任务”。

语境是关键。

循环神经网络在做出预测时必须使用上下文,但在这种程度上,还必须学习所需的上下文。

...循环神经网络包含循环,这些循环将来自前一时间步的网络激活作为网络的输入,以影响当前时间步的预测。这些激活存储在网络的内部状态中,其原则上可以保存长期时间上下文信息。该机制允许 RNN 在输入序列历史上利用动态变化的上下文窗口

LSTM 兑现承诺

LSTM 的成功在于它们声称是首批克服技术问题并实现循环神经网络承诺的工具之一。

因此,在相关输入事件和目标信号之间存在大于 5-10 个离散时间步长的情况下,标准 RNN 无法学习。消失的错误问题使人们怀疑标准 RNN 是否确实能够在基于时间窗口的前馈网络上表现出显着的实际优势。最近的模型“长期短期记忆”(LSTM)不受此问题的影响。 LSTM 可以通过在特殊单元(称为单元格)内通过“恒定误差转盘”(CEC)强制执行恒定误差来学习跨越超过 1000 个离散时间步长的最小时间滞后

LSTM 克服的两个技术问题是消失的梯度和爆炸的梯度,这两者都与网络的训练方式有关。

不幸的是,标准 RNN 可以访问的上下文信息的范围在实践中非常有限。问题在于,给定输入对隐藏层的影响,以及因此对网络输出的影响,当它围绕网络的循环连接循环时,会以指数方式衰减或爆炸。这个缺点......在文献中被称为消失梯度问题......长短期记忆(LSTM)是一种 RNN 架构,专门用于解决消失的梯度问题。

LSTM 解决技术问题的关键是模型中使用的单元的特定内部结构。

......受其处理消失和梯度爆炸的能力的支配,这是设计和训练 RNN 的最常见挑战。为了应对这一挑战,引入了一种特殊形式的经常性网络,称为 LSTM,并在翻译和序列生成方面取得了巨大成功。

LSTM 如何工作?

类比是一个有用的工具,可以快速掌握它们的工作方式,而不是进入控制 LSTM 如何拟合的方程式。

我们使用具有一个输入层,一个隐藏层和一个输出层的网络......(完全)自连接隐藏层包含存储器单元和相应的门单元......

每个存储器单元的内部架构保证在其恒定误差转盘 CEC 内的恒定误差...这代表了弥合很长时间滞后的基础。两个门单元学习在每个存储器单元的 CEC 内打开和关闭对错误的访问。乘法输入门保护 CEC 免受无关输入的扰动。同样,乘法输出门保护其他单元免受当前不相关的存储器内容的扰动。

多个类比可以帮助购买 LSTM 与简单神经元组成的传统神经网络的区别。

长短期记忆架构的动机是对现有 RNN 中的错误流进行分析,发现现有架构无法获得长时间滞后,因为反向传播的错误会以指数方式爆炸或衰减。

LSTM 层由一组循环连接的块组成,称为内存块。这些块可以被认为是数字计算机中存储芯片的可区分版本。每个包含一个或多个循环连接的存储器单元和三个乘法单元 - 输入,输出和忘记门 - 为单元提供连续的写,读和复位操作模拟。 ......网只能通过门与细胞相互作用。

— Alex Graves, et al., Framewise Phoneme Classification with Bidirectional LSTM and Other Neural Network Architectures, 2005.

有趣的是,即使经过 20 多年,简单(或香草)LSTM 仍然是应用该技术时最好的起点。

最常用的 LSTM 架构(vanilla LSTM)在各种数据集上表现相当不错......

学习率和网络规模是最关键的可调 LSTM 超参数...

...这意味着超参数可以独立调整。特别是,可以使用相当小的网络首先校准学习率,从而节省大量的实验时间。

什么是 LSTM 应用程序?

掌握 LSTM 适合解决的确切类型的序列学习问题非常重要。

长期短期记忆(LSTM)可以解决以前的循环神经网络(RNN)学习算法无法解决的大量任务。

... LSTM 承诺任何顺序处理任务,我们怀疑可能存在层次分解,但事先并不知道这种分解是什么。

— Felix A. Gers, et al., Learning to Forget: Continual Prediction with LSTM, 2000

循环神经网络(RNN)是神经序列模型,其在包括语言建模,语音识别和机器翻译的重要任务上实现最先进的表现。

由于 LSTM 在捕获长期时间依赖性方面是有效的,而不会遇到困扰简单复发网络(SRN)的优化障碍,因此它们已被用于推进许多难题的现有技术水平。这包括手写识别和生成,语言建模和翻译,语音声学建模,语音合成,蛋白质二级结构预测,音频分析和视频数据等。

— Klaus Greff, et al., LSTM: A Search Space Odyssey, 2015

什么是双向 LSTM?

LSTM 的一个常见改进是双向 LSTM。

双向循环神经网络的基本思想是将每个训练序列向前和向后呈现给两个单独的循环网络,这两个网络都连接到相同的输出层。 ......这意味着对于给定序列中的每个点,BRNN 都有关于它之前和之后所有点的完整,顺序信息。此外,由于网络可根据需要自由使用此上下文,因此无需查找(任务相关的)时间窗口或目标延迟大小。

......对于像语音识别这样的时间问题,依赖于对未来的了解似乎乍看之下就是违反因果关系...我们如何能够根据我们对尚未说过的内容所听到的内容进行理解?然而,人类听众正是这样做的。根据未来的背景,听起来,单词甚至整个句子最初都意味着没有任何意义。

— Alex Graves, et al., Framewise Phoneme Classification with Bidirectional LSTM and Other Neural Network Architectures, 2005.

传统 RNN 的一个缺点是它们只能利用先前的上下文。 ...双向 RNN(BRNN)通过使用两个单独的隐藏层处理两个方向上的数据来完成此操作,然后将这些隐藏层转发到同一输出层。 ...将 BRNN 与 LSTM 相结合,可以提供双向 LSTM,可以在两个输入方向上访问远程上下文

与传统的 RNN 不同,双向 RNN 通过使用两个单独的隐藏层处理来自两个方向的数据来利用先前和未来的上下文。一层处理正向输入序列,而另一层处理反向输入。然后通过组合两个层的隐藏向量来生成当前时间步长的输出...

  • Di Wang 和 Eric Nyberg, 问题回答,2015 年答案句选择的长短期记忆模型

什么是 seq2seq LSTM 或 RNN 编解码器?

序列到序列 LSTM,也称为编解码器 LSTM,是 LSTM 的应用,由于其令人印象深刻的能力而受到很多关注。

......长期短期记忆(LSTM)架构的直接应用可以解决序列问题的一般顺序。

想法是使用一个 LSTM 来读取输入序列,一次一个步骤,以获得大的固定维向量表示,然后使用另一个 LSTM 从该向量中提取输出序列。第二个 LSTM 本质上是一个循环神经网络语言模型,除了它以输入序列为条件。

LSTM 成功学习具有长距离时间依赖性的数据的能力使其成为该应用的自然选择,因为输入与其相应输出之间存在相当大的时间滞后。

我们能够在长句子上做得好,因为我们颠倒了源句中的单词顺序,而不是训练和测试集中的目标句子。通过这样做,我们引入了许多短期依赖关系,使优化问题更加简单。 ......扭转源句中单词的简单技巧是这项工作的关键技术贡献之一

“编码器”RNN 读取源句子并将其转换为富的固定长度向量表示,其又用作生成目标句子的“解码器”RNN 的初始隐藏状态。在这里,我们建议遵循这个优雅的秘籍,用深度卷积神经网络(CNN)代替编码器 RNN。 ...使用 CNN 作为图像“编码器”是很自然的,首先将其预训练用于图像分类任务,并使用最后隐藏层作为生成句子的 RNN 解码器的输入。

... RNN 编解码器,由两个循环神经网络(RNN)组成,它们充当编码器和解码器对。编码器将可变长度源序列映射到固定长度向量,并且解码器将向量表示映射回可变长度目标序列。

摘要

在这篇文章中,您通过开发和应用这些技术的研究科学家的话来温和地介绍了 LSTM。

这为您提供了关于 LSTM 是什么以及它们如何工作的清晰而准确的概念,以及关于 LSTM 在循环神经网络领域的承诺的重要阐述。

引用是否有助于您理解或激励您? 请在下面的评论中告诉我。

在序列预测问题上充分利用 LSTM

原文: machinelearningmastery.com/get-the-most-out-of-lstms/

长短期记忆(LSTM)循环神经网络是一种强大的深度学习类型,适用于序列预测问题。

使用 LSTM 时可能存在的一个问题是,模型增加的复杂性是否会提高模型的技能,或者实际上导致技能低于简单模型。

在这篇文章中,您将发现可以运行的简单实验,以确保您在序列预测问题上充分利用 LSTM。

阅读这篇文章后,你会知道:

  • 如何测试模型是否在输入数据中利用顺序依赖性。
  • 如何测试您的模型是否在 LSTM 模型中利用内存。
  • 如何在拟合模型时测试模型是否正在利用 BPTT。

让我们潜入。

Get the Most out of LSTMs on Your Sequence Prediction Problem

在你的序列预测问题上充分利用 LSTM DoD News 的照片,保留一些权利。

3 LSTMS 的能力

LSTM 循环神经网络具有一些关键功能,使该方法在广泛的序列预测问题上具有令人印象深刻的能力。

在不深入研究 LSTM 理论的情况下,我们可以总结一下我们可以在模型中配置的 LSTM 的一些离散行为:

  • 顺序依赖。序列预测问题需要观察之间的排序,而较简单的监督学习问题则不需要,并且该顺序可以在训练和预测之前随机化。通过随机化观察的顺序,可以将序列预测问题转换为更简单的形式。
  • 记忆。 LSTM 在输入序列中具有跨观察的内部存储器,而诸如多层感知机之类的简单神经网络则没有。通过在每次输入观察后重置内部状态,LSTM 可能会丢失此内存。
  • BPTT 。循环神经网络使用训练算法来估计输入序列的所有时间步长上的权重更新的方向,而其他类型的网络仅限于单个样本(在两种情况下不包括跨批次输入的平均)。 LSTM 可以通过处理一个长度的观察序列来忽略梯度估计中先前时间步的误差贡献。

这三种功能及其对更简单形式的可配置性为您可以执行的 3 个实验提供了基础,可以准确了解 LSTM 的哪些属性,并且可以利用序列预测问题。

你在利用秩序依赖吗?

序列预测问题的一个关键特征是观察之间存在顺序依赖性。

也就是说,观察的顺序很重要。

假设:预计观察顺序对于预测序列预测问题很重要。

您可以通过使用仅将先前观察作为输入并对训练和测试数据集进行混洗的模型来开发表现基线来检查该假设是否成立。

这可以通过多种方式实现。两个示例实现包括:

  • 具有改组训练和测试装置的多层感知机(MLP)。
  • LSTM 具有改组训练和测试集,每个样本后更新和状态重置(批量大小为 1)。

测试:如果顺序依赖对预测问题很重要,那么利用每个输入序列中的观察与输入序列之间的顺序的模型应该比没有预测问题的模型实现更好的表现。

你在利用 LSTM 内存吗?

LSTM 的一个关键功能是它们可以记住长输入序列。

也就是说,每个存储器单元保持内部状态,该内部状态可以被认为是在做出预测时使用的局部变量。

假设:模型的内部状态预计对模型技能很重要。

您可以通过使用从一个样本到下一个样本没有内存的模型开发表现基线来检查这种假设是否成立。

这可以通过在每次观察之后重置 LSTM 的内部状态来实现。

测试:如果内部存储器对预测问题很重要,那么在输入序列中具有跨越观察结果的存储器的模型应该比不存在的模型具有更好的表现。

你是否正在利用反向传播?

训练循环神经网络的关键是反向传播时间(BPTT)算法。

该算法允许从序列中的所有观察(或截断的 BPTT 的情况下的子集)估计权重更新的梯度。

假设:预计 BPTT 权重更新算法对于序列预测问题的模型技能很重要。

您可以通过开发表现基线来检查此假设是否成立,其中梯度估计基于单个时间步长。

这可以通过分割输入序列来实现,使得每个观察表示单个输入序列。这与调度重量更新和重置内部状态的时间无关。

测试:如果 BPTT 对预测问题很重要,那么估算多个时间步长的权重更新梯度的模型应该比使用单个时间步长的模型获得更好的表现。

摘要

在这篇文章中,您发现了 LSTM 的三个关键功能,这些功能为该技术提供了强大的功能,以及如何根据您自己的序列预测问题测试这些属性。

特别:

  • 如何测试模型是否在输入数据中利用顺序依赖性。
  • 如何测试您的模型是否在 LSTM 模型中利用内存。
  • 如何在拟合模型时测试模型是否正在利用 BPTT。

你有任何问题吗? 将您的问题发布到下面的评论中,我会尽力回答。

编解码器循环神经网络的全局注意力的温和介绍

原文: machinelearningmastery.com/global-attention-for-encoder-decoder-recurrent-neural-networks/

编解码器模型提供了使用循环神经网络来解决具有挑战性的序列到序列预测问题(例如机器翻译)的模式。

注意力是编解码器模型的扩展,其改进了较长序列的方法的表现。全球关注是一种简化的注意力,可能更容易在像 Keras 这样的声明式深度学习库中实现,并且可能比传统的注意机制获得更好的结果。

在这篇文章中,您将发现编解码器循环神经网络模型的全局关注机制。

阅读这篇文章后,你会知道:

  • 用于序列到序列预测问题的编解码器模型,例如机器翻译。
  • 提高编解码器模型在长序列上的表现的注意机制。
  • 全球关注机制简化了注意机制,可以取得更好的效果。

让我们开始吧。

Gentle Introduction to Global Attention for Encoder-Decoder Recurrent Neural Networks

编解码器循环神经网络全球注意力的温和介绍 Kathleen Tyler Conklin 的照片,保留一些权利。

概观

本教程分为 4 个部分;他们是:

  1. 编解码器模型
  2. 注意
  3. 全球关注
  4. 全球关注更多细节

编解码器模型

编解码器模型是组织循环神经网络以解决序列到序列预测问题的一种方式,其中输入和输出时间步骤的数量不同。

该模型是针对机器翻译问题而开发的,例如将法语翻译成英语。

该模型涉及两个子模型,如下:

  • 编码器:一种 RNN 模型,它将整个源序列读取为固定长度编码。
  • 解码器:使用编码输入序列并对其进行解码以输出目标序列的 RNN 模型。

下图显示了编码器和解码器模型之间的关系。

Example of an Encoder-Decode Network

编码器 - 解码网络 的示例,取自“使用神经网络的序列到序列学习”,2014。

长短期记忆复现神经网络通常用于编码器和解码器。描述源序列的编码器输出用于开始解码过程,以到目前为止已经作为输出生成的字为条件。具体地,用于输入的最后时间步长的编码器的隐藏状态用于初始化解码器的状态。

LSTM 通过首先获得由 LSTM 的最后隐藏状态给出的输入序列(x1,...,xT)的固定维度表示 v,然后计算 y1,...,yT'的概率来计算该条件概率。标准 LSTM-LM 公式,其初始隐藏状态设置为 x1,...,xT 的表示 v

下图显示了源序列对上下文向量 c 的显式编码,该上下文向量 c 与目前生成的字一起使用以输出目标序列中的下一个字。

Encoding of Source Sequence to a Context Vector Which is Then Decoded

将源序列编码到随后被解码的上下文向量 取自“使用 RNN 编解码器进行统计机器翻译的学习短语表示”,2014。

但是,yt 和 h(t)也都以 yt-1 和输入序列的汇总 c 为条件。

注意

编解码器模型被证明是端到端模型,其在有挑战性的序列到序列预测问题(例如机器翻译)上表现良好。

该模型似乎限于很长的序列。其原因被认为是源序列的固定长度编码。

这种编解码器方法的潜在问题是神经网络需要能够将源句子的所有必要信息压缩成固定长度的向量。这可能使神经网络难以处理长句,特别是那些比训练语料库中的句子长的句子。

在 2015 年题为“_ 神经机器翻译通过联合学习对齐和翻译 _”的论文中,“Bahdanau,et al。描述了解决这个问题的注意机制。

注意力是一种机制,其提供源序列的更丰富的编码,从该源序列构造可由解码器使用的上下文向量。

注意允许模型了解源序列中的哪些编码单词要注意以及在预测目标序列中的每个单词期间的程度。

Example of the Encoder-Decoder model with Attention

具有注意力的编解码器模型示例 取自“通过联合学习对齐和翻译的神经机器翻译”,2015。

从编码器收集每个输入时间步的隐藏状态,而不是源序列的最后时间步的隐藏状态。

为目标序列中的每个输出字专门构建上下文向量。首先,使用神经网络对来自编码器的每个隐藏状态进行评分,然后归一化为编码器隐藏状态的概率。最后,概率用于计算编码器隐藏状态的加权和,以提供要在解码器中使用的上下文向量。

有关 Bahdanau 注意力如何与工作示例一起使用的更全面解释,请参阅帖子:

全球关注

在他们的论文“基于注意力的神经机器翻译的有效途径”,“斯坦福 NLP 研究人员 Minh-Thang Luong ,等。提出了一种用于机器翻译的编解码器模型的注意机制,称为“全球关注”。

它被提议作为 Bahdanau 等人提出的注意机制的简化。在他们的论文“通过联合学习对齐和翻译神经机器翻译。”在 Bahdanau 注意,注意力计算需要从前一时间步骤输出解码器。

另一方面,全局注意力仅使用编码器和解码器的输出用于当前时间步长。这使得在诸如 Keras 的向量化库中实现它具有吸引力。

......我们的计算路径更简单;我们从 ht - &gt; at - &gt; ct - &gt; 〜然后做出预测[...]另一方面,在任何时间 t,Bahdanau 等人。 (2015)从先前的隐藏状态构建 ht-1 - &gt; at - &gt; ct - &gt; ht,反过来,在做出预测之前,会经历深度输出和最大值。

该模型在 Luong 等人的评估中进行了评估。论文与 Bahdanau 等人提出的论文不同。 (例如,反向输入序列而不是双向输入,LSTM 而不是 GRU 元素和使用丢失),然而,具有全局关注的模型的结果在标准机器翻译任务上获得了更好的结果。

......全球关注方法显着提升了+2.8 BLEU,使我们的模型略好于 Bahdanau 等人的基础注意系统。

Effective Approaches to Attention-based Neural Machine Translation, 2015.

接下来,让我们仔细看看如何计算全球关注度。

全球关注更多细节

全球关注是循环神经网络的注意编解码器模型的扩展。

虽然是为机器翻译而开发的,但它与其他语言生成任务相关,例如字幕生成和文本摘要,甚至是序列预测任务。

我们可以将全局关注的计算划分为以下计算步骤,用于编解码器网络,其预测给定输入序列的一个时间步长。请参阅论文了解相关方程式。

  • 问题。输入序列作为编码器(X)的输入提供。
  • 编码。编码器 RNN 对输入序列进行编码并输出相同长度(hs)的序列。
  • 解码。解码器解释编码并输出目标解码(ht)。
  • 对齐。使用目标解码对每个编码的时间步进行评分,然后使用 softmax 函数对得分进行归一化。提出了四种不同的评分函数:
    • dot :目标解码和源编码之间的点积。
    • general :目标解码和加权源编码之间的点积。
    • concat :一种神经网络处理的级联源编码和目标解码。
    • 位置:加权目标解码的 softmax。
  • 上下文向量。通过计算加权和来将对齐权重应用于源编码,以得到上下文向量。
  • 最终解码。使用 tanh 函数连接,加权和传送上下文向量和目标解码。

最终解码通过 softmax 传递,以预测输出词汇表中序列中下一个词的概率。

下图提供了计算全局关注时数据流的高级概念。

Depiction of Global Attention in an Encoder-Decoder Recurrent Neural Network

编解码器循环神经网络中全局关注的描述。 取自“基于注意力的神经机器翻译的有效方法”。

作者评估了所有评分函数,并发现简单的点评分函数似乎表现良好。

值得注意的是,dot 对全球的关注效果很好......

Effective Approaches to Attention-based Neural Machine Translation, 2015.

由于更简单和更多的数据流,全局关注可能是在声明性深度学习库中实现的良好候选者,例如 TensorFlow,Theano 和像 Keras 这样的包装器。

进一步阅读

如果您希望深入了解,本节将提供有关该主题的更多资源。

编解码器

注意

全球关注

摘要

在这篇文章中,您发现了编解码器循环神经网络模型的全局关注机制。

具体来说,你学到了:

  • 用于序列到序列预测问题的编解码器模型,例如机器翻译。
  • 提高编解码器模型在长序列上的表现的注意机制。
  • 全球关注机制简化了注意机制,可以取得更好的效果。

你有任何问题吗? 在下面的评论中提出您的问题,我会尽力回答。

如何利用长短期记忆循环神经网络处理很长的序列

原文: machinelearningmastery.com/handle-long-sequences-long-short-term-memory-recurrent-neural-networks/

长短期记忆或 LSTM 循环神经网络能够学习和记忆长输入序列。

如果您的问题为每个输入都有一个输出,如时间序列预测或文本转换,LSTM 可以很好地工作。但是当你有很长的输入序列而只有一个或几个输出时,LSTM 可能很难使用。

这通常称为序列标记或序列分类。

一些例子包括:

  • 包含数千个单词的文件中的情感分类(自然语言处理)。
  • 数千个时间步骤(医学)的脑电图痕迹的分类。
  • 数千个 DNA 碱基对序列的编码或非编码基因的分类(生物信息学)。

当使用像 LSTM 这样的循环神经网络时,这些所谓的序列分类任务需要特殊处理。

在这篇文章中,您将发现 6 种方法来处理序列分类问题的很长序列。

让我们开始吧。

How to Handle Very Long Sequences with Long Short-Term Memory Recurrent Neural Networks

如何使用长短期记忆循环神经网络处理很长的序列 照片由 Justin Jensen ,保留一些权利。

1.按顺序使用序列

起点是按原样使用长序列数据而不做任何更改。

这可能导致训练时间非常长的问题。

更麻烦的是,尝试在非常长的输入序列上反向传播可能会导致梯度渐渐消失,反过来又会导致难以理解的模型。

对于大型 LSTM 模型,通常在实践中使用 250-500 时间步长的合理限制。

2.截断序列

处理很长序列的常用技术是简单地截断它们。

这可以通过从输入序列的开头或结尾有选择地删除时间步骤来完成。

这将允许您以丢失数据为代价强制序列处于可管理的长度。

截断输入序列的风险是,为了进行准确预测而对模型有价值的数据正在丢失。

3.总结序列

在一些问题域中,可以概括输入序列。

例如,在输入序列是单词的情况下,可以从输入序列中移除高于指定单词频率的所有单词(例如“和”,“该”等)。

这可以被构造为仅将观察结果保持在整个训练数据集中的排名频率高于某个固定值的位置。

总结可以导致将问题集中在输入序列的最显着部分上并且充分减少输入序列的长度。

4.随机采样

较不系统的方法可以是使用随机采样来总结序列。

可以从序列中选择随机时间步骤并将其从序列中移除,以便将它们减少到特定长度。

或者,可以选择随机连续子序列以在期望长度上构建新的采样序列,小心处理域所需的重叠或非重叠。

这种方法可能适用于没有明显方法来系统地减少序列长度的情况。

该方法还可以用作一种数据增强方案,以便从每个输入序列创建许多可能的不同输入序列。当可用的训练数据有限时,这些方法可以提高模型的鲁棒性。

5.使用截断反向传播

不是基于整个序列更新模型,而是可以从最后时间步骤的子集估计梯度。

这被称为通过时间截断反向传播,或简称为 TBPTT。它可以显着加速长序列中 LSTM 等循环神经网络的学习过程。

这将允许所有序列作为输入提供并执行正向传递,但是仅使用最后几十或几百个时间步长来估计梯度并用于权重更新。

LSTM 的一些现代实现允许您指定用于更新的时间步数,将用作输入序列的时间步长分开。例如:

  • Theano 中的“truncate_gradient”参数。

6.使用编解码器架构

您可以使用自编码器来学习长序列的新表示长度,然后使用解码器网络将编码表示解释为所需的输出。

这可能涉及无监督自编码器作为序列的预处理传递,或者用于自然语言翻译的更新近的编解码器 LSTM 样式网络

同样,从非常长的序列中学习可能仍然存在困难,但是更复杂的架构可以提供额外的杠杆或技能,特别是如果与上述一种或多种技术结合使用。

荣誉提及和疯狂的想法

本节列出了一些未经过深思熟虑的其他想法。

  • 探索将输入序列分成多个固定长度的子序列,并训练具有每个子序列的模型作为单独的特征(例如,并行输入序列)。
  • 探索双向 LSTM,其中对中的每个 LSTM 适合输入序列的一半,并且每个层的结果被合并。从 2 缩放到更多以适当地减少子序列的长度。
  • 探索使用序列感知编码方案,投影方法甚至散列,以减少特定于域的方式中的时间步数。

你有自己的疯狂想法吗? 请在评论中告诉我。

进一步阅读

本节列出了一些用于进一步阅读序列分类问题的资源:

摘要

在这篇文章中,您发现了在训练像 LSTM 这样的复现神经网络时如何处理很长的序列。

具体来说,你学到了:

  • 如何使用截断,摘要和随机采样来减少序列长度。
  • 如何调整学习使用截断反向传播的时间。
  • 如何调整网络架构以使用编解码器结构。

你有任何问题吗? 在评论中提出您的问题,我会尽力回答。

如何在 Python 中单热编码序列数据

原文: machinelearningmastery.com/how-to-one-hot-encode-sequence-data-in-python/

机器学习算法不能直接使用分类数据。

必须将分类数据转换为数字。

当您处理序列分类类型问题并计划使用深度学习方法(如长期短期记忆循环神经网络)时,这适用。

在本教程中,您将了解如何将输入或输出序列数据转换为单热编码,以便在 Python 中使用深度学习进行序列分类问题。

完成本教程后,您将了解:

  • 什么是整数编码和单热编码,以及为什么它们在机器学习中是必需的。
  • 如何在 Python 中手动计算整数编码和单热编码。
  • 如何使用 scikit-learn 和 Keras 库在 Python 中自编码序列数据。

让我们开始吧。

How to One Hot Encode Sequence Classification Data in Python

如何在 Python 中使用热编码序列分类数据 照片由 Elias Levy 拍摄,保留一些权利。

教程概述

本教程分为 4 个部分;他们是:

  1. 什么是单热编码?
  2. 手动单热编码
  3. 单热门编码与 scikit-learn
  4. 单热门编码与 Keras

什么是单热编码?

一种热编码是将分类变量表示为二进制向量。

这首先要求将分类值映射到整数值。

然后,每个整数值表示为二进制向量,除了整数的索引外,它都是零值,用 1 标记。

单热编码的工作示例

让我们用一个有效的例子来具体化。

假设我们有一系列标签,其值为“红色”和“绿色”。

我们可以将'red'指定为整数值 0,将'green'指定为整数值 1.只要我们总是将这些数字指定给这些标签,就称为整数编码。一致性很重要,以便我们可以稍后反转编码并从整数值返回标签,例如在做出预测时。

接下来,我们可以创建一个二进制向量来表示每个整数值。对于 2 个可能的整数值,向量的长度为 2。

编码为 0 的“红色”标签将用二进制向量[1,0]表示,其中第零个索引用值 1 标记。反过来,编码为 1 的“绿色”标签将用二进制向量[0,1],其中第一个索引标记为值 1。

如果我们有序列:

'red', 'red', 'green'

我们可以用整数编码来表示它:

0, 0, 1

和热门编码:

[1, 0]
[1, 0]
[0, 1]

为什么要使用单热编码?

一种热编码允许分类数据的表示更具表现力。

许多机器学习算法不能直接使用分类数据。必须将类别转换为数字。这对于分类的输入和输出变量都是必需的。

我们可以直接使用整数编码,在需要的地方重缩放。这可能适用于类别之间存在自然序数关系的问题,反过来又是整数值,例如温度“冷”,“暖”和“热”的标签。

当没有顺序关系并且允许表示依赖于任何这样的关系可能有损于学习解决问题时可能存在问题。一个例子可能是标签'狗'和'猫'

在这些情况下,我们希望为网络提供更具表现力的能力,以便为每个可能的标签值学习类似概率的数字。这有助于使问题更容易让网络建模。当单热编码用于输出变量时,它可以提供比单个标签更细微的预测集。

手动单热编码

在这个例子中,我们假设我们有一个字母字母的示例字符串,但示例序列并未涵盖所有可能的示例。

我们将使用以下字符的输入序列:

hello world

我们假设所有可能输入的范围是小写字符和空格的完整字母表。因此,我们将以此为借口演示如何推出自己的热门编码。

下面列出了完整的示例。

from numpy import argmax
# define input string
data = 'hello world'
print(data)
# define universe of possible input values
alphabet = 'abcdefghijklmnopqrstuvwxyz '
# define a mapping of chars to integers
char_to_int = dict((c, i) for i, c in enumerate(alphabet))
int_to_char = dict((i, c) for i, c in enumerate(alphabet))
# integer encode input data
integer_encoded = [char_to_int[char] for char in data]
print(integer_encoded)
# one hot encode
onehot_encoded = list()
for value in integer_encoded:
	letter = [0 for _ in range(len(alphabet))]
	letter[value] = 1
	onehot_encoded.append(letter)
print(onehot_encoded)
# invert encoding
inverted = int_to_char[argmax(onehot_encoded[0])]
print(inverted)

首先运行该示例打印输入字符串。

从 char 值到整数值创建所有可能输入的映射。然后使用该映射对输入字符串进行编码。我们可以看到输入'h'中的第一个字母编码为 7,或者可能输入值(字母表)数组中的索引 7。

然后将整数编码转换为单热编码。这是一次完成一个整数编码字符。创建 0 值的列表,使用字母表的长度,以便可以表示任何预期的字符。

接下来,特定字符的索引标记为 1.我们可以看到编码为 7 的第一个字母'h'整数由长度为 27 且第 7 个索引标记为 1 的二进制向量表示。

最后,我们反转第一个字母的编码并打印结果。我们通过使用 NumPy argmax()函数定位具有最大值的二进制向量中的索引,然后在字符值的反向查找表中使用整数值来实现此操作。

注意:输出已格式化以便于阅读。

hello world

[7, 4, 11, 11, 14, 26, 22, 14, 17, 11, 3]

[[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

h

现在我们已经看到了如何从零开始编写自己的热编码,让我们看看如何在输入序列完全捕获预期输入值范围的情况下,使用 scikit-learn 库自动执行此映射。

单热门编码与 scikit-learn

在此示例中,我们假设您具有以下 3 个标签的输出序列:

"cold"
"warm"
"hot"

10 个时间步长的示例序列可以是:

cold, cold, warm, cold, hot, hot, warm, cold, warm, hot

这将首先需要整数编码,例如 1,2,3。接下来是一个整数的热编码到具有 3 个值的二进制向量,例如[1,0,0]。

该序列提供序列中每个可能值的至少一个示例。因此,我们可以使用自动方法来定义标签到整数和整数到二进制向量的映射。

在这个例子中,我们将使用 scikit-learn 库中的编码器。具体地, LabelEncoder 创建标签的整数编码, OneHotEncoder 用于创建整数编码值的单热编码。

The complete example is listed below.

from numpy import array
from numpy import argmax
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
# define example
data = ['cold', 'cold', 'warm', 'cold', 'hot', 'hot', 'warm', 'cold', 'warm', 'hot']
values = array(data)
print(values)
# integer encode
label_encoder = LabelEncoder()
integer_encoded = label_encoder.fit_transform(values)
print(integer_encoded)
# binary encode
onehot_encoder = OneHotEncoder(sparse=False)
integer_encoded = integer_encoded.reshape(len(integer_encoded), 1)
onehot_encoded = onehot_encoder.fit_transform(integer_encoded)
print(onehot_encoded)
# invert first example
inverted = label_encoder.inverse_transform([argmax(onehot_encoded[0, :])])
print(inverted)

首先运行该示例将打印标签序列。接下来是标签的整数编码,最后是单热编码。

训练数据包含所有可能示例的集合,因此我们可以依赖整数和单热编码变换来创建标签到编码的完整映射。

默认情况下,OneHotEncoder 类将返回更有效的稀疏编码。这可能不适合某些应用程序,例如与 Keras 深度学习库一起使用。在这种情况下,我们通过设置 sparse = False 参数来禁用稀疏返回类型。

如果我们在这个 3 值热编码中接收到预测,我们可以轻松地将变换反转回原始标签。

首先,我们可以使用 argmax()NumPy 函数来定位具有最大值的列的索引。然后可以将其馈送到 LabelEncoder 以计算反向变换回文本标签。

这在示例的结尾处被证明,其中第单热编码示例的逆变换返回到标签值'cold'。

再次注意,输入的格式是为了便于阅读。

['cold' 'cold' 'warm' 'cold' 'hot' 'hot' 'warm' 'cold' 'warm' 'hot']

[0 0 2 0 1 1 2 0 2 1]

[[ 1\.  0\.  0.]
 [ 1\.  0\.  0.]
 [ 0\.  0\.  1.]
 [ 1\.  0\.  0.]
 [ 0\.  1\.  0.]
 [ 0\.  1\.  0.]
 [ 0\.  0\.  1.]
 [ 1\.  0\.  0.]
 [ 0\.  0\.  1.]
 [ 0\.  1\.  0.]]

['cold']

在下一个示例中,我们将看看如何直接对一个整数值序列进行热编码。

单热门编码与 Keras

您可能有一个已经整数编码的序列。

在进行一些缩放之后,您可以直接使用整数。或者,您可以直接对整数进行热编码。如果整数没有真正的序数关系并且实际上只是标签的占位符,则需要考虑这一点。

Keras 库提供了一个名为 to_categorical()的函数,您可以将其用于单热编码整数数据。

在这个例子中,我们有 4 个整数值[0,1,2,3],我们有以下 10 个数字的输入序列:

data = [1, 3, 2, 0, 3, 2, 2, 1, 0, 1]

序列有一个所有已知值的示例,因此我们可以直接使用 to_categorical()函数。或者,如果序列从 0 开始(从 0 开始)并且不代表所有可能的值,我们可以指定 num_classes 参数 to_categorical(num_classes = 4)

下面列出了此功能的完整示例。

from numpy import array
from numpy import argmax
from keras.utils import to_categorical
# define example
data = [1, 3, 2, 0, 3, 2, 2, 1, 0, 1]
data = array(data)
print(data)
# one hot encode
encoded = to_categorical(data)
print(encoded)
# invert encoding
inverted = argmax(encoded[0])
print(inverted)

首先运行示例定义并打印输入序列。

然后将整数编码为二进制向量并打印。我们可以看到第一个整数值 1 被编码为[0,1,0,0],就像我们期望的那样。

然后,我们通过在序列中的第一个值上使用 NumPy argmax()函数来反转编码,该函数返回第一个整数的期望值 1。

[1 3 2 0 3 2 2 1 0 1]

[[ 0\.  1\.  0\.  0.]
 [ 0\.  0\.  0\.  1.]
 [ 0\.  0\.  1\.  0.]
 [ 1\.  0\.  0\.  0.]
 [ 0\.  0\.  0\.  1.]
 [ 0\.  0\.  1\.  0.]
 [ 0\.  0\.  1\.  0.]
 [ 0\.  1\.  0\.  0.]
 [ 1\.  0\.  0\.  0.]
 [ 0\.  1\.  0\.  0.]]

1

进一步阅读

本节列出了一些可供进一步阅读的资源。

摘要

在本教程中,您了解了如何使用 Python 中的单热编码对分类序列数据进行编码以进行深度学习。

具体来说,你学到了:

  • 什么是整数编码和单热编码,以及为什么它们在机器学习中是必需的。
  • 如何在 Python 中手动计算整数编码和单热编码。
  • 如何使用 scikit-learn 和 Keras 库在 Python 中自编码序列数据。

您对准备序列数据有任何疑问吗? 在评论中提出您的问题,我会尽力回答。