Yolov5s模型在全志V853平台上的部署方法和应用
AI部署這個詞兒大家肯定不陌生,可能有些人還不是很清楚這個是干嘛的,但總歸是聽過了。近些年來,在深度學習算法已經足夠卷之后,深度學習的另一個偏向于工程的方向–部署應用落地,才開始被談論的多了起來。當然這也是大勢所趨,畢竟AI算法那么多,如果用不著,只在學術圈搞研究的話沒有意義。因此很多AI部署相關行業和AI芯片相關行業也在迅速發展,現在雖然已經2022年了,但我認為AI部署相關的行業還未到頭,AI也遠遠沒有普及,還有很多的場景未能落地。隨著人工智能逐漸普及,使用神經網絡處理各種任務的需求越來越多,如何在生產環境中快速、穩定、高效地運行模型,成為很多公司不得不考慮的問題。不論是通過提升模型速度降低latency提高用戶的使用體驗,還是加速模型訓練降低訓練成本,都是很有用的,很多公司也需要這樣的人才。在經歷了算法的神仙打架、諸神黃昏,? 灰飛煙滅,再到現在的直接車毀人亡、人間地獄,唯有應用部署還能廣大工程師留點活路。
最常見的場景,AI算法部署需要涉及三種不同的分工角色,包括算法提供者(算法商,開源算法社區,比如商湯,darknet, github上各類開源算法等等),算力平臺提供者(SOC廠商,目的是提供算力平臺),以及深度學習IP Vendor(比如算豐,比特大陸,芯原等等).它們的關系簡單理解如下:
當然也會有些垂直類的廠商,比如RK,他既可以做神經網絡加速器IP,同是也具備集成研發SOC以及部署工具開發的能力,這樣的整合同時會加強它的競爭力。比較郁悶的是算法廠商,很難找到一條合適的盈利模式,純做算法的算法嘗試盈利模式單一,再加上各類AI應用場景的碎片化,這些年早已風光不再,生存比較艱難。
扯了這么多,主要是想說明AI部署是需要重視的專業技術方向,今天就以為全志V853部署YOLOV5模型為例,講一點AI應用部署的干貨,拋磚引玉。
V853 平臺介紹
根據官網資料得到的數據如下:
YOLOV5S模型介紹
2020年2月,yolo之父Joseph Redmon宣布退出計算機視覺研究的時候,很多人認為目標檢測器YOLO系列就此終結,沒想到的是,2020年4月份曾經參與YOLO項目維護Alexey Bochkovskiy帶著論文《Optimal Speed and Accuracy of Object Detection》和代碼在Github上重磅開源,YOLOv4正式發布!令我們更沒想到的是,2020年6月份,短短兩個月,Ultralytics LLC?公司的創始人兼 CEO Glenn Jocher 在 GitHub 上發布了YOLOV5?的一個開源實現,標志著YOLOv5的到來!
YOLOV5分為多個版本,它們詳細的推理數據如下,數據來源于github:
https://github.com/ultralytics/yolov5/releases
這里根據對V853 1T算力的評估,我們部署其中的小模型版本,yolov5s-v6.1,也就是圖中的第二行數據代表的模型。yolov5s-v6.1 mAP數據如下圖所示,橙色的線就是YOLOV5Sd的數據,可以看到它的最高mAP可以達到45個點,根據mAP的計算方式,這個指標表示它能對數據集中的絕大部分目標進行有效檢測了。
下面開始介紹具體的部署操作
部署邏輯
以darknet為例,端側部署和板端部署的對應關系如下:
模型部署的流程如下圖所示:
下面的操作我們會用到兩個工具,分別是acuity進行模型編譯,優化,而IDE則進行模型的仿真和性能,數據分析。
模型獲取
模型的下載地址:
https://github.com/ultralytics/yolov5/releases
使用最新的6.1版本的yolov5s模型,默認是pytorch版。
下載下來后,由于V853部署工具對ONNX模型的支持比較友好,所有按照博客:
pytorch yolov5 推理和訓練環境搭建_papaofdoudou的博客-CSDN博客_yolov5推理
中介紹的方式,將其轉換為onnx模型,并用工具onnxsim對模型進行優化,得到最終的部署模型yolov5s-sim.onnx.
模型結構:
YOLOV5S是一個但輸入,單輸出的網絡,但是我們可以修改其結構,將其變為多輸出網絡,這樣做是有一定好處的,后文我們將會針對具體問題加以闡述。
模型部署
按照文檔要求創建部署目錄如下,由于我們的目的是在端側進行推理部署,使用的是訓練好的模型,這里放入dataset數據集的目的是為工具提供量化的依據,我這里只是為了演示,僅僅放置了兩張圖片,實際產品部署的時候,這里最好放足夠多的圖像,并且這些圖像最好和算法實際部署場景的圖像保持同樣的像素分布為好。
浮點部署:
進行浮點部署的目的是獲取golden tensor,驗證原型模型的精度,由于浮點部署不需要經過量化,所以部署后的模型精度就是原始模型的精度(也就是說不會出現掉精度的情況). 依次執行如下命令,導入模型:
pegasus.py import onnx --model yolov5s-sim.onnx --output-data yolov5s-sim.data --output-model yolov5s-sim.jsonpegasus.py import onnx --model yolov5s-sim.onnx --output-data yolov5s-sim.data --output-model yolov5s-sim.json pegasus.py generate inputmeta --model yolov5s-sim.json --input-meta-output yolov5s-sim-inputmeta.yml pegasus.py generate postprocess-file --model yolov5s-sim.json --postprocess-file-output yolov5s-sim-postprocess-file.yml pegasus.py inference --model yolov5s-sim.json --model-data yolov5s-sim.data --batch-size 1 --dtype float32 --device CPU --with-input-meta yolov5s-sim-inputmeta.yml --postprocess-file yolov5s-sim-postprocess-file.yml pegasus.py export ovxlib --model yolov5s-sim.json --model-data yolov5s-sim.data --dtype float32 --batch-size 1 --save-fused-graph --target-ide-project 'linux64' --with-input-meta yolov5s-sim-inputmeta.yml --postprocess-file yolov5s-sim-postprocess-file.yml --output-path ovxlib/yolov5s/yolov5sprj --pack-nbg-unify --optimize "VIP9000PICO_PID0XEE" --viv-sdk ${VIV_SDK}需要注意的是,需要在執行第三步結束后,將input yml scale參數設置為1/255=0.0039,為了和訓練時保持一致。?
這部操作專業的叫法叫做歸一化,具體操作是減均值,除標準差,用公式表示就是:
具體原理可以看下面博客的分析,這里不在贅述。
imagenet數據集的歸一化參數_papaofdoudou的博客-CSDN博客_imagenet歸一化l
非對稱UINT8量化版部署:
pegasus.py import onnx --model yolov5s-sim.onnx --output-data yolov5s-sim.data --output-model yolov5s-sim.jsonpegasus.py import onnx --model yolov5s-sim.onnx --output-data yolov5s-sim.data --output-model yolov5s-sim.json pegasus.py generate inputmeta --model yolov5s-sim.json --input-meta-output yolov5s-sim-inputmeta.yml pegasus.py generate postprocess-file --model yolov5s-sim.json --postprocess-file-output yolov5s-sim-postprocess-file.yml pegasus.py quantize --model yolov5s-sim.json --model-data yolov5s-sim.data --batch-size 1 --device CPU --with-input-meta yolov5s-sim-inputmeta.yml --rebuild --model-quantize yolov5s-sim.quantize --quantizer asymmetric_affine --qtype uint8 pegasus.py inference --model yolov5s-sim.json --model-data yolov5s-sim.data --batch-size 1 --dtype quantized --model-quantize yolov5s-sim.quantize --device CPU --with-input-meta yolov5s-sim-inputmeta.yml --postprocess-file yolov5s-sim-postprocess-file.yml pegasus.py export ovxlib --model yolov5s-sim.json --model-data yolov5s-sim.data --dtype quantized --model-quantize yolov5s-sim.quantize --batch-size 1 --save-fused-graph --target-ide-project 'linux64' --with-input-meta yolov5s-sim-inputmeta.yml --postprocess-file yolov5s-sim-postprocess-file.yml --output-path ovxlib/yolov5s/yolov5sprj --pack-nbg-unify --optimize "VIP9000PICO_PID0XEE" --viv-sdk ${VIV_SDK}部署結束后,對比輸出tensor的相似度,發現量化后,最后一層的相似度精度下降很多:
$ python /home/caozilong/VeriSilicon/acuity-toolkit-whl-6.6.1/bin/tools/compute_tensor_similarity.py ./quant quant_input.tensor quanti_output.tensor (vip) caozilong@AwExdroid-AI:~/Workspace/yolov5s-v6.1-deploy$ python /home/caozilong/VeriSilicon/acuity-toolkit-whl-6.6.1/bin/tools/compute_tensor_similarity.py ./quant quant_input.tensor quanti_output.tensor (vip) caozilong@AwExdroid-AI:~/Workspace/yolov5s-v6.1-deploy$ python /home/caozilong/VeriSilicon/acuity-toolkit-whl-6.6.1/bin/tools/compute_tensor_similarity.py ./quanti_output.tensor ./iter_0_attach_Concat_Concat_255_out0_0_out0_1_25200_85.tensor 2022-07-02 15:46:43.269805: W tensorflow/stream_executor/platform/default/dso_loader.cc:59] Could not load dynamic library 'libcudart.so.10.1'; dlerror: libcudart.so.10.1: cannot open shared object file: No such file or directory 2022-07-02 15:46:43.269853: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine. 2022-07-02 15:47:09.426509: W tensorflow/stream_executor/platform/default/dso_loader.cc:59] Could not load dynamic library 'libcuda.so.1'; dlerror: libcuda.so.1: cannot open shared object file: No such file or directory 2022-07-02 15:47:09.426556: W tensorflow/stream_executor/cuda/cuda_driver.cc:312] failed call to cuInit: UNKNOWN ERROR (303) 2022-07-02 15:47:09.426580: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:156] kernel driver does not appear to be running on this host (AwExdroid-AI): /proc/driver/nvidia/version does not exist 2022-07-02 15:47:09.453345: I tensorflow/core/platform/profile_utils/cpu_utils.cc:104] CPU Frequency: 2393990000 Hz 2022-07-02 15:47:09.455335: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x5598fe9cc010 initialized for platform Host (this does not guarantee that XLA will be used). Devices: 2022-07-02 15:47:09.455375: I tensorflow/compiler/xla/service/service.cc:176] StreamExecutor device (0): Host, Default Version WARNING:tensorflow:From /home/caozilong/anaconda3/envs/vip/lib/python3.6/site-packages/tensorflow/python/util/dispatch.py:201: calling cosine_distance (from tensorflow.python.ops.losses.losses_impl) with dim is deprecated and will be removed in a future version. Instructions for updating: dim is deprecated, use axis instead euclidean_distance 20061.037 cos_similarity 0.972248余弦相似度只有97%,歐氏距離也很大,有問題,數據是有形狀的,我們在仔細看一下這兩筆tensor的分布情況:
量化輸入:
浮點輸入:
?可以看到兩筆tensor的形狀是非常相似的,這說明量化非常好的保留了輸入數據的特征,那再看輸出數據呢?
量化輸出:
浮點推理輸出:
輸出數據的特征非常明顯,大部分的數值集中在0附近,但是也有個別極大的點分布在0-600之間。雖然量化也很好的保留了原始數據的分布形狀,但是這種形態的輸出是對量化非常不友好的,量化最prefer那種接近于正態分布的數據分布。針對這個模型來說,如果按照量化方式部署,最終造成的后果,可能就是模型的精度掉的太厲害,無法滿足場景要求。
怎么解決這個問題呢?我們要找出癥結所在,這個癥結就在網絡最后幾層:
YOLOV5S網絡特殊的地方在于,圖中被紅色框框住的部分,數據分布不適合做量化處理,通常被認為被框住的三個紅色分支應屬于后處理的部分,應該由CPU去做而不是NPU。所以正確的做法應該是從transpose輸出頭哪里將輸出輸出出來,從新定義輸出節點,從圖中可以看到,這里有三個輸出頭,就應該有三個輸出節點。
之所以能這樣做還有一個原因,就是模型部署工具是支持自定義輸入層和數層的,你可以通過命令行指定的方式將任何一層定義為輸出或者輸入層。
指定輸出層重新部署
通過命令行指定輸出層--outputs "onnx::Sigmoid_339 onnx::Sigmoid_377 onnx::Sigmoid_415"
pegasus.py import onnx --model yolov5s-sim.onnx --output-data yolov5s-sim.data --output-model yolov5s-sim.json --outputs "onnx::Sigmoid_339 onnx::Sigmoid_377 onnx::Sigmoid_415" pegasus.py generate inputmeta --model yolov5s-sim.json --input-meta-output yolov5s-sim-inputmeta.yml pegasus.py generate postprocess-file --model yolov5s-sim.json --postprocess-file-output yolov5s-sim-postprocess-file.yml pegasus.py quantize --model yolov5s-sim.json --model-data yolov5s-sim.data --batch-size 1 --device CPU --with-input-meta yolov5s-sim-inputmeta.yml --rebuild --model-quantize yolov5s-sim.quantize --quantizer asymmetric_affine --qtype uint8 pegasus.py inference --model yolov5s-sim.json --model-data yolov5s-sim.data --batch-size 1 --dtype quantized --model-quantize yolov5s-sim.quantize --device CPU --with-input-meta yolov5s-sim-inputmeta.yml --postprocess-file yolov5s-sim-postprocess-file.yml --iterations 2 pegasus.py export ovxlib --model yolov5s-sim.json --model-data yolov5s-sim.data --dtype quantized --model-quantize yolov5s-sim.quantize --batch-size 1 --save-fused-graph --target-ide-project 'linux64' --with-input-meta yolov5s-sim-inputmeta.yml --postprocess-file yolov5s-sim-postprocess-file.yml --output-path ovxlib/yolov5s/yolov5sprj --pack-nbg-unify --optimize "VIP9000PICO_PID0XEE" --viv-sdk ${VIV_SDK}得到推理階段的tensor,由于是現在模型改成了三個輸出,所以,data目錄中的兩張圖像對應了六筆輸出tensor.
更改輸出層后的網絡模型結構,可以看到之前需要后處理的那一坨不見了,轉而成了三個獨立的輸出層:
后處理:
參考如下連接的代碼,將后處理部分摳出來改成綠色小程序:
https://github.com/OAID/Tengine/blob/tengine-lite/examples/tm_yolov5s_timvx.cpp代碼如下:
#include <iostream> #include <stdio.h> #include <vector> #include <math.h> #include <float.h> #include <stdlib.h> #include <string.h> using namespace std;enum Yolov5OutType {p8_type = 1,p16_type = 2,p32_type = 3, };typedef struct __Rect__ {float x, y, width, height; } Rect;struct Object {Rect rect;int label;float prob; };static float overlap(float x1, float w1, float x2, float w2) {float l1 = x1 - w1/2;float l2 = x2 - w2/2;float left = l1 > l2 ? l1 : l2;float r1 = x1 + w1/2;float r2 = x2 + w2/2;float right = r1 < r2 ? r1 : r2;return right - left; }float box_intersection(Rect a, Rect b) {float w = overlap(a.x, a.width, b.x, b.width);float h = overlap(a.y, a.height, b.y, b.height);if(w < 0 || h < 0) return 0;float area = w*h;return area; }static inline float sigmoid(float x) {return static_cast<float>(1.f / (1.f + exp(-x))); }static inline float intersection_area(const Object& a, const Object& b) {return box_intersection(a.rect, b.rect); }static void qsort_descent_inplace(std::vector<Object>& faceobjects, int left, int right) {int i = left;int j = right;float p = faceobjects[(left + right) / 2].prob;while (i <= j){while (faceobjects[i].prob > p)i++;while (faceobjects[j].prob < p)j--;if (i <= j){// swapstd::swap(faceobjects[i], faceobjects[j]);i++;j--;}}#pragma omp parallel sections{ #pragma omp section{if (left < j) qsort_descent_inplace(faceobjects, left, j);} #pragma omp section{if (i < right) qsort_descent_inplace(faceobjects, i, right);}} }static void qsort_descent_inplace(std::vector<Object>& faceobjects) {if (faceobjects.empty())return;qsort_descent_inplace(faceobjects, 0, faceobjects.size() - 1); }static void nms_sorted_bboxes(const std::vector<Object>& faceobjects, std::vector<int>& picked, float nms_threshold) {picked.clear();const int n = faceobjects.size();std::vector<float> areas(n);for (int i = 0; i < n; i++){areas[i] = faceobjects[i].rect.width * faceobjects[i].rect.height;;}for (int i = 0; i < n; i++){const Object& a = faceobjects[i];int keep = 1;for (int j = 0; j < (int)picked.size(); j++){const Object& b = faceobjects[picked[j]];// intersection over unionfloat inter_area = intersection_area(a, b);float union_area = areas[i] + areas[picked[j]] - inter_area;if (inter_area / union_area > nms_threshold)keep = 0;}if (keep)picked.push_back(i);} }static void generate_proposals(int stride, const float* feat, float prob_threshold, std::vector<Object>& objects,int letterbox_cols, int letterbox_rows) {static float anchors[18] = {10, 13, 16, 30, 33, 23, 30, 61, 62, 45, 59, 119, 116, 90, 156, 198, 373, 326};int anchor_num = 3;int feat_w = letterbox_cols / stride;int feat_h = letterbox_rows / stride;int cls_num = 80;int anchor_group;if (stride == 8)anchor_group = 1;if (stride == 16)anchor_group = 2;if (stride == 32)anchor_group = 3;for (int h = 0; h <= feat_h - 1; h++){for (int w = 0; w <= feat_w - 1; w++){for (int a = 0; a <= anchor_num - 1; a++){//process cls scoreint class_index = 0;float class_score = -FLT_MAX;for (int s = 0; s <= cls_num - 1; s++){float score = feat[a * feat_w * feat_h * (cls_num + 5) + h * feat_w * (cls_num + 5) + w * (cls_num + 5) + s + 5];if (score > class_score){class_index = s;class_score = score;}}//process box scorefloat box_score = feat[a * feat_w * feat_h * (cls_num + 5) + (h * feat_w) * (cls_num + 5) + w * (cls_num + 5) + 4];float final_score = sigmoid(box_score) * sigmoid(class_score);if (final_score >= prob_threshold){int loc_idx = a * feat_h * feat_w * (cls_num + 5) + h * feat_w * (cls_num + 5) + w * (cls_num + 5);float dx = sigmoid(feat[loc_idx + 0]);float dy = sigmoid(feat[loc_idx + 1]);float dw = sigmoid(feat[loc_idx + 2]);float dh = sigmoid(feat[loc_idx + 3]);float pred_cx = (dx * 2.0f - 0.5f + w) * stride;float pred_cy = (dy * 2.0f - 0.5f + h) * stride;float anchor_w = anchors[(anchor_group - 1) * 6 + a * 2 + 0];float anchor_h = anchors[(anchor_group - 1) * 6 + a * 2 + 1];float pred_w = dw * dw * 4.0f * anchor_w;float pred_h = dh * dh * 4.0f * anchor_h;float x0 = pred_cx - pred_w * 0.5f;float y0 = pred_cy - pred_h * 0.5f;float x1 = pred_cx + pred_w * 0.5f;float y1 = pred_cy + pred_h * 0.5f;Object obj;obj.rect.x = x0;obj.rect.y = y0;obj.rect.width = x1 - x0;obj.rect.height = y1 - y0;obj.label = class_index;obj.prob = final_score;objects.push_back(obj);}}}} }static int detect_yolov5(std::vector<Object>& objects, float **output) {int size0 = 1*3*80*80*85;int size1 = 1*3*40*40*85;int size2 = 1*3*20*50*85;std::vector<float> p8_data(output[0], &output[0][size0-1]);std::vector<float> p16_data(output[1], &output[1][size1-1]);std::vector<float> p32_data(output[2], &output[2][size2-1]);// set default letterbox sizeint letterbox_rows = 640;int letterbox_cols = 640;/* postprocess */const float prob_threshold = 0.4f;const float nms_threshold = 0.45f;std::vector<Object> proposals;std::vector<Object> objects8;std::vector<Object> objects16;std::vector<Object> objects32;//std::vector<Object> objects;generate_proposals(32, p32_data.data(), prob_threshold, objects32, letterbox_cols, letterbox_rows);proposals.insert(proposals.end(), objects32.begin(), objects32.end());generate_proposals(16, p16_data.data(), prob_threshold, objects16, letterbox_cols, letterbox_rows);proposals.insert(proposals.end(), objects16.begin(), objects16.end());generate_proposals(8, p8_data.data(), prob_threshold, objects8, letterbox_cols, letterbox_rows);proposals.insert(proposals.end(), objects8.begin(), objects8.end());qsort_descent_inplace(proposals);std::vector<int> picked;nms_sorted_bboxes(proposals, picked, nms_threshold);/* yolov5 draw the result */float scale_letterbox;int resize_rows;int resize_cols;if ((letterbox_rows * 1.0 / 640) < (letterbox_cols * 1.0 / 640)){scale_letterbox = letterbox_rows * 1.0 / 640;}else{scale_letterbox = letterbox_cols * 1.0 / 640;}resize_cols = int(scale_letterbox * 640);resize_rows = int(scale_letterbox * 640);int tmp_h = (letterbox_rows - resize_rows) / 2;int tmp_w = (letterbox_cols - resize_cols) / 2;float ratio_x = (float)640 / resize_rows;float ratio_y = (float)640 / resize_cols;int count = picked.size();fprintf(stderr, "detection num: %d\n", count);objects.resize(count);for (int i = 0; i < count; i++){objects[i] = proposals[picked[i]];float x0 = (objects[i].rect.x);float y0 = (objects[i].rect.y);float x1 = (objects[i].rect.x + objects[i].rect.width);float y1 = (objects[i].rect.y + objects[i].rect.height);x0 = (x0 - tmp_w) * ratio_x;y0 = (y0 - tmp_h) * ratio_y;x1 = (x1 - tmp_w) * ratio_x;y1 = (y1 - tmp_h) * ratio_y;x0 = std::max(std::min(x0, (float)(640 - 1)), 0.f);y0 = std::max(std::min(y0, (float)(640 - 1)), 0.f);x1 = std::max(std::min(x1, (float)(640 - 1)), 0.f);y1 = std::max(std::min(y1, (float)(640 - 1)), 0.f);objects[i].rect.x = x0;objects[i].rect.y = y0;objects[i].rect.width = x1 - x0;objects[i].rect.height = y1 - y0;printf("%s line %d, x:%f, y %f, w: %f, h %f.\n", __func__, __LINE__, objects[i].rect.x, objects[i].rect.y, objects[i].rect.width, objects[i].rect.height);}return 0; }int yolov5s_postprocess(float **output) {printf("yolov5s_postprocess.c run. \n");std::vector<Object> objects;detect_yolov5(objects, output);return 0; }int get_tensor_data(string file_path, float **data) {int len = 0;static float *memory = NULL;static int max_len = 10*1024*1024;if (memory == NULL){memory = (float *)malloc(max_len * sizeof(float));}FILE *fp = NULL;if ((fp = fopen(file_path.c_str(), "r")) == NULL){cout << "open tensor file error ! file name : " << file_path << endl;exit(-1);}int file_len = 0;while (!feof(fp)){fscanf(fp, "%f ", &memory[len++]);}*data = (float *)malloc(len * sizeof(float));memcpy(*data, memory, len * sizeof(float));fclose(fp);if (len == 0 || *data == NULL){cout << "read tensor error happened ! " << "len : " << len << " data address: " << *data << endl;exit(-1);}return len; }int main(int argc, char **argv) {float *tensor0;float *tensor1;float *tensor2;float *tensor[3];get_tensor_data(argv[1], &tensor0);get_tensor_data(argv[2], &tensor1);get_tensor_data(argv[3], &tensor2);tensor[0] = tensor0;tensor[1] = tensor1;tensor[2] = tensor2;yolov5s_postprocess(tensor);return 0; }編譯后,對輸出的兩張圖像的tensor做后處理:
?可以看到,針對圖像1,檢測出來了3個目標,坐標狂也被打印出來:
修改輸出層后的輸出數據分布情況
iter_0_attach_Transpose_Transpose_200_out0_0_out0_1_3_80_80_85.tensor:
iter_0_attach_Transpose_Transpose_219_out0_1_out0_1_3_40_40_85.tensor:
iter_0_attach_Transpose_Transpose_238_out0_2_out0_1_3_20_20_85.tensor:
iter_1_attach_Transpose_Transpose_200_out0_0_out0_1_3_80_80_85.tensor:
iter_1_attach_Transpose_Transpose_219_out0_1_out0_1_3_40_40_85.tensor:
iter_1_attach_Transpose_Transpose_238_out0_2_out0_1_3_20_20_85.tensor:
通過直方圖可以看出,新定義的三個輸出節點的分布形狀很接近于正態分布,就像前文所講,正態分布對量化是非常友好的。所以能夠解決精度丟失的問題。
關于直方圖的生成方式,可以參考博客:
根據歐式距離和余弦相似度來計算tensor之間的相似度的代碼實現_papaofdoudou的博客-CSDN博客
結束
總結
以上是生活随笔為你收集整理的Yolov5s模型在全志V853平台上的部署方法和应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c4d软件安装上打开不了_手机上可以安装
- 下一篇: java 打印异常内容_java自定义异