Py学习  »  机器学习算法

带你一文透彻学习【PyTorch深度学习实践】分篇——线性模型 & 梯度下降

CSDN学习 • 1 年前 • 122 次点击  


本篇文章来源于专栏《Python从入门到人工智能》,更多内容可复制链接到此查看:https://blog.csdn.net/qq_44731019/category_11717408.html

对于一个算法(模型)。在深度学习中,简要的处理方式是:

准备数据集(Datasets)—>> Model(选择模型) —>> Training (模型训练) —>> 推理(进行推理预测)。

至于优化等,可以理解为后续的补充。

监督学习:数据集需要 交付给算法模型 进行训练,利用所训练的模型,在获得 新的数据时 可以得到相应的输出。

线性模型的基本模型如下,其中的ω和  是模型中的参数,训练模型的过程即为确定模型中参数的过程:

   
在本模型中设置成      对于不同的  有不同的线性模型及图像与之对应。

在模型训练中 会先随机取得一个值,继而 计算其和标准量之间的 偏移量,从而判断 当前模型 是否符合预期。

记实际值为  ,模型对应的预测值为  ,则其中的偏移量为  ,以此来代表 模型估计值 对原值的误差。

通常,该公式定义为Training Loss (Error)

  

本例中,原题目中的几种  所对应的Loss如下 几个图所示:

其中的每行 为   不同时 的 单个样本的损失,最后一行为 平均损失。

对于单个样本,loss 可用于 指代样本误差。对于所有样本,可同理用Mean Square Error (MSE)来指代 整体样本的平均平方误差(均方差cost)

MSE:均方误差(Mean Square Error)。,即 每个样本对应的损失值 (平方) 求和,再除以样本总个数。

  

  :

  :

  :


由cost的计算公式可知,当平均损失为0时,模型最佳,但由于 仅当数据无噪声 且 模型完美贴合数据 的情况下才会出现这种情况,因此 模型训练的目的 应当是 误差(损失)尽可能小,而非找到 误差为0的情况。

不同  得到的 MSE:

1.1.1 基础练习  

根据上面的分析,code如下:注释我已经写的比较清楚啦。

# 昵 称:XieXu# 时 间: 2023/2/12/0012 21:10
# 导入必要的工具包import numpy as npimport matplotlib.pyplot as plt
# 自定义 简单数据集。x与y 一一对应 (训练集)x_data = [1.0, 2.0, 3.0]y_data = [2.0, 4.0, 6.0]

# 模型,(前馈)def forward(x): return x * w

# 损失函数def loss(x, y): y_pred = forward(x) # 即 y_hat。相当于 预测的值 return (y_pred - y) * (y_pred - y) # 平方(均方误差)。简单形式的均方误差。这个计算的 为单个样本的误差
# 【穷举法】# 如下两个列表 保存 权重 及其对应的损失值w_list = []mse_list = []
# [0.0,4.1),间隔为0.1。即 0.0, 0.1, 0.2, 0.3 ... 4.0 起始值(含),停止值(不含),步长for w in np.arange(0.0, 4.1, 0.1): # 外层循环 控制权重 2023.2.13 08:20 print("w=", w) l_sum = 0 # zip 将x_data 和 y_data 打包为一个tuple(元组),方便同时遍历。 for x_val, y_val in zip(x_data, y_data): # 内层循环控制进行 权重:调用forward函数 对应的预测,以及 调用上面定义的loss函数 进行损失值计算 y_pred_val = forward(x_val) # 计算 每个样本的 预测值 loss_val = loss(x_val, y_val) # 计算 每个样本的 损失值 l_sum += loss_val # 将 所有样本的 损失求和(这里没做均值) print("\t", x_val, y_val, y_pred_val, loss_val) # 打印出 每一个样本:x,x对应的y,预测值y_hat,损失值 print("MSE=", l_sum / 3) # 这里 除以样本总数,进行均值,即 均方误差 w_list.append(w) # 将每次 用完的 权重添加到列表中,用以 下面画图 的横坐标~ mse_list.append(l_sum / 3) # 除以样本总数。每个权重 对应的 所有样本的平均误差(均方误差) MSE,也叫均值吧。下面绘图的纵坐标
# 绘制图形plt.plot(w_list, mse_list) # 横坐标、纵坐标 的取值plt.ylabel("Loss") # 纵坐标y的标签plt.xlabel("w") # 横坐标w的标签plt.show()

可以得到结果:红色为我做的标注~

从下面 控制台打印的日志中,我们 可以 很容易看出来上图 的由来:

开始时,随着W增加,平均损失MSE逐渐减小,


到W为2.0时,MSE达到最小 即0,

后面   继续增加,MSE又变大了:


1.1.2 练习  

同样地,基于上面的例子,练习:

实现线性模型  并输出loss的3D图像。

不同之处在于,定义的模型,与上面相比,加了个 偏置项B。

# 昵 称:XieXu# 时 间: 2023/2/13/0012 12:26
# 练习题# 作业题目:实现线性模型(y=wx+b)并画出loss的3D图像。import numpy as npimport matplotlib.pyplot as pltfrom mpl_toolkits.mplot3d import Axes3D
# 以下两行代码 解决坐标轴不能显示中文问题 否则会报类似错误:FigureCanvasAgg.draw(self) (图上的 中文无法正常显示)from pylab import *
mpl.rcParams['font.sans-serif'] = ['SimHei']
# 输入数据,设函数为y=3x+2x_data = [1.0, 2.0, 3.0]y_data = [5.0, 8.0, 11.0]
# 定义模型def forward(x): return x * w + b
# 定义损失函数def loss(x, y): y_pred = forward(x) return (y_pred - y) * (y_pred - y)

mse_list = [] # 保存mse,均方误差W = np.arange(0.0, 4.1, 0.1)B = np.arange(0.0, 4.1, 0.1)[w, b] = np.meshgrid(W, B) # 通过 两个坐标轴上的点 在平面上画网格
l_sum = 0for x_val, y_val in zip(x_data, y_data): y_pred_val = forward(x_val) print(y_pred_val) loss_val = loss(x_val, y_val) l_sum += loss_val
fig = plt.figure()ax = Axes3D(fig)ax.plot_surface(w, b, l_sum / 3)# 以下三行 设置坐标的文字说明ax.set_xlabel("权重W")ax.set_ylabel("偏置项 B")ax.set_zlabel("损失值")plt.show() # 画图

可以得到如下图形:这就是 (   ) 损失值的3D图形。

其中,控制台输出的日志中,含有警告:


定位到源代码中:

ax = Axes3D(fig)

翻译一下上面的警告:大意即 由于版本不同,可暂时忽略。

1.1.3 其它:在深度学习训练中,横轴往往是Epoch

在深度学习训练的可视化图形中,一般横轴是Epoch,即训练轮数:往往在训练集上表现是,随着训练轮数增多,损失越来越低;而在测试集(老师读 开发集)上的效果是,损失在刚开始会下降,而后到某个点,又会逐渐上升。而我们的目标就是,想找到损失最低的那个点;从而进一步 对超参数进行处理等其它操作。

深度学习中可能需要考虑更多问题,比如 可视化问题 / 训练的时间问题(可能需要几天,甚至连续几周都是有可能的) / 断点问题(比如训练7天会结束,但是第6天程序崩溃了,这期间产生的数据 结果 怎么办)

老师提到了 Pytorch可视化工具——Visdom。目前我没有用到…稍微大型的项目可能会用到吧。可以在百度搜索Visdom并查看它的相关信息,以及其GitHub官网。

💧1.2 Gradient Descent(梯度下降)

1.2.1 梯度下降法 的由来:问题背景

在上面 线性模型的方法中,所使用的思想是基于穷举,即提前已经设定好 参数的准确值 在某个区间内并以某个步长进行穷举,如(np.arange(0.0,4.1,0.1))。

但是,这种思想 在多维的情况下,即多个参数的时候,会引起维度诅咒的现象,在一个N维曲面中找一个最低点,容易 使得原问题变得不可解。那么 既然有这样的问题,就需要对算法 进行改进。

那么,使用 分治法 如何?

即:大化小,小化无,先对整体 进行分割采样,在相对最低点进行进一步采样,直到其步长与误差符合条件。

但是,分治法有两个缺点:

  • 容易只找到局部最优解,而不易找到一个全局最优解。

  • 如果需要分得更加细致,则计算量仍然巨大。

同时,由于以上问题的存在,引起了参数优化的问题,即求解使loss最小时的参数的值。

简言之,即求得  ,使得loss值最小。

  

如何优化,求得符合条件的  呢?

1.2.2 何为梯度?

梯度,即 导数变化最大的值,其方向导数变化最大的方向

这里,可以使用高等数学中关于 一个点处 导数的定义:

  

(这里仅简单理解,并非严谨数学推导)对于  ,若增函数,梯度为上升方向,若减函数,梯度为下降方向。由数学知识 知道,需要取梯度下降的方向 即梯度的反方向作为变化方向,才能尽可能地求得极小值(最小值)。

下面的图将便于理解:

1.2.3 梯度下降

在深度学习中,所说的凸函数,是与高等数学定义中的 凸函数,完全反过来的,知道这一点就好。如下图所示,在深度学习中,将其看做一个凸函数。

当前,  的取值点 与 全局最小值点:

那么,取值点 需要 向下更新,所取的梯度即为  ,更新的公式如下,其中   学习率,即 所下降的步长,不宜取太大。

  

  按梯度下降方向进行更新:

局限性

  • [1] 梯度下降算法 容易进入 局部最优解(非凸函数),但是

    实际问题中的局部最优点较少

    ,或 已经

    基本可以当成全局最优点。

  • [2] 梯度下降算法容易陷入鞍点(鞍点处,梯度值为0)(总之不是取得 损失最小值的点,简单理解为 局部最优:在优化问题中,鞍点 是一种特殊的局部最优解,是一个难以优化的点,因为优化算法 可能 很难从鞍点 附近找到 全局最优解)。(陷入到鞍点,导致无法继续 进行迭代!)

1.2.4 梯度下降的公式 如何得来的?(理解)

通过线性模型,我们知道 均方误差的公式即:其中  

  

进一步地,可以对  求偏导,一步一步来:

  

  

  是常数,对求  的导数没有影响,可以提到前面来:

  

根据数学知识,由于是对  求导,那么 可以 把(  )看做一个整体,【复合函数求导】:对含有  的 (外部)整体 求导,乘以内部 对  求导,故而得:

  

而(  )对  求导的结果 显然为  ,调整一下顺序,就得到对  的最终求导结果:

  

因此,梯度下降的更新公式为:

  

下图即上述公式的推导过程:

梯度的求解公式,应用到code中的示例:

1.2.5 随机梯度下降(Stochastic gradient descent,SGD);Mini-batch!

平时用的比较多的,是随机梯度下降(SGD)

SGD采用单个训练样本的损失来近似平均损失,故 SGD 用单个训练数据即可对模型参数进行一次更新,大大加快了训练速度。

随机梯度下降 每次 只需要计算 一个样本关于  的导数


随机梯度下降SGD 与 标准梯度下降的区别:

  • 标准梯度下降 在权值更新前 汇总所有样例得到的标准梯度,随机梯度下降 则是通过考察每次训练实例来更新。

  • 标准梯度下降的是使用准确的梯度,理直气壮地走,随机梯度下降使用的是近似的梯度,小心翼翼地走。

  • 标准梯度下降的步长 比 随机梯度下降 的大。

  • 当有多个局部极小值时,随机梯度 反而更可能 避免 进入局部极小值中。

同时,为了降低随机梯度的方差,使迭代算法更加稳定,在真实操作中,会同时处理若干训练数据,该方法叫做小批量随机梯度下降法(Mini_Batch Gradient Densent)。,这才是真正地运用了 随机梯度下降(SGD),目前,在实际应用中,我们所说的梯度下降,(batch)都是指的 Mini-batch SGD,小批量 随机梯度下降。 这在神经网络中,尤其明显!!!使用及其广泛!!!(既 保证 性能,又保证时间复杂度不是特别高)

小结

普通梯度下降算法利用数据整体,不容易避免鞍点,算法性能欠佳但算法效率高随机梯度下降需要利用每个的单个数据,虽然算法性能良好,但计算过程 环环相扣 无法将样本抽离开 并行运算,因此算法效率低,时间复杂度高

综上所述,可采取一种折中的方法,即批量梯度下降方法。

将若干个样本分为一组,记录一组的梯度 用以代替随机梯度下降中的单个样本。

该方法最为常用,也是默认接口。一般,mini-batch可以在2的幂次中挑选最优取值。例如16、32、64、128、256等。

1.2.6 梯度下降练习:方法一(推荐 完全 自己 多手写几遍)

给定一个数据集,x_data、y_data。寻找y=wx模型的w最优解。

code练习如下,注释中,我已经介绍的比较详细啦!我想这可以帮助绝大多数朋友理解。

# 昵 称:XieXu# 时 间: 2023/2/13/0013 13:26
# 梯度下降算法
# 注:在 深度学习算法 中,并没有过多的局部最优点。# 即 一般 通过梯度下降 就可以求得 最优点
import numpy as npimport matplotlib.pyplot as plt
# 训练数据,x与y一一对应x_data = [1.0, 2.0, 3.0]y_data = [2.0, 4.0, 6.0]
epoch_list = [] # # 2023.2.13 13:58 保存训练轮数cost_list = [] # 2023.2.13 13:58 每一轮对应的损失值
w = 1.0 # 初始化,我们指定一个W取值

# 前馈计算(定义模型)def forward(x): return x * w

# 求MSE (定义cost函数,计算均方误差)def cost(xs, ys): cost = 0 for x, y in zip(xs, ys): y_pred = forward(x) cost += (y_pred - y) ** 2 return cost / len(xs) # 计算 均方误差(损失)

# 求梯度(求W的偏导数)def gradient(xs, ys): grad = 0 for x, y in zip(xs, ys): temp = forward(x) grad += 2 * x * (temp - y) # 由导数公式得 return grad / len(xs)

print("Predict(before training)", 4, forward(4)) # 这是根据 我们 给定的那个W,计算y=wx得到的值,forward(4) 即4
# 开始训练for epoch in range(1, 101, 1): cost_val = cost(x_data, y_data) # 计算均方误差(损失) grad_val = gradient(x_data, y_data) # 求梯度(W的偏导数) w -= 0.01 * grad_val # 梯度下降,更新梯度(W) print("Epoch: ", epoch, "w = ", w, "loss = ", cost_val) # w就会用在下一轮的训练中
epoch_list.append(epoch) # 保存轮数1~100,做绘图的横坐标 cost_list.append(cost(x_data, y_data)) # 每轮对应的 损失值
print("Predict(after training)", 4, forward(4)) # 根据100轮 之后的权重W,计算x取4时,预测的y值。实际应该是无限接近2,即2
# 绘图plt.plot(epoch_list, cost_list) # 横坐标 和 纵坐标 分别是 轮数 和 对应该轮的 损失值plt.title("train loss")plt.xlabel('Epoch')plt.ylabel("Cost")plt.show()

得到结果:可以看到,在20轮左右,损失值就已经很接近0了。


控制台的结果:为了便于展示,这里我也特意截取到了20轮的训练结果。

1.2.7 方法二(与法一类似,不过这里 纵轴 是 权值的收敛变化

第二种方式:与第一种类似,只不过这里纵坐标取的是 权值,另外,直接把梯度下降 放到Epoch训练里面了。代码如下:

# 方法二import numpy as npimport matplotlib.pyplot as plt
x_data = [1.0, 2.0, 3.0]y_data = [2.0, 4.0, 6.0]
scope_list = [] # 轮数w_list = [] # 权值W
w = 60 # 我们给W一个初始值
# 学习率k = 0.01 # 定义学习率
# 开始训练for i in range(1,201,1): # 计算cost(loss的和) loss_sum = 0
# 求梯度 for x_val, y_val in zip(x_data, y_data): loss_sum += 2 * x_val * (w * x_val - y_val) cost = loss_sum / 3
# 计算本轮w w = w - k * cost print("Epoch:",i,"W:",w) scope_list.append(i) w_list.append(w)
# 按说纵坐标不应该取权值的,既然取了,那么可以从图形中,看出来大概 50左右,权值就收敛了plt.plot(scope_list, w_list) # 横坐标 取值 依然是 轮数,纵坐标取值是按照W来取的值plt.xlabel("scope")plt.ylabel("W")plt.show()


结果如下所示:

我们也可以看一下,Console控制台输出的内容:可以看到,50多轮依然还在收敛,100轮左右就已经收敛的比较好了~ 最终权值应为2

1.2.8 随机梯度下降练习(一),可视化

这里,即普通的随机梯度下降,每轮训练中,每次计算的 关于  的 偏导数,是仅仅只计算 一个样本的,而非 所有样本的关于  的偏导数 求和 再取均值。

 即 注:这里,随机梯度主要是指,每次拿一个训练数据来训练,然后更新梯度参数。

# 昵 称:XieXu# 时 间: 2023/2/13/0013 15:11# 随机梯度下降
# 方法一# 注:这里,随机梯度主要是指,每次拿一个训练数据来训练,然后更新梯度参数。# 本算法中 梯度总共更新100(epoch)x3 = 300次。梯度下降算法中 梯度总共更新100(epoch)次。import matplotlib.pyplot as plt
x_data = [1.0, 2.0, 3.0]y_data = [2.0, 4.0, 6.0]
w = 1.0

def forward(x): return x * w

# calculate loss function(损失函数--均方误差)def loss(x, y): y_pred = forward(x) return (y_pred - y) ** 2 # 仅 计算一个样本 的损失函数

# define the gradient function sgd(定义随机梯度下降 函数)def gradient(x, y): return 2 * x * (x * w - y) # 注:这里,仅计算 “一个样本” 关于W 的 偏导数

epoch_list = [] # 训练轮数,100轮 2023.2.13 19:24loss_list = []print('predict (before training)', 4, forward(4))for epoch in range(1, 101, 1): for x, y in zip(x_data, y_data): grad = gradient(x, y) # 这里,每轮训练中,三个 样本中 都分别计算了 梯度,for...zip循环。应该是没有用到“随机” w = w - 0.01 * grad # update weight by every grad of sample of training set。根据梯度,更新W。。。 print("\tgrad:", x, y, grad) # 打印每轮训练中,每个样本的x、y、根据其计算的梯度。2023.2.13 19:36 l = loss(x, y) # 根据梯度得到的 更新后的W,进一步 计算损失函数(均方误差) 2023.2.13 19:36 print("progress:", epoch, "w=", w, "loss=", l) # 打印该轮中,通过每一个样本 得到的 W 以及 损失函数值!!!2023.2.13 19:37 epoch_list.append(epoch) loss_list.append(l)
print('predict (after training)', 4, forward(4))plt.plot(epoch_list, loss_list)plt.title("train loss")plt.ylabel('loss')plt.xlabel('epoch')plt.show()


以此类推:

得到的损失函数值,关于 训练轮数的图像如下:

1.2.9 随机梯度下降练习(二),可视化

上面的随机梯度下降算法中,貌似仅仅 是只计算了 每一个样本的梯度,好像没有体现“随机”。

这里再换个类似的算法,基本一样,但用到了random随机。

该方法与目录1.2.6类似。

# 方法二import randomimport numpy as npimport matplotlib.pyplot as plt
x_data = [1.0, 2.0, 3.0]y_data = [2.0, 4.0, 6.0]
scope_list = [] # 训练轮数w_list = [] # 权值
w = 60
# 学习率k = 0.01
for i in range(1, 201, 1): # 计算cost(即随机一个loss当cost用) rand = random.randint(0, 2) # 取值为[0,2],即 随机生成0~2内的 某个 整数,包含0和2。(三个样本,随机取一个) cost = 2 * x_data[rand] * (w * x_data[rand] - y_data[rand]) # 从而计算得到 关于W的偏导数
# 计算本轮w w = w - k * cost # 更新W print("Epoch=", i, "W=", w) # 本轮更新后的W 2023.2.13 20:30 scope_list.append(i) w_list.append(w)
plt.plot(scope_list, w_list) # 横坐标为轮数,纵坐标为权值。plt.xlabel("scope")plt.ylabel("W")plt.show()

如下图所示,可以看出,75轮左右,权值就已经逐渐收敛了~

以上内容来自《Python从入门到人工智能》,该专栏有CSDN优质博主精心打造,主打Python和人工智能,若您觉得文章写的不错,可以扫码购买该付费专栏哦,作者会不定时更新博文,且您有相关技术疑问也可直接咨询博主。

扫码直接购买Python从入门到人工智能》




好啦,今天的内容分享就到这,感觉不错的同学记得分享点赞哦!





    



    
点分享
点收藏
点点赞
点在看

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/153483
 
122 次点击