LSTM
参考: https://blog.csdn.net/m0_37917271/article/details/82350571
以下图示的每个黄色块都可以视为一层神经元(非一个神经元,因为一个神经元w的shape是[1, 输入维度]), 整个绿色区域也可视为一层神经元
①忘记门
决定了我们应该忘记哪些信息
②记忆门
哪些该记住
③更新门
把老的cell state更新为新的cell state
④输出门
由记忆来决定输出什么门
class LSTM_Cell(object):
def __int__(self):
with tf.variable_scope("input,", initializer=None):
self.ix, self.__init__(input())
'''
参数num_nodes代表了该层神经元的个数,可以随意定义,与输入输出无关
'''
def __init__(self, train_data, train_label, num_nodes=64):
with tf.variable_scope("input", initializer=tf.truncated_normal_initializer(-0.1, 0.1)) as input_layer:
self.ix, self.im, self.ib = self._generate_w_b(
x_weights_size=[config.vocabulary_size, num_nodes],
m_weights_size=[num_nodes, num_nodes],
biases_size=[1, num_nodes])
with tf.variable_scope("memory", initializer=tf.truncated_normal_initializer(-0.1, 0.1)) as update_layer:
self.cx, self.cm, self.cb = self._generate_w_b(
x_weights_size=[config.vocabulary_size, num_nodes],
m_weights_size=[num_nodes, num_nodes],
biases_size=[1, num_nodes])
with tf.variable_scope("forget", initializer=tf.truncated_normal_initializer(-0.1, 0.1)) as forget_layer:
self.fx, self.fm, self.fb = self._generate_w_b(
x_weights_size=[config.vocabulary_size, num_nodes],
m_weights_size=[num_nodes, num_nodes],
biases_size=[1, num_nodes])
with tf.variable_scope("output", initializer=tf.truncated_normal_initializer(-0.1, 0.1)) as output_layer:
self.ox, self.om, self.ob = self._generate_w_b(
x_weights_size=[config.vocabulary_size, num_nodes],
m_weights_size=[num_nodes, num_nodes],
biases_size=[1, num_nodes])
self.w = tf.Variable(tf.truncated_normal([num_nodes, config.vocabulary_size], -0.1, 0.1))
self.b = tf.Variable(tf.zeros([config.vocabulary_size]))
self.saved_output = tf.Variable(tf.zeros([config.batch_size, num_nodes]), trainable=False)
self.saved_state = tf.Variable(tf.zeros([config.batch_size, num_nodes]), trainable=False)
self.train_data = train_data # train_data是个list,元素类型是tensor
self.train_label = train_label
def _generate_w_b(self, x_weights_size, m_weights_size, biases_size):
x_w = tf.get_variable("x_weights", x_weights_size)
m_w = tf.get_variable("m_weigths", m_weights_size)
b = tf.get_variable("biases", config.batch_size, initializer=tf.constant_initializer(0.0))
return x_w, m_w, b
def _run(self, input, output, state):
forget_gate = tf.sigmoid(tf.matmul(input, self.fx) + tf.matmul(output, self.fm) + self.fb)
input_gate = tf.sigmoid(tf.matmul(input, self.ix) + tf.matmul(output, self.im) + self.ib)
update = tf.matmul(input, self.cx) + tf.matmul(output, self.cm) + self.cb
state = state * forget_gate + tf.tanh(update) * input_gate # 这里的*代表矩阵乘法运算
output_gate = tf.sigmoid(tf.matmul(input, self.ox) + tf.matmul(output, self.om) + self.ob)
return output_gate * tf.tanh(state), state
def loss_func(self):
outputs = list()
output = self.saved_output # 保存的是上一组数据的最后一个输出,作用是作为一个新的序列到来时,首次的h值
state = self.saved_state
for i in self.train_data: # train_data是个list,大小为num_unrollings,意味着把同一个神经元(上面_run方法构造了一个lstm神经元)赋值/拉伸了num_unrollings次
output, state = self._run(i, output,
state) # output是每次预测一个字符, output是公式中的h_t(既是标记,又是下一次的输入), state即是公式中的c_t(细胞状态)
outputs.append(output) # 将每次预测的字符拼接到一起,预测len(self.train_data)次,即num_unrollings次
'''
因为不是顺序执行语言,一般模型如果不是相关的语句,其执行是没有先后顺序的,control_dependencies 的作用就是建立先后顺序,保证前面两句被执行后,才执行后面的内容
'''
with tf.control_dependencies([
self.saved_output.assign(output), # saved_output, saved_state:初始的ht和ct(ht该LSTM cell的输出向量,ct该LSTM cell的状态向量)
self.saved_state.assign(state)
]):
# 定义输出
logits = tf.nn.xw_plus_b(tf.concat(outputs, 0), self.w, self.b)
# the label should fix the size of ouputs
loss = tf.reduce_mean(
tf.nn.softmax_cross_entropy_with_logits(
labels=tf.concat(self.train_label, 0),
logits=logits))
train_prediction = tf.nn.softmax(logits)
'''
这里范围outputs是自己添加的,目的是为了调试,查看数据结构
'''
return logits, loss, train_prediction, outputs
1. 该示例里只有一个神经网络层, 即_run方法定义的, num_unrollings的个数意味着把同一个神经元(上面_run方法构造了一个lstm神经元)赋值/拉伸了num_unrollings次
2. 该层中, 神经元的个数为num_nodes
3. 可以把每个图示中的黄色块视为一层神经层运算, 也可以把_run方法封装的运算(即lstm)视为一层神经层
关于lstm梯度消失问题:
在反向传播过程中, 如果乘积大于1,则梯度会随着反向传播层数n的增加而成指数增长,导致梯度爆炸;如果乘积小于1,经过多层传播后,小于1的数累乘后结果趋近于0,导致梯度消失.
关于lstm中的forget gate的理解:
(1)原始的lstm是没有forget gate的,或者说相当于forget gate恒为1,所以不存在梯度消失问题
(2)现在的lstm被引入了forget gate,但是lstm的一个初始化技巧就是将forget gate的bias置为正数(例如1或5,这点可以查看各大框架代码),这样模型刚开始训练时forget gate的值接近于1,不回发生梯度消失
(3)随着训练过程的进行,forget gate就不再恒为1了。不过对于一个已经训练好的模型,需要选择性地记住或者遗忘某些信息,所以forget gate要么是1,要么是0,很少有类似0.5这样的中间值,相当于一个二元的开关。例如在某个序列里,forget gate全为1,那么梯度不会消失;否则,若某一个forget gate是0,这时候虽然会导致梯度消失,但是体现了模型的选择性,刻意遗忘某些信息。
相对于普通rnn, lstm可以缓解梯度消失问题,但不能彻底避免