ナード戦隊データマン

機械学習, 自然言語処理, データサイエンスについてのブログ

MTNTに対するモデリング

MTNTについての解説1や、WMT19でのタスク2などを書いてきましたが、今回は試しに実装してみます。

パイプライン

  1. 必要なデータのダウンロード
  2. データに対する準備
  3. baselineモデルの訓練
  4. 逆翻訳の実行とファインチューニング
  5. アンサンブルモデルの作成
  6. 評価

上記パイプラインをまとめたコードは以下で公開しています。

github.com

Warning: run.shに対するテストはしていません。

モデル

import os
from tensor2tensor.data_generators import translate, generator_utils
from tensor2tensor.data_generators import text_encoder
from tensor2tensor.utils import registry
import tensorflow as tf

EOS = text_encoder.EOS_ID


def token_generator_by_source_target(source_path,
                                     target_path,
                                     source_token_vocab,
                                     target_token_vocab,
                                     eos=None):
    eos_list = [] if eos is None else [eos]
    with tf.gfile.GFile(source_path, mode="r") as srcfile, \
         tf.gfile.GFile(target_path, mode="r") as tgtfile:
        src, tgt = srcfile.readline(), tgtfile.readline()
        while src and tgt:
            src_ints = source_token_vocab.encode(src.strip()) + eos_list
            tgt_ints = target_token_vocab.encode(tgt.strip()) + eos_list
            yield {"inputs": src_ints, "targets": tgt_ints}
            src, tgt = srcfile.readline(), tgtfile.readline()


@registry.register_problem
class Translate_JAEN(translate.TranslateProblem):
    @property
    def approx_vocab_size(self):
        return 2**15  # 32k

    @property
    def source_vocab_name(self):
        return "ja-en_ja.vocab.txt"

    @property
    def target_vocab_name(self):
        return "ja-en_en.vocab.txt"

    def feature_encoders(self, data_dir):
        source_vocab_filename = os.path.join(data_dir, self.source_vocab_name)
        target_vocab_filename = os.path.join(data_dir, self.target_vocab_name)
        source_encoder = text_encoder.TokenTextEncoder(source_vocab_filename,
                                                       replace_oov="<unk>")
        target_encoder = text_encoder.TokenTextEncoder(target_vocab_filename,
                                                       replace_oov="<unk>")
        return {"inputs": source_encoder, "targets": target_encoder}

    def generator(self, data_dir, tmp_dir, train):
        dataset_path = ("ja-en" if train else "valid.ja-en")
        train_path = os.path.join(tmp_dir, dataset_path)
        source_token_path = os.path.join(data_dir, self.source_vocab_name)
        target_token_path = os.path.join(data_dir, self.target_vocab_name)
        source_token_vocab = text_encoder.TokenTextEncoder(source_token_path,
                                                           replace_oov="<unk>")
        target_token_vocab = text_encoder.TokenTextEncoder(target_token_path,
                                                           replace_oov="<unk>")
        return token_generator_by_source_target(train_path + ".ja",
                                                train_path + ".en",
                                                source_token_vocab,
                                                target_token_vocab, EOS)

    def generate_data(self, data_dir, tmp_dir, task_id=-1):
        train_paths = self.training_filepaths(data_dir, 100, shuffled=True)
        dev_paths = self.dev_filepaths(data_dir, 1, shuffled=True)
        generator_utils.generate_files(self.generator(data_dir, tmp_dir, True),
                                       train_paths)
        generator_utils.generate_files(
            self.generator(data_dir, tmp_dir, False), dev_paths)


@registry.register_problem
class Translate_ENJA(translate.TranslateProblem):
    @property
    def approx_vocab_size(self):
        return 2**14  # 16k

    @property
    def source_vocab_name(self):
        return "en-ja_joint.vocab.txt"

    @property
    def target_vocab_name(self):
        return "en-ja_joint.vocab.txt"

    def feature_encoders(self, data_dir):
        source_vocab_filename = os.path.join(data_dir, self.source_vocab_name)
        target_vocab_filename = os.path.join(data_dir, self.target_vocab_name)
        source_encoder = text_encoder.TokenTextEncoder(source_vocab_filename,
                                                       replace_oov="<unk>")
        target_encoder = text_encoder.TokenTextEncoder(target_vocab_filename,
                                                       replace_oov="<unk>")
        return {"inputs": source_encoder, "targets": target_encoder}

    def generator(self, data_dir, tmp_dir, train):
        dataset_path = ("en-ja" if train else "valid.en-ja")
        train_path = os.path.join(tmp_dir, dataset_path)
        source_token_path = os.path.join(data_dir, self.source_vocab_name)
        target_token_path = os.path.join(data_dir, self.target_vocab_name)
        source_token_vocab = text_encoder.TokenTextEncoder(source_token_path,
                                                           replace_oov="<unk>")
        target_token_vocab = text_encoder.TokenTextEncoder(target_token_path,
                                                           replace_oov="<unk>")
        return token_generator_by_source_target(train_path + ".en.true",
                                                train_path + ".ja",
                                                source_token_vocab,
                                                target_token_vocab, EOS)

    def generate_data(self, data_dir, tmp_dir, task_id=-1):
        train_paths = self.training_filepaths(data_dir, 100, shuffled=True)
        dev_paths = self.dev_filepaths(data_dir, 1, shuffled=True)
        generator_utils.generate_files(self.generator(data_dir, tmp_dir, True),
                                       train_paths)
        generator_utils.generate_files(
            self.generator(data_dir, tmp_dir, False), dev_paths)
from tensor2tensor.models.transformer import transformer_base
from tensor2tensor.utils import registry


@registry.register_hparams
def transformer_jaen():
    """HParams for transformer base model for single GPU."""
    hparams = transformer_base()
    hparams.batch_size = 1024
    hparams.shared_embedding_and_softmax_weights = False
    hparams.optimizer_adam_beta1 = 0.9
    hparams.optimizer_adam_beta2 = 0.98
    hparams.attention_dropout = 0.3
    hparams.relu_dropout = 0.3
    hparams.learning_rate = 0.001
    hparams.learning_rate_schedule = "constant*linear_warmup*rsqrt_decay"
    hparams.learning_rate_constant = 0.1
    hparams.learning_rate_warmup_steps = 16000
    return hparams


@registry.register_hparams
def transformer_enja():
    """HParams for transformer base model for single GPU."""
    hparams = transformer_base()
    hparams.batch_size = 1024
    hparams.optimizer_adam_beta1 = 0.9
    hparams.optimizer_adam_beta2 = 0.98
    hparams.attention_dropout = 0.3
    hparams.relu_dropout = 0.3
    hparams.learning_rate = 0.001
    hparams.learning_rate_schedule = "constant*linear_warmup*rsqrt_decay"
    hparams.learning_rate_constant = 0.1
    hparams.learning_rate_warmup_steps = 16000
    return hparams

結果

[baseline]
BLEU+case.mixed+numrefs.1+smooth.exp+tok.13a+version.1.3.7 = 3.2 24.3/5.7/1.7/0.5 (BP = 0.959 ratio = 0.960 hyp_len = 13013 ref_len = 13555)

[逆翻訳データを使わないバージョンのファインチューニング]
BLEU+case.mixed+numrefs.1+smooth.exp+tok.13a+version.1.3.7 = 5.2 31.8/9.0/3.5/1.4 (BP = 0.845 ratio = 0.856 hyp_len = 11601 ref_len = 13555)

[逆翻訳データを使ったバージョンのファインチューニング]
BLEU+case.mixed+numrefs.1+smooth.exp+tok.13a+version.1.3.7 = 5.2 29.7/8.6/3.3/1.3 (BP = 0.899 ratio = 0.904 hyp_len = 12247 ref_len = 13555)

ぜーーんぜんダメダメじゃん!

考察

モデリングについて、なにか勘違いをしている部分があるのかもしれません。placeholderの仕組みは組み込んでいませんが、以前紹介したNTTの論文ではbaselineのJa-EnモデルのBLUEが10ぐらいだったので、明らかにそれを下回っています。

忠実にfairseq3を使えばスコアが上がる可能性はありますが、確証はありません。

参考