当前位置:   article > 正文

【玩转yolov5】使用TensorRT C++ API搭建yolov5s-v4.0网络结构(1)_yolov5s c++

yolov5s c++

 

在这里插入图片描述

注意:对于yolov5s-v4.0网络结构,上图仅作参考,实际结构以代码为准,存在少量差异!

  1. #需要一个全局的ILogger对象,用于记录日志信息
  2. static Logger gLogger;
  3. #创建一个网络生成器
  4. IBuilder* builder = createInferBuilder(gLogger);
  5. #使用IBuilder类方法创建一个空的网络
  6. INetworkDefinition* network = builder->createNetworkV2(0U);

        builder是构建器,他会自动搜索cuda内核目录以获得最快的可用实现,构建和运行时的GPU需要保持一致。由builder构建的引擎(engine)不能跨平台和TensorRT版本移植。上面由builder创建了一个空的网络结构,后面就需要通过tensorrt c++ api来逐填充该网络结构,直至完整构建yolov5s-v4.0网络。

         首先构造focus结构,在yolov3和yolov4中并没有这个结构,其中比较关键的是切片操作。以我训练的输入为640*640*3的yolov5s的结构为例,原始640*640*3的图像输入focus结构,采用切片操作,生成320*320*12的特征图,再经过一个输出通道为32的卷积操作,生成320*320*32的特征图。focus结构的意义在于可以最大程度的减少信息损失而进行下采样操作。focus结构中需要用到的一个重要的tensorrt api就是addSlice接口,它用于创建一个slice层。

  1. virtual ISliceLayer* nvinfer1::INetworkDefinition::addSlice(    ITenso&     input,
  2. Dims     start,
  3. Dims     size,
  4. Dims     stride 

https://docs.nvidia.com/deeplearning/tensorrt/api/c_api/classnvinfer1_1_1_i_network_definition.html#ab3d64683b10afdbc944075da818fb086

  1. ILayer* focus(INetworkDefinition *network, std::map<std::string, Weights>& weightMap, ITensor& input, int inch, int outch, int ksize, std::string lname) {
  2. ISliceLayer *s1 = network->addSlice(input, Dims3{ 0, 0, 0 }, Dims3{ inch, Yolo::INPUT_H / 2, Yolo::INPUT_W / 2 }, Dims3{ 1, 2, 2 });
  3. ISliceLayer *s2 = network->addSlice(input, Dims3{ 0, 1, 0 }, Dims3{ inch, Yolo::INPUT_H / 2, Yolo::INPUT_W / 2 }, Dims3{ 1, 2, 2 });
  4. ISliceLayer *s3 = network->addSlice(input, Dims3{ 0, 0, 1 }, Dims3{ inch, Yolo::INPUT_H / 2, Yolo::INPUT_W / 2 }, Dims3{ 1, 2, 2 });
  5. ISliceLayer *s4 = network->addSlice(input, Dims3{ 0, 1, 1 }, Dims3{ inch, Yolo::INPUT_H / 2, Yolo::INPUT_W / 2 }, Dims3{ 1, 2, 2 });
  6. ITensor* inputTensors[] = { s1->getOutput(0), s2->getOutput(0), s3->getOutput(0), s4->getOutput(0) };
  7. auto cat = network->addConcatenation(inputTensors, 4); #通道维度上的拼接
  8. auto conv = convBlock(network, weightMap, *cat->getOutput(0), outch, ksize, 1, 1, lname + ".conv");
  9. return conv;
  10. }

接下来是一个CBL结构,这个比较好理解,拆开来看就是:Conv + BN + Silu。注意,虽然上面的全局网络结构图中展示的CBL中的激活函数是LeakyRelu,但是在v4.0中激活函数是Silu(Sigmoid Weighted Linear Unit),是一种较为平滑的激活函数。

  1. ILayer* convBlock(INetworkDefinition *network, std::map<std::string, Weights>& weightMap, ITensor& input, int outch, int ksize, int s, int g, std::string lname) {
  2. Weights emptywts{ DataType::kFLOAT, nullptr, 0 };
  3. int p = ksize / 2;
  4. IConvolutionLayer* conv1 = network->addConvolutionNd(input, outch, DimsHW{ ksize, ksize }, weightMap[lname + ".conv.weight"], emptywts);
  5. assert(conv1);
  6. conv1->setStrideNd(DimsHW{ s, s });
  7. conv1->setPaddingNd(DimsHW{ p, p });
  8. conv1->setNbGroups(g);
  9. IScaleLayer* bn1 = addBatchNorm2d(network, weightMap, *conv1->getOutput(0), lname + ".bn", 1e-3);
  10. // silu = x * sigmoid
  11. auto sig = network->addActivation(*bn1->getOutput(0), ActivationType::kSIGMOID);
  12. assert(sig);
  13. auto ew = network->addElementWise(*bn1->getOutput(0), *sig->getOutput(0), ElementWiseOperation::kPROD);
  14. assert(ew);
  15. return ew;
  16. }

因为后面要频繁用到该结构,这里拆开来详细讲解一下。首先是卷积,调用addConvolutionNd来创建一个新的卷积层。 因为没有bias一项,定义的bias的Weights结构中values为nullptr。stride,padding,group等参数通过IConvolutionLayer的内部成员函数来设置。

  1. Weights emptywts{ DataType::kFLOAT, nullptr, 0 };
  2. int p = ksize / 2;
  3. IConvolutionLayer* conv1 = network->addConvolutionNd(input, outch, DimsHW{ ksize, ksize }, weightMap[lname + ".conv.weight"], emptywts);
  4. assert(conv1);
  5. conv1->setStrideNd(DimsHW{ s, s });
  6. conv1->setPaddingNd(DimsHW{ p, p });
  7. conv1->setNbGroups(g);

然后是BN层,回顾一下BN层的定义:

E [ x ] 是batch的均值,V a r [ x ] 是batch的方差,ϵ为了防止除0,γ 对应batch学习得到的权重,β 就是偏置。

TensorRT中并没有直接的BatchNorm层,该层实际上是通过转换系数依靠Scale层来完成。

好了,万事具备,可以手撕代码了。

  1. IScaleLayer* addBatchNorm2d(INetworkDefinition *network, std::map<std::string, Weights>& weightMap, ITensor& input, std::string lname, float eps) {
  2. float *gamma = (float*)weightMap[lname + ".weight"].values;
  3. float *beta = (float*)weightMap[lname + ".bias"].values;
  4. float *mean = (float*)weightMap[lname + ".running_mean"].values; //均值
  5. float *var = (float*)weightMap[lname + ".running_var"].values; //方差
  6. int len = weightMap[lname + ".running_var"].count;
  7. //scale
  8. float *scval = reinterpret_cast<float*>(malloc(sizeof(float) * len));
  9. for (int i = 0; i < len; i++) {
  10. scval[i] = gamma[i] / sqrt(var[i] + eps);
  11. }
  12. Weights scale{ DataType::kFLOAT, scval, len };
  13. //shift
  14. float *shval = reinterpret_cast<float*>(malloc(sizeof(float) * len));
  15. for (int i = 0; i < len; i++) {
  16. shval[i] = beta[i] - mean[i] * gamma[i] / sqrt(var[i] + eps);
  17. }
  18. Weights shift{ DataType::kFLOAT, shval, len };
  19. //power
  20. float *pval = reinterpret_cast<float*>(malloc(sizeof(float) * len));
  21. for (int i = 0; i < len; i++) {
  22. pval[i] = 1.0;
  23. }
  24. Weights power{ DataType::kFLOAT, pval, len };
  25. weightMap[lname + ".scale"] = scale;
  26. weightMap[lname + ".shift"] = shift;
  27. weightMap[lname + ".power"] = power;
  28. //BatchNorm是channel维度的操作
  29. IScaleLayer* scale_1 = network->addScale(input, ScaleMode::kCHANNEL, shift, scale, power);
  30. assert(scale_1);
  31. return scale_1;
  32. }

然后就是激活函数Silu,从下面的公式可以看出来其实就是给sigmoid激活函数加了一个权重,这个权重恰恰就是输入。

f(x)=x⋅σ(x)    

f′(x)=f(x)+σ(x)(1−f(x))

在这里插入图片描述

同样,TensorRT中也没有直接提供Silu的api,通过addActivation配合addElementWise中的乘操作可以轻松构建Silu。

  1. // silu = x * sigmoid
  2. auto sig = network->addActivation(*bn1->getOutput(0), ActivationType::kSIGMOID);
  3. assert(sig);
  4. auto ew = network->addElementWise(*bn1->getOutput(0), *sig->getOutput(0), ElementWiseOperation::kPROD);
  5. assert(ew);

【参考文献】

https://zhuanlan.zhihu.com/p/172121380

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/300329
推荐阅读
相关标签
  

闽ICP备14008679号