0


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

这个实验的要求写的还是挺清楚的(与上学期相比),本博客采用python实现,科学计算库采用

  1. numpy

,作图采用

  1. matplotlib.pyplot

,为了简便在文件开头import如下:

  1. import numpy as np
  2. import matplotlib.pyplot as plt

本实验用到的numpy函数

一般把

  1. numpy

简写为

  1. np

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

  1. import numpy as np

np.array

该函数返回一个

  1. numpy.ndarray

对象,可以理解为一个多维数组(本实验中仅会用到一维(可以当作列向量)和二维(矩阵))。下面用小写的

  1. x
  2. \pmb x
  3. xx表示列向量,大写的
  4. A
  5. A
  6. A表示矩阵。
  1. A.T

表示

  1. A
  2. A
  3. A的转置。对
  1. ndarray

的运算一般都是逐元素的。

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

np.random

  1. np.random

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

  1. >>> np.random.rand(3,3)# 生成3 * 3 随机矩阵,每个元素服从[0,1)均匀分布
  2. 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)# 生成单个随机数
  3. array([0.70944563])>>> np.random.rand(5)# 长为5的一维随机数组
  4. array([0.03911319,0.67572368,0.98884287,0.12501456,0.39870096])>>> np.random.randn(3,3)# 同上,但每个元素服从N(0, 1)(标准正态)

数学函数

本实验中只用到了

  1. np.sin

。这些数学函数是对

  1. np.ndarray

逐元素操作的:

  1. >>> x = np.array([0,3.1415,3.1415/2])# 0, pi, pi / 2>>> np.round(np.sin(x))# 先求sin再四舍五入: 0, 0, 1
  2. array([0.,0.,1.])

此外,还有

  1. np.log

  1. np.exp

等与python的

  1. math

库相似的函数(只不过是对多维数组进行逐元素运算)。

np.dot

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

  1. n
  2. ×
  3. 1
  4. n\times1
  5. n×1
  6. 1
  7. ×
  8. n
  9. .
  10. 1\times n.
  11. 1×n.
  1. >>> x = np.array([1,2,3])# 一维数组>>> A = np.array([[1,1,1],[2,2,2],[3,3,3]])# 3 * 3矩阵>>> np.dot(x,A)
  2. array([14,14,14])>>> np.dot(A,x)
  3. array([6,12,18])>>> x_2D = np.array([[1,2,3]])# 这是一个二维数组(1 * 3矩阵)>>> np.dot(x_2D, A)# 可以运算
  4. array([[14,14,14]])>>> np.dot(A, x_2D)# 行列不匹配
  5. Traceback (most recent call last):
  6. File "<stdin>", line 1,in<module>
  7. File "<__array_function__ internals>", line 5,in dot
  8. ValueError: shapes (3,3)and(1,3)not aligned:3(dim 1)!=1(dim 0)

np.eye

  1. np.eye(n)

返回一个n阶单位阵。

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

线性代数相关

  1. np.linalg

是与线性代数有关的库。

  1. >>> A
  2. array([[1,0,0],[0,2,0],[0,0,3]])>>> np.linalg.inv(A)# 求逆(本实验不考虑逆不存在)
  3. array([[1.,0.,0.],[0.,0.5,0.],[0.,0.,0.33333333]])>>> x = np.array([1,2,3])>>> np.linalg.norm(x)# 返回向量x的模长(平方求和开根号)3.7416573867739413>>> np.linalg.eigvals(A)# A的特征值
  4. array([1.,2.,3.])

生成数据

生成数据要求加入噪声(误差)。上课讲的时候举的例子就是正弦函数,我们这里也采用标准的正弦函数

  1. y
  2. =
  3. sin
  4. x
  5. .
  6. y=\sin x.
  7. y=sinx.(加入噪声后即为
  8. y
  9. =
  10. sin
  11. x
  12. +
  13. ϵ
  14. ,
  15. y=\sin x+\epsilon,
  16. y=sinx+ϵ,其中
  17. ϵ
  18. N
  19. (
  20. 0
  21. ,
  22. σ
  23. 2
  24. )
  25. \epsilon\sim N(0, \sigma^2)
  26. ϵ∼N(02),由于
  27. sin
  28. x
  29. \sin x
  30. sinx的最大值为
  31. 1
  32. 1
  33. 1,我们把误差的方差设小一点,这里设成
  34. 1
  35. 25
  36. \frac{1}{25}
  37. 251​)。
  1. '''
  2. 返回数据集,形如[[x_1, y_1], [x_2, y_2], ..., [x_N, y_N]]
  3. 保证 bound[0] <= x_i < bound[1].
  4. - N 数据集大小, 默认为 100
  5. - bound 产生数据横坐标的上下界, 应满足 bound[0] < bound[1], 默认为(0, 10)
  6. '''defget_dataset(N =100, bound =(0,10)):
  7. l, r = bound
  8. # np.random.rand 产生[0, 1)的均匀分布,再根据l, r缩放平移# 这里sort是为了画图时不会乱,可以去掉sorted试一试
  9. x =sorted(np.random.rand(N)*(r - l)+ l)# np.random.randn 产生N(0,1),除以5会变为N(0, 1 / 25)
  10. y = np.sin(x)+ np.random.randn(N)/5return np.array([x,y]).T

产生的数据集每行为一个平面上的点。产生的数据看起来像这样:
在这里插入图片描述
隐隐约约能看出来是个正弦函数的形状。产生上面图像的代码如下:

  1. dataset = get_dataset(bound =(-3,3))# 绘制数据集散点图for[x, y]in dataset:
  2. plt.scatter(x, y, color ='red')
  3. plt.show()

最小二乘法拟合

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

解析解推导

简单回忆一下最小二乘法的原理:现在我们想用一个

  1. m
  2. m
  3. m次多项式
  4. f
  5. (
  6. x
  7. )
  8. =
  9. w
  10. 0
  11. +
  12. w
  13. 1
  14. x
  15. +
  16. w
  17. 2
  18. x
  19. 2
  20. +
  21. .
  22. .
  23. .
  24. +
  25. w
  26. m
  27. x
  28. m
  29. f(x)=w_0+w_1x+w_2x^2+...+w_mx^m
  30. f(x)=w0​+w1x+w2x2+...+wmxm

来近似真实函数

  1. y
  2. =
  3. sin
  4. x
  5. .
  6. y=\sin x.
  7. y=sinx.我们的目标是最小化数据集
  8. (
  9. x
  10. 1
  11. ,
  12. y
  13. 1
  14. )
  15. ,
  16. (
  17. x
  18. 2
  19. ,
  20. y
  21. 2
  22. )
  23. ,
  24. .
  25. .
  26. .
  27. ,
  28. (
  29. x
  30. N
  31. ,
  32. y
  33. N
  34. )
  35. (x_1,y_1),(x_2,y_2),...,(x_N,y_N)
  36. (x1​,y1​),(x2​,y2​),...,(xN​,yN​)上的损失
  37. L
  38. L
  39. Lloss),这里损失函数采用平方误差:
  40. L
  41. =
  42. i
  43. =
  44. 1
  45. N
  46. [
  47. y
  48. i
  49. f
  50. (
  51. x
  52. i
  53. )
  54. ]
  55. 2
  56. L=\sum\limits_{i=1}^N[y_i-f(x_i)]^2
  57. L=i=1N​[yi​−f(xi​)]2

为了求得使均方误差最小(因此最贴合目标曲线)的参数

  1. w
  2. 0
  3. ,
  4. w
  5. 1
  6. ,
  7. .
  8. .
  9. .
  10. ,
  11. w
  12. m
  13. ,
  14. w_0,w_1,...,w_m,
  15. w0​,w1​,...,wm​,我们需要分别求损失
  16. L
  17. L
  18. L关于
  19. w
  20. 0
  21. ,
  22. w
  23. 1
  24. ,
  25. .
  26. .
  27. .
  28. ,
  29. w
  30. m
  31. w_0,w_1,...,w_m
  32. w0​,w1​,...,wm​的导数。为了方便,我们采用线性代数的记法:
  33. X
  34. =
  35. (
  36. 1
  37. x
  38. 1
  39. x
  40. 1
  41. 2
  42. x
  43. 1
  44. m
  45. 1
  46. x
  47. 2
  48. x
  49. 2
  50. 2
  51. x
  52. 2
  53. m
  54. 1
  55. x
  56. N
  57. x
  58. N
  59. 2
  60. x
  61. N
  62. m
  63. )
  64. N
  65. ×
  66. (
  67. m
  68. +
  69. 1
  70. )
  71. ,
  72. Y
  73. =
  74. (
  75. y
  76. 1
  77. y
  78. 2
  79. y
  80. N
  81. )
  82. N
  83. ×
  84. 1
  85. ,
  86. W
  87. =
  88. (
  89. w
  90. 0
  91. w
  92. 1
  93. w
  94. m
  95. )
  96. (
  97. m
  98. +
  99. 1
  100. )
  101. ×
  102. 1
  103. .
  104. 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}.
  105. X=⎝⎛​111x1x2xN​​x12x22xN2​​⋯⋯⋯​x1mx2m​⋮xNm​​⎠⎞​N×(m+1)​,Y=⎝⎛​y1y2​⋮yN​​⎠⎞​N×1​,W=⎝⎛​w0w1​⋮wm​​⎠⎞​(m+11​.

在这种表示方法下,有

  1. (
  2. f
  3. (
  4. x
  5. 1
  6. )
  7. f
  8. (
  9. x
  10. 2
  11. )
  12. f
  13. (
  14. x
  15. N
  16. )
  17. )
  18. =
  19. X
  20. W
  21. .
  22. \begin{pmatrix}f(x_1)\\ f(x_2) \\ \vdots \\ f(x_N)\end{pmatrix}= XW.
  23. ⎝⎛​f(x1​)f(x2​)⋮f(xN​)​⎠⎞​=XW.

如果有疑问可以自己拿矩阵乘法验证一下。继续,误差项之和可以表示为

  1. (
  2. f
  3. (
  4. x
  5. 1
  6. )
  7. y
  8. 1
  9. f
  10. (
  11. x
  12. 2
  13. )
  14. y
  15. 2
  16. f
  17. (
  18. x
  19. N
  20. )
  21. y
  22. N
  23. )
  24. =
  25. X
  26. W
  27. Y
  28. .
  29. \begin{pmatrix}f(x_1)-y_1 \\ f(x_2)-y_2 \\ \vdots \\ f(x_N)-y_N\end{pmatrix}=XW-Y.
  30. ⎝⎛​f(x1​)−y1f(x2​)−y2​⋮f(xN​)−yN​​⎠⎞​=XWY.

因此,损失函数

  1. L
  2. =
  3. (
  4. X
  5. W
  6. Y
  7. )
  8. T
  9. (
  10. X
  11. W
  12. Y
  13. )
  14. .
  15. L=(XW-Y)^T(XW-Y).
  16. L=(XWY)T(XWY).

(为了求得向量

  1. x
  2. =
  3. (
  4. x
  5. 1
  6. ,
  7. x
  8. 2
  9. ,
  10. .
  11. .
  12. .
  13. ,
  14. x
  15. N
  16. )
  17. T
  18. \pmb x=(x_1,x_2,...,x_N)^T
  19. xx=(x1​,x2​,...,xN​)T各分量的平方和,可以对
  20. x
  21. \pmb x
  22. xx作内积,即
  23. x
  24. T
  25. x
  26. .
  27. \pmb x^T \pmb x.
  28. xxTxx.)

为了求得使

  1. L
  2. L
  3. L最小的
  4. W
  5. W
  6. W(这个
  7. W
  8. W
  9. W是一个列向量),我们需要对
  10. L
  11. L
  12. L求偏导数,并令其为
  13. 0
  14. :
  15. 0:
  16. 0:
  17. L
  18. W
  19. =
  20. W
  21. [
  22. (
  23. X
  24. W
  25. Y
  26. )
  27. T
  28. (
  29. X
  30. W
  31. Y
  32. )
  33. ]
  34. =
  35. W
  36. [
  37. (
  38. W
  39. T
  40. X
  41. T
  42. Y
  43. T
  44. )
  45. (
  46. X
  47. W
  48. Y
  49. )
  50. ]
  51. =
  52. W
  53. (
  54. W
  55. T
  56. X
  57. T
  58. X
  59. W
  60. W
  61. T
  62. X
  63. T
  64. Y
  65. Y
  66. T
  67. X
  68. W
  69. +
  70. Y
  71. T
  72. Y
  73. )
  74. =
  75. W
  76. (
  77. W
  78. T
  79. X
  80. T
  81. X
  82. W
  83. 2
  84. Y
  85. T
  86. X
  87. W
  88. +
  89. Y
  90. T
  91. Y
  92. )
  93. (
  94. 容易验证
  95. ,
  96. W
  97. T
  98. X
  99. T
  100. Y
  101. =
  102. Y
  103. T
  104. X
  105. W
  106. ,
  107. 因而可以将其合并
  108. )
  109. =
  110. 2
  111. X
  112. T
  113. X
  114. W
  115. 2
  116. X
  117. T
  118. Y
  119. \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}
  120. WL​​=∂W∂​[(XWY)T(XWY)]=∂W∂​[(WTXTYT)(XWY)]=∂W∂​(WTXTXWWTXTYYTXW+YTY)=∂W∂​(WTXTXW2YTXW+YTY)(容易验证,WTXTY=YTXW,因而可以将其合并)=2XTXW2XTY

说明:
(1)从第3行到第4行,由于

  1. W
  2. T
  3. X
  4. T
  5. Y
  6. W^TX^TY
  7. WTXTY
  8. Y
  9. T
  10. X
  11. W
  12. Y^TXW
  13. YTXW都是数(或者说
  14. 1
  15. ×
  16. 1
  17. 1\times1
  18. 1×1矩阵),二者互为转置,因此值相同,可以合并成一项。

(2)从第4行到第5行的矩阵求导,第一项

  1. W
  2. (
  3. W
  4. T
  5. (
  6. X
  7. T
  8. X
  9. )
  10. W
  11. )
  12. \frac{\partial}{\partial W}(W^T(X^TX)W)
  13. W∂​(WT(XTX)W)是一个关于
  14. W
  15. W
  16. W的二次型,其导数就是
  17. 2
  18. X
  19. T
  20. X
  21. W
  22. .
  23. 2X^TXW.
  24. 2XTXW.

(3)对于一次项

  1. 2
  2. Y
  3. T
  4. X
  5. W
  6. -2Y^TXW
  7. 2YTXW的求导,如果按照实数域的求导应该得到
  8. 2
  9. Y
  10. T
  11. X
  12. .
  13. -2Y^TX.
  14. 2YTX.但检查一下发现矩阵的型对不上,需要做一下转置,变为
  15. 2
  16. X
  17. T
  18. Y
  19. .
  20. -2X^TY.
  21. 2XTY.

矩阵求导线性代数课上也没有系统教过,只对这里出现的做一下说明。(多了我也不会 )
令偏导数为0,得到

  1. X
  2. T
  3. X
  4. W
  5. =
  6. Y
  7. T
  8. X
  9. ,
  10. X^TXW=Y^TX,
  11. XTXW=YTX,

左乘

  1. (
  2. X
  3. T
  4. X
  5. )
  6. 1
  7. (X^TX)^{-1}
  8. (XTX)−1
  9. X
  10. T
  11. X
  12. X^TX
  13. XTX的可逆性见下方的补充说明),得到
  14. W
  15. =
  16. (
  17. X
  18. T
  19. X
  20. )
  21. 1
  22. X
  23. T
  24. Y
  25. .
  26. W=(X^TX)^{-1}X^TY.
  27. W=(XTX)−1XTY.

这就是我们想求的

  1. W
  2. W
  3. W的解析解,我们只需要调用函数算出这个值即可。
  1. '''
  2. 最小二乘求出解析解, m 为多项式次数
  3. 最小二乘误差为 (XW - Y)^T*(XW - Y)
  4. - dataset 数据集
  5. - m 多项式次数, 默认为 5
  6. '''deffit(dataset, m =5):
  7. X = np.array([dataset[:,0]** i for i inrange(m +1)]).T
  8. Y = dataset[:,1]return np.dot(np.dot(np.linalg.inv(np.dot(X.T, X)), X.T), Y)

稍微解释一下代码:第一行即生成上面约定的

  1. X
  2. X
  3. X矩阵,
  1. dataset[:,0]

即数据集第0列

  1. (
  2. x
  3. 1
  4. ,
  5. x
  6. 2
  7. ,
  8. .
  9. .
  10. .
  11. ,
  12. x
  13. N
  14. )
  15. T
  16. (x_1,x_2,...,x_N)^T
  17. (x1​,x2​,...,xN​)T;第二行即
  18. Y
  19. Y
  20. Y矩阵;第三行返回上面的解析解。(如果不熟悉python语法或者
  1. numpy

库还是挺不友好的)

简单地验证一下我们已经完成的函数的结果:为此,我们先写一个

  1. draw

函数,用于把求得的

  1. W
  2. W
  3. W对应的多项式
  4. f
  5. (
  6. x
  7. )
  8. f(x)
  9. f(x)画到
  1. pyplot

库的图像上去:

  1. '''
  2. 绘制给定系数W的, 在数据集上的多项式函数图像
  3. - dataset 数据集
  4. - w 通过上面四种方法求得的系数
  5. - color 绘制颜色, 默认为 red
  6. - label 图像的标签
  7. '''defdraw(dataset, w, color ='red', label =''):
  8. X = np.array([dataset[:,0]** i for i inrange(len(w))]).T
  9. Y = np.dot(X, w)
  10. plt.plot(dataset[:,0], Y, c = color, label = label)

然后是主函数:

  1. if __name__ =='__main__':
  2. dataset = get_dataset(bound =(-3,3))# 绘制数据集散点图for[x, y]in dataset:
  3. plt.scatter(x, y, color ='red')# 最小二乘
  4. coef1 = fit(dataset)
  5. draw(dataset, coef1, color ='black', label ='OLS')# 绘制图像
  6. plt.legend()
  7. plt.show()

在这里插入图片描述
可以看到5次多项式拟合的效果还是比较不错的(数据集每次随机生成,所以跟第一幅图不一样)。

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

  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. '''
  4. 返回数据集,形如[[x_1, y_1], [x_2, y_2], ..., [x_N, y_N]]
  5. 保证 bound[0] <= x_i < bound[1].
  6. - N 数据集大小, 默认为 100
  7. - bound 产生数据横坐标的上下界, 应满足 bound[0] < bound[1]
  8. '''defget_dataset(N =100, bound =(0,10)):
  9. l, r = bound
  10. x =sorted(np.random.rand(N)*(r - l)+ l)
  11. y = np.sin(x)+ np.random.randn(N)/5return np.array([x,y]).T
  12. '''
  13. 最小二乘求出解析解, m 为多项式次数
  14. 最小二乘误差为 (XW - Y)^T*(XW - Y)
  15. - dataset 数据集
  16. - m 多项式次数, 默认为 5
  17. '''deffit(dataset, m =5):
  18. X = np.array([dataset[:,0]** i for i inrange(m +1)]).T
  19. Y = dataset[:,1]return np.dot(np.dot(np.linalg.inv(np.dot(X.T, X)), X.T), Y)'''
  20. 绘制给定系数W的, 在数据集上的多项式函数图像
  21. - dataset 数据集
  22. - w 通过上面四种方法求得的系数
  23. - color 绘制颜色, 默认为 red
  24. - label 图像的标签
  25. '''defdraw(dataset, w, color ='red', label =''):
  26. X = np.array([dataset[:,0]** i for i inrange(len(w))]).T
  27. Y = np.dot(X, w)
  28. plt.plot(dataset[:,0], Y, c = color, label = label)if __name__ =='__main__':
  29. dataset = get_dataset(bound =(-3,3))# 绘制数据集散点图for[x, y]in dataset:
  30. plt.scatter(x, y, color ='red')
  31. coef1 = fit(dataset)
  32. draw(dataset, coef1, color ='black', label ='OLS')
  33. plt.legend()
  34. plt.show()

补充说明

上面有一块不太严谨:对于一个矩阵

  1. X
  2. X
  3. X而言,
  4. X
  5. T
  6. X
  7. X^TX
  8. XTX不一定可逆。然而在本实验中,可以证明其为可逆矩阵。由于这门课不是线性代数课,我们就不费太多篇幅介绍这个了,仅作简单提示:

(1)

  1. X
  2. X
  3. X是一个
  4. N
  5. ×
  6. (
  7. m
  8. +
  9. 1
  10. )
  11. N\times(m+1)
  12. N×(m+1)的矩阵。其中数据数
  13. N
  14. N
  15. N远大于多项式次数
  16. m
  17. m
  18. m,有
  19. N
  20. >
  21. m
  22. +
  23. 1
  24. ;
  25. N>m+1;
  26. N>m+1;

(2)为了说明

  1. X
  2. T
  3. X
  4. X^TX
  5. XTX可逆,需要说明
  6. (
  7. X
  8. T
  9. X
  10. )
  11. (
  12. m
  13. +
  14. 1
  15. )
  16. ×
  17. (
  18. m
  19. +
  20. 1
  21. )
  22. (X^TX)_{(m+1)\times(m+1)}
  23. (XTX)(m+1)×(m+1)​满秩,即
  24. R
  25. (
  26. X
  27. T
  28. X
  29. )
  30. =
  31. m
  32. +
  33. 1
  34. ;
  35. R(X^TX)=m+1;
  36. R(XTX)=m+1;

(3)在线性代数中,我们证明过

  1. R
  2. (
  3. X
  4. )
  5. =
  6. R
  7. (
  8. X
  9. T
  10. )
  11. =
  12. R
  13. (
  14. X
  15. T
  16. X
  17. )
  18. =
  19. R
  20. (
  21. X
  22. X
  23. T
  24. )
  25. ;
  26. R(X)=R(X^T)=R(X^TX)=R(XX^T);
  27. R(X)=R(XT)=R(XTX)=R(XXT);

(4)

  1. X
  2. X
  3. X是一个范德蒙矩阵,由其性质可知其秩等于
  4. m
  5. i
  6. n
  7. {
  8. N
  9. ,
  10. m
  11. +
  12. 1
  13. }
  14. =
  15. m
  16. +
  17. 1.
  18. min\{N,m+1\}=m+1.
  19. min{N,m+1}=m+1.

添加正则项(岭回归)

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

  1. if __name__ =='__main__':
  2. dataset = get_dataset(bound =(-3,3))# 绘制数据集散点图for[x, y]in dataset:
  3. plt.scatter(x, y, color ='red')# 取前50个点进行训练
  4. coef1 = fit(dataset[:50], m =3)# 再画出整个数据集上的图像
  5. draw(dataset, coef1, color ='black', label ='OLS')

在这里插入图片描述
过拟合在

  1. m
  2. m
  3. m较大时尤为严重(上面图像为
  4. m
  5. =
  6. 3
  7. m=3
  8. m=3时)。当多项式次数升高时,为了尽可能贴近所给数据集,计算出来的系数的数量级将会越来越大,在未见样本上的表现也就越差。如上图,可以看到拟合在前50个点(大约在横坐标
  9. [
  10. 3
  11. ,
  12. 0
  13. ]
  14. [-3,0]
  15. [−3,0]处)表现很好;而在测试集上表现就很差(
  16. [
  17. 0
  18. ,
  19. 3
  20. ]
  21. [0,3]
  22. [0,3]处)。为了防止过拟合,可以引入正则化项。此时损失函数
  23. L
  24. L
  25. L变为
  26. L
  27. =
  28. (
  29. X
  30. W
  31. Y
  32. )
  33. T
  34. (
  35. X
  36. W
  37. Y
  38. )
  39. +
  40. λ
  41. W
  42. 2
  43. 2
  44. L=(XW-Y)^T(XW-Y)+\lambda||W||_2^2
  45. L=(XWY)T(XWY)+λ∣∣W∣∣22

其中

  1. 2
  2. 2
  3. ||\cdot||_2^2
  4. ∣∣⋅∣∣22​表示
  5. L
  6. 2
  7. L_2
  8. L2​范数的平方,在这里即
  9. W
  10. T
  11. W
  12. ;
  13. λ
  14. W^TW;\lambda
  15. WTW;λ为正则化系数。该式子也称岭回归(Ridge Regression)。它的思想是兼顾损失函数与所得参数
  16. W
  17. W
  18. W的模长(在
  19. L
  20. 2
  21. L_2
  22. L2​范数时),防止
  23. W
  24. W
  25. W内的参数过大。

举个例子(数是随便编的):当正则化系数为

  1. 1
  2. 1
  3. 1,若方案1在数据集上的平方误差为
  4. 0.5
  5. ,
  6. 0.5,
  7. 0.5,此时
  8. W
  9. =
  10. (
  11. 100
  12. ,
  13. 200
  14. ,
  15. 300
  16. ,
  17. 150
  18. )
  19. T
  20. W=(100,-200,300,150)^T
  21. W=(100,−200,300,150)T;方案2在数据集上的平方误差为
  22. 10
  23. ,
  24. 10,
  25. 10,此时
  26. W
  27. =
  28. (
  29. 1
  30. ,
  31. 3
  32. ,
  33. 2
  34. ,
  35. 1
  36. )
  37. W=(1,-3,2,1)
  38. W=(1,−3,2,1),那我们选择方案2
  39. W
  40. .
  41. W.
  42. W.正则化系数
  43. λ
  44. \lambda
  45. λ刻画了这种对于
  46. W
  47. W
  48. W模长的重视程度:
  49. λ
  50. \lambda
  51. λ越大,说明
  52. W
  53. W
  54. W的模长升高带来的惩罚也就越大。当
  55. λ
  56. =
  57. 0
  58. ,
  59. \lambda=0,
  60. λ=0,岭回归即变为普通的最小二乘法。与岭回归相似的还有LASSO,就是将正则化项换为
  61. L
  62. 1
  63. L_1
  64. L1​范数。

重复上面的推导,我们可以得出解析解为

  1. W
  2. =
  3. (
  4. X
  5. T
  6. X
  7. +
  8. λ
  9. E
  10. m
  11. +
  12. 1
  13. )
  14. 1
  15. X
  16. T
  17. Y
  18. .
  19. W=(X^TX+\lambda E_{m+1})^{-1}X^TY.
  20. W=(XTXEm+1​)−1XTY.

其中

  1. E
  2. m
  3. +
  4. 1
  5. E_{m+1}
  6. Em+1​为
  7. m
  8. +
  9. 1
  10. m+1
  11. m+1阶单位阵。容易得到
  12. (
  13. X
  14. T
  15. X
  16. +
  17. λ
  18. E
  19. m
  20. +
  21. 1
  22. )
  23. (X^TX+\lambda E_{m+1})
  24. (XTXEm+1​)也是可逆的。

该部分代码如下。

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

两种方法的对比如下:
在这里插入图片描述
对比可以看出,岭回归显著减轻了过拟合(此时为

  1. m
  2. =
  3. 3
  4. ,
  5. λ
  6. =
  7. 0.3
  8. m=3,\lambda=0.3
  9. m=3,λ=0.3)。

梯度下降法

梯度下降法并不是求解该问题的最好方法,很容易就无法收敛。先简单介绍梯度下降法的基本思想:若我们想求取复杂函数

  1. f
  2. (
  3. x
  4. )
  5. f(x)
  6. f(x)的最小值(最值点)(这个
  7. x
  8. x
  9. x可能是向量等),即
  10. x
  11. m
  12. i
  13. n
  14. =
  15. argmin
  16. x
  17. f
  18. (
  19. x
  20. )
  21. x_{min}=\argmin_{x}f(x)
  22. xmin​=xargminf(x)

梯度下降法重复如下操作:
(0)(随机)初始化

  1. x
  2. 0
  3. (
  4. t
  5. =
  6. 0
  7. )
  8. x_0(t=0)
  9. x0​(t=0);

(1)设

  1. f
  2. (
  3. x
  4. )
  5. f(x)
  6. f(x)在
  7. x
  8. t
  9. x_t
  10. xt​处的梯度(当
  11. x
  12. x
  13. x为一维时,即导数)
  14. f
  15. (
  16. x
  17. t
  18. )
  19. \nabla f(x_t)
  20. f(xt​);

(2)

  1. x
  2. t
  3. +
  4. 1
  5. =
  6. x
  7. t
  8. η
  9. f
  10. (
  11. x
  12. t
  13. )
  14. x_{t+1}=x_t-\eta\nabla f(x_t)
  15. xt+1​=xt​−η∇f(xt​)

(3)若

  1. x
  2. t
  3. +
  4. 1
  5. x_{t+1}
  6. xt+1​与
  7. x
  8. t
  9. x_t
  10. xt​相差不大(达到预先设定的范围)或迭代次数达到预设上限,停止算法;否则重复(1)(2).

其中

  1. η
  2. \eta
  3. η为学习率,它决定了梯度下降的步长。

下面是一个用梯度下降法求取

  1. y
  2. =
  3. x
  4. 2
  5. y=x^2
  6. y=x2的最小值点的示例程序:
  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. deff(x):return x **2defdraw():
  4. x = np.linspace(-3,3)
  5. y = f(x)
  6. plt.plot(x, y, c ='red')
  7. cnt =0# 初始化 x
  8. x = np.random.rand(1)*3
  9. learning_rate =0.05whileTrue:
  10. grad =2* x
  11. # -----------作图用,非算法部分-----------
  12. plt.scatter(x, f(x), c ='black')
  13. plt.text(x +0.3, f(x)+0.3,str(cnt))# -------------------------------------
  14. new_x = x - grad * learning_rate
  15. # 判断收敛ifabs(new_x - x)<1e-3:break
  16. x = new_x
  17. cnt +=1
  18. draw()
  19. plt.show()

在这里插入图片描述
上图标明了

  1. x
  2. x
  3. x随着迭代的演进,可以看到
  4. x
  5. x
  6. x不断沿着正半轴向零点靠近。需要注意的是,学习率不能过大(虽然在上面的程序中,学习率设置得有点小了),需要手动进行尝试调整,否则容易想象,
  7. x
  8. x
  9. x在正负半轴来回震荡,难以收敛。

在最小二乘法中,我们需要优化的函数是损失函数

  1. L
  2. =
  3. (
  4. X
  5. W
  6. Y
  7. )
  8. T
  9. (
  10. X
  11. W
  12. Y
  13. )
  14. .
  15. L=(XW-Y)^T(XW-Y).
  16. L=(XWY)T(XWY).

下面我们用梯度下降法求解该问题。在上面的推导中,

  1. L
  2. W
  3. =
  4. 2
  5. X
  6. T
  7. X
  8. W
  9. 2
  10. X
  11. T
  12. Y
  13. ,
  14. \begin{aligned}\frac{\partial L}{\partial W}=2X^TXW-2X^TY\end{aligned},
  15. WL​=2XTXW2XTY​,

于是我们每次在迭代中对

  1. W
  2. W
  3. W减去该梯度,直到参数
  4. W
  5. W
  6. W收敛。不过经过实验,平方误差会使得梯度过大,过程无法收敛,因此采用均方误差(MSE)替换之,就是给原来的式子除以
  7. N
  8. N
  9. N:
  1. '''
  2. 梯度下降法(Gradient Descent, GD)求优化解, m 为多项式次数, max_iteration 为最大迭代次数, lr 为学习率
  3. 注: 此时拟合次数不宜太高(m <= 3), 且数据集的数据范围不能太大(这里设置为(-3, 3)), 否则很难收敛
  4. - dataset 数据集
  5. - m 多项式次数, 默认为 3(太高会溢出, 无法收敛)
  6. - max_iteration 最大迭代次数, 默认为 1000
  7. - lr 梯度下降的学习率, 默认为 0.01
  8. '''defGD(dataset, m =3, max_iteration =1000, lr =0.01):# 初始化参数
  9. w = np.random.rand(m +1)
  10. N =len(dataset)
  11. X = np.array([dataset[:,0]** i for i inrange(len(w))]).T
  12. Y = dataset[:,1]try:for i inrange(max_iteration):
  13. pred_Y = np.dot(X, w)# 均方误差(省略系数2
  14. grad = np.dot(X.T, pred_Y - Y)/ N
  15. w -= lr * grad
  16. '''
  17. 为了能捕获这个溢出的 Warning,需要import warnings并在主程序中加上:
  18. warnings.simplefilter('error')
  19. '''except RuntimeWarning:print('梯度下降法溢出, 无法收敛')return w

这时如果

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

在这里插入图片描述

共轭梯度法

共轭梯度法(Conjugate Gradients)可以用来求解形如

  1. A
  2. x
  3. =
  4. b
  5. A\pmb x=\pmb b
  6. Axx=bb的方程组,或最小化二次型
  7. f
  8. (
  9. x
  10. )
  11. =
  12. 1
  13. 2
  14. x
  15. T
  16. A
  17. x
  18. b
  19. T
  20. x
  21. +
  22. c
  23. .
  24. f(\pmb x)=\frac12\pmb x^TA\pmb x-\pmb b^T \pmb x+c.
  25. f(xx)=21xxTAxxbbTxx+c.(可以证明对于正定的
  26. A
  27. A
  28. A,二者等价)其中
  29. A
  30. A
  31. A为**正定**矩阵。在本问题中,我们要求解
  32. X
  33. T
  34. X
  35. W
  36. =
  37. Y
  38. T
  39. X
  40. ,
  41. X^TXW=Y^TX,
  42. XTXW=YTX,

就有

  1. A
  2. (
  3. m
  4. +
  5. 1
  6. )
  7. ×
  8. (
  9. m
  10. +
  11. 1
  12. )
  13. =
  14. X
  15. T
  16. X
  17. ,
  18. b
  19. =
  20. Y
  21. T
  22. .
  23. A_{(m+1)\times(m+1)}=X^TX,\pmb b=Y^T.
  24. A(m+1)×(m+1)​=XTX,bb=YT.若我们想加一个正则项,就变成求解
  25. (
  26. X
  27. T
  28. X
  29. +
  30. λ
  31. E
  32. )
  33. W
  34. =
  35. Y
  36. T
  37. X
  38. .
  39. (X^TX+\lambda E)W=Y^TX.
  40. (XTXE)W=YTX.

首先说明一点:

  1. X
  2. T
  3. X
  4. X^TX
  5. XTX不一定是正定的但一定是半正定的(证明见此)。但是在实验中我们基本不用担心这个问题,因为
  6. X
  7. T
  8. X
  9. X^TX
  10. XTX有极大可能是正定的,我们只在代码中加一个断言(assert),不多关注这个条件。

共轭梯度法的思想来龙去脉和证明过程比较长,可以参考这个系列,这里只给出算法步骤(在上面链接的第三篇开头):

(0)初始化

  1. x
  2. (
  3. 0
  4. )
  5. ;
  6. x_{(0)};
  7. x(0)​;

(1)初始化

  1. d
  2. (
  3. 0
  4. )
  5. =
  6. r
  7. (
  8. 0
  9. )
  10. =
  11. b
  12. A
  13. x
  14. (
  15. 0
  16. )
  17. ;
  18. d_{(0)}=r_{(0)}=b-Ax_{(0)};
  19. d(0)​=r(0)​=bAx(0)​;

(2)令

  1. α
  2. (
  3. i
  4. )
  5. =
  6. r
  7. (
  8. i
  9. )
  10. T
  11. r
  12. (
  13. i
  14. )
  15. d
  16. (
  17. i
  18. )
  19. T
  20. A
  21. d
  22. (
  23. i
  24. )
  25. ;
  26. \alpha_{(i)}=\frac{r_{(i)}^Tr_{(i)}}{d_{(i)}^TAd_{(i)}};
  27. α(i)​=d(i)TAd(i)​r(i)Tr(i)​​;

(3)迭代

  1. x
  2. (
  3. i
  4. +
  5. 1
  6. )
  7. =
  8. x
  9. (
  10. i
  11. )
  12. +
  13. α
  14. (
  15. i
  16. )
  17. d
  18. (
  19. i
  20. )
  21. ;
  22. x_{(i+1)}=x_{(i)}+\alpha_{(i)}d_{(i)};
  23. x(i+1)​=x(i)​+α(i)​d(i)​;

(4)令

  1. r
  2. (
  3. i
  4. +
  5. 1
  6. )
  7. =
  8. r
  9. (
  10. i
  11. )
  12. α
  13. (
  14. i
  15. )
  16. A
  17. d
  18. (
  19. i
  20. )
  21. ;
  22. r_{(i+1)}=r_{(i)}-\alpha_{(i)}Ad_{(i)};
  23. r(i+1)​=r(i)​−α(i)​Ad(i)​;

(5)令

  1. β
  2. (
  3. i
  4. +
  5. 1
  6. )
  7. =
  8. r
  9. (
  10. i
  11. +
  12. 1
  13. )
  14. T
  15. r
  16. (
  17. i
  18. +
  19. 1
  20. )
  21. r
  22. (
  23. i
  24. )
  25. T
  26. r
  27. (
  28. i
  29. )
  30. ,
  31. d
  32. (
  33. i
  34. +
  35. 1
  36. )
  37. =
  38. r
  39. (
  40. i
  41. +
  42. 1
  43. )
  44. +
  45. β
  46. (
  47. i
  48. +
  49. 1
  50. )
  51. d
  52. (
  53. i
  54. )
  55. .
  56. \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)}.
  57. β(i+1)​=r(i)Tr(i)​r(i+1)Tr(i+1)​​,d(i+1)​=r(i+1)​+β(i+1)​d(i)​.

(6)当

  1. r
  2. (
  3. i
  4. )
  5. r
  6. (
  7. 0
  8. )
  9. <
  10. ϵ
  11. \frac{||r_{(i)}||}{||r_{(0)}||}<\epsilon
  12. ∣∣r(0)​∣∣∣∣r(i)​∣∣​<ϵ时,停止算法;否则继续从(2)开始迭代。
  13. ϵ
  14. \epsilon
  15. ϵ为预先设定好的很小的值,我这里取的是
  16. 1
  17. 0
  18. 5
  19. .
  20. 10^{-5}.
  21. 105.

下面我们按照这个过程实现代码:

  1. '''
  2. 共轭梯度法(Conjugate Gradients, CG)求优化解, m 为多项式次数
  3. - dataset 数据集
  4. - m 多项式次数, 默认为 5
  5. - regularize 正则化参数, 若为 0 则不进行正则化
  6. '''defCG(dataset, m =5, regularize =0):
  7. X = np.array([dataset[:,0]** i for i inrange(m +1)]).T
  8. A = np.dot(X.T, X)+ regularize * np.eye(m +1)assert np.all(np.linalg.eigvals(A)>0),'矩阵不满足正定!'
  9. b = np.dot(X.T, dataset[:,1])
  10. w = np.random.rand(m +1)
  11. epsilon =1e-5# 初始化参数
  12. d = r = b - np.dot(A, w)
  13. r0 = r
  14. whileTrue:
  15. alpha = np.dot(r.T, r)/ np.dot(np.dot(d, A), d)
  16. w += alpha * d
  17. new_r = r - alpha * np.dot(A, d)
  18. beta = np.dot(new_r.T, new_r)/ np.dot(r.T, r)
  19. d = beta * d + new_r
  20. r = new_r
  21. # 基本收敛,停止迭代if np.linalg.norm(r)/ np.linalg.norm(r0)< epsilon:breakreturn w

相比于朴素的梯度下降法,共轭梯度法收敛迅速且稳定。不过在多项式次数增加时拟合效果会变差:在

  1. m
  2. =
  3. 7
  4. m=7
  5. m=7时,其与最小二乘法对比如下:

在这里插入图片描述
此时,仍然可以通过正则项部分缓解(图为

  1. m
  2. =
  3. 7
  4. ,
  5. λ
  6. =
  7. 1
  8. m=7,\lambda=1
  9. m=7,λ=1):

blog.csdnimg.cn/49f5b3380f1d45e48033c94208ed2b2c.png)
最后附上四种方法的拟合图像(基本都一样)和主函数,可以根据实验要求调整参数:
在这里插入图片描述

  1. if __name__ =='__main__':
  2. warnings.simplefilter('error')
  3. dataset = get_dataset(bound =(-3,3))# 绘制数据集散点图for[x, y]in dataset:
  4. plt.scatter(x, y, color ='red')# 最小二乘法
  5. coef1 = fit(dataset)# 岭回归
  6. coef2 = ridge_regression(dataset)# 梯度下降法
  7. coef3 = GD(dataset, m =3)# 共轭梯度法
  8. coef4 = CG(dataset)# 绘制出四种方法的曲线
  9. draw(dataset, coef1, color ='red', label ='OLS')
  10. draw(dataset, coef2, color ='black', label ='Ridge')
  11. draw(dataset, coef3, color ='purple', label ='GD')
  12. draw(dataset, coef4, color ='green', label ='CG(lambda:0)')# 绘制标签, 显示图像
  13. plt.legend()
  14. plt.show()
标签: 机器学习 python

本文转载自: https://blog.csdn.net/wyn1564464568/article/details/126819062
版权归原作者 Castria 所有, 如有侵权,请联系我们删除。

“哈工大2022机器学习实验一:曲线拟合”的评论:

还没有评论