copy net

输入数据处理
每个batch包含source和target两个矩阵,source矩阵第二维代表输入句子长度,句子长度不同时用0填充到相同的长度,同时在最后加一个全0列,比如:
[
[1,2,0],
[1,0,0]
]
第一个句子的长度为2,第二个句子的长度为1. target矩阵和source一样的处理方法。
target矩阵在求出embeding后的shape为[batch,sentence_len, embeding_zise], 需要在每个句子的开头加上一个起始符号0,同时将句子最后面一个0的emebding删除掉得到的形状还是[batch,sentence_len, embeding_zise]

测试阶段解码时,每一个step的计算方式:
先求出词的概率分布:

p(j) = p[0:MAX_VOC] (生成概率) 串联上 p[source sentence len](copy概率)。

源句子中位置j处的词如果属于[0:MAX_VOC], 则这个词实际概率为:p[s[j]]+p[MAX_VOC+j], 由生成概率和copy概率两部分组成 其中s[j]代表词的ID;然后将p[MAX_VOC+j]设置为0. 其他词的概率不用变。

本文只包含decoder网络

decoder数据准备

识别target句子中的每个词和源句子中哪些位置的词相同,在返回的cc矩阵里标注
返回值 的shape为 [source.shape[0], target.shape[1], source.shape[1]]

  • source.shape[0]:为一个batch里,源句子数;
  • target.shape[1] :每个目标句子的长度
  • source.shape[1]:每个源句子的长度

代码如下:

    def cc_martix(source, target):
        cc = np.zeros((source.shape[0], target.shape[1], source.shape[1]), dtype='float32')
        for k in xrange(source.shape[0]):
            for j in xrange(target.shape[1]):
                for i in xrange(source.shape[1]):
                    if (source[k, i] == target[k, j]) and (source[k, i] > 0):
                        cc[k][j][i] = 1.
        return cc

对source和target用下面这个函数处理一下。将data中大于config[‘voc_size’]的元素都设置为1,其他保持不变。词典按照频率从大到小的顺序排列,所以index越大,频率越小。

    def unk_filter(data):
        if config['voc_size'] == -1:
            return copy.copy(data)
        else:
            mask = (np.less(data, config['voc_size'])).astype(dtype='int32')
            data = copy.copy(data * mask + (1 - mask))
            return data

下面函数的target代表解码时一个batch的输入,这个函数的作用是准备好数据用来解码

    def prepare_xy(self, target, cc_matrix):
        '''
        target:      (nb_samples, index_seq) nb_samples代表每个batch里有多少个句子
        cc_matrix:   (nb_samples, maxlen_t, maxlen_s)目标词和源句子中哪些位置的词相同
        context:     (nb_samples)

        return:
         Y_mask 其实是一个mask,标记target中哪些位置是真实的词(标记为1),哪些位置的值是填充的值(然后标记为0)
         Y的shape (nb_samples, maxlen_t, embedding_dim)
         X: 就是target句子的embeding结果,在每个句子的开头加上了起始符号,也就是全0
         X_mask:在Y_mask的第二维的开始插入一个1,同时去掉第二维最后一个词,形状为[nb_samples, maxlen_t]
         LL :就是CC矩阵,没变化
         XL_mask:形状为[nb_samples, maxlen_t],标记目标句子中的词是否和源句子中的某个词相同
         Count:记录了每个句子的长度,shape为[nb_samples,1]
        '''
        Y,  Y_mask  = self.Embed(target, True)
        #Y[:, :-1, :] 这里面-1表示不包含数组的最后一个元素

        #在Y的第二维的第一个位置插入一个全0,同时去掉第二维最后一个词,得到形状为(nb_samples, maxlen_t, embedding_dim)
        X           = T.concatenate([alloc_zeros_matrix(Y.shape[0], 1, Y.shape[2]), Y[:, :-1, :]], axis=1)

        LL = cc_matrix

        #T.gt(a,b)将a里值大于b的位置标记为true,其他的位置标记为false
        #XL_mask的形状为[nb_samples, maxlen_t],得到目标句子中的每个词是否和源句子中的某个词相同
        XL_mask     = T.cast(T.gt(T.sum(LL, axis=2), 0), dtype='float32')

        if not self.config['use_input']:
            X *= 0

        #在Y_mask的第二维的开始插入一个1,同时去掉第二维最后一个词,形状为[nb_samples, maxlen_t]
        X_mask    = T.concatenate([T.ones((Y.shape[0], 1)), Y_mask[:, :-1]], axis=1)
        #Count里记录了每个句子的长度[nb_samples,1]
        Count     = T.cast(T.sum(X_mask, axis=1), dtype=theano.config.floatX)
        return X, X_mask, LL, XL_mask, Y_mask, Count

decoder attention

计算一个step的attention权重,返回shape 为(nb_samples, maxlen_s), 其中nb_samples就是batch size。
这里用到了coverage机制 可以参考 Get To The Point: Summarization with Pointer-Generator Networks 一文中的Coverage mechanism。大体的意思是:在解码step t 时,对之前每个解码step的attention权重求和。

    def __call__(self, X, S,
                 Smask=None,
                 return_log=False,
                 Cov=None):
        assert X.ndim + 1 == S.ndim, 'source should be one more dimension than target.'
        # X is the key:    (nb_samples, x_dim) 解码时一个step的hidden state
        # S (nb_samples, maxlen_s, ctx_dim) encoder里每一个step的hidden state
        # Cov is the coverage vector (nb_samples, maxlen_s)
        # (nb_samples, source_num, hidden_dims)
        Eng   = dot(X[:, None, :], self.Wa) + dot(S, self.Ua)  
        Eng   = self.tanh(Eng)
        # location aware:
        if self.coverage:
            # (nb_samples, source_num, hidden_dims)
            Eng += dot(Cov[:, :, None], self.Ca)  

        #(nb_samples, source_num, hidden_dims) * (hidden_dims*1) 得到(nb_samples, source_num, 1)
        Eng   = dot(Eng, self.va) 
        Eng   = Eng[:, :, 0]                      # 降维为 (nb_samples, source_num)

        if Smask is not None:
            # I want to use mask!
            EngSum = logSumExp(Eng, axis=1, mask=Smask)
            if return_log:
                return (Eng - EngSum) * Smask
            else:
                return T.exp(Eng - EngSum) * Smask
        else:
            if return_log:
                return T.log(self.softmax(Eng))
            else:
                return self.softmax(Eng)

计算decoder

    def build_decoder(self,
                      target,
                      cc_matrix,
                      context,
                      c_mask,
                      return_count=False,
                      train=True):
        """
        Build the Computational Graph ::> Context is essential
        c_mask :二维数组[nb_samples, max_len_s]
        context 保存encoder里每一步的hidden state
        """

        # context: (nb_samples, max_len, contxt_dim),输入到一个全连接层,改变最后一维的长度,得到(h_j * W_c)
        #后面用来计算update
        context_A = self.Is(context)  # (nb_samples, max_len, embed_dim)
        X, X_mask, LL, XL_mask, Y_mask, Count = self.prepare_xy(target, cc_matrix)

        # input drop-out if any.
        if self.dropout > 0:
            X     = self.D(X, train=train)

        # Initial state of RNN 第二维第一个位置代表句子最后一个词的隐藏状态?
        Init_h   = self.Initializer(context[:, 0, :])  # default order ->
        Init_a   = T.zeros((context.shape[0], context.shape[1]), dtype='float32')
        coverage = T.zeros((context.shape[0], context.shape[1]), dtype='float32')

        X        = X.dimshuffle((1, 0, 2))
        X_mask   = X_mask.dimshuffle((1, 0))
        LL       = LL.dimshuffle((1, 0, 2))            # (maxlen_t, nb_samples, maxlen_s) maxlen_t锛歮ax target size
        XL_mask  = XL_mask.dimshuffle((1, 0))          # (maxlen_t, nb_samples)

        def _recurrence(x, x_mask, ll, xl_mask, prev_h, prev_a, cov, cc, cm, ca):
            """
            x:      (nb_samples, embed_dims)
            x_mask: (nb_samples, )
            ll:     (nb_samples, maxlen_s)
            xl_mask:(nb_samples, )
            -----------------------------------------
            prev_h: (nb_samples, hidden_dims)
            prev_a: (nb_samples, maxlen_s)
            cov:    (nb_samples, maxlen_s)  *** coverage ***
            -----------------------------------------
            cc:     (nb_samples, maxlen_s, cxt_dim)
            cm:     c_mask (nb_samples, maxlen_s)
            ca:     (nb_samples, maxlen_s, ebd_dim) context_A 用来计算copy时的评分
            """
            #根据上一步的h,计算下一步的c_i
            prob  = self.attention_reader(prev_h, cc, Smask=cm, Cov=cov)

            #更新coverage分布向量
            ncov  = cov + prob
            #c_i 代表上一步的attention vector,会由于这一步的RNN输入
            cxt   = T.sum(cc * prob[:, :, None], axis=1)

            # compute input word embedding (mixed). ca * prev_a[:, :, None]得到的是update
            x_in  = T.concatenate([x, T.sum(ca * prev_a[:, :, None], axis=1)], axis=-1)

            # compute the current hidden states of the RNN.
            x_out = self.RNN(x_in, mask=x_mask, C=cxt, init_h=prev_h, one_step=True)

            # compute the current readout vector.
            r_in  = [x_out]

            # copynet decoding (nb_samples, out_put_dim+context_dim)
            #根据x_out,计算取vocabulary里每个词的概率
            r_out = self.hidden_readout(x_out)  # (nb_samples, voc_size)
            #将r_in最后一维的长度变为和cc最后一维的长度相同,这样的话两者的最后一维上就可以做element-wise乘法了
            key     = self.Os(r_in)  # (nb_samples, cxt_dim) :: key
            #计算key和encoder里每个step的相关性,即得到每个位置的权重
            Eng     = T.sum(key[:, None, :] * cc, axis=-1)

            #下面两步其实相当于求softmax,在后面会具体讲一下
            EngSum  = logSumExp(Eng, axis=-1, mask=cm, c=r_out)
            next_p  = T.concatenate([T.exp(r_out - EngSum), T.exp(Eng - EngSum) * cm], axis=-1)
            #copy模式下的概率. 对于一个target词,只留下源句子中和其相同的位置的概率
            next_c  = next_p[:, self.config['dec_voc_size']:] * ll           # (nb_samples, maxlen_s)
            #生成模式下的概率
            next_b  = next_p[:, :self.config['dec_voc_size']]
            #下面两项计算update值
            sum_a   = T.sum(next_c, axis=1, keepdims=True)                   # (nb_samples,1)
            next_a  = (next_c / (sum_a + err)) * xl_mask[:, None]            # numerically consideration
            return x_out, next_a, ncov, sum_a, next_b

        #代入参数时,顺序为sequences, outputs_info, non_sequences
        outputs, _ = theano.scan(
            _recurrence,
            sequences=[X, X_mask, LL, XL_mask],
            outputs_info=[Init_h, Init_a, coverage, None, None],#None的值不传入函数
            non_sequences=[context, c_mask, context_A] 
        )

        X_out, source_prob, coverages, source_sum, prob_dist = [z.dimshuffle((1, 0, 2)) for z in outputs]
        X        = X.dimshuffle((1, 0, 2))
        X_mask   = X_mask.dimshuffle((1, 0))
        XL_mask  = XL_mask.dimshuffle((1, 0))


        '''
        当词是unk并且这个词在源句中时,在target里是用1代替的,1就代表UNK,同时在XL_mask中标记这个位置的词是和源句中的某个词相同。
        所以下面两行的功能是:只留下target矩阵中非填充的词,且当target词大于了voc即UNK词,且没在原句中出现时,才标记为1。
        得到的shape为[nb_samples, maxlen_t],
        '''
        U_mask   = T.ones_like(target) * (1 - T.eq(target, 1))
        U_mask  += (1 - U_mask) * (1 - XL_mask)

        '''
        概率计算分四种:
        1、当target词属于vocabulary且target词不在原句中时,用生成概率;
        2、当target词属于vocabulary且target词在原句中时,用生成概率加上这个词在x中的copy概率和;
        3、当target词为UNK,且不在源句子中时,用生成UNK的概率
        4、当target词为UNK,且在源句中时,用原句中每个和UNK词相同的位置的概率和

        self._grab_prob(prob_dist, target) * U_mask :计算1、2中的生成概率、3
        source_sum.sum(axis=-1):  source_sum的shape为[nb_samples, maxlen_t, 1]。计算2中的copy概率以及第四条
        '''
        #prob_dist :[nb_samples, maxlen_t, voc_size]
        self._grab_prob(prob_dist, target) * U_mask
        #log_prob : shape (nb_samples,)是一个矩阵
        log_prob = T.sum(T.log(
                self._grab_prob(prob_dist, target) * U_mask +
                source_sum.sum(axis=-1) + err
        ) * X_mask, axis=1)
        #(nb_samples,)每个句子对应的perplex,即每个词的平均概率
        log_ppl  = log_prob / (Count + err)

        if return_count:
            return log_prob, Count
        else:
            return log_prob, log_ppl

The log-sum-exp trick

参考The log-sum-exp trick in Machine Learning
Let’s say we have an n-dimensional vector and want to calculate:
这里写图片描述

if you try to calculate it naively, you quite quickly will encounter underflows or overflows, depending on the scale of xi . Even if you work in log-space, the limited precision of computers is not enough and the result will be INF or -INF. So what can we do?

We can show, that the following equation holds:
这里写图片描述

其中a取x中的最大值,如果用上式右边替代y来计算,就不会出现上面的问题. 这样计算softmax就可以按照下面的方法:
这里写图片描述
这张图就对应上面代码的

            EngSum  = logSumExp(Eng, axis=-1, mask=cm, c=r_out)

            next_p  = T.concatenate([T.exp(r_out - EngSum), T.exp(Eng - EngSum) * cm], axis=-1)
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yiqingyang2012

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值