🤗

Hugging Face Courseで学ぶ自然言語処理とTransformer 【part4】

8 min read

はじめに

この記事はHugging Face CourseのModels~あたりの内容をベースに自身の解釈なども含めてまとめたものになります。

一個前の記事はこちら
コードの実行は今回もGoogle Colabで行う例になります。

Transformerモデルのインスタンス作成

TransformersライブラリではAutoModelクラスを用いて使いたいモデルのcheckpointを指定することで簡単にモデルを使う準備ができます。


from youtube video "Instantiate a Transformers model" https://www.youtube.com/watch?v=AhChOFRegn4&t=100s

以下、前回の記事でも見た日本語版BERTを使う例です。

from transformers import AutoModel

checkpoint = 'cl-tohoku/bert-base-japanese-whole-word-masking'
model = AutoModel.from_pretrained(checkpoint)

AutoModelはライブラリから呼び出して使えるモデルのラッパークラスになっており、指定したcheckpointから適切なモデルの構造を呼び出してインスタンス化してくれる、非常に優れた仕組みになっています。

使いたいモデルがはっきりしている場合は、そのモデルのクラスを直接使ってインスタンスを作ることも可能です。
BERTを使う場合は、

from transformers import BertConfig, BertModel

config = BertConfig()

model = BertModel(config)

とすればBERTモデルが読み込まれます。

まずはBertConfigを読み込んでいます。
print(config)で中身を確認するとこんな感じです。

BertConfig {
  "attention_probs_dropout_prob": 0.1,
  "gradient_checkpointing": false,
  "hidden_act": "gelu",
  "hidden_dropout_prob": 0.1,
  "hidden_size": 768,
  "initializer_range": 0.02,
  "intermediate_size": 3072,
  "layer_norm_eps": 1e-12,
  "max_position_embeddings": 512,
  "model_type": "bert",
  "num_attention_heads": 12,
  "num_hidden_layers": 12,
  "pad_token_id": 0,
  "position_embedding_type": "absolute",
  "transformers_version": "4.8.2",
  "type_vocab_size": 2,
  "use_cache": true,
  "vocab_size": 30522
}

BertConfigでそれぞれ何の値なのかが説明されていますが、このconfigで設定されているそれぞれの属性の値によってBERTモデルの構造が定義されています。
例えばhidden_sizeは前回に見た、出力される高次元ベクトルの次元数を示しているのがわかると思います。

そして次にこのconfigをBertModelに与えてモデルのインスタンスを作成します。

ただしここで読み込んだBERTは学習前のもので、重みパラメータなどが全て初期化されたものになっています。
このままでは使い物にならないので、データを使って学習させていく必要があるのですが[1]、学習には大量のデータセットが必要ですし、時間・計算リソースが潤沢にないと非効率的です。
そのためTransformersライブラリで用意されている学習済みモデルを使うのが良いですよ、ということですね。

checkpoint = 'cl-tohoku/bert-base-japanese-whole-word-masking'
model = BertModel.from_pretrained(checkpoint)

BertModelからも同様のcheckpointからモデルを読み込むことができますが、AutoModelの方がcheckpointによらないコードの書き方ができるので便利です。

その他

  • 初回モデル読み込み時には重みパラメータのダウンロードが走りますが、2回目以降はローカルにキャッシュされたものから読み込まれるようになっています。
    (デフォルトだと~/.cache/huggingface/transformersにキャッシュが残るようです。)

  • checkpointはモデルごとにModel Hub上で公開さています。
    BERTのcheckpointだけでも1711個もあるみたいです。checkpointは学習タスクやデータセット、言語ごとに色々な種類が用意されているようですが、なかなか多いですね。
    BERT+japaneseで検索すると現時点で14種類ヒットしました。
    日本語対応のものはまだまだ少ないのかなという感じもしますが、最近BERT本[2]なるものが発売され注目されたりしているので、今後プレイヤーが増えていき日本語学習済みモデルの種類も増えていくのではないかという期待が持てます。(私も作ってみたいな・・・)

    モデルの保存&ロード

    モデルの保存&ロードも簡単です。

    model.save_pretrained("<保存先ディレクトリへのパス>")
    

    保存先に指定するディレクトリは未作成の場合でも自動で作ってくれます。
    保存のために出力されるファイルは2種類あります。

    • config.json
    • pytorch_model.bin[3]

    config.jsonの方は上で見たBertConfigで読み込めるものと同じ内容の情報が保存されています。
    pytorch_model.binはバイナリ形式のファイルで、モデルの重みパラメータの情報が格納されています。PyTorchでいうところのstate_dictという、モデルの各レイヤーに対応した重みの情報を保持するオブジェクトに相当するものです。

config.jsonの中身は以下のようになっています。

{
  "_name_or_path": "cl-tohoku/bert-base-japanese-whole-word-masking",
  "architectures": [
    "BertModel"
  ],
  "attention_probs_dropout_prob": 0.1,
  "gradient_checkpointing": false,
  "hidden_act": "gelu",
  "hidden_dropout_prob": 0.1,
  "hidden_size": 768,
  "initializer_range": 0.02,
  "intermediate_size": 3072,
  "layer_norm_eps": 1e-12,
  "max_position_embeddings": 512,
  "model_type": "bert",
  "num_attention_heads": 12,
  "num_hidden_layers": 12,
  "pad_token_id": 0,
  "position_embedding_type": "absolute",
  "tokenizer_class": "BertJapaneseTokenizer",
  "transformers_version": "4.8.2",
  "type_vocab_size": 2,
  "use_cache": true,
  "vocab_size": 32000
}

保存したモデルをもう一度ロードしたい場合はfrom_pretrainedメソッドで保存先のディレクトリパスを指定すればOKです。

saved_model = model.from_pretrained("<保存先ディレクトリへのパス>")
【雑考察】モデルを保存する際にconfigと重み情報が分けられるのは何故だろうか

※雑多な考察です。読み飛ばしていただいて構いません。

configではモデルの構造を保存し、バイナリ形式ファイルの方に重みパラメータの情報を保存しています。
「まとめて保存しても良いのでは?」という気になるかもしれないですが、分けて保存する形になっているのは最近のDeepLearningフレームワークの主流はdefine-by-runという設計になっているなっていることが背景としてあるのかなと思っています。[4]

以前TensorFlowなどではdefine-and-runという「モデルの構造からパラメータまで全て定義したものを実行する」という設計思想を取り入れており[5]、この方式だと一つのファイルにモデルの構造もパラメータも保存される形式でよくてわざわざ分ける必要がありません。
define-and-runは最適化の効率が良い、つまり学習が速いというメリットがある一方、構築されたモデルのネットワーク(計算グラフ)の途中経過を確認しに行ったりするのが面倒、系列データに対して入力ごとに挙動の異なる手法を実装する際に複雑になってしまう...といった課題がありました。

そこを改善するためにChainerやPyTorchで導入されたのがdefine-by-runで、「実行時にモデルの構築もスクリプトの内容に沿って順伝播的にに行われる」という設計になっています。 JupyterNotebookでセルごとに分けて処理が書かれていて順番に実行していくようなイメージでしょうか。Pythonライクな書き方になっているとも言えるでしょう。

define-by-run方式にすればモデルのネットワーク構造自体はスクリプトとして書かれているものを読めば良いので、configでどのスクリプトをどういうパラメータで呼べば良いかを指定、重み情報のみ別で保存 といった形に自然となったのではないかなと思っています。ネットワーク構造をファイルに保存する必要がないため、こっちの方がファイルのサイズも小さくすることができるしいいじゃん、というのもありそうです。

モデルの推論処理

Transformerモデルに対して推論処理を行いたい対象となるテキストデータを用意します。
前回見たように、テキストデータはそのままではモデルに入力できず、Tokenizerによってテンソル形式の数値データに変換させる必要があります。
Tokenizerについては次の記事でまとめて網羅したいので、ここではとりあえずAutoTokenizerで読み込んだものを使います。

from transformers import AutoTokenizer

# 入力テキスト
sequences = [
  "こんにちは。",
  "今日も暑いね。",
  "いいね、それ!"
]

# AutoTokenizerでモデルと同じcheckpointからtokenizerを読み込む
tokenizer = AutoTokenizer.from_pretrained(checkpoint)

# 入力テキストをトークン化し、テンソル型へ変換する。
encoded_sequences = tokenizer(sequences, padding=True, truncation=True, return_tensors='pt')

print(encoded_sequences)

出力結果

{'input_ids': tensor([[    2, 10350, 25746, 28450,     8,     3,     0,     0],
        [    2,  3246,    28, 16860, 28457,  1852,     8,     3],
        [    2,  2575,  1852,     6,   218,   679,     3,     0]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 0, 0],
        [1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 0]])}

モデルへ入力するのはこのうちinput_idsに含まれるものになります。

outputs = model(encoded_sequences.input_ids)

modelへの引数argsは他にもいくつかありますが、とりあえず必須なのはこのinput_idsのみです。

まとめ

モデルをロードしてインスタンスを作成する方法、保存方法、推論処理の実行方法について確認できました。
これまで何度か同じようなことはやってはいつつも、ちょっとずつ深入りしてきているのかなという感じです。
今回は内容的に少なく物足りない気もしたので役に立たない雑な考察なども差し込んでみましたが、間違ったこと書いてそうで怖い・・・。何かあればご指摘お願いします。🙇‍♂️

次回はTokenizerあたりについて見ていきます。

脚注
  1. https://towardsdatascience.com/how-to-train-a-bert-model-from-scratch-72cfce554fc6
    Transformerモデルを1から学習させたい場合はこちらの記事など参考になるかなと思います。 ↩︎

  2. https://www.ohmsha.co.jp/book/9784274227264/
    BERTの使用例をググっても英語のデータに対するものが多い中、日本語のデータに対する体系的な解説をされているありがたい一冊となっていると思います(まだ全然読めていない)。 ↩︎

  3. TensorFlowの場合はtf_model.h5が出力される。 ↩︎

  4. define-by-runあたりの話は
    https://www.slideshare.net/unnonouno/chainer-59664785
    https://anieca.work/archives/62
    あたりのスライドや記事が参考になるかなと思います。 ↩︎

  5. https://qiita.com/halhorn/items/09a64e98a02022e6ccc2
    TensorFlowもver2.0以降はdefine-by-run方式がデフォルトになっています。 ↩︎