基于树的回归模型的不光滑性

最近在做一些多元回归的工作,用到了一些传统工科领域不太常见的基于树的回归模型。随机森林回归以及 LightGBM 都是典型的例子。相比传统的回归方法,基于树的模型更容易得到交叉验证下更良好的结果,但它也有一个很明显的问题,其拟合面是不光滑的。这在很多情况下是无所谓的,只要训练集足够大,拟合面可以非常接近实际的曲面。但在一些应用中,比如需要计算拟合模型的导数时,就会存在问题了。

这里举一个实际的例子来看一下,用支持向量机 SVR 以及随机森林做回归分别有什么特点。

In [2]:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
%config InlineBackend.figure_format = 'svg'

from sklearn.svm import SVR
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split

人工地构造特征,这里为了简化,假定输入是3个特征,且均匀分布在2和3之间,输出是一个还算比较复杂的表达式:

$$ y=x_1^2 + \frac{10\sin(3x_2)}{2+x_3} + x_3(x_1-\cos(x_2)) $$

In [18]:
x1 = np.linspace(2, 3, 1000)
x2 = np.linspace(2, 3, 1000)
x3 = np.linspace(2, 3, 1000)
X = np.vstack((x1, x2, x3)).T.copy()
y = x1 ** 2 + 10*np.sin(3*x2) / (1 + x3) + x3 * (x1 - 2*np.cos(x2))

分别用 support vector regressor 和 random forest regressor 尝试做一下拟合。

In [19]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1)
In [21]:
reg1 = SVR(C=1e5)
reg1.fit(X_train, y_train)

y_pred = reg1.predict(X_test)
plt.plot(y_test, y_pred, 'o', mfc='none')
plt.plot([8, 25], [8, 25], 'r--')
Out[21]:
[<matplotlib.lines.Line2D at 0x2198c0737b8>]
In [22]:
reg2 = RandomForestRegressor()
reg2.fit(X_train, y_train)

y_pred = reg2.predict(X_test)
plt.plot(y_test, y_pred, 'o', mfc='none')
plt.plot([8, 25], [8, 25], 'r--')
Out[22]:
[<matplotlib.lines.Line2D at 0x2198da3e0b8>]

不需要看具体 metric,肉眼就可以看得出来,相对而言,RF 在不需要调参的情况下,很轻易地就得到了看起来更好的结果。

但看局部光滑性呢:

我们随便取一个特征点 X_test[1, :],然后令它的第一个轴在一个小范围内变化:

In [25]:
xx = np.repeat(X_test[1, :], 100).reshape(100, -1)
xx[:, 0] *= np.linspace(0.97, 1.03, 100)
xx[:5, :]
Out[25]:
array([[ 2.42936937,  2.5045045 ,  2.5045045 ],
       [ 2.43088725,  2.5045045 ,  2.5045045 ],
       [ 2.43240513,  2.5045045 ,  2.5045045 ],
       [ 2.43392301,  2.5045045 ,  2.5045045 ],
       [ 2.4354409 ,  2.5045045 ,  2.5045045 ]])

看看两个模型分别给出的y对应如何变化:

In [26]:
y_svr = reg1.predict(xx)
y_rf = reg2.predict(xx)
plt.plot(xx[:, 0], y_svr, label='svr')
plt.plot(xx[:, 0], y_rf, label='rf')
plt.legend()
Out[26]:
<matplotlib.legend.Legend at 0x2198db73518>

这图图应该足够说明问题了。SVR给出的模型,对连续的输入,其输出也是连续且光滑的;而RF得到的模型,给出的输出是阶跃的不光滑的。这时候如果尝试用差分的方法求偏层数,RF在局部有可能给出完全离谱的结果,很多时候是0,也有很多时候非常大,尽管在更大的范围内有可能是对的。而SVR可以给出一个相对靠谱的值。

links

social