哈工大2022机器学习实验一:曲线拟合

这个实验的要求写的还是挺清楚的(与上学期相比),本博客采用python实现,科学计算库采用 numpy,作图采用 matplotlib.pyplot,为了简便在文件开头import如下:

import numpy as np
import matplotlib.pyplot as plt

本实验用到的numpy函数

一般把 numpy简写为 np(import numpy as np)。下面简单介绍一下实验中用到的numpy函数。下面的代码均需要在最前面加上 import numpy as np

np.array

该函数返回一个 numpy.ndarray对象,可以理解为一个多维数组(本实验中仅会用到一维(可以当作列向量)和二维(矩阵))。下面用小写的x \pmb x x x表示列向量,大写的A A A表示矩阵。 A.T表示A A A的转置。对 ndarray的运算一般都是逐元素的。

>>> x = np.array([1,2,3])
>>> x
array([1, 2, 3])
>>> A = np.array([[2,3,4],[5,6,7]])
>>> A
array([[2, 3, 4],
       [5, 6, 7]])
>>> A.T
array([[2, 5],
       [3, 6],
       [4, 7]])
>>> A + 1
array([[3, 4, 5],
       [6, 7, 8]])
>>> A * 2
array([[ 4,  6,  8],
       [10, 12, 14]])

np.random

np.random模块中包含几个生成随机数的函数。在本实验中用随机初始化参数(梯度下降法),给数据添加噪声。

>>> np.random.rand(3, 3)
array([[8.18713933e-01, 5.46592778e-01, 1.36380542e-01],
       [9.85514865e-01, 7.07323389e-01, 2.51858374e-04],
       [3.14683662e-01, 4.74980699e-02, 4.39658301e-01]])

>>> np.random.rand(1)
array([0.70944563])
>>> np.random.rand(5)
array([0.03911319, 0.67572368, 0.98884287, 0.12501456, 0.39870096])
>>> np.random.randn(3, 3)

数学函数

本实验中只用到了 np.sin。这些数学函数是对 np.ndarray逐元素操作的:

>>> x = np.array([0, 3.1415, 3.1415 / 2])
>>> np.round(np.sin(x))
array([0., 0., 1.])

此外,还有 np.lognp.exp等与python的 math库相似的函数(只不过是对多维数组进行逐元素运算)。

np.dot

返回两个矩阵的乘积。与线性代数中的矩阵乘法一致。要求第一个矩阵的列等于第二个矩阵的行数。特殊地,当其中一个为一维数组时,形状会自动适配为n × 1 n\times1 n ×1或1 × n . 1\times n.1 ×n .

>>> x = np.array([1,2,3])
>>> A = np.array([[1,1,1],[2,2,2],[3,3,3]])
>>> np.dot(x,A)
array([14, 14, 14])
>>> np.dot(A,x)
array([ 6, 12, 18])

>>> x_2D = np.array([[1,2,3]])
>>> np.dot(x_2D, A)
array([[14, 14, 14]])
>>> np.dot(A, x_2D)
Traceback (most recent call last):
  File "", line 1, in <module>
  File "", line 5, in dot
ValueError: shapes (3,3) and (1,3) not aligned: 3 (dim 1) != 1 (dim 0)

np.eye

np.eye(n)返回一个n阶单位阵。

>>> A = np.eye(3)
>>> A
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])

线性代数相关

np.linalg是与线性代数有关的库。

>>> A
array([[1, 0, 0],
       [0, 2, 0],
       [0, 0, 3]])
>>> np.linalg.inv(A)
array([[1.        , 0.        , 0.        ],
       [0.        , 0.5       , 0.        ],
       [0.        , 0.        , 0.33333333]])
>>> x = np.array([1,2,3])
>>> np.linalg.norm(x)
3.7416573867739413
>>> np.linalg.eigvals(A)
array([1., 2., 3.])

生成数据

生成数据要求加入噪声(误差)。上课讲的时候举的例子就是正弦函数,我们这里也采用标准的正弦函数y = sin ⁡ x . y=\sin x.y =sin x .(加入噪声后即为y = sin ⁡ x + ϵ , y=\sin x+\epsilon,y =sin x +ϵ,其中ϵ ∼ N ( 0 , σ 2 ) \epsilon\sim N(0, \sigma^2)ϵ∼N (0 ,σ2 ),由于sin ⁡ x \sin x sin x的最大值为1 1 1,我们把误差的方差设小一点,这里设成1 25 \frac{1}{25}25 1 ​)。

'''
返回数据集,形如[[x_1, y_1], [x_2, y_2], ..., [x_N, y_N]]
保证 bound[0]
def get_dataset(N = 100, bound = (0, 10)):
    l, r = bound

    x = sorted(np.random.rand(N) * (r - l) + l)

    y = np.sin(x) + np.random.randn(N) / 5
    return np.array([x,y]).T

产生的数据集每行为一个平面上的点。产生的数据看起来像这样:

哈工大2022机器学习实验一:曲线拟合
隐隐约约能看出来是个正弦函数的形状。产生上面图像的代码如下:
dataset = get_dataset(bound = (-3, 3))

for [x, y] in dataset:
    plt.scatter(x, y, color = 'red')
plt.show()

最小二乘法拟合

下面我们分别用四种方法(最小二乘,正则项/岭回归,梯度下降法,共轭梯度法)以用多项式拟合上述干扰过的正弦曲线。

解析解推导

简单回忆一下最小二乘法的原理:现在我们想用一个m m m次多项式
f ( x ) = w 0 + w 1 x + w 2 x 2 + . . . + w m x m f(x)=w_0+w_1x+w_2x^2+…+w_mx^m f (x )=w 0 ​+w 1 ​x +w 2 ​x 2 +…+w m ​x m
来近似真实函数y = sin ⁡ x . y=\sin x.y =sin x .我们的目标是最小化数据集( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x N , y N ) (x_1,y_1),(x_2,y_2),…,(x_N,y_N)(x 1 ​,y 1 ​),(x 2 ​,y 2 ​),…,(x N ​,y N ​)上的损失L L L(loss),这里损失函数采用平方误差:
L = ∑ i = 1 N [ y i − f ( x i ) ] 2 L=\sum\limits_{i=1}^N[y_i-f(x_i)]^2 L =i =1 ∑N ​[y i ​−f (x i ​)]2
为了求得使均方误差最小(因此最贴合目标曲线)的参数w 0 , w 1 , . . . , w m , w_0,w_1,…,w_m,w 0 ​,w 1 ​,…,w m ​,我们需要分别求损失L L L关于w 0 , w 1 , . . . , w m w_0,w_1,…,w_m w 0 ​,w 1 ​,…,w m ​的导数。为了方便,我们采用线性代数的记法:
X = ( 1 x 1 x 1 2 ⋯ x 1 m 1 x 2 x 2 2 ⋯ x 2 m ⋮ ⋮ 1 x N x N 2 ⋯ x N m ) N × ( m + 1 ) , Y = ( y 1 y 2 ⋮ y N ) N × 1 , W = ( w 0 w 1 ⋮ w m ) ( m + 1 ) × 1 . X=\begin{pmatrix}1 & x_1 & x_1^2 & \cdots & x_1^m\ 1 & x_2 & x_2^2 & \cdots & x_2^m\ \vdots & & & &\vdots\ 1 & x_N & x_N^2 & \cdots & x_N^m\\end{pmatrix}{N\times(m+1)},Y=\begin{pmatrix}y_1 \ y_2 \ \vdots \y_N\end{pmatrix}{N\times1},W=\begin{pmatrix}w_0 \ w_1 \ \vdots \w_m\end{pmatrix}_{(m+1)\times1}.X =⎝⎛​1 1 ⋮1 ​x 1 ​x 2 ​x N ​​x 1 2 ​x 2 2 ​x N 2 ​​⋯⋯⋯​x 1 m ​x 2 m ​⋮x N m ​​⎠⎞​N ×(m +1 )​,Y =⎝⎛​y 1 ​y 2 ​⋮y N ​​⎠⎞​N ×1 ​,W =⎝⎛​w 0 ​w 1 ​⋮w m ​​⎠⎞​(m +1 )×1 ​.

在这种表示方法下,有
( f ( x 1 ) f ( x 2 ) ⋮ f ( x N ) ) = X W . \begin{pmatrix}f(x_1)\ f(x_2) \ \vdots \ f(x_N)\end{pmatrix}= XW.⎝⎛​f (x 1 ​)f (x 2 ​)⋮f (x N ​)​⎠⎞​=X W .
如果有疑问可以自己拿矩阵乘法验证一下。继续,误差项之和可以表示为
( f ( x 1 ) − y 1 f ( x 2 ) − y 2 ⋮ f ( x N ) − y N ) = X W − Y . \begin{pmatrix}f(x_1)-y_1 \ f(x_2)-y_2 \ \vdots \ f(x_N)-y_N\end{pmatrix}=XW-Y.⎝⎛​f (x 1 ​)−y 1 ​f (x 2 ​)−y 2 ​⋮f (x N ​)−y N ​​⎠⎞​=X W −Y .
因此,损失函数
L = ( X W − Y ) T ( X W − Y ) . L=(XW-Y)^T(XW-Y).L =(X W −Y )T (X W −Y ).

(为了求得向量x = ( x 1 , x 2 , . . . , x N ) T \pmb x=(x_1,x_2,…,x_N)^T x x =(x 1 ​,x 2 ​,…,x N ​)T各分量的平方和,可以对x \pmb x x x作内积,即x T x . \pmb x^T \pmb x.x x T x x .)
为了求得使L L L最小的W W W(这个W W W是一个列向量),我们需要对L L L求偏导数,并令其为0 : 0:0 :
∂ L ∂ W = ∂ ∂ W [ ( X W − Y ) T ( X W − Y ) ] = ∂ ∂ W [ ( W T X T − Y T ) ( X W − Y ) ] = ∂ ∂ W ( W T X T X W − W T X T Y − Y T X W + Y T Y ) = ∂ ∂ W ( W T X T X W − 2 Y T X W + Y T Y ) ( 容易验证 , W T X T Y = Y T X W , 因而可以将其合并 ) = 2 X T X W − 2 X T Y \begin{aligned}\frac{\partial L}{\partial W}&=\frac{\partial}{\partial W}[(XW-Y)^T(XW-Y)]\ &=\frac{\partial}{\partial W}[(W^TX^T-Y^T)(XW-Y)] \ &=\frac{\partial}{\partial W}(W^TX^TXW-W^TX^TY-Y^TXW+Y^TY)\ &=\frac{\partial}{\partial W}(W^TX^TXW-2Y^TXW+Y^TY)(容易验证,W^TX^TY=Y^TXW,因而可以将其合并)\ &=2X^TXW-2X^TY\end{aligned}∂W ∂L ​​=∂W ∂​[(X W −Y )T (X W −Y )]=∂W ∂​[(W T X T −Y T )(X W −Y )]=∂W ∂​(W T X T X W −W T X T Y −Y T X W +Y T Y )=∂W ∂​(W T X T X W −2 Y T X W +Y T Y )(容易验证,W T X T Y =Y T X W ,因而可以将其合并)=2 X T X W −2 X T Y ​
说明:
(1)从第3行到第4行,由于W T X T Y W^TX^TY W T X T Y和Y T X W Y^TXW Y T X W都是数(或者说1 × 1 1\times1 1 ×1矩阵),二者互为转置,因此值相同,可以合并成一项。
(2)从第4行到第5行的矩阵求导,第一项∂ ∂ W ( W T ( X T X ) W ) \frac{\partial}{\partial W}(W^T(X^TX)W)∂W ∂​(W T (X T X )W )是一个关于W W W的二次型,其导数就是2 X T X W . 2X^TXW.2 X T X W .
(3)对于一次项− 2 Y T X W -2Y^TXW −2 Y T X W的求导,如果按照实数域的求导应该得到− 2 Y T X . -2Y^TX.−2 Y T X .但检查一下发现矩阵的型对不上,需要做一下转置,变为− 2 X T Y . -2X^TY.−2 X T Y .

矩阵求导线性代数课上也没有系统教过,只对这里出现的做一下说明。(多了我也不会 )
令偏导数为0,得到
X T X W = Y T X , X^TXW=Y^TX,X T X W =Y T X ,
左乘( X T X ) − 1 (X^TX)^{-1}(X T X )−1(X T X X^TX X T X的可逆性见下方的补充说明),得到
W = ( X T X ) − 1 X T Y . W=(X^TX)^{-1}X^TY.W =(X T X )−1 X T Y .
这就是我们想求的W W W的解析解,我们只需要调用函数算出这个值即可。

'''
最小二乘求出解析解, m 为多项式次数
最小二乘误差为 (XW - Y)^T*(XW - Y)
- dataset 数据集
- m 多项式次数, 默认为 5
'''
def fit(dataset, m = 5):
    X = np.array([dataset[:, 0] ** i for i in range(m + 1)]).T
    Y = dataset[:, 1]
    return np.dot(np.dot(np.linalg.inv(np.dot(X.T, X)), X.T), Y)

稍微解释一下代码:第一行即生成上面约定的X X X矩阵, dataset[:,0]即数据集第0列( x 1 , x 2 , . . . , x N ) T (x_1,x_2,…,x_N)^T (x 1 ​,x 2 ​,…,x N ​)T;第二行即Y Y Y矩阵;第三行返回上面的解析解。(如果不熟悉python语法或者 numpy库还是挺不友好的)

简单地验证一下我们已经完成的函数的结果:为此,我们先写一个 draw函数,用于把求得的W W W对应的多项式f ( x ) f(x)f (x )画到 pyplot库的图像上去:

'''
绘制给定系数W的, 在数据集上的多项式函数图像
- dataset 数据集
- w 通过上面四种方法求得的系数
- color 绘制颜色, 默认为 red
- label 图像的标签
'''
def draw(dataset, w, color = 'red', label = ''):
    X = np.array([dataset[:, 0] ** i for i in range(len(w))]).T
    Y = np.dot(X, w)

    plt.plot(dataset[:, 0], Y, c = color, label = label)

然后是主函数:

if __name__ == '__main__':
    dataset = get_dataset(bound = (-3, 3))

    for [x, y] in dataset:
        plt.scatter(x, y, color = 'red')

    coef1 = fit(dataset)
    draw(dataset, coef1, color = 'black', label = 'OLS')

    plt.legend()
    plt.show()

哈工大2022机器学习实验一:曲线拟合
可以看到5次多项式拟合的效果还是比较不错的(数据集每次随机生成,所以跟第一幅图不一样)。

截至这部分全部的代码,后面同名函数不再给出说明:

import numpy as np
import matplotlib.pyplot as plt

'''
返回数据集,形如[[x_1, y_1], [x_2, y_2], ..., [x_N, y_N]]
保证 bound[0]
def get_dataset(N = 100, bound = (0, 10)):
    l, r = bound
    x = sorted(np.random.rand(N) * (r - l) + l)
    y = np.sin(x) + np.random.randn(N) / 5
    return np.array([x,y]).T

'''
最小二乘求出解析解, m 为多项式次数
最小二乘误差为 (XW - Y)^T*(XW - Y)
- dataset 数据集
- m 多项式次数, 默认为 5
'''
def fit(dataset, m = 5):
    X = np.array([dataset[:, 0] ** i for i in range(m + 1)]).T
    Y = dataset[:, 1]
    return np.dot(np.dot(np.linalg.inv(np.dot(X.T, X)), X.T), Y)
'''
绘制给定系数W的, 在数据集上的多项式函数图像
- dataset 数据集
- w 通过上面四种方法求得的系数
- color 绘制颜色, 默认为 red
- label 图像的标签
'''
def draw(dataset, w, color = 'red', label = ''):
    X = np.array([dataset[:, 0] ** i for i in range(len(w))]).T
    Y = np.dot(X, w)

    plt.plot(dataset[:, 0], Y, c = color, label = label)

if __name__ == '__main__':

    dataset = get_dataset(bound = (-3, 3))

    for [x, y] in dataset:
        plt.scatter(x, y, color = 'red')

    coef1 = fit(dataset)
    draw(dataset, coef1, color = 'black', label = 'OLS')

    plt.legend()
    plt.show()

补充说明

上面有一块不太严谨:对于一个矩阵X X X而言,X T X X^TX X T X不一定可逆。然而在本实验中,可以证明其为可逆矩阵。由于这门课不是线性代数课,我们就不费太多篇幅介绍这个了,仅作简单提示:
(1)X X X是一个N × ( m + 1 ) N\times(m+1)N ×(m +1 )的矩阵。其中数据数N N N远大于多项式次数m m m,有N > m + 1 ; N>m+1;N >m +1 ;
(2)为了说明X T X X^TX X T X可逆,需要说明( X T X ) ( m + 1 ) × ( m + 1 ) (X^TX)_{(m+1)\times(m+1)}(X T X )(m +1 )×(m +1 )​满秩,即R ( X T X ) = m + 1 ; R(X^TX)=m+1;R (X T X )=m +1 ;
(3)在线性代数中,我们证明过R ( X ) = R ( X T ) = R ( X T X ) = R ( X X T ) ; R(X)=R(X^T)=R(X^TX)=R(XX^T);R (X )=R (X T )=R (X T X )=R (X X T );
(4)X X X是一个范德蒙矩阵,由其性质可知其秩等于m i n { N , m + 1 } = m + 1. min{N,m+1}=m+1.min {N ,m +1 }=m +1.

添加正则项(岭回归)

最小二乘法容易造成过拟合。为了说明这种缺陷,我们用所生成数据集的前50个点进行训练(这样抽样不够均匀,这里只是为了说明过拟合),得出参数,再画出整个函数图像,查看拟合效果:

if __name__ == '__main__':
    dataset = get_dataset(bound = (-3, 3))

    for [x, y] in dataset:
        plt.scatter(x, y, color = 'red')

    coef1 = fit(dataset[:50], m = 3)

    draw(dataset, coef1, color = 'black', label = 'OLS')

哈工大2022机器学习实验一:曲线拟合
过拟合在m m m较大时尤为严重(上面图像为m = 3 m=3 m =3时)。当多项式次数升高时,为了尽可能贴近所给数据集,计算出来的系数的数量级将会越来越大,在未见样本上的表现也就越差。如上图,可以看到拟合在前50个点(大约在横坐标[ − 3 , 0 ] [-3,0][−3 ,0 ]处)表现很好;而在测试集上表现就很差([ 0 , 3 ] [0,3][0 ,3 ]处)。为了防止过拟合,可以引入正则化项。此时损失函数L L L变为
L = ( X W − Y ) T ( X W − Y ) + λ ∣ ∣ W ∣ ∣ 2 2 L=(XW-Y)^T(XW-Y)+\lambda||W||_2^2 L =(X W −Y )T (X W −Y )+λ∣∣W ∣∣2 2 ​
其中∣ ∣ ⋅ ∣ ∣ 2 2 ||\cdot||_2^2 ∣∣⋅∣∣2 2 ​表示L 2 L_2 L 2 ​范数的平方,在这里即W T W ; λ W^TW;\lambda W T W ;λ为正则化系数。该式子也称岭回归(Ridge Regression)。它的思想是兼顾损失函数与所得参数W W W的模长(在L 2 L_2 L 2 ​范数时),防止W W W内的参数过大。

举个例子(数是随便编的):当正则化系数为1 1 1,若方案1在数据集上的平方误差为0.5 , 0.5,0.5 ,此时W = ( 100 , − 200 , 300 , 150 ) T W=(100,-200,300,150)^T W =(100 ,−200 ,300 ,150 )T;方案2在数据集上的平方误差为10 , 10,10 ,此时W = ( 1 , − 3 , 2 , 1 ) W=(1,-3,2,1)W =(1 ,−3 ,2 ,1 ),那我们选择方案2的W . W.W .正则化系数λ \lambda λ刻画了这种对于W W W模长的重视程度:λ \lambda λ越大,说明W W W的模长升高带来的惩罚也就越大。当λ = 0 , \lambda=0,λ=0 ,岭回归即变为普通的最小二乘法。与岭回归相似的还有LASSO,就是将正则化项换为L 1 L_1 L 1 ​范数。

重复上面的推导,我们可以得出解析解为
W = ( X T X + λ E m + 1 ) − 1 X T Y . W=(X^TX+\lambda E_{m+1})^{-1}X^TY.W =(X T X +λE m +1 ​)−1 X T Y .
其中E m + 1 E_{m+1}E m +1 ​为m + 1 m+1 m +1阶单位阵。容易得到( X T X + λ E m + 1 ) (X^TX+\lambda E_{m+1})(X T X +λE m +1 ​)也是可逆的。

该部分代码如下。

'''
岭回归求解析解, m 为多项式次数, l 为 lambda 即正则项系数
岭回归误差为 (XW - Y)^T*(XW - Y) + λ(W^T)*W
- dataset 数据集
- m 多项式次数, 默认为 5
- l 正则化参数 lambda, 默认为 0.5
'''
def ridge_regression(dataset, m = 5, l = 0.5):
    X = np.array([dataset[:, 0] ** i for i in range(m + 1)]).T
    Y = dataset[:, 1]
    return np.dot(np.dot(np.linalg.inv(np.dot(X.T, X) + l * np.eye(m + 1)), X.T), Y)

两种方法的对比如下:

哈工大2022机器学习实验一:曲线拟合
对比可以看出,岭回归显著减轻了过拟合(此时为m = 3 , λ = 0.3 m=3,\lambda=0.3 m =3 ,λ=0.3)。

梯度下降法

梯度下降法并不是求解该问题的最好方法,很容易就无法收敛。先简单介绍梯度下降法的基本思想:若我们想求取复杂函数f ( x ) f(x)f (x )的最小值(最值点)(这个x x x可能是向量等),即
x m i n = arg min ⁡ x f ( x ) x_{min}=\argmin_{x}f(x)x min ​=x arg min ​f (x )
梯度下降法重复如下操作:
(0)(随机)初始化x 0 ( t = 0 ) x_0(t=0)x 0 ​(t =0 );
(1)设f ( x ) f(x)f (x )在x t x_t x t ​处的梯度(当x x x为一维时,即导数)∇ f ( x t ) \nabla f(x_t)∇f (x t ​);
(2)x t + 1 = x t − η ∇ f ( x t ) x_{t+1}=x_t-\eta\nabla f(x_t)x t +1 ​=x t ​−η∇f (x t ​)
(3)若x t + 1 x_{t+1}x t +1 ​与x t x_t x t ​相差不大(达到预先设定的范围)或迭代次数达到预设上限,停止算法;否则重复(1)(2).

其中η \eta η为学习率,它决定了梯度下降的步长。
下面是一个用梯度下降法求取y = x 2 y=x^2 y =x 2的最小值点的示例程序:

import numpy as np
import matplotlib.pyplot as plt

def f(x):
    return x ** 2

def draw():
    x = np.linspace(-3, 3)
    y = f(x)
    plt.plot(x, y, c = 'red')

cnt = 0

x = np.random.rand(1) * 3
learning_rate = 0.05

while True:
    grad = 2 * x

    plt.scatter(x, f(x), c = 'black')
    plt.text(x + 0.3, f(x) + 0.3, str(cnt))

    new_x = x - grad * learning_rate

    if abs(new_x - x) < 1e-3:
        break

    x = new_x
    cnt += 1

draw()
plt.show()

哈工大2022机器学习实验一:曲线拟合
上图标明了x x x随着迭代的演进,可以看到x x x不断沿着正半轴向零点靠近。需要注意的是,学习率不能过大(虽然在上面的程序中,学习率设置得有点小了),需要手动进行尝试调整,否则容易想象,x x x在正负半轴来回震荡,难以收敛。

在最小二乘法中,我们需要优化的函数是损失函数
L = ( X W − Y ) T ( X W − Y ) . L=(XW-Y)^T(XW-Y).L =(X W −Y )T (X W −Y ).

下面我们用梯度下降法求解该问题。在上面的推导中,
∂ L ∂ W = 2 X T X W − 2 X T Y , \begin{aligned}\frac{\partial L}{\partial W}=2X^TXW-2X^TY\end{aligned},∂W ∂L ​=2 X T X W −2 X T Y ​,
于是我们每次在迭代中对W W W减去该梯度,直到参数W W W收敛。不过经过实验,平方误差会使得梯度过大,过程无法收敛,因此采用均方误差(MSE)替换之,就是给原来的式子除以N N N:

'''
梯度下降法(Gradient Descent, GD)求优化解, m 为多项式次数, max_iteration 为最大迭代次数, lr 为学习率
注: 此时拟合次数不宜太高(m
def GD(dataset, m = 3, max_iteration = 1000, lr = 0.01):

    w = np.random.rand(m + 1)

    N = len(dataset)
    X = np.array([dataset[:, 0] ** i for i in range(len(w))]).T
    Y = dataset[:, 1]

    try:
        for i in range(max_iteration):
            pred_Y = np.dot(X, w)

            grad = np.dot(X.T, pred_Y - Y) / N
            w -= lr * grad
    '''
    为了能捕获这个溢出的 Warning,需要import warnings并在主程序中加上:
    warnings.simplefilter('error')
    '''
    except RuntimeWarning:
        print('梯度下降法溢出, 无法收敛')

    return w

这时如果m m m设置得稍微大一点(比如4),在迭代过程中梯度就会溢出,使参数无法收敛。在收敛时,拟合效果还算可以:

哈工大2022机器学习实验一:曲线拟合

共轭梯度法

共轭梯度法(Conjugate Gradients)可以用来求解形如A x = b A\pmb x=\pmb b A x x =b b的方程组,或最小化二次型f ( x ) = 1 2 x T A x − b T x + c . f(\pmb x)=\frac12\pmb x^TA\pmb x-\pmb b^T \pmb x+c.f (x x )=2 1 ​x x T A x x −b b T x x +c .(可以证明对于正定的A A A,二者等价)其中A A A为 正定矩阵。在本问题中,我们要求解X T X W = Y T X , X^TXW=Y^TX,X T X W =Y T X ,
就有A ( m + 1 ) × ( m + 1 ) = X T X , b = Y T X . A_{(m+1)\times(m+1)}=X^TX,\pmb b=Y^TX.A (m +1 )×(m +1 )​=X T X ,b b =Y T X .若我们想加一个正则项,就变成求解
( X T X + λ E ) W = Y T X . (X^TX+\lambda E)W=Y^TX.(X T X +λE )W =Y T X .
首先说明一点:X T X X^TX X T X不一定是正定的但一定是半正定的(证明见此)。但是在实验中我们基本不用担心这个问题,因为X T X X^TX X T X有极大可能是正定的,我们只在代码中加一个断言(assert),不多关注这个条件。
共轭梯度法的思想来龙去脉和证明过程比较长,可以参考这个系列,这里只给出算法步骤(在上面链接的第三篇开头):

(0)初始化x ( 0 ) ; x_{(0)};x (0 )​;
(1)初始化d ( 0 ) = r ( 0 ) = b − A x ( 0 ) ; d_{(0)}=r_{(0)}=b-Ax_{(0)};d (0 )​=r (0 )​=b −A x (0 )​;
(2)令α ( i ) = r ( i ) T r ( i ) d ( i ) T A d ( i ) ; \alpha_{(i)}=\frac{r_{(i)}^Tr_{(i)}}{d_{(i)}^TAd_{(i)}};α(i )​=d (i )T ​A d (i )​r (i )T ​r (i )​​;
(3)迭代x ( i + 1 ) = x ( i ) + α ( i ) d ( i ) ; x_{(i+1)}=x_{(i)}+\alpha_{(i)}d_{(i)};x (i +1 )​=x (i )​+α(i )​d (i )​;
(4)令r ( i + 1 ) = r ( i ) − α ( i ) A d ( i ) ; r_{(i+1)}=r_{(i)}-\alpha_{(i)}Ad_{(i)};r (i +1 )​=r (i )​−α(i )​A d (i )​;
(5)令β ( i + 1 ) = r ( i + 1 ) T r ( i + 1 ) r ( i ) T r ( i ) , d ( i + 1 ) = r ( i + 1 ) + β ( i + 1 ) d ( i ) . \beta_{(i+1)}=\frac{r_{(i+1)}^Tr_{(i+1)}}{r_{(i)}^Tr_{(i)}},d_{(i+1)}=r_{(i+1)}+\beta_{(i+1)}d_{(i)}.β(i +1 )​=r (i )T ​r (i )​r (i +1 )T ​r (i +1 )​​,d (i +1 )​=r (i +1 )​+β(i +1 )​d (i )​.

(6)当∣ ∣ r ( i ) ∣ ∣ ∣ ∣ r ( 0 ) ∣ ∣ < ϵ \frac{||r_{(i)}||}{||r_{(0)}||}时,停止算法;否则继续从(2)开始迭代。ϵ \epsilon ϵ为预先设定好的很小的值,我这里取的是1 0 − 8 . 10^{-8}.1 0 −8 .
下面我们按照这个过程实现代码:

'''
共轭梯度法(Conjugate Gradients, CG)求优化解, m 为多项式次数
- dataset 数据集
- m 多项式次数, 默认为 5
- regularize 正则化参数, 若为 0 则不进行正则化
'''
def CG(dataset, m = 5, regularize = 0):
    X = np.array([dataset[:, 0] ** i for i in range(m + 1)]).T
    A = np.dot(X.T, X) + regularize * np.eye(m + 1)
    assert np.all(np.linalg.eigvals(A) > 0), '矩阵不满足正定!'
    b = np.dot(X.T, dataset[:, 1])
    w = np.random.rand(m + 1)
    epsilon = 1e-8

    d = r = b - np.dot(A, w)
    r0 = r
    while True:
        alpha = np.dot(r.T, r) / np.dot(np.dot(d, A), d)
        w += alpha * d
        new_r = r - alpha * np.dot(A, d)
        beta = np.dot(new_r.T, new_r) / np.dot(r.T, r)
        d = beta * d + new_r
        r = new_r

        if np.linalg.norm(r) / np.linalg.norm(r0) < epsilon:
            break
    return w

相比于朴素的梯度下降法,共轭梯度法收敛迅速且稳定。

最后附上四种方法的拟合图像(基本都一样)和主函数,可以根据实验要求调整参数:

哈工大2022机器学习实验一:曲线拟合
if __name__ == '__main__':
    warnings.simplefilter('error')

    dataset = get_dataset(bound = (-3, 3))

    for [x, y] in dataset:
        plt.scatter(x, y, color = 'red')

    coef1 = fit(dataset)

    coef2 = ridge_regression(dataset)

    coef3 = GD(dataset, m = 3)

    coef4 = CG(dataset)

    draw(dataset, coef1, color = 'red', label = 'OLS')
    draw(dataset, coef2, color = 'black', label = 'Ridge')
    draw(dataset, coef3, color = 'purple', label = 'GD')
    draw(dataset, coef4, color = 'green', label = 'CG(lambda:0)')

    plt.legend()
    plt.show()

Original: https://blog.csdn.net/wyn1564464568/article/details/126819062
Author: Castria
Title: 哈工大2022机器学习实验一:曲线拟合

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/670767/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球