BiLSTM+CRF (一)双向RNN 浅谈

版权声明:本文为博主jmh原创文章,未经博主允许不得转载。 https://blog.csdn.net/jmh1996/article/details/83476061

引言

为什么要使用双向的RNN? 一般的按序列顺序过来的RNN会记录、保存来自前面序列的信息,这些历史信息对当前的输出是很有帮助的。但是有些问题,序列当前位置历史信息和这个位置未来的信息会共同对计算当前位置的输出有帮助,例如在NLP里面的人名识别里面, 如果我们很确信下一个字符是人名的开始,那么当前位置再是人名的开始的概率就会相当的低。

于是,我们就可以就想啊,能不能搞个子双向的RNN来,让模型当前的输出即能使用历史消息又能使用未来的信息。

显然 这是可以的哇,O(∩_∩)O哈

而且Tensorflow里面就提供了这样的接口。

试想一下,如果是我们来实现Bi-RNN,我们应该怎么做呢?既然是Bi-RNN,那么这个东西对于输入 s i s_{i} 的输出部分就应该有两部分:1. 从前往后到 s i s_{i} 的输出 F i F_{i} 2. 从后往前到 s i s_{i} 的输出 B i B_{i}

假设我们手上已经设计好了单向的Single-RNN。一种可行的思路就是,既然我们的已经设计好了单向的RNN,那么给定一个序列 S = [ s 1 , s 2 , s 3 , , s n ] S=[s_{1},s_{2},s_{3},\dots,s_{n}] ,调用Single-RNN就可以从 s 1 s_{1} 依次往后传,从前向后得到各个 s i s_{i} 上的输出 F i F_{i}

那么从后向前的怎么处理呢?

很简单,哈哈哈,我们把 S S 做一个转置得到 S T = [ s n , s n 1 , , s 2 , s 1 ] S^{T}=[{s_{n},s_{n-1},\dots,s_{2},s_{1}}] 。如果我们对这个转置跑一波Single-RNN是不是就得到从后往前的各个输出了呢?此时的第一个输出对应 s n s_{n} 的输出,第二输出对应 S n 1 S_{n-1} 的输出。第i个输出对应的输入是 s n i s_{n-i} 。如果再对这些输出做一个转置,这个输出的第i个分量刚好就是从后往前处理到 s i s_{i} 的输出 B i B_{i} 了。
此时对于输入的任意一个 s i s_{i} ,我们都拿到了它的前向过来的输出 F i F_{i} 和后向过来的输出 B i B_{i}

这样一拍脑袋想感觉好像是可行的哇;不过,我们先来欣赏一些Tensorflow是如何实现Bi-RNN的。

欣赏Tensorflow的Bi-RNN实现

def bidirectional_dynamic_rnn(cell_fw, cell_bw, inputs, sequence_length=None,
                              initial_state_fw=None, initial_state_bw=None,
                              dtype=None, parallel_iterations=None,
                              swap_memory=False, time_major=False, scope=None):
  if not _like_rnncell(cell_fw):
    raise TypeError("cell_fw must be an instance of RNNCell")
  if not _like_rnncell(cell_bw):
    raise TypeError("cell_bw must be an instance of RNNCell")

  with vs.variable_scope(scope or "bidirectional_rnn"):
    # Forward direction
    with vs.variable_scope("fw") as fw_scope:
      output_fw, output_state_fw = dynamic_rnn(
          cell=cell_fw, inputs=inputs, sequence_length=sequence_length,
          initial_state=initial_state_fw, dtype=dtype,
          parallel_iterations=parallel_iterations, swap_memory=swap_memory,
          time_major=time_major, scope=fw_scope)

    # Backward direction
    if not time_major:
      time_dim = 1
      batch_dim = 0
    else:
      time_dim = 0
      batch_dim = 1

    def _reverse(input_, seq_lengths, seq_dim, batch_dim):
      if seq_lengths is not None:
        return array_ops.reverse_sequence(
            input=input_, seq_lengths=seq_lengths,
            seq_dim=seq_dim, batch_dim=batch_dim)
      else:
        return array_ops.reverse(input_, axis=[seq_dim])

    with vs.variable_scope("bw") as bw_scope:
      inputs_reverse = _reverse(
          inputs, seq_lengths=sequence_length,
          seq_dim=time_dim, batch_dim=batch_dim)
      tmp, output_state_bw = dynamic_rnn(
          cell=cell_bw, inputs=inputs_reverse, sequence_length=sequence_length,
          initial_state=initial_state_bw, dtype=dtype,
          parallel_iterations=parallel_iterations, swap_memory=swap_memory,
          time_major=time_major, scope=bw_scope)

  output_bw = _reverse(
      tmp, seq_lengths=sequence_length,
      seq_dim=time_dim, batch_dim=batch_dim)

  outputs = (output_fw, output_bw)
  output_states = (output_state_fw, output_state_bw)

  return (outputs, output_states)

ok,Tensorflow里面的实现就是我们刚刚拍脑袋想的那样子。
首先是对输入数据inputs,调用dynamic_rnn从前往后跑一波,得到output_fw,output_state,fw。

with vs.variable_scope(scope or "bidirectional_rnn"):
    # Forward direction
    with vs.variable_scope("fw") as fw_scope:
      output_fw, output_state_fw = dynamic_rnn(
          cell=cell_fw, inputs=inputs, sequence_length=sequence_length,
          initial_state=initial_state_fw, dtype=dtype,
          parallel_iterations=parallel_iterations, swap_memory=swap_memory,
          time_major=time_major, scope=fw_scope)

然后想办法得到从后往前的输出。
先定义一个局部函数:

def _reverse(input_, seq_lengths, seq_dim, batch_dim):
      if seq_lengths is not None:
        return array_ops.reverse_sequence(
            input=input_, seq_lengths=seq_lengths,
            seq_dim=seq_dim, batch_dim=batch_dim)
      else:
        return array_ops.reverse(input_, axis=[seq_dim])

把输入的input_ 按照长度为seq_lengths 调用array_ops.rerverse_sequence 做一次转置。

之后先把inputs转置成inputs_reverse,然后对这个inputs_reverse跑一波dynamic_rnn得到tmp,output_state_bw.

   with vs.variable_scope("bw") as bw_scope:
      inputs_reverse = _reverse(
          inputs, seq_lengths=sequence_length,
          seq_dim=time_dim, batch_dim=batch_dim)
      tmp, output_state_bw = dynamic_rnn(
          cell=cell_bw, inputs=inputs_reverse, sequence_length=sequence_length,
          initial_state=initial_state_bw, dtype=dtype,
          parallel_iterations=parallel_iterations, swap_memory=swap_memory,
          time_major=time_major, scope=bw_scope)

再把这个输出tmp反转一下得到Output_bw向量。

  output_bw = _reverse(
      tmp, seq_lengths=sequence_length,
      seq_dim=time_dim, batch_dim=batch_dim)

然后把output_fw和output_bw堆叠在一起得到bi-rnn的输出:

  outputs = (output_fw, output_bw)

把隐藏层状态output_state_fw和output_bw堆叠在一起得到bi-rnn的隐藏层状态:

  output_states = (output_state_fw, output_state_bw)

可能有人会有疑问,为啥不把output_state_bw转置一下嘞??其实dynamic_rnn只输出状态的最后一个,

使用biredictional_dynamic_rnn

拿之前的minist手写体代码简单的改一波试一试
源博文:https://blog.csdn.net/jmh1996/article/details/78821216
就简单的把RNN函数的几行改掉就行。

#coding:utf-8
__author__ = 'jmh081701'
import  tensorflow as tf
from  tensorflow.examples.tutorials.mnist import  input_data
from tensorflow.contrib import  rnn

mnist=input_data.read_data_sets("./minist/data",one_hot=True)

train_rate=0.001
train_step=10000
batch_size=1280
display_step=100

frame_size=28
sequence_length=28
hidden_num=100
n_classes=10

#定义输入,输出
x=tf.placeholder(dtype=tf.float32,shape=[None,sequence_length*frame_size],name="inputx")
y=tf.placeholder(dtype=tf.float32,shape=[None,n_classes],name="expected_y")
#定义权值
weights=tf.Variable(tf.truncated_normal(shape=[hidden_num,n_classes]))
bias=tf.Variable(tf.zeros(shape=[n_classes]))

def RNN(x,weights,bias):
    x=tf.reshape(x,shape=[-1,sequence_length,frame_size])
    rnn_cell_fw=tf.nn.rnn_cell.BasicRNNCell(hidden_num)
    #前向RNN
    rnn_cell_bw=tf.nn.rnn_cell.BasicRNNCell(hidden_num)
    #后向RNN
    # 其实这是一个双向深度RNN网络,对于每一个长度为n的序列[x1,x2,x3,...,xn]的每一个xi,都会在深度方向跑一遍RNN,跑上hidden_num个隐层单元
    output,states=tf.nn.bidirectional_dynamic_rnn(rnn_cell_fw,rnn_cell_bw,x,dtype=tf.float32)
    #注意output有两部分:output_fw和output_bw.
    #我们需要把这两部结合起来,结合的方法可以是把它们堆叠起来;当然也可以加起来···,在这里,我就直接让他们相加了,这方式不是很合理,但是可以反映出来要注意输出有两部分即可。
    return tf.nn.softmax(tf.matmul(output[0][:,-1,:]+output[1][:,-1,:],weights)+bias,1)
predy=RNN(x,weights,bias)
cost=tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=predy,labels=y))
train=tf.train.AdamOptimizer(train_rate).minimize(cost)

correct_pred=tf.equal(tf.argmax(predy,1),tf.argmax(y,1))
accuracy=tf.reduce_mean(tf.to_float(correct_pred))

sess=tf.Session()
sess.run(tf.initialize_all_variables())
step=1
testx,testy=mnist.test.next_batch(batch_size)
while step<train_step:
    batch_x,batch_y=mnist.train.next_batch(batch_size)
#    batch_x=tf.reshape(batch_x,shape=[batch_size,sequence_length,frame_size])
    _loss,__=sess.run([cost,train],feed_dict={x:batch_x,y:batch_y})
    if step % display_step ==0:

        acc,loss=sess.run([accuracy,cost],feed_dict={x:testx,y:testy})
        print(step,acc,loss)

    step+=1

训练效果

100 0.740625 1.7244365
200 0.8148438 1.6486847
300 0.83671874 1.6269295
400 0.8484375 1.6133264
500 0.85078126 1.6091878
600 0.84453124 1.6150846
700 0.8570312 1.6035169
800 0.8570312 1.6019013
900 0.865625 1.5946525
1000 0.865625 1.5940073
1100 0.86796874 1.5910122
1200 0.871875 1.5884101
1300 0.875 1.5867693
1400 0.875 1.5838099
1500 0.8703125 1.5896256
1600 0.96171874 1.5016277
1700 0.9640625 1.5011679
1800 0.9671875 1.4953251
1900 0.975 1.4906135
2000 0.9710938 1.4919841
2100 0.978125 1.4868753
2200 0.97265625 1.4891269
2300 0.97578126 1.4865962
2400 0.9734375 1.4897311
2500 0.9734375 1.489102
2600 0.97734374 1.4861523
2700 0.9765625 1.4852589
2800 0.97578126 1.4861305
2900 0.97578126 1.4850746
3000 0.9742187 1.4873846
3100 0.98046875 1.4815388

不咋地这是因为,我们只是简单的把fw和bw的输出加和在一起,正确的做法是把fw,bw拼成一个长向量,然后输给下一层去处理。

多个方向上的rnn

既然双向RNN可以考虑从前向后和从后向前的信息,那么如果是个二维的图像怎么搞呢?此时可以跑四个RNN。从左向右一个,从右向左一个,从上往下一个,从下往上一个。方法就是类似于tensorflow实现bi-dynamic-rnn那样。

猜你喜欢

转载自blog.csdn.net/jmh1996/article/details/83476061