zoukankan      html  css  js  c++  java
  • 论文阅读笔记: Natural Language Inference over Interaction Space

    这篇文章提出了DIIN(DENSELY INTERACTIVE INFERENCE NETWORK)模型. 是解决NLI(NATURAL LANGUAGE INFERENCE)问题的很好的一种方法.

    模型结构

    首先, 论文提出了IIN(Interactive Inference Network)网络结构的组成, 是一种五层的结构, 每层的结构有其固定的作用, 但是每层的实现可以使用任意能达到目的的子模型. 整体的结构如下图:

    模型结构从上到下依次为:

    1. Embedding Layer: 常见的对word进行向量化的方法, 如word2vec, GloVe, fasttext等方法. 此外文章中还使用了两种方法对char进行编码, 对每个word中的char进行编码, 并拼接上char的特征, 组合成一个向量, 拼接在word embedding vector上. 这样就包含了更多的信息. 具体的方法在后文讲到.
    2. Encoding Layer: 对句子进行编码, 可以用多种模型进行编码, 然后将他们编码的结果合并起来, 从而获得更多方面的关于句子的信息.
    3. Interaction Layer: 在这一层将两个句子的信息进行交互. 可以采用对两个句子中的单词word-by-word的相互作用的方法. 相互作用的方法也有很多选择.
    4. Feature Extraction Layer: 从上层得到两个句子相互作用产生的Tensor, 使用一些常见的CNN网络进行处理, 如AlexNet, VGG, Inception, ResNet, DenseNet等. 注意这里使用的是处理图像的2-D的卷积核, 而不是文本常用的1-D卷积核. 使用方法见下文.
    5. Output Layer: 将特征转换为最终结果的一层. 只需要设置好输出类的数量即可.

    DIIN模型结构

    具体的解析DIIN模型每层的结构.

    Embedding Layer

    这里将三种生成word的方法拼接起来: word embedding, character feature, syntactical features.

    1. word embedding

      论文中使用的是通过GloVe预训练好的向量. 而且, 论文中提到在训练时, 要打开word embedding的训练, 跟随着任务一起训练.

    2. character feature

      这里指的是对一个word中的char进行自动的feature.

      首先使用char embedding对每个char进行向量化, 然后对char向量进行1-D的卷积, 最后使用MaxPool得到这一个单词对应的char特征向量.

      使用keras实现如下:

      character_embedding_layer = TimeDistributed(Sequential([
                      Embedding(input_dim=100, output_dim=char_embedding_size, input_length=chars_per_word),
                      Conv1D(filters=char_conv_filters, kernel_size=char_conv_kernel_size),
                      GlobalMaxPooling1D()
                  ]), name='CharEmbedding')
      character_embedding_layer.build(input_shape=(None, None, chars_per_word))
      premise_char_embedding    = character_embedding_layer(premise_char_input)
      hypothesis_char_embedding = character_embedding_layer(hypothesis_char_input)
      
    3. syntactical features

      添加这种的目的是为OOV(out-of-vocabulary)的word提供额外补充的信息. 论文中提到的方法有:

      1. part-of-speech(POS), 词性特征, 使用词性的One-Hot特征.
      2. binary exact match(EM)特征, 指的是一个句字中的某个word与另一个句子中对应的word的词干stem和辅助项lemma相同, 则是1, 否则为0. 具体的实现和作用在论文中有另外详细的阐述.

    通过这三种方法, 就得到了premise句子(Pin{mathbb{R}^{p imes{d}}})hypothesis句子(Hin{mathbb{R}^{h imes{d}}})的表示方法, 其中(p)(h)分别表示premise句子和hypothesis句子的长度, (d)表示最终每个单词向量的长度.

    对于对char编码过程中使用到的Conv1D, 两个句子共享同样的参数, 这是毋庸置疑的.

    Encoding Layer

    在这一层中, premisehypothesis会经过一个两层的神经网络, 得到句子中的每一个word将会用一种新的方式表示. 然后将转换过的表示方法传入到一个self-attention layer中. 这种attenion结构在解决NLI问题的模型中经常出现, 目的是考虑word的顺序和上下文信息. 以premise为例:

    假设(hat{P}in{mathbb{R}^{p imes{d}}})是经过转换后的premise句子, (hat{P}_i)是时序(i)位置上的word新的向量. 同理, (hat{H}in{mathbb{R}^{h imes{d}}})是转换后的新的hypothesis句子.

    在转换时, 我们需要考虑当前单词与它的上下文之间的关系, 文中使用的方法是self-attention layer, 具体来说就是每个时间上经过编码后新的向量, 由整个句子中所有位置上的原向量考虑权重地加和产生. 而两个单词向量之间的权值就要借助attention weight来得到了. 以premise句子为例, 整个过程如下:

    1. 对于premise句子的任意两个向量(hat{P}_{i})(hat{P}_{j}), 通过([ extbf{a}; extbf{b}; extbf{a}cdot extbf{b}])的形式组成一个交互的向量. 原向量的长度为(d), 则新向量的长度为(3d). 则长度为(p)的句子经过此步, 就会得到((p,p,3d)).

    2. 使用共享的attention weight( extbf{w}_a)([ extbf{a}; extbf{b}; extbf{a}cdot extbf{b}])进行点乘. ( extbf{w}_a)的是长度为(3d)的向量. 所有的word之间共享这一参数向量. 因此点乘的结果为一个形状为((p,p))的矩阵, 用(A)表示, (A_{ij})则是两个word之间的关系值.

    3. 使用softmax的方法计算权重, 即对于每一行(i), 对应的新的向量为:

      [ar{P}_i=sumlimits_{j=1}^{p}frac{exp(A_{ij})}{sum_{k=1}^{p}exp(A_{kj})}hat{P}_{j} ]

      每个词的新向量都会考虑句子中其他所有的向量.

      以上三步的代码类似于:

      ''' Alpha '''
              # P                                                     # (batch, p, d)
              mid = broadcast_last_axis(P)                            # (batch, p, d, p)
              up = K.permute_dimensions(mid, pattern=(0, 3, 2, 1))    # (batch, p, d, p)
              alphaP = K.concatenate([up, mid, up * mid], axis=2)     # (batch, p, 3d, p)
              A = K.dot(self.w_itr_att, alphaP)                       # (batch, p, p)
      
              ''' Self-attention '''
              # P_itr_attn[i] = sum of for j = 1...p:
              #                           s = sum(for k = 1...p:  e^A[k][j]
              #                           ( e^A[i][j] / s ) * P[j]  --> P[j] is the j-th row, while the first part is a number
              # So P_itr_attn is the weighted sum of P
              # SA is column-wise soft-max applied on A
              # P_itr_attn[i] is the sum of all rows of P scaled by i-th row of SA
              SA = softmax(A, axis=2)        # (batch, p, p)
              itr_attn = K.batch_dot(SA, P)  # (batch, p, d)
      
    4. 然后将新得到的(d)维向量和原本的(d)维向量合并在一起组成(2d)向量, 再传入semantic composite fuse gate(fuse gate), 这种把encoding后的向量和原特征向量拼在一起在传入下一层模型的方法, 如同skip connection(类比于ResNet). fuse gate结构如下:

      [z_i= anh(W_1^T[hat{P}_i;ar{P}_i]+ extbf{b}_1) ]

      [r_i=sigma(W_2^T[hat{P}_i;ar{P}_i]+ extbf{b}_2) ]

      [f_i=sigma(W_3^T[hat{P}_i;ar{P}_i]+ extbf{b}_3) ]

      [ ilde{P}_i= extbf{r}_i cdot hat{P}_i + extbf{f}_i cdot extbf{z}_i ]

      这里的(W_1), (W_2), (W_3)的形状为((2d,d)), (b_1), (b_2), (b_3)为长度为(d)的向量. 都是可训练的参数. (sigma)sigmoid函数.

    需要注意的是, 在这一层中, premisehypothesis两个句子是不共享参数的, 但是为了让两个句子的参数相近, 两个句子在相同位置上的变量, 会对他们之间的差距做L2正则惩罚, 将这种惩罚计入总的loss, 从而在训练过程中, 保证了参数的近似.

    那么对于premisehypothesis两个句子来源一个分布的情况, 是否可以共用一组参数呢? 需要进一步的实验.

    Interaction Layer

    对两个句子的word进行编码之后, 就要考虑两个句子相互作用的问题了. 对于长度为(p)premise和长度为(h)hypothesis, 对于他们的每个单词(i)(j), 将代表它们的向量逐元素点乘, 这样就得到了一个形状为((p, h, d))的两个句子相互作用后的结果. 可以把他们认为是一个2-d的图像, 有d个通道.

    Feature Extraction Layer

    由于两个句子相互作用产生了一个2-d的结果, 因此我们可以通过使用那些平常用在图像上的CNN方法结构, 来提取特征, 例如ResNet效果就很好. 但考虑到模型的效率, 与参数的多少, 论文中使用了DenseNet这种结构. 这种结构的具体论文参见Densely Connected Convolutional Networks. 这一层整体的过程如下:

    1. 上一步得到的结果我们记为(I), 形状为((p, h, d)). 首先使用一个(1 imes{1})的卷积核, 按一定的缩小比例(eta), 将现有的(d)层通道缩小为(floor(d imes{eta})).

    2. 再将得到的结果传入到一个三层结构中, 三层的结构完全相同. 每一层由一对Dense blocktransition block组成. 两种block是串联关系.

      1. DenseNet本身是由n层的(3 imes{3})的卷积层组成, 中间没有池化层. 每层的输出通道数量是一样的, 记为growth rate. 且每层的输出, 都会并上这一层的输入, 作为下一层的输入. 这样也起到了类似于ResNetskip效果. 代码如下:

        def __dense_block(self, x, nb_layers, growth_rate, dropout_rate=None, apply_batch_norm=False):
        	for i in range(nb_layers):
        		cb = self.__conv_block(x, growth_rate, dropout_rate, apply_batch_norm=apply_batch_norm)
        		x = concatenate([x, cb], axis=self.concat_axis)
            return x, K.int_shape(x)[self.concat_axis]
        
      2. transition block这是一层简单的(1 imes{1})的卷积层, 目的是按照一定的比例压缩输出的通道. 这里的压缩比例跟上面的(eta)是不相关的, 记为( heta). 之后再在后面接上一个MaxPool, 考虑的范围是(2 imes{2})大小. 代码如下:

            def __transition_block(self, x, nb_filter, compression, apply_batch_norm):
                if apply_batch_norm:
                    x = BatchNormalization(axis=self.concat_axis, epsilon=1.1e-5)(x)
                x = Conv2D(int(nb_filter * compression), (1, 1), padding='same', activation=None)(x)
                x = MaxPooling2D(strides=(2, 2))(x)
                return x
        

    Output Layer

    结果上面一层就使用2-d的方法得到了两个句子交互的特征. 然后把他们展平, 拼接到输出层的Dense上就可以. Dense的输出维度为类别的数量.

    # Flatten if the shapes are known otherwise apply average pooling
    try:    x = Flatten()(x)
    except: x = GlobalAveragePooling2D()(x)
    
    x = Dense(classes, activation=activation)(x)
    

    论文代码

    在github上有使用keras实现的模型DIIN-in-Keras. 代码使用了自定义Model, Layer, Optimizer的方式实现了这个模型, 形式非常灵活, 值得借鉴学习.

  • 相关阅读:
    R 包安装问题
    特征值分解与奇异值分解
    向量内积&外积
    hdu_3449(有依赖背包)
    Gene co-expression analysis for functional classification and gene–disease predictions
    MCMC & 贝叶斯
    继承(来自视频)
    面向对象 创建对象
    mongodb笔记(三)
    mongodb笔记(二)
  • 原文地址:https://www.cnblogs.com/databingo/p/9311892.html
Copyright © 2011-2022 走看看