Làm thế nào để thực hiện khớp đường cong hàm mũ và logarit trong Python? Tôi chỉ tìm thấy sự phù hợp đa thức


157

Tôi có một bộ dữ liệu và tôi muốn so sánh dòng nào mô tả nó tốt nhất (đa thức của các đơn hàng khác nhau, hàm mũ hoặc logarit).

Tôi sử dụng Python và Numpy và để phù hợp đa thức có một hàm polyfit(). Nhưng tôi không tìm thấy các chức năng như vậy để phù hợp theo cấp số nhân và logarit.

Có ai không Hoặc làm thế nào để giải quyết nó khác?

Câu trả lời:


222

Để khớp y = A + B log x , chỉ cần khớp y với (log x ).

>>> x = numpy.array([1, 7, 20, 50, 79])
>>> y = numpy.array([10, 19, 30, 35, 51])
>>> numpy.polyfit(numpy.log(x), y, 1)
array([ 8.46295607,  6.61867463])
# y ≈ 8.46 log(x) + 6.62

Để phù hợp với y = Ae Bx , lấy logarit của cả hai bên đưa ra log y = log A + Bx . Vì vậy, phù hợp (log y ) so với x .

Lưu ý rằng khớp (log y ) như thể nó là tuyến tính sẽ nhấn mạnh các giá trị nhỏ của y , gây ra độ lệch lớn cho y lớn . Điều này là do polyfit(hồi quy tuyến tính) hoạt động bằng cách giảm thiểu Σ iY ) 2 = Σ i ( Y i - y i ) 2 . Khi Y i = log y i , phần dư Y i = Δ (log y i ) Δ y i / | y i |. Vì vậy, ngay cả khipolyfitđưa ra một quyết định rất tồi tệ cho y lớn , "chia cho | | y |" yếu tố sẽ bù đắp cho nó, gây ra polyfitnhững giá trị nhỏ.

Điều này có thể được giảm bớt bằng cách cho mỗi mục nhập "trọng lượng" tỷ lệ với y . polyfithỗ trợ bình phương có trọng số nhỏ nhất thông qua wđối số từ khóa.

>>> x = numpy.array([10, 19, 30, 35, 51])
>>> y = numpy.array([1, 7, 20, 50, 79])
>>> numpy.polyfit(x, numpy.log(y), 1)
array([ 0.10502711, -0.40116352])
#    y ≈ exp(-0.401) * exp(0.105 * x) = 0.670 * exp(0.105 * x)
# (^ biased towards small values)
>>> numpy.polyfit(x, numpy.log(y), 1, w=numpy.sqrt(y))
array([ 0.06009446,  1.41648096])
#    y ≈ exp(1.42) * exp(0.0601 * x) = 4.12 * exp(0.0601 * x)
# (^ not so biased)

Lưu ý rằng Excel, LibreScript và hầu hết các máy tính khoa học thường sử dụng công thức không trọng số (thiên vị) cho các đường hồi quy / xu hướng theo cấp số nhân. Nếu bạn muốn kết quả của mình tương thích với các nền tảng này, không bao gồm các trọng số ngay cả khi nó cung cấp kết quả tốt hơn.


Bây giờ, nếu bạn có thể sử dụng scipy, bạn có thể sử dụng scipy.optimize.curve_fitđể phù hợp với bất kỳ mô hình nào mà không cần chuyển đổi.

Với y = A + B log x kết quả giống như phương thức biến đổi:

>>> x = numpy.array([1, 7, 20, 50, 79])
>>> y = numpy.array([10, 19, 30, 35, 51])
>>> scipy.optimize.curve_fit(lambda t,a,b: a+b*numpy.log(t),  x,  y)
(array([ 6.61867467,  8.46295606]), 
 array([[ 28.15948002,  -7.89609542],
        [ -7.89609542,   2.9857172 ]]))
# y ≈ 6.62 + 8.46 log(x)

Tuy nhiên, đối với y = Ae Bx , chúng ta có thể có sự phù hợp tốt hơn vì nó tính (log y ) trực tiếp. Nhưng chúng tôi cần cung cấp một dự đoán khởi tạo để curve_fitcó thể đạt đến mức tối thiểu cục bộ mong muốn.

>>> x = numpy.array([10, 19, 30, 35, 51])
>>> y = numpy.array([1, 7, 20, 50, 79])
>>> scipy.optimize.curve_fit(lambda t,a,b: a*numpy.exp(b*t),  x,  y)
(array([  5.60728326e-21,   9.99993501e-01]),
 array([[  4.14809412e-27,  -1.45078961e-08],
        [ -1.45078961e-08,   5.07411462e+10]]))
# oops, definitely wrong.
>>> scipy.optimize.curve_fit(lambda t,a,b: a*numpy.exp(b*t),  x,  y,  p0=(4, 0.1))
(array([ 4.88003249,  0.05531256]),
 array([[  1.01261314e+01,  -4.31940132e-02],
        [ -4.31940132e-02,   1.91188656e-04]]))
# y ≈ 4.88 exp(0.0553 x). much better.

so sánh hồi quy theo cấp số nhân


2
@Tomas: Phải. Thay đổi cơ sở của nhật ký chỉ cần nhân một hằng số thành log x hoặc log y, điều này không ảnh hưởng đến r ^ 2.
kennytm

4
Điều này sẽ cho trọng số lớn hơn cho các giá trị ở y nhỏ. Do đó, tốt hơn là đóng góp trọng số cho các giá trị chi bình phương theo y_i
Rupert Nash

17
Giải pháp này là sai trong ý nghĩa truyền thống của phù hợp đường cong. Nó sẽ không giảm thiểu bình phương tổng của phần dư trong không gian tuyến tính, nhưng trong không gian nhật ký. Như đã đề cập trước đây, điều này thay đổi hiệu quả trọng số của các điểm - các quan sát ở nơi ynhỏ sẽ bị thừa cân một cách giả tạo. Tốt hơn là xác định hàm (tuyến tính, không phải là chuyển đổi nhật ký) và sử dụng bộ tạo đường cong hoặc bộ thu nhỏ.
Santon

3
@santon Đã giải quyết sự thiên vị trong hồi quy theo cấp số nhân.
kennytm

2
Cảm ơn bạn đã thêm trọng lượng! Nhiều / hầu hết mọi người không biết rằng bạn có thể nhận được kết quả xấu về mặt hài hước nếu bạn cố gắng chỉ lấy nhật ký (dữ liệu) và chạy một dòng qua nó (như Excel). Giống như tôi đã làm trong nhiều năm. Khi giáo viên Bayes của tôi chỉ cho tôi điều này, tôi giống như "Nhưng họ không dạy cách [sai] trong vật lý sao?" - "Vâng, chúng tôi gọi đó là 'vật lý trẻ em', đó là một sự đơn giản hóa. Đây là cách chính xác để làm điều đó".
DeusXMachina

102

Bạn cũng có thể phù hợp với một tập hợp các dữ liệu để bất cứ điều gì bạn có chức năng như sử dụng curve_fittừ scipy.optimize. Ví dụ: nếu bạn muốn phù hợp với hàm số mũ (từ tài liệu ):

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

def func(x, a, b, c):
    return a * np.exp(-b * x) + c

x = np.linspace(0,4,50)
y = func(x, 2.5, 1.3, 0.5)
yn = y + 0.2*np.random.normal(size=len(x))

popt, pcov = curve_fit(func, x, yn)

Và sau đó nếu bạn muốn vẽ, bạn có thể làm:

plt.figure()
plt.plot(x, yn, 'ko', label="Original Noised Data")
plt.plot(x, func(x, *popt), 'r-', label="Fitted Curve")
plt.legend()
plt.show()

(Lưu ý: *trước poptkhi bạn vẽ sẽ mở rộng ra các điều khoản vào a, bcrằng func. Dự kiến)


2
Đẹp. Có cách nào để kiểm tra mức độ phù hợp của chúng tôi không? Giá trị bình phương R? Có các tham số thuật toán tối ưu hóa khác nhau mà bạn có thể cố gắng để có được một giải pháp tốt hơn (hoặc nhanh hơn) không?
dùng391339

Để đảm bảo sự phù hợp, bạn có thể ném các tham số được tối ưu hóa được trang bị vào chức năng tối ưu hóa scipy; nó trả về 2 giá trị, giá trị thứ 2 là giá trị p.

Bất kỳ ý tưởng về làm thế nào để chọn các thông số a, bc?
I_told_you_so

47

Tôi đã gặp một số rắc rối với điều này vì vậy hãy để tôi rất rõ ràng để những người như tôi có thể hiểu.

Hãy nói rằng chúng tôi có một tệp dữ liệu hoặc một cái gì đó tương tự

# -*- coding: utf-8 -*-

import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
import numpy as np
import sympy as sym

"""
Generate some data, let's imagine that you already have this. 
"""
x = np.linspace(0, 3, 50)
y = np.exp(x)

"""
Plot your data
"""
plt.plot(x, y, 'ro',label="Original Data")

"""
brutal force to avoid errors
"""    
x = np.array(x, dtype=float) #transform your data in a numpy array of floats 
y = np.array(y, dtype=float) #so the curve_fit can work

"""
create a function to fit with your data. a, b, c and d are the coefficients
that curve_fit will calculate for you. 
In this part you need to guess and/or use mathematical knowledge to find
a function that resembles your data
"""
def func(x, a, b, c, d):
    return a*x**3 + b*x**2 +c*x + d

"""
make the curve_fit
"""
popt, pcov = curve_fit(func, x, y)

"""
The result is:
popt[0] = a , popt[1] = b, popt[2] = c and popt[3] = d of the function,
so f(x) = popt[0]*x**3 + popt[1]*x**2 + popt[2]*x + popt[3].
"""
print "a = %s , b = %s, c = %s, d = %s" % (popt[0], popt[1], popt[2], popt[3])

"""
Use sympy to generate the latex sintax of the function
"""
xs = sym.Symbol('\lambda')    
tex = sym.latex(func(xs,*popt)).replace('$', '')
plt.title(r'$f(\lambda)= %s$' %(tex),fontsize=16)

"""
Print the coefficients and plot the funcion.
"""

plt.plot(x, func(x, *popt), label="Fitted Curve") #same as line above \/
#plt.plot(x, popt[0]*x**3 + popt[1]*x**2 + popt[2]*x + popt[3], label="Fitted Curve") 

plt.legend(loc='upper left')
plt.show()

kết quả là: a = 0.849195983017, b = -1.18101681765, c = 2.24061176543, d = 0.816643894816

Dữ liệu thô và chức năng được trang bị


8
y = [np.exp(i) for i in x]Rất chậm; Một lý do numpy đã được tạo ra là để bạn có thể viết y=np.exp(x). Ngoài ra, với sự thay thế đó, bạn có thể thoát khỏi phần lực lượng tàn bạo của bạn. Trong ipython, có %timeitphép thuật từ đó In [27]: %timeit ylist=[exp(i) for i in x] 10000 loops, best of 3: 172 us per loop In [28]: %timeit yarr=exp(x) 100000 loops, best of 3: 2.85 us per loop
diễn ra vào

1
Cảm ơn bạn, bạn đã đúng, nhưng phần tàn bạo tôi vẫn cần sử dụng khi tôi xử lý dữ liệu từ csv, xls hoặc các định dạng khác mà tôi đã sử dụng thuật toán này. Tôi nghĩ rằng việc sử dụng nó chỉ có ý nghĩa khi ai đó đang cố gắng khớp một chức năng từ dữ liệu thử nghiệm hoặc mô phỏng, và theo kinh nghiệm của tôi, dữ liệu này luôn có các định dạng lạ.
Leandro

3
x = np.array(x, dtype=float)sẽ cho phép bạn thoát khỏi sự hiểu biết danh sách chậm.
Ajasja

8

Tôi đoán bạn luôn có thể sử dụng:

np.log   -->  natural log
np.log10 -->  base 10
np.log2  -->  base 2

Hơi sửa đổi câu trả lời của IanVS :

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

def func(x, a, b, c):
  #return a * np.exp(-b * x) + c
  return a * np.log(b * x) + c

x = np.linspace(1,5,50)   # changed boundary conditions to avoid division by 0
y = func(x, 2.5, 1.3, 0.5)
yn = y + 0.2*np.random.normal(size=len(x))

popt, pcov = curve_fit(func, x, yn)

plt.figure()
plt.plot(x, yn, 'ko', label="Original Noised Data")
plt.plot(x, func(x, *popt), 'r-', label="Fitted Curve")
plt.legend()
plt.show()

Kết quả này trong biểu đồ sau:

nhập mô tả hình ảnh ở đây


Có một giá trị bão hòa phù hợp gần đúng? Nếu vậy, làm thế nào có thể truy cập nó?
Ben

7

Đây là một tùy chọn tuyến tính hóa trên dữ liệu đơn giản sử dụng các công cụ từ scikit learn .

Được

import numpy as np

import matplotlib.pyplot as plt

from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import FunctionTransformer


np.random.seed(123)

# General Functions
def func_exp(x, a, b, c):
    """Return values from a general exponential function."""
    return a * np.exp(b * x) + c


def func_log(x, a, b, c):
    """Return values from a general log function."""
    return a * np.log(b * x) + c


# Helper
def generate_data(func, *args, jitter=0):
    """Return a tuple of arrays with random data along a general function."""
    xs = np.linspace(1, 5, 50)
    ys = func(xs, *args)
    noise = jitter * np.random.normal(size=len(xs)) + jitter
    xs = xs.reshape(-1, 1)                                  # xs[:, np.newaxis]
    ys = (ys + noise).reshape(-1, 1)
    return xs, ys
transformer = FunctionTransformer(np.log, validate=True)

Phù hợp với dữ liệu số mũ

# Data
x_samp, y_samp = generate_data(func_exp, 2.5, 1.2, 0.7, jitter=3)
y_trans = transformer.fit_transform(y_samp)             # 1

# Regression
regressor = LinearRegression()
results = regressor.fit(x_samp, y_trans)                # 2
model = results.predict
y_fit = model(x_samp)

# Visualization
plt.scatter(x_samp, y_samp)
plt.plot(x_samp, np.exp(y_fit), "k--", label="Fit")     # 3
plt.title("Exponential Fit")

nhập mô tả hình ảnh ở đây

Dữ liệu nhật ký phù hợp

# Data
x_samp, y_samp = generate_data(func_log, 2.5, 1.2, 0.7, jitter=0.15)
x_trans = transformer.fit_transform(x_samp)             # 1

# Regression
regressor = LinearRegression()
results = regressor.fit(x_trans, y_samp)                # 2
model = results.predict
y_fit = model(x_trans)

# Visualization
plt.scatter(x_samp, y_samp)
plt.plot(x_samp, y_fit, "k--", label="Fit")             # 3
plt.title("Logarithmic Fit")

nhập mô tả hình ảnh ở đây


Chi tiết

Các bước chung

  1. Áp dụng một hoạt động đăng nhập vào các giá trị dữ liệu ( x, yhoặc cả hai)
  2. Hồi quy dữ liệu thành mô hình tuyến tính
  3. Vẽ sơ đồ bằng cách "đảo ngược" mọi hoạt động nhật ký (với np.exp()) và phù hợp với dữ liệu gốc

Giả sử dữ liệu của chúng tôi theo xu hướng theo cấp số nhân, phương trình tổng quát + có thể là:

nhập mô tả hình ảnh ở đây

Chúng ta có thể tuyến tính hóa phương trình sau (ví dụ y = chặn + dốc * x) bằng cách lấy nhật ký :

nhập mô tả hình ảnh ở đây

Cho một phương trình tuyến tính ++ và các tham số hồi quy, chúng ta có thể tính toán:

  • Athông qua đánh chặn ( ln(A))
  • Bqua độ dốc ( B)

Tóm tắt kỹ thuật tuyến tính hóa

Relationship |  Example   |     General Eqn.     |  Altered Var.  |        Linearized Eqn.  
-------------|------------|----------------------|----------------|------------------------------------------
Linear       | x          | y =     B * x    + C | -              |        y =   C    + B * x
Logarithmic  | log(x)     | y = A * log(B*x) + C | log(x)         |        y =   C    + A * (log(B) + log(x))
Exponential  | 2**x, e**x | y = A * exp(B*x) + C | log(y)         | log(y-C) = log(A) + B * x
Power        | x**2       | y =     B * x**N + C | log(x), log(y) | log(y-C) = log(B) + N * log(x)

+ Lưu ý: tuyến tính hóa các hàm số mũ hoạt động tốt nhất khi nhiễu nhỏ và C = 0. Sử dụng cẩn thận.

++ Lưu ý: trong khi thay đổi dữ liệu x giúp tuyến tính hóa dữ liệu theo cấp số nhân , thay đổi dữ liệu y giúp tuyến tính hóa dữ liệu nhật ký .


0

Chúng tôi chứng minh các tính năng lmfittrong khi giải quyết cả hai vấn đề.

Được

import lmfit

import numpy as np

import matplotlib.pyplot as plt


%matplotlib inline
np.random.seed(123)

# General Functions
def func_log(x, a, b, c):
    """Return values from a general log function."""
    return a * np.log(b * x) + c


# Data
x_samp = np.linspace(1, 5, 50)
_noise = np.random.normal(size=len(x_samp), scale=0.06)
y_samp = 2.5 * np.exp(1.2 * x_samp) + 0.7 + _noise
y_samp2 = 2.5 * np.log(1.2 * x_samp) + 0.7 + _noise

Cách tiếp cận 1 - lmfitMô hình

Phù hợp với dữ liệu số mũ

regressor = lmfit.models.ExponentialModel()                # 1    
initial_guess = dict(amplitude=1, decay=-1)                # 2
results = regressor.fit(y_samp, x=x_samp, **initial_guess)
y_fit = results.best_fit    

plt.plot(x_samp, y_samp, "o", label="Data")
plt.plot(x_samp, y_fit, "k--", label="Fit")
plt.legend()

nhập mô tả hình ảnh ở đây

Cách tiếp cận 2 - Mô hình tùy chỉnh

Dữ liệu nhật ký phù hợp

regressor = lmfit.Model(func_log)                          # 1
initial_guess = dict(a=1, b=.1, c=.1)                      # 2
results = regressor.fit(y_samp2, x=x_samp, **initial_guess)
y_fit = results.best_fit

plt.plot(x_samp, y_samp2, "o", label="Data")
plt.plot(x_samp, y_fit, "k--", label="Fit")
plt.legend()

nhập mô tả hình ảnh ở đây


Chi tiết

  1. Chọn một lớp hồi quy
  2. Cung có tên, dự đoán ban đầu tôn trọng miền của hàm

Bạn có thể xác định các tham số được suy ra từ đối tượng hồi quy. Thí dụ:

regressor.param_names
# ['decay', 'amplitude']

Lưu ý: hàm ExponentialModel()sau phân rã , chấp nhận hai tham số, một trong số đó là âm.

nhập mô tả hình ảnh ở đây

Xem thêm ExponentialGaussianModel(), chấp nhận nhiều tham số hơn .

Cài đặt thư viện thông qua > pip install lmfit.


0

Wolfram có một giải pháp dạng kín để phù hợp với cấp số nhân . Họ cũng có các giải pháp tương tự để phù hợp với luật logaritquyền lực .

Tôi thấy điều này hoạt động tốt hơn so với cur_fit của scipy. Đây là một ví dụ:

import numpy as np
import matplotlib.pyplot as plt

# Fit the function y = A * exp(B * x) to the data
# returns (A, B)
# From: https://mathworld.wolfram.com/LeastSquaresFittingExponential.html
def fit_exp(xs, ys):
    S_x2_y = 0.0
    S_y_lny = 0.0
    S_x_y = 0.0
    S_x_y_lny = 0.0
    S_y = 0.0
    for (x,y) in zip(xs, ys):
        S_x2_y += x * x * y
        S_y_lny += y * np.log(y)
        S_x_y += x * y
        S_x_y_lny += x * y * np.log(y)
        S_y += y
    #end
    a = (S_x2_y * S_y_lny - S_x_y * S_x_y_lny) / (S_y * S_x2_y - S_x_y * S_x_y)
    b = (S_y * S_x_y_lny - S_x_y * S_y_lny) / (S_y * S_x2_y - S_x_y * S_x_y)
    return (np.exp(a), b)


xs = [33, 34, 35, 36, 37, 38, 39, 40, 41, 42]
ys = [3187, 3545, 4045, 4447, 4872, 5660, 5983, 6254, 6681, 7206]

(A, B) = fit_exp(xs, ys)

plt.figure()
plt.plot(xs, ys, 'o-', label='Raw Data')
plt.plot(xs, [A * np.exp(B *x) for x in xs], 'o-', label='Fit')

plt.title('Exponential Fit Test')
plt.xlabel('X')
plt.ylabel('Y')
plt.legend(loc='best')
plt.tight_layout()
plt.show()

nhập mô tả hình ảnh ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.