Files
labs/Notebooks-CSAI/Lab8_Deepfake_Detector.ipynb

46 KiB

Module 8 — Bắt video deepfake bằng "dấu vân tay thống kê": luật tay đối đầu Random Forest

Bài thực hành Module 8 (GAN cho an ninh mạng). Chạy lần lượt từ trên xuống (Shift + Enter), kernel csai. Mỗi ô là một bước nhỏ, chạy đâu hiểu đấy. Chỗ có # ✏️ SỬA: là anh/chị đổi số rồi chạy lại để xem thay đổi.

Vì sao bài này dùng dữ liệu tự sinh, không phải log honeypot? Các bài khác dùng log tấn công thật của máy chủ. Nhưng deepfake thì không có kho video thật để đưa vào lớp. Nên ở đây ta tự sinh 300 clip bằng bộ sinh số ngẫu nhiên — và anh/chị sẽ đọc từng dòng của bộ sinh đó, nên biết chính xác dữ liệu từ đâu ra. Đây đúng là tinh thần Module 8: GAN sinh ra thứ giả, còn ta học cách phát hiện thứ giả đó.

🎯 Mục tiêu của bài

Học xong bài này, bạn:

  1. Đặt được bài toán xác minh danh tính bằng video: quyết định gì, sai thì mất gì (Phần 0).
  2. Đọc bộ sinh dữ liệu để biết dữ liệu từ đâu ra, và nhìn dữ liệu thô (Phần 1).
  3. Kiểm tra làm sạch, và hiểu vì sao dữ liệu tự sinh "quá sạch" so với đời thực (Phần 2).
  4. Chạy luật tay của chuyên gia và thấy nó lọt loại deepfake tinh vi (Phần 3).
  5. Tạo thêm đặc trưng, huấn luyện Random Forest, so công bằng với luật tay trên cùng tập kiểm tra (Phần 4).
  6. Đọc độ quan trọng đặc trưng và đề xuất giải pháp kết hợp (Phần 5, 6).

🗺️ Bài này: bài toán gì, mô hình nào, vì sao?

  • Bài toán: mỗi clip video → thật (cho qua) hay deepfake (chặn): phân loại 2 lớp; bỏ lọt đắt hơn báo nhầm.
  • Dữ liệu: bảng số: 5 chỉ số đo được của mỗi clip, có nhãn (tự sinh, Phần 1).
  • Vì vậy chọn: mốc = luật tay 3 ngưỡng; có nhãn + bảng số → Random Forest (kết hợp mềm dẻo nhiều chỉ số). Bảng ① ② ở mục Trước khi bắt đầu ngay dưới đây.
  • Xử lý gì trước: hiểu dữ liệu từ đâu ra (đọc bộ sinh), tạo thêm đặc trưng gộp (Phần 4.1).
  • Thước đo: recall (bắt được bao nhiêu deepfake) trước, rồi báo nhầm; xem riêng loại tinh vi.

Pipeline — cách làm mọi bài toán AI trong khoá, nhớ thuộc lòng 7 bước. Mỗi phần ghi 📍 Bước ở đầu:

Bước Câu hỏi phải trả lời Phần
1. Thu thập dữ liệu dữ liệu gì, từ đâu ra, có nhãn hay không nhãn? Phần 1 (5 câu hỏi, bộ sinh, dữ liệu thô)
2. Làm sạch dữ liệu có ô trống, trùng, giá trị lạ không? Phần 2
3. Chọn mô hình phù hợp với bài toán bài toán gì, sai thì mất gì; cách cũ (luật tay) làm được đến đâu? Phần 0, 3
4. Coding tạo đặc trưng, chuẩn hoá, chia học / kiểm tra, huấn luyện Random Forest Phần 4
5. Lưu ý khi coding lỗi nào làm so sánh sai mà không báo lỗi? 4.2 (chia giữ đúng tỉ lệ), 4.3 (so trên cùng tập kiểm tra)
6. Đánh giá các mô hình bắt được bao nhiêu, theo từng tầng; mô hình dựa vào đâu? 4.3, Phần 5
7. Quy tắc bàn tay phải ✋** giải pháp, mang gì theo khi đi làm Phần 6

📌 Tra cứu nhanh: Chọn mô hình nào? Khi nào dùng gì, đánh đổi gì — mục Trước khi bắt đầu, ngay dưới đây.

Trước khi bắt đầu — Chọn mô hình nào? Khi nào dùng gì, đánh đổi gì

(Đọc thêm — để tra lại khi cần. Lần đầu có thể đọc lướt, chạy xong bài rồi quay lại.)

Không có mô hình "tốt nhất" cho mọi bài toán (No Free Lunch). Chọn mô hình = trả lời lần lượt 4 câu hỏi:

① Có đáp án (nhãn) không?

  • Có nhãn từ quá khứ (email đã biết sạch / lừa đảo, file đã biết là mã độc) → học có giám sát: Naïve Bayes, Logistic Regression, cây quyết định, Random Forest, XGBoost, SVM.
  • Không có nhãn, hoặc phải tìm cái chưa từng thấy (zero-day) → học không giám sát: Isolation Forest tìm điểm lạ, K-Means gom nhóm hành vi (ví dụ UBA: tách bot tự động khỏi thao tác của người mà không cần ai gắn nhãn). ⚠️ Mô hình không giám sát chỉ nói "khác đám đông", không nói "xấu". Trên honeypot, đám đông chính là kẻ tấn công (Lab 3, 5.0).
  • Có rất ít nhãn → bắt đầu bằng luật + không giám sát, rồi gom nhãn dần từ các cảnh báo đã được người phân tích xác nhận.

② Dữ liệu dạng gì?

Dạng dữ liệu Thường dùng Trong khoá
Bảng số (mỗi đối tượng = một hàng số) mô hình cây: cây quyết định, Random Forest, XGBoost · Logistic Regression làm mốc so sánh Lab 3 (mỗi IP 7 số), M5, Lab 7 (hồ sơ hành vi), M8–M10
Văn bản (email, tin nhắn, log dạng chữ) Naïve Bayes / Logistic Regression trên số đếm từ (TF-IDF) · mô hình ngôn ngữ (BERT, LLM) khi cần hiểu ngữ cảnh M1–M2 (Naïve Bayes), Lab 4 (Perceptron, SVM, Logistic Regression, Naive Bayes, Voting), Lab 10
Chuỗi theo thời gian (số kết nối mỗi giờ, CPU mỗi phút, chuỗi lời gọi API của một chương trình) phân rã chuỗi thời gian (xu hướng + nhịp ngày + phần dư) · HMM, LSTM khi cần học thứ tự sự kiện M4.2, Lab 6 Phần 3
Tệp nhị phân (mã độc) trích đặc trưng (kích thước, entropy…) rồi dùng mô hình cây · đổi file thành ảnh + CNN là hướng nghiên cứu, cần nhiều dữ liệu và GPU M5

③ Sợ bỏ lọt hay sợ báo nhầm?

  • Sợ bỏ lọt (mã độc, xâm nhập, lỗ hổng khi pentest) → ưu tiên recall. Sợ báo nhầm (chặn email khách hàng, khoá tài khoản nhân viên) → ưu tiên precision.
  • ⚠️ Hiểu lầm hay gặp: "muốn precision cao thì chọn Naïve Bayes / SVM". Thực ra gần như mọi mô hình đều ra một điểm / xác suất, và ngưỡng cắt điểm đó mới quyết định đánh đổi: hạ ngưỡng → bắt thêm nhưng báo nhầm thêm (Lab 3 9E.3; Lab 4 9.3). Chọn mô hình tốt rồi chọn ngưỡng theo sức đội SOC.
  • Xác suất của Naïve Bayes thường quá tự tin (sát 0 hoặc 1); SVM mặc định không ra xác suất → muốn đặt ngưỡng theo xác suất phải hiệu chỉnh (calibration) trước.
  • Tìm bất thường / gom nhóm: không có đáp án để tính precision, recall → dùng điểm bất thường, độ tách nhóm (Silhouette), và quan trọng nhất là người phân tích xem lại top N (Lab 3, 5.6).

④ Ràng buộc khi chạy thật

  • Giải thích được: cần báo cáo kiểm toán / ban giám đốc → hộp trắng (luật, cây quyết định nông, Logistic Regression, Naïve Bayes: đọc được nhánh IF/ELSE hoặc trọng số từ). Cần bắt kẻ tinh vi (APT, tấn công "chậm mà chắc") → chấp nhận hộp đen (Random Forest nhiều cây, XGBoost, deep learning) rồi giải trình bằng SHAP (Lab 3 Phần 7, Lab 6 4.2).
  • Tốc độ / phần cứng: router, IoT, máy trạm (RAM < 1 GB, CPU yếu) → luật, cây nông, Logistic Regression, Isolation Forest nhỏ. Trung tâm dữ liệu / cloud có GPU → deep learning, LLM.
  • Mất cân bằng lớp (tấn công hiếm): không mô hình nào tự xử lý — phải chỉnh class_weight hoặc ngưỡng. Lab 3 đo ở 9E: tấn công càng hiếm, mô hình mặc định càng bỏ lọt nhiều; class_weight="balanced" bắt thêm được, đổi lại báo nhầm tăng.
  • Bị né: mô hình dựa vào đặc trưng mà kẻ tấn công tự đổi được (tên phần mềm, tốc độ thử) thì dễ bị né (Lab 3 Phần 8, Lab 4 Phần 10).

Bảng tra nhanh: khi nào dùng gì, đánh đổi gì

Công cụ Dùng khi Được Phải chấp nhận Trong khoá
Luật / ngưỡng (IF/ELSE) kiểu tấn công đã biết rõ nhanh, không cần dữ liệu học, ai cũng hiểu chỉ bắt đúng cái đã viết; dễ bị né (thử chậm, Lab 3 1.2) M1, Lab 3 Phần 1
Naïve Bayes văn bản, ít dữ liệu, cần nhanh học rất nhanh, chạy được trên máy yếu, xem được từ nào "nặng ký" coi các từ độc lập với nhau; xác suất quá tự tin; bị lách bằng cách đổi từ M1–M2, Lab 4 Phần 7
Logistic Regression cần mốc so sánh đơn giản, cần giải thích bằng trọng số nhanh, ổn định, xác suất dùng được chỉ vẽ được ranh giới "thẳng"; quan hệ phức tạp thì kém Lab 4 Phần 6
SVM dữ liệu vừa, nhiều chiều (văn bản) tách tốt khi ít mẫu chậm khi dữ liệu lớn; mặc định không ra xác suất; khó giải thích Lab 4 Phần 4–5
Perceptron mốc đơn giản nhất: "cộng điểm từng đặc trưng" cực đơn giản, nhanh, tự tính tay được chỉ ranh giới thẳng; không ra xác suất; dễ bị lách Lab 4 Phần 3
Hội đồng (Voting) đã có vài mô hình khá, muốn bù chỗ sai cho nhau thường ổn định hơn từng mô hình chậm hơn, khó giải thích hơn; chia phiếu đều không tự động hơn mô hình giỏi nhất Lab 4 Phần 8
Cây quyết định cần luật đọc được cho người không chuyên ra đúng dạng IF/ELSE cây sâu dễ "học thuộc lòng" (overfit); dữ liệu đổi chút là cây đổi Lab M2 (M2.3), M5
Random Forest dữ liệu bảng có nhãn, cần ổn định chính xác, ít phải tinh chỉnh, ít overfit hơn một cây khó đọc từng quyết định (cần SHAP / độ quan trọng đặc trưng); nặng hơn một cây Lab 3 Phần 9, M8–M10
XGBoost dữ liệu bảng lớn, cần chính xác nhất thường đứng đầu trên dữ liệu bảng; chạy được GPU nhiều tham số phải chỉnh, chỉnh ẩu dễ overfit; khó giải thích M5, Lab 10 5.1
Isolation Forest không có nhãn, tìm cái lạ không cần ai gắn nhãn; ra thứ hạng ưu tiên "lạ" ≠ "xấu"; dữ liệu bẩn làm lệch (Lab 3 Phần 6); phải chọn contamination nếu dùng predict (Lab 3, 5.6) Lab 3 Phần 5, Lab 6 Phần 4, Lab 7 Phần 7
K-Means gom nhóm hành vi (người dùng, IP) thấy được "các kiểu" hành vi phải chọn trước số nhóm k; phải chuẩn hoá; chỉ hợp với nhóm "tròn", cỡ gần nhau Lab 6 Phần 6, Lab 7
Phân rã chuỗi thời gian số liệu theo giờ / ngày có nhịp lặp tách nhịp bình thường khỏi đột biến cần chuỗi đủ dài; nhịp thay đổi thì phải tính lại M4.2, Lab 6 Phần 3
Deep learning (LSTM, CNN) dữ liệu rất lớn, dạng chuỗi / ảnh học được mẫu phức tạp cần rất nhiều dữ liệu và GPU; hộp đen Lab 10 5.1 (mạng nơ-ron MLP nhỏ)
LLM đọc, tóm tắt, giải thích log / cảnh báo viết báo cáo nhanh có thể bịa; tốn tiền; không để LLM tự quyết định chặn Lab 3 Phần 10

Bài này dùng gì? dữ liệu bảng số có nhãn (5 chỉ số mỗi clip) → mốc là luật tay 3 ngưỡng, mô hình là Random Forest (học có giám sát trên bảng số).

Tóm lại: chọn mô hình là bài toán cân đối (thiết kế cả hệ thống, không chỉ chọn một thuật toán): biết mình có dữ liệu gì (có nhãn không, dạng bảng / chữ / chuỗi) và cần tối ưu điều gì (bỏ lọt hay báo nhầm, giải thích hay tốc độ). Hệ thống thật hầu như luôn kết hợp nhiều lớp (luật + không giám sát + có giám sát) — quy tắc bàn tay ở Lab 3 Phần 11.

📌 Pipeline xử lý luôn là 7 bước (chi tiết từng bước ở bảng đầu bài):

  1. Thu thập dữ liệu — có nhãn hay không nhãn?
  2. Làm sạch dữ liệu
  3. Chọn mô hình phù hợp với bài toán (4 câu hỏi ① – ④ ở trên)
  4. Coding
  5. Lưu ý khi coding — những lỗi không báo lỗi
  6. Đánh giá các mô hình
  7. Quy tắc bàn tay phải ✋

Phần 0 — Đặt vấn đề

📍 Bước 3 (đặt bài toán): quyết định gì, sai thì mất gì — phải rõ trước khi chọn mô hình.

Một công ty cho nhân viên làm việc từ xa. Để xác minh danh tính (KYC), mỗi người quay một clip video ngắn. Tình báo an ninh cảnh báo: khoảng 40% clip gửi tới là deepfake — khuôn mặt do AI (GAN) tạo ra để mạo danh.

  • Quyết định của hệ thống: mỗi clip → "thật" (cho qua) hay "deepfake" (chặn, bắt xác minh lại).
  • Sai kiểu 1 — bỏ lọt (false negative): một deepfake được cho qua → kẻ gian vào được hệ thống. Rất nguy hiểm.
  • Sai kiểu 2 — báo nhầm (false positive): một người thật bị chặn → phiền, mất thời gian, nhưng không mất an ninh.

Ta không xem được khuôn mặt (ở đây), chỉ có 5 con số đo được từ mỗi clip. Câu hỏi: 5 con số đó có đủ để tách deepfake khỏi người thật không? Và luật ngưỡng do chuyên gia đặt tay so với một mô hình học máy — cái nào tốt hơn, tốt hơn ở đâu?

👉 Mục tiêu bài học: thấy luật tay bắt tốt deepfake lộ liễu nhưng bỏ trắng loại tinh vi; còn Random Forest kết hợp cả 5 chỉ số (thêm 2 chỉ số tự tạo) bắt được cả loại tinh vi.

In [ ]:
# Thư viện dùng trong cả bài. Chạy ô này một lần.
import numpy as np                 # tính toán trên mảng số
import pandas as pd                # bảng dữ liệu (DataFrame), như Excel trong Python
import matplotlib.pyplot as plt    # vẽ biểu đồ
%matplotlib inline

pd.set_option("display.width", 160)
pd.set_option("display.max_colwidth", 40)
print("numpy", np.__version__, "| pandas", pd.__version__)

Phần 1 — Dữ liệu — 5 chỉ số của mỗi clip, và 3 tầng deepfake

📍 Bước 1 — Thu thập dữ liệu.

5 câu hỏi về dữ liệu — trả lời trước khi viết dòng code nào:

Câu hỏi Trả lời cho dữ liệu bài này
Dữ liệu gì? bảng số: mỗi clip video một hàng, 5 chỉ số đo được + tầng (tier) + nhãn (ground_truth)
Lấy ở đâu? tự sinh bằng bộ sinh ở 1.1 (hạt giống cố định) — khoá học không có kho video deepfake thật để đưa vào lớp
Thu thập thế nào? mỗi chỉ số bốc ngẫu nhiên trong một khoảng: người thật trong khoảng "bình thường"; deepfake có xác suất rơi vào khoảng "đáng ngờ", giảm dần từ tầng 1 tới tầng 3
Có nhãn chưa? có: ground_truth do chính bộ sinh đặt → học có giám sát. Ngoài đời, nhãn phải do người thẩm định xác minh từng clip
Có cần làm sạch? dữ liệu tự sinh nên sạch — Phần 2 vẫn kiểm tra; dữ liệu thật sẽ có ô trống, trùng lặp, giá trị vô lý

Mỗi clip được rút gọn thành 5 con số (đo được bằng xử lý ảnh/âm thanh, không cần AI):

Chỉ số (tên cột) Đo cái gì Người thật Deepfake hay bị
frame_rate_std độ dao động tốc độ khung hình ổn định (0.5–2.0) giật cục (thấp bất thường)
skin_color_entropy độ "giàu" màu da (độ hỗn loạn màu) cao (2.5–4.5) thấp (<2.0): da mịn giả tạo
blink_interval_std độ lệch nhịp chớp mắt cao (1.5–4.0) thấp (<0.3): chớp đều như máy
audio_sync_delta_ms lệch tiếng–hình (mili giây) nhỏ (10–80) quá khớp (<4) hoặc lệch hẳn (>150)
lip_movement_score độ tự nhiên cử động môi cao (0.65–0.95) thấp (0.30–0.65)

Deepfake chia 3 tầng theo độ tinh vi: t1 lộ liễu (nhiều chỉ số sai rõ), t2 khá, t3 tinh vi (chỉ sai một phần, rất giống thật). Càng tầng cao càng khó bắt — đó là điểm mấu chốt của bài.

1.1 Bộ sinh dữ liệu — đọc từng dòng để biết dữ liệu từ đâu ra

Ô dưới tạo ra 300 clip. Không có gì bí ẩn: mỗi chỉ số được bốc ngẫu nhiên trong một khoảng. Người thật bốc trong khoảng "bình thường"; deepfake có xác suất bốc vào khoảng "đáng ngờ", xác suất đó càng cao ở tầng thấp (lộ liễu), càng thấp ở tầng 3 (tinh vi). seed=42 để cả lớp ra bộ dữ liệu giống hệt nhau.

In [ ]:
# ====== BỘ SINH 300 CLIP (seed cố định => cả lớp giống nhau) ======
SEED = 42                          # ✏️ SỬA: đổi seed để ra một bộ dữ liệu khác (số sẽ lệch chút ít)
rng = np.random.default_rng(SEED)  # bộ sinh số ngẫu nhiên riêng, không ảnh hưởng chỗ khác

FEATURES = ["frame_rate_std", "skin_color_entropy", "blink_interval_std", "audio_sync_delta_ms", "lip_movement_score"]

# Khoảng "đáng ngờ" (fake-like): trông lạ nhưng CHƯA chắc vượt ngưỡng chặn của luật tay
FAKE_LIKE = {"frame_rate_std": (0.05, 0.5), "skin_color_entropy": (2.0, 2.8),
             "blink_interval_std": (0.3, 0.9), "lip_movement_score": (0.30, 0.65)}
# Khoảng "tố cáo mạnh" (strong): vượt hẳn ngưỡng chặn (da < 2.0, chớp mắt < 0.3)
STRONG = {"skin_color_entropy": (1.5, 1.95), "blink_interval_std": (0.1, 0.28)}

def real_base(n):
    # Người thật: mọi chỉ số nằm trong khoảng bình thường
    return {"frame_rate_std": rng.uniform(0.5, 2.0, n), "skin_color_entropy": rng.uniform(2.5, 4.5, n),
            "blink_interval_std": rng.uniform(1.5, 4.0, n), "audio_sync_delta_ms": rng.uniform(10, 80, n),
            "lip_movement_score": rng.uniform(0.65, 0.95, n)}

def real(n):
    d = real_base(n)
    # ~12% người thật có ĐÚNG MỘT chỉ số lạ (webcam kém, đèn xấu) => luật một chiều không bao giờ hoàn hảo
    for i in np.where(rng.random(n) < 0.12)[0]:          # np.where(...)[0] = vị trí các clip "có 1 chỉ số lạ"
        f = rng.choice(list(FAKE_LIKE))                  # chọn ngẫu nhiên 1 trong các chỉ số
        d[f][i] = rng.uniform(*FAKE_LIKE[f])             # gán cho nó một giá trị lạ
    return d

def sync_fake(n):
    # Âm thanh của deepfake: hoặc quá khớp (<4 ms) hoặc lệch hẳn (>150 ms)
    return np.where(rng.random(n) < 0.5, rng.uniform(0.5, 3.9, n), rng.uniform(151, 220, n))

def mix(n, probs, sync_share):
    # probs[chỉ_số] = (xác suất chỉ số này "fake-like", xác suất "tố cáo mạnh" nếu đã fake-like)
    d = real_base(n)
    for f, (pr, strong) in probs.items():
        vals = rng.uniform(*FAKE_LIKE[f], size=n)
        if f in STRONG:                                  # với da & chớp mắt: có thể đẩy sâu vào vùng "tố cáo mạnh"
            vals = np.where(rng.random(n) < strong, rng.uniform(*STRONG[f], size=n), vals)
        d[f] = np.where(rng.random(n) < pr, vals, d[f])  # với xác suất pr thì lấy giá trị đáng ngờ, không thì giữ bình thường
    d["audio_sync_delta_ms"] = np.where(rng.random(n) < sync_share, sync_fake(n), d["audio_sync_delta_ms"])
    return d

# 3 tầng: xác suất "đáng ngờ" giảm dần => tầng 3 rất giống thật
t1 = lambda n: mix(n, {"frame_rate_std": (0.95, 0), "skin_color_entropy": (0.99, 0.97), "blink_interval_std": (0.99, 0.97), "lip_movement_score": (0.9, 0)}, 0.98)
t2 = lambda n: mix(n, {"frame_rate_std": (0.5, 0),  "skin_color_entropy": (0.90, 0.85), "blink_interval_std": (0.90, 0.85), "lip_movement_score": (0.6, 0)}, 0.90)
t3 = lambda n: mix(n, {"frame_rate_std": (0.4, 0),  "skin_color_entropy": (0.70, 0.80), "blink_interval_std": (0.70, 0.80), "lip_movement_score": (0.5, 0)}, 0.55)

parts = []
for ten, ham, n, nhan in [("real", real, 180, 0), ("deepfake_t1", t1, 40, 1), ("deepfake_t2", t2, 40, 1), ("deepfake_t3", t3, 40, 1)]:
    d = pd.DataFrame(ham(n)); d["tier"] = ten; d["ground_truth"] = nhan   # ground_truth = đáp án thật, GIẤU khi làm luật tay
    parts.append(d)
clips = pd.concat(parts, ignore_index=True).sample(frac=1, random_state=SEED).reset_index(drop=True)  # trộn đều
clips.insert(0, "clip_id", ["CLIP-%03d" % (i + 1) for i in range(len(clips))])
clips[FEATURES] = clips[FEATURES].round(3)

# 📦 clips = DataFrame 300 dòng × 8 cột (clip_id, 5 chỉ số, tier, ground_truth)
print("📦 clips =", type(clips).__name__, clips.shape, "| cột:", list(clips.columns))
print("   thật:", int((clips.ground_truth == 0).sum()), "| deepfake:", int((clips.ground_truth == 1).sum()),
      "=> tỉ lệ deepfake %.0f%%" % (100 * clips.ground_truth.mean()))

Kết quả (seed 42): clips là bảng 300 dòng × 8 cột. 180 clip thật, 120 deepfake → deepfake chiếm 40%. ground_truth (0 = thật, 1 = deepfake) là đáp án thật, ta giấu nó ở phần luật tay và chỉ dùng để chấm điểm.

1.2 Xem dữ liệu thô — thử "nhìn bằng mắt" xem clip nào giả

In [ ]:
# 10 clip đầu, KHÔNG xem cột ground_truth (giả vờ ta chưa biết đáp án)
clips.drop(columns="ground_truth").head(10)

Đố nhanh (nhìn bằng mắt): trong 10 dòng trên, clip nào anh/chị nghĩ là deepfake? Dựa vào cột nào? Rất khó đúng không — mắt người không nhìn ra "entropy màu da 1.8 là bất thường". Đó là lý do cần quy tắc/mô hình. Giờ xem phân bố từng chỉ số, tách theo thật / deepfake.

Cách đọc biểu đồ dưới: mỗi ô là một chỉ số. Cột xanh = clip thật, cột đỏ = deepfake. Trục ngang = giá trị chỉ số, trục dọc = có bao nhiêu clip rơi vào khoảng đó. Nếu hai màu tách nhau → chỉ số đó phân biệt tốt; nếu chồng lên nhau → chỉ số đó một mình không đủ.

In [ ]:
fig, ax = plt.subplots(2, 3, figsize=(13, 6))
for a, f in zip(ax.ravel(), FEATURES):
    a.hist(clips[clips.ground_truth == 0][f], bins=30, alpha=.6, color="tab:blue", label="thật")
    a.hist(clips[clips.ground_truth == 1][f], bins=30, alpha=.6, color="tab:red", label="deepfake")
    a.set_title(f); a.set_yticks([])
ax.ravel()[0].legend()
ax.ravel()[-1].axis("off")   # ô thừa (5 chỉ số / 6 ô)
plt.tight_layout(); plt.show()

👉 Hiểu: skin_color_entropy và blink_interval_std tách rõ nhất — deepfake dồn về bên trái (giá trị thấp). Nhưng vẫn có vùng chồng nhau ở giữa: đó là các deepfake tầng 3 (tinh vi) trốn trong khoảng của người thật, và vài người thật có một chỉ số lạ. audio_sync_delta_ms thì deepfake nằm ở cả hai cực (quá khớp hoặc lệch hẳn) — một đường thẳng không cắt được, nên chỉ số này cần kết hợp với chỉ số khác.


Phần 2 — Làm sạch — và một sự thật cần nói thẳng

📍 Bước 2 — Làm sạch dữ liệu.

Trước khi chạy mô hình, luôn kiểm tra dữ liệu "bẩn" không: thiếu giá trị, sai kiểu, trùng lặp, ngoài khoảng.

In [ ]:
print("Thiếu giá trị mỗi cột (NaN):")
print(clips[FEATURES].isna().sum().to_string())                 # .isna() = True chỗ trống; .sum() = đếm
print("\nSố clip trùng hệt nhau:", int(clips[FEATURES].duplicated().sum()))
print("\nKhoảng giá trị (min → max) từng chỉ số:")
print(clips[FEATURES].agg(["min", "max"]).round(2).to_string())

Kết quả: 0 ô trống, 0 dòng trùng, mọi giá trị nằm trong khoảng hợp lý. Dữ liệu sạch tinh.

👉 Sự thật cần nói thẳng: dữ liệu tự sinh bao giờ cũng sạch hơn đời thực. Log thật (như các bài honeypot) sẽ có ô trống, giá trị âm vô lý, trùng lặp, ký tự lạ — và ta phải xử lý (điền trung vị, bỏ dòng hỏng, ép kiểu). Ở đây không có gì để làm sạch, nên ta chỉ kiểm tra và xác nhận. Đừng vì bài này sạch mà tưởng bước làm sạch là thừa — ở dữ liệu thật nó chiếm phần lớn công sức. (Nhắc lại: ~12% clip thật ở Phần 1 có một chỉ số lạ — đó là "nhiễu thật", ta giữ nguyên, không coi là bẩn.)


Phần 3 — Thuật toán 1 — Luật tay của chuyên gia (3 điều kiện AND)

📍 Bước 3 — Chọn mô hình (mốc so sánh): cách cũ — luật tay — làm được đến đâu?

Chuyên gia SOC nhìn dữ liệu và đặt ra một quy tắc: chặn clip khi CẢ BA điều kiện cùng đúng.

skin_color_entropy < 2.0 VÀ blink_interval_std < 0.3 VÀ (audio_sync_delta_ms > 150 hoặc < 4)

Dùng AND (cả ba) để chắc tay — chỉ chặn khi rất nghi. Xem nó bắt được bao nhiêu.

In [ ]:
# Luật tay: trả về True (nghi là deepfake) cho mỗi clip. Dùng & (và), | (hoặc) trên cả cột cùng lúc.
def luat_tay(df):
    return (df.skin_color_entropy < 2.0) & (df.blink_interval_std < 0.3) & \
           ((df.audio_sync_delta_ms > 150) | (df.audio_sync_delta_ms < 4))

co_ngo = luat_tay(clips)              # 📦 co_ngo = Series 300 phần tử True/False, True = luật nghi deepfake
that = clips.ground_truth == 1        # 📦 that = Series True ở các clip thực sự là deepfake (đáp án)
print("📦 co_ngo: True =", int(co_ngo.sum()), "clip bị luật nghi | ví dụ 5 giá trị đầu:", co_ngo.head().tolist())

# Bảng nhầm lẫn (confusion): luật nói gì vs sự thật
tp = int((co_ngo & that).sum());  fn = int((~co_ngo & that).sum())    # bắt đúng deepfake / bỏ lọt deepfake
fp = int((co_ngo & ~that).sum()); tn = int((~co_ngo & ~that).sum())   # báo nhầm người thật / để yên người thật
print("\n                  luật CHẶN   luật cho qua")
print("  deepfake (120)   %6d       %5d   <- bỏ lọt %d" % (tp, fn, fn))
print("  người thật(180)  %6d       %5d" % (fp, tn))
print("\n  recall (bắt được bao nhiêu %% deepfake) = %.1f%%" % (100 * tp / (tp + fn)))
print("  precision (chặn đúng bao nhiêu %%)       = %.1f%%" % (100 * tp / max(1, tp + fp)))

Kết quả (seed 42): luật bắt 65/120 deepfake, bỏ lọt 55, và không báo nhầm người thật nào. → recall 54.2%, precision 100%. Chặt tay nên không oan ai, nhưng hơn nửa số deepfake đi lọt.

3.1 Vì sao lọt? Tách theo từng tầng

In [ ]:
print("Luật tay bắt được bao nhiêu / mỗi tầng:")
for tang in ["deepfake_t1", "deepfake_t2", "deepfake_t3"]:
    m = clips.tier == tang
    print("  %-12s %2d / %2d" % (tang, int((co_ngo & m).sum()), int(m.sum())))

Kết quả: t1 (lộ liễu) 37/40, t2 22/40, t3 (tinh vi) chỉ 6/40.

👉 Hiểu: luật AND đòi cả ba chỉ số cùng vượt ngưỡng. Deepfake tinh vi chỉ đẩy một phần các chỉ số, hiếm khi đủ cả ba → lọt gần hết. Muốn bắt t3 phải nới một ngưỡng, nhưng nới thì sẽ bắt đầu báo nhầm người thật (nhớ 12% người thật có một chỉ số lạ). Đây chính là bế tắc của luật cứng: chặt thì lọt, lỏng thì oan. Cần cách kết hợp nhiều chỉ số mềm dẻo hơn.


Phần 4 — Thuật toán 2 — Random Forest (rừng cây quyết định)

📍 Bước 4 — Coding (4.1, 4.2), Bước 5 — Lưu ý (chia giữ tỉ lệ, so trên cùng tập) và Bước 6 — Đánh giá (4.3).

Ý tưởng: thay vì ta đặt ngưỡng tay, để máy tự tìm ngưỡng từ dữ liệu. Một cây quyết định là chuỗi câu hỏi "chỉ số X có < ngưỡng không?" dẫn tới kết luận. Một cây dễ học vẹt. Random Forest = hàng trăm cây, mỗi cây xem một phần dữ liệu/đặc trưng, rồi bỏ phiếu — nhờ vậy vững hơn và ít học vẹt.

Thư viện: scikit-learn (sklearn) — bộ công cụ học máy chuẩn của Python. Ta dùng: RandomForestClassifier (mô hình), train_test_split (chia học/kiểm tra), StandardScaler (đưa các chỉ số về cùng thang), và các hàm chấm điểm.

4.1 Thêm 2 đặc trưng tự tạo (feature engineering)

Đôi khi tỉ lệ hay tích của hai chỉ số nói nhiều hơn từng chỉ số riêng. Ta tạo thêm:

  • blink_skin_ratio = blink_interval_std / skin_color_entropy — kết hợp hai dấu hiệu mạnh nhất vào một con số.
  • sync_entropy_product = audio_sync_delta_ms × skin_color_entropy.
In [ ]:
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix
import time

d = clips.copy()
d["blink_skin_ratio"]     = d.blink_interval_std / d.skin_color_entropy     # tỉ lệ 2 dấu hiệu mạnh
d["sync_entropy_product"] = d.audio_sync_delta_ms * d.skin_color_entropy    # tích tiếng-hình × màu da
DAC_TRUNG = FEATURES + ["blink_skin_ratio", "sync_entropy_product"]         # 5 gốc + 2 tự tạo = 7
print("📦 DAC_TRUNG =", DAC_TRUNG)
d[DAC_TRUNG].head(3)

4.2 Đưa về cùng thang (StandardScaler) rồi chia học / kiểm tra

Các chỉ số có thang rất khác nhau (audio_sync tới 220, lip_movement dưới 1). StandardScaler biến mỗi cột về trung bình 0, độ lệch 1 — để không cột nào "to tiếng" chỉ vì đơn vị lớn. (Với cây thì không bắt buộc, nhưng là thói quen tốt và cần cho các mô hình khác.) Sau đó chia 70% để học, 30% để kiểm tra. Mô hình không bao giờ thấy 30% này lúc học.

In [ ]:
y = d.ground_truth.values                              # 📦 y = mảng 0/1, đáp án; CHỈ dùng để học & chấm, không phải đặc trưng
X = StandardScaler().fit_transform(d[DAC_TRUNG])       # 📦 X = mảng 300×7 đã chuẩn hoá

# stratify=y: giữ đúng tỉ lệ 40% deepfake ở cả phần học lẫn phần kiểm tra
Xtr, Xte, ytr, yte, idx_tr, idx_te = train_test_split(X, y, d.index, test_size=0.3, stratify=y, random_state=42)
print("học:", len(Xtr), "clip | kiểm tra:", len(Xte), "clip (deepfake trong phần kiểm tra: %d)" % int(yte.sum()))

# ✏️ SỬA: thử SO_CAY = 50 hay 500, SAU_TOI_DA = 4 hay 12 rồi chạy lại 4.2–4.3 xem điểm đổi thế nào
SO_CAY = 300
SAU_TOI_DA = 8
rf = RandomForestClassifier(n_estimators=SO_CAY, max_depth=SAU_TOI_DA, n_jobs=2, random_state=42)
t0 = time.perf_counter()
rf.fit(Xtr, ytr)                                       # học từ 70%
print("đã học %d cây trong %.2f giây" % (SO_CAY, time.perf_counter() - t0))

4.3 Chấm điểm trên 30% chưa từng thấy — và so thẳng với luật tay trên cùng 30% clip đó

In [ ]:
du_doan = rf.predict(Xte)                              # 📦 du_doan = mảng 0/1 mô hình đoán cho 90 clip kiểm tra
tn, fp, fn, tp = confusion_matrix(yte, du_doan, labels=[0, 1]).ravel()
print("RANDOM FOREST trên 90 clip kiểm tra:")
print("  acc=%.3f  precision=%.3f  recall=%.3f  f1=%.3f | bỏ lọt deepfake=%d  báo nhầm=%d" % (
      accuracy_score(yte, du_doan), precision_score(yte, du_doan), recall_score(yte, du_doan), f1_score(yte, du_doan), fn, fp))

# luật tay trên ĐÚNG 90 clip đó (công bằng: cùng tập kiểm tra)
luat_te = luat_tay(d.loc[idx_te]).astype(int).values
print("\nLUẬT TAY trên cùng 90 clip:")
print("  acc=%.3f  precision=%.3f  recall=%.3f  f1=%.3f" % (
      accuracy_score(yte, luat_te), precision_score(yte, luat_te, zero_division=0), recall_score(yte, luat_te), f1_score(yte, luat_te)))

print("\nBắt được / mỗi tầng (trên 90 clip kiểm tra):")
tier_te = d.loc[idx_te, "tier"].values
for tang in ["deepfake_t1", "deepfake_t2", "deepfake_t3"]:
    m = tier_te == tang
    print("  %-12s Random Forest %2d/%2d | luật tay %2d/%2d" % (
          tang, int(((du_doan == 1) & m).sum()), int(m.sum()), int(((luat_te == 1) & m).sum()), int(m.sum())))

Kết quả (seed 42):

Trên 90 clip kiểm tra recall (bắt deepfake) precision bỏ lọt báo nhầm
Random Forest 97.2% 94.6% 1 2
Luật tay 52.8% 100% 17 0

Và theo tầng, chỗ khác biệt lớn nhất nằm ở tầng tinh vi: deepfake_t3 Random Forest bắt 12/13, còn luật tay chỉ bắt 2/13.

👉 Hiểu: Random Forest bắt gần gấp đôi số deepfake (97.2% so với 52.8%). Cái giá là 2 lần báo nhầm người thật (precision 94.6% thay vì 100% của luật tay) — một đánh đổi thường đáng, vì bỏ lọt một deepfake (kẻ gian vào được) nguy hiểm hơn làm phiền một người thật. Random Forest làm được vì kết hợp mềm dẻo cả 7 chỉ số thay vì đòi 3 ngưỡng cứng cùng đúng — nên tóm được cả loại tinh vi mà luật tay bỏ trắng.


Phần 5 — Đặc trưng nào "tố cáo" deepfake mạnh nhất?

📍 Bước 6 — Đánh giá các mô hình: mô hình dựa vào chỉ số nào?

Random Forest cho biết nó dựa vào chỉ số nào nhiều nhất (feature_importances_). Tổng các cột = 1 (100%).

Cách đọc: cột càng dài → mô hình càng dựa vào chỉ số đó để phân biệt. Đây là toàn bộ mô hình, không phải một clip.

In [ ]:
tam_quan_trong = pd.Series(rf.feature_importances_, index=DAC_TRUNG).sort_values()
ax = tam_quan_trong.plot.barh(figsize=(8, 4), color="tab:purple")
ax.set_xlabel("độ quan trọng (tổng = 1)"); ax.set_title("Chỉ số nào tố cáo deepfake nhất")
for i, v in enumerate(tam_quan_trong):
    ax.text(v + .003, i, "%.3f" % v, va="center", fontsize=9)
plt.tight_layout(); plt.show()
print(tam_quan_trong.sort_values(ascending=False).round(3).to_string())

Kết quả (seed 42): skin_color_entropy 0.324, blink_interval_std 0.248, rồi đặc trưng tự tạo blink_skin_ratio 0.171 — ba cái đầu đã chiếm ~3/4. Đúng với biểu đồ phân bố ở Phần 1: hai chỉ số đó tách thật/giả rõ nhất.

👉 Hiểu: GAN giỏi làm khuôn mặt trông thật, nhưng để lại dấu vân tay thống kê: da quá mịn (entropy thấp), chớp mắt quá đều. Đặc trưng tự tạo blink_skin_ratio lọt vào top 3 → đôi khi gộp hai dấu hiệu mạnh hơn để riêng.


Phần 6 — Giải pháp & kết luận

📍 Bước 7 — Quy tắc bàn tay phải: giải pháp và kết luận.

Bài toán: tách video deepfake khỏi người thật, chỉ từ 5 chỉ số đo được, bỏ lọt càng ít càng tốt.

Luật tay (3 ngưỡng AND) Random Forest (7 đặc trưng)
Bắt deepfake (recall, 90 clip) 52.8% 97.2%
Báo nhầm người thật 0 2
Bắt loại tinh vi (t3) 2/13 12/13
Hiểu/giải thích rất dễ (3 dòng) khó hơn (xem độ quan trọng)
Cập nhật khi deepfake đổi chiêu phải sửa tay học lại từ dữ liệu mới

Giải pháp đề xuất: dùng cả hai tầng. Luật tay làm "bộ lọc nhanh, giải thích được" cho ca lộ liễu; Random Forest bắt phần tinh vi còn lại. Luôn giữ người thẩm định cho các ca mô hình phân vân (xác suất gần 50%).

Nối sang Module 9: ta vừa thấy mô hình dựa nhiều nhất vào skin_color_entropy. Câu hỏi của kẻ tấn công: "nếu tôi chỉnh riêng chỉ số đó cho giống thật thì sao?" — đó chính là tấn công mô hình (adversarial / AI pentesting), nội dung Module 9. Phòng thủ: đừng phụ thuộc một đặc trưng; theo dõi phân bố đầu vào; học lại khi dữ liệu đổi.

Bài học rút ra:

  1. 5 con số đo được đủ để tách phần lớn deepfake — không cần xem mặt.
  2. Luật cứng chặt thì lọt, lỏng thì oan; mô hình học nhiều đặc trưng mềm dẻo hơn nên bắt được loại tinh vi.
  3. GAN để lại dấu vân tay thống kê (da, nhịp chớp mắt) — và đó là chỗ ta bắt nó.
  4. Dữ liệu tự sinh sạch hơn đời thực: ở log thật, bước làm sạch mới là phần tốn công nhất.

Quy tắc bàn tay ✋ áp vào bài này

Xòe bàn tay, đếm từ ngón cái: 5 điều không được bỏ của mọi bài toán phát hiện tấn công bằng AI (đầy đủ ở Lab 3 Phần 11). Bài này đã làm đủ 5 điều:

Ngón Điều Câu hỏi phải tự trả lời Trong bài này Quên điều này thì…
👍 1. Ngón cái DỮ LIỆU sạch Có ô trống, trùng, giá trị lạ không? 0 ô trống, 0 dòng trùng (Phần 2) — dữ liệu tự sinh sạch hơn đời thực chuyển sang video thật mà quên bước kiểm tra
☝️ 2. Ngón trỏ ĐẶC TRƯNG đúng Chỉ số nào tố cáo deepfake? Gộp lại có mạnh hơn không? 5 chỉ số + 2 đặc trưng tự tạo (4.1); blink_skin_ratio vào top 3 (Phần 5) chỉ dùng 3 ngưỡng cứng cùng đúng
✋ 3. Ngón giữa CHỌN CÔNG CỤ Có nhãn, bảng số → dùng gì? luật tay làm mốc (Phần 3), Random Forest (Phần 4) chỉ luật tay: bắt 2 / 13 deepfake tinh vi (4.3)
💍 4. Ngón áp út ĐO trung thực So trên cùng tập chưa? Xem theo từng tầng chưa? cùng 90 clip kiểm tra: Random Forest bắt 97,2%, luật tay 52,8%; tầng 3: 12 / 13 so với 2 / 13 (4.3) so trên hai tập khác nhau → kết luận sai
🤙 5. Ngón út GIẢI THÍCH & CẢNH GIÁC Mô hình dựa vào đâu? Kẻ tấn công nắn được không? skin_color_entropy 0,324, blink_interval_std 0,248 (Phần 5) — đúng chỗ kẻ tấn công sẽ nắn (Module 9); giữ người thẩm định ca phân vân (Phần 6) phụ thuộc một chỉ số; bị lừa mà không biết