web-dev-qa-db-ja.com

Python自然な平滑化スプライン

私は、ユーザー選択可能なスムージング係数を使用して自然なスムージングスプラインに適合するオプションを提供するpythonパッケージを見つけようとしています。そうでない場合、自分で実装するために利用可能なものをどのように使用しますか?

  • 自然なスプラインとは、端点での近似関数の2次導関数がゼロ(線形)であるという条件があることを意味します。

  • スプラインを平滑化するということは、スプラインが「すべてのデータポイントを通過する」「補間」であってはならないことを意味します。正しい平滑化係数lambda(スプラインの平滑化については Wikipediaページ を参照)を自分で決定したいと思います。

私が見つけたもの

  • scipy.interpolate.CubicSpline [ link ]:自然な(キュービック)スプラインフィッティングを行います。補間を行い、データを平滑化する方法はありません。

  • scipy.interpolate.UnivariateSpline [ link ]:ユーザーが選択可能な平滑化係数を使用してスプラインフィッティングを行います。ただし、スプラインを自然にするオプションはありません。

9
np8

数時間の調査の後、ユーザーが制御可能な滑らかさで自然な3次スプラインに適合するピップインストール可能なパッケージは見つかりませんでした。しかし、自分自身で書くことを決めた後、トピックについて読んでいると、私はgithubユーザー madrury による ブログ投稿 を見つけました。彼はpython自然な3次スプラインモデルを生成できるコードを書いた。

モデルコードは、 here (NaturalCubicSpline)と BSDライセンス で入手できます。彼は IPythonノートブック でいくつかの例を書いています。

しかし、これはインターネットであり、リンクは消滅する傾向があるため、ここでソースコードの関連部分と私が作成したヘルパー関数(get_natural_cubic_spline_model)をコピーし、その使用方法の例を示します。フィットの滑らかさは、異なる数のノットを使用して制御できます。ノットの位置もユーザーが指定できます。

from matplotlib import pyplot as plt
import numpy as np

def func(x):
    return 1/(1+25*x**2)

# make example data
x = np.linspace(-1,1,300)
y = func(x) + np.random.normal(0, 0.2, len(x))

# The number of knots can be used to control the amount of smoothness
model_6 = get_natural_cubic_spline_model(x, y, minval=min(x), maxval=max(x), n_knots=6)
model_15 = get_natural_cubic_spline_model(x, y, minval=min(x), maxval=max(x), n_knots=15)
y_est_6 = model_6.predict(x)
y_est_15 = model_15.predict(x)


plt.plot(x, y, ls='', marker='.', label='originals')
plt.plot(x, y_est_6, marker='.', label='n_knots = 6')
plt.plot(x, y_est_15, marker='.', label='n_knots = 15')
plt.legend(); plt.show()

Example of natural cubic splines with varying smoothness.

get_natural_cubic_spline_modelのソースコード

import numpy as np
import pandas as pd
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline


def get_natural_cubic_spline_model(x, y, minval=None, maxval=None, n_knots=None, knots=None):
    """
    Get a natural cubic spline model for the data.

    For the knots, give (a) `knots` (as an array) or (b) minval, maxval and n_knots.

    If the knots are not directly specified, the resulting knots are equally
    space within the *interior* of (max, min).  That is, the endpoints are
    *not* included as knots.

    Parameters
    ----------
    x: np.array of float
        The input data
    y: np.array of float
        The outpur data
    minval: float 
        Minimum of interval containing the knots.
    maxval: float 
        Maximum of the interval containing the knots.
    n_knots: positive integer 
        The number of knots to create.
    knots: array or list of floats 
        The knots.

    Returns
    --------
    model: a model object
        The returned model will have following method:
        - predict(x):
            x is a numpy array. This will return the predicted y-values.
    """

    if knots:
        spline = NaturalCubicSpline(knots=knots)
    else:
        spline = NaturalCubicSpline(max=maxval, min=minval, n_knots=n_knots)

    p = Pipeline([
        ('nat_cubic', spline),
        ('regression', LinearRegression(fit_intercept=True))
    ])

    p.fit(x, y)

    return p


class AbstractSpline(BaseEstimator, TransformerMixin):
    """Base class for all spline basis expansions."""

    def __init__(self, max=None, min=None, n_knots=None, n_params=None, knots=None):
        if knots is None:
            if not n_knots:
                n_knots = self._compute_n_knots(n_params)
            knots = np.linspace(min, max, num=(n_knots + 2))[1:-1]
            max, min = np.max(knots), np.min(knots)
        self.knots = np.asarray(knots)

    @property
    def n_knots(self):
        return len(self.knots)

    def fit(self, *args, **kwargs):
        return self


class NaturalCubicSpline(AbstractSpline):
    """Apply a natural cubic basis expansion to an array.
    The features created with this basis expansion can be used to fit a
    piecewise cubic function under the constraint that the fitted curve is
    linear *outside* the range of the knots..  The fitted curve is continuously
    differentiable to the second order at all of the knots.
    This transformer can be created in two ways:
      - By specifying the maximum, minimum, and number of knots.
      - By specifying the cutpoints directly.  

    If the knots are not directly specified, the resulting knots are equally
    space within the *interior* of (max, min).  That is, the endpoints are
    *not* included as knots.
    Parameters
    ----------
    min: float 
        Minimum of interval containing the knots.
    max: float 
        Maximum of the interval containing the knots.
    n_knots: positive integer 
        The number of knots to create.
    knots: array or list of floats 
        The knots.
    """

    def _compute_n_knots(self, n_params):
        return n_params

    @property
    def n_params(self):
        return self.n_knots - 1

    def transform(self, X, **transform_params):
        X_spl = self._transform_array(X)
        if isinstance(X, pd.Series):
            col_names = self._make_names(X)
            X_spl = pd.DataFrame(X_spl, columns=col_names, index=X.index)
        return X_spl

    def _make_names(self, X):
        first_name = "{}_spline_linear".format(X.name)
        rest_names = ["{}_spline_{}".format(X.name, idx)
                      for idx in range(self.n_knots - 2)]
        return [first_name] + rest_names

    def _transform_array(self, X, **transform_params):
        X = X.squeeze()
        try:
            X_spl = np.zeros((X.shape[0], self.n_knots - 1))
        except IndexError: # For arrays with only one element
            X_spl = np.zeros((1, self.n_knots - 1))
        X_spl[:, 0] = X.squeeze()

        def d(knot_idx, x):
            def ppart(t): return np.maximum(0, t)

            def cube(t): return t*t*t
            numerator = (cube(ppart(x - self.knots[knot_idx]))
                         - cube(ppart(x - self.knots[self.n_knots - 1])))
            denominator = self.knots[self.n_knots - 1] - self.knots[knot_idx]
            return numerator / denominator

        for i in range(0, self.n_knots - 2):
            X_spl[:, i+1] = (d(i, X) - d(self.n_knots - 2, X)).squeeze()
        return X_spl
10
np8

このnumpy/scipy実装 を使用して、単変量/多変量データの平滑化に自然な3次平滑化スプラインを使用できます。平滑化パラメーターの範囲は[0.0、1.0]でなければなりません。 1.0に等しい平滑化パラメーターを使用すると、データの平滑化なしで自然な3次スプライン補間が得られます。また、実装は単変量データのベクトル化をサポートします。

一変量の例:

import numpy as np
import matplotlib.pyplot as plt

import csaps

np.random.seed(1234)

x = np.linspace(-5., 5., 25)
y = np.exp(-(x/2.5)**2) + (np.random.Rand(25) - 0.2) * 0.3

sp = csaps.UnivariateCubicSmoothingSpline(x, y, smooth=0.85)

xs = np.linspace(x[0], x[-1], 150)
ys = sp(xs)

plt.plot(x, y, 'o', xs, ys, '-')
plt.show()

enter image description here

二変量の例:

import numpy as np

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

import csaps

xdata = [np.linspace(-3, 3, 61), np.linspace(-3.5, 3.5, 51)]
i, j = np.meshgrid(*xdata, indexing='ij')

ydata = (3 * (1 - j)**2. * np.exp(-(j**2) - (i + 1)**2)
         - 10 * (j / 5 - j**3 - i**5) * np.exp(-j**2 - i**2)
         - 1 / 3 * np.exp(-(j + 1)**2 - i**2))

np.random.seed(12345)
noisy = ydata + (np.random.randn(*ydata.shape) * 0.75)

sp = csaps.MultivariateCubicSmoothingSpline(xdata, noisy, smooth=0.988)
ysmth = sp(xdata)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

ax.plot_wireframe(j, i, noisy, linewidths=0.5, color='r')
ax.scatter(j, i, noisy, s=5, c='r')

ax.plot_surface(j, i, ysmth, linewidth=0, alpha=1.0)

plt.show()

enter image description here

5
iroln

プログラミング言語Rは、自然な3次平滑化スプラインの非常に優れた実装を提供します。 PythonでR関数をrpy2とともに使用できます。

import rpy2.robjects as robjects
r_y = robjects.FloatVector(y_train)
r_x = robjects.FloatVector(x_train)

r_smooth_spline = robjects.r['smooth.spline'] #extract R function# run smoothing function
spline1 = r_smooth_spline(x=r_x, y=r_y, spar=0.7)
ySpline=np.array(robjects.r['predict'](spline1,robjects.FloatVector(x_smooth)).rx2('y'))
plt.plot(x_smooth,ySpline)

lambdaを直接設定する場合:spline1 = r_smooth_spline(x=r_x, y=r_y, lambda=42)は機能しません。Pythonでlambdaはすでに別の意味を持っていますが、解決策があります: RPMでsmooth.splineのlambda引数を使用する方法Pythonをlambda として解釈する方法。

コードを実行するには、最初にデータx_trainおよびy_trainを定義する必要があり、フルHD解像度で-3から5の間でプロットする場合はx_smooth=np.array(np.linspace(-3,5,1920)).を定義できます。

0
Jakob