FPN综述保姆级教程
本文是FPN综述教程,先介绍FPN开山之作,含简介、相关工作、结构及基于ResNet18的代码实现。还讲解了PAFPN,其在FPN基础上增加自底向上路径,给出结构与代码。最后阐述BiFPN,包括简介、结构及多特征图示例的代码实现。

FPN综述教程(保姆级)
0 前言
鸽了好久的项目,看平台对FPN介绍不多,我来捡个漏。带大家逐行coding

1 FPN开山
1.1 简介
论文链接Feature Pyramid Networks for Object Detection
特征金字塔是识别系统中用于检测不同尺度目标的基本组件。但是最近的深度学习目标检测器已经开始因为内存与计算密集开始避免使用特征金字塔结构了。在本文中,作者通过利用深度卷积网络内在的多尺度、金字塔分级来构造具有少量额外成本的特征金字塔。开发了一种具有横向连接的自顶向下架构,用于在所有尺度上构建高级语义特征映射。这种称为特征金字塔网络(FPN)的架构在几个应用程序中作为通用特征提取器表现出了显著的改进,是多尺度目标检测的实用和准确的解决方案。1.2 相关工作

1.3 FPN block(这里简短介绍一下理论,下面介绍代码)

为了防止大家迷糊,我这里对上图从新绘制个细节图如下(这里我采用的是resnet18作为特征提取网络)

1.4 代码
这里将带大家逐行code(主讲FPN,resnet一笔带过)In [1]# 导入相关的包import paddleimport paddle.nn.functional as Fimport paddle.nn as nn登录后复制下图为resnet的主要模块

# 构建resnet18的基础模块# Identity模块表示没有任何操作class Identity(nn.Layer): def __init_(self): super().__init__() def forward(self, x): return x# Block模块是构成resnet的主要模块# 通过判断步长(stride == 2)和通道数(in_dim != out_dim)# 判断indentity = self.downsample(h)中,self.downsample()下采样方式class Block(nn.Layer): def __init__(self, in_dim, out_dim, stride): super().__init__() ## 补充代码 self.conv1 = nn.Conv2D(in_dim, out_dim, 3, stride=stride, padding=1, bias_attr=False) self.bn1 = nn.BatchNorm2D(out_dim) self.conv2 = nn.Conv2D(out_dim, out_dim, 3, stride=1, padding=1, bias_attr=False) self.bn2 = nn.BatchNorm2D(out_dim) self.relu = nn.ReLU() if stride == 2 or in_dim != out_dim: self.downsample = nn.Sequential(*[ nn.Conv2D(in_dim,out_dim,1,stride=stride), nn.BatchNorm2D(out_dim)]) else: self.downsample = Identity() def forward(self, x): ## 补充代码 h = x x = self.conv1(x) x = self.bn1(x) x = self.relu(x) x = self.conv2(x) x = self.bn2(x) indentity = self.downsample(h) x = x + indentity x = self.relu(x) return x登录后复制In [3]
# 搭建resnet18主干网络class ResNet18(nn.Layer): def __init__(self, in_dim=64): super().__init__() self.in_dim = in_dim # stem layer self.conv1 = nn.Conv2D(in_channels=3,out_channels=in_dim,kernel_size=3,stride=1,padding=1,bias_attr=False) self.bn1 = nn.BatchNorm2D(in_dim) self.relu = nn.ReLU() #blocks self.layers1 = self._make_layer(dim=64,n_blocks=2,stride=1) self.layers2 = self._make_layer(dim=128,n_blocks=2,stride=2) self.layers3 = self._make_layer(dim=256,n_blocks=2,stride=2) self.layers4 = self._make_layer(dim=512,n_blocks=2,stride=2) def _make_layer(self, dim, n_blocks, stride): layer_list = [] layer_list.append(Block(self.in_dim, dim, stride=stride)) self.in_dim = dim for i in range(1,n_blocks): layer_list.append(Block(self.in_dim, dim, stride=1)) return nn.Sequential(*layer_list) def forward(self, x): # 创建一个存放不同尺度特征图的列表 fpn_list = [] x = self.conv1(x) x = self.bn1(x) x = self.relu(x) x = self.layers1(x) # x [2, 64, 64, 64] fpn_list.append(x) x = self.layers2(x) # x [2, 128, 32, 32] fpn_list.append(x) x = self.layers3(x) # x [2, 256, 16, 16] fpn_list.append(x) x = self.layers4(x) # x [2, 512, 8, 8] fpn_list.append(x) return fpn_list登录后复制In [4]
# FPN构建# fpn_list中包含以下特征维度,对应章节1.3中的图# C2 [2, 64, 64, 64]# C3 [2, 128, 32, 32]# C4 [2, 256, 16, 16]# C5 [2, 512, 8, 8]class FPN(nn.Layer): def __init__(self,in_channel_list,out_channel): super(FPN, self).__init__() self.inner_layer=[] # 1x1卷积,统一通道数 self.out_layer=[] # 3x3卷积,对add后的特征图进一步融合 for in_channel in in_channel_list: self.inner_layer.append(nn.Conv2D(in_channel,out_channel,1)) self.out_layer.append(nn.Conv2D(out_channel,out_channel,kernel_size=3,padding=1)) def forward(self,x): head_output=[] # 存放最终输出特征图 corent_inner=self.inner_layer[-1](x[-1]) # 过1x1卷积,对C5统一通道数操作 head_output.append(self.out_layer[-1](corent_inner)) # 过3x3卷积,对统一通道后过的特征进一步融合,加入head_output列表 print(self.out_layer[-1](corent_inner).shape) for i in range(len(x)-2,-1,-1): # 通过for循环,对C4,C3,C2进行 pre_inner=corent_inner corent_inner=self.inner_layer[i](x[i]) # 1x1卷积,统一通道数操作 size=corent_inner.shape[2:] # 获取上采样的大小(size) pre_top_down=F.interpolate(pre_inner,size=size) # 上采样操作(这里大家去看一下interpolate这个上采样api) add_pre2corent=pre_top_down+corent_inner # add操作 head_output.append(self.out_layer[i](add_pre2corent)) # 3x3卷积,特征进一步融合操作,并加入head_output列表 print(self.out_layer[i](add_pre2corent).shape) return head_output# head_output 中包含以下特征维度,对应章节1.3中的图# P5 [2, 256, 8, 8]# P4 [2, 256, 16, 16]# P3 [2, 256, 32, 32]# P2 [2, 256, 64, 64]登录后复制In [ ]
model = ResNet18()# print(model)# [64,128,256,512]表示输入特征通道数的列表(C2-C5的通道数列表),256表示过FPN后最终通道数(P2-P5的通道数)fpn=FPN([64,128,256,512],256)x = paddle.randn([2, 3, 64, 64])fpn_list = model(x)out = fpn(fpn_list)# print(list(reversed(out)))登录后复制
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/nn/layer/norm.py:653: UserWarning: When training, we now always track global mean and variance. "When training, we now always track global mean and variance.")登录后复制登录后复制
[2, 256, 8, 8][2, 256, 16, 16][2, 256, 32, 32][2, 256, 64, 64]登录后复制
2 PAFPN
2.1 简介
论文链接Path Aggregation Network for Instance SegmentationPANet在FPN的自上向下的路径之后又添加了一个自底向上的路径,通过这个路径PANet得到 (N1-N4) 共4个Feature Map。PANet的融合模块如下图所示


# 构建一个用于下采样的卷积池化模块class ConvNormLayer(nn.Layer): def __init__(self, in_channel, out_channel, kernel_size, stride, padding=1): super(ConvNormLayer, self).__init__() self.conv = nn.Conv2D(in_channel, out_channel, kernel_size, stride, padding) self.norm = nn.BatchNorm2D(out_channel) def forward(self, inputs): out = self.conv(inputs) out = self.norm(out) return out登录后复制In [ ]
# PAFPN构建# fpn_list中包含以下特征维度,对应章节2.1中的图# C2 [2, 64, 64, 64]# C3 [2, 128, 32, 32]# C4 [2, 256, 16, 16]# C5 [2, 512, 8, 8]class PAFPN(nn.Layer): def __init__(self,in_channel_list,out_channel): super(PAFPN, self).__init__() self.fpn = FPN(in_channel_list, out_channel) self.bottom_up = ConvNormLayer(out_channel, out_channel, 3, 2) # 2倍下采样模块 self.inner_layer=[] # 1x1卷积,统一通道数,处理P3-P5的输出,这里要注意P2和P3-P5的输入通道是不同的,可以看2.1图3, self.out_layer=[] # 3x3卷积,对concat后的特征图进一步融合 for i in range(len(in_channel_list)): if i==0: self.inner_layer.append(nn.Conv2D(out_channel, out_channel,1)) # 处理P2 else: self.inner_layer.append(nn.Conv2D(out_channel*2, out_channel,1)) # 处理P3-P5 self.out_layer.append(nn.Conv2D(out_channel,out_channel,kernel_size=3,padding=1)) def forward(self,x): head_output=[] # 存放最终输出特征图 fpn_out = self.fpn(x) # FPN操作 print('------------FPN--PAFPN--------------') # PAFPN操作分割线 corent_inner=self.inner_layer[0](fpn_out[-1]) # 过1x1卷积,对P2统一通道数操作 head_output.append(self.out_layer[0](corent_inner)) # 过3x3卷积,对统一通道后过的特征进一步融合,加入head_output列表 print(self.out_layer[0](corent_inner).shape) for i in range(1,len(fpn_out),1): pre_bottom_up = corent_inner pre_concat = self.bottom_up(pre_bottom_up) # 下采样 pre_inner = paddle.concat([fpn_out[-1-i],pre_concat], 1) # concat corent_inner=self.inner_layer[i](pre_inner) # 1x1卷积压缩通道 head_output.append(self.out_layer[i](corent_inner)) # 3x3卷积进一步融合 print(self.out_layer[i](corent_inner).shape) return head_output# head_output 中包含以下特征维度,对应章节1.3中的图# N2 [2, 256, 64, 64]# N3 [2, 256, 32, 32]# N4 [2, 256, 16, 16]# N5 [2, 256, 8, 8]登录后复制In [ ]model = ResNet18()# print(model)# [64,128,256,512]表示输入特征通道数的列表(C2-C5的通道数列表),256表示过FPN后最终通道数(P2-P5的通道数)pafpn=PAFPN([64,128,256,512],256)x = paddle.randn([2, 3, 64, 64])fpn_list = model(x)out = pafpn(fpn_list)登录后复制
[2, 256, 8, 8][2, 256, 16, 16][2, 256, 32, 32][2, 256, 64, 64]------------FPN--PAFPN--------------[2, 256, 64, 64][2, 256, 32, 32][2, 256, 16, 16][2, 256, 8, 8]登录后复制
3 BiFPN
3.1 简介
论文链接EfficientDet: Scalable and Efficient Object DetectionBiFPN ,在 PANet 简化版的基础上,若输入和输出结点是同一 level 的,则添加一条额外的边,在不增加 cost 的同时融合更多的特征。(注意, PANet 只有一条 top-down path 和一条 bottom-up path ,而本文作者是将 BiFPN 当作一个 feature network layer 来用的,重复多次。如下图所示
3.1 BiFPN block
下图为 BiFPN block
3.2 代码实现步骤图

图中各个模块说明:(这里没有对原论文加入注意力机制进行实现,只是实现了模型结构,并略微改进了构建方式)C(2-5)到P(3-7)下采样块 C_to_P:将C(2-5)四个特征图变为P(3-7)五个特征图(原论文是将3个变为5个,下采样方法一样),图中的P(3-7)块没有任何操作,只是个标识1x1和3x3卷积模块Sepconv_BN_Swish:构建3.1节每个彩色节点模块绿色块BiFPN_block1:这里跟原论文代码构建方式不同,原论文是通过一层层进行搭建,这样限定了输入必须是五个特征图,本项目的搭建方式可以实现n个(3In [5]蓝色块BiFPN_block2:接受BiFPN_block1传出特征图,同样连接方式采用concat方式,下采样采用最大池化方式橙色块BiFPN_block:表示BiFPN_block1、BiFPN_block2共同组合一个BiFPN_block模块,这里第一个BiFPN_block与第二个BiFPN_block输出特征图通道略有不同,后续BiFPN_block(3、4....)特征通道数均与第二个BiFPN_block相同Identity:Identity模块不作任何处理输入=输出(这里使用是方便简化代码的构建)
# 构建每个彩色节点模块,由3x3和1x1卷积构成class Sepconv_BN_Swish(nn.Layer): def __init__(self, in_channels, out_channels=None): super(Sepconv_BN_Swish, self).__init__() if out_channels is None: out_channels = in_channels self.depthwise_conv = nn.Conv2D(in_channels, in_channels, kernel_size=3, stride=1, padding=1) self.pointwise_conv = nn.Conv2D(in_channels, out_channels, kernel_size=1, stride=1) self.norm = nn.BatchNorm2D(out_channels) self.act = nn.Swish() def forward(self, inputs): out = self.depthwise_conv(inputs) out = self.pointwise_conv(out) out = self.norm(out) out = self.act(out) return out登录后复制In [20]
# 由(C2-C5)产生(P3-P7),操作是将C5降采样# C2 [2, 64, 64, 64]# C3 [2, 128, 32, 32]# C4 [2, 256, 16, 16]# C5 [2, 512, 8, 8]class C_to_P(nn.Layer): def __init__(self,inputs_size=8): # inputs_size是C5的size super(C_to_P, self).__init__() self.max_pool = nn.AdaptiveMaxPool2D(inputs_size//2) def forward(self, inputs): output=[] # 存放最终输出特征图(P3-P7) output = inputs out = self.max_pool(inputs[-1]) # 最大池化用于下采样 output.append(out) return output# P3 [2, 64, 64, 64]# P4 [2, 128, 32, 32]# P5 [2, 256, 16, 16]# P6 [2, 512, 8, 8]# P7 [2, 512, 4, 4]登录后复制In [24]
# BiFPN_block1实现的操作是(5->3)这层的操作class BiFPN_block1(nn.Layer): def __init__(self,in_channel_list,out_channel): super(BiFPN_block1, self).__init__() self.block1_layer=[] # 用于3个存放彩色节点模块和1个Identity(以5个特征图为例) for i in range(len(in_channel_list)-2): if i == 0: in_channel = in_channel_list[-1-i] + in_channel_list[-2-i] else: in_channel = out_channel + in_channel_list[-2-i] self.block1_layer.append(Sepconv_BN_Swish(in_channel,out_channel)) self.block1_layer.append(Identity()) def forward(self,x): print('----block1----') head_output = [] # 存放最终输出特征图 channel_list = [] # 存放block1_layer操作后通道变化 feat_size = [] # 存放特征图尺寸的列表 head_output.append(x[-1]) channel_list.append(x[-1].shape[1]) feat_size.append(x[-1].shape[2]) print(x[-1].shape) for i in range(len(x)-1): size = x[-2-i].shape[2:] if i == 0: # 看3.2图,上采样输入在第一次是P7,后三次是过3x3和1x1卷积 pre_upsampling = x[-1] else: pre_upsampling = N_layer # N_layer来自第一次循环产生的 upsampling = F.interpolate(pre_upsampling,size=size) # 上采样操作 pre_N = paddle.concat([upsampling,x[-2-i]], 1) # concat链接 N_layer = self.block1_layer[i](pre_N) # 过3x3和1x1卷积,最后一个是Identity(看本代码块12行) if i < len(x)-2: # P(4,5,6)跨层连接,P7无跨层连接操作 pre_block2 = paddle.concat([N_layer,x[-2-i]],1) else: pre_block2 = N_layer head_output.append(pre_block2) channel_list.append(pre_block2.shape[1]) feat_size.append(pre_block2.shape[2]) print(pre_block2.shape) return channel_list, head_output, max(feat_size) # max(feat_size)获取所有特征图的最大size,用于block2中的下采样登录后复制In [25]# BiFPN_block2实现的操作是(3->5)这层的操作class BiFPN_block2(nn.Layer): def __init__(self,in_channel_list,out_channel, max_size): super(BiFPN_block2, self).__init__() self.block2_layer=[] # 用于5个存放彩色节点模块 for i in range(len(in_channel_list)): if i == 0: in_channel = in_channel_list[-1] else: in_channel = in_channel_list[-1-i] + out_channel self.block2_layer.append(Sepconv_BN_Swish(in_channel,out_channel)) downsampling_size = max_size # P3 size(特征图最大size),用于下采样 self.max_pool=[] for i in range(len(in_channel_list)-1): # 用于4个下采样模块 self.max_pool.append(nn.AdaptiveMaxPool2D(downsampling_size//2)) downsampling_size = downsampling_size//2 def forward(self,x): print('----block2----') head_output=[] # 存放最终输出特征图 corent_block2 = self.block2_layer[0](x[-1]) head_output.append(corent_block2) print(corent_block2.shape) for i in range(len(x)-1): downsampling = self.max_pool[i](corent_block2) # 获取上层下采样结果 pre_block2 = paddle.concat([downsampling,x[-2-i]],1) # 将下采样结果和block1的输出concat corent_block2 = self.block2_layer[1+i](pre_block2) # 过3x3和1x1卷积 head_output.append(corent_block2) print(corent_block2.shape) return head_output登录后复制In [14]# 将BiFPN_block1和将BiFPN_block2合并class BiFPN_block(nn.Layer): def __init__(self,in_channel_list,out_channel): super(BiFPN_block, self).__init__() self.out_channel = out_channel self.bifpn_block1 = BiFPN_block1(in_channel_list, out_channel) def forward(self,x): block1_channel_list, out, max_size = self.bifpn_block1(x) bifpn_block2 = BiFPN_block2(block1_channel_list, self.out_channel, max_size) out = bifpn_block2(out) return out登录后复制In [15]
# 将BiFPN_block构建为num个,这里需要注意第一个BiFPN_block和第二个BiFPN_block输入特征图通道数的区别,# 后续(3、4...)输入特征图通道数均和第二个BiFPN_block相同class BiFPN(nn.Layer): def __init__(self,in_channel_list0,out_channel,num): super(BiFPN, self).__init__() self.bifpn_layer=[] in_channel_list1=[] for i in range(len(in_channel_list0)): in_channel_list1.append(out_channel) for i in range(num): if i==0: in_channel_list = in_channel_list0 else: in_channel_list = in_channel_list1 self.bifpn_layer.append(BiFPN_block(in_channel_list,out_channel)) def forward(self,x): out = x for layer in self.bifpn_layer: print('--------bifpn_layer--------') out = layer(out) return out登录后复制In [26]# 以5个特征图为例model = ResNet18()x = paddle.randn([2, 3, 64, 64])fpn_list = model(x)c_to_p = C_to_P()out = c_to_p(fpn_list)in_channel_list = [64,128,256,512,512]out_channel = 256bifpn_num = 3bifpn = BiFPN(in_channel_list,out_channel,bifpn_num)out = bifpn(out)登录后复制
--------bifpn_layer------------block1----[2, 512, 4, 4][2, 768, 8, 8][2, 512, 16, 16][2, 384, 32, 32][2, 320, 64, 64]----block2----[2, 256, 64, 64][2, 256, 32, 32][2, 256, 16, 16][2, 256, 8, 8][2, 256, 4, 4]--------bifpn_layer------------block1----[2, 256, 4, 4][2, 512, 8, 8][2, 512, 16, 16][2, 512, 32, 32][2, 512, 64, 64]----block2----[2, 256, 64, 64][2, 256, 32, 32][2, 256, 16, 16][2, 256, 8, 8][2, 256, 4, 4]--------bifpn_layer------------block1----[2, 256, 4, 4][2, 512, 8, 8][2, 512, 16, 16][2, 512, 32, 32][2, 512, 64, 64]----block2----[2, 256, 64, 64][2, 256, 32, 32][2, 256, 16, 16][2, 256, 8, 8][2, 256, 4, 4]登录后复制In [27]
# 以4个特征图为例model = ResNet18()x = paddle.randn([2, 3, 64, 64])fpn_list = model(x)in_channel_list = [64,128,256,512]out_channel = 256bifpn_num = 5bifpn = BiFPN(in_channel_list,out_channel,bifpn_num)out = bifpn(fpn_list)登录后复制
--------bifpn_layer------------block1----[2, 512, 8, 8][2, 512, 16, 16][2, 384, 32, 32][2, 320, 64, 64]----block2----[2, 256, 64, 64][2, 256, 32, 32][2, 256, 16, 16][2, 256, 8, 8]--------bifpn_layer------------block1----[2, 256, 8, 8][2, 512, 16, 16][2, 512, 32, 32][2, 512, 64, 64]----block2----[2, 256, 64, 64][2, 256, 32, 32][2, 256, 16, 16][2, 256, 8, 8]--------bifpn_layer------------block1----[2, 256, 8, 8][2, 512, 16, 16][2, 512, 32, 32][2, 512, 64, 64]----block2----[2, 256, 64, 64][2, 256, 32, 32][2, 256, 16, 16][2, 256, 8, 8]--------bifpn_layer------------block1----[2, 256, 8, 8][2, 512, 16, 16][2, 512, 32, 32][2, 512, 64, 64]----block2----[2, 256, 64, 64][2, 256, 32, 32][2, 256, 16, 16][2, 256, 8, 8]--------bifpn_layer------------block1----[2, 256, 8, 8][2, 512, 16, 16][2, 512, 32, 32][2, 512, 64, 64]----block2----[2, 256, 64, 64][2, 256, 32, 32][2, 256, 16, 16][2, 256, 8, 8]登录后复制In [21]
# 以6个特征图为例model = ResNet18()x = paddle.randn([2, 3, 64, 64])fpn_list = model(x)c_to_p1 = C_to_P(inputs_size=8)out = c_to_p1(fpn_list)c_to_p2 = C_to_P(inputs_size=4)out = c_to_p2(out)in_channel_list = [64,128,256,512,512,512]out_channel = 256bifpn_num = 5bifpn = BiFPN(in_channel_list,out_channel,bifpn_num)out = bifpn(out)登录后复制
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/nn/layer/norm.py:653: UserWarning: When training, we now always track global mean and variance. "When training, we now always track global mean and variance.")登录后复制登录后复制
--------bifpn_layer------------1----[2, 512, 2, 2][2, 768, 4, 4][2, 768, 8, 8][2, 512, 16, 16][2, 384, 32, 32][2, 320, 64, 64]----2----[2, 256, 64, 64][2, 256, 32, 32][2, 256, 16, 16][2, 256, 8, 8][2, 256, 4, 4][2, 256, 2, 2]--------bifpn_layer------------1----[2, 256, 2, 2][2, 512, 4, 4][2, 512, 8, 8][2, 512, 16, 16][2, 512, 32, 32][2, 512, 64, 64]----2----[2, 256, 64, 64][2, 256, 32, 32][2, 256, 16, 16][2, 256, 8, 8][2, 256, 4, 4][2, 256, 2, 2]--------bifpn_layer------------1----[2, 256, 2, 2][2, 512, 4, 4][2, 512, 8, 8][2, 512, 16, 16][2, 512, 32, 32][2, 512, 64, 64]----2----[2, 256, 64, 64][2, 256, 32, 32][2, 256, 16, 16][2, 256, 8, 8][2, 256, 4, 4][2, 256, 2, 2]--------bifpn_layer------------1----[2, 256, 2, 2][2, 512, 4, 4][2, 512, 8, 8][2, 512, 16, 16][2, 512, 32, 32][2, 512, 64, 64]----2----[2, 256, 64, 64][2, 256, 32, 32][2, 256, 16, 16][2, 256, 8, 8][2, 256, 4, 4][2, 256, 2, 2]--------bifpn_layer------------1----[2, 256, 2, 2][2, 512, 4, 4][2, 512, 8, 8][2, 512, 16, 16][2, 512, 32, 32][2, 512, 64, 64]----2----[2, 256, 64, 64][2, 256, 32, 32][2, 256, 16, 16][2, 256, 8, 8][2, 256, 4, 4][2, 256, 2, 2]登录后复制
相关攻略
Pywinrm 通过Windows远程管理(WinRM)协议,让Python能够像操作本地一样执行远程Windows命令,真正打通了跨平台管理的最后一公里。 在混合IT环境中,Linux机器管理Wi
早些时候,聊过 Python 领域那场惊心动魄的供应链攻击。当时我就感叹,虽然我们 JavaScript 开发者对这类套路烂熟于心,但亲眼目睹这种规模的“投毒”还是头一次。 早些时候,聊过 Pyth
Toga 是 BeeWare 家族的核心成员,号称“写一次,跑遍所有平台”,而且用的是系统原生控件,不是那种一看就是网页套壳的界面 。 写了这么多年 Python,你是不是也想过:要是能一套代码跑
异常处理的核心:让错误在正确的地方被有效处理。正确的地方,就是别在底层就把异常吞了,也别在顶层还抛裸奔的 Exception。 异常处理写得好,半夜不用起来改 bug。1 你是不是也这么干过?tr
1 Skills机制概述 提起OpenClaw的Skills机制,不少人可能会把它想象成传统意义上的可执行插件。其实,它的内涵要更精妙一些。 简单说,Skills本质上是一套基于提示驱动的能力扩展机制。它并不是一个可以独立“跑”起来的程序模块,而是通过一份结构化描述文件(核心就是那个SKILL m
热门专题
热门推荐
加密货币行业翘首以盼的监管里程碑,终于有了实质性进展。美国证券交易委员会(SEC)主席保罗·阿特金斯(Paul Atkins)近日证实,那份允许加密项目在早期获得注册豁免权的“安全港”框架提案,已经正式送抵白宫,进入了最终审查阶段。 在范德堡大学与区块链协会联合举办的数字资产峰会上,阿特金斯透露了这
微策略Strategy报告:第一季录得144 6亿美元浮亏 再斥资约3 3亿美元买进4871枚比特币 市场震荡的威力有多大?看看Strategy的最新季报就明白了。根据其最新向美国证管会(SEC)提交的8-K报告,受市场剧烈波动影响,这家公司所持的比特币在第一季度录得了一笔惊人的数字——144 6亿
稳定币巨头Tether的动向,向来是加密世界的风向标。这不,它向Web3基础设施的版图扩张,又迈出了关键一步。公司执行长Paolo Ardoino在社交平台X上透露,其工程团队正在全力“烹制”一个新项目——去中心化搜索引擎 “Hypersearch”。这个消息一出,立刻引发了行业的广泛猜想。 采用D
基地位于Coinbase旗下以太坊Layer2网络Base的Seamless Protocol,日前正式宣告了服务的终结。这个曾经吸引了超过20万用户的原生DeFi借贷协议,在运营不到三年后,终究没能跑赢时间。它主打的核心产品是Integrated Leverage Markets(ILMs)——一
PAAL代币揭秘:深度解析Web3社区治理的核心钥匙 在去中心化自治组织的浪潮中,谁真正掌握了项目的话语权?PAAL代币提供了一套系统化的答案。它不仅是生态内流转的价值媒介,更是开启链上治理大门的核心凭证。通过持有并质押PAAL代币,用户能够对协议升级、资金分配乃至战略方向等关键事务投出决定性的一票





