目录
- ML基础
- 前言
- 1. 复习sqrt函数
- 2. 线性回归预测房价
- 2.1 问题分析
- 2.2 代码实现
- 2.3 总结
- 个人总结
ML基础
前言
手写AI推出的全新保姆级从零手写自动驾驶CV课程,链接。记录下个人学习笔记,仅供自己参考。
本次课程主要学习复习 sqrt 函数和线性回归预测房价。
课程大纲可看下面的思维导图。
1. 复习sqrt函数
我们正式进入人工智能的学习,首先我们来回顾下之前求取 2 \sqrt 2 2 的例子
示例代码如下:
def sqrt(x):
p = x / 2 # 初始化
lr = 0.01 # 学习率
def forward(p, x):
return (p * p - x) ** 2
def backward(p, x, lr):
delta_p = 4 * (p * p - x) * p
return p - lr * delta_p
while True:
loss = forward(p, x)
if loss < 1e-5:
break
p = backward(p, x, lr)
return p
print(sqrt(2))
上述求解 2 \sqrt 2 2 的代码与我们之前讲解的有所不同
在这段代码中,函数 forward
计算了当前参数
p
p
p 的预测值与目标值
x
x
x 之间的误差,可以看作是神经网络中的损失函数
而函数 backward
则根据当前的误差以及学习率
l
r
lr
lr,通过对参数
p
p
p 求梯度更新参数值,可以看作是神经网络中的后向传播过程
因此,可以把这段代码看作是一个简单的单层神经网络,使用梯度下降法来求解 2 \sqrt 2 2
2. 线性回归预测房价
我们从房价预测的案例入手来讲解线性回归
回归模型可以分为一元线性回归和多元线性回归
一元线性回归是指一个自变量与一个因变量之间的线性关系的回归模型。例如,本次讲解的例子中我们可以用一元线性回归模型来研究房价与时间之间的关系。一元线性回归模型的数学描述如下:
h
θ
(
x
)
=
θ
0
x
0
+
θ
1
x
1
(
x
0
=
1
)
h_{\theta}(x)=\theta_0x_0+\theta_1x_1(x_0=1)
hθ(x)=θ0x0+θ1x1(x0=1)
即利用一条直线来拟合房价与时间之间的关系:
y
=
k
x
+
b
y = kx+b
y=kx+b
多元线性回归是指包含两个或两个以上自变量与一个因变量之间的线性关系的回归模型。例如,我们可以用多元线性回归模型来研究房屋售价与房屋面积、房间数量、地理位置等多个因素之间的关系。多元线性回归模型的数学描述如下:
h
θ
(
x
)
=
θ
0
x
0
+
θ
1
x
1
+
⋯
+
θ
n
x
n
=
∑
i
=
0
n
θ
i
x
i
=
θ
T
X
(
x
0
=
1
)
h_{\theta}(x)=\theta_{0}x_{0}+\theta_{1}x_{1}+\cdots+\theta_{n}x_{n}=\sum_{i=0}^{n}\theta_{i}x_{i}=\theta^{T}X(x_{0}=1)
hθ(x)=θ0x0+θ1x1+⋯+θnxn=i=0∑nθixi=θTX(x0=1)
2.1 问题分析
问题描述:假设现在有一组数据,该数据描述了从 2008-2012 年上海的房价,现在请你根据该数据预测 2013 年的上海房价。
问题分析:房价预测问题就是一个典型的线性回归问题,我们的目的是建立一个数学模型来描述时间和房价之间的关系,由于自变量只有一个,因此这是一个一元线性回归模型,我们可以使用一条直线来对二者之间的关系进行拟合,即 y = k x + b y = kx + b y=kx+b
- 特性1:若 b = 0 b = 0 b=0 则 y = k x y=kx y=kx 直线必过零点(模型表达能力会欠缺)
- 特性2:当 x = 0 x = 0 x=0 时, y y y 值就是 b b b 即截距,而 k k k 是斜率(导数求斜率) k = d y d x = y 1 − y 0 x 1 − x 0 k = \frac{\mathrm{d} y}{\mathrm{d} x} = \frac{y_1-y_0}{x_1-x_0} k=dxdy=x1−x0y1−y0,若 x 1 = x 0 x_1=x_0 x1=x0 则 k k k 无意义即当直线与 y y y 轴垂直时 y y y 无意义,不能用 y = k x + b y=kx+b y=kx+b 表示
房价数据如下:
年份 | 2008 | 2009 | 2010 | 2011 | 2012 | 2013 |
---|---|---|---|---|---|---|
价格 | 6000 | 8000 | 9000 | 11000 | 15000 | ? |
可视化后如下:
如何找到对应的直线呢?
我们可以先初始化一条直线 y = k x + b y=kx+b y=kx+b ,假设这就是我们要找的直线
如何衡量这条直线的好坏呢?
我们可以通过计算每个点到直线的距离,并求和取平均,使得总距离最短,如图 2-2 所示
如何去调整对应的直线使其能尽可能的符合预期呢?
我们需要根据总距离最短这个目的不断去更新直线的参数 k k k 和 b b b
如何更新参数 k k k 和 b b b ?
我们可以从求解 2 \sqrt 2 2 的例子中得到启发,定义损失函数 L L L 为总距离,利用梯度下降法将 k k k 和 b b b 沿着负梯度方向更新即可,如下所示,其中 l r lr lr 为学习率
k = k − l r ∗ ∂ L ∂ k k = k - lr * \frac{\partial L}{\partial k} k=k−lr∗∂k∂Lb = b − l r ∗ ∂ L ∂ b b = b - lr * \frac{\partial L}{\partial b} b=b−lr∗∂b∂L
2.2 代码实现
我们先来简单实现,慢慢调试看有什么问题,代码如下:
import numpy as np
import matplotlib.pyplot as plt
data = np.array([
[2008, 6000],
[2009, 8000],
[2010, 9000],
[2011, 11000],
[2012, 15000]
])
# 定义初始化的参数 k 和 b
k = np.random.random()
b = np.random.random()
X, Y = data.T
iter = 0 # 迭代次数
while True:
iter += 1
P = k * X + b # 预测值
L = np.mean(0.5 * (P - Y)**2) # 定义损失函数
print(f"{iter}. Loss: {L:.5f}, k = {k:.5f}, b = {b:.5f}")
if L < 1e-5: # 终止条件
break
delta_k = np.mean((P - Y) * X) # 对 k 求偏导
delta_b = np.mean(P - Y) # 对 b 求偏导
lr = 0.1
k = k - lr * delta_k # k 更新
b = b - lr * delta_b # b 更新
该代码实现了简单的线性回归预测,对一组房价和年份的数据进行拟合,以预测房价随时间的变化趋势。代码中首先随机初始化了 k k k 和 b b b 两个参数,然后使用梯度下降算法不断迭代,通过损失函数计算预测值和真实值的误差,再根据误差反向传播来更新 k k k 和 b b b,以期望使预测值更加接近真实值,最终得到一条直线,表示房价随时间的变化趋势。(from chatGPT)
输出如下:
1. Loss: 35297416.85023, k = 0.98189, b = 0.60411
2. Loss: 5000821078946638848.00000, k = 1573405.73810, b = 783.18369
3. Loss: 816251591822830492155922874368.00000, k = -635668580725.72083, b = -316252868.49308
4. Loss: 133231453522481572668586509982929722015744.00000, k = 256816018332930592.00000, b = 127769100099268.25000
...
59. Loss: nan, k = nan, b = nan
60. Loss: nan, k = nan, b = nan
61. Loss: nan, k = nan, b = nan
62. Loss: nan, k = nan, b = nan
63. Loss: nan, k = nan, b = nan
64. Loss: nan, k = nan, b = nan
65. Loss: nan, k = nan, b = nan
66. Loss: nan, k = nan, b = nan
可以看到直接跑飞了😂,可以看到一开始 L o s s Loss Loss 非常大,这意味着 Δ k \Delta k Δk 和 Δ b \Delta b Δb 非常大,在下一次更新后 k k k 和 b b b 非常大,最终没有迭代多少轮就放飞自我了。
根据输出结果,可以发现随着迭代次数的增加,损失函数的值呈现指数级别的增长,同时 k k k 和 b b b 的值也变得异常巨大,最终导致损失函数的值变为 NaN。
我们可以将学习率 l r lr lr 设置的非常小来适配我们的 Δ k \Delta k Δk 和 Δ b \Delta b Δb,但这似乎也不太好使,因为你不知道给多少合适,它本质上就是一个超参数,需要去调节,我们想固定 l r lr lr,能不能也得到一个比较好的效果呢?
当然是可以的,这个时候我们就要去限制 Δ k \Delta k Δk 和 Δ b \Delta b Δb 了,而 Δ k \Delta k Δk 和 Δ b \Delta b Δb 又是根据原始数据年份 X X X 和价格 Y Y Y 得来,那么我们可以考虑减小 X X X 和 Y Y Y 的规模,对数据做缩放,让其在一个比较小的范围内
具体如何缩放呢?我们最容易想到的就是除以最大值,如下:
X = X / 2012
print(X) # [0.99801193 0.99850895 0.99900596 0.99950298 1. ]
但是又出现了新的问题,那就是缩放后的数据区分度太小了,所以我们对数据做预处理要满足两个条件:
- 对数据做缩放,保证规模
- 缩放时,确保数据具有区分性
我们可以使用最大最小值归一化方法
X = (X - X.min()) / (X.max() - X.min())
Y = (Y - Y.min()) / (Y.max() - Y.min())
print(X) # [0. 0.25 0.5 0.75 1. ]
print(Y) # [0. 0.22222222 0.33333333 0.55555556 1. ]
加入该方法后再来运行输出结果如下:
1. Loss: 0.32445, k = 0.93289, b = 0.75717
2. Loss: 0.24929, k = 0.89283, b = 0.67703
3. Loss: 0.19207, k = 0.85828, b = 0.60691
4. Loss: 0.14851, k = 0.82852, b = 0.54552
5. Loss: 0.11533, k = 0.80295, b = 0.49177
6. Loss: 0.09005, k = 0.78103, b = 0.44467
7. Loss: 0.07079, k = 0.76229, b = 0.40337
8. Loss: 0.05611, k = 0.74631, b = 0.36714
9. Loss: 0.04490, k = 0.73275, b = 0.33533
...
似乎没啥问题,也看不出来,我们来简单进行可视化下,代码如下所示:
import numpy as np
import matplotlib.pyplot as plt
data = np.array([
[2008, 6000],
[2009, 8000],
[2010, 9000],
[2011, 11000],
[2012, 15000]
])
# 定义初始化的参数 k 和 b
k = np.random.random()
b = np.random.random()
X, Y = data.T
x_alpha = X.min()
x_beta = X.max() - X.min()
y_alpha = Y.min()
y_beta = Y.max() - Y.min()
X = (X - x_alpha) / x_beta
Y = (Y - y_alpha) / y_beta
iter = 0 # 迭代次数
while True:
iter += 1
P = k * X + b # 预测值
L = np.mean(0.5 * (P - Y)**2) # 定义损失函数
raw_x = X * x_beta + x_alpha # x 复原
raw_y = Y * y_beta + y_alpha # y 复原
raw_p = P * y_beta + y_alpha # p 复原
plt.cla()
plt.xlim(2007, 2013)
plt.ylim(0, 30000)
plt.plot(X, Y, "b*")
plt.plot(raw_x, raw_y, "b*")
plt.plot(raw_x, raw_p, "r-")
plt.pause(0.01)
print(f"{iter}. Loss: {L:.5f}, k = {k:.5f}, b = {b:.5f}")
if L < 1e-5: # 终止条件
break
delta_k = np.mean((P - Y) * X) # 对 k 求偏导
delta_b = np.mean(P - Y) # 对 b 求偏导
lr = 0.1
k = k - lr * delta_k # k 更新
b = b - lr * delta_b # b 更新
可视化图如下所示:
好像是那么回事,我们再来分析下,最大最小值归一化是不是会出现 x = 0 x=0 x=0 和 y = 0 y=0 y=0 的情况,是不是意味着 y = k x + b y = kx+b y=kx+b 归零点,意味着 b = 0 b=0 b=0,我们可以看到输出中 b b b 是不断减小逼近于 0 的,因为减去 X m i n X_{min} Xmin 和 Y m i n Y_{min} Ymin 相当于是把偏置 b b b 减掉了,相当于模型表达能力减弱了。
因此我们引入第二种归一化方法即正则化(对应正态分布)
- X = ( X − m e a n ) / s t d X = (X - mean) / std X=(X−mean)/std
- 能够更好更快的收敛,稳定性更强
- 最大最小值归一化中当出现噪声点时(离散值),会严重影响归一化后的数据分布,稳定性差
x_alpha = X.mean()
x_beta = X.std()
y_alpha = Y.mean()
y_beta = Y.std()
X = (X - x_alpha) / x_beta
Y = (Y - y_alpha) / y_beta
可视化如下图:
可以看到收敛更加稳定,对噪声带来的误差更小。
我们可以让其迭代到 200 次后退出循环,利用此时的 k k k 和 b b b 做 2013 年的房价预测,代码如下所示:
import numpy as np
import matplotlib.pyplot as plt
data = np.array([
[2008, 6000],
[2009, 8000],
[2010, 9000],
[2011, 11000],
[2012, 15000]
])
# 定义初始化的参数 k 和 b
k = np.random.random()
b = np.random.random()
X, Y = data.T
x_alpha = X.mean()
x_beta = X.std()
y_alpha = Y.mean()
y_beta = Y.std()
X = (X - x_alpha) / x_beta
Y = (Y - y_alpha) / y_beta
iter = 0 # 迭代次数
while True:
iter += 1
P = k * X + b # 预测值
L = np.mean(0.5 * (P - Y)**2) # 定义损失函数
raw_x = X * x_beta + x_alpha # x 复原
raw_y = Y * y_beta + y_alpha # y 复原
raw_p = P * y_beta + y_alpha # p 复原
plt.cla()
plt.xlim(2007, 2013)
plt.ylim(0, 30000)
plt.plot(X, Y, "b*")
plt.plot(raw_x, raw_y, "b*")
plt.plot(raw_x, raw_p, "r-")
plt.pause(0.01)
print(f"{iter}. Loss: {L:.5f}, k = {k:.5f}, b = {b:.5f}")
if L < 1e-5: # 终止条件
break
delta_k = np.mean((P - Y) * X) # 对 k 求偏导
delta_b = np.mean(P - Y) # 对 b 求偏导
lr = 0.1
k = k - lr * delta_k # k 更新
b = b - lr * delta_b # b 更新
if iter == 200:
break
# 房价预测
year = 2013
normalize_pred = k * (year - x_alpha) / x_beta + b
real_pred = normalize_pred * y_beta + y_alpha
print(f"对于{year}年预测的房价是:{real_pred:.3f} 元")
输出如下:
对于2013年预测的房价是:16100.000 元
可以看到和我们猜想的预期是差不多的😀
2.3 总结
-
y
=
k
x
+
b
y = kx + b
y=kx+b 能够表示一个线性模型
- b = 0 b = 0 b=0 时,直线必定过 0 点,此时模型表达能力弱
- b b b 是截距, k k k 是斜率,对于垂直于 y y y 轴的直线,无法表示
- 对于 numpy 的矩阵,可以使用 x,y=data.T 在行方向上解包
- 训练时,需要对数据进行预处理,目的是确保步长在比较小的范围内
- 此时 l r lr lr 就相对比较固定,0.1 就行
- 预处理的方法中,最大最小值归一化 x = (x - x.min()) / (x.max() - x.min())
- 它会对异常数据比较敏感,不是一个好的做法
- 预处理方法中,正则化:x = (x - x.mean()) / x.std()
- 它考虑了所有数据的均值和方差,这是以后常见的方法,比较推荐
个人总结
本次课程正式进入人工智能的学习,首先我们从一个不同的角度复习了 sqrt 函数,然后我们讲解了利用线性回归来进行房价预测,利用直线模型来对数据进行拟合,要注意的是我们需要对数据进行预处理,否则步长在一个很大的范围,无法完成收敛,常见预处理包括最大最小值归一化和正则化,前者对数据比较敏感,后者考虑了所有数据的均值和方差,收敛性更好,值得推荐。