Tensorflow学习笔记——word2vec
本筆記記錄一下鄙人在使用tf的心得,好讓自己日后可以回憶一下。其代碼內(nèi)容都源于tf的tutorial里面的Vector Representations of Words。
現(xiàn)在我們一起來實現(xiàn)通過tf實現(xiàn)word2vec吧。
代碼地址:https://github.com/tensorflow/tensorflow/blob/r1.2/tensorflow/examples/tutorials/word2vec/word2vec_basic.py
step1 數(shù)據(jù)集
url = 'http://mattmahoney.net/dc/'def maybe_download(filename, expected_bytes):passfilename = maybe_download('text8.zip', 31344016)filename 是我們的待處理的目標(biāo)文件。其實是它是在http://mattmahoney.net/dc/text8.zip 里面,而這個函數(shù)就判斷本地時候存在該文件,若沒有就網(wǎng)上讀取(鄙人就先下載下來)。我不知道為毛它要判斷文件大小跟預(yù)期一樣,也不影響我們后面的工作。
step2 讀取數(shù)據(jù)
def read_data(filename):"""提取第一個文件當(dāng)中的詞列表:param filename::return:"""with zipfile.ZipFile(filename) as f:data = tf.compat.as_str(f.read(f.namelist()[0])).split()return data這里不用細說啦。因為都是簡單的i/o
vocabulary = read_data(filename) vocabulary_size = 50000讀取了文件里面的詞語后,為了方便,我們先定義自己的詞典大小為5W。
step3 建立詞典
def build_dataset(words, n_words):"""建立字典數(shù)據(jù)庫:param words::param n_words::return:"""count = [['UNK', -1]]# 記錄前49999個高頻詞,各自出現(xiàn)的次數(shù)count.extend(collections.Counter(words).most_common(n_words - 1))# Key value pair : {word: dictionary_index}dictionary = dict()for word, _ in count:dictionary[word] = len(dictionary)# 記錄每個詞語對應(yīng)與詞典的索引data = list()unk_count = 0for word in words:if word in dictionary:index = dictionary[word]else:index = 0unk_count += 1data.append(index)# 記錄沒有在詞典中的詞語數(shù)量count[0][1] = unk_countreversed_dictionary = dict(zip(dictionary.values(), dictionary.keys()))return data, count, dictionary, reversed_dictionary- count:記錄詞語和對應(yīng)詞頻的key-value
- data:記錄文本內(nèi)的每一個次對應(yīng)詞典(dictionary)的索引
- dictionary:記錄詞語和相應(yīng)詞典索引
- reversed_dicitonary:記錄詞典索引和相應(yīng)的詞語,跟dictionary的key-value相反
這里的['UNK', -1]記錄這一些詞典沒有記錄的詞語,因為我們只拿文本中出現(xiàn)次數(shù)最多的前49999詞作為詞典中的詞語。這意味著有一些我們不認識的詞語啊。那我們就將其當(dāng)作是我們詞典的“盲區(qū)”,不認識的詞(unknown words)簡稱UNK。
# 詞語索引,每個詞語詞頻,詞典,詞典的反轉(zhuǎn)形式 data, count, dictionary, reverse_dictionary = build_dataset(vocabulary, vocabulary_size)調(diào)用該函數(shù)我們就獲得詞典的內(nèi)容
step4 開始建立skip-gram模型需要的數(shù)據(jù)集合(data, label)
對于傳統(tǒng)的ML或者DL都會使用有監(jiān)督型的數(shù)據(jù)進行訓(xùn)練。對于skip-gram模型,我需要的數(shù)據(jù)集合應(yīng)該是{(x)data: target word, (y)label: context words}。它跟CBOW是截然不同的,因為CBOW是需要通過上下文推斷目標(biāo)詞語,所以需要的數(shù)據(jù)集合是{data: context words, label target word}。現(xiàn)在我們根據(jù)文本內(nèi)容和從文本獲得詞典,我們開始建立訓(xùn)練數(shù)據(jù)集合。
# 給skip-gram模型生成訓(xùn)練集合 def generate_batch(batch_size, num_skips, skip_window):""":param batch_size: 訓(xùn)練批次(batch)的大小:param num_skips: 采樣的次數(shù):param skip_window: 上下文的大小:return:"""global data_indexassert batch_size % num_skips == 0assert num_skips <= 2 * skip_windowbatch = np.ndarray(shape=(batch_size), dtype=np.int32)labels = np.ndarray(shape=(batch_size, 1), dtype=np.int32)# 上下文的組成:[skip_window target skip_window]span = 2 * skip_window + 1# 緩沖區(qū)buffer = collections.deque(maxlen=span)for _ in range(span):buffer.append(data[data_index])data_index = (data_index + 1) % len(data)for i in range(batch_size // num_skips):target = skip_windowtarget_to_avoid = [skip_window]for j in range(num_skips):#while target in target_to_avoid:target = random.randint(0, span - 1)target_to_avoid.append(target)# 記錄輸入數(shù)據(jù)(中心詞)batch[i * num_skips + j] = buffer[skip_window]# 記錄輸入數(shù)據(jù)對應(yīng)的類型(上下文內(nèi)容)labels[i * num_skips + j, 0] = buffer[target]buffer.append(data[data_index])data_index = (data_index + 1) % len(data)data_index = (data_index + len(data) - span) % len(data)return batch, labels這里我們得到的batch就是我們想要的輸入詞語/數(shù)據(jù)(詞語在詞典當(dāng)中的索引),另外label是batch對應(yīng)的目標(biāo)詞語/數(shù)據(jù)(詞語在詞典當(dāng)中的索引)。這里舉個例子,我們現(xiàn)在給定batch_size為8,num_skips為2,skip_window=1,給出文本為
"I am good at studying and learning ML. However, I don't like to read the English document."
我粗略算算詞典
['I', 'am', 'good', 'at', 'studying', 'and', 'learning', 'ML', 'However', 'I', 'don't', 'like', 'to', 'read', the', 'English', 'document']
根據(jù)generate_batch的內(nèi)容和給定參數(shù),我們第一次獲得內(nèi)容應(yīng)該是
['I', 'am', 'good', 'at', 'studying', 'and', 'learning', 'ML']
我們的上下文窗口(span)應(yīng)該是 2 * 1 + 1 = 3。也就是窗口應(yīng)該是
buffer=['I', 'am', 'good']
顯然target應(yīng)該是'am'也就是為buffer[skip_window]而context word應(yīng)該是['I', 'good']。這就構(gòu)成了{x: data, y: label}之間的關(guān)系。
對于skip-gram模型的數(shù)據(jù)集合
- {(x)data: 'am', (y)label: 'I'}
- {(x)data: 'am', (y)label: 'good'}
如此類推。那num_skips有啥用呢?其實num_skips意味著需要對buffer進行多少次才采樣,才開始對下一個buffer進行采樣。
step5 開始建立skip-gram模型(重點來了)
batch_size = 128 embedding_size = 128 # 嵌入向量的維度 skip_window = 1 # 上下文的詞數(shù) num_skips = 2 # 多少次后重用輸入的生成類別# 我們使用隨機鄰居樣本生成評估集合,這里我們限定了 # 評估樣本一些數(shù)字ID詞語,這些ID是通過詞頻產(chǎn)生 valid_size = 16 valid_window = 100 valid_examples = np.random.choice(valid_window, valid_size, replace=False) num_sample = 64graph = tf.Graph()with graph.as_default():train_inputs = tf.placeholder(tf.int32, shape=[batch_size])train_labels = tf.placeholder(tf.int32, shape=[batch_size, 1])valid_dataset = tf.constant(valid_examples, dtype=tf.int32)with tf.device('/cpu:0'):# 隨機生成初始詞向量embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))# 根據(jù)batch的大小設(shè)置輸入數(shù)據(jù)的batchembed = tf.nn.embedding_lookup(embeddings, train_inputs)# 設(shè)置權(quán)值nce_weights = tf.Variable(tf.truncated_normal([vocabulary_size, embedding_size], stddev=1.0 / math.sqrt(embedding_size)))nce_biases = tf.Variable(tf.zeros([vocabulary_size]))# 計算誤差平均值loss = tf.reduce_mean(tf.nn.nce_loss(weights=nce_weights,biases=nce_biases,labels=train_labels,inputs=embed,num_sampled=num_sample,num_classes=vocabulary_size))# learning rate 1.0optimizer = tf.train.GradientDescentOptimizer(1.0).minimize(loss)norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keep_dims=True))# 對詞向量進行歸一化normalized_embeddings = embeddings / norm# 根據(jù)校驗集合,查找出相應(yīng)的詞向量valid_embeddings = tf.nn.embedding_lookup(normalized_embeddings, valid_dataset)# 計算cosine相似度similarity = tf.matmul(valid_embeddings, normalized_embeddings, transpose_b=True)init = tf.global_variables_initializer()這里可能沒有之前這么簡單了,因為不懂word2vec數(shù)學(xué)原理的人,完全看不懂代碼,盡管你精通Python,也不知道為毛有這行代碼和代碼的含義。這里我不多講word2vec的數(shù)學(xué)原理,遲點我會再一遍文章講解word2vec的原理和疑問。這里我給出一篇我看過的詳細的文章word2vec的數(shù)學(xué)原理,大家可以先閱覽一下。我在這里稍微講一下代碼和附帶的原理內(nèi)容。
# 隨機生成初始詞向量embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))大家都知道word2vec就是說把詞語變成向量形式,而在CBOW和skip-gram模型中,詞向量是副產(chǎn)品,真正的目的是推斷出上下文內(nèi)容。這里就來一個要點了(是鄙人私以為的):
在模型的訓(xùn)練過程中,調(diào)整詞向量和不斷是推斷逼近目標(biāo)詞語是同時進行。也就是說調(diào)整詞向量->優(yōu)化推斷->調(diào)整詞向量->優(yōu)化推斷->調(diào)整詞向量->優(yōu)化推斷.... 最后達到兩者同時收斂。這就是我們最后的目標(biāo)。這是我從EM算法中類比獲得的想法,關(guān)于EM算法,我會在之后添加文章(算法推導(dǎo)+代碼)。
在DL和ML中我們都說到損失函數(shù),不斷優(yōu)化損失函數(shù)使其最小,是我們的目標(biāo)。這里的損失函數(shù)是什么呢?那就是
tf.nn.nce_loss(weights=nce_weights,biases=nce_biases,labels=train_labels,inputs=embed,num_sampled=num_sample,num_classes=vocabulary_size)我們剛剛說到要把推斷出哪個詞應(yīng)該出現(xiàn)在上下文當(dāng)中,就是涉及到一個概率問題了。既然是推斷那就是要比較大小啦。那就是把詞典中所有的詞的有可能出現(xiàn)在上下文的概率都算一遍嗎?確實!在早期word2vec論文發(fā)布時,就是這么粗暴。現(xiàn)在就當(dāng)然不是啦。那就是用negative sample來推斷進行提速啦。
我們知道在訓(xùn)練過程中,我們都知道label是哪個詞。這意味著其他詞對于這個樣本就是negative了。那就好辦啦。我就使得label詞的概率最大化,其他詞出現(xiàn)的概率最小化。當(dāng)中涉及的數(shù)學(xué)知識就是Maximum likelihood 最大似然估計。不懂的回去復(fù)習(xí)唄。
之后我們用梯度下降法進行訓(xùn)練,這樣我們就得到訓(xùn)練模型了。
step6 開始進行無恥的訓(xùn)練
num_steps = 100001with tf.Session(graph=graph) as session:# 初始化變量init.run()print("Initialized")average_loss = 0for step in xrange(num_steps):batch_inputs, batch_labels = generate_batch(batch_size, num_skips, skip_window)feed_dict = {train_inputs: batch_inputs, train_labels: batch_labels}_, loss_val = session.run([optimizer, loss], feed_dict=feed_dict)average_loss += loss_valif step % 2000 == 0:if step > 0:average_loss /= 2000print('Average loss at step ', step, ': ', average_loss)average_loss = 0if step % 10000 == 0:sim = similarity.eval()for i in xrange(valid_size):valid_word = reverse_dictionary[valid_examples[i]]top_k = 8nearest = (-sim[i, :]).argsort()[1: top_k + 1]log_str = 'Nearest to %s: ' % valid_wordfor k in xrange(top_k):close_word = reverse_dictionary[nearest[k]]log_str = "%s %s," % (log_str, close_word)print(log_str)final_embeddings = normalized_embeddings.eval()在每次訓(xùn)練中我們都給數(shù)據(jù)模型喂養(yǎng)(feed)一小批數(shù)據(jù)(batch_input, batch_labels)。這些數(shù)據(jù)是通過generate_batch()生成的。通過暴力的迭代,我們最后得到最終詞向量(final_embedding)。在訓(xùn)練過程中,每2000次迭代打印損失值,每10000次迭代打印校驗詞的相似詞(通過cosin相似度來判斷)。
最后還差一個詞向量降維后的圖片,我遲點不上。現(xiàn)在準(zhǔn)備煮飯咯....
作者:Salon_sai
鏈接:http://www.jianshu.com/p/1624ede1f2ac
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
總結(jié)
以上是生活随笔為你收集整理的Tensorflow学习笔记——word2vec的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: All of Recurrent Neu
- 下一篇: Tensorflow[实战篇]——Fac