当前位置: 首页 > news >正文

ONNX的结构与转换

ONNX的结构与转换

  • 1. 背景
  • 2. ONNX结构分析与修改工具
    • 2.1. ONNX结构分析
    • 2.2. ONNX的兼容性问题
    • 2.3. 修改ONNX模型
  • 3. 各大深度学习框架如何转换到ONNX?
    • 3.1. MXNet转换ONNX
    • 3.2. TensorFlow模型转ONNX
    • 3.3. PyTorch模型转ONNX
    • 3.4. PaddlePaddle模型转ONNX
      • 3.4.1. 简介
      • 3.4.2. 模型导出为ONNX协议
        • 3.4.2.1. 动态图导出ONNX协议
        • 3.4.2.2. 静态图导出ONNX协议
      • 3.4.3. ONNX模型的验证
      • 3.4.4. ONNXRuntime推理
  • 4. ONNX到目标平台

1. 背景

深度学习模型在训练完成之后,部署并应用在生产环境的这一步至关重要,毕竟训练出来的模型不能只接受一些公开数据集和榜单的检验,还需要在真正的业务场景下创造价值,不能只是为了PR而躺在实验机器上

在现有条件下,一般涉及到模型的部署就要涉及到模型的转换,而转换的过程也是随着对应平台的不同而不同,一般工程师接触到的平台分为GPU云平台、手机和其他嵌入式设备

对于GPU云平台来说,在上面部署本应该是最轻松的事情,但是实际情况往往比较复杂。有历史遗留问题,比如说3年前的古董级的模型因为效率和推理速度问题需要进行优化,也有算法团队使用了一些比较小众或者自定义的OP的问题。其实解决这类问题有非常直接的方式,例如直接用最新的框架重新训练,或者算法团队对模型做一些妥协,替换掉一些骚操作,那么对部署工程师来说问题就简单了很多。但是有些情况下(算法团队很忙或者必须效果优先),我们只能自己从框架的层面来解决这个问题,包括但不限于:实现新OP、修改不兼容的属性、修改不兼容的权重形状

当然这里无论是模型转换还是模型部署,我个人比较推荐的做法是都是使用ONNX作为中间媒介。所以我们有必要对ONNX做一个比较透彻的了解

2. ONNX结构分析与修改工具

2.1. ONNX结构分析

对于ONNX的了解,很多人可能仅仅停留在它是一个开源的深度学习模型标准,能够用于模型转换及部署但是对于其内部是如何定义这个标准,如何实现和组织的,却并不十分了解,所以在转换模型到ONNX的过程中,对于出现的不兼容不支持的问题有些茫然。

ONNX结构的定义基本都在这一个onnx.proto文件里面了,如何你对protobuf不太熟悉的话,可以先简单了解一下再回来看这个文件。当然我们也不必把这个文件每一行都看明白,只需要了解其大概组成即可,有一些部分几乎不会使用到可以忽略。

这里我把需要重点了解的对象列出来

  • ModelProto
  • GraphProto
  • NodeProto
  • AttributeProto
  • ValueInfoProto
  • TensorProto

我用尽可能简短的语言描述清楚上述几个Proto之间的关系:当我们将ONNX模型load进来之后,得到的是一个ModelProto,它包含了一些版本信息,生产者信息和一个非常重要的GraphProto;在GraphProto中包含了四个关键的repeated数组,分别是node(NodeProto类型),input(ValueInfoProto类型),output(ValueInfoProto类型)和initializer(TensorProto类型),其中node中存放着模型中的所有计算节点,input中存放着模型所有的输入节点,output存放着模型所有的输出节点,initializer存放着模型所有的权重;那么节点与节点之间的拓扑是如何定义的呢?非常简单,每个计算节点都同样会有inputoutput这样的两个数组(不过都是普通的string类型),通过inputoutput的指向关系,我们就能够利用上述信息快速构建出一个深度学习模型的拓扑图。最后每个计算节点当中还包含了一个AttributeProto数组,用于描述该节点的属性,例如Conv层的属性包含grouppadsstrides等等,具体每个计算节点的属性、输入和输出可以参考这个Operators.md文档。

需要注意的是,刚才我们所说的GraphProto中的input输入数组不仅仅包含我们一般理解中的图片输入的那个节点,还包含了模型当中所有权重。举个例子,Conv层中的W权重实体是保存在initializer当中的,那么相应的会有一个同名的输入在input当中,其背后的逻辑应该是把权重也看作是模型的输入,并通过initializer中的权重实体来对这个输入做初始化(也就是把值填充进来)

2.2. ONNX的兼容性问题

为什么要修改ONNX模型?因为ONNX版本迭代以及框架之间对OP定义的不兼容,导致转换出来的ONNX模型千奇百怪,会有一些莫名奇妙的错误。

比如在MXNet框架下有一个非常灵活的写法,可以在BatchNormalization后面直接跟FC层,如下图所示(请忽略其中的dropout,我也不知道算法同学是如何在inference/eval模式下把dropout层导出到ONNX模型里面来的):

在这里插入图片描述
使用onnxruntime进行inference的时候,报错提示FAIL : Node (pre_fc1) Op (Gemm) [ShapeInferenceError] First input does not have rank 2,那么为什么这个在MXNet下正常的模型转换成ONNX之后就出错了呢?严格来说,BatchNormalization出来output的维度为4,而FC层所接受的输入维度是2,这两者之间差了一个Flatten操作,但是MXNet(可能不限于MXNet)隐含的帮我们完成了这个步骤,所以说灵活性背后是有代价的。

这还仅仅是冰山一角,事实上你只要翻一翻onnxruntime这个repo的issue,还能看到不少类似的不兼容问题,例如BatchNormalization中的spatial属性引发的问题:

  • https://github.com/onnx/models/issues/156
  • https://github.com/microsoft/onnxruntime/issues/2175

此外还有PyTorch模型转换到ONNX模型中非常常见的reshape的问题:
在这里插入图片描述
一个简单的PyTorch中的view操作,期待出来的就是一个简单的reshape操作,但是如果你的形状参数用的是形如x.size(0)这样的话就会出现上图的问题,必须做一个强制类型转换int(x.size(0),或者用onnx-simplifier来处理一下模型

2.3. 修改ONNX模型

解决问题的最好办法是从根源入手,也就是从算法同学那边的模型代码入手,我们需要告诉他们问题出在哪里,如何修改。但是也有一些情况是无法通过修改模型代码解决的,或者与其浪费那个时间,不如我们部署工程师直接在ONNX模型上动刀解决问题。

还有一种更dirty的工作是,我们需要debug原模型和转换后的ONNX模型输出结果是否一致(误差小于某个阈值),如果不一致问题出现在哪一层,现有的深度学习框架我们有很多办法能够输出中间层的结果用于对比,而据我所知,ONNX中并没有提供这样的功能;这就导致了我们的debug工作极为繁琐

所以如果有办法能够随心所欲的修改ONNX模型就好了。要做到这一点,就需要了解上文所介绍的ONNX结构知识了。

比如说我们要在网络中添加一个节点,那么就需要先创建相应的NodeProto,参照文档设定其的属性,指定该节点的输入与输出,如果该节点带有权重那还需要创建相应的ValueInfoProtoTensorProto分别放入graph中的inputinitializer中,以上步骤缺一不可。

经过一段时间的摸索和熟悉,我写了一个小工具onnx-surgery并集成了一些常用的功能进去,实现的逻辑非常简单,也非常容易拓展。代码比较简陋,但是足以完成一些常见的修改操作

3. 各大深度学习框架如何转换到ONNX?

(需要说明的是,由于深度学习领域发展迅速,本文提到的几个框架也在快速的迭代过程中,所以希望本文提到的一些坑和bug在未来的版本当中能够逐一解决,也希望大家永远不要踩本文所提到的那些坑)

3.1. MXNet转换ONNX

MXNet官方文档给出了一个非常简单的例子展示如何转换

import mxnet as mx
import numpy as np
from mxnet.contrib import onnx as onnx_mxnet
import logging
logging.basicConfig(level=logging.INFO)# Download pre-trained resnet model - json and params by running following code.
path='http://data.mxnet.io/models/imagenet/'
[mx.test_utils.download(path+'resnet/18-layers/resnet-18-0000.params'),mx.test_utils.download(path+'resnet/18-layers/resnet-18-symbol.json'),mx.test_utils.download(path+'synset.txt')]# Downloaded input symbol and params files
sym = './resnet-18-symbol.json'
params = './resnet-18-0000.params'# Standard Imagenet input - 3 channels, 224*224
input_shape = (1,3,224,224)# Path of the output file
onnx_file = './mxnet_exported_resnet50.onnx'# Invoke export model API. It returns path of the converted onnx model
converted_model_path = onnx_mxnet.export_model(sym, params, [input_shape], np.float32, onnx_file)

这个重点提一下MXNet转换ONNX模型可能会遇到的一些问题,不排除在未来版本MXNet修复了相关问题,也不排除未来ONNX版本更新又出现新的不兼容问题。

第一个问题与MXNet的BatchNorm层中的fix_gamma参数有关,当fix_gamma参数为True时,其含义是将gamma这个参数固定为1,即(x-mean)/var * gamma + beta;但是这里就出现了不兼容的问题,因为在ONNX当中是没有fix_gamma这个属性的,如果fix_gamma为False不会有问题,如果fix_gamma为True就会出现两者计算结果不一致问题。解决方法很直观,当fix_gamma参数为True时,我们必须手动将ONNX当中的gamma参数全部置为1

第二个问题与MXNet的Pooling层中的count_include_pad属性有关,这个问题应该是MXNet贡献者的疏忽,当Pooling层的类型为’avg’时,忘记了在生成ONNX节点时设置该属性。解决方法就是在_op_translation.py文件里增加一个分支,将这个遗漏属性补上。

count_include_pad = 1 if attrs.get("count_include_pad", "True") in ["True", "1"] else 0
# ...
# ...
# ...
elif pool_type == "avg":node = onnx.helper.make_node(pool_types[pool_type],input_nodes,  # input[name],count_include_pad=count_include_pad,kernel_shape=kernel,pads=pad_dims,strides=stride,name=name)

当然,如果你不想直接修改MXNet的导出代码,也可以直接修改ONNX模型达到同样的目的,方法可以参考上一篇文章中我写的小工具

3.2. TensorFlow模型转ONNX

tf的模型转换ONNX已经有现成的转换工具,https://github.com/onnx/tensorflow-onnx,先将tf的模型freeze_graph之后得到pb文件,再利用该转换工具即可转换为onnx模型

freeze_graph的方式网上有很多版本,我这里用的是一个老版本的方法(tensorflow==1.8.0)

# your network def
import networkinput_size = (224, 224)
ckpt_model_path = "./model.ckpt"
pb_model_path = "./model.pb"
output_node_name = "your model output name"graph = tf.Graph()
with graph.as_default():placeholder = tf.placeholder(dtype=tf.float32, shape=[None, input_size[0], input_size[1], 3], name="pb_input")output = network(placeholder)# your can get all the tensor names if you do not know your input and output name in your ckpt with this code# nl = [n.name for n in tf.get_default_graph().as_graph_def().node]# for n in nl:#     print(n)saver = tf.train.Saver()sess = tf.Session(config=tf.ConfigProto(gpu_options=tf.GPUOptions(allow_growth=True, per_process_gpu_memory_fraction=1.0),allow_soft_placement=True))saver.restore(sess, ckpt_model_path)output_graph_def = graph_util.convert_variables_to_constants(sess, sess.graph_def, [output_node_name])with tf.gfile.FastGFile(pb_model_path, mode="wb") as f:f.write(output_graph_def.SerializeToString())# you can get the input and output name of your model.pb file# maybe a "import/" is needed to append before the name if you# get some error# gf = tf.GraphDef()# gf.ParseFromString(open('./model.pb', 'rb').read())# nl2 = [n.name + '=>' +  n.op for n in gf.node if n.op in ('Softmax', 'Placeholder')]# for n in nl2:#     print(n)

需要指出的是大部分tf模型的输入layout都是NHWC,而ONNX模型的输入layout为NCHW,因此建议在转换的时候加上--inputs-as-nchw这个选项,其他选项可以参考文档,非常详细

典型的转换命令如下所示:

python3 -m tf2onnx.convert --input xxxx.pb --inputs pb_input:0 --inputs-as-nchw pb_input:0 --outputs resnet_v2_101/predictions/Softmax:0 --output xxxx.onnx

注意,由于tensorflow的模型输入一般会比较灵活,输入的batch_size可以留空,可以在运行时传入不同大小的batch_size数据。但是一般在ONNX和TensorRT这些框架中,我们习惯于指定一个固定的batch_size,那如何修改呢,可以参考上一篇文章中我写的那个小工具,有一个例子展示如何修改ONNX模型的batch_size

3.3. PyTorch模型转ONNX

在PyTorch推出jit之后,很多情况下我们直接用torch scirpt来做inference会更加方便快捷,并不需要转换成ONNX格式了,当然如果你追求的是极致的效率,想使用TensorRT的话,那么还是建议先转换成ONNX的。

import torch
import torchvisiondummy_input = torch.randn(10, 3, 224, 224, device='cuda')
model = torchvision.models.alexnet(pretrained=True).cuda()# Providing input and output names sets the display names for values
# within the model's graph. Setting these does not change the semantics
# of the graph; it is only for readability.
#
# The inputs to the network consist of the flat list of inputs (i.e.
# the values you would pass to the forward() method) followed by the
# flat list of parameters. You can partially specify names, i.e. provide
# a list here shorter than the number of inputs to the model, and we will
# only set that subset of names, starting from the beginning.
input_names = [ "actual_input_1" ] + [ "learned_%d" % i for i in range(16) ]
output_names = [ "output1" ]torch.onnx.export(model, dummy_input, "alexnet.onnx", verbose=True, input_names=input_names, output_names=output_names)

注意上面的input_names和output_names不是必需的参数,省略也是可以的

3.4. PaddlePaddle模型转ONNX

https://www.bookstack.cn/read/paddlepaddle-2.0-zh/e1d1bbfc28a54089.md

3.4.1. 简介

ONNX (Open Neural Network Exchange) 是针对机器学习所设计的开源文件格式,用于存储训练好的模型。它使得不同的人工智能框架可以采用相同格式存储模型并交互。通过ONNX格式,Paddle模型可以使用OpenVINO、ONNX Runtime等框架进行推理。

Paddle转ONNX协议由 paddle2onnx 实现,下面介绍如何将Paddle模型转换为ONNX模型并验证正确性。

本教程涉及的示例代码,可点击 IPython 获取, 除Paddle以外,还需安装以下依赖:

    pip install paddle2onnx onnx onnxruntime // -i https://mirror.baidu.com/pypi/simple 如果网速不好,可以使用其他源下载

3.4.2. 模型导出为ONNX协议

3.4.2.1. 动态图导出ONNX协议

Paddle动态图模型转换为ONNX协议,首先会将Paddle的动态图 paddle.nn.Layer 转换为静态图, 详细原理可以参考 动态图转静态图 。然后依照ONNX的算子协议,将Paddle的算子一一映射为ONNX的算子。动态图转换ONNX调用 paddle.onnx.export() 接口即可实现,该接口通过 input_spec 参数为模型指定输入的形状和数据类型,支持TensorInputSpec ,其中 InputSpec 支持动态的shape。

关于 paddle.onnx.export 接口更详细的使用方法,请参考 API 。

    import paddlefrom paddle import nnfrom paddle.static import InputSpecclass LinearNet(nn.Layer):def __init__(self):super(LinearNet, self).__init__()self._linear = nn.Linear(784, 10)def forward(self, x):return self._linear(x)# export to ONNXlayer = LinearNet()save_path = 'onnx.save/linear_net'x_spec = InputSpec([None, 784], 'float32', 'x')paddle.onnx.export(layer, save_path, input_spec=[x_spec])
3.4.2.2. 静态图导出ONNX协议

Paddle 2.0以后将主推动态图组网方式,如果您的模型来自于旧版本的Paddle,使用静态图组网,请参考paddle2onnx的 使用文档 和 示例 。

3.4.3. ONNX模型的验证

ONNX官方工具包提供了API可验证模型的正确性,主要包括两个方面,一是算子是否符合对应版本的协议,二是网络结构是否完整。

    # check by ONNXimport onnxonnx_file = save_path +  '.onnx'onnx_model = onnx.load(onnx_file)onnx.checker.check_model(onnx_model)print('The model is checked!')

如果模型检查失败,请到 Paddle 或 paddle2onnx 提出Issue,我们会跟进相应的问题。

3.4.4. ONNXRuntime推理

本节介绍使用ONNXRuntime对已转换的Paddle模型进行推理,并与使用Paddle进行推理的结果进行对比。

    import numpy as npimport onnxruntimex = np.random.random((2, 784)).astype('float32')# predict by ONNX Runtimeort_sess = onnxruntime.InferenceSession(onnx_file)ort_inputs = {ort_sess.get_inputs()[0].name: x}ort_outs = ort_sess.run(None, ort_inputs)print("Exported model has been predicted by ONNXRuntime!")# predict by Paddlelayer.eval()paddle_outs = layer(x)# compare ONNX Runtime and Paddle resultsnp.testing.assert_allclose(ort_outs[0], paddle_outs.numpy(), rtol=1.0, atol=1e-05)print("The difference of results between ONNXRuntime and Paddle looks good!")

4. ONNX到目标平台

ONNX实际只是一套标准,里面只不过存储了网络的拓扑结构和权重(其实每个深度学习框架最后固化的模型都是类似的),脱离开框架是没有办法直接进行inference的。大部分框架(除了tensorflow)基本都做了ONNX模型inference的支持,这里就不进行展开了。

那么如果你想直接使用ONNX模型来做部署的话,有下列几种情况:第一种情况,目标平台是CUDA或者X86的话,又怕环境配置麻烦采坑,比较推荐使用的是微软的onnxruntime,毕竟是微软亲儿子;第二种情况,而如果目标平台是CUDA又追求极致的效率的话,可以考虑转换成TensorRT;第三种情况,如果目标平台是ARM或者其他IoT设备,那么就要考虑使用端侧推理框架了,例如NCNN、MNN和MACE等等。

第一种情况应该是坑最少的一种了,但要注意的是官方的onnxruntime安装包只支持CUDA 10和Python 3,如果是其他环境可能需要自行编译。安装完成之后推理部署的代码可以直接参考官方文档。

第二种情况要稍微麻烦一点,你需要先搭建好TensorRT的环境,然后可以直接使用TensorRT对ONNX模型进行推理;然后更为推荐的做法是将ONNX模型转换为TensorRT的engine文件,这样可以获得最优的性能。关于ONNX parser部分的代码,NVIDIA是开源出来了的(当然也包括其他parser比如caffe的),不过这一块如果你所使用的模型中包括一些比较少见的OP,可能是会存在一些坑的,比如我们的模型中包含了一个IBN结构,引入了InstanceNormalization这个OP,解决的过程可谓是一波三折;好在NVIDIA有一个论坛,有什么问题或者bug可以在上面进行反馈,专门有NVIDIA的工程师在上面解决大家的问题,不过从我两次反馈bug的响应速度来看NVIDIA还是把TensorRT开源最好,这样方便大家自己去定位bug

第三种情况的话一般问题也不大,由于是在端上执行,计算力有限,所以确保你的模型是经过精简和剪枝过的能够适配移动端的。几个端侧推理框架的性能到底如何并没有定论,由于大家都是手写汇编优化,以卷积为例,有的框架针对不同尺寸的卷积都各写了一种汇编实现,因此不同的模型、不同的端侧推理框架,不同的ARM芯片都有可能导致推理的性能有好有坏,这都是正常情况。

参考: 深度学习模型转换与部署那些事(含ONNX格式详细分析)
参考:
参考:
参考:

相关文章:

ONNX的结构与转换

ONNX的结构与转换 1. 背景2. ONNX结构分析与修改工具2.1. ONNX结构分析2.2. ONNX的兼容性问题2.3. 修改ONNX模型 3. 各大深度学习框架如何转换到ONNX?3.1. MXNet转换ONNX3.2. TensorFlow模型转ONNX3.3. PyTorch模型转ONNX3.4. PaddlePaddle模型转ONNX3.4.1. 简介3.4…...

vue3中,使用html2canvas截图包含视频、图片、文字的区域

需求:将页面中指定区域进行截图,区域中包含了图片、文字、视频。 第一步,先安装 npm install html2canvas第二步,在页面引入: import html2canvas from html2canvas;第三步,页面使用: 1&…...

后端神器!代码写完直接调试!

Apipost推出IDEA插件非常省时高效,写完代码直接可以进行调试,而且支持生成接口文档,真是后端神器啊! 可以点击下方链接安装更新或在插件商店中搜索安装 下载链接:https://plugins.jetbrains.com/plugin/22676-apipos…...

MATLAB | 万圣节来画个简单的可爱鬼叭!

万圣节要到啦一起来画个可爱鬼吧~ 代码比较的短: 完整代码 figure(Units,normalized,Position,[.2,.1,.52,.72]); axgca;hold on;axis off; ax.DataAspectRatio[1,1,1]; ax.YDirreverse; ax.XLim[0,100]; ax.YLim[0,100]; [X,Y]meshgrid(linspace(0,1,200)); Zsq…...

贪心算法学习------优势洗牌

目录 一,题目 二,题目接口 三,解题思路和代码 全部代码: 一,题目 给定两个数组nums1和nums2,nums1相对于nums2的优势可以用满足nums1[i]>nums2[i]的索引i的数目来描述。 返回nums1的任意排序,使其优…...

音视频rtsp rtmp gb28181在浏览器上的按需拉流

按需拉流是从客户视角来看待音视频的产品功能,直观,好用,为啥hls flv大行其道也是这个原因,不过上述存在的问题是延迟没法降到实时毫秒级延迟,也不能随心所欲的控制。通过一段时间的努力,结合自己闭环技术栈…...

Java 算法篇-深入了解二分查找法

🔥博客主页: 小扳_-CSDN博客 ❤感谢大家点赞👍收藏⭐评论✍ 目录 1.0 二分查找法的说明 2.0 二分查找实现的多种版本 2.1 二分查找的基础版本 2.2 二分查找的改动版本 2.3 二分查找的平衡版本 2.4 二分查找的官方版本 3.0 二分查找的应用 1…...

Data-Centric Financial Large Language Models

本文是LLM系列文章,针对《Data-Centric Financial Large Language Models》的翻译。 以数据为中心的大语言金融模型 摘要1 引言2 背景3 方法4 实验5 结论和未来工作 摘要 大型语言模型(LLM)有望用于自然语言任务,但在直接应用于…...

【HarmonyOS】服务卡片 API6 JSUI跳转不同页面并携带参数

【关键字】 服务卡片、卡片跳转不同页面、卡片跳转页面携带参数 【写在前面】 本篇文章主要介绍开发服务卡片时,如何实现卡片点击跳转不同页面,并携带动态参数到js页面。在此篇文章“服务卡片 API6 JSUI跳转不同页面”中说明了如果跳转不同页面&#xf…...

SQL server数据库端口访问法

最近数据库连接&#xff0c;也是无意中发现了这个问题&#xff0c;数据库可根据端口来连接 网址:yii666.com< 我用的是sql2014测试的&#xff0c;在安装其他程序是默认安装了sql(sql的tcp/ip端口为xxx)&#xff0c;服务也不相同&#xff0c;但是由于比较不全&#xff0c;我…...

深孔枪钻厂家,科研管理系统思路

序号 名称 参数及技术指标 &#xff08;一&#xff09;系统性能要求 1&#xff0e;系统界面&#xff1a;支持中英文界面自由切换。 2. 系统兼容性&#xff1a;支持主流浏览器&#xff0c;如&#xff1a;IE11 以上、 360 安全浏览器、Firefox、Google Ch…...

【论文阅读笔记】GLM-130B: AN OPEN BILINGUAL PRE-TRAINEDMODEL

Glm-130b:开放式双语预训练模型 摘要 我们介绍了GLM-130B&#xff0c;一个具有1300亿个参数的双语(英语和汉语)预训练语言模型。这是一个至少与GPT-3(达芬奇)一样好的100b规模模型的开源尝试&#xff0c;并揭示了如何成功地对这种规模的模型进行预训练。在这一过程中&#xff0…...

Object常用方法

Object常用方法目录 1. equals(Object obj)&#xff1a; 2. toString()&#xff1a; 3. hashCode()&#xff1a; 4. getClass()&#xff1a; 5. notify() 和 notifyAll()&#xff1a; 6. wait() 和 wait(long timeout)&#xff1a; 7. clone()&#xff1a; 8. fina…...

【VR开发】【Unity】【VRTK】2-关于VR的基础知识

【概述】 在VRTK的实操讲解之前&#xff0c;本篇先介绍几个重要的VR认识。 【VR对各个行业的颠覆】 如果互联网几乎把所有行业都重做了一遍&#xff0c;VR在接下来的几年很可能再把现有的行业都重做一遍&#xff0c;包括但不限于教育&#xff0c;房地产&#xff0c;零售&…...

jeecg-uniapp 转成小程序的过程 以及报错 uniapp点击事件

uniapp 点击事件 tap: 单击事件 confirm: 回车事件 blur:失去焦点事件 touchstart: 触摸开始事件 touchmove: 触摸移动事件。 touchend: 触摸结束事件。 longpress: 长按事件。 input: 输入框内容变化事件。 change: 表单元素值变化事件。 submit: 表单提交事件。 scroll: 滚动…...

Django的静态文件目录(路径)如何配置?

通常用下面的三条语句配置Django的静态文件目录 STATICFILES_DIRS [os.path.join(BASE_DIR, static)] STATIC_URL /static/ STATIC_ROOT os.path.join(BASE_DIR, /static)那么这三条语句分别的作用是什么呢&#xff1f; 请参考博文 https://blog.csdn.net/wenhao_ir/articl…...

函数应用(MySQL)

--数值类函数 --绝对值 select abs(-1) --seiling ceil 向上取整 select ceil(1.1) --floor 向下取整 select floor(1.9); --四舍五入 select round(1.17, 1); --rand 随机数 select rand(rand()*1000); --字符串函数 utf8mb3 utfmb4 select length(小三) --查找字符数…...

数据分析过程中,发现数值缺失,怎么办?

按照数据缺失机制&#xff0c;数据分析过程中&#xff0c;我们可以将其分为以下几类&#xff1a; &#xff08;1&#xff09;完全随机缺失&#xff08;MCAR&#xff09;&#xff1a;所缺失的数据发生的概率既与已观察到的数据无关&#xff0c;也与未观察到的数据无关。 &#x…...

Vue3.0 toRef toRefs :VCA模式

简介 作用&#xff1a; 创建一个ref对象&#xff0c;其value值指向另一个对象中的某个属性 语法&#xff1a; const name toRef(person, name) 应用&#xff1a; 要将响应式对象中的某个属性单独供应给外部使用时 扩展&#xff1a; toRefs与toRef功能一致&#xff0c;但可…...

VS Code提取扩展时出错。XHR failed

需求&#xff1a;想要在扩展中心下载插件&#xff0c;发现报错 原因&#xff1a;vs code之前设置了代理&#xff0c;需要删除即可...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

让AI看见世界:MCP协议与服务器的工作原理

让AI看见世界&#xff1a;MCP协议与服务器的工作原理 MCP&#xff08;Model Context Protocol&#xff09;是一种创新的通信协议&#xff0c;旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天&#xff0c;MCP正成为连接AI与现实世界的重要桥梁。…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP

编辑-虚拟网络编辑器-更改设置 选择桥接模式&#xff0c;然后找到相应的网卡&#xff08;可以查看自己本机的网络连接&#xff09; windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置&#xff0c;选择刚才配置的桥接模式 静态ip设置&#xff1a; 我用的ubuntu24桌…...

深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用

文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么&#xff1f;1.1.2 感知机的工作原理 1.2 感知机的简单应用&#xff1a;基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...

Linux 中如何提取压缩文件 ?

Linux 是一种流行的开源操作系统&#xff0c;它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间&#xff0c;使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的&#xff0c;要在 …...

08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险

C#入门系列【类的基本概念】&#xff1a;开启编程世界的奇妙冒险 嘿&#xff0c;各位编程小白探险家&#xff01;欢迎来到 C# 的奇幻大陆&#xff01;今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类&#xff01;别害怕&#xff0c;跟着我&#xff0c;保准让你轻松搞…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

在树莓派上添加音频输入设备的几种方法

在树莓派上添加音频输入设备可以通过以下步骤完成&#xff0c;具体方法取决于设备类型&#xff08;如USB麦克风、3.5mm接口麦克风或HDMI音频输入&#xff09;。以下是详细指南&#xff1a; 1. 连接音频输入设备 USB麦克风/声卡&#xff1a;直接插入树莓派的USB接口。3.5mm麦克…...