腾讯TNN神经网络推理框架手动实现多设备单算子卷积推理
文章目录
- 前言
- 1. 简介
- 2. 快速开始
- 2.1 onnx转tnn
- 2.2 编译目标平台的 TNN 引擎
- 2.3 使用编译好的 TNN 引擎进行推理
- 3. 手动实现单算子卷积推理(浮点)
- 4. 代码解析
- 4.1 构建模型(单卷积层)
- 4.2 构建解释器
- 4.3 初始化tnn
- 5. 模型量化
- 5.1 编译量化工具
- 5.2 量化scale的计算
- 5.3 量化流程
- 6. im2col实现卷积计算
- 6.1 input为单通道,weight为单通道(输出)
- 6.2 input为多通道,weight为单通道(输出)
- 6.3 input为多通道,weight为多通道(输出)
- 结束语
前言
近期调研了一下腾讯的TNN
神经网络推理框架,因此这篇博客主要介绍一下TNN
的基本架构、模型量化以及手动实现x86
和arm
设备上单算子卷积推理。
1. 简介
TNN
是由腾讯优图实验室开源的高性能、轻量级神经网络推理框架,同时拥有跨平台、高性能、模型压缩、代码裁剪等众多突出优势。TNN
框架在原有Rapidnet
、ncnn
框架的基础上进一步加强了移动端设备的支持以及性能优化,同时借鉴了业界主流开源框架高性能和良好拓展性的特性,拓展了对于后台X86
、NV GPU
的支持。手机端TNN
已经在手机QQ
、微视、P
图等众多应用中落地,服务TNN
作为腾讯云AI
基础加速框架已为众多业务落地提供加速支持。
TNN
开源地址:https://github.com/Tencent/TNN
2. 快速开始
2.1 onnx转tnn
&emsp目前 TNN
支持业界主流的模型文件格式,包括ONNX
、PyTorch
、TensorFlow
、TesorFlow-Lite
以及 Caffe
等。如上图所示,TNN
将 ONNX
作为中间层,借助于ONNX
开源社区的力量,来支持多种模型文件格式。如果要将PyTorch
、TensorFlow
以及 Caffe
等模型文件格式转换为 TNN
,首先需要使用对应的模型转换工具,统一将各种模型格式转换成为 ONNX
模型格式,然后将 ONNX
模型转换成 TNN
模型。
为了简化 convert2tnn
转换工具的安装和编译步骤,官方推荐使用docker
镜像:
# 建议直接从 docker hub 上拉取镜像
docker pull ccr.ccs.tencentyun.com/qcloud/tnn-convert# 对 docker 镜像进行重命名
docker tag ccr.ccs.tencentyun.com/qcloud/tnn-convert tnn-convert:latest
docker rmi ccr.ccs.tencentyun.com/qcloud/tnn-convert# 通过打印 convert2tnn 的帮助信息来验证下 docker 镜像能够正常使用
docker run -it tnn-convert:latest python3 ./converter.py -h
进一步的,查看下ONNX
转TNN
工具:
docker run -it tnn-convert:latest python3 ./converter.py onnx2tnn -h
具体参数不再进行过多详述,可参阅官方文档。
本例就以Resnet50
为例,将其转为tnn
格式:
import torch
from torchvision.models.resnet import resnet50if __name__ == '__main__':model = resnet50()model.load_state_dict(torch.load('model/resnet50-0676ba61.pth'))model.eval()input_data = torch.randn(size=(1, 3, 224, 224), dtype=torch.float32)input_names, output_names = ["input"], ["output"]torch.onnx.export(model, input_data, "model/resnet50.onnx", input_names=input_names, output_names=output_names)
# 当然,也可以直接使用onnx格式的resnet50,下载链接为:https://github.com/onnx/models/tree/main/vision/classification/resnet/model
# 启动docker
docker run -v /home/liyanpeng/tnn_docker:/home/liyanpeng/tnn_docker --rm -it tnn-convert:latest /bin/bash
# cd /opt/TNN/tools/convert2tnn(default)
# onnx2tnn
python3 ./converter.py onnx2tnn /home/liyanpeng/tnn_docker/model/resnet50.onnx -in input:1,3,224,224
2.2 编译目标平台的 TNN 引擎
编译相关注意事项请参考官方文档。
arm-linux
平台编译:
apt-get install g++-aarch64-linux-gnu gcc-aarch64-linux-gnu
apt-get install g++-arm-linux-gnueabihf gcc-arm-linux-gnueabihf
# apt-get install vim gdbcd scripts
./build_aarch_linux.sh
x86-linux
平台编译:
cd scripts
./build_linux_native.sh
2.3 使用编译好的 TNN 引擎进行推理
上面那个没有编译具体的实例,接下来编译x86
平台下各任务下的TNN
引擎:
# x86平台编译
cd examples/linux/x86
./build_linux_native.sh# arm-linux交叉编译
# cd examples/linux/cross
# ./build_aarch64_linux.sh
执行图像分类任务:
./demo_x86_imageclassify -p /home/liyanpeng/tnn_docker/model/resnet50.tnnproto -m /home/liyanpeng/tnn_docker/model/resnet50.
tnnmodel -i /home/liyanpeng/tnn_docker/model/tiger_cat.jpg
推理结果也是正确的:
各任务源码位置:
examples/linux/src
3. 手动实现单算子卷积推理(浮点)
TNN
框架构建神经网络推理实例需要输入两个文件,一个是模型结构文件.tnnproto
,一个是模型权重文件.tnnmodel
,这两个文件是必须的。但由于一些特殊的需要,这种文件的方式不太适用,因此我这里提供了一个手动创建模型结构的实例,不用依赖于模型文件。
仿照
examples/linux/src
目录下的TNNImageClassify
图像分类demo
,我在根目录下创建了一个my_cnn_model
目录,其中包括my_conv.cpp
和CMakeLists.txt
两个文件。
my_conv.cpp
文件内容如下:
// Author: xiayouran
// Email: youran.xia@foxmail.com
// Datetime: 2023/4/8 15:17
// Filename: my_conv.cpp
#include "tnn/core/tnn.h"
#include "tnn/interpreter/abstract_model_interpreter.h"
#include "tnn/interpreter/tnn/model_interpreter.h"using namespace TNN_NS;int main(int argc, char* argv[]) {auto model_type = MODEL_TYPE_TNN;auto device_type = DEVICE_X86;auto data_type = DATA_TYPE_FLOAT;ModelConfig model_config;model_config.model_type = model_type;NetworkConfig net_config;net_config.device_type = device_type;TNN tnn;Status status = tnn.MyInit(model_config);auto instance = tnn.CreateInst(net_config, status);BlobMap input_blobs;status = instance->GetAllInputBlobs(input_blobs);Blob* input_blob = input_blobs.begin()->second;float* data_ptr = static_cast<float*>(input_blob->GetHandle().base);for (int i = 0; i < 1 * 1 * 4 * 4; i++) {data_ptr[i] = (float)1.0 + i;}status = instance->Forward();BlobMap output_blobs;status = instance->GetAllOutputBlobs(output_blobs);Blob* output_blob = output_blobs.begin()->second;float* out_data_ptr = static_cast<float*>(output_blob->GetHandle().base);for (int i = 0; i < 1 * 1 * 2 * 2; i++) {std::cout << out_data_ptr[i] << std::endl;}return 0;
}
卷积的输入shape
为(1, 1, 4, 4)
,卷积的shape
为(1, 1, 3, 3)
,卷积的输出shape
为(1, 1, 2, 2)
,具体为:
运行结果如下:
在CMakeLists.txt
文件中除了添加了本示例代码my_conv.cpp
,还添加了官方提供的图像分类demo
的TNNImageClassify.cc
及其依赖,具体内容如下:
file(GLOB MyCNNModel_SRCS my_conv.cpp)
file(GLOB ImageClassify_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../examples/linux/src/TNNImageClassify/TNNImageClassify.cc)message(${MyCNNModel_SRCS})
message(${ImageClassify_SRCS})#include_directories(../include)
#include_directories(../source)include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../examples/base)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../examples/base/utils)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../examples/utils)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../third_party/gflags ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/gflags)
get_target_property(GFLAGS_INCLUDE_DIRS gflags INTERFACE_INCLUDE_DIRECTORIES)
include_directories(BEFORE "${GFLAGS_INCLUDE_DIRS}")
link_libraries(gflags)
file(GLOB FLAG_SRC "${CMAKE_CURRENT_SOURCE_DIR}/../examples/linux/src/*.cc")
file(GLOB_RECURSE BASE_SRC"${CMAKE_CURRENT_SOURCE_DIR}/../examples/base/*.cc""${CMAKE_CURRENT_SOURCE_DIR}/../examples/base/utils/*.cc")
file(GLOB_RECURSE UTIL_SRC"${CMAKE_CURRENT_SOURCE_DIR}/../examples/utils/*.cc")include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../source/tnn/interpreter/tnn)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../third_party/stb)add_executable(my_conv_cmd ${MyCNNModel_SRCS})
add_executable(demo_x86_imageclassify_cmd ${ImageClassify_SRCS} ${BASE_SRC} ${UTIL_SRC} ${FLAG_SRC})target_link_libraries(my_conv_cmd TNN)
target_link_libraries(demo_x86_imageclassify_cmd TNN)set_target_properties(my_conv_cmd PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
set_target_properties(demo_x86_imageclassify_cmd PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
4. 代码解析
按照官方提供的API说明,运行一个神经网络需要五个步骤:
# Step1. 模型解析
model_config.params.push_back(proto_buffer);# proto文件内容存入proto_buffer
model_config.params.push_back(model_buffer);# model文件内容存入model_buffer
Status ret = tnn.Init(model_config);# Step2. 网络构建
auto net_instance = tnn.CreateInst(config, status);# Step3. 输入设定
auto status = net_instance->SetInputMat(input_mat, input_cvt_param);# Step4. 网络运行
auto status = net_instance->Forward();# Step5. 输出获取
auto status = instance->GetOutputMat(output_mat);
在第一步模型解析中涉及到文件操作,理论上只要按照其他模型转tnn
的格式写模型文件是不需要修改源码的,这里没有阅读这部分源码,因此就直接修改了源码。
经过源码分析,手动构建一个模型主要需要构建神经网络模型的各层layer
并完成参数的初始化、模型解释器及tnn
的初始化的构建,具体如下:
4.1 构建模型(单卷积层)
在source/tnn/interpreter/tnn/model_interpreter.cc
文件中新增了ModelInterpreter::MyInterpret()
函数,区别于官方的ModelInterpreter::Interpret(std::vector<std::string> ¶ms)
函数,本函数不需要从文件中去解析模型的结构和权重:
// Interpret the proto and model without file.
Status ModelInterpreter::MyInterpret() {Status status = TNN_OK;/****************初始化卷积层参数****************/NetStructure *structure = GetNetStructure();structure->source_model_type = MODEL_TYPE_TNN;DimsVector &input_shape = structure->inputs_shape_map["input"];input_shape.push_back(1);input_shape.push_back(1);input_shape.push_back(4);input_shape.push_back(4);DataType data_type = DATA_TYPE_FLOAT;// DATA_TYPE_FLOATstructure->input_data_type_map["input"] = data_type;structure->outputs.insert("output");auto cur_layer = std::make_shared<LayerInfo>();std::string type_str = "Convolution";type_str = Transfer(type_str);LayerType type = GlobalConvertLayerType(type_str);cur_layer->type = type;cur_layer->type_str = type_str;cur_layer->name = Transfer("Conv_0");cur_layer->inputs.clear();cur_layer->outputs.clear();cur_layer->inputs.push_back("input");structure->blobs.insert("input");cur_layer->outputs.push_back("output");structure->blobs.insert("output");LayerParam *layer_param = NULL;LayerParam** param = &layer_param;auto p = CreateLayerParam<ConvLayerParam>(param);p->input_channel = 1;p->output_channel = 1;p->kernels = {3, 3};p->strides = {1, 1};p->pads = {0, 0, 0, 0};p->dialations = {1, 1};p->bias = 0;p->pad_type = -1;p->group = 1;p->activation_type = 0;layer_param->type = cur_layer->type_str;layer_param->name = cur_layer->name;if (data_type == DATA_TYPE_INT8) {layer_param->quantized = true;}cur_layer->param = shared_ptr<LayerParam>(layer_param);structure->layers.push_back(cur_layer);/**************卷积层参数初始化结束**************//****************初始化卷积层权重****************/NetResource *net_resource = GetNetResource();LayerResource *layer_resource = NULL;LayerResource** resource = &layer_resource;auto layer_res = CreateLayerRes<ConvLayerResource>(resource);layer_res->filter_format = OIHW;// weightRawBuffer weight_buf;DimsVector weight_dims = {1, 1, 3, 3};weight_buf = TNN_NS::RawBuffer(1*1*3*3*4);weight_buf.SetDataType(data_type);weight_buf.SetBufferDims(weight_dims);float weight_data[1][1][3][3] = {{{{1.0, 0.0, 0.0},{0.0, 1.0, 0.0},{0.0, 0.0, 1.0}}}};memcpy(weight_buf.force_to<float*>(), weight_data, 1*1*3*3*4);layer_res->filter_handle = weight_buf;// biasRawBuffer bias_buf;DimsVector bias_dims = {1};bias_buf = TNN_NS::RawBuffer(1);bias_buf.SetDataType(data_type);bias_buf.SetBufferDims(bias_dims);float bias_data[1] = {0.0};memcpy(bias_buf.force_to<float*>(), bias_data, 1*4);layer_res->bias_handle = bias_buf;// scaleRawBuffer scale_buf;DimsVector scale_dims = {1};scale_buf = TNN_NS::RawBuffer(1);scale_buf.SetDataType(DATA_TYPE_FLOAT);scale_buf.SetBufferDims(scale_dims);float scale_data[1] = {1.0};memcpy(scale_buf.force_to<float*>(), scale_data, 1*4);layer_res->scale_handle = scale_buf;// zero_pointRawBuffer zero_point_buf;DimsVector zero_point_dims = {1};zero_point_buf = TNN_NS::RawBuffer(1);zero_point_buf.SetDataType(DATA_TYPE_INT8);zero_point_buf.SetBufferDims(zero_point_dims);int zero_point_data[1] = {0};memcpy(zero_point_buf.force_to<int*>(), zero_point_data, 1*4);layer_res->zero_point_handle = zero_point_buf;net_resource->resource_map["Conv_0"] = std::shared_ptr<LayerResource>(layer_resource);// 不用解析constant_map/**************卷积层权重初始化结束**************/return status;
}
相应的,需要在source/tnn/interpreter/tnn/model_interpreter.h
、source/tnn/interpreter/abstract_model_interpreter.h
和source/tnn/interpreter/ncnn/ncnn_model_interpreter.h
三个文件中添加本函数的声明:
// model_interpreter.h文件中的ModelInterpreter
virtual Status MyInterpret();// abstract_model_interpreter.h文件中的AbstractModelInterpreter
virtual Status MyInterpret() = 0;// ncnn_model_interpreter.h文件中的NCNNModelInterpreter
virtual Status MyInterpret();
4.2 构建解释器
在source/tnn/core/tnn_impl_default.cc
文件中新增了TNNImplDefault::MyInit(ModelConfig& config)
函数,函数实现大体与官方的TNNImplDefault::Init(ModelConfig& config)
函数一样,只不过这里构建解释器时使用了MyInterpret()
函数:
Status TNNImplDefault::MyInit(ModelConfig& config) {auto status = TNNImpl::MyInit(config);if (status != TNN_OK) {return status;}auto interpreter = CreateModelInterpreter(config.model_type);if (!interpreter) {return Status(TNNERR_NET_ERR, "interpreter is nil");}interpreter_ = std::shared_ptr<AbstractModelInterpreter>(interpreter);return interpreter_->MyInterpret();
}
TNNImpl::MyInit(config)
函数的实现在在source/tnn/core/tnn_impl.cc
文件中:
Status TNNImpl::MyInit(ModelConfig &config) {model_config_.model_type = config.model_type;return TNN_OK;
}
相应的,需要在source/tnn/core/tnn_impl_default.h
和source/tnn/core/tnn_impl.h
两个文件中添加本函数的声明:
// tnn_impl_default.h文件中的MyInit
virtual Status MyInit(ModelConfig& config);// tnn_impl.h文件中的MyInit
virtual Status MyInit(ModelConfig& config);
4.3 初始化tnn
为了使tnn
能够正确按照我们的方法进行初始化,需要添加TNN::MyInit(ModelConfig& config)
函数以代替官方的TNN::Init(ModelConfig& config)
函数进行初始化,具体在source/tnn/core/tnn.cc
文件中:
Status TNN::MyInit(ModelConfig& config) {impl_ = TNNImplManager::GetTNNImpl(config.model_type);if (!impl_) {LOGE("Error: not support mode type: %d. If TNN is a static library, link it with option -Wl,--whole-archive tnn -Wl,--no-whole-archive on android or add -force_load on iOS\n", config.model_type);return Status(TNNERR_NET_ERR, "unsupported mode type, If TNN is a static library, link it with option -Wl,--whole-archive tnn -Wl,--no-whole-archive on android or add -force_load on iOS");}return impl_->MyInit(config);
}
相应的,需要在include/tnn/core/tnn.h
文件中添加本函数的声明:
// tnn.h文件中的MyInit
Status MyInit(ModelConfig& config);
至此,手动构建单算子卷积推理所需的要素已经构建完毕,在根目录下的CMakeLists.txt
文件中添加本示例的代码目录进行编译即可:
add_subdirectory(my_cnn_model)
5. 模型量化
5.1 编译量化工具
# 编译
cd platforms/linux/
./build_quanttool.sh -c# 执行量化
cd build_quantize/
./quantization_cmd -p /home/liyanpeng/tnn_docker/model/resnet50.tnnproto -m /home/liyanpeng/tnn_docker/model/resnet50.tnnmodel -i /home/liyanpeng/tnn_docker/imagenet128/ -o resnet50
浮点模型大小为98M
,量化后的定点模型为26M
:
使用量化模型进行推理:
./demo_x86_imageclassify -p /opt/TNN/platforms/linux/build_quantize/resnet50.quantized.tnnproto -m /opt/TNN/platforms/linux/build_quantize/resnet50.quantized.tnnmodel -i /home/liyanpeng/tnn_docker/model/tiger_cat.jpg
这里只是用128
张图片进行的量化,所以精度损失较大,推理结果不大对:
5.2 量化scale的计算
这部分主要涉及到量化过程中个scale
的计算,包括feature map
的scale
和算子权重的scale
:
feature map
的scale
计算如下:
float max_val = std::max(std::abs(range_per_channel_[i].first), std::abs(range_per_channel_[i].second));
valid_channel_[i] = max_val > 0.00001;
if (valid_channel_[i]) {interval_per_channel_[i] = (float)bin_nums_ / max_val;
}int index = static_cast<int>(std::abs(val) * interval_per_channel_[channel_idx]);
index = std::min(index, bin_nums_ - 1);
distribute_data[index] += 1.0;
// 相当于是统计数据,2048个bar,每个bar中有多少个数据
// 即将feature map中的数据分到2048个bar中// 对于MIN_MAX来说 threshold 为 2047
scale_vec = ((float)threshold + 0.5) / interval / 127.0;
weight
的scale
计算如下:
// weight_multiby_inputscale[idx] = weight_data[idx] * input_scale_data[s_idx];
// 下面的weight已经是weight*input_scale
auto minmax = std::minmax_element(weight_start, weight_start + s_size);
float max_val_abs = std::max(std::abs(*minmax.first), std::abs(*minmax.second));weight_scale[s_idx] = max_val_abs / 127.0f;
/*
s_size为in_c*k_w*k_h = 3*3*3=27
*/
scale_float2int8 = 1 / weight_scale[s_idx];// quantize weights
for (int i = 0; i < s_size; ++i) {int value = static_cast<int>(std::round(weight_start[i] * scale_float2int8));weight_q_start[i] = std::min(127, std::max(-127, value));
}// quantize bias
bias_quantized_data[oc] = static_cast<int32_t>(bias_data[oc] / weight_scale_data[weight_scale_idx]);
5.3 量化流程
TNN
默认采用Min-Max
量化方式,除此之外,feature map
支持KL
量化方法,weight
支持ADMM
量化方法,具体的量化流程如下:
calibration.Init(net_config, model_config)
/*根据输入shape,计算出每个网络层的输出shape*/calibration.SetCalibrationParams(cali_params)
/*设置量化方式为MIN_MAX*/calibration.RunCalibration(dataset)
/*scale计算和量化*/CalBlobScale(dataset);// Compute Feature ScaleInitFeatureMap();// Init Feature map(在此之前进行了reshape),初始化每个feature map的range_per_channel_等参数UpdateBlobRange(dataset);// Collect the Range of Feature map,更新range_per_channel_UpdateRange()UpdateBlobDistribute(dataset);// Calculate Distribute of Feature mapResetDistribute()// 根据range_per_channel_计算valid_channel_和interval_per_channel_,并初始化distribute_per_channel_UpdateDistribute()// CalculateScale(scale_vec, zero_point_vec);// Compute Scale of Feature map and save to resource mapQuantizeParams();// Quantize paramsMergeBlobScale();// Merge Blob Scale of some layerscalibration.Serialize(output_name + ".quantized.tnnproto", output_name + ".quantized.tnnmodel")
/*保存量化模型*/
其中range_per_channel_
表示每个channel
中的最大最小值:first(min)
,second(max)
。
量化源码位置在:
tools/quantization
。
6. im2col实现卷积计算
根据硬件具体实现,大部分卷积的计算都会转换为矩阵乘法(GEMM)
,最常用的方法就是im2col
,下面给出一些im2col
实现卷积计算的示例图,结合这篇博客一起食用效果更佳!
6.1 input为单通道,weight为单通道(输出)
6.2 input为多通道,weight为单通道(输出)
6.3 input为多通道,weight为多通道(输出)
结束语
本篇博客主要介绍了TNN的基本使用、量化工具的使用以及手动实现单算子卷积推理,除了浮点卷积推理外,8bit
定点卷积推理也有实现,不过目前的结果还没有对上,后续再进行补充8bit
定点卷积推理的实现代码。
相关文章:

腾讯TNN神经网络推理框架手动实现多设备单算子卷积推理
文章目录前言1. 简介2. 快速开始2.1 onnx转tnn2.2 编译目标平台的 TNN 引擎2.3 使用编译好的 TNN 引擎进行推理3. 手动实现单算子卷积推理(浮点)4. 代码解析4.1 构建模型(单卷积层)4.2 构建解释器4.3 初始化tnn5. 模型量化5.1 编译量化工具5.2 量化scale的计算5.3 量化流程6. i…...

基础解惑:Linux 下文件描述符标志和文件状态标志区别
简述 文件描述符标志,是体现进程的文件描述符的状态,fork进程时,文件描述符被复制;目前只有一种文件描述符:FD_CLOEXEC文件状态标志,是体现进程打开文件的一些标志,fork时不会复制file 结构&am…...

学弟:如何在3个月内学会自动化测试?
有小学弟问:如何在3个月内学会自动化测试? 老实说如果你现在上班,之前主要在做功能测试,或者编程基础比较弱的话,三个月够呛。 如果你是脱产学习,每天能保持6~8小时学习时间的话,可…...

C-NCAP 2025主动安全ADAS测试研究
中汽中心汽车测评管理中心(简称“中汽测评”)是负责运营C-NCAP、CCRT等测评项目的管理机构。中汽测评以引领汽车行业进步、支撑汽车强国建设为使命,通过独立、公正、专业、开放的测试评价,服务消费者,当好选车购车参谋…...

【Apifox】测试工具自动编写接口文档
在开发过程中,我们总是避免不了进行接口的测试, 而相比手动敲测试代码,使用测试工具进行测试更为便捷,高效 今天发现了一个非常好用的接口测试工具Apifox 相比于Postman,他还拥有一个非常nb的功能, 在接…...

解决brew安装opencv报错问题
目录1.报错12. 解决方案3. 报错24. 解决方案4.1 原因分析4.2 手动下载portable-ruby-2.6.8_1.el_capitan.bottle.tar.gz4.3 拷贝portable-ruby-2.6.8_1.el_capitan.bottle.tar.gz到指定目录1.报错1 mac本用brew报如下错误: xialiangzhideMacBook-Pro:~ xialz$ bre…...

Linux软件安装---Tomcat安装
安装Tomcat 操作步骤: 使用xftp上传工具将tomcat的 二进制发布包上传到Linux解压安装包,命令为tar -zxvf apache-tomcat*** -C /usr/local进入Tomcat的bin的启动目录,命令为sh startup.sh或者./startup.sh 验证Tomcat启动是否成功࿰…...

提示工程师是什么工作?
提示工程师是什么工作? 因为ChatGPT的爆火,大家都把眼光锁定在这个号称“ChatGPT新兴职业” 的“提示工程师”上。“提示工程师”是什么工作?为什么说未来所有职业 都需要提示工程的能力? 先解释一下“提示”,它最早…...

WXSS-WXML-WXS语法
目录: 1 WXSS编写程序样式 2 Mustache语法绑定 3 WXML的条件渲染 4 WXML的列表渲染 5 WXS语法基本使用 6 WXS语法案例练习 小程序的自适应单位rpx。在设计稿为iPhone6的时候1px2rpx wxml必须是闭合标签,或者单标签加/,否则会报错&#…...

POSIX正则表达式
维基百科 POSIX基本表达式 https://en.wikibooks.org/wiki/Regular_Expressions/POSIX_Basic_Regular_Expressions POSIX扩展正则表达式 https://en.wikibooks.org/wiki/Regular_Expressions/POSIX-Extended_Regular_Expressions 正则表达式 https://en.wikipedia.org/wiki/R…...

数据分析工具集合:Tableau入门及其他工具简介
目录 一、Tableau简介 1、下载链接 2、使用技巧 二、其他常用数据分析工具 1、Microsoft Excel简介 1.1、下载链接 1.2、使用技巧 2、Python简介 2.1、下载链接 2.2、常用库的安装方式和使用技巧 2.2.1、Pandas 2.2.2、NumPy 2.2.3、Matplotlib 3、R语言简介 3.…...

响应式布局的五种方法
响应式布局的五种方法1.百分比布局2.rem布局3. 媒体查询 media screen4. flex布局5.vw 和 vh响应式布局是同一页面在不同的屏幕上有不同的布局,即只需要一套代码使页面适应不同的屏幕。 1.百分比布局 1.有父元素就相对于父元素 2.没有父元素就相对于视口的大小 举一…...

Javase学习文档------数组
Java 数组是 Java 编程中非常基础和重要的一个知识点。 以下是 Java 数组的主要学习内容: 数组的几个特点 数组在声明时必须指定长度,且长度不可变:数组的长度在声明时就需要确定,一旦确定就不能修改。因此,在使用数组…...

百度高德地图JS-API学习手记:地图基本设置与省市区数据加载
无论是百度还是高德地图开发,还是高德地图开发。官方的给的案例启示很多,copy再修改下,就完成了 概述-地图 JS API | 高德地图API 地图 JS API | 百度地图API SDK 这个大致看一下,我想。有点GIS基础都能完成地图开发。 个人认…...

c语言—指针详解***内存地址***指针字节数***注意事项
创作不易,本篇文章如果帮助到了你,还请点赞支持一下♡>𖥦<)!! 主页专栏有更多知识,如有疑问欢迎大家指正讨论,共同进步! 给大家跳段街舞感谢支持!ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ…...

VMware虚拟机之WindowsXP系统超详细下载安装与使用教程
文章目录前言一、WindowsXP虚拟机系统下载二、WindowsXP虚拟机系统安装三、WindowsXP虚拟机系统使用总结前言 本博客的主要内容为使用VMware虚拟机下载安装与使用WindowsXP系统,WindowsXP系统虽然早已过时,但是仍对我们的学习有着很大的帮助,…...

【VMD-SSA-LSSVM】基于变分模态分解与麻雀优化Lssvm的负荷预测【多变量】(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

积极心态,助力人生成功
无论生活中遇到多少困难和挫折,只要我们保持积极心态、努力拼搏,就有望最终实现自己的梦想和目标。...
ADRC线性跟踪微分器(ST+SCL语言)
ADRC自抗扰相关算法源代码和公式请参看下面文章链接: ADRC/Matlab一步步实现跟踪微分器TD(附完整PLC测试代码链接)_ladrc线性跟踪微分器差分方程_RXXW_Dor的博客-CSDN博客关于Adrc的理论分析不是本篇博客的重点,主要也是能力所限,相关理论大家可以看韩京清教授的论文,专栏…...

Linux C/C++ 崩溃诊断大师:解锁软件问题定位与修复的秘密武器
让崩溃成为历史:详解有效诊断与解决技巧引言崩溃信息的类型设置信号处理函数(Setting up signal handlers)信号来源和上下文信息使用 siginfo_t 结构体获取信号来源信息使用 ucontext 结构体获取上下文信息将崩溃信息写入日志标准的信号处理函…...

ChatGPT能代替Oracle DBA吗?用Oracle OCP(1z0-083)的真题测试一下。
让我们来看看ChatGPT不能通过Oracle OCP的考试? 文章目录引言测试过程总结和分析关于博主,姚远:Oracle ACE(Oracle和MySQL数据库方向)。Oracle MAA 大师。华为云MVP。《MySQL 8.0运维与优化》的作者。拥有 Oracle 10g和…...

《扬帆优配》二季度投资策略出炉 机构调仓换股露踪迹
随着多家上市公司公告发布,其发表的股东数据使得基金的最新持仓浮出水面。与此同时,组织也在密集调研中寻觅出资时机。站在二季度的起点,基金公司二季度出资策略渐次发表。多家基金公司以为,宏观经济将延续修正态势,仍…...

【SpringMVC】2—传统方式实现增删改查
⭐⭐⭐⭐⭐⭐ Github主页👉https://github.com/A-BigTree 笔记链接👉https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ 如果可以,麻烦各位看官顺手点个star~😊 如果文章对你有所帮助,可以点赞👍…...

图像阈值化
图像阈值化 图像阈值化简介 ⚫ 图像阈值化是图像处理的重要基础部分, 应用很广泛, 可以根据灰度差异来分割图像不同部分 ⚫ 阈值化处理的图像一般为单通道图像(灰度图) ⚫ 阈值化参数的设置可以使用滑动条来debug ⚫ 阈值化处理易光照影响, 处理时应注意 ⚫ 本节主要介绍…...

1.5 极限运算法则
思维导图: 我的理解: 如果一个数列{a_n}是一个无穷小,那么它的极限为0,即lim(n→∞)a_n0。同样地,如果另一个数列{b_n}也是一个无穷小,那么它的极限为0,即lim(n→∞)b_n0。 当我们考虑这两个无…...

首批因AI失业的人出现-某游戏公司裁掉半数原画师
如今各种AI爆火,不可避免的的会与某些功能撞车职业发生冲突,每一次生产力的变革,在带来技术进步与更高效率的同时,也都无可避免的会带来一波失业浪潮,当下的人工智能浪潮自然也不例外。 现在,第一批因为AI…...

字符串转换整数(atoi)
请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C 中的 atoi 函数)。 函数 myAtoi(string s) 的算法如下: 读入字符串并丢弃无用的前导空格 检查下一个字符(假设还未到字符…...

Servlet练习
练习准备 编写Student和StudentDao package beans;public class Student{private String num;private String name;public Student(){}public String getNum() {return num;}public String getName() {return name;}public void setNum(String num) {this.num num;}public v…...

美国高速公路信号灯控制项目的大致逻辑和步骤 智慧公路设计
美国高速公路信号灯控制项目的大致逻辑和步骤: 美国那边先提供一个关于具体做什么需求、那边的设备(信号灯)有什么参数,什么接口,分别是什么属性等等的详细设计文档,开发人员拿到这个文档以后把它看懂&…...

数字电源专用IC,国产C2000, QX320F280049
一、特性参数 1、独立双核,32位CPU,单核主频400MHz 2、IEEE 754 单精度浮点单元 (FPU) 3、三角函数单元 (TMU) 4、1MB 的 FLASH (ECC保护) 5、1MB 的 SRAM (ECC保护&…...