我正在开发一个根据汽车属性预测价格的模型。我注意到LinearRegression
模型的预测结果会因输入类型(numpy.ndarray
和scipy.sparse.csr.csr_matrix
)的不同而有所差异。
我的数据包括一些数值和分类属性,没有NaN值。
以下是一个简单的准备数据代码(对于我接下来描述的所有情况都通用):
from sklearn.pipeline import Pipelinefrom sklearn.compose import ColumnTransformerfrom sklearn.linear_model import LinearRegression# 划分测试和训练集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)# 数值属性管道num_pipeline = Pipeline([ ("scaler", StandardScaler()) ])# 分类属性管道cat_pipeline = Pipeline([ ("encoder", OneHotEncoder(handle_unknown="ignore")) ])# 完整管道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 LinearRegressionlin_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() # 这里X_test = full_pipeline.transform(X_test).toarray() # 和这里from sklearn.linear_model import LinearRegressionlin_reg = LinearRegression().fit(X_train, y_train)pred = lin_reg.predict(X_test) r2_score(y_test, pred) # -7.919935999010152e+19 出了问题
我还测试了DecisionTreeRegressor
、RandomForestRegressor
和SVR
,但只有LinearRegression
出现了这个问题。
回答:
在源代码中,你可以看到如果你的输入是稀疏矩阵,它会进行一些居中处理,然后调用稀疏版本的线性最小二乘法。如果数组是密集的,它会调用numpy版本的线性最小二乘法。
然而,这个例子中更大的问题是在执行独热编码之前,你应该检查是否有任何分类值只有一个条目:
data_orig.select_dtypes(['object']).apply(lambda x:min(pd.Series.value_counts(x)))model 1transmission 2708fuelType 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 pltdef 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指出的问题)。
尽管如此,我认为在投入管道之前,应该检查测试和训练数据之间是否有这样的缺失因素。对于这个数据集来说,它很可能不是过定,因此稀疏与密集不应该是差异所在。