Keras词级自然语言模型

简介: 语言模型是许多自然语言处理模型(如机器翻译和语音识别)中的关键元素,本文就两个小例子带你创建简单的语言模型,附加中有源码!

更多深度文章,请关注云计算频道:https://yq.aliyun.com/cloud


语言模型是许多自然语言处理模型(如机器翻译和语音识别)中的关键元素它可以根据给出的单词序列预测到序列中的下一个单词。在选择语言模型的框架时需要注意与语言模型的目的匹配。


本教程分为5个部分他们是:

1.语言建模框架。

2.模型1:单字输入,单字输出序列。

3.模型2:逐行序列。

4.模型3:双字输入,单字输出序列。

1语言建模框架

语言模型是挑战自然语言处理问题(如机器翻译和语音识别)较大模型中的关键组成部分。它们也可以作为独立模型开发,用于生成与源文本具有相同统计性的新序列。

语言模型可以同时学习和预测一个单词。网络的训练包括提供一系列的单词作为输入,在这个过程中,每一个输入序列都可以进行预测和学习。

同样地,在进行预测时,可以用一个或几个单词来表示这个过程然后将预测得到的单词进行收集,作为对后续预测输入的源文本。

因此,每个模型涉及源文本分解为输入和输出序列,使得模型可以学习如何预测单词。

在本教程中,我们将探索在深度学习库Keras中开发基于词的语言模型的三种不同方式。

模型1:单字输入,单字输出序列

我们可以从一个非常简单的模型开始。

给定一个单词作为输入,模型将学习预测序列中的下一个单词。例如:

X, y
Jack, and
and, Jill
Jill, went

第一步是将文本编码化为整数。

源文本中的每个小写单词都被赋予一个唯一的整数,我们可以将单词序列转换为整数序列。

Keras提供可用于执行此编码的Tokenizer类。首先,Tokenizer适合源文本来开发从单词到唯一整数的映射。然后通过调用texts_to_sequences()函数将文本序列转换为整数序列。

# integer encode text
tokenizer = Tokenizer()
tokenizer.fit_on_texts([data])
encoded = tokenizer.texts_to_sequences([data])[0]

我们需要通过访问word_index从经过训练Tokenizer检索词汇表的大小得出以后的词汇的大小,以便在模型中定义词嵌入层,并且使用一个热编码来编码输出词。

# determine the vocabulary size
vocab_size = len(tokenizer.word_index) + 1
print('Vocabulary Size: %d' % vocab_size)

在这个例子中,我们可以看到词汇的大小是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))

运行这一块显示,我们总共有24个输入输出来训练网络。

Total Sequences: 24

然后,我们可以将这些序列分解为输入(X)和输出元素(y)。这很简单,因为我们只有两列数据。

# split into X and y elements
sequences = array(sequences)
X, y = sequences[:,0],sequences[:,1]

我们将使用我们的模型来预测词汇表中所有单词的概率分布。这意味着我们需要将输出元素从单个整数转换为一个热门编码,对于词汇表中的每个单词都有一个0,对于实际单词来说,该值为1。这为网络提供了一个基本事实,从中可以计算出错误的地方并更新模型。

Keras提供了to_categorical(函数,我们可以使用该函数将整数转换为一个热门编码,同时指定类的数量作为词汇大小。

# one hot encode outputs
y = to_categorical(y, num_classes=vocab_size)

我们现在准备定义神经网络模型。

该模型使用了一个在输入层的学习词。这对词汇表中的每个单词都有一个实值向量,其中每个单词向量都有一个指定的长度。在本例中,我们将使用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())

网络的结构可以概括如下:

_________________________________________________________________
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
_________________________________________________________________

接下来,我们可以在编码的文本数据上编译和适配网络。从技术上讲,我们正在使用分类交叉熵损失函数构建一个解决分类问题(预测词汇中的单词)的模型

由于网络配置的原因, 我们选择了超出规定的配置,以确保我们可以专注于语言模型的框架

# compile network
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit network
model.fit(X, y, epochs=500, verbose=2)

模型拟合后,我们通过从词汇中传递给定的单词来测试它,让模型预测下一个单词。在这里,我们通过对“ 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)

这个过程可以重复几次,以建立一个生成的单词序列。

为了使这更容易,我们将这个行为包含在一个函数中,我们可以通过传入我们的模型和种子词来调用这个函数。

# 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

运行示例打印每个训练时期的损失和准确性。

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
我们可以看到,模型并不记住源序列,可能因为在输入序列中有一些不明确的地方模型并没有记住源序列。例如:

jack => and
jack => fell

在运行结束时,“ Jack ”被入并产生预测或新的序列。

我们基于一些元素的来源,会得到一个合理的序列作为输出。

Jack and jill came tumbling after down

这是一个很好的语言模型,但是没有充分利用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

这种方法可以允许模型使用每一行的上下文来帮助模型,在这种情况下,一个简单的单词进出模型会造成模糊性。

在这种情况下,这是以跨行预测单词为代价的,如果我们只对建模和生成文本行感兴趣,现在可能会很好。

请注意,在这种表示中,我们将需要填充序列以确保它们符合固定长度的输入。这是使用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))

接下来,我们可以填充准备好的序列。我们可以使用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)

接下来,我们可以将序列分成输入和输出元素,就像以前一样。

# split into input and output elements
sequences = array(sequences)
X, y = sequences[:,:-1],sequences[:,-1]
y = to_categorical(y, num_classes=vocab_size)

然后可以像以前那样定义模型,除了现在输入序列比单个单词长。具体来说,它们的长度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)

我们可以像以前一样使用该模型生成新的序列。所述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

附件中提供了完整的代码示例。运行该示例可以更好地适应源数据。增加的上下文使模型能够消除一些例子的歧义。仍然有两行文字以“ 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

在运行结束时,我们会生成两个不同种子词的序列:“ Jack ”和“ Jill ”。

第一个生成的行看起来不错,直接匹配源文本。第二个看着有点奇怪,那是因为网络只能在序列中看到“ Jill ”为了最后一行押韵,它强制输出单词“ Jill ”

Jack fell down and broke
Jill jill came tumbling after

这是例子恰当的表明了框架可能会生成更好的新行,但是它并不是一段好的输入行。

模型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)

完整的例子在附件中

再次运行这个例子得到合适的源文本的准确率在95%左右。

...
Epoch 496/500
Epoch 500/500
0s - loss: 0.0684 - acc: 0.9565

我们看看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

开始生成的第一个行是正确的,但是第二个不是。第二种情况是第四行的一个例子,与第一行的内容不一致。

我们可以看到,如何选择语言模型的框架必须和如何使用模型的要求是匹配的。如何选择语言模型的框架,以及如何使用模型的要求必须是兼容的。通常使用语言模型时需要仔细的设计,也许随后需要通过序列生成的现场测试来确认模型要求是否得到满足。

作者信息


Dr. Jason Brownlee 是一名机器学习从业者,学术研究人员,致力于帮助开发人员从入门到精通机器学习。

本文由北邮@爱可-爱生老师推荐,阿里云云栖组织翻译。

文章原标题《How to Develop Word-Based Neural Language Models in Python with Keras

作者:Dr.Jason Brownlee译者:乌拉乌拉,审阅:袁虎

文章为简译,更为详细的内容,请查看原文














相关文章
|
26天前
|
自然语言处理 索引
大模型开发: 解释自然语言处理(NLP)中的词嵌入。
**词嵌入技术在NLP中将文本转为数值表示,捕获词汇的语义和语法关系。过程包括:词汇索引、训练嵌入矩阵(如Word2Vec、GloVe、BERT)、文本向量化及向量输入到NLP模型(如情感分析、命名实体识别)。词嵌入是连接文本与机器理解的关键桥梁。**
25 2
|
8月前
|
机器学习/深度学习 自然语言处理 达摩院
通义大模型:打造更智能、更灵活的自然语言处理技术
大家好,今天我想向大家介绍一款备受瞩目的自然语言处理技术——通义大模型。作为一种基于深度学习的人工智能技术,通义大模型能够模拟人类的思维方式,实现更智能、更灵活的自然语言处理,为我们的生活和工作带来了极大的便利。
398 1
通义大模型:打造更智能、更灵活的自然语言处理技术
|
3月前
|
人工智能 自然语言处理 运维
NLP国内外大模型汇总列表[文心一言、智谱、百川、星火、通义千问、盘古等等]
NLP国内外大模型汇总列表[文心一言、智谱、百川、星火、通义千问、盘古等等]
NLP国内外大模型汇总列表[文心一言、智谱、百川、星火、通义千问、盘古等等]
|
4月前
|
自然语言处理 Python
【Python自然语言处理】文本向量化的六种常见模型讲解(独热编码、词袋模型、词频-逆文档频率模型、N元模型、单词-向量模型、文档-向量模型)
【Python自然语言处理】文本向量化的六种常见模型讲解(独热编码、词袋模型、词频-逆文档频率模型、N元模型、单词-向量模型、文档-向量模型)
244 0
|
2月前
|
机器学习/深度学习 自然语言处理 算法
大模型在自然语言处理中的应用
大模型在自然语言处理中的应用
70 1
|
2月前
|
人工智能 自然语言处理 机器人
自然语言开发AI应用,利用云雀大模型打造自己的专属AI机器人
如今,大模型层出不穷,这为自然语言处理、计算机视觉、语音识别和其他领域的人工智能任务带来了重大的突破和进展。大模型通常指那些参数量庞大、层数深、拥有巨大的计算能力和数据训练集的模型。 但不能不承认的是,普通人使用大模型还是有一定门槛的,首先大模型通常需要大量的计算资源才能进行训练和推理。这包括高性能的图形处理单元(GPU)或者专用的张量处理单元(TPU),以及大内存和高速存储器。说白了,本地没N卡,就断了玩大模型的念想吧。 其次,大模型的性能往往受到模型调优和微调的影响。这需要对模型的超参数进行调整和优化,以适应特定任务或数据集。对大模型的调优需要一定的经验和专业知识,包括对深度学
自然语言开发AI应用,利用云雀大模型打造自己的专属AI机器人
|
3月前
|
机器学习/深度学习 数据采集 人工智能
【NLP】Datawhale-AI夏令营Day3打卡:Bert模型
【NLP】Datawhale-AI夏令营Day3打卡:Bert模型
|
3月前
|
机器学习/深度学习 自然语言处理 搜索推荐
AIGC核心技术——自然语言处理(NLP)预训练大模型
【1月更文挑战第13天】AIGC核心技术——自然语言处理(NLP)预训练大模型
117 1
AIGC核心技术——自然语言处理(NLP)预训练大模型
|
3月前
|
自然语言处理 算法
自然语言处理第3天:Word2Vec模型
自然语言处理第3天:Word2Vec模型
50 1
|
3月前
|
机器学习/深度学习 自然语言处理 数据格式
训练你自己的自然语言处理深度学习模型,Bert预训练模型下游任务训练:情感二分类
训练你自己的自然语言处理深度学习模型,Bert预训练模型下游任务训练:情感二分类
55 0