使用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),認為這通常是一種更有效的方式來利用需要這種嵌入的模型 – 部分歸因於「工業級」 「生成它們所需的工作量和數據大小。然而在專門文本的情況下,特別是如果可以訓練單詞嵌入的語料庫相當大,訓練自己的嵌入仍然可以更有效。