import torch from torch import nn class DLCorr(nn.Module): def __init__(self): """相似推荐,从指定数据集寻找最自己最接近的数据 - 要从x2中找到与x1相似的数据 return ------------------------- 返回与自己最相近的数据的索引 """ super().__init__() def forward(self, X): """正向传播 - 计算目标数据中与自己相似度最高的数据的索引 """ X = X.unsqueeze(1) # [B, 1, D] x1 = X[0,0,:] x1 = x1.unsqueeze(0) x1 = x1.unsqueeze(0) x2 = X[1:,:,:] # 计算距离,每个样本与所有样本的距离 distance_matrix = torch.sqrt(torch.sum(X ** 2, dim=2)) print("distance_matrix:",distance_matrix.shape) index = torch.argmin(distance_matrix, dim=0) return index X = X.unsqueeze(1) # [B, 1, D] x1 = X[0,0,:] x1 = x1.unsqueeze(0) x1 = x1.unsqueeze(0) x2 = X[1:,:,:] 数据拆分onnx支持,但可能不友好,能不用就不用 onnx对广播机器支持的不太好,尽量自己将矩阵的shape对齐 # 计算距离,每个样本与所有样本的距离 distance_matrix = torch.sqrt(torch.sum(X ** 2, dim=2)) 对于矩阵计算,numpy也能现实,但要转Onnx就需要全部使用torch的方法 distance_matrix = np.sqrt(((X[:, np.newaxis] - self.X)**2).sum(axis=2)) return index 返回结果最好是列表,而不是标量 修改前的代码有维度判断 X.ndim != 2 这可能导致模型无法正确处理输入,进而导致 ONNX 导出失败。 已删除该代码 X = X.unsqueeze(1) # [B, 1, D] 这一行代码将一个2维矩阵转为三维 这是因为深度学习输入一般至少3维,就是文本处理,[B,C,L] 如果是图像处理,那么是4维,[B,C,H,W] 但这里要实现的,其实类似于机器学习,是2维数表 为了迎合深度学习的格式,增加了一维 其实没有必要 因为不管是[B,C,L],还是[B,C,H,W]这种格式, 是线性变换的需要 本算法求数据第1个元素与后面元素的相似度,根本就没有使用线性变换 因此不需要故意地转换维度 修改代码如下 class DLCorr(nn.Module): def __init__(self): """相似推荐,从指定数据集寻找最自己最接近的数据 - 求数据第1个元素与后面元素的相似度 return ------------------------- 返回与自己最相近的数据的索引 """ super().__init__() def forward(self, X): """正向传播 - 计算目标数据中与自己相似度最高的数据的索引 """ x1 = X[:1,:] x2 = X[1:,:] # 计算距离,每个样本与所有样本的距离 distance_matrix = torch.sqrt(torch.sum((x1-x2) ** 2, dim=1)) index = torch.argmin(distance_matrix, dim=0) return index 求距离这一行可以优化为 distance_matrix = torch.sum((x1-x2) ** 2, dim=1) 开方是为了套公式 不开方为了工程优化,就是求一个极值,开不开方一样的; 但工程上少计算了一个环节 |
|
|
|
|
将pytorch模型保存为onnx 仅将pytorch模型保存为onnx,并不加载运行,只是为其他程序提供onnx格式的模型, 这时仅安装pytorch包就可以 import torch import torchvision model = torchvision.models.resnet18() model.eval() #[B,C,H,W],trace需要通过实际运行一遍模型导出其静态图,故需要一个输入数据 h0 = torch.zeros(1, 3, 32, 32) # trace方式,在模型设计时不要用for循环, # 能在模型外完成的数据操作不要在模型中写, # 不用inplace等高大上的语法,保持简单,简洁,否则onnx可能无法完全转换过去 torch.onnx.export( model=model, # model的参数,就是原来y_out = model(args)的args在这里指定了 # 有其shape能让模型运行一次就行,不需要真实数据 args=(h0,), # 储存的文件路径 f="model02.onnx", # 导出模型参数,默认为True export_params = True, # eval推理模式,dropout,BatchNorm等超参数固定或不生效 training=torch.onnx.TrainingMode.EVAL, # 打印详细信息 verbose=True, # 为输入和输出节点指定名称,方便后面查看或者操作 input_names=["input1"], output_names=["output1"], # 这里的opset,指各类算子以何种方式导出,对应于symbolic_opset11 opset_version=11, # batch维度是动态的,其他的避免动态 dynamic_axes={ "input1": {0: "batch"}, "output1": {0: "batch"}, } ) |
加载onnx import onnx model_onnx = onnx.load("model02.onnx") # 加载onnx模型 onnx.checker.check_model(model_onnx) # 验证onnx模型是否加载成功 调用onnx import onnxruntime import numpy as np # 创建会话 session = onnxruntime.InferenceSession("model02.onnx",providers=[ 'CPUExecutionProvider']) # session = onnxruntime.InferenceSession("model02.onnx",providers=['CUDAExecutionProvider', 'CPUExecutionProvider']) x = np.random.randn(1, 3, 32, 32) ort_input = {session.get_inputs()[0].name: x.astype(np.float32)} ort_output = session.run(None, ort_input) |
示例1 #[B,C,H,W],trace需要通过实际运行一遍模型导出其静态图,故需要一个输入数据 h0 = torch.zeros(1, 1350) # trace方式,在模型设计时不要用for循环, # 能在模型外完成的数据操作不要在模型中写, # 不用inplace等高大上的语法,保持简单,简洁,否则onnx可能无法完全转换过去 torch.onnx.export( model=model, # model的参数,就是原来y_out = model(args)的args在这里指定了 # 有其shape能让模型运行一次就行,不需要真实数据 args=(h0,), # 储存的文件路径 f=f"model/{pm.model_name}_{pm.model_version}.onnx", # 导出模型参数,默认为True export_params = True, # eval推理模式,dropout,BatchNorm等超参数固定或不生效 training=torch.onnx.TrainingMode.EVAL, # 打印详细信息 verbose=True, # 为输入和输出节点指定名称,方便后面查看或者操作 input_names=["input"], output_names=["output"], # 这里的opset,指各类算子以何种方式导出,对应于symbolic_opset11 opset_version=11, # batch维度是动态的,其他的避免动态 dynamic_axes={ "input": {0: "batch"}, "output": {0: "batch"}, } ) import onnxruntime import numpy as np # 创建会话 session = onnxruntime.InferenceSession(f"model/{pm.model_name}_{pm.model_version}.onnx",providers=[ 'CPUExecutionProvider']) # session = onnxruntime.InferenceSession("model02.onnx",providers=['CUDAExecutionProvider', 'CPUExecutionProvider']) x = np.random.randn(1, 1350) ort_input = {session.get_inputs()[0].name: x.astype(np.float32)} ort_output = session.run(None, ort_input) ort_output [array([0.48087886, 0.51912105], dtype=float32)] session.get_inputs()[0].name 'input' |
|
|
代码摘要 #统一转换到负数(非正),这样exp运算后也不会出现极大的数 x = torch.exp(x-x.max()) out = (x[0]/x[0].sum()).unsqueeze(dim=0) for i in range(1,batch_size): row = (x[i]/x[i].sum()).unsqueeze(dim=0) out = torch.cat((out, row), dim=0) 下面的代码,虽然简洁,但对数据做了内部修改,这是梯度计算不允许的 batch_size = x.shape[0] #统一转换到负数(非正),这样exp运算后也不会出现极大的数 x = torch.exp(x-x.max()) for i in range(batch_size): x[i]=x[i]/x[i].sum() 全代码 import torch from torch import nn import torchvision class DLModel(nn.Module): """模型定义 """ def __init__(self, in_features, out_features): """参数网络设计 - 总体来说,做的事件是将数据从一个维度转换到另外一个维度 """ super(DLModel, self).__init__() self.linear = nn.Linear(in_features=in_features, out_features=out_features) def forward(self, X): """正向传播 - 调用定义的参数网络 - 让数据流过参数网络,常量数据流过不过的参数产生不同的值 - 这个过程参数本身不会变 - 让参数变化的是后面的优化器 """ x = self.linear(X) batch_size = x.shape[0] print("batch_size:",batch_size) #统一转换到负数(非正),这样exp运算后也不会出现极大的数 x = torch.exp(x-x.max()) for i in range(batch_size): x[i]=x[i]/x[i].sum() out = x return out model = DLModel(in_features=32, out_features=32) #[B,C,H,W] h0 = torch.zeros(64, 3, 32, 32) y_out = model(X=h0) y_out.shape #[B,C,H,W],trace需要通过实际运行一遍模型导出其静态图,故需要一个输入数据 h0 = torch.zeros(1, 3, 32, 32) # trace方式,在模型设计时不要用for循环, # 能在模型外完成的数据操作不要在模型中写, # 不用inplace等高大上的语法,保持简单,简洁,否则onnx可能无法完全转换过去 torch.onnx.export( model=model, # model的参数,就是原来y_out = model(args)的args在这里指定了 # 有其shape能让模型运行一次就行,不需要真实数据 args=(h0,), # 储存的文件路径 f="model02.onnx", # 导出模型参数,默认为True export_params = True, # eval推理模式,dropout,BatchNorm等超参数固定或不生效 training=torch.onnx.TrainingMode.EVAL, # 打印详细信息 verbose=True, # 为输入和输出节点指定名称,方便后面查看或者操作 input_names=["input1"], output_names=["output1"], # 这里的opset,指各类算子以何种方式导出,对应于symbolic_opset11 opset_version=11, # batch维度是动态的,其他的避免动态 dynamic_axes={ "input1": {0: "batch"}, "output1": {0: "batch"}, } ) batch_size: tensor(1) Exported graph: graph(%input1 : Float(*, 3, 32, 32, strides=[3072, 1024, 32, 1], requires_grad=0, device=cpu), %linear.bias : Float(32, strides=[1], requires_grad=1, device=cpu), %onnx::MatMul_62 : Float(32, 32, strides=[1, 32], requires_grad=0, device=cpu)): %/linear/MatMul_output_0 : Float(*, 3, 32, 32, device=cpu) = onnx::MatMul[onnx_name="/linear/MatMul"](%input1, %onnx::MatMul_62), scope: __main__.DLModel::/torch.nn.modules.linear.Linear::linear # /ai/app/anaconda3/lib/python3.9/site-packages/torch/nn/modules/linear.py:114:0 ... ... ... %/Concat_2_output_0 : Long(4, strides=[1], device=cpu) = onnx::Concat[axis=0, onnx_name="/Concat_2"](%/Constant_11_output_0, %/Slice_2_output_0), scope: __main__.DLModel:: # /tmp/ipykernel_482/950586334.py:31:0 %/Reshape_2_output_0 : Float(*, *, *, *, device=cpu) = onnx::Reshape[onnx_name="/Reshape_2"](%/Expand_2_output_0, %/Concat_2_output_0), scope: __main__.DLModel:: # /tmp/ipykernel_482/950586334.py:31:0 %output1 : Float(*, 3, 32, 32, strides=[3072, 1024, 32, 1], requires_grad=1, device=cpu) = onnx::ScatterND[onnx_name="/ScatterND_2"](%/ScatterND_1_output_0, %/Constant_12_output_0, %/Reshape_2_output_0), scope: __main__.DLModel:: # /tmp/ipykernel_482/950586334.py:31:0 return (%output1) |
重写代码 import torch from torch import nn softmax=nn.Softmax(dim=1) a= torch.tensor([[2,2,5],[2,3,5]],dtype=torch.float32) # 将原来微小的差距变大了,但并不影响最终到业务的转换 softmax(a) tensor([[0.0453, 0.0453, 0.9094], [0.0420, 0.1142, 0.8438]]) batch_size = 2 a1=torch.exp(a) a2=a1.sum() a=a1/a2 a=a*batch_size a tensor([[0.0436, 0.0436, 0.8754], [0.0436, 0.1185, 0.8754]]) 逻辑说明 pytorch 转onnx时,不支持softmax,解决办法就是上面的代码 sum求和是整个批次的和,因为转Onnx时不支持指定维度 这导致最终结果多除一个批次 折中办法是为最终结果再乘一个批次 效果 训练时与原来有些出入, 从局部看,部分行少的部分是多到别的行上去了 预测时,通常预测的是一行,批次为1,除以1或乘以1结果无区别, 其结果概率和为1,与使用softmax完全一致 另外一个比较笨的方法是,知道batch=32 就写 a1[0] = a1[0]/a1[0].sum() a1[1] = a1[1]/a1[1].sum() ... a1[31] = a1[31]/a1[31].sum() 也不用循环,就这么写32行,应该也是可以的 这么写是符合了原来的逻辑,但一定效果会好吗?还需要验证 |
|
|
|
Get Started with ORT for Java DOC https://onnxruntime.ai/docs/get-started/with-java.html API https://onnxruntime.ai/docs/api/ https://onnxruntime.ai/docs/api/java/index.html https://sider.ai/share/b19ad768ed777cfc59fa5a26923494c6 实验环境 os:ubantu maven:国内镜像 jdk:1.8 maven依赖 <dependency> <groupId>com.microsoft.onnxruntime</groupId> <artifactId>onnxruntime</artifactId> <version>1.15.0</version> </dependency> Java代码 package org.example; import ai.onnxruntime.*; import ai.onnxruntime.OrtSession.Result; import java.util.Collections; /** * onnx demo */ public class App { public static void main( String[] args ) throws OrtException { OrtEnvironment env = OrtEnvironment.getEnvironment(); float[][] sourceArray = new float[1][1350]; OnnxTensor tensorFromArray = OnnxTensor.createTensor(env,sourceArray); String onnx_path = "/opt/tpf/aiwks/code/aisty/jupyter/work/model/SFModel1_1.onnx"; OrtSession session = env.createSession(onnx_path, new OrtSession.SessionOptions()); try (Result results = session.run(Collections.singletonMap("input", tensorFromArray))) { OnnxValue value = results.get(0); //要转换为什么类型,取决于模型输出的shape //这里模型输出的是一个浮点列表,因此转换为1维float数组 float[] res = (float[])(value.getValue()); System.out.println(res[0]); //交易判定为正常的概率 System.out.println(res[1]); //交易判定为欺诈的概率 } System.out.println( "----onnx demo over!---" ); } } 输出 0.52192116 0.47807884 ----onnx demo over!--- Java代码说明 Onnx模型由pytroch的神经网络书写, 输入是二维数据,类似于csv中一行行数据 一行是一个样本,共1350行,行数就是批次 模型的输入是二维数表, 输出是一维数表,共两个值,第1位代表正常的概率,第2位代表欺诈的概率 session.run中指定的input就是当初输出onnx时指定的名称 |
实验环境 os:ubantu maven:阿里镜像 jdk:21 maven依赖 <dependency> <groupId>com.microsoft.onnxruntime</groupId> <artifactId>onnxruntime</artifactId> <version>1.20.0</version> </dependency> java代码 package com.example.onnx; import ai.onnxruntime.*; import ai.onnxruntime.OrtSession.Result; import java.util.Collections; public class TestOnnx { public static void main(String[] args) throws OrtException { OrtEnvironment env = OrtEnvironment.getEnvironment(); String onnx_path = "/home/xt/wks/spring/demo/src/main/resources/model_knn2.onnx"; OrtSession session = env.createSession(onnx_path, new OrtSession.SessionOptions()); float[][] target = new float[1][3]; target[0][0] = 0.3745f; target[0][1] = 0.9507f; target[0][2] = 0.7320f; float[][] hisArray = new float[2][3]; /* [[0.5987, 0.1560, 0.1560], [0.0581, 0.8662, 0.6011]] * */ hisArray[0][0] = 0.5987f; hisArray[0][1] = 0.1560f; hisArray[0][2] = 0.1560f; hisArray[1][0] = 0.0581f; hisArray[1][1] = 0.8662f; hisArray[1][2] = 0.6011f; float[][] sourceArray = new float[3][3]; /*数据格式说明 [[0.3745, 0.9507, 0.7320], [0.5987, 0.1560, 0.1560], [0.0581, 0.8662, 0.6011]] 第1行为要对比的目标数据,之后两行为历史数据 * */ sourceArray[0][0] = target[0][0]; sourceArray[0][1] = target[0][1]; sourceArray[0][2] = target[0][2]; sourceArray[1][0] = hisArray[0][0]; sourceArray[1][1] = hisArray[0][1]; sourceArray[1][2] = hisArray[0][2]; sourceArray[2][0] = hisArray[1][0]; sourceArray[2][1] = hisArray[1][1]; sourceArray[2][2] = hisArray[1][2]; OnnxTensor tensorFromArray = OnnxTensor.createTensor(env,sourceArray); String inputName = session.getInputNames().iterator().next(); try (Result results = session.run(Collections.singletonMap(inputName, tensorFromArray))) { OnnxValue value = results.get(0); System.out.println(value.toString()); //要转换为什么类型,取决于模型输出类型 System.out.println(value.getValue()); long[] res = (long[])(value.getValue()); int index = (int)res[0]; float[] corr = hisArray[index]; System.out.print("["); for (float v : corr) { System.out.printf("%.4f ", v); } System.out.print("]"); // float[] res = (float[])(value.getValue()); // System.out.println(res[0]); // System.out.println(res[1]); } System.out.println( "----onnx demo over!---" ); } } 与示例1的主要区别在于 - 名称可以动态取,不必固定于Input - 返回结果如果是Int类型,则使用Long - res包含多少个数值,取决于模型输出 |
|
|
|
pip install lightgbm onnx onnxruntime skl2onnx onnxmltools import numpy as np from lightgbm import LGBMClassifier from sklearn.datasets import load_iris import onnxruntime as rt from skl2onnx import update_registered_converter, to_onnx from skl2onnx.common.shape_calculator import calculate_linear_classifier_output_shapes from onnxmltools.convert.lightgbm.operator_converters.LightGbm import convert_lightgbm # 加载数据 data = load_iris() X = data.data[:, :2] y = data.target # 训练模型 model = LGBMClassifier(n_estimators=3) model.fit(X, y) # 注册LightGBM模型的转换器 update_registered_converter( LGBMClassifier, "LightGbmLGBMClassifier", calculate_linear_classifier_output_shapes, convert_lightgbm, options={"nocl": [True, False], "zipmap": [True, False, "columns"]} ) # 转换模型为ONNX格式 model_onnx = to_onnx(model, X[:1].astype(np.float32), target_opset={"": 12, "ai.onnx.ml": 2}) # 保存ONNX模型 with open("model.onnx", "wb") as f: f.write(model_onnx.SerializeToString()) # 加载ONNX模型 sess = rt.InferenceSession("model.onnx", providers=["CPUExecutionProvider"]) # 查看模型的输入名称 input_name = sess.get_inputs()[0].name print("Input name:", input_name) # 进行预测 pred_onx = sess.run(None, {input_name: X[:5].astype(np.float32)}) print("predict", pred_onx[0]) print("predict_proba", pred_onx[1][:1]) Input name: X predict [0 0 0 0 0] predict_proba [{0: 0.519957959651947, 1: 0.2454928159713745, 2: 0.23454922437667847}] |
from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split import lightgbm as lgb from onnxconverter_common.data_types import FloatTensorType import onnxmltools iris = load_iris() X, y = iris.data, iris.target X_train, X_test, y_train, y_test = train_test_split(X, y) train_data = lgb.Dataset(X_train, label=y_train) test_data = lgb.Dataset(X_test, label=y_test) param = {'num_leaves': 3, 'objective': 'multiclass','num_class':3} param['metric'] = ['multi_logloss','multi_error'] num_round = 10 bst = lgb.train(param, train_data, num_round, valid_sets=[test_data]) bst.save_model('model.txt') with open('model.txt') as fd: for line in fd: if line.startswith("max_feature_idx"): max_feature_idx = int(line.split("=")[1]) lgb_regression_model = lgb.Booster(model_file='model.txt') initial_type = [("float_input", FloatTensorType([None, max_feature_idx+1]))] onnx_model = onnxmltools.convert_lightgbm(lgb_regression_model, initial_types = initial_type, target_opset=9 ) onnxmltools.utils.save_model(onnx_model, 'model.onnx') import onnxruntime as rt import numpy sess = rt.InferenceSession("model.onnx") input_name = sess.get_inputs()[0].name label_name = sess.get_outputs()[0].name pred_onx = sess.run([label_name], {input_name: X_test.astype(numpy.float32)})[0] print(pred_onx) [1 1 2 0 2 1 0 2 0 0 1 0 1 2 0 0 1 2 2 0 2 2 1 2 2 2 2 0 0 2 2 0 0 0 0 1 2 1] 2025-02-13 17:32:25.156722713 [W:onnxruntime:, execution_frame.cc:870 VerifyOutputSizes] Expected shape from model of {1} does not match actual shape of {38} for output label import numpy as np # 加载ONNX模型 sess = rt.InferenceSession("model.onnx", providers=["CPUExecutionProvider"]) # 查看模型的输入名称 input_name = sess.get_inputs()[0].name print("Input name:", input_name) # 进行预测 pred_onx = sess.run(None, {input_name: X[:5].astype(np.float32)}) print("predict", pred_onx[0]) print("predict_proba", pred_onx[1][:1]) Input name: float_input predict [0 0 0 0 0] predict_proba [{0: 0.7972142696380615, 1: 0.10541584342718124, 2: 0.09736990183591843}] 2025-02-13 17:32:25.171188105 [W:onnxruntime:, execution_frame.cc:870 VerifyOutputSizes] Expected shape from model of {1} does not match actual shape of {5} for output label |
|
|
|
from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsClassifier # 加载数据集 iris = load_iris() X, y = iris.data, iris.target # 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 训练KNN模型 knn = KNeighborsClassifier(n_neighbors=3) knn.fit(X_train, y_train) X_train.shape #(120, 4) knn.predict_proba(X_test).shape #(30, 3) |
pip install scikit-learn onnx skl2onnx from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorType #当输入数据维度可变时,需特别声明输入数据的维度: variable_dim=X_train.shape[1] initial_types = [('input', FloatTensorType([None, variable_dim]))] initial_types #[('input', FloatTensorType(shape=[None, 4]))] # 将模型转换为ONNX格式 onnx_model = convert_sklearn(knn, initial_types=initial_types) # 保存ONNX模型 import onnx onnx.save_model(onnx_model, 'knn_model.onnx') |
import onnxruntime as ort # 加载ONNX模型 ort_session = ort.InferenceSession('knn_model.onnx',providers=["CPUExecutionProvider"]) # 获取输入和输出的名称 input_name = ort_session.get_inputs()[0].name output_name = ort_session.get_outputs()[0].name # 准备输入数据 # X_test_onnx = X_test.astype(np.float32).tolist() # ONNX Runtime期望输入为float32类型的列表 X_test_onnx = X_test.astype(np.float32) # ONNX Runtime期望输入为float32类型的列表 # 进行推理 # result = ort_session.run([output_name], {input_name: X_test_onnx}) #第1个参数为None才会输出概率 result = ort_session.run(None, {input_name: X_test_onnx}) #分类 # print(result[0]) # 概率 print(result[1][0]) #{0: 0.0, 1: 1.0, 2: 0.0} 特别注意以下两行 # 这种方式不会输出概率,输出结果只有分类 # result = ort_session.run([output_name], {input_name: X_test_onnx}) #第1个参数为None才会输出概率 result = ort_session.run(None, {input_name: X_test_onnx}) y_test_pred2[:7] #array([1, 0, 2, 1, 1, 0, 1], dtype=int64) y_test[:7] # array([1, 0, 2, 1, 1, 0, 1]) mse = ((y_test_pred2 - y_test)**2).mean() mse #0.0 |
from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsClassifier # 加载数据集 iris = load_iris() X, y = iris.data, iris.target # 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 训练KNN模型 knn = KNeighborsClassifier(n_neighbors=3) knn.fit(X_train, y_train) from tpf.mlib import save_onnx_ml,run_onnx_ml save_onnx_ml(knn,in_features=X_train.shape[1],file_path="knn1.onnx") y_pred = run_onnx_ml(file_path="knn1.onnx",X=X_test,proba=False) ((y_pred-y_test)**2).sum() #0 y_pred[:7] array([1, 0, 2, 1, 1, 0, 1], dtype=int64) y_test[:7] array([1, 0, 2, 1, 1, 0, 1]) |
|