2025, Oct 07 21:33

GCV-आधारित smoothing spline में lam को बायस से नियंत्रित करें

SciPy make_smoothing_spline के GCV से चुने lam पर नियंत्रण पाएं: smooth_bias फैक्टर के साथ पुनः-इम्प्लीमेंटेशन, अधिक स्मूदिंग या निष्ठा के लिए स्पष्ट, दोहराने योग्य तरीका.

डेटा पर स्मूदिंग स्प्लाइन को कितना आक्रामक बनाकर फिट करना है, यह नियंत्रित करना अक्सर जरूरी होता है। SciPy का make_smoothing_spline() GCV मानदंड के जरिए नियमितीकरण पैरामीटर lam को अपने-आप चुनता है, लेकिन यह फ़ंक्शन उस मान को बाद में समायोजित करने के लिए उपलब्ध नहीं कराता। अगर आप परिणाम को अधिक स्मूदिंग या अधिक निष्ठा की ओर झुकाना चाहते हैं, तो आपको स्वचालित चयन छोड़े बिना lam को हल्का-सा बदलने का तरीका चाहिए।

समस्या का अवलोकन

make_smoothing_spline() में GCV-आधारित lam अंदर ही अंदर निकाला जाता है और लौटाया नहीं जाता। इसका मतलब है कि आप बाद में किसी गुणक से उसे गुणा करके समतलता और डेटा-अनुरूपता के बीच संतुलन नहीं बदल सकते।

from scipy.interpolate import make_smoothing_spline
# GCV अंदरूनी रूप से lam चुनता है
spline_obj = make_smoothing_spline(x_values, y_values)
# यहां स्वतः निर्धारित `lam` को स्केल करने के लिए कोई एक्सेस नहीं है
# (इसे make_smoothing_spline द्वारा न तो सहेजा जाता है, न लौटाया जाता है)

सोर्स कोड देखने पर पुष्टि होती है कि lam कॉल के बाद कहीं भी सहेजा नहीं जाता, इसलिए बाद में उसे स्केल करने का कोई सार्वजनिक तरीका नहीं है। यदि आप GCV मानदंड बनाए रखते हुए भी परिणाम को दिशा देना चाहते हैं, तो सिस्टम हल करने से पहले ही यह स्केलिंग करनी होगी।

यह क्यों होता है

यह फ़ंक्शन आंतरिक रूप से GCV-इष्ट lam निकालता है और सीधे स्प्लाइन गुणांकों के लिए हल करता है। चूंकि यह मान न तो लौटाए गए ऑब्जेक्ट में सहेजा जाता है और न ही रिटर्न वैल्यू के रूप में दिया जाता है, आप बीच में उसे पकड़ नहीं सकते। दूसरे शब्दों में, यह व्यवहार डिजाइन के अनुरूप है: lam को एक आंतरिक विवरण की तरह माना गया है।

समाधान: एक बायस फैक्टर के साथ पुनः कार्यान्वयन

व्यावहारिक उपाय यह है कि make_smoothing_spline का पुनः-कार्यान्वयन करें और स्वतः आंके गए lam पर लागू होने वाला एक गुणात्मक फैक्टर जोड़ें। 1 से बड़ा फैक्टर परिणाम को अधिक प्रबल स्मूदिंग की ओर झुकाता है, जबकि 0 और 1 के बीच का फैक्टर फिट को डेटा के और करीब ले जाता है। 1 का फैक्टर व्यवहार को जस का तस छोड़ता है। नीचे दिया गया इम्प्लीमेंटेशन मूल तर्क, जिसमें GCV-आधारित आकलन शामिल है, को सुरक्षित रखता है और प्राप्त lam को आपके चुने हुए फैक्टर से गुणा करता है।

import numpy as np
from scipy.interpolate._bsplines import _coeff_of_divided_diff as _dd_coef, _compute_optimal_gcv_parameter as _gcv_param
from scipy._lib._array_api import array_namespace as _xp_ns
from scipy.interpolate import BSpline as _BS
from scipy.linalg import solve_banded as _solve_banded
from numpy.core.multiarray import normalize_axis_index as _norm_axis
def fit_spline_gcv_tilt(x_coords, y_data, smooth_bias=1.0, weights=None, alpha=None, *, axis=0):
    """
    Build a cubic smoothing spline using the same logic as SciPy's implementation,
    but multiply the automatically estimated GCV parameter by `smooth_bias`.
    Inputs:
    - smooth_bias >= 0. Values > 1 favor smoother output; values < 1 favor fidelity.
    - alpha: if provided, acts as the regularization parameter directly (non-negative).
    """
    xp_mod = _xp_ns(x_coords, y_data)
    x_arr = np.ascontiguousarray(x_coords, dtype=float)
    y_arr = np.ascontiguousarray(y_data, dtype=float)
    if smooth_bias < 0:
        raise ValueError("`smooth_bias` must be non-negative")
    if any(x_arr[1:] - x_arr[:-1] <= 0):
        raise ValueError("`x` must be strictly increasing")
    if x_arr.ndim != 1 or x_arr.shape[0] != y_arr.shape[axis]:
        raise ValueError(f"`x` must be 1D and {x_arr.shape = } == {y_arr.shape = }")
    if weights is None:
        w_vec = np.ones(len(x_arr))
    else:
        w_vec = np.ascontiguousarray(weights)
        if any(w_vec <= 0):
            raise ValueError("Invalid vector of weights")
    knts = np.r_[[x_arr[0]] * 3, x_arr, [x_arr[-1]] * 3]
    n_pts = x_arr.shape[0]
    if n_pts <= 4:
        raise ValueError("`x` and `y` length must be at least 5")
    axis_idx = _norm_axis(axis, y_arr.ndim)
    y_arr = np.moveaxis(y_arr, axis_idx, 0)
    y_tail_shape = y_arr.shape[1:]
    if y_tail_shape != ():
        y_arr = y_arr.reshape((n_pts, -1))
    # B-spline आधार में डिज़ाइन मैट्रिक्स (बैंडेड रूप)
    dm = _BS.design_matrix(x_arr, knts, 3)
    band_A = np.zeros((5, n_pts))
    for ii in range(1, 4):
        band_A[ii, 2:-2] = dm[ii: ii - 4, 3: -3][np.diag_indices(n_pts - 4)]
    band_A[1, 1] = dm[0, 0]
    band_A[2, :2] = ((x_arr[2] + x_arr[1] - 2 * x_arr[0]) * dm[0, 0],
                     dm[1, 1] + dm[1, 2])
    band_A[3, :2] = ((x_arr[2] - x_arr[0]) * dm[1, 1], dm[2, 2])
    band_A[1, -2:] = (dm[-3, -3], (x_arr[-1] - x_arr[-3]) * dm[-2, -2])
    band_A[2, -2:] = (dm[-2, -3] + dm[-2, -2],
                      (2 * x_arr[-1] - x_arr[-2] - x_arr[-3]) * dm[-1, -1])
    band_A[3, -2] = dm[-1, -1]
    band_B = np.zeros((5, n_pts))
    band_B[2:, 0] = _dd_coef(x_arr[:3]) / w_vec[:3]
    band_B[1:, 1] = _dd_coef(x_arr[:4]) / w_vec[:4]
    for jj in range(2, n_pts - 2):
        band_B[:, jj] = (x_arr[jj + 2] - x_arr[jj - 2]) * _dd_coef(x_arr[jj - 2: jj + 3]) / w_vec[jj - 2: jj + 3]
    band_B[:-1, -2] = -_dd_coef(x_arr[-4:]) / w_vec[-4:]
    band_B[:-2, -1] = _dd_coef(x_arr[-3:]) / w_vec[-3:]
    band_B *= 6
    # नियमितीकरण तय करें और बायस लागू करें
    if alpha is None:
        lam_val = _gcv_param(band_A, band_B, y_arr, w_vec)
        lam_val *= smooth_bias
    elif alpha < 0.0:
        raise ValueError("Regularization parameter should be non-negative")
    else:
        lam_val = alpha
    # नैचुरल स्प्लाइनों के आधार में हल निकालें
    if np.ndim(lam_val) == 0:
        coef = _solve_banded((2, 2), band_A + lam_val * band_B, y_arr)
    elif np.ndim(lam_val) == 1:
        coef = np.empty((n_pts, lam_val.shape[0]))
        for ii in range(lam_val.shape[0]):
            coef[:, ii] = _solve_banded((2, 2), band_A + lam_val[ii] * band_B, y_arr[:, ii])
    else:
        raise RuntimeError("Internal error, please report it to SciPy developers.")
    coef = coef.reshape((coef.shape[0], *y_tail_shape))
    p0, p1 = coef[0:1, ...], coef[1:2, ...]
    pm0, pm1 = coef[-1:-2:-1, ...], coef[-2:-3:-1, ...]
    coeff_full = np.r_[p0 * (knts[5] + knts[4] - 2 * knts[3]) + p1,
                       p0 * (knts[5] - knts[3]) + p1,
                       coef[1: -1, ...],
                       pm0 * (knts[-4] - knts[-6]) + pm1,
                       pm0 * (2 * knts[-4] - knts[-5] - knts[-6]) + pm1]
    knts_xp, coeff_full_xp = xp_mod.asarray(knts), xp_mod.asarray(coeff_full)
    return _BS.construct_fast(knts_xp, coeff_full_xp, 3, axis=axis_idx)

यह इम्प्लीमेंटेशन SciPy की निजी API के फ़ंक्शनों का उपयोग करता है और SciPy v1.16.1 पर सफलतापूर्वक परीक्षण किया गया। यदि आपको स्प्लाइन के साथ प्रयुक्त वास्तविक lam भी चाहिए, तो सीधा तरीका है कि रिटर्न में उस मान को जोड़ दें और कॉल साइट पर उसे अनपैक करें।

# यदि आप फ़ंक्शन से `lam_val` भी लौटाने का निर्णय लेते हैं तो एक उदाहरण पैटर्न
# return _BS.construct_fast(knts_xp, coeff_full_xp, 3, axis=axis_idx), lam_val
# इसके बाद कॉल करें: spline_obj, lam_used = fit_spline_gcv_tilt(...)

व्यवहार में बायस का उपयोग

यदि smooth_bias 1 है, तो व्यवहार डिफ़ॉल्ट जैसा ही रहता है। फैक्टर बढ़ाने पर कर्व अधिक स्मूद होते हैं; घटाने पर फिट अवलोकनों के और नजदीक जाता है। आप GCV-चालित चयन को बनाए रखते हैं, बस हल निकालने से पहले उसे स्केल करते हैं।

# डिफ़ॉल्ट से अधिक स्मूदिंग
s_smooth = fit_spline_gcv_tilt(x_values, y_values, smooth_bias=2.0)
# डिफ़ॉल्ट से अधिक डेटा निष्ठा
s_faithful = fit_spline_gcv_tilt(x_values, y_values, smooth_bias=0.5)

यह विवरण क्यों महत्वपूर्ण है

भले ही स्वचालित चयन अच्छा काम करे, उत्पादन कार्यभार में अक्सर डाउनस्ट्रीम बाधाओं को पूरा करने, व्युत्पन्नों को स्थिर रखने या डेटासेट्स में सुसंगतता बनाए रखने के लिए समतलता पर स्पष्ट नियंत्रण की जरूरत होती है। एक छोटा, पारदर्शी नॉब आपको GCV की सुविधा बरकरार रखते हुए उसका समझौता स्पष्ट और दोहराने योग्य बनाता है।

मुख्य बातें

make_smoothing_spline स्वतः चुने गए lam को उजागर नहीं करता, इसलिए कॉल के बाद आप उसे समायोजित नहीं कर सकते। यदि यह क्षमता चाहिए, तो उसी तर्क को दोहराएँ और GCV-इष्ट पैरामीटर को एक फैक्टर से गुणा करें। 1 से बड़े मान समतलता को तरजीह देते हैं; 1 से छोटे मान निष्ठा को। यह तरीका SciPy की निजी API पर निर्भर है और SciPy 1.16.1 पर परखा गया, इसलिए अपने परिवेश में इसे उसी अनुसार संभालें।

यह लेख StackOverflow पर प्रश्न David Pagnon द्वारा और David Pagnon के उत्तर पर आधारित है।