TensoFlow 實戰 3層網絡求解嵌入詞向量,附代碼詳解

已經介紹了Word2Vec中的Skip-Gram模型的基本原理,以及訓練過程的3個策略,詳細請參考:算法


斯坦福大學NLP-cs224課程筆記2:詞向量模型
微信


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
time

from collections import Counter

import numpy as np

import tensorflow as tf

加載訓練網絡所用的語料,以下:

with  open('data/text8') as f:

    text = f.read()

數據預處理部分主要作的工做包括:

  1. 替換文本中特殊符號,好比:逗號,分號,感嘆號等

  2. 對文本分詞

  3. 去除低頻詞,減小噪音

  4. 構建語料

  5. 單詞映射表


替換文本中的特殊字符:

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 
f w i )  是單詞 wi 在整個數據集中的出現頻次

P ( w i )  是單詞被刪除的機率

# 在詞彙表中找到單詞的索引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筆記|爲何會有它?

    TensorFlow筆記|Get Started

    Tensorflow筆記|tensorflow作線性回

    Tensorflow|Tensor, 與Numpy比較,Constant

    Tensorflow|經過Variable及assign()感悟一點設計之道

    Tensorflow|Session和InteractiveSession


點擊如下標題查看相關內容: 



關於算法channel


算法channel是一個提供系統入門,工程與學術相結合的原創乾貨公衆號,包括基礎算法,機器學習,深度學習,NLP,工具庫使用等10餘個頻道,若是你對算法和人工智能感興趣,歡迎在公衆號後臺點擊「交流羣」。



本文分享自微信公衆號 - Python與算法社區(alg-channel)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索