更多深度文章,请关注云计算频道:https://yq.aliyun.com/cloud
语言模型是许多自然语言处理模型(如机器翻译和语音识别)中的关键元素,它可以根据给出的单词序列预测到序列中的下一个单词。在选择语言模型的框架时需要注意与语言模型的目的匹配。
本教程分为5个部分; 他们是:
1.语言建模框架。
2.模型1:单字输入,单字输出序列。
3.模型2:逐行序列。
4.模型3:双字输入,单字输出序列。
1、语言建模框架
语言模型是挑战自然语言处理问题(如机器翻译和语音识别)较大模型中的关键组成部分。它们也可以作为独立模型开发,用于生成与源文本具有相同统计属性的新序列。
语言模型可以同时学习和预测一个单词。网络的训练包括提供一系列的单词作为输入,在这个过程中,每一个输入序列都可以进行预测和学习。
同样地,在进行预测时,可以用一个或几个单词来表示这个过程,然后将预测得到的单词进行收集,作为对后续预测输入的源文本。
因此,每个模型都涉及将源文本分解为输入和输出序列,使得模型可以学习如何预测单词。
在本教程中,我们将探索在深度学习库Keras中开发基于单词的语言模型的三种不同方式。
模型1:单字输入,单字输出序列
我们可以从一个非常简单的模型开始。
给定一个单词作为输入,模型将学习预测序列中的下一个单词。例如:
X, y Jack, and and, Jill Jill, went
AI 代码解读
第一步是将文本编码化为整数。
源文本中的每个小写单词都被赋予一个唯一的整数,我们可以将单词序列转换为整数序列。
Keras提供可用于执行此编码的Tokenizer类。首先,Tokenizer适合源文本来开发从单词到唯一整数的映射。然后,通过调用texts_to_sequences()函数将文本序列转换为整数序列。
# integer encode text
tokenizer = Tokenizer()
tokenizer.fit_on_texts([data])
encoded = tokenizer.texts_to_sequences([data])[0]
AI 代码解读
我们需要通过访问word_index类,从经过训练的Tokenizer中检索词汇表的大小得出以后的词汇的大小,以便在模型中定义词的嵌入层,并且使用一个热编码来编码输出词。
# determine the vocabulary size
vocab_size = len(tokenizer.word_index) + 1
print('Vocabulary Size: %d' % vocab_size)
AI 代码解读
在这个例子中,我们可以看到词汇的大小是21个单词。
因为我们需要将最大的编码的整数指定为一个数组索引,例如编码1到21的数组编号为0到21或22的位置,所以我们需要再添加一个编码。
接下来,我们需要创建一个单词序列,以一个单词作为输入,一个单词作为输出来匹配模型。
# create word -> word sequences
sequences = list()
for i in range(1, len(encoded)):
sequence = encoded[i-1:i+1]
sequences.append(sequence)
print('Total Sequences: %d' % len(sequences))
AI 代码解读
运行这一块显示,我们总共有24个输入输出来训练网络。
Total Sequences: 24
AI 代码解读
然后,我们可以将这些序列分解为输入(X)和输出元素(y)。这很简单,因为我们只有两列数据。
# split into X and y elements
sequences = array(sequences)
X, y = sequences[:,0],sequences[:,1]
AI 代码解读
我们将使用我们的模型来预测词汇表中所有单词的概率分布。这意味着我们需要将输出元素从单个整数转换为一个热门编码,对于词汇表中的每个单词都有一个0,对于实际单词来说,该值为1。这为网络提供了一个基本事实,从中可以计算出错误的地方并更新模型。
Keras提供了to_categorical()函数,我们可以使用该函数将整数转换为一个热门编码,同时指定类的数量作为词汇量大小。
# one hot encode outputs
y = to_categorical(y, num_classes=vocab_size)
AI 代码解读
我们现在准备定义神经网络模型。
该模型使用了一个在输入层中的学习词。这对词汇表中的每个单词都有一个实值向量,其中每个单词向量都有一个指定的长度。在本例中,我们将使用10维投影。输入序列包含一个单词,因此input_length = 1。
该模型有一个隐藏的LSTM层,有50个单元。这远远超出了需要。在词汇表中,输出层由一个神经元组成,并使用一个softmax激活函数,以确保能够标准输出。
# define model
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())
AI 代码解读
网络的结构可以概括如下:
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_1 (Embedding) (None, 1, 10) 220 _________________________________________________________________ lstm_1 (LSTM) (None, 50) 12200 _________________________________________________________________ dense_1 (Dense) (None, 22) 1122 ================================================================= Total params: 13,542 Trainable params: 13,542 Non-trainable params: 0 _________________________________________________________________
AI 代码解读
接下来,我们可以在编码的文本数据上编译和适配网络。从技术上讲,我们正在使用分类交叉熵损失函数构建一个解决多元分类问题(预测词汇中的单词)的模型。
由于网络配置的原因, 我们选择了超出规定的配置,以确保我们可以专注于语言模型的框架上。
# compile network model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # fit network model.fit(X, y, epochs=500, verbose=2)
AI 代码解读
模型拟合后,我们通过从词汇中传递给定的单词来测试它,让模型预测下一个单词。在这里,我们通过对“ Jack ”进行编码并调用model.predict_classes()来获取预测单词的整数输出。然后在词汇映射中查找这个关联词。
# evaluate 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)
AI 代码解读
这个过程可以重复几次,以建立一个生成的单词序列。
为了使这更容易,我们将这个行为包含在一个函数中,我们可以通过传入我们的模型和种子词来调用这个函数。
# generate a sequence from the model
def generate_seq(model, tokenizer, seed_text, n_words):
in_text, result = seed_text, seed_text
# generate a fixed number of words
for _ in range(n_words):
# encode the text as integer
encoded = tokenizer.texts_to_sequences([in_text])[0]
encoded = array(encoded)
# predict a word in the vocabulary
yhat = model.predict_classes(encoded, verbose=0)
# map predicted word index to word
out_word = ''
for word, index in tokenizer.word_index.items():
if index == yhat:
out_word = word
break
# append to input
in_text, result = out_word, result + ' ' + out_word
return result
AI 代码解读
运行该示例,打印出每个训练时期的损失和准确性。
Epoch 496/500 0s - loss: 0.2358 - acc: 0.8750 Epoch 497/500 0s - loss: 0.2355 - acc: 0.8750 Epoch 498/500 0s - loss: 0.2352 - acc: 0.8750 Epoch 499/500 0s - loss: 0.2349 - acc: 0.8750 Epoch 500/500 0s - loss: 0.2346 - acc: 0.8750
AI 代码解读
我们可以看到,模型并不记住源序列,可能因为在输入序列中有一些不明确的地方模型并没有记住源序列。例如:
AI 代码解读
jack => and jack => fell
AI 代码解读
在运行结束时,“ Jack ”被输入并产生预测或新的序列。
我们基于一些元素的来源,会得到一个合理的序列作为输出。
Jack and jill came tumbling after down
AI 代码解读
这是一个很好的语言模型,但是没有充分利用LSTM处理输入序列的能力问题,但通过使用更广泛的上下文来消除一些不明确的成对序列。
模型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
AI 代码解读
这种方法可以允许模型使用每一行的上下文来帮助模型,在这种情况下,一个简单的单词进出模型会造成模糊性。
在这种情况下,这是以跨行预测单词为代价的,如果我们只对建模和生成文本行感兴趣,现在可能会很好。
请注意,在这种表示中,我们将需要填充序列以确保它们符合固定长度的输入。这是使用Keras时的要求。
首先,我们可以使用已经适合源文本的Tokenizer逐行地创建整数序列。
# create line-based sequences
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))
AI 代码解读
接下来,我们可以填充准备好的序列。我们可以使用Keras中提供的pad_sequences()函数来做到这一点。首先涉及找到最长的序列,然后用它作为填充所有其他序列的长度。
# pad input 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)
AI 代码解读
接下来,我们可以将序列分成输入和输出元素,就像以前一样。
# split into input and output elements
sequences = array(sequences)
X, y = sequences[:,:-1],sequences[:,-1]
y = to_categorical(y, num_classes=vocab_size)
AI 代码解读
然后可以像以前那样定义模型,除了现在输入序列比单个单词长。具体来说,它们的长度是max_length-1,因为当我们计算序列的最大长度时,它们包括输入和输出元素。
# define model 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()) # compile network model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # fit network model.fit(X, y, epochs=500, verbose=2)
AI 代码解读
我们可以像以前一样使用该模型生成新的序列。将所述generate_seq()函数更新通过后加入预测,以输入字每次迭代列表来建立一个输入序列。
# generate a sequence from a language model def generate_seq(model, tokenizer, max_length, seed_text, n_words): in_text = seed_text # generate a fixed number of words for _ in range(n_words): # encode the text as integer encoded = tokenizer.texts_to_sequences([in_text])[0] # pre-pad sequences to a fixed length encoded = pad_sequences([encoded], maxlen=max_length, padding='pre') # predict probabilities for each word yhat = model.predict_classes(encoded, verbose=0) # map predicted word index to word out_word = '' for word, index in tokenizer.word_index.items(): if index == yhat: out_word = word break # append to input in_text += ' ' + out_word return in_text
AI 代码解读
附件中提供了完整的代码示例。运行该示例可以更好地适应源数据。增加的上下文使模型能够消除一些例子的歧义。仍然有两行文字以“ Jack ” 开头,可能仍然是网络的问题。
Epoch 496/500 0s - loss: 0.1039 - acc: 0.9524 Epoch 497/500 0s - loss: 0.1037 - acc: 0.9524 Epoch 498/500 0s - loss: 0.1035 - acc: 0.9524 Epoch 499/500 0s - loss: 0.1033 - acc: 0.9524 Epoch 500/500 0s - loss: 0.1032 - acc: 0.9524
AI 代码解读
在运行结束时,我们会生成两个不同种子词的序列:“ Jack ”和“ Jill ”。
第一个生成的行看起来不错,直接匹配源文本。第二个看着有点奇怪,那是因为网络只能在序列中看到“ Jill ”,为了最后一行押韵,它强制输出单词“ Jill ”。
Jack fell down and broke Jill jill came tumbling after
AI 代码解读
这是例子恰当的表明了框架可能会生成更好的新行,但是它并不是一段好的输入行。
模型3:双字输入,单字输出序列
我们可以使用单词输入法和整个句子输入法之间的媒介,输入一个单词的子序列。
这将提供两个框架之间的权衡,从而允许生成新的线路。
我们将使用3个单词作为输入来预测一个字作为输出。序列的制备与第一个例子非常相似,除了源序列阵列中的偏移量不同之外,如下所示:
# encode 2 words -> 1 word sequences = list() for i in range(2, len(encoded)): sequence = encoded[i-2:i+1] sequences.append(sequence)
AI 代码解读
完整的例子在附件中
再次运行这个例子得到合适的源文本的准确率在95%左右。
... Epoch 496/500 Epoch 500/500 0s - loss: 0.0684 - acc: 0.9565
AI 代码解读
我们看看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
AI 代码解读
开始生成的第一个行是正确的,但是第二个不是。第二种情况是第四行的一个例子,与第一行的内容不一致。
我们可以看到,如何选择语言模型的框架必须和如何使用模型的要求是匹配的。如何选择语言模型的框架,以及如何使用模型的要求必须是兼容的。通常使用语言模型时需要仔细的设计,也许随后需要通过序列生成的现场测试来确认模型要求是否得到满足。
作者信息
Dr. Jason Brownlee 是一名机器学习从业者,学术研究人员,致力于帮助开发人员从入门到精通机器学习。
本文由北邮@爱可可-爱生活老师推荐,阿里云云栖社区组织翻译。
文章原标题《How to Develop Word-Based Neural Language Models in Python with Keras》
作者:Dr.Jason Brownlee译者:乌拉乌拉,审阅:袁虎
文章为简译,更为详细的内容,请查看原文