Multilingual T5で日本語の文章要約

[Ateam Group Manager ＆ Specialist Advent Calendar 2020](https://qiita.com/advent-calendar/2020/ateam)の5日目は 株式会社エイチームライフスタイルの @yuko1658 が担当します。最近、文章生成を使って何かできないかと企んでおり、今回は日本語の文章要約をやってみました。

## Multilingual T5とは

まず[T5](https://github.com/google-research/text-to-text-transfer-transformer)についてですが、__T__ext-__T__o-__T__ext __T__ransfer __T__ransformerの略で、様々なタスクをText-to-Textで学習できるモデルです。
![t5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12305/a330be24-8185-db0c-ca99-5598101945e4.png)
図の例では翻訳、分類、回帰、要約のタスクに一つのモデルで対応できていることがわかります。
T5には[Colossal Clean Crawled Corpus](https://www.tensorflow.org/datasets/catalog/c4)(以下 C4)という大規模コーパスによる事前学習モデルが用意されています。
C4は英語のデータセットですが、日本語を含む101言語が含まれたmC4が公開されており、[Multilingual T5](https://github.com/google-research/multilingual-t5)(以下 mT5)はmC4で事前学習されています。
今回はこのmT5を利用して日本語の文章要約をやってみたいと思います。

## wikiHow要約データ

文章要約のデータセットは[wikihow_japanese](https://github.com/Katsumata420/wikihow_japanese)を利用させていただきます。
[wikiHow](https://www.wikihow.jp/%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%9A%E3%83%BC%E3%82%B8)のデータは構造化されており、見出し部分が要約文として抽出されています。
詳しくは以下の記事で解説されています。
[wikiHowから日本語要約データを作成してみた](https://tech.retrieva.jp/entry/2020/07/02/121208)

REDEMEの手順通りに記事をダウンロードした後、mT5でFine-Tuningする用に整形しておきます。

```python
import pandas as pd

df = pd.read_json('data/output/test.jsonl', orient='records', lines=True)
df['inputs'] = df['src'].str.replace('\n', ' ').str.replace('\t', ' ')
df['targets'] = df['tgt'].str.replace('\n', ' ').str.replace('\t', ' ')
df[['inputs', 'targets']].to_csv('test.tsv', sep='\t', index=False)
```

元データはsrcに本文、tgtに要約文が入っているので、それぞれ改行とタブを削除してinputs, targetsというカラムでtsvに出力しています。改行とタブを削除したのは、Fine-Tuningのさいに学習がうまく進まなかったためです。
上記はtest用ですが、train用(train.tsv), validation用(dev.tsv)も同様に作成しています。
データの内容は以下のようになっています。
![スクリーンショット 2020-12-04 11.57.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12305/2f14923e-1740-a06f-022a-2ba6d4233378.png)


## Fine-Tuning

データが用意できたので、mT5でFine-Tuningしていきます。
以下の2つの記事を参考にさせていただきました。
[はじめての自然言語処理 第7回 T5 によるテキスト生成の検証](https://www.ogis-ri.co.jp/otc/hiroba/technical/similar-document-search/part7.html)
[Multilingual T5をカスタマーサポートの対話でfine-tuning](https://www.ai-shift.co.jp/techblog/1364)
実行環境は[Google Colaboratory](https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja)のGPU環境を利用しています。

まず必要なライブラリをインストールします。

```bash
# T5ライブラリ
!pip install t5[gcp]==0.7.1
!pip install tensorflow-gpu
!git clone https://github.com/google-research/multilingual-t5.git

# 日本語を扱う上で必要なライブラリ
!apt-get install mecab mecab-ipadic-utf8
!pip install mecab-python3==0.996.5 sumeval
!apt-get install nkf
!pip install janome
```

さきほど作成したデータ(train.tsv, dev.tsv, test.tsv)をColabにアップロードしておきます。
次にFine-Tuningするためのモジュールを作成します。

```python
%%bash
cat <<EOF > multilingual-t5/t5_wikihow.py
import t5.data
from t5.data import sentencepiece_vocabulary
from t5.evaluation import metrics
from t5.data import preprocessors
from t5.data import TaskRegistry
from t5.data import TextLineTask

import numpy as np
import functools
import tensorflow as tf
from sumeval.metrics.rouge import RougeCalculator

rouge_cal = RougeCalculator(stopwords=True, lang="ja")

DEFAULT_SPM_PATH = "gs://t5-data/vocabs/mc4.250000.100extra/sentencepiece.model"
DEFAULT_VOCAB = sentencepiece_vocabulary.SentencePieceVocabulary(
    DEFAULT_SPM_PATH)
DEFAULT_OUTPUT_FEATURES = {
    "inputs": t5.data.Feature(
        vocabulary=DEFAULT_VOCAB, add_eos=True, required=False),
    "targets": t5.data.Feature(
        vocabulary=DEFAULT_VOCAB, add_eos=True)
}

# rouge-1, rouge-2, rouge-lを評価指標とします
def rouge(targets, predictions):
  predictions = [tf.compat.as_text(x) for x in predictions]

  if isinstance(targets[0], list):
    targets = [[tf.compat.as_text(x) for x in target] for target in targets]
  else:
    targets = [tf.compat.as_text(x) for x in targets]
    targets = [targets]

  list_1, list_2, list_l = [], [], []
  for i in range(len(predictions)):
    list_1.append(rouge_cal.rouge_n(
            summary=predictions[i],
            references=targets[0][i],
            n=1))
    list_2.append(rouge_cal.rouge_n(
            summary=predictions[i],
            references=targets[0][i],
            n=2))
    list_l.append(rouge_cal.rouge_l(
            summary=predictions[i],
            references=targets[0][i]))

  return {"rouge_1": np.array(list_1).mean(),
          "rouge_2": np.array(list_2).mean(),
          "rouge_l": np.array(list_l).mean()}

task_name = "t5_wikihow"

tsv_path = {
    "train": "/content/train.tsv",
    "validation": "/content/dev.tsv",
    "test": "/content/test.tsv",
}

TaskRegistry.add(
    task_name,
    TextLineTask,
    split_to_filepattern=tsv_path,
    text_preprocessor=[
      functools.partial(
          preprocessors.parse_tsv,
          field_names=["inputs", "targets"]),
    ],
    output_features=DEFAULT_OUTPUT_FEATURES,
    metric_fns=[rouge])
EOF
```

事前学習モデルをダウンロードします。
事前学習モデルはパラメータ数によって[大小用意されている](https://github.com/google-research/multilingual-t5#released-model-checkpoints)のですが、今回は色々試してColabのメモリサイズでぎりぎり動くlargeを選択しました。

```bash
!gsutil cp gs://t5-data/pretrained_models/mt5/large/checkpoint /content/large
!gsutil cp gs://t5-data/pretrained_models/mt5/large/model.ckpt-1000000* /content/large
!gsutil cp gs://t5-data/pretrained_models/mt5/large/operative_config.gin /content/large
```

作成したモジュールと事前学習モデルを使ってFine-Tuningを実行します。

```bash
!export PYTHONPATH=${PYTHONPATH}:. && cd multilingual-t5 && \
  \
  PRE_TRAINED_MODEL_DIR='/content/large' && \
  OPERATIVE_CONFIG=$PRE_TRAINED_MODEL_DIR'/operative_config.gin' && \
  FINE_TUNED_MODEL_DIR='/content/large' && \
  FINE_TUNING_BATCH_SIZE=1024 && \
  PRE_TRAINGING_STEPS=1000000 && \
  FINE_TUNING_STEPS=`expr $PRE_TRAINGING_STEPS + 1000` && \
  INPUT_SEQ_LEN=512 &&\
  TARGET_SEQ_LEN=64 &&\
  \
  echo "OPERATIVE_CONFIG=$OPERATIVE_CONFIG" &&\
  echo "FINE_TUNED_MODEL_DIR=$FINE_TUNED_MODEL_DIR" &&\
  echo "FINE_TUNING_BATCH_SIZE=$FINE_TUNING_BATCH_SIZE" &&\
  echo "PRE_TRAINGING_STEPS=$PRE_TRAINGING_STEPS" &&\
  echo "FINE_TUNING_STEPS=$FINE_TUNING_STEPS" && \
  echo "INPUT_SEQ_LEN=$INPUT_SEQ_LEN" && \
  echo "TARGET_SEQ_LEN=$TARGET_SEQ_LEN" && \
  \
  t5_mesh_transformer \
  --model_dir="$FINE_TUNED_MODEL_DIR" \
  --module_import="t5_wikihow" \
  --gin_file="dataset.gin" \
  --gin_file="$OPERATIVE_CONFIG" \
  --gin_param="run.layout_rules=''" \
  --gin_param="run.mesh_shape=''" \
  --gin_param="utils.get_variable_dtype.activation_dtype='float32'" \
  --gin_param="MIXTURE_NAME = 't5_wikihow'" \
  --gin_file="learning_rate_schedules/constant_0_001.gin" \
  --gin_param="run.train_steps=$FINE_TUNING_STEPS" \
  --gin_param="run.sequence_length = {'inputs': $INPUT_SEQ_LEN, 'targets': $TARGET_SEQ_LEN}" \
  --gin_param="run.save_checkpoints_steps=200" \
  --gin_param="run.batch_size=('tokens_per_batch', $FINE_TUNING_BATCH_SIZE)"
```

入力のシーケンス長を512、出力を64とし、学習のステップ数は1,000に設定しています。
学習時間はColabで30分ほどで、GPUはTesla P100が割り当てられていました。

## 結果

wikiHow要約データをFine-Tuningしたモデルが出来上がったので、どんな文章が生成されるのかと、評価指標を見ていきます。

### モデルから要約文を生成

モデルを使って要約文を生成します。

```bash
!export PYTHONPATH=${PYTHONPATH}:. && cd multilingual-t5 && \
  \
  FINE_TUNED_MODEL_DIR='/content/large' && \
  OPERATIVE_CONFIG=$FINE_TUNED_MODEL_DIR'/operative_config.gin' && \
  \
  echo "OPERATIVE_CONFIG=$OPERATIVE_CONFIG" &&\
  echo "FINE_TUNED_MODEL_DIR=$FINE_TUNED_MODEL_DIR" &&\
  \
  t5_mesh_transformer \
  --model_dir="$FINE_TUNED_MODEL_DIR" \
  --module_import="t5_wikihow" \
  --gin_file="$OPERATIVE_CONFIG" \
  --gin_param="run.layout_rules=''" \
  --gin_param="run.mesh_shape=''" \
  --gin_file="infer.gin" \
  --gin_file="beam_search.gin" \
  --gin_param="utils.get_variable_dtype.slice_dtype='float32'" \
  --gin_param="utils.get_variable_dtype.activation_dtype='float32'" \
  --gin_param="MIXTURE_NAME='t5_wikihow'" \
  --gin_param="run.batch_size=('tokens_per_batch', 512)" \
  --gin_param="infer_checkpoint_step=1001000" \
  --gin_param="input_filename='/content/inputs.txt'" \
  --gin_param="output_filename='/content/outputs.txt'"
```

inputs.txtというファイルを用意して、test.tsvからinputsをいくつかピックアップして入力としています。
outputs.txtにモデルから生成された要約文が出力されますが、byte形式で出力されるため、以下のようにしてデコードします。

```python
import pandas as pd
pd.read_csv('outputs.txt-1001000', header=None)[0].apply(lambda s: eval(s).decode("utf-8"))
```

いくつか生成された文章を見てみます。

#### 例1

<details><summary>本文</summary><div>
> 脅威を感じた蛇は再び襲いかかります。したがって、噛まれた際は速やかに蛇の攻撃範囲から離れましょう。 少なくとも6mは間合いを取りましょう。できる限り速やかに医療処置を求めることが大切です。ほとんどの病院は、毒蛇用の抗毒素（血清）を用意しています。病院に到着する前の応急手当だけでは、あまり症状の改善にはつながりません。被害現場からすぐさま救急サービスに通報できれば不幸中の幸いです。救急車を呼べない場合は、何としても助けを求め、みなさんまたは被害者を最寄りの病院へ搬送しなければなりません。みなさんに噛みついた蛇がガラガラヘビかどうかが分からない場合でも、すぐに病院へ直行しましょう。実際に毒が体に回り、症状が出始めたとしても、病院にいれば安心できるでしょう。噛まれた箇所を心臓よりも上に置くと、毒を含んだ血液が猛スピードで心臓に流れ込みます。救助が来るまでの間、できれば被害者の体を静止させましょう。体を動かすと血流が増大し、あっという間に毒が回ります。したがって、毒蛇に噛まれた際は体の動きを最小限に抑えて安静にすることが大切です。もちろん、みなさんの周りに誰もいなければ、じっとしている場合ではありません。すぐに助けを求めましょう。
</div></details>

正解の要約文
> ガラガラヘビから離れましょう。医療処置を受けましょう。決して患部を心臓よりも高い位置に置いてはいけません。体を動かさずにじっとしましょう。

生成された要約文
> 噛みついた蛇に噛まれた場合は、すぐに病院へ直行しましょう。

短めでちょっと文がおかしいですが、要点を抑えた文章にはなっていそうです。

#### 例2

<details><summary>本文</summary><div>
> NASAと言えば、一番最初に頭に浮かぶのは宇宙飛行士でしょう。宇宙に行くことに興味がなくても、NASAには他に魅力的な仕事があります。以下に挙げるのはNASAが雇用している専門的な職種のほんの一部です。医師、看護士、心理士やカウンセラー研究者、エンジニア、地質学者、微生物学者、物理学者ライター、人事管理者、通信技術者コンピュータープログラマー、IT技術者NASAで働くための計画の手始めに、なるべく早い段階で自分が何が得意なのか掘り下げてみるとよいでしょう。NASAのどのポジションが自分に合っているか考えをまとめる手助けとなるでしょう。以下の事を考えてみましょう。学校で秀でていた科目はなんですか？例えば、物理の実験クラスで、みんながあなたとペアになりたがったとしたら、NASAの物理分野が向いているかもしれません。例えば、数学や化学が得意だったとしても、NASAでの仕事では求められるものが大きく、応募条件を満たすための勉強もレベルが高く膨大なものになるでしょう。そのため、自分が秀でているものだけではなく、情熱をかけられる方向を選択することも考慮に入れましょう。目指したい職種が定まったのなら、高校や大学で選択すべき学科の大枠を注意深く立てましょう。定期的に履修学科アドバイザー（アカデミックアドバイザー）に会い、正しい学科を選択しているか、単位が足りているか確認しましょう。NASAの宇宙飛行士、エンジニア、科学者になりたいという目標がある場合は、STEM（科学、技術、工学、数学の教育分野の総称）に特化した教育を受けましょう。目指しているNASAでの職種に大学院を履修する必要がある場合は、早めに進むべき道を決断しましょう。その計画によって、どの学校に行くか、大学でどの科目を取るべきかに影響してくるでしょう。NASAに寄せられる、どうやったらNASAで働けますか？という質問に対する答えに「死ぬ気で勉強すること」という冗談がありますが、実際、一生懸命勉強することが夢を叶えるための鍵となります。真剣に勉強しましょう。ただ単位を取ればいいというのではなく、学んでいることをしっかりと自分のものにしましょう。この記事を読んでいるあなたがまだ高校生であるならば、今の内からNASAを目指す計画を立てて正解です。STEM教育に力を入れている大学や学校をじっくり探し、できるだけ条件に近い学校に入学できるようにしましょう。どのように道を切り開けば良いか探るには、先人の足跡を参考にするというのも一つの手です。NASAのウェブサイトに行って、NASAで成功している人の略歴やプロフィールを読んでみましょう。どこの大学と大学院を卒業しているか、インターンシップやフェローシップ（大学の特別研究員）などの経歴があるか注意深く読んでみましょう。経歴にあった大学に入学できそうですか？もうすでに大学に通っているが、履修しているプログラムが十分でない、または一流ではないという場合は、最後の1年～2年に他の大学に編入することも可能かもしれません。STEM教科に力を入れつつも、文系の教科も忘れないようにしましょう。例えば、哲学、歴史、倫理の教科は役に立つでしょう。文系の学科から複雑な文書の読解と分析、問題解決と批判的思考（クリティカル・シンキング）を磨き、道徳的な質問に深く考えを巡らすといった事柄を学ぶことができるでしょう。これらの事柄は将来NASAに就職した際に役に立つ時が来るでしょう。自己を磨き成長することも優先するようにしましょう。つまり、知識を増やすことだけに労力を費やすのではなく、自分自身をケアし、人を育て、リーダーシップを取ることも大切にしましょう。くつろぐ方法と楽しみを見つけることも大切です。バランス感覚を養うためにも課外活動やクラブ活動へ参加する時間を作るようにしましょう。例えば、化学クラブ、数学クラブ、討論クラブ、バレーボールクラブ、吹奏楽部などクラブ活動に参加したり、生徒会に立候補したりするなどしてみましょう。
</div></details>

正解の要約文
> NASAの様々な職業について学びましょう。自分が得意な学問が何かを定めましょう。興味を持って情熱を注げる分野は何かを考えてみましょう。大学や教育機関でどの学科を選択するべきか計画を立てましょう。一生懸命勉強しましょう。正しい学校を選びましょう。現在NASAに所属している人たちの経歴を調べてみましょう。先人と同じような道筋を辿れるのかどうか自問しましょう。幅広く学習しましょう。物事へのバランス感覚を養いましょう。

生成された要約文
> 自分の何が得意なのか掘り下げましょう。自分の何が得意なのか掘り下げましょう。

要約しすぎている感じですかね。なぜか同じ文を繰り返してしまいました。

#### 例3

<details><summary>本文</summary><div>
> 流水で数分洗ったらペーパータオルで余分な水分を吸い取り乾燥させます。小さなボウルにバターとレモン果汁を入れて完全に混ざり合うまでかき混ぜます。滑らかなバター液が出来上がりました。粉類を配合して、小麦粉の配合が滑らかに仕上がるまで同様にかき混ぜます。バターの効果で魚に粉類がつきます。魚を軽くふり、余分な粉類を落とします。風味付けに魚の上からパプリカをふります。パセリの小枝とレモンスライスで飾り付けをしたら、温かいうちにお召し上がりください。
</div></details>


正解の要約文
> タラの切り身の下ごしらえ。バターとレモン果汁を混ぜます。小麦粉、塩、白胡椒を別のボウルで混ぜます。タラの切り身をバター液に付けた後、配合した粉類をまぶします。残っているバター液を魚の上からかけます。盛りつけをします。

生成された要約文
> 魚を洗う。バターとレモン果汁を混ぜる。バターとレモン果汁を混ぜる。バターとレモン果汁を混ぜる。

本文のはじめの方には「魚」という言葉は出てきていないのに、魚を洗っていると分かっているのはすごいと思いました。
ただ後半はまた繰り返しになってしまっています。

他にもいくつか見てみましたが、同じ文を繰り返してしまう現象が多く見られました。
入出力のシーケンス長を変えたり、学習のステップ数を増やしたりしてみましたが、改善はされなかったです。

### 評価指標の比較

生成された文はいまいちでしたが、一応評価指標も確認してみます。
要約の評価指標であるROUGE-1, 2, Lを用いました。ROUGEは正解の要約文と生成した要約文がどれだけ一致しているかという指標で、1に近いほどよいです。
以下で詳しく解説させています。
[ROUGEを訪ねて三千里:より良い要約の評価を求めて](https://qiita.com/icoxfog417/items/65faecbbe27d3c53d212)

testデータの評価値を計算します。

```bash
!export PYTHONPATH=${PYTHONPATH}:. && cd multilingual-t5 && \
  \
  FINE_TUNED_MODEL_DIR='/content/large' && \
  OPERATIVE_CONFIG=$FINE_TUNED_MODEL_DIR'/operative_config.gin' && \
  \
  echo "OPERATIVE_CONFIG=$OPERATIVE_CONFIG" &&\
  echo "FINE_TUNED_MODEL_DIR=$FINE_TUNED_MODEL_DIR" &&\
  \
  t5_mesh_transformer \
  --model_dir="$FINE_TUNED_MODEL_DIR" \
  --module_import="t5_wikihow" \
  --gin_file="$OPERATIVE_CONFIG" \
  --gin_param="run.layout_rules=''" \
  --gin_param="run.mesh_shape=''" \
  --gin_file="eval.gin" \
  --gin_file="beam_search.gin" \
  --gin_param="utils.get_variable_dtype.slice_dtype='float32'" \
  --gin_param="utils.get_variable_dtype.activation_dtype='float32'" \
  --gin_param="MIXTURE_NAME = 't5_wikihow'" \
  --gin_param="run.dataset_split='test'" \
  --gin_param="run.batch_size=('tokens_per_batch', 512)" \
  --gin_param="eval_checkpoint_step = 1001000" 2>&1 | tee test.log
```

比較対象として、本文の始めの3文を要約とみなしたもの(Lead-3)を用いました。

| 手法 | ROUGE-1 | ROUGE-2 | ROUGE-L |
|:-:|:-:|:-:|:-:|
| Lead-3  | 0.300  | 0.084  | 0.212  |
| mT5  | 0.255  |  0.081 | 0.234  |

ROUGE-Lでは勝っているものの、良い精度とは言い難い結果になりました。
生成された文が短かったり、同じ文が続けて生成されてしまう問題が原因かと考えています。

## まとめ

Multilingual T5でwikiHowの日本語要約データを用いて要約文の生成をやってみました。
うまく文が生成されず、悔しい結果となりましたが、要点をとらえた文章が生成されるのはすごいなと感じました。
引き続き原因を調べつつ、ちゃんとインスタンスを立ててより大きな事前学習モデルも試してみたいなと思います。

## さいごに

[Ateam Group Manager ＆ Specialist Advent Calendar 2020](https://qiita.com/advent-calendar/2020/ateam)の6日目は @NMura3 がお送りします。お楽しみに！