言語処理100本ノック2020 (98. ドメイン適応)



# はじめに
本記事は言語処理100本ノックの解説です。
100本のノックを全てこなした記録をQiitaに残します。
使用言語はPythonです。

https://nlp100.github.io/2020/ja/

今回は第10章: 機械翻訳(98. ドメイン適応)の解答例をご紹介します。



# 98. ドメイン適応
>Japanese-English Subtitle Corpus (JESC)やJParaCrawlなどの翻訳データを活用し，KFTTのテストデータの性能向上を試みよ．

**準備**
fairseqを入れます。
```text:コマンド
$ git clone https://github.com/pytorch/fairseq
$ pip install --editable fairseq/
```
分かち書きをするためにMecabを入れます。
```text:コマンド
$ pip install mecab-python3 unidic
$ python -m unidic download
```
## データの作成
Japanese-English Subtitle Corpus (JESC)を訓練データに追加して学習をします。そのために以下のコードでJESCの日本語を分かち書きにして、KFTTのデータとマージします。
```python:コード
import pandas as pd
import MeCab
m = MeCab.Tagger("-Owakati")
def Wakachi(text):
  w_text = m.parse(text)
  w_text = w_text.replace(" \n", "")
  return w_text

def MakeDataset(source):
  df = pd.read_table("[PATH]/JESC/split/{}".format(source), names=("English", "Japanese"))
  df["Japanese"].apply(Wakachi).to_csv("[PATH]/Source-JESC/JESC-{}.ja".format(source), header=False, index=False)
  df["English"].to_csv("[PATH]/Source-JESC/JESC-{}.en".format(source), header=False, index=False)

MakeDataset("test")
MakeDataset("dev")
MakeDataset("train")

def MargeDataset(name):
  !cat "[PATH]/Source-JESC/JESC-"$name".ja" "[PATH]/sorce/kyoto-"$name".ja" > "[PATH]/Source-98/Source98-"$name".ja"
  !cat "[PATH]/Source-JESC/JESC-"$name".en" "[PATH]/sorce/kyoto-"$name".en" > "[PATH]/Source-98/Source98-"$name".en"

MargeDataset("test")
MargeDataset("dev")
MargeDataset("train")
```
次にfairseqで学習できる形式にします。
```text:コマンド
$ fairseq-preprocess -s ja -t en \
--trainpref  "[PATH]/Source-98/Source98-train" \
--validpref "[PATH]/Source-98/Source98-dev" \
--testpref "[PATH]/Source-98/Source98-test" \
--destdir "[PATH]/processed-98/" \
--task translation \
--thresholdsrc 5 \
--thresholdtgt 5
```

## モデルの学習
```text:コマンド
$ fairseq-train "[PATH]/processed-98/" \
  --task translation \
  --arch transformer \
  --tensorboard-logdir "logs" \
  --source-lang ja --target-lang en \
  --max-epoch 100 \
  --lr 1e-6 \
  --batch-size 32 \
  --optimizer adam \
  --save-interval 5 \
  --save-dir "[PATH]/model-98/" \
  --restore-file  "[PATH]/model-98/checkpoint_last.pt" \
  --skip-invalid-size-inputs-valid-test \
| tee -a "[PATH]/log-98/train.log"
```

## 再学習用のデータ作成
KFTTのテストデータの性能向上が目標なので、いったんJESC+KFTTで学習を行った後にKFTTのデータのみでfine-tuningをします。ポイントは辞書の指定をJESC+KFTTで作った辞書にするところです。
```text:コマンド
$ fairseq-preprocess -s ja -t en \
--trainpref  "[PATH]/kftt-data-1.0/sorce/kyoto-train" \
--validpref "[PATH]/kftt-data-1.0/sorce/kyoto-dev" \
--testpref "[PATH]/kftt-data-1.0/sorce/kyoto-test" \
--destdir "[PATH]/processed-98-relearning/" \
--srcdict "[PATH]/processed-98/dict.ja.txt" \
--tgtdict "[PATH]/processed-98/dict.en.txt" \
--task translation \
--thresholdsrc 5 \
--thresholdtgt 5
```

## Fine-tuning
```text:コマンド
$ fairseq-train "[PATH]/processed-98-FT/" \
  --task translation \
  --arch transformer \
  --tensorboard-logdir "logs" \
  --source-lang ja --target-lang en \
  --max-epoch 30 \
  --lr 1e-6 \
  --batch-size 32 \
  --optimizer adam \
  --save-interval 5 \
  --save-dir "[PATH]/model-98-FT/" \
  --restore-file  "[PATH]/model-98-FT/checkpoint_last.pt" \
  --skip-invalid-size-inputs-valid-test \
| tee -a "[PATH]/log-98-FT/train.log"
```

## テストデータの精度確認
fairseq-generateでテストデータに対して翻訳を実行します。その後、fairseq-scoreでbleuを算出します。
```text:コマンド
$ fairseq-generate "[PATH]/processed-98-FT/" \
--path "[PATH]/model-98-FT/checkpoint_best.pt" \
--task translation \
--gen-subset test \
| tee >(grep "^H" | cut -f3 > "./test-transform.txt")\
| grep "^T" | cut -f2 > "./test-true.txt"

$ fairseq-score --sys  "./test-transform.txt"\
--ref  "./test-true.txt"
```
**出力結果**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3579871/47a2958f-00ce-8cec-1e50-e01b8f95c4a5.png)
Bleuスコア: 21.25
## コメント
膨大な量のデータで事前にモデルを学習して、その後にドメインのデータでFine-tuningする手法はよく使われます。今後は如何に大量の学習データを用意できるか、学習リソースを確保できるかがモデルの優劣を決める時代になると思います。

# 他章の解答例

https://qiita.com/Dukapan100knock/items/d418f2aa37b2b3d16428