语言建模涉及根据已有的词序列预测序列中的下一个词。
语言模型是许多自然语言处理模型(如机器翻译和语音识别)中的关键要素。语言模型的构建方式必须与其预期用途相匹配。
在本教程中,您将了解语言模型的构建方式如何影响模型在从童谣生成短序列时的效果。
完成本教程后,您将了解:
- 为给定应用程序开发一个好的基于词的语言模型的挑战。
- 如何开发基于词的语言模型的一词输入、二词输入和基于行的构建方式。
- 如何使用训练好的语言模型生成序列。
通过我的新书《深度学习与自然语言处理》启动您的项目,其中包括逐步教程和所有示例的 Python 源代码文件。
让我们开始吧。

如何使用Keras在Python中开发基于词的神经语言模型
图片作者:Stephanie Chapman,保留部分权利。
教程概述
本教程分为5个部分,它们是:
- 语言模型构建
- Jack and Jill 童谣
- 模型 1:一词输入,一词输出序列
- 模型 2:逐行序列
- 模型 3:两词输入,一词输出序列
需要深度学习处理文本数据的帮助吗?
立即参加我的免费7天电子邮件速成课程(附代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
语言模型构建
统计语言模型从原始文本中学习,并根据序列中已有的词预测序列中下一个词的概率。
语言模型是解决机器翻译和语音识别等具有挑战性的自然语言处理问题的更大模型中的关键组件。它们也可以作为独立模型开发,用于生成与源文本具有相同统计特性的新序列。
语言模型一次学习和预测一个词。网络的训练涉及提供词序列作为输入,这些词序列被逐一处理,以便为每个输入序列进行预测和学习。
同样,在进行预测时,可以以一个或几个词作为种子,然后收集预测的词并将其作为后续预测的输入,以便构建生成的输出序列。
因此,每个模型都将涉及将源文本拆分为输入和输出序列,以便模型可以学习预测词。
对于语言建模,有许多方法可以从源文本构建序列。
在本教程中,我们将探讨在 Keras 深度学习库中开发基于词的语言模型的三种不同方法。
没有单一的最佳方法,只有适合不同应用程序的不同构建方式。
Jack and Jill 童谣
Jack and Jill 是一首简单的童谣。
它由四行组成,如下:
Jack and Jill went up the hill
To fetch a pail of water
Jack fell down and broke his crown
And Jill came tumbling after
我们将使用它作为源文本,探索基于词的语言模型的不同构建方式。
我们可以在 Python 中定义此文本,如下所示:
1 2 3 4 5 |
# 源文本 data = """ Jack and Jill went up the hill\n To fetch a pail of water\n Jack fell down and broke his crown\n And Jill came tumbling after\n """ |
模型 1:一词输入,一词输出序列
我们可以从一个非常简单的模型开始。
给定一个词作为输入,模型将学习预测序列中的下一个词。
例如
1 2 3 4 5 |
X, y Jack, and and, Jill Jill, went ... |
第一步是将文本编码为整数。
源文本中的每个小写词都分配一个唯一的整数,我们可以将词序列转换为整数序列。
Keras 提供了 Tokenizer 类,可用于执行此编码。首先,Tokenizer 在源文本上进行拟合,以开发从词到唯一整数的映射。然后,可以通过调用 texts_to_sequences() 函数将文本序列转换为整数序列。
1 2 3 4 |
# 整数编码文本 tokenizer = Tokenizer() tokenizer.fit_on_texts([data]) encoded = tokenizer.texts_to_sequences([data])[0] |
我们稍后将需要知道词汇量的大小,以便在模型中定义词嵌入层,以及使用独热编码对输出词进行编码。
词汇量的大小可以通过访问训练好的 Tokenizer 的 word_index 属性来检索。
1 2 3 |
# 确定词汇量大小 vocab_size = len(tokenizer.word_index) + 1 print('Vocabulary Size: %d' % vocab_size) |
运行此示例,我们可以看到词汇量大小为 21 个词。
我们加一,因为我们需要将最大编码词的整数指定为数组索引,例如,编码为 1 到 21 的词,数组索引为 0 到 21 或 22 个位置。
接下来,我们需要创建词序列,以一个词作为输入,一个词作为输出来拟合模型。
1 2 3 4 5 6 |
# 创建词 -> 词序列 sequences = list() for i in range(1, len(encoded)): sequence = encoded[i-1:i+1] sequences.append(sequence) print('Total Sequences: %d' % len(sequences)) |
运行此代码段显示我们总共有 24 个输入-输出对来训练网络。
1 |
总序列数:24 |
然后我们可以将序列拆分为输入(X)和输出元素(y)。这很简单,因为数据中只有两列。
1 2 3 |
# 拆分为 X 和 y 元素 sequences = array(sequences) X, y = sequences[:,0],sequences[:,1] |
我们将拟合我们的模型来预测词汇表中所有词的概率分布。这意味着我们需要将输出元素从单个整数转换为独热编码,其中词汇表中每个词为 0,实际词为 1。这为网络提供了一个目标,我们可以从中计算误差并更新模型。
Keras 提供了 to_categorical() 函数,我们可以使用它将整数转换为独热编码,同时将类别数指定为词汇量大小。
1 2 |
# one hot 编码输出 y = to_categorical(y, num_classes=vocab_size) |
我们现在准备定义神经网络模型。
该模型在输入层使用一个学习到的词嵌入。词嵌入为词汇表中的每个词提供一个实值向量,其中每个词向量具有指定长度。在这种情况下,我们将使用 10 维投影。输入序列包含单个词,因此 input_length=1。
该模型具有一个包含 50 个单元的隐藏 LSTM 层。这比实际需要的要多。输出层由词汇表中每个词的一个神经元组成,并使用 softmax 激活函数来确保输出归一化为概率。
1 2 3 4 5 6 |
# 定义模型 model = Sequential() model.add(Embedding(vocab_size, 10, input_length=1)) model.add(LSTM(50)) model.add(Dense(vocab_size, activation='softmax')) print(model.summary()) |
网络的结构可以概括如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
_________________________________________________________________ 层(类型) 输出形状 参数数量 ================================================================= embedding_1 (Embedding) (None, 1, 10) 220 _________________________________________________________________ lstm_1 (LSTM) (None, 50) 12200 _________________________________________________________________ dense_1 (Dense) (None, 22) 1122 ================================================================= 总参数:13,542 可训练参数:13,542 不可训练参数: 0 _________________________________________________________________ |
我们将为本教程中的每个示例使用相同的通用网络结构,对学习到的嵌入层进行微小更改。
接下来,我们可以在编码文本数据上编译和拟合网络。从技术上讲,我们正在对多类别分类问题(预测词汇表中的词)进行建模,因此使用分类交叉熵损失函数。我们使用高效的 Adam 梯度下降实现,并在每个 epoch 结束时跟踪准确率。模型拟合 500 个训练 epoch,这可能超过所需的。
网络配置未针对此实验和后续实验进行调整;选择了一个过度规定的配置,以确保我们可以专注于语言模型的构建方式。
1 2 3 4 |
# 编译网络 model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # 拟合网络 model.fit(X, y, epochs=500, verbose=2) |
模型拟合后,我们通过向其传递词汇表中的给定词并让模型预测下一个词来测试它。这里我们通过编码 'Jack' 并调用 model.predict_classes() 来获取预测词的整数输出。然后,在词汇映射中查找该整数以获得相关的词。
1 2 3 4 5 6 7 8 9 |
# 评估 in_text = 'Jack' print(in_text) encoded = tokenizer.texts_to_sequences([in_text])[0] encoded = array(encoded) yhat = model.predict_classes(encoded, verbose=0) for word, index in tokenizer.word_index.items(): if index == yhat: print(word) |
这个过程可以重复几次,以建立一个生成的词序列。
为了简化这个过程,我们将行为封装在一个函数中,我们可以通过传入我们的模型和种子词来调用它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# 从模型生成序列 def generate_seq(model, tokenizer, seed_text, n_words): in_text, result = seed_text, seed_text # 生成固定数量的词 for _ in range(n_words): # 将文本编码为整数 encoded = tokenizer.texts_to_sequences([in_text])[0] encoded = array(encoded) # 预测词汇表中的一个词 yhat = model.predict_classes(encoded, verbose=0) # 将预测的词索引映射到词 out_word = '' for word, index in tokenizer.word_index.items(): if index == yhat: out_word = word break # 附加到输入 in_text, result = out_word, result + ' ' + out_word return result |
我们可以将所有这些结合起来。完整的代码清单如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
from numpy import array from keras.preprocessing.text import Tokenizer from keras.utils import to_categorical from keras.models import Sequential from keras.layers import Dense 从 keras.layers 导入 LSTM from keras.layers import Embedding # 从模型生成序列 def generate_seq(model, tokenizer, seed_text, n_words): in_text, result = seed_text, seed_text # 生成固定数量的词 for _ in range(n_words): # 将文本编码为整数 encoded = tokenizer.texts_to_sequences([in_text])[0] encoded = array(encoded) # 预测词汇表中的一个词 yhat = model.predict_classes(encoded, verbose=0) # 将预测的词索引映射到词 out_word = '' for word, index in tokenizer.word_index.items(): if index == yhat: out_word = word break # 附加到输入 in_text, result = out_word, result + ' ' + out_word return result # 源文本 data = """ Jack and Jill went up the hill\n To fetch a pail of water\n Jack fell down and broke his crown\n And Jill came tumbling after\n """ # 整数编码文本 tokenizer = Tokenizer() tokenizer.fit_on_texts([data]) encoded = tokenizer.texts_to_sequences([data])[0] # 确定词汇量大小 vocab_size = len(tokenizer.word_index) + 1 print('Vocabulary Size: %d' % vocab_size) # 创建词 -> 词序列 sequences = list() for i in range(1, len(encoded)): sequence = encoded[i-1:i+1] sequences.append(sequence) print('Total Sequences: %d' % len(sequences)) # 拆分为 X 和 y 元素 sequences = array(sequences) X, y = sequences[:,0],sequences[:,1] # one hot 编码输出 y = to_categorical(y, num_classes=vocab_size) # 定义模型 model = Sequential() model.add(Embedding(vocab_size, 10, input_length=1)) model.add(LSTM(50)) model.add(Dense(vocab_size, activation='softmax')) print(model.summary()) # 编译网络 model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # 拟合网络 model.fit(X, y, epochs=500, verbose=2) # 评估 print(generate_seq(model, tokenizer, 'Jack', 6)) |
注意:您的结果可能会因算法或评估过程的随机性,或数值精度的差异而有所不同。考虑多次运行示例并比较平均结果。
运行示例将打印每个训练 epoch 的损失和准确率。
1 2 3 4 5 6 7 8 9 10 11 |
... Epoch 496/500 0s - 损失: 0.2358 - 准确率: 0.8750 Epoch 497/500 0s - 损失: 0.2355 - 准确率: 0.8750 Epoch 498/500 0s - 损失: 0.2352 - 准确率: 0.8750 Epoch 499/500 0s - 损失: 0.2349 - 准确率: 0.8750 Epoch 500/500 0s - 损失: 0.2346 - 准确率: 0.8750 |
我们可以看到模型没有记忆源序列,这可能是因为输入序列中存在一些歧义,例如:
1 2 |
jack => and jack => fell |
等等。
运行结束时,传入 'Jack' 并生成预测或新序列。
我们得到一个合理的输出序列,其中包含源的一些元素。
1 |
Jack and jill came tumbling after down |
这是一个很好的初步语言模型,但没有充分利用 LSTM 处理输入序列和通过使用更广泛的上下文消除一些模糊成对序列歧义的能力。
模型 2:逐行序列
另一种方法是逐行拆分源文本,然后将每一行分解为一系列逐步构建的词。
例如
1 2 3 4 5 6 7 |
X, y _,_,_,_,_,Jack,and _,_,_,_,Jack,and Jill _,_,_,Jack,and,Jill,went _,_,Jack,and,Jill,went,up _,Jack,and,Jill,went,up,the Jack,and,Jill,went,up,the,hill |
这种方法可以使模型利用每行的上下文,在简单的一词进出模型产生歧义的情况下帮助模型。
在这种情况下,这以跨行预测词为代价,如果目前我们只对文本行建模和生成感兴趣,这可能没问题。
请注意,在这种表示中,我们将需要对序列进行填充,以确保它们达到固定长度输入。这是使用 Keras 时的要求。
首先,我们可以使用已在源文本上拟合的 Tokenizer 逐行创建整数序列。
1 2 3 4 5 6 7 8 |
# 创建基于行的序列 sequences = list() for line in data.split('\n'): encoded = tokenizer.texts_to_sequences([line])[0] for i in range(1, len(encoded)): sequence = encoded[:i+1] sequences.append(sequence) print('Total Sequences: %d' % len(sequences)) |
接下来,我们可以填充准备好的序列。我们可以使用 Keras 中提供的 pad_sequences() 函数来完成此操作。这首先涉及找到最长的序列,然后将其用作填充所有其他序列的长度。
1 2 3 4 |
# 填充输入序列 max_length = max([len(seq) for seq in sequences]) sequences = pad_sequences(sequences, maxlen=max_length, padding='pre') print('Max Sequence Length: %d' % max_length) |
接下来,我们可以像以前一样将序列拆分为输入和输出元素。
1 2 3 4 |
# 分割输入和输出元素 sequences = array(sequences) X, y = sequences[:,:-1],sequences[:,-1] y = to_categorical(y, num_classes=vocab_size) |
然后可以像以前一样定义模型,只是输入序列现在比单个词长。具体来说,它们的长度为 max_length-1,因为当我们计算序列的最大长度时,它们包含了输入和输出元素。
1 2 3 4 5 6 7 8 9 10 |
# 定义模型 model = Sequential() model.add(Embedding(vocab_size, 10, input_length=max_length-1)) model.add(LSTM(50)) model.add(Dense(vocab_size, activation='softmax')) print(model.summary()) # 编译网络 model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # 拟合网络 model.fit(X, y, epochs=500, verbose=2) |
我们可以像以前一样使用模型生成新序列。generate_seq() 函数可以更新为通过在每次迭代中将预测添加到输入词列表中来构建输入序列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# 从语言模型生成序列 def generate_seq(model, tokenizer, max_length, seed_text, n_words): in_text = seed_text # 生成固定数量的词 for _ in range(n_words): # 将文本编码为整数 encoded = tokenizer.texts_to_sequences([in_text])[0] # 将序列预填充到固定长度 encoded = pad_sequences([encoded], maxlen=max_length, padding='pre') # 预测每个词的概率 yhat = model.predict_classes(encoded, verbose=0) # 将预测的词索引映射到词 out_word = '' for word, index in tokenizer.word_index.items(): if index == yhat: out_word = word break # 附加到输入 in_text += ' ' + out_word return in_text |
将所有这些结合起来,完整的代码示例如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
from numpy import array from keras.preprocessing.text import Tokenizer from keras.utils import to_categorical from keras.preprocessing.sequence import pad_sequences from keras.models import Sequential from keras.layers import Dense 从 keras.layers 导入 LSTM from keras.layers import Embedding # 从语言模型生成序列 def generate_seq(model, tokenizer, max_length, seed_text, n_words): in_text = seed_text # 生成固定数量的词 for _ in range(n_words): # 将文本编码为整数 encoded = tokenizer.texts_to_sequences([in_text])[0] # 将序列预填充到固定长度 encoded = pad_sequences([encoded], maxlen=max_length, padding='pre') # 预测每个词的概率 yhat = model.predict_classes(encoded, verbose=0) # 将预测的词索引映射到词 out_word = '' for word, index in tokenizer.word_index.items(): if index == yhat: out_word = word break # 附加到输入 in_text += ' ' + out_word return in_text # 源文本 data = """ Jack and Jill went up the hill\n To fetch a pail of water\n Jack fell down and broke his crown\n And Jill came tumbling after\n """ # 在源文本上准备分词器 tokenizer = Tokenizer() tokenizer.fit_on_texts([data]) # 确定词汇量大小 vocab_size = len(tokenizer.word_index) + 1 print('Vocabulary Size: %d' % vocab_size) # 创建基于行的序列 sequences = list() for line in data.split('\n'): encoded = tokenizer.texts_to_sequences([line])[0] for i in range(1, len(encoded)): sequence = encoded[:i+1] sequences.append(sequence) print('Total Sequences: %d' % len(sequences)) # 填充输入序列 max_length = max([len(seq) for seq in sequences]) sequences = pad_sequences(sequences, maxlen=max_length, padding='pre') print('Max Sequence Length: %d' % max_length) # 分割输入和输出元素 sequences = array(sequences) X, y = sequences[:,:-1],sequences[:,-1] y = to_categorical(y, num_classes=vocab_size) # 定义模型 model = Sequential() model.add(Embedding(vocab_size, 10, input_length=max_length-1)) model.add(LSTM(50)) model.add(Dense(vocab_size, activation='softmax')) print(model.summary()) # 编译网络 model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # 拟合网络 model.fit(X, y, epochs=500, verbose=2) # 评估模型 print(generate_seq(model, tokenizer, max_length-1, 'Jack', 4)) print(generate_seq(model, tokenizer, max_length-1, 'Jill', 4)) |
注意:您的结果可能会因算法或评估过程的随机性,或数值精度的差异而有所不同。考虑多次运行示例并比较平均结果。
运行示例后,对源数据的拟合度更高。添加的上下文使模型能够消除一些示例的歧义。
仍有两行以“Jack”开头的文本,这可能仍然是网络的一个问题。
1 2 3 4 5 6 7 8 9 10 11 |
... Epoch 496/500 0s - 损失:0.1039 - 准确率:0.9524 Epoch 497/500 0s - 损失:0.1037 - 准确率:0.9524 Epoch 498/500 0s - 损失:0.1035 - 准确率:0.9524 Epoch 499/500 0s - 损失:0.1033 - 准确率:0.9524 Epoch 500/500 0s - 损失:0.1032 - 准确率:0.9524 |
运行结束时,我们生成了两个带有不同种子词的序列:“Jack”和“Jill”。
生成的第一行看起来不错,直接与源文本匹配。第二行有点奇怪。这是有道理的,因为网络只在输入序列中看到过“Jill”,而没有在序列开头看到过,因此它强制输出使用单词“Jill”,即童谣的最后一行。
1 2 |
Jack fell down and broke Jill jill came tumbling after |
这是一个很好的例子,说明框架如何可能产生更好的新行,但不是好的部分输入行。
模型 3:两词输入,一词输出序列
我们可以在“单个单词输入”和“整个句子输入”方法之间取一个中间值,并以单词子序列作为输入。
这将在这两种框架之间提供一个权衡,允许生成新行,并允许从行中间开始生成。
我们将使用3个单词作为输入来预测一个单词作为输出。序列的准备与第一个示例非常相似,只是源序列数组中的偏移量不同,如下所示
1 2 3 4 5 |
# 编码2个单词 -> 1个单词 sequences = list() for i in range(2, len(encoded)): sequence = encoded[i-2:i+1] sequences.append(sequence) |
完整的示例列在下面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
from numpy import array from keras.preprocessing.text import Tokenizer from keras.utils import to_categorical from keras.preprocessing.sequence import pad_sequences from keras.models import Sequential from keras.layers import Dense 从 keras.layers 导入 LSTM from keras.layers import Embedding # 从语言模型生成序列 def generate_seq(model, tokenizer, max_length, seed_text, n_words): in_text = seed_text # 生成固定数量的词 for _ in range(n_words): # 将文本编码为整数 encoded = tokenizer.texts_to_sequences([in_text])[0] # 将序列预填充到固定长度 encoded = pad_sequences([encoded], maxlen=max_length, padding='pre') # 预测每个词的概率 yhat = model.predict_classes(encoded, verbose=0) # 将预测的词索引映射到词 out_word = '' for word, index in tokenizer.word_index.items(): if index == yhat: out_word = word break # 附加到输入 in_text += ' ' + out_word return in_text # 源文本 data = """ Jack and Jill went up the hill\n To fetch a pail of water\n Jack fell down and broke his crown\n And Jill came tumbling after\n """ # 将单词序列整数编码 tokenizer = Tokenizer() tokenizer.fit_on_texts([data]) encoded = tokenizer.texts_to_sequences([data])[0] # 检索词汇量大小 vocab_size = len(tokenizer.word_index) + 1 print('Vocabulary Size: %d' % vocab_size) # 编码2个单词 -> 1个单词 sequences = list() for i in range(2, len(encoded)): sequence = encoded[i-2:i+1] sequences.append(sequence) print('Total Sequences: %d' % len(sequences)) # 填充序列 max_length = max([len(seq) for seq in sequences]) sequences = pad_sequences(sequences, maxlen=max_length, padding='pre') print('Max Sequence Length: %d' % max_length) # 分割输入和输出元素 sequences = array(sequences) X, y = sequences[:,:-1],sequences[:,-1] y = to_categorical(y, num_classes=vocab_size) # 定义模型 model = Sequential() model.add(Embedding(vocab_size, 10, input_length=max_length-1)) model.add(LSTM(50)) model.add(Dense(vocab_size, activation='softmax')) print(model.summary()) # 编译网络 model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # 拟合网络 model.fit(X, y, epochs=500, verbose=2) # 评估模型 print(generate_seq(model, tokenizer, max_length-1, 'Jack and', 5)) print(generate_seq(model, tokenizer, max_length-1, 'And Jill', 3)) print(generate_seq(model, tokenizer, max_length-1, 'fell down', 5)) print(generate_seq(model, tokenizer, max_length-1, 'pail of', 5)) |
注意:您的结果可能会因算法或评估过程的随机性,或数值精度的差异而有所不同。考虑多次运行示例并比较平均结果。
再次运行示例,对源文本的拟合度很好,准确率约为95%。
1 2 3 4 5 6 7 8 9 10 11 |
... Epoch 496/500 0s - 损失:0.0685 - 准确率:0.9565 Epoch 497/500 0s - 损失:0.0685 - 准确率:0.9565 Epoch 498/500 0s - 损失:0.0684 - 准确率:0.9565 Epoch 499/500 0s - 损失:0.0684 - 准确率:0.9565 Epoch 500/500 0s - 损失:0.0684 - 准确率:0.9565 |
我们查看了4个生成示例,其中两个是行首情况,两个是行中开始的情况。
1 2 3 4 |
Jack and jill went up the hill And Jill went up the fell down and broke his crown and pail of water jack fell down and |
第一个行首情况生成正确,但第二个不正确。第二个情况是第4行的示例,与第1行的内容有歧义。也许进一步扩展到3个输入词会更好。
两个行中生成示例均正确生成,与源文本匹配。
我们可以看到,语言模型的框架选择以及模型使用要求必须兼容。通常,在使用语言模型时需要仔细设计,然后通过序列生成进行抽样测试,以确认模型要求已满足。
扩展
本节列出了一些您可能希望探索的扩展本教程的想法。
- 整个童谣作为序列。考虑更新上述示例之一,将整个童谣构建为输入序列。模型应该能够根据第一个单词的种子生成整个内容,请演示这一点。
- 预训练嵌入。探索在嵌入中使用预训练的词向量,而不是将嵌入作为模型的一部分进行学习。对于如此小的源文本来说,这不是必需的,但可能是一个好的实践。
- 字符模型。探索使用基于字符的语言模型处理源文本,而不是本教程中演示的基于词的方法。
进一步阅读
如果您想深入了解此主题,本节提供了更多资源。
总结
在本教程中,您学习了如何为简单的童谣开发不同的基于词的语言模型。
具体来说,你学到了:
- 为给定应用程序开发一个好的基于词的语言模型的挑战。
- 如何开发基于词的语言模型的一词输入、二词输入和基于行的构建方式。
- 如何使用训练好的语言模型生成序列。
你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。
嗨,Jason – 谢谢。语言模型如何用于“评分”不同的文本句子。假设有一个语音识别引擎,它输出真实的单词,但当它们组合成一个句子时,它们没有意义。我们可以使用语言模型“评分”每个句子,看看哪个更可能出现吗?谢谢!
很好的问题。
语言模型可以接受原始输入并预测预期的序列或序列,然后可以使用集束搜索探索这些结果,而不是评分。
谢谢,我希望能看到一个例子作为这篇帖子的附录。顺便说一下——我真的很喜欢你的博客,对这些例子感激不尽。
谢谢。我有一篇关于集束搜索的帖子已经安排好了。
你好,我正在尝试使用 python 3.x 开发带有 GUI 的下一个词预测模型,但我不能。有人能帮我吗?
非常感谢!
也许先开发一个独立的语言模型,然后再将其集成到您的应用程序中。
嵌入中的第二个参数是什么意思?
我是否正确理解,每个单词都被编码为0到10之间的数字?
我创建了一个用于预测大量单词的神经网络,损失下降得太慢了,所以我认为我做错了什么。
也许它应该这样,我不知道(在字符生成中快得多),如果能提供建议,我将不胜感激。
https://pastebin.com/PPWiuMXf
第二个参数是嵌入的维度,即每个单词的编码向量表示的维度数。
常见的值是50、100、500或1000。
如何用 base 来做,意思是如何在 python 中从 timit 数据库中提取转录。
抱歉,我没有使用 TIMIT 数据集的示例。
感谢这篇精彩的帖子。一个新手的问题——我有一个大型图书数据集,我想用它训练一个 LSTM。但是,当我尝试一次使用整个数据集进行训练时,我遇到了内存错误。有没有办法将数据分解并使用部分数据训练模型?或者我必须用硬件来解决这个问题?
您可以使用 Keras 中的渐进加载,每次只加载或生成一批数据。
我有一篇关于这个的帖子已经安排好了,但在那之前,请阅读 Keras 数据生成器。
亲爱的 Jason,
非常感谢这篇帖子。我正在尝试使用您的“模型2:逐行序列”并将其扩展以创建RNN语言模型。我对数据的表示方式有两个问题
1. 有没有更有效的方法来训练嵌入+RNN语言模型,而不是将单个句子分成多个实例,每一步添加一个单词?
2. 在这种表示中,我们需要一遍又一遍地将同一序列的一部分输入到模型中。通过更频繁地呈现句子开头的单词(作为X),我们是否会使模型偏向于更好地了解句子开头部分,而不是更频繁地出现在句子末尾的单词?
此致,谢谢您,
Christoph
我鼓励您探索其他框架,看看它们如何比较。没有唯一的正确方法。
它可能会使模型产生偏差,也许您可以对此进行测试。
嗨,Jason,如果你有多个句子要分批训练怎么办?在这种情况下,你的输入将是3维的,并且拟合会返回一个错误,因为嵌入层只接受2维。
除了每次发送一个句子的训练集之外,有没有更有效的方法来处理它?
我当然可以假装所有单词都是一个句子的一部分,但是 LSTM 如何检测句子的结尾呢?
谢谢!
您可以将每个句子作为样本提供,将样本分组到批次中,LSTM 将在每个批次结束时重置状态。
谢谢您的回复,Jason!我明白 LSTM 会在批次结束时重置状态,但我们不应该让它在每个批次中的每个句子/样本之后重置状态吗?
也许吧。尝试一下,看看它是否能提高模型技能。
我发现它的影响比人们预期的要小得多。
我无法做到这一点,因为这将导致维度问题,阻止 Keras 嵌入层给出正确的输出。如果您有解决方案,我希望看到您的代码。
精彩的帖子!但我正在研究一个需要不使用库构建的 RNN 语言模型的东西。这里代码中使用的 Keras 功能是否可以用自写的代码替换,并且有人已经这样做过了吗?是否有相关的 Github 仓库?
这将需要大量工作,重新实现已经快速可靠的系统。听起来是个坏主意。
你的动机究竟是什么?
没关系,先生,我自己也意识到那是个多么糟糕的主意。不过,谢谢您这篇精彩的文章!
不客气。
我如何实现相同的脚本来返回给定特定上下文的所有可能句子。
例如:如果我的数据集包含我访问过的地方列表。
我访问过印度,我访问过美国,我访问过德国……
上面的脚本返回第一个可能的匹配。我如何让脚本返回所有地方?
这可能吗?
听起来你可能对实体提取感兴趣
https://en.wikipedia.org/wiki/Named-entity_recognition
太棒了!!!
如果您能分享如何改进模型或参数以预测更大文本(例如小说)中的单词,我将不胜感激。增加另一个 LSTM 层或更多层会是个好主意吗?还是增加 LSTM 的大小就足够了?
再次感谢您的所有帖子,非常有帮助
好问题。
我在这里有一些关于调整深度学习模型的通用建议
https://machinelearning.org.cn/improve-deep-learning-performance/
我在这里有关于模型配置最佳实践的建议
https://machinelearning.org.cn/best-practices-document-classification-deep-learning/
我们如何计算交叉熵和困惑度?
Keras 可以计算交叉熵。
抱歉,我没有计算困惑度的示例。
嗨,Jason 先生,我如何在这个算法中计算困惑度度量?
抱歉,我没有计算困惑度的示例。
嗨,我尝试将我的模型保存为
# 将模型序列化为JSON
model_json = model.to_json()
with open(“new_model_OneinOneOut.json”, “w”) as json_file
json_file.write(model_json)
# 将权重序列化为HDF5
model.save_weights(“weights_OneinOneOut.h5”)
print(“Saved model to disk”)
但我无法加载和使用它。我该怎么做?我保存得对吗?
您必须加载 json 和 h5 文件。
您具体遇到了什么问题?
你好,
您似乎对输出向量使用独热向量。在词汇量非常大的情况下,这将是一个巨大的问题。您建议我们应该怎么做?
这不像你想象的那么大的问题,它确实可以很好地扩展到1万和10万个词汇。
您可以使用搜索方法在结果概率向量上获取多个不同的输出序列。
您还可以使用分层版本的 softmax 来提高效率。
嗨,Jason,
感谢这篇精彩的帖子。我有两个问题。我正在处理的语料库包含长度不等的句子,有些是3个词长,有些是30个词长。我想训练一个基于句子的语言模型,即训练数据不应尝试将语料库中的两个或多个句子组合在一起。
我有点困惑如何设置训练数据。目前,我用0填充了较短的句子,以使其与最长句子的长度匹配。示例
句子:I like fish – 这个句子将分解如下
0 0 0 —-> I
00 I —-> like
0 I like —->fish
I like fish —->
这种方法给了我大约110,000个训练点,但是使用带有100个节点的LSTM架构,我的准确率收敛到50%。你认为我错误地设置了数据吗?
第二点是,您能否就如何将预训练的词嵌入与Keras中的LSTM语言模型结合起来提出建议。
谢谢
填充是可行的方法,然后使用掩码层忽略零填充。
我有很多使用预训练词嵌入的例子,这里是一个很好的开始
https://machinelearning.org.cn/use-word-embedding-layers-deep-learning-keras/
你好,
我好奇为什么我们需要在测试时使用
print(generate_seq(model, tokenizer, **max_length-1**, 'Jack and', 5))
而不是
print(generate_seq(model, tokenizer, **max_length**, 'Jack and', 5))
如果不减1,它确实不起作用。为什么会这样呢?
非常感谢!
正如帖子中解释的那样
嗨 Jason
是否可以将这些模型用于标点符号或冠词预测(LSTM 神经网络,其中 y(标点符号/冠词/其他)取决于特定数量的先前/后续词)?您对此任务有什么建议?
谢谢!
当然,LSTM 将是一个很好的方法。
您会为此类任务进行 X_test X_train 分割吗?如果新文本(此处为 X_test)中存在 Keras 未为 X_train 标记化的单词,该如何处理(将训练好的模型应用于包含新单词的文本)?
是的。您需要确保您的训练数据集能够代表问题,就像所有机器学习问题一样。
你好,这很好,
对于一个特定的任务,我遇到了一个问题。
例如,我的训练集中有一个数据
“嘿,杰克,你要去大学吗?”
现在我有一个文本序列
“嘿,杰克,你……”
我有2个选项
1. going
2. coming
我必须找出下一个单词“going”和“coming”的概率。显然,“going”的概率是1,“coming”的概率是零
我如何从我的选项中检查下一个单词的概率
我推荐使用基于词汇的语言模型,该模型可以给出词汇表中每个词在序列中作为下一个词的概率。
尊敬的Jason博士,我一直在关注您的教程,它非常有趣。
现在,我有一些关于OCR的问题。
1. 您能给我一个简单的例子,说明如何实现CNN + LSTM + CTC来识别扫描文本图像吗(例如,如果扫描图像是“near the door”,等效文本是“near the door”,那么如何提供图像和文本进行训练呢?)?
抱歉,我没有这样的具体例子。
嗨,Jason,
我有两个问题
1. 我有一个预测下一个词的项目,我想使用您的例子作为基础。
我的数据包括多个文档。我想到的一个方法是将所有文档连接成一个词元列表(带句子开始词元),然后以固定大小切片作为模型的输入。第二种方法是使用填充分别处理每个句子。哪种方法会更好?
2. 如果我以后想将这个语言模型用于其他目的,它如何工作?我是否像预训练词嵌入(例如word2vec)那样使用它?您有这方面的例子吗?输入是什么样的?(例如在预训练词嵌入中,输入是每个词的向量)
谢谢你
也许可以尝试两种方法,看看哪种最适合您的数据和模型。
是的,您可以保存模型权重,稍后加载它们,并将其用作输入或输出语言模型的一部分。
嗨,Jason,
我们为什么要将y转换为one-hot编码(to_categorical)?这是必须的吗?我们为什么不直接将其保留为整数?我的词汇量很大,这导致了内存错误……
另外——在创建vocab_size时,我们为什么要给word_index的长度加“+1”?
非常感谢。这篇文章真的很有帮助。
这样我们就可以预测每个词的概率,并选择概率最高的词作为下一个词。
这不是必需的,你可以预测词的整数,但one-hot编码通常效果更好。
我添加+1是为了给“0”留出空间,它表示“我不知道”或“未知”。
我遇到了同样的问题
我们可以使用“sparse_categorical_crossentropy”损失,而不是预测整数,这样我们就不必以这种方式进行y的独热编码,从而避免了内存错误。
我们当然可以!
我想是老习惯,我自己不这样做。
更多信息请看这里:
https://machinelearning.org.cn/how-to-choose-loss-functions-when-training-deep-learning-neural-networks/
这到底是为了什么
for word, index in tokenizer.word_index.items()
if index == yhat
out_word = word
break
你每次做出预测时都会遍历这个字典来查找与索引对应的词吗?为什么不直接反转字典一次然后查找值呢?
是的。是的,我确信有更有效的方法来编写它,也许你可以分享一些?
Jason,我一直在关注Ashu Prasad在:https://towardsdatascience.com/natural-language-processing-with-tensorflow-e0a701ef5cef上的文章。
在某个地方,他做了这个(搜索“我们使用一个辅助函数反转包含编码词的字典,该函数有助于我们绘制嵌入。”)。
[我无法打印代码,因为它是一张图片。]
它依赖于从模型中提取权重;我尝试过复制它,但失败了。
如果有人能让它工作,那可能就是大家在这里寻找的。
如果你能做到,请告诉我:bdecicco2001@yahoo.com
也许可以直接联系文章的作者?
嗨,Jason,
感谢这篇精彩的文章。我有两个问题
1- 如果我的模型已经训练好,之后需要添加新词,最好的方法是什么,而无需从头开始重新训练?
2- 如果我用错误的句子训练了模型。例如,我用了“Hi Jason, hooo are you?”,但正确的应该是“Hi Jason, how are you?”,我想在不从头开始重新训练的情况下纠正它。进行这种强化学习的最佳方法是什么?
最简单的方法:将新词标记为“未知”。
另一种方法是使用模型权重作为起点,然后使用小学习率和新/更新的数据重新训练模型。
你好 Jason,
这是一篇写得非常好的文章,谢谢。
1. 我想知道,是否有一种方法可以在不输入第一个词的情况下,使用RNN/LSTM模型生成文本,就像您在generate_seq方法中那样,类似于markovify包,特别是make_sentence()/make_short_sentence(char_length)函数。
2. 另外,使用Word2Vec或GloVe词嵌入是否允许我们使用训练语料库中没有的词?
是的,你可以以任何你希望的方式构建问题,例如输入一个词,得到一个句子或一段话。
模型只能在训练语料库中的词上进行训练。新词被标记为“未知”。
你好,
我想根据文章标题和摘要构建一个文章推荐系统,如何使用语言建模来衡量用户个人资料和文章之间的相似度呢?
谢谢你
我没有这方面的例子。也许是词向量差异的总和?
Jason,这篇帖子非常好!我正在制作相同的模型来预测文本中的未来词,但遇到了验证损失增加的问题。我将数据分成训练集和测试集,训练损失在增加,验证损失也在增加。所以我认为这意味着过拟合。即使在你的例子中,如果我们向fit方法添加validation_split参数,我们也会看到验证损失也在增加。我觉得这不正常。你的看法是什么?
我关于诊断和改进深度学习模型的最佳建议在这里
https://machinelearning.org.cn/start-here/#better
你好,杰森!
感谢您如此详细的文章。我有两个问题
1. LSTM(units=COUNT)中的COUNT变化对这种词预测神经网络会有什么影响?
2. 我是否理解正确,如果我删除具有相同输入和输出的序列,创建一个唯一序列的列表,它将减少要学习的模式数量并且不会影响最终结果?(训练时间优化)
好问题,更多的节点意味着模型有更大的容量,我在这里解释了这一点
https://machinelearning.org.cn/how-to-control-neural-network-model-capacity-with-nodes-and-layers/
也许会吧,听起来是个有趣的实验,Alex。
如何从包含其他信息的车辆识别码图像中提取车辆识别码?
也许可以使用经典的计算机视觉技术来隔离文本,然后提取文本。
我认为Python中的OpenCV可能是一个很好的起点。
除了一个预测,我怎样才能让它有几个预测,并允许用户从中选择一个?
一些想法
也许你可以对输出概率进行采样,以生成几种不同的输出。
也许你可以尝试运行模型几次以获得不同的输出?
也许你可以训练并使用几个并行模型来获得不同的输出?
嗨,
如果我想在输入两个单词后预测最可能的前3个单词,我该如何修改代码?这个模型会生成下一个单词,并考虑整个字符串来预测下一个单词。目前我正在尝试用这个来制作一个键盘。
例如
如果我输入“I read”,模型应该生成“it”,“book”和“your”之类的词。
你可以查看下一个单词的概率,然后选择概率最高的3个单词。
你好,这真是一篇好文章,我仔细阅读了每一个例子,并且开始喜欢它。
您能否提供一个“前一个词”序列的语法,可以进行训练?我在网上找到的大多数例子都是下一个词预测器。我的要求是预测前一个词,您已经提到过使用LSTM,但如果您能提供一个X、y序列,那就太有帮助了。
我不明白,抱歉。你能详细说明一下吗?
你好,我正在寻找模型2
X, y
_,_,_,_,_,Jack,and
_,_,_,_,Jack,and Jill
_,_,_,Jack,and,Jill,went
_,_,Jack,and,Jill,went,up
_,Jack,and,Jill,went,up,the
Jack,and,Jill,went,up,the,hill
————
sequences = list()
for line in data.split(‘\n’)
encoded = tokenizer.texts_to_sequences([line])[0]
for i in range(1, len(encoded))
sequence = encoded[:i+1]
sequences.append(sequence)
—
我这里的疑问是,我如何编写这些代码来预测“上一个”词。y变成了
上一个词
这会奏效吗?或者它应该如何奏效?
X, y
Jack,and, Jill, went, up, the, hill 换行
and, Jill, went, up, the, hill, _ Jack
Jill, went, up, the, hill, _, _ and
went, up, the, _ , _, _ Jill
up,the,_, _ , _, _ went
the,_, _ , _, _,_ up
不需要预测上一个词,因为它已经可用了。
如果你想学习如何在没有其他信息的情况下预测前一个词,你可以在训练时简单地反转输入序列的顺序。
谢谢Jason的帮助。认为那会有帮助。你想看看我的确切问题是什么吗?
我试图解决的问题是
我有一行
Line1: Jack and Jill went up the hill
Line2: To fetch a pail of water
Line3: Jack fell down and broke his crown
Line4: And Jill came tumbling after
现在我想重写Line4,用一个押韵的词“water”。在我的例子中,“mother”会是正确的词。
Line4 : And _, _ I love my mother
或者我想改变单词“tumbling”,那个位置最适合什么?
Line4: And Jill came “_” after
如果我要实现这一点,我可以反转该行并训练模型。然后我必须再维护一个用于下一个词预测的模型。
我想了解,是否在任何层/技术中有内置功能可以同时用于下一个/上一个词预测。我还没有完全理解LSTM,我只是认为LSTM可以记住上一个词?
谢谢。
解决问题的方法有很多。
一个简单/朴素的方法——可能有效——是按原样输入文本,模型的输出是直接预测缺失的词或词语。
将其作为第一步尝试。使用特殊标记来表示缺失的词。
如果我们使用带有num words的tokenizer,词汇量是多少?
如果我使用带有num_words的Tokenizer
tokenizer = Tokenizer(num_words=num_words, lower=True)
现在我们有这一行
y = to_categorical(y, num_classes=vocab_size)
我应该这样调用它吗?
y = to_categorical(y, num_classes=num_words)
?
那是因为实际词语的数量应该更小。
我的词汇量大约有80万个词,而pad_sequences总是出现MemoryError。这就是我问的原因。
谢谢!
你可能把术语搞反了?
词汇量会比词的数量小得多,因为词的数量包括重复的词。
在“一个词输入,一个词输出”的框架下使用LSTM是杀鸡用牛刀,因为没有使用序列(长度为1)。
我们可以在嵌入层之后直接使用一个Flatten层,并将其连接到一个Dense层。
当然。将这个例子看作您自己项目的起点。
“一个词输入 -> 一个词输出”的实现也创建了以下2-gram:
hill->to
from
Jack and Jill went up the hill
To fetch a pail of water
这是不正确的。
我们需要按行创建2-gram。
另外,如果文本是一个段落,我们需要将段落分割成句子,然后对数据集进行2-gram提取。
嗨,Jason,
您的文章非常清晰易懂。我按照这篇文章创建了下一个词/序列预测模型。我遇到了模型推断输出的问题。
例如,如果我输入模型——“Where can I buy”,我得到输出——“Where can I buy a bicycle”和“Where can I buy spare parts for my bicycle”。这两个都非常完美。
我还得到了一些语法不正确的输出——“Where can I buy of bicycle”,“Where can I buy went to bicycle”。
您有什么方法可以过滤掉语法不正确的输出,以便我们只保留好的句子作为输出吗?感谢您的帮助。
仅供参考 – 训练数据创建 –
我采用的方法是输入中的三元组。例如,对于句子“I am reading this article”,我使用以下数据进行训练。
(I, am, reading) > (this)
(am, reading, this) > (article)
谢谢。
没有,除了训练一个更好的模型,减少错误。
嗨 Jason
感谢您的信息丰富的教程。
我对此感到好奇
model.add(Embedding(vocab_size, 10, input_length=1))
model.add(LSTM(50))
单个隐藏的LSTM层(50个单元)是否应该等于嵌入层的长度,我的意思是序列input_length?
嵌入的大小和LSTM层中的单元数量没有关系。
您好吗?抱歉,我有一个疑问,我正在使用您的示例来预测下一个词,使用一个数据语料库(“句子”),我将其连接起来形成一个单一文本并执行该过程,然而我的网络没有训练,准确率从“0.04”开始,并且各个时期几乎相同,我检查了一切,甚至词汇处理也很好……我不知道该怎么办
也许这些教程能帮助您提高性能
https://machinelearning.org.cn/start-here/#better
应用了上述方法,但模型仍有问题。
你好,
如何使用所呈现的语言模型来纠正语音识别结果?
语音识别是一个不同的主题,但您可以考虑识别器识别的不是一个词,而是多个具有不同概率的词。通常,您会选择概率最高的单个词作为输出,但使用语言模型,您可以根据序列中最高的概率作为输出,同时考虑当前词之前的词。
教授您的文章总是很有帮助!!!非常感谢您的努力!!!……我的问题是如何将上述代码应用于基于编码器-解码器神经网络的聊天机器人系统???
嗨,Aklilu……您可能需要研究序列到序列模型来达到此目的
https://towardsdatascience.com/generative-chatbots-using-the-seq2seq-model-d411c8738ab5