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