【深度强化学习】关于同一设备上cuda和gpu计算结果不一致问题

文章目录

  • 问题描述
    • 关于seed: 跟原文一致
    • 补充:万能seed
  • 问题结论
  • cpu和gpu差异来源分析
    • 浮点数精度的差异
    • 补充报错:Expected all tensors to be on the same device!
    • 常见运算上的差异
      • 累加运算的差异
      • exp运算的差异
      • matmul运算的差异
    • forward上的差异(激活函数的运算差异)
      • 补充: .to(device)在普通张量和类上的差异:
    • backward上的差异
  • 总结或建议
  • 补充:写在后面


打假:基于pytorch的代码在GPU和CPU上训练时,训练输出结果不同问题
这里他说的话是没错的,但结论是错的,训练结果还是会不一致的。

问题描述

发现控制随机种子和所有超参数后,以及随机数都让cpu来产生,但实际运行后还是发现,同样的代码,结果还是不一致的问题。

代码:ppo代码(原代码为cuda版本)
动手学强化学习 github代码
只将device修改为如下,就为cpu版本

device = torch.device("cpu")    

此时上一版为cpu版本。

为了保持在环境采样中选择的一致性,在采样时还是选择了cpu来采样,只有训练模型时利用gpu来训练。此时为cpu+gpu版本。
结果每轮eposide的return如下:第一行为cpu+gpu版本。第二行cpu版本。
在这里插入图片描述
在第56列数据时出现了数据不一样的结果。

当然这里也验证了cuda版本的情况:
在第一个return数据就和cpu版本的不一致了。
(action第二个就不一致了,原因:第1,选择action时,使用forward了(即:actor(state)),利用cpu的forward 和利用cuda的forward,在结果上有细小差别(后面证明)。第2,后续更新还是利用了forward,加大了区别。)

关于seed: 跟原文一致

不考虑在不同设备上复现的话,只考虑在同一设备上复现的话:
在此代码:只需设置

torch.manual_seed(0) 
以及环境的随机种子env.reset(seed=0)

因为使用的是to.(device)代码,所以都是从cpu上随机产生随机数字,然后再移到其他设备,所以这里不需要设置cuda生成随机数相同。
举例:

import os
import torch
import torch.nn as nn
import torch.optim as optim
torch.manual_seed(0)# 定义一个简单的模型
class SimpleModel(nn.Module):def __init__(self):super(SimpleModel, self).__init__()self.linear = nn.Linear(1, 1)def forward(self, x):return self.linear(x)# 创建模型和优化器
model = SimpleModel().to("cuda") #加不加to("cuda")结果一样# 输出模型参数
print("Model weights:", model.linear.weight.item())
### Model weights: -0.007486820220947266

补充:万能seed

使用在cuda上生成的随机数的情况:
需设置:

torch.cuda.manual_seed(seed) 

在这里插入图片描述
若是有多个gpu:
则需设置

torch.cuda.manual_seed_all(seed)

由于每个gpu的性能和硬件不同而导致的选择神经网络加速算法不同
cudnn:CUDA Deep Neural Network library(gpu加速库)
还需设置:

    torch.backends.cudnn.deterministic = Truetorch.backends.cudnn.benchmark = False torch.backends.cudnn.enabled = False

意思是不适用cudnn加速库,使用确定性算法,且不选择速度最快的一次。
默认为:
在这里插入图片描述
也就是说,大多数情况下,第二个基准算法为了追求快,不追求同一设备每次结果一致的话可以设置为True,当然默认我们想要同一设备上结果一致,默认为False。

如果有import random
还需设置

random.seed(seed)

另外的配置:

哈希种子(没看出加了和没加有什么区别)
另外这个值在环境中默认是没有的。

os.environ['PYTHONHASHSEED'] = str(seed)

猜测:
这里是为Python脚本配置一个确定的哈希值。
就像:给一个值一个哈希数一样

# 随机一个hash值
print(hash('hello')) #-1149467266229358681

另外的两个配置,详细见:最靠谱的pytorch训练可复现性设置方案
万能seed:

def set_seed(seed):# seed init.random.seed(seed)np.random.seed(seed)os.environ['PYTHONHASHSEED'] = str(seed)# torch seed init.torch.manual_seed(seed)torch.cuda.manual_seed(seed)torch.cuda.manual_seed_all(seed)torch.backends.cudnn.deterministic = Truetorch.backends.cudnn.benchmark = Falsetorch.backends.cudnn.enabled = False # train speed is slower after enabling this opts.# https://pytorch.org/docs/stable/generated/torch.use_deterministic_algorithms.htmlos.environ['CUBLAS_WORKSPACE_CONFIG'] = ':16:8'# avoiding nondeterministic algorithms (see https://pytorch.org/docs/stable/notes/randomness.html)torch.use_deterministic_algorithms(True)set_seed(11)

还要加上环境里的env 的seed。env.reset(seed =0 ) #gym0.26.2版本后

问题结论

参考:cuda官方文档

根据NVIDIA的文档,CPU和GPU计算结果不一致的原因主要与浮点数的表示和运算方式有关。总结如下:

1、浮点数表示差异:CPU和GPU使用不同的浮点数表示标准,如IEEE
754。这些标准在处理浮点数时可能会有细微的差异,尤其是在处理极端情况或边界值时。

2、舍入误差:在浮点数运算中,由于精度限制,舍入误差是不可避免的。CPU和GPU可能在舍入策略上有所不同,导致结果的微小差异。

3、运算精度:CPU和GPU可能在支持的运算精度上有所不同。例如,某些GPU可能更倾向于使用单精度浮点数,而CPU可能更倾向于使用双精度浮点数。这种差异可能导致计算结果的不同。

4、硬件架构差异:CPU和GPU的硬件架构差异可能导致它们在执行相同计算任务时的性能和准确性有所不同。GPU通常设计为并行处理大量数据,而CPU则更侧重于通用计算。

5、优化和近似算法:在某些情况下,GPU可能会使用特定的优化或近似算法来提高性能,这可能会影响计算结果的准确性。

6、软件和驱动程序差异:CPU和GPU的软件栈和驱动程序也可能导致计算结果的差异。不同的编译器优化、库函数实现等都可能影响最终的计算结果。

说人话,就是这种计算结果不一致基本避免不了。

我单步调试后确实如此,两者几乎采取的随机数都是一样的

在我的两个例子中:打印return的结果:
在这里插入图片描述
发现前面55个计算结果都一样,而后面的数开始不一样了。

cpu和gpu差异来源分析

浮点数精度的差异

先普及一下浮点数含义:

参考:
float的精度和取值范围

import numpy as np
print(0.1 * 6)
print(6 / 10 )
a = 0.1 * 6
print(np.array([a])[0]) #默认是float64
print(np.array([a], dtype=np.float32)[0])  # 32位浮点数可以表示小数点后7位
print('{}'.format(np.array([a], dtype=np.float32)[0]))  # 32位浮点数可以表示小数点后7位
print(np.array([a], dtype=np.float64)[0])   # 64位浮点数可以表示小数点后16位
print(np.array([a], dtype=np.float32)[0]+0.0000000000000001)   
print(np.array([a], dtype=np.float64)[0]+0.0000001)
print('{:.8f}'.format(1e-8))
print(0.6 - np.array([a], dtype=np.float32)[0])
'''
0.6000000000000001
0.6
0.6000000000000001
0.6
0.6000000238418579
0.6000000000000001
0.600000023841858
0.6000001
0.00000001
-2.384185793236071e-08
'''
print(0.1 *6 )   #小数后有16位,精确到15位
print(0.1 * 0.1)   #小数后有18位,精确到17位
print(0.1 * 3)  #小数后有17位,精确到16位
## 所以 float32 可以精确到小数点后7位,float64可以精确到小数点后15-17 位
'''
0.6000000000000001
0.010000000000000002
0.30000000000000004'''

结论:float32 可以精确到小数点后7位,
float64可以精确到小数点后15-17 位。

上述cuda官方上有说,cpu可能是可以精确到float80位,然后截断到64位,所以可能会运算的比cuda更加精确。
但实际上:
在这里插入图片描述
两者的在同一浮点精度下,得到的数是相等的。

为了避免这个偏差,我这里设置为相同的精度运算方法float32,且每一个数都由cpu产生。(默认cpu产生,即去掉了device=的操作)

补充报错:Expected all tensors to be on the same device!

注意:张量的操作都只能同时在同一设备上进行,否则报错。

这个操作往往很难发现:
比如:下面这种情况却不会报错。因为PyTorch 在执行这种操作时会自动处理设备不匹配。它会将所有的操作数移动到同一设备上,然后执行操作。
在这里插入图片描述

而我如果进行大量的操作时:就会出来,所以还是要分开来运算。

在这里插入图片描述
为了避免出现因为pytorch自动的处理,和为了累计误差,我下面都会尽量使用大量的操作。

常见运算上的差异

这里看下常见的加法,幂运算exp,矩阵乘法matmul运算上的差异

import torch
import gym
torch.manual_seed(0)
env_name = 'CartPole-v1' #动作是
env = gym.make(env_name)
s = env.reset(seed=0)
# print(s)
print('原始值:',s[0]) #tensor
print('原始值的为float32,小数后7位为精确值,第8位开始乱数字,例:第一个值为{}'.format(s[0][0])) #numpy.float32
s_cpu = torch.tensor(s[0], dtype=torch.float32).to('cpu')
s_cuda = torch.tensor(s[0], dtype=torch.float32).to('cuda')for i in range(10000): #1000时没有nn =torch.randn(4, dtype=torch.float32)s_cpu +=  torch.exp(nn.to('cpu'))s_cuda += torch.exp(nn.to('cuda'))
s_cuda_cpu = s_cuda.to('cpu')
print(s_cpu)
print(s_cuda)
print(s_cuda_cpu)
print("torch.exp累加误差:",torch.max(torch.abs(s_cuda_cpu - s_cpu)).item())print('原始值:',s[0]) #tensor
s_cpu = torch.tensor(s[0], dtype=torch.float32).to('cpu')
s_cuda = torch.tensor(s[0], dtype=torch.float32).to('cuda')
for i in range(10000): #nn =torch.randn(4, dtype=torch.float32)s_cpu +=  nn.to('cpu')s_cuda += nn.to('cuda')
s_cuda_cpu = s_cuda.to('cpu') #将CUDA结果移回CPUprint(s_cpu)
print(s_cuda)
print(s_cuda_cpu)
print("随机数累加误差:",torch.max(torch.abs(s_cuda_cpu - s_cpu)).item())
#print(torch.max(torch.abs(s_cuda_cpu - s_cuda).item())) #报错: cuda和cpu的tensor不能同时运算print('原始值:',s[0]) #tensor
s_cpu = torch.tensor(s[0], dtype=torch.float32).to('cpu')
s_cuda = torch.tensor(s[0], dtype=torch.float32).to('cuda')
for i in range(2): #1000时没有nn =torch.randn(4, dtype=torch.float32)s_cpu +=  torch.matmul(nn.to('cpu'),nn.to('cpu').t())s_cuda += torch.matmul(nn.to('cuda'),nn.to('cuda').t())
s_cuda_cpu = s_cuda.to('cpu')
print(s_cpu)
print(s_cuda)
print(s_cuda_cpu)
print("torch.matmul累加误差:",torch.max(torch.abs(s_cuda_cpu - s_cpu)).item())

结果为:
在这里插入图片描述
在累加的操作下,差异几乎为0
幂运算的操作下,会出现差异
矩阵运算的操作下,会出现差异

我在实验的时候发现,将第一个和第二个顺序互换,有时会发现,exp的差异为0。

累加运算的差异

为了继续验证exp的运算有差异:
使随机的数增多:
在这里插入图片描述
结果:有差异了。

exp运算的差异

为了继续验证exp的运算有差异:
使随机的数增多:

import torchtorch.manual_seed(0)
# 创建一个随机张量
x = torch.randn(10000, dtype=torch.float32)
# 在CPU上进行累加
x_cpu = x.clone() #.clone() 复制张量
sum_cpu = torch.zeros_like(x_cpu)
#print(sum_cpu)
for _ in range(10000):sum_cpu += torch.exp(x_cpu)   #torch.mean()
# 在CUDA上进行累加
if torch.cuda.is_available():x_cuda = x.to('cuda')sum_cuda = torch.zeros_like(x_cuda).to('cuda') #zeros_like()创建一个与输入张量相同大小的全0张量#print(sum_cuda)for _ in range(10000):sum_cuda += torch.exp(x_cuda)# 将CUDA结果移回CPUsum_cuda_cpu = sum_cuda.to('cpu')# 计算差异diff = torch.max(torch.abs(sum_cpu - sum_cuda_cpu))print(f"Max difference between CPU and CUDA: {diff.item()}")
else:print("CUDA is not available.")

结果:差异变的很明显了。
在这里插入图片描述

matmul运算的差异

同理:

import torch
torch.manual_seed(0)
# 创建一个随机张量
x = torch.randn(1000, 1000, dtype=torch.float32)
# 在CPU上计算
x_cpu = x.to('cpu')
result_cpu = torch.matmul(x_cpu, x_cpu.t()) #矩阵乘法
# 在CUDA上计算
if torch.cuda.is_available():x_cuda = x.to('cuda')result_cuda = torch.matmul(x_cuda, x_cuda.t())# 将CUDA结果移回CPUresult_cuda_cpu = result_cuda.to('cpu')# 计算差异diff = torch.max(torch.abs(result_cpu - result_cuda_cpu))print(f"Max difference between CPU and CUDA: {diff.item()}")
else:print("CUDA is not available.")

差异为:
在这里插入图片描述
差异也变大了。

`结论:这里仅简单得到一些常见的运算差异,据此推测,其他的运算在大量的运算下,差异会越来越明显。

forward上的差异(激活函数的运算差异)

这里用一个最简单的单层qnet表示其差异,激活函数为relu。

.cuda() 和to(‘cuda’)的结果一样,只是to(‘cuda:0’),可以指定哪个gpu,我这里单个gpu,所以两个函数一个意思。

代码: 因为这里没有用到反向传播forward,所以在第一次forward后,模型的实例没有变。

import torch
import torch.nn.functional as F# 设置随机种子
torch.manual_seed(0)# 定义模型
class ValueNet(torch.nn.Module):def __init__(self, state_dim, hidden_dim):super(ValueNet, self).__init__()self.fc1 = torch.nn.Linear(state_dim, hidden_dim)self.fc2 = torch.nn.Linear(hidden_dim, 1)def forward(self, x):x = F.relu(self.fc1(x))return self.fc2(x)# 设置模型和数据
state_dim = 10
hidden_dim = 20
model = ValueNet(state_dim, hidden_dim)
input_data = torch.randn(100, state_dim,dtype=torch.float32)  # 假设有100个样本# 在CPU上运行
model.cpu()
input_data = input_data.cpu()
cpu_output = model(input_data)# 在GPU上运行
if torch.cuda.is_available():model.cuda()input_data = input_data.cuda()gpu_output = model(input_data)
else:print("No GPU available")# 比较输出结果
if torch.cuda.is_available():# 将GPU输出转移到CPU上进行比较gpu_output_cpu = gpu_output.cpu()# 计算差异difference = torch.abs(cpu_output - gpu_output_cpu)print("Max difference:", difference.max().item())print("Average difference:", difference.mean().item())
else:print("No GPU available for comparison")

单次运行结果差异:
在这里插入图片描述

补充: .to(device)在普通张量和类上的差异:

张量的to(device):它会创建一个新的张量副本,并将这个副本移动到指定的设备上。原始张量不会被修改。

在类(模型)的to(device):修改模型实例的存储位置(参数和缓冲区),模型实例本身并不会被重新创建。

所以上文的代码可以写成这样:

## gpu 与 cpu 差异来源分析
import torch
import torch.nn.functional as F
import copy# 设置随机种子
torch.manual_seed(0)# 定义模型
class ValueNet(torch.nn.Module):def __init__(self, state_dim, hidden_dim):super(ValueNet, self).__init__()self.fc1 = torch.nn.Linear(state_dim, hidden_dim)self.fc2 = torch.nn.Linear(hidden_dim, 1)def forward(self, x):x = F.relu(self.fc1(x))return self.fc2(x)# 设置模型和数据
state_dim = 10
hidden_dim = 20
model = ValueNet(state_dim, hidden_dim)
input_data = torch.randn(100, state_dim,dtype=torch.float32)  # 假设有100个样本
model_cpu = copy.deepcopy(model) #复制模型
model_cuda = model.to('cuda')
print("Model_cpu weights:", model_cpu.fc1.weight[0][0])
print("Model_gpu weights:", model_cuda.fc1.weight[0][0])
# 在CPU上运行input_data_cpu = input_data.to('cpu')
input_data_cuda = input_data.to('cuda')cpu_output = model_cpu(input_data_cpu)gpu_output = model_cuda(input_data_cuda)gpu_output_cpu = gpu_output.to('cpu')
# 计算差异
difference = torch.abs(cpu_output - gpu_output_cpu)
print("Max difference:", difference.max().item())
print("Average difference:", difference.mean().item())

在这里插入图片描述
确实保证了模型的参数一致,有了差异。

backward上的差异

为了剔除掉forward()函数上的激活函数影响,这里使用最简单的模型

# 定义一个简单的线性模型
model = nn.Linear(1, 1) # 输入维度是1,输出维度是1

来验证其差异性:

# 探讨backward()函数的差异
import torch
import torch.nn as nn
import torch.optim as optim
import copy
#设置随机种子
torch.manual_seed(0)# 生成一些随机的输入和标签
x = torch.randn(100, 1,dtype=torch.float32) # 100个样本,每个样本有1个特征  randn
y = 3 * x + 5 + torch.randn(100, 1,dtype=torch.float32) # 100个样本,每个样本有1个标签,服从 y = 3x + 5 + 噪声 的分布# 定义一个简单的模型
class SimpleModel(nn.Module):def __init__(self):super(SimpleModel, self).__init__()self.linear = nn.Linear(1, 1)def forward(self, x):return self.linear(x)
# 定义一个简单的线性模型
model = nn.Linear(1, 1) # 输入维度是1,输出维度是1
#model = SimpleModel()  # 也可以使用此模型,结果一样,但最好不要同时设置,即 model = nn.Linear(1, 1) ; model = SimpleModel()
#因为每次调用 nn.Linear 都会使用随机数生成器来初始化权重,会使结果不一致
model_cpu = copy.deepcopy(model)
model_cuda = model.to('cuda')   
# 定义一个均方误差损失函数
criterion = nn.MSELoss()
# 定义一个随机梯度下降优化器
optimizer_cpu = optim.SGD(model_cpu.parameters(), lr=0.01) # 学习率是0.01
optimizer_cuda = optim.SGD(model_cuda.parameters(), lr=0.01) # 学习率是0.01epochs = 1000
# 在CPU上训练
for epoch in range(epochs):output = model_cpu(x)loss = criterion(output, y)optimizer_cpu.zero_grad()loss.backward()optimizer_cpu.step()
# 在GPU上训练
for epoch in range(epochs):output = model_cuda(x.to('cuda'))loss = criterion(output, y.to('cuda'))optimizer_cuda.zero_grad()loss.backward()optimizer_cuda.step()
model_cuda_cpu = model_cuda.to('cpu')x_ = torch.randn(100, 1,dtype=torch.float32)
#print(x_)
cpu_output = model_cpu(x_)
gpu_output = model_cuda_cpu(x_)diff =torch.max(torch.abs(cpu_output - gpu_output)).item()
print("Max difference:{}".format(diff))
# 打印模型参数
print( 'cpu上:',model_cpu.weight.item(), model_cpu.bias.item()) #用SimpleModel()时,要改为model_cpu.linear.weight.item(), model_cpu.linear.bias.item()
print('cuda上:',model_cuda.weight.item(), model_cuda.bias.item())

结果为:
在这里插入图片描述
在epoch为1000时,差异还是0,在10000时就有了细小的差异。

总结或建议

1、在一般使用中,可以不必追求cpu和gpu计算的结果一致性,也也避免不了,且cpu和gpu导致的细小差别,在训练的效果上几乎没有区别。

2、同时,在同一台设备上,我们尽量要求该程序的结果能复现,是为了更好修改超参数。(见:本文万能seed,适用于单机多卡)

3、不必追求在不同的设备上能复现一致结果,最终的效果在相同的超参数和输入下,输出的结果也相差无几。

4、cpu和gpu在设计时的目的也不同,有差异理所应当,这里本文只分析了在计算上和训练神经网络时的出现的差异,仅作参考。

补充:写在后面

–24.5.24
发现了一个区别:
还是此代码:
代码:ppo代码(原代码为cuda版本)
动手学强化学习 github代码

现象:
在环境’CartPole-v0’的情况下:
我将while not done : 改成了 如下情况
在这里插入图片描述
发现在cpu版本的情况下,其他参数不变,依然能够收敛;
在这里插入图片描述

而cuda版本下,就不收敛了。
在这里插入图片描述
而cpu+cuda 的版本也不收敛。

这里的情况就是在done这个状态相当于没有的情况下,cpu依然能够学习到可以收敛的参数。
结论:
1、猜测cpu的计算精度确实比cuda要高的多。
2、状态的设置对于模型的收敛至关重要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/14454.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习云环境搭建

在 https://support.huaweicloud.com/browsertg-obs/obs_03_1003.html 下载对应版本的 OBS Broswer 软件,如图,红框内的为安装文件,蓝色框内的为对应安装文件的校验文件(无需下载) 以 64 位机为例,下载完…

景源畅信电商:抖店需要的成本高吗?

在数字化时代的浪潮中,短视频平台迅速崛起,成为连接用户与商家的新桥梁。抖音作为其中的佼佼者,不仅改变了人们的娱乐方式,也催生了新型的电商模式——抖店。许多人好奇,入驻这样一个充满活力的平台,需要承…

618知识狂欢,挑本好书,点亮智慧生活!

618精选编程书单:提升你的代码力 一年一度的618又到啦!今年的618就不要乱买啦,衣服买多了会被淘汰,电子产品买多了会过时,零食买多了会增肥,最后怎么看都不划算。可是如果你购买知识,坚持阅读&a…

后端之路第二站(正片)——SprintBoot之:分层解耦

很抽象,我自己也不好理解,仅作为一个前端转后端的个人理解 一、先解释一个案例,以这个案例来分析“三层架构” 这里我先解释一下黑马程序员里的这个案例,兄弟们看视频的可以跳过这节课:Day05-08. 请求响应-响应-案例_…

【webrtc】m98:Call的创建及Call对音频接收处理

call中多個流共享相同的辅助组件 这几个是与外部共用的 线程传输send控制module 线程任务队列工厂call的辅助组件中各种统计以及接收测的cc是自己创建的 call自己的多个辅助组件是外部传递来的 call 创建多个接收流 这里用一个set 来保存所有指针,并没有要map的意思:

【因果推断从入门到精通二】随机实验3

目录 检验无因果效应假说 硬币投掷的特殊性何在? 检验无因果效应假说 无因果效应假说认为,有些人存活,有些人死亡,但接受mAb114治疗而不是ZMapp与此无关。在174例接受mAb14治疗的患者中,113/17464.9%存活了28天&…

Java入门基础学习笔记47——ArrayList

什么是集合呢? 集合是一种容器,用来装数据的,类似数组。 有数组,为什么还要学习集合呢? 数组定义完成并启动后,长度就固定了。 而集合是大小可变,开发中用的最多的。 集合的特点:大…

汇聚荣科技有限公司优点有哪些?

在当今快速发展的科技时代,企业之间的竞争愈发激烈。作为一家专注于科技创新与研发的公司,汇聚荣科技有限公司凭借其卓越的技术实力和创新能力,在业界树立了良好的口碑。那么,汇聚荣科技有限公司究竟有哪些优点呢?接下来&#xf…

C++利用TinyXML读取XML文件

TinyXML是什么? TinyXML是一个轻量级的C XML解析器,它提供了一种简单的方法来解析和操作XML文档。TinyXML被设计为易于使用和集成到C项目中,并且非常适合处理小型XML文件。 以下是TinyXML的一些主要特点和优点: 轻量级: T…

OSPF问题

.ospf 选路 域内 --- 1类,2类LSA 域间 --- 3类LSA 域外 --- 5类,7类LSA --- 根据开销值的计算规则不同,还分为类型1和类型2 ospf 防环机制 区域内防环:在同一OSPF区域内,所有路由器通过交换链路状态通告&#xff…

226.翻转二叉树

翻转一棵二叉树。 思路: 指针做交换 用递归(前序or后序,中序不行) 前序:中左右 遍历到“中”的时候,交换它的左右孩子 然后分别对它的左孩子和右孩子使用“交换函数”(定义的)&a…

【网络版本计算器的实现】

本章重点 理解应用层的作用, 初识HTTP协议理解传输层的作用, 深入理解TCP的各项特性和机制对整个TCP/IP协议有系统的理解对TCP/IP协议体系下的其他重要协议和技术有一定的了解学会使用一些分析网络问题的工具和方法 ⭐注意!! 注意!! 注意!! 本课是网络编程的理论基础.是一个服务…

【YOLOv5/v7改进系列】替换激活函数为SiLU、ReLU、LeakyReLU、FReLU、PReLU、Hardswish、Mish、ELU等

一、导言 激活函数在目标检测中的作用至关重要,它们主要服务于以下几个关键目的: 引入非线性:神经网络的基本构建块(如卷积层、全连接层等)本质上是线性变换,而激活函数通过引入非线性,使得网络…

保安维稳,四信以科技构筑高速公路安全智慧防线

近日,广东梅大高速发生严重塌方事故,造成了严重的人员伤亡和财产损失。这一事件在公众心中敲响了安全的警钟,再次引起了公众对于交通设施运营安全性的重点关注。 国务院安委会办公室和国家防灾减灾救灾委员会办公室等主管机构先后印发紧急通知…

Spring Security整合Gitee第三方登录

文章目录 学习链接环境准备1. 搭建基本web应用引入依赖ThirdApp启动类创建index页面application.yml配置访问测试 2. 引入security引入依赖ProjectConfig访问测试 第三方认证简介注册gitee客户端实现1引入依赖application.yml配置文件创建index.html页面启动类InfoControllerPr…

【数学建模】储药柜的设计

2014高教社杯全国大学生数学建模竞赛D题目 题目描述 储药柜的结构类似于书橱,通常由若干个横向隔板和竖向隔板将储药柜分割成若干个储药槽(如图1所示)。为保证药品分拣的准确率,防止发药错误,一个储药槽内只能摆放同一种药品。药品在储药槽…

docker搭建gitlab及默认密码修改及配置修改

推荐官方文档 https://docs.gitlab.com/17.0/ee/install/docker.html 我使用的是docker run的方式,官方文档后面有docker-compose、swarm、k8s的部署文档 版本说明 1:可以部署gitlab-ce社区版和gitlab-ee企业版,然后,鉴于是个人…

AIGC绘画设计基础-建筑设计应用

一、AI及AIGC 对于AI大家都不陌生,但是AIGC这个概念好多人其实不大清楚。“AI”是指人工智能技术本身,而“AIGC”是指基于人工智能技术而生成的内容。 生成式人工智能——AIGC(Artificial Intelligence Generated Content)&…

SpringFramework实战指南

二、SpringFramework实战指南 目录 一、技术体系结构 1.1 总体技术体系1.2 框架概念和理解 二、SpringFramework介绍 2.1 Spring 和 SpringFramework概念2.2 SpringFramework主要功能模块2.3 SpringFramework 主要优势 三、Spring IoC容器和核心概念 3.1 组件和组件管理概念3…