精华内容
下载资源
问答
  • RNN做了一个自动写诗的程序,包含已经训练好的model,可以直接运行,训练数据也包含在里面
  • 在TensorFlow (RNN)深度学习下 BiLSTM+CRF 跑 sequence labeling  双向LSTM+CRF跑序列标注问题 去年底样子一直在做NLP相关task,是个关于序列标注问题。这 sequence labeling属于NLP的经典问题了,开始尝试用HMM...

    在TensorFlow RNN 深度学习下 BiLSTM+CRF 实现 sequence labeling 

    双向LSTM+CRF 序列标注问题

    源码


    去年底样子一直在做NLP相关task,是个关于序列标注问题。这 sequence labeling属于NLP的经典问题了,开始尝试用HMM,哦不,用CRF做baseline,by the way, 用的CRF++。

    关于CRF的理论就不再啰嗦了,街货。顺便提下,CRF比HMM在理论上以及实际效果上都要好不少。但我要说的是CRF跑我这task还是不太乐观。P值0.6样子,R低的离谱,所以F1很不乐观。mentor告诉我说是特征不足,师兄说是这个task本身就比较难做,F1低算是正常了。


    CRF做完baseline后,一直在着手用BiLSTM+CRF跑 sequence labeling,奈何项目繁多,没有多余的精力去按照正常的计划做出来。后来还是一点一点的,按照大牛们的步骤以及参考现有的代码,把 BiLSTM+CRF的实现拿下了。后来发现,跑出来的效果也不太理想……可能是这个task确实变态……抑或模型还要加强吧~


    这里对比下CRF与LSTM的cell,先说RNN吧,RNN其实是比CNN更适合做序列问题的模型,RNN隐层当前时刻的输入有一部分是前一时刻的隐层输出,这使得他能通过循环反馈连接看到前面的信息,将一段序列的前面的context capture 过来参与此刻的计算,并且还具备非线性的拟合能力,这都是CRF无法超越的地方。而LSTM的cell很好的将RNN的梯度弥散问题优化解决了,他对门卫gate说:老兄,有的不太重要的信息,你该忘掉就忘掉吧,免得占用现在的资源。而双向LSTM就更厉害了,不仅看得到过去,还能将未来的序列考虑进来,使得上下文信息充分被利用。而CRF,他不像LSTM能够考虑长远的上下文信息,它更多地考虑整个句子的局部特征的线性加权组合(通过特征模板扫描整个句子),特别的一点,他计算的是联合概率,优化了整个序列,而不是拼接每个时刻的最优值。那么,将BILSTM与CRF一起就构成了还比较不错的组合,这目前也是学术界的流行做法~


    另外针对目前的跑通结果提几个改进点:

    1.+CNN,通过CNN的卷积操作去提取英文单词的字母细节。

    2.+char representation,作用与上相似,提取更细粒度的细节。

    3.more joint model to go.


    fine,叨了不少。codes time:


    完整代码以及相关预处理的数据请移步githubscofiled's github/bilstm+crf


    requirements:

    ubuntu14

    python2.7

    tensorflow 0.8

    numpy

    pandas0.15


    BILSTM_CRF.py

    import math
    import helper
    import numpy as np
    import tensorflow as tf
    from tensorflow.models.rnn import rnn, rnn_cell
    
    class BILSTM_CRF(object):
        
        def __init__(self, num_chars, num_classes, num_steps=200, num_epochs=100, embedding_matrix=None, is_training=True, is_crf=True, weight=False):
            # Parameter
            self.max_f1 = 0
            self.learning_rate = 0.002
            self.dropout_rate = 0.5
            self.batch_size = 128
            self.num_layers = 1   
            self.emb_dim = 100
            self.hidden_dim = 100
            self.num_epochs = num_epochs
            self.num_steps = num_steps
            self.num_chars = num_chars
            self.num_classes = num_classes
            
            # placeholder of x, y and weight
            self.inputs = tf.placeholder(tf.int32, [None, self.num_steps])
            self.targets = tf.placeholder(tf.int32, [None, self.num_steps])
            self.targets_weight = tf.placeholder(tf.float32, [None, self.num_steps])
            self.targets_transition = tf.placeholder(tf.int32, [None])
            
            # char embedding
            if embedding_matrix != None:
                self.embedding = tf.Variable(embedding_matrix, trainable=False, name="emb", dtype=tf.float32)
            else:
                self.embedding = tf.get_variable("emb", [self.num_chars, self.emb_dim])
            self.inputs_emb = tf.nn.embedding_lookup(self.embedding, self.inputs)
            self.inputs_emb = tf.transpose(self.inputs_emb, [1, 0, 2])
            self.inputs_emb = tf.reshape(self.inputs_emb, [-1, self.emb_dim])
            self.inputs_emb = tf.split(0, self.num_steps, self.inputs_emb)
    
            # lstm cell
            lstm_cell_fw = tf.nn.rnn_cell.BasicLSTMCell(self.hidden_dim)
            lstm_cell_bw = tf.nn.rnn_cell.BasicLSTMCell(self.hidden_dim)
    
            # dropout
            if is_training:
                lstm_cell_fw = tf.nn.rnn_cell.DropoutWrapper(lstm_cell_fw, output_keep_prob=(1 - self.dropout_rate))
                lstm_cell_bw = tf.nn.rnn_cell.DropoutWrapper(lstm_cell_bw, output_keep_prob=(1 - self.dropout_rate))
    
            lstm_cell_fw = tf.nn.rnn_cell.MultiRNNCell([lstm_cell_fw] * self.num_layers)
            lstm_cell_bw = tf.nn.rnn_cell.MultiRNNCell([lstm_cell_bw] * self.num_layers)
    
            # get the length of each sample
            self.length = tf.reduce_sum(tf.sign(self.inputs), reduction_indices=1)
            self.length = tf.cast(self.length, tf.int32)  
            
            # forward and backward
            self.outputs, _, _ = rnn.bidirectional_rnn(
                lstm_cell_fw, 
                lstm_cell_bw,
                self.inputs_emb, 
                dtype=tf.float32,
                sequence_length=self.length
            )
            
            # softmax
            self.outputs = tf.reshape(tf.concat(1, self.outputs), [-1, self.hidden_dim * 2])
            self.softmax_w = tf.get_variable("softmax_w", [self.hidden_dim * 2, self.num_classes])
            self.softmax_b = tf.get_variable("softmax_b", [self.num_classes])
            self.logits = tf.matmul(self.outputs, self.softmax_w) + self.softmax_b
    
            if not is_crf:
                pass
            else:
                self.tags_scores = tf.reshape(self.logits, [self.batch_size, self.num_steps, self.num_classes])
                self.transitions = tf.get_variable("transitions", [self.num_classes + 1, self.num_classes + 1])
                
                dummy_val = -1000
                class_pad = tf.Variable(dummy_val * np.ones((self.batch_size, self.num_steps, 1)), dtype=tf.float32)
                self.observations = tf.concat(2, [self.tags_scores, class_pad])
    
                begin_vec = tf.Variable(np.array([[dummy_val] * self.num_classes + [0] for _ in range(self.batch_size)]), trainable=False, dtype=tf.float32)
                end_vec = tf.Variable(np.array([[0] + [dummy_val] * self.num_classes for _ in range(self.batch_size)]), trainable=False, dtype=tf.float32) 
                begin_vec = tf.reshape(begin_vec, [self.batch_size, 1, self.num_classes + 1])
                end_vec = tf.reshape(end_vec, [self.batch_size, 1, self.num_classes + 1])
    
                self.observations = tf.concat(1, [begin_vec, self.observations, end_vec])
    
                self.mask = tf.cast(tf.reshape(tf.sign(self.targets),[self.batch_size * self.num_steps]), tf.float32)
                
                # point score
                self.point_score = tf.gather(tf.reshape(self.tags_scores, [-1]), tf.range(0, self.batch_size * self.num_steps) * self.num_classes + tf.reshape(self.targets,[self.batch_size * self.num_steps]))
                self.point_score *= self.mask
                
                # transition score
                self.trans_score = tf.gather(tf.reshape(self.transitions, [-1]), self.targets_transition)
                
                # real score
                self.target_path_score = tf.reduce_sum(self.point_score) + tf.reduce_sum(self.trans_score)
                
                # all path score
                self.total_path_score, self.max_scores, self.max_scores_pre  = self.forward(self.observations, self.transitions, self.length)
                
                # loss
                self.loss = - (self.target_path_score - self.total_path_score)
            
            # summary
            self.train_summary = tf.scalar_summary("loss", self.loss)
            self.val_summary = tf.scalar_summary("loss", self.loss)        
            
            self.optimizer = tf.train.AdamOptimizer(learning_rate=self.learning_rate).minimize(self.loss) 
    
        def logsumexp(self, x, axis=None):
            x_max = tf.reduce_max(x, reduction_indices=axis, keep_dims=True)
            x_max_ = tf.reduce_max(x, reduction_indices=axis)
            return x_max_ + tf.log(tf.reduce_sum(tf.exp(x - x_max), reduction_indices=axis))
    
        def forward(self, observations, transitions, length, is_viterbi=True, return_best_seq=True):
            length = tf.reshape(length, [self.batch_size])
            transitions = tf.reshape(tf.concat(0, [transitions] * self.batch_size), [self.batch_size, 6, 6])
            observations = tf.reshape(observations, [self.batch_size, self.num_steps + 2, 6, 1])
            observations = tf.transpose(observations, [1, 0, 2, 3])
            previous = observations[0, :, :, :]
            max_scores = []
            max_scores_pre = []
            alphas = [previous]
            for t in range(1, self.num_steps + 2):
                previous = tf.reshape(previous, [self.batch_size, 6, 1])
                current = tf.reshape(observations[t, :, :, :], [self.batch_size, 1, 6])
                alpha_t = previous + current + transitions
                if is_viterbi:
                    max_scores.append(tf.reduce_max(alpha_t, reduction_indices=1))
                    max_scores_pre.append(tf.argmax(alpha_t, dimension=1))
                alpha_t = tf.reshape(self.logsumexp(alpha_t, axis=1), [self.batch_size, 6, 1])
                alphas.append(alpha_t)
                previous = alpha_t           
                
            alphas = tf.reshape(tf.concat(0, alphas), [self.num_steps + 2, self.batch_size, 6, 1])
            alphas = tf.transpose(alphas, [1, 0, 2, 3])
            alphas = tf.reshape(alphas, [self.batch_size * (self.num_steps + 2), 6, 1])
    
            last_alphas = tf.gather(alphas, tf.range(0, self.batch_size) * (self.num_steps + 2) + length)
            last_alphas = tf.reshape(last_alphas, [self.batch_size, 6, 1])
    
            max_scores = tf.reshape(tf.concat(0, max_scores), (self.num_steps + 1, self.batch_size, 6))
            max_scores_pre = tf.reshape(tf.concat(0, max_scores_pre), (self.num_steps + 1, self.batch_size, 6))
            max_scores = tf.transpose(max_scores, [1, 0, 2])
            max_scores_pre = tf.transpose(max_scores_pre, [1, 0, 2])
    
            return tf.reduce_sum(self.logsumexp(last_alphas, axis=1)), max_scores, max_scores_pre        
    
        def train(self, sess, save_file, X_train, y_train, X_val, y_val):
            saver = tf.train.Saver()
    
            char2id, id2char = helper.loadMap("char2id")
            label2id, id2label = helper.loadMap("label2id")
    
            merged = tf.merge_all_summaries()
            summary_writer_train = tf.train.SummaryWriter('loss_log/train_loss', sess.graph)  
            summary_writer_val = tf.train.SummaryWriter('loss_log/val_loss', sess.graph)     
            
            num_iterations = int(math.ceil(1.0 * len(X_train) / self.batch_size))
    
            cnt = 0
            for epoch in range(self.num_epochs):
                # shuffle train in each epoch
                sh_index = np.arange(len(X_train))
                np.random.shuffle(sh_index)
                X_train = X_train[sh_index]
                y_train = y_train[sh_index]
                print "current epoch: %d" % (epoch)
                for iteration in range(num_iterations):
                    # train
                    X_train_batch, y_train_batch = helper.nextBatch(X_train, y_train, start_index=iteration * self.batch_size, batch_size=self.batch_size)
                    y_train_weight_batch = 1 + np.array((y_train_batch == label2id['B']) | (y_train_batch == label2id['E']), float)
                    transition_batch = helper.getTransition(y_train_batch)
                    
                    _, loss_train, max_scores, max_scores_pre, length, train_summary =\
                        sess.run([
                            self.optimizer, 
                            self.loss, 
                            self.max_scores, 
                            self.max_scores_pre, 
                            self.length,
                            self.train_summary
                        ], 
                        feed_dict={
                            self.targets_transition:transition_batch, 
                            self.inputs:X_train_batch, 
                            self.targets:y_train_batch, 
                            self.targets_weight:y_train_weight_batch
                        })
    
                    predicts_train = self.viterbi(max_scores, max_scores_pre, length, predict_size=self.batch_size)
                    if iteration % 10 == 0:
                        cnt += 1
                        precision_train, recall_train, f1_train = self.evaluate(X_train_batch, y_train_batch, predicts_train, id2char, id2label)
                        summary_writer_train.add_summary(train_summary, cnt)
                        print "iteration: %5d, train loss: %5d, train precision: %.5f, train recall: %.5f, train f1: %.5f" % (iteration, loss_train, precision_train, recall_train, f1_train)  
                        
                    # validation
                    if iteration % 100 == 0:
                        X_val_batch, y_val_batch = helper.nextRandomBatch(X_val, y_val, batch_size=self.batch_size)
                        y_val_weight_batch = 1 + np.array((y_val_batch == label2id['B']) | (y_val_batch == label2id['E']), float)
                        transition_batch = helper.getTransition(y_val_batch)
                        
                        loss_val, max_scores, max_scores_pre, length, val_summary =\
                            sess.run([
                                self.loss, 
                                self.max_scores, 
                                self.max_scores_pre, 
                                self.length,
                                self.val_summary
                            ], 
                            feed_dict={
                                self.targets_transition:transition_batch, 
                                self.inputs:X_val_batch, 
                                self.targets:y_val_batch, 
                                self.targets_weight:y_val_weight_batch
                            })
                        
                        predicts_val = self.viterbi(max_scores, max_scores_pre, length, predict_size=self.batch_size)
                        precision_val, recall_val, f1_val = self.evaluate(X_val_batch, y_val_batch, predicts_val, id2char, id2label)
                        summary_writer_val.add_summary(val_summary, cnt)
                        print "iteration: %5d, valid loss: %5d, valid precision: %.5f, valid recall: %.5f, valid f1: %.5f" % (iteration, loss_val, precision_val, recall_val, f1_val)
    
                        if f1_val > self.max_f1:
                            self.max_f1 = f1_val
                            save_path = saver.save(sess, save_file)
                            print "saved the best model with f1: %.5f" % (self.max_f1)
    
        def test(self, sess, X_test, X_test_str, output_path):
            char2id, id2char = helper.loadMap("char2id")
            label2id, id2label = helper.loadMap("label2id")
            num_iterations = int(math.ceil(1.0 * len(X_test) / self.batch_size))
            print "number of iteration: " + str(num_iterations)
            with open(output_path, "wb") as outfile:
                for i in range(num_iterations):
                    print "iteration: " + str(i + 1)
                    results = []
                    X_test_batch = X_test[i * self.batch_size : (i + 1) * self.batch_size]
                    X_test_str_batch = X_test_str[i * self.batch_size : (i + 1) * self.batch_size]
                    if i == num_iterations - 1 and len(X_test_batch) < self.batch_size:
                        X_test_batch = list(X_test_batch)
                        X_test_str_batch = list(X_test_str_batch)
                        last_size = len(X_test_batch)
                        X_test_batch += [[0 for j in range(self.num_steps)] for i in range(self.batch_size - last_size)]
                        X_test_str_batch += [['x' for j in range(self.num_steps)] for i in range(self.batch_size - last_size)]
                        X_test_batch = np.array(X_test_batch)
                        X_test_str_batch = np.array(X_test_str_batch)
                        results = self.predictBatch(sess, X_test_batch, X_test_str_batch, id2label)
                        results = results[:last_size]
                    else:
                        X_test_batch = np.array(X_test_batch)
                        results = self.predictBatch(sess, X_test_batch, X_test_str_batch, id2label)
                    
                    for i in range(len(results)):
                        doc = ''.join(X_test_str_batch[i])
                        outfile.write(doc + "<@>" +" ".join(results[i]).encode("utf-8") + "\n")
    
        def viterbi(self, max_scores, max_scores_pre, length, predict_size=128):
            best_paths = []
            for m in range(predict_size):
                path = []
                last_max_node = np.argmax(max_scores[m][length[m]])
                # last_max_node = 0
                for t in range(1, length[m] + 1)[::-1]:
                    last_max_node = max_scores_pre[m][t][last_max_node]
                    path.append(last_max_node)
                path = path[::-1]
                best_paths.append(path)
            return best_paths
    
        def predictBatch(self, sess, X, X_str, id2label):
            results = []
            length, max_scores, max_scores_pre = sess.run([self.length, self.max_scores, self.max_scores_pre], feed_dict={self.inputs:X})
            predicts = self.viterbi(max_scores, max_scores_pre, length, self.batch_size)
            for i in range(len(predicts)):
                x = ''.join(X_str[i]).decode("utf-8")
                y_pred = ''.join([id2label[val] for val in predicts[i] if val != 5 and val != 0])
                entitys = helper.extractEntity(x, y_pred)
                results.append(entitys)
            return results
    
        def evaluate(self, X, y_true, y_pred, id2char, id2label):
            precision = -1.0
            recall = -1.0
            f1 = -1.0
            hit_num = 0
            pred_num = 0
            true_num = 0
            for i in range(len(y_true)):
                x = ''.join([str(id2char[val].encode("utf-8")) for val in X[i]])
                y = ''.join([str(id2label[val].encode("utf-8")) for val in y_true[i]])
                y_hat = ''.join([id2label[val] for val in y_pred[i]  if val != 5])
                true_labels = helper.extractEntity(x, y)
                pred_labels = helper.extractEntity(x, y_hat)
                hit_num += len(set(true_labels) & set(pred_labels))
                pred_num += len(set(pred_labels))
                true_num += len(set(true_labels))
            if pred_num != 0:
                precision = 1.0 * hit_num / pred_num
            if true_num != 0:
                recall = 1.0 * hit_num / true_num
            if precision > 0 and recall > 0:
                f1 = 2.0 * (precision * recall) / (precision + recall)
            return precision, recall, f1  
    


    util.py

    #encoding:utf-8
    import re
    import os
    import csv
    import time
    import pickle
    import numpy as np
    import pandas as pd
    
    def getEmbedding(infile_path="embedding"):
    	char2id, id_char = loadMap("char2id")
    	row_index = 0
    	with open(infile_path, "rb") as infile:
    		for row in infile:
    			row = row.strip()
    			row_index += 1
    			if row_index == 1:
    				num_chars = int(row.split()[0])
    				emb_dim = int(row.split()[1])
    				emb_matrix = np.zeros((len(char2id.keys()), emb_dim))
    				continue
    			items = row.split()
    			char = items[0]
    			emb_vec = [float(val) for val in items[1:]]
    			if char in char2id:
    				emb_matrix[char2id[char]] = emb_vec
    	return emb_matrix
    
    def nextBatch(X, y, start_index, batch_size=128):
        last_index = start_index + batch_size
        X_batch = list(X[start_index:min(last_index, len(X))])
        y_batch = list(y[start_index:min(last_index, len(X))])
        if last_index > len(X):
            left_size = last_index - (len(X))
            for i in range(left_size):
                index = np.random.randint(len(X))
                X_batch.append(X[index])
                y_batch.append(y[index])
        X_batch = np.array(X_batch)
        y_batch = np.array(y_batch)
        return X_batch, y_batch
    
    def nextRandomBatch(X, y, batch_size=128):
        X_batch = []
        y_batch = []
        for i in range(batch_size):
            index = np.random.randint(len(X))
            X_batch.append(X[index])
            y_batch.append(y[index])
        X_batch = np.array(X_batch)
        y_batch = np.array(y_batch)
        return X_batch, y_batch
    
    # use "0" to padding the sentence
    def padding(sample, seq_max_len):
    	for i in range(len(sample)):
    		if len(sample[i]) < seq_max_len:
    			sample[i] += [0 for _ in range(seq_max_len - len(sample[i]))]
    	return sample
    
    def prepare(chars, labels, seq_max_len, is_padding=True):
    	X = []
    	y = []
    	tmp_x = []
    	tmp_y = []
    
    	for record in zip(chars, labels):
    		c = record[0]
    		l = record[1]
    		# empty line
    		if c == -1:
    			if len(tmp_x) <= seq_max_len:
    				X.append(tmp_x)
    				y.append(tmp_y)
    			tmp_x = []
    			tmp_y = []
    		else:
    			tmp_x.append(c)
    			tmp_y.append(l)	
    	if is_padding:
    		X = np.array(padding(X, seq_max_len))
    	else:
    		X = np.array(X)
    	y = np.array(padding(y, seq_max_len))
    
    	return X, y
    
    def extractEntity(sentence, labels):
        entitys = []
        re_entity = re.compile(r'BM*E')
        m = re_entity.search(labels)
        while m:
            entity_labels = m.group()
            start_index = labels.find(entity_labels)
            entity = sentence[start_index:start_index + len(entity_labels)]
            labels = list(labels)
            # replace the "BM*E" with "OO*O"
            labels[start_index: start_index + len(entity_labels)] = ['O' for i in range(len(entity_labels))] 
            entitys.append(entity)
            labels = ''.join(labels)
            m = re_entity.search(labels)
        return entitys
    
    def loadMap(token2id_filepath):
    	if not os.path.isfile(token2id_filepath):
    		print "file not exist, building map"
    		buildMap()
    
    	token2id = {}
    	id2token = {}
    	with open(token2id_filepath) as infile:
    		for row in infile:
    			row = row.rstrip().decode("utf-8")
    			token = row.split('\t')[0]
    			token_id = int(row.split('\t')[1])
    			token2id[token] = token_id
    			id2token[token_id] = token
    	return token2id, id2token
    
    def saveMap(id2char, id2label):
    	with open("char2id", "wb") as outfile:
    		for idx in id2char:
    			outfile.write(id2char[idx] + "\t" + str(idx)  + "\r\n")
    	with open("label2id", "wb") as outfile:
    		for idx in id2label:
    			outfile.write(id2label[idx] + "\t" + str(idx) + "\r\n")
    	print "saved map between token and id"
    
    def buildMap(train_path="train.in"):
    	df_train = pd.read_csv(train_path, delimiter='\t', quoting=csv.QUOTE_NONE, skip_blank_lines=False, header=None, names=["char", "label"])
    	chars = list(set(df_train["char"][df_train["char"].notnull()]))
    	labels = list(set(df_train["label"][df_train["label"].notnull()]))
    	char2id = dict(zip(chars, range(1, len(chars) + 1)))
    	label2id = dict(zip(labels, range(1, len(labels) + 1)))
    	id2char = dict(zip(range(1, len(chars) + 1), chars))
    	id2label =  dict(zip(range(1, len(labels) + 1), labels))
    	id2char[0] = "<PAD>"
    	id2label[0] = "<PAD>"
    	char2id["<PAD>"] = 0
    	label2id["<PAD>"] = 0
    	id2char[len(chars) + 1] = "<NEW>"
    	char2id["<NEW>"] = len(chars) + 1
    
    	saveMap(id2char, id2label)
    	
    	return char2id, id2char, label2id, id2label
    
    def getTrain(train_path, val_path, train_val_ratio=0.99, use_custom_val=False, seq_max_len=200):
    	char2id, id2char, label2id, id2label = buildMap(train_path)
    	df_train = pd.read_csv(train_path, delimiter='\t', quoting=csv.QUOTE_NONE, skip_blank_lines=False, header=None, names=["char", "label"])
    
    	# map the char and label into id
    	df_train["char_id"] = df_train.char.map(lambda x : -1 if str(x) == str(np.nan) else char2id[x])
    	df_train["label_id"] = df_train.label.map(lambda x : -1 if str(x) == str(np.nan) else label2id[x])
    	
    	# convert the data in maxtrix
    	X, y = prepare(df_train["char_id"], df_train["label_id"], seq_max_len)
    
    	# shuffle the samples
    	num_samples = len(X)
    	indexs = np.arange(num_samples)
    	np.random.shuffle(indexs)
    	X = X[indexs]
    	y = y[indexs]
    	
    	if val_path != None:
    		X_train = X
    		y_train = y	
    		X_val, y_val = getTest(val_path, is_validation=True, seq_max_len=seq_max_len)
    	else:
    		# split the data into train and validation set
    		X_train = X[:int(num_samples * train_val_ratio)]
    		y_train = y[:int(num_samples * train_val_ratio)]
    		X_val = X[int(num_samples * train_val_ratio):]
    		y_val = y[int(num_samples * train_val_ratio):]
    
    	print "train size: %d, validation size: %d" %(len(X_train), len(y_val))
    
    	return X_train, y_train, X_val, y_val
    
    def getTest(test_path="test.in", is_validation=False, seq_max_len=200):
    	char2id, id2char = loadMap("char2id")
    	label2id, id2label = loadMap("label2id")
    
    	df_test = pd.read_csv(test_path, delimiter='\t', quoting=csv.QUOTE_NONE, skip_blank_lines=False, header=None, names=["char", "label"])
    	
    	def mapFunc(x, char2id):
    		if str(x) == str(np.nan):
    			return -1
    		elif x.decode("utf-8") not in char2id:
    			return char2id["<NEW>"]
    		else:
    			return char2id[x.decode("utf-8")]
    
    	df_test["char_id"] = df_test.char.map(lambda x:mapFunc(x, char2id))
    	df_test["label_id"] = df_test.label.map(lambda x : -1 if str(x) == str(np.nan) else label2id[x])
    	
    	if is_validation:
    		X_test, y_test = prepare(df_test["char_id"], df_test["label_id"], seq_max_len)
    		return X_test, y_test
    	else:
    		df_test["char"] = df_test.char.map(lambda x : -1 if str(x) == str(np.nan) else x)
    		X_test, _ = prepare(df_test["char_id"], df_test["char_id"], seq_max_len)
    		X_test_str, _ = prepare(df_test["char"], df_test["char_id"], seq_max_len, is_padding=False)
    		print "test size: %d" %(len(X_test))
    		return X_test, X_test_str
    
    def getTransition(y_train_batch):
    	transition_batch = []
    	for m in range(len(y_train_batch)):
    		y = [5] + list(y_train_batch[m]) + [0]
    		for t in range(len(y)):
    			if t + 1 == len(y):
    				continue
    			i = y[t]
    			j = y[t + 1]
    			if i == 0:
    				break
    			transition_batch.append(i * 6 + j)
    	transition_batch = np.array(transition_batch)
    	return transition_batch
    

    train.py

    import time
    import helper
    import argparse
    import numpy as np
    import pandas as pd
    import tensorflow as tf
    from BILSTM_CRF import BILSTM_CRF
    
    # python train.py train.in model -v validation.in -c char_emb -e 10 -g 2
    
    parser = argparse.ArgumentParser()
    parser.add_argument("train_path", help="the path of the train file")
    parser.add_argument("save_path", help="the path of the saved model")
    parser.add_argument("-v","--val_path", help="the path of the validation file", default=None)
    parser.add_argument("-e","--epoch", help="the number of epoch", default=100, type=int)
    parser.add_argument("-c","--char_emb", help="the char embedding file", default=None)
    parser.add_argument("-g","--gpu", help="the id of gpu, the default is 0", default=0, type=int)
    
    args = parser.parse_args()
    
    train_path = args.train_path
    save_path = args.save_path
    val_path = args.val_path
    num_epochs = args.epoch
    emb_path = args.char_emb
    gpu_config = "/cpu:0"
    #gpu_config = "/gpu:"+str(args.gpu)
    num_steps = 200 # it must consist with the test
    
    start_time = time.time()
    print "preparing train and validation data"
    X_train, y_train, X_val, y_val = helper.getTrain(train_path=train_path, val_path=val_path, seq_max_len=num_steps)
    char2id, id2char = helper.loadMap("char2id")
    label2id, id2label = helper.loadMap("label2id")
    num_chars = len(id2char.keys())
    num_classes = len(id2label.keys())
    if emb_path != None:
    	embedding_matrix = helper.getEmbedding(emb_path)
    else:
    	embedding_matrix = None
    
    print "building model"
    config = tf.ConfigProto(allow_soft_placement=True)
    with tf.Session(config=config) as sess:
    	with tf.device(gpu_config):
    		initializer = tf.random_uniform_initializer(-0.1, 0.1)
    		with tf.variable_scope("model", reuse=None, initializer=initializer):
    			model = BILSTM_CRF(num_chars=num_chars, num_classes=num_classes, num_steps=num_steps, num_epochs=num_epochs, embedding_matrix=embedding_matrix, is_training=True)
    
    		print "training model"
    		tf.initialize_all_variables().run()
    		model.train(sess, save_path, X_train, y_train, X_val, y_val)
    
    		print "final best f1 is: %f" % (model.max_f1)
    
    		end_time = time.time()
    		print "time used %f(hour)" % ((end_time - start_time) / 3600)

    test.py

    import time
    import helper
    import argparse
    import numpy as np
    import pandas as pd
    import tensorflow as tf
    from BILSTM_CRF import BILSTM_CRF
    
    # python test.py model test.in test.out -c char_emb -g 2
    
    parser = argparse.ArgumentParser()
    parser.add_argument("model_path", help="the path of model file")
    parser.add_argument("test_path", help="the path of test file")
    parser.add_argument("output_path", help="the path of output file")
    parser.add_argument("-c","--char_emb", help="the char embedding file", default=None)
    parser.add_argument("-g","--gpu", help="the id of gpu, the default is 0", default=0, type=int)
    args = parser.parse_args()
    
    model_path = args.model_path
    test_path = args.test_path
    output_path = args.output_path
    gpu_config = "/cpu:0"
    emb_path = args.char_emb
    num_steps = 200 # it must consist with the train
    
    start_time = time.time()
    
    print "preparing test data"
    X_test, X_test_str = helper.getTest(test_path=test_path, seq_max_len=num_steps)
    char2id, id2char = helper.loadMap("char2id")
    label2id, id2label = helper.loadMap("label2id")
    num_chars = len(id2char.keys())
    num_classes = len(id2label.keys())
    if emb_path != None:
    	embedding_matrix = helper.getEmbedding(emb_path)
    else:
    	embedding_matrix = None
    
    print "building model"
    config = tf.ConfigProto(allow_soft_placement=True)
    with tf.Session(config=config) as sess:
    	with tf.device(gpu_config):
    		initializer = tf.random_uniform_initializer(-0.1, 0.1)
    		with tf.variable_scope("model", reuse=None, initializer=initializer):
    			model = BILSTM_CRF(num_chars=num_chars, num_classes=num_classes, num_steps=num_steps, embedding_matrix=embedding_matrix, is_training=False)
    
    		print "loading model parameter"
    		saver = tf.train.Saver()
    		saver.restore(sess, model_path)
    
    		print "testing"
    		model.test(sess, X_test, X_test_str, output_path)
    
    		end_time = time.time()
    		print "time used %f(hour)" % ((end_time - start_time) / 3600)


    相关预处理的数据请参考github: scofiled's github/bilstm+crf













    展开全文
  • https://cloud.tencent.com/developer/article/1144238添加链接描述
    展开全文
  • 深度学习RNN循环神经网络RNN RNN 深度学习RNN

    深度学习之RNN循环神经网络

    RNN

    深度学习之RNN

    展开全文
  • RNN

    万次阅读 多人点赞 2018-06-06 23:40:03
    RNN(Recurrent Neural Network)是一类用于处理序列数据的神经网络。首先我们要明确什么是序列数据,摘取百度百科词条:时间序列数据是指在不同时间点上收集到的数据,这类数据反映了某一事物、现象等随时间的变化...

    本文部分参考和摘录了以下文章,在此由衷感谢以下作者的分享!
    https://zhuanlan.zhihu.com/p/28054589
    https://blog.csdn.net/qq_16234613/article/details/79476763
    http://www.cnblogs.com/pinard/p/6509630.html
    https://zhuanlan.zhihu.com/p/28687529
    http://www.cnblogs.com/pinard/p/6509630.html
    https://zhuanlan.zhihu.com/p/26892413
    https://zhuanlan.zhihu.com/p/21462488?refer=intelligentunit


    RNN(Recurrent Neural Network)是一类用于处理序列数据的神经网络。首先我们要明确什么是序列数据,摘取百度百科词条:时间序列数据是指在不同时间点上收集到的数据,这类数据反映了某一事物、现象等随时间的变化状态或程度。这是时间序列数据的定义,当然这里也可以不是时间,比如文字序列,但总归序列数据有一个特点——后面的数据跟前面的数据有关系。

    RNN的结构及变体

    我们从基础的神经网络中知道,神经网络包含输入层、隐层、输出层,通过激活函数控制输出,层与层之间通过权值连接。激活函数是事先确定好的,那么神经网络模型通过训练“学“到的东西就蕴含在“权值“中。
    基础的神经网络只在层与层之间建立了权连接,RNN最大的不同之处就是在层之间的神经元之间也建立的权连接。如图。
    这里写图片描述

    这是一个标准的RNN结构图,图中每个箭头代表做一次变换,也就是说箭头连接带有权值。左侧是折叠起来的样子,右侧是展开的样子,左侧中h旁边的箭头代表此结构中的“循环“体现在隐层。
    在展开结构中我们可以观察到,在标准的RNN结构中,隐层的神经元之间也是带有权值的。也就是说,随着序列的不断推进,前面的隐层将会影响后面的隐层。图中O代表输出,y代表样本给出的确定值,L代表损失函数,我们可以看到,“损失“也是随着序列的推荐而不断积累的。
    除上述特点之外,标准RNN的还有以下特点:
    1、权值共享,图中的W全是相同的,U和V也一样。
    2、每一个输入值都只与它本身的那条路线建立权连接,不会和别的神经元连接。

    以上是RNN的标准结构,然而在实际中这一种结构并不能解决所有问题,例如我们输入为一串文字,输出为分类类别,那么输出就不需要一个序列,只需要单个输出。如图。
    这里写图片描述
    同样的,我们有时候还需要单输入但是输出为序列的情况。那么就可以使用如下结构:
    这里写图片描述

    还有一种结构是输入虽是序列,但不随着序列变化,就可以使用如下结构:
    这里写图片描述

    原始的N vs N RNN要求序列等长,然而我们遇到的大部分问题序列都是不等长的,如机器翻译中,源语言和目标语言的句子往往并没有相同的长度。
    下面我们来介绍RNN最重要的一个变种:N vs M。这种结构又叫Encoder-Decoder模型,也可以称之为Seq2Seq模型。
    这里写图片描述
    从名字就能看出,这个结构的原理是先编码后解码。左侧的RNN用来编码得到c,拿到c后再用右侧的RNN进行解码。得到c有多种方式,最简单的方法就是把Encoder的最后一个隐状态赋值给c,还可以对最后的隐状态做一个变换得到c,也可以对所有的隐状态做变换。
    这里写图片描述

    除了以上这些结构以外RNN还有很多种结构,用于应对不同的需求和解决不同的问题。还想继续了解可以看一下下面这个博客,里面又介绍了几种不同的结构。但相同的是循环神经网络除了拥有神经网络都有的一些共性元素之外,它总要在一个地方体现出“循环“,而根据“循环“体现方式的不同和输入输出的变化就形成了多种RNN结构。

    https://blog.csdn.net/qq_16234613/article/details/79476763

    标准RNN的前向输出流程

    上面介绍了RNN有很多变种,但其数学推导过程其实都是大同小异。这里就介绍一下标准结构的RNN的前向传播过程。
    这里写图片描述

    再来介绍一下各个符号的含义:x是输入,h是隐层单元,o为输出,L为损失函数,y为训练集的标签。这些元素右上角带的t代表t时刻的状态,其中需要注意的是,因策单元h在t时刻的表现不仅由此刻的输入决定,还受t时刻之前时刻的影响。V、W、U是权值,同一类型的权连接权值相同。

    有了上面的理解,前向传播算法其实非常简单,对于t时刻:
    h(t)=ϕ(Ux(t)+Wh(t1)+b)h^{(t)}=\phi(Ux^{(t)}+Wh^{(t-1)}+b)
    其中ϕ()\phi()为激活函数,一般来说会选择tanh函数,b为偏置。

    t时刻的输出就更为简单:
    o(t)=Vh(t)+co^{(t)}=Vh^{(t)}+c

    最终模型的预测输出为:
    y^(t)=σ(o(t))\widehat{y}^{(t)}=\sigma(o^{(t)})

    其中σ\sigma为激活函数,通常RNN用于分类,故这里一般用softmax函数。

    RNN的训练方法——BPTT

    BPTT(back-propagation through time)算法是常用的训练RNN的方法,其实本质还是BP算法,只不过RNN处理时间序列数据,所以要基于时间反向传播,故叫随时间反向传播。BPTT的中心思想和BP算法相同,沿着需要优化的参数的负梯度方向不断寻找更优的点直至收敛。综上所述,BPTT算法本质还是BP算法,BP算法本质还是梯度下降法,那么求各个参数的梯度便成了此算法的核心。
    这里写图片描述

    再次拿出这个结构图观察,需要寻优的参数有三个,分别是U、V、W。与BP算法不同的是,其中W和U两个参数的寻优过程需要追溯之前的历史数据,参数V相对简单只需关注目前,那么我们就来先求解参数V的偏导数。
    L(t)V=L(t)o(t)o(t)V\frac{\partial L^{(t)}}{\partial V}=\frac{\partial L^{(t)}}{\partial o^{(t)}}\cdot \frac{\partial o^{(t)}}{\partial V}
    这个式子看起来简单但是求解起来很容易出错,因为其中嵌套着激活函数函数,是复合函数的求道过程。

    RNN的损失也是会随着时间累加的,所以不能只求t时刻的偏导。
    L=t=1nL(t)L=\sum_{t=1}^{n}L^{(t)} LV=t=1nL(t)o(t)o(t)V\frac{\partial L}{\partial V}=\sum_{t=1}^{n}\frac{\partial L^{(t)}}{\partial o^{(t)}}\cdot \frac{\partial o^{(t)}}{\partial V}

    W和U的偏导的求解由于需要涉及到历史数据,其偏导求起来相对复杂,我们先假设只有三个时刻,那么在第三个时刻 L对W的偏导数为:
    L(3)W=L(3)o(3)o(3)h(3)h(3)W+L(3)o(3)o(3)h(3)h(3)h(2)h(2)W+L(3)o(3)o(3)h(3)h(3)h(2)h(2)h(1)h(1)W\frac{\partial L^{(3)}}{\partial W}=\frac{\partial L^{(3)}}{\partial o^{(3)}}\frac{\partial o^{(3)}}{\partial h^{(3)}}\frac{\partial h^{(3)}}{\partial W}+\frac{\partial L^{(3)}}{\partial o^{(3)}}\frac{\partial o^{(3)}}{\partial h^{(3)}}\frac{\partial h^{(3)}}{\partial h^{(2)}}\frac{\partial h^{(2)}}{\partial W}+\frac{\partial L^{(3)}}{\partial o^{(3)}}\frac{\partial o^{(3)}}{\partial h^{(3)}}\frac{\partial h^{(3)}}{\partial h^{(2)}}\frac{\partial h^{(2)}}{\partial h^{(1)}}\frac{\partial h^{(1)}}{\partial W}
    相应的,L在第三个时刻对U的偏导数为:
    L(3)U=L(3)o(3)o(3)h(3)h(3)U+L(3)o(3)o(3)h(3)h(3)h(2)h(2)U+L(3)o(3)o(3)h(3)h(3)h(2)h(2)h(1)h(1)U\frac{\partial L^{(3)}}{\partial U}=\frac{\partial L^{(3)}}{\partial o^{(3)}}\frac{\partial o^{(3)}}{\partial h^{(3)}}\frac{\partial h^{(3)}}{\partial U}+\frac{\partial L^{(3)}}{\partial o^{(3)}}\frac{\partial o^{(3)}}{\partial h^{(3)}}\frac{\partial h^{(3)}}{\partial h^{(2)}}\frac{\partial h^{(2)}}{\partial U}+\frac{\partial L^{(3)}}{\partial o^{(3)}}\frac{\partial o^{(3)}}{\partial h^{(3)}}\frac{\partial h^{(3)}}{\partial h^{(2)}}\frac{\partial h^{(2)}}{\partial h^{(1)}}\frac{\partial h^{(1)}}{\partial U}

    可以观察到,在某个时刻的对W或是U的偏导数,需要追溯这个时刻之前所有时刻的信息,这还仅仅是一个时刻的偏导数,上面说过损失也是会累加的,那么整个损失函数对W和U的偏导数将会非常繁琐。虽然如此但好在规律还是有迹可循,我们根据上面两个式子可以写出L在t时刻对W和U偏导数的通式:
    L(t)W=k=1tL(t)o(t)o(t)h(t)(j=k+1th(j)h(j1))h(k)W\frac{\partial L^{(t)}}{\partial W}=\sum_{k=1}^{t}\frac{\partial L^{(t)}}{\partial o^{(t)}}\frac{\partial o^{(t)}}{\partial h^{(t)}}(\prod_{j=k+1}^{t}\frac{\partial h^{(j)}}{\partial h^{(j-1)}})\frac{\partial h^{(k)}}{\partial W} L(t)U=k=1tL(t)o(t)o(t)h(t)(j=k+1th(j)h(j1))h(k)U\frac{\partial L^{(t)}}{\partial U}=\sum_{k=1}^{t}\frac{\partial L^{(t)}}{\partial o^{(t)}}\frac{\partial o^{(t)}}{\partial h^{(t)}}(\prod_{j=k+1}^{t}\frac{\partial h^{(j)}}{\partial h^{(j-1)}})\frac{\partial h^{(k)}}{\partial U}
    整体的偏导公式就是将其按时刻再一一加起来。

    前面说过激活函数是嵌套在里面的,如果我们把激活函数放进去,拿出中间累乘的那部分:
    j=k+1thjhj1=j=k+1ttanhWs\prod_{j=k+1}^{t}{\frac{\partial{h^{j}}}{\partial{h^{j-1}}}} = \prod_{j=k+1}^{t}{tanh^{'}}\cdot W_{s}或是
    j=k+1thjhj1=j=k+1tsigmoidWs\prod_{j=k+1}^{t}{\frac{\partial{h^{j}}}{\partial{h^{j-1}}}} = \prod_{j=k+1}^{t}{sigmoid^{'}}\cdot W_{s}

    我们会发现累乘会导致激活函数导数的累乘,进而会导致“梯度消失“和“梯度爆炸“现象的发生。

    至于为什么,我们先来看看这两个激活函数的图像。
    这是sigmoid函数的函数图和导数图。
    这里写图片描述
    这是tanh函数的函数图和导数图。
    这里写图片描述
    它们二者是何其的相似,都把输出压缩在了一个范围之内。他们的导数图像也非常相近,我们可以从中观察到,sigmoid函数的导数范围是(0,0.25],tanh函数的导数范围是(0,1],他们的导数最大都不大于1。

    这就会导致一个问题,在上面式子累乘的过程中,如果取sigmoid函数作为激活函数的话,那么必然是一堆小数在做乘法,结果就是越乘越小。随着时间序列的不断深入,小数的累乘就会导致梯度越来越小直到接近于0,这就是“梯度消失“现象。其实RNN的时间序列与深层神经网络很像,在较为深层的神经网络中使用sigmoid函数做激活函数也会导致反向传播时梯度消失,梯度消失就意味消失那一层的参数再也不更新,那么那一层隐层就变成了单纯的映射层,毫无意义了,所以在深层神经网络中,有时候多加神经元数量可能会比多家深度好。

    你可能会提出异议,RNN明明与深层神经网络不同,RNN的参数都是共享的,而且某时刻的梯度是此时刻和之前时刻的累加,即使传不到最深处那浅层也是有梯度的。这当然是对的,但如果我们根据有限层的梯度来更新更多层的共享的参数一定会出现问题的,因为将有限的信息来作为寻优根据必定不会找到所有信息的最优解。

    之前说过我们多用tanh函数作为激活函数,那tanh函数的导数最大也才1啊,而且又不可能所有值都取到1,那相当于还是一堆小数在累乘,还是会出现“梯度消失“,那为什么还要用它做激活函数呢?原因是tanh函数相对于sigmoid函数来说梯度较大,收敛速度更快且引起梯度消失更慢。

    还有一个原因是sigmoid函数还有一个缺点,Sigmoid函数输出不是零中心对称。sigmoid的输出均大于0,这就使得输出不是0均值,称为偏移现象,这将导致后一层的神经元将上一层输出的非0均值的信号作为输入。关于原点对称的输入和中心对称的输出,网络会收敛地更好。

    RNN的特点本来就是能“追根溯源“利用历史数据,现在告诉我可利用的历史数据竟然是有限的,这就令人非常难受,解决“梯度消失“是非常必要的。这里说两种改善“梯度消失”的方法:
    1、选取更好的激活函数
    2、改变传播结构

    关于第一点,一般选用ReLU函数作为激活函数,ReLU函数的图像为:
    这里写图片描述
    ReLU函数的左侧导数为0,右侧导数恒为1,这就避免了小数的连乘,但反向传播中仍有权值的累乘,所以说ReLU函数不能说完全解决了“梯度消失”现象,只能说改善。有研究表明,在RNN中使用ReLU函数配合将权值初始化到单位矩阵附近,可以达到接近LSTM网络的效果。但恒为1的导数容易导致“梯度爆炸“,但设定合适的阈值可以解决这个问题。还有一点就是如果左侧横为0的导数有可能导致把神经元学死,不过设置合适的步长(学习率)也可以有效避免这个问题的发生。

    关于第二点,LSTM结构就是传统RNN的改善。

    总结一下,sigmoid函数的缺点:
    1、导数值范围为(0,0.25],反向传播时会导致“梯度消失“。tanh函数导数值范围更大,相对好一点。
    2、sigmoid函数不是0中心对称,tanh函数是,可以使网络收敛的更好。


    LSTM

    下面来了解一下LSTM(long short-term memory)。长短期记忆网络是RNN的一种变体,RNN由于梯度消失的原因只能有短期记忆,LSTM网络通过精妙的门控制将加法运算带入网络中,一定程度上解决了梯度消失的问题。只能说一定程度上,过长的序列还是会出现“梯度消失”(我记得有个老外的博客上说长度超过300就有可能出现),所以LSTM叫长一点的“短时记忆”。
    由于已经存在了一篇写得非常好的博客,我在这里就直接转载过来,再在其中夹杂点自己的理解。原文连接如下。

    作者:朱小虎Neil 链接:https://www.jianshu.com/p/9dc9f41f0b29 來源:简书

    在此感谢原作者!

    长期依赖(Long-Term Dependencies)问题

    RNN 的关键点之一就是他们可以用来连接先前的信息到当前的任务上,例如使用过去的视频段来推测对当前段的理解。如果 RNN 可以做到这个,他们就变得非常有用。但是真的可以么?答案是,还有很多依赖因素。

    有时候,我们仅仅需要知道先前的信息来执行当前的任务。例如,我们有一个语言模型用来基于先前的词来预测下一个词。如果我们试着预测 “the clouds are in the sky” 最后的词,我们并不需要任何其他的上下文 —— 因此下一个词很显然就应该是 sky。在这样的场景中,相关的信息和预测的词位置之间的间隔是非常小的,RNN 可以学会使用先前的信息。
    在这里插入图片描述
    不太长的相关信息和位置间隔
    但是同样会有一些更加复杂的场景。假设我们试着去预测“I grew up in France… I speak fluent French”最后的词。当前的信息建议下一个词可能是一种语言的名字,但是如果我们需要弄清楚是什么语言,我们是需要先前提到的离当前位置很远的 France 的上下文的。这说明相关信息和当前预测位置之间的间隔就肯定变得相当的大。

    不幸的是,在这个间隔不断增大时,RNN 会丧失学习到连接如此远的信息的能力。
    在这里插入图片描述
    相当长的相关信息和位置间隔
    在理论上,RNN 绝对可以处理这样的 长期依赖 问题。人们可以仔细挑选参数来解决这类问题中的最初级形式,但在实践中,RNN 肯定不能够成功学习到这些知识。Bengio, et al. (1994)等人对该问题进行了深入的研究,他们发现一些使训练 RNN 变得非常困难的相当根本的原因。

    然而,幸运的是,LSTM 并没有这个问题

    LSTM 网络

    Long Short Term 网络—— 一般就叫做 LSTM ——是一种 RNN 特殊的类型,可以学习长期依赖信息。LSTM 由Hochreiter & Schmidhuber (1997)提出,并在近期被Alex Graves进行了改良和推广。在很多问题,LSTM 都取得相当巨大的成功,并得到了广泛的使用。
    LSTM 通过刻意的设计来避免长期依赖问题。记住长期的信息在实践中是 LSTM 的默认行为,而非需要付出很大代价才能获得的能力!

    所有 RNN 都具有一种重复神经网络模块的链式的形式。在标准的 RNN 中,这个重复的模块只有一个非常简单的结构,例如一个 tanh 层。
    在这里插入图片描述

    LSTM 同样是这样的结构,但是重复的模块拥有一个不同的结构。不同于 单一神经网络层,整体上除了h在随时间流动,细胞状态c也在随时间流动,细胞状态c就代表着长期记忆。
    在这里插入图片描述
    不必担心这里的细节。我们会一步一步地剖析 LSTM 解析图。现在,我们先来熟悉一下图中使用的各种元素的图标。
    在这里插入图片描述

    • 黄色的矩形是学习得到的神经网络层
    • 粉色的圆形表示一些运算操作,诸如加法乘法
    • 黑色的单箭头表示向量的传输
    • 两个箭头合成一个表示向量的连接
    • 一个箭头分开表示向量的复制

    LSTM 的核心思想

    LSTM 的关键就是细胞状态,水平线在图上方贯穿运行。

    细胞状态类似于传送带。直接在整个链上运行,只有一些少量的线性交互。信息在上面流传保持不变会很容易。
    在这里插入图片描述
    LSTM 有通过精心设计的称作为“门”的结构来去除或者增加信息到细胞状态的能力。门是一种让信息选择式通过的方法。他们包含一个 sigmoid 神经网络层和一个 pointwise 乘法操作。
    在这里插入图片描述

    Sigmoid 层输出 0 到 1 之间的数值,描述每个部分有多少量可以通过。0 代表“不许任何量通过”,1 就指“允许任意量通过”!

    LSTM 拥有三个门,来保护和控制细胞状态。

    逐步理解 LSTM

    在我们 LSTM 中的第一步是决定我们会从细胞状态中丢弃什么信息。这个决定通过一个称为遗忘门完成。该门会读取ht1h_{t-1}xtx_t,输出一个在 0 到 1 之间的数值给每个在细胞状态Ct1C_{t-1}中的数字。1 表示“完全保留”,0 表示“完全舍弃”。

    让我们回到语言模型的例子中来基于已经看到的预测下一个词。在这个问题中,细胞状态可能包含当前主语的性别,因此正确的代词可以被选择出来。当我们看到新的主语,我们希望忘记旧的主语。
    在这里插入图片描述
    这里可以抛出两个问题:这个门怎么做到“遗忘“的呢?怎么理解?既然是遗忘旧的内容,为什么这个门还要接收新的xtx_{t}?
    对于第一个问题,“遗忘“可以理解为“之前的内容记住多少“,其精髓在于只能输出(0,1)小数的sigmoid函数和粉色圆圈的乘法,LSTM网络经过学习决定让网络记住以前百分之多少的内容。对于第二个问题就更好理解,决定记住什么遗忘什么,其中新的输入肯定要产生影响。

    下一步是确定什么样的新信息被存放在细胞状态中。这里包含两个部分。第一,sigmoid 层称 “输入门层” 决定什么值我们将要更新。然后,一个 tanh 层创建一个新的候选值向量,C~t\tilde{C}_t,会被加入到状态中。下一步,我们会讲这两个信息来产生对状态的更新。

    在我们语言模型的例子中,我们希望增加新的主语的性别到细胞状态中,来替代旧的需要忘记的主语。
    这里写图片描述
    现在是更新旧细胞状态的时间了,Ct1C_{t-1} 更新为 CtC_t。前面的步骤已经决定了将会做什么,我们现在就是实际去完成。

    我们把旧状态与ftf_t相乘,丢弃掉我们确定需要丢弃的信息。接着加上itC~ti_t * \tilde{C}_t。这就是新的候选值,根据我们决定更新每个状态的程度进行变化。

    有了上面的理解基础输入门,输入门理解起来就简单多了。sigmoid函数选择更新内容,tanh函数创建更新候选。
    这里写图片描述
    最终,我们需要确定输出什么值。这个输出将会基于我们的细胞状态,但是也是一个过滤后的版本。首先,我们运行一个 sigmoid 层来确定细胞状态的哪个部分将输出出去。接着,我们把细胞状态通过 tanh 进行处理(得到一个在 -1 到 1 之间的值)并将它和 sigmoid 门的输出相乘,最终我们仅仅会输出我们确定输出的那部分。
    这里写图片描述

    这三个门虽然功能上不同,但在执行任务的操作上是相同的。他们都是使用sigmoid函数作为选择工具,tanh函数作为变换工具,这两个函数结合起来实现三个门的功能。

    LSTM 的变体

    我们到目前为止都还在介绍正常的 LSTM。但是不是所有的 LSTM 都长成一个样子的。实际上,几乎所有包含 LSTM 的论文都采用了微小的变体。差异非常小,但是也值得拿出来讲一下。

    其中一个流行的 LSTM 变体,就是由 Gers & Schmidhuber (2000) 提出的,增加了 “peephole connection”。是说,我们让 门层 也会接受细胞状态的输入。
    在这里插入图片描述
    上面的图例中,我们增加了 peephole 到每个门上,但是许多论文会加入部分的 peephole 而非所有都加。

    另一个变体是通过使用 coupled 忘记和输入门。不同于之前是分开确定什么忘记和需要添加什么新的信息,这里是一同做出决定。我们仅仅会当我们将要输入在当前位置时忘记。我们仅仅输入新的值到那些我们已经忘记旧的信息的那些状态 。

    另一个变体是通过使用 coupled 忘记和输入门。不同于之前是分开确定什么忘记和需要添加什么新的信息,这里是一同做出决定。我们仅仅会当我们将要输入在当前位置时忘记。我们仅仅输入新的值到那些我们已经忘记旧的信息的那些状态 。
    在这里插入图片描述
    另一个改动较大的变体是 Gated Recurrent Unit (GRU),这是由 Cho, et al. (2014) 提出。它将忘记门和输入门合成了一个单一的 更新门。同样还混合了细胞状态和隐藏状态,和其他一些改动。最终的模型比标准的 LSTM 模型要简单,也是非常流行的变体。
    在这里插入图片描述
    这里只是部分流行的 LSTM 变体。当然还有很多其他的,如Yao, et al. (2015) 提出的 Depth Gated RNN。还有用一些完全不同的观点来解决长期依赖的问题,如Koutnik, et al. (2014) 提出的 Clockwork RNN。

    要问哪个变体是最好的?其中的差异性真的重要吗?Greff, et al. (2015) 给出了流行变体的比较,结论是他们基本上是一样的。Jozefowicz, et al. (2015) 则在超过 1 万种 RNN 架构上进行了测试,发现一些架构在某些任务上也取得了比 LSTM 更好的结果。
    在这里插入图片描述

    写在2021-01-13凌晨

    RNN/LSTM是一个伟大的算法,但终究也成为了过去式。目前最强的特征抽取器当属transformer,bert、gpt等预训练模型也是基于transformer。从序列长度问题上,transformer依靠其独特的self-attention解决了RNN长序列梯度消失的问题;从训练方式上,transformer结构上的非序列性也解决了RNN难以并行的问题,可谓从各方面吊打了RNN,称得上革命性的创举。感兴趣的朋友可以移步:深入理解transformer源码

    写在2021-03-11凌晨

    本文撰写时间较早,当时我自己也没想清楚为什么LSTM能缓解梯度消失,现丰富知识储备后专门重新开一篇博文记录这个问题,感兴趣的朋友请移步:为什么LSTM可以缓解梯度消失?

    展开全文
  • 深度学习(十一)RNN入门学习

    千次阅读 2016-01-27 11:04:33
    RNN入门学习 原文地址:http://blog.csdn.net/hjimce/article/details/49095371 作者:hjimce 一、相关理论 RNN(Recurrent Neural Networks)中文名又称之为:循环神经网络(原来还有一个递归神经网络,也叫RNN...
  • 深度学习笔记——RNN(LSTM、GRU、双向RNN)学习总结

    万次阅读 多人点赞 2017-07-23 22:27:47
    本文是关于RNNRNN的变种LSTM、GRU以及BiRN的学习总结。
  • RNN中梯度消失的分析
  • 本文来自于个人博客,本文为深度学习课程笔记,通过流程图,详细介绍了深度学习模型的基本结构,希望对您的学习有所帮助。深度学习的基本步骤:定义模型-->定义损失函数-->找到优化方法课程大纲1、熟悉定义...
  • 深度学习RNN笔记整理

    2019-10-30 11:10:40
    RNN RNN概述 1、基础的RNN网络结构。 2、反向传播。 3、word2Vec理论 以及 代码。 4、双向RNN. 5、LSTM 为什么又BP神经网络、CNN,还需要RNN BP神经网络和CNN的输入输出都是相互独立的,在实际应用中有些场景之间的...
  • 深度学习RNN实现股票预测实战(附数据、代码)

    万次阅读 多人点赞 2017-12-23 17:01:02
    背景知识最近再看一些量化交易相关的材料,偶然在网上看到了一个关于用RNN实现股票预测的文章,出于好奇心把文章中介绍的代码在本地跑了一遍,发现可以work。于是就花了两个晚上的时间学习了下代码,顺便把核心的...
  • RNN LSTM与GRU深度学习模型学习笔记

    万次阅读 2017-01-09 20:39:48
    RNN(Recurrent Neural Network), LSTM(Long Short-Term Memory)与GRU(Gated Recurrent Unit)都是自然语言处理领域常见的深度学习模型。本文是一个关于这些模型的笔记,依次简单介绍了RNN, LSTM和GRU。在学习了大量的...
  • Rmsprop:权值更新算法: https://blog.csdn.net/tsq292978891/article/details/78619384 深度学习笔记——RNN(LSTM、GRU、双向RNN)学习总结: ... 深度学习——RNN(2)双向RNN深度RNN几种变种: htt...
  • 深度学习之三:RNN

    千次阅读 2016-05-06 20:45:22
    RNN,也就是Recurrent Neural Network,循环神经网络,是非线性动态系统,将序列映射到序列,主要参数有五个:[Whv, Whh, Woh, bh, bo, h0] ,典型的结构图如下: 解释一下上图: 和普通神经网络一样,RNN...
  • dl-rnn 循环深度学习教程和示例:RNN、GRU、LSTM 所有 ipython notebook 都可以在 dl-machine AWS 实例上运行: :
  • 大白话循环神经网络RNN-从此爱上RNN 李虎,联想集团PCSD业务UDS平...
  • 深度学习RNN

    千次阅读 2018-09-02 23:51:26
    与前馈神经网络不同之处在于,RNN可以使用其内部状态(记忆)来处理输入序列,递归/周期性的recurrent是指其每一个节点都执行相同的任务,但是输出依赖于输入和记忆,RNN适用于连续的手写识别、语音识别、机器翻译等...
  • 深度学习---RNN网络细节视频教程,希望能够学习者提供帮助,实现对RNN网络细节基础知识的掌握与理解,为后续学习做好铺垫,实现RNN网络细节知识的灵活运用
  • 上期我们一起学习了如何训练RNN并预测时序信号,深度学习算法(第19期)----RNN如何训练并预测时序信号?今天我们一起简单学习下创意RNN和深度RNN的实现。1. 创意RNN经过上期...
  • 深度学习RNN模型》

    2020-10-07 01:17:18
    今天我们学习另外一种网络结构RNN,它具有什么特征结构呢?我们来一起学习下,只能说网络种类太多了。 一:RNN的介绍 之前我们学习的BP,CNN都具有一个共同的特点就是,每时每刻的输出都是且仅仅与当
  • 深度学习:循环神经网络RNN

    千次阅读 2017-09-01 19:49:34
    循环神经网络(recurrent neural network,RNN)是一种具有反馈结构的神经网络,其输出不但与当前输入和网络的权值有关,而且也与之前网络的输入有关;RNN通过添加跨越时间点的自连接隐藏层,对时间进行建模;...
  • RNN:深度学习噪声抑制(初体验)

    千次阅读 2019-05-10 17:45:34
    1.RNNoise: Learning Noise Suppression(深度学习噪声抑制) 2.rnnoise 训练步骤 1.项目地址:https://github.com/xiph/rnnoise RNNoise is a noise suppression library based on a recurrent neural network To ...
  • 深度学习:循环神经网络RNN的变体

    千次阅读 2017-10-16 09:09:52
    双向RNN:BRNN模型(Bidirectional RNN) BRNN(Bi-directional RNN)由 Schuster 在"Bidirectional recurrent neural networks, 1997"中提出,是单向 RNN 的一种扩展形式。普通 R...
  • 深度学习入门课程--- RNN网络结构视频教程,希望能够学习者提供帮助,实现对RNN网络结构基础知识的掌握与理解,为后续学习做好铺垫,实现RNN网络结构的灵活运用
  • 深度学习——CNN+RNN

    2020-06-30 12:08:03
    文章目录CNN,RNN图片标注视频行为识别图片/视频问答 ...CNN高级100+深度,RNN深度有限 组合后 大量信息同时具有时间空间特性:视频,图文结合,真实的场景对话 带有图像的对话,文本表达更具体 视频

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 41,553
精华内容 16,621
关键字:

rnn深度学习