使用Tensorflow 2.0 Reimagine Plutarch

  • 2019 年 10 月 5 日
  • 筆記

作者 | Almis Povilaitis

来源 | 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),认为这通常是一种更有效的方式来利用需要这种嵌入的模型 – 部分归因于“工业级” “生成它们所需的工作量和数据大小。然而在专门文本的情况下,特别是如果可以训练单词嵌入的语料库相当大,训练自己的嵌入仍然可以更有效。

Exit mobile version