52 KiB
52 KiB
In [ ]:
import pandas as pd
# Bảng hiện đủ nội dung tin (mặc định pandas cắt ở 50 ký tự)
pd.set_option("display.max_colwidth", 150)
# Đọc file CSV: mỗi dòng 1 tin nhắn, 5 cột
tin = pd.read_csv("~/lab/day1_labs/Manual_SOC_Dataset.csv")
# Đặt tên ngắn cho 5 cột: mã tin, kênh, người gửi, nội dung, nhãn đúng
tin.columns = ["ma", "kenh", "nguoi_gui", "noi_dung", "nhan"]
print("Số tin:", len(tin))
# Đếm mỗi loại nhãn có bao nhiêu tin
print(tin.nhan.value_counts().to_string())
# Dòng cuối của ô = bảng hiện ra bên dưới: 5 tin đầu
tin.head(5)In [ ]:
# ✏️ SỬA: danh sách TỪ KHÓA đáng ngờ (viết chữ thường). Tin nào chứa 1 từ trong đây -> coi là lừa đảo
TU_KHOA = ["thanh toán gấp", "chuyển gấp", "bảo trì hệ thống", "số tài khoản mới",
"tai nạn giao thông", "viện phí", "quên mang ví", "trúng thưởng",
"chiết khấu 50%", "quay trộm", "mờ ám", "bitcoin", "vneid", ".apk"]
# ✏️ SỬA: danh sách TÊN MIỀN giả mạo. Người gửi hoặc nội dung có tên miền này -> lừa đảo
TEN_MIEN = ["doitac-uytln", "gmali", "vnpt-billing-pay", "viettel-nhanthuong",
"shopee-sale-khung", "vieclam-tainha", "cong-an-dieu-tra", "webmail-verify", "micros0ft"]
print(len(TU_KHOA), "từ khóa,", len(TEN_MIEN), "tên miền")In [ ]:
def la_lua_dao(noi_dung, nguoi_gui, tu_khoa, ten_mien):
"""Trả về (True/False, lý do). True = dính luật = lừa đảo."""
# Đổi hết sang chữ thường để "TRÚNG THƯỞNG" cũng khớp "trúng thưởng"
noi_dung = noi_dung.lower()
nguoi_gui = nguoi_gui.lower()
# Luật 1: nội dung có chứa từ khóa nào không?
for tu in tu_khoa:
if tu in noi_dung:
return True, "từ khóa '" + tu + "'"
# Luật 2: người gửi hoặc nội dung có tên miền giả mạo nào không?
for mien in ten_mien:
if mien in nguoi_gui or mien in noi_dung:
return True, "tên miền '" + mien + "'"
# Không dính luật nào -> an toàn
return False, ""
# ✏️ SỬA: viết thử tin nhắn của bạn trong ngoặc kép (nội dung, người gửi) rồi chạy lại
print(la_lua_dao("Chúc mừng bạn đã TRÚNG THƯỞNG 1 chiếc iPhone!", "Shopee", TU_KHOA, TEN_MIEN))
print(la_lua_dao("Tối nay mấy giờ đá bóng nhỉ?", "Hưng IT", TU_KHOA, TEN_MIEN))In [ ]:
def cham_bo_tin(bo_tin, tu_khoa, ten_mien):
"""Chạy la_lua_dao trên từng tin của bảng, thêm 3 cột: may_doan, ly_do, dung"""
kq = bo_tin.copy()
doan = [la_lua_dao(nd, ng, tu_khoa, ten_mien) for nd, ng in zip(kq.noi_dung, kq.nguoi_gui)]
kq["may_doan"] = ["Lừa đảo" if bat else "An toàn" for bat, ly_do in doan]
kq["ly_do"] = [ly_do for bat, ly_do in doan]
# Đúng khi máy đoán "Lừa đảo" và nhãn đúng cũng là lừa đảo (hoặc cả hai đều an toàn)
kq["dung"] = (kq.may_doan == "Lừa đảo") == kq.nhan.str.contains("Lừa đảo")
return kq
kq = cham_bo_tin(tin, TU_KHOA, TEN_MIEN)
print("Đoán đúng:", kq.dung.sum(), "/", len(kq), f"({kq.dung.mean():.0%})")
# Bảng các tin máy đoán SAI (~ nghĩa là "không")
kq[~kq.dung][["ma", "nguoi_gui", "noi_dung", "nhan", "may_doan"]]In [ ]:
import time
# ✏️ SỬA: số tin muốn thử (10_000 = mười nghìn). Thử 100_000 rồi 1_000_000
SO_TIN = 10_000
# Nhân bản 50 tin cho đủ SO_TIN tin
nhieu_tin = pd.concat([tin] * (SO_TIN // len(tin)), ignore_index=True)
bat_dau = time.perf_counter()
kq_nhieu = cham_bo_tin(nhieu_tin, TU_KHOA, TEN_MIEN)
giay = time.perf_counter() - bat_dau
print(f"Máy quét {len(nhieu_tin):,} tin trong {giay:.2f} giây, đúng {kq_nhieu.dung.mean():.0%}")
print(f"Người đọc 10 giây/tin cần {len(nhieu_tin) * 10 / 3600:,.1f} giờ")In [ ]:
# 10 tin lừa đảo được viết lại, tránh đúng các chữ có trong luật
le = pd.read_csv("~/lab/day1_labs/Evasion_Dataset.csv")
le.columns = ["ma", "kenh", "nguoi_gui", "noi_dung", "nhan"]
kq_le = cham_bo_tin(le, TU_KHOA, TEN_MIEN)
print("Bắt được:", (kq_le.may_doan == "Lừa đảo").sum(), "/", len(le), "tin lừa đảo")
kq_le[["ma", "nguoi_gui", "noi_dung", "may_doan", "ly_do"]]In [ ]:
# ✏️ SỬA: thêm từ khóa mới (nhìn các tin BỎ LỌT ở bảng trên để chọn), rồi chạy lại ô này
THEM_TU_KHOA = ["tông xe", "chuyển khoản", "tài khoản"]
TU_KHOA_MOI = TU_KHOA + THEM_TU_KHOA
moi_le = cham_bo_tin(le, TU_KHOA_MOI, TEN_MIEN)
moi_50 = cham_bo_tin(tin, TU_KHOA_MOI, TEN_MIEN)
print("10 tin lách luật: bắt được", (moi_le.may_doan == "Lừa đảo").sum(), "/ 10 (luật cũ: 2)")
print("50 tin cũ : đoán đúng", moi_50.dung.sum(), "/ 50 (luật cũ: 48)")
# Tin AN TOÀN mà luật mới bắt nhầm thành lừa đảo
nham = moi_50[(moi_50.may_doan == "Lừa đảo") & moi_50.nhan.str.contains("An toàn")]
print("Tin an toàn bị báo nhầm:", len(nham))
nham[["ma", "nguoi_gui", "noi_dung", "ly_do"]]In [ ]:
%%bash
# Xem tên 3 file log đầu tiên (mỗi ngày 1 file, tên có ngày)
ls /opt/csai/data/honeypot_m1 | head -3
# Lấy 3 dòng đầu của file ngày 22/09 và trình bày lại cho dễ đọc (python -m json.tool)
head -3 /opt/csai/data/honeypot_m1/cowrie.json.2026-09-22 | python -m json.tool --json-linesIn [ ]:
%%bash
# Có bao nhiêu file log? (ls liệt kê, wc -l đếm số dòng = số file)
ls /opt/csai/data/honeypot_m1 | wc -l
# Cả thư mục nặng bao nhiêu? (du -sh = dung lượng, dạng dễ đọc)
du -sh /opt/csai/data/honeypot_m1
# Tổng cộng bao nhiêu dòng log? (cat ghép 30 file, wc -l đếm dòng)
cat /opt/csai/data/honeypot_m1/cowrie.json.* | wc -lIn [ ]:
%%bash
# (1) Mỗi LOẠI sự kiện có bao nhiêu dòng?
# grep -oh = chỉ lấy phần khớp mẫu "eventid": "..." ; sort | uniq -c = gom giống nhau và đếm ; sort -rn = nhiều nhất lên đầu
grep -oh '"eventid": "[^"]*"' /opt/csai/data/honeypot_m1/cowrie.json.* | sort | uniq -c | sort -rn
# (2) Có bao nhiêu IP KHÁC NHAU đã kết nối?
# lấy dòng kết nối -> cắt riêng phần IP -> sort -u = bỏ trùng -> đếm dòng
grep -h '"cowrie.session.connect"' /opt/csai/data/honeypot_m1/cowrie.json.* | grep -o '"src_ip": "[^"]*"' | sort -u | wc -l
# (3) Top 5 IP kết nối nhiều nhất
grep -h '"cowrie.session.connect"' /opt/csai/data/honeypot_m1/cowrie.json.* | grep -o '"src_ip": "[^"]*"' | sort | uniq -c | sort -rn | head -5In [ ]:
import glob, os
import pandas as pd
# Lấy danh sách 30 file log, đọc từng file (mỗi dòng 1 sự kiện JSON) rồi ghép lại
cac_file = sorted(glob.glob("/opt/csai/data/honeypot_m1/cowrie.json.*"))
df = pd.concat(pd.read_json(f, lines=True, dtype=False, convert_dates=False) for f in cac_file)
print("Đọc xong:", len(df), "dòng, trộn", df.eventid.nunique(), "loại sự kiện,", len(df.columns), "cột:", list(df.columns))
# Mỗi loại sự kiện chỉ dùng vài cột -> các ô còn lại trống (NaN)
df.head(5)In [ ]:
# Kết nối: giờ + IP ; Đăng nhập (thất bại + thành công): IP + username + password ; Phần mềm: IP + tên phần mềm
ket_noi = df[df.eventid == "cowrie.session.connect"][["timestamp", "src_ip"]]
dang_nhap = df[df.eventid.isin(["cowrie.login.failed", "cowrie.login.success"])][["src_ip", "username", "password"]]
phan_mem = df[df.eventid == "cowrie.client.version"][["src_ip", "version"]]
print("ket_noi:", len(ket_noi), "dòng | dang_nhap:", len(dang_nhap), "dòng | phan_mem:", len(phan_mem), "dòng")
dang_nhap.head(3)In [ ]:
# Đếm ô trống trong từng cột của bảng đăng nhập
print("Ô trống mỗi cột:\n" + dang_nhap.isna().sum().to_string())
# Lần thử đăng nhập KHÔNG gửi mật khẩu -> ghi rõ "(không có)", không xóa dòng
dang_nhap = dang_nhap.fillna({"password": "(không có)"})
print("Sau khi điền, còn ô trống:", dang_nhap.password.isna().sum())In [ ]:
# Log ghi giờ UTC (chữ Z ở cuối). Việt Nam = UTC + 7 giờ
ket_noi["gio_vn"] = pd.to_datetime(ket_noi.timestamp, utc=True).dt.tz_convert("Asia/Ho_Chi_Minh")
# Đặt 2 cột cạnh nhau để so, rồi chỉ giữ giờ VN + IP
print(ket_noi[["timestamp", "gio_vn"]].head(3).to_string(index=False))
ket_noi = ket_noi[["gio_vn", "src_ip"]]In [ ]:
# Lưu vào thư mục ~/m12_sach (các bước sau mở lại từ đây, kể cả khi bạn khởi động lại kernel)
thu_muc = os.path.expanduser("~/m12_sach")
os.makedirs(thu_muc, exist_ok=True)
ket_noi.to_csv(thu_muc + "/ket_noi.csv", index=False)
dang_nhap.to_csv(thu_muc + "/dang_nhap.csv", index=False)
phan_mem.to_csv(thu_muc + "/phan_mem.csv", index=False)
print("Đã lưu:", sorted(os.listdir(thu_muc)))
print("395124 dòng trộn lẫn ->", len(ket_noi), "+", len(dang_nhap), "+", len(phan_mem), "dòng trong 3 bảng gọn")In [ ]:
import pandas as pd
# Mở bảng kết nối đã làm sạch ở bước ④
ket_noi = pd.read_csv("~/m12_sach/ket_noi.csv")
# Đếm mỗi IP xuất hiện bao nhiêu lần, xếp nhiều nhất lên đầu
so_lan = ket_noi.src_ip.value_counts()
# len(ket_noi) = số kết nối; len(so_lan) = số IP khác nhau
print("Số lần kết nối:", len(ket_noi), "| Số IP khác nhau:", len(so_lan))
# .head(3) = 3 IP đầu bảng
print("Top 3 IP kết nối nhiều nhất:\n" + so_lan.head(3).to_string())
# (so_lan == 1).sum() = đếm IP chỉ xuất hiện đúng 1 lần
print("Số IP chỉ ghé 1 lần:", (so_lan == 1).sum())In [ ]:
import pandas as pd
ket_noi = pd.read_csv("~/m12_sach/ket_noi.csv")
# Đọc lại cột giờ Việt Nam thành thời gian thật, rồi lấy riêng GIỜ (0-23)
gio = pd.to_datetime(ket_noi.gio_vn, utc=True).dt.tz_convert("Asia/Ho_Chi_Minh").dt.hour
# Đếm số kết nối mỗi giờ, xếp theo giờ từ 0h đến 23h
dem = gio.value_counts().sort_index()
# Mỗi giờ in 1 dòng: giờ | số kết nối | thanh # (giờ đông nhất = 40 dấu #)
for h, n in dem.items():
print(f"{h:02d}h | {n:5d} | " + "#" * int(n * 40 / dem.max()))
# idxmax() = giờ đông nhất; dem.max() / dem.min() = giờ đông gấp mấy lần giờ vắng
print("Giờ đông nhất:", dem.idxmax(), "h (giờ VN) | gấp", round(dem.max() / dem.min(), 1), "lần giờ vắng nhất")In [ ]:
import pandas as pd
# dtype=str: đọc mọi thứ là chữ (mật khẩu "123456" không bị biến thành số); keep_default_na=False: giữ mật khẩu rỗng
dang_nhap = pd.read_csv("~/m12_sach/dang_nhap.csv", dtype=str, keep_default_na=False)
print("Tổng số lần thử đăng nhập:", len(dang_nhap))
# 5 username và 5 password bị thử nhiều nhất
print("Top 5 username:\n" + dang_nhap.username.value_counts().head(5).to_string())
print("Top 5 password:\n" + dang_nhap.password.value_counts().head(5).to_string())In [ ]:
import pandas as pd
phan_mem = pd.read_csv("~/m12_sach/phan_mem.csv")
# Đếm từng tên phần mềm SSH mà kẻ tấn công khai báo
dem = phan_mem.version.value_counts()
print("Top 5 phần mềm:\n" + dem.head(5).to_string())
# dem.index[0] = tên đứng đầu; dem.iloc[0] / len(phan_mem) = tỉ lệ phần trăm của nó
print(f"Riêng {dem.index[0]} chiếm {dem.iloc[0] / len(phan_mem):.0%} số lần khai báo")In [ ]:
import pandas as pd
ket_noi = pd.read_csv("~/m12_sach/ket_noi.csv")
# File CSV lưu thời gian dưới dạng chữ -> đổi lại thành thời gian thật (giờ Việt Nam)
thoi_gian = pd.to_datetime(ket_noi.gio_vn, utc=True).dt.tz_convert("Asia/Ho_Chi_Minh")
# .dt.date = lấy NGÀY -> đếm -> lấy 5 ngày đông nhất
print("TOP 5 NGÀY bị tấn công nhiều nhất:")
print(thoi_gian.dt.date.rename("ngay").value_counts().head(5).to_string())
# .dt.day_name() = lấy THỨ trong tuần (tiếng Anh: Monday, Tuesday...) -> đếm
print("\nTấn công theo THỨ trong tuần:")
print(thoi_gian.dt.day_name().rename("thu").value_counts().to_string())In [ ]:
import pandas as pd
# Mở bảng THỬ ĐĂNG NHẬP. dtype=str: đọc mọi thứ là chữ (mật khẩu "123456" không bị biến thành số)
# keep_default_na=False: giữ nguyên mật khẩu rỗng, không coi là ô trống
dang_nhap = pd.read_csv("~/m12_sach/dang_nhap.csv", dtype=str, keep_default_na=False)
# Ghép 2 cột thành 1 chuỗi "username / password" rồi đếm -> top theo CẶP
print("TOP 10 CẶP username / password:")
print((dang_nhap.username + " / " + dang_nhap.password).value_counts().head(10).to_string())In [ ]:
import pandas as pd
# ===== SỬA DÒNG NÀY: username muốn soi (thử "admin", "ubuntu", "postgres"...) =====
USER_CAN_SOI = "root"
dang_nhap = pd.read_csv("~/m12_sach/dang_nhap.csv", dtype=str, keep_default_na=False)
# Lọc: chỉ giữ các lần thử có username đúng bằng USER_CAN_SOI (== nghĩa là "bằng")
loc = dang_nhap[dang_nhap.username == USER_CAN_SOI]
print("Username", USER_CAN_SOI, "bị thử", len(loc), "lần, từ", loc.src_ip.nunique(), "IP khác nhau")
print("TOP 10 mật khẩu kẻ tấn công thử với", USER_CAN_SOI + ":")
print(loc.password.value_counts().head(10).to_string())In [ ]:
import pandas as pd
# Mở cả 3 bảng sạch
ket_noi = pd.read_csv("~/m12_sach/ket_noi.csv")
dang_nhap = pd.read_csv("~/m12_sach/dang_nhap.csv", dtype=str, keep_default_na=False)
phan_mem = pd.read_csv("~/m12_sach/phan_mem.csv")
# set(...) = tập hợp các IP KHÁC NHAU của mỗi bảng
ip_ket_noi = set(ket_noi.src_ip)
ip_dang_nhap = set(dang_nhap.src_ip)
# Phép trừ tập hợp: IP có kết nối nhưng KHÔNG hề thử mật khẩu -> chỉ "gõ cửa" rồi đi (quét, dò dịch vụ)
print("KIỂU 1 - Chỉ kết nối rồi đi (quét / dò dịch vụ):", len(ip_ket_noi - ip_dang_nhap), "IP")
# Phép giao tập hợp: IP vừa kết nối VỪA thử mật khẩu -> dò mật khẩu (brute-force)
print("KIỂU 2 - Có thử mật khẩu (brute-force): ", len(ip_ket_noi & ip_dang_nhap), "IP")
# Rút gọn tên phần mềm: "SSH-2.0-Go" -> "Go", "SSH-2.0-PuTTY_Release_0.84" -> "PuTTY"
ho_phan_mem = phan_mem.version.str.extract(r"^SSH-[\d.]+-([A-Za-z]+)")[0].fillna("khác").rename("cong_cu")
print("\nTOP 5 CÔNG CỤ tấn công (theo số lần khai báo):")
print(ho_phan_mem.value_counts().head(5).to_string())In [ ]:
import pandas as pd
# ===== SỬA DÒNG NÀY: IP muốn soi (lấy từ kết quả TOP 10 IP ở trên) =====
IP_CAN_SOI = "154.18.197.29"
ket_noi = pd.read_csv("~/m12_sach/ket_noi.csv")
dang_nhap = pd.read_csv("~/m12_sach/dang_nhap.csv", dtype=str, keep_default_na=False)
phan_mem = pd.read_csv("~/m12_sach/phan_mem.csv")
# Lọc từng bảng: chỉ giữ dòng của đúng IP này
kn = ket_noi[ket_noi.src_ip == IP_CAN_SOI]
dn = dang_nhap[dang_nhap.src_ip == IP_CAN_SOI].replace("", "(trống)") # ô rỗng -> ghi "(trống)" cho dễ đọc
pm = phan_mem[phan_mem.src_ip == IP_CAN_SOI]
thoi_gian = pd.to_datetime(kn.gio_vn, utc=True).dt.tz_convert("Asia/Ho_Chi_Minh")
print("HỒ SƠ IP", IP_CAN_SOI)
# .min() = sớm nhất, .max() = muộn nhất, .dt.date.nunique() = số ngày khác nhau, .dt.hour.nunique() = số giờ khác nhau
print(" Lần đầu:", thoi_gian.min(), "| lần cuối:", thoi_gian.max())
print(" Xuất hiện", thoi_gian.dt.date.nunique(), "ngày, trong", thoi_gian.dt.hour.nunique(), "khung giờ khác nhau của ngày")
print(" Số lần kết nối:", len(kn), "| số lần thử đăng nhập:", len(dn))
print(" Phần mềm đã khai:", ", ".join(pm.version.unique()))
print(" TOP 5 username:", ", ".join(dn.username.value_counts().head(5).index))
print(" TOP 5 password:", ", ".join(dn.password.value_counts().head(5).index))In [ ]:
import pandas as pd
ket_noi = pd.read_csv("~/m12_sach/ket_noi.csv")
# Cắt số cuối của IP: "109.160.32.18" -> tách ở dấu chấm CUỐI -> "109.160.32" -> thêm ".0/24"
ket_noi["dai_mang"] = ket_noi.src_ip.str.rsplit(".", n=1).str[0] + ".0/24"
# Gom theo dải: đếm số kết nối và số IP khác nhau trong mỗi dải
bang = ket_noi.groupby("dai_mang").agg(so_ket_noi=("src_ip", "size"), so_ip=("src_ip", "nunique"))
print("TOP 10 DẢI MẠNG /24 (cùng 3 số đầu của IP):")
print(bang.sort_values("so_ket_noi", ascending=False).head(10).to_string())In [ ]:
import pandas as pd
ket_noi = pd.read_csv("~/m12_sach/ket_noi.csv")
ket_noi["ngay"] = pd.to_datetime(ket_noi.gio_vn, utc=True).dt.tz_convert("Asia/Ho_Chi_Minh").dt.date
# Với mỗi IP: đếm số NGÀY khác nhau nó xuất hiện -> xếp giảm dần -> lấy 10
so_ngay = ket_noi.groupby("src_ip").ngay.nunique().sort_values(ascending=False)
print("TOP 10 IP 'kiên trì' nhất (xuất hiện nhiều ngày nhất trong 30 ngày):")
print(so_ngay.head(10).to_string())
# (so_ngay == 30).sum() = có bao nhiêu IP xuất hiện đủ 30/30 ngày
print("Số IP xuất hiện đủ 30/30 ngày:", (so_ngay == 30).sum())
print("Số IP chỉ xuất hiện 1 ngày: ", (so_ngay == 1).sum())In [ ]:
import pandas as pd
# ===== SỬA DÒNG NÀY: ngày muốn soi (giờ Việt Nam), dạng năm-tháng-ngày, trong khoảng 2026-08-24 .. 2026-09-22 =====
NGAY_CAN_SOI = "2026-09-20"
ket_noi = pd.read_csv("~/m12_sach/ket_noi.csv")
thoi_gian = pd.to_datetime(ket_noi.gio_vn, utc=True).dt.tz_convert("Asia/Ho_Chi_Minh")
# Lọc các kết nối có NGÀY đúng bằng ngày cần soi (.astype(str) đổi ngày thành chữ để so sánh)
mot_ngay = ket_noi[thoi_gian.dt.date.astype(str) == NGAY_CAN_SOI]
gio_mot_ngay = thoi_gian[thoi_gian.dt.date.astype(str) == NGAY_CAN_SOI].dt.hour.rename("gio")
print("Ngày", NGAY_CAN_SOI, ":", len(mot_ngay), "kết nối từ", mot_ngay.src_ip.nunique(), "IP")
print("TOP 5 IP trong ngày:")
print(mot_ngay.src_ip.value_counts().head(5).to_string())
print("TOP 3 giờ trong ngày:")
print(gio_mot_ngay.value_counts().head(3).to_string())In [ ]:
import pandas as pd
# 10 email mẫu (tiếng Anh): spam = lừa đảo, ham = email thật
EMAIL = [
("URGENT: Your account will be closed. Please verify your password here.", "spam"),
("Hey team, just checking in on the project status.", "ham"),
("Claim your FREE lottery winnings now! Click this link.", "spam"),
("Meeting notes from yesterday's sync are attached.", "ham"),
("Verify your banking details immediately to prevent suspension.", "spam"),
("Let's grab lunch tomorrow around noon.", "ham"),
("Security Alert: We detected unusual login activity. Reset password.", "spam"),
("Please review the attached invoice for Q3.", "ham"),
("You have 1 new voice message. Listen here.", "spam"),
("Don't forget to submit your timesheets by Friday.", "ham"),
]
# Nhân mỗi email lên 10 bản -> bảng 100 dòng, 2 cột: text, label
email = pd.DataFrame(EMAIL * 10, columns=["text", "label"])
print("Tổng số dòng:", len(email), "| số email KHÁC NHAU:", email.text.nunique())In [ ]:
# ✏️ SỬA: từ khóa đáng ngờ (chữ thường)
TU_KHOA_EMAIL = ["urgent", "verify", "free", "reset", "password"]
for text, label in EMAIL:
# Điểm = số từ khóa xuất hiện trong email
diem = sum(1 for tu in TU_KHOA_EMAIL if tu in text.lower())
print(f"điểm {diem} | thật là: {label:4s} | {text}")In [ ]:
from sklearn.model_selection import GroupShuffleSplit
# ✏️ SỬA: đổi số 42 thành số khác -> máy chia 7/3 theo cách khác -> kết quả các ô sau đổi theo
HAT_GIONG = 42
# groups=email.text: 10 bản sao của một email luôn đi CÙNG nhau (hoặc cùng vào học, hoặc cùng vào thi)
chia = GroupShuffleSplit(n_splits=1, test_size=0.3, random_state=HAT_GIONG)
vi_tri_hoc, vi_tri_thi = next(chia.split(email, email.label, groups=email.text))
hoc, thi = email.iloc[vi_tri_hoc], email.iloc[vi_tri_thi]
print("Phần HỌC:", hoc.text.nunique(), "email (", len(hoc), "dòng) | Phần THI:", thi.text.nunique(), "email (", len(thi), "dòng)")
print("Email vừa có trong phần học vừa có trong phần thi:", len(set(hoc.text) & set(thi.text)))
print("Đề thi:")
for t in thi.text.unique():
print(" -", t)In [ ]:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
# Lập từ điển từ phần HỌC (bỏ các từ quá phổ biến tiếng Anh như "the", "your"), biến mỗi email thành 1 hàng số
tfidf = TfidfVectorizer(stop_words="english")
X_hoc = tfidf.fit_transform(hoc.text)
X_thi = tfidf.transform(thi.text)
print("Từ điển máy học được:", len(tfidf.vocabulary_), "từ")
print(sorted(tfidf.vocabulary_))
# Naive Bayes học: từ nào hay đi với spam, từ nào hay đi với ham
mo_hinh = MultinomialNB().fit(X_hoc, hoc.label)
print("Đã học xong.")In [ ]:
from sklearn.metrics import accuracy_score, precision_score, recall_score, confusion_matrix
doan = mo_hinh.predict(X_thi)
print(f"Accuracy (đúng bao nhiêu phần) : {accuracy_score(thi.label, doan):.2f}")
print(f"Precision (báo spam thì đúng bao nhiêu) : {precision_score(thi.label, doan, pos_label='spam', zero_division=0):.2f}")
print(f"Recall (bắt được bao nhiêu spam thật) : {recall_score(thi.label, doan, pos_label='spam', zero_division=0):.2f}")
print("Bảng nhầm lẫn (hàng = thật ham/spam, cột = máy đoán ham/spam):")
print(confusion_matrix(thi.label, doan, labels=["ham", "spam"]))
print()
for t in thi.text.unique():
du_doan = mo_hinh.predict(tfidf.transform([t]))[0]
that = email[email.text == t].label.iloc[0]
print("ĐÚNG" if du_doan == that else "SAI ", f"| máy đoán {du_doan:4s} | thật là {that:4s} |", t)In [ ]:
# ✏️ SỬA: viết email của bạn (tiếng Anh) trong ngoặc kép rồi chạy lại
EMAIL_CUA_TOI = "Please verify your password to claim a free gift"
xac_suat = mo_hinh.predict_proba(tfidf.transform([EMAIL_CUA_TOI]))[0]
for nhan, p in zip(mo_hinh.classes_, xac_suat):
print(f"{nhan}: {p:.0%}")
print("=> máy đoán:", mo_hinh.classes_[xac_suat.argmax()])In [ ]:
from sklearn.model_selection import train_test_split
# Cách chia SAI: chia ngẫu nhiên 100 DÒNG 70/30, không để ý các dòng là bản sao của nhau
hoc2, thi2 = train_test_split(email, test_size=0.3, random_state=42)
trung = set(hoc2.text) & set(thi2.text)
print("Đề thi có", thi2.text.nunique(), "email khác nhau,", len(trung), "email trong số đó ĐÃ CÓ trong phần học")
tfidf2 = TfidfVectorizer(stop_words="english")
mo_hinh2 = MultinomialNB().fit(tfidf2.fit_transform(hoc2.text), hoc2.label)
print(f"Accuracy: {accuracy_score(thi2.label, mo_hinh2.predict(tfidf2.transform(thi2.text))):.2f}")