长短期记忆网络(LSTM)与门控循环单元(GRU)的原理及实战应用
1. 长短期记忆网络(LSTM)
1.1 LSTM 简介
LSTM 是 RNN 的一种变体,能够学习长期依赖关系。它由 Hochreiter 和 Schmidhuber 首次提出,并经过众多研究人员的改进。LSTM 在各种问题上表现出色,是应用最广泛的 RNN 类型。
与简单 RNN 不同,LSTM 不是通过单个 tanh 层来实现循环,而是由四个特定的层相互作用。在训练过程中,LSTM 会学习这些门的参数,从而解决梯度消失问题。
1.2 LSTM 门控机制
LSTM 包含输入门(i)、遗忘门(f)、输出门(o)和内部隐藏状态(g)。以下是它们的作用:
-
遗忘门(f)
:决定上一时刻的隐藏状态 $h_{t - 1}$ 有多少可以通过。
-
输入门(i)
:决定当前输入 $x_t$ 新计算的状态有多少可以通过。
-
输出门(o)
:决定内部状态有多少可以暴露给下一层。
-
内部隐藏状态(g)
:基于当前输入 $x_t$ 和上一隐藏状态 $h_{t - 1}$ 计算得出。
通过这些门,LSTM 可以有效地结合之前的记忆和新的输入。例如,将遗忘门设置为 0 可以忽略旧记忆,将输入门设置为 0 可以忽略新计算的状态。最后,时刻 t 的隐藏状态 $h_t$ 通过将记忆 $c_t$ 与输出门相乘得到。
1.3 使用 Keras 实现 LSTM 进行情感分析
1.3.1 数据准备
我们使用来自 Kaggle 的 UMICH SI650 情感分类竞赛的约 7000 个短句子作为训练集。每个句子被标记为 1(积极情感)或 0(消极情感)。
首先,我们进行数据探索性分析,确定语料库中唯一单词的数量和每个句子的单词数量。然后,我们设置词汇表大小,并创建两个查找表,用于将单词转换为索引,反之亦然。
from keras.layers.core import Activation, Dense, Dropout, SpatialDropout1D
from keras.layers.embeddings import Embedding
from keras.layers.recurrent import LSTM
from keras.models import Sequential
from keras.preprocessing import sequence
from sklearn.model_selection import train_test_split
import collections
import matplotlib.pyplot as plt
import nltk
import numpy as np
import os
DATA_DIR = "../data"
MAX_FEATURES = 2000
MAX_SENTENCE_LENGTH = 40
maxlen = 0
word_freqs = collections.Counter()
num_recs = 0
ftrain = open(os.path.join(DATA_DIR, "umich-sentiment-train.txt"), 'rb')
for line in ftrain:
label, sentence = line.strip().split("t")
words = nltk.word_tokenize(sentence.decode("ascii", "ignore").lower())
if len(words) > maxlen:
maxlen = len(words)
for word in words:
word_freqs[word] += 1
num_recs += 1
ftrain.close()
vocab_size = min(MAX_FEATURES, len(word_freqs)) + 2
word2index = {x[0]: i+2 for i, x in enumerate(word_freqs.most_common(MAX_FEATURES))}
word2index["PAD"] = 0
word2index["UNK"] = 1
index2word = {v:k for k, v in word2index.items()}
X = np.empty((num_recs, ), dtype=list)
y = np.zeros((num_recs, ))
i = 0
ftrain = open(os.path.join(DATA_DIR, "umich-sentiment-train.txt"), 'rb')
for line in ftrain:
label, sentence = line.strip().split("t")
words = nltk.word_tokenize(sentence.decode("ascii", "ignore").lower())
seqs = []
for word in words:
if word2index.has_key(word):
seqs.append(word2index[word])
else:
seqs.append(word2index["UNK"])
X[i] = seqs
y[i] = int(label)
i += 1
ftrain.close()
X = sequence.pad_sequences(X, maxlen=MAX_SENTENCE_LENGTH)
Xtrain, Xtest, ytrain, ytest = train_test_split(X, y, test_size=0.2, random_state=42)
1.3.2 模型构建与训练
我们构建一个简单的 RNN 模型,使用嵌入层将输入的单词索引转换为密集向量,然后通过 LSTM 层进行处理,最后使用全连接层和 sigmoid 激活函数输出情感预测结果。
EMBEDDING_SIZE = 128
HIDDEN_LAYER_SIZE = 64
BATCH_SIZE = 32
NUM_EPOCHS = 10
model = Sequential()
model.add(Embedding(vocab_size, EMBEDDING_SIZE, input_length=MAX_SENTENCE_LENGTH))
model.add(SpatialDropout1D(Dropout(0.2)))
model.add(LSTM(HIDDEN_LAYER_SIZE, dropout=0.2, recurrent_dropout=0.2))
model.add(Dense(1))
model.add(Activation("sigmoid"))
model.compile(loss="binary_crossentropy", optimizer="adam", metrics=["accuracy"])
history = model.fit(Xtrain, ytrain, batch_size=BATCH_SIZE, epochs=NUM_EPOCHS, validation_data=(Xtest, ytest))
1.3.3 模型评估
我们可以使用测试集评估模型的性能,并绘制损失和准确率曲线。
plt.subplot(211)
plt.title("Accuracy")
plt.plot(history.history["acc"], color="g", label="Train")
plt.plot(history.history["val_acc"], color="b", label="Validation")
plt.legend(loc="best")
plt.subplot(212)
plt.title("Loss")
plt.plot(history.history["loss"], color="g", label="Train")
plt.plot(history.history["val_loss"], color="b", label="Validation")
plt.legend(loc="best")
plt.tight_layout()
plt.show()
score, acc = model.evaluate(Xtest, ytest, batch_size=BATCH_SIZE)
print("Test score: %.3f, accuracy: %.3f" % (score, acc))
for i in range(5):
idx = np.random.randint(len(Xtest))
xtest = Xtest[idx].reshape(1,40)
ylabel = ytest[idx]
ypred = model.predict(xtest)[0][0]
sent = " ".join([index2word[x] for x in xtest[0].tolist() if x != 0])
print("%.0ft%dt%s" % (ypred, ylabel, sent))
1.4 模型结构流程图
graph LR
A[输入句子] --> B[嵌入层]
B --> C[SpatialDropout1D]
C --> D[LSTM层]
D --> E[全连接层]
E --> F[激活函数(sigmoid)]
F --> G[输出情感预测结果]
1.5 实验结果
通过上述代码,我们可以得到模型在训练过程中的损失和准确率变化情况,以及在测试集上的最终得分和准确率。实验结果显示,模型的准确率接近 99%。
2. 门控循环单元(GRU)
2.1 GRU 简介
GRU 是 LSTM 的一种变体,由 K. Cho 引入。它保留了 LSTM 对梯度消失问题的抗性,但内部结构更简单,因此训练速度更快,因为更新隐藏状态所需的计算更少。
2.2 GRU 门控机制
GRU 包含更新门(z)和重置门(r)。更新门决定保留多少先前的记忆,重置门决定如何将新输入与先前的记忆结合。与 LSTM 不同,GRU 没有与隐藏状态不同的持久单元状态。
2.3 使用 Keras 实现 GRU 进行词性标注
2.3.1 数据准备
我们使用 NLTK 中 10% 的 Penn Treebank 样本作为训练数据。该数据集包含约 450 万个单词的美国英语,并进行了词性标注。
from keras.layers.core import Activation, Dense, Dropout, RepeatVector, SpatialDropout1D
from keras.layers.embeddings import Embedding
from keras.layers.recurrent import GRU
from keras.layers.wrappers import TimeDistributed
from keras.models import Sequential
from keras.preprocessing import sequence
from keras.utils import np_utils
from sklearn.model_selection import train_test_split
import collections
import nltk
import numpy as np
import os
DATA_DIR = "../data"
fedata = open(os.path.join(DATA_DIR, "treebank_sents.txt"), "wb")
ffdata = open(os.path.join(DATA_DIR, "treebank_poss.txt"), "wb")
sents = nltk.corpus.treebank.tagged_sents()
for sent in sents:
words, poss = [], []
for word, pos in sent:
if pos == "-NONE-":
continue
words.append(word)
poss.append(pos)
fedata.write("{:s}\n".format(" ".join(words)))
ffdata.write("{:s}\n".format(" ".join(poss)))
fedata.close()
ffdata.close()
def parse_sentences(filename):
word_freqs = collections.Counter()
num_recs, maxlen = 0, 0
fin = open(filename, "rb")
for line in fin:
words = line.strip().lower().split()
for word in words:
word_freqs[word] += 1
if len(words) > maxlen:
maxlen = len(words)
num_recs += 1
fin.close()
return word_freqs, maxlen, num_recs
s_wordfreqs, s_maxlen, s_numrecs = parse_sentences(os.path.join(DATA_DIR, "treebank_sents.txt"))
t_wordfreqs, t_maxlen, t_numrecs = parse_sentences(os.path.join(DATA_DIR, "treebank_poss.txt"))
MAX_SEQLEN = 250
S_MAX_FEATURES = 5000
T_MAX_FEATURES = 45
s_vocabsize = min(len(s_wordfreqs), S_MAX_FEATURES) + 2
s_word2index = {x[0]: i+2 for i, x in enumerate(s_wordfreqs.most_common(S_MAX_FEATURES))}
s_word2index["PAD"] = 0
s_word2index["UNK"] = 1
s_index2word = {v: k for k, v in s_word2index.items()}
t_vocabsize = len(t_wordfreqs) + 1
t_word2index = {x[0]: i for i, x in enumerate(t_wordfreqs.most_common(T_MAX_FEATURES))}
t_word2index["PAD"] = 0
t_index2word = {v: k for k, v in t_word2index.items()}
def build_tensor(filename, numrecs, word2index, maxlen, make_categorical=False, num_classes=0):
data = np.empty((numrecs, ), dtype=list)
fin = open(filename, "rb")
i = 0
for line in fin:
wids = []
for word in line.strip().lower().split():
if word2index.has_key(word):
wids.append(word2index[word])
else:
wids.append(word2index["UNK"])
if make_categorical:
data[i] = np_utils.to_categorical(wids, num_classes=num_classes)
else:
data[i] = wids
i += 1
fin.close()
pdata = sequence.pad_sequences(data, maxlen=maxlen)
return pdata
X = build_tensor(os.path.join(DATA_DIR, "treebank_sents.txt"), s_numrecs, s_word2index, MAX_SEQLEN)
Y = build_tensor(os.path.join(DATA_DIR, "treebank_poss.txt"), t_numrecs, t_word2index, MAX_SEQLEN, True, t_vocabsize)
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X, Y, test_size=0.2, random_state=42)
2.3.2 模型构建与训练
我们构建一个基于 GRU 的模型,用于词性标注。模型包括嵌入层、GRU 层、RepeatVector 层、全连接层和 softmax 激活函数。
EMBED_SIZE = 128
HIDDEN_SIZE = 64
BATCH_SIZE = 32
NUM_EPOCHS = 1
model = Sequential()
model.add(Embedding(s_vocabsize, EMBED_SIZE, input_length=MAX_SEQLEN))
model.add(SpatialDropout1D(Dropout(0.2)))
model.add(GRU(HIDDEN_SIZE, dropout=0.2, recurrent_dropout=0.2))
model.add(RepeatVector(MAX_SEQLEN))
model.add(GRU(HIDDEN_SIZE, return_sequences=True))
model.add(TimeDistributed(Dense(t_vocabsize)))
model.add(Activation("softmax"))
model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])
model.fit(Xtrain, Ytrain, batch_size=BATCH_SIZE, epochs=NUM_EPOCHS, validation_data=[Xtest, Ytest])
score, acc = model.evaluate(Xtest, Ytest, batch_size=BATCH_SIZE)
print("Test score: %.3f, accuracy: %.3f" % (score, acc))
2.4 模型结构流程图
graph LR
A[输入句子] --> B[嵌入层]
B --> C[SpatialDropout1D]
C --> D[GRU层(编码器)]
D --> E[RepeatVector层]
E --> F[GRU层(解码器)]
F --> G[TimeDistributed全连接层]
G --> H[激活函数(softmax)]
H --> I[输出词性标注结果]
2.5 LSTM 与 GRU 对比
| 模型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| LSTM | 能够学习长期依赖关系,对梯度消失问题有较好的抗性 | 结构复杂,训练时间长 | 数据量充足,需要处理复杂序列的任务 |
| GRU | 结构简单,训练速度快 | 表达能力相对较弱 | 数据量有限,对训练速度要求较高的任务 |
2.6 实验结果
通过上述代码,我们可以得到模型在训练过程中的损失和准确率变化情况,以及在测试集上的最终得分和准确率。实验结果显示,模型在第一个 epoch 后表现良好。
2.7 模型替换实验
我们可以将 GRU 替换为 LSTM,重新运行代码,验证它们的可互换性。实验结果表明,基于 GRU 的网络和基于 LSTM 的网络的结果相当。
2.8 序列到序列模型的应用
序列到序列模型是一类非常强大的模型,其最典型的应用是机器翻译,但还有许多其他应用,如词性标注、命名实体识别、句子解析和图像字幕生成等。
3. LSTM 与 GRU 在不同场景下的调优策略
3.1 超参数调优
在实际应用中,超参数的选择对模型性能影响很大。以下是一些常见超参数及其调优建议:
| 超参数 | 含义 | 调优建议 |
| ---- | ---- | ---- |
|
EMBEDDING_SIZE
/
EMBED_SIZE
| 嵌入层向量维度 | 可以从较小的值(如 64)开始尝试,逐步增加到 256 或更高,观察模型性能变化。值太小可能无法捕捉到足够的语义信息,太大则可能导致过拟合。 |
|
HIDDEN_LAYER_SIZE
/
HIDDEN_SIZE
| 隐藏层神经元数量 | 同样从较小值开始,如 32,逐步增加到 128 或更高。隐藏层神经元数量决定了模型的表达能力,但过多会增加计算量和过拟合风险。 |
|
BATCH_SIZE
| 批量大小 | 可以尝试不同的批量大小,如 16、32、64 等。较小的批量大小可以增加模型的随机性,有助于跳出局部最优解;较大的批量大小可以加快训练速度,但可能会导致模型收敛到较差的局部最优。 |
|
NUM_EPOCHS
| 训练轮数 | 可以通过观察训练过程中的损失和准确率曲线来确定合适的训练轮数。当验证集损失开始上升时,可能意味着模型已经过拟合,此时可以停止训练。 |
3.2 正则化方法
为了防止模型过拟合,可以使用正则化方法。在前面的代码中,我们已经使用了
Dropout
层,它可以随机丢弃一些神经元,减少神经元之间的依赖关系,从而提高模型的泛化能力。除此之外,还可以使用 L1 和 L2 正则化:
from keras.regularizers import l1, l2
# 在全连接层中添加 L2 正则化
model.add(Dense(1, kernel_regularizer=l2(0.01)))
3.3 模型评估与选择
在训练多个模型时,需要选择性能最优的模型。可以使用交叉验证的方法,将数据集划分为多个子集,轮流使用不同的子集作为验证集,最后取平均性能作为模型的评估指标。另外,还可以使用混淆矩阵、ROC 曲线等方法来更全面地评估模型性能。
4. 总结与展望
4.1 总结
本文详细介绍了长短期记忆网络(LSTM)和门控循环单元(GRU)的原理、门控机制,并通过情感分析和词性标注两个实例展示了如何使用 Keras 实现这两种模型。在情感分析任务中,LSTM 模型取得了接近 99% 的准确率;在词性标注任务中,GRU 模型在第一个 epoch 后表现良好。同时,我们还对比了 LSTM 和 GRU 的优缺点,以及它们在不同场景下的适用情况。
4.2 展望
虽然 LSTM 和 GRU 在处理序列数据方面表现出色,但它们仍然存在一些局限性。例如,在处理长序列时,计算效率仍然较低;对于一些复杂的语义理解任务,模型的表达能力还不够强。未来的研究方向可能包括:
-
新型网络结构
:探索更高效、更强大的网络结构,如 Transformer 架构,它在自然语言处理和计算机视觉领域取得了显著的成果。
-
多模态融合
:将文本、图像、音频等多种模态的数据融合在一起,提高模型的综合理解能力。
-
强化学习与序列模型的结合
:使用强化学习的方法来优化序列模型的训练过程,提高模型的决策能力。
4.3 实践建议
如果你想进一步深入学习和实践 LSTM 和 GRU,可以按照以下步骤进行:
1.
复现本文代码
:将本文中的代码在自己的环境中运行,观察模型的训练过程和性能表现。
2.
尝试不同的数据集
:寻找其他的情感分析或词性标注数据集,使用 LSTM 和 GRU 进行训练和评估,对比不同数据集上的性能差异。
3.
进行超参数调优
:根据前面介绍的超参数调优建议,尝试不同的超参数组合,找到最优的模型配置。
4.
拓展应用场景
:将 LSTM 和 GRU 应用到其他序列数据处理任务中,如时间序列预测、语音识别等。
graph LR
A[开始] --> B[选择模型(LSTM/GRU)]
B --> C[数据准备]
C --> D[模型构建]
D --> E[超参数调优]
E --> F[模型训练]
F --> G[模型评估]
G --> H{性能是否满足要求}
H -- 是 --> I[应用模型]
H -- 否 --> E
I --> J[结束]
通过以上步骤,你可以更好地掌握 LSTM 和 GRU 的原理和应用,为解决实际问题提供有力的工具。
超级会员免费看
4044

被折叠的 条评论
为什么被折叠?



