hans

hans

【Python】三、Scipy——《用Python做科学计算》


1. 最小二乘拟合#

假设有一组实验数据 (x [i], y [i]),我们知道它们之间的函数关系 =
f (x),通过这些已知信息,需要确定函数中的一些参数项。例如,如果 f 是一个线型函数 f (x) =
k*x+b,那么参数 k 和 b 就是我们需要确定的值。如果将这些参数用 p 表示的话,那么我们就是要找到一组 p 值使得如下公式中的 S 函数最小:
1668631852453.jpg
这种算法被称之为最小二乘拟合 (Least-square fitting)。


用已知数据集(x,y)去确定一组参数(k,b)使得实际的 y 值减去预测的 y 值的平方和最小。


scipy 中的子函数库 optimize 已经提供了实现最小二乘拟合算法的函数 leastsq。下面是用 leastsq 进行数据拟合的一个例子:

# -*- coding: utf-8 -*-
# 注意如果代码里有中文,文件开头必须添加上面这句话,或者添加“#encoding:utf-8”。
import numpy as np
from scipy.optimize import leastsq
import pylab as pl

def func(x, p):
    """
    数据拟合所用的函数: 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):
    """
    实验数据x, y和拟合函数之间的差,p为拟合需要找到的系数
    """
    return y - func(x, p)

x = np.linspace(0, -2*np.pi, 100)
A, k, theta = 10, 0.34, np.pi/6 # 真实数据的函数参数
y0 = func(x, [A, k, theta]) # 真实数据
y1 = y0 + 2 * np.random.randn(len(x)) # 加入噪声之后的实验数据    

p0 = [7, 0.2, 0] # 第一次猜测的函数拟合参数

# 调用leastsq进行数据拟合
# residuals为计算误差的函数
# p0为拟合参数的初始值
# args为需要拟合的实验数据
plsq = leastsq(residuals, p0, args=(y1, x))

print u"真实参数:", [A, k, theta]
print u"拟合参数", plsq[0] # 实验数据拟合后的参数

pl.plot(x, y0, label=u"真实数据")
pl.plot(x, y1, label=u"带噪声的实验数据")
pl.plot(x, func(x, plsq[0]), label=u"拟合数据")
pl.legend()
pl.show()

这个例子中我们要拟合的函数是一个正弦波函数,它有三个参数 A, k, theta ,分别对应振幅、频率、相角。假设我们的实验数据是一组包含噪声的数据 x,
y1,其中 y1 是在真实数据 y0 的基础上加入噪声的到了。

通过 leastsq 函数对带噪声的实验数据 x, y1 进行数据拟合,可以找到 x 和真实数据 y0 之间的正弦关系的三个参数: A, k, theta。
最后我们可以发现拟合参数虽然和真实参数完全不同,但是由于正弦函数具有周期性,实际上拟合参数得到的函数和真实参数对应的函数是一致的。

2. 函数最小值#

optimize 库提供了几个求函数最小值的算法:fmin, fmin_powell, fmin_cg,
fmin_bfgs。下面的程序通过求解卷积的逆运算演示 fmin 的功能。

对于一个离散的线性时不变系统 h, 如果它的输入是 x,那么其输出 y 可以用 x 和 h 的卷积表示:
y = x*h

现在的问题是如果已知系统的输入 x 和输出 y,如何计算系统的传递函数 h;或者如果已知系统的传递函数 h 和系统的输出 y,如何计算系统的输入 x。这种运算被称为反卷积运算,是十分困难的,特别是在实际的运用中,测量系统的输出总是存在误差的。

下面用 fmin 计算反卷积,这种方法只能用在很小规模的数列之上,因此没有很大的实用价值,不过用来评价 fmin 函数的性能还是不错的。

# -*- coding: utf-8 -*-
# 本程序用各种fmin函数求卷积的逆运算

import scipy.optimize as opt
import numpy as np

def test_fmin_convolve(fminfunc, x, h, y, yn, x0):
    """
    x (*) h = y, (*)表示卷积
    yn为在y的基础上添加一些干扰噪声的结果
    x0为求解x的初始值
    """
    def convolve_func(h):
        """
        计算 yn - x (*) h 的power
        fmin将通过计算使得此power最小
        """
        return np.sum((yn - np.convolve(x, h))**2)

    # 调用fmin函数,以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):
    """
    随机产生x, h, y, yn, x0等数列,调用各种fmin函数求解b
    m为x的长度, n为h的长度, nscale为干扰的强度
    """
    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)

if __name__ == "__main__"

上面这句代码的功能是在当前文件下编译运行的时候,执行 if 下面的语句。如果在其他文件内通过 import 调用本文件,if 内语句不执行。


3. 非线性方程组求解#

optimize 库中的 fsolve 函数可以用来对非线性方程组进行求解。它的基本调用形式如下:

fsolve(func, x0)

func (x) 是计算方程组误差的函数,它的参数 x 是一个矢量,表示方程组的各个未知数的一组可能解,func 返回将 x 代入方程组之后得到的误差;x0 为未知数矢量的初始值。如果要对如下方程组进行求解的话:

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)]

下面是一个实际的例子,求解如下方程组的解:

5*x1 + 3 = 0
4*x0*x0 - 2*sin(x1*x2) = 0
x1*x2 - 1.5 = 0

程序如下:

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 函数在调用函数 f 时,传递的参数为数组,因此如果直接使用数组中的元素计算的话,计算速度将会有所降低,因此这里先用 float 函数将数组中的元素转换为 Python 中的标准浮点数,然后调用标准 math 库中的函数进行运算。

在对方程组进行求解时,fsolve 会自动计算方程组的雅可比矩阵,如果方程组中的未知数很多,而与每个方程有关的未知数较少时,即雅可比矩阵比较稀疏时,传递一个计算雅可比矩阵的函数将能大幅度提高运算速度。
雅可比矩阵
雅可比矩阵是一阶偏导数以一定方式排列的矩阵,它给出了可微分方程与给定点的最优线性逼近,因此类似于多元函数的导数。例如前面的函数 f1,f2,f3 和未知数 u1,u2,u3 的雅可比矩阵如下:
1668631974254.jpg
使用雅可比矩阵的 fsolve 实例如下,计算雅可比矩阵的函数 j 通过 fprime 参数传递给 fsolve,函数 j 和函数 f 一样,有一个未知数的解矢量参数 x,函数 j 计算非线性方程组在矢量 x 点上的雅可比矩阵。由于这个例子中未知数很少,因此程序计算雅可比矩阵并不能带来计算速度的提升。

# -*- 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)

4. 数值积分#

数值积分是对定积分的数值求解,例如可以利用数值积分计算某个形状的面积。下面让我们来考虑一下如何计算半径为 1 的半圆的面积,根据圆的面积公式,其面积应该等于 PI/2。单位半圆(X 轴上面的半圆)曲线可以用下面的函数表示:

def half_circle(x):
    return (1-x**2)**0.5

下面的程序使用经典的分小矩形计算面积总和的方式,计算出单位半圆的面积:

>>> N = 10000
>>> x = np.linspace(-1, 1, N)
>>> dx = 2.0/N # 这个值其实就是上面x每相邻两个点之间的距离,也就是举行的宽
>>> y = half_circle(x) # 每一点的纵坐标,每一点到下一点极端的看成高一致。这样每一点的y值乘以dx就是当前点到下一点之间矩形的面积。
>>> dx * np.sum(y) * 2 # 求面积的两倍方便看pi数值
3.1412751679988937

利用上述方式计算出的圆上一系列点的坐标,还可以用 numpy.trapz 进行数值积分:

>>> import numpy as np
>>> np.trapz(y, x) * 2 # 面积的两倍
3.1415893269316042

此函数计算的是以 x,y 为顶点坐标的折线与 X 轴所夹的面积。同样的分割点数,trapz 函数的结果更加接近精确值一些。

如果我们调用 scipy.integrate 库中的 quad 函数的话,将会得到非常精确的结果:

>>> from scipy import integrate
>>> pi_half, err = integrate.quad(half_circle, -1, 1)
>>> pi_half*2
3.1415926535897984

这一节我搬过来的内容少很多,想学更多内容的读者建议去原地址学习。
本文参考自: http://old.sebug.net/paper/books/scipydoc/scipy_intro.html#id1

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.