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格式详细分析)
参考:
参考:
参考:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/128989.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高阶数据结构学习——LRU Cache

文章目录 1、了解LRU Cache(Least Recently Used缩写)2、代码实现 1、了解LRU Cache(Least Recently Used缩写) Cache是缓存,在磁盘和内存之间,内存和寄存器之间都存在,CPU和内存之间存在三级缓…

基于PyTorch的中文情绪分析器设计与开发

收藏和点赞,您的关注是我创作的动力 文章目录 概要 一、相关基础理论2.1 主流深度学习框架2.2 神经网络2.2.1 神经网络基础 二、中文情感分类模型构建3.1 开发环境3.2 数据部分3.3 文本特征提取3.3.1、过滤标点符号3.3.2 中文分词、单词过滤 三 运行结果与分析五 结…

快速批量重命名:高效修改文件名并转换为大写扩展名

在文件管理中,批量重命名文件是一个常见的需求。通过快速批量重命名,我们可以高效地修改文件名并将扩展字母转换为大写形式。这样做不仅可以提高文件的可读性,还可以更好地组织和管理文件。用云炫文件管理器可以快速批量重命名,能…

k8s之pod进阶---资源限制与探针

目录 一、资源限制 二、探针(健康检查) 2.1 含义 2.2 探针的三种规则 2.3 probe支持三种检查方法 2.4 探针的示例 1、存活探针:livenessProbe (1)exec方式 (2)httpGet方式 (…

如何使用ps制作ico图标文件

如何使用ps制作ico图标文件 Chapter1 如何使用ps制作ico图标文件Chapter2 ICOFormat.8bi(Photoshop Ico、Cur插件)的下载使用1. ICOFormat.8bi的作用2. ICOFormat.8bi使用 Chapter3 ps手机计算机图标教程,手绘设计精美手机APP软件图标的PS教程步骤 01 制…

目标检测中常见指标 - mAP

文章目录 1. 评价指标2. 计算示例3. COCO评价指标 1. 评价指标 在目标检测领域,比较常用的两个公开数据集:pascal voc和coco。 目标检测与图像分类明显差距是很大的,在图像分类中,我们通常是统计在验证集当中,分类正…

佳易王定制开发流水线商品标签自动打印软件,打印格式可定制

佳易王定制开发流水线商品标签自动打印软件,打印格式可以定制 软件特色: 定制试用商品标签打印管理V16.0,打印标签可以自动计算到期日期和品控日期,并打印品名、包装规格、生产日期、到期日期、储存条件、生产包装、品控日期等信…

阿里测试8年,肝到P8只剩他了····

在阿里工作了8年,工作压力大,节奏快,但是从技术上确实得到了成长,尤其是当你维护与大促相关的系统的时候,熬到P7也费了不少心思,小编也是个爱学习的人,把这几年的工作经验整理成了一份完整的笔记…

Spring Cloud智慧工地源码,利用计算机技术、互联网、物联网、云计算、大数据等新一代信息技术开发,微服务架构

智慧工地系统充分利用计算机技术、互联网、物联网、云计算、大数据等新一代信息技术,以PC端,移动端,设备端三位一体的管控方式为企业现场工程管理提供了先进的技术手段。让劳务、设备、物料、安全、环境、能源、资料、计划、质量、视频监控等…

Python自动化运维监控——批量监听页面发邮件(自由配置ini文件+smtplib)

一、程序样式 1.listen.ini配置文件 2.监控页面 3.日志 二、核心点 smtplib库:这里使用了smtp.qq.com与smtp.163.com两个发送邮件的地址,使用邮箱用户名与授权码来实现登录,端口都使用465,最后抛出异常,finally里…

springboot的请求与响应

一,简单参数 Get请求:只需要在postman中的Params参数与方法中的形参一致就可以Post请求与Get方法一致只需要在 如果参数名不一致--通过RequestParam中的value属性执行请求参数名 RequestParam(name "name",required false) //表示name参数不是…

【CesiumJS】(1)Hello world

介绍 Cesium 起源于2011年,初衷是航空软件公司(Analytical Graphics, Inc.)的一个团队要制作世界上最准确、性能最高且具有时间动态性的虚拟地球。取名"Cesium"是因为元素铯Cesium让原子钟非常准确(1967年,人们依据铯原子的振动而对…

【实战Flask API项目指南】之三 路由和视图函数

实战Flask API项目指南之 路由和视图函数 本系列文章将带你深入探索实战Flask API项目指南,通过跟随小菜的学习之旅,你将逐步掌握 Flask 在实际项目中的应用。让我们一起踏上这个精彩的学习之旅吧! 前言 当小菜踏入Flask后端开发的世界时&…

柯桥俄语考级培训,俄语专八如何备考

1.用好真题 真题一共分为:口语表述、听力、词汇语法句法、文学常识、国情、阅读理解、俄汉互译、作文等部分。 第一,要自己动手做真题,然后对答案,看错题错在什么地方,还有哪些知识点是盲区。 第二,分析每…

HTTP/HTTPS、SSL/TLS、WS/WSS 都是什么?

有同学问我,HTTP/HTTPS、SSL/TLS、WS/WSS 这都是些什么?那我们就先从概念说起: HTTP 是超文本传输协议,信息是通过明文传输。HTTPS 是在 HTTP 的基础上信息通过加密后再传输。SSL 是实现 HTTPS 信息传输加密的算法。TLS 是 SSL 的…

基于C语言实现扫雷小游戏

扫雷游戏 1. 扫雷游戏分析和设计1.1 扫雷游戏的功能说明1.2 游戏的分析和设计1.2.1 数据结构的分析 2. 扫雷游戏的代码实现3. 扫雷游戏的扩展 1. 扫雷游戏分析和设计 1.1 扫雷游戏的功能说明 使用控制台实现经典的扫雷游戏 游戏可以通过菜单实现继续玩或者退出游戏 扫雷的棋…

React使用富文本CKEditor 5,上传图片并可设置大小

上传图片 基础使用(标题、粗体、斜体、超链接、缩进段落、有序无序、上传图片) 官网查看:https://ckeditor.com/docs/ckeditor5/latest/installation/integrations/react.html 安装依赖 npm install --save ckeditor/ckeditor5-react cked…

k8s系列文章一:安装指南

前言 k8s是docker的升级版,可用于docker集群配置管理微服务 一、更新ubuntu系统版本 sudo apt update sudo apt upgrade二、添加GPG密钥(阿里源) 尽管我不知道gpg是个什么东西,反正跟着做就完了 curl https://mirrors.aliyun.com/kubernetes/apt/do…

【Echarts】玫瑰饼图数据交互

在学习echarts玫瑰饼图的过程中,了解到三种数据交互的方法,如果对您也有帮助,不胜欣喜。 一、官网教程 https://echarts.apache.org/examples/zh/editor.html?cpie-roseType-simple (该教程数据在代码中) import *…

echarts 画散点图, x周,y周在指定位置标志一下

文章目录 echarts 画散点图, x周,y周在指定位置标志一下示例一例子二示例三 echarts 画散点图, x周,y周在指定位置标志一下 示例一 let scatterData {data: [[[-0.2, -0.6],[0.4, 0.3],[0.1, 0.4],[0.3, 0.5],[0.09, 0.1],[0.7,…