Фиксация граничных значений на сплайне?

У меня есть данные 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()

И получившийся график, на котором довольно хорошо видно, что сплайн обеспечивает плохое приближение около нуля:

Существует более общий способ подгонки сплайна, который позволяет создать ограничение на значение сплайна в определенной точке. (Они ограничивают первую производную, но этого можно избежать, удалив der=1.) Возможно, есть способ сделать это в сплайновом API, не уверен.

Nick ODell 15.05.2024 17:31

Большое спасибо - я попробую. (Я видел этот ответ, но думал, что он предназначен только для 1-й и более высоких производных.)

Geordie Williamson 15.05.2024 21:00

Можете ли вы включить для этого свой существующий код?

Nick ODell 15.05.2024 21:21

Я добавил простой пример. (Функция, которую я на самом деле оцениваю, довольно сложна, и для ее включения потребуется около 100 строк...)

Geordie Williamson 15.05.2024 21:47
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
4
115
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Одна из идей для подбора этой функции — сказать, что 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, но этот подход намного чище.

Geordie Williamson 16.05.2024 00:59

Одна из проблем с вышеизложенным заключается в том, что мне бы хотелось, чтобы результат гарантированно принимал значение 0 при 0, 1 и альфа. (Я аппроксимирую около 5000 таких функций, а затем мне нужно найти фиксированное значение от 0 до 1. Иногда это значение близко к 0 или 1, и я хочу всегда возвращать значение.) В любом случае, Я ценю это предложение и, вероятно, в конечном итоге воспользуюсь им вместе с предложением try, кроме...

Geordie Williamson 16.05.2024 02:56

Как я сказал в комментарии к другому ответу: make_interp_spline должен иметь возможность принимать нулевую производную в качестве граничного значения. То, что это не так, является банальным запросом на улучшение, и исправление будет приветствоваться.

ev-br 17.05.2024 09:24
Ответ принят как подходящий

Вам нужна функция, которая создает фиксированный 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.
ev-br 17.05.2024 07:27

Конечно, комментарий выше касается make_lsq_spline, а не make_interp_spline.

ev-br 18.05.2024 18:58

Спасибо, это было очень полезно. @ev-br: Я новичок, поэтому понятия не имею, как открыть задачу/отправить запрос на включение. Однако это не должно запрещать кому-то более знающему сделать это!

Geordie Williamson 20.05.2024 03:36

Другие вопросы по теме