46 KiB
46 KiB
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__)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()))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)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()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())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)))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())))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)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))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())))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())