Xử lý dữ liệu, tăng cường dữ liệu, dữ liệu mất cân bằng
Mục tiêu
Phần tiêu đề “Mục tiêu”Sau bài này, bạn sẽ:
- Xử lý được ba vấn đề hay gặp của bảng dữ liệu thật: giá trị thiếu (missing value), biến phân loại (categorical variable) và đặc trưng lệch thang đo, bằng
SimpleImputer,OneHotEncoder,StandardScalergộp trongColumnTransformer. - Hiểu vì sao mọi bước xử lý phải học chỉ từ tập train, và nhận ra rò rỉ dữ liệu (data leakage) khi bước xử lý được khớp trên toàn bộ dữ liệu.
- Biết tăng cường dữ liệu (data augmentation) là gì và khi nào dùng được.
- Xử lý được dữ liệu mất cân bằng (imbalanced data): đo bằng precision, recall, F1, chia bằng
stratify, và chọn giữa trọng số lớp, lấy mẫu lại, dời ngưỡng.
Cần biết trước
Phần tiêu đề “Cần biết trước”- Đánh giá mô hình: Accuracy, Precision, Recall, F1, thuộc phần Học máy (Đã học)
- Numpy và Pandas, thuộc phần Python (Đã học)
Trực giác
Phần tiêu đề “Trực giác”Bảng dữ liệu thật không sạch
Phần tiêu đề “Bảng dữ liệu thật không sạch”Bạn muốn đoán giá thuê căn hộ. Lần này dữ liệu không phải bảng gọn gàng như bài Hồi quy tuyến tính, mà là 8 căn hộ do người thuê tự điền qua một biểu mẫu:
| Căn | Diện tích (m²) | Tầng | Quận | Ban công |
|---|---|---|---|---|
| 1 | 30 | 5 | Cầu Giấy | có |
| 2 | 45 | trống | Ba Đình | không |
| 3 | trống | 12 | Hoàng Mai | có |
| 4 | 60 | 3 | Cầu Giấy | trống |
| 5 | 52 | 8 | Hoàng Mai | không |
| 6 | 38 | 2 | Ba Đình | có |
| 7 | 75 | 15 | trống | có |
| 8 | 41 | 6 | Cầu Giấy | không |
Bảng này có ba chỗ mà mô hình chưa nuốt được:
- Ô trống. Mô hình tuyến tính tính . Một ô trống trong làm phép tính đó vô nghĩa, và scikit-learn sẽ báo lỗi.
- Cột chữ. “Cầu Giấy” không nhân được với trọng số. Phải đổi chữ thành số, nhưng đổi thế nào cho khỏi bịa ra quan hệ không có thật?
- Thang đo lệch nhau. Diện tích lên tới hàng chục, tầng chỉ vài đơn vị, còn cột “có/không” sẽ thành 0 và 1. Bài Gradient Descent đã cho thấy thang đo lệch làm GD chậm hẳn.
Lớp hiếm và dữ liệu ít
Phần tiêu đề “Lớp hiếm và dữ liệu ít”Một bài toán khác: đoán giao dịch thẻ nào là gian lận. Cứ 100 giao dịch chỉ có khoảng 5 gian lận. Một mô hình lười, luôn nói “không gian lận”, đúng tới 95%. Như bài Đánh giá mô hình: Accuracy, Precision, Recall, F1 đã chỉ ra, accuracy cao mà mô hình vô dụng, vì nó không bắt được giao dịch gian lận nào.
Còn khi dữ liệu quá ít, ví dụ vài trăm ảnh, bạn có thể tự tạo thêm mẫu: một tấm ảnh mèo lật ngang vẫn là ảnh mèo.
Bài toán của bài này là: biến một bảng dữ liệu thật (có ô trống, có cột chữ, thang đo lệch, lớp hiếm) thành đầu vào mà mô hình học được, mà không để thông tin của tập kiểm tra lọt vào quá trình học.
Lý thuyết
Phần tiêu đề “Lý thuyết”Giá trị thiếu
Phần tiêu đề “Giá trị thiếu”Bỏ đi. Cách đơn giản nhất là bỏ mọi dòng có ô trống (df.dropna()). Ở bảng trên, bốn ô trống nằm ở bốn căn khác nhau, nên bỏ dòng là mất một nửa dữ liệu. Bỏ cả cột chỉ hợp lý khi cột đó thiếu gần hết. Bỏ dòng còn nguy hiểm ở chỗ khác: nếu người thuê căn rẻ hay bỏ trống ô diện tích, phần còn lại không còn đại diện cho dữ liệu thật.
Điền vào (impute). Thay ô trống bằng một giá trị tính từ cột đó:
- Trung bình (mean) hoặc trung vị (median) cho cột số. Diện tích của 7 căn có dữ liệu có trung bình và trung vị . Trung vị ít bị kéo bởi điểm ngoại lai (outlier): thay căn 75 m² bằng một biệt thự 500 m², trung bình nhảy lên khoảng 109, còn trung vị vẫn là 45.
- Giá trị hay gặp nhất (mode) cho cột phân loại. Cột quận có Cầu Giấy 3 lần, nên căn 7 được điền Cầu Giấy.
Thêm cờ đánh dấu thiếu. Bản thân việc thiếu có thể mang thông tin: có thể chủ nhà cố ý giấu diện tích của căn quá nhỏ. Vì vậy, ngoài việc điền, người ta thường thêm một cột 0/1 (missing indicator) cho biết ô đó vốn bị trống. Mô hình tự học xem cờ này có đáng tin không.
Biến phân loại
Phần tiêu đề “Biến phân loại”Cách nghĩ ngay ra là đánh số: Ba Đình = 0, Cầu Giấy = 1, Hoàng Mai = 2, gọi là mã hóa thứ tự (ordinal encoding). Với mô hình tuyến tính, phần đóng góp của cột quận vào giá là :
Mô hình bị ép tin rằng ba quận cách đều nhau theo đúng thứ tự bạn tình cờ đặt, và Cầu Giấy luôn nằm “giữa” hai quận kia. Đổi thứ tự đánh số là đổi mô hình, dù dữ liệu không đổi gì. Đó là bẫy.
One-hot encoding thay một cột quận bằng ba cột 0/1, mỗi quận một cột. Mỗi căn có đúng một số 1:
| Căn | Quận | quận Ba Đình | quận Cầu Giấy | quận Hoàng Mai |
|---|---|---|---|---|
| 1 | Cầu Giấy | 0 | 1 | 0 |
| 2 | Ba Đình | 1 | 0 | 0 |
| 3 | Hoàng Mai | 0 | 0 | 1 |
Giờ mỗi quận có trọng số riêng , , , và mô hình tự học xem quận nào đắt hơn bao nhiêu. Giá phải trả là số cột: cột có 500 giá trị khác nhau thành 500 cột.
Mã hóa thứ tự chỉ đúng khi các giá trị có thứ tự thật, ví dụ nội thất “không, cơ bản, đầy đủ” (OrdinalEncoder). Cây quyết định (bài Decision Tree và Random Forest) cũng chịu được mã thứ tự tốt hơn mô hình tuyến tính, vì nó chỉ hỏi “mã có nhỏ hơn ngưỡng không”.
Một chi tiết khi dùng thật: tập test có thể có quận chưa từng xuất hiện ở tập train. OneHotEncoder(handle_unknown="ignore") khi đó cho cả ba cột bằng 0 thay vì báo lỗi.
Co giãn đặc trưng
Phần tiêu đề “Co giãn đặc trưng”Hai cách hay dùng, cho đặc trưng thứ :
Chuẩn hóa z-score (standardization, StandardScaler) trừ trung bình và chia cho độ lệch chuẩn , nên cột mới có trung bình 0, độ lệch chuẩn 1. Chuẩn hóa min-max (MinMaxScaler) đưa cột về đoạn , như bài Gradient Descent. Min-max nhạy với điểm ngoại lai hơn: một căn 500 m² làm mọi căn còn lại dồn về gần 0.
Mô hình nào cần co giãn?
- KNN (bài K-Nearest Neighbors): khoảng cách cộng bình phương chênh lệch của từng cột, nên cột có số lớn nhất quyết định ai là láng giềng.
- Gradient descent: thang đo lệch làm mặt mất mát thành thung lũng hẹp, GD chậm hoặc phân kỳ.
- Mô hình có phạt (bài Ridge, Lasso, Elastic Net): khoản phạt đối xử mọi như nhau, nên độ lớn của không được phụ thuộc vào đơn vị.
Mô hình không cần: cây quyết định và rừng ngẫu nhiên. Cây chỉ chia mẫu theo câu hỏi kiểu “diện tích ?”. Sau z-score, câu hỏi thành ”?”, vẫn chia đúng những căn đó, nên cây không đổi.
Điểm ngoại lai. Trước khi xử lý, hãy xem điểm đó là lỗi nhập liệu (diện tích 4500 m² vì gõ thừa số 0) hay là thật (một căn penthouse). Lỗi thì sửa hoặc bỏ. Thật thì giữ, và cân nhắc trung vị khi điền, hay RobustScaler (co giãn theo trung vị và khoảng tứ phân vị) thay vì min-max. Bài Hồi quy tuyến tính có ví dụ một điểm ngoại lai kéo lệch cả đường thẳng.
Rò rỉ dữ liệu khi xử lý
Phần tiêu đề “Rò rỉ dữ liệu khi xử lý”Để ý rằng mọi bước ở trên đều học một thứ gì đó từ dữ liệu: trung vị để điền, danh sách quận để one-hot, và để chuẩn hóa. Theo đúng tinh thần của bài Tổng quan về Học máy, những thứ đó phải học chỉ từ tập train. Quy tắc của scikit-learn:
- Trên tập train:
fit(học tham số xử lý) rồitransform, viết gọn làfit_transform. - Trên tập test và dữ liệu mới: chỉ
transform, dùng lại đúng tham số đã học.
Nếu bạn fit bộ chuẩn hóa trên toàn bộ dữ liệu rồi mới chia, đã “thấy” tập test. Với chuẩn hóa, sai lệch thường nhỏ. Nhưng với bước dùng cả nhãn, như chọn những đặc trưng tương quan với nhãn mạnh nhất, sai lệch có thể khổng lồ: ô code thứ hai cho thấy một mô hình đạt 85% trên dữ liệu hoàn toàn ngẫu nhiên.
Kiểm định chéo (cross-validation) làm việc này khó hơn, vì mỗi fold có tập train riêng, nên phải khớp lại bộ xử lý ở mỗi fold. Pipeline lo việc đó: nó nối các bước xử lý với mô hình thành một khối có fit và predict. Khi cross_val_score khớp pipeline trên một fold, mọi bước bên trong chỉ học từ phần train của fold ấy. ColumnTransformer cho phép mỗi nhóm cột một chuỗi xử lý riêng (cột số thì điền trung vị và chuẩn hóa, cột chữ thì điền mode và one-hot), rồi ghép kết quả lại.
Tăng cường dữ liệu
Phần tiêu đề “Tăng cường dữ liệu”Tăng cường dữ liệu là tạo thêm mẫu train bằng cách biến đổi mẫu có sẵn theo những cách không làm đổi nhãn. Mô hình học được rằng các biến đổi đó không quan trọng, nên ít học thuộc chi tiết vặt của từng mẫu.
- Ảnh: lật ngang, xoay nhẹ, cắt một phần, đổi độ sáng. Phải chọn biến đổi hợp với bài toán: lật ngang ảnh mèo vẫn là mèo, nhưng xoay ngược chữ số 6 thành 9 thì nhãn sai. Phần Thị giác máy tính sẽ nói kỹ.
- Dữ liệu bảng: cộng nhiễu nhỏ vào cột số, ví dụ với ngẫu nhiên nhỏ so với . Cách này thường kém hiệu quả hơn ở ảnh, vì khó biết biến đổi nào giữ nguyên nhãn.
- Văn bản ngắn: thay vài từ bằng từ đồng nghĩa, hoặc dịch sang ngôn ngữ khác rồi dịch ngược lại (back-translation).
Chỉ tăng cường tập train, và chỉ sau khi đã chia. Bản biến đổi của một ảnh train mà lọt vào tập test thì gần như là mẫu trùng.
Dữ liệu mất cân bằng
Phần tiêu đề “Dữ liệu mất cân bằng”Đo cho đúng. Accuracy bị lớp đông lấn át, nên hãy nhìn precision, recall và F1 của lớp hiếm (bài Đánh giá mô hình: Accuracy, Precision, Recall, F1). Khi chia train/test, dùng train_test_split(..., stratify=y) để hai tập giữ đúng tỉ lệ lớp. Không có stratify, với 100 mẫu chỉ 5 mẫu hiếm, tập test có thể rơi vào cảnh chỉ có 0 hay 1 mẫu hiếm.
Mô hình học từ dữ liệu lệch thường đẩy lớp hiếm sang một góc: sai cả 5 mẫu hiếm vẫn rẻ hơn sai vài trong 95 mẫu đông. Có ba nhóm cách chữa.
1. Trọng số lớp (class weight). Nhân mất mát của mỗi mẫu với trọng số của lớp nó. Gọi là mất mát của mẫu thứ , là trọng số của lớp :
với là số lớp và là số mẫu của lớp . Công thức của là chế độ class_weight="balanced" của scikit-learn. Nhân với được cho mọi lớp: mỗi lớp góp cùng một tổng trọng số, dù đông hay hiếm. Ở ô code thứ ba, tập train có , , , nên:
Mỗi giao dịch gian lận nặng gần 18 lần một giao dịch thường, đúng bằng .
2. Lấy mẫu lại (resampling) tập train.
- Oversampling ngẫu nhiên: bốc lặp lại (có hoàn lại) các mẫu lớp hiếm cho tới khi đủ số. Một mẫu xuất hiện lần góp vào tổng mất mát, giống hệt trọng số . Vì vậy oversampling ngẫu nhiên gần như tương đương trọng số lớp, chỉ tốn bộ nhớ hơn.
- Undersampling: bỏ bớt mẫu lớp đông. Nhanh, nhưng vứt đi dữ liệu thật; hợp khi lớp đông có hàng triệu mẫu.
- SMOTE (Chawla và cộng sự, 2002): thay vì chép nguyên, tạo mẫu hiếm mới nằm giữa một mẫu hiếm và một láng giềng gần của nó (cũng thuộc lớp hiếm) :
Với , và , mẫu mới là , trung điểm của đoạn nối hai mẫu. SMOTE có trong thư viện imbalanced-learn; thư viện này không có sẵn trong bộ gói Pyodide mà trang dùng, nên ô code của bài tự viết oversampling ngẫu nhiên bằng NumPy.
3. Dời ngưỡng quyết định. Giữ nguyên mô hình, chỉ hạ ngưỡng : dự đoán lớp hiếm khi xác suất , với nhỏ hơn 0.5. Recall tăng, precision thường giảm. Ngưỡng là siêu tham số, chọn trên tập kiểm định như bài Đánh giá mô hình: Accuracy, Precision, Recall, F1.
Như tăng cường dữ liệu, mọi cách lấy mẫu lại chỉ áp dụng trên tập train. Tập test phải giữ đúng tỉ lệ thật, vì đó là tỉ lệ mô hình sẽ gặp khi dùng.
Giới hạn
Phần tiêu đề “Giới hạn”- Không có công thức xử lý chung. Điền trung vị hay thêm cờ, giữ hay bỏ điểm ngoại lai: tùy dữ liệu và mô hình. Hãy so các lựa chọn bằng kiểm định chéo, với mọi bước nằm trong pipeline. Bài Dự án ML từ đầu đến cuối làm điều này trên một bộ dữ liệu trọn vẹn.
- Không cách chữa mất cân bằng nào tạo ra thông tin mới. Trọng số lớp, oversampling hay dời ngưỡng chủ yếu đổi chỗ đánh đổi giữa precision và recall (ô code thứ ba). Muốn tốt hơn thật thì cần thêm dữ liệu lớp hiếm hoặc đặc trưng tốt hơn.
- Tăng cường dữ liệu dựa vào hiểu biết về bài toán. Biến đổi sai làm đổi nhãn, và mô hình học điều sai.
Minh họa
Phần tiêu đề “Minh họa”Bảng dưới gom mọi bước của bài: mỗi bước học gì từ tập train, dùng lớp nào của scikit-learn, và mô hình nào cần nó. Cột thứ hai là danh sách những thứ không được học từ tập test.
| Bước | Học gì từ tập train | scikit-learn | Ai cần |
|---|---|---|---|
| Điền cột số | trung bình hoặc trung vị | SimpleImputer(strategy="median") | mọi mô hình không nhận NaN |
| Cờ đánh dấu thiếu | cột nào có ô trống | MissingIndicator, hoặc add_indicator=True | khi việc thiếu có thể mang thông tin |
| Điền cột phân loại | giá trị hay gặp nhất | SimpleImputer(strategy="most_frequent") | mọi mô hình |
| One-hot | danh sách giá trị của cột | OneHotEncoder(handle_unknown="ignore") | mô hình tuyến tính, KNN, mạng nơ-ron |
| Chuẩn hóa | , (hoặc min, max) | StandardScaler, MinMaxScaler | KNN, GD, mô hình có phạt; cây thì không |
| Chọn đặc trưng | đặc trưng nào liên quan tới nhãn | SelectKBest | khi có rất nhiều đặc trưng |
| Trọng số lớp | số mẫu mỗi lớp | class_weight="balanced" | dữ liệu mất cân bằng |
| Lấy mẫu lại, tăng cường | mẫu nào để nhân bản, biến đổi | tự viết, hoặc imbalanced-learn | dữ liệu mất cân bằng, dữ liệu ít |
Ví dụ code
Phần tiêu đề “Ví dụ code”Ba ô dưới đây chạy độc lập. Ô chạy đầu tiên lâu hơn, vì trình duyệt phải tải thêm scikit-learn (và pandas ở ô thứ nhất).
Một ColumnTransformer cho bảng 8 căn hộ
Phần tiêu đề “Một ColumnTransformer cho bảng 8 căn hộ”Ô đầu tiên xử lý đúng bảng ở mục Trực giác (tên quận viết không dấu). Cột số được điền trung vị rồi chuẩn hóa z-score; hai cột cờ đánh dấu ô số nào bị trống; cột phân loại được điền mode rồi one-hot.
Điền, one-hot, chuẩn hóa
import numpy as npimport pandas as pdfrom sklearn.compose import ColumnTransformerfrom sklearn.impute import MissingIndicator, SimpleImputerfrom sklearn.pipeline import make_pipelinefrom sklearn.preprocessing import OneHotEncoder, StandardScaler
# 8 căn hộ tự tạo; np.nan và None là ô bị thiếu.df = pd.DataFrame({ "area": [30, 45, np.nan, 60, 52, 38, 75, 41], "floor": [5, np.nan, 12, 3, 8, 2, 15, 6], "district": ["Cau Giay", "Ba Dinh", "Hoang Mai", "Cau Giay", "Hoang Mai", "Ba Dinh", None, "Cau Giay"], "balcony": ["yes", "no", "yes", None, "no", "yes", "yes", "no"],})print(df.isna().sum().to_dict()) # số ô thiếu mỗi cột
# Cột số: điền trung vị rồi chuẩn hóa z-score.num = make_pipeline(SimpleImputer(strategy="median"), StandardScaler())# Cột phân loại: điền giá trị hay gặp nhất (mode) rồi one-hot.cat = make_pipeline(SimpleImputer(strategy="most_frequent"), OneHotEncoder(handle_unknown="ignore"))prep = ColumnTransformer([ ("num", num, ["area", "floor"]), ("miss", MissingIndicator(), ["area", "floor"]), # cờ 1 = ô bị thiếu ("cat", cat, ["district", "balcony"]),], verbose_feature_names_out=False)
X = prep.fit_transform(df) # học trung vị, mode, mu, sigma rồi biến đổiout = pd.DataFrame(X, columns=prep.get_feature_names_out())out.columns = [c.replace("missingindicator_", "miss_") for c in out]pd.set_option("display.width", 200)pd.set_option("display.max_columns", None)print(out.round(2))sc = prep.named_transformers_["num"][-1]print("mu =", sc.mean_.round(2), " sigma =", sc.scale_.round(2))Lần chạy đầu cần tải Python, khoảng 10 MB.
Mỗi cột thiếu đúng một ô. Bảng sau xử lý có 9 cột, toàn số, không còn ô trống:
- Căn 3 (dòng có chỉ số 2) bị trống diện tích, được điền trung vị 45 m², nên có
area, giống hệt căn 2, căn thật sự rộng 45 m². Chỉ cộtmiss_areaphân biệt hai căn. - Căn 7 (dòng 6) thiếu quận, được điền Cầu Giấy, giá trị hay gặp nhất. Căn 4 thiếu ban công, được điền “có”.
- Dòng cuối là tham số chuẩn hóa: m² và tầng, và . của diện tích tính sau khi điền, nên là , không phải .
Đây chính là các tham số mà prep.transform(df_test) sẽ dùng lại cho căn hộ mới.
Rò rỉ dữ liệu: chọn đặc trưng trước khi chia
Phần tiêu đề “Rò rỉ dữ liệu: chọn đặc trưng trước khi chia”Ô thứ hai dựng một bộ dữ liệu không có gì để học: 100 mẫu, 5000 đặc trưng ngẫu nhiên, nhãn tung đồng xu. Mô hình trung thực chỉ đoán đúng khoảng 50%. Ô so hai cách chọn 20 đặc trưng tương quan với nhãn mạnh nhất (SelectKBest), rồi đo accuracy bằng kiểm định chéo 5 fold.
Chọn đặc trưng ngoài và trong Pipeline
import numpy as npfrom sklearn.feature_selection import SelectKBest, f_classiffrom sklearn.linear_model import LogisticRegressionfrom sklearn.model_selection import cross_val_scorefrom sklearn.pipeline import make_pipelinefrom sklearn.preprocessing import StandardScaler
# Nhiễu thuần: 100 mẫu, 5000 đặc trưng ngẫu nhiên, nhãn tung đồng xu.# Không có quy luật nào để học: mô hình trung thực chỉ đúng khoảng 50%.rng = np.random.default_rng(0)X = rng.normal(size=(100, 5000))y = rng.integers(0, 2, size=100)
# SAI: chọn 20 đặc trưng "liên quan nhất" trên TOÀN BỘ dữ liệu, rồi mới# kiểm định chéo. Bước chọn đã nhìn thấy nhãn của các fold kiểm định.X_sel = SelectKBest(f_classif, k=20).fit_transform(X, y)model = make_pipeline(StandardScaler(), LogisticRegression())leak = cross_val_score(model, X_sel, y, cv=5)print(f"select before split: {leak.mean():.3f}")
# ĐÚNG: đặt bước chọn vào Pipeline; mỗi fold chỉ chọn trên phần train.pipe = make_pipeline(SelectKBest(f_classif, k=20), StandardScaler(), LogisticRegression())honest = cross_val_score(pipe, X, y, cv=5)print(f"select inside pipeline: {honest.mean():.3f}")Lần chạy đầu cần tải Python, khoảng 10 MB.
Chọn trên toàn bộ dữ liệu rồi mới kiểm định chéo cho accuracy 0.850, trên dữ liệu ngẫu nhiên. Lý do: trong 5000 cột ngẫu nhiên, thế nào cũng có những cột tình cờ khớp với 100 nhãn. Bước chọn đã nhìn thấy nhãn của cả các fold kiểm định, nên giữ lại đúng những cột khớp tình cờ với chúng. Đặt bước chọn vào Pipeline, mỗi fold tự chọn trên phần train của mình, và những cột đó không còn khớp với phần kiểm định: accuracy về 0.480, đúng mức đoán bừa.
Ví dụ này là kinh điển (sách The Elements of Statistical Learning gọi nó là cách sai và cách đúng để kiểm định chéo). Nó hay xảy ra với dữ liệu ít mẫu mà nhiều đặc trưng, như dữ liệu gen.
Mất cân bằng: bốn cách trên cùng dữ liệu
Phần tiêu đề “Mất cân bằng: bốn cách trên cùng dữ liệu”Ô thứ ba sinh 2000 mẫu bằng make_classification, lớp 1 (coi như giao dịch gian lận) chiếm khoảng 5%. Chia 70/30 có stratify, rồi so bốn cách dùng hồi quy logistic (bài Hồi quy Logistic): mặc định; mặc định nhưng hạ ngưỡng xuống 0.2; class_weight="balanced"; và oversampling ngẫu nhiên tự viết. Mọi chỉ số tính trên tập test, cho lớp 1.
Trọng số lớp, oversampling, dời ngưỡng
import numpy as npfrom sklearn.datasets import make_classificationfrom sklearn.linear_model import LogisticRegressionfrom sklearn.metrics import accuracy_score, precision_recall_fscore_supportfrom sklearn.model_selection import train_test_split
# 2000 mẫu, lớp 1 (thiểu số) chỉ chiếm khoảng 5%.X, y = make_classification(n_samples=2000, n_features=5, n_informative=2, n_redundant=0, weights=[0.95, 0.05], random_state=2)# stratify=y: tập train và test giữ cùng tỉ lệ hai lớp.X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, stratify=y, random_state=0)print(f"positives: train {y_tr.sum()}/{len(y_tr)}, " f"test {y_te.sum()}/{len(y_te)}")
# Oversampling ngẫu nhiên, CHỈ trên tập train: bốc lặp lại các mẫu# lớp 1 cho tới khi bằng số mẫu lớp 0.rng = np.random.default_rng(0)pos, neg = np.where(y_tr == 1)[0], np.where(y_tr == 0)[0]extra = rng.choice(pos, size=len(neg) - len(pos), replace=True)idx = np.concatenate([neg, pos, extra])X_os, y_os = X_tr[idx], y_tr[idx]
def report(name, model, X_fit, y_fit, threshold=0.5): model.fit(X_fit, y_fit) p = model.predict_proba(X_te)[:, 1] # xác suất thuộc lớp 1 y_hat = (p >= threshold).astype(int) pr, rc, f1, _ = precision_recall_fscore_support( y_te, y_hat, average="binary", zero_division=0) acc = accuracy_score(y_te, y_hat) print(f"{name:<14} acc={acc:.3f} P={pr:.3f} R={rc:.3f} F1={f1:.3f}")
# Ngưỡng 0.2 đặt sẵn để minh họa; làm thật thì chọn trên tập kiểm định.report("plain", LogisticRegression(), X_tr, y_tr)report("threshold 0.2", LogisticRegression(), X_tr, y_tr, threshold=0.2)report("balanced", LogisticRegression(class_weight="balanced"), X_tr, y_tr)report("oversample", LogisticRegression(), X_os, y_os)Lần chạy đầu cần tải Python, khoảng 10 MB.
Tập train có 74 mẫu lớp 1 trên 1400, tập test 31 trên 600: cùng khoảng 5% nhờ stratify. Mô hình luôn đoán lớp 0 đã có accuracy , nên đừng nhìn cột acc.
- Mặc định: accuracy 0.968 trông rất đẹp, nhưng recall chỉ 0.516: bỏ sót gần nửa số giao dịch gian lận (bắt được 16 trên 31).
class_weight="balanced": recall lên 0.903 (28 trên 31), nhưng precision rơi từ 0.800 xuống 0.509: mô hình báo 55 giao dịch, chỉ 28 là gian lận thật. F1 nhích từ 0.627 lên 0.651.- Oversampling ngẫu nhiên cho đúng cùng các số với
balanced, như lý thuyết đã dự đoán: nhân bản mẫu cũng là nhân trọng số. - Hạ ngưỡng xuống 0.2 ở đây cho F1 tốt nhất, 0.788 (recall 0.839, precision 0.743). Trọng số
balancedđẩy mô hình đi quá xa về phía recall; hạ ngưỡng vừa phải thì giữ được precision.
Không có cách nào thắng tuyệt đối: chọn cách nào tùy bỏ sót hay báo nhầm đắt hơn. Ngưỡng 0.2 được đặt sẵn cho ví dụ. Làm thật, bạn phải chọn ngưỡng trên tập kiểm định, không phải nhìn tập test như ở đây.
Lỗi hay gặp
Phần tiêu đề “Lỗi hay gặp”Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Tóm tắt và đọc thêm
Phần tiêu đề “Tóm tắt và đọc thêm”Ý chính:
- Giá trị thiếu: bỏ dòng thì mất dữ liệu; điền trung vị (cột số) hoặc mode (cột phân loại), thêm cờ đánh dấu thiếu khi việc thiếu có thể mang thông tin. Biến phân loại không thứ tự dùng one-hot, không gán 0, 1, 2.
- Chuẩn hóa z-score hoặc min-max cho KNN, gradient descent, mô hình có phạt; cây quyết định không cần.
- Mọi bước học từ dữ liệu (trung vị, danh sách giá trị, , , đặc trưng được chọn) chỉ học từ tập train.
PipelinevàColumnTransformerbảo đảm điều đó, kể cả trong kiểm định chéo. - Dữ liệu mất cân bằng: đo bằng precision, recall, F1, chia có
stratify. Trọng số lớp , oversampling (gần như tương đương trọng số lớp), undersampling, SMOTE, hay dời ngưỡng đều đổi chỗ đánh đổi precision–recall. Lấy mẫu lại và tăng cường dữ liệu chỉ làm trên tập train, sau khi chia.
Đọc thêm:
- Vũ Hữu Tiệp (2017). Machine Learning cơ bản, Bài 11: Giới thiệu về Feature Engineering, mục Feature Scaling and Normalization. Tiếng Việt, có rescaling (min-max), standardization và chuẩn hóa về độ dài 1.
- scikit-learn developers (2007–2026). scikit-learn 1.8 User Guide: Common pitfalls and recommended practices, mục Data leakage. Ví dụ chọn đặc trưng trên dữ liệu ngẫu nhiên, cùng ý với ô code thứ hai, và lời khuyên dùng
Pipeline. - scikit-learn developers (2007–2026). scikit-learn 1.8 User Guide: Preprocessing data, Imputation of missing values và Pipelines and composite estimators. Tra cứu các lớp của bài:
StandardScaler,OneHotEncoder,SimpleImputer,MissingIndicator,ColumnTransformer. - Chawla, N. V., Bowyer, K. W., Hall, L. O., Kegelmeyer, W. P. (2002). SMOTE: Synthetic Minority Over-sampling Technique. Journal of Artificial Intelligence Research 16, 321–357. Bài báo đề xuất SMOTE. Cách dùng trong Python: tài liệu imbalanced-learn, mục Over-sampling.
- He, H., Garcia, E. A. (2009). Learning from Imbalanced Data. IEEE Transactions on Knowledge and Data Engineering 21(9), 1263–1284. Tổng quan về lấy mẫu lại, học có trọng số chi phí và cách đánh giá khi lớp lệch.
- Kaufman, S., Rosset, S., Perlich, C., Stitelman, O. (2012). Leakage in Data Mining: Formulation, Detection, and Avoidance. ACM Transactions on Knowledge Discovery from Data 6(4), 1–21. Các dạng rò rỉ dữ liệu gặp trong các cuộc thi và dự án thật.
Trình duyệt đang chặn lưu dữ liệu. Tiến độ sẽ mất khi bạn tải lại trang.
Nội dung theo giấy phép CC BY-NC-SA 4.0. Mã nguồn theo giấy phép MIT.Giới thiệu