Scikit-learn 返回的决定系数 (R^2) 值小于 -1
我正在做一个简单的线性模型。我有
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 个回答
在意大利的统计书籍中,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
如果你发现你的回归模型得分是负数的 r^2 值,记得在训练模型之前,把数据集里的任何唯一标识符(比如“id”或者“rownum”)去掉。这是个简单的检查,但能帮你省去不少麻烦。
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
虽然 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
其实,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
来做到这一点。实际上,你应该通过将StandardScaler
和LinearRegression
结合在一起,创建一个新的估计器,使用sklearn.pipeline.Pipeline
来实现。接下来,你可以尝试岭回归。