見出し画像

Huggingface Transformers 入門 (5) - 言語モデルをTrainerで学習

以下の記事を参考に書いてます。

How to train a new language model from scratch using Transformers and Tokenizers

前回

1. はじめに

この数ヶ月間、モデルをゼロから学習しやすくするため、「Transformers」と「Tokenizers」に改良を加えました。

この記事では、「エスペラント語」で小さなモデル(84Mパラメータ= 6層、768の隠れ層、12のアテンションヘッド - DistilBERTと同じ数のレイヤーとヘッド)を事前学習した後、品詞タグ付けのファインチューニングを行います。

私たちはこのモデルの名前を「EsperBERTo」と呼ぶことにしました。

2. データセットの準備

はじめに、「エスペラント語」のコーパスを準備します。

今回は、「OSCARコーパス」のエスペラント部分を使います。

画像1

これは299Mしかないので、ニュース、文学、wikipediaなどの多様なソースからのテキストからなる「Leipzig Corpora Collection」のエスペラント部分と連結することにします。

最終的な学習コーパスのサイズは3GBです。これはまだ小さいです。事前学習のデータは、多ければ多いほど、より良い結果が得られます。

# このノートブックではシンプルさとパフォーマンスのため1ファイル(OSCAR)のみ取得
!wget -c https://cdn-datasets.huggingface.co/EsperBERTo/data/oscar.eo.txt

3. トークナイザーの学習

今回は、「RoBERTa」と同じスペシャルトークンを持つ「ByteLevelBPETokenizer」(GPT-2と同じ)を学習します。そのサイズを任意に52,000にしてみます。

私たちは、byte-level BPE(BERTのようなWordPieceトークナイザーではなく)で学習することをお勧めします。なぜなら、BPEはシングルバイトのアルファベットから語彙を構築し始めるので、全ての単語がトークンに分解可能になります。

# TensorFlowのアンインストール
!pip uninstall -y tensorflow

# Transformersのインストール
!pip install git+https://github.com/huggingface/transformers
!pip list | grep -E 'transformers|tokenizers'
%%time 
from pathlib import Path
from tokenizers import ByteLevelBPETokenizer

# パス ['oscar.eo.txt']
paths = [str(x) for x in Path(".").glob("**/*.txt")]

# トークナイザーの初期化
tokenizer = ByteLevelBPETokenizer()

# 学習
tokenizer.train(files=paths, vocab_size=52_000, min_frequency=2, special_tokens=[
    "<s>",
    "<pad>",
    "</s>",
    "<unk>",
    "<mask>",
])

モデルを保存します。

# モデルの保存
!mkdir EsperBERTo
tokenizer.save_model("EsperBERTo")
['EsperBERTo/vocab.json', 'EsperBERTo/merges.txt']

以下の2つのファイルが生成されます。

vocab.json : 頻度の高いトークンのリスト

{
    "<s>": 0,
    "<pad>": 1,
    "</s>": 2,
    "<unk>": 3,
    "<mask>": 4,
    "!": 5,
    "\"": 6,
    "#": 7,
    "$": 8,
    "%": 9,
    "&": 10,
    "'": 11,
    "(": 12,
    ")": 13,
  :

・merges.txt : マージのリスト

l a
Ġ k
o n
Ġ la
t a
Ġ e
Ġ d
Ġ p
  :

このトークナイザーは、「エスペラント語」用に最適化されています。英語用のトークナイザーと比較して、より多くの単語を分割されていないトークンで表現できます。エスペラント語で使われるアクセント記号である、ĉ、ĝ、ĝ、ĥ、ĵ、ŝ、ŭなどの発音記号もエンコードされています。また、より効率的な方法でシーケンスを表現しています。今回のコーパスでは、事前学習したGPT-2のトークナイザーと比べて、エンコードされた文字列の平均長さが30%ほど短くなっています。

ここでは、「RoBERTa」のスペシャルトークンの取り扱いを含め、トークナイザーでの使用方法を紹介します。もちろん、Transformersから直接使用することもできます。

トークナイザーを準備します。

from tokenizers.implementations import ByteLevelBPETokenizer
from tokenizers.processors import BertProcessing

# トークナイザーの準備
tokenizer = ByteLevelBPETokenizer(
    "./EsperBERTo/vocab.json",
    "./EsperBERTo/merges.txt",
)

tokenizer._tokenizer.post_processor = BertProcessing(
   ("</s>", tokenizer.token_to_id("</s>")),
   ("<s>", tokenizer.token_to_id("<s>")),
)
tokenizer.enable_truncation(max_length=512)​

エンコードおよびトークン化を行います。

# エンコード
tokenizer.encode("Mi estas Julien.")
Encoding(num_tokens=7, attributes=[ids, type_ids, tokens, offsets, attention_mask, special_tokens_mask, overflowing])
# トークン化
tokenizer.encode("Mi estas Julien.").tokens
['<s>', 'Mi', 'Ġestas', 'ĠJuli', 'en', '.', '</s>']

4. 言語モデルをゼロから学習

このモデルはBERTに似ているので、Masked language modelingのタスク、つまり、データセットの中でランダムにマスクした任意のトークンをどのように埋めるかを予測するタスクで学習します。

◎ GPUの確認
はじめにGPUをチェックします。

# GPUのチェック
!nvidia-smi

2020年9月現在、P100が一番速いGPUになります。

・P100 : 計算時間 250.1sec
・T4 : 計算時間 411.4sec
・K80 : 計算時間 723.3sec

PyTorchがGPUを見ていることを確認します。

# PyTorchがGPUを見ていることを確認
import torch
torch.cuda.is_available()

◎ モデルの準備
モデルの設定を定義します。

from transformers import RobertaConfig

# モデルの設定
config = RobertaConfig(
    vocab_size=52_000,
    max_position_embeddings=514,
    num_attention_heads=12,
    num_hidden_layers=6,
    type_vocab_size=1,
)

Transformersでトークナイザーを再作成します。

from transformers import RobertaTokenizerFast

# トークナイザーの作成
tokenizer = RobertaTokenizerFast.from_pretrained("./EsperBERTo", max_len=512)

最後にモデルを初期化します。

【重要】ゼロからの学習なので、事前学習されたモデルやチェックポイントからではなく、設定からのみで初期化を行います。
from transformers import RobertaForMaskedLM

# モデルの作成
model = RobertaForMaskedLM(config=config)

モデルが84億パラメータであることを確認します。

model.num_parameters()
84095008

◎ 学習データセットの準備
トークナイザーをテキストファイルに適用して、データセットを作成します。

今回は、1つのテキストファイルしかないので、Datasetをカスタマイズする必要はありません。LineByLineDatasetをそのまま使用します。

%%time
from transformers import LineByLineTextDataset

# データセットの作成
dataset = LineByLineTextDataset(
    tokenizer=tokenizer,
    file_path="./oscar.eo.txt",
    block_size=128,
)

run_language_modeling.pyと同様に、data_collatorを定義する必要があります。

これは、データセットの異なるサンプルをバッチ処理して、PyTorchがbackpropを実行する方法を知っているオブジェクトにするための、小さなヘルパーです。

from transformers import DataCollatorForLanguageModeling

# data_collatorの作成
data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer, mlm=True, mlm_probability=0.15
)

◎ トレーナーの準備
トレーナーを作成します。

from transformers import Trainer, TrainingArguments

# トレーナーの引数の作成
training_args = TrainingArguments(
    output_dir="./EsperBERTo",
    overwrite_output_dir=True,
    num_train_epochs=1,
    per_gpu_train_batch_size=64,
    save_steps=10_000,
    save_total_limit=2,
)

# トレーナーの作成
trainer = Trainer(
    model=model,
    args=training_args,
    data_collator=data_collator,
    train_dataset=dataset,
    prediction_loss_only=True,
)

◎ 学習の開始
学習を開始します。

%%time
trainer.train()

最終的なモデル(トークナイザーと設定を含む)を保存します。

trainer.save_model("./EsperBERTo")

5. 言語モデルが学習されていることを確認

学習や評価の損失が減少しているかどうかはさておき、言語モデルが学習されていることを確認する最も簡単な方法は、FillMaskPipelineを使うことです。

パイプラインはトークナイザーやモデルの単純なラッパーで、'fill-mask' はマスクされたトークン(ここでは <mask>)を含むテキストを入力して、最も可能性の高いトークンのリストとその確率を返します。

from transformers import pipeline

# fill-maskパイプラインの作成
fill_mask = pipeline(
    "fill-mask",
    model="./EsperBERTo",
    tokenizer="./EsperBERTo"
)

入力テキストを渡します。

# The sun <mask>.
fill_mask("La suno <mask>.")
[{'score': 0.02119220793247223,
'sequence': '<s> La suno estas.</s>',
'token': 316},
{'score': 0.012403824366629124,
'sequence': '<s> La suno situas.</s>',
'token': 2340},
{'score': 0.011061107739806175,
'sequence': '<s> La suno estis.</s>',
'token': 394},
{'score': 0.008284995332360268,
'sequence': '<s> La suno de.</s>',
'token': 274},
{'score': 0.006471084896475077,
'sequence': '<s> La suno akvo.</s>',
'token': 1833}]

シンプルな構文/文法はうまくいきました。もう少し面白いプロンプトを試してみます。

# This is the beginning of a beautiful <mask>.
fill_mask("Jen la komenco de bela <mask>.")
[{'score': 0.01814725436270237,
'sequence': '<s> Jen la komenco de bela urbo.</s>',
'token': 871},
{'score': 0.015888698399066925,
'sequence': '<s> Jen la komenco de bela vivo.</s>',
'token': 1160},
{'score': 0.015662025660276413,
'sequence': '<s> Jen la komenco de bela tempo.</s>',
'token': 1021},
{'score': 0.015555007383227348,
'sequence': '<s> Jen la komenco de bela mondo.</s>',
'token': 945},
{'score': 0.01412549614906311,
'sequence': '<s> Jen la komenco de bela tago.</s>',
'token': 1633}]

6. モデルの共有

最後に、素敵なモデルができたら、それをコミュニティで共有することを考えてみてください。

(1) CLI( transformers-cli upload)を使ってモデルをアップロード
(2) README.mdモデルカードを書き、model_cards/の下のリポジトリに追加。
モデルカードには、以下が含まれるのが理想的です。

・モデルの説明
・学習パラメータ(データセット、前処理、ハイパーパラメータ)
・評価結果
・意図された用途と制限

モデルは http://huggingface.co/models にページを持っており、誰もが AutoModel.from_pretrained("username/model_name") を使用してロードすることができます。

次回



この記事が気に入ったらサポートをしてみませんか?