已經介紹了Word2Vec中的Skip-Gram模型的基本原理,以及訓練過程的3個策略,詳細請參考:算法
接下來開始動手用 TensorFlow 實現本身的 Word2Vec 模型,本篇文章將利用 TensorFlow 來完成 Skip-Gram 模型。還不是很瞭解Skip-Gram思想的小夥伴能夠參考以上推送文章。app
本篇 TensorFlow 實戰參考 天雨粟 的實現思路,實戰代碼的主要目的是加深對Skip-Gram 模型中一些思想和訓練技巧的理解,選用了知足本身訓練目的的語料規模,對語料質量和算法細節作基本的約束要求,儘量的下降訓練成本。同時,運用 TensorFlow 框架創建網絡結構,求解嵌入的詞向量。框架
實戰工具
Skip-Gram 模型的訓練所使用的語言,庫,及工具以下所示:dom
語言:Python 3機器學習
包:TensorFlow包,Numpy包編輯器
編輯器:Pycharm函數
線上GPU:floyd (https://www.floydhub.com/),這是一個很是不錯的在線訓練深度學習神經網絡的平臺工具
數據集:維基百科英文文章預料,參考 天雨粟 給出的預料,預料字節數爲90+M.
數據預處理
首先,導入用到的包,以下:
import random
import timefrom collections import Counter
import numpy as np
import tensorflow as tf
加載訓練網絡所用的語料,以下:
with open('data/text8') as f:
text = f.read()
數據預處理部分主要作的工做包括:
替換文本中特殊符號,好比:逗號,分號,感嘆號等
對文本分詞
去除低頻詞,減小噪音
構建語料
單詞映射表
替換文本中的特殊字符:
text = text.replace('.', ' <PER> ').replace(',', ' <COM> ').replace('"', ' <QUO> ').\
replace(';', ' <SEM> ').replace('!', ' <EXC> ').replace('?', ' <QUE> ').\
replace('(', ' <LEF> ').replace(')', ' <RIG> ').replace('--', ' <HYP> ').\
replace('?', ' <QUE> ').replace(':', ' <COL> ')
根據空格分詞
words = self.text.split()
剔除低頻詞
word_counts = Counter(words)
words = [word for word in words if word_counts[word] > self.low_freq_del]
去重後的詞彙表
vocab = set(words)
在詞彙表中創建映射關係
vocab_to_int = {w: c for c, w in enumerate(vocab)}
int_to_vocab = {c: w for c, w in enumerate(vocab)}
去除低頻率的單詞,同時去除高頻出現的停用詞,例如「the」, 「of」以及「for」這類單詞進行剔除。剔除這些單詞之後可以加快咱們的訓練過程,同時減小訓練過程當中的噪音。採用如下公式:
其中 t 是一個閾值參數,通常爲 1e-3 至 1e-5
是單詞 wi 在整個數據集中的出現頻次
# 在詞彙表中找到單詞的索引list
word_vocab_index = [vocab_to_int[w] for w in words]
# 統計單詞出現頻次
word_index_counter = Counter(word_vocab_index)
total_count = len(word_vocab_index)
# 計算單詞頻率字典
word_freq_dict = {w: c / total_count for w, c in word_index_counter.items()}
# 計算單詞和刪除值的字典
word_del_dict = {w: 1 - np.sqrt(self.high_freq_del_t / word_freq_dict[w]) for w in word_index_counter}
# 對單詞進行採樣
train_words_index = [w for w in word_vocab_index if word_del_dict[w] < self.high_freq_del_prob]
輸入樣本
Skip-Gram模型的輸入是基於中心詞的上下文窗依次配對,經過必定批次大小構建輸入樣本。
對於一個給定詞,離它越近的詞可能與它越相關,離它越遠的詞越不相關,這裏咱們設置窗口大小爲 5,對於每一個訓練單詞,咱們還會在 [1:5] 之間隨機生成一個整數 R,用 R 做爲咱們最終選擇 output word 的窗口大小。這裏之因此多加了一步隨機數的窗口從新選擇步驟,是爲了可以讓模型更聚焦於當前 input word 的鄰近詞。
def generate_batches(self,train_words_index):
for idx in range(0, len(train_words_index), self.batch_size):
x, y = [], []
batch = train_words_index[idx: idx + self.batch_size]
for i in range(len(batch)):
batch_x = batch[i]
random_window_size = np.random.randint(1, window_size + 1)
# 要考慮input word前面單詞不夠的狀況
start_point = idx - random_window_size if (idx - random_window_size) > 0 else 0
end_point = idx + random_window_size
# output words(即窗口中的上下文單詞)
batch_y = train_words_index[start_point: idx] + words[idx + 1: end_point + 1]
# 因爲一個input word會對應多個output word,所以須要長度統一
x.extend([batch_x] * len(batch_y))
y.extend(batch_y)
yield x, y
三層網絡
該部分主要包括:
輸入層
Embedding,
嵌入矩陣的矩陣形狀爲 vocab_size×hidden_units_size ,TensorFlow 中的 tf.nn.embedding_lookup 函數能夠實現 lookup 的計算方式
Negative Sampling,負採樣主要是爲了解決梯度降低計算速度慢的問題,詳細的實現細節請參考 Word2vec之Skip-Gram訓練網絡的3種技術,TensorFlow中的 tf.nn.sampled_softmax_loss 會在 softmax 層上進行採樣計算損失,實現相似於 negative sampling 的功能。
詳細實現代碼以下:
def build_tf_nn(self,train_words_index, int_to_vocab):
# 輸入層網絡
train_graph = tf.Graph()
with train_graph.as_default():
# 1.輸入層
inputs = tf.placeholder(tf.int32, shape=[None], name='inputs')
labels = tf.placeholder(tf.int32, shape=[None, None], name='labels')
# 2.嵌入層
vocab_size = len(int_to_vocab)
embedding_size = 200 # 嵌入維度
embedding = tf.Variable(tf.random_uniform([vocab_size, embedding_size], -1, 1)) # 嵌入層權重矩陣
embed = tf.nn.embedding_lookup(embedding, inputs) # 實現lookup
# 3.Softmax 層(Negative sampling)
smw = tf.Variable(tf.truncated_normal([vocab_size, embedding_size], stddev=0.1))
smb = tf.Variable(tf.zeros(vocab_size))
# 計算negative sampling下的損失
loss = tf.nn.sampled_softmax_loss(smw, smb, labels, embed, self.negative_sample_cnt, vocab_size)
cost = tf.reduce_mean(loss)
optimizer = tf.train.AdamOptimizer().minimize(cost)
驗證
抽取幾個詞,找出各自最相近的 topk 個單詞。首先把測試樣本加入到圖中,
with train_graph.as_default():
#隨機挑選一些單詞
valid_size = 16
valid_window = 100
#從不一樣位置各選8個單詞
valid_examples = np.array(random.sample(range(valid_window), valid_size/2))
valid_examples = np.append(valid_examples, random.sample(range(1000, 1000 + valid_window), valid_size/2))
# 驗證單詞集
valid_dataset = tf.constant(valid_examples, dtype=tf.int32)
# 計算每一個詞向量的模並進行單位化
norm = tf.sqrt(tf.reduce_sum(tf.square(embedding), 1, keepdims=True))
normalized_embedding = embedding / norm
# 查找驗證單詞的詞向量
valid_embedding = tf.nn.embedding_lookup(normalized_embedding, valid_dataset)
# 計算餘弦類似度
similarity = tf.matmul(valid_embedding, tf.transpose(normalized_embedding))
接下來,運行以上默認圖:
def run_graph(self,train_words,train_graph,int_to_vocab,valid_examples,similarity):
epochs = self.epochs # 迭代輪數
batch_size = self.batch_size # batch大小
window_size = self.window_size # 窗口大小
with train_graph.as_default():
saver = tf.train.Saver() # 文件存儲
with tf.Session(graph=train_graph) as sess:
iteration = 1
loss = 0
sess.run(tf.global_variables_initializer())
for e in range(1, epochs + 1):
batches = self.generate_batches(train_words)
start = time.time()
#
for x, y in batches:
feed = {inputs: x,
labels: np.array(y)[:, None]}
train_loss, _ = sess.run([cost, optimizer], feed_dict=feed)
loss += train_loss
if iteration % 100 == 0:
end = time.time()
print("Epoch {}/{}".format(e, epochs),
"Iteration: {}".format(iteration),
"Avg. Training loss: {:.4f}".format(loss / 100),
"{:.4f} sec/batch".format((end - start) / 100))
loss = 0
start = time.time()
# 計算類似的詞
if iteration % 1000 == 0:
# 計算similarity
sim = similarity.eval()
for i in range(valid_size):
valid_word = int_to_vocab[valid_examples[i]]
top_k = 8 # 取最類似單詞的前8個
nearest = (-sim[i, :]).argsort()[1:top_k + 1]
log = 'Nearest to [%s]:' % valid_word
for k in range(top_k):
close_word = int_to_vocab[nearest[k]]
log = '%s %s,' % (log, close_word)
print(log)
iteration += 1
save_path = saver.save(sess, "checkpoints/text8.ckpt")
embed_mat = sess.run(normalized_embedding)
每 1000 個時步打印一次,從最後的訓練結果來看,模型仍是學到了一些常見詞的語義,好比 one 等計數詞以及 gold 之類的金屬詞,animals 中的類似詞也相對準確,列表以下:
爲了可以更全面地觀察咱們訓練結果,咱們採用 sklearn 中的 TSNE 來對高維詞向量進行可視化。
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
viz_words = 500
tsne = TSNE()
embed_tsne = tsne.fit_transform(embed_mat[:viz_words, :])
fig, ax = plt.subplots(figsize=(14, 14))
for idx in range(viz_words):
plt.scatter(*embed_tsne[idx, :], color='steelblue')
plt.annotate(int_to_vocab[idx], (embed_tsne[idx, 0], embed_tsne[idx, 1]), alpha=0.7)
以上即是在 TensorFlow 中完整源碼實現Word2vec之Skip-Gram模型的詳細過程代碼。
相關連接
Tensorflow|Tensor, 與Numpy比較,Constant
Tensorflow|經過Variable及assign()感悟一點設計之道
Tensorflow|Session和InteractiveSession
點擊如下標題查看相關內容:
關於算法channel
算法channel是一個提供系統入門,工程與學術相結合的原創乾貨公衆號,包括基礎算法,機器學習,深度學習,NLP,工具庫使用等10餘個頻道,若是你對算法和人工智能感興趣,歡迎在公衆號後臺點擊「交流羣」。
本文分享自微信公衆號 - Python與算法社區(alg-channel)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。