“梦想使你迷醉,距离就成了快乐;追求使你充实,失败和成功都是伴奏;当生命以美的形式证明其价值的时候,幸福是享受,痛苦也是享受。”
--------史铁生《好运设计》
🎯作者主页:追光者♂🔥
🌸个人简介:计算机专业硕士研究生💖、2022年CSDN博客之星人工智能领域TOP4🌟、阿里云社区特邀专家博主🏅、CSDN-人工智能领域新星创作者🏆、预期2023年10月份 · 准CSDN博客专家📝
【无限进步,一起追光!】
🍎欢迎大家 点赞👍 收藏⭐ 留言📝
在阅读本篇前,建议阅读:
🌿鉴于PyTorch深度学习实践系列文章,篇幅较长,有粉丝朋友反馈说不便阅读。因此这里将会分篇发布,以便于大家阅读。本次发布的是 “基础 模型&算法 回顾”章节中的
前馈(算损失)、反馈(算梯度)、更新(使用 (随机) 梯度下降算法 更新权重)!
前馈计算损失,反馈计算梯度!
用PyTorch进行深度学习模型构建的一般过程:
设计
用于计算最终结果的模型
(Design model)前馈、反馈、更新
在pytorch中,若使用mini-batch的 (小批量随机梯度下降) 风格,一次性求出一个批量的y^\widehat yy,则需要xxx以及y^\widehat yy作为矩阵参与计算【注:只要知道了 输入xxx 和 输出y^\widehat yy 各自的维度,那么www和bbb的维度就能推出来】,此时利用 numpy的广播机制,可以将原标量参数ω\omegaω 扩写为 同维度的矩阵[w][w][w],参与运算而不改变其Tensor的性质。
广播机制/扩充维度,以便可以实现矩阵相加~
www和bbb 都会 自动扩充!例如 下所示:
在下述 线性模型的计算图中,红框区域为线性单元
,其中的ω\omegaω以及bbb是需要反复训练确定的,在设计时,需要事先设计出二者的维度。
(而由于公式y^=ωx+b\widehat y = \omega x +by=ωx+b,因此,只要确定了y^\widehat yy以及xxx的维度,就可以确定上述两个量的维度大小)
例如:已知xxx 和 zzz 或(yyy)的维度,即可求www和bbb的维度.
线性模型 “计算图”:
由上述理论,由于前边的计算过程都是针对矩阵的,因此最后的losslossloss也是矩阵,但由于 要进行 反向传播 调整参数,因此losslossloss应当是个标量,因此要对矩阵[loss][loss][loss]内的每个量求和 并求 均值(MSE)。
loss=1NΣ[loss1⋮lossn]loss = \frac{1}{N}\Sigma \begin{bmatrix} {loss_1}\\ {\vdots}\\ {loss_n}\\ \end{bmatrix}loss=N1Σloss1⋮lossn
使用PyTorch实现线性回归:
准备数据集
用类封装设计一个模型。目的是 为了前向传播forward,即计算y^\widehat yy (预测值)
使用PyTorch的API 来定义 loss 和 optimizer。其中,计算loss是为了计算损失 从而反向传播 求解梯度,optimizer是为了根据得到的梯度 更新权重。
训练过程 :forward+backward+update (前馈 反馈 更新)
首先是几张图,便于理解:
无论哪种,目的都是把维度拼出来:
torch.nn.Linear 中的 部分参数 说明:
此外,为了便于理解下图中 forward()的参数,这里我已经做了说明。可回顾再往下的 Python基础语法知识~
注意:Python中,一个类实例要变成一个可调用对象,只需要实现一个特殊方法__ call __(),Module实现了函数 __ call __(),call()里面有一条语句是要调用forward()。因此新写的类中需要重写forward()覆盖掉父类中的forward()。
call()函数的作用是可以直接在对象后面加(),例如实例化的model对象 model = LinearModel()
,和实例化的linear对象 self.linear = torch.nn.Linear(1, 1),y_pred = self.linear(x)
。(这在下面的最终代码中 都将会看到)
self.linear(x)也由于函数call的实现 将会调用torch.nn.Linear类中的forward,至此完成封装,也就是说forward最终是在torch.nn.Linear类中实现的
,具体怎么实现,可以不用关心,大概就是完成y= wx + b操作。
(其实,也可以从源码中可见一斑:)
Python基础语法知识回顾(函数中 多个参数的传递,*args,**kwargs):当调用函数时,可以传入 数目 正好的值:
def func(a, b, c, x, y):passfunc(1, 2, 3, x=6, y=7)
也可以传入 多个数量的值,这样即可实现:
def func(*args, x, y): # 定义 *args,那么用户就可以传入多个参数print(args)func(1, 2, 3, 9, x=6, y=7) # 用户传入 多个参数 1 2 3 9,然后再是x、y的匹配
输出:元组的形式,(进而可以取元素进行遍历)
上述内容中,x=6,y=7 也可 在函数中其他 形式来表示:
def func(*args, **kwargs): # 定义 *args,那么用户就可以传入多个参数,以元组的形式print(args)print(kwargs) # 两个star,那么 就会把参数变成 字典的形式func(1, 2, 3, 9, x=6, y=7) # 用户传入 多个参数 1 2 3 9,然后再是x、y的匹配
输出:
以上操作,是函数里面,参数传递 常用到方式~
那么,
class Foobar:def __init__(self):pass# 让对象可调用!!def __call__(self, *args, **kwargs):print("Hello" + str(args[0]))foobar = Foobar()
foobar(1, 2, 3)
构造 损失函数和优化器: 我想,我已经写的很细了… :
老师讲到,backward之前,要把梯度清零,这是框架里面的需求,老师特意强调的(在之前 自己 手动实现的 梯度下降---->反向传播 目录 1.3内的code,是 在 反向传播之后 才把梯度清零的…)en…总之,这里记得 在框架里面的话,backward 和 梯度下降 更新权重之前,先梯度清零!
基础上面所有这些分析,本次关于 【线性回归——PyTorch实现】的练习code如下,注释我写的也比较细:
# 昵 称:XieXu
# 时 间: 2023/2/15/0015 19:10
# 2023.2.16 08:53
import torch# 1、准备数据集
# 数据作为矩阵参与Tensor计算
# x,y都是矩阵,(3行1列),即:共三个数据,每个数据 只有一个特征
x_data = torch.Tensor([[1.0], [2.0], [3.0]]) # 注意 x 和 y的值 都必须是“矩阵”
y_data = torch.Tensor([[2.0], [4.0], [6.0]])# 2、使用类来 设计模型
''' 2023.2.16 08:41
我们的模型类应该继承于 nn.Module,它是所有神经网络模块的基类。
必须实现成员方法 __init__() 和 forward()
nn.linear类包含两个成员Tensors:weight和bias。
nn.linear类已经实现了神奇的方法__call__(),这使得该类的实例可以像函数一样被调用。
通常情况下,forward()将被调用。
'''
# 将我们的模型 定义为 类!! 2023.2.15 20:18
# LinearModel类 继承于 Module类 (Module这个父类 中有许多方法,是未来 模型训练过程中 要用到的!!)
class LinearModel(torch.nn.Module):# 定义“构造函数”# 即初始化 对象的时候 默认调用的函数def __init__(self):# 调用父类的init。【这就像是模板一样,写上就行!必须写!】super(LinearModel, self).__init__() # 调用父类的initial# Linear 是一个 类,torch.nn.Linear() 加括号 就是在“构建对象”! 2023.2.15 20:28# Linear()这个 对象 包含 权重 weight(w)以及 偏置bias(b) 两个Tensor。。因此可以直接用linear 来完成 权重乘以输入 加上 偏置 的计算# (1,1)是指输入x和输出y的特征维度,这里数据集中的x和y的特征 都是1维的 2023.2.16 08:44# 该线性程 需要学习的参数 是w和b,获取w和b的方式分别是 linear.weight 和 linear.biasself.linear = torch.nn.Linear(1, 1)# 注:Linear 也是继承 自Module的,所以可以自动地进行 反向传播 ~ 2023.2.15 20:31# self.linear 是个对象,这个对象的类型 是torch下 nn这个模块 中的 Linear这个类 ~ 2023.2.15 20:33# nn为缩写,即 Neural Network 神经网络。(神经网络中的一个组件:Linear 它能够完成 权重和x相乘 算出中间值如Z Z再加上偏移量B 作为输出)# 前馈函数forward,对父类函数 overwrite(重写、覆盖)2023.2.16 08:46# 就得叫做 forward,一定要叫 这个名!!这是必须要定义的 2023.2.15 20:19# 前馈的过程中 所要实现的计算~def forward(self, x):# 调用linear中的call(),以利用父类forward()计算wx+by_pred = self.linear(x)return y_pred# 之所以 这里没有 反馈函数backward,是因为 由Module构建的对象 会自动根据“计算图”生成model = LinearModel() # 实例化模型# 3、定义loss和优化器
# 构造的criterion对象所接受的参数为(y',y)
# criterion = torch.nn.MSELoss(size_average=False) # UserWarning: size_average and reduce args will be deprecated, please use reduction='sum' instead. warnings.warn(warning.format(ret))
criterion = torch.nn.MSELoss(reduction='sum') # 由上面警告,改为这样 即可~ 2023.2.15 23:13# model.parameters()用于检查模型中所能进行优化的张量。即 model.parameters() 自动完成参数的初始化操作
# learningrate(lr)表学习率,可以统一 也可以不统一
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)# 4、训练周期:前馈、反馈、更新
for epoch in range(1000):# 前馈计算y_predy_pred = model(x_data)# 前馈计算损失lossloss = criterion(y_pred, y_data)# 打印调用loss时,会自动调用内部__str__()函数,避免产生计算图# print(epoch, loss)print(epoch, loss.item()) # 还是得以item来访问,输出loss的 取值 2023.2.16 09:01# 梯度清零optimizer.zero_grad() # 也有朋友把这称作“初始化梯度”...# 梯度反向传播,计算图清除loss.backward() # 自动反向传播,计算梯度值# 根据传播的梯度以及学习率更新参数optimizer.step() # 更新参数w和b 2023.2.16 08:51# Output 输出最终的w和b
print('w = ', model.linear.weight.item())
print('b = ', model.linear.bias.item())# TestModel 测试部分
x_test = torch.Tensor([[4.0]])
y_test = model(x_test)print('y_pred = ', y_test.data)
输出:
直接输出y_pred的值:
print('y_pred = ', y_test.item())
附:在翻看评论时发现的,便于理解吧~ (2023.2.17 23:24)
2023.2.17 23:34.
另外,可尝试 不同的优化器 在如上 的线性模型中的效果:
所用的code依然是基于上面小目录1.4.3,只是稍微改动一下,顺便加个可视化。(这里由于在调用LBFGS优化器 有些问题,因此这里就不测试了…故LBFGS相关注释可不用打开!!)
# 昵 称:XieXu
# 时 间: 2023/2/16/0016 9:57# 验证不同优化器的效果
import torch
import matplotlib.pyplot as plt
import matplotlibmatplotlib.rc("font", family='Microsoft YaHei') # 坐标系中汉字说明x_data = torch.Tensor([[1.0], [2.0], [3.0]])
y_data = torch.Tensor([[2.0], [4.0], [6.0]])epoch_list = []
Loss_list = []class LinearModel(torch.nn.Module):def __init__(self): # 构造函数super(LinearModel, self).__init__()self.linear = torch.nn.Linear(1, 1) # 构造对象,并说明输入输出的维数,第三个参数默认为true,表示用到bdef forward(self, x):y_pred = self.linear(x) # 可调用对象,计算y=wx+breturn y_predmodel = LinearModel() # 实例化模型criterion = torch.nn.MSELoss(reduction='sum')
# model.parameters()会扫描module中的所有成员,如果成员中有相应权重,那么都会将结果加到要训练的参数集合上optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # lr为学习率
# optimizer = torch.optim.Adagrad(model.parameters(), lr=0.01)
# optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
# optimizer = torch.optim.Adamax(model.parameters(), lr=0.01)
# optimizer = torch.optim.ASGD(model.parameters(), lr=0.01)
# optimizer = torch.optim.LBFGS(model.parameters(), lr=0.01) # TypeError: step() missing 1 required positional argument: 'closure'
# optimizer = torch.optim.RMSprop(model.parameters(), lr=0.01)
# optimizer = torch.optim.Rprop(model.parameters(), lr=0.01)# def closure(): # 这里写的有问题,所以 暂时不测试 LBFGS优化器了!!
# y_pred = model(x_data)
# loss = criterion(y_pred, y_data)
# loss.backward()
# return loss
# optimizer.step(closure=closure)for epoch in range(1000):y_pred = model(x_data)loss = criterion(y_pred, y_data)print(epoch, loss.item())optimizer.zero_grad()loss.backward()optimizer.step() # 使用LBFGS优化器时会报错:TypeError: step() missing 1 required positional argument: 'closure'# optimizer.step(closure=closure) # LBFGS优化器时报错,暂时不测试该优化器了...# optimizer.zero_grad() # 梯度清零放到这里也行。。2023.2.16 16:08epoch_list.append(epoch)Loss_list.append(loss.item())print('w=', model.linear.weight.item())
print('b=', model.linear.bias.item())x_test = torch.Tensor([[4.0]])
y_test = model(x_test)
print('y_pred = ', y_test.data)
# print('y_pred = ', y_test.item())plt.plot(epoch_list,Loss_list)plt.xlabel("Epoch")
plt.ylabel("Loss")plt.title("所用优化器:SGD")
# plt.title("所用优化器:Adagrad")
# plt.title("所用优化器:Adam")
# plt.title("所用优化器:Adamax")
# plt.title("所用优化器:ASGD")
# plt.title("所用优化器:LBFGS") # TypeError: step() missing 1 required positional argument: 'closure'...LBFGS优化器时报错,暂时不测试该优化器了..
# plt.title("所用优化器:RMSprop")
# plt.title("所用优化器:Rprop")plt.show()
为了便于查看,我将轮数设置为100轮,上面的1000后面可自行修改~
此外,为了关于观察,我将所有图形都分别截取,然后放到如下一张图里面了。大家在实验时可以 自行打开注释,分别测试几个优化器即可~
可以看出,Adagrad优化器的损失是比较大的,其它几个优化器也各有千秋,有损失收敛比较快的,也由曲线形式收敛的~本例中,老师最初选取的是SGD–小批量随机梯度下降 优化器。
(训练1000轮,效果是比较好的,近乎准确.)
附:torch.nn.Linear的pytorch官方文档,可以看看官方的描述,进去后 甚至可以按照torch的版本 来选文档来看。
关于这部分内容,可参阅:2023年春《移动计算技术》:基于Python的【道路交通流仿真】示例:直行道路/环形路口/十字交叉路口(含红绿灯) 交通流仿真。此外,在近期的其它部分Blog中也部分涉及到该仿真模拟,具体请参阅其它Blog。
这里仅展示核心部分吧,完整地较长,可以在资源里面下载。
test_2.py
# Run后,也可以大致演示交通流量预测的状况。2023.2.28 19:28from trafficSimulator import *# Create simulation
sim = Simulation()# Add multiple roads
sim.create_roads([((0, 100), (148, 100)),((148, 100), (300, 100)),((150, 0), (150, 98)),((150, 98), (150, 200)),
])sim.create_gen({'vehicle_rate': 20,'vehicles': [[1, {"path": [0, 1]}],[1, {"path": [0, 3]}],[1, {"path": [2, 3]}],[1, {"path": [2, 3]}]]
})sim.create_signal([[0], [2]])# Start simulation
win = Window(sim)
win.offset = (-150, -110)
win.run(steps_per_update=5)
test_2即可执行文件。
即若路况为曲线时,执行的生成函数:
def curve_points(start, end, control, resolution=5):# If curve is a straight lineif (start[0] - end[0])*(start[1] - end[1]) == 0:return [start, end]# If not return a curvepath = []for i in range(resolution+1):t = i/resolutionx = (1-t)**2 * start[0] + 2*(1-t)*t * control[0] + t**2 *end[0]y = (1-t)**2 * start[1] + 2*(1-t)*t * control[1] + t**2 *end[1]path.append((x, y))return pathdef curve_road(start, end, control, resolution=15):points = curve_points(start, end, control, resolution=resolution)return [(points[i-1], points[i]) for i in range(1, len(points))]TURN_LEFT = 0
TURN_RIGHT = 1
def turn_road(start, end, turn_direction, resolution=15):# Get control pointx = min(start[0], end[0])y = min(start[1], end[1])if turn_direction == TURN_LEFT:control = (x - y + start[1],y - x + end[0])else:control = (x - y + end[1],y - x + start[0])return curve_road(start, end, control, resolution=resolution)
from scipy.spatial import distance
from collections import dequeclass Road:def __init__(self, start, end):self.start = startself.end = endself.vehicles = deque()self.init_properties()def init_properties(self):self.length = distance.euclidean(self.start, self.end)self.angle_sin = (self.end[1]-self.start[1]) / self.lengthself.angle_cos = (self.end[0]-self.start[0]) / self.length# self.angle = np.arctan2(self.end[1]-self.start[1], self.end[0]-self.start[0])self.has_traffic_signal = Falsedef set_traffic_signal(self, signal, group):self.traffic_signal = signalself.traffic_signal_group = groupself.has_traffic_signal = True@propertydef traffic_signal_state(self):if self.has_traffic_signal:i = self.traffic_signal_groupreturn self.traffic_signal.current_cycle[i]return Truedef update(self, dt):n = len(self.vehicles)if n > 0:# Update first vehicleself.vehicles[0].update(None, dt)# Update other vehiclesfor i in range(1, n):lead = self.vehicles[i-1]self.vehicles[i].update(lead, dt)# Check for traffic signalif self.traffic_signal_state:# If traffic signal is green or doesn't exist# Then let vehicles passself.vehicles[0].unstop()for vehicle in self.vehicles:vehicle.unslow()else:# If traffic signal is redif self.vehicles[0].x >= self.length - self.traffic_signal.slow_distance:# Slow vehicles in slowing zoneself.vehicles[0].slow(self.traffic_signal.slow_factor*self.vehicles[0]._v_max)if self.vehicles[0].x >= self.length - self.traffic_signal.stop_distance and\self.vehicles[0].x <= self.length - self.traffic_signal.stop_distance / 2:# Stop vehicles in the stop zoneself.vehicles[0].stop()
🍒 热门专栏推荐:
持续创作优质好文ing…✍✍✍
记得一键三连哦!!!
求关注!求点赞!求个收藏啦!