在线性回归中比较StandardScaler和Normalizer的结果

时间:2019-01-07 01:12:09

标签: python machine-learning scikit-learn linear-regression

我正在研究不同情况下的线性回归示例,比较使用NormalizerStandardScaler的结果,结果令人困惑。

我正在使用波士顿住房数据集,并以此方式进行准备:

import numpy as np
import pandas as pd
from sklearn.datasets import load_boston
from sklearn.preprocessing import Normalizer
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression

#load the data
df = pd.DataFrame(boston.data)
df.columns = boston.feature_names
df['PRICE'] = boston.target

我目前正在尝试从以下情况中得出我得出的结果:

  • 使用参数normalize=True和使用Normalizer初始化线性回归
  • 通过参数fit_intercept = False初始化线性回归,并进行标准化和不进行标准化。

总的来说,我发现结果令人困惑。

这是我设置所有内容的方式:

# Prep the data
X = df.iloc[:, :-1]
y = df.iloc[:, -1:]
normal_X = Normalizer().fit_transform(X)
scaled_X = StandardScaler().fit_transform(X)

#now prepare some of the models
reg1 = LinearRegression().fit(X, y)
reg2 = LinearRegression(normalize=True).fit(X, y)
reg3 = LinearRegression().fit(normal_X, y)
reg4 = LinearRegression().fit(scaled_X, y)
reg5 = LinearRegression(fit_intercept=False).fit(scaled_X, y)

然后,我创建了3个单独的数据框,以比较每个模型的R_score,系数值和预测。

要创建数据框以比较每个模型的系数值,我做了以下操作:

#Create a dataframe of the coefficients
coef = pd.DataFrame({
    'coeff':                       reg1.coef_[0],
    'coeff_normalize_true':        reg2.coef_[0],
    'coeff_normalizer':            reg3.coef_[0],
    'coeff_scaler':                reg4.coef_[0],
    'coeff_scaler_no_int':         reg5.coef_[0]
})

这是我创建数据框以比较每个模型的R ^ 2值的方法:

scores = pd.DataFrame({
    'score':                        reg1.score(X, y),
    'score_normalize_true':         reg2.score(X, y),
    'score_normalizer':             reg3.score(normal_X, y),
    'score_scaler':                 reg4.score(scaled_X, y),
    'score_scaler_no_int':          reg5.score(scaled_X, y)
    }, index=range(1)
)

最后,这是比较每个预测的数据框:

predictions = pd.DataFrame({
    'pred':                        reg1.predict(X).ravel(),
    'pred_normalize_true':         reg2.predict(X).ravel(),
    'pred_normalizer':             reg3.predict(normal_X).ravel(),
    'pred_scaler':                 reg4.predict(scaled_X).ravel(),
    'pred_scaler_no_int':          reg5.predict(scaled_X).ravel()
}, index=range(len(y)))

这是结果数据框:

系数: Dataframe comparing the coefficients from different models

得分: Dataframe comparing the R^2 values from different models

预测: Dataframe comparing the prediction values from different models

我有三个我无法调和的问题:

  1. 为什么前两个模型之间完全没有差异?似乎设置normalize=False无效。我可以理解预测值和R ^ 2值相同,但是我的特征具有不同的数值范围,因此我不确定为什么归一化根本没有效果。当您考虑使用StandardScaler会显着改变系数时,这会令人困惑。
  2. 我不明白为什么使用Normalizer的模型会导致彼此之间如此根本不同的系数值,尤其是当使用LinearRegression(normalize=True)的模型完全没有变化时。

如果您要查看每个文档,它们看起来很相似,甚至不相同。

摘自sklearn.linear_model.LinearRegression()上的文档:

  

标准化:布尔值,可选,默认为False

     

当fit_intercept设置为False时,将忽略此参数。如果为True,则将在回归之前通过减去均值并除以l2-范数来对回归变量X进行归一化。

与此同时,sklearn.preprocessing.Normalizer states that it normalizes to the l2 norm by default上的文档。

我没有看到这两个选项之间的区别,也没有看到为什么一个选项的系数值与另一个选项会有如此根本的差异。

  1. 使用StandardScaler的模型的结果与我一致,但我不明白为什么使用StandardScaler并设置set_intercept=False的模型的性能如此差。

摘自Linear Regression module上的文档:

  

fit_intercept:布尔值,可选,默认为True

     

是否计算此模型的截距。如果设置为False,否
     截距将用于计算(例如,预计数据已经是
     居中)。

StandardScaler使您的数据居中,所以我不明白为什么将fit_intercept=False与数据一起使用会产生不一致的结果。

3 个答案:

答案 0 :(得分:8)

  1. 前两个模型之间的系数没有差异的原因是Sklearn在根据归一化的输入数据计算出系数后,对幕后系数进行了归一化。 Reference

已经完成了这种去规格化处理,以便任何测试数据,我们都可以直接应用协效应。并通过标准化测试数据获得预测。

因此,设置normalize=True确实会影响系数,但无论如何它们不会影响最佳拟合线。

  1. Normalizer对每个样本进行归一化(意味着逐行)。您会看到参考代码here

From documentation:

  

将样本分别归一化为单位范数。

normalize=True对每个列/特征进行归一化。 Reference

示例以了解规范化对数据的不同维度的影响。让我们将二维x1和x2设为目标变量。目标变量值在图中用颜色编码。

import matplotlib.pyplot as plt
from sklearn.preprocessing import Normalizer,StandardScaler
from sklearn.preprocessing.data import normalize

n=50
x1 = np.random.normal(0, 2, size=n)
x2 = np.random.normal(0, 2, size=n)
noise = np.random.normal(0, 1, size=n)
y = 5 + 0.5*x1 + 2.5*x2 + noise

fig,ax=plt.subplots(1,4,figsize=(20,6))

ax[0].scatter(x1,x2,c=y)
ax[0].set_title('raw_data',size=15)

X = np.column_stack((x1,x2))

column_normalized=normalize(X, axis=0)
ax[1].scatter(column_normalized[:,0],column_normalized[:,1],c=y)
ax[1].set_title('column_normalized data',size=15)

row_normalized=Normalizer().fit_transform(X)
ax[2].scatter(row_normalized[:,0],row_normalized[:,1],c=y)
ax[2].set_title('row_normalized data',size=15)

standardized_data=StandardScaler().fit_transform(X)
ax[3].scatter(standardized_data[:,0],standardized_data[:,1],c=y)
ax[3].set_title('standardized data',size=15)

plt.subplots_adjust(left=0.3, bottom=None, right=0.9, top=None, wspace=0.3, hspace=None)
plt.show()

enter image description here

您会看到图1,2和4中数据的最佳拟合线是相同的;表示R2_得分不会由于列/功能规范化或标准化数据而改变。就是这样,最终会带来不同的协同效应。值。

注意:fig3的最佳拟合线会有所不同。

  1. 设置fit_intercept = False时,将从预测中减去偏差项。 意味着将截距设置为零,否则将是目标变量的均值。

对于没有按比例缩放目标变量(均值= 0)的问题,预期截距为零的prediction会表现不好。您可以在每行中看到22.532的差异,这表示输出的影响。

答案 1 :(得分:4)

回答第一季度

我假设您对前两个模型的意思是reg1reg2。让我们知道是否是这种情况。

如果您对数据进行归一化,则线性回归具有相同的预测能力。因此,使用normalize=True对预测没有影响。理解这一点的一种方法是,看到归一化(逐列)是对每一列((x-a)/b)的线性运算,而线性回归中数据的线性变换不会影响系数估计,只改变它们的系数即可。价值观。请注意,对于Lasso / Ridge / ElasticNet,此语句不正确。

那么,为什么系数没有不同?好吧,normalize=True还考虑到用户通常想要的是原始特征的系数,而不是归一化特征。这样,它调整系数。一种检查是否有意义的方法是使用一个简单的示例:

# two features, normal distributed with sigma=10
x1 = np.random.normal(0, 10, size=100)
x2 = np.random.normal(0, 10, size=100)

# y is related to each of them plus some noise
y = 3 + 2*x1 + 1*x2 + np.random.normal(0, 1, size=100)

X = np.array([x1, x2]).T  # X has two columns

reg1 = LinearRegression().fit(X, y)
reg2 = LinearRegression(normalize=True).fit(X, y)

# check that coefficients are the same and equal to [2,1]
np.testing.assert_allclose(reg1.coef_, reg2.coef_) 
np.testing.assert_allclose(reg1.coef_, np.array([2, 1]), rtol=0.01)

这证实了这两种方法都能正确捕获[x1,x2]和y之间的真实信号,即分别捕获2和1。

回答第二季度

Normalizer不是您所期望的。它将每行标准化。因此,结果将发生巨大变化,并可能破坏要素和您要避免的目标之间的关系,除非是特定情况(例如TF-IDF)。

要了解如何进行操作,请假设上面的示例,但考虑与x3不相关的其他功能y。使用Normalizer导致x1x3的值修改,从而降低了与y的关系。

模型(1,2)和(4,5)之间的系数差异

系数之间的差异在于,当您在拟合之前进行标准化时,系数将相对于标准化特征,与我在答案第一部分中提到的系数相同。可以使用reg4.coef_ / scaler.scale_将它们映射到原始参数:

x1 = np.random.normal(0, 10, size=100)
x2 = np.random.normal(0, 10, size=100)
y = 3 + 2*x1 + 1*x2 + np.random.normal(0, 1, size=100)
X = np.array([x1, x2]).T

reg1 = LinearRegression().fit(X, y)
reg2 = LinearRegression(normalize=True).fit(X, y)
scaler = StandardScaler()
reg4 = LinearRegression().fit(scaler.fit_transform(X), y)

np.testing.assert_allclose(reg1.coef_, reg2.coef_) 
np.testing.assert_allclose(reg1.coef_, np.array([2, 1]), rtol=0.01)

# here
coefficients = reg4.coef_ / scaler.scale_
np.testing.assert_allclose(coefficients, np.array([2, 1]), rtol=0.01)

这是因为在数学上设置z = (x - mu)/sigma时,模型reg4正在求解y = a1*z1 + a2*z2 + a0。我们可以通过简单的代数y = a1*[(x1 - mu1)/sigma1] + a2*[(x2 - mu2)/sigma2] + a0恢复y和x之间的关系,可以将其简化为y = (a1/sigma1)*x1 + (a2/sigma2)*x2 + (a0 - a1*mu1/sigma1 - a2*mu2/sigma2)

reg4.coef_ / scaler.scale_在以上表示法中表示[a1/sigma1, a2/sigma2],这正是normalize=True为确保系数相同所做的工作。

模型5的得分下降。

标准化特征为零均值,但目标变量不一定是零。因此,不拟合截距将导致模型忽略目标的均值。在我一直使用的示例中,y = 3 + ...中的“ 3”未拟合,这自然降低了模型的预测能力。 :)

答案 2 :(得分:1)

关于带有fit_intercept = 0和标准化数据的不一致结果的最后一个问题(3)尚未得到完全回答。

OP可能期望StandardScaler标准化X和y,这将使截距必定为0(Gurobi license向下的1/3)。

但是StandardScaler忽略y。参见proof

api提供了一种解决方案。这种方法对于因变量的非线性变换(例如y的对数变换)也很有用(但请考虑TransformedTargetRegressor)。

以下是解决OP问题#3的示例:

from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.pipeline import make_pipeline
from sklearn.datasets import make_regression
from sklearn.compose import TransformedTargetRegressor
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler
import numpy as np

# define a custom transformer
class stdY(BaseEstimator,TransformerMixin):
    def __init__(self):
        pass
    def fit(self,Y):
        self.std_err_=np.std(Y)
        self.mean_=np.mean(Y)
        return self
    def transform(self,Y):
        return (Y-self.mean_)/self.std_err_
    def inverse_transform(self,Y):
        return Y*self.std_err_+self.mean_

# standardize X and no intercept pipeline
no_int_pipe=make_pipeline(StandardScaler(),LinearRegression(fit_intercept=0)) # only standardizing X, so not expecting a great fit by itself.

# standardize y pipeline
std_lin_reg=TransformedTargetRegressor(regressor=no_int_pipe, transformer=stdY()) # transforms y, estimates the model, then reverses the transformation for evaluating loss.

#after returning to re-read my answer, there's an even easier solution, use StandardScaler as the transfromer:
std_lin_reg_easy=TransformedTargetRegressor(regressor=no_int_pipe, transformer=StandardScaler())

# generate some simple data
X, y, w = make_regression(n_samples=100,
                          n_features=3, # x variables generated and returned 
                          n_informative=3, # x variables included in the actual model of y
                          effective_rank=3, # make less than n_informative for multicollinearity
                          coef=True,
                          noise=0.1,
                          random_state=0,
                          bias=10)

std_lin_reg.fit(X,y)
print('custom transformer on y and no intercept r2_score: ',std_lin_reg.score(X,y))

std_lin_reg_easy.fit(X,y)
print('standard scaler on y and no intercept r2_score: ',std_lin_reg_easy.score(X,y))

no_int_pipe.fit(X,y)
print('\nonly standard scalar and no intercept r2_score: ',no_int_pipe.score(X,y))

返回

custom transformer on y and no intercept r2_score:  0.9999343800041816

standard scaler on y and no intercept r2_score:  0.9999343800041816

only standard scalar and no intercept r2_score:  0.3319175799267782