zoukankan      html  css  js  c++  java
  • 哈工大 机器学习 lab1


    哈尔滨工业大学计算机科学与技术学院
    实验报告


    课程名称: 机器学习
    课程类型: 必修
    实验题目: 多项式拟合正弦函数


    一.实验目标

    掌握最小二乘法求解(无惩罚项的损失函数)、掌握加惩罚项(2范数)的损失函数优化、梯度下降法、共轭梯度法、理解过拟合、克服过拟合的方法(如加惩罚项、增加样本)

    二.实验要求和实验环境

    实验要求
    • 生成数据,加入噪声;
    • 用高阶多项式函数拟合曲线;
    • 用解析解求解两种loss的最优解(无惩罚项和有惩罚项)
    • 优化方法求解最优解(梯度下降,共轭梯度);
    • 用你得到的实验数据,解释过拟合。
    • 用不同数据量,不同超参数,不同的多项式阶数,比较实验效果。
    • 语言不限,可以用matlab,python。求解解析解时可以利用现成的矩阵求逆。梯度下降,共轭梯度要求自己求梯度,迭代优化自己写。不许用现成的平台,例如pytorch,tensorflow的自动微分工具。

    实验环境
    • Microsoft win10 1809
    • python 3.7.0
    • Sublime Text 3

    1569076824442

    三.设计思想

    1.算法原理

    (0)数据的产生

    • x为 之间的随机数.
    • 噪音由一个标准正态分布函数 *0.1 产生
    • y = sin(x) +
    • 利用循环, 根据x产生范德蒙德矩阵X
    • 将 X, y 都转为矩阵类型

    (1) 解析解(不带惩罚项)

    误差函数:

    将上式写成矩阵形式:

    通过将上式求导我们可以得到式

    得到

    (2) 解析解(带惩罚项)

    为了防止参数多时具有较大的绝对值,即过拟合。在优化目标函数式中增加的惩罚项,因此我们得到了:

    同样我们可以将上式写成矩阵形式, 我们得到:

    对上式求导我们得到:

    我们得到, 为单位阵。

    (3)梯度下降法

    点可微且有定义,顺着梯度的反方向 即为下降最快的方向。

    在不断迭代的过程中, w会走向极值点, 此时为极小值.

    判断是否接近极值点在于两次迭代的差值是否足够小;

    若后一次迭代比前一次迭代的loss更大则说明要减少学习率.

    (4)共轭梯度法求解最优解

    共轭梯度法解决形如的线性方程组解的问题 (必须是对称的、正定的)。

    共轭梯度法是一个典型的共轭方向法,它的每一个搜索方向是互相共轭的,而这些搜索方向d仅仅是负梯度方向与上一次迭代的搜索方向的组合,因此,存储量少,计算方便。

    对于第k步的残差,我们根据残差去构造下一步的搜索方向,初始时我们令。然后利用方法依次构造互相共轭的搜素方向,具体构造的时候需要先得到第k+1步的残差,即,其中如后面的式

    根据第k+1步的残差构造下一步的搜索方向,其中

    然后可以得到,其中

    2.算法的实现

    (0)数据的产生

     x0 = np.random.random((data_size,1)) * data_range
     x0 = np.sort(x0,0) # 按列排序, 便于画图
     loss = np.random.randn(data_size,1) * 0.1 # 以标准的正态分布*0.1产生噪音
     y = np.sin(x0) + loss
     return np.matrix(x0),np.matrix(y)

    (1) 解析解(不带惩罚项)

     w = (x.T * x).I *x.T * t

    (2) 解析解(带惩罚项)

     w = (x.T * x + lamb * np.eye(m) ).I *x.T * t

    (3) 梯度下降法

     while abs(loss1 -loss0) > 1e-8: # 当两次loss相差不大时, 表示接近极值点了.
      count +=1
      w = w - learning_rate * gradient # 注意此处
      loss0 = loss1
      loss1 = loss_function(x,y,w)
      # 若发现迭代过程中loss变大, 则减小学习率
      if np.all(loss1-loss0>0):
      learning_rate *= 0.5
      gradient = gradient_function(x,y,w)
     return w

    (4) 共轭梯度法

     while not np.all(np.absolute(gradient) <= 1e-3):
      count +=1
      w = w - learning_rate * gradient # 注意此处
      error1 = error_function(x,y,w)
      if np.all(error1-error>0):
      learning_rate *= 0.5
      gradient = gradient_function(x,y,w)
      if count >10000:
      print(gradient)
      count -= 10000
      print(count)
     return w

    四、实验结果与分析

    (1) 解析解(不带惩罚项)

    (1.1)固定训练集大小, 改变阶数

    1569116968396

    1569116981923

    1569117001919

    1569117014226

    1569117034636

    可以看到当m = 5时就拟合的曲线就已经比较准确了, 但当m = 9时, 即使拟合的曲线经过了所有的点, 但和真实曲线相比差距很大, 即发生了过拟合现象.

    (1.2)下用loss函数说明过拟合:

    我们选择部分数据用来训练模型, 然后用训练出的模型在新数据上做测试, 观察在不同阶时, loss函数在训练集和测试集上的区别.

    如下图, 当m >7时,训练集的loss 仍然保持减小的趋势, 但测试集的loss开始增大, 说明了该模型此时过拟合,已经失去了泛化能力.

    1569316368664

    (1.3)固定阶数 改变训练集大小

    1569116664284

    1569116697834

    1569116743060

    1569116770465

    可以看到当阶数固定时, 随着训练集的增大, 拟合的曲线越来越准确.这是因为大量数据可以减少噪声带来的负面影响, 从而减小过拟合情况的发生.

    (2) 解析解(带惩罚项)

    (2.1) 确定超参数

    为了确定最佳的超参数,我们需要通过根均方(RMS)误差来确定,其中RMS的定义如下

    取不同的值代入, 多次实验观察最小根均方误差对应的值,

    1569323685766

    1569323794208

    1569324007120

    我们发现当超参值左右效果较好, 于是我们取.

    (2.2) 不同阶的拟合情况

    1569117224922

    1569117243115

    1569117256913

    1569117268316

    1569117280295

    可以看到,带惩罚项的解析解在m = 9 时并没有发生明显的过拟合, 说明带惩罚项可以减少过拟合现相的发生.


    根据上述结果解释: 为什么会发生过拟合?

    由于训练集中的数据有限,并且数据附带一定的噪声.随着阶数的上升, 模型的表达能力增强, 能够越来越准确的描述训练集中的数据, 为了准确描述这些有噪声的数据, 参数的部分项会非常大,.虽然此时模型准确的表达了训练集, 但这也意味着它将训练集中所有噪声都接纳进来了, 此举不利于模型描述训练集之外的数据,即此时模型的泛化能力将变的很差.

    为什么增加惩罚项会有效防止过拟合?

    将正则项加入损失函数的方法后, 若参数中部分项过大, 那么损失函数的值就会很大, 为了取得损失函数的极小值, 那么就需要取一个范数较小的值, 而这正好可以有效抑制过拟合.

    为什么增加训练集的规模可以有效防止过拟合?

    当训练集规模增加时, 模型可以根据更多的数据来不断的改进自己, 使自己更准确,泛化能力更强.

    同时, 由于噪声是随机产生的, 大量数据可以降低一些噪声较大数据带来的负面影响.


    (3) 梯度下降法

    1569129191785

    1569129452845

    1569129656314

    1569129673194

    1569129690686

    在实验的过程中我发现随着m的增加, 梯度下降法必须要减小学习率, 否则很容易导致无法收敛.

    当然在梯度下降法运行的过程中我们需要动态的调节学习率,如过后一次迭代的loss比前一次的loss大,那么就必须将学习率减半.

    如下:

     # 若发现迭代过程中loss变大, 则减小学习率
      if np.all(loss1-loss0>0):
      learning_rate *= 0.5

    若果两次迭代的loss差值小于一个指定的很小的数, 那么我们认为此时非常接近极值点, 可以停止迭代了.

    (4) 共轭梯度法

    阶数迭代次数
    10
    32
    54
    76
    98

    1569117870588

    1569117926610

    1569117942357

    1569117970442

    1569117986210

    从上述表中我们可以看到, 共轭梯度法迭代非常快, 只需要几次就可以迭代出结果.

    五、结论

    1. 解析解中加入惩罚项可以有效的防止过拟合.
    2. 增加训练集的数据规模可以有效地防止过拟合.
    3. 和梯度下降法相比, 共轭梯度法更快,迭代次数更少, 而且结果更准确.
    4. 在梯度下降法中, 选择合适的学习率很重要, 学习率过小, 迭代次数就会很大, 迭代时间很长.学习率过大, 就可能无法收敛, 或者得到错误的结果.
    5. 在带有惩罚项的解析解中, 选择合适超参才能使模型更准确.

    六、参考文献

    1. http://cs229.stanford.edu/syllabus.html
    2. Pattern Recognition and Machine Learning.
    3. https://baike.baidu.com/item/共轭梯度法/7139204?fr=aladdin

    七、附录:源代码(带注释)

     # ML-lab1.py
     # author 李国庆 1170300523
     # time 2019/9/18
     # theme 曲线拟合多项式
     
     import numpy as np
     import math
     import matplotlib.pyplot as plt
     
     
     # 全局变量
     m = 9     # 拟合阶数
     data_size = 30 #训练集规模
     data_range = math.pi * 2  # x的范围[0,data_range]
     lambd = 0.01  # 超参数,用于惩罚项中
     learning_rate = 0.0001   # 学习率, 用于梯度下降
     method_name = ''  # 选择方法
     
     
     # 主函数
     def main():
     
      # 生成数据
      x0, y = generate_data()
      x = Vandermonde_x(x0)
     
     
      # 以下是四种方法
      global method_name
      method_name_set = ['analytical_solution','analytical_solution_reg',
      'gradient_descent','conjugate_gradient']
      method_name = method_name_set[1]  # 从列表中选择方法, 选2时较慢(梯度下降)
     
      if method_name == 'analytical_solution':
      w = analytical_solution(x,y)
      elif method_name == 'analytical_solution_reg':
      w = analytical_solution_reg(x,y)
      elif method_name == 'gradient_descent':
      w = gradient_descent(x,y,learning_rate)
      else:
      w = conjugate_gradient(x,y)
     
      test(x0,y,w) # 测试w的拟合情况
     
     
     # 以sin(x)生成数据,附带噪声
     def generate_data():
      x0 = np.random.random((data_size,1)) * data_range
      x0 = np.sort(x0,0) # 按列排序, 便于画图
      loss = np.random.randn(data_size,1) * 0.1 # 以标准的正态分布*0.1产生噪音
      y = np.sin(x0) + loss
      return np.matrix(x0),np.matrix(y)
     
     # 生成范德蒙德行列式
     def Vandermonde_x(x0):
      x = np.ones((data_size,1))
      for i in range(1,m):
      x = np.hstack((x,np.power(x0,i)))
      return np.matrix(x)
     
     # 解析解(不带惩罚项)
     def analytical_solution(x,y):
      w = (x.T * x).I * x.T * y
      return w
     
     # 解析解(带惩罚项)
     def analytical_solution_reg(x,y):
      w =(x.T * x + lambd * np.eye(m)).I * x.T * y
      return w
     
     
     # 梯度函数
     def gradient_function(x, y, w):
      return (1.0/m) *( x.T * x * w - x.T * y + 0.001 * w)
     
     # 损失函数 loss
     def loss_function( x, y, w):
      diff = x * w  - y
      loss = 1.0/(2*data_size)*(diff.T * diff)
      return loss[0,0]
     
     # 梯度下降法
     def gradient_descent(x, y,learning_rate):
      w = np.zeros((m,1))
      gradient = gradient_function(x,y,w)
      loss0 = 0
      loss1 = loss_function(x,y,w)
      count = 0
      xw = np.linspace(0,2*math.pi,40)
      while abs(loss1 -loss0) > 1e-8: # 当两次loss相差不大时, 表示接近极值点了.
      count +=1
      w = w - learning_rate * gradient # 注意此处
      loss0 = loss1
      loss1 = loss_function(x,y,w)
      # 若发现迭代过程中loss变大, 则减小学习率
      if np.all(loss1-loss0>0):
      learning_rate *= 0.5
      gradient = gradient_function(x,y,w)
     
      # 每1000次迭代绘一次图
      if count >1000:
      print(loss1-loss0)
      count -= 1000
      yw = np.polyval(w[::-1],xw)
      plt.scatter(x[:,1].tolist(),y.tolist(),edgecolor="b")
      plt.plot(xw,yw,'r')
      plt.pause(0.001)
      plt.clf()
      return w
     
     
     # 共轭梯度法
     def conjugate_gradient(x, y):
      lambd = 0.001
      Q = (1 / m) * (x.T * x + lambd * np.mat(np.eye(x.shape[1])))
      w = np.mat(np.zeros(x.shape[1])).T
      r = -gradient_function(x, y, w)
      p = r
      count = 0
      for i in range(1, x.shape[1]):
         count += 1
         a = float((r.T * r) / (p.T * Q * p))
         r_prev = r
         w = w + a * p
         r = r - a * Q * p
         p = r + float((r.T * r) / (r_prev.T * r_prev)) * p
      return w
     
     # 测试效果
     def test(x0,y,w):
      # 画出给定点的散点图
      plt.scatter(x0.tolist(),y.tolist(),edgecolor="b", label="training data")
      # 画出结果的模拟曲线
      xw = np.linspace(0,2*math.pi,40)
      yw = np.polyval(w[::-1],xw)
      plt.plot(xw,np.sin(xw),'y',label = 'sin(x)')
      info = 'm = '+ str(w.shape[0])+'   training_data_size = '+str(data_size)
      plt.plot(xw,yw,'r',label = method_name)
      plt.legend()
      plt.title(info)
      plt.show()
     
     
     if __name__ == '__main__':
      main()

  • 相关阅读:
    Unity3D 4.0 界面 基础 入门
    try catch finally 用法
    Mysql表引擎Innodb、MyIsam、Memory
    初步的kudu+impala vs dorisdb vs tidb
    mysql创建类似oracle的dblink
    jedis请求keys超时报错
    php 1223
    php 1214
    php 1216
    php 1222
  • 原文地址:https://www.cnblogs.com/lee3258/p/11996333.html
Copyright © 2011-2022 走看看