DS

[Tech Blog #10 | Data Science] Bài Toán Phân Loại Với Logistic Regression & Giải Mã Bộ Thước Đo Thực Chiến: Accuracy, Precision, Recall, F1-Score

0 views
[Tech Blog #10 | Data Science] Bài Toán Phân Loại Với Logistic Regression & Giải Mã Bộ Thước Đo Thực Chiến: Accuracy, Precision, Recall, F1-Score

1. Đặt vấn đề: "Bẫy độ chính xác" (The Accuracy Paradox)

Hãy tưởng tượng bạn được giao bài toán xây dựng mô hình phát hiện giao dịch gian lận thẻ tín dụng (Credit Card Fraud Detection).
Trong thực tế, tỷ lệ gian lận rất hiếm: cứ 10.000 giao dịch thì chỉ có 10 giao dịch là gian lận (0.1%), còn 9.990 giao dịch là hợp lệ (99.9%).

Một thực tập sinh tạo ra một "mô hình lười biếng": Dự đoán MỌI giao dịch đều là HỢP LỆ (Không gian lận).

  • Kết quả tính toán độ chính xác (Accuracy):
    Accuracy=9.99010.000=99.9%\text{Accuracy} = \frac{9.990}{10.000} = 99.9\%

Một con số 99.9% nghe có vẻ hoàn hảo. Nhưng trên thực tế, mô hình này hoàn toàn vô dụng vì nó bỏ lọt 100% các vụ lừa đảo, khiến ngân hàng mất trắng tiền.

Hiện tượng này được gọi là Accuracy Paradox (Nghịch lý độ chính xác) khi làm việc với tập dữ liệu mất cân bằng (Imbalanced Data). Để đánh giá đúng chất lượng của một mô hình phân loại (Classification), chúng ta phải nhìn sâu hơn vào Confusion Matrix, Precision, Recall, và F1-Score.


2. Logistic Regression: Từ đường thẳng sang đường cong xác suất

Ở [Tech Blog #06], chúng ta đã biết Linear Regression dùng để dự đoán một con số liên tục (−∞-\infty đến +∞+\infty). Nhưng trong bài toán phân loại nhị phân (Yes/No, Spam/Ham, Rời bỏ/Ở lại), đầu ra chúng ta cần là một Xác suất (Probability) nằm nghiêm ngặt trong khoảng từ 00 đến 11 (hoặc 0% đến 100%).

Thuật toán kinh điển nhất để làm việc này là Logistic Regression (Hồi quy Logistic).

       Xác suất P(y=1)
          1.0 |                   .-------- (Khách hàng Rời bỏ)
              |                  /
              |                 /  <--- Hàm Sigmoid (Đường cong chữ S)
          0.5 |----------------* (Ngưỡng quyết định - Decision Threshold)
              |               /
              |              /
          0.0 |________.____/________ (Khách hàng Ở lại)
                             Giá trị z = w*x + b

Hàm kích hoạt Sigmoid (S-curve)

Logistic Regression lấy kết quả tuyến tính z=w⋅x+bz = w \cdot x + b và ép nó đi qua hàm Sigmoid: σ(z)=11+e−z\sigma(z) = \frac{1}{1 + e^{-z}}

  • Nếu zz rất lớn →σ(z)≈1\rightarrow \sigma(z) \approx 1 (Xác suất rất cao).
  • Nếu zz rất nhỏ →σ(z)≈0\rightarrow \sigma(z) \approx 0 (Xác suất gần bằng 0).
  • Mặc định, nếu xác suất ≥0.5\ge 0.5 →\rightarrow Dự đoán nhãn 1 (Dương tính - Positive); ngược lại →\rightarrow Dự đoán nhãn 0 (Âm tính - Negative).

3. Ma trận nhầm lẫn (Confusion Matrix) & Bộ thước đo vàng

Khi so sánh kết quả dự đoán với thực tế, mọi dự đoán đều rơi vào 1 trong 4 ô của Confusion Matrix:

Thực tế là DƯƠNG (1)Thực tế là ÂM (0)
Mô hình đoán DƯƠNG (1)True Positive (TP)<br>(Đoán trúng có bệnh)False Positive (FP) - Lỗi loại I<br>(Báo động giả: Khỏe mạnh nhưng đoán có bệnh)
Mô hình đoán ÂM (0)False Negative (FN) - Lỗi loại II<br>(Bỏ sót: Có bệnh nhưng đoán khỏe mạnh)True Negative (TN)<br>(Đoán trúng khỏe mạnh)

3.1. Precision (Độ chuẩn xác)

  • Câu hỏi cốt lõi: "Trong tất cả các trường hợp mô hình đoán là Dương tính, có bao nhiêu phần trăm là đúng thật?"
  • Công thức: Precision=TPTP+FP\text{Precision} = \frac{\text{TP}}{\text{TP} + \text{FP}}
  • Khi nào ưu tiên Precision? Khi chi phí của việc báo động giả (False Positive) quá đắt đỏ.
    • Ví dụ: Bộ lọc Spam Email. Nếu một email quan trọng của đối tác bị mô hình đoán nhầm là Spam (FP) và ném vào thùng rác, người dùng có thể mất hợp đồng tiền tỷ. Thà để sót một vài thư rác vào hộp thư đến còn hơn là chặn nhầm thư quan trọng.

3.2. Recall (Độ bao phủ / Độ nhạy - Sensitivity)

  • Câu hỏi cốt lõi: "Trong tất cả các trường hợp thực tế là Dương tính, mô hình đã tóm được bao nhiêu phần trăm?"
  • Công thức: Recall=TPTP+FN\text{Recall} = \frac{\text{TP}}{\text{TP} + \text{FN}}
  • Khi nào ưu tiên Recall? Khi chi phí của việc bỏ sót (False Negative) là thảm họa.
    • Ví dụ: Chẩn đoán khối u ung thư hoặc phát hiện bom ở sân bay. Báo động giả (FP) chỉ khiến bệnh nhân phải đi xét nghiệm lại lần hai; nhưng nếu bỏ sót một ca ung thư (FN), bệnh nhân có thể mất đi cơ hội chữa trị và nguy hiểm đến tính mạng.

3.3. F1-Score: Cán cân công lý

  • Bản chất: Precision và Recall luôn có sự đánh đổi (Trade-off). Muốn tăng Precision thì Recall thường giảm và ngược lại.
  • F1-Score là Trung bình điều hòa (Harmonic Mean) giữa Precision và Recall, dùng để đánh giá mô hình toàn diện khi dữ liệu bị mất cân bằng: F1-Score=2×Precision×RecallPrecision+Recall\text{F1-Score} = 2 \times \frac{\text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}}

4. Thực hành thực chiến: Huấn luyện & Đánh giá mô hình bằng Python Scikit-Learn

Dưới đây là đoạn mã Python hoàn chỉnh từ khâu chuẩn bị dữ liệu mất cân bằng (Dự đoán khách hàng rời bỏ - Customer Churn), huấn luyện Logistic Regression đến trích xuất Confusion Matrix và Classification Report:

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix, classification_report, roc_auc_score

# 1. Tạo tập dữ liệu mô phỏng bài toán Churn (Mất cân bằng: 85% Ở lại, 15% Rời bỏ)
np.random.seed(42)
n_samples = 1000

# Biến đầu vào: Thời gian dùng app (tháng) và Số lần gọi lên tổng đài hỗ trợ
tenure_months = np.random.exponential(scale=12, size=n_samples) # Dùng càng lâu càng ít rời bỏ
support_calls = np.random.poisson(lam=2, size=n_samples)        # Gọi hỗ trợ càng nhiều càng dễ rời bỏ

# Tạo xác suất rời bỏ thực tế ngầm định
logits = -0.15 * tenure_months + 0.6 * support_calls - 1.5
prob_churn = 1 / (1 + np.exp(-logits))
churn_label = (np.random.rand(n_samples) < prob_churn).astype(int)

df = pd.DataFrame({
    "tenure_months": tenure_months,
    "support_calls": support_calls,
    "churn": churn_label
})

print(f"Tỷ lệ phân phối nhãn Churn:\n{df['churn'].value_counts(normalize=True).round(3)}")

# 2. Chia tập Train (80%) và Test (20%) - Dùng stratify để giữ nguyên tỷ lệ nhãn
X = df[["tenure_months", "support_calls"]]
y = df["churn"]

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

# 3. Huấn luyện mô hình Logistic Regression
# Tham số class_weight='balanced' giúp tự động phạt nặng hơn khi đoán sai nhãn thiểu số
model = LogisticRegression(class_weight="balanced", random_state=42)
model.fit(X_train, y_train)

# 4. Dự đoán nhãn và xác suất trên tập Test
y_pred = model.predict(X_test)
y_prob = model.predict_proba(X_test)[:, 1] # Lấy xác suất của nhãn 1 (Churn)

# 5. Xuất Ma trận nhầm lẫn (Confusion Matrix)
cm = confusion_matrix(y_test, y_pred)
tn, fp, fn, tp = cm.ravel()

print("\n--- MA TRẬN NHẦM LẪN (CONFUSION MATRIX) ---")
print(f"True Negative  (TN - Ở lại đúng) : {tn}")
print(f"False Positive (FP - Báo động giả) : {fp}")
print(f"False Negative (FN - Bỏ sót churn) : {fn}")
print(f"True Positive  (TP - Bắt trúng churn) : {tp}")

# 6. Báo cáo chi tiết Precision, Recall, F1-Score
print("\n--- BÁO CÁO PHÂN LOẠI CHI TIẾT (CLASSIFICATION REPORT) ---")
print(classification_report(y_test, y_pred, target_names=["Ở lại (0)", "Rời bỏ (1)"]))

# 7. Tính chỉ số ROC-AUC (Đo lường khả năng phân biệt giữa 2 lớp)
roc_auc = roc_auc_score(y_test, y_prob)
print(f"Điểm số ROC-AUC Score: {roc_auc:.4f}")

5. Câu hỏi phỏng vấn thực tế (Interview Q&A)

Câu 1 (Mức độ: Entry / Junior)

Câu hỏi: "Tại sao chỉ số Accuracy không đáng tin cậy khi đánh giá mô hình học máy trên dữ liệu mất cân bằng (Imbalanced Data)?"

Gợi ý trả lời:
"Accuracy chỉ đơn thuần lấy tổng số dự đoán đúng chia cho tổng số mẫu. Khi tập dữ liệu bị mất cân bằng nghiêm trọng (ví dụ 99% lớp A và 1% lớp B), một mô hình thô sơ chỉ cần đoán tất cả là lớp A cũng sẽ đạt Accuracy 99%. Mặc dù điểm số cao, mô hình hoàn toàn mất đi khả năng nhận diện lớp thiểu số B — vốn thường là mục tiêu kinh doanh quan trọng nhất (như phát hiện gian lận, bệnh nhân ung thư, hoặc khách hàng rời mạng). Vì vậy, trong dữ liệu mất cân bằng, ta bắt buộc phải sử dụng Precision, Recall, F1-Score hoặc ROC-AUC thay vì Accuracy."

Câu 2 (Mức độ: Junior / Mid-level)

Câu hỏi: "Trong bài toán phát hiện giao dịch lừa đảo ngân hàng, bạn sẽ tối ưu hóa mô hình hướng tới Precision cao hay Recall cao? Tại sao?"

Gợi ý trả lời:
"Trong bài toán lừa đảo ngân hàng, mục tiêu hàng đầu là tối đa hóa Recall để hạn chế tối đa False Negative (bỏ sót kẻ gian). Nếu để sót một giao dịch lừa đảo lớn, khách hàng sẽ mất tiền và uy tín ngân hàng bị tổn hại nghiêm trọng.
Tuy nhiên, chúng ta không thể để Precision quá thấp vì sẽ dẫn đến quá nhiều False Positive (khóa nhầm thẻ của khách hàng lương thiện), gây trải nghiệm cực kỳ tệ cho người dùng. Do đó, giải pháp thực chiến là: Đặt mục tiêu Recall ở mức rất cao (ví dụ ≥90%\ge 90\%), sau đó tối ưu hóa F1-Score hoặc điều chỉnh ngưỡng xác suất (Decision Threshold) kết hợp quy trình xác thực 2 lớp (gửi OTP/gọi điện xác nhận) cho các giao dịch bị nghi ngờ thay vì tự động khóa thẻ ngay lập tức."


6. Key Takeaways (Ghi nhớ nhanh)

  1. Đừng bao giờ tin Accuracy trên dữ liệu mất cân bằng: Hãy mở Ma trận nhầm lẫn (Confusion Matrix) ra trước tiên.
  2. Quy tắc phân biệt Precision vs. Recall:
    • Precision: Sợ bắt nhầm người vô tội (Báo động giả - FP).
    • Recall: Sợ bỏ lọt tội phạm (Bỏ sót - FN).
  3. F1-Score là thước đo cân bằng: Sử dụng trung bình điều hòa để phạt nặng nếu một trong hai chỉ số Precision hoặc Recall quá thấp.
  4. Logistic Regression cho ra xác suất: Bạn hoàn toàn có thể chủ động điều chỉnh ngưỡng quyết định (Threshold, ví dụ nâng lên 0.7 hoặc hạ xuống 0.3) để phù hợp với mục tiêu kinh doanh thay vì giữ cố định mức mặc định 0.5.
0Claps