我正在研究一个模型,该模型通过属性来预测汽车价格。我注意到LinearRegression
模型的预测取决于输入类型(numpy.ndarray
,scipy.sparse.csr.csr_matrix
)的不同。
我的数据由一些数字和分类属性组成,没有NaN。
这是一个简单的数据准备代码(这在我稍后描述的每种情况下都很常见):
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LinearRegression
# Splitting to test and train
X = data_orig.drop("price", axis=1)
y = data_orig[["price"]]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Numerical attributes pipeline
num_pipeline = Pipeline([ ("scaler", StandardScaler()) ])
# Categorical attributes pipeline
cat_pipeline = Pipeline([ ("encoder", OneHotEncoder(handle_unknown="ignore")) ])
# Complete pipeline
full_pipeline = ColumnTransformer([
("cat", cat_pipeline, ["model", "transmission", "fuelType"]),
("num", num_pipeline, ["year", "mileage", "tax", "mpg", "engineSize"]),
])
让我们建立一个LinearRegression
模型(X_train
和X_test
将是scipy.sparse.csr.csr_matrix
的实例):
...
X_train = full_pipeline.fit_transform(X_train)
X_test = full_pipeline.transform(X_test)
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression().fit(X_train, y_train)
pred = lin_reg.predict(X_test)
r2_score(y_test, pred) # 0.896044623680753 OK
如果我将X_test
和X_train
转换为numpy.ndarray
,则模型的预测是完全错误的:
...
X_train = full_pipeline.fit_transform(X_train).toarray() # Here
X_test = full_pipeline.transform(X_test).toarray() # And here
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression().fit(X_train, y_train)
pred = lin_reg.predict(X_test)
r2_score(y_test, pred) # -7.919935999010152e+19 Something is wrong
我也测试了DecisionTreeRegressor
,RandomForestRegressor
和SVR
,但问题仅发生在LinearRegression
上。
答案 0 :(得分:5)
在source code中,您可以查看您的输入是否为稀疏矩阵,它会进行一些居中,然后调用the sparse version of linear least square。如果数组密集,它将调用numpy version of linear least square。
但是,此示例的更大问题是,在执行onehot编码之前,您应该检查任何分类值是否只有1个条目:
data_orig.select_dtypes(['object']).apply(lambda x:min(pd.Series.value_counts(x)))
model 1
transmission 2708
fuelType 28
如果我们检查模型:
data_orig['model'].value_counts().tail()
SQ7 8
S8 4
S5 3
RS7 1
A2 1
因此,如果RS7和A2在测试中而不在训练中,则该系数将完全是垃圾,因为其所有值均为零。如果我们尝试使用其他种子来拆分数据,则可以看到两个拟合相似:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=22)
使用稀疏/密集数据进行拟合的函数:
import matplotlib.pyplot as plt
def fit(X_tr,X_ts,y_tr,y_ts,is_sparse=True):
data_train = full_pipeline.fit_transform(X_tr)
data_test = full_pipeline.transform(X_ts)
if not is_sparse:
data_train = data_train.toarray()
data_test = data_test.toarray()
lin_reg = LinearRegression().fit(data_train, y_tr)
pred = lin_reg.predict(data_test)
plt.scatter(y_ts,pred)
return {'r2_train':r2_score(y_tr, lin_reg.predict(data_train)),
'r2_test':r2_score(y_ts, pred),
'pred':pred}
我们可以看到r2用于训练和测试:
sparse_pred = fit(X_train,X_test,y_train,y_test,is_sparse=True)
[sparse_pred['r2_train'],sparse_pred['r2_test']]
[0.8896333645670668, 0.898030271986993]
sparse_pred = fit(X_train,X_test,y_train,y_test,is_sparse=False)
[sparse_pred['r2_train'],sparse_pred['r2_test']]
[0.8896302753422759, 0.8980115229388697]
您可以在示例中使用种子(42)尝试上述操作,您将看到用于训练的r ^ 2相似。这是个预兆。
因此,如果您使用稀疏矩阵,则最小二乘方很可能返回全零列的废话系数(最有可能@piterbarg指向的是)。
仍然,我认为有意义的是在降低管道之前检查数据,以检查测试和训练之间是否存在此类缺失因素。对于此数据集,它很可能不会被确定得太高,因此稀疏与稠密不应有区别。
答案 1 :(得分:4)
线性回归涉及某些矩阵的数值求逆,或更确切地说,涉及求解某些线性方程式,请参见here。在某些情况下,这些矩阵不是奇异的就是奇异的(“条件差”的),当模型的“特征”不是独立或几乎不独立时,通常就是这种情况。
在这种情况下,简单的反演会导致灾难性的错误累积,从而导致最终的反演/解决方案大打折扣。对于这里的大量尺寸,这种情况更为普遍(据我所知36个)
当矩阵几乎是奇数时,它的求逆在很大程度上取决于所涉及数字(其基数),舍入误差,计算的精确顺序等方面的细微差异。在您的情况下,我相信,这就是为什么这两个答案截然不同。似乎涉及稀疏矩阵表示的计算路径可以比numpy表示更好地处理几乎奇异的矩阵。为什么会这样,所以我不能确定我上面已经写的内容。
通过稍微规范化问题来测试我的断言很容易。例如,可以使用Tikhonov regularization(也称为Ridge回归)代替普通的线性回归。 Tikhonov回归在矩阵中添加了一个(小)项,这使得近奇矩阵在反演下表现得更好。
这是代码中的一行更改:
from sklearn.linear_model import Ridge
lin_reg = Ridge(alpha=1e-4).fit(X_train, y_train)
现在,两种情况都应产生(几乎)相同的答案。请注意alpha
参数,该参数指定了正则化的强度以及我在其中使用的较小值。