上面讲到了我们通过采样多个数据样本及,然后找到一条最好的直线,使其尽可能地让所有采集点到该直线到误差之和最小。
但是由于观测误差的存在,当我们采集多个数据点时,可能不存在一条直线完美的穿过所有采样点。退而求其次,我们希望能找到一条比较“好”的位于采样点中间的直线。
那么怎么衡量“好”与“不好”呢?一个很自然的想法就是,求出当前模型的 所有采样点上的预测值𝑤𝑥(𝑖) + 𝑏与真实值𝑦(𝑖)之间的差的平方和作为总误差L:
latex公式的写法
L = \frac{1}{n} \sum_{i=1}^{n}\left (W ^{x\left ( i \right )} + b - y^{\left ( i \right )}\right)^{2}
然后搜索一组参数w,b使得L最小,对应的直线就是我们要寻找的最优直线
w, b = arg_{w,b} min\frac{1}{n} \sum_{i=1}^{n}\left (W ^{x\left ( i \right )} + b - y^{\left ( i \right )}\right)^{2}
𝑛表示采样点的个数。这种误差计算方法称为均方误差MSE。
优化:
最简单的优化方法就是暴力搜索或随机试验,比如要找出最合适的𝑤∗和𝑏∗,我们就可以从 (部分)实数空间中随机采样任意的𝑤和𝑏,并计算出对应模型的误差值L,然后从测试过的 {L}中挑出最好的L∗,它所对应的𝑤和𝑏就可以作为我们要找的最优𝑤∗和𝑏∗。
这种算法固然简单直接,但是面对大规模、高维度数据的优化问题时计算效率极低, 基本不可行。梯度下降算法(Gradient Descent)是神经网络训练中最常用的优化算法,配合 强大的图形处理芯片 GPU(Graphics Processing Unit)的并行加速能力,非常适合优化海量数 据的神经网络模型,自然也适合优化我们这里的神经元线性模型。
我们在高中时代学过导数(Derivative)的概念,如果要求解一个函数的极大、极小值, 可以简单地令导数函数为 0,求出对应的自变量点(称为驻点),再检验驻点类型即可。
函数导数(虚线)为 0 的点即为𝑓(𝑥)的驻点,函数的极大值和极小值点均出现在驻点中。导数大于0,单调递增,导数小于0,单调递减。
箭头的方向总是指向当前位置函数值增速最 大的方向,函数曲面越陡峭,箭头的长度也就越长,梯度的模也越大。
我们能直观地感受到,函数在各处的梯度方向∇𝑓总是指向函数值增 大的方向,那么梯度的反方向−∇𝑓应指向函数值减少的方向。利用这一性质,我们只需要 按照
没有在希腊字母中找到∇。
来迭代更新𝒙′,就能获得越来越小的函数值,其中𝜂用来缩放梯度向量,一般设置为某较小
的值,如 0.01、0.001 等。特别地,对于一维函数,上述向量形式可以退化成标量形式:
通过上式迭代更新𝑥′若干次,这样得到的𝑥′处的函数值𝑦′,总是更有可能比在𝑥处的函数值𝑦小。
通过优化参数的方法称为梯度下降算法,它通过循环计算函数的梯度∇𝑓并 更新待优化参数𝜃,从而得到函数𝑓获得极小值时参数𝜃的最优数值解。需要注意的是,在深度学习中,一般𝒙表示模型输入,模型的待优化参数一般用𝜃、𝑤、𝑏等符号表示。
在计算出误差函数在𝑤和𝑏处的梯度后,我们可以根据式(2.1)来更新𝑤和𝑏的值。我们把 对数据集的所有样本训练一次称为一个 Epoch,共循环迭代 num_iterations 个 Epoch。
# #我在这个简单实验中遇到的问题,
# 1. 是复制代码的时候,计算梯度的for语句被注释,
# 2.是初始赋值顺序错误
# 3. 还是对于原理理解不透彻
# 这个原理是使loss最小,所以需要计算梯度来使得w,b对应的函数损失值更小。
# #我在这个简单实验中遇到的问题,
# 1. 是复制代码的时候,计算梯度的for语句被注释,
# 2. 是初始赋值顺序错误
# 3. 还是对于原理理解不透彻
# 4. 损失函数是用来衡量预测值与真实值之间的差距,所以当该损失函数越小时,说明有越多的点时和实际值相符合的,所以需要反向梯度传播算法。
# 5. 梯度方向总是指向函数增大的方向,我们需要减小,才能实现。所以该算法才叫反向梯度传播算法。
# 这个原理是使loss最小,所以需要计算梯度来使得w,b对应的函数损失值更小。
线性回归问题
jupyter notebook代码:
import numpy as np
data = []# 保存样本集的列表
for i in range(100): # 循环采样 100 个点
x = np.random.uniform(-10., 10.) # 随机采样输入 x # 采样高斯噪声
eps = np.random.normal(0., 0.01)
# 得到模型的输出
y = 1.477 * x + 0.089 + eps
data.append([x, y]) # 保存样本点
data = np.array(data) # 转换为 2D Numpy 数组
import matplotlib.pyplot as plt#约定俗成的写法plt
#plt.figure()
x = data[:,0]
y = data[:,1]
fig, ax1 = plt.subplots()
ax1.set_xlabel('Time (s)')
ax1.set_ylabel('Throughput (Gbps)', color='black')
ax1.set_ylim(-10,10,1)
ax1.set_xlim(-10,10, 1)
l1, = ax1.plot(x, y,color='red',label='first_flow')
plt.show()
def mse(b, w, points):
# 根据当前的 w,b 参数计算均方差损失
totalError = 0
for i in range(0, len(points)): # 循环迭代所有点
x = points[i, 0] # 获得i号点的输入x
y = points[i, 1] # 获得i号点的输出y # 计算差的平方,并累加
totalError += (y - (w * x + b)) ** 2 # 将累加的误差求平均,得到均方差
#print(totalError / float(len(points)))
return totalError / float(len(points))
def step_gradient(b_current, w_current, points, lr): # 计算误差函数在所有点上的导数,并更新 w,b
b_gradient = 0
w_gradient = 0
M = float(len(points)) # 总样本数
for i in range(0, len(points)):
x = points[i, 0]
y = points[i, 1]
# 误差函数对 b 的导数:grad_b = 2(wx+b-y),参考公式(2.3)
b_gradient += (2/M) * ( (w_current * x + b_current) - y) # 误差函数对 w 的导数:grad_w = 2(wx+b-y)*x,参考公式(2.2)
w_gradient += (2/M) * x * ((w_current * x + b_current) - y ) # 根据梯度下降算法更新 w',b',其中 lr 为学习率
#print(b_gradient,w_gradient)
new_b = b_current - (lr * b_gradient)
new_w = w_current - (lr * w_gradient)
return [new_b, new_w]
def gradient_descent(points, starting_b, starting_w, lr, num_iterations): # 循环更新 w,b 多次
b = starting_b # b的初始值
w = starting_w # w的初始值
#print(b,w)
# 根据梯度下降算法更新多次
for step in range(num_iterations):
# 计算梯度并更新一次
loss = mse(b, w, points) # 计算当前的均方差,用于监控训练进度
b, w = step_gradient(b, w, np.array(points), lr)
if step%50 == 0: # 打印误差和实时的 w,b 值
print(f"iteration:{step}, loss:{loss}, w:{w}, b:{b}")
return [b, w] # 返回最后一次的 w,b
def main():
lr = 0.001 # 学习率
initial_b = 0# 初始化b为0
initial_w = 0 # 初始化w为0
num_iterations = 1000
# 训练优化1000次,返回最优w*,b*和训练Loss的下降过程
[b, w]= gradient_descent(data, initial_b, initial_w, lr, num_iterations)
loss = mse(b, w, data) # 计算最优数值解w,b上的均方差
print(f'Final loss:{loss}, w:{w}, b:{b}')
if __name__ == '__main__':
main()
fig, ax1 = plt.subplots()
ax1.set_xlabel('Time (s)')
ax1.set_ylabel('Throughput (Gbps)', color='black')
ax1.set_ylim(-10,10,1)
ax1.set_xlim(-10,10, 1)
l2, = ax1.plot(x, 1.4768604696062602 * x + 0.07821812190464268,color='red',label='first_flow')
put.show()
线性回归问题其实可以理解为一组连续值(向量)的预测问题。给定数据集𝔻,我们需要从𝔻中学习到数据的真实模型,从而预测未见过的样本的输出值。在假定模型的类型后,学习过程就变成了搜索模型参数的问题,比如我们假设神经元为线性模型,那么训练过程即为搜索线性模型的𝒘和𝑏参数的过程。训练完成后,利用学到的模型,对于任意的新输入𝒙,我们就可以使用学习模型输出值作为真实值的近似。从 这个角度来看,它就是一个连续值的预测问题。
在现实生活中,连续值预测问题是非常常见的,比如股价的走势预测、天气预报中温 度和湿度等的预测、年龄的预测、交通流量的预测等。对于预测值是连续的实数范围,或 者属于某一段连续的实数区间,我们把这种问题称为回归(Regression)问题。特别地,如果 使用线性模型去逼近真实模型,那么我们把这一类方法叫做线性回归(Linear Regression, 简称 LR),线性回归是回归问题中的一种具体的实现。
分类问题:
在机器学习中间, 一般希望数据的范围在 0 周围的小范围内分布。通过预处理步骤,我们把[0,255]像素范围 归一化(Normalize)到[0,1. ]区间,再缩放到[−1,1]区间,从而有利于模型的训练。
每一张图片的计算流程是通用的,我们在计算的过程中可以一次进行多张图片的计 算,充分利用 CPU 或 GPU 的并行计算能力。我们用形状为[h, 𝑤]的矩阵来表示一张图片, 对于多张图片来说,我们在前面添加一个数量维度(Dimension),使用形状为[𝑏, h, 𝑤]的张量 来表示,其中𝑏代表了批量(Batch Size);多张彩色图片可以使用形状为[𝑏, h, 𝑤, 𝑐]的张量来 表示,其中𝑐表示通道数量(Channel),彩色图片𝑐 = 3。通过 TensorFlow 的 Dataset 对象可 以方便完成模型的批量训练,只需要调用 batch()函数即可构建带 batch 功能的数据集对 象。
考虑输入格式,一张灰度图片𝒙使用矩阵方式存储,形状为:[h, 𝑤],𝑏张图片使用形状 为[𝑏, h, 𝑤]的张量𝑿存储。而我们模型只能接受向量形式的输入特征向量,因此需要将[h, 𝑤] 的矩阵形式图片特征打平成[h ∙ 𝑤]长度的向量,如图 3.5 所示,其中输入特征的长度𝑑in = h ∙ 𝑤。
矩阵打平操作示意图
对于输出标签𝑦,前面已经介绍了数字编码,它可以用一个数字来表示标签信息,此 时输出只需要一个节点即可表示网络的预测类别值,例如数字 1 表示猫,数字 3 表示鱼等 (编程实现时一般从 0 开始编号)。但是数字编码一个最大的问题是,数字之间存在天然的 大小关系,例如1 < 2 < 3,如果 1、2、3 分别对应的标签是猫、狗、鱼,他们之间并没有 大小关系,所以采用数字编码的时候会迫使模型去学习这种不必要的约束。
那么怎么解决这个问题呢?可以将输出设置为𝑑out个输出节点的向量,𝑑out与类别数相 同,让第𝑖 ∈ [1, 𝑑out]个输出节点的值表示当前样本属于类别𝑖的概率𝑃(𝒙属于类别𝑖|𝒙)。我 们只考虑输入图片只输入一个类别的情况,此时输入图片的真实标签已经唯一确定:如果 物体属于第𝑖类的话,那么索引为𝑖的位置上设置为 1,其他位置设置为 0,我们把这种编码 方式叫作 one-hot 编码(独热编码)。
One-hot 编码是非常稀疏 (Sparse)的,相对于数字编码来说,占用较多的存储空间,所以一般在存储时还是采用数字
编码,在计算时,根据需要来把数字编码转换成 One-hot 编码,通过 tf.one_hot 函数即可实现。
举例说明:
tf.constant([0,1,2,3]) # 数字编码的4个样本标签
y = tf.one_hot(y, depth=10) # one-hot编码,指定类别总数为10
y
<tf.Tensor: id=21, shape=(4, 10), dtype=float32, numpy=
array([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 1., 0., 0., 0., 0., 0., 0.]], dtype=float32)>
对于分类问题来说,我们的目标是最大化某个性能指标,比如准确度𝑎𝑐𝑐,但是把准确度当作损失函数去优化时,会发现𝜕𝑎𝑐𝑐其实是不可导的,无法利用梯度下降算法优化网络参数𝜃。一般的做法是,设立一个平滑可导的代理目标函数,比如优化模型的输出 与 One-hot 编码后的真实标签𝒚之间的距离(Distance),通过优化代理目标函数得到的模型,一般在测试性能上也能有良好的表现。因此,相对回归问题而言,分类问题的优化目标函数和评价目标函数是不一致的。模型的训练目标是通过优化损失函数L来找到最优数值解𝑾 , 𝒃 :
w, b = \underbrace{arg min}_{w, b}L(o, y)
对于分类问题的误差计算来说,更常见的是采用交叉熵(Cross Entropy)损失函数,较少采用 回归问题中介绍的均方差损失函数。我们将在后续章节介绍交叉熵损失函数,这里仍然使 用均方差损失函数来求解手写数字识别问题。
既然线性模型不可行,我们可以给线性模型嵌套一个非线性函数,即可将其转换为非线性模型。我们把这个非线性函数称为激活函数(Activation Function),用𝜎表示:
在 TensorFlow 中间,为了表达方便,一般把标量、向量、矩阵也统称为张量,不作区 分,需要根据张量的维度数或形状自行判断,本书也沿用此方式。
tf.constant([1,2.,3.3])
<tf.Tensor: id=23, shape=(3,), dtype=float32, numpy=array([1. , 2. , 3.3], dtype=float32)>
id 是 TensorFlow 中内部索引对象的编号,shape 表示张量的形状,dtype 表示张量的数 值精度,张量 numpy()方法可以返回 Numpy.array 类型的数据。