香橙派 Kunpeng Pro:基于ncnn的深度學習模型量化與部署實踐

一 引言

近10年里以深度學習為代表的機器學習技術在圖像處理,語音識別,自然語言處理等領域里取得了非常多的突破,其背后的核心算法是深度學習為代表的AI基礎模型。

一般來講,我們進行AI項目研發時,遵循三個步驟。

第一步,我們需要針對目標任務選定一個合適的模型架構,然后訓練出滿足精度的模型,這就是模型設計;

第二步,我們需要基于第一步訓練好的模型,在不顯著降低其精度的前提下,對模型的冗余參數進行精簡,對高精度計算進行低精度近似,這就是模型壓縮;

第三步,將模型運用于實際的生產環境,即面向用戶的產品,這就是模型部署。

深度學習模型必須要部署到實際的生產環境中才能產生真正的應用價值。在各類落地場景中,有的是服務端應用,它需要模型有更高的精度、更復雜的功能;有的是嵌入式平臺應用,諸如手機等各類移動端設備與車載設備,它需要模型具有體積小、低延遲特性。因此我們在進行模型設計與部署的時候,需要根據應用場景選擇不同的模型和工具。當前模型優化和部署的工具非常多,常見的包括TensorRT、NCNN等;當前的硬件計算平臺也非常多,包括CPU、GPU,NPU、FPGA等。

現在市面上有各種算力的嵌入式設備,強勁一點的如NVIDIA Jetson,弱一點的如樹莓派,本次我們從CSDN社區收到了一塊OrangePi Kunpeng Pro(香橙派開發板),正好來試一試這塊板子的使用與性能。

下面是這塊板子的實拍圖與開機界面。

?

二 熟悉板子

拿到板子后當然首先要熟悉一下基本情況,包括軟硬件相關信息。

第一步:使用/etc/os-release命令查看系統信息。

該板子的操作系統是openeuler 22.03(LTS-SP3),前身是華為自主研發的服務器操作系統EulerOS,其名字來自于1752年數學家歐拉所發現的歐拉公式。后來華為將其捐贈給開放原子開源基金會(OpenAtom Foundation)孵化及運營,項目地址為https://gitee.com/openeuler

openEuler本質上也是一個Linux系統,每兩年推出一個LTS版本,每半年發布一次創新版,支持鯤鵬及其他多種處理器,許多系統底層命令與一般Linux系統無異,一般的文件與目錄操作等命令,即ls,cd,cp等均不變,軟件安裝則與centos一樣使用yum或者dnf管理。

第二步:修改一些相關配置

一個全新的Linux系統,總有一些東西需要先改一改,比如默認的軟件源。直接打開/etc/yum.repos.d/openEuler.repo,將其內容替換為以下內容。

[OS]name=OSbaseurl=https://mirrors.aliyun.com/openeuler/openEuler-22.03-LTS-SP2/OS/$basearch/enabled=1gpgcheck=1gpgkey=https://mirrors.aliyun.com/openeuler/openEuler-22.03-LTS-SP2/OS/$basearch/RPM-GPG-KEY-openEuler[everything]name=everythingbaseurl=https://mirrors.aliyun.com/openeuler/openEuler-22.03-LTS-SP2/everything/$basearch/enabled=1gpgcheck=1gpgkey=https://mirrors.aliyun.com/openeuler/openEuler-22.03-LTS-SP2/everything/$basearch/RPM-GPG-KEY-openEuler[EPOL]name=EPOLbaseurl=https://mirrors.aliyun.com/openeuler/openEuler-22.03-LTS-SP2/EPOL/main/$basearch/enabled=1gpgcheck=1gpgkey=https://mirrors.aliyun.com/openeuler/openEuler-22.03-LTS-SP2/OS/$basearch/RPM-GPG-KEY-openEuler[debuginfo]name=debuginfobaseurl=https://mirrors.aliyun.com/openeuler/openEuler-22.03-LTS-SP2/debuginfo/$basearch/enabled=1gpgcheck=1gpgkey=https://mirrors.aliyun.com/openeuler/openEuler-22.03-LTS-SP2/debuginfo/$basearch/RPM-GPG-KEY-openEuler[source]name=sourcebaseurl=https://mirrors.aliyun.com/openeuler/openEuler-22.03-LTS-SP2/source/enabled=1gpgcheck=1gpgkey=https://mirrors.aliyun.com/openeuler/openEuler-22.03-LTS-SP2/source/RPM-GPG-KEY-openEuler[update]name=updatebaseurl=https://mirrors.aliyun.com/openeuler/openEuler-22.03-LTS-SP2/update/$basearch/enabled=1gpgcheck=1gpgkey=https://mirrors.aliyun.com/openeuler/openEuler-22.03-LTS-SP2/OS/$basearch/RPM-GPG-KEY-openEuler[update-source]name=update-sourcebaseurl=https://mirrors.aliyun.com/openeuler/openEuler-22.03-LTS-SP2/update/source/enabled=1gpgcheck=1gpgkey=https://mirrors.aliyun.com/openeuler/openEuler-22.03-LTS-SP2/source/RPM-GPG-KEY-openEuler

再運行命令更新

sudo dnf clean all
sudo dnf makecache

第三步:查看硬件配置

由于接下來我們要測試AI項目,因此對于板子的硬件能力心里還是要有底。

?

?可以看到是64bit系統,6個CPU,8GB內存,算力暫時不詳。

第四步:安裝一些必要的庫,包括opencv-cv,protobuf等。

sudo dnf install protobuf-compiler protobuf # 文件格式庫
sudo dnf install gtk2-devel gtk2-devel-docs # 桌面顯示庫
sudo dnf install pkg-config # 編譯器的輔助工具,可以幫助 GCC 找到所需要的頭文件與庫文件路徑
sudo dnf install mlocate # 文件查找庫
sudo dnf install opencv opencv-devel
sudo pip install opencv-python # opencv庫
sudo pip install torch # torch庫
sudo pip install torchvision # torchvision庫
sudo pip install onnxruntime # onnx庫

系統已經預裝了python3.9,后續使用如果還缺什么就用pip裝就是。

?

三 模型部署

接下來我們使用移動端NCNN框架,以及香橙派,來體驗典型的移動端模型部署流程,包括模型的格式轉換、模型量化、基于C++的模型推理部署。

框架介紹

本次我們選擇的部署框架是NCNN,它是一個在工業界被廣為使用的框架,具有非常好的性能。

NCNN是一個純C++實現的框架,無任何第三方庫依賴,不依賴 BLAS/NNPACK 等計算框架,提供了ARM NEON 匯編級良心優化,計算速度極快。NCNN提供了對所有主流操作系統的支持,如圖所示;

標題NCNN支持的操作系統?

?

NCNN支持PaddlePaddle/PyTorch/TensorFlow/Caffe/MXNet/DarkNet/OneFlow/ONNX等深度學習框架文件格式,支持CNN、GAN等常用網絡模型結構;

NCNN支持Intel架構的CPU與GPU,AMD架構的CPU與GPU,Arm架構的CPU與GPU,高通架構的CPU與GPU,Apple架構的CPU與GPU,其中對高通公司的CPU,ARM公司的CPU以及Apple公司的CPU提供了非常高效率地優化加速;

NCNN支持FP32/FP16/INT8/UINT8等多種運算精度;

NCNN支持C/C++/Python API;

NCNN支持直接內存零拷貝引用加載網絡模型,可注冊自定義層實現并擴展;

要使用NCNN,首先需要下載源碼進行編譯安裝,相關代碼命令如下:

git clone https://github.com/Tencent/ncnn
cd ncnn
mkdir build && cd build
cmake ..
make -j
make install

?

?

?

安裝完之后,就可以在build/install目錄下看到生成的一系列可執行文件和需要的庫文件,它們分別存儲于bin子目錄和include子目錄。如果github訪問不了,就換gitee地址。

模型轉換與量化

在進行部署之前,需要對模型格式進行轉換,相關工具在ncnn根目錄/build/install/bin目錄下,包括???

caffe2ncnn:caffe模型轉換工具
darknet2ncnn: mxnet模型轉換工具
mxnet2ncnn: mxnet模型轉換工具
onnx2ncnn: onnx模型轉換工具
ncnn2table, ncnn2int8: 模型量化工具
ncnn2mem:模型加密可執行文件:
ncnnoptimize:模型優化可執行文件
ncnnmerge:模型合并可執行文件

完整的模型量化流程可以分為3步,以ONNX格式為例:

第一步,將ONNX格式的模型轉換為NCNN格式的模型,所使用的模型是一個訓練好的圖像分類模型。

其模型配置如下:

## 簡單模型定義
class simpleconv5(nn.Module):def __init__(self,nclass):super(simpleconv5,self).__init__()self.conv1 = nn.Conv2d(3, 32, 3, 2, 1, bias=False)self.bn1 = nn.BatchNorm2d(32)self.conv2 = nn.Conv2d(32, 64, 3, 2, 1, bias=False)self.bn2 = nn.BatchNorm2d(64)self.conv3 = nn.Conv2d(64, 128, 3, 2, 1, bias=False)self.bn3 = nn.BatchNorm2d(128)self.conv4 = nn.Conv2d(128, 256, 3, 2, 1, bias=False)self.bn4 = nn.BatchNorm2d(256)self.conv5 = nn.Conv2d(256, 512, 3, 2, 1, bias=False)self.bn5 = nn.BatchNorm2d(512)self.fc = nn.Linear(512, nclass)def forward(self , x):x = F.relu(self.bn1(self.conv1(x)))x = F.relu(self.bn2(self.conv2(x)))x = F.relu(self.bn3(self.conv3(x)))x = F.relu(self.bn4(self.conv4(x)))x = F.relu(self.bn5(self.conv5(x)))x = nn.AvgPool2d(7)(x)x = x.view(x.size(0), -1) x = self.fc(x)return x
onnx2ncnn simpleconv5.onnx simpleconv5.param simpleconv5.bin

生成的ncnn格式的模型包括兩個文件,simpleconv5.param是網絡的配置文件,simpleconv5.bin是網絡的權重文件。

第二步,生成int8量化所需要的校準表。

ncnn2table models/simpleconv5.param models/simpleconv5.bin images.txt simpleconv5.table mean=[127.5,127.5,127.5] norm=[0.00784,0.00784,0.00784] shape=[48,48,3] pixel=RGB

?其中ncnn2table工具默認使用基于KL散度的8bit量化算法,它輸入模型文件simpleconv5.param和simpleconv5.bin,校準表圖片路徑images.txt,預處理均值mean與標準化norm值,輸入圖片尺寸,RGB圖片的格式,輸出simpleconv5.table,即校準表。pixel=RGB表示輸入網絡的圖片是RGB格式,當我們使用OpenCV進行圖片讀取后是BGR格式,兩者需要進行區分。由于ncnn框架讀取的圖片數據像素值范圍是0到127,而模型訓練時采用的預處理操作包括除以255進行歸一化,再減去均值向量[0.5,0.5,0.5],除以方差向量[0.5,0.5,0.5],因此這里對應的預處理操作需要將歸一化操作合并到減均值操作和除以方差操作中,mean=255×[0.5,0.5,0.5]=[127.5,127.5,127.5], norm=1/255/[0.5,0.5,0.5]=[0.00784,0.00784,0.00784]

第三步,基于校準表進行量化。

量化前模型大小為6.4MB,量化后模型大小為1.6MB,8bit模型大小為float32模型大小的1/4,減少了存儲空間。

模型部署測試

接下來我們使用C++接口對模型進行部署測試,并比較量化前后的模型精度是否受到嚴重影響,測試的核心C++功能函數代碼如下:

#include "net.h"#include <algorithm>
#if defined(USE_NCNN_SIMPLEOCV)
#include "simpleocv.h"
#else
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#endif
#include <stdio.h>
#include <vector>
#include <cmath>
//推理函數
static int detect_simpleconv5net(const ncnn::Net &simpleconv5net,const cv::Mat& bgr, std::vector<float>& cls_scores)
{ncnn::Mat in = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR2RGB, bgr.cols, bgr.rows, 224, 224);//讀取圖片數據const float mean_vals[3] = {0.5f*255.f, 0.5f*255.f, 0.5f*255.f};const float norm_vals[3] = {1/0.5f/255.f, 1/0.5f/255.f, 1/0.5f/255.f};in.substract_mean_normalize(mean_vals, norm_vals); //預處理ncnn::Extractor ex = simpleconv5net.create_extractor();//創建推理引擎ex.input("input.1", in); //填充數據ncnn::Mat out;float start_time = cv::getTickCount(); //計算模型推理時間ex.extract("59", out); //獲得模型推理結果float end_time = cv::getTickCount();fprintf(stderr, "%s = %f %s\n", "inference time = ", (end_time-start_time)/cv::getTickFrequency()*1000, " ms");cls_scores.resize(out.w); //取softmax分類概率結果,指數減去固定值防止溢出處理float maxscore = 0.0;for (int j = 0; j < out.w; j++){if(out[j] >= maxscore) maxscore = out[j]; cls_scores[j] = out[j];}float sum = 0.0;for (int j = 0; j < out.w; j++){cls_scores[j] = std::exp(cls_scores[j]-maxscore);sum += cls_scores[j]; }for (int j = 0; j < out.w; j++){cls_scores[j] = cls_scores[j] / sum;}return 0;
}int main(int argc, char** argv)
{if (argc != 5){fprintf(stderr, "Usage: %s%s%s [modelparam modelbin imagepath resultpath]\n", argv[0], argv[1], argv[2], argv[3]);return -1;}const char* modelparam = argv[1];const char* modelbin = argv[2];const char* imagepath = argv[3];const char* resultpath = argv[4];//初始化模型ncnn::Net simpleconv5net;simpleconv5net.opt.use_vulkan_compute = true;simpleconv5net.load_param(modelparam);simpleconv5net.load_model(modelbin);cv::Mat image = cv::imread(imagepath, 1);if (image.empty()){fprintf(stderr, "cv::imread %s failed\n", imagepath);return -1;}//獲得topk的分類概率std::vector<float> cls_scores;detect_simpleconv5net(simpleconv5net, image, cls_scores);int topk = 1;int size = cls_scores.size();std::vector<std::pair<float, int> > vec;vec.resize(size);for (int i = 0; i < size; i++){vec[i] = std::make_pair(cls_scores[i], i);}std::partial_sort(vec.begin(), vec.begin() + topk, vec.end(),std::greater<std::pair<float, int> >());for (int i = 0; i < topk; i++){float score = vec[i].first;int index = vec[i].second;fprintf(stderr, "%d = %f\n", index, score);}//繪制結果std::string text;std::string label = "c="+std::to_string(vec[0].second);std::string prob = "prob="+std::to_string(vec[0].first);text.assign(label+"   ");text.append(prob);int font_face = cv::FONT_HERSHEY_COMPLEX; double font_scale = 0.75;int thickness = 2;//將文本框居中繪制cv::Mat showimage = image.clone();cv::resize(showimage,showimage,cv::Size(256,256));cv::Point origin; origin.x = showimage.cols / 20;origin.y = showimage.rows / 2;cv::putText(showimage, text, origin, font_face, font_scale, cv::Scalar(0, 255, 255), thickness, 8, 0);cv::namedWindow("image",0);cv::imshow("image",showimage);//cv::waitKey(0);cv::imwrite(resultpath,showimage);return 0;
}

NCNN中每一層的數據被保存為自定義的Mat類型數據,它使用from_pixels_resize函數將OpenCV讀取的Mat矩陣數據進行轉換,由于計算使用了匯編,非常高效。網絡定義為一個ncnn::Net類,格式與Caffe中的Net類非常相似,包含了layers和blobs成員變量,其中layers儲存了每一層的信息,blobs儲存了網絡的中間數據。

在進行推理時,首先根據net實例化一個ncnn::Extractor類,extractor中的net會被轉為const類。我們可以給extractor的任意一層送入數據,如extractor.input("data", in)就是給輸入數據層賦值。通過extractor.extract函數可以取出任意層的數據,在extract方法中,它會調用forward_layer方法遞歸地遍歷網絡。

我們從20類中每一類隨機選取一張圖片來進行測試,比較量化前的模型推理結果和量化后的模型推理結果,下圖展示了每一張圖片的預測類別及經過Softmax映射后的概率,其中奇數行為量化前的模型推理結果,偶數行為量化后的模型推理結果。

?

從圖中樣本的預測結果可以看出,量化前后模型的預測概率是有差異的,但是差異非常小,大多在1%以內,所選測試圖片的預測結果都是正確的,說明該模型經過量化后精度沒有精度損失。

下圖從上到下分別展示了量化前和量化后的模型推理時間,每一張圖片的推理時間是通過重復100次推理后計算出來的平均值,這是為了讓推理時間的計算更加穩定。可以看出,對于大部分樣本,量化前模型的推理時間約為4.4ms左右,量化后模型的推理時間約為3.7ms左右,量化后模型的推理速度提升了16%,驗證了模型量化的加速效果。

量化前推理時間

?

量化后推理時間

?

?我們也用python對onnx模型以及原生pytorch模型進行了計時,計算了平均推理時間約為25ms,相關代碼如下:

#coding:utf8
import torch
import torchvision
from torchsummary import summary
import time
import cv2
import sys
import onnxruntime
import numpy as np
import PIL.Image as Image
import os,glob
from simpleconv5 import simpleconv5
torch.manual_seed(0)
os.environ['CUDA_LAUNCH_BLOCKING'] = '1'# 圖像預處理函數
def process_image(img):input_size = [224,224]mean = (0.5,0.5,0.5)std = (0.5,0.5,0.5)img = np.asarray(img.resize((input_size[0],input_size[1]),resample=Image.NEAREST)).astype(np.float32) / 255.0img[:,:,] -= meanimg[:,:,] /= stdimage = img.transpose((2,0,1))[np.newaxis, ...]return imageimgdir = "../GHIM-20"'''
##-----------------------test pytorch------------------------##
# 加載模型
model = simpleconv5(20)
modelpath = '../models/model_best.pth.tar'
model.load_state_dict(torch.load(modelpath,map_location='cpu')['state_dict'])
model.eval()
acc = 0.0
nums = 0.0
start_Inference = time.time()
for imgpath in glob.glob(os.path.join(imgdir, "**/*.jpg"),recursive=True):img = Image.open(imgpath).convert('RGB')data_input = process_image(img)input_data = torch.from_numpy(data_input)data_output = model(input_data)output = data_output.squeeze().cpu().detach().numpy()pred1 = output.argmax()label = int(imgpath.split('/')[-2])if label == pred1:acc += 1.0nums += 1.0end_Inference = time.time()
print('Inference use time='+str((end_Inference-start_Inference)*1000/nums)+' ms')
print("acc=",acc/nums)
'''##-----------------------test onnx------------------------##
onnx_path = '../models/simpleconv5.onnx'
result_path = 'onnx_results'
session = onnxruntime.InferenceSession(onnx_path)
inname = [input.name for input in session.get_inputs()]
outname = [output.name for output in session.get_outputs()]
acc = 0.0
nums = 0.0
print("inputs name:",inname,"|| outputs name:",outname)
start = cv2.getTickCount()
for imgpath in glob.glob(os.path.join(imgdir, "**/*.jpg"),recursive=True):img = Image.open(imgpath).convert('RGB')data_input = process_image(img)data_output = session.run(outname, {inname[0]: data_input})output = np.squeeze(data_output[0])pred1 = output.argmax()prob = output[pred1]label = int(imgpath.split('/')[-2])if label == pred1:acc += 1.0nums += 1.0
end = cv2.getTickCount()
print('ONNX Inference Time='+str((end-start)/nums/cv2.getTickFrequency()*1000)+' ms')
print("acc=",acc/nums)

可以看出,C++推理速度相比于python推理速度,有6倍左右的提升。量化后相比于量化前的提升速度并不大,這是因為香橙派本身的硬件足夠出色,筆者的MacBook Pro?Apple M2上的onnx推理時間也需要8ms左右。

總體來講,香橙派的性能比我之前的EAIDK-310開發套件性能強多了,同樣的模型EAIDK-310量化后的推理時間需要20ms以上,以后做嵌入式項目演示,就用香橙派 Kunpeng Pro了!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/21263.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/21263.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/21263.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

LabVIEW步進電機的串口控制方法與實現

本文介紹了在LabVIEW環境中通過串口控制步進電機的方法&#xff0c;涵蓋了基本的串口通信原理、硬件連接步驟、LabVIEW編程實現以及注意事項。通過這些方法&#xff0c;用戶可以實現對步進電機的精確控制&#xff0c;適用于各種自動化和運動控制應用場景。 步進電機與串口通信…

python3.8環境下安裝pyqt5

1.實驗目的 測試python可視化工具包pyqt5,為后期做系統前端頁面做鋪墊 2.實驗環境 1.軟件 anaconda2.5 pycharm2024.1.1 pyqt5 2.硬件 GPU 4070TI Intel I7 1400K 3. 安裝步驟 (base) C:\Users\PC>conda -V conda 23.7.4(base) C:\Users\PC>conda create qttest p…

spring項目修改時間格式

一、配置方式 在application.yml上添加 spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT8 二、注解方式 1、添加依賴 <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId&…

解釋def __int__(self):和def __init__(self):的區別

文章目錄 __init__ 方法例子 __int__ 方法例子 總結 def __int__(self): 和 def __init__(self): 是Python中兩個不同的特殊方法&#xff08;或魔法方法&#xff09;&#xff0c;它們有著不同的用途和含義。 __init__ 方法 作用&#xff1a;__init__ 方法是類的構造函數。當你…

大文件分片【筆記】

createChunk.js Spark-md5計算文件各分片MD5生成文件指紋 可以幫助我們更加方便地進行文件哈希計算和文件完整性檢測等操作。 import sparkMd5 from ./sparkmd5.jsexport function createChunk(file, index, chunkSize) {return new Promise((resolve, reject) > {const sta…

整理好了!2024年最常見 20 道 Kafka面試題(一)

一、什么是Apache Kafka&#xff0c;它主要用于什么場景&#xff1f; Apache Kafka是一個分布式流處理平臺&#xff0c;最初由LinkedIn開發&#xff0c;后來成為Apache軟件基金會的一個開源項目。它被設計為一個高吞吐量、可擴展、容錯的消息隊列系統&#xff0c;能夠處理實時…

【java】【python】leetcode刷題記錄--棧與隊列

232 用棧實現隊列 題目描述 兩個棧模擬隊列的思路是利用棧&#xff08;后進先出結構&#xff09;的特性來實現隊列&#xff08;先進先出結構&#xff09;的行為。這種方法依賴于兩個棧來逆轉元素的入隊和出隊順序&#xff0c;從而實現隊列的功能。 入隊操作&#xff08;使用s…

GIS、GPS、RS綜合應用

劉老師&#xff08;副教授&#xff09;&#xff0c;北京重點高校資深專家&#xff0c;擁有豐富的科研及工程技術經驗&#xff0c;長期從事3S在環境中的應用等領域的研究和教學工作&#xff0c;具有資深的技術底蘊和專業背景。 第一章、3S 技術及應用簡介 1.1、3S 技術及集成簡…

前端技術專家崗(虛擬崗)

定位&#xff1a; 團隊技術負責人、技術領導者&#xff1b;確保框架、工具的低門檻、高性能、可擴展&#xff1b; 素質要求&#xff1a; 具備架構設計能力&#xff1b;一個或者多個領域的技術專家&#xff1b;較為豐富的基礎建設經驗&#xff1b;項目管理能力、任務分解、協…

跨模型知識融合:大語言模型的知識融合

大語言模型&#xff08;LLMs&#xff09;在多個領域的應用日益廣泛&#xff0c;但確保它們的行為與人類價值觀和意圖一致卻充滿挑戰。傳統對齊方法&#xff0c;例如基于人類反饋的強化學習&#xff08;RLHF&#xff09;&#xff0c;雖取得一定進展&#xff0c;仍面臨諸多難題&a…

1211. 查詢結果的質量和占比

1211. 查詢結果的質量和占比 題目鏈接&#xff1a;1211. 查詢結果的質量和占比 代碼如下&#xff1a; # Write your MySQL query statement below select query_name,round(avg(rating/position),2) as quality,round(sum(if(rating<3,1,0))*100/count(*),2) as poor_quer…

wandb安裝與使用 —— 用于跟蹤、可視化和協作機器學習實驗的工具

文章目錄 一、wandb簡介二、wandb注冊與登陸&#xff08;網頁&#xff09; —— 若登錄&#xff0c;則支持在線功能三、wandb安裝與登陸&#xff08;命令行&#xff09; —— 若不登錄&#xff0c;則只保留離線功能四、函數詳解4.1、wandb.init() —— 初始化一個新的 wandb 實…

上位機圖像處理和嵌入式模塊部署(f407 mcu中fatfs中間件使用)

【 聲明&#xff1a;版權所有&#xff0c;歡迎轉載&#xff0c;請勿用于商業用途。 聯系信箱&#xff1a;feixiaoxing 163.com】 前面我們已經實現了spi norflash的驅動&#xff0c;理論上這已經可以實現數據的持久化保存了。為什么還需要一個文件系統呢&#xff1f;主要原因還…

在 Win系統安裝 Ubuntu20.04子系統 WSL2 (默認是C盤,第7步開始遷移到D盤,也可以不遷移)

1、簡介 WSL在Windows 10上原生運行Linux二進制可執行文件&#xff0c;不用單獨安裝虛擬機。 WSL2是WSL的第二個版本&#xff0c;提供了與WSL相比的顯著性能改進和完全的系統呼叫兼容性。通過運行Linux內核在一個輕量級虛擬機&#xff08;VM&#xff09;中實現。 2、安裝 電…

ThingsBoard MQTT 連接認證過程 源碼分析+圖例

整個連接過程如圖所示&#xff1a; 高清圖片鏈接 1、環境準備 thingsboard3.5.1 源碼啟動。&#xff08;不懂怎么啟動的&#xff0c;大家可以看我的博文ThingsBoard3.5.1源碼啟動&#xff09;MQTTX 客戶端&#xff08;用來連接 thingsboard MQTT&#xff09;默認配置。queue.…

7-15 位模式(dump_bits)---PTA實驗C++

一、題目描述 為方便調試位運算相關程序&#xff0c;先做個展現位模式的小工具。 建議參照以下接口實現&#xff1a; // 利用函數重載特性&#xff1a;string dump_bits(char x);string dump_bits(short x);string dump_bits(int x);string dump_bits(long long x);// 或用函…

JVM類加載過程

在Java虛擬機規范中&#xff0c;把描述類的數據從class文件加載到內存&#xff0c;并對數據進行校驗、轉換解析和初始化&#xff0c;最終形成可以被虛擬機直接使用的java.lang.Class對象&#xff0c;這個過程被稱作類加載過程。一個類在整個虛擬機周期內會經歷如下圖的階段&…

C++編程法則365天一天一條(323)main函數執行之前和之后的動作

在C和C程序中&#xff0c;main 函數之前和之后執行的函數是由編譯器、鏈接器和運行時環境共同決定的。以下是一些通常會在這些階段執行的關鍵函數&#xff1a; 在 main 函數之前執行的函數 啟動代碼&#xff08;Start-up Code&#xff09;: 這是由編譯器提供的一段代碼&#…

DIYP對接駱駝后臺IPTV管理,退出菜單中顯示用戶名已經網絡信息,MAC,剩余天數,套餐名稱等

演示&#xff1a;https://url03.ctfile.com/f/1779803-1042599473-4dc000?p8976 (訪問密碼: 8976) 后臺加上EPG&#xff0c;增加一些播放源的動態端口替換。 前臺app上&#xff0c;退出菜單中顯示用戶名已經網絡信息&#xff0c;MAC&#xff0c;剩余天數&#xff0c;套餐名稱…

Python知識點17---包

提前說一點&#xff1a;如果你是專注于Python開發&#xff0c;那么本系列知識點只是帶你入個門再詳細的開發點就要去看其他資料了&#xff0c;而如果你和作者一樣只是操作其他技術的Python API那就足夠了。 Python的包&#xff0c;你可以把它看成是一個大的模塊&#xff0c;它…