時(shí)隔若干個(gè)月,又繞到了word2vec。關(guān)于word2vec的原理我就不敘述了,具體可見(jiàn)word2vec中的數(shù)學(xué),寫(xiě)的非常好。
我后來(lái)自己用Python實(shí)現(xiàn)了一遍word2vec,過(guò)程寫(xiě)在自己動(dòng)手寫(xiě)word2vec (一):主要概念和流程以及后續(xù)的若干文章中
我當(dāng)時(shí)使用的是Hierarchical Softmax+CBOW的模型。給我的感覺(jué)是比較累,既要費(fèi)力去寫(xiě)huffman樹(shù),還要自己寫(xiě)計(jì)算梯度的代碼,完了按層softmax速度還慢。這次我決定用tensorflow來(lái)寫(xiě),除了極大的精簡(jiǎn)了代碼以外,可以使用gpu對(duì)運(yùn)算進(jìn)行加速。此外,這次使用了負(fù)采樣(negative sampling)+skip-gram模型,從而避免了使用Huffman樹(shù)導(dǎo)致訓(xùn)練速度變慢的情況,適合大規(guī)模的文本。
一些相關(guān)的資料:
word2vec 中的數(shù)學(xué)原理詳解-基于 Negative Sampling 的模型
自己動(dòng)手寫(xiě)word2vec (四):CBOW和skip-gram模型
tensorflow筆記:流程,概念和簡(jiǎn)單代碼注釋
tensorflow筆記 :常用函數(shù)說(shuō)明
其實(shí)google已經(jīng)實(shí)現(xiàn)過(guò)一遍word2vec了(點(diǎn)這里),我看了一下代碼,感覺(jué)核心代碼非常簡(jiǎn)介干練,我自己寫(xiě)的許多運(yùn)算和函數(shù)調(diào)用也是參照它來(lái)的,但是關(guān)于外圍的代碼,包括數(shù)據(jù)集的生成等方面,我不是很喜歡,而且也與我的要求不符,所以我重新寫(xiě)了一下,并且進(jìn)行了封裝,增加了模型的存/取,訓(xùn)練過(guò)程可視化等功能,并且簡(jiǎn)化了流程。
我的模型主要分成兩部分:由輸入單詞生成訓(xùn)練集的外圍代碼,以及用于描述模型,訓(xùn)練的核心代碼。在訓(xùn)練的時(shí)候,外圍代碼收到一個(gè)分好詞的句子,例如[‘我’,’中午’,’吃飯’],然后根據(jù)skip-gram模型,將其轉(zhuǎn)化成輸入集和標(biāo)簽集。例如
[我,中午,中午,吃飯]
[中午,我,吃飯,中午]
當(dāng)然了,實(shí)際過(guò)程中輸入集和標(biāo)簽集都是用id來(lái)表示的。生成輸入集和標(biāo)簽集以后,將其輸入核心代碼進(jìn)行訓(xùn)練。那就先從核心代碼講起吧。這篇文章中的代碼是不完全的,想看完整版的可以移步https://github.com/multiangle/tfword2vec
核心代碼主要就是描述模型,計(jì)算loss,根據(jù)loss優(yōu)化參數(shù)等步驟。這里計(jì)算loss直接使用了tf封裝好的tf.nn.nce_loss方法,比較方便。優(yōu)化方法這里也是選的最簡(jiǎn)單的梯度下降法。具體的描述就放在代碼里說(shuō)好了
self.graph = tf.Graph() self.graph = tf.Graph() with self.graph.as_default(): # 首先定義兩個(gè)用作輸入的占位符,分別輸入輸入集(train_inputs)和標(biāo)簽集(train_labels) self.train_inputs = tf.placeholder(tf.int32, shape=[self.batch_size]) self.train_labels = tf.placeholder(tf.int32, shape=[self.batch_size, 1]) # 詞向量矩陣,初始時(shí)為均勻隨機(jī)正態(tài)分布 self.embedding_dict = tf.Variable( tf.random_uniform([self.vocab_size,self.embedding_size],-1.0,1.0) ) # 模型內(nèi)部參數(shù)矩陣,初始為截?cái)嗾植?/span> self.nce_weight = tf.Variable(tf.truncated_normal([self.vocab_size, self.embedding_size], stddev=1.0/math.sqrt(self.embedding_size))) self.nce_biases = tf.Variable(tf.zeros([self.vocab_size])) # 將輸入序列向量化,具體可見(jiàn)我的【常用函數(shù)說(shuō)明】那一篇 embed = tf.nn.embedding_lookup(self.embedding_dict, self.train_inputs) # batch_size # 得到NCE損失(負(fù)采樣得到的損失) self.loss = tf.reduce_mean( tf.nn.nce_loss( weights = self.nce_weight, # 權(quán)重 biases = self.nce_biases, # 偏差 labels = self.train_labels, # 輸入的標(biāo)簽 inputs = embed, # 輸入向量 num_sampled = self.num_sampled, # 負(fù)采樣的個(gè)數(shù) num_classes = self.vocab_size # 類(lèi)別數(shù)目 ) ) # tensorboard 相關(guān) tf.scalar_summary('loss',self.loss) # 讓tensorflow記錄參數(shù) # 根據(jù) nce loss 來(lái)更新梯度和embedding,使用梯度下降法(gradient descent)來(lái)實(shí)現(xiàn) self.train_op = tf.train.GradientDescentOptimizer(learning_rate=0.1).minimize(self.loss) # 訓(xùn)練操作 # 計(jì)算與指定若干單詞的相似度 self.test_word_id = tf.placeholder(tf.int32,shape=[None]) vec_l2_model = tf.sqrt( # 求各詞向量的L2模 tf.reduce_sum(tf.square(self.embedding_dict),1,keep_dims=True) ) avg_l2_model = tf.reduce_mean(vec_l2_model) tf.scalar_summary('avg_vec_model',avg_l2_model) self.normed_embedding = self.embedding_dict / vec_l2_model # self.embedding_dict = norm_vec # 對(duì)embedding向量正則化 test_embed = tf.nn.embedding_lookup(self.normed_embedding, self.test_word_id) self.similarity = tf.matmul(test_embed, self.normed_embedding, transpose_b=True) # 變量初始化操作 self.init = tf.global_variables_initializer() # 匯總所有的變量記錄 self.merged_summary_op = tf.merge_all_summaries() # 保存模型的操作 self.saver = tf.train.Saver()
外圍代碼其實(shí)有很多,例如訓(xùn)練過(guò)程中變量的記錄,模型的保存與讀取等等,不過(guò)這與訓(xùn)練本身沒(méi)什么關(guān)系,這里還是貼如何將句子轉(zhuǎn)化成輸入集和標(biāo)簽集的代碼。對(duì)其他方面感興趣的看官可以到github上看完整的代碼。
def train_by_sentence(self, input_sentence=[]): # input_sentence: [sub_sent1, sub_sent2, ...] # 每個(gè)sub_sent是一個(gè)單詞序列,例如['這次','大選','讓'] sent_num = input_sentence.__len__() batch_inputs = [] batch_labels = [] for sent in input_sentence: # 輸入有可能是多個(gè)句子,這里每個(gè)循環(huán)處理一個(gè)句子 for i in range(sent.__len__()): # 處理單個(gè)句子中的每個(gè)單詞 start = max(0,i-self.win_len) # 窗口為 [-win_len,+win_len],總計(jì)長(zhǎng)2*win_len+1 end = min(sent.__len__(),i+self.win_len+1) # 將某個(gè)單詞對(duì)應(yīng)窗口中的其他單詞轉(zhuǎn)化為id計(jì)入label,該單詞本身計(jì)入input for index in range(start,end): if index == i: continue else: input_id = self.word2id.get(sent[i]) label_id = self.word2id.get(sent[index]) if not (input_id and label_id): # 如果單詞不在詞典中,則跳過(guò) continue batch_inputs.append(input_id) batch_labels.append(label_id) if len(batch_inputs)==0: # 如果標(biāo)簽集為空,則跳過(guò) return batch_inputs = np.array(batch_inputs,dtype=np.int32) batch_labels = np.array(batch_labels,dtype=np.int32) batch_labels = np.reshape(batch_labels,[batch_labels.__len__(),1]) # 生成供tensorflow訓(xùn)練用的數(shù)據(jù) feed_dict = { self.train_inputs: batch_inputs, self.train_labels: batch_labels } # 這句操控tf進(jìn)行各項(xiàng)操作。數(shù)組中的選項(xiàng),train_op等,是讓tf運(yùn)行的操作,feed_dict選項(xiàng)用來(lái)輸入數(shù)據(jù) _, loss_val, summary_str = self.sess.run([self.train_op,self.loss,self.merged_summary_op], feed_dict=feed_dict) # train loss,記錄這次訓(xùn)練的loss值 self.train_loss_records.append(loss_val) # self.train_loss_k10 = sum(self.train_loss_records)/self.train_loss_records.__len__() self.train_loss_k10 = np.mean(self.train_loss_records) # 求loss均值 if self.train_sents_num % 1000 == 0 : self.summary_writer.add_summary(summary_str,self.train_sents_num) print("{a} sentences dealed, loss: " .format(a=self.train_sents_num,b=self.train_loss_k10)) # train times self.train_words_num += batch_inputs.__len__() self.train_sents_num += input_sentence.__len__() self.train_times_num += 1
聯(lián)系客服