机器学习之多项式曲线拟合的Python实现

1、什么是多项式曲线拟合

多项式拟合就是我们需要得到一个无限逼近真实曲线的的多项式:
y ( x , W ) = w 0 + w 1 x + w 2 x 2 + … + w k x k = ∑ i = 0 k w i x i y(x,W) = w_0 + w_1x + w_2x^2 + … + w_kx^k = \sum_{i=0}^{k}w_ix^i y(x,W)=w0+w1x+w2x2++wkxk=i=0kwixi
注:

k k k 为多项式的阶数

w 0 , … , w k w_0,…,w_k w0,,wk为多项式的系数,记为 W W W

使用均方误差作为误差函数对拟合出的多项式进行评估,公式如下:
E ( W ) = 1 2 ∑ i = 1 n ( y ( x i , W ) − t n ) 2 = 1 2 ( X W − T ) T ( X W − T ) E(W) = \frac{1}{2} \sum_{i=1}^{n}(y(x_i,W)-t_n)^2=\frac{1}{2} (XW-T)^T(XW-T) E(W)=21i=1n(y(xi,W)tn)2=21(XWT)T(XWT)
其中:
W = [ w 0 w 1 ⋮ w k ] , X = [ 1 x 1 ⋯ x 1 k 1 x 2 ⋯ x 2 k ⋮ ⋮ ⋱ ⋮ 1 x n ⋯ x n k ] W =\begin{bmatrix} w_0 \\ w_1 \\ \vdots \\ w_k \\ \end{bmatrix},X=\begin{bmatrix} 1 & x_1 & \cdots & x_1^k \\ 1 & x_2 & \cdots & x_2^k \\ \vdots & \vdots & \ddots & \vdots \\ 1 & x_n & \cdots & x_n^k \\ \end{bmatrix} W=w0w1wkX=111x1x2xnx1kx2kxnk
注:

n n n为样本的点数

k k k为拟合出的多项式的阶数

拟合数据的目的即为最小化误差函数,因为误差函数是多项式系数W的二次函数,所以存在唯一最小值,且在导数为零处取得。对W求导并令导数为零得到:
∂ E ( W ) ∂ W = X T X W − X T T W = ( X T X ) − 1 X T T \frac{\partial E(W)}{\partial W} = X^TXW-X^TT \\ W = (X^TX)^{-1}X^TT WE(W)=XTXWXTTW=(XTX)1XTT
故可以通过矩阵运算得到W。

2、泛化能力

泛化能力(generalization ability)是指机器学习算法对新鲜样本的适应能力,简而言之是在原有的数据集上添加新的数据集,通过训练输出一个合理的结果。学习的目的是学到隐含在数据背后的规律,对具有同一规律的学习集以外的数据,经过训练的网络也能给出合适的输出,该能力称为泛化能力。

2.1、欠拟合

欠拟合(under-fitting)是指模型拟合程度不高,数据距离拟合曲线较远,模型没有很好地捕捉到数据特征,不能够很好地拟合数据。

2.2、过拟合

过拟合(over-fitting)其实就是所建的机器学习模型或者是深度学习模型在训练样本中表现得过于优越学到了很多没必要的特征,导致在验证数据集以及测试数据集中表现不佳。

3、如何对拟合结果做出评估

3.1、使用拟合优度指数进行评价

y y y为待拟合数值,其均值为 y ˉ \bar y yˉ,拟合值为 y ^ \hat y y^,则:

总平方和( S S T SST SST):
S S T = ∑ i = 1 n ( y i − y ˉ ) 2 SST = \sum_{i=1}^{n}(y_i- \bar y)^2 SST=i=1n(yiyˉ)2
回归平方和( S S E SSE SSE):
S S R = ∑ i = 1 n ( y ^ i − y ˉ ) 2 SSR = \sum_{i=1}^{n}(\hat y_i- \bar y)^2 SSR=i=1n(y^iyˉ)2
残差平方和( S S E SSE SSE)实际值与预测值之间差的平方之和:
S S E = ∑ i = 1 n ( y i − y ^ i ) 2 SSE = \sum_{i=1}^{n}(y_i - \hat y_i)^2 SSE=i=1n(yiy^i)2
则有:
S S T = S S R + S S E SST = SSR + SSE SST=SSR+SSE
决定系数( R 2 R^2 R2):
R 2 = S S R S S T = ∑ i = 1 n ( y ^ i − y ˉ ) 2 ∑ i = 1 n ( y i − y ^ i ) 2 = 1 − S S E S S T R^2 = \frac{SSR}{SST} = \frac{\sum_{i=1}^{n}(\hat y_i- \bar y)^2}{\sum_{i=1}^{n}(y_i - \hat y_i)^2} = 1 - \frac{SSE}{SST} R2=SSTSSR=i=1n(yiy^i)2i=1n(y^iyˉ)2=1SSTSSE
通常认为,用 y ˉ \bar y yˉ去预测 y y y是一个最差情况(实际上可能比这个还差,比如实际值都是 1 1 1,预测值却都是 10000 10000 10000),任何预测都应该比这个准。

于是 R 2 = 1 − S S E S S T R^2 = 1 - \frac{SSE}{SST} R2=1SSTSSE(也就是预测值和真实值的残差平方和)要小于平均值和真实值的残差平方和,即 S S E < S S T SSESSE<SST R 2 R_2 R2一般介于 0 0 0 1 1 1之间,越大拟合效果越好,但如果模型出乎意料的非常差,可能为负。

4、利用正则化改善过拟合

最小化误差函数用于衡量对于任意给定的 w w w值,函数 y ( x , W ) y(x,W) y(x,W)与训练数据的差异(即将 x x x带入函数 y ( x , W ) y(x,W) y(x,W)得到的值与训练数据集中相应的值得差异)。

其中,误差函数为:
E ( W ) = 1 2 ∑ n = 1 N { y ( x n , W ) − t n } 2 E(W) = \frac{1}{2} \sum_{n=1}^{N} \{y(x_n,W)-t_n \}^2 E(W)=21n=1N{ y(xn,W)tn}2
注:

1 2 \frac{1}{2} 21是为了方便计算引入的

N N N为样本的个数

可以通过选择使得 E ( w ) E(w) E(w)尽可能小的 w w w来解决曲线拟合问题。

由于误差函数是系数 w w w的二次函数,因此它关于系数的导数是 w w w的线性函数,所以误差函数的最小值有一个唯一解,记作 W ∗ W∗ W,可以用解析的方式求出。最终的多项式函数由函数 y ( x , W ∗ ) y(x, W∗) y(x,W)给出。

在定义误差函数是增加惩罚项(这种惩罚项最简单的形式采用所有系数的平方和),使多项式系数被有效控制,不会过大。

则惩罚项为:
∣ ∣ w ∣ ∣ 2 = w T w = w 0 2 + w 1 2 + … + w k 2 ||w||^2 = wTw = {w_0}^2 + {w_1}^2 + … + {w_k}^2 w2=wTw=w02+w12++wk2
新定义的误差函数为:
E ~ ( w ) = 1 2 ∑ n = 1 N { y ( x n , w ) − t n } 2 + λ 2 ∣ ∣ w ∣ ∣ 2 \tilde{E}(w) = \frac{1}{2} \sum_{n=1}^{N} \{y(x_n,w)-t_n \}^2+\frac{\lambda}{2}||w||^2 E~(w)=21n=1N{ y(xn,w)tn}2+2λw2
注:

系数 λ \lambda λ控制了正则化项相对于平方和误差项的重要性,被称之为正则化系数。

求导置零得到:
W = ( X T X + λ E m + 1 ) − 1 X T T W=(X^TX+\lambda E_{m+1})^{-1}X^TT W=(XTX+λEm+1)1XTT

W W W为多项式的系数

通过改变 λ \lambda λ的大小(如: λ \lambda λ较小时 l n λ = − 18 ln\lambda = − 18 lnλ=18 λ \lambda λ较大时 l n λ = − 0 ln\lambda = − 0 lnλ=0 ))可以降低多项式的阶数,防止过拟合。

代码实现

import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import Ridge
from sklearn.metrics import r2_score
from sklearn.preprocessing import PolynomialFeatures

from PolynomialFitting.PolynomialFittingTest import PolynomialFittingTest


class PolynomialFitting:
    @staticmethod
    def fitting(x_list, y_list, degree=2):
        """
        拟合
        :param y_list:Y坐标
        :param x_list: X坐标
        :param degree: 阶数
        :return:w参数矩阵, r2拟合优度指数, f可以参与计算的多项式
        """
        # 参数格式效验
        if not isinstance(x_list, list):
            raise Exception(print("X坐标参数类型错误"))
        if not isinstance(y_list, list):
            raise Exception(print("Y坐标参数类型错误"))
        if not isinstance(degree, int):
            raise Exception(print("多项式阶数错误"))

        # 将list装换为ndarray
        try:
            x_array = np.array(x_list)
            y_array = np.array(y_list)
        except:
            raise Exception(print("坐标转换失败"))

        # 对X坐标和Y坐标的形状进行比较,不一致则无法完成拟合
        x_shape = x_array.shape
        y_shape = y_array.shape
        if x_shape != y_shape:
            raise Exception("X坐标与Y坐标无法对应")

        # w为拟合后的多项式的系数
        try:
            w = np.polyfit(x_array, y_array, degree)
        except:
            raise Exception("多项式阶数过高")

        f = PolynomialFitting.get_fx(w)
        r2 = PolynomialFitting.get_r2(y_array, x_array, f)

        return w, r2, f

    @staticmethod
    def fitting_with_lambda(x_list, y_list, degree=2, lambda_=0.001):
        """
        正则化拟合(利用岭回归为其添加惩罚项)
        :param x_list: 训练集的X坐标
        :param y_list: 训练集的Y坐标
        :param degree: 要拟合的多项式的阶数
        :param lambda_:
        :return:
        """
        # 参数格式效验
        if not isinstance(x_list, list):
            raise Exception(print("X坐标参数类型错误"))
        if not isinstance(y_list, list):
            raise Exception(print("Y坐标参数类型错误"))
        if not isinstance(degree, int):
            raise Exception(print("多项式阶数错误"))
        if not isinstance(lambda_, float):
            raise Exception(print("lambda系数错误"))

        if lambda_ <= 0.0:
            raise Exception(print("非法的lambda值"))

        # 将list装换为ndarray
        try:
            x_array = np.array([x_list])
            y_array = np.array([y_list])
        except:
            raise Exception(print("坐标转换失败"))

        # 对X坐标和Y坐标的形状进行比较,不一致则无法完成拟合
        x_shape = x_array.shape
        y_shape = y_array.shape
        if x_shape != y_shape:
            raise Exception("X坐标与Y坐标无法对应")

        # 矩阵转置
        x_array = x_array.T
        y_array = y_array.T

        # 设置多项式的阶数
        poly = PolynomialFeatures(degree=degree)

        x_list_ploy = poly.fit_transform(x_array)

        # Ridge(岭回归)通过对系数大小施加惩罚来解决拟合的一些问题。
        # alpha >= 0 是控制系数收缩量的复杂性参数,alpha值越大,收缩量越大,模型对共线性的鲁棒性也更强。
        lr = Ridge(alpha=(lambda_ / 2))
        # Ridge 用 fit 方法完成拟合,并将模型系数 w 存储在其 coef_ 成员中
        lr.fit(x_list_ploy, y_array)
        # 获取多项式系数(参数从低向高)
        w = lr.coef_[0]
        # 逆序
        w_l = w.tolist()
        w_l.reverse()
        w = np.array(w_l)

        # 获取可以参与计算的多项式表达式
        f = PolynomialFitting.get_fx(w)
        # 计算r2拟合优度指数
        r2 = PolynomialFitting.get_r2(y_list, x_list, f)
        return w, r2, f

    @staticmethod
    def print_polynomial(w_list):
        """
        获取多项式
        :param w_list: 参数列表
        :return:
        """
        fx = "y = "
        for i in range(0, len(w_list)):
            param = w_list[i]
            order = len(w_list) - 1 - i
            if order:
                fx += "{} * x ^ {} + ".format(param, order)
            else:
                fx += "{}".format(param)
        return fx

    @staticmethod
    def get_fx(w_list):
        """
        获取可以参与计算的多项式
        :param w_list:w参数矩阵
        :return:可以参与计算的多项式
        """
        f = np.poly1d(w_list)
        return f

    @staticmethod
    def get_r2(y_ture, x_ture, f):
        """
        计算拟合优度指数
        :param y_ture:Y坐标真实值
        :param x_ture:X坐标真实值
        :param f:可以参与计算的多项式
        :return:r2拟合优度指数
        """
        # 计算R2
        # coefficient_of_determination = r2_score(y_ture, f(x_ture))
        r2 = r2_score(y_ture, f(x_ture))
        return r2

    @staticmethod
    def get_best_fitting(x_list, y_list):
        """
        获取最优拟合结果
        :param x_list: X坐标数组
        :param y_list: Y坐标数组
        :return:
        """
        degree = 1
        best_degree = 1
        w_r, r2_r, f_r = PolynomialFitting.fitting(x_list, y_list, degree)
        while True:
            try:
                # 多项式拟合
                w, r2, f = PolynomialFitting.fitting(x_list, y_list, degree)
                # print("多项式参数列表:{}".format(w))
                # print("多项式阶数:{}".format(order))
                # print("拟合优度指数:{}".format(r2))
                print("阶数:{}\t拟合优度指数:{}".format(degree, r2))

                if r2 <= 0 or r2 > 1:
                    break

                if w[0] == 0:
                    degree += 1
                    continue

                if r2 > r2_r:
                    w_r = w
                    r2_r = r2
                    f_r = f
                    best_degree = degree

                degree += 1
            except:
                break

            # time.sleep(0.5)
        print("正常结束")
        return w_r, r2_r, f_r, best_degree

    @staticmethod
    def get_best_fitting_with(x_real, y_real):
        best_degree = 2
        w_r, r2_r, f_r = PolynomialFitting.fitting_with_lambda(x_real, y_real)
        lambda_r = np.exp(0)

        for degree in range(2, 200):
            for i in range(0, -19, -1):
                lambda_ = np.exp(i)
                w, r2, f = PolynomialFitting.fitting_with_lambda(x_real, y_real, degree=degree, lambda_=lambda_)
                print("多项式阶数:{},lambda系数:{}".format(degree, lambda_))

                # print("多项式参数列表:{}".format(w_2))
                # print("多项式阶数:{}".format(degree))
                # print("lambda系数:{}".format(lambda_))
                # print("拟合优度指数:{}".format(r2_2))
                if r2 > r2_r:
                    w_r = w
                    r2_r = r2
                    f_r = f
                    best_degree = degree
                    lambda_r = lambda_

        return w_r, r2_r, f_r, best_degree, lambda_r


if __name__ == '__main__':
    # 生成测试数据
    X_train, y_train = PolynomialFittingTest.create_data(100)
    print(X_train)
    print(y_train)
    x_l = X_train.reshape((1, 100))[0].tolist()
    y_l = y_train.reshape((1, 100))[0].tolist()

    # 未考虑正则化
    w_1, r2_1, f1, best_degree_1 = PolynomialFitting.get_best_fitting(x_l, y_l)
    print("多项式参数列表:{}".format(w_1))
    print("多项式阶数:{}".format(best_degree_1))
    print("拟合优度指数:{}".format(r2_1))

    # 考虑正则化
    w_2, r2_2, f2, best_degree_2, lambda_2 = PolynomialFitting.get_best_fitting_with(x_l, y_l)
    print("多项式参数列表:{}".format(w_2))
    print("多项式阶数:{}".format(best_degree_2))
    print("lambda系数:{}".format(lambda_2))
    print("拟合优度指数:{}".format(r2_2))

    # 绘制画布
    plt.figure()
    # 解决中文显示问题
    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.rcParams['axes.unicode_minus'] = False
    # 绘制散点图
    plt.scatter(x_l, y_l)

    # 常规拟合
    y_pre_1 = f1(x_l)
    plt.plot(x_l, y_pre_1, color='b', label="常规拟合")

    # 绘制岭回归曲线
    y_pre_2 = f2(x_l)
    plt.plot(x_l, y_pre_2, color='r', label="岭回归")

    # 真实曲线
    y_sin = PolynomialFittingTest.sin_fun(X_train)
    plt.plot(x_l, y_sin, color='g', label="$sin(x)$")

    # 设置图片标题
    plt.title("预测曲线")
    # 显示图例
    plt.legend()
    plt.show()

参考资料

[Python] 多项式曲线拟合(Polynomial Curve Fitting):https://blog.csdn.net/m0_38068229/article/details/105202554

机器学习入门之多项式曲线拟合:https://blog.csdn.net/xwl198937/article/details/52210156

Numpy实现多项式曲线拟合:https://www.cnblogs.com/zhjblogs/p/14725864.html

numpy进行多项式拟合:https://www.cnblogs.com/yjybupt/p/12972682.html

过拟合和欠拟合:https://blog.csdn.net/weixin_42575020/article/details/82949285

泛化能力_百度百科:https://baike.baidu.com/item/%E6%B3%9B%E5%8C%96%E8%83%BD%E5%8A%9B/3323240

欠拟合_百度百科:https://baike.baidu.com/item/%E6%AC%A0%E6%8B%9F%E5%90%88/22692155

多项式曲线拟合:https://zhuanlan.zhihu.com/p/53056358

你可能感兴趣的:(Python,机器学习,python,人工智能,过拟合,多项式拟合)