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