【玩转yolov5】使用TensorRT C++ API搭建yolov5s-v4.0网络结构(1)
?
注意:對于yolov5s-v4.0網絡結構,上圖僅作參考,實際結構以代碼為準,存在少量差異!
#需要一個全局的ILogger對象,用于記錄日志信息 static Logger gLogger; #創建一個網絡生成器 IBuilder* builder = createInferBuilder(gLogger); #使用IBuilder類方法創建一個空的網絡 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層。
virtual ISliceLayer* nvinfer1::INetworkDefinition::addSlice(?? ?ITenso& ?? ?input,Dims ?? ?start,Dims ?? ?size,Dims ?? ?stride? )?https://docs.nvidia.com/deeplearning/tensorrt/api/c_api/classnvinfer1_1_1_i_network_definition.html#ab3d64683b10afdbc944075da818fb086
ILayer* focus(INetworkDefinition *network, std::map<std::string, Weights>& weightMap, ITensor& input, int inch, int outch, int ksize, std::string lname) {ISliceLayer *s1 = network->addSlice(input, Dims3{ 0, 0, 0 }, Dims3{ inch, Yolo::INPUT_H / 2, Yolo::INPUT_W / 2 }, Dims3{ 1, 2, 2 });ISliceLayer *s2 = network->addSlice(input, Dims3{ 0, 1, 0 }, Dims3{ inch, Yolo::INPUT_H / 2, Yolo::INPUT_W / 2 }, Dims3{ 1, 2, 2 });ISliceLayer *s3 = network->addSlice(input, Dims3{ 0, 0, 1 }, Dims3{ inch, Yolo::INPUT_H / 2, Yolo::INPUT_W / 2 }, Dims3{ 1, 2, 2 });ISliceLayer *s4 = network->addSlice(input, Dims3{ 0, 1, 1 }, Dims3{ inch, Yolo::INPUT_H / 2, Yolo::INPUT_W / 2 }, Dims3{ 1, 2, 2 });ITensor* inputTensors[] = { s1->getOutput(0), s2->getOutput(0), s3->getOutput(0), s4->getOutput(0) };auto cat = network->addConcatenation(inputTensors, 4); #通道維度上的拼接auto conv = convBlock(network, weightMap, *cat->getOutput(0), outch, ksize, 1, 1, lname + ".conv");return conv; }接下來是一個CBL結構,這個比較好理解,拆開來看就是:Conv + BN + Silu。注意,雖然上面的全局網絡結構圖中展示的CBL中的激活函數是LeakyRelu,但是在v4.0中激活函數是Silu(Sigmoid Weighted Linear Unit),是一種較為平滑的激活函數。
ILayer* convBlock(INetworkDefinition *network, std::map<std::string, Weights>& weightMap, ITensor& input, int outch, int ksize, int s, int g, std::string lname) {Weights emptywts{ DataType::kFLOAT, nullptr, 0 };int p = ksize / 2;IConvolutionLayer* conv1 = network->addConvolutionNd(input, outch, DimsHW{ ksize, ksize }, weightMap[lname + ".conv.weight"], emptywts);assert(conv1);conv1->setStrideNd(DimsHW{ s, s });conv1->setPaddingNd(DimsHW{ p, p });conv1->setNbGroups(g);IScaleLayer* bn1 = addBatchNorm2d(network, weightMap, *conv1->getOutput(0), lname + ".bn", 1e-3);// silu = x * sigmoidauto sig = network->addActivation(*bn1->getOutput(0), ActivationType::kSIGMOID);assert(sig);auto ew = network->addElementWise(*bn1->getOutput(0), *sig->getOutput(0), ElementWiseOperation::kPROD);assert(ew);return ew; }因為后面要頻繁用到該結構,這里拆開來詳細講解一下。首先是卷積,調用addConvolutionNd來創建一個新的卷積層。?因為沒有bias一項,定義的bias的Weights結構中values為nullptr。stride,padding,group等參數通過IConvolutionLayer的內部成員函數來設置。
Weights emptywts{ DataType::kFLOAT, nullptr, 0 };int p = ksize / 2;IConvolutionLayer* conv1 = network->addConvolutionNd(input, outch, DimsHW{ ksize, ksize }, weightMap[lname + ".conv.weight"], emptywts);assert(conv1);conv1->setStrideNd(DimsHW{ s, s });conv1->setPaddingNd(DimsHW{ p, p });conv1->setNbGroups(g);然后是BN層,回顧一下BN層的定義:
E [ x ] 是batch的均值,V a r [ x ] 是batch的方差,?為了防止除0,γ 對應batch學習得到的權重,β 就是偏置。
TensorRT中并沒有直接的BatchNorm層,該層實際上是通過轉換系數依靠Scale層來完成。
好了,萬事具備,可以手撕代碼了。
IScaleLayer* addBatchNorm2d(INetworkDefinition *network, std::map<std::string, Weights>& weightMap, ITensor& input, std::string lname, float eps) {float *gamma = (float*)weightMap[lname + ".weight"].values;float *beta = (float*)weightMap[lname + ".bias"].values;float *mean = (float*)weightMap[lname + ".running_mean"].values; //均值float *var = (float*)weightMap[lname + ".running_var"].values; //方差int len = weightMap[lname + ".running_var"].count;//scalefloat *scval = reinterpret_cast<float*>(malloc(sizeof(float) * len));for (int i = 0; i < len; i++) {scval[i] = gamma[i] / sqrt(var[i] + eps);}Weights scale{ DataType::kFLOAT, scval, len };//shiftfloat *shval = reinterpret_cast<float*>(malloc(sizeof(float) * len));for (int i = 0; i < len; i++) {shval[i] = beta[i] - mean[i] * gamma[i] / sqrt(var[i] + eps);}Weights shift{ DataType::kFLOAT, shval, len };//powerfloat *pval = reinterpret_cast<float*>(malloc(sizeof(float) * len));for (int i = 0; i < len; i++) {pval[i] = 1.0;}Weights power{ DataType::kFLOAT, pval, len };weightMap[lname + ".scale"] = scale;weightMap[lname + ".shift"] = shift;weightMap[lname + ".power"] = power;//BatchNorm是channel維度的操作IScaleLayer* scale_1 = network->addScale(input, ScaleMode::kCHANNEL, shift, scale, power);assert(scale_1);return scale_1; }然后就是激活函數Silu,從下面的公式可以看出來其實就是給sigmoid激活函數加了一個權重,這個權重恰恰就是輸入。
f(x)=x?σ(x)? ??
f′(x)=f(x)+σ(x)(1?f(x))
同樣,TensorRT中也沒有直接提供Silu的api,通過addActivation配合addElementWise中的乘操作可以輕松構建Silu。
// silu = x * sigmoidauto sig = network->addActivation(*bn1->getOutput(0), ActivationType::kSIGMOID);assert(sig);auto ew = network->addElementWise(*bn1->getOutput(0), *sig->getOutput(0), ElementWiseOperation::kPROD);assert(ew);【參考文獻】
https://zhuanlan.zhihu.com/p/172121380
?
?
總結
以上是生活随笔為你收集整理的【玩转yolov5】使用TensorRT C++ API搭建yolov5s-v4.0网络结构(1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 直流电机制动matlab,他励直流电机:
- 下一篇: AJAX 同步请求导致的UI阻塞问题