Transformers documentation

Phân loại token

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.1).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Phân loại token

Phân loại token gán một nhãn cho từng token riêng lẻ trong một câu. Một trong những tác vụ phân loại token phổ biến nhất là Nhận thực thể có tên (NER). NER cố gắng tìm một nhãn cho mỗi thực thể trong câu, chẳng hạn như một người, địa điểm hoặc tổ chức.

Hướng dẫn này sẽ cho bạn thấy cách:

  1. Huấn luyện lại DistilBERT trên bộ dữ liệu WNUT 17 để phát hiện các thực thể mới.
  2. Sử dụng mô hình đã tinh chỉnh của bạn để suy luận.

Để xem tất cả các kiến trúc và checkpoint tương thích với tác vụ này, chúng tôi khuyến nghị bạn kiểm tra trang tác vụ.

Trước khi bắt đầu, hãy đảm bảo bạn đã cài đặt tất cả các thư viện cần thiết:

pip install transformers datasets evaluate seqeval

Chúng tôi khuyến khích bạn đăng nhập vào tài khoản Hugging Face của mình để có thể tải lên và chia sẻ mô hình của bạn với cộng đồng. Khi được yêu cầu, hãy nhập token của bạn để đăng nhập:

>>> from huggingface_hub import notebook_login

>>> notebook_login()

Tải bộ dữ liệu WNUT 17

Bắt đầu bằng cách tải bộ dữ liệu WNUT 17 từ thư viện 🤗 Datasets:

>>> from datasets import load_dataset

>>> wnut = load_dataset("flaitenberger/wnut_17")

Sau đó, hãy xem một ví dụ:

>>> 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', '.']
}

Mỗi số trong ner_tags đại diện cho một thực thể. Chuyển đổi các số thành tên nhãn của chúng để tìm hiểu các thực thể là gì:

>>> 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",
]

Chữ cái đứng trước mỗi ner_tag cho biết vị trí token của thực thể:

  • B- chỉ ra sự bắt đầu của một thực thể.
  • I- cho biết một token nằm bên trong cùng một thực thể (ví dụ, token State là một phần của thực thể như Empire State Building).
  • 0 cho thấy token không tương ứng với bất kỳ thực thể nào.

Tiền xử lý

Bước tiếp theo là nạp một tokenizer DistilBERT để tiền xử lý trường tokens:

>>> from transformers import AutoTokenizer

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

Như bạn đã thấy trong trường tokens ở ví dụ trên, có vẻ như đầu vào đã được tách token. Nhưng thực tế đầu vào vẫn chưa được tách token và bạn sẽ cần đặt is_split_into_words=True để tách các từ thành các subword. Ví dụ:

>>> 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]']

Tuy nhiên, điều này thêm một số token đặc biệt [CLS] và [SEP] và việc phân tách subword tạo ra sự không khớp giữa đầu vào và nhãn. Một từ duy nhất tương ứng với một nhãn duy nhất giờ có thể bị tách thành hai subword. Bạn sẽ cần căn chỉnh lại các token và nhãn bằng cách:

  1. Ánh xạ tất cả các token sang từ tương ứng của chúng bằng phương thức word_ids.
  2. Gán nhãn -100 cho các token đặc biệt [CLS] và [SEP] để chúng bị bỏ qua bởi hàm loss của PyTorch (xem CrossEntropyLoss).
  3. Chỉ gán nhãn cho token đầu tiên của một từ nhất định. Gán -100 cho các subtoken khác từ cùng từ đó.

Dưới đây là cách bạn có thể tạo một hàm để căn chỉnh lại các token và nhãn, đồng thời cắt ngắn các chuỗi để không dài hơn chiều dài đầu vào tối đa của 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

Để áp dụng hàm tiền xử lý lên toàn bộ bộ dữ liệu, hãy sử dụng hàm map của 🤗 Datasets. Bạn có thể tăng tốc hàm map bằng cách đặt batched=True để xử lý nhiều phần tử của bộ dữ liệu cùng lúc:

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

Bây giờ hãy tạo một batch các ví dụ bằng cách sử dụng DataCollatorWithPadding. Việc đệm động các câu đến độ dài dài nhất trong một batch trong quá trình collation sẽ hiệu quả hơn so với việc đệm toàn bộ dataset đến độ dài tối đa.

>>> from transformers import DataCollatorForTokenClassification

>>> data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer)

Đánh giá

Việc đưa một chỉ số vào trong quá trình huấn luyện thường hữu ích để đánh giá hiệu suất của mô hình. Bạn có thể nhanh chóng tải một phương pháp đánh giá bằng thư viện 🤗 Evaluate. Đối với tác vụ này, hãy tải framework seqeval (xem quick tour của 🤗 Evaluate để tìm hiểu thêm về cách tải và tính toán một chỉ số). Seqeval thực sự tạo ra một số điểm số: precision, recall, F1 và accuracy.

>>> import evaluate

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

Trước tiên, hãy lấy các nhãn NER, sau đó tạo một hàm truyền các dự đoán thực và nhãn thực của bạn vào compute để tính toán các điểm số:

>>> 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"],
...     }

Hàm compute_metrics của bạn đã sẵn sàng, và bạn sẽ quay lại với nó khi thiết lập quá trình huấn luyện.

Huấn luyện

Trước khi bắt đầu huấn luyện mô hình của bạn, hãy tạo một bản đồ ánh xạ các id dự kiến tương ứng với nhãn của chúng bằng id2label và label2id:

>>> 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,
... }

Nếu bạn chưa quen với việc tinh chỉnh mô hình bằng Trainer, hãy xem hướng dẫn cơ bản tại đây!

Bạn đã sẵn sàng để bắt đầu huấn luyện mô hình của mình! Hãy nạp DistilBERT bằng AutoModelForTokenClassification cùng với số nhãn dự kiến và các ánh xạ nhãn:

>>> from transformers import AutoModelForTokenClassification, TrainingArguments, Trainer

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

Tới bước này, chỉ còn ba bước nữa:

  1. Định nghĩa các siêu tham số huấn luyện của bạn trong TrainingArguments. Tham số bắt buộc duy nhất là output_dir, chỉ định nơi lưu trữ mô hình của bạn. Bạn sẽ đẩy mô hình này lên Hub bằng cách đặt push_to_hub=True (bạn cần đăng nhập vào Hugging Face để tải lên mô hình). Vào cuối mỗi epoch, Trainer sẽ đánh giá các điểm seqeval và lưu checkpoint huấn luyện.
  2. Truyền các đối số huấn luyện vào Trainer cùng với mô hình, tập dữ liệu, tokenizer, data collator và hàm compute_metrics.
  3. Gọi train() để tinh chỉnh mô hình của bạn.
>>> 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()

Khi quá trình huấn luyện hoàn tất, hãy chia sẻ mô hình của bạn lên Hub bằng phương thức push_to_hub() để mọi người có thể sử dụng mô hình của bạn:

>>> trainer.push_to_hub()

Để xem một ví dụ chi tiết hơn về cách tinh chỉnh mô hình cho phân loại token, hãy xem notebook PyTorch tương ứng.

Suy luận

Tuyệt, giờ đây bạn đã tinh chỉnh một mô hình, bạn có thể sử dụng nó để suy luận!

Lấy một đoạn văn bản mà bạn muốn chạy suy luận:

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

Cách đơn giản nhất để thử nghiệm mô hình đã tinh chỉnh của bạn cho suy luận là sử dụng nó trong một pipeline(). Khởi tạo một pipeline cho NER với mô hình của bạn và truyền văn bản của bạn vào đó:

>>> 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}]

Bạn cũng có thể tự động tái tạo kết quả của pipeline nếu muốn:

Phân tách token văn bản và trả về các tensor PyTorch:

>>> from transformers import AutoTokenizer

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

Truyền các đầu vào của bạn vào mô hình và trả về logits:

>>> from transformers import AutoModelForTokenClassification

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

Lấy lớp có xác suất cao nhất và sử dụng ánh xạ id2label của mô hình để chuyển đổi nó thành nhãn văn bản:

>>> 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']
Update on GitHub