Я пытаюсь создать собственную функцию сигмовидной формы, потому что хочу масштабировать данные во время предварительной обработки. По сути, цель состоит в том, чтобы получить функцию сигмовидной формы, которая выводит значения от 0 до 1 и принимает только положительные входные значения (она приближается к 0, когда входные данные приближаются к 0, и к 1, если входные данные приближаются к + бесконечности). Ключевым моментом является то, что я хочу иметь возможность выбирать точки перегиба S-образной формы по своему желанию. У меня есть небольшой набросок (простите за умение рисовать) .
Точки, которые я хочу выбрать, отмечены как A и B, и в идеале они находятся где-то посередине кривой, соединяющей линейную часть функции к асимптотам.
Вот как я это сделал на данный момент; Я попытался вписать классическую логистику функция в двух точках.
Вот функция =:
def sigmoid(x,x0, k):
y = 1 / (1 + np.exp(-k * (x - x0)))
return y
И вот подходит:
ydata = [0.1, 0.9]
xdata = [0.22, 1.34]
p0 = [np.median(xdata), 1] # this is a mandatory initial guess
from scipy.optimize import curve_fit
popt, pcov = curve_fit(sigmoid, xdata, ydata, p0=p0, method='dogbox')
Здесь xdata соответствует точкам A и B на оси x (которые я хочу иметь возможность изменять), а ydata — это произвольные точки, на которые я хочу сопоставить A и B, чтобы они примерно находились в точках перегиба оси. S-кривая (возможно, я не знаю, есть ли лучший способ сделать это).
Далее сюжет:
x = np.linspace(0, 5, 1000)
y = sigmoid(x,*popt)
plt.figure()
plt.plot(xdata, ydata, 'o', label='10th/90th percentiles')
plt.plot(x, y, label='sigmoid curve')
plt.ylim(0, 1.3)
plt.legend(loc='best')
plt.show()
дает цифру: (игнорируйте метку процентилей в легенде, это мои баллы A/b)
Это не очень хорошая форма. Особенно к 0, переход не такой плавный и постепенный. Я бы хотел сдвинуть функцию вправо, чтобы кривая была более плавной, но при этом перехватывать точки A и B в точках перегиба. Есть ли у вас какие-либо предложения о том, как мне этого добиться? Добавление сдвига в определение сигмовидной функции не сработает, поскольку смещение будет просто перезаписано аппроксимацией кривой. Есть ли более разумный способ решить эту проблему, чем мой подход, которого я не вижу?
Вам нужно использовать стандартную сигмовидную функцию? Пробовали ли вы что-то вроде плавной версии сдвинутой абсолютной функции?
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-2,5,100)
a = 1
f = lambda x, eps : ((x-a)/np.sqrt((x-a)**2+eps) + 1)/2
plt.figure()
for eps in np.logspace(-3,0,4):
plt.plot(x,f(x,eps),"--", label = str(eps))
plt.legend()
plt.show()
Возможно, я неправильно понял вашу проблему. Кажется, что эта кривая растягивает ее еще больше (по крайней мере, для ваших A и B), но я не уверен, сможете ли вы растянуть ее еще дальше, поскольку у вас есть только две степени свободы и две точки, которые вы хотите перехватить, пока принуждение хвостов к 0 и 1. Для моей маленькой функции «a» будет значением x для точки, где вторая производная равна нулю, а eps заставит хвост работать (увеличение отталкивает его), но вы не будете иметь возможность менять эпсилон без потери эффекта интерполяции для точек А и В.
Проблема вашей модели в ее симметрии. Для вашего набора данных требуется сильная асимметричная сигмоида.
import numpy as np
import matplotlib.pyplot as plt
from scipy import optimize, special, stats
Давайте добавим к вашим данным две почти безобидные точки, чтобы иметь достаточно точек для подгонки как минимум двух параметров и обеспечить прохождение кривой мимо начала координат.
ydata = [0, 0.1, 0.9, 1]
xdata = [0, 0.22, 1.34, 5]
p0 = [np.median(xdata), 1.] # this is a mandatory initial guess
sigma=[0.1, 0.01, 0.01, 100]
Мы также добавляем веса (сигмы), чтобы отдать приоритет вашим точкам и началу координат.
Теперь мы можем сравнить вашу модель (которая симметрична):
def model1(x, k, x0):
return special.expit(k * (x - x0))
popt1, pcov1 = optimize.curve_fit(model1, xdata, ydata, p0=p0, sigma=sigma)
# array([3.92654466, 0.78030023]
С двумя асимметричными сигмоидами (хорошим кандидатом для этого являются CDF асимметричных распределений). Мы соответственно выбрали: распределения Вейбулла и логарифмически нормальное:
def model2(x, c, loc):
return stats.invweibull(c=c, loc=loc).cdf(x)
popt2, pcov2 = optimize.curve_fit(model2, xdata, ydata, p0=p0, sigma=sigma)
# array([ 3.48553148, -0.56719092]
def model3(x, s, loc):
return stats.lognorm(s=s, loc=loc).cdf(x)
popt3, pcov3 = optimize.curve_fit(model3, xdata, ydata, p0=p0, sigma=sigma)
# array([ 0.41684656, -0.36610887]
Он отображается следующим образом:
Нет, мне не обязательно использовать стандартную функцию, мне подходит любая функция, подобная той, что вы предложили. Но как мне тогда выбрать точки перегиба? Думаю, я не понимаю, как это могло бы решить мою проблему, хотя форма, возможно, лучше.