《白话机器学习的数学》-多项式回归与正则化

《白话机器学习的数学》-多项式回归与正则化

引言

在机器学习和统计学中,多项式回归是一种强大的工具,用于建模和分析数据之间的非线性关系。然而,多项式回归模型在高阶情况下容易出现过拟合问题,即模型对训练数据拟合得过于完美,但在新的数据上表现不佳,本质原因是多项式高次项的影响过大。为了解决这一问题,正则化技术被引入,以限制模型拟合的高次项系数过大的问题。本文将详细介绍多项式回归的基本原理、正则化方法及其在实际应用中的效果。

1.多项式回归基础

1.1定义

多项式回归是一种特殊的线性回归,它通过引入多项式特征来扩展线性模型的能力。对于一个一维输入变量\(x\),多项式回归模型可以表示为:

\[y=\theta_0+\theta_1 x+\theta_2 x^2+\cdots+\theta_n x^n \]

其中,\(\theta_0,\theta_1,\ldots,\theta_n\)是模型参数。

1.2特征矩阵

在实际应用中,输入数据通常需要转换为特征矩阵。对于多项式回归,特征矩阵\(X\)的每一列对应于输入变量\(x\)的不同幂次。例如,对于一个三阶多项式回归,特征矩阵可以表示为:

\[X=\begin{bmatrix} 1&x_1&x_1^2&x_1^3\\ 1&x_2&x_2^2&x_2^3\\ \vdots&\vdots&\vdots&\vdots\\ 1&x_m&x_m^2&x_m^3\\ \end{bmatrix} \]

其中,\(m\)是样本数量。

1.3模型拟合

多项式回归的目标是找到一组参数\(\theta\),使得模型对训练数据的拟合误差最小化。书中使用的是如下目标函数:

\[E(\theta)=\frac{1}{2}\sum_{i=1}^{m}(y_i-f(x_i))^2 \]

其中,\(f(x)=\theta_0+\theta_1 x+\theta_2 x^2+\cdots+\theta_n x^n\)是预测函数。

2.过拟合问题

2.1定义

过拟合是指模型在训练数据上表现得非常好,但在新的、未见过的数据上表现不佳的现象。多项式回归模型在高阶情况下容易出现过拟合,因为模型过于复杂,能够完美拟合训练数据中的噪声。

2.2原因

模型复杂度:高阶多项式模型具有更多的参数,能够拟合更复杂的函数关系,但也更容易捕捉到训练数据中的噪声。
数据量不足:当训练数据量较少时,模型更容易过拟合。

2.3影响

过拟合会导致模型在新数据上的预测性能下降,降低模型的泛化能力。

3.正则化技术

3.1定义

正则化是一种用于防止过拟合的技术,通过在损失函数中添加一个惩罚项来限制模型的复杂度。常见的正则化方法包括L1正则化(Lasso)和L2正则化(Ridge)。

3.2 L1正则化(Lasso)

L1正则化通过在损失函数中添加参数的绝对值作为惩罚项:

\[E(\theta)=\frac{1}{2}\sum_{i=1}^{m}(y_i-f(x_i))^2+\lambda\sum_{j=1}^{n}|\theta_j| \]

其中,\(\lambda\)是正则化强度,控制惩罚项的大小。
L1正则化的一个重要特性是能够使部分参数变为零,从而实现特征选择。

3.3 L2正则化(Ridge)

L2正则化通过在损失函数中添加参数的平方和作为惩罚项:

\[E(\theta)=\frac{1}{2}\sum_{i=1}^{m}(y_i-f(x_i))^2+\lambda\sum_{j=1}^{n}\theta_j^2 \]

其中,\(\lambda\)是正则化强度。
L2正则化可以使参数的值变小,但不会使参数变为零,从而平滑模型的权重。

3.4弹性网络正则化(Elastic Net)

弹性网络正则化结合了L1和L2正则化,通过同时添加两种惩罚项来限制模型的复杂度:

\[E(\theta)=\frac{1}{2}\sum_{i=1}^{m}(y_i-f(x_i))^2+\lambda_1\sum_{j=1}^{n}|\theta_j|+\lambda_2\sum_{j=1}^{n}\theta_j^2 \]

其中,\(\lambda_1\)\(\lambda_2\)分别控制L1和L2正则化的强度。

4.实验与分析

4.1实验设置

为了验证多项式回归和正则化的效果,我们使用一个简单的非线性函数\(g(x)=0.1(x^3+x^2+x)\)生成训练数据,并在数据中加入随机噪声。我们分别使用未正则化的多项式回归和正则化的多项式回归进行拟合。

# 真正的函数
def g(x):
    return 0.1 * (x ** 3 + x ** 2 + x)

# 随意准备一些向真正的函数加入了一点噪声的训练数据
train_x = np.linspace(-2, 2, 8)
train_y = g(train_x) + np.random.randn(train_x.size) * 0.05

# 绘图确认
x = np.linspace(-2, 2, 100)
plt.plot(train_x, train_y, 'o')  # 绘制训练数据点
plt.plot(x, g(x), linestyle='dashed')  # 绘制真实的函数曲线
plt.ylim(-1, 2)  # 设置y轴的范围
plt.show()  # 显示图形

4.2数据标准化

Z得分标准化,也称为标准分数标准化或Z-score标准化,是一种常用的数据标准化方法。它通过计算数据点与数据集均值的差,然后除以数据集的标准差,来转换数据。这种方法使得转换后的数据具有均值为0和标准差为1的性质。

Z得分标准化的公式

Z得分标准化的公式为:

\[z^{(i)} = \frac{x^{(i)} - \mu}{\sigma} \]

# 标准化
mu = train_x.mean()
sigma = train_x.std()
def standardize(x):
    return (x - mu) / sigma

train_z = standardize(train_x)

Z得分标准化的作用

  1. 消除量纲影响
    Z得分标准化可以消除不同特征之间的量纲影响,使得它们可以在同一尺度上进行比较。

  2. 提高模型性能
    对于许多机器学习算法(如线性回归、逻辑回归、支持向量机等),标准化后的数据可以提高模型的收敛速度和预测性能。

创建训练数据的矩阵

# 创建训练数据的矩阵
def to_matrix(x):
    return np.vstack([
        np.ones(x.size),
        x,
        x ** 2,
        x ** 3,
        x ** 4,
        x ** 5,
        x ** 6,
        x ** 7,
        x ** 8,
        x ** 9,
        x ** 10,
    ]).T

X = to_matrix(train_z)

更新参数\(\theta\)

未正则化的多项式回归

梯度下降法

为了最小化损失函数,我们可以使用梯度下降法。梯度下降法的基本思想是通过迭代更新参数来减少损失函数的值。每次更新的公式为:

\[\theta_j=\theta_j-\eta\frac{\partial E(\theta)}{\partial\theta_j} \]

其中,\(\eta\)是学习率,\(\frac{\partial E(\theta)}{\partial\theta_j}\)是损失函数对参数\(\theta_j\)的偏导数。

计算偏导数

我们需要计算损失函数对每个参数\(\theta_j\)的偏导数。对于给定的参数\(\theta_j\),偏导数为:

\[\frac{\partial E(\theta)}{\partial\theta_j}=\frac{\partial}{\partial\theta_j}\left(\frac{1}{2}\sum_{i=1}^{m}(y_i-f(x_i))^2\right) \]

\[=\frac{1}{2}\sum_{i=1}^{m}2(y_i-f(x_i))\frac{\partial(y_i-f(x_i))}{\partial\theta_j} \]

\[=\sum_{i=1}^{m}(y_i-f(x_i))\frac{\partial f(x_i)}{\partial\theta_j} \]

由于\(f(x_i)=\theta_0+\theta_1 x_i+\theta_2 x_i^2+\ldots+\theta_d x_i^d\),我们有:

\[\frac{\partial f(x_i)}{\partial\theta_j}=x_i^j \]

因此,偏导数为:

\[\frac{\partial E(\theta)}{\partial\theta_j}=\sum_{i=1}^{m}(y_i-f(x_i))x_i^j \]

参数更新公式

将偏导数代入梯度下降法的更新公式,得到参数更新公式:

\[\theta_j=\theta_j-\eta\sum_{i=1}^{m}(y_i-f(x_i))x_i^j \]

这就是多项式回归的参数更新公式。通过迭代更新参数,我们可以最小化损失函数,从而找到最优的多项式回归模型。

# 参数初始化
theta = np.random.randn(X.shape[1])

# 预测函数
def f(x):
    return np.dot(x, theta)

# 损失函数(均方误差)
def E(x, y):
    return 0.5 * np.sum((y - f(x)) ** 2)

# 学习率
ETA = 1e-4

# 误差
diff = 1

# 更新次数
count = 0

# 重复学习
error = E(X, train_y)


while error >1e-1:
    theta = theta - ETA * np.dot(f(X) - train_y, X)
    error = E(X, train_y)
    count += 1
    print(f"count {count}: theta = {theta}, error = {error}")

x = np.linspace(-2, 2, 100)
z = standardize(x)
plt.plot(train_z, train_y, 'o')  # 绘制训练数据点
plt.plot(z, f(to_matrix(z)))  # 绘制预测函数曲线
plt.show()

正则化的多项式回归

目标函数:

\[E(\theta)=\frac{1}{2}\sum_{i=1}^{m}(y_i-f(x_i))^2+\frac{\lambda}{2}\sum_{j=1}^{m}\theta_j^2 \]

同样运用梯度下降法:

梯度下降法更新参数

为了最小化损失函数,我们同样使用梯度下降法。梯度下降法的更新公式为:

\[\theta_j=\theta_j-\eta\frac{\partial E(\theta)}{\partial\theta_j} \]

其中,\(\eta\)是学习率,\(\frac{\partial E(\theta)}{\partial\theta_j}\)是损失函数对参数\(\theta_j\)的偏导数。

计算偏导数

我们需要计算损失函数对每个参数\(\theta_j\)的偏导数。对于给定的参数\(\theta_j\),偏导数为:

\[\frac{\partial E(\theta)}{\partial\theta_j}=\frac{\partial}{\partial\theta_j}\left(\frac{1}{2}\sum_{i=1}^{m}(y_i-f(x_i))^2\right)+\frac{\partial}{\partial\theta_j}\left(\frac{\lambda}{2}\sum_{j=1}^{m}\theta_j^2\right) \]

第一部分是原始损失函数的偏导数,第二部分是L2正则化项的偏导数。计算得到:

\[\frac{\partial E(\theta)}{\partial\theta_j}=\sum_{i=1}^{m}(y_i-f(x_i))\frac{\partial f(x_i)}{\partial\theta_j}+\lambda\theta_j \]

由于\(f(x_i)=\theta_0+\theta_1 x_i+\theta_2 x_i^2+\ldots+\theta_d x_i^d\),我们有:

\[\frac{\partial f(x_i)}{\partial\theta_j}=x_i^j \]

因此,偏导数为:

\[\frac{\partial E(\theta)}{\partial\theta_j}=\sum_{i=1}^{m}(y_i-f(x_i))x_i^j+\lambda\theta_j \]

参数更新公式

将偏导数代入梯度下降法的更新公式,得到参数更新公式:

\[\theta_j=\theta_j-\eta\left(\sum_{i=1}^{m}(y_i-f(x_i))x_i^j+\lambda\theta_j\right)(j>0) \]

由于正则化项的\(\theta_0\)项不参与正则化,所以对其求导为0。

\(\theta_0\)的更新公式不同与其他项,其更新公式为:

\[\theta_0=\theta_0-\eta\left(\sum_{i=1}^{m}(y_i-f(x_i))x_i^j\right) \]

# 参数初始化
theta = np.random.randn(X.shape[1])

# 预测函数
def f(x):
    return np.dot(x, theta)

# 损失函数(均方误差)
def E(x, y):
    return 0.5 * np.sum((y - f(x)) ** 2)

# 学习率
ETA = 1e-4

# 误差
diff = 1

# 更新次数
count = 0

# 重复学习
error = E(X, train_y)

# 正则化常量
Lambda = 1

while error >1e-1:
    reg_term = Lambda * np.hstack([0, theta[1:]])
    theta = theta - ETA * (np.dot(f(X) - train_y, X) +reg_term)
    error = E(X, train_y)
    count += 1
    print(f"count {count}: theta = {theta}, error = {error}")

x = np.linspace(-2, 2, 100)
z = standardize(x)
plt.plot(train_z, train_y, 'o')  # 绘制训练数据点
plt.plot(z, f(to_matrix(z)))  # 绘制预测函数曲线
plt.show()

5.实验结果

5.1未正则化的多项式回归

输出结果:ount 13000: theta = [ 0.32640492 -0.43825585 -0.98789911  0.64256821  0.38741359  0.81662205
-0.35208609 -0.8714038   0.90111606  0.20930602 -0.31723059], error = 0.09999612459480543

输出结果2:count 23595: theta = [-0.24284925  0.23642262  1.39769326  0.17111653  0.13012056 -0.0250435
-1.61520782 -0.05166621  0.76012831  0.02598586 -0.07753932], error = 0.09999855414889833

每一次的输出结果基本都不一样,我这里只输出了两次,拟合的都还行,我主要想通过结果来看正则化相对没正则化的作用。

5.2正则化的多项式回归

输出结果:count 5010: theta = [ 0.25202562  0.24848219  0.40114505 -0.11122506 -1.49636194 -0.36291042
0.68096777  0.7441237   0.39375661 -0.22923125 -0.18662535], error = 0.09996575790301986

输出结果2:count 6406: theta = [ 0.24642288  0.11344846 -0.06116742  0.13901859  0.58049771  0.08822995
-0.56083389 -0.04527615 -0.06436864  0.00863868  0.08957412], error = 0.09999514505590282

结论

通过这两次测试,发现两件事,正则化高次项系数普遍比较小,但也有可能比不正则化高,这并不是一定的,第二正则化收敛的更快,不正则化要上万次,正则化基本几千次就行了。

忘了说了,书中跳出循环的条件是diff足够小,而我跳出循环的条件改为了error足够小。我认为这样更合理,因为说不定误差error很大的时候,可能还就diff很小然后跳出循环,这样明显不合理。

posted @ 2025-08-15 17:23  数理生风  阅读(40)  评论(0)    收藏  举报