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]
函數(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()
通過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()
我們看到即使初始值只相差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)的位置