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

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。