【Yolov系列】Yolov5学习(一)补充2:Focus模块详解

一、相关知识

Focus模块是一种用于特征提取的卷积神经网络层,用于将输入特征图中的信息进行压缩和组合,从而提取出更高层次的特征表示,它被用作网络中的第一个卷积层,用于对输入特征图进行下采样,以减少计算量和参数量。

1、下采样相关知识

1. 下采样

下采样就是一种缩小图像的手法,用来降低特征的维度并保留有效信息,一定程度上避免过拟合,都是以牺牲部分信息为代价,换取数据量的减少。下采样就是池化操作。但是池化的目的不仅如此,还需要考虑旋转、平移、伸缩不变形等。采样有最大值采样,平均值采样,随机区域采样等,对应池化:比如最大值池化,平均值池化,随机池化等。

卷积神经网络中,卷积是最基本的模块,在W*H*C的图像操作中,卷积就是输入图像区域和滤波器进行内积求和的过程,卷积就是一种下采样的方式。具体操作如下:

2. 常见下采样操作

(1)采用stride为2的池化层

  • 最大值池化(Max-pooling):对邻域内特征点取最大,类似锐化,突出滑窗内的细节点(特殊点)。
  • 平均值池化(Average-pooling):对邻域内特征点只求平均,有点像平滑滤波,根据滑窗的尺寸控制下采样的力度,尺寸越大,它的采样率越高,但边缘信息损失越大。

(2)采用stride为2的卷积层

  • 下采样的过程是一个信息损失的过程,而池化层是不可学习的,用stride为2的可学习卷积层来代替pooling可以得到更好的效果,当然同时也增加了一定的计算量。

3. 下采样的作用

下采样实际上就是缩小图像,主要目的是为了使得图像符合显示区域的大小,生成对应图像的缩略图。比如说在CNN中的池化层或卷积层就是下采样。不过卷积过程导致的图像变小是为了提取特征,而池化下采样是为了降低特征的维度。

下采样层有两个作用:

  1. 减少计算量,防止过拟合;
  2. 增大感受野,使得后面的卷积核能够学到更加全局的信息。

2、上采样相关知识

1. 上采样

在卷积神经网络中,由于输入图像通过卷积神经网络(CNN)提取特征后,输出的尺寸往往会变小,而有时我们需要将图像恢复到原来的尺寸以便进行进一步的计算(如图像的语义分割),这个使图像由小分辨率映射到大分辨率的操作,叫做上采样。

2. 常见上采样操作

常见的上采样操作有反卷积(Deconvolution,也称转置卷积)、上池化(UnPooling)方法、双线性插值(各种插值算法)。具体如下:

  1. 插值。一般使用的是双线性插值,因为效果最好,虽然计算上比其他插值方式复杂,但是相对于卷积计算可以说不值一提,其他插值方式还有最近邻插值、三线性插值等;
  2. 转置卷积又或是说反卷积(Transpose Conv)。通过对输入feature map间隔填充0,再进行标准的卷积计算,可以使得输出feature map的尺寸比输入更大;
  3. 上池化(UpPooling)。 最大值向上池化(Max Unpooling) 、均值向上池化( Avg Unpooling),在对称的max pooling位置记录最大值的索引位置,然后在unpooling阶段时将对应的值放置到原先最大值位置,其余位置补0;

unpooling的操作与unsampling类似,区别是unpooling记录了原来pooling是取样的位置,在unpooling的时候将输入feature map中的值填充到原来记录的位置上,而其他位置则以0来进行填充。

3. 上采样作用

上采样实际上就是放大图像,指的是任何可以让图像变成更高分辨率的技术。

3、Focus前身:Yolov2中的PassThrough层

  • Yolov2中的passthrough层的本质是特征重排,将相邻的特征堆积在不同的通道中,这样可以将大尺度特征图下采样后与小尺度特征图进行融合,进而增加了小目标检测的精确度。

  • 一般而言,两个特征层要从通道上进行拼接,需要保持空间大小一致。比如(26,26,512)的特征层要和(13,13,1024)的特征层在通道维度上进行拼接,那么需要将空间维度的26x26下采样成13*13,或者13*13上采样成26*26。最简单的做法就是通过池化下采样即可,但是Yolov2为了保留feature map的更多细节,在空间维度上进行拆分(见图中红绿部分所示),得到4个13*13*512的特征层,然后将这4个特征层在通道维度上拼接(concat)成一个13*13*2048的特征层。最后就可以将(13,13,1024)和(13,13,2048)两个特征层在通道维度上进行拼接就得到(13,13,3072)。
  • 看操作与Focus好像是一样的,改了个名字?

二、Focus层

1、Focus层原理

  • Focus层在YOLOv5中是图片进入Backbone前,对图片进行切片操作,原理与Yolov2的passthrough层类似,采用切片操作把高分辨率的图片(特征图)拆分成多个低分辨率的图片或特征图,即隔列采样+拼接
  • 具体操作是在一张图片中每隔一个像素拿到一个值,类似于邻近下采样,这样就拿到了四张图片,四张图片互补,长得差不多,但是没有信息丢失,这样一来,将W、H信息就集中到了通道空间,输入通道扩充了4倍,即拼接起来的图片相对于原先的RGB三通道模式变成了12个通道,最后将得到的新图片再经过卷积操作,最终得到了没有信息丢失情况下的二倍下采样特征图。
  • 以YOLOv5s为例,原始的640 × 640 × 3的图像输入Focus结构,采用切片操作,先变成320 × 320 × 12的特征图,拼接(Concat)后,再经过一次卷积(CBL,后改为SiLU(CBS))操作,最终变成320 × 320 × 32的特征图。

切片操作如下:

2、代码分析

Focus层及相关代码如下:

def autopad(k, p=None):  # kernel, padding自动填充的设计,更加灵活多变
    # Pad to 'same'
    if p is None:
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]  
# auto-pad自动填充,通过自动设置填充数p
        #如果k是整数,p为k与2整除后向下取整;如果k是列表等,p对应的是列表中每个元素整除2。
    return p
 
class Conv(nn.Module):
    # 这里对应结构图部分的CBL,CBL = conv+BN+Leaky ReLU,后来改成了SiLU(CBS)
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super().__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2) 
#将其变为均值为0,方差为1的正态分布,通道数为c2
        self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
#其中nn.Identity()是网络中的占位符,并没有实际操作,在增减网络过程中,可以使得整个网络层数据不变,便于迁移权重数据;nn.SiLU()一种激活函数(S形加权线性单元)。
 
    def forward(self, x):#正态分布型的前向传播
        return self.act(self.bn(self.conv(x)))
 
    def forward_fuse(self, x):#普通前向传播
        return self.act(self.conv(x))
 
class Focus(nn.Module):
    # Focus wh information into c-space
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super().__init__()
        self.conv = Conv(c1 * 4, c2, k, s, p, g, act)
        # self.contract = Contract(gain=2)
 
    def forward(self, x):  # x(b,c,w,h) -> y(b,4c,w/2,h/2)
        return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1)) 
        #图片被分为4块。x[..., ::2, ::2]即行按2叠加取,列也是,对应上面原理图的“1”块块), x[..., 1::2, ::2]对应“3”块块,x[..., ::2, 1::2]指“2”块块,x[..., 1::2, 1::2]指“4”块块。都是每隔一个采样(采奇数列)。用cat连接这些采样图,生成通道数为12的特征图
        # return self.conv(self.contract(x))
  1. 先采取切片操作(x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2] ),把图片分成1,3,2,4共4块(如上面的原理图);
  2. 然后进行一个连接(concat);
  3. 最后再来一次卷积,这里的卷积是自定义卷积:先进行一次卷积,然后变化成正态分布,最后来个SiLU激活,即CBS=Conv+BN+SiLU。

3、Focus层变化

在Yolov5较新版本源码(如Yolov5-6.2、Yolov5-7.0)中,代码中存在Focus层但是在Yolov5s、Yolov5l、Yolov5m、Yolov5x中均没有使用,以Yolov5s网络结构为例:

1. 旧版Yolov5s网络结构:

  • Focus层是3*3*32的结构,输入通道为3通道,输出通道数为32。
  • 作用增大感受野的同时降低运算量。如果没有初始的切片操作,直接使用一个3*3的卷积,那么感受野相对会小了一半;如果直接使用一个6*6的卷积,同样是输入3通道,输出32通道,那么运算量会是前面的4倍。
  • Focus层的具体结构如下:

以分辨率为640*640的三通道图像为例:

  • Focus切片操作把紫色的归第1组,绿色的归第2组,蓝色的归第3组,红色的归第4组,每组仍然是3个通道,然后把4组排在一起(按第1个维度合并(从第0维度开始算),每组都是一个(b,3,320,320)的张量,b就是批大小),就得到了12个通道的输入,即(b,12,320,320);

  • 使用一个输入通道12,输出通道32的3*3卷积运算(按yolov5s算),输出分辨率还是320乘320。

2. Yolov5-7.0的Yolov5s网络结构:

Focus层原本处于整个网络结构的第一层,现在已经被蓝框中的6*6卷积层替换掉了。

注意:这里Yolov5s的第一个卷积层的输出通道数并不是64,在s、l、m、x的yaml文件中写的均为64,但是实际的输出通道数还要乘以上面的width_multiple:0.50,这个值在几个yaml中是不一样的,它控制了不同规格的模型的通道数。所以在yolov5s中第一个卷积层的输出通道数是64*0.5=32。

Yolov5-7.0中使用一个6*6且步长为2的卷积替换了Focus层,具体过程参考issue: https://github.com/ultralytics/yolov5/issues/4825

具体流程为:

在Yolov5目录下新建一个py文件,命名为Focus_test.py(自己随意命名,无所谓),直接运行Focus_test.py文件(注意,这里的输入通道数是3,输出通道数写的是64)

  1. 使用代码实例化一个Focus层。
  2. 将Focus层的卷积层参数拷贝给一个6乘6步长2的卷积层
  3. 给Focus层和6*6步长为2的卷积层以相同的输入,比较它们的输出。使用用torch.allclose函数,精度设为10的负6次方,因为浮点数计算顺序不同,可能结果是有误差的,比较一下两者的性能
import torch
from models.common import Focus, Conv
from utils.torch_utils import profile

# 实例化一个Focus层。输入通道为3,输出通道为64,卷积核大小为3*3
focus = Focus(3, 64, k=3).eval()
# 实例化一个卷积层。输入通道为3,输出通道为64,卷积核大小为6*6,步长为2,padding为2
conv = Conv(3, 64, k=6, s=2, p=2).eval()

# Express focus layer as conv layer
# 将Focus层参数传递给卷积层
conv.bn = focus.conv.bn
conv.conv.weight.data[:, :, ::2, ::2] = focus.conv.conv.weight.data[:, :3]
conv.conv.weight.data[:, :, 1::2, ::2] = focus.conv.conv.weight.data[:, 3:6]
conv.conv.weight.data[:, :, ::2, 1::2] = focus.conv.conv.weight.data[:, 6:9]
conv.conv.weight.data[:, :, 1::2, 1::2] = focus.conv.conv.weight.data[:, 9:12]

# Compare
# 随机一个张量,批大小为16
x = torch.randn(16, 3, 640, 640)
with torch.no_grad():
    # Results are not perfectly identical, errors up to about 1e-7 occur (probably numerical)
    assert torch.allclose(focus(x), conv(x), atol=1e-6)

# Profile
# device选择所使用的设备,GPU还是CPU,我电脑只能用cpu测试
results = profile(input=torch.randn(16, 3, 640, 640), ops=[focus, conv, focus, conv], n=10, device='cpu')

相同的输入,输出结果肯定相同,但对于不同的显卡性能方面可能存在。比如Yolov5作者在V100上测试时分别统计了前向传播和反向传播的时间,4行记录分别是focus,conv,focus,conv。在batch-size为16和1的情况下均进行了测试。

下面是我在自己笔记本CPU上进行的测试:

(有条件还是用GPU测试吧,配置不行,不具有参考意义) 

结论:

  1. 计算结果来看,Focus层和6*6步长为2的卷积层等价;
  2. 从性能上来看,性能较好的设备上Conv层比Focus层快,尤其是反向传播上;较老的设备上,Conv层比Focus层慢一些。

3. 替换详解

3.1 前向传播是否等价

分析Focus层和Conv层等价原因,Focus参数拷贝给Conv层方法。

(1)打印Focus层和Conv层的参数尺寸

# Express focus layer as conv layer
# 将Focus层参数传递给卷积层
conv.bn = focus.conv.bn
conv.conv.weight.data[:, :, ::2, ::2] = focus.conv.conv.weight.data[:, :3]
conv.conv.weight.data[:, :, 1::2, ::2] = focus.conv.conv.weight.data[:, 3:6]
conv.conv.weight.data[:, :, ::2, 1::2] = focus.conv.conv.weight.data[:, 6:9]
conv.conv.weight.data[:, :, 1::2, 1::2] = focus.conv.conv.weight.data[:, 9:12]
print(focus.conv.conv.weight.data.shape)
print(conv.conv.weight.data.shape)

  • Focus层输入通道为12,输出通道为64,卷积核大小为3*3。由输入通道为12,那么需要有12个3*3的卷积核,输出通道为64,那么要有64*12个3*3的卷积核,所以卷积层参数的形状为(64,12,3,3)。
  • Conv层输入通道为3,输出通道为64,卷积核大小为6*6。由输入通道为3,那么需要有3个6*6的卷积核,输出通道为64,那么要有64*3个6*6的卷积核,所以卷积层参数的形状为(64,3,6,6)。
  • 两者的输出通道相同,参数数量相同,区别在于输入通道和卷积核大小。因此只需要关心64组卷积核中的一组如何传递即可,对于Focus层,一组就是12个3*3的卷积核;对于Conv层来说,一组就是3个6*6的卷积核。

(2)卷积过程对比

1. Focus的卷积层输入是12*320*320的图像,如图:

  • 12个3*3的卷积核如下,这12个卷积核与输入图像的12个通道是对应的,各卷各的,相互独立。即既紫色只跟紫色、绿色只跟绿色、蓝色只跟蓝色、红色只跟红色。(卷积核也按4组分了颜色,方便后续指明对应关系)

  • 将Focus层的12个卷积核合并成3个,将12个通道的输入图还原成3通道的输入图

2. Conv中6*6且步长为2的卷积输入是3*640*640的图像,如图:

  • 3个6*6的卷积核如下,步长为2,且3个卷积核跟图像中3个对应的通道进行卷积,各卷各的,相互独立。其次,由于步长为2,(对应通道)卷积核中紫色部分只会去跟图像中的紫色部分进行卷积,绝对不会跟绿色、蓝色、红色卷积。同理绿色只跟绿色、蓝色只跟蓝色、红色只跟红色。

3. 若1和2均只按照“紫色只跟紫色、绿色只跟绿色、蓝色只跟蓝色、红色只跟红色”的原则来进行卷积的话,那么计算过程就是完全等价的

  • 所以两者的卷积过程是等价的,这个卷积参数的拷贝代码跟下图右边的图示是对应的。
conv.bn = focus.conv.bn
conv.conv.weight.data[:, :, ::2, ::2] = focus.conv.conv.weight.data[:, :3]
conv.conv.weight.data[:, :, 1::2, ::2] = focus.conv.conv.weight.data[:, 3:6]
conv.conv.weight.data[:, :, ::2, 1::2] = focus.conv.conv.weight.data[:, 6:9]
conv.conv.weight.data[:, :, 1::2, 1::2] = focus.conv.conv.weight.data[:, 9:12]

3.2 反向传播是否等价

答案是等价的。因为卷积层的梯度就是由它的输入决定的,卷积就是乘法和加法运算,前向传播运算过程完全等价,那么梯度当然是一样的。用代码验证一下:

import torch
from models.common import Focus, Conv
from copy import deepcopy
 
focus = Focus(3, 64, k=3).train()
focus2 = deepcopy(focus).train()
conv = Conv(3, 64, k=6, s=2, p=2).train()
 
# Express focus layer as conv layer
conv.bn = deepcopy(focus.conv.bn)
conv.conv.weight.data[:, :, ::2, ::2] = deepcopy(focus.conv.conv.weight.data[:, :3])
conv.conv.weight.data[:, :, 1::2, ::2] = deepcopy(focus.conv.conv.weight.data[:, 3:6])
conv.conv.weight.data[:, :, ::2, 1::2] = deepcopy(focus.conv.conv.weight.data[:, 6:9])
conv.conv.weight.data[:, :, 1::2, 1::2] = deepcopy(focus.conv.conv.weight.data[:, 9:12])
 
# Compare
x = torch.randn(16, 3, 640, 640, requires_grad=False)
 
with torch.no_grad():
    # Results are not perfectly identical, errors up to about 1e-7 occur (probably numerical)
    assert torch.allclose(focus(x), conv(x), atol=1e-6)
 
label = torch.randn(16, 64, 320, 320, requires_grad=False)
 
optimizer1 = torch.optim.SGD(focus.parameters(), lr=0.001, momentum=0.9, nesterov=True)
# optimizer1 = smart_optimizer(focus, 'SGD')
optimizer1.zero_grad()
loss1 = torch.mean(focus(x) - label)  # 要想计算loss,得有个标量输出,所以这里mean了一下。注意,你要是用sum,就会导致误差变大,最终梯度就不等了哦
loss1.backward()
optimizer1.step()
 
 
optimizer2 = torch.optim.SGD(conv.parameters(), lr=0.001, momentum=0.9, nesterov=True)
optimizer2.zero_grad()
loss2 = torch.mean(conv(x) - label)  # 同上
loss2.backward()
optimizer2.step()
 
print(f'loss1: {loss1.item():.10f}, loss2: {loss2.item():.10f}')
 
equivalent_grad = torch.zeros(64, 3, 6, 6, dtype=torch.float32)
equivalent_grad[:, :, ::2, ::2] = deepcopy(focus.conv.conv.weight.grad[:, :3])
equivalent_grad[:, :, 1::2, ::2] = deepcopy(focus.conv.conv.weight.grad[:, 3:6])
equivalent_grad[:, :, ::2, 1::2] = deepcopy(focus.conv.conv.weight.grad[:, 6:9])
equivalent_grad[:, :, 1::2, 1::2] = deepcopy(focus.conv.conv.weight.grad[:, 9:12])
 
assert torch.allclose(equivalent_grad, conv.conv.weight.grad, atol=1e-6)
print('梯度等价')

三、参考文章

Yolov5中的Focus层

(五)目标检测yolov2

上采样、下采样到底是什么?

yolov5 Focus模块详解

yolov5源码解析(0)--focus层哪去了?

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

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

相关文章

IS62C256AL-45TLI功能参数介绍及如何优化性能

IS62C256AL-45TLI功能和参数介绍及如何优化性能-公司新闻-配芯易-深圳市亚泰盈科电子有限公司 产品品种:静态随机存取存储器 RoHS:是 存储容量:256 kbit 组织:32 k x 8 访问时刻:45 ns 接口类型:Parallel 电源电压-最大:5.5 V 电源电压-最小:4.5 V 电源电流—最大值:25 mA 最小…

如何高效协作?

前言 上一篇文章我们分享了《如何高效沟通》,本篇文章继续延伸至更复杂的场景,跨团队/跨部门协作。 什么是协同? 协同也会被称为协作合作,无论是哪种类型的团队,无论哪种方式的工作场景,都一定会有协作发…

力扣110. 平衡二叉树

思路:与二叉树最大高度类似,但是这里需要返回 -1 的高度来标识不是平衡二叉树,判断左右子树的高度相差大于1则不平衡,否则就是平衡。 class Solution {public boolean isBalanced(TreeNode root) {int ans func(root);if(ans >…

MySQL主从复制实现高可用性和负载均衡

大家好,我是咕噜铁蛋,今天我想和大家聊聊MySQL主从复制如何帮助我们实现高可用性和负载均衡。在如今的大数据时代,数据库的稳定性和性能成为了企业关注的重点,而MySQL主从复制正是解决这两个问题的重要工具。 一、MySQL主从复制简…

Linux进程和任务管理

目录 一.程序和进程的关系 程序 进程 线程 线程与进程的区别 二.查看进程信息ps 方法一 常用选项 方法二 三.TOP动态查看进程信息 进程信息区各列解释 top常用命令 系统查看命令总结 查看进程信息pgrep 查看进程树pstree 四.控制进程 进程的启动方式 进程的前…

[C语言实现]码林盟主秘籍——《手撕八大排序》

​ ​ 🥰作者: FlashRider 🌏专栏: 初阶数据结构 🍖知识概要:详解八大排序的原理、时间复杂度分析、以及代码实现。 目录 八大排序 插入排序 直接插入排序 希尔插入排序 选择排序 冒泡排序 计数排序 堆排序 快速排序 霍…

Gather:基于 DePIN 体系构建的 Web3 社交生态

“Gather 通过搭建一套基于 DePIN 的 Web3 社交体系,正在成为 SocialFi 革命的早期推动者。” 基于互联网的社交,正在为人们提供了远距离沟通和连接的便利,打破了地理障碍,这种具备包容性、广泛性的线上连接,加速了信息…

Git禁止松散对象loose objects弹窗

打开仓库时,弹窗如图 This repository currently has approximately XXXX loose objects.解决办法:见How to skip “Loose Object” popup when running ‘git gui’ Git v1.7.9 或以上版本,执行git config --global gui.gcwarning false

什么是XXE攻击?如何进行防护

安全性很难做到正确,即使在当今具有安全意识的世界中,也存在一些严重的漏洞,例如 XML 外部实体 (XXE),它们被忽视并最终成为破坏的原因。 XML 外部实体 (XXE) 攻击是一种计算机安全漏洞,通常存在于 Web 应用程序中&…

Uds诊断协议的请求和响应的寻址

一根总线上挂载着很多ECU,那么基于CAN协议UDS的诊断请求报文,诊断仪是如何发给ECU的?如何精准的找到想要诊断的那个ECU?ECU又是如何将诊断响应的报文返回给诊断仪? 在UDS协议中,规定了诊断请求和响应报文发…

kerberos:适配华为FI

文章目录 一、hive1、hive thrift连接方式 一、hive 1、hive thrift连接方式 kerberos认证失败信息 缺少配置:{“hadoop.rpc.protection”:“privacy”},具体可参考:kerbros认证相关问题 华为FI参考资料: https://github.com…

Hive 解决数据倾斜方法

数据倾斜问题, 通常是指参与计算的数据分布不均, 即某个 key 或者某些 key 的数据量远超其他 key, 导致在 shuffle 阶段, 大量相同 key 的数据被发往同一个 Reduce, 进而导致该 Reduce 所需的时间远超其他 Reduce&…

easyui datagrid单元格点击进入编辑时,行会自动向上错位

现象描述,点击第20行可编辑的单元格进入编辑状态时,滚动条自动滚动到第19行了。导致第20行被分页遮挡,看不到无法编辑。 排查了一天百度AI说是滚动定位问题,最后发现是自己设置的列有问题,表格总共五列,全…

mysql面试题八(SQL语句)

目录 1.SQL 基本组成部分 常用操作示例 创建表 插入数据 查询数据 更新数据 删除数据 创建索引 授予用户权限 2.常见的聚合查询 1. 计数(COUNT) 2. 求和(SUM) 3. 平均值(AVG) 4. 最大值&…

4套java智慧型管理系统源码-智慧校园-智慧工地-智慧城管-智慧3D导诊

第一套:Java智慧校园系统源码 智慧学校源码 微信小程序电子班牌 智慧校园系统简介: 智慧校园的建设逐渐被师生、家长认可接受,智慧校园通过对在校师生、教务等所有人员的信息以及各种信息搜集与储存,进行数据优化与管理&#xf…

Formik:让用户体验更加出色的表单解决方案

hi, 大家好,我是徐小夕, 今天又到了我们的博学时间。今天和大家分享一款非常有价值的开源项目——Formik。 这款开源项目也是我研究零代码搭建平台——H5-Dooring 时参考的项目之一,它可以提高表单渲染引擎的性能和效率,构建出性能…

弱口令之暴力破解

目录 前言 弱口令与暴力破解介绍 漏洞挖掘实战专栏 个人介绍 第一关:基于表单的暴力破解 绕过步骤 1.第一步抓包观察 2.使用burp的攻击模块 3.选择攻击模式以及爆破字典 ​编辑 4.进行爆破 第二关 验证码绕过(on server) 绕过步骤 1.观察输入错误观察返回结果 2…

MATLAB中左边的大括号最后一行为什么会留很大的空白——解决

看了一些帖子说改字体,但是并没有什么用,在此给出亲测有效的方法:改变矩阵的行间距 先说一下问题 上图中留有大块空白 **解决办法:**光标放在矩阵上 格式——矩阵——更改矩阵,在矩阵设置中选中“行高相等”&#xff…

网络IO模型 select poll epoll的区别

epoll与select、poll的对比 1. 用户态将文件描述符传入内核的方式 select:创建3个文件描述符集并拷贝到内核中,分别监听读、写、异常动作。这里受到单个进程可以打开的fd数量限制,默认是1024。 poll:将传入的struct pollfd结构…

基于Springboot的社区防疫物资申报系统(有报告)。Javaee项目,springboot项目。

演示视频: 基于Springboot的社区防疫物资申报系统(有报告)。Javaee项目,springboot项目。 项目介绍: 采用M(model)V(view)C(controller)三层体系…