Bỏ qua để đến nội dung

Ridge, Lasso, Elastic Net

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

  • Hiểu regularization là cộng vào hàm mất mát một khoản phạt cho trọng số lớn, và vì sao điều đó làm đa thức bậc cao bớt overfitting.
  • Tự dẫn được nghiệm dạng đóng (closed-form solution) của hồi quy Ridge (Ridge regression) từ phương trình chuẩn.
  • Giải thích được vì sao Lasso đưa nhiều hệ số về đúng 0 còn Ridge thì không, bằng một ví dụ một biến và bằng hình học.
  • Cài đặt được Ridge, Lasso, Elastic Net trong scikit-learn có chuẩn hóa đặc trưng (feature standardization), đổi đúng λ\lambda sang alpha, và chọn λ\lambda bằng kiểm định chéo (cross-validation).

Nhớ lại Chi ở bài trước: đường bậc 15 của Chi đi qua đúng cả 16 điểm train, nhưng giữa các điểm thì vọt lên, cắm xuống. Bài trước chữa bằng cách hạ bậc xuống 3. Bài này thử cách thứ ba đã hẹn: giữ nguyên bậc 15, nhưng không cho đường cong uốn tùy thích.

Đường bậc 15 uốn được như vậy nhờ các hệ số lớn tới hàng chục tỉ, đổi dấu xen kẽ: số hạng này kéo đường lên rất mạnh, số hạng kế bên kéo xuống gần mạnh như thế. Tại 16 điểm train, các lực kéo triệt tiêu nhau gần như hoàn hảo; giữa các điểm, chỉ lệch nhau một chút là đường vọt khỏi khung. Đường cong mềm bậc 3 của Bình không cần hệ số lớn như thế. Vậy hệ số khổng lồ là triệu chứng của overfitting.

Hãy tưởng tượng mỗi hệ số phải đóng thuế theo độ lớn của nó. Mô hình phải cân giữa đi gần các điểm train (MSE nhỏ) và giữ hệ số nhỏ (thuế ít). Hệ số chỉ để uốn theo một hạt nhiễu thì không đáng tiền thuế. Còn hai câu hỏi:

  • Thuế bao nhiêu? Quá nhẹ thì vẫn overfitting. Quá nặng thì mọi hệ số bị ép về gần 0, đường gần như nằm ngang: underfitting. Mức thuế là con số λ\lambda bạn phải chọn.
  • Tính thuế thế nào? Theo bình phương hệ số (Ridge) hay trị tuyệt đối (Lasso). Lasso đưa nhiều hệ số về đúng 0, Ridge thì không.

Dữ liệu vẫn là 24 điểm của bài trước: 16 điểm train để khớp, 8 điểm test để chấm.

Xem bảng 24 điểm
16 điểm train, tự sinh từ đường sin y = sin(2πx) cộng nhiễu. Không phải số liệu thật.
xy
0.030.15
0.121.06
0.150.84
0.230.96
0.260.83
0.350.94
0.410.30
0.47−0.09
0.51−0.17
0.60−0.32
0.64−0.68
0.73−0.84
0.77−0.28
0.85−0.82
0.90−0.53
0.98−0.02
8 điểm test, sinh cùng cách với tập train và không dùng khi khớp.
xy
0.060.33
0.180.66
0.320.93
0.430.43
0.55−0.42
0.67−1.11
0.80−1.18
0.94−0.45

Bài toán của bài này là: giữ đa thức bậc 15, cộng vào MSE một khoản phạt cho hệ số lớn, rồi chọn mức phạt bằng dữ liệu, sao cho đường cong mềm vừa đủ để đoán tốt điểm mới.

Mô hình vẫn là đa thức bậc 15, tức hồi quy tuyến tính trên n=15n = 15 đặc trưng: y^=w⊤x+b\hat{y} = \mathbf{w}^\top\mathbf{x} + b. Hàm mất mát L\mathcal{L} vẫn là MSE trên m=16m = 16 điểm train. Regularization thay việc làm nhỏ L\mathcal{L} bằng việc làm nhỏ một hàm mục tiêu (objective function) JJ, gồm L\mathcal{L} cộng một khoản phạt:

Ridge:J(w,b)=L(w,b)+λ∥w∥22Lasso:J(w,b)=L(w,b)+λ∥w∥1\begin{aligned} \text{Ridge:}\quad J(\mathbf{w}, b) &= \mathcal{L}(\mathbf{w}, b) + \lambda\lVert\mathbf{w}\rVert_2^2 \\ \text{Lasso:}\quad J(\mathbf{w}, b) &= \mathcal{L}(\mathbf{w}, b) + \lambda\lVert\mathbf{w}\rVert_1 \end{aligned}
  • λ≥0\lambda \ge 0 là hệ số phạt (penalty strength), một siêu tham số. λ=0\lambda = 0 cho lại bình phương tối thiểu thường, tức đường bậc 15 của Chi.
  • ∥w∥22=w12+⋯+wn2\lVert\mathbf{w}\rVert_2^2 = w_1^2 + \dots + w_n^2 là bình phương của chuẩn L2 (L2 norm), tức bình phương độ dài vectơ w\mathbf{w}. ∥w∥1=∣w1∣+⋯+∣wn∣\lVert\mathbf{w}\rVert_1 = \lvert w_1\rvert + \dots + \lvert w_n\rvert là chuẩn L1 (L1 norm). Vì vậy Ridge còn gọi là phạt L2, Lasso là phạt L1.
  • Hệ số tự do (bias, còn gọi là intercept) bb không bị phạt. bb chỉ dời cả đường lên xuống, không làm đường uốn. Phạt bb sẽ kéo đường về phía y=0y = 0 một cách vô cớ.

Ridge do Hoerl và Kennard đề xuất (1970). Lasso của Tibshirani (1996) viết tắt “least absolute shrinkage and selection operator”: vừa co (shrinkage) hệ số, vừa chọn đặc trưng.

Khoản phạt đối xử mọi wjw_j như nhau, nhưng độ lớn của wjw_j phụ thuộc đơn vị của đặc trưng. Đổi xjx_j từ mét sang centimét, cột số lớn lên 100 lần, wjw_j phải nhỏ đi 100 lần, nên phạt λwj2\lambda w_j^2 nhỏ đi 10 000 lần. Chỉ vì đổi đơn vị mà mô hình khớp ra khác.

Cách chữa là chuẩn hóa đặc trưng: zj=(xj−μj)/σjz_j = (x_j - \mu_j)/\sigma_j, với μj\mu_j, σj\sigma_j là trung bình và độ lệch chuẩn của xjx_j trên tập train (StandardScaler). Mọi đặc trưng khi đó có trung bình 0, độ lệch chuẩn 1, nên bị phạt công bằng. Với đa thức, zk=(xk−μk)/σkz_k = (x^k - \mu_k)/\sigma_k, và từ đây wkw_k là hệ số của zkz_k, không còn của xkx^k.

Gọi X\mathbf{X} là ma trận m×nm \times n có phần tử ở hàng ii, cột kk là zk(i)z^{(i)}_k. Khác bài Hồi quy tuyến tính, X\mathbf{X} không có cột số 1, vì bb được tính riêng. Mỗi cột có trung bình 0 nên tổng bằng 0: X⊤1=0\mathbf{X}^\top\mathbf{1} = \mathbf{0}, với 1\mathbf{1} là vectơ toàn số 1. Hàm mục tiêu của Ridge là:

J(w,b)=1m∥Xw+b1−y∥2+λ∥w∥22J(\mathbf{w}, b) = \frac{1}{m}\left\lVert\mathbf{X}\mathbf{w} + b\mathbf{1} - \mathbf{y}\right\rVert^2 + \lambda\lVert\mathbf{w}\rVert_2^2

Bước 1: tìm bb. Khoản phạt không chứa bb, nên đạo hàm riêng theo bb giống bài Hồi quy tuyến tính: 2m∑i(y^(i)−y(i))\frac{2}{m}\sum_{i}\left(\hat{y}^{(i)} - y^{(i)}\right). Cho nó bằng 0 rồi thay y^(i)=∑kwkzk(i)+b\hat{y}^{(i)} = \sum_{k} w_k z^{(i)}_k + b:

∑kwk∑izk(i)⏟= 0+mb=∑iy(i)⟹b=yˉ\sum_{k} w_k \underbrace{\sum_{i} z^{(i)}_k}_{=\,0} + mb = \sum_{i} y^{(i)} \quad\Longrightarrow\quad b = \bar{y}

Mỗi tổng theo cột bằng 0 vì cột đã trừ trung bình. Vậy bb luôn bằng yˉ\bar{y} của tập train, với mọi λ\lambda.

Bước 2: thay b=yˉb = \bar{y} vào. Đặt yc=y−yˉ1\mathbf{y}_c = \mathbf{y} - \bar{y}\mathbf{1} (nhãn đã trừ trung bình). Khi đó Xw+yˉ1−y=Xw−yc\mathbf{X}\mathbf{w} + \bar{y}\mathbf{1} - \mathbf{y} = \mathbf{X}\mathbf{w} - \mathbf{y}_c, và hàm mục tiêu chỉ còn theo w\mathbf{w}:

J(w)=1m∥Xw−yc∥2+λ(w12+⋯+wn2)J(\mathbf{w}) = \frac{1}{m}\left\lVert\mathbf{X}\mathbf{w} - \mathbf{y}_c\right\rVert^2 + \lambda\left(w_1^2 + \dots + w_n^2\right)

Bước 3: đạo hàm theo w\mathbf{w}, cho bằng 0. Như bài Hồi quy tuyến tính, đạo hàm riêng của phần MSE theo wjw_j là hàng jj của 2mX⊤(Xw−yc)\frac{2}{m}\mathbf{X}^\top(\mathbf{X}\mathbf{w} - \mathbf{y}_c). Đạo hàm của λwj2\lambda w_j^2 là 2λwj2\lambda w_j, xếp lại thành 2λw2\lambda\mathbf{w}:

2mX⊤(Xw−yc)+2λw=0\frac{2}{m}\mathbf{X}^\top\left(\mathbf{X}\mathbf{w} - \mathbf{y}_c\right) + 2\lambda\mathbf{w} = \mathbf{0}

Bước 4: gom w\mathbf{w} về một vế.

X⊤Xw−X⊤yc+mλw=0X⊤Xw+mλIw=X⊤yc(X⊤X+mλI)w=X⊤yc\begin{aligned} \mathbf{X}^\top\mathbf{X}\mathbf{w} - \mathbf{X}^\top\mathbf{y}_c + m\lambda\mathbf{w} &= \mathbf{0} \\ \mathbf{X}^\top\mathbf{X}\mathbf{w} + m\lambda\mathbf{I}\mathbf{w} &= \mathbf{X}^\top\mathbf{y}_c \\ \left(\mathbf{X}^\top\mathbf{X} + m\lambda\mathbf{I}\right)\mathbf{w} &= \mathbf{X}^\top\mathbf{y}_c \end{aligned}

Dòng đầu nhân hai vế với m2\frac{m}{2} rồi phá ngoặc. Dòng thứ hai chuyển X⊤yc\mathbf{X}^\top\mathbf{y}_c sang phải và viết w=Iw\mathbf{w} = \mathbf{I}\mathbf{w}, với I\mathbf{I} là ma trận đơn vị (identity matrix) n×nn \times n. Dòng thứ ba đặt w\mathbf{w} ra ngoài. Cuối cùng, X⊤yc=X⊤y−yˉ X⊤1=X⊤y\mathbf{X}^\top\mathbf{y}_c = \mathbf{X}^\top\mathbf{y} - \bar{y}\,\mathbf{X}^\top\mathbf{1} = \mathbf{X}^\top\mathbf{y}, nên:

(X⊤X+mλI)w=X⊤y,b=yˉ\left(\mathbf{X}^\top\mathbf{X} + m\lambda\mathbf{I}\right)\mathbf{w} = \mathbf{X}^\top\mathbf{y}, \qquad b = \bar{y}

So với phương trình chuẩn, chỉ khác số mλm\lambda cộng vào đường chéo. Số nhỏ này đổi hẳn tình thế:

  • Luôn có nghiệm duy nhất khi λ>0\lambda > 0. Với mọi v≠0\mathbf{v} \ne \mathbf{0}, v⊤(X⊤X+mλI)v=∥Xv∥2+mλ∥v∥2>0\mathbf{v}^\top\left(\mathbf{X}^\top\mathbf{X} + m\lambda\mathbf{I}\right)\mathbf{v} = \lVert\mathbf{X}\mathbf{v}\rVert^2 + m\lambda\lVert\mathbf{v}\rVert^2 > 0, nên ma trận khả nghịch, kể cả khi các cột cộng tuyến (collinearity) hay có ít mẫu hơn đặc trưng.
  • Đây là mλm\lambda trong code. Ridge của scikit-learn làm nhỏ ∥y−Xw∥2+α∥w∥22\lVert\mathbf{y} - \mathbf{X}\mathbf{w}\rVert^2 + \alpha\lVert\mathbf{w}\rVert_2^2, tức m⋅Jm \cdot J khi α=mλ\alpha = m\lambda, nên code viết Ridge(alpha=m * lam).

Xét một đặc trưng đã chuẩn hóa zz, nên 1m∑i(z(i))2=1\frac{1}{m}\sum_i \left(z^{(i)}\right)^2 = 1. Gọi c=1m∑iz(i)yc(i)c = \frac{1}{m}\sum_i z^{(i)}y^{(i)}_c và s=1m∑i(yc(i))2s = \frac{1}{m}\sum_i\left(y^{(i)}_c\right)^2. Khai triển MSE theo ww:

1m∑i(wz(i)−yc(i))2=w2⋅1m∑i(z(i))2−2w⋅1m∑iz(i)yc(i)+s=w2−2wc+s=(w−c)2+s−c2\begin{aligned} \frac{1}{m}\sum_{i}\left(wz^{(i)} - y^{(i)}_c\right)^2 &= w^2\cdot\frac{1}{m}\sum_i\left(z^{(i)}\right)^2 - 2w\cdot\frac{1}{m}\sum_i z^{(i)}y^{(i)}_c + s \\ &= w^2 - 2wc + s \\ &= (w - c)^2 + s - c^2 \end{aligned}

Dòng đầu khai triển (a−b)2=a2−2ab+b2(a - b)^2 = a^2 - 2ab + b^2 và đưa ww ra ngoài các tổng. Dòng thứ hai dùng 1m∑i(z(i))2=1\frac{1}{m}\sum_i\left(z^{(i)}\right)^2 = 1 và định nghĩa của cc. Dòng cuối dùng w2−2wc=(w−c)2−c2w^2 - 2wc = (w - c)^2 - c^2. Vì s−c2s - c^2 không chứa ww, MSE là parabol theo ww với đáy ở cc, nghiệm khi không phạt.

Với Ridge, làm nhỏ (w−c)2+λw2(w - c)^2 + \lambda w^2. Đạo hàm theo ww cho bằng 0:

2(w−c)+2λw=0⟹w=c1+λ2(w - c) + 2\lambda w = 0 \quad\Longrightarrow\quad w = \frac{c}{1 + \lambda}

Ridge chia cc cho 1+λ>11 + \lambda > 1: hệ số nhỏ lại, giữ dấu, nhưng không bao giờ bằng đúng 0 (trừ khi c=0c = 0).

Khi có nhiều đặc trưng, Ridge co mạnh nhất theo những hướng mà dữ liệu “nói” ít nhất. Các cột zkz_k gần như trùng nhau (z14z_{14} và z15z_{15} có hệ số tương quan 0.9996), nên “tăng w14w_{14} thật nhiều, giảm w15w_{15} thật nhiều” là một hướng mà đổi theo nó thì dự đoán đổi rất ít. Các hệ số hàng chục tỉ sinh ra theo những hướng như vậy, và một chút phạt rất nhỏ đã đủ dập chúng.

Ví dụ một biến. Với Lasso, làm nhỏ (w−c)2+λ∣w∣(w - c)^2 + \lambda\lvert w\rvert. Hàm ∣w∣\lvert w\rvert gãy góc ở 0, nên xét từng phía:

  • w>0w > 0: đạo hàm là 2(w−c)+λ2(w - c) + \lambda, bằng 0 khi w=c−λ2w = c - \frac{\lambda}{2}. Chỉ hợp lệ khi kết quả dương, tức c>λ2c > \frac{\lambda}{2}.
  • w<0w < 0: đạo hàm là 2(w−c)−λ2(w - c) - \lambda, cho w=c+λ2w = c + \frac{\lambda}{2}. Hợp lệ khi c<−λ2c < -\frac{\lambda}{2}.
  • Khi ∣c∣≤λ2\lvert c\rvert \le \frac{\lambda}{2}: bên phải 0 đạo hàm 2w+(λ−2c)2w + (\lambda - 2c) luôn dương, bên trái 0 đạo hàm 2w−(λ+2c)2w - (\lambda + 2c) luôn âm. Hàm tăng khi rời 0 theo cả hai hướng, nên nhỏ nhất tại w=0w = 0.
w={c−λ2,c>λ20,∣c∣≤λ2c+λ2,c<−λ2w = \begin{cases} c - \frac{\lambda}{2}, & c > \frac{\lambda}{2} \\ 0, & \lvert c\rvert \le \frac{\lambda}{2} \\ c + \frac{\lambda}{2}, & c < -\frac{\lambda}{2} \end{cases}

Lasso trừ một lượng cố định λ2\frac{\lambda}{2} khỏi độ lớn của cc rồi cắt ở 0, gọi là soft-thresholding. So hai cách với c=0.3c = 0.3:

λ\lambdaRidge: c/(1+λ)c/(1 + \lambda)Lasso
0.20.250.2
0.40.21430.1
10.150

Gốc rễ nằm ở độ dốc của khoản phạt gần 0. Phạt Ridge λw2\lambda w^2 có độ dốc 2λw2\lambda w, sát 0 thì gần như phẳng, nên dữ liệu kéo yếu mấy cũng đưa được ww ra khỏi 0. Phạt Lasso λ∣w∣\lambda\lvert w\rvert có độ dốc λ\lambda ngay cả sát 0: lực kéo của dữ liệu (2∣c∣2\lvert c\rvert) phải thắng λ\lambda thì ww mới rời 0.

Hình học. Có thể chứng minh rằng với mỗi λ\lambda có một “ngân sách” tt sao cho bài toán có phạt cùng nghiệm với bài toán: làm MSE nhỏ nhất, với điều kiện độ lớn của w\mathbf{w} không vượt quá tt. Với hai trọng số, vùng cho phép của Ridge, w12+w22≤tw_1^2 + w_2^2 \le t, là hình tròn; của Lasso, ∣w1∣+∣w2∣≤t\lvert w_1\rvert + \lvert w_2\rvert \le t, là hình thoi có bốn đỉnh trên hai trục. Đường đồng mức của MSE là những elip quanh nghiệm bình phương tối thiểu. Khi nghiệm đó nằm ngoài vùng cho phép, phình elip ra tới khi chạm vùng: điểm chạm là nghiệm.

Hình tròn không có góc nhô ra, nên điểm chạm thường có cả hai tọa độ khác 0. Hình thoi có đỉnh nhọn nằm đúng trên các trục, và elip rất dễ chạm ở một đỉnh, nơi một tọa độ bằng 0. Với 15 trọng số, “hình thoi” có rất nhiều đỉnh và cạnh như vậy, nên nhiều hệ số bằng 0 cùng lúc: mô hình thưa (sparse).

Ridge (L2)w1w2Điểm chạm:cả hai trọng số khác 0Lasso (L1)w1w2Điểm chạm:nằm trên trụcNghiệm không phạt
Vùng cho phép w12+w22≤tw_1^2 + w_2^2 \le t là hình tròn, ∣w1∣+∣w2∣≤t\lvert w_1\rvert + \lvert w_2\rvert \le t là hình thoi; elip đồng mức của MSE thường chạm hình thoi ở một đỉnh, nơi một trọng số bằng đúng 0.

Lasso không có nghiệm dạng đóng. scikit-learn giải bằng coordinate descent: lần lượt cập nhật từng hệ số, mỗi lần dùng phép soft-thresholding ở trên.

Elastic Net (Zou và Hastie, 2005) cộng cả hai khoản phạt:

J(w,b)=L(w,b)+λ1∥w∥1+λ2∥w∥22J(\mathbf{w}, b) = \mathcal{L}(\mathbf{w}, b) + \lambda_1\lVert\mathbf{w}\rVert_1 + \lambda_2\lVert\mathbf{w}\rVert_2^2

λ2=0\lambda_2 = 0 là Lasso, λ1=0\lambda_1 = 0 là Ridge. Khi vài đặc trưng tương quan rất mạnh, như z14z_{14} và z15z_{15}, Lasso thường giữ một cái, bỏ cái kia khá tùy ý. Phần L2 chia trọng số đều hơn giữa các đặc trưng giống nhau, phần L1 vẫn cho hệ số đúng 0. Đổi lại, có hai siêu tham số phải chọn. ElasticNet viết chúng thành alpha và l1_ratio (cách đổi ở comment của ô code thứ ba).

λ\lambda là siêu tham số, nên chọn như chọn bậc ở bài trước: thử một lưới λ\lambda (thường cách nhau theo lũy thừa của 10), giữ mức có MSE kiểm định chéo nhỏ nhất, khớp lại trên cả tập train, rồi chấm tập test một lần. Đặt StandardScaler trong pipeline, để mỗi lần khớp nó chỉ học từ phần đang dùng để khớp.

  • Không thắng được việc chọn đúng mô hình. Ở bộ dữ liệu này, Ridge và Lasso bậc 15 vẫn kém đa thức bậc 3 (cuối mục Ví dụ code).
  • λ\lambda chọn ra dao động khi dữ liệu ít (ô code thứ ba).
  • Lasso chọn đặc trưng khá tùy ý khi chúng tương quan mạnh. Đừng coi tập nó giữ lại là “đặc trưng quan trọng thật”.
  • Không chỉ cho hồi quy tuyến tính. Hồi quy logistic và mạng nơ-ron cũng phạt trọng số (phạt L2 ở đó thường gọi là weight decay).

Hình dưới vẽ điểm train (chấm đặc), điểm test (vòng rỗng) và đa thức bậc 15 khớp với hệ số phạt λ\lambda. Thanh trượt đổi λ\lambda qua 17 mức: 0, rồi 10−14,…,1,1010^{-14}, \dots, 1, 10. Hai nút Ridge và Lasso đổi kiểu phạt, giữ nguyên λ\lambda. Bên dưới là hai MSE, số hệ số bằng đúng 0 (vòng “0” trên biểu đồ cột) và 15 cột hệ số wkw_k. Hãy thử:

  1. Lúc đầu là Ridge, λ=0\lambda = 0: đường bậc 15 của bài trước, MSE test 133.20, cả 15 cột vượt khung.
  2. Kéo tới λ=0.001\lambda = 0.001: MSE train 0.0491, MSE test 0.0894, nhỏ nhất trên thanh trượt (dấu ✓). Mọi cột nằm trong khung.
  3. Bấm Lasso: MSE train 0.0314, MSE test 0.0851, và 10/15 hệ số bằng đúng 0, chỉ còn k=1,2,4,9,15k = 1, 2, 4, 9, 15.
  4. Kéo tới λ=10\lambda = 10: Ridge có MSE train 0.3148, MSE test 0.4750, underfitting. Lasso có cả 15 hệ số bằng 0, đường nằm ngang ở yˉ=0.0831\bar{y} = 0.0831.

Dấu ✓ nhìn vào tập test, việc mà khi làm thật bạn không được làm. Phần Lasso của hình được tính sẵn bằng một cách giải chính xác, vì ở λ\lambda rất nhỏ coordinate descent hội tụ rất chậm.

Kéo thanh trượt để đổi λ, bấm Ridge hoặc Lasso để đổi kiểu phạt. Đa thức luôn có bậc 15 và chỉ được khớp trên các điểm train.
  • Điểm train
  • Điểm test
  • Đa thức bậc 15 đã khớp
MSE train0.0000
MSE test133.20
Hệ số bằng đúng 00/15
  • Vượt khung (trị tuyệt đối lớn hơn 10)

Overfitting: phạt quá nhẹ, hệ số rất lớn và đường cong uốn theo cả nhiễu, MSE test lớn.

16 điểm train, x từ 0.03 tới 0.98, đi theo một vòng sóng: lên gần 1 khi x từ 0.1 tới 0.35, xuống gần −1 khi x từ 0.7 tới 0.85, rồi về gần 0. Các điểm lệch khỏi dạng sóng vì có nhiễu. 8 điểm test nằm xen giữa các điểm train, theo cùng dạng sóng.

Mỗi cột là hệ số wₖ của đặc trưng xᵏ đã chuẩn hóa, k từ 1 đến 15. Cột hướng lên là hệ số dương, hướng xuống là hệ số âm.

Ba ô dưới đây chạy độc lập. Ô chạy đầu tiên lâu hơn một chút, vì trình duyệt phải tải thêm scikit-learn. Mọi ô dùng cùng pipeline: PolynomialFeatures tạo các cột x,…,x15x, \dots, x^{15}, StandardScaler chuẩn hóa chúng, rồi tới mô hình có phạt. model[-1] là bước cuối, có coef_ (các wkw_k) và intercept_ (bb).

Ô đầu tiên khớp Ridge với năm mức λ\lambda, in hai MSE, hệ số lớn nhất về trị tuyệt đối, và bb.

Ridge trên đa thức bậc 15

import numpy as np
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_squared_error
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
# 24 điểm tự sinh từ y = sin(2πx) cộng nhiễu: 16 điểm train, 8 điểm test.
# dataset: sine-train
x_train = np.array([0.03, 0.12, 0.15, 0.23, 0.26, 0.35, 0.41, 0.47, 0.51, 0.6, 0.64, 0.73, 0.77, 0.85, 0.9, 0.98])
y_train = np.array([0.15, 1.06, 0.84, 0.96, 0.83, 0.94, 0.3, -0.09, -0.17, -0.32, -0.68, -0.84, -0.28, -0.82, -0.53, -0.02])
# dataset: sine-test
x_test = np.array([0.06, 0.18, 0.32, 0.43, 0.55, 0.67, 0.8, 0.94])
y_test = np.array([0.33, 0.66, 0.93, 0.43, -0.42, -1.11, -1.18, -0.45])
m = len(x_train) # 16 điểm train
for lam in [1e-12, 1e-8, 1e-3, 1e-1, 10]:
# Ridge của scikit-learn không chia tổng bình phương cho m,
# nên phải đặt alpha = m * lambda để khớp hàm mục tiêu của bài.
model = make_pipeline(PolynomialFeatures(15, include_bias=False),
StandardScaler(), Ridge(alpha=m * lam))
model.fit(x_train[:, None], y_train) # chỉ học từ tập train
tr = mean_squared_error(y_train, model.predict(x_train[:, None]))
te = mean_squared_error(y_test, model.predict(x_test[:, None]))
w = model[-1].coef_ # 15 trọng số của các đặc trưng đã chuẩn hóa
b = model[-1].intercept_ # hệ số tự do, không bị phạt
print(f"lambda = {lam:g}: MSE train = {tr:.4f}, test = {te:.4f}, "
f"max |w| = {np.abs(w).max():.2f}, b = {b:.4f}")

Lần chạy đầu cần tải Python, khoảng 10 MB.

Chỉ λ=10−12\lambda = 10^{-12} đã kéo MSE test từ khoảng 133 xuống 0.5375, hệ số lớn nhất từ hàng chục tỉ còn 26594.49: đó là các “hướng mà dữ liệu nói ít”. Ở λ=0.001\lambda = 0.001, MSE test nhỏ nhất (0.0894) và hệ số lớn nhất chỉ còn 2.75. Ở λ=10\lambda = 10, MSE train 0.3148, gần gấp đôi đường thẳng bậc 1 của bài trước (0.1663): underfitting. MSE train tăng dần theo λ\lambda, còn MSE test giảm rồi tăng: hình chữ U như khi đổi bậc, chỉ là trục ngang đảo chiều. b=0.0831b = 0.0831 ở cả năm mức, đúng bằng yˉ\bar{y}, như Bước 1.

Ô thứ hai khớp Lasso với ba mức λ\lambda, đếm số hệ số bằng đúng 0 (w == 0) và in các kk còn giữ lại. Cuối ô là Ridge ở λ=0.001\lambda = 0.001 để so sánh.

Lasso: đếm hệ số bằng 0

import numpy as np
from sklearn.linear_model import Lasso, Ridge
from sklearn.metrics import mean_squared_error
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
# 24 điểm tự sinh từ y = sin(2πx) cộng nhiễu: 16 điểm train, 8 điểm test.
# dataset: sine-train
x_train = np.array([0.03, 0.12, 0.15, 0.23, 0.26, 0.35, 0.41, 0.47, 0.51, 0.6, 0.64, 0.73, 0.77, 0.85, 0.9, 0.98])
y_train = np.array([0.15, 1.06, 0.84, 0.96, 0.83, 0.94, 0.3, -0.09, -0.17, -0.32, -0.68, -0.84, -0.28, -0.82, -0.53, -0.02])
# dataset: sine-test
x_test = np.array([0.06, 0.18, 0.32, 0.43, 0.55, 0.67, 0.8, 0.94])
y_test = np.array([0.33, 0.66, 0.93, 0.43, -0.42, -1.11, -1.18, -0.45])
def fit(model):
# Đa thức bậc 15, chuẩn hóa đặc trưng, rồi mô hình có phạt.
pipe = make_pipeline(PolynomialFeatures(15, include_bias=False),
StandardScaler(), model)
return pipe.fit(x_train[:, None], y_train)
for lam in [1e-3, 1e-2, 1e-1]:
# Lasso của scikit-learn có thêm 1/2 ở trước MSE, nên alpha = lambda / 2.
model = fit(Lasso(alpha=lam / 2, max_iter=100_000, tol=1e-6))
tr = mean_squared_error(y_train, model.predict(x_train[:, None]))
te = mean_squared_error(y_test, model.predict(x_test[:, None]))
w = model[-1].coef_
kept = np.flatnonzero(w) + 1 # bậc k của các hệ số khác 0
print(f"lambda = {lam:g}: MSE train = {tr:.4f}, test = {te:.4f}")
print(f" zeros = {np.sum(w == 0)}/15, kept k = {kept.tolist()}")
# So sánh: Ridge cùng lambda = 0.001 không đưa hệ số nào về đúng 0.
w = fit(Ridge(alpha=len(x_train) * 1e-3))[-1].coef_
print(f"ridge 0.001: zeros = {np.sum(w == 0)}/15, "
f"min |w| = {np.abs(w).min():.2f}")

Lần chạy đầu cần tải Python, khoảng 10 MB.

  • Ở λ=0.001\lambda = 0.001, Lasso có MSE test 0.0851, nhỉnh hơn Ridge cùng mức (0.0894), và chỉ giữ 5 hệ số: k=1,2,4,9,15k = 1, 2, 4, 9, 15. Ở λ=0.01\lambda = 0.01 còn 4, ở λ=0.1\lambda = 0.1 còn 3 (lý thuyết không bảo đảm lúc nào cũng tăng như vậy).
  • Ridge cùng mức 0.001 có 0/15 hệ số bằng 0; nhỏ nhất là 0.07, nhỏ nhưng khác 0.

Các kk được giữ đổi khá tùy ý (4 và 9, rồi 6 và 7), vì các cột zkz_k gần như trùng nhau: đó là lý do của Elastic Net. Ô không dùng λ\lambda từ 10−410^{-4} trở xuống, vì ở đó coordinate descent không kịp hội tụ (hộp thứ ba ở mục Lỗi hay gặp).

Ô thứ ba chọn λ\lambda chỉ bằng 16 điểm train, với KFold(n_splits=4, shuffle=True, random_state=0) như bài trước. Ridge thử λ\lambda từ 10−610^{-6} tới 10. Lasso và Elastic Net (λ1=λ2=λ\lambda_1 = \lambda_2 = \lambda) chỉ thử 0.01 và 0.1, vì ở 0.001 Lasso không hội tụ kịp trong các fold. alpha của Ridge giữ là 16λ16\lambda cả trong các fold chỉ có 12 điểm, như RidgeCV vẫn làm, nên λ\lambda thật trong fold là 43λ\frac{4}{3}\lambda. Thử với alpha =12λ= 12\lambda vẫn chọn cùng mức.

Chọn λ bằng kiểm định chéo

import numpy as np
from sklearn.linear_model import ElasticNet, Lasso, Ridge
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import KFold, cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
# dataset: sine-train
x_train = np.array([0.03, 0.12, 0.15, 0.23, 0.26, 0.35, 0.41, 0.47, 0.51, 0.6, 0.64, 0.73, 0.77, 0.85, 0.9, 0.98])
y_train = np.array([0.15, 1.06, 0.84, 0.96, 0.83, 0.94, 0.3, -0.09, -0.17, -0.32, -0.68, -0.84, -0.28, -0.82, -0.53, -0.02])
# dataset: sine-test
x_test = np.array([0.06, 0.18, 0.32, 0.43, 0.55, 0.67, 0.8, 0.94])
y_test = np.array([0.33, 0.66, 0.93, 0.43, -0.42, -1.11, -1.18, -0.45])
# Ridge giữ alpha = 16 * lambda cả trong fold 12 điểm (như RidgeCV).
makers = {
"ridge": lambda lam: Ridge(alpha=len(x_train) * lam),
"lasso": lambda lam: Lasso(alpha=lam / 2, max_iter=100_000, tol=1e-6),
# lambda_1 = lambda_2 = lam: alpha = lam/2 + lam, l1_ratio = (lam/2)/alpha
"enet": lambda lam: ElasticNet(alpha=1.5 * lam, l1_ratio=1 / 3,
max_iter=100_000, tol=1e-6),
}
cv = KFold(n_splits=4, shuffle=True, random_state=0) # chỉ chia tập train
for name, make in makers.items():
# Lasso ở 0.001 không hội tụ kịp trong các fold: bắt đầu từ 0.01.
lams = 10.0 ** np.arange(-6, 2) if name == "ridge" else [0.01, 0.1]
pipes, cv_mse = {}, {}
for lam in lams:
pipes[lam] = make_pipeline(PolynomialFeatures(15, include_bias=False),
StandardScaler(), make(lam))
scores = cross_val_score(pipes[lam], x_train[:, None], y_train,
cv=cv, scoring="neg_mean_squared_error")
cv_mse[lam] = -scores.mean()
print(f"{name} lambda = {lam:g}: CV MSE = {cv_mse[lam]:.4f}")
best = min(cv_mse, key=cv_mse.get)
# Chọn xong mới khớp lại trên cả tập train, chấm một lần trên test.
model = pipes[best].fit(x_train[:, None], y_train)
te = mean_squared_error(y_test, model.predict(x_test[:, None]))
print(f" best: lambda = {best:g}, test MSE = {te:.4f}, "
f"zeros = {np.sum(model[-1].coef_ == 0)}")

Lần chạy đầu cần tải Python, khoảng 10 MB.

Kiểm định chéo chọn Ridge λ=0.1\lambda = 0.1 (MSE kiểm định chéo 0.1605), cho MSE test 0.1747. Nó không chọn λ=0.001\lambda = 0.001, mức tốt nhất trên tập test ở ô đầu: ở đó MSE kiểm định chéo là 0.3409, lớn gấp đôi.

Vì sao lệch nhau? Điểm test nằm xen giữa điểm train (từ x=0.06x = 0.06 tới 0.940.94), nên không bắt đường cong đoán ở chỗ trống. Kiểm định chéo thì có: fold giữ lại x=0.03x = 0.03 hay 0.980.98 bắt đa thức bậc 15 phải ngoại suy (extrapolation), fold giữ lại cả 0.850.85 lẫn 0.90.9 để trống từ 0.77 tới 0.98. Ở đó, đường phạt nhẹ vung mạnh: khi giữ lại x=0.98x = 0.98, bình phương sai số tại điểm này là 0.88 với λ=0.001\lambda = 0.001, chỉ 0.02 với λ=0.1\lambda = 0.1. Nên ở đây kiểm định chéo nghiêng về phạt nặng. Nó vẫn là cách đúng: 0.0894 lạc quan vì đã nhìn tập test để chọn, còn 0.1747 thì trung thực.

Lasso chọn λ=0.01\lambda = 0.01: MSE test 0.1341, 11 hệ số bằng 0. Elastic Net cũng chọn λ=0.01\lambda = 0.01, MSE kiểm định chéo 0.1696 (Lasso là 0.1765), MSE test 0.1481, và chỉ 6 hệ số bằng 0, vì phần L2 chia trọng số cho các cột giống nhau thay vì bỏ hẳn chúng.

Mô hìnhλ\lambda chọn bằngMSE test
Bậc 15, không phạtkhoảng 133
Bậc 3, không phạtkiểm định chéo (bài trước)0.0754
Ridge bậc 15, λ=0.001\lambda = 0.001nhìn tập test (không được làm)0.0894
Lasso bậc 15, λ=0.001\lambda = 0.001nhìn tập test (không được làm)0.0851
Ridge bậc 15, λ=0.1\lambda = 0.1kiểm định chéo0.1747
Lasso bậc 15, λ=0.01\lambda = 0.01kiểm định chéo0.1341
Elastic Net bậc 15, λ=0.01\lambda = 0.01kiểm định chéo0.1481

Nói thẳng: ở bộ dữ liệu này, chọn đúng bậc 3 vẫn tốt nhất. Ngay cả ở mức λ\lambda tốt nhất trên lưới, chọn bằng tập test, Ridge kém bậc 3 khoảng 19%, Lasso khoảng 13%. Ridge với λ\lambda khoảng 3×10−43 \times 10^{-4}, giữa hai nấc của lưới, cho khoảng 0.085, vẫn kém khoảng 12%. Quy luật thật rất gần một đa thức bậc 3, nên “chọn bậc” là đúng núm vặn cho bài toán này. Vậy vì sao vẫn cần regularization?

  • Không phải dò từng tập đặc trưng. Ở đây các đặc trưng có thứ tự tự nhiên, nên thử bậc 1 tới 15 là đủ. Với 50 đặc trưng như diện tích, số phòng, khoảng cách tới trung tâm, không có “bậc” để dò, còn số tập con là 2502^{50}, tức hơn một triệu tỉ. λ\lambda là một núm duy nhất cho mọi đặc trưng.
  • Chặn thảm họa. Từ khoảng 133 xuống 0.1747 chỉ bằng một tham số, kể cả khi kiểm định chéo chọn thận trọng.
  • Lasso tự chọn đặc trưng, và kiểm định chéo có thể chọn cùng lúc cả bậc lẫn λ\lambda.
Câu 1/4 Bạn khớp Ridge trên hai đặc trưng: chiều dài đo bằng mét và cân nặng đo bằng kg, không chuẩn hóa. Đồng nghiệp đổi cột chiều dài sang centimét rồi khớp lại với cùng λ\lambda. Chuyện gì xảy ra?
Câu 2/4 Một đặc trưng đã chuẩn hóa, bình phương tối thiểu cho c=−0.5c = -0.5. Với Lasso, hàm mục tiêu là (w−c)2+λ∣w∣(w - c)^2 + \lambda\lvert w\rvert (bỏ hằng số). Với λ=0.6\lambda = 0.6, ww bằng bao nhiêu?
Câu 3/4 Với m=50m = 50 điểm train và λ=0.02\lambda = 0.02, bạn muốn khớp Ridge với hàm mục tiêu L+λ∥w∥22\mathcal{L} + \lambda\lVert\mathbf{w}\rVert_2^2 và Lasso với L+λ∥w∥1\mathcal{L} + \lambda\lVert\mathbf{w}\rVert_1, trong đó L\mathcal{L} là MSE (chia mm). Viết bằng scikit-learn thế nào cho đúng?
Câu 4/4 Kiểm định chéo trên tập train chọn λ=1\lambda = 1. Sau đó bạn thử vài mức trên tập test và thấy λ=0.01\lambda = 0.01 có MSE test nhỏ hơn. Nên làm gì?

Ý chính:

  • Regularization làm nhỏ MSE cộng một khoản phạt: Ridge λ∥w∥22\lambda\lVert\mathbf{w}\rVert_2^2, Lasso λ∥w∥1\lambda\lVert\mathbf{w}\rVert_1, Elastic Net cả hai; bb không bị phạt. λ\lambda nhỏ quá là overfitting, lớn quá là underfitting.
  • Ridge: (X⊤X+mλI)w=X⊤y\left(\mathbf{X}^\top\mathbf{X} + m\lambda\mathbf{I}\right)\mathbf{w} = \mathbf{X}^\top\mathbf{y}, b=yˉb = \bar{y} (đặc trưng đã chuẩn hóa). Mọi hệ số co lại nhưng không về đúng 0.
  • Lasso trừ một lượng cố định rồi cắt ở 0 (một biến: w=0w = 0 khi ∣c∣≤λ/2\lvert c\rvert \le \lambda/2), nên cho mô hình thưa. Hình thoi L1 có đỉnh trên các trục, hình tròn L2 thì không.
  • Chuẩn hóa đặc trưng trong pipeline. Đổi đúng λ\lambda sang alpha (Ridge: mλm\lambda, Lasso: λ/2\lambda/2). Chọn λ\lambda bằng kiểm định chéo, kể cả khi nó thận trọng hơn mức tốt nhất trên tập test.

Đọc thêm:

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