Tensorflow学习笔记——word2vec
本筆記記錄一下鄙人在使用tf的心得,好讓自己日后可以回憶一下。其代碼內容都源于tf的tutorial里面的Vector Representations of Words。
現在我們一起來實現通過tf實現word2vec吧。
代碼地址:https://github.com/tensorflow/tensorflow/blob/r1.2/tensorflow/examples/tutorials/word2vec/word2vec_basic.py
step1 數據集
url = 'http://mattmahoney.net/dc/'def maybe_download(filename, expected_bytes):passfilename = maybe_download('text8.zip', 31344016)filename 是我們的待處理的目標文件。其實是它是在http://mattmahoney.net/dc/text8.zip 里面,而這個函數就判斷本地時候存在該文件,若沒有就網上讀取(鄙人就先下載下來)。我不知道為毛它要判斷文件大小跟預期一樣,也不影響我們后面的工作。
step2 讀取數據
def read_data(filename):"""提取第一個文件當中的詞列表: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):"""建立字典數據庫:param words::param n_words::return:"""count = [['UNK', -1]]# 記錄前49999個高頻詞,各自出現的次數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)# 記錄每個詞語對應與詞典的索引data = list()unk_count = 0for word in words:if word in dictionary:index = dictionary[word]else:index = 0unk_count += 1data.append(index)# 記錄沒有在詞典中的詞語數量count[0][1] = unk_countreversed_dictionary = dict(zip(dictionary.values(), dictionary.keys()))return data, count, dictionary, reversed_dictionary- count:記錄詞語和對應詞頻的key-value
- data:記錄文本內的每一個次對應詞典(dictionary)的索引
- dictionary:記錄詞語和相應詞典索引
- reversed_dicitonary:記錄詞典索引和相應的詞語,跟dictionary的key-value相反
這里的['UNK', -1]記錄這一些詞典沒有記錄的詞語,因為我們只拿文本中出現次數最多的前49999詞作為詞典中的詞語。這意味著有一些我們不認識的詞語啊。那我們就將其當作是我們詞典的“盲區”,不認識的詞(unknown words)簡稱UNK。
# 詞語索引,每個詞語詞頻,詞典,詞典的反轉形式 data, count, dictionary, reverse_dictionary = build_dataset(vocabulary, vocabulary_size)調用該函數我們就獲得詞典的內容
step4 開始建立skip-gram模型需要的數據集合(data, label)
對于傳統的ML或者DL都會使用有監督型的數據進行訓練。對于skip-gram模型,我需要的數據集合應該是{(x)data: target word, (y)label: context words}。它跟CBOW是截然不同的,因為CBOW是需要通過上下文推斷目標詞語,所以需要的數據集合是{data: context words, label target word}。現在我們根據文本內容和從文本獲得詞典,我們開始建立訓練數據集合。
# 給skip-gram模型生成訓練集合 def generate_batch(batch_size, num_skips, skip_window):""":param batch_size: 訓練批次(batch)的大小:param num_skips: 采樣的次數: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# 緩沖區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)# 記錄輸入數據(中心詞)batch[i * num_skips + j] = buffer[skip_window]# 記錄輸入數據對應的類型(上下文內容)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就是我們想要的輸入詞語/數據(詞語在詞典當中的索引),另外label是batch對應的目標詞語/數據(詞語在詞典當中的索引)。這里舉個例子,我們現在給定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']
根據generate_batch的內容和給定參數,我們第一次獲得內容應該是
['I', 'am', 'good', 'at', 'studying', 'and', 'learning', 'ML']
我們的上下文窗口(span)應該是 2 * 1 + 1 = 3。也就是窗口應該是
buffer=['I', 'am', 'good']
顯然target應該是'am'也就是為buffer[skip_window]而context word應該是['I', 'good']。這就構成了{x: data, y: label}之間的關系。
對于skip-gram模型的數據集合
- {(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 # 上下文的詞數 num_skips = 2 # 多少次后重用輸入的生成類別# 我們使用隨機鄰居樣本生成評估集合,這里我們限定了 # 評估樣本一些數字ID詞語,這些ID是通過詞頻產生 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))# 根據batch的大小設置輸入數據的batchembed = tf.nn.embedding_lookup(embeddings, train_inputs)# 設置權值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# 根據校驗集合,查找出相應的詞向量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數學原理的人,完全看不懂代碼,盡管你精通Python,也不知道為毛有這行代碼和代碼的含義。這里我不多講word2vec的數學原理,遲點我會再一遍文章講解word2vec的原理和疑問。這里我給出一篇我看過的詳細的文章word2vec的數學原理,大家可以先閱覽一下。我在這里稍微講一下代碼和附帶的原理內容。
# 隨機生成初始詞向量embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))大家都知道word2vec就是說把詞語變成向量形式,而在CBOW和skip-gram模型中,詞向量是副產品,真正的目的是推斷出上下文內容。這里就來一個要點了(是鄙人私以為的):
在模型的訓練過程中,調整詞向量和不斷是推斷逼近目標詞語是同時進行。也就是說調整詞向量->優化推斷->調整詞向量->優化推斷->調整詞向量->優化推斷.... 最后達到兩者同時收斂。這就是我們最后的目標。這是我從EM算法中類比獲得的想法,關于EM算法,我會在之后添加文章(算法推導+代碼)。
在DL和ML中我們都說到損失函數,不斷優化損失函數使其最小,是我們的目標。這里的損失函數是什么呢?那就是
tf.nn.nce_loss(weights=nce_weights,biases=nce_biases,labels=train_labels,inputs=embed,num_sampled=num_sample,num_classes=vocabulary_size)我們剛剛說到要把推斷出哪個詞應該出現在上下文當中,就是涉及到一個概率問題了。既然是推斷那就是要比較大小啦。那就是把詞典中所有的詞的有可能出現在上下文的概率都算一遍嗎?確實!在早期word2vec論文發布時,就是這么粗暴。現在就當然不是啦。那就是用negative sample來推斷進行提速啦。
我們知道在訓練過程中,我們都知道label是哪個詞。這意味著其他詞對于這個樣本就是negative了。那就好辦啦。我就使得label詞的概率最大化,其他詞出現的概率最小化。當中涉及的數學知識就是Maximum likelihood 最大似然估計。不懂的回去復習唄。
之后我們用梯度下降法進行訓練,這樣我們就得到訓練模型了。
step6 開始進行無恥的訓練
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()在每次訓練中我們都給數據模型喂養(feed)一小批數據(batch_input, batch_labels)。這些數據是通過generate_batch()生成的。通過暴力的迭代,我們最后得到最終詞向量(final_embedding)。在訓練過程中,每2000次迭代打印損失值,每10000次迭代打印校驗詞的相似詞(通過cosin相似度來判斷)。
最后還差一個詞向量降維后的圖片,我遲點不上。現在準備煮飯咯....
作者:Salon_sai
鏈接:http://www.jianshu.com/p/1624ede1f2ac
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
總結
以上是生活随笔為你收集整理的Tensorflow学习笔记——word2vec的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: All of Recurrent Neu
- 下一篇: Tensorflow[实战篇]——Fac