在TensorFlow中基於lstm構建分詞系統筆記


在TensorFlow中基於lstm構建分詞系統筆記(一)

https://www.jianshu.com/p/ccb805b9f014

前言

我打算基於lstm構建一個分詞系統,通過這個例子來學習下TensorFlow中如何訓練循環遞歸神經網絡。我們將從最粗糙的版本開始搭建這個小系統,然后一步步優化其中的每一部分,包括網絡架構的優化,數據處理的優化,甚至整個代碼架構的優化。希望想我一樣的入門選手看到其中的每一步實現以及如何去優化。
關於LSTM網絡的介紹,可以看官網推薦的一篇博客,寫的實在是太棒了http://colah.github.io/posts/2015-08-Understanding-LSTMs
另外這篇翻譯也很贊啊http://www.jianshu.com/p/9dc9f41f0b29,這里不在詳述。
我們第一個版本的模型來自官網的tutorials中Recurrent Neural Networks部分內容,官網的數據並不利於我們去直接感受模型訓練的帶來的結果,所以后來我想了下用它來實現一個中文分詞,可能更有利於初學者去直觀的感受。第一個版本會我寫的很粗糙,主要是為了理解在TensorFlow中如何搭建LSTM網絡。

模型搭建

我對官網中的例子用我自己更喜歡的結構重寫了下。 首先我們來看下如何搭建這個模型。開始我把模型部分代碼主要由inference(),loss()和training()三部分構成,inference()部分負責模型搭建,loss()負責計算損失,為優化做准備,training()負責優化部分,主要是對損失函數應用梯度下降,更新參數。我把上面三部分寫封裝一個類里面。后來發現這樣實現會存在些問題,然后又把inference()的實現直接放在了類的init()函數里面。下面先看下模型的整體實現,

class ptb_lstm(): def __init__(self,config): ... def loss(self): .... def train(self): .... 

這里,我們在init()中傳了一個config類,這個config主要是一些模型參數,大致形式是下面這樣,這篇筆記就不詳講了

class config(): '''參數配置類''' init_scale = 0.1 learning_rate = 1.0 max_grad_norm = 5 num_layers = 2 hidden_size = 200 keep_prob = 1.0 lr_decay = 0.5 batch_size = 50 num_steps = 50 vocab_size = 3000 output_size = 3 learningrate = 0.5 

好了,接下來我們先看init()部分

class lstm_model(): def __init__(self, config): ''' :param config:config類,一些訓練參數設置 ''' self.config = config self.x = tf.placeholder(tf.int32, shape=(config.batch_size, config.num_steps)) self.y = tf.placeholder(tf.int32, shape=(config.batch_size, config.num_steps)) def lstm_cell(): #構建lstm基本單元 return tf.contrib.rnn.BasicLSTMCell( self.config.hidden_size, forget_bias=0.0, state_is_tuple=True) attn_cell = lstm_cell if config.keep_prob < 1: #如果config.keep_prob參數小於1,對lstm單元進行dropout,防止過擬合 def attn_cell(): return tf.contrib.rnn.DropoutWrapper( lstm_cell(), output_keep_prob=config.keep_prob) cell = tf.contrib.rnn.MultiRNNCell( [attn_cell() for _ in range(config.num_layers)], state_is_tuple=True) #構建多層的lstm,config.num_layers是層數參數 self._initial_state = cell.zero_state(self.config.batch_size, tf.float32) #初始化lstm的state with tf.device("/cpu:0"): embedding = tf.get_variable( "embedding", [self.config.vocab_size, self.config.hidden_size], dtype=tf.float32) inputs = tf.nn.embedding_lookup(embedding,self.x) #詞嵌入 outputs = [] state = self._initial_state with tf.variable_scope("RNN"): for time_step in range(self.config.num_steps): if time_step > 0: tf.get_variable_scope().reuse_variables() (cell_output, state) = cell(inputs[:, time_step, :], state) outputs.append(cell_output) #前向傳播,計算每個單元的cell_output和state,把cell_output添加到outputs,把state傳遞到下個單元,最終outputs的為(config.num_steps,config.batch_size,config.hidden_size) output = tf.reshape(tf.concat(axis=1, values=outputs), [-1, self.config.hidden_size]) #output的形狀為(config.num_steps*config.batch_size,config.hedden_size) softmax_w = tf.get_variable( "softmax_w", [self.config.hidden_size, self.config.output_size], dtype=tf.float32) softmax_b = tf.get_variable("softmax_b", [self.config.output_size], dtype=tf.float32) self.logits = tf.matmul(output, softmax_w) + softmax_b #得到最終的網絡輸出logits形狀為(config.num_steps*config.batch_size,config.output_size) 

接着是loss(self,logits)

    def loss(self): logits = self.logits loss = tf.contrib.legacy_seq2seq.sequence_loss_by_example( [logits], [tf.reshape(self.y, [-1])], [tf.ones([self.config.batch_size * self.config.num_steps], dtype=tf.float32)]) # 交叉熵損失函數,下一篇專門講下tensorflow中的幾個損失函數的實現 cost = tf.reduce_sum(loss) / self.config.batch_size 

最后是后向傳播參數更新部分training(self)

def training(self): loss = self.loss() tvars = tf.trainable_variables() grads, _ = tf.clip_by_global_norm(tf.gradients(loss, tvars), self.config.max_grad_norm) optimizer = tf.train.GradientDescentOptimizer(self.config.learningrate) #優化器 train_op = optimizer.apply_gradients( zip(grads, tvars), global_step=tf.contrib.framework.get_or_create_global_step()) #梯度下降 return train_op


作者:MUTU洋
鏈接:https://www.jianshu.com/p/ccb805b9f014
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。
 
https://www.jianshu.com/p/142b4358aaf9
 

上一節我們介紹了我們的模型部分,這一節來介紹下我們的數據來源和數據預處理。對初學者可能常常面臨的問題的是模型的輸入到底是怎樣的,例如,在rnn可以處理任意長度的句子,所以很多初學者可能會認為是不是在TensorFlow中輸入不需要特殊處理。理論上rnn是可以處理任意長度的句子,但在工程實現上考慮到效率等一些列問題,TensorFlow中的rnn(包括它的變形,lstm,gru...)需要把不同長度的句子pading到同一個長度,一種是把所有句子都處理成同一個長度,另一種是我們只需要在同一個batch中的句子同一個長度。本節采用第一種。

數據

一 數據來源

這里我們自己構造了一份訓練數據,我隨意找了一份京東評論數據,你也可以采用其他的文本數據。然后利用jieba分詞對這些文本進行分詞。例如,有這樣一句話,s1 = “迪士尼發行了四部票房超過10億美元的電影”,我們對s1分詞后成為s1_seg = ['迪士尼', '發行', '了', '電影'],其中,“迪士尼”就是一個詞,我們把它處理成['B','I','I'],‘B’代表詞的開始,‘I’代表詞的中間。這樣s1就可以標記為s1_tag=['B','I','I','B','I','B','B','I'],這樣我們就可以得到我們的訓練數據。注意,jieba本身就可能分錯,我們這里只是想看下我們的模型能不能學習到訓練數據的分布。

二 數據預處理

剛才我們已經得到我們的訓練數據,現在我們要把它處理成符合輸入要求的數據格式。為了簡單,我這里把所以數據都處理成同樣長度的序列(上一章中我們構建的模型就是要求的所有的序列長度一樣)。由於在訓練的時,我們需要在數據上不斷的迭代更新參數。這里需要把數據處理成不同的batch,然后在每個batch上迭代。這里我們構造了一個類,這個類有一個next_batch方法。通過這個方法可以不斷的產生batch_size的訓練數據。


class DataSet(object): def __init__(self,x_data,y_data,): #這個類主要用於不斷產生訓練數據 self._x_data = np.array(x_data) self._y_data = np.array(y_data) self._epochs_completed = 0 self._index_in_epoch = 0 self._num_examples = len(x_data) @property def x_data(self): return self._x_data @property def y_data(self): return self._y_data @property def num_examples(self): return self._num_examples @property def epochs_completed(self): return self._epochs_completed def next_batch(self, batch_size, shuffle=True): """返回下一個`batch_size`數據""" start = self._index_in_epoch # 第一個epoch時做亂序處理 if self._epochs_completed == 0 and start == 0 and shuffle: perm0 = np.arange(self._num_examples) np.random.shuffle(perm0) self._x_data = self.x_data[perm0] self._y_data = self.y_data[perm0] # 進入到下一個epoch if start + batch_size > self._num_examples: # Finished epoch self._epochs_completed += 1 # Get the rest examples in this epoch rest_num_examples = self._num_examples - start x_rest_part = self._x_data[start:self._num_examples] y_rest_part = self._y_data[start:self._num_examples] # 數據亂序處理 if shuffle: perm = np.arange(self._num_examples) np.random.shuffle(perm) self._x_data = self._x_data[perm] self._y_data = self._y_data[perm] # 開始下一個epoch start = 0 self._index_in_epoch = batch_size - rest_num_examples end = self._index_in_epoch x_new_part = self._x_data[start:end] y_new_part = self._y_data[start:end] return np.concatenate((x_rest_part, x_new_part), axis=0), np.concatenate( (y_rest_part, y_new_part), axis=0) else: self._index_in_epoch += batch_size end = self._index_in_epoch return self._x_data[start:end], self._y_data[start:end] def word_to_id(dict_data): #遍歷所以的中文句子里的字符,建立一個Vocabulary,通過字符的頻次把每個字符映射到一個數字 counter = collections.Counter(''.join(dict_data.keys())) count_pairs = sorted(counter.items(), key=lambda x: (-x[1], x[0])) words, _ = list(zip(*count_pairs)) word_id = dict(zip(words, range(3, len(words) + 3))) word_id['B'] = 1 word_id['I'] = 2 return word_id def datas(dict_data,num_step): #讀取數據 x_data = [] y_data = [] word_id = word_to_id(dict_data) for line in dict_data: x_list = [word_id[word] for word in list(line)][:num_step] y_list = [word_id[word] for word in dict_data[line]][:num_step] x_len = len(x_list) y_len = len(y_list) assert x_len == y_len if x_len<num_step: x_list.extend([0]*(num_step-x_len)) y_list.extend([0]*(num_step-y_len)) x_data.append(x_list) y_data.append(y_list) return x_data,y_data def read_data_sets(fileName,num_step): #通過調用這個函數不斷的產生next batch的訓練數據 with open(fileName) as f: dict_data = json.load(f) x_data, y_data = datas(dict_data, num_step) return DataSet(x_data, y_data) 

通過調用read_data_sets來產生訓練數據,注意這里的參數dict_data參數指的是,key是字符串,例如前面的s1,value是該字符串的標記,例如s1的標記是s1_tag。
下一節我們將介紹訓練過程。



作者:MUTU洋
鏈接:https://www.jianshu.com/p/142b4358aaf9
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM