单语适配器代码复现

本文介绍了基于fairseq框架复现MonolingualAdaptersforZero-ShotNeuralMachineTranslation的实验,利用单语适配器改进多语言transformer模型。通过在编码器和解码器层后添加适配器结构并进行微调,观察到训练和微调过程中损失曲线的变化,以及评估时BLEU得分的提升,证明了这种方法的有效性。
摘要由CSDN通过智能技术生成

单语适配器

代码复现

本次复现是基于fairseq框架对Monolingual Adapters for Zero-Shot Neural Machine Translation这篇论文提出的单语适配器结构进行复现,因为fairseq已经实现了multilingual transformer结构,所以本篇文章就直接对框架中的transformer进行复用。

采用的数据集是iwslt2014中的de-en和fa-en语言向的数据,前期的数据处理部分这里就忽略了。
github仓库:https://github.com/rainyuniverse/fairseq-adapter

  1. 添加参数

    首先需要参数来指定是否为微调模式,以及adapter中间映射的维度

    # multilingual_transformer.py
    args.finetune = getattr(args, "finetune", False)
    args.adapter_latent_size = getattr(args, "adapter_latent_size", 64)
    
    # transformer_config.py
    finetune: bool = field(
        default=False,
        metadata={"help": "False for training mode, and True for finetuning mode"}
    )
    
    adapter_latent_size: Optional[int] = field(
        default=64,
        metadata={"help": "Dimensions of the adapter intermediate mapping"}
    )
    
  2. 定义单语adapter结构

    文章中提到,单语adapter结构是放在每一层编码器和解码器之后的,并且每一种源语言和目标语言单独使用一个adapter,其结构图如下所示,简单来说,就是首先进行了layernorm,然后再进行down projection、nonlinearity和up projection操作,最后再往外面套一个残差,为了重复使用该结构,笔者将adapter类定义在transformer_layer.py文件中

在这里插入图片描述

class AdapterLayer(nn.Module):
    def __init__(self,
                 adapter_input_size,
                 adapter_latent_size,
                 residual=True):
        super(AdapterLayer, self).__init__()

        self.adapter_input_size = adapter_input_size
        self.adapter_latent_size = adapter_latent_size

        # nonlinearity function
        self.non_linearity = torch.relu

        self.residual = residual

        # down-project
        self.down_proj = nn.Linear(self.adapter_input_size, self.adapter_latent_size)
        # up-project
        self.up_proj = nn.Linear(self.adapter_latent_size, self.adapter_input_size)

        self.init_weights()

    def init_weights(self):
        '''
            Initialize the adapter weights
        '''
        std = 0.01
        self.down_proj.weight.data.normal_(mean=0.0, std=std)
        self.down_proj.bias.data.zero_()

        self.up_proj.weight.data.normal_(mean=0.0, std=std)
        self.up_proj.bias.data.zero_()

    def forward(self, x):
        output = self.up_proj(self.non_linearity(self.down_proj(x)))
        if self.residual:
            output = x + output
        return output
  1. homogeneous batches实现

homogeneous batches指的是每一个batch中只包含一种语言对的数据,这样可以在微调的时候对每一个单语adapter进行分别训练,为了实现这一点,同时不改变原有的采样策略,笔者在原有的基础上去除每个batch包含的其他语言对的数据(在字典中将其他语言对的数据pop出来),具体当前batch包含哪一种语言对取决于当前batch序数对包含的所有语言对长度取余的结果

   # train.py
   # finetune mode
   # one language pair for one batch
   if cfg.model.finetune:
       lang_pair_length = len(cfg.task.lang_pairs)
   
       for j in range(lang_pair_length):
           # remain
           if j == (i % lang_pair_length):
               continue
           # remove
           else:
               for k in range(len(samples)):
                   samples[k].pop(cfg.task.lang_pairs[j])

在multilingual_translation中还有对所有语言对的判断(如果batch中的数据没有包含所有语言对会抛出异常),这里根据是否是微调模式进行一下特判

   if self.args.finetune:
       curr_lang_pairs = [
           lang_pair
           for lang_pair in sample.keys()
       ]
   else:
       curr_lang_pairs = [
           lang_pair
           for lang_pair in self.model_lang_pairs
           if sample[lang_pair] is not None and len(sample[lang_pair]) != 0
       ]
  1. 将adapter结构加入到transformer每一层编码器和解码器的后面
  • 对当前所有的语言对进行记录,然后根据当前的源语言数量和目标语言数量初始化adapter,分别将编码器的adapter和解码器的adapter封装在moduledict中,以语言的名字作为key,需要注意的是,以下代码需要分别写在TransformerEncoderLayerBase类的init函数和TransformerDecoderLayerBase类的init函数中,笔者这里是为了方便理解所以写到了一起
   # transformer_layer.py
   # Adapter config
   self.lang_pairs = cfg.lang_pairs
   
   self.src_lang_list = []
   self.tgt_lang_list = []
   for i in range(len(self.lang_pairs)):
       cur_lang_pair = self.lang_pairs[i].split("-")
       if cur_lang_pair[0] not in self.src_lang_list:
           self.src_lang_list.append(cur_lang_pair[0])
       if cur_lang_pair[1] not in self.tgt_lang_list:
           self.tgt_lang_list.append(cur_lang_pair[1])
   
   self.finetune = cfg.finetune
   self.adapter_latent_size = cfg.adapter_latent_size
   
   # encoder
   self.adapters = torch.nn.ModuleDict({
       self.src_lang_list[i] : AdapterLayer(self.embed_dim, self.adapter_latent_size)
       for i in range(len(self.src_lang_list))
   })
   
   # decoder
   self.adapters = torch.nn.ModuleDict({
       self.tgt_lang_list[i] : AdapterLayer(self.embed_dim, self.adapter_latent_size)
       for i in range(len(self.tgt_lang_list))
   })
  • 分别在编码器层和解码器层的forward函数最后加入adapter
   # transformer_layer.py
   # encoder
   # monolingual adapter
   if self.finetune:
       src_lang = kwargs['lang_pair'].split("-")[0]
       x = self.adapters[src_lang](x)
   
   # decoder
   # monolingual adapter
   if self.finetune:
       tgt_lang = kwargs['lang_pair'].split("-")[1]
       x = self.adapters[tgt_lang](x)

其实这里还涉及到参数的传递问题,为了不破坏原有fairseq框架的完整性,同时还需要将每个batch的lang_pair参数在训练的过程中传递进来,笔者将lang_pair参数作为**kwargs集合关键字参数,再往前看,在label_smoothed_cross_entropy.py文件中,实际上是将lang_pair作为网络的一个输入参数传递进来的

   # label_smoothed_cross_entropy.py 
   # forward(....,lang_pair,...)
   sample["net_input"]['lang_pair'] = lang_pair
   net_output = model(**sample["net_input"])

另外还需要修改评估模式下的代码,保证参数传递的正确性

   # sequence_generator.py
   kwargs = {'lang_pair': net_input['lang_pair']}
   lprobs, avg_attn_scores = self.model.forward_decoder(
       tokens[:, : step + 1],
       encoder_outs,
       incremental_states,
       self.temperature,
       **kwargs
   )
  1. 在微调模式下冻结除adapter外所有的结构

为了实现这一点,笔者在transformer_layer.py文件中的编码器层和解码器层在初始化(init函数中)时就先判断一下是否为微调模式,如果是微调模式,则将这一层中adapter前所有结构的参数冻结(将require_grad变为False)

   # freeze above parameters if finetune
   if self.finetune:
       for param in self.parameters():
           param.requires_grad = False

另外需要注意的是,除了transformer_layer中的这些结构外,还有编码器和解码器的embedding层、以及解码器后的output_project层是需要单独进行参数冻结的,它们分别在transformer_encoder.py文件和transformer_decoder.py文件中

训练

  1. 训练脚本

    CUDA_VISIBLE_DEVICES=4,5 fairseq-train /data2/lypan/data/data-bin \
          --max-epoch 45 \
          --ddp-backend=legacy_ddp \
          --task multilingual_translation --lang-pairs de-en,fa-en \
          --arch multilingual_transformer_iwslt_de_en \
          --share-decoders --share-decoder-input-output-embed \
          --optimizer adam --adam-betas '(0.9, 0.98)' \
          --lr 0.0005 --lr-scheduler inverse_sqrt \
          --warmup-updates 4000 --warmup-init-lr '1e-07' \
          --label-smoothing 0.1 --criterion label_smoothed_cross_entropy \
          --dropout 0.3 --weight-decay 0.0001 \
          --save-dir ../checkpoints \
          --max-tokens 4000 \
          --update-freq 8 \
          --tensorboard-logdir ../logs
    
  2. 训练过程loss曲线变化

    # 使用tensorboard可视化
    tensorboard --logdir logs
    

  3. 评估

    • 评估脚本

      fairseq-generate ../data/data-bin \
         --path ../fairseq/checkpoints/checkpoint_best.pt \
         --task multilingual_translation --lang-pairs de-en,fa-en \
         --source-lang de --target-lang en \
         --batch-size 128 --beam 5 --remove-bpe
      
    • 评估结果

      # de-en
      BLEU4 = 30.90, 65.6/40.2/26.5/17.8 (BP=0.925, ratio=0.928, syslen=128648, reflen=138697)
      
      # fa-en
      BLEU4 = 21.35, 58.4/30.0/17.7/10.8 (BP=0.888, ratio=0.894, syslen=71057, reflen=79524)
      

微调

  1. 微调过程loss曲线变化

在这里插入图片描述

  1. 评估结果

    # de-en
    BLEU4 = 31.52, 65.0/39.9/26.5/17.9 (BP=0.947, ratio=0.948, syslen=131493, reflen=138697)
    
    # fa-en
    BLEU4 = 22.18, 57.6/29.8/17.7/10.9 (BP=0.926, ratio=0.929, syslen=73854, reflen=79524)
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值