сигмоидальная регрессия с помощью scipy, numpy, python и др. - PullRequest
26 голосов
/ 30 ноября 2010

У меня есть две переменные (x и y), которые имеют несколько сигмоидальные отношения друг с другом, и мне нужно найти какое-то уравнение прогнозирования, которое позволит мне предсказать значение y при любом значении x. Мое уравнение прогнозирования должно показать несколько сигмоидальную связь между двумя переменными. Поэтому я не могу согласиться с уравнением линейной регрессии, которое производит линию. Мне нужно увидеть постепенное, криволинейное изменение наклона, которое происходит как справа, так и слева от графика двух переменных.

Я начал использовать numpy.polyfit после погугления криволинейной регрессии и Python, но это дало мне ужасные результаты, которые вы можете увидеть, запустив код ниже. Может кто-нибудь показать мне, как переписать код ниже, чтобы получить тип сигмоидального уравнения регрессии, который я хочу?

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

Как я могу изменить свой код, чтобы он наилучшим образом соответствовал сигмоидальной функции, предпочтительно с использованием scipy, numpy и python? Вот текущая версия моего кода, которая должно быть исправлено:

import numpy as np
import matplotlib.pyplot as plt

# Create numpy data arrays
x = np.array([821,576,473,377,326])
y = np.array([255,235,208,166,157])

# Use polyfit and poly1d to create the regression equation
z = np.polyfit(x, y, 3)
p = np.poly1d(z)
xp = np.linspace(100, 1600, 1500)
pxp=p(xp)

# Plot the results
plt.plot(x, y, '.', xp, pxp, '-')
plt.ylim(140,310)
plt.xlabel('x')
plt.ylabel('y')
plt.grid(True)
plt.show()

РЕДАКТИРОВАТЬ НИЖЕ: (переформулировал вопрос)

Ваш ответ и его скорость очень впечатляют. Спасибо, unutbu. Но, чтобы получить более достоверные результаты, мне нужно изменить структуру данных. Это означает повторное приведение значений x в процентах от максимального значения x, а повторное приведение значений y в процентах от значений x в исходных данных. Я попытался сделать это с вашим кодом и придумал следующее:

import numpy as np 
import matplotlib.pyplot as plt 
import scipy.optimize 

# Create numpy data arrays 
'''
# Comment out original data
#x = np.array([821,576,473,377,326]) 
#y = np.array([255,235,208,166,157]) 
'''

# Re-calculate x values as a percentage of the first (maximum)
# original x value above
x = np.array([1.000,0.702,0.576,0.459,0.397])

# Recalculate y values as a percentage of their respective x values
# from original data above
y = np.array([0.311,0.408,0.440,0.440,0.482])

def sigmoid(p,x): 
    x0,y0,c,k=p 
    y = c / (1 + np.exp(-k*(x-x0))) + y0 
    return y 

def residuals(p,x,y): 
    return y - sigmoid(p,x) 

p_guess=(600,200,100,0.01) 
(p,  
 cov,  
 infodict,  
 mesg,  
 ier)=scipy.optimize.leastsq(residuals,p_guess,args=(x,y),full_output=1,warning=True)  

'''
# comment out original xp to allow for better scaling of
# new values
#xp = np.linspace(100, 1600, 1500) 
'''

xp = np.linspace(0, 1.1, 1100) 
pxp=sigmoid(p,xp) 

x0,y0,c,k=p 
print('''\ 
x0 = {x0}
y0 = {y0}
c = {c}
k = {k}
'''.format(x0=x0,y0=y0,c=c,k=k)) 

# Plot the results 
plt.plot(x, y, '.', xp, pxp, '-') 
plt.ylim(0,1) 
plt.xlabel('x') 
plt.ylabel('y') 
plt.grid(True) 
plt.show()

Можете ли вы показать мне, как исправить этот исправленный код?
ПРИМЕЧАНИЕ. При повторном приведении данных я по существу повернул сигмоиду 2d (x, y) вокруг оси z на 180 градусов. Кроме того, 1.000 на самом деле не максимум значений х. Вместо этого 1.000 - это среднее значение диапазона значений от разных участников теста в максимальных условиях теста.


ВТОРОЕ РЕДАКТИРОВАНИЕ НИЖЕ:

Спасибо, Ubuntu. Я внимательно прочитал ваш код и посмотрел его аспекты в документации scipy. Поскольку ваше имя, похоже, всплывало как автор документации, я надеюсь, что вы можете ответить на следующие вопросы:

1.) Вызывает ли leastsq () функцию residuals (), которая затем возвращает разницу между входным y-вектором и y-вектором, возвращаемым функцией sigmoid ()? Если да, то как он учитывает разницу в длине входного y-вектора и y-вектора, возвращаемого функцией sigmoid ()?

2.) Похоже, я могу вызвать leastsq () для любого математического уравнения, если я получаю доступ к этому математическому уравнению через функцию невязок, которая, в свою очередь, вызывает математическую функцию. Это правда?

3.) Также я заметил, что p_guess имеет то же количество элементов, что и p. Означает ли это, что четыре элемента p_guess соответствуют по порядку соответственно значениям, возвращаемым x0, y0, c и k?

4.) Является ли p, переданный в качестве аргумента функциям residuals () и sigmoid (), тем же p, который будет выведен функцией leastsq (), а функция leastsq () использует этот p перед возвратом это?

5.) Могут ли p и p_guess иметь любое количество элементов в зависимости от сложности уравнения, используемого в качестве модели, при условии, что количество элементов в p равно количеству элементов в p_guess?

Ответы [ 4 ]

36 голосов
/ 30 ноября 2010

Использование scipy.optimize.leastsq :

import numpy as np
import matplotlib.pyplot as plt
import scipy.optimize

def sigmoid(p,x):
    x0,y0,c,k=p
    y = c / (1 + np.exp(-k*(x-x0))) + y0
    return y

def residuals(p,x,y):
    return y - sigmoid(p,x)

def resize(arr,lower=0.0,upper=1.0):
    arr=arr.copy()
    if lower>upper: lower,upper=upper,lower
    arr -= arr.min()
    arr *= (upper-lower)/arr.max()
    arr += lower
    return arr

# raw data
x = np.array([821,576,473,377,326],dtype='float')
y = np.array([255,235,208,166,157],dtype='float')

x=resize(-x,lower=0.3)
y=resize(y,lower=0.3)
print(x)
print(y)
p_guess=(np.median(x),np.median(y),1.0,1.0)
p, cov, infodict, mesg, ier = scipy.optimize.leastsq(
    residuals,p_guess,args=(x,y),full_output=1,warning=True)  

x0,y0,c,k=p
print('''\
x0 = {x0}
y0 = {y0}
c = {c}
k = {k}
'''.format(x0=x0,y0=y0,c=c,k=k))

xp = np.linspace(0, 1.1, 1500)
pxp=sigmoid(p,xp)

# Plot the results
plt.plot(x, y, '.', xp, pxp, '-')
plt.xlabel('x')
plt.ylabel('y',rotation='horizontal') 
plt.grid(True)
plt.show()

выход

alt text

с сигмовидными параметрами

x0 = 0.826964424481
y0 = 0.151506745435
c = 0.848564826467
k = -9.54442292022

Обратите внимание, что для более новых версий scipy (например, 0.9) есть также функция scipy.optimize.curve_fit , которая проще в использовании, чем leastsq.Соответствующее обсуждение подбора сигмоидов с использованием curve_fit можно найти здесь .

Редактировать: добавлена ​​функция resize, так что необработанные данные можно было изменить и изменить в соответствии с любым желаемымограничивающий прямоугольник.


"ваше имя появляется как автор документации scipy"

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я не автор документации scipy.Я просто пользователь и новичок в этом.Многое из того, что я знаю о leastsq, пришло от прочтения этого урока , написанного Трэвисом Олифантом.

1.) Вызывает ли leastsq () функцию residuals (), которая затем возвращаетразница между входным y-вектором и y-вектором, возвращаемым функцией sigmoid ()?

Да!точно.

Если это так, как он учитывает разницу в длине входного y-вектора и y-вектора, возвращаемого функцией sigmoid ()?

Длины одинаковы:

In [138]: x
Out[138]: array([821, 576, 473, 377, 326])

In [139]: y
Out[139]: array([255, 235, 208, 166, 157])

In [140]: p=(600,200,100,0.01)

In [141]: sigmoid(p,x)
Out[141]: 
array([ 290.11439268,  244.02863507,  221.92572521,  209.7088641 ,
        206.06539033])

Одна из замечательных особенностей Numpy заключается в том, что он позволяет писать «векторные» уравнения, которые оперируют целыми массивами.

y = c / (1 + np.exp(-k*(x-x0))) + y0

может показаться, что он работает с плавающей точкой (на самом деле так и будет), но если вы сделаете x массив с пустым фрагментом и c, k, x0, y0 с плавающей точкой, то уравнение определяет y какмассив numpy такой же формы, как x.Таким образом, sigmoid(p,x) возвращает пустой массив.Существует более полное объяснение того, как это работает в numpybook (требуется чтение для серьезных пользователей numpy).

2.) Похоже, я могу вызвать leastsq ()для любого математического уравнения, пока я получаю доступ к этому математическому уравнению через функцию невязок, которая в свою очередь вызывает математическую функцию.Это правда?

Правда.leastsq пытается минимизировать сумму квадратов остатков (разностей).Он ищет пространство параметров (все возможные значения p), ища p, который минимизирует эту сумму квадратов.x и y, отправленные на residuals, являются вашими значениями необработанных данных.Они исправлены.Они не меняются.Это p s (параметры в сигмоидальной функции), которые leastsq пытается минимизировать.

3.) Также я заметил, что p_guess имеет то же количество элементов, что и p.Означает ли это, что четыре элемента p_guess соответствуют по порядку соответственно значениям, возвращаемым x0, y0, c и k?

Точно так же!Как и метод Ньютона, leastsq требуется начальное предположение для p.Вы поставляете это как p_guess.Когда вы видите

scipy.optimize.leastsq(residuals,p_guess,args=(x,y))

, вы можете думать, что как часть алгоритма leastsq (на самом деле алгоритм Левенбурга-Марквардта) в качестве первого прохода, leastsq вызывает residuals(p_guess,x,y).Обратите внимание на визуальное сходство между

(residuals,p_guess,args=(x,y))

и

residuals(p_guess,x,y)

Это может помочь вам вспомнить порядок и значение аргументов для leastsq.

residuals, например, sigmoid возвращает пустой массив.Значения в массиве возводятся в квадрат, а затем суммируются.Это число, чтобы бить.p_guess затем изменяется, так как leastsq ищет набор значений, который минимизирует residuals(p_guess,x,y).

4.) Является ли p, который отправляется в качестве аргумента для residuals () и sigmoid() работает с тем же p, который будет выводиться функцией leastsq (), а функция leastsq () использует это p внутренне, прежде чем его вернуть?

Ну, не совсем так.Как вы уже знаете, p_guess изменяется, так как leastsq ищет значение p, которое минимизирует residuals(p,x,y).p (er, p_guess), который отправляется на leastsq, имеет ту же форму, что и p, который возвращается leastsq.Очевидно, что значения должны отличаться, если вы не чертовски догадайтесь:)

5.) Может ли p и p_guess иметь любое количество элементов, в зависимости отсложность уравнения, используемого в качестве модели, при условии, что количество элементов в p равно количеству элементов в p_guess?

Да.Я не тестировал стресс leastsq для очень большого числа параметров, но это потрясающе мощный инструмент.

2 голосов
/ 30 ноября 2010

Я не думаю, что вы получите хорошие результаты с полиномиальной подгонкой любой степени - поскольку все полиномы уходят в бесконечность для достаточно большого и малого X, но сигмоидальная кривая будет асимптотически приближаться к некоторому конечному значению в каждомdirection.

Я не программист на Python, поэтому я не знаю, есть ли у numpy более общая процедура подбора кривой.Если вам нужно создать свою собственную, возможно, эта статья о Логистическая регрессия даст вам несколько идей.

1 голос
/ 04 сентября 2018

Как указывает @unutbu выше scipy теперь предоставляет scipy.optimize.curve_fit , которые обладают менее сложным вызовом.Если кому-то нужна быстрая версия того, как тот же процесс будет выглядеть в этих терминах, я приведу ниже минимальный пример:

def sigmoid(x, k, x0):

    return 1.0 / (1 + np.exp(-k * (x - x0)))

# Parameters of the true function
n_samples = 1000
true_x0 = 15
true_k = 1.5
sigma = 0.2

# Build the true function and add some noise
x = np.linspace(0, 30, num=n_samples)
y = sigmoid(x, k=true_k, x0=true_x0) 
y_with_noise = y + sigma * np.random.randn(n_samples)

# Sample the data from the real function (this will be your data)
some_points = np.random.choice(1000, size=30)  # take 30 data points
xdata = x[some_points]
ydata = y_with_noise[some_points]

# Fit the curve
popt, pcov = curve_fit(return_sigmoid, xdata, ydata)
estimated_k, estimated_x0 = popt

# Plot the fitted curve
y_fitted = sigmoid(x, k=estimated_k, x0=estimated_x0)

# Plot everything for illustration
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, y_fitted, '--', label='fitted')
ax.plot(x, y, '-', label='true')
ax.plot(xdata, ydata, 'o', label='samples')

ax.legend()

Результат этого показан на следующем рисунке:

enter image description here

1 голос
/ 28 апреля 2011

Для логистической регрессии в Python scikits-learn предоставляет высокопроизводительный код подбора:

http://scikit -learn.sourceforge.net / модули / linear_model.html # логистической регрессии

...