10 如何在Keras中学习和加载Word Embedding模型

Word Embedding模型提供了单词及其相对含义的密集表示。它们是对使用的稀疏表示文本的词袋模型的改进。我们可以单独地从文本数据中训练Word Embedding模型模型,并在多个项目中重用,也可以作为在文本数据上拟合神经网络模型的一部分和具体任务绑定一起训练。在本教程中,您将了解在深度学习keras框架中如何使用Word Embedding模型。完成本教程后,您将了解:

  • 关于单词嵌入和Keras通过Embedding层支持词嵌入模型。
  • 如何在拟合神经网络时训练词嵌入模型。
  • 如何在神经网络中使用预先训练词嵌入模型。

10.1 教程概述

本教程分为以下几部分:

  1. 单词嵌入
  2. Keras嵌入层
  3. 学习嵌入的示例
  4. 使用预训练GloVe嵌入的示例
  5. 训练Word Embedding模型模型清洗文本的提示

10.2 单词嵌入

单词嵌入是使用密集向量表示来表示单词和文档的一类方法。它是对传统的词袋模型编码方案的改进,词袋模型使用巨大稀疏矢量来表示每个单词或者对矢量中的每个单词进行评分以表示整个词汇表。显然由词袋模型编码的单词或文档将主要由零值组成的大向量表示。

相反,在嵌入模型中,单词由密集向量表示,向量表示单词到连续向量空间的映射。向量空间中的单词的位置是从文本中学习得到的,并且每个单词的位置都是由其在文本中的伴生词来决定的。学习向量空间中的单词的位置被称为其嵌入。从文本中学习单词嵌入的两种流行方法示例包括:

  • Word2Vec
  • GloVe

除了这些精心设计的方法之外,还可以把学习单词嵌入模型作为深度学习模型的一部分。这可能会比较耗时,但可以根据特定的训练数据集定制自己适合的词嵌入模型。

10.3 Keras嵌入层

Keras提供了一个Embedding层,可用于神经网络处理文本数据,它要求输入数据是整数编码,因此每个单词由唯一的整数表示。可以使用Keras提供的Tokenizer API来执行该数据准备步骤。

使用随机权重初始化Embedding层,并将学习训练数据集中所有单词的嵌入。Embedding层非常灵活,可以以多种方式使用,例如:

  • 它可以用于单独训练词嵌入模型,保存下来,并留以后用于其他模型。
  • 它可以用作深度学习模型的一部分,其中词嵌入模型与模型本身一起被学习。
  • 它可以用于加载预训练词嵌入模型,一种转移学习方法。

嵌入层被定义为网络的第一个隐藏层。它必须指定3个参数:

  • Input_dim:这是文本数据中词汇表的大小。例如,如果您的数据整数编码为0-10之间的值,那么词汇表的大小将为11个单词。
  • Output_dim:这是将单词嵌入的向量空间的大小。它为每个单词定义了该层的输出向量的大小。例如,它可以32100甚至更大。最好在实际问题中测试不同的值。
  • Input_length:这是输入序列的长度,正如您为Keras模型的任何输入层定义的那样。例如,如果所有输入文档都包含1000个单词,则为1000

例如,下面我们定义一个词汇表为200的嵌入层(例如,从0199的整数编码词,包括0199),一个32维的向量空间,其中将嵌入单词,输入文档每个有50个单词。

e = Embedding(200, 32, input_length=50)

代码清单10.1:创建单词嵌入层的示例

Embedding层的权重必须被训练,如果将训练好的模型保存到文件,则将包括Embedding层的权重。嵌入层的输出是2D矢量,在输入的单词序列(输入文档)中都会有一个向量对应。如果希望将Dense图层直接连接到嵌入图层,则必须首先使用Flatten层将2D输出矩阵展平为1D向量。现在,让我们看看我们如何在实践中使用Embedding层。

10.4 学习嵌入的示例

在本节中,我们将看看如何在将神经网络拟的文本分类问题中训练和使用词嵌入模型。我们将定义一个有10个文本文档的小问题,每个文档都有一个关于学生提交的作业的评价。每个文本文档可被分为正面情绪1或负面情绪0。最后归纳成一个简单的情感分析问题。首先,我们将定义文档及其类标签。

# define documents
docs = ['Well done!',
        'Good work',
        'Great effort',
        'nice work',
        'Excellent!',
        'Weak',
        'Poor effort!',
        'not good',
        'poor work',
        'Could have done better.']
# define class labels
labels = array([1, 1, 1, 1, 1, 0, 0, 0, 0, 0])

代码清单10.2:一个小型人为分类问题的例子

接下来,我们使用整数编码每个文档,这意味着Embedding层的输入是整数序列。我们可以尝试其他更复杂的单词模型编码:计数或TF-IDFKeras提供了one_hot()函数将每个单词的哈希值作为其有效的整数编码。我们设定词汇量大小为50,这远大于问题所需,目的是减少哈希函数冲突概率

# integer encode the documents
vocab_size = 50
encoded_docs = [one_hot(d, vocab_size) for d in docs]
print(encoded_docs)

代码清单10.3:整数对文本进行编码

序列具有不同的长度,Keras更喜欢输入以进行矢量化,并且所有输入具有相同的长度。我们将填充所有输入序列的长度为4.再次,我们可以使用内置的Keras函数执行此操作,在本例中为pad_sequences()函数。

# pad documents to a max length of 4 words
max_length = 4
padded_docs = pad_sequences(encoded_docs, maxlen=max_length, padding= 'post' )
print(padded_docs)

代码清单10.4:填充编码文本

我们现在准备将嵌入层定义为神经网络模型的一部分。嵌入层的词汇量为50,输入长度为4.我们将选择8维的小嵌入空间。该模型是一个简单的二元分类模型。重要的是,嵌入层的输出将是4个向量,每个向量8个维度,每个单词一个。我们将其展平为一个32元素向量,以传递给Dense输出层。

# define the model
model = Sequential()
model.add(Embedding(vocab_size, 8, input_length=max_length))
model.add(Flatten())
model.add(Dense(1, activation= 'sigmoid' ))
# compile the model
model.compile(optimizer= 'adam' , loss= 'binary_crossentropy' , metrics=[ 'acc' ])
# summarize the model
model.summary()

代码清单10.5:使用嵌入输入定义一个简单模型

最后,我们可以拟合和评估分类模型。

# fit the model
model.fit(padded_docs, labels, epochs=50, verbose=0)
# evaluate the model
loss, accuracy = model.evaluate(padded_docs, labels, verbose=0)
print( 'Accuracy:%f' % (accuracy*100))

代码清单10.6:训练模型和打印模型的准确性。

完整的代码清单如下。

from numpy import array
from keras.preprocessing.text import one_hot
from keras.preprocessing.sequence import pad_sequences
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers.embeddings import Embedding

# define documents
docs = [' Well done! ',
        ' Good work ',
        ' Great effort ',
        ' nice work ',
        ' Excellent! ',
        ' Weak ',
        ' Poor effort! ',
        ' not good ',
        ' poor work ',
        ' Could have done better. ']
# define class labels
labels = array([1, 1, 1, 1, 1, 0, 0, 0, 0, 0])
# integer encode the documents
vocab_size = 50
encoded_docs = [one_hot(d, vocab_size) for d in docs]
print(encoded_docs)
# pad documents to a max length of 4 words
max_length = 4
padded_docs = pad_sequences(encoded_docs, maxlen=max_length, padding='post')
print(padded_docs)
# define the model
model = Sequential()
model.add(Embedding(vocab_size, 8, input_length=max_length))
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))
# compile the model
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['acc'])
# summarize the model
model.summary()
# fit the model
model.fit(padded_docs, labels, epochs=1000, verbose=0)
# evaluate the model
loss, accuracy = model.evaluate(padded_docs, labels, verbose=0)
print('Accuracy:%f' % (accuracy * 100))

代码清单10.7:使用嵌入输入层拟合和评估Keras模型的示例

首先运行该示例打印整数编码的文档。

[[42, 7], [43, 3], [49, 12], [34, 3], [35], [32], [6, 12], [36, 43], [6, 3], [41, 21, 7, 10]]

代码清单10.8:编码文档的输出示例

然后打印每个文档的填充版本,使它们均匀长度。

[[42  7  0  0]

 [43  3  0  0]

 [49 12  0  0]

 [34  3  0  0]

 [35  0  0  0]

 [32  0  0  0]

 [ 6 12  0  0]

 [36 43  0  0]

 [ 6  3  0  0]

 [41 21  7 10]]

代码清单10.9:填充编码文档的示例输出

定义网络后,将打印图层摘要。我们可以看到,正如预期的那样,嵌入层的输出是一个4 x 8矩阵,它被Flatten层展平为32个元素的向量。

_________________________________________________________________

Layer (type)                 Output Shape              Param #   

==========================================================

embedding_3 (Embedding)      (None, 4, 8)              400       

_________________________________________________________________

flatten_3 (Flatten)          (None, 32)                0         

_________________________________________________________________

dense_3 (Dense)              (None, 1)                 33        

==========================================================

Total params: 433

Trainable params: 433

Non-trainable params: 0

_________________________________________________________________

代码清单10.10:模型摘要的示例输出

最后,打印训练模型的准确性,表明它完美地学习了训练数据集(这并不奇怪)。

意:鉴于神经网络的随机性,您的具体结果可能会有所不同。考虑运行几次示例。

Accuracy:100.000000

代码清单10.11:模型精度的示例输出

您可以将已学习的权重从嵌入层保存到文件中,以便以后在其他模型中使用。您可以使用此模型对测试数据集中的其他文档进行分类。接下来,让我们看看在Keras中加载预先训练好的单词嵌入。

10.5 使用预训练GloVe嵌入的示例

Keras嵌入层还可以使用其他预训练模型。在自然语言处理领域中训练、保存和免费发布的词嵌入模型随处可见。例如,GloVe方法背后的研究人员在公共领域许可下在其网站上提供了一套预训练的词嵌入模型。

最小的嵌入模型包是822兆字节,称为glove.6B.zip。它是在10亿个令牌(单词)的数据集上训练得到的,词汇量为40万字,有不同的嵌入矢量大小:50,100,200300维度。您可以下载此词嵌入模型集合,使用预训练嵌入的权重初始化kerasEmbedding层,然后使用Embedding层编码问题训练集中的单词。

这个例子的灵感来自Keras项目中的一个例子:pretrained_word_embeddings.py

下载并解压后,您将看到一些文件,其中一个是glove.6B.100d.txt,其中包含100维版本的词嵌入模型。如果您查看文件内部,您将看到一个标记(单词),后面跟着每行的权重(100个数字)。例如,下面是词嵌入模型ASCII文本文件的第一行显示嵌入的

the -0.038194 -0.24487 0.72812 -0.39961 0.083172 0.043953 -0.39141 0.3344 -0.57545 0.087459 0.28787 -0.06731 0.30906 -0.26384 -0.13231 -0.20757 0.33395 -0.33848 -0.31743 -0.48336 0.1464 -0.37304 0.34577 0.052041 0.44946 -0.46971 0.02628 -0.54155 -0.15518 -0.14107 -0.039722 0.28277 0.14393 0.23464 -0.31021 0.086173 0.20397 0.52624 0.17164 -0.082378 -0.71787 -0.41531 0.20335 -0.12763 0.41367 0.55187 0.57908 -0.33477 -0.36559 -0.54857 -0.062892 0.26584 0.30205 0.99775 -0.80481 -3.0243 0.01254 -0.36942 2.2167 0.72201 -0.24978 0.92136 0.034514 0.46745 1.1079 -0.19358 -0.074575 0.23353 -0.052062 -0.22044 0.057162 -0.15806 -0.30798 -0.41625 0.37972 0.15006 -0.53212 -0.2055 -1.2526 0.071624 0.70565 0.49744 -0.42063 0.26148 -1.538 -0.30223 -0.073438 -0.28312 0.37104 -0.25217 0.016215 -0.017099 -0.38984 0.87424 -0.72569 -0.51058 -0.52028 -0.1459 0.8278 0.27062

代码清单10.12:单词'the'GloVe单词向量示例

与前一节一样,第一步是定义示例,将它们编码为整数,然后将序列填充为相同的长度。在这种情况下,我们需要能够将单词映射到整数以及将整数映射到单词。Keras提供了一个Tokenizer类,可以拟合训练数据,可以通过调用Tokenizer类上的texts to sequences()方法将文本转换为序列,并提供对word_index属性中单词到整数的字典映射的访问。

# define documents
docs = ['Well done!',
        'Good work',
        'Great effort',
        'nice work',
        'Excellent!',
        'Weak',
        'Poor effort!',
        'not good',
        'poor work',
        'Could have done better.']
# define class labels
labels = array([1, 1, 1, 1, 1, 0, 0, 0, 0, 0])
# prepare tokenizer
t = Tokenizer()
t.fit_on_texts(docs)
vocab_size = len(t.word_index) + 1
# integer encode the documents
encoded_docs = t.texts_to_sequences(docs)
print(encoded_docs)
# pad documents to a max length of 4 words
max_length = 4
padded_docs = pad_sequences(encoded_docs, maxlen=max_length, padding='post')
print(padded_docs)

代码清单10.13:定义编码和填充示例文档

接下来,我们需要将整个GloVe字嵌入文件作为嵌入数组的字典加载到内存中。

# load the whole embedding into memory
embeddings_index = dict()
f = open( 'glove.6B.100d.txt' )
for line in f:
    values = line.split()
    word = values[0]
    coefs = asarray(values[1:], dtype= 'float32' )
    embeddings_index[word] = coefs
f.close()
print( ' Loaded %s word vectors. ' % len(embeddings_index))

代码清单10.14:将GloVe字嵌入到内存中。

这很慢。最好能过滤训练数据中出现不多的单词。接下来,我们需要为训练数据集中的每个单词创建一个嵌入矩阵。我们可以通过枚举Tokenizer.word_index中的所有唯一单词并从加载的GloVe嵌入中查找词嵌入权重向量来实现。结果是我们在训练期间仅看到的是单词的权重矩阵。

# create a weight matrix for words in training docs
embedding_matrix = zeros((vocab_size, 100))
for word, i in t.word_index.items():
    embedding_vector = embeddings_index.get(word)
    if embedding_vector is not None:
        embedding_matrix[i] = embedding_vector

代码清单10.15:将单词嵌入转换为权重矩阵。

现在我们可以像以前一样定义我们的模型,拟合和评估它。关键的区别在于嵌入层可以使用GloVe字嵌入权重进行转化文本到数字。我们选择了100维版本,因此Embeddingoutput_dim必须设置成100,最后,我们不想更新此模型中的单词权重,因此我们将模型的Embedding层可训练属性设置为False

e = Embedding(vocab_size, 100, weights=[embedding_matrix], input_length=4, trainable=False)

代码清单10.16:使用预加载的权重创建一个嵌入层

下面列出了完整的工作示例。

from numpy import array
from numpy import asarray
from numpy import zeros
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import Embedding

# define documents
docs = ['Well done!',
        'Good work',
        'Great effort',
        'nice work',
        'Excellent!',
        'Weak',
        'Poor effort!',
        'not good',
        'poor work',
        'Could have done better.']
# define class labels
labels = array([1, 1, 1, 1, 1, 0, 0, 0, 0, 0])
# prepare tokenizer10.5. Example of Using Pre-Trained GloVe Embedding141
t = Tokenizer()
t.fit_on_texts(docs)
vocab_size = len(t.word_index) + 1
# integer encode the documents
encoded_docs = t.texts_to_sequences(docs)
print(encoded_docs)
# pad documents to a max length of 4 words
max_length = 4
padded_docs = pad_sequences(encoded_docs, maxlen=max_length, padding='post')
print(padded_docs)
# load the whole embedding into memory
embeddings_index = dict()
f = open('glove/glove.6B.100d.txt', mode='rt', encoding='utf-8')
for line in f:
    values = line.split()
    word = values[0]
    coefs = asarray(values[1:], dtype='float32')
    embeddings_index[word] = coefs
f.close()
print('Loaded %s word vectors.' % len(embeddings_index))
# create a weight matrix for words in training docs
embedding_matrix = zeros((vocab_size, 100))
for word, i in t.word_index.items():
    embedding_vector = embeddings_index.get(word)
    if embedding_vector is not None:
        embedding_matrix[i] = embedding_vector
# define model
model = Sequential()
e = Embedding(vocab_size, 100, weights=[embedding_matrix], input_length=4, trainable=False)
model.add(e)
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))
# compile the model
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['acc'])
# summarize the model
model.summary()
# fit the model
model.fit(padded_docs, labels, epochs=50, verbose=0)
# evaluate the model
loss, accuracy = model.evaluate(padded_docs, labels, verbose=0)
print('Accuracy: %f' % (accuracy * 100))

代码清单10.17:将预先训练的GloVe权重加载到嵌入输入层的示例

运行示例可能需要更长的时间,但随后证明它能够适应这个简单的问题。

意:鉴于神经网络的随机性,您的具体结果可能会有所不同。考虑运行几次示例。

。。。。。。

Accuracy: 100.000000

代码清单10.18:将预先训练的GloVe权重加载到嵌入输入层的示例输出。

在实践中,我建议您尝试使用经过预先训练的嵌入来学习单词嵌入,该嵌入是固定的并且尝试在预训练嵌入的基础上进行学习,了解哪种方法最适合您的具体问题。

10.6 词嵌入模型清洗文本的小贴士

最近,自然语言处理领域已逐渐从词袋模型和单词编码转向词嵌入模型。词嵌入模型的好处在于,它们将每个单词编码为一个密集的向量,捕获其在训练文本中的相对含义,这意味着在嵌入空间中将自动学习诸如大小写、拼写、标点符号等单词的变体。反过来,这可能意味着您的文本所需的清洗量可能会更少,也许完全不要传统的文本清理流程。例如,可能不在需要清洗停止词和删除标点符号。

Tomas MikolovWord2Vec的开发者之一,Word2Vec是一种流行的文字嵌入方法。他建议在学习单词嵌入模型时只需要做非常少的文本清理工作。下面是他在回答有关如何最好地为Word2Vec准备文本数据的问题时的回答。

没有普遍的答案,这一切都取决于你打算使用的词向量。根据我的经验,通常可以从单词中断开(或删除)标点符号,有时还会将所有字符转换为小写。也可以使用某些单个标记替换所有数字(可能大于某些常量)。

所有这些预处理步骤旨在减少词汇量,而不删除任何重要内容(在某些情况下,当你小写某些单词时可能出现问题,即'Bush''bush'不同,而'Another'通常具有与another的意义相同。词汇量越小,记忆复杂度越低,估计词的参数越稳健。您还必须以相同的方式预处理测试数据。

[...]

简而言之,如果你要进行实验,你会更好地理解这一切。

 


0 条 查看最新 评论

没有评论
暂时无法发表评论