GPU上創建目標檢測Pipeline管道


GPU上創建目標檢測Pipeline管道

Creating an Object Detection Pipeline for GPUs

今年3月早些時候,展示了retinanet示例,這是一個開源示例,演示了如何加快gpu目標檢測管道的訓練和部署。在聖何塞舉行的英偉達GPU技術會議上介紹了這個項目。這篇文章討論了這項工作的動機,對體系結構的一個高級描述,以及所采用的優化的一個簡單的介紹。如果對GPUs上的目標檢測還不熟悉,建議參考GPUs上的實時目標檢測10分鍾開始。

理論基礎             

雖然有幾個優秀的開源項目專注於目標檢測,但覺得有幾個原因需要創建和發布這個示例。             

推理性能和准確性之間的權衡。用於目標檢測的深度學習模型可以大致分為兩類:單級檢測器(如單次激發檢測器、YOLO、YOLOv2)和兩級檢測器(如更快的RCNN)。兩級檢測器首先提取區域建議(可能的目標)然后對其進行分類,而一級檢測器則在所有背景和前景位置上實現密集分類。             

由於背景和可能目標之間的大類不平衡,單級檢測器在檢測精度上一直落后於最新水平。然而,兩級網絡精度的提高是以推理過程中較長的延遲為代價的。今天,目標應用程序的約束將決定哪種類型的模型最合適。在理想情況下,可以設計出一個具有高精度和高推理性能的模型。             

對端到端GPU處理的渴望。在一個典型的不使用GPU的目標檢測管道中,會出現一些步驟,例如圖像預處理和檢測后處理。通過將這些函數移到GPU來加速這些函數是提高訓練和推理整體性能的一種潛在策略。這還將有助於減少CPU和GPU內存之間代價高昂的數據傳輸,例如將完整的預處理輸入張量移動到GPU,並將大的特征映射從GPU傳遞回CPU以進行邊界框后處理。兩者都可能是推理過程中的瓶頸。             

把英偉達深度學習庫放在一起。NVIDIA提供了各種各樣的軟件庫,每一個都解決了深度學習工作流的不同部分:DALI用於圖像預處理,APEX/AMP用於簡單的混合精度訓練,TensorRT用於優化用於部署的訓練模型,DeepStream用於創建智能視頻分析應用程序。想創建一個端到端的例子,利用這些庫中的每一個來演示GPU計算平台的價值,開發人員可以開始使用開箱即用,並為自己的用例進行擴展。

Architecture Details

接下來,需要決定在最初的設計中使用哪種架構。本節對本項目中使用的RetinaNet體系結構進行了高層次的概述,討論了如何將其用於目標檢測,並探討了設計選擇對檢測精度和推理性能的影響。

RetinaNet

RetinaNet是FAIR研究人員在其論文《密集目標檢測中的焦點損失》中提出的一種單級目標檢測器。基於一個相對簡單的設計,如圖1所示,由以下部分組成:

特征提取主干網             

通常是ResNet的味道。用於從圖像中提取語義信息進行后續檢測。             

特征金字塔網絡(FPN)             

允許網絡通過向上采樣頂層(豐富的語義信息)和從先前的特征提取層添加細節來“查看”更小、更詳細的對象。提供不同維度的功能圖,以便檢測不同大小的對象。             

每一個特征映射被輸入到兩個子網絡中             

分類子網對特征地圖上的每個位置進行分類,為每個潛在的對象類和背景打分。             

功能映射也會貫穿box子網             

回歸每個潛在對象周圍邊界框的坐標。

 Figure 1: The one-stage RetinaNet network architecture

RetinaNet還引入了焦距損失的概念,一種改進的損失函數來解決一級和兩級探測器之間的精度差距。焦距損失補償了背景前景類的不平衡,降低了簡單示例(如背景)的重要性,使硬示例更重要。             

靈活性是這類網絡的一個重要特點。特征提取主干可以很容易地改變,而無需對網絡的其部分進行任何修改。這允許用戶選擇針對所考慮的任務或應用程序定制的特定性能精度權衡。提供了多個骨干網和明確的指標,以便於在此實現中進行選擇。

Post Processing

在RetinaNet輸出的推理過程中得到的邊界框需要經過后處理才能得到最終的相關框。             

前面提到過,box子網會對邊界框的坐標進行回歸。實際上,特征映射上的每個位置都有一組預定義的錨定框,這些錨定框具有各種縱橫比和比例。框子子網實際上輸出精確的坐標,即從每個實際錨定框坐標(dx、dy、dw、dh)預測的邊界框的增量。因此,后處理的第一步是執行從定位框增量到優化對象邊界框的坐標轉換。             

解碼邊界框坐標后,來自不同錨定的多個預測邊界框通常聚集在同一對象周圍。這就需要第二個后處理步驟,稱為非最大抑制。此計算比較類似的預測邊界框,並確保僅為最終輸出檢測選擇每個對象得分最高的最相關邊界框。             

這兩個后處理步驟都可以並行化,並與其檢測模型一起在GPU上運行,顯著提高了性能。

Performance Overview

在開發了第一個實現之后,分析了對象檢測管道的推理性能,看看是否達到了預期的目標。收集了幾個RetinaNet模型的批處理大小1(包括前處理和后處理)的初步端到端推斷延遲測量,這些模型是在具有NVIDIA T4 GPU的本地系統上收集的。             

每個模型對COCO 2017驗證數據集中的圖像進行推斷,這些圖像使用DALI調整大小並填充到1280×1280像素的固定輸入大小。使用TensorRT優化Pythorch的RetinaNet模型,以便在T4上以INT8精度部署。             

使用TensorRT INT8 precision在T4上運行模型使其Tensor Core微體系結構能夠提高吞吐量和降低延遲,同時仍然保持強大的檢測精度。應用這些優化后,獲得的最終端到端推理性能取決於所選主干,在0.31 mAP時每個圖像的延遲為18ms,在0.39 mAP時為33ms。這些結果表明,可以設計出高精度的目標檢測模型,並且仍然能夠在低推理延遲的GPU上部署。

引擎

現在讓看看使用的工具以及如何幫助優化目標檢測管道。

使用RetinaNet CLI的基本概述 Basic overview of using RetinaNet CLI

此項目的代碼是開源的,可以在NVIDIA GitHub頁面上找到。提供了一個可安裝的命令行模塊,允許用戶快速訓練、測試(推斷)和導出對象檢測模型。這使很容易自己試驗這個項目。             

訓練RetinaNet模型就像指定用於訓練/評估的主干架構(在本例中,是基於ResNet50的FPN)和數據集一樣簡單:

retinanet train retinanet_rn50fpn.pth --backbone ResNet50FPN \

    --images /coco/images/train2017/ --annotations /coco/annotations/instances_train2017.json \

    --val-images /coco/images/val2017/ --val-annotations /coco/annotations/instances_val2017.json

retinanet train命令還可用於微調另一個數據集的現有模型。微調The --fine-tune retinanet_rn50fpn.pth參數將加載預先訓練的模型,去掉用於從原始數據集預測類的現有cls_heads的最后一層,並附加為新微調數據集定制的新的cls_heads:

retinanet train model_mydataset.pth \
    --fine-tune retinanet_rn50fpn.pth \
    --classes 20 --iters 10000 --val-iters 1000 --lr 0.0005 \
    --resize 512 --jitter 480 640 --images /voc/JPEGImages/ \
    --annotations /voc/pascal_train2012.json --val-annotations /voc/pascal_val2012.json

訓練模型后,可以使用retinanet infer命令評估其精度:

retinanet infer retinanet_rn50fpn.pth --images /coco/images/val2017/ --annotations /coco/annotations/instances_val2017.json

retinanet export命令抽象了將PyTorch retinanet模型轉換為TensorRT引擎的復雜性,並用單個調用替換:

retinanet export model.pth engine.plan

默認情況下,retinanet導出將生成針對FP16精度的TensorRT引擎。但是,用戶可以利用TensorRT的INT8校准算法,通過指定校准圖像的路徑來生成更高性能的INT8引擎:

retinanet export model.pth engine.plan --int8 --calibration-images /coco/images/val2017/

也可以使用retinanet infer來評估TensorRT引擎:

retinanet infer engine.plan --images /coco/images/val2017/ --annotations /coco/annotations/instances_val2017.json

DALI訓練

NVIDIA的開源DALI項目專注於加速深度學習應用程序的預處理流程。DALI提供了一組高度優化的構建塊,可以在CPU或GPU上運行,用於常用的預處理功能。支持多種數據格式,可以很容易地與流行的深度學習框架集成,允許預處理管道跨工作負載進行移植。             

首先需要標識訓練/推理預處理管道使用DALI所需的一組運算符。使用這些運算符定義圖形,以描述如何將數據轉換為完全預處理的張量,這些張量可供模型使用。             

在用例中定義的圖有一個單獨的訓練和推理預處理路徑。推理圖相當簡單:從磁盤讀取JPEG圖像以及相應的檢測邊界框及其類。然后將傳輸到GPU,在那里解碼、調整大小、規范化並填充到適當的大小。             

訓練圖使用稍微復雜一點的操作集。首先,對圖像中的邊界框進行隨機的預期裁剪,丟棄任何不屬於裁剪區域的檢測。使用GPU僅部分解碼包含在隨機裁剪中的圖像的相關部分。然后根據訓練超參數隨機調整解碼圖像的大小,隨機水平翻轉,最后進行歸一化和填充。

def define_graph(self):
 
        images, bboxes, labels, img_ids = self.reader()
 
        if self.training:
            crop_begin, crop_size, bboxes, labels = self.bbox_crop(bboxes, labels)
            images = self.decode_train(images, crop_begin, crop_size)
            resize = self.rand4()
            images, attrs = self.resize_train(images, resize_longer=resize)
 
            flip = self.coin_flip()
            bboxes = self.bbox_flip(bboxes, horizontal=flip)
            images = self.img_flip(images, horizontal=flip)
 
        else:
            images = self.decode_infer(images)
            images, attrs = self.resize_infer(images)
 
        resized_images = images
        images = self.normalize(self.pad(images))
 
        return images, bboxes, labels, img_ids, attrs, resized_images

現在已經定義了一個DALI預處理管道,用一個用於訓練和推理的迭代器來包裝。該迭代器負責從DALI管道獲取張量輸出,執行任何最終轉換(例如重新縮放邊界框以匹配調整大小的圖像),並將轉換為PyTorch張量,以用於訓練或推理。此設計還允許使DALI數據加載器與本地PyTorch數據加載器互換,以便於實驗。             

比較了在Tesla T4上使用兩個數據加載器為TensorRT INT8精度優化的ResNet18FPN骨干網RetinaNet網絡模型的總體推斷時間。注意到,與本機PyTorch數據加載器相比,使用帶有TensorRT推斷的DALI可以減少數據加載開銷,從而獲得更好的性能。對於批處理1,這種差異不大,但對於更大的批處理大小,在推斷之前需要做更多的預處理工作,這種差異就變得很大。

Automatic Mixed Precision on Tensor Cores

利用混合精度進行深度學習訓練是在Volta和Turing-gpu上實現張量核性能最大化的有效途徑。在GTC SJ 2019上,宣布了來自英偉達APEX庫的Pythorch內部自動混合精度功能(AMP)的更新。已經將這些工具集成到項目中,使RetinaNet模型的混合精度訓練簡單易用。遷移現有的FP32精度訓練腳本以試驗AMP的混合精度訓練功能非常簡單,只需要兩個小的代碼更改。             

使用AMP的第一個必要更改是使用給定的opt_level注冊模型和優化器,范圍從O0(常規的FP32訓練)到O3(純FP16訓練)。這些opt_level對應於一組預定義參數,這些參數定義AMP將自動應用於模型和優化器的幕后更改。在實現中,使用了氧的opt_水平進行混合精度訓練。默認情況下,這個opt_level將所有輸入強制轉換為FP16,將模型權重強制轉換為FP16,在FP32中保留批處理規范化操作,並在FP32中維護模型權重的主副本,優化器將在optimizer.step(). 通過指定靜態loss_scale損耗標度值128而不是動態損耗標度來覆蓋此opt_level的損耗標度參數。

model, optimizer = amp.initialize(model, optimizer,
                                opt_level = 'O2' if mixed_precision else 'O0',
                                keep_batchnorm_fp32 = True,
                                loss_scale = 128.0,
                                verbosity = is_master)

訓練腳本的第二個更改涉及向后傳球。在使用Pythorch進行FP32訓練時,人通常稱my_loss.backward()計算用於優化步驟的漸變。然而,在計算梯度之前,需要在混合精度訓練中縮放損失值,以避免任何潛在的數值問題。為此,使用amp.scale_loss函數自動執行損耗縮放,然后對新的縮放損耗scaled_loss調用.backward()。因為已經用AMP注冊了優化器對象,所以仍然使用相同的optimizer.step()以更新模型權重。

with amp.scale_loss(cls_loss + box_loss, optimizer) as scaled_loss:
      scaled_loss.backward()
optimizer.step()

TensorRT

TensorRT是NVIDIA的高性能深度學習推理平台。提供了一個優化器組件來優化GPU上部署的深度學習模型,以及一個運行時來運行生產中的推理。為了優化用於TensorRT部署的RetinaNet模型,首先將核心PyTorch RetinaNet模型(不包括模型的包圍框解碼和NMS后處理部分)導出到ONNX,ONNX是深度學習模型的一種與框架無關的中間表示。接下來,使用TensorRT提供的ONNX解析器將ONNX模型表示形式中的結構和權重轉換為TensorRT優化表示形式,稱為INetworkDefinition對象。             

為了獲得最佳性能,希望將推理管道的包圍框解碼和NMS步驟作為單個TensorRT INetworkDefinition對象的一部分。但是,這兩個函數不容易在ONNX中表示,並且像網絡的其部分一樣導入TensorRT。為了使這個工作,利用TensorRT的插件層API來定義自己的自定義層,用於邊界框解碼和NMS。在編寫優化的CUDA內核以在GPU上運行這些函數之后,使用這些內核來定義TensorRT IPluginV2對象。這個IPluginV2對象包含了TensorRT將自定義函數集成到INetworkDefinition的其余部分所需的所有信息,就好像是本機TensorRT層類型一樣。

class DecodePlugin : public IPluginV2 {
void configureWithFormat(const Dims* inputDims, …) override;
int enqueue(int batchSize, const void *const *inputs, …) override;
void serialize(void *buffer, …) const override;
}
 
class DecodePluginCreator : public IPluginCreator {
IPluginV2 *createPlugin (const char *name, …) override; 
IPluginV2 *deserializePlugin (const char *name, …) override;
}
REGISTER_TENSORRT_PLUGIN(DecodePluginCreator);

將DecodePlugin連接到FPN的每個級別的class/bbox頭對的輸出,並將所有DecodePlugin輸出組合到一個最終的NMSPlugin中,該NMSPlugin從輸入圖像中選擇頂級檢測。

// Parse ONNX FCN
auto parser = createParser(*network, gLogger);
parser->parse(onnx_model, onnx_size);
// Add decode plugins
for (int i = 0; i < nbBoxOutputs; i++) {
auto decodePlugin = DecodePlugin(score_thresh, top_n, anchors[i], scale);
auto layer = network->addPluginV2(inputs.data(), inputs.size(), decodePlugin);
}
// Add NMS plugin
auto nmsPlugin = NMSPlugin(nms_thresh, detections_per_im);
auto layer = network->addPluginV2(concat.data(), concat.size(), nmsPlugin);
// Build CUDA inference engine
auto engine = builder->buildCudaEngine(*network);

至此,已經成功地將整個PyTorch RetinaNet模型導入TensorRT。TensorRT開發工作流的下一步是優化INetworkDefinition以進行部署。使用單個API調用,TensorRT將其推理優化套件應用於網絡,並生成TensorRT IEngine對象。此對象包含模型的完全優化表示,可以保存到文件中,然后重新加載以在GPU上執行模型的推斷。

// Build engine
    cout << "Applying optimizations and building TRT CUDA engine..." << endl;
    _engine = builder->buildCudaEngine(*network);
    cout << "Writing to " << path << "..." << endl;
    auto serialized = _engine->serialize();
    ofstream file(path, ios::out | ios::binary);
    file.write(reinterpret_cast(serialized->data()), serialized->size());

指定優化的TensorRT引擎在默認情況下應該使用FP16精度。這樣做是為了利用Volta和Turing GPUs中的張量核微體系結構來獲得更好的推理性能。還提供了工作流程,為模型啟用INT8精度,以獲得更高的性能。             

與FP32和FP16精度不同,對TensorRT使用INT8精度需要額外的步驟。需要提供一個校准數據集,以便在優化過程中使用,以確定網絡中每個層的FP32和INT8精度之間的適當比例因子,從而將推理精度的損失降到最低。通過對這些RetinaNet網絡模型和COCO17數據集的實驗確定,可以在精度損失最小的情況下校准模型以達到INT8精度。

if (int8) {
        builder->setInt8Mode(int8);
        ImageStream stream(batch, inputDims, calibration_images);
        Int8EntropyCalibrator* calib = new Int8EntropyCalibrator(stream, model_name, calibration_table);
        builder->setInt8Calibrator(calib);
    }

Conclusion

演示了如何為gpu創建對象檢測管道的示例,並介紹了用於優化端到端工作流的NVIDIA庫。相信,這項工作可以作為一個大綱,為開發人員尋求有效地創建和部署對象檢測模型的gpu,並作為一個詳細的例子,如何統一元素的NVIDIA深度學習軟件堆棧到一個單一的工作流程。將繼續開發這個項目,包括將模型集成到NVIDIA DeepStream應用程序中。一定要查看存儲庫以備將來更新。查看繼續工作的后續帖子。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM