基础论文学习(2)——DETR

目标检测 DETR:End-to-End Detection with Transformer

detr是facebook提出的引入transformer到目标检测领域的算法,效果很好,做法也很简单,相较于RCNN和YOLO系列算法,避免了Proposal/Anchor+NMS的复杂流程。

1. detr基本架构

对于目标检测任务,其要求输出给定图片中所有前景物体的类别和bbox坐标,该任务实际上是无序集合预测问题。针对该问题,detr做法非常简单:给定一张图片,经过CNN进行特征提取,然后变成特征序列输入到transformer的编解码器中,直接输出指定长度为N的无序集合,集合中每个元素包含物体类别和坐标。 其中N表示整个数据集中图片上最多物体的数目(默认100),因为整个训练和测试都Batch进行,如果不设置最大输出集合数,无法进行batch训练,如果图片中物体不够N个,那么就采用no object填充,表示该元素是背景。
在这里插入图片描述
整个思想看起来非常简单,相比faster rcnn或者yolo算法那就简单太多了,因为其不需要设置先验anchor,超参几乎没有,也不需要nms(因为输出的无序集合没有重复情况),并且在代码程度相比faster rcnn那就不知道简单多少倍了,通过简单修改就可以应用于全景分割任务。

相比faster rcnn等做法,detr最大特点是将目标检测问题转化为无序集合预测问题(所有前景物体集合: 类别+bbox坐标),直接预测N=100个坐标框和类别概率。论文中特意指出faster rcnn这种设置一大堆anchor,然后基于anchor进行分类和回归其实属于代理(anchor作为代理中介),这种做法不是最直接做法,目标检测任务就是输出无序集合,而faster rcnn等算法通过各种操作,并结合复杂后处理最终才得到无序集合属于绕路了,而detr就比较纯粹了。

在这里插入图片描述

1.1 Backnone(CNN+PE)

输入图片(b,c,h,w) 经过backbone网络完成特征提取的工作,这里用的是Resnet50,得到最终的feature map维度为(b,2048,h//32,w//32),其中h’=h//32和w’=w//32(类似于将图像划分为32x32的patch,共h'xw'个patch),因为2048比较大,为了节省计算量,先采用1x1卷积降维为256,(b,256,h//32,w//32),然后将最终的feature map进行展开,每个batch_size展开成h'xw'个 token,每个token长度为 c=256 的visual tokens,Encoder的输入shape=(h' x w', b, 256)
在这里插入图片描述

self.input_proj = nn.Conv2d(backbone.num_channels, hidden_dim, kernel_size=1)
# 输出是(b,256,h//32,w//32)
src=self.input_proj(src)
# 变成序列模式,(h'xw',b,256),256是每个词的编码长度
src = src.flatten(2).permute(2, 0, 1)

1.2 Transformer 编码器

encoder就是类似于之前VIT的结构,它的输入和输出tokens的维度和数量不变,内部transformer的结构大同小异,就知道它是综合输入各个tokens之间关系的结构就行了。
在这里插入图片描述

注意:在DETR的encoder中,只给K、Q加了spatial positional encoding,而且这里的spatial positional encoding在encoder的每一层都有加,之前只是在encoder的第一层加上,这里可以理解为,目标检测任务是一个特别看重位置的任务,每一层都加的话会不断强化patches的位置关系。

a) 位置编码需要考虑2d空间

由于图像特征是2d特征,故位置嵌入向量也需要考虑xy方向。前面说过编码方式可以采用sincos,也可以设置为可学习,本文采用的依然是sincos模式,和前面说的一样,但是需要考虑xy两个方向(前面说的序列只有x方向)。

#输入是b,c,h,w
#tensor_list的类型是NestedTensor,内部自动附加了mask,
#用于表示动态shape,是pytorch中tensor新特性https://github.com/pytorch/nestedtensor
x = tensor_list.tensors # 原始tensor数据
# 附加的mask,shape是b,h,w 全是false
mask = tensor_list.mask  
not_mask = ~mask
# 因为图像是2d的,所以位置编码也分为x,y方向
# 1 1 1 1 ..  2 2 2 2... 3 3 3...
y_embed = not_mask.cumsum(1, dtype=torch.float32) 
# 1 2 3 4 ... 1 2 3 4...
x_embed = not_mask.cumsum(2, dtype=torch.float32)
if self.normalize:eps = 1e-6y_embed = y_embed / (y_embed[:, -1:, :] + eps) * self.scalex_embed = x_embed / (x_embed[:, :, -1:] + eps) * self.scale# 0~127 self.num_pos_feats=128,因为前面输入向量是256,编码是一半sin,一半cos
dim_t = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device)
# 归一化
dim_t = self.temperature ** (2 * (dim_t // 2) / self.num_pos_feats)pos_x = x_embed[:, :, :, None] / dim_t
pos_y = y_embed[:, :, :, None] / dim_t
# 输出shape=b,h,w,128
pos_x = torch.stack((pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4).flatten(3)
pos_y = torch.stack((pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4).flatten(3)
pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2)
# 每个特征图的xy位置都编码成256的向量,其中前128是y方向编码,而128是x方向编码
return pos  # b,n=256,h,w

可以看出对于h//32,w//32的2d图像特征,不是类似vision transoformer做法简单的将其拉伸为h//32 x w//32,然后从0-n进行长度为256的位置编码,而是考虑了xy方向同时编码,每个方向各编码128维向量,这种编码方式更符合图像特定。

b) QKV处理逻辑不同

作者设置编码器一共6个,并且位置编码向量仅仅加到QK中,V中没有加入位置信息,这个和原始做法不一样,原始做法是QKV都加上了位置编码,论文中也没有写为啥要如此修改。

其余地方就完全相同了,故代码就没必要贴了。总结下和原始transformer编码器不同的地方:

  • 输入编码器的位置编码需要考虑2d空间位置
  • 位置编码向量需要加入到每个编码器中(在每层的Attention之前+PE)
  • 在编码器内部位置编码仅仅和QK相加,V不做任何处理
  • 经过6个编码器forward后,输出shape为(h//32xw//32,b,256)。
# 6个编码器整体forward流程如下:
class TransformerEncoder(nn.Module):def __init__(self, encoder_layer, num_layers, norm=None):super().__init__()# 编码器copy6份self.layers = _get_clones(encoder_layer, num_layers)self.num_layers = num_layersself.norm = normdef forward(self, src,mask: Optional[Tensor] = None,src_key_padding_mask: Optional[Tensor] = None,pos: Optional[Tensor] = None):# 内部包括6个编码器,顺序运行# src是图像特征输入,shape=hxw,b,256output = srcfor layer in self.layers:# 每个编码器都需要加入pos位置编码# 第一个编码器输入来自图像特征,后面的编码器输入来自前一个编码器输出output = layer(output, src_mask=mask,src_key_padding_mask=src_key_padding_mask, pos=pos)return output
# 每个Encoder编码器内部运行流程如下:
def forward_post(self,src,src_mask: Optional[Tensor] = None,src_key_padding_mask: Optional[Tensor] = None,pos: Optional[Tensor] = None):# 和标准做法有点不一样,src加上位置编码得到q和k,但是v依然还是src,# 也就是v和qk不一样q = k = src+pos  # PEsrc2 = self.self_attn(q, k, value=src, attn_mask=src_mask,key_padding_mask=src_key_padding_mask)[0]  # Attentfionsrc = src + self.dropout1(src2)  # 残差src = self.norm1(src)  # LN src2 = self.linear2(self.dropout(self.activation(self.linear1(src))))  # FFNsrc = src + self.dropout2(src2)  # 残差src = self.norm2(src)  # LN return src

1.3 Transformer 解码器

解码器是cross attention结构,K和V是由encoder提供的,Q是重新定义了一个object queries(每个object对应一个query token),在DETR中设定这个object queries为100(认为一张图片中的物体不会超过100个),object queries这个东西类似于之前的MAE中mask tokens,是可以学习的;另一部分是来自之前encoder的输出,具体如何连接请看下图:
在这里插入图片描述
a) 新引入Object queries

object queries(shape是(100,256))可以简单认为是随机初始化的, 其作用主要是在学习过程中提供目标对象和全局图像之间的关系,相当于全局注意力,必不可少非常关键。代码形式上是可学习位置编码矩阵。和编码器一样,该可学习位置编码向量也会输入到每一个解码器中。我们可以尝试通俗理解:object queries矩阵内部通过学习建模了100个物体之间的全局关系,例如房间里面的桌子旁边(A类)一般是放椅子(B类),而不会是放一头大象(C类),那么在推理时候就可以利用该全局注意力更好的进行解码预测输出。

# num_queries=100,hidden_dim=256
self.query_embed = nn.Embedding(num_queries, hidden_dim)  # 100个256维token

论文中指出object queries作用非常类似faster rcnn中的anchor,只不过这里是可学习的,不是提前设置好的。

补充一个object queries通俗理解:假设其维度是(100,256),在训练过程中每个格子(共N个)的向量都会包括整个训练集相关的位置和类别信息,例如第0个格子里面存储的一定是某个空间位置的大象类别的嵌入向量,注意该大象类别嵌入向量和某一张图片的大象特征无关,而是通过训练考虑了所有图片的某个位置附近的大象编码特征,属于和位置有关的全局大象统计信息。训练完成后每个格子里面都会压缩入所有类别的图片位置相关的统计信息。现在开始测试:假设图片中有大象、狗和猫三种物体,该图片会输入到编码器中进行特征编码,假设特征没有丢失,该编码器输出的编码向量就是KV,而object queries是Q,现在通过注意力模块将Q和K计算,然后加权V得到解码器输出。对于第0个格子的q会和K中的所有向量进行计算,目的是查找某个位置附近有没有大象,如果有那么该特征就会加权输出,整个过程计算完成后就可以把编码向量中的大象、狗和猫的编码嵌入信息提取出来,然后后面接fc进行分类和回归就比较容易,因为特征已经对齐了。

在整个分析过程中可以总结下:object queries在训练过程中对于N个格子会压缩入对应的和位置和类别相关的统计信息,在测试阶段就可以利用该Q去和编码特征KV计算加权计算,从而提出想要的对齐的特征,最后进行分类和回归。所以前面才会说object queries作用非常类似faster rcnn中的anchor,这个anchor是可学习的,由于维度比较高,故可以表征的东西丰富,当然维度越高,训练时长就会越长。

b) 位置编码也需要

编码器环节采用的sincos位置编码向量也可以考虑引入,且该位置编码向量输入到每个解码器的第二个Multi-Head Attention中,后面有是否需要该位置编码的对比实验。

c) QKV处理逻辑不同

解码器一共包括6个,和编码器中QKV一样,V不会加入位置编码。

d) 一次解码输出全部无序集合

和原始transformer顺序解码操作不同的是,detr一次就把N个无序框并行输出了(因为任务是无序集合,做成顺序推理有序输出没有很大必要)。为了说明如何实现该功能,我们需要先回忆下原始transformer的顺序解码过程:输入BOS_WORD,解码器输出i;输入前面已经解码的BOS_WORD和i,解码器输出am…,输入已经解码的BOS_WORD、i、am、a和student,解码器输出解码结束标志位EOS_WORD,每次解码都会利用前面已经解码输出的所有单词嵌入信息。现在就是一次解码,故只需要初始化时候输入一个全0的查询向量A,类似于BOS_WORD作用,然后第一个解码器接受该输入A,解码输出向量作为下一个解码器输入,不断推理即可,最后一层解码输出即为我们需要的输出,不需要在第二个解码器输入时候考虑BOS_WORD和第一个解码器输出。

总结下和原始transformer解码器不同的地方:

  • 额外引入可学习的Object queries,相当于可学习anchor,提供全局注意力
  • 编码器采用的sincos位置编码向量也需要输入解码器中,并且每个解码器都输入
  • QKV处理逻辑不同
  • 不需要顺序解码,一次即可输出N个无序集合
  • 相较于Transformer不使用Mask

整体架构:

  • 先让object queries做1次self attention;
  • 再让object queries作为Q,encoder输出作为K,V,做M次cross attention;
  • 最后进行FFN输出Decoder
    在这里插入图片描述

n个解码器整体流程如下:

class TransformerDecoder(nn.Module):def forward(self, tgt, memory,tgt_mask: Optional[Tensor] = None,memory_mask: Optional[Tensor] = None,tgt_key_padding_mask: Optional[Tensor] = None,memory_key_padding_mask: Optional[Tensor] = None,pos: Optional[Tensor] = None,query_pos: Optional[Tensor] = None):# 首先query_pos是query_embed,可学习输出位置向量shape=100,b,256# tgt = torch.zeros_like(query_embed),用于进行一次性解码输出output = tgt# 存储每个解码器输出,后面中继监督需要intermediate = []# 编码每个解码器for layer in self.layers:# 每个解码器都需要输入query_pos和pos# memory是最后一个编码器输出# 每个解码器都接受output作为输入,然后输出新的outputoutput = layer(output, memory, tgt_mask=tgt_mask,memory_mask=memory_mask,tgt_key_padding_mask=tgt_key_padding_mask,memory_key_padding_mask=memory_key_padding_mask,pos=pos, query_pos=query_pos)if self.return_intermediate:intermediate.append(self.norm(output))if self.return_intermediate:return torch.stack(intermediate)  # 6个输出都返回return output.unsqueeze(0)

内部每个解码器运行流程为:

def forward_post(self, tgt, memory,tgt_mask: Optional[Tensor] = None,memory_mask: Optional[Tensor] = None,tgt_key_padding_mask: Optional[Tensor] = None,memory_key_padding_mask: Optional[Tensor] = None,pos: Optional[Tensor] = None,query_pos: Optional[Tensor] = None):# query_pos首先是可学习的,其作用主要是在学习过程中提供目标对象和全局图像之间的关系# 这个相当于全局注意力输入,是非常关键的# query_pos是解码器特有q = k = tgt+query_pos# 第一个自注意力模块tgt2 = self.self_attn(q, k, value=tgt, attn_mask=tgt_mask,key_padding_mask=tgt_key_padding_mask)[0]tgt = tgt + self.dropout1(tgt2)tgt = self.norm1(tgt)# memory是最后一个编码器输出,pos是和编码器输入中完全相同的sincos位置嵌入向量# 输入参数是最核心细节,query是tgt+query_pos,而key是memory+pos# v直接用memorytgt2 = self.multihead_attn(query=tgt+query_pos,key=memory+pos,value=memory, attn_mask=memory_mask,key_padding_mask=memory_key_padding_mask)[0]tgt = tgt + self.dropout2(tgt2)tgt = self.norm2(tgt)tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt))))tgt = tgt + self.dropout3(tgt2)tgt = self.norm3(tgt)return tgt

解码器最终输出shape是(6,b,100,256),6是指6个解码器的输出。

1.4 Head与loss

加上检测头才能完成检测任务。

DETR的输出:

  1. logits_class:(batch_size, num_queries, num_classes+1)
  2. pred_boxes:(batch_size, num_queries, 4)

在这里插入图片描述
预测框有100个,然而真实框可能只有10个,因此我们需要找到一种匹配方式,为这10个真实框找到各自的预测框,剩余的预测框按照背景处理。DETR中采用pair-wise matching cost来度量预测框和真实框之间的相似度。注意这里不是计算损失,而是计算匹配度。它与两部分有关,一部分是与分类有关,一部分与回归有关,与回归有关这部分是由giou和L1损失计算得到,与类别有关的部分满足条件是为-1不满足时为0。
在这里插入图片描述

注意生成的Cost矩阵的维度是n_queries*n_targets,比如100*10也就是说,得到了预测框和真实框两两之间的关系度量(匹配度)。然后使用匈牙利算法找到与真实框对应的预测框,其他框都是背景框,实际是调用了scipy中的API,在这里就知道这一步的目的是完成分配就行了,就是以一种科学的方式完成预测框和真实框的对应就好了。
在这里插入图片描述

对应上之后,就可以计算损失了,再梳理一下,我们的目的是计算损失才能根据损失进行backward修正网络完成训练,但是因为transformer的机制,在计算loss之前,不得不找到哪些框和哪个真实框对应,在以往的目标检测算法中,比如anchor-base的方法,其实根据置信度筛选完,再计算iou之后,留下的框其实是有和真实框之间的对应的,比如一个真实框对应了几个比较好的回归框。这里loss分为三部分,CE Loss计算分类损失,GIOU Loss计算回归损失,还有一个L1Loss计算回归损失。

在这里插入图片描述

在解码器输出基础上构建分类和bbox回归head即可输出检测结果,比较简单:

self.class_embed = nn.Linear(256, 92)
self.bbox_embed = MLP(256, 256, 4, 3)# hs是(6,b,100,256),outputs_class输出(6,b,100,92),表示6个分类分支
outputs_class = self.class_embed(hs)
# 输出(6,b,100,4),表示6个bbox坐标回归分支
outputs_coord = self.bbox_embed(hs).sigmoid() 
# 取最后一个解码器输出即可,分类输出(b,100,92),bbox回归输出(b,100,4)
out = {'pred_logits': outputs_class[-1], 'pred_boxes': outputs_coord[-1]}
if self.aux_loss:# 除了最后一个输出外,其余编码器输出都算辅助lossout['aux_outputs'] = self._set_aux_loss(outputs_class, outputs_coord)

作者实验发现,如果对M个解码器的 每个decoder输出 都计算辅助的分类和回归loss,可以提升性能,故作者除了对最后一个编码层的输出进行Loss监督外,还对其余5个编码器采用了同样的loss监督,只不过权重设置低一点而已。

1.5 模型全流程

在这里插入图片描述
当然上面是简化代码,和实际代码不一样。具体流程是:

将(b,3,800,1200)图片输入到resnet50中进行特征提取,输出shape=(b,1024,25,38)
通过1x1卷积降维,变成(b,256,25,38)
利用sincos函数计算位置编码
将图像特征和位置编码向量相加,作为编码器输入,输出编码后的向量,shape不变
初始化全0的(100,b,256)的输出嵌入向量,结合位置编码向量和query_embed,进行解码输出,解码器输出shape为(6,b,100,256),后面的解码器接受该输出,然后再次结合置编码向量和query_embed进行输出,不断前向
将最后一个解码器输出输入到分类和回归head中,得到100个无序集合
对100个无序集合进行后处理,主要是提取前景类别和对应的bbox坐标,乘上(800,1200)即可得到最终坐标,后处理代码如下:

prob = F.softmax(out_logits, -1)
scores, labels = prob[..., :-1].max(-1)
# convert to [x0, y0, x1, y1] format
boxes = box_ops.box_cxcywh_to_xyxy(out_bbox)
# and from relative [0, 1] to absolute [0, height] coordinates
img_h, img_w = target_sizes.unbind(1)
scale_fct = torch.stack([img_w, img_h, img_w, img_h], dim=1)
boxes = boxes * scale_fct[:, None, :]
results = [{'scores': s, 'labels': l, 'boxes': b} for s, l, b in zip(scores, labels, boxes)]

既然训练时候对6个解码器输出都进行了loss监督,那么在测试时候也可以考虑将6个解码器的分类和回归分支输出结果进行nms合并,稍微有点性能提升。

2.实验分析

在这里插入图片描述

Faster RCNN-DC5是指的resnet的最后一个stage采用空洞率=stride设置代替stride,目的是在不进行下采样基础上扩大感受野,输出特征图分辨率保持不变。+号代表采用了额外的技巧提升性能例如giou、多尺度训练和9xepoch训练策略。可以发现detr效果稍微好于faster rcnn各种版本,证明了视觉transformer的潜力。但是可以发现其小物体检测能力远远低于faster rcnn,这是一个比较大的弊端。
在这里插入图片描述
detr整体做法非常简单,基本上没有改动原始transformer结构,其显著优点是:不需要设置啥先验,超参也比较少,训练和部署代码相比faster rcnn算法简单很多,理解上也比较简单。但是其缺点是:改了编解码器的输入,在论文中也没有解释为啥要如此设计,而且很多操作都是实验对比才确定的,比较迷。算法层面训练epoch次数远远大于faster rcnn(300epoch),在同等epoch下明显性能不如faster rcnn,而且训练占用内存也大于faster rcnn。

整体而言,虽然效果不错,但是整个做法还是显得比较原始,很多地方感觉是尝试后得到的做法,没有很好的解释性,而且最大问题是训练epoch非常大和内存占用比较多,对应的就是收敛慢,期待后续作品。

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

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

相关文章

jvm开启远程调试功能;idea远程debug

概述 有时候一些问题本地调试无法复现,这个时候可以开启jvm的远程调试功能 jar包启动 jdk8 java -agentlib:jdwptransportdt_socket,address8787,servery,suspendn -jar xxx.jarjdk11/17 java -agentlib:jdwptransportdt_socket,address*:8787,servery,suspe…

STM32F103 4G Cat.1模块EC200S使用

一、简介 EC200S-CN 是移远通信最近推出的 LTE Cat 1 无线通信模块,支持最大下行速率 10Mbps 和最大上行速率 5Mbps,具有超高的性价比;同时在封装上兼容移远通信多网络制式 LTE Standard EC2x(EC25、EC21、EC20 R2.0、EC20 R2.1&a…

Linux--进程地址空间

1.线程地址空间 所谓进程地址空间(process address space),就是从进程的视角看到的地址空间,是进程运行时所用到的虚拟地址的集合。 简单地说,进程就是内核数据结构和代码和本身的代码和数据,进程本身不能…

代码随想录第29天|491.递增子序列,46.全排列,47.全排列II

491.递增子序列 491. 递增子序列 这道题的特点是有序的子序列(不能对原数组排序),最终结果集res不能有重复子集。所以这道题又是子集又是去重 回溯三部曲 1.递归函数参数 本题求子序列,很明显一个元素不能重复使用,所以需要startIndex&a…

【C++练习】普通方法+利用this 设置一个矩形类(Rectangle), 包含私有成员长(length)、 宽(width), 定义一下成员函数

题目 设置一个矩形类(Rectangle), 包含私有成员长(length)、 宽(width), 定义成员函数: void set_ len(int l); //设置长度 设置宽度void set_ wid(int w); 获取长度: int get len(); 获取宽度: int get _wid); 显示周长和面积: v…

汽车电子笔记之:AUTOSAR方法论及基础概念

目录 1、AUTOSAR方法论 2、AUTOSAR的BSW 2.1、MCAL 2.2、ECU抽象层 2.3、服务层 2.4、复杂驱动 3、AUTOSAR的RTE 4、AUTOSAR的应用层 4.1、SWC 4.2、AUTOSAR的通信 4.3、AUTOSAR软件接口 1、AUTOSAR方法论 AUTOSAR为汽车电子软件系统开发过程定义了一套通用的技术方法…

分布式事务篇-2.4 Spring-Boot整合Seata

文章目录 前言一、pom jar导入:二、项目配置:2.1 配置 说明:2.1 .1 seata server 端:2.1 .2 seata client 端: 2.2 开启seata 对于数据源的代理:2.3 seata-client 的注册中心:2.4 seata-client 的配置中心:2.5 去掉手写的数据源代…

二叉树链式结构的实现

文章目录 1.前置说明 2.二叉树的遍历 文章内容 1.前置说明 学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在我们对于二叉树的了解还处于初级阶段,所以我们手动创建一棵简单的二叉树,以便…

javeee eclipse项目导入idea中

步骤一 复制项目到idea工作空间 步骤二 在idea中导入项目 步骤三 配置classes目录 步骤四 配置lib目录 步骤五 添加tomcat依赖 步骤六 添加artifacts 步骤七 部署到tomcat

电商项目part06 微服务网关整合OAuth2.0授权中心

微服务网关整合 OAuth2.0 思路分析 网关整合 OAuth2.0 有两种思路,一种是授权服务器生成令牌, 所有请求统一在网关层验证,判断权 限等操作;另一种是由各资源服务处理,网关只做请求转发。 比较常用的是第一种,把API网关…

认识Mybatis的关联关系映射,灵活关联表对象之间的关系

目录 一、概述 ( 1 ) 介绍 ( 2 ) 关联关系映射 ( 3 ) 关联讲述 二、一对一关联映射 2.1 数据库创建 2.2 配置文件 2.3 代码生成 2.4 编写测试 三、一对多关联映射 四 、多对多关联映射 给我们带来的收获 一、概述 ( 1 ) 介绍 关联关系映射是指在数据库中&…

RH1288V3 - 初识物理服务器

如果你拥有一台物理服务器(不是云服务器) 个人比较推荐你用物理服务器,虽然性能会比云要来的差,但是不用每月交钱上。云服务固然方便,但是几个核的性能和一点存储,想做一个动漫网站固然要很多mp4这种影视资源,云服务器…

人工智能在现代招聘中的崛起:超越传统筛选的未来

引言 在过去的几十年里,招聘一直是企业的核心活动之一。传统的招聘流程依赖于人力资源专家手工筛选简历、面试候选人并进行背景调查。这种方法不仅耗时,而且可能受到人为偏见的影响。随着技术的进步,特别是人工智能(AI)的发展,招聘的面貌正在发生深刻的变化。人工智能在…

【Ubuntu20.04安装Nvidia驱动、CUDA和CUDNN】

Ubuntu20.04安装Nvidia驱动、CUDA和CUDNN 1 Nvidia驱动安装1.1 安装1.2 安装Nvidia可能会遇到的问题1.2.1 NVIDIA 驱动与 Nouveau 驱动不兼容1.2.2 ERROR: Unable to find the development tool cc 2 CUDA安装2.1 下载和安装2.2 配置CUDA环境 3 安装CUDNN4 切换CUDA版本 1 Nvid…

在 macOS 中安装 TensorFlow 1g

tensorflow 需要多大空间 pip install tensorflow pip install tensorflow Looking in indexes: https://pypi.douban.com/simple/ Collecting tensorflowDownloading https://pypi.doubanio.com/packages/1a/c1/9c14df0625836af8ba6628585c6d3c3bf8f1e1101cafa2435eb28a7764…

在其他python环境中使用jupyter notebook

1、切换到目标python环境 activate 目标python环境 2、安装notebook内核包 pip install ipykernel 3、加环境加入到notebook中 python -m ipykernel install 目标python环境 4、切换到base环境 activate base 5、打开目标项目的对应盘 如果,项目在c盘&…

DEFORMABLE DETR: DEFORMABLE TRANSFORMERS FOR END-TO-END OBJECT DETECTION 论文精度笔记

DEFORMABLE DETR DEFORMABLE DETR: DEFORMABLE TRANSFORMERS FOR END-TO-END OBJECT DETECTION 参考:AI-杂货铺-Transformer跨界CV又一佳作!Deformable DETR:超强的小目标检测算法! 摘要 摘要部分,作者主要说明了如…

Datawhale AI夏令营 - 用户新增预测挑战赛 | 学习笔记

任务1:跑通Baseline # 1. 导入需要用到的相关库 # 导入 pandas 库,用于数据处理和分析 import pandas as pd # 导入 numpy 库,用于科学计算和多维数组操作 import numpy as np # 从 sklearn.tree 模块中导入 DecisionTreeClassifier 类 # De…

开源容灾备份软件,开源cdp备份软件

数据的安全性和完整性面临着硬件问题、黑客攻击、人为错误等各种威胁。在这种环境下,开源容灾备份软件应运而生,通过提供自动数据备份和恢复,有效地保证了公司的数据安全。 一、开源容灾备份软件的定义和作用 开源容灾备份软件是一种基于开源…

全流程R语言Meta分析核心技术教程

详情点击链接:全流程R语言Meta分析核心技术教程 一,Meta分析的选题与检索 1、Meta分析的选题与文献检索 1)什么是Meta分析? 2)Meta分析的选题策略 3)精确检索策略,如何检索全、检索准 4)文献的管理与清洗,如何制定文…