Kernel Smoothing — Local polynomial regression
本文以一维核平滑为例,假设有观测数据集
{xi,yi}i=0N ,xi,yi∈R,目标是为了找到一个回归函数
y=f(x) 来拟合观测数据。
1. 核平滑方法
核平滑
(Kernel Smoothing)的基本思想:
(1) 只使用与目标点
xi 邻近位置
x∈Ni={x ∣∣ ∣∣x−xi∣∣<δ} 的一些观测点来进行拟合
(2) 这种局部化是通过核函数
(kernel function) 来刻画
离目标点
xi 越近的观测点对
xi 的估计影响更大、具有更大的权值
离目标点
xi 越远的观测点对
xi 的估计影响更小、具有更小的权值
例如,图
1 中的高斯核函数(其中
h 为标准差,控制邻域宽度)
K(x,xi)=K(∥x−xi∥)=e−2h2(x−xi)2, ∀ x∈Ni
图1 对于目标点为
(xi,yi) 处的黑色实心点,
x 轴上的红色实心点表示与
xi 相邻的位置
x∈Ni,红色空心点为
x 轴上红色实心点对应的高斯权值
另外,图中还画出了一部分代表观测数据的黑色空心点处的高斯权值曲线
核平滑,也就是局部加权平均,核回归函数为:
y=f(x)=i=0∑NK(x,xi)i=0∑NyiK(x,xi)
图2 常见的三种核函数:Epanechnikov和Tri-cube是紧支的,而Gaussian是非紧支的。
图片取自于《The Elements of Statistical Learning - Data Mining, Inference, and Prediction》Fig 6.2
Epanechnikov 二次核:
K(x,xi)=D(h∥x−xi∥),其中
h 确定
xi 的邻域宽度。
D(t)=⎩⎨⎧43(1−t2)0,∣t∣<1,其他
Tri-
cube 核:
D(t)=⎩⎨⎧(1−∣t∣3)30,∣t∣<1,其他
显然,
h 值越小,相同情况下的
t=h∥x−xi∥ 越大,对于紧支撑的核函数
D(t) 而言,用于实现局部加权平均的局部观测数据就越少。
反之,
h 值越大,相同情况下的
t=h∥x−xi∥ 越小,参与局部加权平均的局部观测数据就越多。
代码实现
import numpy as np
import matplotlib.pyplot as plt
def kernel_regression(x, h, h0):
weight_e = lambda t: (1-t**2)*3/4
weight_t = lambda t: (1-t**3)**3
y = np.sin(4*x)
y_noise = y + np.random.randn(len(x))/3
num = len(x)
y_rec_e = np.zeros(num)
y_rec_t = np.zeros(num)
y_rec_g = np.zeros(num)
for i in range(num):
dist = np.abs(x-x[i])/h
w_e = weight_e(dist)*np.where(dist<=1,1,0)
y_rec_e[i] = np.sum(y_noise*w_e)/np.sum(w_e)
w_t = weight_t(dist)*np.where(dist<=1,1,0)
y_rec_t[i] = np.sum(y_noise*w_t)/np.sum(w_t)
gaussian_kernel = lambda d,h: np.exp(-dist**2/(2*(h**2)))/(np.sqrt(2*np.pi)*h)
for i in range(num):
dist = np.abs(x-x[i])
w = gaussian_kernel(dist,h0)
y_rec_g[i] = np.sum(y_noise*w)/np.sum(w)
return y_rec_g, y_rec_e, y_rec_t, y_noise, y
if __name__ == '__main__':
plt.figure
snum = 100
h = 0.3
h0 = 0.1
x = np.linspace(0,1,snum)
y_rec_g, y_rec_e, y_rec_t, y_noise, y = kernel_regression(x,h,h0)
plt.plot(x,y)
plt.plot(x,y_noise,'yo',markerfacecolor='none')
plt.plot(x,y_rec_e,'k')
plt.plot(x,y_rec_t,'r')
plt.plot(x,y_rec_g,'m')
plt.legend(labels=['original data','noise data','epanechnikov','tri-cube','gaussian'],loc='upper right')
plt.title('kernel regression:y=sin(4x),h=0.3')
plt.show()
运行结果:
数据集不同时,
h 的值也要进行相应调整。
2. 局部多项式核回归
由于核函数在边界区域上无法满足对称性(例如,图
1中左侧边界点
x=−4 处只有右侧邻域中的观测点可以用,右侧边界点
x=4 处只有左侧邻域中的观测点可以用),局部加权平均在边界处会出现较大的误差(如上图所示),局部多项式回归可以缓解这一问题。
2.1 加权最小二乘法(Weighted least squares)
如下图所示,已知观测样本集
{xi,yi}i=0N,采用线性模型:
φ(x)=n=0∑Manφn(x)=a0φ0(x)+a1φ1(x)+⋯+aMφM(x)=θTϕ(x)=ϕT(x)θ
其中,
θ=[a0,a1,⋯,aM]T,
ϕ(x)=[φ0(x),φ1(x),⋯,φM(x)]T
上图中,线性模型关于每个观测点
(xi,yi) 的
ℓ2 损失(平方误差)为:
[φ(xi)−yi]2
假设每个观测点的对误差的影响各不相同,因此,引入加权系数
{wi}i=0N,将“整个数据集的总误差”设为加权损失函数
(weighted loss function),也就是:
J(θ)=w0[φ(x0)−y0]2+w1[φ(x1)−y1]2+⋯+wN[φ(xN)−yN]2=i=0∑Nwi[φ(xi)−yi]2=i=0∑Nwi[θTϕ(xi)−yi]2=(Φθ−y)TW(Φθ−y)
其中,
y=[y0,y1,⋯,yN]T
W=⎣⎢⎢⎡w0w1⋱wN⎦⎥⎥⎤
Φ=⎣⎢⎢⎢⎡ϕ(x0)Tϕ(x1)T⋮ϕ(xN)T⎦⎥⎥⎥⎤=⎣⎢⎢⎢⎡φ0(x0)φ0(x1)⋮φ0(xN)φ1(x0)φ1(x1)⋮φ1(xN)⋯⋯ ⋯φM(x0)φM(x1)⋮φM(xN)⎦⎥⎥⎥⎤
损失函数
J(θ) 对系数
θ 求偏导:
∂θ∂J(θ)=2ΦTWΦθ−2ΦTWy=0
可求得:
θ=(ΦTWΦ)−1ΦTWy
关于本节内容更详细的解释,请参考《函数的最佳逼近问题:最小二乘法》
2.2 局部多项式核回归(Local polynomial kernel regression)
局部加权回归,是在每个目标点
xi 单独求一个加权最小二乘解。
若最小二乘逼近采用多项式模型:
φ(x)=n=0∑Manφn(x)=a0φ0(x)+a1φ1(x)+⋯+aMφM(x)=a0+a1x+a2x2+⋯+aMxM=n=0∑Manxn
也就是:
φ0(x)=1 和
φn(x)=xn, n=1∼M
特别地,当
M=1 时,多项式模型就变成了线性回归模型。
当
M=2 时,多项式模型就变成了抛物线回归模型。
因此,局部加权回归可以描述为:
(1) 采用
M阶多项式模型来求加权最小二乘解
(2) 采用高斯核函数
K(x,xi)=K(∥x−xi∥) 来刻画目标点
xi 的邻域位置
x∈Ni={x ∣∣ ∣∣x−xi∣∣<δ} 处观测数据的权值,作为加权最小二乘解中的权值
也就是求每个观测点
xi 的加权最小二乘解,此时的目标函数为:
Ji(θ)=xj∈Ni∑K(xi,xj)[yj−n=0∑Man(xi)xjn]2=xj∈Ni∑K(xi,xj)[yj−θiTϕ(xj)]2
其中,
θi=[a0(xi),a1(xi),a2(xi),⋯,aM(xi)]T,
ϕ(x)=[1,x,x2,⋯,xM]T
因此,对观测点
xi 的(关于局部数据集的)目标函数
Ji(θ) 可求出在观测点
xi 的加权最小二乘解
θi,也就是用与观测点
xi 邻近的局部观测数据来拟合局部曲线。
局部多项式核回归的示意图如图
3 所示:
图3 在目标点为
(xi,yi) 处的局部多项式核回归示意图(假设蓝色曲线为“局部多项式核回归”的结果)
(1)待求解的黑色实心点目标值
yi 对应于
x 轴的位置为黑色空心点
xi
(2)在示意图中,使用了
xi 位置的左边和右边的
4 个邻近位置的观测数据(红色实心点)作为“局部观测数据集”
Ni,来求加权最小二乘解
f~(x)
(3)各个局部观测数据
x∈Ni 的权值由高斯核函数求得,即图中的“+”号所表示的
(4)将局部多项式核回归的解
f~(xi) 作为黑色空心点位置
xi 的目标值,即
yi=f~(xi)
代码实现
import numpy as np
import matplotlib.pyplot as plt
def gen_data(x):
y = x ** 2 + 30 * np.sin(x)
y_noise = y + np.random.randn(len(x))*5
return y, y_noise
def weighted_least_squares(y_noise,x,M,W):
design_matrix = np.asmatrix(np.ones(len(x))).T
for i in range(1,M+1):
arr = np.asmatrix(np.power(x,i)).T
design_matrix = np.concatenate((design_matrix ,arr),axis=1)
coef = (design_matrix.T*W*design_matrix).I*(design_matrix.T*W*(np.asmatrix(y_noise).T))
return np.asarray(coef)
def local_polynomial_kernel_regression(y_noise,x,M,width,sigma):
kernel = lambda x,c,sig: np.exp(-(x-x[c])**2/(2*(sig**2)))/(np.sqrt(2*np.pi)*sig)
for i in range(len(x)):
local_y = y_noise[max(0,i-width):min(len(x),i+width)]
local_x = x[max(0,i-width):min(len(x),i+width)]
weight = kernel(x,i,sigma)
local_weight = weight[max(0,i-width):min(len(x),i+width)]
W = np.diag(local_weight)
coef = weighted_least_squares(local_y,local_x,M,W)
if M==1:
y[i] = coef[1]*x[i] + coef[0]
if M==2:
y[i] = coef[2]*x[i]*x[i] + coef[1]*x[i] + coef[0]
return y
if __name__ == '__main__':
plt.figure
x = np.linspace(-8, 6, 200)
y, y_noise = gen_data(x)
plt.plot(x,y,'b')
plt.plot(x,y_noise,'yx')
y_rec = local_polynomial_kernel_regression(y_noise,x,1,20,0.8)
plt.plot(x,y_rec,'r')
y_rec = local_polynomial_kernel_regression(y_noise,x,2,20,0.8)
plt.plot(x,y_rec,'k')
plt.legend(labels=['original data','noise data','local linear','local polynomial'],loc='upper right')
plt.title('local polynomial kernel regression')
plt.show()
运行结果:
从上图中可以看出,与第1节核平滑方法中的“局部加权平均”的核回归方法相比,局部多项式回归在左右边界点处的拟合明显更好。