来源 | Medium
编辑 | 代码医生团队
前言
普鲁塔克的贵族希腊人和罗马人的生活,也被称为平行生活或只是普鲁塔克的生活,是一系列着名的古希腊人和罗马人的传记,从忒修斯和Lycurgus到马库斯安东尼斯。
研究了使用gensim库训练自己的单词嵌入。在这里将主要关注利用TensorFlow 2.0平台的嵌入层一词; 目的是更好地了解该层如何工作以及它如何为更大的NLP模型的成功做出贡献。
为了帮助轻松复制,已将代码改编为Google Colab,并突出显示了该平台的独特之处 - 否则整个代码可以使用Python 3.6+和相关软件包在本地计算机上运行。代码在整篇文章中介绍,但将跳过一些补充或次要代码 - 整个代码可以在Github存储库中找到。
本分析中使用的文本已由Project Gutenberg提供。
https://colab.research.google.com/notebooks/welcome.ipynb
https://github.com/mlai-demo/TextExplore
https://www.gutenberg.org/ebooks/674
把事情搞定
在Colab上,运行时类型更改为GPU,然后导入最新的TensorFlow版本 - 下面的代码片段仅适用于Colab,否则只需使用pip或conda install命令在机器上上传最新的TensorFlow。
from __future__ import absolute_import, division, print_function, unicode_literals
try:
# %tensorflow_version only exists in Colab.
%tensorflow_version 2.x
except Exception:
pass
import tensorflow as tf
print(tf.__version__)
还需要操作系统和正则表达式库,然后保存并打印文件路径以供将来参考:
import os
import re
fpath = os.getcwd(); fpath
将文本(Plutarch.txt)导入到Google Colab驱动器中 - 需要记住,文件是短暂的,需要在每次使用平台后更长时间上传它们:
from google.colab import files
uploaded = files.upload()
for fn in uploaded.keys():
print('User uploaded file "{name}" with length {length} bytes'.format(
name=fn, length=len(uploaded[fn])))
# Click Files tab - the uploaded file(s) will be there
上面的代码也可以在Colab的Code Snippets选项卡下找到 - 除了许多其他非常有用的代码之外。执行此代码时,将看到Colab上传文件,然后可以单击左侧的Colab Files选项卡以确保该文件与Google的默认Sample Data目录一起存在。
阅读文本并做一些基本的正则表达式操作:
import re
corpus = open(fpath + '/Plutarch.txt', 'rb').read().lower().decode(encoding='utf-8')
corpus = re.sub('\n', ' ', corpus) #remove new line
corpus = re.sub('\r', ' ', corpus) #remove "return"
由于将文本分成句子,因此新行对分析没有意义。此外在使用文本标记器时,注意到“\ r”(表示回车)会创建错误的唯一单词,例如“us”和“us\ r” - 再次,在案例中并不重要。因此,“\ n”和“\ r”都需要去。
建立字典
当向实际的单词嵌入方向前进时,将文本标记为句子:
import nltk
from nltk.tokenize import sent_tokenize
nltk.download('punkt') #need in Colab upon resetting the runtime
# tokenize at sentence level
sentences = nltk.sent_tokenize(corpus)
print("The number of sentences is {}".format(len(sentences)))
将看到该文本总共有16,989个句子。接下来需要计算最长句子中的单词数量 - 原因将在后面的教程中变得明显:
from nltk.tokenize import word_tokenize
word_count = lambda sentence: len(word_tokenize(sentence))
longest_sentence = max(sentences, key=word_count)
length_longest_sentence = len(word_tokenize(longest_sentence))
print("The longest sentence has {} words".format(length_longest_sentence))
事实证明,最长的句子是370字长。接下来将整个文本转换为正数,以便可以开始使用TensorFlow讲一种通用语言:
from tensorflow.keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer()
tokenizer.fit_on_texts(sentences)
sent_numeric = tokenizer.texts_to_sequences(sentences)
len(tokenizer.word_index.items())
从上面还发现该文本有20241个唯一单词,因为tokenizer每个相同的单词只分配一个数字。为了标准化所有句子的长度(即将输入数据制作成单个,相同的形状张量以使其可处理/更容易为模型 - 在这里满足机器的需求),需要转换表示单词(sent_numeric)到实际字典(word_index)中的数字列表,并添加填充。还可以将截断非常长的句子与填充短句子结合起来,但在这种情况下,只需填充最长句子的长度。
word_index = {k:v for k,v in tokenizer.word_index.items()}
word_index["<PAD>"] = 0
vocab_size = len(word_index)
maxLen = length_longest_sentence
data = tf.keras.preprocessing.sequence.pad_sequences(sent_numeric,
value=word_index["<PAD>"],
padding='post',
maxlen=maxLen)
由于为填充添加0,词汇量大小(也就是唯一词的数量)将增加1,达到20,242。键入“data [0]”(即第一个句子)以查看填充的第一个句子的样子。
为了能够在单词及其数字表示之间来回转换,需要为查找添加反向单词索引:
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
def decode_data(text):
return ' '.join([reverse_word_index.get(i, '?') for i in text])
仔细检查单词索引和转换是有意义的 - 一个错误可能会抛弃整个数据集,使其难以理解。交叉检查的例子 - 转换之前和之后 - 在Github存储库中可用。
模型
最后,构建并运行模型。TensorFlow提供了一个很好的教程,正在适应需求。
https://www.tensorflow.org/beta/tutorials/text/word_embeddings
但首先,只需运行嵌入层,这将产生一个嵌入的数组。已经读过这样的数组可以保存并在另一个模型中使用 - 是的它可以,但是在跳过新模型中的嵌入步骤之外,不太确定实用程序,因为为每个单词生成的向量是对待解决的问题不可知:
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding
embedding_dim = 100
model_justembed = Sequential()
model_justembed.add(Embedding(vocab_size, embedding_dim, input_length=maxLen))
model_justembed.compile('adam', 'mse')
model_justembed.summary()
output_array = model.predict(data)
不会花太多时间在上面,而是专注于嵌入只是第一部分的模型。
在导入相关库之后,继续构建新的,非常基本的模型架构:
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Embedding
embedding_dim=100
model = tf.keras.Sequential([
layers.Embedding(vocab_size, embedding_dim, input_length=maxLen, mask_zero=TRUE),
layers.GlobalAveragePooling1D(),
layers.Dense(1, activation='sigmoid')
])
model.summary()
嵌入层 - 通常可以用作模型中的第一层 - 将数字编码的唯一字序列(作为提醒,其中20,241个加上填充编码为零)转换为向量序列,后者被学习为模型训练。每个向量将有100个维度(embedding_dim = 100),因此将得到一个20242 x 100的矩阵。输入长度将固定为最长句子的长度,即370个单词,就像每个单词一样模型认为由于填充而具有相同的大小。Mask_zero通知模型输入值0是否是应该被屏蔽掉的特殊填充值,这在模型可以处理变量输入长度的循环层中特别有用。
在训练之后,具有相似含义的足够有意义的数据词可能具有相似的向量。
这是模型摘要(具有额外密集层的模型位于github存储库中):
在模型摘要中,将看到嵌入层的参数数量是2,024,200,这是嵌入维度100的20,242个字。
前面提到的TensorFlow教程使用评论数据集,每个评论标记为1或0,具体取决于积极或消极的情绪。没有标签的奢侈品,但仍然想要试驾这个模型,所以只需创建一个0的数组并附加到每个句子; 该模型需要这样的结构。这不会是机器智能遭遇无法解决的任务的第一次或最后一次,但仍然需要提供解决方案。训练这个模型:
import numpy as np
adam = tf.keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=None, decay=0.0, amsgrad=False)
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])
batch_size = 16989 #number of sentences
data_labels = np.zeros([batch_size, 1])
history = model.fit(
data,
data_labels,
epochs=200,
batch_size=batch_size,
verbose = 0)
嵌入式训练。在转向可视化之前,快速检查gensim的单词相似度。首先,需要创建矢量文件 - 将其暂时保存在Colab中或下载到本地机器:
f = open('vectors.tsv' ,'w')
f.write('{} {}\n'.format(vocab_size-1, embedding_dim))
vectors = model.get_weights()[0]
for words, i in tokenizer.word_index.items():
str_vec = ' '.join(map(str, list(vectors[i, :])))
f.write('{} {}\n'.format(words, str_vec))
f.close()
# download the file to the local machine by double-clicking the Colab file or using this:
try:
from google.colab import files
except ImportError:
pass
else:
files.download('vectors.tsv')
其次
import gensim
w2v = gensim.models.KeyedVectors.load_word2vec_format('./vectors.tsv', binary=False)
w2v.most_similar('rome')
最后,检查Pompey和Caesar之间的相似性,它们在之前训练过的CBOW模型中显示出很高的相似性:
round(w2v.similarity('pompey', 'caesar'),4)
单词之间的关系很高。此外,正如人们所预料的那样,凯撒与罗马高度相似。
对于那些对更复杂模型感兴趣的人,Github文件中提供了其他变体,包括Recurrent Neural Networks(长短期记忆),但请记住,它们的训练速度比上面的简单模型慢得多。
https://github.com/mlai-demo/TextExplore/blob/master/RePlutarch_TFembPub.ipynb
可视化
对于嵌入的可视化,很难击败TensorFlow投影仪,所以创建矢量和元(即对应于这些矢量的文字)文件供其使用:
https://projector.tensorflow.org/
import io
out_v = io.open('vecs.tsv', 'w', encoding='utf-8')
out_m = io.open('meta.tsv', 'w', encoding='utf-8')
for word_num in range(vocab_size): #had vocab_size-2 before
word = reverse_word_index[word_num]
embeddings = weights[word_num]
out_m.write(word + "\n")
out_v.write('\t'.join([str(x) for x in embeddings]) + "\n")
out_v.close()
out_m.close()
在本地导入文件,然后可以转到TensorFlow的投影仪,上传文件以替换默认数据,并尝试网站上提供的各种选项。以下是文本的整个向量空间的Principal Components Analysis视图:
这里只是100个单词的向量空间,与“罗马”最相似。
结论
在本文中,简要介绍了嵌入层一词在深度学习模型中的作用。在这种模型的上下文中,该层支持解决特定的NLP任务 - 例如文本分类 - 并且通过迭代训练单词向量以最有利于最小化模型损失。一旦模型被训练,就可以通过相似性计算和可视化来检查嵌入层输出。
嵌入层也可用于加载预训练的字嵌入(例如GloVe,BERT,FastText,ELMo),认为这通常是一种更有效的方式来利用需要这种嵌入的模型 - 部分归因于“工业级” “生成它们所需的工作量和数据大小。然而在专门文本的情况下,特别是如果可以训练单词嵌入的语料库相当大,训练自己的嵌入仍然可以更有效。