Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

LOWESS

散布図に従う近似線を描くために開発された局所回帰。 LOESS (locally estimated scatterplot smoothing)LOWESS (locally weighted scatterplot smoothing) と呼ばれる。

Source
import numpy as np
np.random.seed(0)
x = np.linspace(0, 7, 100)
y = np.sin(x) + np.random.normal(0, 0.2, 100)

import statsmodels.api as sm
smoothed = sm.nonparametric.lowess(exog=x, endog=y, frac=0.2, it=3)

import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.scatter(x, y)
ax.plot(smoothed[:, 0], smoothed[:, 1], c="k")
ax.set(xlabel="x", ylabel="y")
fig.show()
<Figure size 640x480 with 1 Axes>

LOWESSのアルゴリズム

Cleveland (1979). に記されたアルゴリズムは以下の通り。

ii番目のサンプルの目的変数yiy_iを特徴量xix_iとノンパラメトリックの平滑化関数g(xi)g(x_i)で近似することを考える。

yi=g(xi)+ϵiy_i = g(x_i) + \epsilon_i

ここでϵi\epsilon_iは平均0で分散が一定の確率変数である。

1. 重みの計算とrr個の最近傍サンプルの取得

xix_iについて、j=1,,nj=1,\dots,nにわたってxixj|x_i - x_j|で距離を測り、rr番目に近いサンプルとの距離をhih_iとする。

重み関数W()W(\cdot)を用いて、k=1,,nk=1,\dots,nについて

wk(xi)=W(hi1(xkxi))w_k(x_i) = W(h_i^{-1}(x_k - x_i))

を計算する

ここで重み関数W()W(\cdot)は以下の性質を満たすものとする

  1. x<1|x| < 1について W(x)>0W(x) > 0

  2. W(x)=W(x)W(-x)=W(x)

  3. W(x)W(x)x0x \geq 0について 非増加関数

  4. x1|x| \geq 1についてW(x)=0W(x)=0

hi1(xkxi)h_i^{-1}(x_k - x_i)は分子のxkxix_k - x_iの絶対値が分母のhih_iより大きければhi1(xkxi)1|h_i^{-1}(x_k - x_i)| \geq 1になるので重みが0になる。つまり、サンプルとして回帰に使用されなくなる。 なので重み関数は近傍のrr個のサンプルを取り出しつつ、rr個のサンプルにも距離に応じた重みをかける操作となる。

WWの例として tricube functionが考えられる

W(x)={(1x3)3 for x<10 for x1W(x) = \begin{cases} (1-|x|^3)^3 & \text { for } \quad|x|<1 \\ 0 \quad & \text { for } \quad|x| \geqslant 1 \end{cases}
Source
def tricube(x: float) -> float:
    if np.abs(x) >= 1:
        return 0
    return (1 - np.abs(x)**3 )**3

x = np.linspace(-2, 2, 100)
w = [tricube(x_i) for x_i in x]

# 別のやり方
# w = (1 - np.abs(x)**3)**3
# w[np.abs(x) >= 1] = 0
fig, ax = plt.subplots(figsize=[4,3])
ax.plot(x, w)
ax.set(title="tricube weight function", xlabel="x", ylabel="W(x)")
fig.show()
<Figure size 400x300 with 1 Axes>

2. 多項式回帰のフィッティング

非線形回帰としてdd次の多項式回帰を行う

minβ0,,βd k=1nwk(xi)(ykβ0β1xkβdxkd)2\min_{\beta_0,\dots,\beta_d} ~ \sum_{k=1}^n w_k\left(x_i\right)\left(y_k-\beta_0-\beta_1 x_k-\ldots-\beta_d x_k^d\right)^2
y^i=j=0dβ^j(xi)xij\hat{y}_i=\sum_{j=0}^d \hat{\beta}_j\left(x_i\right) x_i^j

3. ロバスト性重みδ\deltaの計算

続いて、外れ値の影響を除外するための重みを計算する。 bisquare weight function B(x)B(x)を以下のように定義する

B(x)={(1x2)2 for x<10 for x1B(x) = \begin{cases} (1 - x^2)^2 & \text { for } \quad|x| < 1 \\ 0 \quad & \text { for } \quad|x| \geqslant 1 \end{cases}

残差ei=yiy^ie_i = y_i - \hat{y}_iの絶対値ei|e_i|の中央値をssとする。ロバスト性重み(robustness weights)を

δk=B(ek/6s)\delta_k = B(e_k / 6s)

と定義する。B(x)B(x)もtricube functionと似た形状であり、残差の絶対値の中央値の6倍(6s6s)以上の絶対値の残差ek/6s1|e_k/6s| \geq 1を持つ外れ値は重みδk\delta_kがゼロになり、推定に含まれなくなるので、推定からハズレ値の影響を除外できる。

Source
def bisquare(x: float) -> float:
    if np.abs(x) >= 1:
        return 0
    return (1 - x**2 )**2

x = np.linspace(-2, 2, 100)
b = [bisquare(x_i) for x_i in x]
fig, ax = plt.subplots(figsize=[4,3])
ax.plot(x, b)
ax.set(title="bisquare weight function", xlabel="x", ylabel="B(x)")
fig.show()
<Figure size 400x300 with 1 Axes>

4. δ\deltaで重み付け回帰を行う

またdd次多項式回帰を行い、新たな推定値y^i\hat{y}_iを得る。このとき、重みはδkwk(xi)\delta_k w_k (x_i)を使う。

5. 繰り返す

3.と4.のステップをtt回繰り返す。

実装

# サンプルデータ
import numpy as np
n = 100
np.random.seed(0)
x = np.linspace(0, 7, n)
y = np.sin(x) + np.random.normal(0, 0.2, n)
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.scatter(x, y)
ax.set(xlabel="x", ylabel="y")
fig.show()
<Figure size 640x480 with 1 Axes>
frac = 0.66 # 使用するサンプルの割合
r = int(frac * n)  # 使用する近傍のサンプル数
d = 3  # 多項式回帰の次数
t = 3  # iteration

def tricube(x: np.array) -> np.array:
    w = (1 - np.abs(x)**3)**3
    w[np.abs(x) >= 1] = 0
    return w

def bisquare(x: np.array) -> np.array:
    w = (1 - np.abs(x)**2)**2
    w[np.abs(x) >= 1] = 0
    return w

# d次多項式を作るための特徴量生成
X = np.vstack([x**j for j in range(d)]).T

n = X.shape[0]
delta = np.ones_like(x)
y_pred = np.zeros_like(x)
for _ in range(t):
    for i in range(n):
        # 重みの計算
        dist = x - x[i]
        idx = np.argsort(np.abs(dist))[:r]
        h_i = np.abs(dist[idx]).max() # r番目に近いdiff
        w = tricube(dist / h_i)
        W = np.diag(delta * w)
    
        # WLS
        beta = np.linalg.inv(X.T @ W @ X) @ X.T @ W @ y
        y_pred[i] = X[i,:] @ beta
    
    e = y - y_pred
    s = np.median(np.abs(e))
    delta = bisquare(e / (6 * s))

import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.scatter(x, y)
ax.plot(x, y_pred, c="k")
ax.set(xlabel="x", ylabel="y")
fig.show()
<Figure size 640x480 with 1 Axes>

最近のLOWESSアルゴリズム

Wikipediaには、重み関数がtricubeではなくGaussianを使うものが紹介されている

Gaussian weight functionとは、2つのデータ点の特徴量ベクトルx,xRmx, x' \in \mathbb{R}^mmmは特徴量の次元数)について、

w(x,x,α)=exp(xx22α2)w(x, x', \alpha)=\exp \left(-\frac{\|x-x'\|^2}{2 \alpha^2}\right)

といったもの。

参考