How to train a new language model from scratch using Transformers and Tokenizersの翻訳です。
本書は抄訳であり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
過去数ヶ月を通じて我々は、スクラッチで新規言語モデルのトレーニングをこれまで以上に簡単にする目的のもと、transformersとtokenizersライブラリに対して幾つかの改善を加えました。
この記事では、Esperantoに対してどのようにDistilBERTと同じ数のlayerとheadを持っている"小規模な"モデル(84 M parameters = 6 layers, 768 hidden size, 12 attention heads)をトレーニングするのかをデモします。そして、POSタギングの後段タスクにおいてモデルをファインチューニングします。
エスペラントは、学習を容易にするというゴールを持っている人工言語です。いくつかの理由からこのデモではこの言語を選択しています:
- 比較的リソースが少ない言語(それでも200万人の話者がいます)であり、このデモはもう一つの英語モデルをトレーニングよりもつまならないものではありません😁
- 文法は高度に規則正しい(すべての共通的な名詞は-oで終わり、すべての形容詞は-aで終わります)ので、小規模データセットにおいても興味深い言語的な結果を得ることができます。
- 最後に、言語の基盤における非常に重要なゴールは、人々を結びつけることであり、これはNLPコミュニティのゴールともアラインしていると言えるでしょう💚
「注意 この記事を理解するためにエスペラントを理解する必要はありませんが、学びたいのであれば280kのアクティブな学習者がいるDuolingoが良い場所となるでしょう。」
我々のモデルはこう呼ばれるでしょう...お待ちください...EsperBERTo😂
1. データセットの特定
最初に、エスペラントのテキストコーパスを見つけましょう。ここでは、INRIAのOSCAR corpusのエスペラントのポーションを使用します。OSCARは言語分類とWebのCommon Crawlによるダンプのフィルタリングによって得られた膨大な多言語コーパスです。
データセットのエスペラントのポーションはたった299Mなので、ニュース、文献、Wikipediaのような様々なソースからのテキストから構成されるLeipzig Corpora Collectionのエスペラントのサブコーパスと結合します。
最終的なトレーニングコーパスのサイズは3GBで以前として小さいものですが、あなたのモデルにおいては、事前トレーニングするデータを増やせばより良い結果を得られることでしょう。
2. トークナイザーのトレーニング
ここでは、(GPT-2と同じ)バイトレベルのバイトペアエンコーディングトークナイザーとRoBERTaと同じ特殊トークンを使用します。サイズが52,000になるように任意にピックアップしましょう。
バイトレベルのBPEは単一バイトのアルファベットからボキャブラリを構築し始め、すべての単語がトークンに分解されるので、バイトレベルのBPEをトレーニングすることをお勧めします(もはや<unk>
トークンは不要です!)。
#! pip install tokenizers
from pathlib import Path
from tokenizers import ByteLevelBPETokenizer
paths = [str(x) for x in Path("./eo_data/").glob("**/*.txt")]
# Initialize a tokenizer
tokenizer = ByteLevelBPETokenizer()
# Customize training
tokenizer.train(files=paths, vocab_size=52_000, min_frequency=2, special_tokens=[
"<s>",
"<pad>",
"</s>",
"<unk>",
"<mask>",
])
# Save files to disk
tokenizer.save_model(".", "esperberto")
🔥🔥 ワオ、なんて速い! ⚡️🔥
これで、頻度でランクされた高頻度トークンのリストであるvocab.json
とマージのリストであるmerges.txt
を手に入れます。
{
"<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%小さくなっています。
ここでは、tokenizers
での活用方法、RoBERTaの特殊トークンの取り扱い方法を説明しますが、もちろん、これらを直接transformers
から活用することができます。
from tokenizers.implementations import ByteLevelBPETokenizer
from tokenizers.processors import BertProcessing
tokenizer = ByteLevelBPETokenizer(
"./models/EsperBERTo-small/vocab.json",
"./models/EsperBERTo-small/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)
print(
tokenizer.encode("Mi estas Julien.")
)
# Encoding(num_tokens=7, ...)
# tokens: ['<s>', 'Mi', 'Ġestas', 'ĠJuli', 'en', '.', '</s>']
3. スクラッチで言語モデルをトレーニング
アップデート: 関連Colabノートブックでは、スクリプトを通じてではなく、新たなTrainerを直接使用しています。ベストと考えるアプローチを採用してください。
それでは、transformers
のrun_language_modeling.pyスクリプト(よりシームレスにスクラッチからのトレーニングをサポートしているのでrun_lm_finetuning.py
から名称変更されました)を用いて言語モデルをトレーニングしましょう。既存のモデルやチェックポイントからではなく、スクラッチからトレーニングするために-model_name_or_path
をNone
のままにしておくことを覚えておいてください。
「BERTライクのモデルにいくつかの変更を加えたRoBERTaライクのモデルをトレーニングします(詳細はドキュメントをご覧ください)。」
モデルはBERTライクなので、データセットをランダムにマスクした任意のトークンをどのように埋めるのかを予測するタスクであるMasked language modelingのタスクでトレーニングを行います。これは、サンプルスクリプトによってケアされます。
以下の2つのことを行う必要があります:
- 我々のテキストファイルからデータをロードする
Dataset
のシンプルなサブクラスの実装- ご自身のユースケースに応じて、提供されているサンプルのいずれか(
TextDataset
やLineByLineTextDataset
)が動作するのであれば、自分でDatasetのサブクラスを記述する必要すらないかもしれません。しかし、ご自身のコーパスの特性に応じて、様々なカスタム調整を追加したいと考えることでしょう。
- ご自身のユースケースに応じて、提供されているサンプルのいずれか(
- 様々なハイパーパラメーターのセットで実験を行って選択
こちらが、EsperantoDatasetのシンプルなバージョンです。
from torch.utils.data import Dataset
class EsperantoDataset(Dataset):
def __init__(self, evaluate: bool = False):
tokenizer = ByteLevelBPETokenizer(
"./models/EsperBERTo-small/vocab.json",
"./models/EsperBERTo-small/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)
# or use the RobertaTokenizer from `transformers` directly.
self.examples = []
src_files = Path("./data/").glob("*-eval.txt") if evaluate else Path("./data/").glob("*-train.txt")
for src_file in src_files:
print("🔥", src_file)
lines = src_file.read_text(encoding="utf-8").splitlines()
self.examples += [x.ids for x in tokenizer.encode_batch(lines)]
def __len__(self):
return len(self.examples)
def __getitem__(self, i):
# We’ll pad at the batch level.
return torch.tensor(self.examples[i])
データセットが大きい場合には、前処理ステップとしてではなく、温座フライでサンプルをロードしてトークナイズすることにオプトインすることができます。
こちらが、スクリプトに引き渡すハイパーパラメーターと引数の固有のセットです:
--output_dir ./models/EsperBERTo-small-v1
--model_type roberta
--mlm
--config_name ./models/EsperBERTo-small
--tokenizer_name ./models/EsperBERTo-small
--do_train
--do_eval
--learning_rate 1e-4
--num_train_epochs 5
--save_total_limit 2
--save_steps 2000
--per_gpu_train_batch_size 16
--evaluate_during_training
--seed 42
いつものように、ご自身のGPU(s)にフィットする最大のバッチサイズを選択します。
🔥🔥🔥 トレーニングをスタートしましょう!! 🔥🔥🔥
ここでは特定のハイパーパラメータのセットをTensorboardでチェックすることができます。
「我々のサンプルスクリプトは、デフォルトで*runs/*配下にTensorboardフォーマットで記録します。ご自身のボードを参照するために
tensorboard dev upload --logdir runs
を実行すると、ご自身のMLエクスペリメントを誰でも共有できるGoodgeマネージドのホスティングバージョンであるtensorboard.devをセットアップします」
4. 実際にLMがトレーニングされたことをチェック
トレーニングを参照し、評価ロスが削減していく様子を見ることに加え、我々の言語モデルが興味深い何かを学習しているのかどうかをチェックする最も簡単な方法はFillMaskPipeline
を通じたものです。
パイプラインはトークナイザーとモデルに対するシンプルなラッパーであり、fill-mask
することで、マスクされたトークン(ここでは<mask>
)を含むシーケンスを入力することができ、確率とともに最も可能性の高いものが埋められたリストが返却されます。
from transformers import pipeline
fill_mask = pipeline(
"fill-mask",
model="./models/EsperBERTo-small",
tokenizer="./models/EsperBERTo-small"
)
# The sun <mask>.
# =>
result = fill_mask("La suno <mask>.")
# {'score': 0.2526160776615143, 'sequence': '<s> La suno brilis.</s>', 'token': 10820}
# {'score': 0.0999930202960968, 'sequence': '<s> La suno lumis.</s>', 'token': 23833}
# {'score': 0.04382849484682083, 'sequence': '<s> La suno brilas.</s>', 'token': 15006}
# {'score': 0.026011141017079353, 'sequence': '<s> La suno falas.</s>', 'token': 7392}
# {'score': 0.016859788447618484, 'sequence': '<s> La suno pasis.</s>', 'token': 4552}
OK、シンプルな構文/文法が動作しています。より面白いプロンプトにトライしてみましょう:
fill_mask("Jen la komenco de bela <mask>.")
# This is the beginning of a beautiful <mask>.
# =>
# {
# 'score':0.06502299010753632
# 'sequence':'<s> Jen la komenco de bela vivo.</s>'
# 'token':1099
# }
# {
# 'score':0.0421181358397007
# 'sequence':'<s> Jen la komenco de bela vespero.</s>'
# 'token':5100
# }
# {
# 'score':0.024884626269340515
# 'sequence':'<s> Jen la komenco de bela laboro.</s>'
# 'token':1570
# }
# {
# 'score':0.02324388362467289
# 'sequence':'<s> Jen la komenco de bela tago.</s>'
# 'token':1688
# }
# {
# 'score':0.020378097891807556
# 'sequence':'<s> Jen la komenco de bela festo.</s>'
# 'token':4580
# }
“Jen la komenco de bela tago”, 本当です!
より複雑なプロンプトによって、言語モデルがより意味のある知識をキャプチャしていることや、ある種の(統計的な)コモンセンスによる理由づけを行っているかどうかをプローブすることができます。
5. 後段のタスクでLMをファインチューニング
これで、後段タスクである品詞のタグづけにおいて我々の新たな言語モデルをファインチューニングすることができます。
上述したように、エスペラントは単語の末尾が通常文法の品詞の条件になる高度に規則正しい言語です。CoNLL-2003フォーマットのアノテーションされたエスペラントのPOSタグのデータセット(以下のサンプルをご覧ください)を用いて、transformers
のrun_ner.pyスクリプトを活用することができます。
「POSのタグづけはNERと同じようにトークンの分類タスクなので、全く同じスクリプトを活用することができます。」
先ほどと同じように、こちらがこのファインチューニングのTensorboardです。GPUごとにバッチサイズ54を用いて3エポックトレーニングしています。
このタスクは比較的簡単なので、トレーニングと評価ロスは少量の残差に収束していますが、エンドツーエンドでトレーニングできるので楽しいものとなっています😃。
今回は、TokenClassificationPipeline
を使いましょう:
from transformers import TokenClassificationPipeline, pipeline
MODEL_PATH = "./models/EsperBERTo-small-pos/"
nlp = pipeline(
"ner",
model=MODEL_PATH,
tokenizer=MODEL_PATH,
)
# or instantiate a TokenClassificationPipeline directly.
nlp("Mi estas viro kej estas tago varma.")
# {'entity': 'PRON', 'score': 0.9979867339134216, 'word': ' Mi'}
# {'entity': 'VERB', 'score': 0.9683094620704651, 'word': ' estas'}
# {'entity': 'VERB', 'score': 0.9797462821006775, 'word': ' estas'}
# {'entity': 'NOUN', 'score': 0.8509314060211182, 'word': ' tago'}
# {'entity': 'ADJ', 'score': 0.9996201395988464, 'word': ' varma'}
動いているようです! 🔥
NERのよりチャレンジングなデータセットについては、WikiANNのシルバースタンダードデータセットでトレーニングすることができる@stefan-itがお勧めです。
6. モデルを共有 🎉
最後に、素晴らしいモデルを手に入れたらコミュニティに共有することを検討してください:
- CLI:
transformers-cli upload
を用いてモデルをアップロード - README.mdモデルカードを記述し、リポジトリの
model_cards/
配下に追加します。理想的にはモデルカードに以下を含めるべきです:- モデルの説明
- トレーニングパラメーター(データセット、前処理、ハイパーパラメータ)
- 評価結果
- 意図される用途と制限
- 他のなんでも助かります!🤓
以上です!
➡️あなたのモデルには https://huggingface.co/models 上のページがあり、AutoModel.from_pretrained("username/model_name")
を用いて誰でもロードすることができます。
別の言語のモデルを見てみたいのであれば、https://huggingface.co/models をチェックしてください。