У меня есть данные x и y, которые представляют собой зашумленные оценки функции f:[0,alpha] -> [0,1]. Я очень мало знаю о своей функции, за исключением того, что f(0) = 0 и f(alpha) = 1.
Есть ли способ обеспечить соблюдение этих граничных условий при установке сплайна?
Вот картинка, где видно, что сплайн хорошо ложится, но аппроксимация плохая около 0, где он принимает значение около 0,08:
Мне известно о bc_type для различных сплайнов, но, насколько я могу судить, это позволяет указать только 1-ю и 2-ю производную, а не фиксировать граничные значения.
(Возможно, этот вопрос выдает мое незнание того, как устанавливаются сплайны, и что то, о чем я прошу, невозможно. Я просто хочу убедиться, что не упускаю чего-то очевидного.)
Вот игрушечный пример:
import numpy as np
from scipy.interpolate import UnivariateSpline
import matplotlib.pyplot as plt
## Generate noisy evaluations of the square root function.
## (In my exmaple, I don't have a closed form expression for my function)
x = np.linspace(0,1,1000)
y = np.sqrt(x) + (np.random.random(1000)-0.5)*0.05
## Fit spline
spline = UnivariateSpline(x,y)
plt.figure(figsize=(7,7))
plt.scatter(x,y,s=1,label = "Monte Carlo samples")
plt.plot(x,spline(x),color='red',label = "spline")
plt.legend()
plt.title("Noisy evaluations of sqrt")
plt.grid()
plt.show()
И получившийся график, на котором довольно хорошо видно, что сплайн обеспечивает плохое приближение около нуля:
Большое спасибо - я попробую. (Я видел этот ответ, но думал, что он предназначен только для 1-й и более высоких производных.)
Можете ли вы включить для этого свой существующий код?
Я добавил простой пример. (Функция, которую я на самом деле оцениваю, довольно сложна, и для ее включения потребуется около 100 строк...)






Одна из идей для подбора этой функции — сказать, что f(0) = 0 и f(1) = 1 — это просто наблюдения за вашими функциями. Конечно, это наблюдения, в которых вы гораздо более уверены, чем в остальных, но это все равно всего лишь наблюдения.
UnivariateSpline принимает аргумент w, чтобы придать конкретным наблюдениям больший вес. Одним из способов «обеспечения соблюдения» этих граничных условий было бы добавление этих точек как обычных, но с высоким весом.
import numpy as np
from scipy.interpolate import UnivariateSpline
import matplotlib.pyplot as plt
## Generate noisy evaluations of the square root function.
## (In my exmaple, I don't have a closed form expression for my function)
x = np.linspace(0,1,1000)
y = np.sqrt(x) + (np.random.random(1000)-0.5)*0.05
# Add fixpoints to function
fixpoint_weight = 1000
fixpoints = np.array([
#X, Y
(0, 0),
(1, 1),
]).T
x_with_fixpoints = np.concatenate([x, fixpoints[0]])
y_with_fixpoints = np.concatenate([y, fixpoints[1]])
weights = np.ones(len(x_with_fixpoints))
weights[-fixpoints.shape[1]:] = fixpoint_weight
# x must be increasing - sort x, y, and weights by x
sort_order = x_with_fixpoints.argsort()
x_with_fixpoints = x_with_fixpoints[sort_order]
y_with_fixpoints = y_with_fixpoints[sort_order]
weights = weights[sort_order]
## Fit spline
spline = UnivariateSpline(x_with_fixpoints, y_with_fixpoints, w=weights, s=0.5)
plt.figure(figsize=(7,7))
plt.scatter(x,y,s=1,label = "Monte Carlo samples")
plt.plot(x,spline(x),color='red',label = "spline")
plt.legend()
plt.title("Noisy evaluations of sqrt")
plt.grid()
plt.show()
print(spline(0) - 0, spline(1) - 1)
большое спасибо, это хорошая идея. (Я не знал о параметре «w».) Я пробовал искусственно добавлять множество точек с одинаковым значением около нуля и 1, но этот подход намного чище.
Одна из проблем с вышеизложенным заключается в том, что мне бы хотелось, чтобы результат гарантированно принимал значение 0 при 0, 1 и альфа. (Я аппроксимирую около 5000 таких функций, а затем мне нужно найти фиксированное значение от 0 до 1. Иногда это значение близко к 0 или 1, и я хочу всегда возвращать значение.) В любом случае, Я ценю это предложение и, вероятно, в конечном итоге воспользуюсь им вместе с предложением try, кроме...
Как я сказал в комментарии к другому ответу: make_interp_spline должен иметь возможность принимать нулевую производную в качестве граничного значения. То, что это не так, является банальным запросом на улучшение, и исправление будет приветствоваться.
Вам нужна функция, которая создает фиксированный B-сплайн, который интерполирует заданные конечные точки. К сожалению, насколько мне известно, ни один scipy-сплайновый интерфейс не реализует это в точности.
Тем не менее, в разделе 9 этого онлайн-курса описан алгоритм решения именно этой задачи, обозначенный как « Глобальная аппроксимация кривой». Ниже я реализую версию этого алгоритма на Python, которая соответствует описанию на веб-сайте.
Функция bspline_least_square_with_clamped_endpoints(x, y, n_ctrl, p) возвращает объект scipy.interpolate.BSpline с коэффициентами n_ctrl, который интерполирует первую и последнюю входные точки по вашему запросу.
Входные данные x,y такие же, как в вашем примере, а параметр p обозначает степень сплайна, которая по умолчанию равна 3.
Параметр n_ctrl — это количество коэффициентов в результирующем B-сплайне, которое вы можете настроить под свои нужды. Обратите внимание, что по мере увеличения результата результат может переобуться.
Вызов функции, как показано ниже, при входе, с дополнительными точками (0,0) и (1,1) в начале и конце, а также с n_ctrl=6, приводит к следующему рисунку.
spline = bspline_least_square_with_clamped_endpoints(x, y, n_ctrl=6, p=3)
Реализация соответствует объяснению на веб-сайте.
from scipy.interpolate import BSpline
from scipy.linalg import solve
def bspline_least_square_with_clamped_endpoints(x, y, n_ctrl, p=3):
"""
Build a clamped BSpline least square approximation of the points (x, y) with
number of coefficients=n_ctrl and spline_degree=p.
The resulting BSpline will satisfy BSpline(x[0]) == y[0] and BSpline(x[-1]) == y[-1].
Based on the algorithm presented in: https://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/INT-APP/CURVE-APP-global.html
"""
# Build clamped knot vector between x[0] and x[-1], (simple) implementation here builds uniform inner knots
num_inner_knots_inc_end_pts = n_ctrl - p + 1 # including knots[p] and knots[-p-1]
inner_knots = np.linspace(x[0], x[-1], num_inner_knots_inc_end_pts)
knots = np.concatenate([np.array([x[0]] * p), inner_knots, np.array([x[-1]] * p)])
N = build_basis_functions_eval_matrix(knots, x, p)
Q = y - N[:, 0] * y[0] - N[:, -1] * y[-1]
# now remove first and last rows and columns since we only need rows 1 to n-1, columns 1 to h-1
N = N[1:-1, 1:-1]
Q = Q[1:-1]
c = solve((N.T).dot(N), (N.T).dot(Q))
c = np.concatenate([[y[0]], c, [y[-1]]]) # append the first and last values (as the interpolating end coefficients)
bs = BSpline(knots, c, p)
return bs
Текущая реализация создает вектор фиксированного узла с равномерно расположенными внутренними узлами. Существуют и другие варианты (которые могут привести к различным базисным функциям и, следовательно, к не совсем одинаковой результирующей кривой).
Основная алгоритмическая работа выполняется в функции build_basis_functions_eval_matrix(knots, x, p) ниже, которая строит матрицу N оценок базисной функции B-сплайна на входах. Форма матрицы равна (len(x), n_ctrl), а запись N[k, i] содержит оценку базисной функции N_i(x_k).
В своей реализации я использую тот факт, что i-я базисная функция B-сплайна может быть восстановлена из scipy.interpolate.BSpline, установив коэффициенты в [0,..,0,1,0,...,0] - см. мой ответ здесь, где обсуждается, как это делается.
def build_basis_functions_eval_matrix(knots, x, p=3):
""" Build N matrix, where N[k, i] contains the evaluation of basis function N_i(x_k)."""
n = len(knots) - p - 1 # number of ctrls == number of columns
m = len(x) # number of samples == number of rows
# Using the hack that scipy.interpolate.BSpline with coefficients [0,...,0, 1, 0,...,0]
# reconstructs the i'th basis function (see https://stackoverflow.com/a/77962609/9702190).
cols = []
for i in range(n):
c = np.zeros((n,))
c[i] = 1
N_i = BSpline(knots, c, p)
col = N_i(x)
cols.append(col.reshape((m, 1)))
N = np.hstack(cols)
return N
make_interp_spline должен позволять указывать «нулевые производные» для значений на границах. То, что сейчас это не удается, можно/должно исправить в scipy. Не стесняйтесь открывать проблему/отправлять запрос на включение в scipy.
Конечно, комментарий выше касается make_lsq_spline, а не make_interp_spline.
Спасибо, это было очень полезно. @ev-br: Я новичок, поэтому понятия не имею, как открыть задачу/отправить запрос на включение. Однако это не должно запрещать кому-то более знающему сделать это!
Существует более общий способ подгонки сплайна, который позволяет создать ограничение на значение сплайна в определенной точке. (Они ограничивают первую производную, но этого можно избежать, удалив
der=1.) Возможно, есть способ сделать это в сплайновом API, не уверен.