仰止

V1

2022/12/16阅读:19主题:默认主题

图文结合-LXMERT

本文介绍一篇图文结合的经典论文,论文发布于2019年,算是最早出来的一批模型

论文信息

论文题目:

LXMERT: Learning Cross-Modality Encoder Representations from Transformers

论文地址:

https://arxiv.org/abs/1908.07490

代码地址:

https://github.com/airsplay/lxmert

主要内容

1、模型结构

LXMERT是典型的双流模型结构,其结构图如下:

(1)input embedding

有word-level sentence embedding和object-level image embedding。

文本的计算如下:

其中i表示位置信息。

图片的计算如下:

其中, 表示region的坐标信息, 表示2048-dim的ROI特征。至于为什么最后需要除以2,论文里并没有说明。猜测可能由于在求图片编码时,前面都使用了LN,最后相加除2是为了保证值域范围与前面相同,并且也能与文本的输出相同。

图片的编码器的实现如下:

class VisualFeatEncoder(nn.Module):
    def __init__(self, config):
        super().__init__()
        feat_dim = VISUAL_CONFIG.visual_feat_dim
        pos_dim = VISUAL_CONFIG.visual_pos_dim

        # Object feature encoding
        self.visn_fc = nn.Linear(feat_dim, config.hidden_size)
        self.visn_layer_norm = BertLayerNorm(config.hidden_size, eps=1e-12)

        # Box position encoding
        self.box_fc = nn.Linear(pos_dim, config.hidden_size)
        self.box_layer_norm = BertLayerNorm(config.hidden_size, eps=1e-12)

        self.dropout = nn.Dropout(config.hidden_dropout_prob)

    def forward(self, visn_input):
        feats, boxes = visn_input

        x = self.visn_fc(feats)
        x = self.visn_layer_norm(x)
        y = self.box_fc(boxes)
        y = self.box_layer_norm(y)
        output = (x + y) / 2

        output = self.dropout(output)
        return output

这里的Object feature表示ROI特征,box pos应该是region的7维坐标特征,论文称之为“bounding box coordinates”。

(2)encoder

作者主要根据两种注意力机制(self-attention & cross attention)构建编码器。LXMERT包含单模态编码器(self-attn)和交叉模态编码器(cross-attn),我们重点介绍下交叉模态编码器。

一个cross-modality encoder包括两个self-attention、两个FFN、以及一个双向cross-attention。其中cross-attention在最前面(至于为什么将cross-attn放在前面,论文并没有说明,应该是根据猜想和实验结果来的,先对两种模态进行交叉,再对交叉后的进行自处理,这种结构可以充分实现不同模态之间的交互),其计算公式为:

为了进一步建立内部连接,在模型设计时,作者将自注意力模块应用于cross-attn的输出,如下所示:

其中k表示网络的层数。

作者将交互层分成了三个部分,文本编码层(l)、视觉编码层(r)和交叉编码层(x)。其中,文本编码层和视觉编码层与bert一致,交叉编码层的实现如下:

class LXRTXLayer(nn.Module):
    def __init__(self, config):
        super().__init__()
        # The cross-attention Layer
        self.visual_attention = BertCrossattLayer(config)

        # Self-attention Layers
        self.lang_self_att = BertSelfattLayer(config)
        self.visn_self_att = BertSelfattLayer(config)

        # Intermediate and Output Layers (FFNs)
        self.lang_inter = BertIntermediate(config)
        self.lang_output = BertOutput(config)
        self.visn_inter = BertIntermediate(config)
        self.visn_output = BertOutput(config)

    def cross_att(self, lang_input, lang_attention_mask, visn_input, visn_attention_mask):
        # Cross Attention
        lang_att_output = self.visual_attention(lang_input, visn_input, ctx_att_mask=visn_attention_mask)
        visn_att_output = self.visual_attention(visn_input, lang_input, ctx_att_mask=lang_attention_mask)
        return lang_att_output, visn_att_output

    def self_att(self, lang_input, lang_attention_mask, visn_input, visn_attention_mask):
        # Self Attention
        lang_att_output = self.lang_self_att(lang_input, lang_attention_mask)
        visn_att_output = self.visn_self_att(visn_input, visn_attention_mask)
        return lang_att_output, visn_att_output

    def output_fc(self, lang_input, visn_input):
        # FC layers
        lang_inter_output = self.lang_inter(lang_input)
        visn_inter_output = self.visn_inter(visn_input)

        # Layer output
        lang_output = self.lang_output(lang_inter_output, lang_input)
        visn_output = self.visn_output(visn_inter_output, visn_input)
        return lang_output, visn_output

    def forward(self, lang_feats, lang_attention_mask,
                      visn_feats, visn_attention_mask):
        lang_att_output = lang_feats
        visn_att_output = visn_feats

        lang_att_output, visn_att_output = self.cross_att(lang_att_output, lang_attention_mask,
                                                          visn_att_output, visn_attention_mask)
        lang_att_output, visn_att_output = self.self_att(lang_att_output, lang_attention_mask,
                                                         visn_att_output, visn_attention_mask)
        lang_output, visn_output = self.output_fc(lang_att_output, visn_att_output)

        return lang_output, visn_output

通过上面一段代码,我们可以知道cross-model的建立主要是cross-attn和self-attn,(上面对cross-attn的命名很奇怪,不知道为什么这样命名,有误导的概率)由于在模型训练时,根据矩阵的计算规则,如 会计算 的所有元素,因此当文本在前时,计算的是每个字和所有视觉特征之间的注意力,反之同理。所以,需要计算两边cross-attn。

同时,还有一个需要注意的是,在计算文本对图片注意力时,mask的是图片,也就是说对于图片的填充部分没有必要算,反之同理。这部分的mask应该是类比文本中的对padding部分的mask,有点疑惑的是,为什么不同时对两者进行mask,猜测可能是文本的空白,其对应的图片不一定空白,反之亦然,所以这样可以更有利于建立图文之间的语义联系,实现语义上的补充。论文里将这一部分称作“双向cross-attn”。

交叉注意力的实现如下:

    def forward(self, hidden_states, context, attention_mask=None):
        mixed_query_layer = self.query(hidden_states)
        mixed_key_layer = self.key(context)
        mixed_value_layer = self.value(context)

        query_layer = self.transpose_for_scores(mixed_query_layer)
        key_layer = self.transpose_for_scores(mixed_key_layer)
        value_layer = self.transpose_for_scores(mixed_value_layer)

可以看到,当target和source不同的时候,对两着进行attention计算,得到的就是两个向量表征的cross-attn。

由于模型在结构上是先经过cross-attn,然后才是各自的self- attn,所以在上面类的代码中也显示的很清楚,先cross-attn的输出作为self-attn的输入。

2、预训练任务

(1)Masked Cross-Modality LM

和BERT的MLM类似的任务,不过在对masked进行预测时(假设文本masked),不仅会使用周围未被mask的文本,还会用到视觉语义信息,对其进行masked,以解决存在的歧义问题,有助于建立视觉信息到语言信息之间的联系。同时,作者使用BERT的参数,对LXMERT进行初始化,发现这一操作会产生负作用,因为BERT仅仅使用了文本信息,使得参数并不带有对应的多模态联系信息,所以最后作者从头开始训练的预训练模型。

(关于这一点,我觉得不能一概而论,记得之前时UNITER还是ViLT做过实验,发现BERT的参数相比于随机化是有所提升的,因为其富含丰富的语义信息,所以,个人认为,能不能提升需要结合具体的数据集和模型结构来判断,并以最后的实验结果为准)

(2)Masked Obecjt Predicition

该认为与LM类似,将从图片提取的ROI特征部分置零,实现mask。一是为了学习图片region之间的语义关系,二是为了学习多模态之间的对齐。为此,作者设计了两个子任务。

ROI-feature Regression

回归任务,以L2 loss作为损失函数,计算输出与对应的 之间的L2损失。

Dctected-Label Classification

分类任务,学习masked区域的label信息,使用CE作为损失函数。同时,作者提到,虽然大多数预训练图像都有注释,但是,注释对象的真实标签在不同的数据集中是不一致的,(比如不同的数据集,同一类型的图片可能有不同的编号),因此,作者最后使用经过Faster-RCNN检测得到的标签作为对应图片的标签,并实验证明这一点有利于最后的结果。

作者对视觉预训练任务进行了消融实验,结果如下:

(3)Cross-Modality Tasks

多模态任务一共有两个子任务

Cross-Modality Matching

图文匹配任务,正负比例1:1

Image Question Answering

根据图片和question预测对应的答案。作者根据实验的表现,只在后10个epoch对该任务进行预训练,因为该任务的收敛更快,并且根据经验需要更小的学习率。作者对QA的效果进行了消融实验: 如上表的row2和row4,加入QA的预训练后,整体提升较大。

预训练数据的选择如下:

对于LXMERT在预训练时涉及的多个loss,作者最后给与相同的权重。

针对上面提到的随机初始化还是使用BERT参数进行初始化,作者在进行预训练消融实验时,也对结果进行了展示,如下:

3、下游任务

LXMERT与当时的SOTA对比实验:

对于 数据集,由于其一个样本存在两个图片+一个文本,所以其无法直接使用LXMERT进行预测,对此,本文的做法是,分别将图片和文本组合,然后concat两个图文的对的输出,然后建立分类器。 具体的计算方式如下:

其中 是sigmoid函数,使用CE作为loss,计算方式为:

总结

这篇论文发表的很早,使用的方法也被后面很多人借鉴。不过,该论文作为一篇经典的双流模型论文,其与单流模型最大的不同就在于模型的结构。单流模型直接在输入层拼接两者的emb,所以在交互层就不需要更多的设计,上一个Transformer就足够了。但是,双流层不同,其需要通过cross-attn对交互层进行精心的设计,比如本文交互层,将cross-attn放在self-attn前,这种网络层顺序的确定,就是一个会对结果产生决定性影响的因素。

所以,这样来看,貌似单流模型更为简单,不需要对模型结构进行更多的思考,但是,同样的,其上限就摆在那,双流模型如果能够设计一个完美的结构,将可以对信息实现完美的交互,进而实现sota。

【往期内容】

图文结合-UNITER

图文结合-ViLT

图文结合-SOHO

图文结合-imageBERT

分类:

人工智能

标签:

自然语言处理

作者介绍

仰止
V1