Dịch máy
Bây giờ chúng ta hãy đi sâu vào dịch máy. Đây là một tác vụ chuỗi sang chuỗi, có nghĩa là đây là một vấn đề có thể được hình thành như đi từ một chuỗi này sang chuỗi khác. Theo nghĩa đó, vấn đề khá giống với tóm tắt và bạn có thể điều chỉnh những gì chúng ta sẽ thấy ở đây thành các vấn đề chuỗi sang chuỗi khác như:
- Chuyển văn phong: Tạo mô hình dịch văn bản được viết theo một phong cách nhất định sang một phong cách khác (ví dụ: từ trang trọng sang thông thường hoặc tiếng Anh Shakespearean sang tiếng Anh hiện đại)
- Hỏi đáp chung: Tạo một mô hình tạo câu trả lời cho các câu hỏi, dựa trên ngữ cảnh
Nếu bạn có một kho văn bản đủ lớn với hai (hoặc nhiều) ngôn ngữ, bạn có thể huấn luyện một mô hình dịch mới từ đầu giống như chúng ta sẽ làm trong phần lập mô hình ngôn ngữ nhân quả. Tuy nhiên, sẽ nhanh hơn nếu tinh chỉnh mô hình dịch hiện có, có thể là mô hình đa ngôn ngữ như mT5 hoặc mBART mà bạn muốn tinh chỉnh cho phù hợp với một cặp ngôn ngữ cụ thể hoặc thậm chí là một mô hình chuyên dụng để dịch từ ngôn ngữ này sang ngôn ngữ khác mà bạn muốn tinh chỉnh để phù hợp với kho dữ liệu cụ thể của mình.
Trong phần này, chúng ta sẽ tinh chỉnh mô hình Marian được huấn luyện trước để dịch từ tiếng Anh sang tiếng Pháp (vì rất nhiều nhân viên của Hugging Face nói cả hai ngôn ngữ đó) trên tập dữ liệu KDE4, là tập dữ liệu các tệp được bản địa hóa cho ứng dụng KDE. Mô hình chúng ta sẽ sử dụng đã được huấn luyện trước trên một kho dữ liệu lớn gồm các văn bản tiếng Pháp và tiếng Anh được lấy từ Tập dữ liệu Opus, thực sự chứa tập dữ liệu KDE4. Nhưng ngay cả khi mô hình huấn luyện trước mà chúng ta sử dụng đã nhìn thấy dữ liệu đó trong quá trình huấn luyện trước của nó, chúng ta sẽ thấy rằng ta có thể nhận được phiên bản tốt hơn của nó sau khi tinh chỉnh.
Sau khi hoàn thành, chúng ta sẽ có một mô hình có thể đưa ra các dự đoán như sau:
Như trong các phần trước, bạn có thể tìm thấy mô hình thực tế mà chúng ta sẽ huấn luyện và tải lên Hub bằng cách sử dụng đoạn mã bên dưới và kiểm tra kỹ các dự đoán của nó tại đây.
Chuẩn bị dữ liệu
Để tinh chỉnh hoặc huấn luyện một mô hình dịch từ đầu, chúng ta sẽ cần một tập dữ liệu phù hợp với tác vụ. Như đã đề cập trước đây, chúng ta sẽ sử dụng tập dữ liệu KDE4 trong phần này, nhưng bạn có thể điều chỉnh đoạn mã để sử dụng dữ liệu của riêng mình khá dễ dàng, miễn là bạn có các cặp của các câu bằng hai ngôn ngữ mà bạn muốn dịch từ và tới. Tham khảo lại Chương 5 nếu bạn cần lời nhắc về cách tải dữ liệu tùy chỉnh của mình trong Dataset
.
Bộ dữ liệu KDE4
Như thường lệ, chúng ta tải xuống tập dữ liệu của mình bằng cách sử dụng hàm load_dataset()
:
from datasets import load_dataset
raw_datasets = load_dataset("kde4", lang1="en", lang2="fr")
Nếu bạn muốn làm việc với một cặp ngôn ngữ khác, bạn có thể chỉ định chúng bằng đoạn mã của chúng. Có tổng số 92 ngôn ngữ có sẵn cho bộ dữ liệu này; bạn có thể thấy tất cả chúng bằng cách mở rộng các thẻ ngôn ngữ trên thẻ dữ liệu của nó.
Hãy xem tập dữ liệu:
raw_datasets
DatasetDict({
train: Dataset({
features: ['id', 'translation'],
num_rows: 210173
})
})
Chúng ta có 210,173 cặp câu, nhưng chỉ trong một lần tách, vì vậy chúng ta sẽ cần tạo bộ kiểm định của riêng mình. Như chúng ta đã thấy trong Chương 5, Dataset
có phương thức train_test_split()
có thể giúp chúng ta. Chúng ta sẽ cung cấp một hạt giống (seed) cho khả năng tái tạo:
split_datasets = raw_datasets["train"].train_test_split(train_size=0.9, seed=20)
split_datasets
DatasetDict({
train: Dataset({
features: ['id', 'translation'],
num_rows: 189155
})
test: Dataset({
features: ['id', 'translation'],
num_rows: 21018
})
})
Bạn có thể đổi "test"
thành "validation"
như sau:
split_datasets["validation"] = split_datasets.pop("test")
Bây giờ chúng ta hãy xem xét một phần tử của tập dữ liệu:
split_datasets["train"][1]["translation"]
{'en': 'Default to expanded threads',
'fr': 'Par défaut, développer les fils de discussion'}
Chúng ta nhận được một từ điển có hai câu bằng cặp ngôn ngữ mà ta yêu cầu. Một điểm đặc biệt của bộ dữ liệu đầy đủ các thuật ngữ khoa học máy tính kỹ thuật này là chúng đều được dịch hoàn toàn bằng tiếng Pháp. Tuy nhiên, các kỹ sư Pháp thường lười biếng và để lại hầu hết các từ chuyên ngành khoa học máy tính bằng tiếng Anh khi họ nói chuyện. Ví dụ, ở đây, từ “threads” có thể xuất hiện trong một câu tiếng Pháp, đặc biệt là trong một cuộc trò chuyện kỹ thuật; nhưng trong tập dữ liệu này, nó đã được dịch thành đúng hơn là “fils de discussion”. Mô hình huấn luyện trước mà chúng ta sử dụng, đã được huấn luyện trước trên một kho ngữ liệu lớn hơn của các câu tiếng Pháp và tiếng Anh, có tùy chọn dễ dàng hơn là để nguyên từ như sau:
from transformers import pipeline
model_checkpoint = "Helsinki-NLP/opus-mt-en-fr"
translator = pipeline("translation", model=model_checkpoint)
translator("Default to expanded threads")
[{'translation_text': 'Par défaut pour les threads élargis'}]
Một ví dụ khác về hành vi này có thể được nhìn thấy với từ “plugin”, đây không phải là một từ chính thức trong tiếng Pháp nhưng hầu hết người bản ngữ sẽ hiểu và không bận tâm đến việc dịch. Trong tập dữ liệu KDE4, từ này đã được dịch bằng tiếng Pháp một cách chính thống hơn thành “module d’extension”:
split_datasets["train"][172]["translation"]
{'en': 'Unable to import %1 using the OFX importer plugin. This file is not the correct format.',
'fr': "Impossible d'importer %1 en utilisant le module d'extension d'importation OFX. Ce fichier n'a pas un format correct."}
Tuy nhiên, mô hình được huấn luyện trước của chúng ta gắn với từ tiếng Anh nhỏ gọn và quen thuộc:
translator(
"Unable to import %1 using the OFX importer plugin. This file is not the correct format."
)
[{'translation_text': "Impossible d'importer %1 en utilisant le plugin d'importateur OFX. Ce fichier n'est pas le bon format."}]
Sẽ rất thú vị khi xem liệu mô hình tinh chỉnh của mình có tiếp thu những đặc điểm đó của tập dữ liệu hay không (cảnh báo spoiler: nó sẽ xảy ra).
✏️ Đến lượt bạn! Một từ tiếng Anh khác thường được sử dụng trong tiếng Pháp là “email”. Tìm mẫu đầu tiên trong tập dữ liệu huấn luyện sử dụng từ này. Nó được dịch như thế nào? Làm thế nào để mô hình huấn luyện trước dịch cùng một câu tiếng Anh?
Chuẩn bị dữ liệu
Bây giờ bạn nên biết điều này: tất cả các văn bản cần được chuyển đổi thành tập hợp các token ID để mô hình có thể hiểu được chúng. Đối với tác vụ này, chúng ta sẽ cần tokenize cả đầu vào và nhãn. Tác vụ đầu tiên của chúng ta là tạo đối tượng tokenizer
. Như đã lưu ý trước đó, chúng ta sẽ sử dụng mô hình huấn luyện trước từ tiếng Anh sang tiếng Pháp của Marian. Nếu bạn đang thử đoạn mã này với một cặp ngôn ngữ khác, hãy đảm bảo điều chỉnh checkpoint của mô hình. Tổ chức Helsinki-NLP cung cấp hơn một nghìn mô hình bằng nhiều ngôn ngữ.
from transformers import AutoTokenizer
model_checkpoint = "Helsinki-NLP/opus-mt-en-fr"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf")
You can also replace the model_checkpoint
with any other model you prefer from the Hub, or a local folder where you’ve saved a pretrained model and a tokenizer.
💡 Nếu bạn đang sử dụng trình tokenize đa ngôn ngữ như mBART, mBART-50 hoặc M2M100, bạn sẽ cần đặt mã ngôn ngữ của đầu vào và nhãn của mình trong trình tokenize bằng cách đặt tokenizer.src_lang
và tokenizer.tgt_lang
ở bên phải các giá trị.
Việc chuẩn bị dữ liệu của chúng ta khá đơn giản. Chỉ có một điều cần nhớ: bạn xử lý các đầu vào như bình thường, nhưng đối với các nhãn, bạn cần phải bọc tokenizer bên trong trình quản lý ngữ cảnh as_target_tokenizer()
.
Trình quản lý ngữ cảnh trong Python được giới thiệu với câu lệnh with
và rất hữu ích khi bạn có hai hoạt động liên quan để thực thi như một cặp. Ví dụ phổ biến nhất về điều này là khi bạn viết hoặc đọc một tệp, thường được thực hiện bên trong một lệnh như:
with open(file_path) as f:
content = f.read()
Ở đây, hai hoạt động liên quan được thực hiện như một cặp là các hành động mở và đóng tệp. Đối tượng tương ứng với tệp đã mở f
chỉ tồn tại bên trong khối được thụt lề dưới dấu with
; sự mở đầu xảy ra trước khối đó và đóng ở cuối khối.
Trong trường hợp này, trình quản lý ngữ cảnh as_target_tokenizer()
sẽ đặt tokenizer ở ngôn ngữ đầu ra (ở đây, tiếng Pháp) trước khi khối được thụt lề được thực thi, sau đó đặt nó trở lại bằng ngôn ngữ đầu vào (ở đây, tiếng Anh).
Vì vậy, việc xử lý trước một mẫu trông như thế này:
en_sentence = split_datasets["train"][1]["translation"]["en"]
fr_sentence = split_datasets["train"][1]["translation"]["fr"]
inputs = tokenizer(en_sentence)
with tokenizer.as_target_tokenizer():
targets = tokenizer(fr_sentence)
Nếu chúng ta quên tokenize các nhãn bên trong trình quản lý ngữ cảnh, chúng sẽ được tokenize bởi trình tokenize đầu vào, trong trường hợp mô hình Marian sẽ không hoạt động tốt chút nào:
wrong_targets = tokenizer(fr_sentence)
print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"]))
print(tokenizer.convert_ids_to_tokens(targets["input_ids"]))
['▁Par', '▁dé', 'f', 'aut', ',', '▁dé', 've', 'lop', 'per', '▁les', '▁fil', 's', '▁de', '▁discussion', '</s>']
['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', '</s>']
Như chúng ta có thể thấy, việc sử dụng trình tokenize tiếng Anh để xử lý trước một câu tiếng Pháp dẫn đến nhiều token hơn, vì trình tokenize không biết bất kỳ từ tiếng Pháp nào (ngoại trừ những từ cũng xuất hiện trong tiếng Anh, như “discussion”).
Cả inputs
và targets
đều là từ điển với các khóa thông thường của chúng ta (ID đầu vào, attention mask, v.v.), vì vậy bước cuối cùng là đặt "labels"
bên trong các đầu vào. Chúng ta thực hiện điều này trong chức năng tiền xử lý mà ta sẽ áp dụng trên các tập dữ liệu:
max_input_length = 128
max_target_length = 128
def preprocess_function(examples):
inputs = [ex["en"] for ex in examples["translation"]]
targets = [ex["fr"] for ex in examples["translation"]]
model_inputs = tokenizer(inputs, max_length=max_input_length, truncation=True)
# Thiết lập tokenizer cho nhãn
with tokenizer.as_target_tokenizer():
labels = tokenizer(targets, max_length=max_target_length, truncation=True)
model_inputs["labels"] = labels["input_ids"]
return model_inputs
Note that we set similar maximum lengths for our inputs and outputs. Since the texts we’re dealing with seem pretty short, we use 128.
💡 Nếu bạn đang sử dụng mô hình T5 (cụ thể hơn là một trong các checkpoint t5-xxx
), mô hình sẽ mong đợi các đầu vào văn bản có tiền tố cho biết tác vụ đang thực hiện, chẳng hạn như translate: English to French:
.
⚠️ Chúng ta không chú ý đến attention mask của các nhãn, vì mô hình sẽ không mong đợi điều đó. Thay vào đó, các nhãn tương ứng với token đệm phải được đặt thành -100
để chúng bị bỏ qua trong tính toán mất mát. Điều này sẽ được thực hiện bởi trình đối chiếu dữ liệu của chúng ta sau này vì chúng ta đang áp dụng đệm động, nhưng nếu bạn sử dụng đệm ở đây, bạn nên điều chỉnh chức năng tiền xử lý để đặt tất cả các nhãn tương ứng với token đệm thành -100
.
Bây giờ chúng ta có thể áp dụng tiền xử lý đó trong một lần trên tất cả các phần của tập dữ liệu của mình:
tokenized_datasets = split_datasets.map(
preprocess_function,
batched=True,
remove_columns=split_datasets["train"].column_names,
)
Bây giờ dữ liệu đã được tiền xử lý, chúng ta đã sẵn sàng để tinh chỉnh mô hình tiền xử lý của mình!
Tinh chỉnh mô hình với API Trainer
Đoạn mã thực sử dụng Trainer
sẽ giống như trước đây, chỉ với một thay đổi nhỏ: chúng ta sử dụng Seq2SeqTrainer
tại đây, là một lớp con của Trainer
sẽ cho phép chúng ta xử lý tốt việc đánh giá, sử dụng phương thức generate()
để dự đoán kết quả đầu ra từ các đầu vào. Chúng ta sẽ đi sâu vào vấn đề đó chi tiết hơn khi ta nói về tính toán số liệu.
Điều đầu tiên, chúng ta cần một mô hình thực tế để tinh chỉnh. Chúng ta sẽ sử dụng API AutoModel
:
from transformers import AutoModelForSeq2SeqLM
model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
Lưu ý rằng lần này chúng ta đang sử dụng một mô hình đã được huấn luyện về tác vụ dịch và thực sự có thể được sử dụng, vì vậy không có cảnh báo nào về việc thiếu các trọng số hoặc những trọng số mới được khởi tạo.
Đối chiếu dữ liệu
Chúng ta sẽ cần một công cụ đối chiếu dữ liệu để xử lý phần đệm cho phân phối động. Chúng ta không thể chỉ sử dụng một DataCollatorWithPadding
như [Chương 3](/course/ chapter3) trong trường hợp này, bởi vì điều đó chỉ đệm các đầu vào (ID đầu vào, attention mask, và loại token ID). Các nhãn của chúng ta cũng phải được đệm theo chiều dài tối đa có trong nhãn. Và, như đã đề cập trước đây, giá trị đệm được sử dụng để đệm các nhãn phải là -100
chứ không phải token đệm của trình tokenize, để đảm bảo các giá trị đệm đó bị bỏ qua trong tính toán mất mát.
Tất cả điều này được thực hiện bởi DataCollatorForSeq2Seq
. Giống như DataCollatorWithPadding
, nó sử dụng tokenizer
được sử dụng để xử lý trước các đầu vào, nhưng nó cũng lấy model
. Điều này là do trình đối chiếu dữ liệu này cũng sẽ chịu trách nhiệm chuẩn bị các ID đầu vào của bộ giải mã, là các phiên bản được dịch chuyển của các nhãn với một token đặc biệt ở đầu. Vì sự thay đổi này được thực hiện hơi khác đối với các kiến trúc khác nhau, nên DataCollatorForSeq2Seq
cần biết đối tượng model
:
from transformers import DataCollatorForSeq2Seq
data_collator = DataCollatorForSeq2Seq(tokenizer, model=model)
Để kiểm tra điều này trên một số mẫu, chúng ta chỉ cần gọi nó trong danh sách các ví dụ từ bộ huấn luyện được tokenize của mình:
batch = data_collator([tokenized_datasets["train"][i] for i in range(1, 3)])
batch.keys()
dict_keys(['attention_mask', 'input_ids', 'labels', 'decoder_input_ids'])
Chúng tôi có thể kiểm tra nhãn đã được đệm đến độ dài tối đa của lô hay chưa, bằng cách sử dụng -100
:
batch["labels"]
tensor([[ 577, 5891, 2, 3184, 16, 2542, 5, 1710, 0, -100,
-100, -100, -100, -100, -100, -100],
[ 1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, 817,
550, 7032, 5821, 7907, 12649, 0]])
Và chúng tôi cũng có thể xem xét các ID đầu vào của bộ giải mã, để biết rằng chúng là các phiên bản được thay đổi của nhãn:
batch["decoder_input_ids"]
tensor([[59513, 577, 5891, 2, 3184, 16, 2542, 5, 1710, 0,
59513, 59513, 59513, 59513, 59513, 59513],
[59513, 1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124,
817, 550, 7032, 5821, 7907, 12649]])
Dưới đây là các nhãn cho các phần tử đầu tiên và thứ hai trong tập dữ liệu của mình:
for i in range(1, 3):
print(tokenized_datasets["train"][i]["labels"])
[577, 5891, 2, 3184, 16, 2542, 5, 1710, 0]
[1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, 817, 550, 7032, 5821, 7907, 12649, 0]
Chúng ta sẽ truyền data_collator
vào Seq2SeqTrainer
. Tiếp theo, chúng ta hãy xem xét chỉ số.
Thước đo
Tính năng mà Seq2SeqTrainer
thêm vào lớp cha Trainer
của nó là khả năng sử dụng phương thức generate()
trong quá trình đánh giá hoặc dự đoán. Trong quá trình huấn luyện, mô hình sẽ sử dụng decoder_input_ids
với attention mask đảm bảo nó không sử dụng các token sau token mà nó đang cố gắng dự đoán, để tăng tốc độ huấn luyện. Trong quá trình luận suy, chúng ta sẽ không thể sử dụng những thứ đó vì chúng ta sẽ không có nhãn, vì vậy, bạn nên đánh giá mô hình của mình với cùng một thiết lập.
Như chúng ta đã thấy trong Chương 1, bộ giải mã thực hiện luận suy bằng cách dự đoán từng token - một thứ được triển khai phía sau trong 🤗 Transformers bằng phương thức generate()
. Seq2SeqTrainer
sẽ cho phép chúng ta sử dụng phương pháp đó để đánh giá nếu chúng ta đặt predict_with_generate=True
.
Chỉ số truyền thống được sử dụng cho bài toán dịch là điểm BLEU, được giới thiệu trong một bài báo năm 2002 bởi Kishore Papineni và cộng sự. Điểm BLEU đánh giá mức độ gần gũi của bản dịch với nhãn của chúng. Nó không đo lường mức độ dễ hiểu hoặc tính đúng ngữ pháp của các đầu ra được tạo ra của mô hình, nhưng sử dụng các quy tắc thống kê để đảm bảo rằng tất cả các từ trong các đầu ra được tạo cũng xuất hiện trong các nhãn. Ngoài ra, có các quy tắc phạt việc lặp lại các từ giống nhau nếu chúng không được lặp lại trong các nhãn (để tránh mô hình xuất ra các câu như "the the the"
) và xuất ra các câu ngắn hơn các câu trong nhãn (để tránh mô hình xuất ra các câu như "the"
).
Một điểm yếu của BLEU là nó mong đợi văn bản đã được tokenize, điều này gây khó khăn cho việc so sánh điểm giữa các mô hình sử dụng các bộ tokenize khác nhau. Vì vậy, thay vào đó, chỉ số được sử dụng phổ biến nhất cho các mô hình dịch điểm chuẩn ngày nay là SacreBLEU, giải quyết điểm yếu này (và các chỉ số khác) bằng cách chuẩn hóa bước tokenize. Để sử dụng chỉ số này, trước tiên chúng ta cần cài đặt thư viện SacreBLEU:
!pip install sacrebleu
Chúng ta có thể tải nó với evaluate.load()
như chúng ta đã làm trong Chương 3:
import evaluate
metric = evaluate.load("sacrebleu")
Chỉ số này sẽ lấy văn bản làm đầu vào và nhãn. Nó được thiết kế để chấp nhận một số nhãn có thể chấp nhận được, vì thường có nhiều bản dịch có thể chấp nhận được của cùng một câu - tập dữ liệu ta đang sử dụng chỉ cung cấp một nhãn, nhưng không hiếm trong NLP để tìm tập dữ liệu cung cấp một số câu dưới dạng nhãn. Vì vậy, các dự đoán phải là một danh sách các câu, nhưng các tham chiếu phải là một danh sách các danh sách các câu.
Hãy thử một mẫu:
predictions = [
"This plugin lets you translate web pages between several languages automatically."
]
references = [
[
"This plugin allows you to automatically translate web pages between several languages."
]
]
metric.compute(predictions=predictions, references=references)
{'score': 46.750469682990165,
'counts': [11, 6, 4, 3],
'totals': [12, 11, 10, 9],
'precisions': [91.67, 54.54, 40.0, 33.33],
'bp': 0.9200444146293233,
'sys_len': 12,
'ref_len': 13}
Ta nhận được điểm BLEU là 46.75, khá tốt - để tham khảo, mô hình Transformer ban đầu trong bài báo “Attention Is All You Need” đạt được điểm BLEU là 41.8 cho một tác vụ dịch tương tự giữa tiếng Anh và tiếng Pháp! (Để biết thêm thông tin về các chỉ số riêng lẻ, như counts
và bp
, hãy xem kho lưu trữ SacreBLEU.) Mặt khác, nếu chúng ta thử với hai loại dự đoán không tốt (nhiều lần lặp lại hoặc quá ngắn) thường xuất hiện trong các mô hình dịch, chúng ta sẽ nhận được điểm BLEU khá tệ:
predictions = ["This This This This"]
references = [
[
"This plugin allows you to automatically translate web pages between several languages."
]
]
metric.compute(predictions=predictions, references=references)
{'score': 1.683602693167689,
'counts': [1, 0, 0, 0],
'totals': [4, 3, 2, 1],
'precisions': [25.0, 16.67, 12.5, 12.5],
'bp': 0.10539922456186433,
'sys_len': 4,
'ref_len': 13}
predictions = ["This plugin"]
references = [
[
"This plugin allows you to automatically translate web pages between several languages."
]
]
metric.compute(predictions=predictions, references=references)
{'score': 0.0,
'counts': [2, 1, 0, 0],
'totals': [2, 1, 0, 0],
'precisions': [100.0, 100.0, 0.0, 0.0],
'bp': 0.004086771438464067,
'sys_len': 2,
'ref_len': 13}
Điểm số có thể tăng từ 0 đến 100 và càng cao thì càng tốt.
To get from the model outputs to texts the metric can use, we will use the tokenizer.batch_decode()
method. We just have to clean up all the -100
s in the labels (the tokenizer will automatically do the same for the padding token):
import numpy as np
def compute_metrics(eval_preds):
preds, labels = eval_preds
# Trong trường hợp mô hình trả về nhiều hơn logit dự đoán
if isinstance(preds, tuple):
preds = preds[0]
decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True)
# Thay các gía trị -100 trong nhãn vì ta không giải mã chúng
labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
# Thực một một xố hậu xủ lý đơn giản
decoded_preds = [pred.strip() for pred in decoded_preds]
decoded_labels = [[label.strip()] for label in decoded_labels]
result = metric.compute(predictions=decoded_preds, references=decoded_labels)
return {"bleu": result["score"]}
Bây giờ điều này đã hoàn tất, chúng ta đã sẵn sàng tinh chỉnh mô hình của mình!
Tinh chỉnh mô hình
Bước đầu tiên là đăng nhập vào Hugging Face để bạn có thể tải kết quả của mình lên Model Hub. Có một chức năng tiện lợi để giúp bạn làm điều này trong notebook:
from huggingface_hub import notebook_login
notebook_login()
Thao tác này sẽ hiển thị một tiện ích mà bạn có thể nhập thông tin đăng nhập Hugging Face của mình.
Nếu bạn không làm việc trong notebook, chỉ cần nhập dòng sau vào terminal của bạn:
huggingface-cli login
Khi điều này được thực hiện, chúng ta có thể xác định Seq2SeqTrainingArguments
. Giống như đối với Trainer
, chúng ta sử dụng một lớp con của TrainingArguments
chứa thêm một số trường:
from transformers import Seq2SeqTrainingArguments
args = Seq2SeqTrainingArguments(
f"marian-finetuned-kde4-en-to-fr",
evaluation_strategy="no",
save_strategy="epoch",
learning_rate=2e-5,
per_device_train_batch_size=32,
per_device_eval_batch_size=64,
weight_decay=0.01,
save_total_limit=3,
num_train_epochs=3,
predict_with_generate=True,
fp16=True,
push_to_hub=True,
)
Ngoài các siêu tham số thông thường (như tốc độ học, số epoch, kích thước lô và một số phân rã trọng số), đây là một số thay đổi so với những gì chúng ta đã thấy trong các phần trước:
- Chúng ta không đặt bất kỳ đánh giá thường xuyên nào, vì quá trình đánh giá sẽ mất một khoảng thời gian; chúng ta sẽ chỉ đánh giá mô hình của mình một lần trước khi huấn luyện và sau đó.
- Chúng ta đặt
fp16=True
, giúp tăng tốc quá trình huấn luyện trên các GPU hiện đại. - Chúng ta đặt
predict_with_generate=True
, như đã thảo luận ở trên. - Chúng ta sử dụng
push_to_hub=True
để tải mô hình lên Hub vào cuối mỗi epoch.
Lưu ý rằng bạn có thể chỉ định tên đầy đủ của kho lưu trữ mà bạn muốn đẩy đến bằng tham số hub_model_id
(đặc biệt, bạn sẽ phải sử dụng tham số này để đẩy đến một tổ chức). Ví dụ: khi chúng ta đẩy mô hình vào tổ chức huggingface-course
, chúng ta đã thêm hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"
thành Seq2SeqTrainingArguments
. Theo mặc định, kho lưu trữ được sử dụng sẽ nằm trong không gian tên của bạn và được đặt tên theo thư mục đầu ra mà bạn đã đặt, vì vậy trong trường hợp của chúng tôi, nó sẽ là "sgugger/marian-finetuned-kde4-en-to-fr"
(là mô hình chúng tôi liên kết đến ở đầu phần này).
💡 Nếu thư mục đầu ra bạn đang sử dụng đã tồn tại, nó cần phải là bản sao cục bộ của kho lưu trữ mà bạn muốn đẩy đến. Nếu không, bạn sẽ gặp lỗi khi xác định Seq2SeqTrainer
của mình và sẽ cần đặt tên mới.
Cuối cùng, ta chỉ cần truyền mọi thứ cho Seq2SeqTrainer
:
from transformers import Seq2SeqTrainer
trainer = Seq2SeqTrainer(
model,
args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation"],
data_collator=data_collator,
tokenizer=tokenizer,
compute_metrics=compute_metrics,
)
Trước khi huấn luyện, trước tiên chúng ta sẽ xem xét điểm mà mô hình của chúng ta nhận được, để kiểm tra kỹ xem chúng ta có đang không làm mọi thứ tồi tệ hơn với việc tinh chỉnh của chúng ta hay không. Lệnh này sẽ mất một chút thời gian, vì vậy bạn có thể uống một ly cà phê trong khi nó thực thi:
trainer.evaluate(max_length=max_target_length)
{'eval_loss': 1.6964408159255981,
'eval_bleu': 39.26865061007616,
'eval_runtime': 965.8884,
'eval_samples_per_second': 21.76,
'eval_steps_per_second': 0.341}
Điểm BLEU là 39 không quá tệ, điều này phản ánh thực tế là mô hình của chúng ta đã rất giỏi trong việc dịch các câu tiếng Anh sang tiếng Pháp.
Tiếp theo là huấn luyện, cũng sẽ mất một chút thời gian:
trainer.train()
Lưu ý rằng trong khi quá trình huấn luyện diễn ra, mỗi khi mô hình được lưu (ở đây, mỗi epoch), nó sẽ được tải lên Hub ở chế độ nền. Bằng cách này, bạn sẽ có thể tiếp tục huấn luyện của mình trên một máy khác nếu cần.
Sau khi huấn luyện xong, chúng ta đánh giá lại mô hình của mình - hy vọng chúng ta sẽ thấy một số cải thiện trong điểm BLEU!
trainer.evaluate(max_length=max_target_length)
{'eval_loss': 0.8558505773544312,
'eval_bleu': 52.94161337775576,
'eval_runtime': 714.2576,
'eval_samples_per_second': 29.426,
'eval_steps_per_second': 0.461,
'epoch': 3.0}
Đó là một cải tiến hơn gần 14 điểm, thật tuyệt vời.
Cuối cùng, chúng ta sử dụng phương thức push_to_hub()
để đảm bảo tải lên phiên bản mới nhất của mô hình. Trainer
cũng soạn thảo một thẻ mô hình với tất cả các kết quả đánh giá và tải nó lên. Thẻ mô hình này chứa siêu dữ liệu giúp Model Hub chọn tiện ích con cho bản trình diễn luận suy. Thông thường, không cần phải nói bất cứ điều gì vì nó có thể suy ra tiện ích con phù hợp từ lớp mô hình, nhưng trong trường hợp này, cùng một lớp mô hình có thể được sử dụng cho tất cả các loại vấn đề chuỗi sang chuỗi, vì vậy chúng ta chỉ định đó là một mô hình dịch:
trainer.push_to_hub(tags="translation", commit_message="Training complete")
Lệnh này trả về URL của cam kết mà nó vừa thực hiện, nếu bạn muốn kiểm tra nó:
'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3'
Ở giai đoạn này, bạn có thể sử dụng tiện ích luận suy trên Model Hub để kiểm tra mô hình của mình và chia sẻ với bạn bè. Bạn đã tinh chỉnh thành công một mô hình trong tác vụ dịch - xin chúc mừng!
Nếu bạn muốn tìm hiểu sâu hơn một chút về vòng lặp huấn luyện, bây giờ chúng tôi sẽ hướng dẫn bạn cách thực hiện điều tương tự bằng cách sử dụng 🤗 Accelerate.
Một vòng huấn luyện tùy chỉnh
Bây giờ chúng ta hãy xem toàn bộ vòng lặp huấn luyện, vì vậy bạn có thể dễ dàng tùy chỉnh các phần bạn cần. Nó sẽ trông rất giống những gì chúng ta đã làm trong phần 2 và Chương 3.
Chuẩn bị mọi thứ cho qua trình huấn luyện
Bạn đã thấy tất cả điều này một vài lần rồi, vì vậy chúng ta sẽ xem qua đoạn mã khá nhanh. Đầu tiên, chúng ta sẽ xây dựng các DataLoader
từ các tập dữ liệu của mình, sau khi đặt các tập dữ liệu thành định dạng"torch"
để chúng ta nhận được các tensor PyTorch:
from torch.utils.data import DataLoader
tokenized_datasets.set_format("torch")
train_dataloader = DataLoader(
tokenized_datasets["train"],
shuffle=True,
collate_fn=data_collator,
batch_size=8,
)
eval_dataloader = DataLoader(
tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8
)
Tiếp theo, chúng ta khôi phục mô hình của mình, để đảm bảo rằng chúng ta không tiếp tục tinh chỉnh từ trước mà bắt đầu lại từ mô hình đã được huấn luyện trước:
model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
Then we will need an optimizer:
from transformers import AdamW
optimizer = AdamW(model.parameters(), lr=2e-5)
Khi chúng ta có tất cả các đối tượng đó, chúng ta có thể gửi chúng đến phương thức accelerator.prepare()
. Hãy nhớ rằng nếu bạn muốn huấn luyện về TPU trong notebook Colab, bạn sẽ cần chuyển tất cả mã này vào một hàm huấn luyện và điều đó sẽ không thực thi bất kỳ ô nào khởi tạo một Accelerator
.
from accelerate import Accelerator
accelerator = Accelerator()
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
model, optimizer, train_dataloader, eval_dataloader
)
Bây giờ, chúng ta đã gửi train_dataloader
của mình tới accelerator.prepare()
, chúng ta có thể sử dụng độ dài của nó để tính số bước huấn luyện. Hãy nhớ rằng chúng ta phải luôn làm điều này sau khi chuẩn bị dataloader, vì phương thức đó sẽ thay đổi độ dài của DataLoader
. Chúng ta sử dụng một lịch trình tuyến tính cổ điển từ tốc độ học đến 0:
from transformers import get_scheduler
num_train_epochs = 3
num_update_steps_per_epoch = len(train_dataloader)
num_training_steps = num_train_epochs * num_update_steps_per_epoch
lr_scheduler = get_scheduler(
"linear",
optimizer=optimizer,
num_warmup_steps=0,
num_training_steps=num_training_steps,
)
Cuối cùng, để đẩy mô hình của mình lên Hub, chúng ta sẽ cần tạo một đối tượng Repository
trong một thư mục đang làm việc. Đầu tiên hãy đăng nhập vào Hugging Face Hub, nếu bạn chưa đăng nhập. Chúng ta sẽ xác định tên kho lưu trữ từ ID mô hình mà chúng ta muốn cung cấp cho mô hình của mình (vui lòng thay thế repo_name
bằng sự lựa chọn của riêng bạn; nó chỉ cần chứa tên người dùng của bạn, đó là những gì hàm get_full_repo_name()
thực hiện):
from huggingface_hub import Repository, get_full_repo_name
model_name = "marian-finetuned-kde4-en-to-fr-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name
'sgugger/marian-finetuned-kde4-en-to-fr-accelerate'
Sau đó, chúng ta có thể sao chép kho lưu trữ đó trong một thư mục cục bộ. Nếu nó đã tồn tại, thư mục cục bộ này phải là bản sao của kho lưu trữ mà chúng ta đang làm việc:
output_dir = "marian-finetuned-kde4-en-to-fr-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
Bây giờ chúng ta có thể tải lên bất cứ thứ gì chúng ta lưu trong output_dir
bằng cách gọi phương thức repo.push_to_hub()
. Điều này sẽ giúp chúng ta tải lên các mô hình trung gian ở cuối mỗi epoch.
Vòng lặp huấn luyện
Bây giờ chúng ta đã sẵn sàng để viết vòng lặp huấn luyện đầy đủ. Để đơn giản hóa phần đánh giá của nó, chúng ta định nghĩa hàm postprocess()
này lấy các dự đoán và nhãn và chuyển đổi chúng thành danh sách các chuỗi mà đối tượng metric
kì vọng:
def postprocess(predictions, labels):
predictions = predictions.cpu().numpy()
labels = labels.cpu().numpy()
decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)
# Thay -100 trong nhãn vì ta không thế giải mã chúng.
labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
# Thực hiện một số hậu xử lý đơn giản
decoded_preds = [pred.strip() for pred in decoded_preds]
decoded_labels = [[label.strip()] for label in decoded_labels]
return decoded_preds, decoded_labels
Vòng lặp huấn luyện trông rất giống với các vòng lặp trong phần 2 và Chương 3, với một vài điểm khác biệt trong phần đánh giá - vì vậy hãy tập trung vào điều đó!
Điều đầu tiên cần lưu ý là chúng ta sử dụng phương thức generate()
để tính toán các dự đoán, nhưng đây là một phương thức trên mô hình cơ sở, không phải mô hình được bao bọc 🤗 Accelerate được tạo trong phương thức prepare()
. Đó là lý do tại sao chúng ta mở mô hình trước, sau đó gọi phương thức này.
Điều thứ hai là, giống như với phân loại token, hai quy trình có thể đã đêm các đầu vào và nhãn thành các hình dạng khác nhau, vì vậy chúng tôi sử dụng accelerator.pad_across_processes()
để đưa ra các dự đoán và nhãn cùng một hình dạng trước khi gọi phương thức gather()
. Nếu chúng ta không làm điều này, đánh giá sẽ bị lỗi hoặc bị treo vĩnh viễn.
from tqdm.auto import tqdm
import torch
progress_bar = tqdm(range(num_training_steps))
for epoch in range(num_train_epochs):
# Huấn luyện
model.train()
for batch in train_dataloader:
outputs = model(**batch)
loss = outputs.loss
accelerator.backward(loss)
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
progress_bar.update(1)
# Đánh giá
model.eval()
for batch in tqdm(eval_dataloader):
with torch.no_grad():
generated_tokens = accelerator.unwrap_model(model).generate(
batch["input_ids"],
attention_mask=batch["attention_mask"],
max_length=128,
)
labels = batch["labels"]
# Cần đệm dự đoán và nhãn để dễ gom lại
generated_tokens = accelerator.pad_across_processes(
generated_tokens, dim=1, pad_index=tokenizer.pad_token_id
)
labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100)
predictions_gathered = accelerator.gather(generated_tokens)
labels_gathered = accelerator.gather(labels)
decoded_preds, decoded_labels = postprocess(predictions_gathered, labels_gathered)
metric.add_batch(predictions=decoded_preds, references=decoded_labels)
results = metric.compute()
print(f"epoch {epoch}, BLEU score: {results['score']:.2f}")
# Lưu và tải
accelerator.wait_for_everyone()
unwrapped_model = accelerator.unwrap_model(model)
unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
if accelerator.is_main_process:
tokenizer.save_pretrained(output_dir)
repo.push_to_hub(
commit_message=f"Training in progress epoch {epoch}", blocking=False
)
epoch 0, BLEU score: 53.47
epoch 1, BLEU score: 54.24
epoch 2, BLEU score: 54.44
Khi điều này được thực hiện, bạn sẽ có một mô hình có kết quả khá giống với mô hình được huấn luyện với Seq2SeqTrainer
. Bạn có thể kiểm tra đoạn mã mà chúng ta đã huấn luyện bằng cách sử dụng mã này tại huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate. Và nếu bạn muốn kiểm tra bất kỳ tinh chỉnh nào đối với vòng lặp huấn luyện, bạn có thể trực tiếp thực hiện chúng bằng cách chỉnh sửa đoạn mã được hiển thị ở trên!
Sử dụng mô hình tinh chỉnh
Chúng tôi đã chỉ cho bạn cách bạn có thể sử dụng mô hình mà ta đã tinh chỉnh trên Model Hub bằng tiện ích luận suy. Để sử dụng nó cục bộ trong một pipeline
, chúng ta chỉ cần chỉ định mã định danh mô hình thích hợp:
from transformers import pipeline
# Thay nó với checkpoint của bạn
model_checkpoint = "huggingface-course/marian-finetuned-kde4-en-to-fr"
translator = pipeline("translation", model=model_checkpoint)
translator("Default to expanded threads")
[{'translation_text': 'Par défaut, développer les fils de discussion'}]
Đúng như mong đợi, mô hình được huấn luyện trước của chúng ta đã điều chỉnh kiến thức của nó cho phù hợp với kho ngữ liệu mà chúng ta đã tinh chỉnh và thay vì để nguyên từ “thread” trong tiếng Anh, giờ đây nó đã dịch nó sang phiên bản chính thức tiếng Pháp. Đối với “plugin” cũng vậy:
translator(
"Unable to import %1 using the OFX importer plugin. This file is not the correct format."
)
[{'translation_text': "Impossible d'importer %1 en utilisant le module externe d'importation OFX. Ce fichier n'est pas le bon format."}]
Một ví dụ tuyệt vời khác về thích ứng chuyện môn!
✏️ Đến lượt bạn! Mô hình trả về cái gì với từ “email” bạn xác định trước đó?