ch06.md 83.8 KB
Newer Older
W
wizardforcel 已提交
1
# 六、RNN 和梯度消失或爆炸问题
W
wizardforcel 已提交
2

W
wizardforcel 已提交
3
较深层的梯度计算为多层网络中许多激活函数梯度的乘积。当这些梯度很小或为零时,它很容易消失。另一方面,当它们大于 1 时,它可能会爆炸。因此,计算和更新变得非常困难。
W
wizardforcel 已提交
4

W
wizardforcel 已提交
5
让我们更详细地解释一下:
W
wizardforcel 已提交
6

W
wizardforcel 已提交
7 8
*   如果权重较小,则可能导致称为消失梯度的情况,其中梯度信号变得非常小,以至于学习变得非常慢或完全停止工作。这通常被称为消失梯度。
*   如果该矩阵中的权重很大,则可能导致梯度信号太大而导致学习发散的情况。这通常被称为爆炸梯度。
W
wizardforcel 已提交
9

W
wizardforcel 已提交
10
因此,RNN 的一个主要问题是消失或爆炸梯度问题,它直接影响表现。事实上,反向传播时间推出了 RNN,创建了一个非常深的前馈神经网络。从 RNN 获得长期背景的不可能性正是由于这种现象:如果梯度在几层内消失或爆炸,网络将无法学习数据之间的高时间距离关系。
W
wizardforcel 已提交
11

W
wizardforcel 已提交
12
下图显示了发生的情况:计算和反向传播的梯度趋于在每个时刻减少(或增加),然后,在一定数量的时刻之后,成本函数趋于收敛到零(或爆炸到无穷大) )。
W
wizardforcel 已提交
13

W
wizardforcel 已提交
14
我们可以通过两种方式获得爆炸梯度。由于激活函数的目的是通过压缩它们来控制网络中的重大变化,因此我们设置的权重必须是非负的和大的。当这些权重沿着层次相乘时,它们会导致成本的大幅变化。当我们的神经网络模型学习时,最终目标是最小化成本函数并改变权重以达到最优成本。
W
wizardforcel 已提交
15

W
wizardforcel 已提交
16
例如,成本函数是均方误差。它是一个纯凸函数,目的是找到凸起的根本原因。如果你的权重增加到一定量,那么下降的时刻就会增加,我们会反复超过最佳状态,模型永远不会学习!
W
wizardforcel 已提交
17

W
wizardforcel 已提交
18
![RNN and the gradient vanishing-exploding problem](img/B09698_06_10.jpg)
W
wizardforcel 已提交
19

W
wizardforcel 已提交
20
在上图中,我们有以下参数:
W
wizardforcel 已提交
21

W
wizardforcel 已提交
22 23 24 25 26 27 28 29
*   `θ`表示隐藏的复现层的参数
*   `θ[x]`表示隐藏层的输入参数
*   `θ[y]`表示输出层的参数
*   `σ`表示隐藏层的激活函数
*   输入表示为`X[t]`
*   隐藏层的输出为`h[t]`
*   最终输出为`o[t]`
*   `t`(时间步长)
W
wizardforcel 已提交
30

W
wizardforcel 已提交
31
注意,上图表示下面给出的循环神经网络模型的时间流逝。现在,如果你回忆一下图 1,输出可以表示如下:
W
wizardforcel 已提交
32

W
wizardforcel 已提交
33
![RNN and the gradient vanishing-exploding problem](img/B09698_06_61.jpg)
W
wizardforcel 已提交
34

W
wizardforcel 已提交
35
现在让 E 代表输出层的损失:`E = f(O[t])`。然后,上述三个方程告诉我们 E 取决于输出`O[t]`。输出`O[t]`相对于层的隐藏状态(`h[t]`)的变化而变化。当前时间步长(`h[t]`)的隐藏状态取决于先前时间步长(`h[t-1]`)的神经元状态。现在,下面的等式将清除这个概念。
W
wizardforcel 已提交
36

W
wizardforcel 已提交
37
相对于为隐藏层选择的参数的损失变化率`= ∂E/∂θ`,这是一个可以表述如下的链规则:
W
wizardforcel 已提交
38

W
wizardforcel 已提交
39
![RNN and the gradient vanishing-exploding problem](img/B09698_06_68.jpg)
W
wizardforcel 已提交
40

W
wizardforcel 已提交
41
(I)
W
wizardforcel 已提交
42

W
wizardforcel 已提交
43
在前面的等式中,项`∂h[t]/∂h[k]`不仅有趣而且有用。
W
wizardforcel 已提交
44

W
wizardforcel 已提交
45
![RNN and the gradient vanishing-exploding problem](img/B09698_06_69.jpg)
W
wizardforcel 已提交
46

W
wizardforcel 已提交
47
(II)
W
wizardforcel 已提交
48

W
wizardforcel 已提交
49
现在,让我们考虑`t = 5``k = 1`然后
W
wizardforcel 已提交
50

W
wizardforcel 已提交
51
![RNN and the gradient vanishing-exploding problem](img/B09698_06_70.jpg)
W
wizardforcel 已提交
52

W
wizardforcel 已提交
53
(III)
W
wizardforcel 已提交
54

W
wizardforcel 已提交
55
微分方程(II)相对于(`h[t-1]`)给出了:
W
wizardforcel 已提交
56

W
wizardforcel 已提交
57
![RNN and the gradient vanishing-exploding problem](img/B09698_06_71.jpg)
W
wizardforcel 已提交
58

W
wizardforcel 已提交
59
(IV)
W
wizardforcel 已提交
60

W
wizardforcel 已提交
61
现在,如果我们将方程(III)和(IV)结合起来,我们可以得到以下结果:
W
wizardforcel 已提交
62

W
wizardforcel 已提交
63
![RNN and the gradient vanishing-exploding problem](img/B09698_06_72.jpg)
W
wizardforcel 已提交
64

W
wizardforcel 已提交
65
在这些情况下,`θ`也随着时间步长而变化。上面的等式显示了当前状态相对于先前状态的依赖性。现在让我们解释这两个方程的解剖。假设您处于时间步长 5(t = 5),那么 k 的范围从 1 到 5(k = 1 到 5),这意味着您必须为以下内容计算 k):
W
wizardforcel 已提交
66

W
wizardforcel 已提交
67
![RNN and the gradient vanishing-exploding problem](img/B09698_06_75.jpg)
W
wizardforcel 已提交
68

W
wizardforcel 已提交
69
现在来看上面的每一个等式(II)
W
wizardforcel 已提交
70

W
wizardforcel 已提交
71
![RNN and the gradient vanishing-exploding problem](img/B09698_06_69.jpg)
W
wizardforcel 已提交
72

W
wizardforcel 已提交
73
而且,它取决于循环层的参数`θ`。如果在训练期间你的权重变大,那么由于每个时间步长的等式(I)(II)的乘法,它们将会出现梯度爆炸的问题。
W
wizardforcel 已提交
74

W
wizardforcel 已提交
75
为了克服消失或爆炸问题,已经提出了基本 RNN 模型的各种扩展。将在下一节介绍的 LSTM 网络就是其中之一。
W
wizardforcel 已提交
76

W
wizardforcel 已提交
77
## LSTM 网络
W
wizardforcel 已提交
78

W
wizardforcel 已提交
79
一种 RNN 模型是  LSTM。 LSTM 的精确实现细节不在本书的范围内。 LSTM 是一种特殊的 RNN 架构,最初由 Hochreiter 和 Schmidhuber 于 1997 年构思。
W
wizardforcel 已提交
80

W
wizardforcel 已提交
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
最近在深度学习的背景下重新发现了这种类型的神经网络,因为它没有消失梯度的问题,并且提供了出色的结果和表现。基于 LSTM 的网络是时间序列的预测和分类的理想选择,并且正在取代许多传统的深度学习方法。

这个名称意味着短期模式不会被遗忘。 LSTM 网络由彼此链接的单元(LSTM 块)组成。每个 LSTM 块包含三种类型的门:输入门,输出门和遗忘门,它们分别实现对单元存储器的写入,读取和复位功能。这些门不是二元的,而是模拟的(通常由映射在[0,1]范围内的 S 形激活函数管理,其中 0 表示总抑制,1 表示总激活)。

如果你认为 LSTM 单元是一个黑盒子,它可以像基本单元一样使用,除了它会表现得更好;训练将更快地收敛,它将检测数据中的长期依赖性。在 TensorFlow 中,您只需使用`BasicLSTMCell`代替`BasicRNNCell`

```py
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units=n_neurons)
```

LSTM 单元管理两个状态向量,并且出于表现原因,它们默认保持独立。您可以通过在创建`BasicLSTMCell`时设置`state_is_tuple=False`来更改此默认行为。那么,LSTM 单元如何工作?基本 LSTM 单元的架构如下图所示:

![LSTM networks](img/B09698_06_11.jpg)

图 11:LSTM 单元的框图

现在,让我们看看这个架构背后的数学符号。如果我们不查看 LSTM 框内的内容,LSTM 单元本身看起来就像常规存储单元,除了它的状态被分成两个向量,`h(t)``c(t)`

*   `c`是一个
*   `h(t)`是短期的
*   `c(t)`是长期状态

现在,让我们打开盒子吧!关键的想法是网络可以学习以下内容:

*   在长期的状态下存储什么
*   什么扔掉
*   怎么读它

由于长期`c(t)`从左到右穿过网络,你可以看到它首先通过一个遗忘门,丢弃一些内存,然后它添加一些新的存储器通过加法运算(增加了输入门选择的存储器)。结果`c(t)`直接发送,没有任何进一步的变换

因此,在每个时间步骤,都会丢弃一些内存并添加一些内存。此外,在加法运算之后,长期状态被复制并通过 tanh 函数,该函数产生[-1,+ 1]范围内的输出。

然后输出门过滤结果。这会产生短期`h(t)`(等于此时间步的单元输出`y(t)`)。现在,让我们来看看新记忆的来源以及大门如何运作。首先,当前输入`x(t)`和之前的短路`h(t-1)`被馈送到四个不同的完全连接。这些门的存在允许 LSTM 单元无限期地记住信息:事实上,如果输入门低于激活阈值,单元格将保持先前的状态,如果启用当前状态,它将与输入值组合。顾名思义,遗忘门重置单元的当前状态(当其值被清除为 0 时),输出门决定是否必须执行单元的值。

以下等式用于对单个实例的单元的长期状态,其短期状态及其在每个时间步的输出进行 LSTM 计算:

![LSTM networks](img/B09698_06_50.jpg)

在前面的方程中,`W[xi]``W[xf]``W[xo]``W[xg]`是四个层中每个层的权重矩阵,用于与输入向量`x(t)`连接。另一方面,`W[hi]``W[hf]``W[ho]`,和`W[hg]`是四层中每一层的权重矩阵,它们与先前的短期状态有关。`b[i]``b[f]``b[o]``b[g]`是四层中每一层的偏差项。 TensorFlow 初始化它们为一个全 1 的向量而不是全 0 的向量。这可以防止它在训练开始时遗忘一切。

## GRU 单元

LSTM 单元还有许多其他变体。一种特别流行的变体是门控循环单元(GRU)。 Kyunghyun Cho 和其他人在 2014 年的论文中提出了 GRU 单元,该论文还介绍了我们前面提到的自编码器网络。

从技术上讲,GRU 单元是 LSTM 单元的简化版本,其中两个状态向量合并为一个称为`h(t)`的向量。单个门控制器控制遗忘门和输入门。如果门控制器的输出为 1,则输入门打开并且遗忘门关闭。

![GRU cell](img/B09698_06_13.jpg)

图 12:GRU 单元

另一方面,如果输出为 0,则相反。每当必须存储存储器时,首先擦除存储它的位置,这实际上是 LSTM 单元本身的常见变体。第二种简化是因为在每个时间步输出满状态向量,所以没有输出门。但是,新的门控制器控制先前状态的哪一部分将显示给主层。

以下等式用于为单个实例,在每个时间步计算 GRU 单元的长期状态,其短期状态及其输出的:

![GRU cell](img/B09698_06_36.jpg)

在 TensorFlow 中创建 GRU 单元非常简单。这是一个例子:

```py
gru_cell = tf.nn.rnn_cell.GRUCell(num_units=n_neurons)
```

这些简化并不是这种架构的弱点;它似乎成功地执行。 LSTM 或 GRU 单元是近年来 RNN 成功背后的主要原因之一,特别是在 NLP 中的应用。

我们将在本章中看到使用 LSTM 的示例,但下一节将介绍使用 RNN 进行垃圾邮件/火腿文本分类的示例。

# 实现 RNN 进行垃圾邮件预测

在本节中,我们将看到如何在 TensorFlow 中实现 RNN 来预测文本中的垃圾邮件。

## 数据描述和预处理

将使用来自 UCI ML 仓库的流行垃圾数据集,可从[此链接](http://archive.ics.uci.edu/ml/machine-learning-databases/00228/smssp)下载`amcollection.zip`

该数据集包含来自多个电子邮件的文本,其中一些被标记为垃圾邮件。在这里,我们将训练一个模型,该模型将学习仅使用电子邮件文本区分垃圾邮件和非垃圾邮件。让我们开始导入所需的库和模型:

```py
import os
import re
import io
import requests
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from zipfile import ZipFile
from tensorflow.python.framework import ops
import warnings
```

另外,如果您需要,我们可以停止打印由 TensorFlow 产生的警告:

```py
warnings.filterwarnings("ignore")
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
ops.reset_default_graph()
```

现在,让我们为图创建 TensorFlow 会话:

```py
sess = tf.Session()
```

下一个任务是设置 RNN 参数:

```py
epochs = 300
batch_size = 250
max_sequence_length = 25
rnn_size = 10
embedding_size = 50
min_word_frequency = 10
learning_rate = 0.0001
dropout_keep_prob = tf.placeholder(tf.float32)
```

让我们手动下载数据集并将其存储在`temp`目录的`text_data.txt`文件中。首先,我们设置路径:

```py
data_dir = 'temp'
data_file = 'text_data.txt'
if not os.path.exists(data_dir):
    os.makedirs(data_dir)
```

现在,我们直接以压缩格式下载数据集:

```py
if not os.path.isfile(os.path.join(data_dir, data_file)):
    zip_url = 'http://archive.ics.uci.edu/ml/machine-learning-databases/00228/smsspamcollection.zip'
    r = requests.get(zip_url)
    z = ZipFile(io.BytesIO(r.content))
    file = z.read('SMSSpamCollection')
```

我们仍然需要格式化数据:

```py
    text_data = file.decode()
    text_data = text_data.encode('ascii',errors='ignore')
    text_data = text_data.decode().split('\n')
```

现在,在文本文件中存储前面提到的目录:

```py
    with open(os.path.join(data_dir, data_file), 'w') as file_conn:
        for text in text_data:
            file_conn.write("{}\n".format(text))
else:
    text_data = []
    with open(os.path.join(data_dir, data_file), 'r') as file_conn:
        for row in file_conn:
            text_data.append(row)
    text_data = text_data[:-1]
```

让我们分开单词长度至少为 2 的单词:

```py
text_data = [x.split('\t') for x in text_data if len(x)>=1]
[text_data_target, text_data_train] = [list(x) for x in zip(*text_data)]
```

现在我们创建一个文本清理函数:

```py
def clean_text(text_string):
    text_string = re.sub(r'([^\s\w]|_|[0-9])+', '', text_string)
    text_string = " ".join(text_string.split())
    text_string = text_string.lower()
    return(text_string)
```

我们调用前面的方法来清理文本:

```py
text_data_train = [clean_text(x) for x in text_data_train]
```

现在我们需要做一个最重要的任务,即创建单词嵌入 - 将文本更改为数字向量:

```py
vocab_processor = tf.contrib.learn.preprocessing.VocabularyProcessor(max_sequence_length, min_frequency=min_word_frequency)
text_processed = np.array(list(vocab_processor.fit_transform(text_data_train)))
```

现在让我们随意改变数据集的平衡:

```py
text_processed = np.array(text_processed)
text_data_target = np.array([1 if x=='ham' else 0 for x in text_data_target])
shuffled_ix = np.random.permutation(np.arange(len(text_data_target)))
x_shuffled = text_processed[shuffled_ix]
y_shuffled = text_data_target[shuffled_ix]
```

现在我们已经改组了数据,我们可以将数据分成训练和测试集:

```py
ix_cutoff = int(len(y_shuffled)*0.75)
x_train, x_test = x_shuffled[:ix_cutoff], x_shuffled[ix_cutoff:]
y_train, y_test = y_shuffled[:ix_cutoff], y_shuffled[ix_cutoff:]
vocab_size = len(vocab_processor.vocabulary_)
print("Vocabulary size: {:d}".format(vocab_size))
print("Training set size: {:d}".format(len(y_train)))
print("Test set size: {:d}".format(len(y_test)))
```

以下是上述代码的输出:

```py
>>>
Vocabulary size: 933
Training set size: 4180
Test set size: 1394

```

在我们开始训练之前,让我们为 TensorFlow 图创建占位符:

```py
x_data = tf.placeholder(tf.int32, [None, max_sequence_length])
y_output = tf.placeholder(tf.int32, [None])
```

让我们创建嵌入:

```py
embedding_mat = tf.get_variable("embedding_mat", shape=[vocab_size, embedding_size], dtype=tf.float32, initializer=None, regularizer=None, trainable=True, collections=None)
embedding_output = tf.nn.embedding_lookup(embedding_mat, x_data)
```

现在是构建我们的  RNN 的时候了。以下代码定义了 RNN 单元:

```py
cell = tf.nn.rnn_cell.BasicRNNCell(num_units = rnn_size)
output, state = tf.nn.dynamic_rnn(cell, embedding_output, dtype=tf.float32)
output = tf.nn.dropout(output, dropout_keep_prob)
```

现在让我们定义从 RNN 序列获取输出的方法:

```py
output = tf.transpose(output, [1, 0, 2])
last = tf.gather(output, int(output.get_shape()[0]) - 1)
```

接下来,我们定义 RNN 的权重和偏差:

```py
weight = bias = tf.get_variable("weight", shape=[rnn_size, 2], dtype=tf.float32, initializer=None, regularizer=None, trainable=True, collections=None)
bias = tf.get_variable("bias", shape=[2], dtype=tf.float32, initializer=None, regularizer=None, trainable=True, collections=None)
```

然后定义`logits`输出。它使用前面代码中的权重和偏差:

```py
logits_out = tf.nn.softmax(tf.matmul(last, weight) + bias)
```

现在我们定义每个预测的损失,以便稍后,它们可以为损失函数做出贡献:

```py
losses = tf.nn.sparse_softmax_cross_entropy_with_logits_v2(logits=logits_out, labels=y_output)
```

然后我们定义损失函数:

```py
loss = tf.reduce_mean(losses)
```

我们现在定义每个预测的准确率:

```py
accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(logits_out, 1), tf.cast(y_output, tf.int64)), tf.float32))
```

然后我们用`RMSPropOptimizer`创建`training_op`

```py
optimizer = tf.train.RMSPropOptimizer(learning_rate)
train_step = optimizer.minimize(loss)
```

现在让我们使用`global_variables_initializer()`方法初始化所有变量  :

```py
init_op = tf.global_variables_initializer()
sess.run(init_op)
```

此外,我们可以创建一些空列表来跟踪每个周期的训练损失,测试损失,训练准确率和测试准确率:

```py
train_loss = []
test_loss = []
train_accuracy = []
test_accuracy = []
```

现在我们已准备好进行训练,让我们开始吧。训练的工作流程如下:

*   随机播放训练数据
*   选择训练集并计算世代
*   为每个批次运行训练步骤
*   运行损失和训练的准确率
*   运行评估步骤。

以下代码包括上述所有步骤:

```py
    shuffled_ix = np.random.permutation(np.arange(len(x_train)))
    x_train = x_train[shuffled_ix]
    y_train = y_train[shuffled_ix]
    num_batches = int(len(x_train)/batch_size) + 1

    for i in range(num_batches):
        min_ix = i * batch_size
        max_ix = np.min([len(x_train), ((i+1) * batch_size)])
        x_train_batch = x_train[min_ix:max_ix]
        y_train_batch = y_train[min_ix:max_ix]
        train_dict = {x_data: x_train_batch, y_output: \
y_train_batch, dropout_keep_prob:0.5}
        sess.run(train_step, feed_dict=train_dict)
        temp_train_loss, temp_train_acc = sess.run([loss,\ 
                         accuracy], feed_dict=train_dict)
    train_loss.append(temp_train_loss)
    train_accuracy.append(temp_train_acc)
    test_dict = {x_data: x_test, y_output: y_test, \ 
dropout_keep_prob:1.0}
    temp_test_loss, temp_test_acc = sess.run([loss, accuracy], \
                    feed_dict=test_dict)
    test_loss.append(temp_test_loss)
    test_accuracy.append(temp_test_acc)
    print('Epoch: {}, Test Loss: {:.2}, Test Acc: {:.2}'.format(epoch+1, temp_test_loss, temp_test_acc))
print('\nOverall accuracy on test set (%): {}'.format(np.mean(temp_test_acc)*100.0))
```

以下是前面代码的输出:

```py
>>>
Epoch: 1, Test Loss: 0.68, Test Acc: 0.82
Epoch: 2, Test Loss: 0.68, Test Acc: 0.82
Epoch: 3, Test Loss: 0.67, Test Acc: 0.82

Epoch: 997, Test Loss: 0.36, Test Acc: 0.96
Epoch: 998, Test Loss: 0.36, Test Acc: 0.96
Epoch: 999, Test Loss: 0.35, Test Acc: 0.96
Epoch: 1000, Test Loss: 0.35, Test Acc: 0.96
Overall accuracy on test set (%): 96.19799256324768 

```

做得好! RNN 的准确率高于 96%,非常出色。现在让我们观察损失如何在每次迭代中传播并随着时间的推移:

```py
epoch_seq = np.arange(1, epochs+1)
plt.plot(epoch_seq, train_loss, 'k--', label='Train Set')
plt.plot(epoch_seq, test_loss, 'r-', label='Test Set')
plt.title('RNN training/test loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend(loc='upper left')
plt.show()
```

![Data description and preprocessing](img/B09698_06_14.jpg)

图 13:a)每个周期的 RNN 训练和测试损失 b)每个周期的测试精度

我们还随时间绘制准确率:

```py
plt.plot(epoch_seq, train_accuracy, 'k--', label='Train Set')
plt.plot(epoch_seq, test_accuracy, 'r-', label='Test Set')
plt.title('Test accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend(loc='upper left')
plt.show()
```

下一个应用使用时间序列数据进行预测建模。我们还将看到如何开发更复杂的 RNN,称为 LSTM 网络。

# 开发时间序列数据的预测模型

RNN,特别是  LSTM 模型,通常是一个难以理解的主题。由于数据中的时间依赖性,时间序列预测是 RNN 的有用应用。时间序列数据可在线获取。在本节中,我们将看到使用 LSTM 处理时间序列数据的示例。我们的 LSTM 网络将能够预测未来的航空公司乘客数量。

## 数据集的描述

我将使用的数据集是 1949 年至 1960 年国际航空公司乘客的数据。该数据集可以从[此链接](https://datamarket.com/data/set/22u3/international-airlinepassengers- monthly-totals-in#!ds=22u3&display=line)。以下屏幕截图显示了国际航空公司乘客的元数据:

![Description of the dataset](img/B09698_06_15.jpg)

图 14:国际航空公司乘客的元数据(来源:<https://datamarket.com/>

W
wizardforcel 已提交
480
您可以通过选择“导出”选项卡,然后在“导出”组中选择 CSV 来下载数据。您必须手动编辑 CSV 文件以删除标题行以及其他页脚行。我已经下载并保存了名为`international-airline-passengers.csv`的数据文件。下图是时间序列数据的一个很好的图:
W
wizardforcel 已提交
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812

![Description of the dataset](img/B09698_06_16.jpg)

图 15:国际航空公司乘客:1 月 49 日至 12 月 60 日的月度总数为千人

## 预处理和探索性分析

现在让我们加载原始数据集并查看一些事实。首先,我们加载时间序列如下(见`time_series_preprocessor.py`):

```py
import csv
import numpy as np
```

在这里,我们可以看到`load_series()`的签名,它是一个用户定义的方法,可以加载时间序列并对其进行正则化:

```py
def load_series(filename, series_idx=1):
    try:
        with open(filename) as csvfile:
            csvreader = csv.reader(csvfile)
            data = [float(row[series_idx]) for row in csvreader if len(row) > 0]
            normalized_data = (data - np.mean(data)) / np.std(data)
        return normalized_data
    except IOError:
       Print("Error occurred")

        return None
```

现在让我们调用前面的方法加载时间序列并打印(在终端上发出`$ python3 plot_time_series.py`)数据集中的序列号:

```py
import csv
import numpy as np
import matplotlib.pyplot as plt
import time_series_preprocessor as tsp
timeseries = tsp.load_series('international-airline-passengers.csv')
print(timeseries)
```

以下是前面代码的输出:

```py
>>>
[-1.40777884 -1.35759023 -1.24048348 -1.26557778 -1.33249593 -1.21538918
 -1.10664719 -1.10664719 -1.20702441 -1.34922546 -1.47469699 -1.35759023
..
 2.85825285  2.72441656  1.9046693   1.5115252   0.91762667  1.26894693]
print(np.shape(timeseries))
```

```py
>>>
144

```

这意味着时间序列中有`144`条目。让我们绘制时间序列:

```py
plt.figure()
plt.plot(timeseries)
plt.title('Normalized time series')
plt.xlabel('ID')
plt.ylabel('Normalized value')
plt.legend(loc='upper left')
plt.show()
```

以下是上述代码的输出:

```py
>>>

```

![Pre-processing and exploratory analysis](img/B09698_06_17.jpg)

图 16:时间序列(y 轴,标准化值与 x 轴,ID)

加载时间序列数据集后,下一个任务是准备训练集。由于我们将多次评估模型以预测未来值,因此我们将数据分为训练和测试。更具体地说,`split_data()`函数将数据集划分为两个部分,用于训练和测试,75%用于训练,25%用于测试:

```py
def split_data(data, percent_train):
    num_rows = len(data)
    train_data, test_data = [], []
    for idx, row in enumerate(data):
        if idx < num_rows * percent_train:
            train_data.append(row)
        else:
            test_data.append(row)
    return train_data, test_data
```

## LSTM 预测模型

一旦我们准备好数据集,我们就可以通过以可接受的格式加载数据来训练预测器。在这一步中,我编写了一个名为`TimeSeriesPredictor.py`的 Python 脚本,它首先导入必要的库和模块(在此脚本的终端上发出`$ python3 TimeSeriesPredictor.py`命令):

```py
import numpy as np
import tensorflow as tf
from tensorflow.python.ops import rnn, rnn_cell
import time_series_preprocessor as tsp
import matplotlib.pyplot as plt
```

接下来,我们为 LSTM 网络定义超参数(相应地调整它):

```py
input_dim = 1
seq_size = 5
hidden_dim = 5
```

我们现在定义权重变量(无偏差)和输入占位符:

```py
W_out = tf.get_variable("W_out", shape=[hidden_dim, 1], dtype=tf.float32, initializer=None, regularizer=None, trainable=True, collections=None) 
b_out = tf.get_variable("b_out", shape=[1], dtype=tf.float32, initializer=None, regularizer=None, trainable=True, collections=None)
x = tf.placeholder(tf.float32, [None, seq_size, input_dim])
y = tf.placeholder(tf.float32, [None, seq_size])
```

下一个任务是构建 LSTM 网络。以下方法`LSTM_Model()`采用三个参数,如下所示:

*   `x`:大小为[T,batch_size,input_size]的输入
*   `W`:完全连接的输出层权重矩阵
*   `b`:完全连接的输出层偏置向量

现在让我们看一下方法的签名:

```py
def LSTM_Model():
        cell = rnn_cell.BasicLSTMCell(hidden_dim)
        outputs, states = rnn.dynamic_rnn(cell, x, dtype=tf.float32)
        num_examples = tf.shape(x)[0]
        W_repeated = tf.tile(tf.expand_dims(W_out, 0), [num_examples, 1, 1])
        out = tf.matmul(outputs, W_repeated) + b_out
        out = tf.squeeze(out)
        return out
```

此外,我们创建了三个空列表来存储训练损失,测试损失和步骤:

```py
train_loss = []
test_loss = []
step_list = []
```

下一个名为`train()`的方法用于训练 LSTM 网络:

```py
def trainNetwork(train_x, train_y, test_x, test_y):
        with tf.Session() as sess:
            tf.get_variable_scope().reuse_variables()
            sess.run(tf.global_variables_initializer())
            max_patience = 3
            patience = max_patience
            min_test_err = float('inf')
            step = 0
            while patience > 0:
                _, train_err = sess.run([train_op, cost], feed_dict={x: train_x, y: train_y})
                if step % 100 == 0:
                    test_err = sess.run(cost, feed_dict={x: test_x, y: test_y})
                    print('step: {}\t\ttrain err: {}\t\ttest err: {}'.format(step, train_err, test_err))
                    train_loss.append(train_err)
                    test_loss.append(test_err)
                    step_list.append(step)
                    if test_err < min_test_err:
                        min_test_err = test_err
                        patience = max_patience
                    else:
                        patience -= 1
                step += 1
            save_path = saver.save(sess, 'model.ckpt')
            print('Model saved to {}'.format(save_path))
```

接下来的任务是创建成本优化器并实例化`training_op`

```py
cost = tf.reduce_mean(tf.square(LSTM_Model()- y))
train_op = tf.train.AdamOptimizer(learning_rate=0.003).minimize(cost)
```

另外,这里有一个叫做保存模型的辅助`op`

```py
saver = tf.train.Saver()
```

现在我们已经创建了模型,下一个方法称为`testLSTM()`,用于测试模型在测试集上的预测能力:

```py
def testLSTM(sess, test_x):
        tf.get_variable_scope().reuse_variables()
        saver.restore(sess, 'model.ckpt')
        output = sess.run(LSTM_Model(), feed_dict={x: test_x})
        return output
```

为了绘制预测结果,我们有一个名为`plot_results()`的函数。签名如下:

```py
def plot_results(train_x, predictions, actual, filename):
    plt.figure()
    num_train = len(train_x)
    plt.plot(list(range(num_train)), train_x, color='b', label='training data')
    plt.plot(list(range(num_train, num_train + len(predictions))), predictions, color='r', label='predicted')
    plt.plot(list(range(num_train, num_train + len(actual))), actual, color='g', label='test data')
    plt.legend()
    if filename is not None:
        plt.savefig(filename)
    else:
        plt.show()
```

## 模型评估

为了评估模型,我们有一个名为`main()`的方法,它实际上调用前面的方法来创建和训练 LSTM 网络。代码的工作流程如下:

1.  加载数据
2.  在时间序列数据中滑动窗口以构建训练数据集
3.  执行相同的窗口滑动策略来构建测试数据集
4.  在训练数据集上训练模型
5.  可视化模型的表现

让我们看看方法的签名:

```py
def main():
    data = tsp.load_series('international-airline-passengers.csv')
    train_data, actual_vals = tsp.split_data(data=data, percent_train=0.75)
    train_x, train_y = [], []
    for i in range(len(train_data) - seq_size - 1):
        train_x.append(np.expand_dims(train_data[i:i+seq_size], axis=1).tolist())
        train_y.append(train_data[i+1:i+seq_size+1])
    test_x, test_y = [], []
    for i in range(len(actual_vals) - seq_size - 1):
        test_x.append(np.expand_dims(actual_vals[i:i+seq_size], axis=1).tolist())
        test_y.append(actual_vals[i+1:i+seq_size+1])
    trainNetwork(train_x, train_y, test_x, test_y)
    with tf.Session() as sess:
        predicted_vals = testLSTM(sess, test_x)[:,0]
        # Following prediction results of the model given ground truth values
        plot_results(train_data, predicted_vals, actual_vals, 'ground_truth_predition.png')
        prev_seq = train_x[-1]
        predicted_vals = []
        for i in range(1000):
            next_seq = testLSTM(sess, [prev_seq])
            predicted_vals.append(next_seq[-1])
            prev_seq = np.vstack((prev_seq[1:], next_seq[-1]))
        # Following predictions results where only the training data was given
        plot_results(train_data, predicted_vals, actual_vals, 'prediction_on_train_set.png')
>>>
```

最后,我们将调用`main()`方法来执行训练。训练完成后,它进一步绘制模型的预测结果,包括地面实况值与预测结果,其中只给出了训练数据:

```py
>>>

```

![Model evaluation](img/B09698_06_18.jpg)

图 17:模型对地面实况值的结果

下图显示了训练数据的预测结果。此过程可用的信息较少,但它仍然可以很好地匹配数据中的趋势:

![Model evaluation](img/B09698_06_19.jpg)

图 18:训练集上模型的结果

以下方法帮助我们绘制训练和测试错误:

```py
def plot_error():
    # Plot training loss over time
    plt.plot(step_list, train_loss, 'r--', label='LSTM training loss per iteration', linewidth=4)
    plt.title('LSTM training loss per iteration')
    plt.xlabel('Iteration')
    plt.ylabel('Training loss')
    plt.legend(loc='upper right')
    plt.show()

    # Plot test loss over time
    plt.plot(step_list, test_loss, 'r--', label='LSTM test loss per iteration', linewidth=4)
    plt.title('LSTM test loss per iteration')
    plt.xlabel('Iteration')
    plt.ylabel('Test loss')
    plt.legend(loc='upper left')
    plt.show()
```

现在我们调用上面的方法如下:

```py
plot_error()
>>>
```

![Model evaluation](img/B09698_06_20.jpg)

图 19:a)每次迭代的 LSTM 训练损失,b)每次迭代的 LSTM 测试损失

我们可以使用时间序列预测器来重现数据中的实际波动。现在,您可以准备自己的数据集并执行其他一些预测分析。下一个示例是关于产品和电影评论数据集的情感分析。我们还将了解如何使用 LSTM 网络开发更复杂的 RNN。

# 用于情感分析的 LSTM 预测模型

情感分析是 NLP 中使用最广泛的任务之一。 LSTM 网络可用于将短文本分类为期望的类别,即分类问题。例如,一组推文可以分为正面或负面。在本节中,我们将看到这样一个例子。

## 网络设计

实现的 LSTM 网络将具有三层:嵌入层,RNN 层和 softmax 层。从下图可以看到对此的高级视图。在这里,我总结了所有层的功能:

*   嵌入层:我们将在第 8 章中看到一个示例,显示文本数据集不能直接馈送到深度神经网络(DNN),因此一个名为嵌入层是必需的。对于该层,我们将每个输入(k 个单词的张量)变换为 k 个 N 维向量的张量。这称为字嵌入,其中 N 是嵌入大小。每个单词都与在训练过程中需要学习的权重向量相关联。您可以在单词的向量表示中更深入地了解单词嵌入。
*   RNN 层:一旦我们构建了嵌入层,就会有一个名为 RNN 层的新层,它由带有压降包装的 LSTM 单元组成。在训练过程中需要学习 LSTM 权重,如前几节所述。动态展开 RNN 层(如图 4 所示),将 k 个字嵌入作为输入并输出 k 个 M 维向量,其中 M 是 LSTM 单元的隐藏大小。
*   Softmax 或 Sigmoid 层:RNN 层的输出在`k`个时间步长上平均,获得大小为`M`的单个张量。最后,例如,softmax 层用于计算分类概率。

    ![Network design](img/B09698_06_21.jpg)

    图 20:用于情感分析的 LSTM 网络的高级视图

稍后我们将看到交叉熵如何用作损失函数,`RMSProp`是最小化它的优化器。

## LSTM 模型训练

UMICH SI650 - 情感分类数据集(删除了重复)包含有关密歇根大学捐赠的产品和电影评论的数据,可以从[此链接下载](https://inclass.kaggle.com/c/si650winter11/data/)。在获取令牌之前,已经清除了不需要的或特殊的字符(参见`data.csv`文件)。

W
wizardforcel 已提交
813
以下脚本还会删除停用词(请参阅`data_preparation.py`)。给出一些标记为阴性或阳性的样本(1 为正面,0 为负面):
W
wizardforcel 已提交
814

W
wizardforcel 已提交
815
| 情感 | 情感文本 |
W
wizardforcel 已提交
816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891
| --- | --- |
| 1 | 达芬奇密码书真棒。 |
| 1 | 我很喜欢达芬奇密码。 |
| 0 | 天哪,我讨厌断背山。 |
| 0 | 我讨厌哈利波特。 |

> 表 1:情感数据集的样本

现在,让我们看一下为此任务训练 LSTM 网络的分步示例。首先,我们导入必要的模块和包(执行`train.py`文件):

```py
from data_preparation import Preprocessing
from lstm_network import LSTM_RNN_Network
import tensorflow as tf
import pickle
import datetime
import time
import os
import matplotlib.pyplot as plt
```

在前面的导入声明中,`data_preparation``lstm_network`是两个辅助 Python 脚本,用于数据集准备和网络设计。我们稍后会看到更多细节。现在让我们为 LSTM 定义参数:

```py
data_dir = 'data/' # Data directory containing 'data.csv'
stopwords_file = 'data/stopwords.txt' # Path to stopwords file
n_samples= None # Set n_samples=None to use the whole dataset

# Directory where TensorFlow summaries will be stored'
summaries_dir= 'logs/'
batch_size = 100 #Batch size
train_steps = 1000 #Number of training steps
hidden_size= 75 # Hidden size of LSTM layer
embedding_size = 75 # Size of embeddings layer
learning_rate = 0.01
test_size = 0.2
dropout_keep_prob = 0.5 # Dropout keep-probability
sequence_len = None # Maximum sequence length
validate_every = 100 # Step frequency to validate
```

我相信前面的参数是不言自明的。下一个任务是准备 TensorBoard 使用的摘要:

```py
summaries_dir = '{0}/{1}'.format(summaries_dir, datetime.datetime.now().strftime('%d_%b_%Y-%H_%M_%S'))
train_writer = tf.summary.FileWriter(summaries_dir + '/train')
validation_writer = tf.summary.FileWriter(summaries_dir + '/validation')
```

现在让我们准备模型目录:

```py
model_name = str(int(time.time()))
model_dir = '{0}/{1}'.format(checkpoints_root, model_name)
if not os.path.exists(model_dir):
    os.makedirs(model_dir)
```

接下来,让我们准备数据并构建 TensorFlow 图(参见`data_preparation.py`文件):

```py
data_lstm = Preprocessing(data_dir=data_dir,
                 stopwords_file=stopwords_file,
                 sequence_len=sequence_len,
                 test_size=test_size,
                 val_samples=batch_size,
                 n_samples=n_samples,
                 random_state=100)
```

在前面的代码段中,`Preprocessing`是一个继续的类(详见`data_preparation.py`)几个函数和构造函数,它们帮助我们预处理训练和测试集以训练 LSTM 网络。在这里,我提供了每个函数及其功能的代码。

该类的构造函数初始化数据预处理器。此类提供了一个接口,用于将数据加载,预处理和拆分为训练,验证和测试集。它需要以下参数:

*   `data_dir`:包含数据集文件`data.csv`的数据目录,其中包含名为`SentimentText``Sentiment`的列。
*   `stopwords_file`:可选。如果提供,它将丢弃原始数据中的每个停用词。
W
wizardforcel 已提交
892
*   `sequence_len`:可选。如果`m`是数据集中的最大序列长度,则需要`sequence_len >= m`。如果`sequence_len``None`,则会自动分配给`m`
W
wizardforcel 已提交
893
*   `n_samples`:可选。它是从数据集加载的样本数(对大型数据集很有用)。如果`n_samples``None`,则将加载整个数据集(注意;如果数据集很大,则可能需要一段时间来预处理每个样本)。
W
wizardforcel 已提交
894
*   `test_size`:可选。 `0 < test_size < 1`。它表示要包含在测试集中的数据集的比例(默认值为`0.2`)。
W
wizardforcel 已提交
895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050
*   `val_samples`:可选但可用于表示验证样本的绝对数量(默认为`100`)。
*   `random_state`:这是随机种子的可选参数,用于将数据分成训练,测试和验证集(默认为`0`)。
*   `ensure_preprocessed`:可选。如果`ensure_preprocessed=True`,它确保数据集已经过预处理(默认为`False`)。

构造函数的代码如下:

```py
def __init__(self, data_dir, stopwords_file=None, sequence_len=None, n_samples=None, test_size=0.2, val_samples=100, random_state=0, ensure_preprocessed=False):
        self._stopwords_file = stopwords_file
        self._n_samples = n_samples
        self.sequence_len = sequence_len
        self._input_file = os.path.join(data_dir, 'data.csv')
        self._preprocessed_file=os.path.join(data_dir,"preprocessed_"+str(n_samples)+ ".npz")
        self._vocab_file = os.path.join(data_dir,"vocab_" + str(n_samples) + ".pkl")
        self._tensors = None
        self._sentiments = None
        self._lengths = None
        self._vocab = None
        self.vocab_size = None

        # Prepare data
        if os.path.exists(self._preprocessed_file)and os.path.exists(self._vocab_file):
            print('Loading preprocessed files ...')
            self.__load_preprocessed()
        else:
            if ensure_preprocessed:
                raise ValueError('Unable to findpreprocessed files.')
            print('Reading data ...')
            self.__preprocess()
        # Split data in train, validation and test sets
        indices = np.arange(len(self._sentiments))
        x_tv, self._x_test, y_tv, self._y_test,tv_indices, test_indices = train_test_split(
            self._tensors,
            self._sentiments,
            indices,
            test_size=test_size,
            random_state=random_state,
            stratify=self._sentiments[:, 0])
            self._x_train,self._x_val,self._y_train,self._y_val,train_indices,val_indices= train_test_split(x_tv, y_tv, tv_indices, test_size=val_samples,random_state = random_state,
               stratify=y_tv[:, 0])
        self._val_indices = val_indices
        self._test_indices = test_indices
        self._train_lengths = self._lengths[train_indices]
        self._val_lengths = self._lengths[val_indices]
        self._test_lengths = self._lengths[test_indices]
        self._current_index = 0
        self._epoch_completed = 0 
```

现在让我们看看前面方法的签名。我们从`_preprocess()`方法开始,该方法从`data_dir` / `data.csv`加载数据,预处理每个加载的样本,并存储中间文件以避免以后进行预处理。工作流程如下:

1.  加载数据
2.  清理示例文本
3.  准备词汇词典
4.  删除最不常见的单词(它们可能是语法错误),将样本编码为张量,并根据`sequence_len`用零填充每个张量
5.  保存中间文件
6.  存储样本长度以备将来使用

现在让我们看看下面的代码块,它代表了前面的工作流程:

```py
def __preprocess(self):
    data = pd.read_csv(self._input_file, nrows=self._n_samples)
    self._sentiments = np.squeeze(data.as_matrix(columns=['Sentiment']))
    self._sentiments = np.eye(2)[self._sentiments]
    samples = data.as_matrix(columns=['SentimentText'])[:, 0]
    samples = self.__clean_samples(samples)
    vocab = dict()
    vocab[''] = (0, len(samples))  # add empty word
    for sample in samples:
        sample_words = sample.split()
        for word in list(set(sample_words)):  # distinct words
            value = vocab.get(word)
            if value is None:
                vocab[word] = (-1, 1)
            else:
                encoding, count = value
                vocab[word] = (-1, count + 1)
      sample_lengths = []
      tensors = []
      word_count = 1
      for sample in samples:
          sample_words = sample.split()
          encoded_sample = []
          for word in list(set(sample_words)):  # distinct words 
              value = vocab.get(word)
              if value is not None:
                  encoding, count = value
                  if count / len(samples) > 0.0001:
                      if encoding == -1:
                          encoding = word_count
                          vocab[word] = (encoding, count)
                          word_count += 1
                      encoded_sample += [encoding]
                  else:
                      del vocab[word]
          tensors += [encoded_sample]
          sample_lengths += [len(encoded_sample)]
      self.vocab_size = len(vocab)
      self._vocab = vocab
      self._lengths = np.array(sample_lengths)
      self.sequence_len, self._tensors = self.__apply_to_zeros(tensors, self.sequence_len)
      with open(self._vocab_file, 'wb') as f:
          pickle.dump(self._vocab, f)
      np.savez(self._preprocessed_file, tensors=self._tensors, lengths=self._lengths, sentiments=self._sentiments)
```

接下来,我们调用前面的方法并加载中间文件,避免数据预处理:

```py
def __load_preprocessed(self):
    with open(self._vocab_file, 'rb') as f:
        self._vocab = pickle.load(f)
    self.vocab_size = len(self._vocab)
    load_dict = np.load(self._preprocessed_file)
    self._lengths = load_dict['lengths']
    self._tensors = load_dict['tensors']
    self._sentiments = load_dict['sentiments']
    self.sequence_len = len(self._tensors[0])
```

一旦我们预处理数据集,下一个任务就是清理样本。工作流程如下:

1.  准备正则表达式模式。
2.  清洁每个样本。
3.  恢复 HTML 字符。
4.  删除@users 和 URL。
5.  转换为小写。
6.  删除标点符号。
7.  用 C 替换 CC(C +)(连续出现两次以上的字符)
8.  删除停用词。

现在让我们以编程方式编写上述步骤。为此,我们有以下函数:

```py
def __clean_samples(self, samples):
    print('Cleaning samples ...')
    ret = []
    reg_punct = '[' + re.escape(''.join(string.punctuation)) + ']'
    if self._stopwords_file is not None:
        stopwords = self.__read_stopwords()
        sw_pattern = re.compile(r'\b(' + '|'.join(stopwords) + r')\b')
    for sample in samples:
        text = html.unescape(sample)
        words = text.split()
        words = [word for word in words if not word.startswith('@') and not word.startswith('http://')]
        text = ' '.join(words)
        text = text.lower()
        text = re.sub(reg_punct, ' ', text)
        text = re.sub(r'([a-z])\1{2,}', r'\1', text)
        if stopwords is not None:
            text = sw_pattern.sub('', text)
        ret += [text]
    return ret
```

W
wizardforcel 已提交
1051
`__apply_to_zeros()`方法返回使用的`padding_length`和填充张量的 NumPy 数组。首先,它找到最大长度`m`,并确保`m>=sequence_len`。然后根据`sequence_len`用零填充列表:
W
wizardforcel 已提交
1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078

```py
def __apply_to_zeros(self, lst, sequence_len=None):
    inner_max_len = max(map(len, lst))
    if sequence_len is not None:
        if inner_max_len > sequence_len:
            raise Exception('Error: Provided sequence length is not sufficient')
        else:
            inner_max_len = sequence_len
result = np.zeros([len(lst), inner_max_len], np.int32)
for i, row in enumerate(lst):
    for j, val in enumerate(row):
        result[i][j] = val
return inner_max_len, result
```

下一个任务是删除所有停用词(在`data` / `StopWords.txt file`中提供)。此方法返回停用词列表:

```py
def __read_stopwords(self):
    if self._stopwords_file is None:
        return None
    with open(self._stopwords_file, mode='r') as f:
        stopwords = f.read().splitlines()
    return stopwords
```

W
wizardforcel 已提交
1079
`next_batch()`方法将`batch_size>0`作为包含的样本数,在完成周期后返回批量大小样本(`text_tensor``text_target``text_length`),并随机抽取训练样本:
W
wizardforcel 已提交
1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317

```py
def next_batch(self, batch_size):
    start = self._current_index
    self._current_index += batch_size
    if self._current_index > len(self._y_train):
        self._epoch_completed += 1
        ind = np.arange(len(self._y_train))
        np.random.shuffle(ind)
        self._x_train = self._x_train[ind]
        self._y_train = self._y_train[ind]
        self._train_lengths = self._train_lengths[ind]
        start = 0
        self._current_index = batch_size
    end = self._current_index
    return self._x_train[start:end], self._y_train[start:end], self._train_lengths[start:end]
```

然后使用称为`get_val_data()`的下一个方法来获取在训练期间使用的验证集。它接受原始文本并返回验证数据。默认情况下,它返回`original_text``original_samples``text_tensor``text_target``text_length`),否则返回`text_tensor``text_target``text_length`

```py
def get_val_data(self, original_text=False):
    if original_text:
        data = pd.read_csv(self._input_file, nrows=self._n_samples)
        samples = data.as_matrix(columns=['SentimentText'])[:, 0]
        return samples[self._val_indices], self._x_val, self._y_val, self._val_lengths
        return self._x_val, self._y_val, self._val_lengths
```

最后,  是一个名为`get_test_data()`的附加方法,用于准备将在模型评估期间使用的测试集:

```py
    def get_test_data(self, original_text=False):
        if original_text:
            data = pd.read_csv(self._input_file, nrows=self._n_samples)
            samples = data.as_matrix(columns=['SentimentText'])[:, 0]
            return samples[self._test_indices], self._x_test, self._y_test, self._test_lengths
        return self._x_test, self._y_test, self._test_lengths
```

现在我们准备数据,以便 LSTM 网络可以提供它:

```py
lstm_model = LSTM_RNN_Network(hidden_size=[hidden_size],
                              vocab_size=data_lstm.vocab_size,
                              embedding_size=embedding_size,
                              max_length=data_lstm.sequence_len,
                              learning_rate=learning_rate)
```

在前面的代码段中,`LSTM_RNN_Network`是一个包含多个函数和构造函数的类,可帮助我们创建 LSTM 网络。即将推出的构造函数构建了 TensorFlow LSTM 模型。它需要以下参数:

*   `hidden_size`:一个数组,保存 rnn 层的 LSTM 单元中的单元数
*   `vocab_size`:样本中的词汇量大小
*   `embedding_size`:将使用此大小的向量对单词进行编码
*   `max_length`:输入张量的最大长度
*   `n_classes`:分类类的数量
*   `learning_rate`:RMSProp 算法的学习率
*   `random_state`:dropout 的随机状态

构造函数的代码如下:

```py
def __init__(self, hidden_size, vocab_size, embedding_size, max_length, n_classes=2, learning_rate=0.01, random_state=None):
    # Build TensorFlow graph
    self.input = self.__input(max_length)
    self.seq_len = self.__seq_len()
    self.target = self.__target(n_classes)
    self.dropout_keep_prob = self.__dropout_keep_prob()
    self.word_embeddings = self.__word_embeddings(self.input, vocab_size, embedding_size, random_state)
    self.scores = self.__scores(self.word_embeddings, self.seq_len, hidden_size, n_classes, self.dropout_keep_prob,
                                random_state)
        self.predict = self.__predict(self.scores)
        self.losses = self.__losses(self.scores, self.target)
        self.loss = self.__loss(self.losses)
        self.train_step = self.__train_step(learning_rate, self.loss)
        self.accuracy = self.__accuracy(self.predict, self.target)
        self.merged = tf.summary.merge_all()
```

下一个函数被称为`_input()`,它采用一个名为 param `max_length`的参数,它是输入张量的最大长度。然后它返回一个输入占位符,其形状为`[batch_size, max_length]`,用于 TensorFlow 计算:

```py
    def __input(self, max_length):
        return tf.placeholder(tf.int32, [None, max_length], name='input')
```

接下来,`_seq_len()`函数返回一个形状为`[batch_size]`的序列长度占位符。它保持给定批次中每个张量的实际长度,允许动态序列长度:

```py
def __seq_len(self):
    return tf.placeholder(tf.int32, [None], name='lengths')
```

下一个函数称为`_target()`。它需要一个名为 param `n_classes`的参数,它包含分类类的数量。最后,它返回形状为`[batch_size, n_classes]`的目标占位符:

```py
def __target(self, n_classes):
    return tf.placeholder(tf.float32, [None, n_classes], name='target')
```

`_dropout_keep_prob()`返回一个持有 dropout 的占位符保持概率以减少过拟合:

```py
def __dropout_keep_prob(self):
    return tf.placeholder(tf.float32, name='dropout_keep_prob')
```

`_cell()`方法用于构建带有压差包装器的 LSTM 单元。它需要以下参数:

*   `hidden_size`:它是 LSTM 单元中的单元数
*   `dropout_keep_prob`:这表示持有 dropout 保持概率的张量
*   `seed`:它是一个可选值,可确保 dropout 包装器的随机状态计算的可重现性。

最后,它返回一个带有 dropout 包装器的 LSTM 单元:

```py
def __cell(self, hidden_size, dropout_keep_prob, seed=None):
    lstm_cell = tf.nn.rnn_cell.LSTMCell(hidden_size, state_is_tuple=True)
    dropout_cell = tf.nn.rnn_cell.DropoutWrapper(lstm_cell, input_keep_prob=dropout_keep_prob, output_keep_prob = dropout_keep_prob, seed=seed)
        return dropout_cell
```

一旦我们创建了 LSTM 单元格,我们就可以创建输入标记的嵌入。为此,`__word_embeddings()`可以解决这个问题。它构建一个形状为`[vocab_size, embedding_size]`的嵌入层,输入参数如`x`,它是形状`[batch_size, max_length]`的输入。 `vocab_size`是词汇量大小,即可能出现在样本中的可能单词的数量。 `embedding_size`是将使用此大小的向量表示的单词,种子是可选的,但确保嵌入初始化的随机状态。

最后,它返回具有形状`[batch_size, max_length, embedding_size]`的嵌入查找张量:

```py
def __word_embeddings(self, x, vocab_size, embedding_size, seed=None):
    with tf.name_scope('word_embeddings'):
        embeddings = tf.get_variable("embeddings",shape=[vocab_size, embedding_size], dtype=tf.float32, initializer=None, regularizer=None, trainable=True, collections=None)
        embedded_words = tf.nn.embedding_lookup(embeddings, x)
    return embedded_words
```

`__rnn_layer ()`方法创建 LSTM 层。它需要几个输入参数,这里描述:

*   `hidden_size`:这是 LSTM 单元中的单元数
*   `x`:这是带形状的输入
*   `seq_len`:这是具有形状的序列长度张量
*   `dropout_keep_prob`:这是持有 dropout 保持概率的张量
*   `variable_scope`:这是变量范围的名称(默认层是`rnn_layer`
*   `random_state`:这是 dropout 包装器的随机状态

最后,它返回形状为`[batch_size, max_seq_len, hidden_size]`的输出:

```py
def __rnn_layer(self, hidden_size, x, seq_len, dropout_keep_prob, variable_scope=None, random_state=None):
    with tf.variable_scope(variable_scope, default_name='rnn_layer'):
        lstm_cell = self.__cell(hidden_size, dropout_keep_prob, random_state)
        outputs, _ = tf.nn.dynamic_rnn(lstm_cell, x, dtype=tf.float32, sequence_length=seq_len)
    return outputs
```

`_score()`方法用于计算网络输出。它需要几个输入参数,如下所示:

*   `embedded_words`:这是具有形状`[batch_size, max_length, embedding_size]`的嵌入查找张量
*   `seq_len`:这是形状`[batch_size]`的序列长度张量
*   `hidden_size`:这是一个数组,其中包含每个 RNN 层中 LSTM 单元中的单元数
*   `n_classes`:这是分类的数量
*   `dropout_keep_prob`:这是持有 dropout 保持概率的张量
*   `random_state`:这是一个可选参数,但它可用于确保 dropout 包装器的随机状态

最后,`_score()`方法返回具有形状`[batch_size, n_classes]`的每个类的线性激活:

```py
def __scores(self, embedded_words, seq_len, hidden_size, n_classes, dropout_keep_prob, random_state=None):
    outputs = embedded_words
    for h in hidden_size:
        outputs = self.__rnn_layer(h, outputs, seq_len, dropout_keep_prob)
    outputs = tf.reduce_mean(outputs, axis=[1])
    with tf.name_scope('final_layer/weights'):
        w = tf.get_variable("w", shape=[hidden_size[-1], n_classes], dtype=tf.float32, initializer=None, regularizer=None, trainable=True, collections=None)
        self.variable_summaries(w, 'final_layer/weights')
    with tf.name_scope('final_layer/biases'):
        b = tf.get_variable("b", shape=[n_classes], dtype=tf.float32, initializer=None, regularizer=None,trainable=True, collections=None)
        self.variable_summaries(b, 'final_layer/biases')
        with tf.name_scope('final_layer/wx_plus_b'):
            scores = tf.nn.xw_plus_b(outputs, w, b, name='scores')
            tf.summary.histogram('final_layer/wx_plus_b', scores)
        return scores
```

`_predict()`方法将得分作为具有形状`[batch_size, n_classes]`的每个类的线性激活,并以形状`[batch_size, n_classes]`返回 softmax(以`[0, 1]`的比例标准化得分)激活:

```py
def __predict(self, scores):
    with tf.name_scope('final_layer/softmax'):
        softmax = tf.nn.softmax(scores, name='predictions')
        tf.summary.histogram('final_layer/softmax', softmax)
    return softmax
```

`_losses()`方法返回具有形状`[batch_size]`的交叉熵损失(因为 softmax 用作激活函数)。它还需要两个参数,例如得分,作为具有形状`[batch_size, n_classes]`的每个类的线性激活和具有形状`[batch_size, n_classes]`的目标张量:

```py
def __losses(self, scores, target):
        with tf.name_scope('cross_entropy'):
            cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(logits=scores, labels=target, name='cross_entropy')
        return cross_entropy
```

`_loss()`函数计算并返回平均交叉熵损失。它只需要一个参数,称为损耗,它表示形状`[batch_size]`的交叉熵损失,并由前一个函数计算:

```py
def __loss(self, losses):
    with tf.name_scope('loss'):
        loss = tf.reduce_mean(losses, name='loss')
        tf.summary.scalar('loss', loss)
    return loss
```

现在,`_train_step()`计算并返回`RMSProp`训练步骤操作。它需要两个参数,`learning_rate`,这是`RMSProp`优化器的学习率;和前一个函数计算的平均交叉熵损失:

```py
def __train_step(self, learning_rate, loss):
    return tf.train.RMSPropOptimizer(learning_rate).minimize(loss)
```

评估表现时,`_accuracy()`函数计算分类的准确率。它需要三个参数,预测,softmax 激活具有哪种形状`[batch_size, n_classes]`;和具有形状`[batch_size, n_classes]`的目标张量和当前批次中获得的平均精度:

```py
def __accuracy(self, predict, target):
    with tf.name_scope('accuracy'):
        correct_pred = tf.equal(tf.argmax(predict, 1), tf.argmax(target, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32), name='accuracy')
        tf.summary.scalar('accuracy', accuracy)
    return accuracy
```

下一个函数被称为`initialize_all_variable()`,正如您可能猜到的那样,它初始化所有变量:

```py
def initialize_all_variables(self):
    return tf.global_variables_initializer()
```

最后,我们有一个名为`variable_summaries()`的静态方法,它将大量摘要附加到 TensorBoard 可视化的张量上。它需要以下参数:
W
wizardforcel 已提交
1318 1319

```py
W
wizardforcel 已提交
1320 1321 1322
var: is the variable to summarize
mean: mean of the summary name.
```
W
wizardforcel 已提交
1323

W
wizardforcel 已提交
1324
签名如下:
W
wizardforcel 已提交
1325

W
wizardforcel 已提交
1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338
```py
    @staticmethod
    def variable_summaries(var, name):
        with tf.name_scope('summaries'):
            mean = tf.reduce_mean(var)
            tf.summary.scalar('mean/' + name, mean)
            with tf.name_scope('stddev'):
                stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
            tf.summary.scalar('stddev/' + name, stddev)
            tf.summary.scalar('max/' + name, tf.reduce_max(var))
            tf.summary.scalar('min/' + name, tf.reduce_min(var))
            tf.summary.histogram(name, var)
```
W
wizardforcel 已提交
1339

W
wizardforcel 已提交
1340
现在我们需要在训练模型之前创建一个 TensorFlow 会话:
W
wizardforcel 已提交
1341

W
wizardforcel 已提交
1342 1343
```py
sess = tf.Session()
W
wizardforcel 已提交
1344 1345
```

W
wizardforcel 已提交
1346
让我们初始化所有变量:
W
wizardforcel 已提交
1347 1348 1349

```py
init_op = tf.global_variables_initializer()
W
wizardforcel 已提交
1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362
sess.run(init_op)
```

然后我们保存  TensorFlow 模型以备将来使用:

```py
saver = tf.train.Saver()
```

现在让我们准备训练集:

```py
x_val, y_val, val_seq_len = data_lstm.get_val_data()
W
wizardforcel 已提交
1363 1364
```

W
wizardforcel 已提交
1365
现在我们应该编写 TensorFlow 图计算的日志:
W
wizardforcel 已提交
1366 1367

```py
W
wizardforcel 已提交
1368
train_writer.add_graph(lstm_model.input.graph)
W
wizardforcel 已提交
1369 1370
```

W
wizardforcel 已提交
1371
此外,我们可以创建一些空列表来保存训练损失,验证损失和步骤,以便我们以图形方式查看它们:
W
wizardforcel 已提交
1372 1373

```py
W
wizardforcel 已提交
1374 1375 1376 1377 1378
train_loss_list = []
val_loss_list = []
step_list = []
sub_step_list = []
step = 0
W
wizardforcel 已提交
1379 1380
```

W
wizardforcel 已提交
1381
现在我们开始训练。在每个步骤中,我们记录训练错误。验证错误记录在每个子步骤中:
W
wizardforcel 已提交
1382 1383

```py
W
wizardforcel 已提交
1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405
for i in range(train_steps):
    x_train, y_train, train_seq_len = data_lstm.next_batch(batch_size)
    train_loss, _, summary = sess.run([lstm_model.loss, lstm_model.train_step, lstm_model.merged],
                                      feed_dict={lstm_model.input: x_train,
                                                 lstm_model.target: y_train,
                                                 lstm_model.seq_len: train_seq_len,
                                                 lstm_model.dropout_keep_prob:dropout_keep_prob})
    train_writer.add_summary(summary, i)  # Write train summary for step i (TensorBoard visualization)
    train_loss_list.append(train_loss)
    step_list.append(i)
        print('{0}/{1} train loss: {2:.4f}'.format(i + 1, FLAGS.train_steps, train_loss))
    if (i + 1) %validate_every == 0:
        val_loss, accuracy, summary = sess.run([lstm_model.loss, lstm_model.accuracy, lstm_model.merged],
                                               feed_dict={lstm_model.input: x_val,
                                                          lstm_model.target: y_val,
                                                          lstm_model.seq_len: val_seq_len,
                                                          lstm_model.dropout_keep_prob: 1})
        validation_writer.add_summary(summary, i)  
        print('   validation loss: {0:.4f} (accuracy {1:.4f})'.format(val_loss, accuracy))
        step = step + 1
        val_loss_list.append(val_loss)
        sub_step_list.append(step)
W
wizardforcel 已提交
1406 1407
```

W
wizardforcel 已提交
1408
以下是上述代码的输出:
W
wizardforcel 已提交
1409 1410 1411 1412

```py
>>>

W
wizardforcel 已提交
1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432
1/1000 train loss: 0.6883
2/1000 train loss: 0.6879
3/1000 train loss: 0.6943

99/1000 train loss: 0.4870
100/1000 train loss: 0.5307
validation loss: 0.4018 (accuracy 0.9200)

199/1000 train loss: 0.1103
200/1000 train loss: 0.1032
validation loss: 0.0607 (accuracy 0.9800)

299/1000 train loss: 0.0292
300/1000 train loss: 0.0266
validation loss: 0.0417 (accuracy 0.9800)

998/1000 train loss: 0.0021
999/1000 train loss: 0.0007
1000/1000 train loss: 0.0004
validation loss: 0.0939 (accuracy 0.9700)
W
wizardforcel 已提交
1433 1434 1435

```

W
wizardforcel 已提交
1436
上述代码打印了训练和验证错误。训练结束后,模型将保存到具有唯一 ID 的检查点目录中:
W
wizardforcel 已提交
1437 1438

```py
W
wizardforcel 已提交
1439 1440 1441
checkpoint_file = '{}/model.ckpt'.format(model_dir)
save_path = saver.save(sess, checkpoint_file)
print('Model saved in: {0}'.format(model_dir))
W
wizardforcel 已提交
1442 1443
```

W
wizardforcel 已提交
1444
以下是上述代码的输出:
W
wizardforcel 已提交
1445 1446 1447

```py
>>>
W
wizardforcel 已提交
1448 1449 1450 1451 1452
Model saved in checkpoints/1517781236

```

检查点目录将至少生成三个文件:
W
wizardforcel 已提交
1453

W
wizardforcel 已提交
1454 1455 1456
*   `config.pkl`包含用于训练模型的参数。
*   `model.ckpt`包含模型的权重。
*   `model.ckpt.meta`包含 TensorFlow 图定义。
W
wizardforcel 已提交
1457

W
wizardforcel 已提交
1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475
让我们看看训练是如何进行的,也就是说,训练和验证损失如下:

```py
# Plot loss over time
plt.plot(step_list, train_loss_list, 'r--', label='LSTM training loss per iteration', linewidth=4)
plt.title('LSTM training loss per iteration')
plt.xlabel('Iteration')
plt.ylabel('Training loss')
plt.legend(loc='upper right')
plt.show()

# Plot accuracy over time
plt.plot(sub_step_list, val_loss_list, 'r--', label='LSTM validation loss per validating interval', linewidth=4)
plt.title('LSTM validation loss per validation interval')
plt.xlabel('Validation interval')
plt.ylabel('Validation loss')
plt.legend(loc='upper left')
plt.show()
W
wizardforcel 已提交
1476 1477
```

W
wizardforcel 已提交
1478
以下是上述代码的输出:
W
wizardforcel 已提交
1479 1480

```py
W
wizardforcel 已提交
1481
>>>
W
wizardforcel 已提交
1482

W
wizardforcel 已提交
1483
```
W
wizardforcel 已提交
1484

W
wizardforcel 已提交
1485
![LSTM model training](img/B09698_06_22.jpg)
W
wizardforcel 已提交
1486

W
wizardforcel 已提交
1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672
图 21:a)测试集上每次迭代的 LSTM 训练损失,b)每个验证间隔的 LSTM 验证损失

如果我们检查前面的绘图,很明显训练阶段和验证阶段的训练都很顺利,只有 1000 步。然而,读者应加大训练步骤,调整超参数,看看它是如何去。

## 通过 TensorBoard 进行可视化

现在让我们观察 TensorBoard 上的 TensorFlow 计算图。只需执行以下命令并在`localhost:6006/`访问 TensorBoard:

```py
tensorboard --logdir /home/logs/
```

图选项卡显示执行图,包括使用的梯度,`loss_op`,精度,最终层,使用的优化器(在我们的例子中是`RMSPro`),LSTM 层(即 RNN 层),嵌入层和`save_op`

![Visualizing through TensorBoard](img/B09698_06_23.jpg)

图 22:TensorBoard 上的执行图

执行图显示,我们为这种基于 LSTM 的分类器进行的情感分析计算是非常透明的。我们还可以观察层中的验证,训练损失,准确率和操作:

![Visualizing through TensorBoard](img/B09698_06_24.jpg)

图 23:TensorBoard 层中的验证,训练损失,准确率和操作

## LSTM 模型评估

我们已经训练了并保存了我们的 LSTM 模型。我们可以轻松恢复训练模型并进行一些评估。我们需要准备测试集并使用先前训练的 TensorFlow 模型对其进行预测。我们马上做吧。首先,我们加载所需的模型:

```py
import tensorflow as tf
from data_preparation import Preprocessing
   import pickle
Then we load to show the checkpoint directory where the model was saved. For our case, it was checkpoints/1505148083.
```

### 注意

对于此步骤,使用以下命令执行`predict.py`脚本:

```py
$ python3 predict.py --checkpoints_dir checkpoints/1517781236

```

```py
# Change this path based on output by 'python3 train.py' 
checkpoints_dir = 'checkpoints/1517781236' 

ifcheckpoints_dir is None:
    raise ValueError('Please, a valid checkpoints directory is required (--checkpoints_dir <file name>)')
```

现在加载测试数据集并准备它以评估模型:

```py
data_lstm = Preprocessing(data_dir=data_dir,
                 stopwords_file=stopwords_file,
                 sequence_len=sequence_len,
                 n_samples=n_samples,
                 test_size=test_size,
                 val_samples=batch_size,
                 random_state=random_state,
                 ensure_preprocessed=True)
```

在上面的代码中,完全按照我们在训练步骤中的操作使用以下参数:

```py
data_dir = 'data/' # Data directory containing 'data.csv'
stopwords_file = 'data/stopwords.txt' # Path to stopwords file.
sequence_len = None # Maximum sequence length
n_samples= None # Set n_samples=None to use the whole dataset
test_size = 0.2
batch_size = 100 #Batch size
random_state = 0 # Random state used for data splitting. Default is 0
```

此评估方法的工作流程如下:

1.  首先,导入元图并使用测试数据评估模型
2.  为计算创建 TensorFlow 会话
3.  导入图并恢复其权重
4.  恢复输入/输出张量
5.  执行预测
6.  最后,我们在简单的测试集上打印精度和结果

步骤 1 之前已经完成  。此代码执行步骤 2 到 5:

```py
original_text, x_test, y_test, test_seq_len = data_lstm.get_test_data(original_text=True)
graph = tf.Graph()
with graph.as_default():
    sess = tf.Session()    
    print('Restoring graph ...')
    saver = tf.train.import_meta_graph("{}/model.ckpt.meta".format(FLAGS.checkpoints_dir))
    saver.restore(sess, ("{}/model.ckpt".format(checkpoints_dir)))
    input = graph.get_operation_by_name('input').outputs[0]
    target = graph.get_operation_by_name('target').outputs[0]
    seq_len = graph.get_operation_by_name('lengths').outputs[0]
    dropout_keep_prob = graph.get_operation_by_name('dropout_keep_prob').outputs[0]
    predict = graph.get_operation_by_name('final_layer/softmax/predictions').outputs[0]
    accuracy = graph.get_operation_by_name('accuracy/accuracy').outputs[0]
    pred, acc = sess.run([predict, accuracy],
                         feed_dict={input: x_test,
                                    target: y_test,
                                    seq_len: test_seq_len,
                                    dropout_keep_prob: 1})
    print("Evaluation done.")
```

以下是上述代码的输出:

```py
>>>
Restoring graph ...
The evaluation was done.

```

做得好!训练结束了,让我们打印结果:

```py
print('\nAccuracy: {0:.4f}\n'.format(acc))
for i in range(100):
    print('Sample: {0}'.format(original_text[i]))
    print('Predicted sentiment: [{0:.4f}, {1:.4f}]'.format(pred[i, 0], pred[i, 1]))
    print('Real sentiment: {0}\n'.format(y_test[i]))
```

以下是上述代码的输出:

```py
>>>
Accuracy: 0.9858

Sample: I loved the Da Vinci code, but it raises many theological questions most of which are very absurd...
Predicted sentiment: [0.0000, 1.0000]
Real sentiment: [0\. 1.]

Sample: I'm sorry I hate to read Harry Potter, but I love the movies!
Predicted sentiment: [1.0000, 0.0000]
Real sentiment: [1\. 0.]

Sample: I LOVE Brokeback Mountain...
Predicted sentiment: [0.0002, 0.9998]
Real sentiment: [0\. 1.]

Sample: We also went to see Brokeback Mountain which totally SUCKED!!!
Predicted sentiment: [1.0000, 0.0000]
Real sentiment: [1\. 0.]

```

精度高于 98%。这太棒了!但是,您可以尝试使用调整的超参数迭代训练以获得更高的迭代次数,您可能会获得更高的准确率。我把它留给读者。

在下一节中,我们将看到如何使用 LSTM 开发更高级的 ML 项目,这被称为使用智能手机数据集的人类活动识别。简而言之,我们的 ML 模型将能够将人类运动分为六类:走路,走楼上,走楼下,坐,站立和铺设。

# 使用 LSTM 模型识别人类活动

人类活动识别(HAR)数据库[​​HTG0] 是通过对携带带有嵌入式惯性传感器的腰部智能手机的 30 名参加日常生活活动(ADL)的参与者进行测量而建立的。目标是将他们的活动分类为前面提到的六个类别之一。

## 数据集描述

实验在一组 30 名志愿者中进行,年龄范围为 19-48 岁。每个人都在腰上戴着三星 Galaxy S II 智能手机,完成了六项活动(步行,走楼上,走楼下,坐着,站着,躺着)。使用加速度计和陀螺仪,作者以 50 Hz 的恒定速率捕获了 3 轴线性加速度和 3 轴角速度。

仅使用两个传感器,加速度计和陀螺仪。通过应用噪声滤波器对传感器信号进行预处理,然后在 2.56 秒的固定宽度滑动窗口中采样,重叠 50%。这样每个窗口提供 128 个读数。来自传感器加速度信号的重力和身体运动分量通过巴特沃斯低通滤波器分离成身体加速度和重力。

欲了解更多信息,请参阅本文:Davide Anguita,Alessandro Ghio,Luca Oneto,Xavier Parra 和 Jorge L. Reyes-Ortiz,使用智能手机进行人类活动识别的公共领域数据集和第 21 届关于人工神经网络的欧洲研讨会,计算智能和机器学习,ESANN 2013.比利时布鲁日 24-26,2013 年 4 月。

为简单起见,假设重力仅具有少量低频分量。因此,使用 0.3Hz 截止频率的滤波器。从每个窗口,通过计算来自时域和频域的变量找到特征向量。

已经对实验进行了视频记录以便于手动标记数据。数据集已被随机分为两组,其中 70%的志愿者被选中用于训练数据,30%用于测试数据。当我浏览数据集时,训练集和测试集都具有以下文件结构:

![Dataset description](img/B09698_06_25.jpg)

图 24:HAR 数据集文件结构

对于数据集中的每条记录,提供以下内容:

*   来自加速度计的三轴加速度和估计的车身加速度
*   来自陀螺仪传感器的三轴角速度
*   具有时域和频域变量的 561 特征向量
*   它的活动标签
*   进行实验的受试者的标识符

因此,我们知道需要解决的问题。现在是探索技术和相关挑战的时候了。
W
wizardforcel 已提交
1673

W
wizardforcel 已提交
1674
## HAR 的 LSTM 模型的工作流程
W
wizardforcel 已提交
1675

W
wizardforcel 已提交
1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997
整个算法有以下工作流程:

1.  加载数据。
2.  定义超参数。
3.  使用命令式编程和超参数设置 LSTM 模型。
4.  应用批量训练。也就是说,选择一批数据,将其提供给模型,然后在一些迭代之后,评估模型并打印批次损失和准确率。
5.  输出图的训练和测试误差。

可以遵循上述步骤并构建一个管道:

![Workflow of the LSTM model for HAR](img/B09698_06_26.jpg)

图 25:用于 HAR 的基于 LSTM 的管道

## 为 HAR 实现 LSTM 模型

首先,我们导入所需的包和模块:

```py
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import tensorflow as tf
from sklearn import metrics
from tensorflow.python.framework import ops
import warnings
import random
warnings.filterwarnings("ignore")
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
```

如前所述,`INPUT_SIGNAL_TYPES`包含一些有用的常量。它们是神经网络的单独标准化输入特征:

```py
INPUT_SIGNAL_TYPES = [
    "body_acc_x_",
    "body_acc_y_",
    "body_acc_z_",
    "body_gyro_x_",
    "body_gyro_y_",
    "body_gyro_z_",
    "total_acc_x_",
    "total_acc_y_",
    "total_acc_z_"
]
```

标签在另一个数组中定义 - 这是用于学习如何分类的输出类:

```py
LABELS = [
    "WALKING",
    "WALKING_UPSTAIRS",
    "WALKING_DOWNSTAIRS",
    "SITTING",
    "STANDING",
    "LAYING"
]
```

我们现在假设您已经从[此链接](https://archive.ics.uci.edu/ml/machine-learning-databases/00240/UCI HAR Dataset.zip)下载了 HAR 数据集并输入了名为`UCIHARDataset`的文件夹(或者您可以选择听起来更合适的合适名称)。此外,我们需要提供训练和测试集的路径:

```py
DATASET_PATH = "UCIHARDataset/"
print("\n" + "Dataset is now located at: " + DATASET_PATH)

TRAIN = "train/"
TEST = "test/"
```

然后我们加载并根据由`[Array [Array [Float]]]`格式的`INPUT_SIGNAL_TYPES`数组定义的输入信号类型,映射每个`.txt`文件的数据。 `X`表示神经网络的训练和测试输入:

```py
def load_X(X_signals_paths):
    X_signals = []

    for signal_type_path in X_signals_paths:
        file = open(signal_type_path, 'r')
        # Read dataset from disk, dealing with text files' syntax
        X_signals.append(
            [np.array(serie, dtype=np.float32) for serie in [
                row.replace('  ', ' ').strip().split(' ') for row in file
            ]]
        )
        file.close()

    return np.transpose(np.array(X_signals), (1, 2, 0))

X_train_signals_paths = [DATASET_PATH + TRAIN + "Inertial Signals/" + signal + "train.txt" for signal in INPUT_SIGNAL_TYPES]
X_test_signals_paths = [DATASET_PATH + TEST + "Inertial Signals/" + signal + "test.txt" for signal in INPUT_SIGNAL_TYPES]

X_train = load_X(X_train_signals_paths)
X_test = load_X(X_test_signals_paths)
```

然后我们加载`y`,神经网络的训练和测试输出的标签:

```py
def load_y(y_path):
    file = open(y_path, 'r')
    # Read dataset from disk, dealing with text file's syntax
    y_ = np.array(
        [elem for elem in [
            row.replace('  ', ' ').strip().split(' ') for row in file
        ]],
        dtype=np.int32
    )
    file.close()

    # We subtract 1 to each output class for 0-based indexing 
    return y_ - 1

y_train_path = DATASET_PATH + TRAIN + "y_train.txt"
y_test_path = DATASET_PATH + TEST + "y_test.txt"

y_train = load_y(y_train_path)
y_test = load_y(y_test_path)
```

让我们看看一些数据集的统计数据,例如训练系列的数量(如前所述,每个系列之间有 50%的重叠),测试系列的数量,每个系列的时间步数,以及每个时间步输入参数:

```py
training_data_count = len(X_train)
test_data_count = len(X_test)
n_steps = len(X_train[0])  
n_input = len(X_train[0][0])  
print("Number of training series: "+ trainingDataCount)
print("Number of test series: "+ testDataCount)
print("Number of timestep per series: "+ nSteps)
print("Number of input parameters per timestep: "+ nInput) 
```

以下是上述代码的输出:

```py
>>>
Number of training series: 7352
Number of test series: 2947
Number of timestep per series: 128
Number of input parameters per timestep: 9

```

现在让我们为训练定义一些核心参数定义。整个神经网络的结构可以通过枚举这些参数和使用 LSTM 这一事实来概括:

```py
n_hidden = 32 # Hidden layer num of features
n_classes = 6 # Total classes (should go up, or should go down)

learning_rate = 0.0025
lambda_loss_amount = 0.0015
training_iters = training_data_count * 300 #Iterate 300 times 
batch_size = 1500
display_iter = 30000 # to show test set accuracy during training
```

我们已经定义了所有核心参数和网络参数。这些是随机选择。我没有进行超参数调整,但仍然运行良好。因此,我建议使用网格搜索技术调整这些超参数。有许多在线资料可供使用。

然而,在构建 LSTM 网络并开始训练之前,让我们打印一些调试信息,以确保执行不会中途停止:

```py
print("Some useful info to get an insight on dataset's shape and normalization:")
print("(X shape, y shape, every X's mean, every X's standard deviation)")
print(X_test.shape, y_test.shape, np.mean(X_test), np.std(X_test))
print("The dataset is therefore properly normalized, as expected, but not yet one-hot encoded.")
```

以下是上述代码的输出:

```py
>>>
Some useful info to get an insight on dataset's shape and normalization:
(X shape, y shape, every X's mean, every X's standard deviation)
(2947, 128, 9) (2947, 1) 0.0991399 0.395671

```

数据集是  ,因此按预期正确标准化,但尚未进行单热编码。

现在训练数据集处于校正和标准化顺序,现在是构建 LSTM 网络的时候了。以下函数从给定参数返回 TensorFlow LSTM 网络。此外,两个 LSTM 单元堆叠在一起,这增加了神经网络的深度:

```py
def LSTM_RNN(_X, _weights, _biases):
    _X = tf.transpose(_X, [1,0,2])# permute n_steps & batch_size
    _X = tf.reshape(_X, [-1, n_input]) 
    _X = tf.nn.relu(tf.matmul(_X, _weights['hidden']) + _biases['hidden'])
    _X = tf.split(_X, n_steps, 0) 
    lstm_cell_1 = tf.nn.rnn_cell.BasicLSTMCell(n_hidden, forget_bias=1.0, state_is_tuple=True)
    lstm_cell_2 = tf.nn.rnn_cell.BasicLSTMCell(n_hidden, forget_bias=1.0, state_is_tuple=True)
    lstm_cells = tf.nn.rnn_cell.MultiRNNCell([lstm_cell_1, lstm_cell_2], state_is_tuple=True)
    outputs, states = tf.contrib.rnn.static_rnn(lstm_cells, _X, dtype=tf.float32)
    lstm_last_output = outputs[-1]
    return tf.matmul(lstm_last_output, _weights['out']) + _biases['out']
```

如果我们仔细查看前面的代码片段,我们可以看到我们得到了“多对一”样式分类器的最后一步输出特征。现在,问题是什么是多对一 RNN 分类器?好吧,类似于图 5,我们接受特征向量的时间序列(每个时间步长一个向量)并将它们转换为输出中的概率向量以进行分类。

现在我们已经能够构建我们的 LSTM 网络,我们需要将训练数据集准备成批量。以下函数从`(X|y)_train`数据中获取`batch_size`数据量:

```py
def extract_batch_size(_train, step, batch_size):
    shape = list(_train.shape)
    shape[0] = batch_size
    batch_s = np.empty(shape)
    for i in range(batch_size):
        index = ((step-1)*batch_size + i) % len(_train)
        batch_s[i] = _train[index]
    return batch_s
```

之后,我们需要将数字索引的输出标签编码为二元类别。然后我们用`batch_size`执行训练步骤。例如,`[[5], [0], [3]]`需要转换为类似于`[[0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0]]`的形状。好吧,我们可以用单热编码来做到这一点。以下方法执行完全相同的转换:

```py
def one_hot(y_):
    y_ = y_.reshape(len(y_))
    n_values = int(np.max(y_)) + 1
    return np.eye(n_values)[np.array(y_, dtype=np.int32)]
```

优秀的!我们的数据集准备就绪,因此我们可以开始构建网络。首先,我们为输入和标签创建两个单独的占位符:

```py
x = tf.placeholder(tf.float32, [None, n_steps, n_input])
y = tf.placeholder(tf.float32, [None, n_classes])
```

然后我们创建所需的权重向量:

```py
weights = {
    'hidden': tf.Variable(tf.random_normal([n_input, n_hidden])), 
    'out': tf.Variable(tf.random_normal([n_hidden, n_classes], mean=1.0))
}
```

然后我们创建所需的偏向量:

```py
biases = {
    'hidden': tf.Variable(tf.random_normal([n_hidden])),
    'out': tf.Variable(tf.random_normal([n_classes]))
}
```

然后我们通过传递输入张量,权重向量和偏置向量来构建模型,如下所示:

```py
pred = LSTM_RNN(x, weights, biases)
```

此外,我们还需要计算`cost` op,正则化,优化器和评估。我们使用 L2 损失进行正则化,这可以防止这种过度杀伤神经网络过度适应训练中的问题:

```py
l2 = lambda_loss_amount * sum(tf.nn.l2_loss(tf_var) for tf_var in tf.trainable_variables())

cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=y, logits=pred)) + l2

optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
correct_pred = tf.equal(tf.argmax(pred,1), tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
Great! So far, everything has been fine. Now we are ready to train the neural network. First, we create some lists to hold some training's performance:
```

```py
test_losses = []
test_accuracies = []
train_losses = []
train_accuracies = []
```

然后我们创建一个  TensorFlow 会话,启动图并初始化全局变量:

```py
sess = tf.InteractiveSession(config=tf.ConfigProto(log_device_placement=False))
init = tf.global_variables_initializer()
sess.run(init)
```

然后我们在每个循环中以`batch_size`数量的示例数据执行训练步骤。我们首先使用批量数据进行训练,然后我们仅在几个步骤评估网络以加快训练速度。另外,我们评估测试集(这里没有学习,只是诊断评估)。最后,我们打印结果:

```py
step = 1
while step * batch_size <= training_iters:
    batch_xs =  extract_batch_size(X_train, step, batch_size)
    batch_ys = one_hot(extract_batch_size(y_train, step, batch_size))
    _, loss, acc = sess.run(
        [optimizer, cost, accuracy],
        feed_dict={
            x: batch_xs,
            y: batch_ys
        }
    )
    train_losses.append(loss)
    train_accuracies.append(acc)
    if (step*batch_size % display_iter == 0) or (step == 1) or (step * batch_size > training_iters):
        print("Training iter #" + str(step*batch_size) + \":   Batch Loss = " + "{:.6f}".format(loss) + \", Accuracy = {}".format(acc))
        loss, acc = sess.run(
            [cost, accuracy], 
            feed_dict={
                x: X_test,
                y: one_hot(y_test)
            }
        )
        test_losses.append(loss)
        test_accuracies.append(acc)
        print("PERFORMANCE ON TEST SET: " + \
              "Batch Loss = {}".format(loss) + \
              ", Accuracy = {}".format(acc))
    step += 1
print("Optimization Finished!")
one_hot_predictions, accuracy, final_loss = sess.run(
    [pred, accuracy, cost],
    feed_dict={
        x: X_test,
        y: one_hot(y_test)
    })
test_losses.append(final_loss)
test_accuracies.append(accuracy)

print("FINAL RESULT: " + \
      "Batch Loss = {}".format(final_loss) + \
      ", Accuracy = {}".format(accuracy))
W
wizardforcel 已提交
1998 1999 2000 2001 2002 2003
```

以下是上述代码的输出:

```py
>>>
W
wizardforcel 已提交
2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032
Training iter #1500:   Batch Loss = 3.266330, Accuracy = 0.15733332931995392
PERFORMANCE ON TEST SET: Batch Loss = 2.6498606204986572, Accuracy = 0.15473362803459167
Training iter #30000:   Batch Loss = 1.538126, Accuracy = 0.6380000114440918
PERFORMANCE ON TEST SET: Batch Loss = 0.5507552623748779, Accuracy = 0.8924329876899719
Optimization Finished!
FINAL RESULT: Batch Loss = 0.6077192425727844, Accuracy = 0.8686800003051758

```

做得好! 训练进展顺利。但是,视觉概述会更有用:

```py
indep_train_axis = np.array(range(batch_size, (len(train_losses)+1)*batch_size, batch_size))
plt.plot(indep_train_axis, np.array(train_losses),     "b--", label="Train losses")
plt.plot(indep_train_axis, np.array(train_accuracies), "g--", label="Train accuracies")
indep_test_axis = np.append(np.array(range(batch_size, len(test_losses)*display_iter, display_iter)[:-1]),
    [training_iters])
plt.plot(indep_test_axis, np.array(test_losses),     "b-", label="Test losses")
plt.plot(indep_test_axis, np.array(test_accuracies), "g-", label="Test accuracies")
plt.title("Training session's progress over iterations")
plt.legend(loc='upper right', shadow=True)
plt.ylabel('Training Progress (Loss or Accuracy values)')
plt.xlabel('Training iteration')
plt.show()
```

以下是上述代码的输出:

```py
W
wizardforcel 已提交
2033 2034 2035 2036
>>>

```

W
wizardforcel 已提交
2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050
![Implementing an LSTM model for HAR](img/B09698_06_27.jpg)

图 26:迭代时的 LSTM 训练课程

我们需要计算其他表现指标,例如`accuracy``precision``recall``f1`度量:

```py
predictions = one_hot_predictions.argmax(1)
print("Testing Accuracy: {}%".format(100*accuracy))
print("")
print("Precision: {}%".format(100*metrics.precision_score(y_test, predictions, average="weighted")))
print("Recall: {}%".format(100*metrics.recall_score(y_test, predictions, average="weighted")))
print("f1_score: {}%".format(100*metrics.f1_score(y_test, predictions, average="weighted")))
```
W
wizardforcel 已提交
2051

W
wizardforcel 已提交
2052
以下是上述代码的输出:
W
wizardforcel 已提交
2053

W
wizardforcel 已提交
2054 2055 2056 2057 2058 2059
```py
>>>
Testing Accuracy: 89.51476216316223%
Precision: 89.65053428376297%
Recall: 89.51476077366813%
f1_score: 89.48593061935716%
W
wizardforcel 已提交
2060

W
wizardforcel 已提交
2061
```
W
wizardforcel 已提交
2062

W
wizardforcel 已提交
2063
由于我们正在接近的问题是多类分类,因此绘制混淆矩阵是有意义的:
W
wizardforcel 已提交
2064

W
wizardforcel 已提交
2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076
```py
print("")
print ("Showing Confusion Matrix")

cm = metrics.confusion_matrix(y_test, predictions)
df_cm = pd.DataFrame(cm, LABELS, LABELS)
plt.figure(figsize = (16,8))
plt.ylabel('True label')
plt.xlabel('Predicted label')
sn.heatmap(df_cm, annot=True, annot_kws={"size": 14}, fmt='g', linewidths=.5)
plt.show()
```
W
wizardforcel 已提交
2077

W
wizardforcel 已提交
2078
以下是上述代码的输出:
W
wizardforcel 已提交
2079

W
wizardforcel 已提交
2080 2081
```py
>>>
W
wizardforcel 已提交
2082

W
wizardforcel 已提交
2083
```
W
wizardforcel 已提交
2084

W
wizardforcel 已提交
2085
![Implementing an LSTM model for HAR](img/B09698_06_%2829%29.jpg)
W
wizardforcel 已提交
2086

W
wizardforcel 已提交
2087
图 27:多类混淆矩阵(预测与实际)
W
wizardforcel 已提交
2088

W
wizardforcel 已提交
2089
在混淆矩阵中,训练和测试数据不是在类之间平均分配,因此正常情况下,超过六分之一的数据在最后一类中被正确分类。话虽如此,我们已经设法达到约 87%的预测准确率。我们很快就会看到更多分析。它可能更高,但是训练是在 CPU 上进行的,因此它的精度很低,当然需要很长时间。因此,我建议你在 GPU 上训练,以获得更好的结果。此外,调整超参数可能是一个不错的选择。
W
wizardforcel 已提交
2090

W
wizardforcel 已提交
2091
# 总结
W
wizardforcel 已提交
2092

W
wizardforcel 已提交
2093
LSTM 网络配备了特殊的隐藏单元,称为存储单元,其目的是长时间记住先前的输入。这些单元在每个时刻采用先前状态和网络的当前输入作为输入。通过将它们与内存的当前内容相结合,并通过其他单元的门控机制决定保留什么以及从内存中删除什么,LSTM 已被证明是非常有用的并且是学习长期依赖性的有效方式。
W
wizardforcel 已提交
2094

W
wizardforcel 已提交
2095
在本章中,我们讨论了 RNN。我们看到了如何使用具有高时间依赖性的数据进行预测。我们看到了如何开发几种真实的预测模型,使用 RNN 和不同的架构变体使预测分析更容易。我们从 RNN 的理论背景开始。
W
wizardforcel 已提交
2096

W
wizardforcel 已提交
2097
然后我们看了几个例子,展示了一种实现图像分类预测模型,电影和产品情感分析以及 NLP 垃圾邮件预测的系统方法。然后我们看到了如何开发时间序列数据的预测模型。最后,我们看到了 RNN 用于人类活动识别的更高级应用,我们观察到分类准确率约为 87%。
W
wizardforcel 已提交
2098

W
wizardforcel 已提交
2099
DNN 以统一的方式构造,使得在网络的每一层,数千个相同的人工神经元执行相同的计算。因此,DNN 的架构非常适合 GPU 可以有效执行的计算类型。 GPU 具有优于 CPU 的额外优势;这些包括具有更多计算单元并具有更高的带宽用于存储器检索。
W
wizardforcel 已提交
2100

W
wizardforcel 已提交
2101
此外,在许多需要大量计算工作的深度学习应用中,可以利用 GPU 的图形特定功能来进一步加速计算。在下一章中,我们将看到如何使训练更快,更准确,甚至在节点之间分配。
W
wizardforcel 已提交
2102