+2

Finetune BERT cho bài toán che thông tin cá nhân - Phần 1: Xử lý dữ liệu

Tổng quan

Với sự phát triển nhanh chóng của công nghệ xử lý ngôn ngữ tự nhiên (NLP), việc bảo vệ thông tin cá nhân trên các nền tảng trực tuyến đã trở thành một vấn đề quan trọng. Một trong những giải pháp hiệu quả để giải quyết vấn đề này là sử dụng mô hình BERT (Bidirectional Encoder Representations from Transformers) để thực hiện bài toán masking thông tin cá nhân.

BERT là một mô hình ngôn ngữ tiên tiến, được phát triển bởi Google, có khả năng hiểu và tạo ra ngôn ngữ một cách hiệu quả. Bằng cách finetune BERT cho bài toán masking thông tin cá nhân, chúng ta có thể tạo ra một mô hình có khả năng phát hiện và che dấu các thông tin cá nhân như tên, địa chỉ, số điện thoại, email, v.v. trong văn bản.

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.

Việc finetune BERT cho bài toán masking thông tin cá nhân không chỉ giúp bảo vệ quyền riêng tư của người dùng, mà còn có thể được ứng dụng trong nhiều lĩnh vực khác như pháp lý, y tế, và tài chính, nơi việc bảo mật thông tin cá nhân là vấn đề then chốt. Ngoài ra, kỹ thuật này cũng có thể được mở rộng để che dấu các loại thông tin nhạy cảm khác, như thông tin tài chính hoặc thông tin liên quan đến an ninh quốc gia. Với sự phát triển không ngừng của công nghệ NLP, việc bảo vệ thông tin cá nhân sẽ trở nên ngày càng quan trọng và cần thiết trong tương lai.

Chuẩn bị dữ liệu

Named entity recognition - NER sử dụng một lược đồ gán nhãn cụ thể, được định nghĩa ở cấp độ từ. Một lược đồ gán nhãn được sử dụng rộng rãi được gọi là IOB-tagging (Inside-Outside-Beginning), trong đó mỗi thẻ (tag) cho biết liệu từ tương ứng có nằm bên trong, bên ngoài hoặc ở đầu của một thực thể có tên cụ thể. Lý do sử dụng cách này là vì các thực thể có tên thường bao gồm nhiều hơn 1 từ.

Ví dụ: Nếu bạn có câu "Barack Obama was born in Hawaï", thì các thẻ tương ứng sẽ là [B-PERS, I-PERS, O, O, O, B-GEO]. B-PERS có nghĩa là từ "Barack" là đầu của một người, I-PERS có nghĩa là từ "Obama" nằm bên trong một người, "O" có nghĩa là từ "was" nằm bên ngoài một thực thể có tên, v.v. Vì vậy thông thường sẽ có nhiều thẻ như số từ trong câu.

Vì vậy, nếu bạn muốn huấn luyện một mô hình học sâu cho NER, nó yêu cầu dữ liệu của bạn phải ở định dạng IOB (hoặc các định dạng tương tự như BILOU). Có nhiều công cụ gán nhãn cho phép tạo ra những loại gán nhãn này tự động (chẳng hạn như Spacy's Prodigy, Tagtog hoặc Doccano). Bạn cũng có thể sử dụng hàm biluo_tags_from_offsets của Spacy để chuyển đổi các gán nhãn ở cấp độ ký tự sang định dạng IOB.

Ở đây, chúng ta sẽ sử dụng một tập dữ liệu NER từ Kaggle đã ở định dạng IOB

data = pd.read_csv("ner_datasetreference.csv", encoding='unicode_escape')
data.head()

# Let's remove "art", "eve" and "nat" named entities, as performance on them will probably be not comparable to the other named entities.
entities_to_remove = ["B-art", "I-art", "B-eve", "I-eve", "B-nat", "I-nat"]
data = data[~data.Tag.isin(entities_to_remove)]

# pandas has a very handy "forward fill" function to fill missing values based on the last upper non-nan value
data = data.fillna(method='ffill')

# let's create a new column called "sentence" which groups the words by sentence 
data['sentence'] = data[['Sentence #','Word','Tag']].groupby(['Sentence #'])['Word'].transform(lambda x: ' '.join(x))
# let's also create a new column called "word_labels" which groups the tags by sentence 
data['word_labels'] = data[['Sentence #','Word','Tag']].groupby(['Sentence #'])['Tag'].transform(lambda x: ','.join(x))
data.head()

Chúng ta tạo 2 từ điển: một cái ánh xạ các thẻ cá nhân tới chỉ số, và một cái ánh xạ chỉ số tới các thẻ cá nhân của chúng. Điều này là cần thiết để tạo ra các nhãn (vì máy tính hoạt động với các số = chỉ số, thay vì các từ = thẻ)

label2id = {k: v for v, k in enumerate(data.Tag.unique())}
id2label = {v: k for v, k in enumerate(data.Tag.unique())}
label2id

Xử lý dữ liệu

Bây giờ dữ liệu của chúng ta đã được tiền xử lý, chúng ta có thể chuyển nó thành các tensor PyTorch để tiến hành finetuning mô hình

Đây là một phần khó khăn của bài toán NER khi sử dụng mô hình BERT, đó là BERT dựa vào tokenization của wordpiece, thay vì tokenization của từ. Điều này có nghĩa là chúng ta cũng nên định nghĩa các nhãn ở cấp độ wordpiece, thay vì ở cấp độ từ!

Ví dụ, nếu bạn có từ như "Washington" được gắn nhãn là "b-gpe", nhưng nó được tokenize thành "Wash", "##ing", "##ton", thì chúng ta sẽ phải truyền nhãn gốc của từ vào tất cả các wordpiece của nó: "b-gpe", "b-gpe", "b-gpe". Mô hình phải có khả năng tạo ra các nhãn chính xác cho mỗi wordpiece cá nhân. Hàm dưới đây thực hiện việc này.

def tokenize_and_preserve_labels(sentence, text_labels, tokenizer):
    """
    Word piece tokenization makes it difficult to match word labels
    back up with individual word pieces. This function tokenizes each
    word one at a time so that it is easier to preserve the correct
    label for each subword. It is, of course, a bit slower in processing
    time, but it will help our model achieve higher accuracy.
    """

    tokenized_sentence = []
    labels = []

    sentence = sentence.strip()

    for word, label in zip(sentence.split(), text_labels.split(",")):

        # Tokenize the word and count # of subwords the word is broken into
        tokenized_word = tokenizer.tokenize(word)
        n_subwords = len(tokenized_word)

        # Add the tokenized word to the final tokenized word list
        tokenized_sentence.extend(tokenized_word)

        # Add the same label to the new list of labels `n_subwords` times
        labels.extend([label] * n_subwords)

    return tokenized_sentence, labels

Tiếp theo, chúng ta định nghĩa một lớp tập dữ liệu PyTorch (chuyển đổi các ví dụ từ dataframe thành các tensor PyTorch). Ở đây, mỗi câu sẽ được tokenize, các token đặc biệt sẽ được thêm vào mà BERT, các token sẽ được đệm hoặc cắt ngắn dựa trên độ dài tối đa của mô hình, mặt nạ chú ý (masking attention) sẽ được tạo ra và các nhãn sẽ được tạo ra dựa trên từ điển mà chúng ta đã định nghĩa ở trên.

class dataset(Dataset):
    def __init__(self, dataframe, tokenizer, max_len):
        self.len = len(dataframe)
        self.data = dataframe
        self.tokenizer = tokenizer
        self.max_len = max_len
        
    def __getitem__(self, index):
        # step 1: tokenize (and adapt corresponding labels)
        sentence = self.data.sentence[index]  
        word_labels = self.data.word_labels[index]  
        tokenized_sentence, labels = tokenize_and_preserve_labels(sentence, word_labels, self.tokenizer)
        
        # step 2: add special tokens (and corresponding labels)
        tokenized_sentence = ["[CLS]"] + tokenized_sentence + ["[SEP]"] # add special tokens
        labels.insert(0, "O") # add outside label for [CLS] token
        labels.insert(-1, "O") # add outside label for [SEP] token

        # step 3: truncating/padding
        maxlen = self.max_len

        if (len(tokenized_sentence) > maxlen):
          # truncate
          tokenized_sentence = tokenized_sentence[:maxlen]
          labels = labels[:maxlen]
        else:
          # pad
          tokenized_sentence = tokenized_sentence + ['[PAD]'for _ in range(maxlen - len(tokenized_sentence))]
          labels = labels + ["O" for _ in range(maxlen - len(labels))]

        # step 4: obtain the attention mask
        attn_mask = [1 if tok != '[PAD]' else 0 for tok in tokenized_sentence]
        
        # step 5: convert tokens to input ids
        ids = self.tokenizer.convert_tokens_to_ids(tokenized_sentence)

        label_ids = [label2id[label] for label in labels]
        # the following line is deprecated
        #label_ids = [label if label != 0 else -100 for label in label_ids]
        
        return {
              'ids': torch.tensor(ids, dtype=torch.long),
              'mask': torch.tensor(attn_mask, dtype=torch.long),
              #'token_type_ids': torch.tensor(token_ids, dtype=torch.long),
              'targets': torch.tensor(label_ids, dtype=torch.long)
        } 
    
    def __len__(self):
        return self.len

Dựa trên lớp mà chúng ta đã định nghĩa ở trên, chúng ta có thể tạo ra 2 tập dữ liệu, một cho tập huấn luyện và một cho tập kiểm tra với tỷ lệ 0.8:0.2

train_size = 0.8
train_dataset = data.sample(frac=train_size,random_state=200)
test_dataset = data.drop(train_dataset.index).reset_index(drop=True)
train_dataset = train_dataset.reset_index(drop=True)

print("FULL Dataset: {}".format(data.shape))
print("TRAIN Dataset: {}".format(train_dataset.shape))
print("TEST Dataset: {}".format(test_dataset.shape))

training_set = dataset(train_dataset, tokenizer, MAX_LEN)
testing_set = dataset(test_dataset, tokenizer, MAX_LEN)

Định nghĩa mô hình

Ở đây, chúng ta định nghĩa mô hình, BertForTokenClassification, và tải các trọng số tiền huấn luyện của "bert-base-uncased". Điều duy nhất chúng ta cần chỉ định thêm là số lượng nhãn (vì điều này sẽ xác định kiến trúc của đầu phân loại token).

Lưu ý:

  • chỉ các lớp cơ bản được khởi tạo với các trọng số tiền huấn luyện. Lớp để phân loại token ở trên chỉ có các trọng số được khởi tạo ngẫu nhiên, những trọng số này sẽ được huấn luyện thông qua việc sử dụng tập dữ liệu có nhãn của chúng ta.
  • chúng ta nên sử dụng GPU để huấn luyện, việc huấn luyện bằng CPU sẽ mất rất nhiều thời gian. Các bạn có thể dùng colab để training vì bài toán này chỉ là finetuning trên 1 dataset nhất định nên gg colab đủ để chúng ta huấn luyện rồi nhé.
model = BertForTokenClassification.from_pretrained('bert-base-uncased', 
                                                   num_labels=len(id2label),
                                                   id2label=id2label,
                                                   label2id=label2id)
model.to(device)

Phần 1 của chúng ta sẽ kết thúc ở đây, mình nghĩ các bạn sẽ cần thời gian để đọc và hiểu quá trình xử lý data để training bài toán NER sử dụng mô hình BERT. Phần 2 chúng ta sẽ tiếp tục đi vào quá trình huấn luyện và đánh giá mô hình, hẹn gặp lại các bạn ở phần 2


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í