Scikit-learn 返回的决定系数 (R^2) 值小于 -1

36 投票
5 回答
63293 浏览
提问于 2025-04-18 02:32

我正在做一个简单的线性模型。我有

fire = load_data()
regr = linear_model.LinearRegression()
scores = cross_validation.cross_val_score(regr, fire.data, fire.target, cv=10, scoring='r2')
print scores

这产生了

[  0.00000000e+00   0.00000000e+00  -8.27299054e+02  -5.80431382e+00
  -1.04444147e-01  -1.19367785e+00  -1.24843536e+00  -3.39950443e-01
   1.95018287e-02  -9.73940970e-02]

这是怎么回事呢?当我用内置的糖尿病数据做同样的事情时,一切都很正常,但用我的数据却得到了这些看起来很荒谬的结果。我是不是哪里做错了?

5 个回答

0

在意大利的统计书籍中,R^2的值只能是零或者正数。而且,如果你把X和Y的位置调换,结果也不会改变。

你可以在Excel中用一个简单的公式来验证这一点,或者在Python中使用Numpy。

下面是一些Numpy的代码示例。

frame = pd.DataFrame([...]) # your dataframe

correlation_matrix = np.corrcoef(frame[target], frame[predict])
correlation_xy = correlation_matrix[0,1]
r_squared = correlation_xy**2
print(r_squared)

correlation_matrix = np.corrcoef(frame[predict], frame[target])
correlation_xy = correlation_matrix[0,1]
r_squared = correlation_xy**2
print(r_squared)

还有一个现成的例子。

y_true = [1,2,3]
y_pred = [4,5,7]
print(r2_score(y_true,y_pred)) # -> -16.0
print(np.corrcoef(y_true, y_pred)[0,1]**2) # -> 0.9642857142857141
1

如果你发现你的回归模型得分是负数的 r^2 值,记得在训练模型之前,把数据集里的任何唯一标识符(比如“id”或者“rownum”)去掉。这是个简单的检查,但能帮你省去不少麻烦。

14

R²的计算公式是:R² = 1 - RSS / TSS。这里的RSS是残差平方和,也就是∑(y - f(x))²,表示实际值y和模型预测值f(x)之间的差距的平方和。而TSS是总平方和,计算公式是∑(y - mean(y))²,表示实际值y和y的平均值之间的差距的平方和。

为了让R²的值大于等于-1,我们需要满足一个条件:RSS/TSS必须小于等于2。不过,其实很容易就能构造出一个模型和数据集,使得这个条件不成立。

>>> x = np.arange(50, dtype=float)
>>> y = x
>>> def f(x): return -100
...
>>> rss = np.sum((y - f(x)) ** 2)
>>> tss = np.sum((y - y.mean()) ** 2)
>>> 1 - rss / tss
-74.430972388955581
20

虽然 R^2 可以是负数,但这并不意味着它应该是负的。

可能性 1:代码中的错误。

一个常见的错误是你传入的参数可能不正确,建议你仔细检查一下:

r2_score(y_true, y_pred) # Correct!
r2_score(y_pred, y_true) # Incorrect!!!!

可能性 2:数据集太小。

如果你得到了负的 R^2 值,可以检查一下是否出现了过拟合的情况。要记住,cross_validation.cross_val_score() 不会随机打乱你的输入数据,所以如果你的样本不小心按顺序排列(比如按日期),那么在每个分组中建立的模型可能对其他分组没有预测能力。

你可以尝试减少特征的数量,增加样本的数量,或者减少分组的数量(如果你在使用 cross_validation)。虽然这里没有官方的规则,但你的 m x n 数据集(其中 m 是样本数量,n 是特征数量)应该满足以下条件:

m > n^2

而当你使用 f 作为分组数量进行交叉验证时,你应该目标是:

m/f > n^2
41

其实,r^2的值是可以是负数的,尽管它的名字里有个^2。这个在文档里也有说明。你可以把r^2看作是你模型的拟合效果(在这里是线性回归,比如说一阶模型)和一个零阶模型(就是只用一个常数来拟合)之间的比较,都是通过最小化平方损失来实现的。这个常数的值是平均值。因为你在做交叉验证时会用到一些未使用的数据,所以测试集的平均值可能和训练集的平均值差别很大。这种情况下,你的预测可能会产生很大的平方误差,甚至比直接预测测试数据的平均值还要差,这就导致了r^2的得分是负数。

在最糟糕的情况下,如果你的数据根本无法解释目标值,这些得分可能会变得非常负。你可以试试

import numpy as np
rng = np.random.RandomState(42)
X = rng.randn(100, 80)
y = rng.randn(100)  # y has nothing to do with X whatsoever
from sklearn.linear_model import LinearRegression
from sklearn.cross_validation import cross_val_score
scores = cross_val_score(LinearRegression(), X, y, cv=5, scoring='r2')

这应该会得到负的r^2值。

In [23]: scores
Out[23]: 
array([-240.17927358,   -5.51819556,  -14.06815196,  -67.87003867,
    -64.14367035])

现在重要的问题是,这是否是因为线性模型在你的数据中找不到任何有用的信息,还是因为数据预处理的某些问题。你有没有尝试过把你的数据列缩放到均值为0,方差为1?你可以使用sklearn.preprocessing.StandardScaler来做到这一点。实际上,你应该通过将StandardScalerLinearRegression结合在一起,创建一个新的估计器,使用sklearn.pipeline.Pipeline来实现。接下来,你可以尝试岭回归。

撰写回答