2025, Oct 22 16:16
cos^2(x) के लिए गॉसियन योग फिट: curve_fit विफलता से समाधान तक
इस गाइड में जानें cos^2(x) तरंगरूप को गॉसियन शिखरों के योग से भरोसेमंद तरीके से फिट करना: SciPy curve_fit की सीमाएँ, find_peaks से शिखर-गिनती और आरंभिक मान।
cos^2(x) तरंगरूप को गॉसियन शिखरों के योग से फिट करना पहली नज़र में सरल लगता है, लेकिन scipy.optimize.curve_fit का सीधा-सादा इस्तेमाल कई बार बेहद खराब फिट तक ले जाता है। समस्या की जड़ न तो डेटा है और न ही फ़ंक्शन का चुनाव, बल्कि यह है कि मॉडल के पैरामीटर कैसे परिभाषित किए गए हैं और उन्हें कैसे आरंभ किया गया है।
समस्या की रूपरेखा
उद्देश्य है cos^2(kx) का सन्निकटन ऐसे गॉसियनों के गुच्छ से करना, जिनके बीच समान अंतर हो। नीचे दिया गया कोड लक्ष्य वक्र, गॉसियन आधार, और उपयोग होने वाले शिखरों की संख्या समेत सभी पैरामीटरों को एक साथ फिट करने की कोशिश करता है।
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
freq = 1
mult = 3
x_lo = -mult * np.pi / (2 * freq)
x_hi =  mult * np.pi / (2 * freq)
grid = np.linspace(x_lo, x_hi, 1000)
target = np.cos(freq * grid) ** 2
def bell(xv, center, sigma):
    return np.exp(- (xv - center) ** 2 / (2 * sigma ** 2))
def bell_stack(xv, center, sigma, spacing, count):
    total = 0
    for idx in range(int(count)):
        pos = center + idx * spacing
        total += bell(xv, pos, sigma)
    return total
est = curve_fit(bell_stack, grid, target)[0]
c0 = est[0]
sg = est[1]
gap = est[2]
num = int(est[3])
approx = 0
for idx in range(num):
    pos = c0 + idx * gap
    approx += bell(grid, pos, sg)
fig, ax = plt.subplots()
ax.plot(grid, target, label="cosine function")
ax.plot(grid, approx, label="gaussian approximation to cosine")
ax.set_xlabel("x")
ax.set_ylabel("Amplitude")
plt.grid()
plt.legend()
plt.show()फिट क्यों विफल होती है
सॉल्वर के खिलाफ दो बातें काम करती हैं। पहली, मॉडल में एक ऐसा पैरामीटर है जो बताता है कि कितने गॉसियन जोड़ने हैं। यह पैरामीटर स्वभावतः विविक्त और गैर-अवकलनीय है। ऑप्टिमाइज़र यदि इसे सूक्ष्म सा भी बदल दे, तो आउटपुट में कोई अंतर नहीं आता। curve_fit जैसा लोकल ऑप्टिमाइज़र स्थानीय ग्रेडिएंट पर निर्भर करता है, इसलिए ऐसे पैरामीटर के लिए सार्थक अपडेट सीख ही नहीं सकता। दूसरी, उद्देश्य-सतह पर कई स्थानीय न्यूनतम हैं। लोकल ऑप्टिमाइज़र शुरुआत में ही फंस सकता है, और क्योंकि शुरुआती अनुमान डिफ़ॉल्ट रूप से 1 होते हैं, यह शिखरों की गिनती और अंतराल के लिए खास तौर पर नुकसानदेह है।
दूसरे संदर्भों में और भी मॉडलिंग रास्ते संभव हैं—जैसे फूरिए डोमेन में काम करना या गॉसियन के बजाय सीधे स्क्वेयर्ड कोसाइन का उपयोग—पर यहां शर्त है कि x-डोमेन में गॉसियन शिखरों के साथ ही रहना है।
व्यावहारिक समाधान: गैर-अवकलनीय पैरामीटर हटाएँ और समझदारी से शुरुआती मान दें
कुंजी यह है कि विविक्त पैरामीटर को फिट से बाहर कर दें और सूचित आरंभिक मान दें। यहां भरोसेमंद तरीका है लक्ष्य सिग्नल में शिखरों की संख्या और स्थान पहचानना। scipy.signal.find_peaks की मदद से आप शिखरों की गिनती निकाल सकते हैं, उनके बीच का अंतराल अनुमानित कर सकते हैं, और पहले शिखर का स्थान चुन सकते हैं। इसके बाद, स्थिर शिखर-गिनती वाला मॉडल curve_fit को दें ताकि वह केवल निरंतर पैरामीटरों का ही अनुकूलन करे।
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
from scipy.signal import find_peaks
from functools import partial
freq = 1
mult = 3
x_lo = -mult * np.pi / (2 * freq)
x_hi =  mult * np.pi / (2 * freq)
grid = np.linspace(x_lo, x_hi, 1000)
target = np.cos(freq * grid) ** 2
def bell(xv, center, sigma):
    return np.exp(- (xv - center) ** 2 / (2 * sigma ** 2))
def bell_stack(xv, center, sigma, spacing, count):
    total = 0
    for idx in range(int(count)):
        pos = center + idx * spacing
        total += bell(xv, pos, sigma)
    return total
peak_idx, _ = find_peaks(target)
num = len(peak_idx)
init_gap = np.mean(np.diff(grid[peak_idx]))
init_c0 = grid[peak_idx[0]]
model = partial(bell_stack, count=num)
fit_params = curve_fit(model, grid, target, p0=(init_c0, 1, init_gap))[0]
c0, sg, gap = fit_params
approx = model(grid, *fit_params)
fig, ax = plt.subplots()
ax.plot(grid, target, label="cosine function")
ax.plot(grid, approx, label="gaussian approximation to cosine")
ax.set_xlabel("x")
ax.set_ylabel("Amplitude")
plt.grid()
plt.legend()
plt.show()यह तरीका गैर-अवकलनीय स्वतंत्रता-डिग्री को स्थिर कर देता है, शुरुआती अंतराल को पहचानी गई तरंगरूप के अनुरूप करता है, और पहले शिखर को आधार देता है। इसके बाद ऑप्टिमाइज़र किसी पूर्णांक पर उलझने के बजाय निरंतर पैरामीटरों को निखारने पर ध्यान दे सकता है।
यह क्यों मायने रखता है
उचित मॉडल होने पर भी अनुकूलन तब विफल हो जाता है जब पैरामीटरीकरण सॉल्वर की मान्यताओं का उल्लंघन करे। curve_fit एक स्थानीय विधि है; उसे ग्रेडिएंट और अच्छे शुरुआती बीज चाहिए। ग्रेडिएंट-आधारित रूटीन में विविक्त विकल्प घुसा देना या डिफ़ॉल्ट आरंभिक अनुमानों पर निर्भर रहना खराब अभिसरण को दावत देता है। इसके उलट, सिग्नल की संरचनात्मक बातों का पहले से आकलन करना और फिट को निरंतर चर पर सीमित करना एल्गोरिद्म को वह देता है जिसे वह सच में अनुकूलित कर सके।
निष्कर्ष
cos^2(x) जैसे संरचित सिग्नल को गॉसियन योग से करीब लाते समय, ध्यान रखें कि ऑप्टिमाइज़र क्या संभाल सकता है। पैरामीटर-स्थान को अवकलनीय रखने के लिए विविक्त गिनतियों को पहले ही स्थिर कर दें, और डेटा से निकली जानकारी—जैसे शिखरों की संख्या, अंतराल, और पहले शिखर का स्थान—से फिट के शुरुआती मान तय करें। इन समायोजनों के साथ, वही फिटिंग टूल कहीं अधिक भरोसेमंद ढंग से काम करते हैं और वैसा सन्निकटन देते हैं जिसकी वास्तव में ज़रूरत है।
यह लेख StackOverflow पर एक प्रश्न (लेखक: Cody Payne) और jared के उत्तर पर आधारित है।