【算法】局部加权回归(Lowess)
文章目录
一、简介
1.1 预测问题
对于预测问题,回归中最简单的线性回归,是以线性的方法拟合出数据的趋势。但是对于有周期性,波动性的数据,并不能简单以线性的方式拟合,否则模型会偏差较大,而局部加权回归(lowess)能较好的处理这种问题。可以拟合出一条符合整体趋势的线,进而做预测。
1.2 平滑问题
同时,局部加权回归(lowess)也能较好的解决平滑问题。在做数据平滑的时候,会有遇到有趋势或者季节性的数据,对于这样的数据,我们不能使用简单的均值正负3倍标准差以外做异常值剔除,需要考虑到趋势性等条件。使用局部加权回归,可以拟合一条趋势线,将该线作为基线,偏离基线距离较远的则是真正的异常值点。
实际上,局部加权回归(Lowess)主要还是处理平滑问题的多,因为预测问题,可以有更多模型做的更精确。但就平滑来说,Lowess很直观而且很有说服力。
二、算法讲解
2.1 算法思想
局部加权回归(Lowess)的大致思路是:以一个点 x x x为中心,向前后截取一段长度为 f r a c frac frac的数据,对于该段数据用权值函数 w w w做一个加权的线性回归,记 ( x , y ^ ) (x,\hat{y}) (x,y^)为该回归线的中心值,其中 y ^ \hat{y} y^为拟合后曲线对应值。对于所有的 n n n个数据点则可以做出 n n n条加权回归线,每条回归线的中心值的连线则为这段数据的Lowess曲线。
2.2 参数讲解
在这个思路中,能提取出的可调参数则是:
1.长度
f
r
a
c
frac
frac,应该截取多长的作为局部处理,
f
r
a
c
frac
frac为原数据量的比例;
2.权值函数
w
w
w,使用什么样的权值函数
w
w
w合适;
3.迭代次数
i
t
it
it,在进行一次局部回归后,是否需要迭代,再次做回归;
4.
d
e
l
t
a
delta
delta回归间隔,是否真的每个点都需要算一次加权回归,能否隔
d
e
l
t
a
delta
delta距离算一次,中间没算的用插值替换即可。
在了解了算法算法的大致思想和可调参数以后,你可以马上上手使用statsmodels.api.nonparametric.lowess
了。使用方法如下:
import statsmodels.api as sm
lowess = sm.nonparametric.lowess
result = lowess(y, x, frac=0.2, it=3, delta=0.0)
但是,在statsmodels
中,你会发现:1、权值w
函数你是不可调的;2、在用了
d
e
l
t
a
delta
delta之后,插值函数你是不可调的。
总之就是,黑盒子,很多你都不可调的。而且它还有一个非常严重的问题,具体可看本文3.3效果对比。
接下来就是看相关文档,了解思路,之后,你可以自己写一个lowess,而且速度不会慢。
相关文档,statsmodels
就给出了比较权威的参考:《Cleveland, W.S. (1979) “Robust Locally Weighted Regression and Smoothing Scatterplots”. Journal of the American Statistical Association 74 (368): 829-836.》。
文章是《鲁棒性的加权回归》,即原始加权基础上迭代,增加鲁棒性。网上还有一些其他的lowess讲解,我看了,和这个不太一样,可以选择性阅读。
2.3 权值函数
理解了lowess之后,可以明白,其实权值函数并不是固定的,只要满足一定的规则条件即可(当然并也非强制),条件如下:
1.
W
(
x
)
>
0
f
o
r
∣
x
∣
<
1
W(x)>0 \; for \; |x|<1
W(x)>0for∣x∣<1;
2.
W
(
−
x
)
=
W
(
x
)
W(-x)=W(x)
W(−x)=W(x);
3.
W
(
x
)
i
s
a
n
o
n
i
n
c
r
e
a
s
i
n
g
f
u
n
c
t
i
o
n
f
o
r
x
⩾
0
W(x) \; is \; a \; nonincreasing \; function \; for \; x \geqslant 0
W(x)isanonincreasingfunctionforx⩾0;
4.
W
(
x
)
=
0
f
o
r
∣
x
∣
⩾
1
W(x)=0 \; for \; |x| \geqslant 1
W(x)=0for∣x∣⩾1
选择该类函数大致思路是:希望
W
(
x
)
W(x)
W(x)大于0,且作用域为[-1,1],且为对称函数,该函数对于中间(0处)的值较大,两边(-1\1)处值较小。
选择思路是,中间的权值较高,对于加权回归的影响较大;[-1,1]的原因是,对于任意不规则的数据段,可以压缩映射到[-1,1],方便处理。
权值函数如,B函数(二次函数):
B
(
x
)
=
{
(
1
−
x
2
)
2
,
f
o
r
∣
x
∣
<
1
0
,
f
o
r
∣
x
∣
⩾
1
B(x)=\left\{ \begin{aligned} & (1-x^{2})^{2}, & for \; |x| < 1 \\ & 0, & for \; |x| \geqslant 1 \end{aligned} \right.
B(x)={(1−x2)2,0,for∣x∣<1for∣x∣⩾1
W函数(三次函数):
W
(
x
)
=
{
(
1
−
∣
x
∣
3
)
3
,
f
o
r
∣
x
∣
<
1
0
,
f
o
r
∣
x
∣
⩾
1
W(x)=\left\{ \begin{aligned} & (1-|x|^{3})^{3}, & for \; |x| < 1 \\ & 0, & for \; |x| \geqslant 1 \end{aligned} \right.
W(x)={(1−∣x∣3)3,0,for∣x∣<1for∣x∣⩾1
二次与三次函数的区别在于,三次函数对于周围权值降速更快,在平滑最初时候效果好,且适用于大多数分布,但增加了残差的方差。
对于权值函数选取,第一次迭代适用W函数(三次函数),之后迭代使用B函数(二次函数)。
权值函数的使用:
1、使用权值函数
W
(
x
)
W(x)
W(x);
2、数据段
[
d
1
,
d
2
]
[d_{1},d_{2}]
[d1,d2],映射成
[
−
1
,
1
]
[-1,1]
[−1,1]对应的坐标;
3、带入函数
W
(
x
)
W(x)
W(x),计算出每个点对应的
w
i
w_{i}
wi;
4、使用加权回归得出模型:
Y
^
=
X
(
X
T
w
X
)
−
1
X
T
w
Y
\hat{Y}=X(X^{T}w X)^{-1}X^{T}w Y
Y^=X(XTwX)−1XTwY(推导见我的另一篇博客:线性回归,加权回归,推导过程)
2.4 回归迭代
上面讲了权值函数的选取和使用,提到了迭代,这里讲解怎么迭代。
首先,原值为
y
y
y,预测值为
y
^
\hat{y}
y^,残差为
e
=
y
−
y
^
e=y-\hat{y}
e=y−y^,记
s
s
s为
∣
e
i
∣
|e_{i}|
∣ei∣的中位数。鲁棒性的权值调整附加值
δ
k
=
W
(
e
k
6
s
)
\delta_{k}=W(\frac{e_{k}}{6s})
δk=W(6sek),修正后的权值为
δ
k
w
k
\delta_{k}w_{k}
δkwk。
迭代过程为:
1.使用W函数(三次函数)作为权值函数,求出
w
i
w_{i}
wi。
2.将
w
i
w_{i}
wi带入加权回归计算出
y
^
\hat{y}
y^
3.求出
e
=
y
−
y
^
e=y-\hat{y}
e=y−y^和
s
s
s
4.以B函数作为修正权值函数,求出
δ
k
=
B
(
e
k
6
s
)
\delta_{k}=B(\frac{e_{k}}{6s})
δk=B(6sek),计算出
δ
k
w
k
\delta_{k}w_{k}
δkwk
5.将
δ
k
w
k
\delta_{k}w_{k}
δkwk作为修正权值,重复2、3、4步骤
该迭代没有明确的终止条件,据大量实验得知,原文中提到是2次迭代就基本收敛了,我做实验的时候,3次左右基本收敛,根文中描述差不多。
2.5 间隔回归,中间插值
在使用局部加权回归的时候,如果每个点都使用一次加权回归,则会比较耗时,所以有了,对于部分点使用加权回归,而未使用加权回归的点采用插值法处理,速度会增快很多,同时不会影响太大效果。
可以每间隔
d
e
l
t
a
delta
delta个点使用一次加权回归,中间点采用:线性插值、二次插值、三次插值等方法。
statsmodels
推荐当数据点
N
>
5000
N > 5000
N>5000的时候,选择
d
e
l
t
a
=
0.01
∗
N
delta = 0.01 * N
delta=0.01∗N
而我一般是设置
d
e
l
t
a
=
4
delta=4
delta=4,同时,我的插值函数是线性插值,因为我不希望插值出负数,大家可以根据自己需求选择。
2.6 其他参数
长度
f
r
a
c
frac
frac比例,文章给出的适用比例是:0.49,statsmodels
默认的是:0.666。
同时,文章中还有一个参数是,使用的是多项式加权回归进行拟合,最高次是
d
d
d,而进行讨论给出的结论是
d
=
1
d=1
d=1
时候,即为线性回归,适合基本大多数场景。所以本博客一开始就使用线性加权回归介绍,感兴趣的可以去看看原文。
三、实验效果
3.1 效果
上面讲了整个思路,和详细的参数意义等,看了之后写出代码应该不难。这里作个伪代码总结:
data_x # x轴数据
data_y # y轴数据
map(x in data_x): # 对每个x
x_list = getRange(x, data_x, frac) # 以x为中心,按照frac的比例截取数据
w = calFuncW(x_list) # 以w函数计算权值函数
y_hat = weightRegression(x_list, data_y[x_list], w) # 计算y_hat
for it in iters: # 迭代iters次
err, s, new_w = calNewWeight(y_hat, data_y[x_list], B) # 使用B函数计算,迭代的new_w
y_hat = weightRegression(x_list, data_y[x_list], new_w) # 使用new_w计算y_hat
result = y_hat[x] # 取结果中心点
3.2 效率
上面自写的lowess,在spark上运行,executors15g4cores,计算30W8数据,每条数据90的数据条长度,10分钟即可完成(包括数据读写),个人觉得效率已经挺高的。
3.3 效果对比
与statsmodels
进行对比,右图为使用statsmodels
的函数的结果,左图为自己根据文章写的运行结果,都是同一数据。
可以看出,对于一些特殊数据,直接使用statsmodels
会有非常意外的结果,而且随着迭代进行,依旧不会收敛。
有的甚至会出现平滑结果很意外,如右图绿线最低部。
随着迭代进行,产生了更加剧烈的震荡。
所以,结论是:慎用statsmodels
的lowess
函数。自己写的更可靠,而且自己可调部分更多。
更多推荐
所有评论(0)