为什么 lasso 不收敛于惩罚参数?

机器算法验证 r 回归 Python 套索 正则化
2022-03-30 20:19:37

为了探索LASSO回归的工作原理,我编写了一小段代码,应该LASSO通过选择最佳 alpha 参数来优化回归。

我无法弄清楚为什么LASSO在交叉验证后回归给我的 alpha 参数结果如此不稳定。

这是我的 Python 代码:

from sklearn.linear_model import Lasso
from sklearn.cross_validation import KFold
from matplotlib import pyplot as plt

# generate some sparse data to play with
import numpy as np
import pandas as pd 
from scipy.stats import norm
from scipy.stats import uniform

### generate your own data here

n = 1000

x1x2corr = 1.1
x1x3corr = 1.0
x1 = range(n) + norm.rvs(0, 1, n) + 50
x2 =  map(lambda aval: aval*x1x2corr, x1) + norm.rvs(0, 2, n) + 500
y = x1 + x2 #+ norm.rvs(0,10, n)

Xdf = pd.DataFrame()
Xdf['x1'] = x1
Xdf['x2'] = x2

X = Xdf.as_matrix()

# Split data in train set and test set
n_samples = X.shape[0]
X_train, y_train = X[:n_samples / 2], y[:n_samples / 2]
X_test, y_test = X[n_samples / 2:], y[n_samples / 2:]

kf = KFold(X_train.shape[0], n_folds = 10, )
alphas = np.logspace(-16, 8, num = 1000, base = 2)

e_alphas = list()
e_alphas_r = list()  # holds average r2 error
for alpha in alphas:
    lasso = Lasso(alpha=alpha, tol=0.004)
    err = list()
    err_2 = list()
    for tr_idx, tt_idx in kf:
        X_tr, X_tt = X_train[tr_idx], X_test[tt_idx]
        y_tr, y_tt = y_train[tr_idx], y_test[tt_idx]
        lasso.fit(X_tr, y_tr)
        y_hat = lasso.predict(X_tt)

        # returns the coefficient of determination (R^2 value)
        err_2.append(lasso.score(X_tt, y_tt))

        # returns MSE
        err.append(np.average((y_hat - y_tt)**2))
    e_alphas.append(np.average(err))
    e_alphas_r.append(np.average(err_2))

## print out the alpha that gives the minimum error
print 'the minimum value of error is ', e_alphas[e_alphas.index(min(e_alphas))]
print ' the minimizer is ',  alphas[e_alphas.index(min(e_alphas))]

##  <<< plotting alphas against error >>>

plt.figsize = (15, 15)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(alphas, e_alphas, 'b-')
ax.plot(alphas, e_alphas_r, 'g--')
ax.set_ylim(min(e_alphas),max(e_alphas))
ax.set_xlim(min(alphas),max(alphas))
ax.set_xlabel("alpha")
plt.show()

如果您重复运行此代码,它会为 alpha 提供截然不同的结果:

>>> 
the minimum value of error is  3.99254192539
 the minimizer is  1.52587890625e-05
>>> ================================ RESTART ================================
>>> 
the minimum value of error is  4.07412455842
 the minimizer is  6.45622425334
>>> ================================ RESTART ================================
>>> 
the minimum value of error is  4.25898253597
 the minimizer is  1.52587890625e-05
>>> ================================ RESTART ================================
>>> 
the minimum value of error is  3.79392968781
 the minimizer is  28.8971008254
>>> 

为什么 alpha 值不能正确收敛?我知道我的数据是合成的,但分布是一样的。x1此外,和的变化非常小x2

是什么导致这种情况如此不稳定?

用 R 编写的相同内容给出了不同的结果 - 它始终返回 alpha 的最高可能值作为“optimal_alpha”。

我也在R中写了这个,这给了我一个稍微不同的答案,我不知道为什么?

library(glmnet)
library(lars)
library(pracma)

set.seed(1)
k = 2 # number of features selected 

n = 1000

x1x2corr = 1.1
x1 = seq(n) + rnorm(n, 0, 1) + 50
x2 =  x1*x1x2corr + rnorm(n, 0, 2) + 500
y = x1 + x2 

filter_out_label <- function(col) {col!="y"}

alphas = logspace(-5, 6, 100)

for (alpha in alphas){
  k = 10
  optimal_alpha = NULL
  folds <- cut(seq(1, nrow(df)), breaks=k, labels=FALSE)
  total_mse = 0
  min_mse = 10000000
  for(i in 1:k){
    # Segement your data by fold using the which() function
    testIndexes <- which(folds==i, arr.ind=TRUE)
    testData <- df[testIndexes, ]
    trainData <- df[-testIndexes, ]

    fit <- lars(as.matrix(trainData[Filter(filter_out_label, names(df))]),
                trainData$y,
                type="lasso")
    # predict
    y_preds <- predict(fit, as.matrix(testData[Filter(filter_out_label, names(df))]),
                       s=alpha, type="fit", mode="lambda")$fit # default mode="step"

    y_true = testData$y
    residuals = (y_true - y_preds)
    mse=sum(residuals^2)
    total_mse = total_mse + mse
  }
  if (total_mse < min_mse){
    min_mse = total_mse
    optimal_alpha = alpha
  }
}

print(paste("the optimal alpha is ", optimal_alpha))

上面 R 代码的输出是:

> source('~.....')
[1] "the optimal alpha is  1e+06"

事实上,无论我为“ alphas = logspace(-5, 6, 100)”行设置什么,我总是取回 alpha 的最高值。

我想这里实际上有两个不同的问题:

  1. 为什么用 Python 编写的版本的 alpha 值如此不稳定?

  2. 为什么用 R 编写的版本会给我不同的结果?(我意识到该logspace函数与Rto不同python,但编写的版本R总是为我alpha提供最佳 alpha 值的最大值,而 python 版本没有)。

知道这些就太好了……

4个回答

我不太了解python,但我确实发现你的R代码有一个问题。

你有2行:

residuals = sum(y_true - y_preds)
mse=residuals^2

对残差求和,然后对它们求平方。这与对残差求平方,然后对它们求和(这似乎是 python 代码正确)非常不同。我怀疑这可能是 R 代码和 python 代码之间差异的很大一部分。修复 R 代码并再次运行以查看其行为是否更像 python 代码。

我还建议您存储所有它们并绘制关系,而不是只保存“最佳” alpha 和相应的 mse。可能是您的设置有一个非常平坦的区域,因此不同点的 mse 之间的差异不是很大。如果是这种情况,那么对数据进行非常小的更改(甚至是交叉验证中的顺序)可能会改变在许多基本相同的点中,哪个点给出最小值。出现导致最优值周围平坦区域的情况通常会导致您所看到的情况,并且所有 alpha 值与相应 mse 值的图可能会很有启发性。

sklearn 有一个示例,几乎与您在此处尝试执行的操作相同:http: //scikit-learn.org/stable/auto_examples/exercises/plot_cv_diabetes.html

实际上,此示例表明,对于该示例中完成的三个折叠中的每一个,您确实得到了非常不同的 alpha 结果。这意味着您不能信任 alpha 的选择,因为它显然高度依赖于您用于训练和选择 alpha 的数据部分。

我认为您不应该将交叉验证视为会“融合”为您提供完美答案的东西。实际上,我认为从概念上讲它几乎与收敛相反。您正在分离数据,并且对于每个折叠,您都将朝着“单独的方向”前进。根据您对测试和训练数据的划分方式,您会得到不同的结果这一事实应该告诉您,收敛到一个完美的结果是不可能的 - 也是不可取的。始终获得一致 alpha 值的唯一方法是使用所有数据进行训练。但是,如果你这样做,你会得到最好的学习结果,但最差的验证结果。

x1和中的多重共线性x2使得αPython 代码中的值不稳定。对于生成这些变量的分布,方差是如此之小,以至于系数的方差被夸大了。可以计算方差膨胀因子 (VIF) 来说明这一点。方差增加后

x1 = range(n) + norm.rvs(0, 1, n) + 50
x2 =  map(lambda aval: aval*x1x2corr, x1) + norm.rvs(0, 2, n) + 500

....至....

x1 = range(n) + norm.rvs(0, 100, n) + 50
x2 =  map(lambda aval: aval*x1x2corr, x1) + norm.rvs(0, 200, n) + 500

然后α值稳定。

R代码与 Python 代码不同的问题仍然是个谜……

我将评论 R 代码:

您在错误的位置重置变量,即变量min_mse应该 在循环 Inf外初始化,并且应该在那里初始化。这变成:foroptimal_alphaNULL

library(glmnet)
library(lars)
library(pracma)

set.seed(1)
k = 2 # number of features selected 

n = 100

x1x2corr = 1.1
x1 = seq(n) + rnorm(n, 0, 1) + 50
x2 =  x1*x1x2corr + rnorm(n, 0, 2) + 500
y = x1 + x2 +rnorm(n,0,0.5)
df = data.frame(x1 = x1, x2 = x2, y = y)
filter_out_label <- function(col) {col!="y"}

alphas = logspace(-5, 6, 50)

###
# INITIALIZE here before loop
###
min_mse = Inf
optimal_alpha = NULL
# Let's store the mse values for good measure
my_mse = c()

for (alpha in alphas){
  k = 10
  folds <- cut(seq(1, nrow(df)), breaks=k, labels=FALSE)
  # DO NOT INITIALIZE min_mse and optimal_alpha here, 
  # then you cannot find them...
  total_mse = 0
  for(i in 1:k){
    # Segement your data by fold using the which() function
    testIndexes <- which(folds==i, arr.ind=TRUE)
    testData <- df[testIndexes, ]
    trainData <- df[-testIndexes, ]

    fit <- lars(as.matrix(trainData[Filter(filter_out_label, names(df))]),
                trainData$y,
                type="lasso")
    # predict
    y_preds <- predict(fit, as.matrix(testData[Filter(filter_out_label,
                       names(df))]),
                       s=alpha, type="fit", mode="lambda")$fit 

    y_true = testData$y
    residuals = (y_true - y_preds)
    mse=sum(residuals^2)
    total_mse = total_mse + mse
  }
  # Let's store the MSE to see the effect
  my_mse <- c(my_mse, total_mse)
  if (total_mse < min_mse){
    min_mse = total_mse
    optimal_alpha = alpha
    # Let's observe the output
    print(min_mse)
  }
}

print(paste("the optimal alpha is ", optimal_alpha))
# Plot the effect of MSE with varying alphas
plot(my_mse)

输出现在应该始终是 alpha 的最小值,因为预测变量中存在很强的共线性,并且响应仅由可用的预测变量构建,即没有我们希望 LASSO 置零的冗余变量,在这种情况下,我们不想进行正则化,即最小的alpha应该是最好的。你可以在这里看到 MSE 的效果:

对mse的影响

请注意,我正在使用与您相同的 50 个 alpha。在 alpha 索引 35 附近,两个变量都被猛击为零,这意味着模型总是在做同样的事情并且 mse 停滞不前。

研究 MSE、CV 和 LASSO 的更好问题

上面的问题对于 LASSO 来说不是很有趣。LASSO 执行模型选择,因此我们希望看到它实际挑选出感兴趣的参数。更令人印象深刻的是,该模型实际上选择了一个实际上降低 MSE 的 alpha,即通过丢弃一些变量为我们提供了更好的预测。这是一个更好的例子,我在其中添加了一堆冗余预测变量。

set.seed(1)
k = 100 # number of features selected 

n = 100

x1x2corr = 1.1
x1 = seq(n) + rnorm(n, 0, 1) + 50
x2 =  x1*x1x2corr + rnorm(n, 0, 2) + 500
# Rest of the variables are just noise
x3 = matrix(rnorm(k-2,0,(k-2)*n),n,k-2)
y = x1 + x2 +rnorm(n,0,0.5)
df = data.frame(x1 = x1, x2 = x2, y = y)
df <- cbind(df,x3)
filter_out_label <- function(col) {col!="y"}

alphas = logspace(-5, 1.5, 100)
min_mse = Inf
optimal_alpha = NULL
my_mse = c()

然后你只需像上面的代码一样运行 for 循环!请注意,我将最大值alphas从 6 设置为 1.5,只是为了查看下图中的效果。现在最好的alpha值不是最低的,但你可以在图中看到交叉验证 MSE 下降并最终再次上升。该图上的最低点对应于具有最低 CV 误差的 alpha 指数。

LASSO 更好的 CV 问题