《算法的乐趣》12.实验数据与曲线拟合------python

文章目录

      • 曲线拟合
        • 曲线拟合的定义
      • 最小二乘法曲线拟合
        • 高斯消元法求解方程组
        • 最小二乘法解决“速度与加速度”实验
      • 三次样条曲线拟合
        • 插值函数
        • 样条函数的定义
        • 边界条件
        • 推导三次样条函数
        • 追赶法求解方程组
        • 三次样条曲线拟合算法实现

曲线拟合

曲线拟合的定义

曲线拟合(curve ftting)是指用连续曲线近似地刻画或比拟平面上一组离散点所表示的坐标之间的函数关系,是一种用解析表达式逼近离散数据的方法。就是将现有数据透过数学方法来代人一条数学方程式的表示方法。
由以上定义可知,曲线拟合不仅仅是根据离散数据画出一条曲线,曲线拟合更重要的意义是通过特定的曲线拟合算法,推算出一个(或一系列)能逼近离散数据并能维持统计误差最小的数学解析表达式,也就是拟合曲线的数学方程。

最小二乘法曲线拟合

最小二乘法是一种通过最小化误差的平方和寻找数据的最佳函数匹配的方法。
多项式为基 { 1 , x , x 2 , ⋯   , x m } \{1,x,x^{2},\cdots,x^{m}\} {1,x,x2,,xm}:
y = a 0 + a 1 x + a 2 x 2 + ⋯ + a m x m y = a_{0} + a_{1}x + a_{2}x^{2}+\cdots +a_{m}x^{m} y=a0+a1x+a2x2++amxm
离散的各点到这条曲线的平方和 F ( a 0 , a 1 , ⋯   , a m ) F(a_{0},a_{1},\cdots,a_{m}) F(a0,a1,,am)为:
F ( a 0 , a 1 , ⋯   , a m ) = ∑ i = 1 n [ y i − ( a 0 + a 1 x i + a 2 x i 2 + ⋯ + a m x i m ) ] 2 F(a_{0},a_{1},\cdots,a_{m})= \sum_{i=1}^{n}{[y_{i}-(a_{0} + a_{1}x_{i} + a_{2}x_{i}^{2}+\cdots +a_{m}x_{i}^{m})]^{2}} F(a0,a1,,am)=i=1n[yi(a0+a1xi+a2xi2++amxim)]2
第一步对 F ( a 0 , a 1 , ⋯   , a m ) F(a_{0},a_{1},\cdots,a_{m}) F(a0,a1,,am)分别求对 a i a_{i} ai的偏导数,得到 m m m个等式:
− 2 ∑ i = 1 n [ y i − ( a 0 + a 1 x i + a 2 x i 2 + ⋯ + a m x i m ) ] = 0 -2\sum_{i=1}^{n}{[y_{i}-(a_{0} + a_{1}x_{i} + a_{2}x_{i}^{2}+\cdots +a_{m}x_{i}^{m})]}=0 2i=1n[yi(a0+a1xi+a2xi2++amxim)]=0
− 2 ∑ i = 1 n [ y i − ( a 0 + a 1 x i + a 2 x i 2 + ⋯ + a m x i m ) ] x i = 0 -2\sum_{i=1}^{n}{[y_{i}-(a_{0} + a_{1}x_{i} + a_{2}x_{i}^{2}+\cdots +a_{m}x_{i}^{m})]}x_{i}=0 2i=1n[yi(a0+a1xi+a2xi2++amxim)]xi=0
⋯ \cdots
− 2 ∑ i = 1 n [ y i − ( a 0 + a 1 x i + a 2 x i 2 + ⋯ + a m x i m ) ] x i m = 0 -2\sum_{i=1}^{n}{[y_{i}-(a_{0} + a_{1}x_{i} + a_{2}x_{i}^{2}+\cdots +a_{m}x_{i}^{m})]}x_{i}^{m}=0 2i=1n[yi(a0+a1xi+a2xi2++amxim)]xim=0
第二步处理就是整理为针对 a 0 , a 1 , ⋯   , a m a_{0},a_{1},\cdots,a_{m} a0,a1,,am的正规方程组:
a 0 n + a 1 ∑ i = 1 n x i + a 2 ∑ i = 1 n x i 2 + ⋯ + a m ∑ i = 1 n x i m = ∑ i = 1 n y i a_{0}n + a_{1}\sum_{i=1}^{n}{x_{i}} + a_{2}\sum_{i=1}^{n}{x_{i}^{2}} + \cdots + a_{m}\sum_{i=1}^{n}{x_{i}^{m}} = \sum_{i=1}^{n}{y_{i}} a0n+a1i=1nxi+a2i=1nxi2++ami=1nxim=i=1nyi
a 0 x i + a 1 ∑ i = 1 n x i 2 + a 2 ∑ i = 1 n x i 3 + ⋯ + a m ∑ i = 1 n x i m + 1 = ∑ i = 1 n x i y i a_{0}x_{i} + a_{1}\sum_{i=1}^{n}{x_{i}^{2}} + a_{2}\sum_{i=1}^{n}{x_{i}^{3}} + \cdots + a_{m}\sum_{i=1}^{n}{x_{i}^{m+1}} = \sum_{i=1}^{n}{x_{i}y_{i}} a0xi+a1i=1nxi2+a2i=1nxi3++ami=1nxim+1=i=1nxiyi
⋯ \cdots
a 0 x i m + a 1 ∑ i = 1 n x i m + 1 + a 2 ∑ i = 1 n x i m + 2 + ⋯ + a m ∑ i = 1 n x i 2 m = ∑ i = 1 n x i m y i a_{0}x_{i}^{m} + a_{1}\sum_{i=1}^{n}{x_{i}^{m+1}} + a_{2}\sum_{i=1}^{n}{x_{i}^{m+2}} + \cdots + a_{m}\sum_{i=1}^{n}{x_{i}^{2m}} = \sum_{i=1}^{n}{x_{i}^{m}y_{i}} a0xim+a1i=1nxim+1+a2i=1nxim+2++ami=1nxi2m=i=1nximyi
第三步处理就是求解这个多元一次方程组,得到多项式系数 a 0 , a 1 , ⋯   , a m a_{0},a_{1},\cdots,a_{m} a0,a1,,am,就可以得到曲线的拟合多项式函数。

高斯消元法求解方程组

高斯消元法的主要思想是通过对系数矩阵进行行变换,将方程组的系数矩阵有对称矩阵变为三角矩阵,从而达到消元的目的,最后通过回代逐个获得方程组的解。
两个步骤:
1.通过选择主元,逐行消元,最终形成方程组系数矩阵的三角矩阵形式;
2.逐步回代的过程,最终矩阵的对角线上的元素就是方程组的解。

参考文章:https://blog.csdn.net/Jerr__y/article/details/53218883

import numpy as np
def arguemented_mat(a, b):
    """增广矩阵"""
    return np.c_[a, b] # 按行转换成矩阵

# 行交换
def swap_row(a, i, j):
    m, n = a.shape
    if i >= m or j >= m:
        print('error: out of index ...')
    else:
        # 异或运算,交换两行
        a[i] = a[i] ^ a[j]
        a[j] = a[i] ^ a[j]
        a[i] = a[i] ^ a[j]
    return a

def trape_mat(sigma):
    """阶梯矩阵转换"""
    m, n = sigma.shape
    # 保存主元所在的列数,一般来说,每行都有一个主元,除非某行全零
    main_factor = []
    main_col = 0
    while main_col < n and len(main_factor) < m:

        # 当行数多于列数的时候,会出现所有的列已经处理完的情况,直接结束
        if main_col == n:
            break
        
        # 逐列找主元,若该列全零(从第i行往下),则没有主元
        first_row = len(main_factor)  # 当前查找小矩阵首行所在原矩阵的行号
        while main_col < n:
            new_col = sigma[first_row:, main_col]
            not_zeros = np.where(new_col > 0)[0]

            # 若该列没有非零值,则该列非主元列
            if len(not_zeros) == 0:
                main_col += 1
                break
            # 否则通过行变换找到主元
            else:
                main_factor.append(main_col)
                index = not_zeros[0]
                # 若首个元素不是主元,需要行变换,此处只是不为0的时候交换
                if index != 0:
                    sigma = swap_row(sigma, first_row, first_row + index)
                # 把该主元下面的元素全部变成0
                if first_row < m - 1:
                    for k in range(first_row+1, m):
                        times = float(sigma[k, main_col]) / sigma[first_row, main_col]
                        sigma[k] = sigma[k] - times * sigma[first_row]        
                main_col += 1
                break
    return sigma, main_factor


# 回代求解
def back_solve(sigma, main_factor):
    # 判断是否有解
    if len(main_factor) == 0:
        print('wrong main_factor ...')
        return None
    m, n = sigma.shape
    if main_factor[-1] == n-1:
        print('no answer ...')
        return None
    # 把所有的主元元素上方的元素变成0
    for i in range(len(main_factor)-1,-1,-1):
        factor = sigma[i, main_factor[i]]
        sigma[i] = sigma[i] / float(factor)
        for j in range(i):
            times = sigma[j, main_factor[i]]
            sigma[j] = sigma[j] - float(times)*sigma[i]
    return sigma

# 结果打印
def print_result(sigma, main_factor):
    if sigma is None:
        print('no answer ...')
        return 
    m, n = sigma.shape
    result = [''] * (n-1)
    main_factor = list(main_factor)
    for i in range(n-1):
        # 如果不是主元列,则为自由变量
        if i not in main_factor:
            result[i] = 'X' + str(i+1) + '(free var)'
        # 否则是主元变量,从对应的行,将主元变量表示成非主元变量的线性组合
        else:
            # row_of_maini表示该主元所在的行
            row_of_maini = main_factor.index(i)
            result[i] = str(sigma[row_of_maini, -1])
            for j in range(i+1, n-1):
                ratio = sigma[row_of_maini, j]
                if ratio > 0:
                    result[i] = result[i] + '-' + str(ratio) + '*X' + str(j+1)
                if ratio < 0:
                    result[i] = result[i] + '+' + str(-ratio) + '*X' + str(j+1)
    print('方程的通解是:\n', )
    for i in range(n-1):
        print('X' + str(i+1), '=', result[i])   
    return result


# 得到简化的阶梯矩阵和主元列
def solve(a, b):
    sigma = arguemented_mat(a, b)
    print('增广矩阵为:')
    print(sigma)

    sigma, main_factor = trape_mat(sigma)
    sigma = back_solve(sigma, main_factor)
    print('方程的简化阶梯矩阵:')
    print(sigma)

    print('方程的主元列为:')
    print(main_factor)

    result = print_result(sigma, main_factor)
    return result

if __name__ == '__main__':
    a = np.array([[1, 6, 2, -5, -2], [0, 0, 2, -8, -1], [0, 0, 0, 0, 1]])
    b = np.array([-4, 3, 7])
    result = solve(a, b)
    print('=' * 30)
    a = np.array([[1, 0, -5], [0, 1, 1], [0, 0, 0]])
    b = np.array([1, 4, 0])
    result  = solve(a,b)
    print('=' * 30)
增广矩阵为:
[[ 1  6  2 -5 -2 -4]
 [ 0  0  2 -8 -1  3]
 [ 0  0  0  0  1  7]]
方程的简化阶梯矩阵:
[[ 1  6  0  3  0  0]
 [ 0  0  1 -4  0  5]
 [ 0  0  0  0  1  7]]
方程的主元列为:
[0, 2, 4]
方程的通解是:

X1 = 0-6*X2-3*X4
X2 = X2(free var)
X3 = 5+4*X4
X4 = X4(free var)
X5 = 7
==============================
增广矩阵为:
[[ 1  0 -5  1]
 [ 0  1  1  4]
 [ 0  0  0  0]]
方程的简化阶梯矩阵:
[[ 1  0 -5  1]
 [ 0  1  1  4]
 [ 0  0  0  0]]
方程的主元列为:
[0, 1]
方程的通解是:

X1 = 1+5*X3
X2 = 4-1*X3
X3 = X3(free var)
==============================

最小二乘法解决“速度与加速度”实验

时间:[3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
速度:[8.41, 9.94, 11.58, 13.02, 14.33, 15.92, 17.54, 19.22, 20.49, 22.01, 23.53, 24.47]

# 生成矩阵组
def init_mat(t, v, m=1):
    t = np.array(t)
    v = np.array(v)
    x = []
    y = []
    a = np.zeros([m+1,m+1])
    b = np.zeros([m+1,1])
    num = len(t)
    for i in range(m+2):
        x.append(sum(t**i))
    for i in range(m+1):
        y.append(sum((t**i)* v))
        a[i] = x[i:i+m+1]
        b[i] = y[i]
    return a, b
t = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]  
v = [8.41, 9.94, 11.58, 13.02, 14.33, 15.92, 17.54, 19.22, 20.49, 22.01, 23.53, 24.47] 
a, b = init_mat(t, v)
result = solve(a, b)
增广矩阵为:
[[  12.    102.    200.46]
 [ 102.   1010.   1916.72]]
方程的简化阶梯矩阵:
[[1.         0.         4.05545455]
 [0.         1.         1.48818182]]
方程的主元列为:
[0, 1]
方程的通解是:

X1 = 4.055454545454562
X2 = 1.4881818181818163
import matplotlib
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(t,v)
func = np.poly1d(np.array([float(result[1]), float(result[0])]).astype(float))
x = np.linspace(3,14,30)
y = func(x)
ax.plot(x, y, color='coral')
plt.show()

《算法的乐趣》12.实验数据与曲线拟合------python_第1张图片

三次样条曲线拟合

插值函数

若在 [ a , b ] [a,b] [a,b]上给出 n + 1 n+1 n+1个点 a ≤ x 0 < x 1 < ⋯ < x n ≤ b a \leq x_{0} < x_{1} < \cdots < x_{n} \leq b ax0<x1<<xnb, f ( x ) f(x) f(x) [ a , b ] [a,b] [a,b]上的实值函数,要求一个具有 n + 1 n+1 n+1个参数的函数 s ( x ; a 0 , ⋯   , a n ) s(x;a_{0},\cdots , a_{n}) s(x;a0,,an)使它满足
s ( x ; a 0 , ⋯   , a n ) = f ( x i ) , i = 0 , 1 , ⋯   , n s(x;a_{0},\cdots , a_{n})=f(x_{i}),i=0,1,\cdots , n s(x;a0,,an)=f(xi),i=0,1,,n
则称 s ( x ) s(x) s(x) f ( x ) f(x) f(x) [ a , b ] [a,b] [a,b]上的插值函数。
s ( x ) s(x) s(x)关于参量 a 0 , a 1 , ⋯   , a n a_{0},a_{1},\cdots ,a_{n} a0,a1,,an是线性关系,即:
s ( x ) = a 0 s 0 ( x ) + a 1 s 1 ( x ) + ⋯ + a n s n ( x ) s(x)=a_{0}s_{0}(x) + a_{1}s_{1}(x)+ \cdots +a_{n}s_{n}(x) s(x)=a0s0(x)+a1s1(x)++ansn(x)
s ( x ) s(x) s(x)就是多项式插值函数,如果 s i ( x ) s_{i}(x) si(x)是三角函数,则 s ( x ) s(x) s(x)就是三角插值函数。

样条函数的定义

设区间 [ a , b ] [a,b] [a,b]上选取 n − 1 n-1 n1个结点(包括区间端点 a a a b b b n + 1 n+1 n+1个节点),将其划分为 n n n个子区间 a = x 0 < x 1 < ⋯ < x n = b a = x_{0} < x_{1} < \cdots < x_{n} = b a=x0<x1<<xn=b,如果存在函数 s ( x ) s(x) s(x),使得 s ( x ) s(x) s(x)满足以下两个条件:
1. s ( x ) s(x) s(x)在整个区间 [ a , b ] [a,b] [a,b]上具有 m − 1 m-1 m1阶连续导数;
2. s ( x ) s(x) s(x)在每个子区间 [ x i − 1 , x i ] , i = 1 , 2 , ⋯   , n [x_{i-1},x_{i}],i=1,2,\cdots,n [xi1,xi],i=1,2,,n上是 m m m次代数多项式(最高次数为 m m m次);
则称 s ( x ) s(x) s(x)是区间 [ a , b ] [a,b] [a,b]上的 m m m次样条函数。
假如区间 [ a , b ] [a,b] [a,b]上存在实值函数 f ( x ) f(x) f(x),使得每个节点处的值 f ( x i ) f(x_{i}) f(xi) s x i s_{x_{i}} sxi相等,即
s ( x i ) = f ( x i ) , i = 0 , 1 , ⋯   , n s(x_{i})=f(x_{i}),i=0,1,\cdots,n s(xi)=f(xi),i=0,1,,n
则称 s ( x ) s(x) s(x)是实值函数 f ( x ) f(x) f(x) m m m次样条插值函数。

需要确定 4 n 4n 4n个系数,本身具备 4 n − 2 4n-2 4n2个条件。

边界条件

第一类边界条件:即满足 s ′ ( x 0 ) = f ′ ( x 0 ) , s ′ ( x n ) = f ′ ( x n ) s'(x_{0})=f'(x_{0}),s'(x_{n})=f'(x_{n}) s(x0)=f(x0),s(xn)=f(xn)两个条件,其中 f ( x ) f(x) f(x)是实值函数。
第二类边界条件:即满足 s ′ ′ ( x 0 ) = f ′ ′ ( x 0 ) , s ′ ′ ( x n ) = f ′ ′ ( x n ) s''(x_{0})=f''(x_{0}),s''(x_{n})=f''(x_{n}) s(x0)=f(x0),s(xn)=f(xn)两个条件,其中 f ( x ) f(x) f(x)是实值函数。又称为自然边界条件。
第三类边界条件:即满足 s ′ ( x 0 − 0 ) = s ′ ( x 0 + 0 ) , s ′ ′ ( x n − 0 ) = s ′ ′ ( x n + 0 ) s'(x_{0}-0)=s'(x_{0}+0),s''(x_{n}-0)=s''(x_{n}+0) s(x00)=s(x0+0),s(xn0)=s(xn+0)两个条件。

推导三次样条函数

基本原理首先求出由待定系数组成的 s ( x ) s(x) s(x),以及其一阶导数 s ′ ( x ) s'(x) s(x)和二阶导数 s ′ ′ ( x ) s''(x) s(x),然后将其带入到 4 n 4n 4n个条件中,得到关于待定系数的方程组,最后求解方程组得到待定系数,并最终确定插值函数 s ( x ) s(x) s(x).

追赶法求解方程组

三次样条曲线拟合算法实现



你可能感兴趣的:(算法的乐趣)