再谈Seq2Seq和Attention

再谈Seq2Seq和Attention

之前做NLP的时候发过一篇Seq2Seq和Attention的博客,其实他Seq2Seq的代码写的是有些问题的。

真正的Seq2Seq应该怎么写

把我最近做交通流的一个Seq2Seq放上来,输入是[batch,timelags,feature_num]。
转换到NLP里就是[batch,sentence_length,feature_num]。

# 训练模型
# ---层定义---
x = Input(shape=(None, feature_num,))
enc_LSTM1 = CuDNNLSTM(units=base_num, return_sequences=True, return_state=True)
enc_LSTM2 = CuDNNLSTM(units=base_num, return_sequences=True, return_state=True)

y = Input(shape=(None, feature_num,))
dec_LSTM1 = CuDNNLSTM(units=base_num, return_sequences=True, return_state=True)
dec_LSTM2 = CuDNNLSTM(units=base_num, return_sequences=True, return_state=True)

# 输出f/v/s/lf
att_W=Dense(units=base_num,bias_regularizer=l1(0.02))
att_layer=AttLayer()
att_max=Maximum()
outDense = Dense(units=4, bias_regularizer=l1(0.02))

fLambda = Lambda(lambda netOut: netOut[:, :, 0] * params[0][1] + params[0][0])
vLambda = Lambda(lambda netOut: netOut[:, :, 1] * params[1][1] + params[1][0])
sLambda = Lambda(lambda netOut: netOut[:, :, 2] * params[2][1] + params[2][0])
lfLambda = Lambda(lambda netOut: netOut[:, :, 3] * params[3][1] + params[3][0])

# ---encoder---
enc_lstm1, state_h1, state_c1 = enc_LSTM1(x)
encoder_state1 = [state_h1, state_c1]

enc_lstm2, state_h2, state_c2 = enc_LSTM2(enc_lstm1)
encoder_state2 = [state_h2, state_c2]

# ---decoder---
decoder_lstm1, _, _ = dec_LSTM1(y, initial_state=encoder_state1)
decoder_lstm2, _, _ = dec_LSTM2(decoder_lstm1, initial_state=encoder_state2)

att_input=att_W(decoder_lstm2)
decoder_att=att_layer([enc_lstm2,att_input])
decoder_att_res=att_max([decoder_lstm2,decoder_att])
decoder_Dense2 = outDense(decoder_att_res)

f_out = fLambda(decoder_Dense2)
v_out = vLambda(decoder_Dense2)
s_out = sLambda(decoder_Dense2)
lf_out = lfLambda(decoder_Dense2)

model = Model([x, y], [f_out, v_out, s_out, lf_out])
model.compile(loss='mse', optimizer=RMSprop(2e-5))
model.summary()

# 推断模型
# ---encoder---
encoder_model = Model(x, [enc_lstm2] + encoder_state1 + encoder_state2)

# ---decoder---
decInput_enc = Input(shape=(None, base_num,))
decInput_h1 = Input(shape=(base_num,))
decInput_c1 = Input(shape=(base_num,))
decInput_h2 = Input(shape=(base_num,))
decInput_c2 = Input(shape=(base_num,))
decInput_state1 = [decInput_h1, decInput_c1]
decInput_state2 = [decInput_h2, decInput_c2]

dec_lstm1, dec_h1, dec_c1 = dec_LSTM1(y, initial_state=decInput_state1)
dec_lstm2, dec_h2, dec_c2 = dec_LSTM2(dec_lstm1, initial_state=decInput_state2)
dec_state1 = [dec_h1, dec_c1]
dec_state2 = [dec_h2, dec_c2]

att_in=att_W(dec_lstm2)
dec_att=att_layer([decInput_enc,att_in])
dec_att_res=att_max([dec_lstm2,dec_att])
dec_Dense2 = outDense(dec_att_res)

f_out = fLambda(dec_Dense2)
v_out = vLambda(dec_Dense2)
s_out = sLambda(dec_Dense2)
lf_out = lfLambda(dec_Dense2)

decoderModel = Model([y, decInput_enc] + decInput_state1 + decInput_state2,
                     [f_out, v_out, s_out, lf_out] + dec_state1 + dec_state2)
decoderModel.summary()

首先,为什么训练模型和推断模型要分开写,因为训练的时候是可以知道输出序列的真值的,可以用到一个技巧叫teacher forcing,就是直接在decoder的每个时刻输出上一个时刻的真值。而这在推断阶段的decoder是做不到的,所以decoder只能一个一个时刻的跑。两者的运行方式不太一样。

那么我训练了训练模型,怎么传递给推断模型呢。

这就要用到基本的对象的思想了,我们知道对象其实是一个指针,指向内存中的一块区域,区域里有他的成员。所以整个模型钟需要完成训练和推掉模型间交互的,其实只有带权重的几个LSTM和Dense层,没有必要涉及到输入层、Lambda层之类的。因此我们在最开始就把层对象定义好,在训练的时候他内存中的权重就会被改变,之后我们直接在推断模型钟加入这些对象的指针,就完成了权重的传递。

注意真正需要传递的只有带权重的层,比如LSTM层,这点其实是非常有用的。

Attention

Attention其实就是,decoder每个时刻生成输出之后,和encoder的每个输出做点乘,点乘的结果表示他们的相似度,然后按encoder结果与每个时刻decoder结果的相似度,加权得出一个新的输出,和原来的decoder输出结合形成新的输出。

根据相似度的计算方式Attention分很多种,点乘是Luong Attention中的dot方法,不需要涉及权重矩阵,很简便也很高效。包括Attention输出和原输出怎么拼接也是个学问。我做的时候取最大效果挺好的。Multi-head就是加入矩阵求相似度之后,做很多个Attention,表示不同模式下的注意力。

keras的代码这样写:

#---temporal Attention---
class AttLayer(Layer):
    def __init__(self, **kwargs):
        super(AttLayer, self).__init__(**kwargs)

    def build(self, input_shape):
        assert isinstance(input_shape, list)
        super(AttLayer, self).build(input_shape)  # be sure you call this somewhere!

    def call(self, inputs, mask=None):
        input_x,input_y=inputs
        # 列数==行数
        # [m,n]*[n,q]=[m,q]
        x_t = K.permute_dimensions(input_x, (0, 2, 1))
        e = K.batch_dot(input_y, x_t)
        a_exp = K.exp(e)
        a_c = K.repeat(K.sum(a_exp, axis=2), K.shape(input_x)[1])
        a_c = K.permute_dimensions(a_c, (0, 2, 1))
        a = a_exp / a_c
        out = K.batch_dot(a, input_x)
        return out

    def get_output_shape_for(self, input_shape):
        assert isinstance(input_shape, list)
        x_shape,y_shape=input_shape
        return y_shape

class定义keras层,init、build、call是必须的,call是真正的执行起来用到的部分,如果输出形状和输入不一样还要重写get_output_shape_for,还是叫compute_output_shape。一样就不用写,我这里就是输入输出形状一样。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值