Bạn chia train và test cẩn thận, nhưng lại tính min và max trên toàn bộ dữ liệu trước đó. Phần tiền xử lý đã nhìn thấy thông tin của test. Một lỗi khác là chuẩn hóa riêng từng tập: cùng một giá trị đầu vào có thể nhận hai biểu diễn khác nhau.
Bài đối chiếu tài liệu scikit-learn 1.9.1 ngày 05/11/2026. Ví dụ chạy bằng Python 3.9.6 thuần để làm rõ phép tính một đặc trưng. Chưa chạy MinMaxScaler, huấn luyện model hay đo mức thay đổi của điểm đánh giá. Helper dưới đây cố ý từ chối cột hằng; không mô phỏng mọi quy tắc của thư viện.
Tách bước học tham số khỏi bước áp dụng
Với khoảng đích từ 0 đến 1 và cột có min khác max, phép đổi là (x - min_train) / (max_train - min_train). Hai số min và max là tham số học từ train. Khi gặp validation, test hoặc dữ liệu dự đoán mới, bạn dùng lại hai số ấy.
Trong ngôn ngữ API của scikit-learn, fit học tham số; transform áp dụng tham số đã có. fit_transform kết hợp cả hai. Vì vậy, train được fit rồi transform; test chỉ transform. Quy tắc này liên quan đến thời điểm dữ liệu được phép tham gia học, không phụ thuộc việc test có nhãn hay chưa.
1. Nhìn thấy lỗi bằng bộ số nhỏ
Lưu đoạn sau vào một file Python và chạy trực tiếp. Dữ liệu chỉ có một cột hữu hạn, không thiếu giá trị:
import math
def fit_bounds(values):
data = list(values)
if not data or not all(math.isfinite(x) for x in data):
raise ValueError("Can du lieu huu han, khong rong")
low, high = min(data), max(data)
if low == high:
raise ValueError("Demo khong xu ly cot hang")
return low, high
def scale(values, bounds):
low, high = bounds
return [(x - low) / (high - low) for x in values]
train = [10.0, 20.0]
test = [15.0, 100.0]
train_bounds = fit_bounds(train)
assert scale(train, train_bounds) == [0.0, 1.0]
assert scale(test, train_bounds) == [0.5, 9.0]
leaked_bounds = fit_bounds(train + test)
assert scale(train, leaked_bounds) == [0.0, 1.0 / 9.0]
assert scale(test, leaked_bounds) == [1.0 / 18.0, 1.0]
print("train bounds:", train_bounds)
print("test transformed:", scale(test, train_bounds))
Giá trị 100 của test làm max toàn bộ tăng từ 20 lên 100. Hệ quả là cách biểu diễn cả train bị đổi. Đây là bằng chứng về luồng thông tin trong phép chuẩn hóa, chưa phải bằng chứng rằng một model cụ thể chắc chắn tăng điểm bao nhiêu.
Số 9.0 ở đầu ra đúng không phải lỗi tính toán. Một giá trị mới vượt max của train có thể nằm ngoài khoảng 0–1 sau transform. MinMaxScaler mặc định clip=False; tên khoảng đích không bảo đảm mọi dữ liệu tương lai đều nằm trong đó.
2. Fit riêng test cũng làm sai hệ tọa độ
Nối tiếp file trên để kiểm tra cùng giá trị 15:
test_bounds = fit_bounds(test)
same_input = [15.0]
assert scale(same_input, train_bounds) == [0.5]
assert scale(same_input, test_bounds) == [0.0]
print("same value, different bounds:",
scale(same_input, train_bounds),
scale(same_input, test_bounds))
Model được học với hệ tọa độ của train nhưng lại nhận đầu vào theo hệ tọa độ khác. Việc cả hai tập đều có min bằng 0 và max bằng 1 không chứng minh preprocessing nhất quán.
Kiểm tra trong project thật
Chia tập trước khi fit bộ biến đổi. Nếu dùng cross-validation, đặt scaler và estimator trong một Pipeline được fit bên trong từng fold; không fit scaler toàn bộ rồi mới đưa ma trận đã đổi vào cross-validation. Mỗi fold phải học tham số từ phần train tương ứng.
Lưu cùng phiên bản model bộ biến đổi đã fit và thứ tự các cột. Khi kiểm tra inference, đưa cùng một bản ghi qua đường xử lý validation và đường xử lý production, rồi đối chiếu vector đầu vào. Với nhiều cột, min và max được học riêng theo từng đặc trưng.
Lỗi thường gặp và giới hạn
Không bật clipping chỉ để che việc dữ liệu mới vượt miền train. Cần quyết định ý nghĩa nghiệp vụ trước; clipping có thể làm mất khác biệt giữa các giá trị vượt biên. Nó cũng không xử lý được distribution shift.
Không dùng helper minh họa cho NaN, cột hằng hoặc ma trận nhiều chiều trong production. Chọn implementation thư viện, viết ca kiểm tra theo dữ liệu thực và xác định rõ quy tắc cho giá trị thiếu. Mục tiêu của ví dụ là giúp bạn nhận ra chỗ gọi fit sai, không thay thế một pipeline tiền xử lý đầy đủ.


