SciPy-數(shù)值計(jì)算庫

SciPy簡介

SciPy函數(shù)庫在NumPy庫的基礎(chǔ)上增加了眾多的數(shù)學(xué)、科學(xué)以及工程計(jì)算中常用的庫函數(shù)。例如線性代數(shù)、常微分方程數(shù)值求解、信號處理、圖像處理、稀疏矩陣等。

Scipy函數(shù)庫的應(yīng)用及實(shí)例

  • 最小二乘擬合

假設(shè)有一組實(shí)驗(yàn)數(shù)據(jù)(x[i], y[i]),我們知道它們之間的函數(shù)關(guān)系:y = f(x),通過這些已知信息,需要確定函數(shù)中的一些參數(shù)項(xiàng)。例如,如果f是一個線型函數(shù)f(x) = k*x+b,那么參數(shù)k和b就是我們需要確定的值。如果將這些參數(shù)用p表示的話,那么我們就是要找到一組p值使得如下公式中的S函數(shù)最小:



這種算法被稱之為最小二乘擬合(Least-square fitting)。

  • scipy中的子函數(shù)庫optimize已經(jīng)提供了實(shí)現(xiàn)最小二乘擬合算法的函數(shù)leastsq
# -*- coding: utf-8 -*-
import numpy as np
from scipy.optimize import leastsq
import pylab as pl

def func(x, p):
    """
    數(shù)據(jù)擬合所用的函數(shù): A*sin(2*pi*k*x + theta)
    """
    A, k, theta = p
    return A*np.sin(2*np.pi*k*x+theta)   

def residuals(p, y, x):
    """
    實(shí)驗(yàn)數(shù)據(jù)x, y和擬合函數(shù)之間的差,p為擬合需要找到的系數(shù)
    """
    return y - func(x, p)

x = np.linspace(0, -2*np.pi, 100)
A, k, theta = 10, 0.34, np.pi/6 # 真實(shí)數(shù)據(jù)的函數(shù)參數(shù)
y0 = func(x, [A, k, theta]) # 真實(shí)數(shù)據(jù)
y1 = y0 + 2 * np.random.randn(len(x)) # 加入噪聲之后的實(shí)驗(yàn)數(shù)據(jù)    

p0 = [7, 0.2, 0] # 第一次猜測的函數(shù)擬合參數(shù)

# 調(diào)用leastsq進(jìn)行數(shù)據(jù)擬合
# residuals為計(jì)算誤差的函數(shù)
# p0為擬合參數(shù)的初始值
# args為需要擬合的實(shí)驗(yàn)數(shù)據(jù)
plsq = leastsq(residuals, p0, args=(y1, x))

print u"真實(shí)參數(shù):", [A, k, theta] 
print u"擬合參數(shù)", plsq[0] # 實(shí)驗(yàn)數(shù)據(jù)擬合后的參數(shù)

pl.plot(x, y0, label=u"真實(shí)數(shù)據(jù)")
pl.plot(x, y1, label=u"帶噪聲的實(shí)驗(yàn)數(shù)據(jù)")
pl.plot(x, func(x, plsq[0]), label=u"擬合數(shù)據(jù)")
pl.legend()
pl.show()

這個例子中我們要擬合的函數(shù)是一個正弦波函數(shù),它有三個參數(shù) A, k, theta ,分別對應(yīng)振幅、頻率、相角。假設(shè)我們的實(shí)驗(yàn)數(shù)據(jù)是一組包含噪聲的數(shù)據(jù) x, y1,其中y1是在真實(shí)數(shù)據(jù)y0的基礎(chǔ)上加入噪聲的到了。
通過leastsq函數(shù)對帶噪聲的實(shí)驗(yàn)數(shù)據(jù)x,y1進(jìn)行數(shù)據(jù)擬合,可以找到x和真實(shí)數(shù)據(jù)y0之間的正弦關(guān)系的三個參數(shù): A, k, theta。下面是程序的輸出:

>>> 真實(shí)參數(shù): [10, 0.34000000000000002, 0.52359877559829882]
>>> 擬合參數(shù) [-9.84152775  0.33829767 -2.68899335]
調(diào)用leastsq函數(shù)對噪聲正弦波數(shù)據(jù)進(jìn)行曲線擬合
  • 函數(shù)最小值

optimize庫提供了幾個求函數(shù)最小值的算法:fmin, fmin_powell, fmin_cg, fmin_bfgs。下面的程序通過求解卷積的逆運(yùn)算演示fmin的功能。

對于一個離散的線性時不變系統(tǒng)如果它的輸入是x,那么其輸出y可以用x和h的卷積表示:y = x * h
現(xiàn)在的問題是如果已知系統(tǒng)的輸入x和輸出y,如何計(jì)算系統(tǒng)的傳遞函數(shù)h;或者如果已知系統(tǒng)的傳遞函數(shù)h和系統(tǒng)的輸出y,如何計(jì)算系統(tǒng)的輸入x。這種運(yùn)算被稱為反卷積運(yùn)算,是十分困難的,特別是在實(shí)際的運(yùn)用中,測量系統(tǒng)的輸出總是存在誤差的。

下面用fmin計(jì)算反卷積,這種方法只能用在很小規(guī)模的數(shù)列之上。

# -*- coding: utf-8 -*-
# 本程序用各種fmin函數(shù)求卷積的逆運(yùn)算

import scipy.optimize as opt 
import numpy as np 

def test_fmin_convolve(fminfunc, x, h, y, yn, x0): 
    """
    x (*) h = y, (*)表示卷積
    yn為在y的基礎(chǔ)上添加一些干擾噪聲的結(jié)果
    x0為求解x的初始值
    """
    def convolve_func(h):
        """
        計(jì)算 yn - x (*) h 的power
        fmin將通過計(jì)算使得此power最小
        """ 
        return np.sum((yn - np.convolve(x, h))**2) 

    # 調(diào)用fmin函數(shù),以x0為初始值
    h0 = fminfunc(convolve_func, x0) 

    print fminfunc.__name__ 
    print "---------------------" 
    # 輸出 x (*) h0 和 y 之間的相對誤差
    print "error of y:", np.sum((np.convolve(x, h0)-y)**2)/np.sum(y**2) 
    # 輸出 h0 和 h 之間的相對誤差
    print "error of h:", np.sum((h0-h)**2)/np.sum(h**2) 
    print 

def test_n(m, n, nscale): 
    """
    隨機(jī)產(chǎn)生x, h, y, yn, x0等數(shù)列,調(diào)用各種fmin函數(shù)求解b
    m為x的長度, n為h的長度, nscale為干擾的強(qiáng)度
    """
    x = np.random.rand(m) 
    h = np.random.rand(n) 
    y = np.convolve(x, h) 
    yn = y + np.random.rand(len(y)) * nscale
    x0 = np.random.rand(n) 

    test_fmin_convolve(opt.fmin, x, h, y, yn, x0) 
    test_fmin_convolve(opt.fmin_powell, x, h, y, yn, x0) 
    test_fmin_convolve(opt.fmin_cg, x, h, y, yn, x0)
    test_fmin_convolve(opt.fmin_bfgs, x, h, y, yn, x0)

if __name__ == "__main__":
    test_n(200, 20, 0.1)

下面是程序的輸出:

fmin
ーーーーーーーーーーー
error of y: 0.00568756699607
error of h: 0.354083287918

fmin_powell
ーーーーーーーーーーー
error of y: 0.000116114709857
error of h: 0.000258897894009

fmin_cg
ーーーーーーーーーーー
error of y: 0.000111220299615
error of h: 0.000211404733439

fmin_bfgs
ーーーーーーーーーーー
error of y: 0.000111220251551
error of h: 0.000211405138529
  • 非線性方程組求解

optimize庫中的fsolve函數(shù)可以用來對非線性方程組進(jìn)行求解。它的基本調(diào)用形式如下:fsolve(func, x0)
func(x)是計(jì)算方程組誤差的函數(shù),它的參數(shù)x是一個矢量,表示方程組的各個未知數(shù)的一組可能解,func返回將x代入方程組之后得到的誤差;x0為未知數(shù)矢量的初始值。如果要對如下方程組進(jìn)行求解的話:

f1(u1,u2,u3) = 0
f2(u1,u2,u3) = 0
f3(u1,u2,u3) = 0
那么func可以如下定義:

def func(x):
    u1,u2,u3 = x
    return [f1(u1,u2,u3), f2(u1,u2,u3), f3(u1,u2,u3)]

下面是一個實(shí)際的例子,求解如下方程組的解:

from scipy.optimize import fsolve
from math import sin,cos

def f(x):
    x0 = float(x[0])
    x1 = float(x[1])
    x2 = float(x[2])
    return [
        5*x1+3,
        4*x0*x0 - 2*sin(x1*x2),
        x1*x2 - 1.5
    ]

result = fsolve(f, [1,1,1])

print result
print f(result)

輸出為:

[-0.70622057 -0.6        -2.5       ]
[0.0, -9.1260332624187868e-14, 5.3290705182007514e-15]
  • 由于fsolve函數(shù)在調(diào)用函數(shù)f時,傳遞的參數(shù)為數(shù)組,因此如果直接使用數(shù)組中的元素計(jì)算的話,計(jì)算速度將會有所降低,因此這里先用float函數(shù)將數(shù)組中的元素轉(zhuǎn)換為Python中的標(biāo)準(zhǔn)浮點(diǎn)數(shù),然后調(diào)用標(biāo)準(zhǔn)math庫中的函數(shù)進(jìn)行運(yùn)算。
  • 在對方程組進(jìn)行求解時,fsolve會自動計(jì)算方程組的雅可比矩陣,如果方程組中的未知數(shù)很多,而與每個方程有關(guān)的未知數(shù)較少時,即雅可比矩陣比較稀疏時,傳遞一個計(jì)算雅可比矩陣的函數(shù)將能大幅度提高運(yùn)算速度。
  • 雅可比矩陣

雅可比矩陣是一階偏導(dǎo)數(shù)以一定方式排列的矩陣,它給出了可微分方程與給定點(diǎn)的最優(yōu)線性逼近,因此類似于多元函數(shù)的導(dǎo)數(shù)。例如前面的函數(shù)f1,f2,f3和未知數(shù)u1,u2,u3的雅可比矩陣如下:


使用雅可比矩陣的fsolve實(shí)例如下

# -*- coding: utf-8 -*-
from scipy.optimize import fsolve
from math import sin,cos
def f(x):
    x0 = float(x[0])
    x1 = float(x[1])
    x2 = float(x[2])
    return [
        5*x1+3,
        4*x0*x0 - 2*sin(x1*x2),
        x1*x2 - 1.5
    ]
    
def j(x):
    x0 = float(x[0])
    x1 = float(x[1])
    x2 = float(x[2])    
    return [
        [0, 5, 0],
        [8*x0, -2*x2*cos(x1*x2), -2*x1*cos(x1*x2)],
        [0, x2, x1]
    ]
 
result = fsolve(f, [1,1,1], fprime=j)
print result
print f(result)

輸出為:

[-0.70622057 -0.6        -2.5       ]
[0.0, -9.126033262418787e-14, 5.329070518200751e-15]

計(jì)算雅可比矩陣的函數(shù)j通過fprime參數(shù)傳遞給fsolve,函數(shù)j和函數(shù)f一樣,有一個未知數(shù)的解矢量參數(shù)x,函數(shù)j計(jì)算非線性方程組在矢量x點(diǎn)上的雅可比矩陣。由于這個例子中未知數(shù)很少,因此程序計(jì)算雅可比矩陣并不能帶來計(jì)算速度的提升。

  • B-Spline樣條曲線

interpolate庫提供了許多對數(shù)據(jù)進(jìn)行插值運(yùn)算的函數(shù)。

使用直線和B-Spline對正弦波上的點(diǎn)進(jìn)行插值的例子。

# -*- coding: utf-8 -*-
import numpy as np
import pylab as pl
from scipy import interpolate 

x = np.linspace(0, 2*np.pi+np.pi/4, 10)
y = np.sin(x)

x_new = np.linspace(0, 2*np.pi+np.pi/4, 100)
f_linear = interpolate.interp1d(x, y)
tck = interpolate.splrep(x, y)
y_bspline = interpolate.splev(x_new, tck)

pl.plot(x, y, "o",  label=u"原始數(shù)據(jù)")
pl.plot(x_new, f_linear(x_new), label=u"線性插值")
pl.plot(x_new, y_bspline, label=u"B-spline插值")
pl.legend()
pl.show()
使用interpolate庫對正弦波數(shù)據(jù)進(jìn)行線性插值和B-Spline插值

通過interp1d函數(shù)直接得到一個新的線性插值函數(shù)。而B-Spline插值運(yùn)算需要先使用splrep函數(shù)計(jì)算出B-Spline曲線的參數(shù),然后將參數(shù)傳遞給splev函數(shù)計(jì)算出各個取樣點(diǎn)的插值結(jié)果。

  • 數(shù)值積分

數(shù)值積分是對定積分的數(shù)值求解,例如可以利用數(shù)值積分計(jì)算某個形狀的面積。下面讓我們來考慮一下如何計(jì)算半徑為1的半圓的面積,根據(jù)圓的面積公式,其面積應(yīng)該等于PI/2。單位半圓曲線可以用下面的函數(shù)表示:

 def half_circle(x):
       return (1-x**2)**0.5
  • 使用經(jīng)典的分小矩形計(jì)算面積總和的方式,計(jì)算出單位半圓的面積:
>>> N = 10000
>>> x = np.linspace(-1, 1, N)
>>> dx = 2.0/N
>>> y = half_circle(x)
>>> dx * np.sum(y[:-1] + y[1:]) # 面積的兩倍
3.1412751679988937
  • 用numpy.trapz進(jìn)行數(shù)值積分:
>>> import numpy as np
>>> np.trapz(y, x) * 2 # 面積的兩倍
3.1415893269316042

此函數(shù)計(jì)算的是以x,y為頂點(diǎn)坐標(biāo)的折線與X軸所夾的面積。同樣的分割點(diǎn)數(shù),trapz函數(shù)的結(jié)果更加接近精確值一些。

  • 調(diào)用scipy.integrate庫中的quad函數(shù)的話,將會得到非常精確的結(jié)果:
>>> from scipy import integrate
>>> pi_half, err = integrate.quad(half_circle, -1, 1)
>>> pi_half*2
3.1415926535897984

多重定積分的求值可以通過多次調(diào)用quad函數(shù)實(shí)現(xiàn),為了調(diào)用方便,integrate庫提供了dblquad函數(shù)進(jìn)行二重定積分,tplquad函數(shù)進(jìn)行三重定積分。

下面以計(jì)算單位半球體積為例說明dblquad函數(shù)的用法。
單位半球上的點(diǎn)(x,y,z)符合如下方程:


因此可以如下定義通過(x,y)坐標(biāo)計(jì)算球面上點(diǎn)的z值的函數(shù):

def half_sphere(x, y):
    return (1-x**2-y**2)**0.5

X-Y軸平面與此球體的交線為一個單位圓,因此積分區(qū)間為此單位圓,可以考慮為X軸坐標(biāo)從-1到1進(jìn)行積分,而Y軸從 -half_circle(x) 到 half_circle(x) 進(jìn)行積分,于是可以調(diào)用dblquad函數(shù):

>>> integrate.dblquad(half_sphere, -1, 1,
    lambda x:-half_circle(x),
    lambda x:half_circle(x))
>>> (2.0943951023931988, 2.3252456653390915e-14)
>>> np.pi*4/3/2 # 通過球體體積公式計(jì)算的半球體積
2.0943951023931953

dblquad函數(shù)的調(diào)用方式為:dblquad(func2d, a, b, gfun, hfun)
對于func2d(x,y)函數(shù)進(jìn)行二重積分,其中a,b為變量x的積分區(qū)間,而gfun(x)到hfun(x)為變量y的積分區(qū)間。
半球體積的積分的示意圖如下:

半球體積的雙重定積分示意圖

X軸的積分區(qū)間為-1.0到1.0,對于X=x0時,通過對Y軸的積分計(jì)算出切面的面積,因此Y軸的積分區(qū)間如圖中紅色點(diǎn)線所示。

  • 解常微分方程組

scipy.integrate庫提供了數(shù)值積分和常微分方程組求解算法odeint。
下面讓我們來看看如何用odeint計(jì)算洛侖茲吸引子的軌跡。洛侖茲吸引子由下面的三個微分方程定義:


[洛侖茲吸引子的詳細(xì)介紹]這三個方程定義了三維空間中各個坐標(biāo)點(diǎn)上的速度矢量。從某個坐標(biāo)開始沿著速度矢量進(jìn)行積分,就可以計(jì)算出無質(zhì)量點(diǎn)在此空間中的運(yùn)動軌跡。其中sigma, rho, beta 為三個常數(shù),不同的參數(shù)可以計(jì)算出不同的運(yùn)動軌跡:x(t), y(t), z(t)。 當(dāng)參數(shù)為某些值時,軌跡出現(xiàn)餛飩現(xiàn)象:即微小的初值差別也會顯著地影響運(yùn)動軌跡。

下面是洛侖茲吸引子的軌跡計(jì)算和繪制程序:

# -*- coding: utf-8 -*-
from scipy.integrate import odeint 
import numpy as np 

def lorenz(w, t, p, r, b): 
    # 給出位置矢量w,和三個參數(shù)p, r, b計(jì)算出
    # dx/dt, dy/dt, dz/dt的值
    x, y, z = w
    # 直接與lorenz的計(jì)算公式對應(yīng) 
    return np.array([p*(y-x), x*(r-z)-y, x*y-b*z]) 

t = np.arange(0, 30, 0.01) # 創(chuàng)建時間點(diǎn) 
# 調(diào)用ode對lorenz進(jìn)行求解, 用兩個不同的初始值 
track1 = odeint(lorenz, (0.0, 1.00, 0.0), t, args=(10.0, 28.0, 3.0)) 
track2 = odeint(lorenz, (0.0, 1.01, 0.0), t, args=(10.0, 28.0, 3.0)) 

# 繪圖
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt 

fig = plt.figure()
ax = Axes3D(fig)
ax.plot(track1[:,0], track1[:,1], track1[:,2])
ax.plot(track2[:,0], track2[:,1], track2[:,2])
plt.show()
用odeint函數(shù)對洛侖茲吸引子微分方程進(jìn)行數(shù)值求解所得到的運(yùn)動軌跡

我們看到即使初始值只相差0.01,兩條運(yùn)動軌跡也是完全不同的。
在程序中先定義一個lorenz函數(shù),它的任務(wù)是計(jì)算出某個位置的各個方向的微分值,這個計(jì)算直接根據(jù)洛侖茲吸引子的公式得出。然后調(diào)用odeint,對微分方程求解,odeint有許多參數(shù),這里用到的四個參數(shù)分別為:

  • lorenz, 它是計(jì)算某個位移上的各個方向的速度(位移的微分)
  • (0.0, 1.0, 0.0),位移初始值。計(jì)算常微分方程所需的各個變量的初始值
  • t, 表示時間的數(shù)組,
  • odeint對于此數(shù)組中的每個時間點(diǎn)進(jìn)行求解,得出所有時間點(diǎn)的位置
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容