本文代码运行环境:
- cudatoolkit = 10.1.243
- cudnn = 7.6.5
- tensorflow-gpu = 2.1.0
- keras-gpu = 2.3.1
相关文章
LSTM 01:理解LSTM网络及训练方法
LSTM 02:如何为LSTM准备数据
LSTM 03:如何使用Keras编写LSTM
LSTM 04:4种序列预测模型及Keras实现
LSTM 05:Keras实现多层LSTM进行序列预测
LSTM 06:Keras实现CNN-LSTM模型
LSTM 07:Keras实现Encoder-Decoder LSTM
LSTM 08:超详细LSTM调参指南
文章目录
3. How to Prepare Data for LSTM
3.1 准备数值数据
训练神经网络(如 LSTM)时,可能需要缩放序列预测问题的数据。当网络适合具有一系列值(例如10到100秒的数量)的无标度数据时,数值较大的输入可能会降低网络的学习和收敛速度,在某些情况下会阻止网络客观地学习。
针对上述情况,可以考虑两种类型的系列缩放:归一化(Normalize)和标准化(Standardize)。这两个都可以通过使用Python中的sklearn机器学习库来实现。
3.1.1 归一化系列数据(Normalize Series Data)
归一化是对原始范围内的数据进行重新缩放,使所有值都在0和1的范围内。归一化要求知道或能够准确估计可观测值的最小值和最大值。可以根据可用的数据估计这些值。如果序列呈上升或下降趋势,估计这些期望值可能会很困难,并且归一化可能不是解决问题的最佳方法。
如果要缩放的值超出最小值和最大值的范围,则结果值将不在0和1的范围内。可以在进行预测之前检查这些观测值,并将其从数据集中删除,或将其限制为预定义的最大值或最小值。可以使用 sklearn 中的 MinMaxScaler
缩放器规范化数据集。其流程如下:
- 对于归一化,使用可用的训练数据计算最大值和最小值,然后通过
fit()
函数应用。注意是用训练集数据进行缩放,即训练集数据和测试集数据均需要使用训练集上的统计量进行缩放,否则会导致数据泄露问题!
可以对归一化的数据进行逆变换,例如将预测值转换回其原始比例以进行打印或绘制。这可以通过调用 inverse_transform()
函数来完成。下面是一个归一化的示例。scaler
对象要求以行和列的矩阵形式提供数据。加载的时间序列数据作为 Pandas.Series
加载。
from pandas import Series
from sklearn.preprocessing import MinMaxScaler
# define contrived series
data = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0]
series = Series(data)
print(series)
# prepare data for normalization
values = series.values
values = values.reshape((len(values), 1))
# train the normalization
scaler = MinMaxScaler(feature_range=(0, 1))
scaler = scaler.fit(values)
print('Min: %f, Max: %f' % (scaler.data_min_, scaler.data_max_))
# normalize the dataset and print
normalized = scaler.transform(values)
print(normalized)
# inverse transform and print
inversed = scaler.inverse_transform(normalized)
print(inversed)
运行示例将打印序列,打印从序列中估计的最小值和最大值,打印归一化序列,然后使用逆变换将值变换回其原始比例。
0 10.0
1 20.0
2 30.0
3 40.0
4 50.0
5 60.0
6 70.0
7 80.0
8 90.0
9 100.0
Min: 10.000000, Max: 100.000000
[[ 0. ]
[ 0.11111111]
[ 0.22222222]
[ 0.33333333]
[ 0.44444444]
[ 0.55555556]
[ 0.66666667]
[ 0.77777778]
[ 0.88888889]
[ 1. ]]
[[ 10.]
[ 20.]
[ 30.]
[ 40.]
[ 50.]
[ 60.]
[ 70.]
[ 80.]
[ 90.]
[ 100.]]
3.1.2 标准化系列数据(Standardize Series Data)
标准化数据集涉及重新调整值的分布,使观测值的平均值为0,标准偏差为1。这可以被认为是去均值或使数据居中(center)。
与归一化一样,标准化也是有用的,甚至在某些机器学习算法中,当数据具有具有不同比例的输入值时,标准化也是必需的。标准化假设观测值符合高斯分布(bell曲线),具有良好的平均值和标准偏差。如果不满足这一假设,仍然可以将时间序列数据标准化,但可能无法获得可靠的结果。
标准化要求知道或能够准确估计可观测值的平均值和标准差。可以根据训练数据估计这些值。与最小值和最大值相比,数据集的平均值和标准差估计对新数据的鲁棒性更强。可以使用 sklearn 的 StandardScaler
对数据集进行标准化。
from pandas import Series
from sklearn.preprocessing import StandardScaler
from math import sqrt
# define contrived series
data = [1.0, 5.5, 9.0, 2.6, 8.8, 3.0, 4.1, 7.9, 6.3]
series = Series(data)
print(series)
# prepare data for normalization
values = series.values
values = values.reshape((len(values), 1))
# train the normalization
scaler = StandardScaler()
scaler = scaler.fit(values)
print( 'Mean: %f, StandardDeviation: %f' % (scaler.mean_, sqrt(scaler.var_)))
# normalize the dataset and print
standardized = scaler.transform(values)
print(standardized)
# inverse transform and print
inversed = scaler.inverse_transform(standardized)
print(inversed)
运行示例打印序列,打印序列估计的平均值和标准差,打印标准值,然后按原始比例打印值。
0 1.0
1 5.5
2 9.0
3 2.6
4 8.8
5 3.0
6 4.1
7 7.9
8 6.3
dtype: float64
Mean: 5.355556, StandardDeviation: 2.712568
[[-1.60569456]
[ 0.05325007]
[ 1.34354035]
[-1.01584758]
[ 1.26980948]
[-0.86838584]
[-0.46286604]
[ 0.93802055]
[ 0.34817357]]
[[1. ]
[5.5]
[9. ]
[2.6]
[8.8]
[3. ]
[4.1]
[7.9]
[6.3]]
3.1.3 缩放时的实际考虑
计算系数。可以从训练数据中计算系数(归一化的最小值和最大值,标准化的平均值和标准差)。检查这些估计,并使用领域知识帮助改进这些估计,以便它们在将来对所有数据都有用地进行更正。
保存系数。保存用于缩放的系数,并在以后进行预测时需要缩放新数据时加载它们。
数据分析。使用数据分析帮助更好地理解数据。例如,直方图可以帮助快速了解数量的分布,看看标准化是否有意义。
缩放每个序列。如果问题有多个序列,请将每个序列视为单独的变量,然后分别缩放。自己写函数进行缩放时需要特别注意!sklearn库中的函数是分别进行缩放的。
在正确的时间应用任何缩放变换都很重要。例如,有一系列非平稳的量,则在第一次使数据平稳后缩放可能比较合适。将序列转换为监督学习问题后缩放该序列是不合适的,因为每个列都会被不同地处理,这是不正确的。
在实践中,应该根据数据选择合适的规范化方法。
3.2 准备分类数据
分类变量通常被称为名词变量。例如:
- 一个pet变量,其值为dog和cat。
- 一个color变量,其值为:红色、绿色和蓝色。
- 具有以下值的place变量:first、second和third。
每个值代表不同的类别。文本中的单词可被视为分类数据,其中每个单词被视为不同的类别。此外,文本数据中的每个字母都可以被视为一个类别。文本输入或输出的序列预测问题可以被认为是分类数据。
有些类别之间可能有一种自然的关系,例如自然顺序。上面的place变量值有自然顺序。这种类型的分类变量称为序数变量。使用LSTMs时,分类数据必须转换为数字。
3.2.1如何将分类数据转换为数值数据
实际上类别变量方法有很多种,这些方法在博客里也总结过,感兴趣的可以在主页搜索类别变量编码。这里只以one-hot编码方法为例进行说明,其包括两个步骤:
- Integer Encoding
- One Hot Encoding
Integer Encoding
第一步,为每个唯一的类别值分配一个整数值。例如,红色是1,绿色是2,蓝色是3。这称为标签编码或整数编码,并且很容易进行逆变换(通过map函数加dict就可以实现)。对于有序变量,可以作为标签训练了, 而无需进一步进行one-hot编码。
整数值之间具有自然的有序关系,机器学习算法可以理解和利用这种关系。例如,上面的place示例这样的序数变量将是一个很好的示例,其中标签编码就足够了。
One Hot Encoding
对于不存在这种顺序关系的分类变量,整数编码是不够的。事实上,使用有序编码并允许模型假定类别之间的自然顺序可能会导致性能差或意外的结果。
在这种情况下,可以对整数表示应用One Hot Encoding。这是删除整数编码变量并为每个唯一整数值添加新二进制变量的位置。在颜色变量示例中,有3个类别,因此需要3个二进制变量。颜色的二进制变量中放置1值,其他颜色的二进制变量中放置0值。例如:
red, green, blue
1, 0, 0
0, 1, 0
0, 0, 1
3.2.2 使用 sklearn 对数据进行 one-hot 编码
在本例中,我们假设您有以下3个标签的输出序列:cold、warm、Hot。10个时间步骤的示例序列可以是:
cold、cold、warm、cold、hot、hot、warm、cold、warm、hot
这首先需要整数编码,例如1、2、3。接下来是对整数进行 one-hot 编码,得到一个包含3个值的二进制向量,例如 [1,0,0]
。
sklearn 提供了上述两种编码方式。即创建标签的整数编码的 LabelEncoder
和创建整数编码值的 one-hot 编码的 OneHotEncoder
。完整代码如下:
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)
运行示例首先打印标签序列。接下来是标签的整数编码,one-hot编码,最后是指定编码的逆变换标签。
默认情况下,OneHotEncoder
类将返回更有效的稀疏编码。这可能不适合深度学习库等应用程序。在这种情况下,我们通过设置 sparse=False
参数来禁用sparse返回类型。
我们可以使用NumPy中的 argmax()
函数来定位具有最大值的列的索引。然后可以将其输入 LabelEncoder
,以得到编码对应的原始标签。
['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']
3.3 准备不同长度的序列
深度学习库假设数据的矢量化表示。在可变长度序列预测问题中,这要求对数据进行转换,使每个序列具有相同的长度。这种矢量化可以使得深度学习算法高效地批量执行矩阵操作。
3.3.1 序列填充(Sequence Padding )
在Keras深度学习库中,pad_sequences
函数可以用来填充可变长度序列,默认的填充值是0.0。可以通过 value
参数来指定填充值。可以用 padding
参数指定是用缺失值前的值进行填充还是用其后的值进行填充,称为前向序列或后向序列填充,如下所示。
前向序列填充(Pre-Sequence Padding)
前向填充是默认值(padding=‘pre’)。下面的示例演示如何使用0值预添加3个输入序列。
from keras.preprocessing.sequence import pad_sequences
# define sequences
sequences = [
[1, 2, 3, 4],
[1, 2, 3],
[1]
]
# pad sequence
padded = pad_sequences(sequences)
print(padded)
运行该示例将打印用零值预填充的3个序列。
[[1 2 3 4]
[0 1 2 3]
[0 0 0 1]]
后向序列填充(Post-Sequence Padding)
填充也可以应用于序列的末尾,这可能更适合于某些问题域。
from keras.preprocessing.sequence import pad_sequences
# define sequences
sequences = [
[1, 2, 3, 4],
[1, 2, 3],
[1]
]
# pad sequence
padded = pad_sequences(sequences, padding='post')
print(padded)
运行示例将打印后置序列填充的相同序列
[[1 2 3 4]
[1 2 3 0]
[1 0 0 0]]
3.3.2 序列截断(Sequence Truncation)
序列的长度也可以修剪到所需的长度。序列所需的长度可以用 maxlen
参数指定为多个时间步。有两种方法可以截断序列:前向序列截断和后向序列截断。
前向序列截断(Pre-Sequence Truncation)
前向序列截断默认的截断方法是从序列开始处删除时间步。下面的示例的序列截断长度=2。
from keras.preprocessing.sequence import pad_sequences
# define sequences
sequences = [
[1, 2, 3, 4],
[1, 2, 3],
[1]
]
# truncate sequence
truncated= pad_sequences(sequences, maxlen=2)
print(truncated)
运行该示例将从第一个序列中删除前两个时间步骤,从第二个序列中删除第一个时间步骤,并填充最后一个序列。
[[3 4]
[2 3]
[0 1]]
后向序列截断(Post-Sequence Truncation)
序列也可以通过删除序列末尾的时间步长来进行修剪。这种方法可能更适合于一些问题领域。通过将截断参数从默认pre更改为Post,可以配置Post序列截断,如下所示:
from keras.preprocessing.sequence import pad_sequences
# define sequences
sequences = [
[1, 2, 3, 4],
[1, 2, 3],
[1]
]
# truncate sequence
truncated= pad_sequences(sequences, maxlen=2, truncating='post')
print(truncated)
运行该示例将从第一个序列中删除最后两个时间步,从第二个序列中删除最后一个时间步,然后再次填充最后一个序列。
[[1 2]
[1 2]
[0 1]]
对于何时填充和何时截断不同长度的输入序列,没有经验法则。例如,为了提高效率,在情感分析中截断很长的文本可能是有意义的,或者填充短文本并让模型学习忽略或显式屏蔽零输入值以确保没有数据丢失也是有意义的。建议为序列预测问题测试一组不同的表示,并重点关注那些产生最佳模型的表示。
3.4 序列预测作为监督学习
序列预测问题,必须重新构造为监督学习问题。也就是说,数据必须从一个序列转换为一对输入和输出对。
3.4.1序列与监督学习
首先需要理解原始输入序列和监督学习数据的形式。考虑一个按时间索引排序的数字序列。这可以看作是有序值的列表或列。例如:
0
1
2
3
4
5
6
7
8
9
有监督学习问题由输入模式(X)和输出模式(y)组成,因此算法可以学习如何从输入模式预测输出模式。例如:
X, y
1, 2
2, 3
3, 4
4, 5
5, 6
6, 7
7, 8
8, 9
这将表示序列的1-滞后变换,使得必须在给定序列的前一时间步的情况下预测当前时间步。
3.4.2 Pandas shift()函数
帮助将时间序列数据转换为监督学习问题的关键函数是Pandas shift()
函数。给定一个 DataFrame
(Pandas中的两个数据类型之一),shift()
函数可用于创建向前推(前面添加了一行NaN值)或向后拉(后面添加了一行NaN值)的列的副本。
这是以监督学习格式为时间序列数据集创建滞后观测列和预测观测列所需的步骤。让我们看看 shift()
函数的一些实际例子。我们可以将模拟时间序列数据集定义为10个数字的序列,在这种情况下,DataFrame
中的单个列如下所示:
from pandas import DataFrame
# define the sequence
df = DataFrame()
df['t'] = [x for x in range(10)]
print(df)
运行此示例将打印包含每个观测的行索引的时间序列数据。
t
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
我们可以通过在顶部插入一个新行,将所有观测值一步一步地向下移动。因为新行没有数据,所以我们可以使用NaN来表示没有数据。 shift()
函数可以为我们这样做,我们可以将这个移位列插入到原始序列的旁边。
from pandas import DataFrame
# define the sequence
df = DataFrame()
df['t'] = [x for x in range(10)]
# shift forward
df['t-1'] = df['t'].shift(1)
print(df)
运行该示例会在数据集中显示两列。第一个是原始观测和一个新的移位列。我们可以看到,将序列向前移动一个时间步会产生一个原始的有监督学习问题。由于NaN值的原因,第一行必须被丢弃。第二行在第二列(input或X)中显示0.0的输入值,在第一列(output或y)中显示1的值。
t t-1
0 0 NaN
1 1 0.0
2 2 1.0
3 3 2.0
4 4 3.0
5 5 4.0
6 6 5.0
7 7 6.0
8 8 7.0
9 9 8.0
如果可以以2、3和更多的移位重复这个过程,可以创建长输入序列(X),用于预测输出值(y)。
移位运算符也可以接受负整数值。这样可以通过在末尾插入新行来将观察结果向上拉。下面是一个例子:
from pandas import DataFrame
# define the sequence
df = DataFrame()
df[ t ] = [x for x in range(10)]
# shift forward
df[ t-1 ] = df[ t ].shift(-1)
print(df)
运行该示例将显示一个新列,最后一个值是NaN值。我们可以看到forecast列可以作为输入(X),第二列作为输出值(y)。即0的输入值可用于预测1的输出值。
t t+1
0 0 1.0
1 1 2.0
2 2 3.0
3 3 4.0
4 4 5.0
5 5 6.0
6 6 7.0
7 7 8.0
8 8 9.0
9 9 NaN
从技术上讲,在时间序列预测术语中,当前时间(t)和未来时间(t+1,t+n)是预测时间,过去的观测(t-1,t-n)用来进行预测。可以看到如何使用正移位和负移位从一个有监督学习问题的输入和输出模式序列的时间序列中创建新的 DataFrame。
这不仅允许经典的 X->y 预测,而且允许输入和输出都可以是序列的 X->y 预测。此外, shift()
函数也适用于所谓的多元时间序列问题。也就是说,有多个观测值(例如温度和压力),而不是一组时间序列的观测值。时间序列中的所有变量都可以向前或向后移动,以创建多变量输入和输出序列。
3.5 进一步阅读
3.5.1 Numeric Scaling APIs
- MinMaxScaler API in scikit-learn.
https://goo.gl/H3qHJU - StandardScaler API in scikit-learn.
https://goo.gl/cA4vQi - Should I normalize/standardize/rescale the data? Neural Nets FAQ.
ftp://ftp.sas.com/pub/neural/FAQ2.html#A_std
3.5.2 Categorical Encoding APIs
- LabelEncoder API in scikit-learn.
https://goo.gl/Y2bn3T - OneHotEncoder API in scikit-learn.
https://goo.gl/ynDMHN - NumPy argmax() API.
https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmax.html
3.5.3 Varied Length Sequence APIs
- pad sequences() API in Keras.
https://keras.io/preprocessing/sequence/
3.5.4 Supervised Learning APIs
- shift() function API in Pandas.
https://goo.gl/N3M3nG
3.6 Summary
- 如何缩放数字数据以及如何转换分类数据。
- 如何填充和截断不同长度的输入序列。
- 如何将输入序列转化为有监督学习问题。
参考:Jason Brownlee《 long-short-term-memory-networks-with-python》chapter 3
本文为个人学习记录,如有不易理解的地方还望读者谅解,谢谢!