线性回归预测因输入类型而异
Posted
技术标签:
【中文标题】线性回归预测因输入类型而异【英文标题】:LinearRegression predictions differ depending on the type of input 【发布时间】:2020-12-11 16:03:54 【问题描述】:我正在研究一个模型,该模型可以通过属性来预测汽车价格。我注意到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
。
【问题讨论】:
您的数据是私密的吗?不然可以分享一下吗? @swag2198 这是来自 Kaggle 的公共数据集,您可以将其下载为 CSV 文件:raw.githubusercontent.com/dsonyy/ml-stuff/master/notebooks/… 或此处kaggle.com/adityadesai13/used-car-dataset-ford-and-mercedes 【参考方案1】:在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 所指向的)。
不过,我认为有意义的是在测试和训练之间检查数据是否存在这些缺失因素,然后再进入管道。对于这个数据集,它很可能没有被过度确定,所以稀疏与密集不应该是区别。
【讨论】:
【参考方案2】:线性回归涉及某些矩阵的数值求逆,或者更准确地说,是求解某些线性方程,请参阅here。在某些情况下,这些矩阵要么是奇异的,要么几乎是奇异的(“条件差”),这通常是当模型的“特征”不独立或几乎不独立时的情况。
在这种情况下,直接的反演可能会导致灾难性的错误累积,由此产生的反演/解决方案基本上会崩溃。这对于您在此处拥有的大量维度更为普遍(据我所知为 36 个)
当一个矩阵几乎是奇异的时,它的求逆很大程度上取决于所涉及的数字表示(它们的基数)、舍入误差、计算的精确顺序等方面的细微差异。在你的情况下,我相信这就是为什么这两个答案截然不同。似乎涉及稀疏矩阵表示的计算路径可以比 numpy 表示更好地处理近奇异矩阵。为什么会这样,除了我上面写的以外,我不能肯定地说。
通过稍微规范问题来测试我的断言很容易。例如,可以使用Tikhonov regularization,也就是岭回归,而不是普通的线性回归。 Tikhonov 回归在矩阵中添加了一个(小)项,使近似奇异矩阵在求逆时表现得更好。
这是您代码中的单行更改:
from sklearn.linear_model import Ridge
lin_reg = Ridge(alpha=1e-4).fit(X_train, y_train)
现在两种情况都应该产生(几乎)相同的答案。注意alpha
参数指定正则化的强度,以及我在那里使用的小值。
【讨论】:
以上是关于线性回归预测因输入类型而异的主要内容,如果未能解决你的问题,请参考以下文章