前言
大多数机器学习的例子都是都是关注视觉和自然语言方向,本人尝试就阅读过的文章整理一系列关于数据预测,时序等的例子,来记录学习机器学习的过程。
机器学习并不是tensorflow等的专利,对于简单的模型,使用一些常规的计算库也可以求解,本文先使用numpy 进行手动的梯度下降,来完成机器学习。
题目
考虑一个实际问题,某城市在 2013 年 - 2017 年的房价如下表所示:
年份 | 2013 | 2014 | 2015 | 2016 | 2017 | 2018 |
---|---|---|---|---|---|---|
房价 | 12000 | 14000 | 15000 | 16500 | 17500 | 18400 |
现在,我们希望通过对该数据进行线性回归,即使用线性模型 y = ax + b 来拟合上述数据,此处 a 和 b 是待求的参数。
环境
开发环境比较简单,安装 最新 anaconda 即可, 我安装的版本默认内置 python 3.8。 使用Spyder 执行代码, spyder默认不能跑plt 图像界面,前往 修改设置即可。
分析
首先,我们定义数据,进行基本的归一化操作。
import numpy as np
X_raw = np.array([2013, 2014, 2015, 2016, 2017,2018], dtype=np.float32)
y_raw = np.array([12000, 14000, 15000, 16500, 17500, 18400], dtype=np.float32)
X = (X_raw - X_raw.min()) / (X_raw.max() - X_raw.min())
y = (y_raw - y_raw.min()) / (y_raw.max() - y_raw.min())
接下来,我们使用梯度下降方法来求线性模型中两个参数 a 和 b 的值 。过程如下:
- 初始化为 a=b=0
- 计算 y_pred = a * X + b, 通过 y_pred 计算 a,b的梯度。grad_a, grad_b = 2 * (y_pred - y).dot(X), 2 * (y_pred - y).sum()
- 按 指定的学习速率 learning_rate , grad_a, grad_b , 计算 新的 a,b ,循环计算。
- 循环指定次数或者达到 目标cost 停止计算,梯度下降结束。
为方便展示,使用plt模块增加了推导进度的显示,可以看到 我们从一个 横线 y=0*x+0, 推导得到 最终的结果。
完整源码:
"""
使用 numpy进行梯度下降的回归训练
X, y 定义了屏幕上的几个点, 找出理想的 y=ax+b来 穿过这些点,差异最小。
默认 a, b = 0, 0
训练的过程中使用 plt 来 展现 X, y 的值 以及 a, b 的变化。 看到 y=ax+b 逐渐贴近 所有的 X,y。
Created on Sat Feb 20 21:30:29 2021
@author: huwp001
"""
import numpy as np
import matplotlib.pyplot as plt
"""
原始数据 X_raw, y_raw
"""
X_raw = np.array([2013, 2014, 2015, 2016, 2017, 2018], dtype=np.float32)
y_raw = np.array([12000, 14000, 15000, 16500, 17500, 18400], dtype=np.float32)
"""
进行归一化,转换为了 0,1 之间的值
"""
X = (X_raw - X_raw.min()) / (X_raw.max() - X_raw.min())
y = (y_raw - y_raw.min()) / (y_raw.max() - y_raw.min())
"""
默认从横线开始训练 y=ax+b
"""
a, b = 0, 0
num_epoch = 10000
learning_rate = 5e-4
for e in range(num_epoch):
# 手动计算损失函数关于自变量(模型参数)的梯度
y_pred = a * X + b
grad_a, grad_b = 2 * (y_pred - y).dot(X), 2 * (y_pred - y).sum()
# 更新参数
a, b = a - learning_rate * grad_a, b - learning_rate * grad_b
if e % 500 == 0 or e==num_epoch-1:
# plot and show learning process
plt.cla()
plt.scatter(X, y)
plt.plot(X, y_pred, 'r-', lw=5)
plt.text(0.5, 0, 'Loss=%.4f' % grad_a, fontdict={'size': 20, 'color': 'red'})
plt.text(0.5, 0.12, 'y=%.4f*x + %.4f' % (a, b), fontdict={'size': 20, 'color': 'red'})
plt.pause(0.1)
plt.ioff()
plt.show()
print(a, b)
运行
执行程序开始推导,红线是初始 y=ax+b, 且 a=b=0
梯度下降执行一段时间后,横线开始接近真实数据。
循环10000次后结果,红线比较完美的穿过数据点。
总结
梯度下降的主要逻辑是 初始化变量,计算损失Loss,计算梯度Grad,用梯度更新变量,继续循环。由于原始数据接近一元一次函数,我们使用的Loss函数,梯度函数都是针对一元一次函数手工编写的。如:grad_a, grad_b = 2 * (y_pred - y).dot(X), 2 * (y_pred - y).sum() 。 修改该代码直接影响训练的效果。手工求函数关于参数的偏导数。如果是简单的函数或许还好,但一旦函数的形式变得复杂(尤其是深度学习模型),手工求导的过程将变得非常痛苦,甚至不可行。
另外,经常需要手工根据求导的结果更新参数。这里使用了最基础的梯度下降方法,因此参数的更新还较为容易。但如果使用更加复杂的参数更新方法(例如 Adam 或者 Adagrad),这个更新过程的编写同样会非常繁杂。
而 TensorFlow 等深度学习框架的出现很大程度上解决了这些痛点,为机器学习模型的实现带来了很大的便利。
参考文章:
简单粗暴 tensorflow2 前往