最近在看RNN模型,为简单起见,本篇就以简单的二进制序列作为训练数据,而不实现具体的论文仿真,主要目的是理解RNN的原理和如何在TensorFlow中构造一个简单基础的模型架构。其中代码参考了这篇博客。
数据集
首先我们看一下实验数据的构造:
输入数据X:在时间t,Xt的值有50%的概率为1,50%的概率为0;
输出数据Y:在实践t,Yt的值有50%的概率为1,50%的概率为0,除此之外,如果`Xt-3 == 1`,Yt为1的概率增加50%, 如果`Xt-8 == 1`,则Yt为1的概率减少25%, 如果上述两个条件同时满足,则Yt为1的概率为75%。
可知,Y与X有两个依赖关系,一个是t-3,一个是t-8。我们实验的目的就是检验RNN能否捕捉到Y与X之间的这两个依赖关系。实验使用交叉熵作为评价标准,则有下面三条理想的实验结果:
- 如果RNN没有学习到任何一条依赖,那么Yt为1的概率就是0.625(0.5+0.5*0.5-0.5*0.25),所以所获得的交叉熵应该是0.66(
-(0.625 * np.log(0.625) + 0.375 * np.log(0.375))
)。 - 如果RNN学习到第一条依赖关系,即Xt-3为1时Yt一定为1。那么,所以最终的交叉熵应该是0.52(
-0.5 * (0.875 * np.log(0.875) + 0.125 * np.log(0.125)) -0.5 * (0.625 * np.log(0.625) + 0.375 * np.log(0.375))
)。 - 如果RNN学习到了两条依赖, 那么有0.25的概率全对,0.5的概率正确率是75%,还有0.25的概率正确率是0.5。所以其交叉熵为0.45(
-0.50 * (0.75 * np.log(0.75) + 0.25 * np.log(0.25)) - 0.25 * (2 * 0.50 * np.log (0.50)) - 0.25 * (0)
)。
数据预处理
这部分主要是生成实验数据,并将其按照RNN模型的输入格式进行切分和batch化。代码入下:
1,生成实验数据:
def gen_data(size=100000):
X = np.array(np.random.choice(2, size=(size,)))
Y = []
for i in range(size):
threshold = 0.5
if X[i-3] == 1:
threshold += 0.5
if X[i-8] == 1:
threshold -= 0.25
if np.random.rand() > threshold:
Y.append(0)
else:
Y.append(1)
return X, np.array(Y)
接下来将生成的数据按照模型参数设置进行切分,这里需要用得到的参数主要包括:batch_size和num_steps,分别是批量数据大小和RNN每层rnn_cell循环的次数,也就是下图中Sn中n的大小。代码入下:
def gen_batch(raw_data, batch_size, num_steps):
raw_x, raw_y = raw_data
data_length = len(raw_x)
batch_partition_length = data_length // batch_size
data_x = np.zeros([batch_size, batch_partition_length], dtype=np.int32)
data_y = np.zeros([batch_size, batch_partition_length], dtype=np.int32)
for i in range(batch_size):
data_x[i] = raw_x[batch_partition_length * i:batch_partition_length * (i + 1)]
data_y[i] = raw_y[batch_partition_length * i:batch_partition_length * (i + 1)]
epoch_size = batch_partition_length // num_steps
for i in range(epoch_size):
x = data_x[:, i * num_steps:(i + 1) * num_steps]
y = data_y[:, i * num_steps:(i + 1) * num_steps]
yield (x, y)
def gen_epochs(n, num_steps):
for i in range(n):
yield gen_batch(gen_data(), batch_size, num_steps)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
根据上面的代码我们可以看出来,这里的数据划分并没有将数据完全的按照原先的数据顺序,而是每隔一段取num_steps个数据,这样组成的batch进行训练==这里是为了省事还是另有原因还有待后面学习中考证。
模型构建
RNN的具体原理我们就不再进行赘述,主要是隐层状态和输入连接后计算新的隐层状态和输出。这里用的是单层的RNN。公式和原理图如下所示:
St=tanh(W(Xt @ St−1)+bs)
Pt=softmax(USt+bp)
至于使用TensorFlow构建RNN模型,主要就是定义rnn_cell类型,然后将其复用即可。代码如下所示:
x = tf.placeholder(tf.int32, [batch_size, num_steps], name='input_placeholder')
y = tf.placeholder(tf.int32, [batch_size, num_steps], name='labels_placeholder')
init_state = tf.zeros([batch_size, state_size])
x_one_hot = tf.one_hot(x, num_classes)
rnn_inputs = tf.unstack(x_one_hot, axis=1)
with tf.variable_scope('rnn_cell'):
W = tf.get_variable('W', [num_classes + state_size, state_size])
b = tf.get_variable('b', [state_size], initializer=tf.constant_initializer(0.0))
def rnn_cell(rnn_input, state):
with tf.variable_scope('rnn_cell', reuse=True):
W = tf.get_variable('W', [num_classes + state_size, state_size])
b = tf.get_variable('b', [state_size], initializer=tf.constant_initializer(0.0))
return tf.tanh(tf.matmul(tf.concat([rnn_input, state], 1), W) + b)
state = init_state
rnn_outputs = []
for rnn_input in rnn_inputs:
state = rnn_cell(rnn_input, state)
rnn_outputs.append(state)
final_state = rnn_outputs[-1]
with tf.variable_scope('softmax'):
W = tf.get_variable('W', [state_size, num_classes])
b = tf.get_variable('b', [num_classes], initializer=tf.constant_initializer(0.0))
logits = [tf.matmul(rnn_output, W) + b for rnn_output in rnn_outputs]
predictions = [tf.nn.softmax(logit) for logit in logits]
y_as_list = tf.unstack(y, num=num_steps, axis=1)
https://www.huxiu.com/member/1650919.html
https://www.huxiu.com/member/1650919/n/.html
https://www.huxiu.com/member/1650919/r/.html
https://www.huxiu.com/member/1650919/j/.html
https://www.huxiu.com/member/1650919/c/.html
https://www.huxiu.com/member/1650919/d/.html
https://www.huxiu.com/member/1650919/l/.html
https://www.huxiu.com/member/1650919/k/.html
https://www.huxiu.com/member/1650919/v/.html
https://www.huxiu.com/member/1650919/v/.html
https://www.huxiu.com/member/1650919/v/.html
https://www.huxiu.com/member/1650919/v/.html
https://www.huxiu.com/member/1650919/v/.html
https://www.huxiu.com/member/1650919/v/.html
https://www.huxiu.com/member/1650919/v/.html
https://www.huxiu.com/member/1650919/v/.html
losses = [tf.nn.sparse_softmax_cross_entropy_with_logits(labels=label, logits=logit) for \
logit, label in zip(logits, y_as_list)]
total_loss = tf.reduce_mean(losses)
train_step = tf.train.AdagradOptimizer(learning_rate).minimize(total_loss)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
模型训练
定义好我们的模型之后,接下来就是将数据传入,然后进行训练,代码入下:
def train_network(num_epochs, num_steps, state_size=4, verbose=True):
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
training_losses = []
for idx, epoch in enumerate(gen_epochs(num_epochs, num_steps)):
training_loss = 0
training_state = np.zeros((batch_size, state_size))
if verbose:
print("\nEPOCH", idx)
for step, (X, Y) in enumerate(epoch):
tr_losses, training_loss_, training_state, _ = \
sess.run([losses,
total_loss,
final_state,
train_step],
feed_dict={x:X, y:Y, init_state:training_state})
training_loss += training_loss_
if step % 100 == 0 and step > 0:
if verbose:
print("Average loss at step", step,
"for last 250 steps:", training_loss/100)
training_losses.append(training_loss/100)
training_loss = 0
return training_losses
training_losses = train_network(1,num_steps)
plt.plot(training_losses)
plt.show()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
实验结果如下所示:
![这里写图片描述](https://img-blog.csdn.net/20170427113426369?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGl1Y2hvbmdl/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
从上图可以看出交叉熵最终稳定在0。52,按照我们上面的分析可以知道:RNN模型成功的学习到了第一条依赖关系,因为我们的循环步长选择的是5,所以他只能学习到t-3的第一条依赖关系,而无法学习到t-8的第二条依赖。
接下来可以尝试num_steps==10,区捕捉第二条依赖关系。最终的结果图如下所示:
从上图可以看出,我们的RNN模型成功的学习到了两条依赖关系。最终的交叉熵未定在0.46附近。
几点改进
1,首先上面的代码中,为了尽可能详细的解释TensorFlow中RNN模型的构造方法,将rnn_cell的定义写的很详细,其实这些工作tf已经封装好了,我们只需要一行命令就可以实现,所以第一个要改进的地方就是将rnn_cell的定义和循环使用部分的代码简化:
with tf.variable_scope('rnn_cell'):
W = tf.get_variable('W', [num_classes + state_size, state_size])
b = tf.get_variable('b', [state_size], initializer=tf.constant_initializer(0.0))
def rnn_cell(rnn_input, state):
with tf.variable_scope('rnn_cell', reuse=True):
W = tf.get_variable('W', [num_classes + state_size, state_size])
b = tf.get_variable('b', [state_size], initializer=tf.constant_initializer(0.0))
return tf.tanh(tf.matmul(tf.concat([rnn_input, state], 1), W) + b)
state = init_state
rnn_outputs = []
for rnn_input in rnn_inputs:
state = rnn_cell(rnn_input, state)
rnn_outputs.append(state)
final_state = rnn_outputs[-1]
cell = tf.contrib.rnn.BasicRNNCell(state_size)
rnn_outputs, final_state = tf.contrib.rnn.static_rnn(cell, rnn_inputs, initial_state=init_state)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
2,使用动态rnn模型,上面的模型中,我们将输入表示成列表的形式,即rnn_inputs是一个长度为num_steps的列表,其中每个元素是[batch_size, features]的tensor(即每个rnn_cell要处理的数据),这样做事比较麻烦的,我们还可以使用tf提供的dynamic_rnn函数,这样做不仅会使编程更加简单,还可以提高计算效率。使用dynamic_rnn 时,我们直接将输入表示成[batch_size, num_steps, features]的三维Tensor即可。最终的动态RNN模型代码如下所示:
x = tf.placeholder(tf.int32, [batch_size, num_steps], name='input_placeholder')
y = tf.placeholder(tf.int32, [batch_size, num_steps], name='labels_placeholder')
init_state = tf.zeros([batch_size, state_size])
rnn_inputs = tf.one_hot(x, num_classes)
#注意这里去掉了这行代码,因为我们不需要将其表示成列表的形式在使用循环去做。
#rnn_inputs = tf.unstack(x_one_hot, axis=1)
cell = tf.contrib.rnn.BasicRNNCell(state_size)
#使用dynamic_rnn函数,动态构建RNN模型
rnn_outputs, final_state = tf.nn.dynamic_rnn(cell, rnn_inputs, initial_state=init_state)
with tf.variable_scope('softmax'):
W = tf.get_variable('W', [state_size, num_classes])
b = tf.get_variable('b', [num_classes], initializer=tf.constant_initializer(0.0))
logits = tf.reshape(
tf.matmul(tf.reshape(rnn_outputs, [-1, state_size]), W) + b,
[batch_size, num_steps, num_classes])
predictions = tf.nn.softmax(logits)
losses = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
total_loss = tf.reduce_mean(losses)
train_step = tf.train.AdagradOptimizer(learning_rate).minimize(total_loss)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
至此,我们就实现了一个很简单的RNN模型的构造,在这个过程中,我们需要注意的主要有以下三点:
- 如何将数据转化成rnn所能接受的输入格式,需要注意batch_size和num_steps之间的关系。
- 定义rnn_cell,这里使用的是下面这条命令:
cell = tf.contrib.rnn.BasicRNNCell(state_size)
- 定义RNN模型,可以使用下面这两条命令分别静态和动态构建:
rnn_outputs, final_state = tf.contrib.rnn.static_rnn(cell, rnn_inputs, initial_state=init_state)
rnn_outputs, final_state = tf.nn.dynamic_rnn(cell, rnn_inputs, initial_state=init_state)