多个目标岭回归如何在scikit中学习?

时间:2018-05-02 10:23:36

标签: scikit-learn linear-regression grid-search regularized multitargeting

我正在努力理解以下内容:

Scikit-learn为Ridge Regression提供了一个多输出版本,只需移交一个2D数组[n_samples,n_targets],但它是如何实现的?

http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Ridge.html

假设每个目标的每个回归都是独立的,这是正确的吗?在这些情况下,我如何调整它以使用每个回归的单个alpha正则化参数?如果我使用GridSeachCV,我将不得不移交一个可能的正则化参数矩阵,或者它将如何工作?

提前致谢 - 我一直在寻找几个小时,但在这个主题上找不到任何内容。

1 个答案:

答案 0 :(得分:1)

因为我一直在为自己的作品研究这一点,所以我会试一试。我会将问题分解为几个部分,以便您只能查看您感兴趣的部分:

第一季度: 多个输出Ridge回归中每个目标(即输出)的回归是否独立?

A1: 我认为具有M个输出 的典型多输出线性回归与M个独立的单输出线性回归相同。我认为是这种情况,因为多输出情况的普通最小二乘表达式与M个独立的单输出情况的(总和)表达式相同。为此,让我们考虑一个没有正则化的愚蠢的双变量输出情况。

让我们考虑两个列向量输入 x 1 x 2 ,以及相应的权重向量 w 1 w 2

这些为我们提供了两个单变量输出,y 1 = x 1 w 1 T + e 1 和y 2 = x 2 w 2 T + e 2 ,其中es是独立错误。

错误平方的总和写为:

e 1 2 + e 2 2 =(y 1 - x 1 w 1 T 2 + (y 2 - x 2 w 2 T 2

我们可以看到这只是两个独立回归的平方误差之和。现在,为了进行优化,我们针对权重进行区分并将其设置为零。由于y 1 不依赖 w 2 ,反之亦然,y 2 w 1 ,可以针对每个目标独立进行优化。

我在这里考虑了一个示例进行说明,但是更多示例并没有太大变化。您可以自己写出来。以| w 1 |形式添加惩罚条款或| w 2 |也不会更改此设置,因为 w 2 仍然不依赖y 1 ,反之亦然,y 2 < / sub>和 w 1

好的,这就是能让您获得C-的证明(当时有一名慷慨的教授)。 就这反映了sklearn而言,手动实现独立回归和内置的多个输出支持将得出相同的结果。因此,让我们用一些代码进行检查(我将py2.7与Jupyter一起使用):

我们需要的东西

 import numpy as np
 import matplotlib.pyplot as plt
 from sklearn import linear_model, model_selection

设置数据

## set up some test data
# N samples, K features, M outputs (aka targets)
T = 1000
K = 100
M = 500

#get the samples from independent, multivariate normal
means_X = np.zeros(K)
cov_X = np.identity(K) 
X = np.random.multivariate_normal(means_X,cov_X,T)

#Make up some random weights. 
#Here I use an exponential form since that means some would be quite small, and thus regularization is likely to help
#However for the purposes of the example it doesn't really matter

#exponential weights
W = 2.0 ** np.random.randint(-10,0,M * K) 

#shape into a weight matrix correctly
W = W.reshape((K,M))

# get the ouput - apply linear transformation
Y = np.matmul(X, W)

# add a bit of noise to the output
noise_level = 0.1
noise_means = np.zeros(M)
noise_cov = np.identity(M) 

Y_nse = Y + noise_level * np.random.multivariate_normal(noise_means,noise_cov,T)

# Start with one alpha value for all targets 
alpha = 1

使用内置的多输出支持的sklearn

#%%timeit -n 1 -r 1
# you can uncomment the above to get timming but note that this runs on a seperate session so 
# the results won't be available here 
## use built in MIMO support 

built_in_MIMO = linear_model.Ridge(alpha = alpha)
built_in_MIMO.fit(X, Y_nse)

使用针对输出独立运行优化

# %%timeit -n 1 -r 1 -o
## manual mimo
manual_MIMO_coefs = np.zeros((K,M))

for output_index in range(M):

    manual_MIMO = linear_model.Ridge(alpha = alpha)
    manual_MIMO.fit(X, Y_nse[:,output_index]) 
    manual_MIMO_coefs[:,output_index] = manual_MIMO.coef_

比较估算值(不包括绘图)

manual_MIMO_coefs_T = manual_MIMO_coefs.T

## check the weights. Plot a couple
check_these_weights = [0, 10]
plt.plot(built_in_MIMO.coef_[check_these_weights[0],:],'r')
plt.plot(manual_MIMO_coefs_T[check_these_weights[0],:], 'k--')

# plt.plot(built_in_MIMO.coef_[check_these_weights[1],:],'b')
# plt.plot(manual_MIMO_coefs_T[check_these_weights[1],:], 'y--')

plt.gca().set(xlabel = 'weight index', ylabel = 'weight value' )
plt.show()

print('Average diff between manual and built in weights is %f ' % ((built_in_MIMO.coef_.flatten()-manual_MIMO_coefs_T.flatten()) ** 2).mean())


## FYI, our estimate are pretty close to the orignal too, 
plt.clf()
plt.plot(built_in_MIMO.coef_[check_these_weights[1],:],'b')
plt.plot(W[:,check_these_weights[1]], 'y--')
plt.gca().set(xlabel = 'weight index', ylabel = 'weight value' )
plt.legend(['Estimated', 'True'])

plt.show()

print('Average diff between manual and built in weights is %f ' % ((built_in_MIMO.coef_.T.flatten()-W.flatten()) ** 2).mean())

输出为(此处不包括图)

Average diff between manual and built in weights is 0.000000 

Average diff between manual and built in weights is 0.000011 

因此,我们看到内置的sklearn估计与我们的手册相同。但是,内建函数要快得多,因为它使用矩阵代数一次来解决整个问题,这与我在这里使用的循环相反(有关Ridge正则化的矩阵公式,请参见Tikhonov正则化的Wiki) 。您可以通过取消注释上面的%% timeit魔术来自己检查此问题)

第二季度: 我们如何为每个回归使用单独的alpha正则化参数?

A2: sklearn Ridge对每个输出(目标)接受不同的正则化。例如,继续上面的代码,对每个输出使用不同的字母:

# now try different alphas for each target.
# Simply randomly assign them between min and max range 
min_alpha = 0
max_alpha = 50
alphas = 2.0 ** np.random.randint(min_alpha,max_alpha,M)
built_in_MIMO = linear_model.Ridge(alpha = alphas)
built_in_MIMO.fit(X, Y_nse) 

如果将此与手动执行的M个独立回归进行比较,每个回归都有自己的alpha:

manual_MIMO_coefs = np.zeros((K,M))

for output_index in range(M):

    manual_MIMO = linear_model.Ridge(alpha = alphas[output_index])
    manual_MIMO.fit(X, Y_nse[:,output_index]) 
    manual_MIMO_coefs[:,output_index] = manual_MIMO.coef_

您得到相同的结果:

manual_MIMO_coefs_T = manual_MIMO_coefs.T

## check the weights. 
print('Average diff between manual and built in weights is %f ' % ((built_in_MIMO.coef_.flatten()-manual_MIMO_coefs_T.flatten()) ** 2).mean())

Average diff between manual and built in weights is 0.000000 

所以这些都是一样的。

但是,在这种情况下,性能很大程度上取决于求解器(由@Vivek Kumar所理解)。

默认情况下,Ridge.fit()带有Cholesky因式分解(至少用于非稀疏数据),在github(https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/linear_model/ridge.py中的_solve_cholesky)中查找代码,我看到当alphas为sklearn实际上为每个目标分别选择了 分别适合它们。我不知道这是Cholesky固有的还是只是实现的东西(我感觉是后者)。

但是,对于不同的求解器,例如基于SVD的(_solve_svd()),代码似乎已经将不同的alpha合并到问题的矩阵公式中,因此整个过程只运行了一次。这意味着,当分别为每个输出选择Alpha时,以及有很多输出时,SVD解算器可以快得多。

Q3:如何使用GridSeachCV?我要交出可能的正则化参数矩阵吗?

A3: 我没有使用内置的网格搜索,因为它不太适合我的问题。然而,根据以上解释,可以很容易地实现这一点。只需使用sklearn.model_selection.KFold()或类似方法获得一些CV折叠,然后使用不同的alpha训练每个折叠:

from sklearn import metrics, model_selection
# just two folds for now
n_splits = 2
#logarithmic grid
alphas = 2.0 ** np.arange(0,10) 
kf = model_selection.KFold(n_splits=n_splits)

# generates some folds
kf.get_n_splits(X)

# we will keep track of the performance of each alpha here
scores = np.zeros((n_splits,alphas.shape[0],M))

#loop over alphas and folds
for j,(train_index, test_index) in enumerate(kf.split(X)):

    for i,alpha in enumerate(alphas):

        cv_MIMO = linear_model.Ridge(alpha = alpha)
        cv_MIMO.fit(X[train_index,:], Y_nse[train_index,:]) 
        cv_preds = cv_MIMO.predict(X[test_index,:])
        scores[j,i,:] = metrics.r2_score(Y_nse[test_index,:], cv_preds, multioutput='raw_values')

## mean CV score  
mean_CV_score = scores.mean(axis = 0)
# best alpha for each target
best_alpha_for_target = alphas[np.argmax(mean_CV_score,axis = 0)]

我相当匆忙地写了这篇,所以请仔细检查。请注意,由于内置的​​Ridge.score()对所有目标进行平均,因此我们需要使用度量模块,此处我们不希望如此。通过使用multioutput ='raw_values'选项,我们保留了每个目标的原始值。

希望有帮助!