前言

        我们正在利用pytorch实现CNN。主要分为四个小部分:数据预处理、神经网络pytorch设计、训练神经网络 和 神经网络实验

        在之前的章节中,我们已经完成了整个CNN框架的设计、训练与简单分析,本节将更进一步讨论神经网络处理过程中的细节问题,以便让我们能够有效地试验我们所构建的训练过程。

1. 优化超参数实验

   1.1 Run Builder类

        首先,我们希望构建一个 Run Builder 类,来实现上个博客最后一节的不同组合超参数实验。

from collections import OrderedDict
from collections import namedtuple
from itertools import productclass RunBuilder():@staticmethod  # 静态方法,默认第一个参数不需要接收类或实例;可以直接用类来调用这个方法,无需创建实例def get_runs(params):Run = namedtuple('Run', params.keys()) # 创建一个具有名字的元组,'Run'是元组名,params.keys()提取参数字典的键runs = []for v in product(*params.values()): # 笛卡尔积runs.append(Run(*v)) # 首先将笛卡尔积分别对应到Run元组中,然后再统一添加到list中return runshyperparam = dict(lr = [.01,.001],batch_size = [100,1000],
)runs = RunBuilder.get_runs(hyperparam) # 直接用类 调用方法
print(runs)
# [Run(lr=0.01, batch_size=100), Run(lr=0.01, batch_size=1000),
#  Run(lr=0.001, batch_size=100), Run(lr=0.001, batch_size=1000)]

        我们来看下之前的代码和现在的对比:

# Before
for lr, batch_size, shuffle in product(*param_values):comment = f'batch_size={batch_size} lr={lr} shuffle={shuffle}'
# 之前我们必须在for循环中列出所有的参数# After
for run in RunBuilder.get_runs(params):comment = f'-{run}'
# 现在不管有多少个参数,都可以自动生成注释

   1.2 同步大量超参数实验

        在上一个博客的代码中可以看到,我们现在的训练循环代码十分臃肿,我们希望将这个代码变得更加易扩展、易管理。因此除了1.1中构建的Run Builder类,还需构建一个Run Manager类

        它将使得我们能够在每一个run中进行管理,一方面可以摆脱冗长的TensorBoard调用,另一方面可以增加一些其他的功能。当parameter和run的数量增多的时候,TensorBoard不再是一个可以查看结果的可行方案。RunManager将在每个执行过程中创建生命周期,还可以跟踪损失和正确的预测数,最终保存将运行结果。

import time
from collections import OrderedDict
from collections import namedtuple
from itertools import product
import torch.nn.functional as Ffrom CNN_network import Network,train_set
import torch.optim as optimimport pandas as pd
import torch
from IPython.core.display_functions import clear_output
from tensorboard.notebook import displayfrom torch.utils.tensorboard import SummaryWriterclass RunBuilder():@staticmethod  # 静态方法,默认第一个参数不需要接收类或实例;可以直接用类来调用这个方法,无需创建实例def get_runs(params):Run = namedtuple('Run', params.keys()) # 创建一个具有名字的元组,'Run'是元组名,params.keys()提取参数字典的键runs = []for v in product(*params.values()): # 笛卡尔积runs.append(Run(*v)) # 首先将笛卡尔积分别对应到Run元组中,然后再统一添加到list中return runs# hyperparam = dict(
# 	lr = [.01,.001],
# 	batch_size = [100,1000],
# )
#
# runs = RunBuilder.get_runs(hyperparam) # 直接用类 调用方法
# print(runs)
# # [Run(lr=0.01, batch_size=100), Run(lr=0.01, batch_size=1000),
# #  Run(lr=0.001, batch_size=100), Run(lr=0.001, batch_size=1000)]class RunManager():def __init__(self):self.start_time = None  # 计算运行时间self.run_params = None  # RunBuilder的返回值self.run_count = 0self.run_data = []# 记录网络、dataloader、tensorboard文件self.network = Noneself.loader = Noneself.tb = Noneself.epoch_count = 0  # epoch数self.epoch_loss = 0  # epoch对应lossself.epoch_num_correct = 0  # 每个epoch预测正确的树木self.epoch_start_time = None  # The start time of an epoch,对应 begin_epoch 和 end_epochdef begin_run(self, run ,network, loader):'''开始运行一次'''self.run_params = runself.start_time = time.time()self.run_count += 1self.tb = SummaryWriter(comment=f'{run}')self.network = networkself.loader = loaderimages, labels = next(iter(loader))self.tb.add_images('images', images)self.tb.add_graph(network, images)def begin_epoch(self):'''开始一个周期'''self.epoch_count += 1self.epoch_start_time = time.time()self.epoch_num_correct = 0self.epoch_loss = 0passdef end_epoch(self):'''结束一个周期,并计算loss等'''epoch_duration = time.time() - self.epoch_start_timerun_duration = time.time() - self.start_timeloss = self.epoch_loss / len(self.loader.dataset)accuracy = self.epoch_num_correct/len(self.loader.dataset)self.tb.add_scalar('Loss', loss, self.epoch_count)self.tb.add_scalar('Accuracy', accuracy, self.epoch_count)for name, weight in self.network.named_parameters():self.tb.add_histogram(name, weight, self.epoch_count)self.tb.add_histogram(f'{name}.grad', weight.grad, self.epoch_count)pass# 建立一个字典,记录所有中途结果,方便在tensorboard中查看分析results = OrderedDict(run=self.run_count,epoch=self.epoch_count,loss=loss,accuracy=accuracy,epoch_duration=epoch_duration,run_duration=run_duration)for k, v in self.run_params._asdict().items():results[k] = vself.run_data.append(results)df = pd.DataFrame.from_dict(self.run_data, orient='columns')clear_output(wait=True)display(df)passdef track_loss(self, loss,batch):'''记录损失'''self.epoch_loss += loss.item() * batch[0].shape[0]passdef track_num_correct(self, preds, labels):'''记录预测正确的数据'''self.epoch_num_correct += self.get_correct_num(preds, labels)def end_run(self):'''结束运行,并将epoch重新设置为0'''self.tb.close()self.epoch_count = 0@torch.no_grad()def _get_correct_num(self,predict, labels): #下划线代表是个内部方法,不被外部使用return predict.argmax(dim=1).eq(labels).sum().item()def save(self,filename):pd.DataFrame.from_dict(self.run_data, orient='columns').to_csv(f'{filename}'.csv, index=False)# 在这里修改参数
params = dict(lr = [.01,.001],batch_size = [100,1000],shuffle = [True,False]
)
manager = RunManager()for run in RunBuilder.get_runs(params):network = Network()train_loader = torch.utils.data.DataLoader(train_set, batch_size=run.batch_size)optimizer = optim.Adam(network.parameters(), lr=run.lr)manager.begin_run(run=run, network=network, loader=train_loader)for epoch in range(5):manager.begin_epoch()for batch in train_loader:images, labels = batchpredict = network(images) # Pass Batchloss = F.cross_entropy(predict, labels) # calculate lossmanager.track_loss(loss, batch)manager.track_num_correct(preds=predict, labels=labels)optimizer.zero_grad() # zero gradientloss.backward() # calculate gradientoptimizer.step() # updata weightspassmanager.end_epoch()manager.end_run()

   1.3 同步不同网络的实验

        这里我们可能还想对不同的网络进行测试,我们可以再定义一个NetworkFactory类,并将其添加到1.2的实验框架中。

Class NetworkFactory():@staticmethoddef get_network(name):if name == 'network1':return nn.Sequential(xxx)elif name == 'network2':return nn.Sequential(xxx)else:return Noneparams = dict(lr = [.01,.001],batch_size = [100,1000],shuffle = [True,False],network = ['network1','network2']device = ['mps','cpu']
)
manager = RunManager()for run in RunBuilder.get_runs(params):# 修改————————————————————————————————————————————————————————————network = NetworkFactory.get_network(run.network).to(device)# ———————————————————————————————————————————————————————————————train_loader = torch.utils.data.DataLoader(train_set, batch_size=run.batch_size)optimizer = optim.Adam(network.parameters(), lr=run.lr)manager.begin_run(run=run, network=network, loader=train_loader)for epoch in range(5):manager.begin_epoch()for batch in train_loader:images, labels = batchpredict = network(images) # Pass Batchloss = F.cross_entropy(predict, labels) # calculate lossmanager.track_loss(loss, batch)manager.track_num_correct(preds=predict, labels=labels)optimizer.zero_grad() # zero gradientloss.backward() # calculate gradientoptimizer.step() # updata weightspassmanager.end_epoch()manager.end_run()

2. 加速训练过程       

        现在需要考虑如何让训练/推理过程更快,特别是对于大规模的神经网络,这点尤为重要。

   2.1 Dataloader 多进程加速

        DataLoader 有一个 num_workers 参数,默认为 0,表示数据加载操作在主进程中进行。可以设置为大于 0 的数值来开启多个子进程。

        注意:num_workers 只影响数据加载阶段的时间。因此,并非 num_workers 越多越好。如果神经网络的前向传播(forward pass)和反向传播(backward pass)所消耗的时间,远大于加载一个 batch 数据所需的时间,那么将 num_workers 设置为1通常就足够了,因为数据加载的瓶颈并不在于此。

(加速的原理相当于主进程在执行fp和bp时,提前准备好数据,省去读取的时间)

loader = DataLoader(train_set, batch_size=64, num_workers= )

   2.2 使用 GPU 加速训练

        PyTorch允许我们在GPU和CPU之间实现数据的无缝转移,当我们想要把数据转去GPU时,我们使用to('cuda') / to('mps'),当我们使用cpu时,我们使用to('cpu')。做tensor运算时,需保持device的一致性。

        在神经网络中,我们的network和data都可以移动到gpu上,这样就无需再从cpu中调取数据。

import torch
from CNN_network import Network,train_setprint(torch.mps.is_available()) # 检查GPU的可用性:Truenetwork = Network()
# t = torch.tensor([1,1,28,28], dtype=torch.float)  注意这种写法是不对的
t = torch.randn(1, 1, 28, 28)   # 我们要随机生成一个shape为(1, 1, 28, 28)的tensor
t.to(float)# 使用cpu
device = torch.device('cpu')
t = t.to(device)
network = network.to(device)
cpu_pred = network(t)
print(cpu_pred.device) # 输出:cpu# 使用gpu
device = torch.device('mps')
t = t.to(device)  # 数据移至gpu
network = network.to(device) # 网络移至gpu
gpu_pred = network(t) 
print(gpu_pred.device) # 输出:mps:0

3. 标准化  Normalization

   3.1 数据标准化

        Normalization 也叫 feature scaling。因为我们经常会将不同的feature转换成相似的形状,保证整个数据集的均值为0,方差为1

Z = \frac{x-mean}{std}

        一般我们在做数据标准化处理时,要考虑数据集大小问题,如果数据集太大,无法一次性载入内存,则需分批载入计算。

import torch
from matplotlib import pyplot as plt
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
from CNN_network import Network,train_set
import torchvision# Easy way:将整个数据集一次性加载到内存中作为tensor调取,计算均值和方差
loader = DataLoader(train_set,batch_size=len(train_set)) # batch_size:一次性导入
data = next(iter(loader))
print(data[0].mean(),data[0].std())
# 输出:tensor(0.2860) tensor(0.3530)# Hard way:如果数据集太大无法一次性导入,就分批导入
loader = DataLoader(train_set,batch_size=1000)
num_of_pixels = len(train_set) * 28 * 28 #计算总共的像素点个数=样本数乘以宽和高
total_sum = 0
for batch in loader:  #一般batch会返回两个tensor:(image_tensor, label_tensor)!!!total_sum += batch[0].sum()
mean = total_sum / num_of_pixelssum_of_squard_error = 0
for batch in loader:sum_of_squard_error += ((batch[0] - mean).pow(2)).sum()
std = torch.sqrt(sum_of_squard_error / num_of_pixels)
print(mean,std) # tensor(0.2860) tensor(0.3530)

        下面我们将数据展平,看一下分布的直方图,并标注数据的均值。可以看到数据介于0~1之间,基本都集中在0左右,竖线为均值

plt.hist(data[0].flatten())
plt.axvline(data[0].mean())
plt.show()

        然后我们重新构建一个标准化之后的数据集,查看数据分布可以看到其均值为0,方差为1。这里要注意,因为我们是个灰度图像,颜色通道数为1;但是如果是RGB三通道,就需要对三个通道做分别的计算。

train_set_normal = torchvision.datasets.FashionMNIST(root='./data',download=True,train=True,transform=transforms.Compose([transforms.ToTensor(), # 要先转化为tensortransforms.Normalize(mean,std)  # 再做标准化] ))loader = DataLoader(train_set_normal,batch_size=len(train_set_normal))
data = next(iter(loader))
print(data[0].mean(),data[0].std()) #均值为0,方差为1 plt.hist(data[0].flatten(),color = 'orange')
plt.axvline(data[0].mean(),color = 'orange')
plt.show()

   3.2 网络层标准化

        3.1中我们介绍了对数据标准化的处理过程,现在我们不仅要对最开始传入的数据进行标准化,还想再层与层之间传递时,也进行标准化处理。在下图中可以看到这个标准化处理和3.1中稍有不同,多了一些参数。

import torch.nn as nn
torch.manual_seed(1)# 
sequential1 = nn.Sequential(nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5),nn.ReLU(),nn.MaxPool2d(2, 2),nn.BatchNorm2d(6),  # 二维标准化nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5),nn.ReLU(),nn.MaxPool2d(2, 2),nn.Flatten(start_dim=1),nn.Linear(in_features=12 * 4 * 4, out_features=120),nn.ReLU(),nn.BatchNorm1d(120),  # 一维标准化nn.Linear(in_features=120, out_features=60),nn.ReLU(),nn.Linear(in_features=60, out_features=10)
)

4. 一些其它补充

   4.1 Pytorch Sequential Model

        nn.Sequential 是 PyTorch 中的一个容器类 (torch.nn.Sequential)。它按顺序存储多个神经网络层或模块。其数据按顺序通过 Sequential 容器中定义的每一层,我们只需要提供一个层的列表(或 OrderedDict)。

        相较于我们之前Class Network的方式,其优点就是简洁,无需显式定义 forward 方法。而缺点就是只能处理简单的层与层之间严格的线性顺序连接。如果网络结构更复杂(例如,有跳跃连接 skip-connections,如 ResNet;或者需要在 forward 过程中进行分支、合并、条件处理等),Sequential 就无法胜任。

        对于之前的网络,我们是这样定义的:

class Network(nn.Module):  # 继承nn.Module基类def __init__(self):super().__init__() # 调用父类(nn.Module)的init,确保父类的属性被正确初始化# 卷积层self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5)self.conv2 = nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5)# 全连接层/线性层self.fc1 = nn.Linear(in_features=12 * 4 * 4, out_features=120)self.fc2 = nn.Linear(in_features=120, out_features=60)# 输出层self.out = nn.Linear(in_features=60, out_features=10)def forward(self,t):t = self.conv1(t)t = F.relu(t)t = F.max_pool2d(t, kernel_size=2, stride=2) # 池化不一定是有效的,可能会损失一些精度t = self.conv2(t)t = F.relu(t)t = F.max_pool2d(t, kernel_size=2, stride=2)t = t.reshape(-1,12*4*4)t = self.fc1(t)t = F.relu(t)t = self.fc2(t)t = F.relu(t)t =self.out(t)return t

        现在用nn.Sequential的方式来定义:

# nn.Sequential
import torch.nn as nn
torch.manual_seed(1)# 定义方式1
sequential1 = nn.Sequential(nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5),nn.ReLU(),nn.MaxPool2d(2, 2),nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5),nn.ReLU(),nn.MaxPool2d(2, 2),nn.Flatten(start_dim=1),nn.Linear(in_features=12 * 4 * 4, out_features=120),nn.ReLU(),nn.Linear(in_features=120, out_features=60),nn.ReLU(),nn.Linear(in_features=60, out_features=10)
)
sequential1 # 实例化# 定义方式2:定义OrderedDict字典
layers = OrderedDict([('conv1', nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5)),('relu1', nn.ReLU()),('maxpool1', nn.MaxPool2d(2, 2)),('conv2', nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5)),('relu2', nn.ReLU()),('maxpool2', nn.MaxPool2d(2, 2)),('flatten', nn.Flatten(start_dim=1)),('fc1', nn.Linear(in_features=12 * 4 * 4, out_features=120)),('relu3', nn.ReLU()),('fc2', nn.Linear(in_features=120, out_features=60)),('relu4', nn.ReLU()),('fc3_out', nn.Linear(in_features=60, out_features=10)),
])
sequential2 = nn.Sequential(layers)
sequential2 # 实例化# 定义方式3
sequential3 = nn.Sequential()
sequential3.add_module('conv1', nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5))
sequential3.add_module('relu1', nn.ReLU())
sequential3.add_module('maxpool1', nn.MaxPool2d(2, 2))
sequential3.add_module('conv2', nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5))
sequential3.add_module('relu2', nn.ReLU())
sequential3.add_module('maxpool2', nn.MaxPool2d(2, 2))
sequential3.add_module('flatten', nn.Flatten(start_dim=1))
sequential3.add_module('fc1', nn.Linear(in_features=12 * 4 * 4, out_features=120))
sequential3.add_module('relu3', nn.ReLU())
sequential3.add_module('fc2', nn.Linear(in_features=120, out_features=60))
sequential3.add_module('relu4', nn.ReLU())
sequential3.add_module('fc3_out', nn.Linear(in_features=60, out_features=10))sequential3 # 实例化

   4.2 重置网络权重

  • 重置单个层的权重:
layer = nn.Linear(2,1)
layer.reset_parameters() # reset parameters
重置 weight和bias
重置 weight和bias

  • 在网络中重置单个层的权重:
network = nn.Sequential(nn.Linear(2,1))
network[0].reset_parameters() # 通过索引来访问layer
  • 重置网络中所有层的权重:
for module in network.children(): # .children()返回网络模型里的组成元素module.reset_parameters()
  • 保存和载入权重
# 保存权重
torch.save(network.state_dict(), './weight/model.pth') # 载入权重
network.load_state_dict(torch.load('./weight/model.pth'))

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

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

相关文章

STM32实践项目(激光炮台)

刚开始设想做一个上半部分可以上下180移动,下半部分底座360移动的激光炮台。于是便开始了实践。 所需材料清单: 序号 名称 数量 备注说明 1 面包板(Breadboard) 2 用于电路搭建和模块连接 2 杜邦线(公对公、公对母等) 若干 建议准备 30~50 根,方便连接 3 MB-102 电源模块…

不止是夹住,更是“感知”:Contactile GAL2触觉型夹爪实现自适应抓取

近日,专注于触觉传感与智能抓取技术的Contactile推出全新Contactile 触觉型夹爪 GAL2,这款集成先进传感技术的双指夹爪,凭借实时触觉反馈能力,为多行业智能抓取场景带来突破性解决方案。 Contactile 触觉型夹爪GAL2是一款多功能即…

Grafana - 监控磁盘使用率Variables使用

1 查询prometheus2 编辑grafana dashboard 2.1 配置变量2.2 配置多选2.3 配置legend2.4 优化显示 1 查询prometheus 指标名称描述node_filesystem_size_bytes文件系统总容量node_filesystem_avail_bytes用户可用空间node_filesystem_files_free剩余inode数量比如我们想看/目…

WindowsAPI|每天了解几个winAPI接口之网络配置相关文档Iphlpapi.h详细分析10

上一篇:WindowsAPI|每天了解几个winAPI接口之网络配置相关文档Iphlpapi.h详细分析9 如果有错误欢迎指正批评,在此只作为科普和参考。 C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\um\iphlpapi.h 文章目录GetNetworkParams&#xff1a…

算法 --- 分治(归并)

分治(归并) 分治(特别是归并)算法适用于解决“整体求解依赖于子问题合并”且子问题相互独立的题目,其典型特征是能将大规模数据分解、递归求解,然后通过合并操作(这正是归并排序中‘归并’的精…

【程序人生】有梦想就能了不起,就怕你没梦想

梦想不是遥不可及的星辰,而是需要我们用脚步丈量的路途两年前的一个夏日,我在日记本上郑重地写下:"我要掌握Web开发,能够独立构建一个完整的Web应用。"那天是2023年6月8日,当时的我连Java和JavaScript都分不…

前端基础(四十二):非固定高度的容器实现折叠面板效果

效果展示源码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head>…

发票、收据合并 PDF 小程序,报销上传 3 秒搞定

每到报销、报税、财务整理时&#xff0c;手里是不是总有一堆格式不一的票据&#xff1a; 聊天记录里的电子发票邮件附件中的 PDF 发票手机相册里的报销收据甚至还有零散的纸质票据扫描件 要上传或交给财务前&#xff0c;还得一个个整理、转换、排版&#xff0c;既耗时又容易出…

GitHub每日最火火火项目(9.4)

1. bytebot-ai / bytebot 项目名称&#xff1a;bytebot项目介绍&#xff1a;基于 TypeScript 开发&#xff0c;是一款自托管的 AI 桌面智能体&#xff0c;能通过自然语言命令自动化执行计算机任务&#xff0c;运行在容器化的 Linux 桌面环境中。它借助自然语言处理和 AI 技术&a…

MMORPG 游戏战斗系统架构

&#x1f30c; MMORPG 游戏战斗系统架构 引用&#xff1a; 游戏服务器同步技术解析&#xff08;C&#xff09;MMORPG移动同步与反外挂 虽然我已离开游戏行业&#xff0c;转而与几位成功的商人共同创业&#xff0c;投身于商用机器人领域&#xff0c;但坦诚地说&#xff0c;游戏…

【数学建模学习笔记】启发式算法:蒙特卡洛算法

蒙特卡洛模拟入门笔记&#xff1a;从原理到代码实践一、什么是蒙特卡洛模拟&#xff1f;蒙特卡洛模拟是一种通过大量随机实验来解决复杂问题的方法。简单说&#xff0c;就是用电脑模拟成千上万次随机事件&#xff0c;然后统计结果&#xff0c;以此估算一个问题的答案。举个生活…

20250904的学习笔记

一、封包与拆包1. 封包&#xff08;Packet Encapsulation&#xff09;封包 是指在发送数据时&#xff0c;将数据从高层协议封装到低层协议的过程。每经过一层协议&#xff0c;数据都会被加上相应的协议头&#xff08;有时也会加上协议尾&#xff09;&#xff0c;形成一个新的数…

STM32F4 + RT-Thread 实战指南:TIM10 硬件定时器驱动开发与 1 秒定时功能实现

目录前言一、STM32定时器10是个什么定时器&#xff1f;二、工程创建、环境配置三、程序代码四、运行前言 在rtthread中&#xff0c;STM32F4的定时器10有些驱动并不完整&#xff0c;对比与其它定时器在使用时需要手动的添加一些代码&#xff0c;我在使用上拆踩了一些坑&#xf…

echarts图库

环形图// 指定图表的配置项和数据this.option {// tooltip: {// trigger: item// },color: [#FFB32F, #FF5757, #57D5FF, #2FA8FF, #95FFF1], // 扇形区域以及列表颜色legend: {orient:vertical,//文字横向排itemGap:20,left: left,textStyle:{color: #F3F9FF,// fontSi…

进程(Process)全面概述

进程&#xff08;Process&#xff09;全面概述 本文档扩展了进程的定义、属性、生命周期、管理机制及示例&#xff0c;涵盖 task_struct 结构、进程链表、状态与优先级、fork 函数及其写时复制示例。 一、进程基本概念 进程&#xff1a;系统进行资源分配和调度的基本单位&#…

Java并发编程:sleep()与wait()核心区别详解

今天完成了实验室纳新网站的工作&#xff0c;大体功能都已经完善&#xff0c;也和前端测试过了&#xff0c;费了点时间&#xff0c;而且今天大部分时间在看langchain4j的东西&#xff0c;就简单复习一下八股&#xff0c;等会再复习一下算法题吧在Java并发编程中&#xff0c;sle…

AR眼镜在智能制造的应用方向和场景用例|阿法龙XR云平台

AR巡检在制造业的应用已形成覆盖设备维护、质量检测、安全监控和远程协作四大类别的成熟场景&#xff0c;不同制造领域的实践各具特色&#xff0c;为行业提供了宝贵参考。在汽车制造领域&#xff0c;AR 巡检主要应用于生产线设备维护和焊接质量检测。在汽车厂总装车间部署 AR 系…

【Linux系统】线程同步

在上一章节中&#xff0c;我们使用互斥量之后&#xff0c;确实解决了数据竞争问题&#xff0c;但出现了新的问题&#xff1a;只有一个线程&#xff08;thread 1&#xff09;在处理所有售票任务。这展示了互斥量的一个局限性&#xff1a;它确保了线程安全&#xff0c;但不保证公…

代码随想录训练营第三十一天|LeetCode56.合并区间、LeetCode738.单调递增的数字

56.合并区间 思路&#xff1a;先让二维数组进行排序&#xff1b; 遍历数组&#xff0c;定义一个min表示重合区间的左边界&#xff0c;max表示重合区间的右边界&#xff1b; 如果当前区间左边大于max&#xff0c;就证明重合区间断了&#xff0c;就要对它进行加入ArrayList&am…

【Unity项目经验分享】实现左右分屏裸眼3D程序

1、实现原理左右分屏原理&#xff0c;左右屏内容左右方向存在些许偏差。通过左右相机&#xff0c;然后左侧相机向左侧偏移一点3cm&#xff0c;右侧相机向右侧屏偏移一定3cm&#xff0c;然后将左右相机渲染内容通过RenderTexture渲染到Canvas上面的左右RawImage上面。2、实现具体…