Pytorch的hook函数

news/2024/4/29 12:14:11/文章来源:https://blog.csdn.net/weixin_43589323/article/details/137103328

hook函数是勾子函数,用于在不改变原始模型结构的情况下,注入一些新的代码用于调试和检验模型,常见的用法有保留非叶子结点的梯度数据(Pytorch的非叶子节点的梯度数据在计算完毕之后就会被删除,访问的时候会显示为None),又或者查看模型的层与层之间的数据传递情况(数据维度、数据大小等),抑或是在不修改原始模型代码的基础上可视化各个卷积特征图。

Pytorch提供了四种hook函数

  1. torch.tensor.register_hook(hooc_func)
  2. torch.nn.Module.register_forward_hook(hook_func)
  3. torch.nn.Module.register_forward_pre_hook(hook_func)
  4. torch.nn.Module.register_backward_hook

1. torch.tensor.register_hook(hooc_func)

解释:注册一个反向传播hook函数,其函数签名如下

def hook(grad):...

输入参数为张量的梯度,实现的hook函数可以在此修改梯度数据(原地修改或者通过返回值返回),或者在此将梯度数据保存、裁剪等。

示例 1

# leaf node data
x = torch.Tensor([0, 1, 2, 3]).requires_grad_()
y = torch.Tensor([4, 5, 6, 7]).requires_grad_()
w = torch.Tensor([1, 2, 3, 4]).requires_grad_()# intermediate variable
z = x + y# output 
o = torch.dot(w, z)# backward to calculate gradient
o.backward()# print gradient infomation
print('x.grad:', x.grad) # tensor([1., 2., 3., 4.])
print('y.grad:', y.grad) # tensor([1., 2., 3., 4.])
print('w.grad:', w.grad) # tensor([ 4.,  6.,  8., 10.])
print('z.grad:', z.grad) # None
print('o.grad:', o.grad) # None

输出:

x.grad: tensor([1., 2., 3., 4.])
y.grad: tensor([1., 2., 3., 4.])
w.grad: tensor([ 4.,  6.,  8., 10.])
z.grad: None
o.grad: None

可以看到代码中的非叶子节点z, o的梯度信息(grad)在计算之后立即被释放,因此都等于None,如果需要显式地声明需要保留非叶子节点的grad,需要使用retain_grad方法,如下例:

import torch 
a = torch.ones(5)
a.requires_grad = Trueb = 2*ab.retain_grad()   # 让非叶子节点b的梯度保持
c = b.mean()
c.backward()print(f'a.grad = {a.grad}\nb.grad = {b.grad}')

输出:

a.grad = tensor([0.4000, 0.4000, 0.4000, 0.4000, 0.4000])
b.grad = tensor([0.2000, 0.2000, 0.2000, 0.2000, 0.2000])

retain_grad()方法会增加显存的占用,我们可以使用hook获取梯度信息而不需要显式地使用retain_grad()强制系统保存梯度信息,如下例:

import torcha = torch.ones(5).requires_grad_()b = 2 * aa.register_hook(lambda x:print(f'a.grad = {x}'))
b.register_hook(lambda x: print(f'b.grad = {x}'))  c = b.mean()print('begin backward'.center(30, '-'))
c.backward()
print('end backward'.center(30, '-'))

输出:

--------begin backward--------
b.grad = tensor([0.2000, 0.2000, 0.2000, 0.2000, 0.2000])
a.grad = tensor([0.4000, 0.4000, 0.4000, 0.4000, 0.4000])
---------end backward---------

上述例子中我们使用hooktensorgrad进行访问,没有使用retain_grad对信息进行保存。输出结果表明,hook执行的时间是在backward之间,从后往前依次执行,首先输出bgrad,然后输出agrad,最后结束backward过程。

上述过程都没有对梯度信息进行改变,其实,如果hook函数的有返回值或者将输入参数grad原地进行修改的话,那么之后的梯度信息都会被改变,这一机制简直就是为梯度裁剪量身定制的。

如下例:

import torchdef hook(grad):torch.clamp_(grad, min=0.5, max=0.2)print(grad)a = torch.ones(5).requires_grad_()
b = 2 * aa.register_hook(hook)
b.register_hook(hook)  c = b.mean()print('begin backward'.center(30, '-'))
c.backward()
print('end backward'.center(30, '-'))

输出:

--------begin backward--------
tensor([0.2000, 0.2000, 0.2000, 0.2000, 0.2000])
tensor([0.2000, 0.2000, 0.2000, 0.2000, 0.2000])
---------end backward---------

对比上一例可以发现a的梯度从0.4被裁剪到了0.2,这里使用的clamp_是直接原地修改,所以不需要返回值。

也可将上述例子中的hook更改为有返回值的函数,效果相同。

部分例子参考:https://zhuanlan.zhihu.com/p/662760483

2. torch.nn.Module.register_forward_hook(hook_func)

除了register_hook是对tensor操作的hook之外,其他的hook都是对module进行操作的,这里的module包括各种layer,例如:Conv2d, Linear

register_forward_hook在执行moduleforward函数之后执行,其函数签名为

def hook(module, inputs, outpus):pass

注意:这里的module是当前被注册的moduleinputs是执行forward之前的inputs,而outputs则是执行forward之后的outputs ,这么设计可能是为了方便读取执行之前的intputs

如下例所示:

import torch
import torch.nn as nn# 定义一个简单的模块
class MyModule(nn.Module):def forward(self, x):print('forward'.center(20, '-'))return x * 2  # 假设这个模块简单地将输入乘以2# 创建模块实例
module = MyModule()# 定义一个hook函数,它接受输入和输出作为参数
def my_hook(module, input, output):print(f"Input: {input}")print(f"Output: {output}")# 注册hook函数
module.register_forward_hook(my_hook)# 创建一个输入张量
input_tensor = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)# 执行前向传播,这将触发hook函数的调用
output_tensor = module(input_tensor)

输出:

------forward-------
Input: (tensor([1., 2., 3.], requires_grad=True),)
Output: tensor([2., 4., 6.], grad_fn=<MulBackward0>)

从中我们可以看到,这里的Input还是执行forward之前的input,但是outputs是执行forward之后的outputs,从打印的------forward-------位置可以知道,这里的forward函数是在执行之后调用的hook

我们可以使用hook实现torchsummary类似的功能,查看resnet18的各个层的输出情况,如下例

import torch
from torch import nn
from torchvision.models import resnet18class Visualize(nn.Module):def __init__(self, model) -> None:super().__init__()self.model = model# Register a hook for each layerfor name, layer in self.model.named_children():# add a property dynamicallylayer.name = name# module.name is the newly added propertylayer.register_forward_hook(lambda module, inputs, outputs:print(f"{module.name}".ljust(10), '-->', f'{outputs.shape}'))def forward(self, x):return self.model(x)model = resnet18()
inputs = torch.randn(1, 3, 224, 224)
vis = Visualize(model)
output = vis(inputs)

输出:

conv1      --> torch.Size([1, 64, 112, 112])
bn1        --> torch.Size([1, 64, 112, 112])
relu       --> torch.Size([1, 64, 112, 112])
maxpool    --> torch.Size([1, 64, 56, 56])
layer1     --> torch.Size([1, 64, 56, 56])
layer2     --> torch.Size([1, 128, 28, 28])
layer3     --> torch.Size([1, 256, 14, 14])
layer4     --> torch.Size([1, 512, 7, 7])
avgpool    --> torch.Size([1, 512, 1, 1])
fc         --> torch.Size([1, 1000])

如果使用使用applyhook进行注册,apply会递归地将model里面的所有layer都进行相同的操作,于是结果就和for name, layer in self.model.named_modules()类似。

import torch
from torch import nn
from torchvision.models import resnet18def hook(module, inputs, outputs):print(module.__class__.__name__.ljust(10), end='')print(outputs.shape)def register(module):if isinstance(module, nn.Conv2d):module.register_forward_hook(hook)model = resnet18()
inputs = torch.randn(1, 3, 224, 224)
# 这里的apply会递归地把所有层都遍历,因此register_forward_hook注册到的层
# 是所有的Conv2d,包括子层,子层中的子层...
model.apply(register)
outputs = model(inputs)

输出为:

Conv2d    torch.Size([1, 64, 112, 112])
Conv2d    torch.Size([1, 64, 56, 56])
Conv2d    torch.Size([1, 64, 56, 56])
Conv2d    torch.Size([1, 64, 56, 56])
Conv2d    torch.Size([1, 64, 56, 56])
Conv2d    torch.Size([1, 128, 28, 28])
Conv2d    torch.Size([1, 128, 28, 28])
Conv2d    torch.Size([1, 128, 28, 28])
Conv2d    torch.Size([1, 128, 28, 28])
Conv2d    torch.Size([1, 128, 28, 28])
Conv2d    torch.Size([1, 256, 14, 14])
Conv2d    torch.Size([1, 256, 14, 14])
Conv2d    torch.Size([1, 256, 14, 14])
Conv2d    torch.Size([1, 256, 14, 14])
Conv2d    torch.Size([1, 256, 14, 14])
Conv2d    torch.Size([1, 512, 7, 7])
Conv2d    torch.Size([1, 512, 7, 7])
Conv2d    torch.Size([1, 512, 7, 7])
Conv2d    torch.Size([1, 512, 7, 7])
Conv2d    torch.Size([1, 512, 7, 7])

apply将所有的Conv2d都注册了,所以输出了所有的Conv2d的输出shape

3.torch.nn.Module.register_backward_hook

在了解了前一个hook的用法之后,这个hook的作用也就不言而喻了,在backward之后执行,这里的hook函数签名如下

def hook_fn(module, grad_in, grad_out):pass

输入参数包括三个,分别是modulegrad_ingrad_out,其中,grad_ingrad_out分别指代当前模块的输入和输出的梯度信息,若grad_ingrad_out包括多个输入输出,则grad_ingrad_out以元组形式呈现。

现在使用会register_backward_hook爆出警告:

module.py:1352: UserWarning: Using a non-full backward hook when the forward contains multiple autograd Nodes is deprecated and will be removed in future versions. This hook will be missing some grad_input. Please use register_full_backward_hook to get the documented behavior.warnings.warn("Using a non-full backward hook when the forward contains multiple autograd Nodes "

解决办法就是使用新的hook函数register_full_backward_hook,新的hook函数功能更加强大,不仅仅包括模块的输入输出梯度信息,还包括内部的一些其他变量的梯度信息,但是register_backward_hookregister_full_backward_hook两者之间的兼容性并不是很完美。

示例

import torch
from torch import nn
from torchvision.models import resnet18def hook_fn(module, grad_in, grad_out):# 当前module的输入和输出梯度# 若module有多个输入,则grad_in为一个元组# y = wx+bprint(module.__class__.__name__)print("------------Input Grad------------")# 容错处理,部分元组中的变量会是Nonefor grad in grad_in:try:print(grad.shape)except AttributeError: print ("None found for Gradient")print("------------Output Grad------------")for grad in grad_out:  try:print(grad.shape)except AttributeError: print ("None found for Gradient")print("\n")net = resnet18()
for name, layer in net.named_children():# 每一个大的子层都注册一个勾子函数layer.register_backward_hook(hook_fn)# 为了能够执行backward,构建一些虚拟的输入输出
dummy_inputs = torch.randn(10, 3, 224, 224)
dummy_labels = torch.randint(0, 1001, (10, ))
loss_fn = nn.CrossEntropyLoss()y_hat = net(dummy_inputs)loss = loss_fn(y_hat, dummy_labels)
loss.backward()

输出:

module.py:1352: UserWarning: Using a non-full backward hook when the forward contains multiple autograd Nodes is deprecated and will be removed in future versions. This hook will be missing some grad_input. Please use register_full_backward_hook to get the documented behavior.warnings.warn("Using a non-full backward hook when the forward contains multiple autograd Nodes "Linear
------------Input Grad------------
torch.Size([1000])
torch.Size([10, 512])
torch.Size([512, 1000])
------------Output Grad------------
torch.Size([10, 1000])AdaptiveAvgPool2d
------------Input Grad------------
torch.Size([10, 512, 7, 7])
------------Output Grad------------
torch.Size([10, 512, 1, 1])Sequential
------------Input Grad------------
torch.Size([10, 512, 7, 7])
------------Output Grad------------
torch.Size([10, 512, 7, 7])Sequential
------------Input Grad------------
torch.Size([10, 256, 14, 14])
------------Output Grad------------
torch.Size([10, 256, 14, 14])Sequential
------------Input Grad------------
torch.Size([10, 128, 28, 28])
------------Output Grad------------
torch.Size([10, 128, 28, 28])Sequential
------------Input Grad------------
torch.Size([10, 64, 56, 56])
------------Output Grad------------
torch.Size([10, 64, 56, 56])MaxPool2d
------------Input Grad------------
torch.Size([10, 64, 112, 112])
------------Output Grad------------
torch.Size([10, 64, 56, 56])ReLU
------------Input Grad------------
torch.Size([10, 64, 112, 112])
------------Output Grad------------
torch.Size([10, 64, 112, 112])BatchNorm2d
------------Input Grad------------
torch.Size([10, 64, 112, 112])
torch.Size([64])
torch.Size([64])
------------Output Grad------------
torch.Size([10, 64, 112, 112])Conv2d
------------Input Grad------------
None found for Gradient
torch.Size([64, 3, 7, 7])
None found for Gradient
------------Output Grad------------
torch.Size([10, 64, 112, 112])

最上面是警告信息可以忽略,然后根据backward的路径,从后往前进行返回。

使用如下代码查看resnet18的层级情况:

for name, layer in net.named_children():print(name)

输出:

conv1
bn1
relu
maxpool
layer1
layer2
layer3
layer4
avgpool
fc

可以看到这里的10个层对应上面hook函数返回的10个层。

综合以上两个部分,用一个示例演示同时构建前向和后向勾子函数:

import torch
import torch.nn as nn# 前向钩子示例
def forward_hook(module, input, output):print("{} forward hook:".format(module.__class__.__name__))print("Input:", input)print("Output:", output)print("")# 反向钩子示例
def backward_hook(module, grad_input, grad_output):print("{} backward hook:".format(module.__class__.__name__))print("Gradient input:")for item in grad_input:if item is not None:print(item.shape)print("Gradient output:")for item in grad_output:if item is not None:print(item.shape)print("")# 示例模型
class SimpleModel(nn.Module):def __init__(self):super(SimpleModel, self).__init__()self.fc1 = nn.Linear(10, 20)self.fc2 = nn.Linear(20, 1)def forward(self, x):x = torch.relu(self.fc1(x))x = self.fc2(x)return x# 示例
model = SimpleModel()# 注册前向钩子
hook_handle = model.fc1.register_forward_hook(forward_hook)# 注册反向钩子
hook_handle2 = model.fc1.register_backward_hook(backward_hook)# 示例输入数据
input_data = torch.randn(1, 10)# 前向传播
output = model(input_data)# 反向传播
loss = output.sum()
loss.backward()# 移除钩子
hook_handle.remove()
hook_handle2.remove()

输出:

Linear forward hook:
Input: (tensor([[-1.6549, -1.1471, -0.2341,  0.1456,  0.6528, -1.0562,  0.1078,  0.9752,0.8794,  1.0463]]),)
Output: tensor([[-0.6406,  0.0515,  0.1893, -0.5211, -0.2393,  0.2923,  0.0143,  0.6929,-0.4688, -0.1708, -0.6461,  0.5460, -0.1515, -0.1707, -0.5409, -0.6382,-0.9836,  0.3446,  0.2147, -0.7682]], grad_fn=<AddmmBackward0>)Linear backward hook:
Gradient input:
torch.Size([20])
torch.Size([10, 20])
Gradient output:
torch.Size([1, 20])

使用hook机制可视化resnet的特征图输出

import cv2
from torchvision import transforms
from torchvision.models import ResNet18_Weights, resnet18
import torch
import matplotlib.pyplot as pltdef viz(name):def imshow(module, input, output):feature_maps = input[0]# feature map dimension:# (batch_size, ch, width, height)# visualize 4 channels at mostmax_ch = min(feature_maps.size(1), 4)imgs = feature_maps[0, :max_ch, :, :]# print(imgs.shape)plt.figure(figsize=(12, 2))for i, img in enumerate(imgs):plt.subplot(1, 4, i+1)# plt.imshow(img.cpu(), cmap='gray')plt.imshow(img.cpu())plt.axis('off')if i == 0:plt.title(name)plt.show()return imshowdef main():trans = transforms.Compose([transforms.ToPILImage(),transforms.Resize((224, 224)),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225])])device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")model = resnet18(weights=ResNet18_Weights.IMAGENET1K_V1).to(device)for name, module in model.named_modules():# 这里只对卷积层的feature map进行显示if isinstance(module, torch.nn.Conv2d):module.register_forward_hook(viz(name))img = cv2.imread(r'faces\ftw1.jpg')img = trans(img).unsqueeze(0).to(device)with torch.no_grad():model(img)main()

输出示例:

在这里插入图片描述
在这里插入图片描述

总结:

  1. 勾子函数可以在不修改源代码的情况下实现功能的注入
  2. 实现过程需要重写对应的勾子函数,需要注意执行的顺序以及参数的含义
    • register_forward_hook:在forward函数之后执行,输入参数为inputoutput,其中inputforward函数之前的输入,outputforwad函数之后的输入。这个勾子函数一般用于可视化特征图
    • register_backward_hook:在执行backward之时执行,backward到哪一个层就执行哪一个层的勾子函数,需要注意的是,输入参数分别为当前层的梯度输入和梯度输出,也即grad_input, grad_output,再者,使用该函数不能有原地修改的操作,否则会报异常。

参考内容

  • 一文搞懂PyTorch Hook
  • Pytorch官方文档
    PyTorch Hook用法解析

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

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

相关文章

react-navigation:

我的仓库地址&#xff1a;https://gitee.com/ruanjianbianjing/bj-hybrid react-navigation&#xff1a; 学习文档&#xff1a;https://reactnavigation.org 安装核心包: npm install react-navigation/native 安装react-navigation/native本身依赖的相关包: react-nativ…

时序预测 | Matlab实现SSA-BP麻雀算法优化BP神经网络时间序列预测

时序预测 | Matlab实现SSA-BP麻雀算法优化BP神经网络时间序列预测 目录 时序预测 | Matlab实现SSA-BP麻雀算法优化BP神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现SSA-BP麻雀算法优化BP神经网络时间序列预测&#xff08;完整源码和数据…

工业镜头常用参数之实效F(Fno.)和像圈

Fno. 工业镜头中常用到的参数F&#xff0c;有时候用F/#&#xff0c;Fno.来表示&#xff0c;指的是镜头通光能力的参数。它可用镜头焦距及入瞳直径来表示&#xff0c;也可通过镜头数值孔径&#xff08;NA&#xff09;和光学放大倍率&#xff08;β&#xff09;来计算。有效Fno.…

maven的依赖继承

先说一下创建子maven工程的步骤 继承 继承的作用&#xff1a;在父工程中&#xff0c;统一管理项目中的依赖信息&#xff0c;进行统一的版本控制 继承的背景是&#xff1a;对一个大型的项目进行了模块拆分&#xff0c;一个project下&#xff0c;创建了很多的module&#xff0c…

golang grpc和protobuf的版本降级问题(version4 -> version3)

最后更新于2024年3月28日 10:57:52 简中没查到类似的文章。一点小事闹麻了&#xff0c;搞了一天&#xff0c;特意发出来造福大家。 所谓的版本就是下面这个东西proto.ProtoPackageIsVersion4或者proto.ProtoPackageIsVersion3&#xff1a; 目的 为了适配旧代码&#xff0c…

linux shell命令(进程管理、用户管理)

一、进程的概念 主要有两点&#xff1a; 1.进程是一个实体。每一个进程都有它自己的地址空间&#xff0c;一般情况下&#xff0c;包括文本区域&#xff08;text region&#xff09;、数据区域&#xff08;data region&#xff09;和堆栈&#xff08;stack region&#xff09;…

百度智能小程序源码系统简洁版 SEO关键词排名推广优化 带完整的安装代码包以及搭建教程

移动互联网的快速发展&#xff0c;小程序以其轻量级、无需下载、即用即走的特点&#xff0c;迅速成为了各大平台争相推广的重要产品形态。百度智能小程序作为百度生态下的重要一环&#xff0c;凭借其强大的流量入口和丰富的功能组件&#xff0c;为开发者提供了广阔的创作空间。…

fastadmin学习05-开启debug以及配置

FastAdmin 框架提供了对 .env 环境变量配置的支持&#xff0c;并附带一个默认示例文件 .env.sample。在安装后&#xff0c;框架并不会自动启用 env 环境变量&#xff0c;需要手动将 .env.sample 复制为 .env 并进行配置。 如果不开启.env会读取database.php中的配置 下面测试…

基于单片机智能可控电源系统设计

**单片机设计介绍&#xff0c;基于单片机智能可控电源系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机智能可控电源系统设计的主要目标是实现电源的智能控制、监测和保护功能&#xff0c;以满足不同应用场景下…

计算机网络:物理层 - 信道极限容量

计算机网络&#xff1a;物理层 - 信道极限容量 实际信道中的数字信号奈式准则香农公式练习 实际信道中的数字信号 信号在传输过程中会受到各种因素的影响&#xff0c;如图所示&#xff1a; 这是一个数字信号&#xff0c;当它通过实际的信道后&#xff0c;波形会产生失真&#…

LLM2LLM: Boosting LLMs with Novel Iterative Data Enhancement

LLM2LLM: Boosting LLMs with Novel Iterative Data Enhancement 相关链接&#xff1a;arXiv GitHub 关键字&#xff1a;LLM、Data Augmentation、Fine-tuning、NLP、Low-data Regime 摘要 预训练的大型语言模型&#xff08;LLMs&#xff09;目前是解决绝大多数自然语言处理任…

如何选择最适合Shopee店铺的支付方式?

Shopee平台为卖家提供了多元化的收款选项&#xff0c;包括了在线支付、虚拟账户余额支付以及线下支付方式。卖家在选择收款方式时&#xff0c;必须充分考虑到市场适应性这一关键因素。因为不同地区和不同国家的消费者对于支付方式有着不同的偏好和习惯&#xff0c;因此&#xf…

零基础入门数据挖掘系列之「特征工程」

摘要&#xff1a;对于数据挖掘项目&#xff0c;本文将学习应该从哪些角度做特征工程&#xff1f;从哪些角度做数据清洗&#xff0c;如何对特征进行增删&#xff0c;如何使用PCA降维技术等。 特征工程&#xff08;Feature Engineering&#xff09;对特征进行进一步分析&#xf…

AI+软件工程:10倍提效!用ChatGPT编写系统功能文档

系统功能文档是一种描述软件系统功能和操作方式的文档。它让开发团队、测试人员、项目管理者、客户和最终用户对系统行为有清晰、全面的了解。 通过ChatGPT&#xff0c;我们能让编写系统功能文档的效率提升10倍以上。 ​《Leetcode算法刷题宝典》一位阿里P8大佬总结的刷题笔记…

深入理解PHP+Redis实现分布式锁的相关问题

概念 PHP使用分布式锁&#xff0c;受语言本身的限制&#xff0c;有一些局限性。 通俗理解单机锁问题&#xff1a;自家的锁锁自家的门&#xff0c;只能保证自家的事&#xff0c;管不了别人家不锁门引发的问题&#xff0c;于是有了分布式锁。分布式锁概念&#xff1a;是针对多个…

通过Caliper进行压力测试程序,且汇总压力测试问题解决

环境要求 第一步. 配置基本环境 部署Caliper的计算机需要有外网权限;操作系统版本需要满足以下要求:Ubuntu >= 16.04、CentOS >= 7或MacOS >= 10.14;部署Caliper的计算机需要安装有以下软件:python 2.7、make、g++(gcc-c++)、gcc及git。第二步. 安装NodeJS # …

RegSeg 学习笔记(待完善)

论文阅读 解决的问题 引用别的论文的内容 可以用 controlf 寻找想要的内容 PPM 空间金字塔池化改进 SPP / SPPF / SimSPPF / ASPP / RFB / SPPCSPC / SPPFCSPC / SPPELAN &#xfffc; ASPP STDC&#xff1a;short-term dense concatenate module 和 DDRNet SE-ResNeXt …

初识React(一)从井字棋游戏开始

写在前面&#xff1a; 磨磨唧唧了好久终于下定决心开始学react&#xff0c;刚刚接触感觉有点无从下脚...新的语法新的格式跟vue就像两种物种...倒是很好奇路由和store是怎么实现的了~v~&#xff0c;一点一点来吧&#xff01;&#xff01;&#xff01; (一)创建项目 使用vite…

Reactor设计模式和Reactor模型

Reactor设计模式 翻译过来就是反应堆&#xff0c;所以Reactor设计模式本质是基于事件驱动。 角色 Handle&#xff08;事件&#xff09;EventHandler&#xff08;事件处理器&#xff09;ConcreteEventHandler&#xff08;具体事件处理器&#xff09;Synchronous Event Demult…

QT实现蒙层效果

一.蒙层的作用 1.为了其他窗口不被误操作&#xff0c;禁止对其他窗口操作 二.应用场景 1.一些触摸屏设备上弹出一个dialog窗口&#xff0c;在操作这个窗口的时候不希望后面的窗口被误操作 2.之前做一个医疗设备就曾有过这种需求&#xff0c;因为医疗设备对安全性要求非常高&…