#
Token classification

トークン分類では、文内の個々のトークンにラベルを割り当てます。最も一般的なトークン分類タスクの 1 つは、固有表現認識 (NER) です。 NER は、人、場所、組織など、文内の各エンティティのラベルを見つけようとします。

このガイドでは、次の方法を説明します。

1. WNUT 17 データセットで DistilBERT を微調整して、新しいエンティティを検出します。
2. 微調整されたモデルを推論に使用します。

このタスクと互換性のあるすべてのアーキテクチャとチェックポイントを確認するには、タスクページ を確認することをお勧めします。

始める前に、必要なライブラリがすべてインストールされていることを確認してください。

    pip install transformers datasets evaluate seqeval

モデルをアップロードしてコミュニティと共有できるように、Hugging Face アカウントにログインすることをお勧めします。プロンプトが表示されたら、トークンを入力してログインします。

    >>> from huggingface_hub import notebook_login

    >>> notebook_login()

##
Load WNUT 17 dataset

まず、🤗 データセット ライブラリから WNUT 17 データセットをロードします。

    >>> from datasets import load_dataset

    >>> wnut = load_dataset("wnut_17")

次に、例を見てみましょう。

    >>> wnut["train"][0]
    {'id': '0',
     'ner_tags': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0],
     'tokens': ['@paulwalk', 'It', "'s", 'the', 'view', 'from', 'where', 'I', "'m", 'living', 'for', 'two', 'weeks', '.', 'Empire', 'State', 'Building', '=', 'ESB', '.', 'Pretty', 'bad', 'storm', 'here', 'last', 'evening', '.']
    }

`ner_tags`内の各数字はエンティティを表します。数値をラベル名に変換して、エンティティが何であるかを調べます。

    >>> label_list = wnut["train"].features[f"ner_tags"].feature.names
    >>> label_list
    [
        "O",
        "B-corporation",
        "I-corporation",
        "B-creative-work",
        "I-creative-work",
        "B-group",
        "I-group",
        "B-location",
        "I-location",
        "B-person",
        "I-person",
        "B-product",
        "I-product",
    ]

各 `ner_tag` の前に付く文字は、エンティティのトークンの位置を示します。

- `B-` はエンティティの始まりを示します。
- `I-` は、トークンが同じエンティティ内に含まれていることを示します (たとえば、`State` トークンは次のようなエンティティの一部です) `Empire State Building`）。
- `0` は、トークンがどのエンティティにも対応しないことを示します。

##
Preprocess

次のステップでは、DistilBERT トークナイザーをロードして`tokens`フィールドを前処理します。

    >>> from transformers import AutoTokenizer

    >>> tokenizer = AutoTokenizer.from_pretrained("distilbert/distilbert-base-uncased")

上の `tokens`フィールドの例で見たように、入力はすでにトークン化されているようです。しかし、実際には入力はまだトークン化されていないため、単語をサブワードにトークン化するには`is_split_into_words=True` を設定する必要があります。例えば：

    >>> example = wnut["train"][0]
    >>> tokenized_input = tokenizer(example["tokens"], is_split_into_words=True)
    >>> tokens = tokenizer.convert_ids_to_tokens(tokenized_input["input_ids"])
    >>> tokens
    ['[CLS]', '@', 'paul', '##walk', 'it', "'", 's', 'the', 'view', 'from', 'where', 'i', "'", 'm', 'living', 'for', 'two', 'weeks', '.', 'empire', 'state', 'building', '=', 'es', '##b', '.', 'pretty', 'bad', 'storm', 'here', 'last', 'evening', '.', '[SEP]']

ただし、これによりいくつかの特別なトークン `[CLS]` と `[SEP]` が追加され、サブワードのトークン化により入力とラベルの間に不一致が生じます。 1 つのラベルに対応する 1 つの単語を 2 つのサブワードに分割できるようになりました。次の方法でトークンとラベルを再調整する必要があります。

1. `word_ids` メソッドを使用して、すべてのトークンを対応する単語にマッピングします。
2. 特別なトークン `[CLS]` と `[SEP]` にラベル `-100` を割り当て、それらが PyTorch 損失関数によって無視されるようにします (CrossEntropyLoss)。
3. 特定の単語の最初のトークンのみにラベルを付けます。同じ単語の他のサブトークンに `-100`を割り当てます。

トークンとラベルを再調整し、シーケンスを DistilBERT の最大入力長以下に切り詰める関数を作成する方法を次に示します。

    >>> def tokenize_and_align_labels(examples):
    ...     tokenized_inputs = tokenizer(examples["tokens"], truncation=True, is_split_into_words=True)

    ...     labels = []
    ...     for i, label in enumerate(examples[f"ner_tags"]):
    ...         word_ids = tokenized_inputs.word_ids(batch_index=i)  # Map tokens to their respective word.
    ...         previous_word_idx = None
    ...         label_ids = []
    ...         for word_idx in word_ids:  # Set the special tokens to -100.
    ...             if word_idx is None:
    ...                 label_ids.append(-100)
    ...             elif word_idx != previous_word_idx:  # Only label the first token of a given word.
    ...                 label_ids.append(label[word_idx])
    ...             else:
    ...                 label_ids.append(-100)
    ...             previous_word_idx = word_idx
    ...         labels.append(label_ids)

    ...     tokenized_inputs["labels"] = labels
    ...     return tokenized_inputs

データセット全体に前処理関数を適用するには、🤗 Datasets `map` 関数を使用します。 `batched=True` を設定してデータセットの複数の要素を一度に処理することで、`map` 関数を高速化できます。

    >>> tokenized_wnut = wnut.map(tokenize_and_align_labels, batched=True)

次に、`DataCollat​​orWithPadding` を使用してサンプルのバッチを作成します。データセット全体を最大長までパディングするのではなく、照合中にバッチ内の最長の長さまで文を 動的にパディング する方が効率的です。

Pytorch
Hide Pytorch content

    >>> from transformers import DataCollatorForTokenClassification

    >>> data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer)

TensorFlow
Hide TensorFlow content

    >>> from transformers import DataCollatorForTokenClassification

    >>> data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer, return_tensors="tf")

##
Evaluate

トレーニング中にメトリクスを含めると、多くの場合、モデルのパフォーマンスを評価するのに役立ちます。 🤗 Evaluate ライブラリを使用して、評価メソッドをすばやくロードできます。このタスクでは、seqeval フレームワークを読み込みます (🤗 Evaluate クイック ツアー を参照してください) ) メトリクスの読み込みと計算の方法について詳しくは、こちらをご覧ください)。 Seqeval は実際に、精度、再現率、F1、精度などのいくつかのスコアを生成します。

    >>> import evaluate

    >>> seqeval = evaluate.load("seqeval")

まず NER ラベルを取得してから、真の予測と真のラベルを `compute` に渡してスコアを計算する関数を作成します。

    >>> import numpy as np

    >>> labels = [label_list[i] for i in example[f"ner_tags"]]

    >>> def compute_metrics(p):
    ...     predictions, labels = p
    ...     predictions = np.argmax(predictions, axis=2)

    ...     true_predictions = [
    ...         [label_list[p] for (p, l) in zip(prediction, label) if l != -100]
    ...         for prediction, label in zip(predictions, labels)
    ...     ]
    ...     true_labels = [
    ...         [label_list[l] for (p, l) in zip(prediction, label) if l != -100]
    ...         for prediction, label in zip(predictions, labels)
    ...     ]

    ...     results = seqeval.compute(predictions=true_predictions, references=true_labels)
    ...     return {
    ...         "precision": results["overall_precision"],
    ...         "recall": results["overall_recall"],
    ...         "f1": results["overall_f1"],
    ...         "accuracy": results["overall_accuracy"],
    ...     }

これで`compute_metrics`関数の準備が整いました。トレーニングをセットアップするときにこの関数に戻ります。

##
Train

モデルのトレーニングを開始する前に、`id2label`と`label2id`を使用して、予想される ID とそのラベルのマップを作成します。

    >>> id2label = {
    ...     0: "O",
    ...     1: "B-corporation",
    ...     2: "I-corporation",
    ...     3: "B-creative-work",
    ...     4: "I-creative-work",
    ...     5: "B-group",
    ...     6: "I-group",
    ...     7: "B-location",
    ...     8: "I-location",
    ...     9: "B-person",
    ...     10: "I-person",
    ...     11: "B-product",
    ...     12: "I-product",
    ... }
    >>> label2id = {
    ...     "O": 0,
    ...     "B-corporation": 1,
    ...     "I-corporation": 2,
    ...     "B-creative-work": 3,
    ...     "I-creative-work": 4,
    ...     "B-group": 5,
    ...     "I-group": 6,
    ...     "B-location": 7,
    ...     "I-location": 8,
    ...     "B-person": 9,
    ...     "I-person": 10,
    ...     "B-product": 11,
    ...     "I-product": 12,
    ... }

Pytorch
Hide Pytorch content

Trainer を使用したモデルの微調整に慣れていない場合は、ここ の基本的なチュートリアルをご覧ください。

これでモデルのトレーニングを開始する準備が整いました。 AutoModelForTokenClassification を使用して、予期されるラベルの数とラベル マッピングを指定して DistilBERT を読み込みます。

    >>> from transformers import AutoModelForTokenClassification, TrainingArguments, Trainer

    >>> model = AutoModelForTokenClassification.from_pretrained(
    ...     "distilbert/distilbert-base-uncased", num_labels=13, id2label=id2label, label2id=label2id
    ... )

この時点で残っているステップは 3 つだけです。

1. TrainingArguments でトレーニング ハイパーパラメータを定義します。唯一の必須パラメータは、モデルの保存場所を指定する `output_dir` です。 `push_to_hub=True`を設定して、このモデルをハブにプッシュします (モデルをアップロードするには、Hugging Face にサインインする必要があります)。各エポックの終了時に、Trainer は連続スコアを評価し、トレーニング チェックポイントを保存します。
2. トレーニング引数を、モデル、データセット、トークナイザー、データ照合器、および `compute_metrics` 関数とともに Trainer に渡します。
3. train() を呼び出してモデルを微調整します。

    >>> training_args = TrainingArguments(
    ...     output_dir="my_awesome_wnut_model",
    ...     learning_rate=2e-5,
    ...     per_device_train_batch_size=16,
    ...     per_device_eval_batch_size=16,
    ...     num_train_epochs=2,
    ...     weight_decay=0.01,
    ...     eval_strategy="epoch",
    ...     save_strategy="epoch",
    ...     load_best_model_at_end=True,
    ...     push_to_hub=True,
    ... )

    >>> trainer = Trainer(
    ...     model=model,
    ...     args=training_args,
    ...     train_dataset=tokenized_wnut["train"],
    ...     eval_dataset=tokenized_wnut["test"],
    ...     processing_class=tokenizer,
    ...     data_collator=data_collator,
    ...     compute_metrics=compute_metrics,
    ... )

    >>> trainer.train()

トレーニングが完了したら、 push_to_hub() メソッドを使用してモデルをハブに共有し、誰もがモデルを使用できるようにします。

    >>> trainer.push_to_hub()

TensorFlow
Hide TensorFlow content

Keras を使用したモデルの微調整に慣れていない場合は、こちら の基本的なチュートリアルをご覧ください。

TensorFlow でモデルを微調整するには、オプティマイザー関数、学習率スケジュール、およびいくつかのトレーニング ハイパーパラメーターをセットアップすることから始めます。

    >>> from transformers import create_optimizer

    >>> batch_size = 16
    >>> num_train_epochs = 3
    >>> num_train_steps = (len(tokenized_wnut["train"]) // batch_size) * num_train_epochs
    >>> optimizer, lr_schedule = create_optimizer(
    ...     init_lr=2e-5,
    ...     num_train_steps=num_train_steps,
    ...     weight_decay_rate=0.01,
    ...     num_warmup_steps=0,
    ... )

次に、TFAutoModelForTokenClassification を使用して、予期されるラベルの数とラベル マッピングを指定して DistilBERT をロードできます。

    >>> from transformers import TFAutoModelForTokenClassification

    >>> model = TFAutoModelForTokenClassification.from_pretrained(
    ...     "distilbert/distilbert-base-uncased", num_labels=13, id2label=id2label, label2id=label2id
    ... )

prepare_tf_dataset() を使用して、データセットを `tf.data.Dataset` 形式に変換します。

    >>> tf_train_set = model.prepare_tf_dataset(
    ...     tokenized_wnut["train"],
    ...     shuffle=True,
    ...     batch_size=16,
    ...     collate_fn=data_collator,
    ... )

    >>> tf_validation_set = model.prepare_tf_dataset(
    ...     tokenized_wnut["validation"],
    ...     shuffle=False,
    ...     batch_size=16,
    ...     collate_fn=data_collator,
    ... )

`compile` を使用してトレーニング用のモデルを設定します。 Transformers モデルにはすべてデフォルトのタスク関連の損失関数があるため、次の場合を除き、損失関数を指定する必要はないことに注意してください。

    >>> import tensorflow as tf

    >>> model.compile(optimizer=optimizer)  # No loss argument!

トレーニングを開始する前にセットアップする最後の 2 つのことは、予測から連続スコアを計算することと、モデルをハブにプッシュする方法を提供することです。どちらも Keras コールバック を使用して行われます。

`compute_metrics` 関数を KerasMetricCallback に渡します。

    >>> from transformers.keras_callbacks import KerasMetricCallback

    >>> metric_callback = KerasMetricCallback(metric_fn=compute_metrics, eval_dataset=tf_validation_set)

PushToHubCallback でモデルとトークナイザーをプッシュする場所を指定します。

    >>> from transformers.keras_callbacks import PushToHubCallback

    >>> push_to_hub_callback = PushToHubCallback(
    ...     output_dir="my_awesome_wnut_model",
    ...     tokenizer=tokenizer,
    ... )

次に、コールバックをまとめてバンドルします。

    >>> callbacks = [metric_callback, push_to_hub_callback]

ついに、モデルのトレーニングを開始する準備が整いました。トレーニングおよび検証データセット、エポック数、コールバックを指定して `fit` を呼び出し、モデルを微調整します。

    >>> model.fit(x=tf_train_set, validation_data=tf_validation_set, epochs=3, callbacks=callbacks)

トレーニングが完了すると、モデルは自動的にハブにアップロードされ、誰でも使用できるようになります。

トークン分類のモデルを微調整する方法のより詳細な例については、対応するセクションを参照してください。 PyTorch ノートブック または TensorFlow ノートブック。

##
Inference

モデルを微調整したので、それを推論に使用できるようになりました。

推論を実行したいテキストをいくつか取得します。

    >>> text = "The Golden State Warriors are an American professional basketball team based in San Francisco."

推論用に微調整されたモデルを試す最も簡単な方法は、それを pipeline() で使用することです。モデルを使用して NER の`pipeline`をインスタンス化し、テキストをそれに渡します。

    >>> from transformers import pipeline

    >>> classifier = pipeline("ner", model="stevhliu/my_awesome_wnut_model")
    >>> classifier(text)
    [{'entity': 'B-location',
      'score': 0.42658573,
      'index': 2,
      'word': 'golden',
      'start': 4,
      'end': 10},
     {'entity': 'I-location',
      'score': 0.35856336,
      'index': 3,
      'word': 'state',
      'start': 11,
      'end': 16},
     {'entity': 'B-group',
      'score': 0.3064001,
      'index': 4,
      'word': 'warriors',
      'start': 17,
      'end': 25},
     {'entity': 'B-location',
      'score': 0.65523505,
      'index': 13,
      'word': 'san',
      'start': 80,
      'end': 83},
     {'entity': 'B-location',
      'score': 0.4668663,
      'index': 14,
      'word': 'francisco',
      'start': 84,
      'end': 93}]

必要に応じて、`pipeline`の結果を手動で複製することもできます。

Pytorch
Hide Pytorch content

テキストをトークン化して PyTorch テンソルを返します。

    >>> from transformers import AutoTokenizer

    >>> tokenizer = AutoTokenizer.from_pretrained("stevhliu/my_awesome_wnut_model")
    >>> inputs = tokenizer(text, return_tensors="pt")

入力をモデルに渡し、`logits`を返します。

    >>> from transformers import AutoModelForTokenClassification

    >>> model = AutoModelForTokenClassification.from_pretrained("stevhliu/my_awesome_wnut_model")
    >>> with torch.no_grad():
    ...     logits = model(**inputs).logits

最も高い確率でクラスを取得し、モデルの `id2label` マッピングを使用してそれをテキスト ラベルに変換します。

    >>> predictions = torch.argmax(logits, dim=2)
    >>> predicted_token_class = [model.config.id2label[t.item()] for t in predictions[0]]
    >>> predicted_token_class
    ['O',
     'O',
     'B-location',
     'I-location',
     'B-group',
     'O',
     'O',
     'O',
     'O',
     'O',
     'O',
     'O',
     'O',
     'B-location',
     'B-location',
     'O',
     'O']

TensorFlow
Hide TensorFlow content

テキストをトークン化し、TensorFlow テンソルを返します。

    >>> from transformers import AutoTokenizer

    >>> tokenizer = AutoTokenizer.from_pretrained("stevhliu/my_awesome_wnut_model")
    >>> inputs = tokenizer(text, return_tensors="tf")

入力をモデルに渡し、`logits`を返します。

    >>> from transformers import TFAutoModelForTokenClassification

    >>> model = TFAutoModelForTokenClassification.from_pretrained("stevhliu/my_awesome_wnut_model")
    >>> logits = model(**inputs).logits

最も高い確率でクラスを取得し、モデルの `id2label` マッピングを使用してそれをテキスト ラベルに変換します。

    >>> predicted_token_class_ids = tf.math.argmax(logits, axis=-1)
    >>> predicted_token_class = [model.config.id2label[t] for t in predicted_token_class_ids[0].numpy().tolist()]
    >>> predicted_token_class
    ['O',
     'O',
     'B-location',
     'I-location',
     'B-group',
     'O',
     'O',
     'O',
     'O',
     'O',
     'O',
     'O',
     'O',
     'B-location',
     'B-location',
     'O',
     'O']

< > Update on GitHub