第十六章 开发单词级模型
语言模型涉及到给定现有的单词序列情况下预测下一个单词,语言模型是许多自然语言模型中的关键元素,例如机器翻译和语音识别,语言模型的框架选择必须与语言模型使用的方式相匹配。在本章中你将了解在童谣生成短序列时,语言模型的框架如何影响模型的性能,接下来你将了解:
- 为给定的应用程序开发基于单词级的语言模型
- 在单词级的语言模型中如何开发单字,双字和行的框架
- 如何使用拟合语言模型生成序列
16.1 概述
本章分为以下几个部分:
- 语言框架建模
- 杰克和吉尔童谣
- 模型1:单字输入,单字输出序列
- 模型2:逐行序列
- 模型3:双字输入,单字输出序列
16.2 语言框架建模
统计语言模型是从原始文本中学习,并且在给定已经存在于单词序列的情况下预测序列后一个单词的概率。语言模型是用于挑战自然语言处理问题大型模型中的关键组件,例如机器翻译,和语音识别。他们也可以作为独立模型开发,并用于生成与源文本具有相同统计属性的新序列。
语言模型的学习和预测一个单词,网络的训练用单词序列作为输入,每次处理一个单词,其中可以为每个输入序列进行预测和学习。类似的,在进行预测时,可以用一个或多个单词作为起点,预测给定单词后面预测的下一个单词,然后收集预测的单词并将其作为起点,预测后面的一个,即将收集预测的单词并作为后续预测的输入,这样就建立起了输出序列。因此每个模型都要考虑拆分原文本作为输入和输出序列,使模型可以学习预测单词,很多方法可以从源文本构建序列进行语言建模。在接下来,我将介绍Keras深度学习库中开发基于单词的计模型的3中不同方法。文本问题没有最好的适用于所有类型的问题,不同的问题只能分别对待。
16.3 杰克和吉尔童谣
这个童谣是一个简单的童谣:
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
我们将使用它作为我们的原文本
data = "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"
16.4 模型1:单字输入,单字输出
输入 | 输出 |
---|---|
Jack | and |
and | Jill |
Jill | went |
… | … |
第一步:将文本编码为整数,源文本的每一个小写字都被赋予一个唯一的整数,我们可以将单词序列转化整数序列。Keras中提供了可用于执行此编码的Tokenizer类。首先,Tokenizer拟合原文本,以开发到单词到唯一整数的映射。然后调用text_to_sequences函数将文本序列转化为整数序列。
tokenizer= Tokenizer()
tokenizer.fit_on_texts([data])
encoded = tokenizer.texts_to_sequences([data])[0]
我们需要确定词汇大小,以稍后在模型中定义嵌入层的大小,以及使用one-hot编码对输出单词进行编码,可以通过访问word_index属性从训练好的Tokenizer中获得词汇表的大小。
vocab_size = len(tokenizer.word_index)+1
print("Vocabulary Size: %d"%vocab_siez)
从中我们得到词汇表中的单词数量为21,我们给其+1,原因在于我们需要将最大编码字的整数指定为数组索引,例如,编码为1到21的字,其中数组指示0到21或22的位置,接下来我们需要创建单词序列以拟合模型,其中一个单词作为输入,一个单词作为输出
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输入和输出来训练网络
然后我们将序列分成输入(x)和输出(y),这很简单,因为我们在数据中只有两列
sequences = ny.array(sequences)
x,y = sequences[:,0],sequences[:,1]
我们训练我们的模型来预测词汇表中所有单词的概率分布,这意味着我们需要将输出元素从单个整数转换为one-hot编码。我们可以根据输入和真实单词的one-hot编码对比计算损失函数并更新模型,Keras提供了to_categorical函数,可以使用它将整数转换为one-hot编码,同时指定词汇表大小为分类的数量。
y = to_categorical(y,num_classes = vocab_size)
现在我们定义神经网络模型,该模型在输入层中使用嵌入层学习单词,词汇表中的每个单词都会生成一个实际向量,向量具有同一的长度,我们定义为10,输入序列是单个词,因此input_length = 1,跟着输入层接着是LSTM层,具有64个单元,这远远超出了需求,输出层是一个全连接层,负责确定是词汇表中的那个单词,分类大小就是词汇表的大小,使用softmax激活函数输出可能单词的概率分布
def define_model(vocab_size):
model = Sequential()
model.add(Embedding(vocab_size,10,input_length=1))
model.add(LSTM(64))
model.add(Dense(vocab_size,activation='softmax'))
model.compile(
loss='categorical_crossentropy',
optimizer='adam',
metrics=['accuracy']
)
model.summary()
return model
对于本教程中的每个示例,我们将使用相同的通用网络结构,,对学习的嵌入层进行微小更改,我们可以在编码的文本数据上编译和拟合网络,从技术上将,我们正在建模多分类问题(预测词汇表中的单词),因此使用分类交叉熵损失函数,我们在每个迭代结束时使用有效的Adam算法实现梯度下降和跟踪精度,该模型训练500次迭代,也许需要不了这么多的迭代训练,网络配置没有针于此和后续实验进行调整,选择一个性能余量的配置,以确保我们可以专注于语言模型的框架设计。
在模型拟合之后,我们通过从词汇表中选定的单词传递给它并让模型预测它的下一个单词,在这里我们选择“Jack”并编码传递给模型,然后调用model.predict_classes还获取预测单词的整数输出,然后根据词汇表和整数的映射找到确定的单词。
text ='Jack'
print(text)
import numpy as ny
encoded = tokenizer.texts_to_sequences([text])[0]
encoded = ny.array(encoded)
yhat = model.predict_classes(encoded)
for word,index in tokenizer.word_index.items():
if index == yhat:
print(word)
然后重复该过程几次以建立生成的单词序列,为了使这更容易,我们将行为结合到一个函数中,我们可以通过传入我们的模型和想要传入的单词来调用它。
def generate_seq(model,tokenizer,seed_text,n_words):
in_text,result = seed_text,seed_text
for _ in range(n_words):
encoded = tolenizer.texts_to_sequences([in_text])[0]
encoded = ny.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
将整个过程结合在一起:
import numpy as ny
from tensorflow.python.keras.preprocessing.text import Tokenizer
from tensorflow.python.keras.utils import to_categorical
from tensorflow.python.keras.models import Sequential
from tensorflow.python.keras.layers import LSTM,Dense,Embedding
def generate_seq(model,tokenizer,seed_text,n_word):
in_text,result =seed_text,seed_text
for _ in range(n_word):
encoded = tokenizer.texts_to_sequences([in_text])[0]
encoded = ny.array(