+3

Finetune BERT cho bài toán che thông tin cá nhân - Phần 2: Huấn luyện và đánh giá mô hình

Tổng quan

Phần 1 chúng ta đã cùng nhau xác định các bước để tạo ra một mô hình xử lý bài toán NER thông qua việc finetuning BERT

Quá trình finetune BERT cho bài toán masking thông tin cá nhân bao gồm các bước sau:

  • Chuẩn bị dữ liệu: Tùy thuộc vào mục tiêu bài toán chúng ta sẽ tập chung vào từng bộ dữ liệu cụ thể

  • Tiền xử lý dữ liệu: Xử lý dữ liệu văn bản để đưa vào mô hình BERT, bao gồm tokenization, padding, và chuyển đổi các thẻ nhãn thành số.

  • Finetune BERT: Sử dụng kiến trúc BERT pre-trained và tiếp tục training mô hình trên tập dữ liệu đã chuẩn bị. Trong quá trình này, mô hình sẽ học cách nhận dạng và che dấu các thông tin cá nhân.

  • Đánh giá và tối ưu hóa: Kiểm tra hiệu suất của mô hình trên tập dữ liệu test, và tiến hành các bước tối ưu hóa như điều chỉnh siêu tham số, thay đổi kiến trúc mô hình, hoặc thu thập thêm dữ liệu nếu cần thiết.

Vậy thì ở phần trước chúng ta đã xử lý xong 2 bước đầu tiên, trong phần 2 này mình sẽ tiếp tục với quá trình finetune BERT và đánh giá mô hình.

Huấn luyện mô hình

Chúng ta cần định nghĩa optimizer. Ở đây, đơn giản sử dụng Adam với learning rate mặc định. Người ta cũng có thể quyết định sử dụng những cái nâng cao hơn như AdamW (Adam với bản sửa lỗi suy giảm trọng số), SGD, hoặc một learning rate schedule. Tuy nhiên để đơn giản ở đây mình chỉnh sử dụng Adam()

optimizer = torch.optim.Adam(params=model.parameters(), lr=LEARNING_RATE)

Xây dựng training functions

# Defining the training function on the 80% of the dataset for tuning the bert model
def train(epoch):
    tr_loss, tr_accuracy = 0, 0
    nb_tr_examples, nb_tr_steps = 0, 0
    tr_preds, tr_labels = [], []
    # put model in training mode
    model.train()
    
    for idx, batch in enumerate(training_loader):
        
        ids = batch['ids'].to(device, dtype = torch.long)
        mask = batch['mask'].to(device, dtype = torch.long)
        targets = batch['targets'].to(device, dtype = torch.long)

        outputs = model(input_ids=ids, attention_mask=mask, labels=targets)
        loss, tr_logits = outputs.loss, outputs.logits
        tr_loss += loss.item()

        nb_tr_steps += 1
        nb_tr_examples += targets.size(0)
        
        if idx % 100==0:
            loss_step = tr_loss/nb_tr_steps
            print(f"Training loss per 100 training steps: {loss_step}")
           
        # compute training accuracy
        flattened_targets = targets.view(-1) # shape (batch_size * seq_len,)
        active_logits = tr_logits.view(-1, model.num_labels) # shape (batch_size * seq_len, num_labels)
        flattened_predictions = torch.argmax(active_logits, axis=1) # shape (batch_size * seq_len,)
        # now, use mask to determine where we should compare predictions with targets (includes [CLS] and [SEP] token predictions)
        active_accuracy = mask.view(-1) == 1 # active accuracy is also of shape (batch_size * seq_len,)
        targets = torch.masked_select(flattened_targets, active_accuracy)
        predictions = torch.masked_select(flattened_predictions, active_accuracy)
        
        tr_preds.extend(predictions)
        tr_labels.extend(targets)
        
        tmp_tr_accuracy = accuracy_score(targets.cpu().numpy(), predictions.cpu().numpy())
        tr_accuracy += tmp_tr_accuracy
    
        # gradient clipping
        torch.nn.utils.clip_grad_norm_(
            parameters=model.parameters(), max_norm=MAX_GRAD_NORM
        )
        
        # backward pass
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    epoch_loss = tr_loss / nb_tr_steps
    tr_accuracy = tr_accuracy / nb_tr_steps
    print(f"Training loss epoch: {epoch_loss}")
    print(f"Training accuracy epoch: {tr_accuracy}")

Vì là finetuning nên việc chúng ta huấn luyện quá nhiều epoch thì sẽ dẫn đến việc mô hình quá bị fit vào dữ liệu huấn luyện mà mất đi tính tổng quát của nó (pretrain model đã được huấn luyện trên 1 tập dữ liệu lớn hơn rất nhiều). Ở đây chúng ta chỉ cần huấn luyện thêm 1,2 epochs để mô hình học được những đặc trưng mới từ dữ liệu huấn luyện

for epoch in range(EPOCHS):
    print(f"Training epoch: {epoch + 1}")
    train(epoch)

Đánh giá mô hình

Sau khi chúng ta đã huấn luyện mô hình của mình, chúng ta có thể đánh giá hiệu suất của nó trên tập kiểm tra được giữ lại (chiếm 20% dữ liệu). Lưu ý rằng ở đây sẽ không cập nhật gradient , mô hình chỉ đơn giản đưa ra các logits của nó.

def valid(model, testing_loader):
    # put model in evaluation mode
    model.eval()
    
    eval_loss, eval_accuracy = 0, 0
    nb_eval_examples, nb_eval_steps = 0, 0
    eval_preds, eval_labels = [], []
    
    with torch.no_grad():
        for idx, batch in enumerate(testing_loader):
            
            ids = batch['ids'].to(device, dtype = torch.long)
            mask = batch['mask'].to(device, dtype = torch.long)
            targets = batch['targets'].to(device, dtype = torch.long)
            
            outputs = model(input_ids=ids, attention_mask=mask, labels=targets)
            loss, eval_logits = outputs.loss, outputs.logits
            
            eval_loss += loss.item()

            nb_eval_steps += 1
            nb_eval_examples += targets.size(0)
        
            if idx % 100==0:
                loss_step = eval_loss/nb_eval_steps
                print(f"Validation loss per 100 evaluation steps: {loss_step}")
              
            # compute evaluation accuracy
            flattened_targets = targets.view(-1) # shape (batch_size * seq_len,)
            active_logits = eval_logits.view(-1, model.num_labels) # shape (batch_size * seq_len, num_labels)
            flattened_predictions = torch.argmax(active_logits, axis=1) # shape (batch_size * seq_len,)
            # now, use mask to determine where we should compare predictions with targets (includes [CLS] and [SEP] token predictions)
            active_accuracy = mask.view(-1) == 1 # active accuracy is also of shape (batch_size * seq_len,)
            targets = torch.masked_select(flattened_targets, active_accuracy)
            predictions = torch.masked_select(flattened_predictions, active_accuracy)
            
            eval_labels.extend(targets)
            eval_preds.extend(predictions)
            
            tmp_eval_accuracy = accuracy_score(targets.cpu().numpy(), predictions.cpu().numpy())
            eval_accuracy += tmp_eval_accuracy
    
    #print(eval_labels)
    #print(eval_preds)

    labels = [id2label[id.item()] for id in eval_labels]
    predictions = [id2label[id.item()] for id in eval_preds]

    #print(labels)
    #print(predictions)
    
    eval_loss = eval_loss / nb_eval_steps
    eval_accuracy = eval_accuracy / nb_eval_steps
    print(f"Validation Loss: {eval_loss}")
    print(f"Validation Accuracy: {eval_accuracy}")

    return labels, predictions

Chúng ta có thể dễ dàng tính được

Validation Loss: 0.02922834331467394
Validation Accuracy: 0.9602300367723403

Tuy nhiên, chỉ số "Validation Accuracy" là không đáng tin cậy, vì nhiều nhãn là "ngoài" (O), thậm chí sau khi loại bỏ các dự đoán trên các token [PAD]. Điều quan trọng là xem xét độ chính xác, giá trị f1 của từng thẻ riêng lẻ.

from seqeval.metrics import classification_report
labels, predictions = valid(model, testing_loader)
print(classification_report([labels], [predictions]))
precision    recall  f1-score   support

         geo       0.79      0.88      0.83      4613
         gpe       0.89      0.89      0.89      1523
         org       0.71      0.56      0.63      2761
         per       0.78      0.81      0.79      2183
         tim       0.82      0.81      0.81      1772

   micro avg       0.79      0.79      0.79     12852
   macro avg       0.80      0.79      0.79     12852
weighted avg       0.79      0.79      0.79     12852

Có một cánh nhanh để kiểm tra tính hiệu quả của mô hình, chúng ta có thể đưa vào một câu của tập test hoặc câu bất kỳ nào đó để kiểm tra

from transformers import pipeline

pipe = pipeline(task="token-classification", model=model.to("cpu"), tokenizer=tokenizer, aggregation_strategy="simple")
pipe("My name is Niels and New York is a city")

Kết quả khá ổn khi mô hình có thể bắt được các token được đánh nhãn là 'per' hoặc 'geo', chúng ta có thể sử đánh giá trên trên một tập dữ liệu lớn để kiểm tra tính hiệu quả và xem xét những điểm cần tối ưu.

[{'end': None,
  'entity_group': 'per',
  'score': 0.71306735,
  'start': None,
  'word': 'ni'},
 {'end': None,
  'entity_group': 'per',
  'score': 0.8921481,
  'start': None,
  'word': '##els'},
 {'end': None,
  'entity_group': 'geo',
  'score': 0.95278734,
  'start': None,
  'word': 'new york'}]

Lưu mô hình

Cuối cùng, hãy lưu các tệp mô hình và bộ mã hóa để chúng ta có thể dễ dàng sử dụng lại chúng sau này. Có 2 options:

  • Chúng ta có thể lưu tất cả cục bộ, chỉ đơn giản bằng cách gọi model.save_pretrained() và tokenizer.save_pretrained(), cung cấp đường dẫn thư mục làm đối số.
  • Cách khác là có thể đẩy các tệp lên HuggingFace hub. Bằng cách này, bạn có thể chia sẻ mô hình của mình với cộng đồng. Tất cả các tệp sẽ được theo dõi bởi git, vì mỗi mô hình trên hub có kho lưu trữ git riêng của nó. Cả hai tùy chọn đều cho phép tái sử dụng mô hình/bộ mã hóa bằng cách sử dụng phương thức from_pretrained()

Các bạn có thể sử dụng đoạn code dưới đây để thực hiện cách 2 nhé

from huggingface_hub import notebook_login
notebook_login()
model_name = "bert-finetuned-ner"

# upload files to the hub
tokenizer.push_to_hub(
    repo_path_or_name=model_name,
    organization="nielsr",
    commit_message="Add tokenizer",
    use_temp_dir=True,
)
model.push_to_hub(
    repo_path_or_name=model_name,
    organization="nielsr",
    commit_message="Add model",
    use_temp_dir=True,
)

Kết luận

Trong bài viết này, chúng ta đã thảo luận về cách Finetune mô hình BERT để giải quyết bài toán che thông tin cá nhân. Chúng ta đã xây dựng một pipeline từ xử lý dữ liệu, đào tạo và đánh giá mô hình, sau đó sử dụng mô hình sau khi training để thực hiện suy luận nhanh chóng trên các câu mới. Kết quả cho thấy việc Finetune BERT đạt hiệu suất ổn trên bộ dữ liệu này, với độ chính xác, F1 ở tương đối tốt. Điều này cho thấy BERT là một nền tảng mạnh mẽ có thể được điều chỉnh để giải quyết các bài toán NLP phức tạp như che thông tin cá nhân. Cũng như cách chia sẻ chúng trên HuggingFace hub để dễ dàng sử dụng lại hoặc chia sẽ với cộng đồng. Hy vọng rằng bài viết này đã cung cấp cho bạn một cái nhìn tổng quan về cách Finetune BERT cho bài toán che thông tin cá nhân. Hãy tiếp tục khám phá và sáng tạo với các kỹ thuật học máy và các bài toán thực tế nhiều hơn nữa để tạo thêm nhiều giá trị mới nhé.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí