Python - Bài 33: Luyện tập hồi quy trên ba bộ dữ liệu (LinearRegression, MSE)

🔄 Bài 33: Luyện tập — Ba bộ dữ liệu, ba bài học

Chặng 8 · Học máy thực chiến (Bài 31 → 42) · đây là bài 3/12 của chặng.

🔄 Đây là bài luyện tập — không có khái niệm nào mới. Toàn bộ công cụ bạn cần đã nằm trong Bài 31 và 32. Hôm nay chỉ đem chúng chạy trên ba bộ dữ liệu khác nhau.

🎯 Mục tiêu: tập nhìn ra khi nào kết quả của máy đáng tin và khi nào không — kỹ năng mà chỉ luyện mới có, không đọc lý thuyết mà thành.

📖 Bài này có nhịp khác: mỗi bộ dữ liệu đi theo bốn bước — nhìn dữ liệu, đoán trước khi chạy, máy trả lời, điều rút ra. Hãy thật sự dừng lại ở bước “đoán trước”, đừng đọc lướt qua. Cái sai của bạn ở bước đó mới là chỗ bài học đọng lại.

Sau bài này bạn sẽ:

  • Chạy được trọn vẹn quy trình hồi quy trên một bộ dữ liệu lạ, không cần xem lại bài cũ.
  • Biết rằng scikit-learn luôn trả về một đường thẳng — kể cả khi dữ liệu chẳng có quan hệ nào.
  • Có một cách đơn giản để tự hỏi: “mô hình này có thật sự khá hơn đoán bừa không?”
  • Nhìn thấy tận mắt một ô dữ liệu nhập sai phá hỏng cả mô hình như thế nào.

Hai bài vừa rồi bạn học ba động tác fitpredict, và cách xếp dữ liệu thành bảng. Đủ đồ nghề rồi. Hôm nay không thêm gì cả — chỉ mang đúng bộ đồ nghề ấy đi qua ba bộ dữ liệu.

Nhưng đừng coi đây là bài dễ. Ba bộ này được chọn có chủ ý: bộ đầu chạy ngon lành, bộ thứ hai lừa bạn, bộ thứ ba bị hỏng vì đúng một ô nhập sai. Cả ba đều chạy trót lọt, không bộ nào báo lỗi. Việc của bạn là phân biệt được chúng.

🧰 Bộ đồ nghề của bạn — chỉ có bấy nhiêu, và hôm nay không thêm dòng nào:

X = x.reshape(-1, 1)                    # Bai 32 - xep thanh bang
mo_hinh = LinearRegression().fit(X, y)   # Bai 31 - cho may hoc
mo_hinh.coef_[0]                          # Bai 31 - do doc w
mo_hinh.intercept_                        # Bai 31 - diem cat b
mo_hinh.predict([[40]])                 # Bai 31 - hoi may
((mo_hinh.predict(X) - y) ** 2).mean()    # Bai 28 - MSE
Ba bộ dữ liệu — cả ba đều chạy trót lọt, không bộ nào báo lỗi Bộ 1 · Cây cao theo ngày Chấm bám sát đường. ✓ Đáng tin Bộ 2 · Điểm theo độ dài tên Chấm nằm lung tung. ✗ Vô nghĩa Bộ 3 · Giá nhà theo diện tích ô nhập sai Một chấm kéo lệch cả đường. ✗ Dốc đi sai chiều

Hình 1 — Ba bộ dữ liệu và đường thẳng mà scikit-learn tìm ra cho mỗi bộ. Hình này vẽ từ đúng những con số bạn sẽ chạy trong phòng thực hành. Hãy để ý đường màu đỏ ở khung thứ ba: nó dốc xuống.

1. Bộ dữ liệu thứ nhất — khi mọi thứ chạy đúng như mong đợi

🌱 Dữ liệu: một cây con được đo chiều cao sau mỗi 5 ngày.

Số ngày51015202530
Chiều cao (cm)7,110,815,219,122,827,3

🤔 Đoán trước khi chạy. Nhìn hai dòng số trên, hãy trả lời trong đầu: mỗi ngày cây cao thêm khoảng bao nhiêu? Và lúc mới trồng (ngày 0) cây cao chừng nào? Có con số trong đầu rồi hãy đọc tiếp.

💻 Máy trả lời:

w = 0.8051        # moi ngay cao them khoang 0,8 cm
b = 2.9600        # luc trong da cao san gan 3 cm
MSE = 0.0339

Bộ số liệu này ta dựng từ một quy luật giấu sẵn: cao = 0,8 × ngày + 3, rồi thêm chút nhiễu đo đạc. Máy tìm lại được 0,80512,96 — gần như đúng y, dù nó chưa từng được cho biết quy luật.

💡 Điều rút ra: khi quan hệ trong dữ liệu là có thật và gần thẳng, mô hình đường thẳng làm rất tốt. Hệ số w đọc được thành lời (“mỗi ngày cao thêm 0,8 cm”), và dự đoán cho ngày 40 — 35,17 cm — là con số đáng tin.

2. Bộ dữ liệu thứ hai — khi máy trả lời rất tự tin về một điều vô nghĩa

📝 Dữ liệu: tám học sinh, ta ghi số ký tự trong tên và điểm thi.

Số ký tự trong tên7129158111013
Điểm thi7,56,08,55,59,06,57,08,0

🤔 Đoán trước khi chạy. Tên dài ngắn thì liên quan gì tới điểm thi? Rõ ràng là không. Vậy bạn nghĩ scikit-learn sẽ làm gì — báo lỗi? trả về w = 0? hay từ chối chạy?

💻 Máy trả lời:

w = -0.3058
b = 10.4987
MSE = 0.7296

predict([[20]]) -> 4.38 diem   # ten 20 ky tu
predict([[ 3]]) -> 9.58 diem   # ten 3 ky tu

Máy không báo lỗi, không do dự, không cảnh báo gì cả. Nó điềm nhiên kết luận: tên càng dài thì điểm càng thấp, cứ mỗi ký tự mất 0,31 điểm. Và nó sẵn sàng dự đoán một bạn tên 3 ký tự sẽ được 9,58 điểm.

Đây là điều quan trọng nhất của cả bài: scikit-learn không bao giờ nói “dữ liệu này chẳng có quan hệ nào”. Bạn bảo nó tìm đường thẳng khớp nhất, nó tìm đường thẳng khớp nhất. Việc phán xét con số ấy có nghĩa hay không là việc của bạn, không phải của thư viện.

📏 Một cách tự kiểm tra rất đơn giản — không cần công cụ mới, chỉ dùng lại đúng MSE của Bài 28.

Hãy so mô hình với cách đoán lười nhất có thể: cứ nhắm mắt trả lời bằng giá trị trung bình, bất kể đầu vào là gì. Nếu mô hình không khá hơn cách đoán lười đó bao nhiêu, nghĩa là nó chẳng học được gì.

MSE_mo_hinh = ((mo_hinh.predict(X) - y) ** 2).mean()
MSE_doan_bua = ((y.mean() - y) ** 2).mean()

ti_le = MSE_mo_hinh / MSE_doan_bua   # cang gan 0 cang tot

Tỉ lệ gần 0% nghĩa là mô hình giỏi hơn hẳn. Gần 100% nghĩa là nó chẳng hơn gì việc đoán bừa. Cách so sánh này có tên gọi riêng trong ngành, ta sẽ gặp ở chặng sau; hôm nay cứ dùng nó như một phép chia thường.

Mô hình khá hơn “đoán bừa bằng trung bình” bao nhiêu? Cột càng ngắn càng tốt. Cột dài gần hết khung nghĩa là mô hình gần như vô dụng. 100% = đoán bừa 0% Bộ 1 · cây 0,07% — giỏi hơn hẳn Bộ 2 · tên 55,59% — gần như không học được gì Bộ 3 · nhà 97,88% — vô dụng

Hình 2 — Cùng một phép chia áp cho cả ba bộ. Bộ 1 gần như chạm vạch 0. Bộ 3 gần chạm vạch 100 — mô hình của nó gần như không hơn gì việc nhắm mắt đọc giá trung bình.

💡 Điều rút ra: chạy được không có nghĩa là đúng. Trước khi tin một con số, hãy hỏi hai điều: quan hệ này có hợp lẽ thường không?mô hình có thật sự khá hơn đoán bừa không?

3. Bộ dữ liệu thứ ba — một ô nhập sai phá hỏng tất cả

🏠 Dữ liệu: tám căn nhà, diện tích và giá bán.

Diện tích (m²)40506070809010055
Giá (tỉ đồng)2,12,63,13,64,14,65,128,5

Bảy căn đầu theo đúng quy luật giá = 0,05 × diện tích + 0,1. Căn cuối cùng — 55 m² mà giá 28,5 tỉ — là một ô nhập sai: người nhập liệu gõ nhầm, đáng ra phải là 2,85.

🤔 Đoán trước khi chạy. Bảy trên tám dòng hoàn toàn sạch, chỉ một dòng sai. Bạn nghĩ mô hình sẽ lệch đi một chút, hay hỏng hẳn?

💻 Máy trả lời:

w = -0.0623       <- SO AM!
b = 10.9591
MSE = 67.2330

predict([[75]]) -> 6.28 ty

Độ dốc mang dấu âm. Nghĩa là mô hình đã kết luận: nhà càng rộng thì càng rẻ. Một ô nhập sai duy nhất không chỉ làm sai lệch con số — nó làm sai cả chiều của quy luật.

Sửa đúng một ô, từ 28.5 thành 2.85, rồi chạy lại:

w = 0.0500        # dung y quy luat that
b = 0.1000        # dung y quy luat that
MSE = 0.0000

predict([[75]]) -> 3.85 ty

Từ vô dụng thành hoàn hảo, chỉ nhờ sửa một chữ số. Và dự đoán cho căn 75 m² nhảy từ 6,28 tỉ xuống 3,85 tỉ — chênh nhau 2,4 tỉ đồng.

💡 Điều rút ra: vì sao chỉ một điểm mà sức phá hoại lớn đến vậy? Bài 28 đã trả lời rồi: MSE bình phương sai số. Căn nhà nhập sai lệch khoảng 25 tỉ so với đường thẳng đúng, bình phương lên thành khoảng 625 — át hết mọi sai số nhỏ của bảy căn còn lại cộng lại. Mô hình buộc phải bẻ cong về phía nó để giảm con số khổng lồ ấy.

Đây là lý do trong công việc thật, dọn dữ liệu chiếm phần lớn thời gian, còn gọi fit thì chỉ mất một dòng.

⚠️ Ba câu hỏi cho mọi kết quả, từ nay về sau. Đây là thứ đáng mang theo nhất từ bài này:

  1. Vẽ dữ liệu ra xem chưa? Cả ba vấn đề hôm nay đều lộ ra ngay khi nhìn Hình 1 — trước khi cần tính toán gì.
  2. Mô hình có khá hơn đoán bừa bằng trung bình không? Một phép chia, hai dòng lệnh.
  3. Hệ số có hợp lẽ thường không? Nhà rộng mà rẻ hơn, tên dài mà điểm thấp hơn — những dấu hiệu này mắt người bắt được, máy thì không.

📝 Ba bộ, đặt cạnh nhau

 Bộ 1 · CâyBộ 2 · TênBộ 3 · Nhà
Chạy có lỗi không?KhôngKhôngKhông
w máy tìm ra0,8051−0,3058−0,0623
MSE0,03390,729667,2330
So với đoán bừa0,07%55,59%97,88%
Hệ số có hợp lẽ?KhôngKhông — sai cả chiều
Vấn đề nằm ở đâuKhông cóDữ liệu vốn không có quan hệMột ô nhập sai
Cách xử lýDùng đượcBỏ đi, tìm đặc trưng khácSửa dữ liệu rồi chạy lại

✎ Kiểm tra nhanh

Câu 1. Với bộ “độ dài tên”, scikit-learn trả về w = −0,3058. Kết luận đúng là:

  • Tên càng dài thì điểm thi càng thấp
  • Thư viện đã tính sai, cần cài lại
  • Máy vẫn tìm đường thẳng khớp nhất như được yêu cầu; con số này không có ý nghĩa thực tế nào
  • Cần thêm dữ liệu thì con số sẽ đúng
Máy làm đúng việc được giao: tìm đường thẳng khớp nhất với các điểm. Nó không có khái niệm “hợp lẽ thường”. Việc nhận ra độ dài tên chẳng liên quan gì tới điểm thi là việc của con người.

Câu 2. MSE của mô hình bằng 97,88% MSE của việc đoán bừa bằng giá trị trung bình. Điều đó nghĩa là:

  • Mô hình chính xác tới 97,88%
  • Mô hình gần như không khá hơn việc nhắm mắt đọc giá trị trung bình
  • Mô hình sai 97,88% số lần dự đoán
  • Cần chạy thêm 97,88% số vòng lặp nữa
Tỉ lệ này càng gần 0 càng tốt, càng gần 100% càng tệ. 97,88% nghĩa là mô hình chỉ giảm được 2,12% sai số so với cách đoán lười nhất — công cốc. Đừng nhầm nó với “độ chính xác”, ý nghĩa ngược hẳn nhau.

Câu 3. Vì sao chỉ một dòng nhập sai lại kéo lệch được cả đường thẳng?

  • Vì scikit-learn ưu tiên dòng cuối cùng của bảng
  • Vì dòng đó có diện tích lớn nhất
  • Vì mô hình chỉ nhìn vào một vài dòng chứ không nhìn hết
  • Vì MSE bình phương sai số, nên một điểm lệch rất xa bị phạt nặng gấp bội và át hết các điểm còn lại
Đúng bài học của Bài 28. Điểm nhập sai lệch khoảng 25 tỉ, bình phương lên thành khoảng 625 — lớn hơn tổng sai số của bảy căn còn lại rất nhiều lần. Mô hình buộc phải bẻ về phía nó.

Câu 4. Khi dữ liệu hoàn toàn không có quan hệ, scikit-learn sẽ:

  • Vẫn trả về một đường thẳng và không cảnh báo gì
  • Báo lỗi NoRelationError
  • Trả về w = 0 chính xác
  • Từ chối chạy fit
Đây là điều cần nhớ nhất của bài. Thư viện luôn đưa ra một câu trả lời, kể cả khi câu hỏi vô nghĩa. Không có cơ chế nào tự động báo cho bạn biết điều đó.

Câu 5. Sau khi sửa ô nhập sai, mô hình cho w = 0,0500b = 0,1000 với MSE bằng 0. Điều này cho thấy:

  • Mô hình đã học thuộc lòng dữ liệu nên không dùng được
  • Có lỗi trong cách tính MSE
  • Bảy căn nhà nằm chính xác trên một đường thẳng, và máy đã tìm lại đúng đường ấy
  • Cần thêm dữ liệu vì kết quả quá đẹp
Bộ dữ liệu này được dựng từ đúng công thức giá = 0,05 × diện tích + 0,1 mà không thêm nhiễu, nên đường thẳng đi qua trọn vẹn mọi điểm. Trong đời thực dữ liệu luôn có nhiễu và MSE bằng 0 là chuyện đáng nghi — Bài 35 sẽ nói kỹ.

Câu 6. Việc đầu tiên nên làm khi nhận một bộ dữ liệu lạ là:

  • Gọi fit ngay để xem mô hình cho ra gì
  • Vẽ dữ liệu ra và nhìn bằng mắt
  • Thêm càng nhiều đặc trưng càng tốt
  • Đổi sang mô hình phức tạp hơn
Cả ba vấn đề trong bài hôm nay đều lộ ra ngay khi nhìn Hình 1, trước khi cần tính bất cứ thứ gì. Mắt người bắt được điểm lạc lõng và bắt được sự “chẳng theo quy luật nào” nhanh hơn mọi con số.

💻 Thực hành: ba bộ, một chương trình

▶ Cách dùng: bấm Khởi động Python một lần (khoảng 15 giây), đợi báo sẵn sàng rồi bấm Chạy code. Hàm khao_sat ở đầu chương trình làm hết mọi việc — ba bộ dữ liệu chỉ việc gọi lại nó.

💡 Thử thách (làm được cả ba là bạn đã nắm chắc bài):

  1. Tự dựng một bộ không có quan hệ. Thay ky_tu bằng tám con số bạn nghĩ ra trong đầu, giữ nguyên diem. Tỉ lệ phần trăm ra bao nhiêu? Chạy vài lần với các con số khác nhau — nó có bao giờ xuống dưới 10% không, và vì sao?
  2. Đổi mức độ sai của ô nhập nhầm. Trong Bộ 3, thử lần lượt 28.5, 10.0, 5.0, 3.5. Ghi lại w mỗi lần. Sai bao nhiêu thì w bắt đầu đổi sang dấu âm? Con số ấy nói gì về việc dữ liệu “bẩn tới mức nào thì còn cứu được”?
  3. Thêm một cây thứ bảy vào Bộ 1 với ngay = 35 nhưng cao = 5 — như thể có người đo nhầm. Bộ 1 vốn rất sạch giờ ra sao? Tỉ lệ phần trăm nhảy lên bao nhiêu?

Bài tiếp theo: hôm nay ta đã đo mô hình trên chính dữ liệu nó vừa học — và Bộ 3 sau khi sửa cho MSE bằng 0 tròn trĩnh. Nghe thì tuyệt, nhưng đó là một cái bẫy lớn. Bài 34 sẽ chỉ ra vì sao chấm điểm bằng chính đề đã ôn là cách chấm vô nghĩa, và cách làm đúng đơn giản đến bất ngờ. Đây là bài quan trọng nhất của cả chặng.