Word2Vec之Skip-Gram與CBOW模型原理

word2vec是一個開源的nlp工具域庇,它可以將所有的詞向量化字币。至于什么是詞向量,最開始是我們熟悉的one-hot編碼一忱,但是這種表示方法孤立了每個詞莲蜘,不能表示詞之間的關(guān)系,當然也有維度過大的原因帘营。后面發(fā)展出一種方法票渠,術(shù)語是詞嵌入。

[1.詞嵌入]

詞嵌入(Word Embedding)是NLP中語言模型與表征學習技術(shù)的統(tǒng)稱芬迄,它就是將one-hot表示的詞“嵌入”到一個低維空間中问顷,簡單點就是嵌入矩陣E與詞的one-hot的乘積,數(shù)學關(guān)系為:


舉個例子,如果有2000個詞的字典杜窄,那么每個詞的one-hot形式就是2000*1肠骆,如果學習到嵌入矩陣為300 * 2000的化,那么將生成300 * 1的詞向量塞耕,就那么簡單蚀腿。還有,嵌入矩陣一般這樣表示扫外,頻數(shù)是統(tǒng)計來的:

[2.Word2Vec]

Word2Vec是很流行的詞嵌入算法莉钙,它通常包含兩個模型,一個是Skip-Gram模型筛谚,這個模型的思想是磁玉,選定中間的詞(Context),然后在這個詞的正負n個詞內(nèi)選擇目標詞(Target)與之對應(yīng)驾讲,構(gòu)造一個用Context預(yù)測輸出為Target的監(jiān)督學習問題蜀涨,訓練一個如下圖結(jié)構(gòu)的網(wǎng)絡(luò)(吳恩達的課件):

Skip-Gram

其實還有其他的一些細節(jié),但是說的太細了蝎毡,寫不完厚柳,具體我一會慢慢完善。

還有一個是CBOW(Continuous Bag-of-Words Model)沐兵,它的工作原理與Skip-Gram原理相反别垮,他是用上下文預(yù)測中間的詞。

接下來扎谎,我將用tensorflow來實現(xiàn)一個Skip-Gram碳想,代碼來自于tensorflow的word2vec的改編,是基于這位博主毁靶,如果想看可以去github看tensorflow的例子胧奔。不羅嗦,直接上代碼预吆。

3. 高亮一段代碼[^code]

import time
import numpy as np
import tensorflow as tf
import random
from collections import Counter

with open('data/text8') as f:
    text = f.read()


#定義函數(shù)來完成數(shù)據(jù)的預(yù)處理
def preprocess(text, freq=5):
    '''
    對文本進行預(yù)處理
    :param text: 文本數(shù)據(jù)
    :param freq: 詞頻閾值
    '''
    #對文本中的符號進行替換龙填,下面都是用英文翻譯來替代符號
    text = text.lower()
    text = text.replace('.', ' <PERIOD> ')
    text = text.replace(',', ' <COMMA> ')
    text = text.replace('"', ' <QUOTATION_MARK> ')
    text = text.replace(';', ' <SEMICOLON> ')
    text = text.replace('!', ' <EXCLAMATION_MARK> ')
    text = text.replace('?', ' <QUESTION_MARK> ')
    text = text.replace('(', ' <LEFT_PAREN> ')
    text = text.replace(')', ' <RIGHT_PAREN> ')
    text = text.replace('--', ' <HYPHENS> ')
    text = text.replace(':', ' <COLON> ')
    words = text.split()

    #刪除低頻詞,減少噪音影響
    word_counts = Counter(words)
    trimmed_words = [word for word in words if word_counts[word] > freq] #這句話很好拐叉,可以模仿

    return trimmed_words


#清洗文本并分詞
words = preprocess(text)
# print(words[: 200])

#構(gòu)建映射表
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)}

print('total words: {}'.format(len(words))) #還有這種輸出方法岩遗,學習一下
print('unique words: {}'.format(len(set(words))))

#對原文本進行vocab到int的轉(zhuǎn)換
int_words = [vocab_to_int[w] for w in words]


#--------------------------------------------------------------------采樣

t = 1e-5  #t值
threshold = 0.8  #刪除概率閾值

#統(tǒng)計單詞出現(xiàn)頻數(shù)
int_word_counts = Counter(int_words)
total_count = len(int_words)
#計算單詞頻率
word_freqs = {w: c / total_count for w, c in int_word_counts.items()}
#計算單詞被刪除的概率
prob_drop = {w: 1 - np.sqrt(t / word_freqs[w]) for w in int_word_counts}
#對單詞進行采樣
train_words = [w for w in int_words if prob_drop[w] < threshold]
print(len(train_words))
#-----------------------------------------------------------------------采樣


#獲得input word的上下文單詞列表
def get_targets(words, idx, window_size = 5):
    '''
    獲得input word的上下文單詞列表
    :param words: 單詞列表
    :param idx: input word 的索引號
    :param window_size: 窗口大小
    '''
    target_window = np.random.randint(1, window_size + 1)  #從1到 window_size+1 之間的數(shù),包括1凤瘦,不包括最后一個
    #這里要考慮input word前面單詞不夠的情況宿礁,但是為什么沒有寫后面單詞不夠的情況呢,
    #因為python里面的list取分片越界時會自動只取到結(jié)尾的
    start_point = idx - target_window if (idx - target_window) > 0 else 0  #雖說不能寫三元表達式蔬芥,但這個挺不錯的
    end_point = idx + target_window
    #output words(即窗口中的上下文單詞梆靖,不包含目標詞控汉,只是它的上下文的詞)
    targets = set(words[start_point: idx] + words[idx + 1: end_point + 1])

    return list(targets)


#構(gòu)造一個獲取batch的生成器
def get_batches(words, batch_size, window_size = 5):
    '''
    構(gòu)造一個獲取batch的生成器
    '''
    n_batches = len(words) // batch_size

    #僅取full batches
    words = words[: n_batches * batch_size]

    for idx in range(0, len(words), batch_size):  #range(start, stop[, step])
        x, y = [], []
        batch = words[idx: idx + batch_size]
        for i in range(len(batch)):
            batch_x = batch[i]
            batch_y = get_targets(batch, i, window_size) #從一個batch的第0位開始,一直往下滑返吻,直到最后一個
            #由于一個input word會對應(yīng)多個output word暇番,因此需要長度統(tǒng)一
            x.extend([batch_x] * len(batch_y))
            y.extend(batch_y)
        yield x, y


#構(gòu)建網(wǎng)絡(luò),該部分主要包括:輸入層思喊,Embedding壁酬,Negative Sampling
train_graph = tf.Graph()
with train_graph.as_default():
    inputs = tf.placeholder(tf.int32, shape=[None], name='inputs')
    labels = tf.placeholder(tf.int32, shape=[None, None], name='labels')  #一般是[batch_size, num_true],num_true一般為1

vocab_size = len(int_to_vocab)
embedding_size = 200 #嵌入維度

with train_graph.as_default():
    #嵌入層權(quán)重矩陣
    embedding = tf.Variable(tf.random_uniform([vocab_size, embedding_size], -1, 1)) #tf.random_uniform((4, 4), minval=low,maxval=high,dtype=tf.float32)))返回4*4的矩陣
                                                                                    # 恨课,產(chǎn)生于low和high之間舆乔,產(chǎn)生的值是均勻分布的。
    embed = tf.nn.embedding_lookup(embedding, inputs) #[None, embedding_size]剂公,一般是[batch_size, embedding_size]


#-----------------------------------------------------------負采樣(Negative Sampling)
n_sampled = 100

with train_graph.as_default():
    sotfmax_w = tf.Variable(tf.truncated_normal([vocab_size, embedding_size], stddev=0.1)) #[vocab_size, dim], dim就是embedding_size
    sotfmax_b = tf.Variable(tf.zeros(vocab_size)) #[vocab_size]

    #計算negative sampling下的損失
    #tf.nn.sampled_softmax_loss()進行了negative sampling希俩,它主要用在分類的類別較大的情況
    loss = tf.nn.sampled_softmax_loss(sotfmax_w, sotfmax_b, labels, embed, n_sampled, vocab_size)
    cost = tf.reduce_mean(loss)
    optimizer = tf.train.AdamOptimizer().minimize(cost)
#-----------------------------------------------------------負采樣


#為了直觀看到訓練結(jié)果,我們將查看訓練出的相近語義的詞
with train_graph.as_default():
    #隨機挑選一些單詞
    valid_size = 16
    valid_window = 100
    #從不同位置各選8個詞
    valid_examples = np.array(random.sample(range(valid_window), valid_size // 2))  #random.sample(seq, n) 從序列seq中選擇n個隨機且獨立的元素
    #np.append([1, 2, 3], [[4, 5, 6], [7, 8, 9]])纲辽,結(jié)果是array([1, 2, 3, 4, 5, 6, 7, 8, 9])
    valid_examples = np.append(valid_examples, random.sample(range(1000, 1000 + valid_window), valid_size // 2))

    valid_size = len(valid_examples)
    #驗證單詞集
    valid_dataset = tf.constant(valid_examples, dtype=tf.int32)

    #計算每個詞向量的模并進行單位化
    norm = tf.sqrt(tf.reduce_sum(tf.squeeze(embedding), 1, keep_dims=True))
    normalized_embedding = embedding / norm
    #查找驗證單詞的詞向量
    valid_embedding = tf.nn.embedding_lookup(normalized_embedding, valid_dataset)
    #計算余弦相似度
    similarity = tf.matmul(valid_embedding, tf.transpose(normalized_embedding))


#進行訓練
epochs = 10 #迭代次數(shù)
batch_size = 1000 #batch大小
window_size = 10 #窗口大小

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 = get_batches(train_words, batch_size, window_size)
        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),  #這是一種數(shù)字格式化的方法颜武,{:.4f}表示保留小數(shù)點后四位
                      '{:.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, 'model/text8.ckpt')
    embed_mat = sess.run(normalized_embedding)


#下面這部分代碼出錯了,暫時沒明白咋回事
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)


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拖吼,一起剝皮案震驚了整個濱河市鳞上,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吊档,老刑警劉巖篙议,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異怠硼,居然都是意外死亡鬼贱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門香璃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來这难,“玉大人,你說我怎么就攤上這事葡秒∫雠遥” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵同云,是天一觀的道長糖权。 經(jīng)常有香客問我堵腹,道長炸站,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任疚顷,我火速辦了婚禮旱易,結(jié)果婚禮上禁偎,老公的妹妹穿的比我還像新娘。我一直安慰自己阀坏,他們只是感情好如暖,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著忌堂,像睡著了一般盒至。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上士修,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天枷遂,我揣著相機與錄音,去河邊找鬼棋嘲。 笑死酒唉,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的沸移。 我是一名探鬼主播痪伦,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼雹锣!你這毒婦竟也來了网沾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蕊爵,失蹤者是張志新(化名)和其女友劉穎绅这,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體在辆,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡证薇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了匆篓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浑度。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鸦概,靈堂內(nèi)的尸體忽然破棺而出箩张,到底是詐尸還是另有隱情,我是刑警寧澤窗市,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布先慷,位于F島的核電站,受9級特大地震影響咨察,放射性物質(zhì)發(fā)生泄漏论熙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一摄狱、第九天 我趴在偏房一處隱蔽的房頂上張望脓诡。 院中可真熱鬧无午,春花似錦、人聲如沸祝谚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽交惯。三九已至次泽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間席爽,已是汗流浹背箕憾。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拳昌,地道東北人袭异。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像炬藤,于是被迫代替她去往敵國和親御铃。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載自:瑪卡瑞納_a63b這篇文章主要是對介紹Word2Vec中的Skip-Gram模型的兩篇英文文檔的翻譯沈矿、理解...
    guisir_zgm閱讀 2,246評論 0 2
  • 前面的文章主要從理論的角度介紹了自然語言人機對話系統(tǒng)所可能涉及到的多個領(lǐng)域的經(jīng)典模型和基礎(chǔ)知識上真。這篇文章,甚至之后...
    我偏笑_NSNirvana閱讀 13,909評論 2 64
  • 今天是個好日子,周末在家休息的日子里居然有這么好的天氣陵像,許久不見的大太陽終于出來了就珠。原本我躺在床上看書,看得昏昏欲...
    精進的醫(yī)生閱讀 909評論 61 63
  • 我踏過生著苔的橋面醒颖,摘下一片淋了雨的葉片妻怎,只想尋著你濕漉漉的鼻尖,安眠泞歉。 我養(yǎng)了一只貓逼侦。 是一只很普通的黃色小貓??...
    墨與梔子閱讀 219評論 0 1
  • We are all searching for someone, that special person who...
    小二哥plus閱讀 324評論 0 0