[BERT Series] Chương 3. Thử nhận diện cảm xúc văn bản Tiếng Việt với PhoBert (Cách #1)

Hi anh em, hôm nay chúng ta sẽ trở lại với series về BERT với bài toán Thử nhận diện cảm xúc văn bản Tiếng Việt với PhoBert.

Trong 2 bài trước, mình đã cùng các bạn tìm hiểu về BERT. Bạn nào chưa xem thì có thể xem lại tại đây [BERT Series] Chương 1. BERT là cái chi chi?[BERT Series] Chương 2. Nghịch một chút với Hugging Face

Trong các bài tiếp theo chúng ta sẽ cùng sử dụng thư viện Hugging Face để làm các Task khác nhau trong “môn võ công” BERT này. Và bắt đầu sẽ là tak thân quên “Nhận diện cảm xúc”

Phần 1 – Đặt vấn đề bài toán nhận diện cảm xúc văn bản với PhoBert.

Bài toán nhận diện cảm xúc này thì nhiều bạn làm, nhiều trang viết rồi, các bạn có thể search thoải mái trên mạng.

Nguồn: Tại đây

Tóm lại , bài toán này là ta sẽ cần xây dựng một model nhận vào một câu văn bản và trả ra được cảm xúc của câu văn bản đó. Cảm xúc ở đây có thể là tích cực, tiêu cực, trung tính hoặc đơn giản là Tốt/Xấu…. tuỳ bài toán.

Ví dụ:

  • “Nhà hàng này rất ngon” thì là Tích cực
  • “Nhà hàng này ăn cũng tạm” thì là Trung tính
  • “Nhà hàng này lừa đảo” thì là Tiêu cực

Lớp bài toán này thì các làm là chúng ta sẽ vector hoá câu văn bản, sau đó đưa vào một mạng LSTM để lấy ra vector output, sau đó ta đưa qua một mạng neural đơn giản hoặc SVM để phân lớp.

Và hôm nay chúng ta sẽ làm cách tương tự đó là sử dụng BERT, đưa câu văn bản vào pretrain PhoBERT, lấy output đầu ra và sử dụng SVM để phân lớp nhé.

Phần 2 – Cách thức thực hiện

Rồi, chúng ta sẽ cùng vạch ra cách thức thực hiện bài toán này với pretrain PhoBert của VinAI ( https://github.com/VinAIResearch/PhoBERT).

Như chúng ta đã biết, BERT là một nửa của Transformer (cụ thể là nửa Encoder) với một loạt các Block Encoder chồng lên nhau (nhiều hay ít tuỳ model Base hay Large):

Nguồn: Tại đây

Nếu chúng ta feed 1 câu văn bản qua PhoBERT thì sẽ lấy ra được embedding output của cả câu sau block encoder cuối cùng. Và đấy, chúng ta sẽ sử dụng output đó để làm đặc trưng classify nhá!

Cụ thể sẽ bao gồm các bước như sau:

  • Bước 1: Tiền xử lý câu văn bản
  • Bước 2: Word segment câu văn bản trước khi đưa vào PhoBert (do PhoBert yêu cầu)
  • Bước 3: Tokenize bằng bộ Tokenizer của PhoBert. Chú ý rằng khi tokenize ta sẽ thêm 2 token đặc biệt là [CLS] và [SEP] vào đầu và cuối câu.
  • Bước 4: Đưa câu văn bản đã được tokenize vào model kèm theo attention mask.
  • Bước 5: Lấy output đầu ra và lấy vector output đầu tiên (chính là ở vị trí token đặc biệt [CLS]) làm đặc trưng cho câu để train hoặc để predict (tuỳ phase).

Chú ý: Chỗ này mình nhấn mạnh đây là cách lazy, còn nếu làm sâu hơn thì nên fine tune lại model BERT nha (mình sẽ nói vào bài sau). Vì chắc sẽ có bạn comment chỗ này nên mình nói luôn để các bạn đỡ tìm.

Đó, đại khái cách làm là như vậy, bạn nào chưa rõ có thể post lên Group trao đổi, chia sẻ: https://facebook.com/groups/miaigroup để cùng thảo luận cho vui nhé!

Phần 3 – Viết code cho chương trình nhận diện cảm xúc văn bản với PhoBert.

Cài đặt thư viện

Đầu tiên chúng ta cùng cài bằng lệnh pip thần thánh:

# Cài đặt hugging face
pip install transformer
# Cài đặt thư viện underthesea để thực hiện word segment
pip install underthesea
# Cài đặt pytorch
pip install torch
# Cài đặt sklearn
pip install scikit-learnCode language: PHP (php)

Chú ý ở đây là transformer hugging face sử dụng framework pytorch nên chúng ta phải cài đặt torch nhé.

Load model PhoBERT

Chúng ta sẽ load bằng đoạn code sau:

# Hàm load model BERT
def load_bert():
    v_phobert = AutoModel.from_pretrained("vinai/phobert-base")
    v_tokenizer = AutoTokenizer.from_pretrained("vinai/phobert-base", use_fast=False)
    return v_phobert, v_tokenizer
Code language: PHP (php)

Chú ý model sẽ được load từ cloud về nên lần chạy đầu tiên sẽ khá chậm nhé.

Chuẩn hoá dữ liệu

Dữ liệu thu thập từ trên mạng thường rất sạn. Sạn ở đây cụ thể là: từ viết tắt, dấu câu, sai chính tả, từ không dấu….và chúng ta phải xử lý để chuẩn hoá dữ liệu thì model mới cho ra kết quả tốt được.

Việc chuẩn hoá này tốn nhiều công sức và thời gian xử lý lắm luôn! Ở đây chỉ để demo nên mình chỉ làm thao tác remove dấu câu thôi nhé:

# Hàm chuẩn hoá câu
def standardize_data(row):
    # Xóa dấu chấm, phẩy, hỏi ở cuối câu
    row = re.sub(r"[\.,\?]+$-", "", row)
    # Xóa tất cả dấu chấm, phẩy, chấm phẩy, chấm thang, ... trong câu
    row = row.replace(",", " ").replace(".", " ") \
        .replace(";", " ").replace("“", " ") \
        .replace(":", " ").replace("”", " ") \
        .replace('"', " ").replace("'", " ") \
        .replace("!", " ").replace("?", " ") \
        .replace("-", " ").replace("?", " ")
    row = row.strip().lower()
    return row
Code language: PHP (php)

Word segmentation và Tokenize

Rồi, sau khi đã chuẩn hoá xong, ta sẽ word segment (phân tách từ) bằng Underthesea (các bạn có thể dùng VnCoreNLP cũng okie nhé, mình cài sẵn Underthesea nên xài luôn)

line = underthesea.word_tokenize(line, format="text")Code language: JavaScript (javascript)

Và đưa vào tokenizer của PhoBert:

line = tokenizer.encode(line)

Padding và đưa vào model PhoBERT trích đặc trưng

Ở đây các bạn chú ý là chúng ta phải padding để đảm bảo các input có cùng độ dài như nhau nhé:

max_len = 100
# Chèn thêm số 1 vào cuối câu nếu như không đủ 100 từ
    padded = numpy.array([i + [1] * (max_len - len(i)) for i in v_tokenized])
Code language: PHP (php)

Tuy nhiên, khi padding thế thì ta phải thêm một attention_mask đẻ model chỉ focus vào các từ trong câu và bỏ qua các từ được padding thêm:

# Đánh dấu các từ thêm vào = 0 để không tính vào quá trình lấy features
attention_mask = numpy.where(padded == 1, 0, 1)Code language: PHP (php)

Và cuối cùng là tống nó vào model và lấy ra output

# Chuyển thành tensor
    padded = torch.tensor(padded).to(torch.long)
    print("Padd = ",padded.size())
    attention_mask = torch.tensor(attention_mask)

    # Lấy features dầu ra từ BERT
    with torch.no_grad():
        last_hidden_states = phobert(input_ids= padded, attention_mask=attention_mask)

    v_features = last_hidden_states[0][:, 0, :].numpy()Code language: PHP (php)

Các bạn để ý dòng cuối, cái chỗ [:,0,:] chính là lấy vector embedding của token đầu tiên. Để mình vẽ cho các bạn dễ hình dung chỗ này:

Train model SVM

Vâng, và sau khi có đầy đủ các vector đặc trưng của từng câu thì ta chỉ cần nhét vào một model SVM đơn giản mà thôi. Thông số của SVM thì các bạn có thể sử dụng GridSearch để tìm nhé. Ở đây mình hardcode vài thông số cho nhanh.

cl = SVC(kernel='linear', probability=True, gamma=0.125)
cl.fit(features, label)

sc = cl.score(X_test, y_test)
print('Kết quả train model, độ chính xác = ', sc*100, '%')Code language: PHP (php)

Predict câu văn bản mới

Với câu mới, ta cũng thực hiện tương tự các bước: chuẩn hoá, word segment rồi thì cũng tokenize, lấy embeđing rồi đưa vào model SVM là ra class của nó:

# Lấy features từ BERT
    with torch.no_grad():
        last_hidden_states = phobert(padded, attention_mask=attention_mask)
    
    v_features = last_hidden_states[0][:, 0, :].numpy()
    # Đưa vào model predict lấy kết quả
    sc = svm_model.predict_proba(v_features)

    cv = numpy.max(sc) # Đây là giá trị probality
    cb = numpy.argmax(sc) # Đây là giá trị index đạt maxCode language: PHP (php)

Vậy thôi, đó là các bước khá đơn giản để làm một bài sentiment analysis theo cách số 1 – không finetune model. Trong bài tiếp theo mình sẽ cùng các bạn làm theo cách có finetune nhé.

Tips: Có một số bạn nói rằng trích xuất vector embedding của token [CLS] trong vài layer cuối sau đó concat lại làm đặc trưng sẽ mang lại kết quả tốt hơn.

Hẹn gặp lại các bạn! Mình xin tặng bạn nào đọc đến đây link github thay lời cảm ơn nha: Tại đây.

Chúc các bạn thành công!

#MìAI

Fanpage: http://facebook.com/miaiblog
Group trao đổi, chia sẻ: https://www.facebook.com/groups/miaigroup
Website: https://miai.vn
Youtube: http://bit.ly/miaiyoutube

Tài liệu tham khảo: https://viblo.asia/p/su-dung-ai-danh-gia-san-pham-tren-lazadatiki-dua-tren-comment-aWj53b9ol6m

Related Post

25 Replies to “[BERT Series] Chương 3. Thử nhận diện cảm xúc văn bản Tiếng Việt với PhoBert (Cách #1)”

    1. File đó là file private nên anh ko share được. Em có thể tự tạo ra bằng cách như sau:
      – Mỗi dòng gồm có câu văn bản và rating của nó (1-5)
      – Phân tách nhau bởi dấu |

      1. Dạ em chào anh Thắng, em thấy anh có đăng data để train bài này trên thư viện rồi mà không rõ cách sử dụng ạ. Mình cần sửa hay thêm gì không ạ? Mong anh sớm trả lời. Em cảm ơn a!

  1. Anh ơi, cái phần thư viện của Hugging face là pip install transformers ạ ( của a thiếu s á a).

  2. a cho e hỏi BPE là gì được k ạ và nó có giống chức năng của w2v k ak

  3. Hi anh, chỗ train trong code là “cl.fit(X_train, label)” mới đúng chứ anh nhỉ? Em thấy mình đang train luôn nguyên tập features

  4. Mong anh hướng dẫn cách dùng PhoBert để phát hiện trùng lặp văn bản ạ.

  5. cho e hỏi đối với bài toán multilabel dùng phobert nó có khác biệt nhiều với cách sử dụng bert với multiclass k ạ?

  6. Cho em hỏi là tại sao mình lại chọn vector đầu tiên mà không phải vector thứ 2 hay toàn bộ ạ.
    trích “Các bạn để ý dòng cuối, cái chỗ [:,0,:] chính là lấy vector embedding của token đầu tiên. Để mình vẽ cho các bạn dễ hình dung chỗ này:”, xin cám ơn.

Leave a Reply

Your email address will not be published. Required fields are marked *