Home

岭回归、Lasso回归和弹性网络

· Package Build

岭回归(Ridge)、LASSO回归(Lasso)和弹性网络(ElasticNet)都是带正则化(regularization)的线性回归方法,核心目的是:解决多重共线性、过拟合,并控制模型复杂度。

它们的区别本质在于:对模型参数施加不同的惩罚方式。

在线教育场景下,我们经常需要回答这样的问题:哪些学习行为真正影响用户成绩?哪些只是噪声?

普通线性回归在这种场景下往往表现不稳定,而正则化回归(Ridge / LASSO / Elastic Net)正是为此设计。

场景假设

假设,我们需要通过用户在平台上的行为来预测其最终考试成绩(score),用户行为特征如下:

特征 含义
study_time 学习时长
video_watch 视频观看量
practice_rate 练习完成度
login_days 登录天数
homework_score 作业成绩

数据集

模拟

我们构造一个包含潜在学习能力的模拟数据。
study_timevideo_watch高度相关、部分变量对成绩无真实贡献、并且存在噪声。

library(tidyverse)

set.seed(123)
n <- 500

# 潜在学习能力(不可观测)
ability <- rnorm(n)

# 构造相关特征
study_time <- 10 + 3*ability + rnorm(n)
video_watch <- study_time*0.8 + rnorm(n, 0, 0.5)
practice_rate <- 0.6*ability + rnorm(n, 0, 0.8)
login_days <- 5 + 1.5*ability + rnorm(n)
homework_score <- 70 + 10*ability + rnorm(n, 0, 3)

# 真实成绩(只有部分变量真正影响)
score <- 60 +
  4*study_time +
  2*practice_rate +
  3*homework_score +
  rnorm(n, 0, 10)

df <- data.frame(
  score,
  study_time,
  video_watch,
  practice_rate,
  login_days,
  homework_score
)

探查

# 变量分布
df |>
  pivot_longer(-score) |>
  ggplot(aes(value)) +
  geom_histogram(bins = 30, fill = "steelblue") +
  facet_wrap(~name, scales = "free") +
  theme_minimal()
# 变量相关性
GGally::ggcorr(df, label = TRUE) 
# 特征与成绩相关性
df |>
  pivot_longer(-score) |>
  ggplot(aes(value, score)) +
  geom_point(alpha = 0.4) +
  geom_smooth(method = "lm", se = FALSE) +
  facet_wrap(~name, scales = "free") +
  theme_minimal()
  • 可见study_timevideo_watch强相关,study_timehomework_score相关,存在 多重共线性问题
  • 多个变量都与成绩相关,难判断因果关系

建模对比

简单线性回归

lm(score ~ ., data = df)
## 
## Call:
## lm(formula = score ~ ., data = df)
## 
## Coefficients:
##    (Intercept)      study_time     video_watch   practice_rate      login_days  
##        56.5695          4.3658         -0.5375          2.0594          0.1158  
## homework_score  
##         3.0465

基于最小二乘法的目标(最小化预测误差)在此场景中存在以下问题:

  • video_watchstudy_time,模型无法稳定分配权重,系数波动大。
  • 容易过拟合,特征多 → 方差大 → 泛化差。
  • 无变量选择能力,无法识别哪些行为真正重要。

正则化方法对比

方法 会删除变量 对共线性稳定 适合目标
OLS 基础拟合
Ridge 预测
LASSO 一般 解释
Elastic Net 综合
library(glmnet)

x <- model.matrix(score ~ . -1, df)
y <- df$score

# 训练三种模型
cv_ridge <- cv.glmnet(x, y, alpha = 0)
cv_lasso <- cv.glmnet(x, y, alpha = 1)
cv_en <- cv.glmnet(x, y, alpha = 0.5)

# 系数对比
coef_df <- tibble(
  variable = rownames(coef(cv_ridge)),
  Ridge = as.numeric(coef(cv_ridge)),
  LASSO = as.numeric(coef(cv_lasso)),
  ElasticNet = as.numeric(coef(cv_en))
) |>
  pivot_longer(-variable)

coef_df |> 
  filter(variable != "(Intercept)") |>
  ggplot(aes(variable, value, fill = name)) +
  geom_col(position="dodge") +
  coord_flip() +
  theme_minimal()
# 预测表现对比
pred <- tibble(
  actual = y,
  ridge = as.numeric(predict(cv_ridge, x, s="lambda.min")),
  lasso = as.numeric(predict(cv_lasso, x, s="lambda.min")),
  elastic = as.numeric(predict(cv_en, x, s="lambda.min"))
) |>
  pivot_longer(-actual, names_to = "model", values_to = "pred")

# 计算模型指标(RMSE + R2)
metric_df <- pred |>
  group_by(model) |>
  summarise(
    rmse = sqrt(mean((actual - pred)^2)),
    r2 = cor(actual, pred)^2,
    .groups = "drop"
  ) |>
  mutate(label = sprintf("RMSE = %.2f\nR² = %.3f", rmse, r2))

# 绘图
pred |>
  ggplot(aes(actual, pred)) +
  geom_point(alpha = 0.3) +
  geom_abline(linetype = 2) +
  facet_wrap(~model) +
  geom_text(
    data = metric_df,
    aes(x = -Inf, y = Inf, label = label),
    hjust = -0.1,
    vjust = 1.1,
    inherit.aes = FALSE
  ) +
  labs(
    x = "Actual",
    y = "Predicted",
    title = "Model Prediction Comparison"
  ) +
  theme_minimal()

拓展

λ该怎么选择?

# λ 路径
# 图含义:横轴-log(λ) 、纵轴-系数大小 、每条线-各变量
# λ增大 → 系数逐渐收缩 → 部分变0
plot(cv_lasso$glmnet.fit, xvar = "lambda")
# 交叉验证选择最优λ
# 原理:
# 1.将数据分成 k 份
# 2.轮流训练验证
# 3.选择预测误差最小的 λ

cv_lasso <- cv.glmnet(x, y, alpha = 1)
plot(cv_lasso)