【问题标题】:ONNX Runtime C# does not remember the state of LSTM networksONNX Runtime C# 不记得 LSTM 网络的状态
【发布时间】:2020-05-27 12:09:53
【问题描述】:

我将经过训练的 LSTM 神经网络从 this example 从 Matlab 导出到 ONNX。然后我尝试用ONNX Runtime C# 运行这个网络。但是,看起来我做错了什么,网络不记得上一步的状态。

网络应使用以下输出响应输入序列:

  • 输入:[0.258881980200294];输出:[0.311363101005554]

  • 输入:[1.354147904050896];输出:[1.241550326347351]

  • 输入:[ 0.258881980200294, 1.354147904050896 ];输出:[ 0.311363101005554, 1.391810059547424 ]

前两个示例是仅包含一个元素的序列。最后一个例子是两个元素的序列。这些输出在 Matlab 中计算。我在 Matlab 中用每个新序列执行网络之间重置了网络。

然后我尝试使用 ONNX 运行时运行相同的网络。这是我的 C# 代码:

using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using System;
using System.Collections;
using System.Collections.Generic;

namespace OnnxTest
{
    public sealed class OnnxRuntimeTest
    {
        public OnnxRuntimeTest(ILogger logger)
        {
            this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        private const string modelPath = @"E:\Documents\MATLAB\NeuralNetworkExport\onnx_lstm_medic.onnx";
        private readonly ILogger logger;

        public void Run()
        {
            using (var session = new InferenceSession(modelPath))
            {
                // Input values from the example above:
                var input1 = GenerateInputValue(0.258881980200294f);
                var input2 = GenerateInputValue(1.35414790405090f);

                // I create a container to push the first value:
                var container = new List<NamedOnnxValue>() { input1 };

                //Run the inference
                using (var results = session.Run(container))  
                {
                    // dump the results
                    foreach (var r in results)
                    {
                        logger.Log(string.Format("Output for {0}", r.Name));
                        logger.Log(r.AsTensor<float>().GetArrayString());

                        // Outputs 0,3113631 - as expected
                    }
                }


                // The same code to push the second value:
                var container2 = new List<NamedOnnxValue>() { input2 };

                using (var results = session.Run(container2)) 
                {
                    // dump the results
                    foreach (var r in results)
                    {
                        logger.Log(string.Format("Output for {0}", r.Name));
                        logger.Log(r.AsTensor<float>().GetArrayString());

                        // Outputs 1,24155 - as though this is the first input value
                    }
                }

            }
        }

        private NamedOnnxValue GenerateInputValue(float inputValue)
        {
            float[] inputData = new float[] { inputValue };
            int[] dimensions = new int[] { 1, 1, 1 };
            var tensor = new DenseTensor<float>(inputData, dimensions);
            return NamedOnnxValue.CreateFromTensor("sequenceinput", tensor);
        }

如您所见,第二次会话运行结果为 1,24155,而不是预期值 (1.391810059547424),就好像网络仍处于初始状态一样。看起来我没有保存 LSTM 网络的状态,但我在文档中找不到如何执行此操作。

那么,有谁知道如何让 LSTM 保持其状态?

【问题讨论】:

    标签: c# neural-network lstm onnx onnxruntime


    【解决方案1】:

    一种方法是按顺序创建您的输入,然后 LSTM 模型将它们一个一个地输入,在单个推理会话中累积其内部状态。例如,这里我有一个 LSTM,它接受维度 [batch_size, sequence_size, input_size] 的输入,其中 1 是我在本例中使用的输入大小。构造函数中没有定义批次和序列大小,但 ONNX 在跟踪模型时会了解它们。

    def __init__(self, config):
        super().__init__()
        self.output_size = config['output_size']
        self.n_layers = config['num_lstm_layers']
        self.hidden_dim = config['lstm_hidden_dim']
        
        # LSTM layers
        self.lstm = nn.LSTM(config['input_size'], 
                            self.hidden_dim, 
                            self.n_layers, 
                            dropout=config['dropout_prob'], 
                            batch_first=True)
        
        # dropout layer
        self.dropout = nn.Dropout(config['dropout_prob'])        
        # linear layer
        self.fc = nn.Linear(self.hidden_dim, config['output_size']) 
    

    这是一个示例,其中 ONNX 模型设置为使用 dynamic_axes 选项接受可变批量大小,但也可以指定其他维度。

    with torch.no_grad():
        net.eval()
        torch_chunk = torch.tensor(chunk, dtype=torch.float32).unsqueeze(1).unsqueeze(0)
        h = net.init_hidden(1, 'cpu')
        h = tuple([each.data for each in h])    
        torch.onnx.export(net,
                          (torch_chunk, h),
                          'traced_network.onnx',
                          dynamic_axes={'input': [0], 'h0': [1], 'c0': [1], 'hn': [1], 'cn': [1], 'output': [0]},
                          input_names=['input', 'h0', 'c0'],
                          output_names=['output', 'hn', 'cn'])
        onnx_model = onnx.load('traced_network.onnx')
        onnx.checker.check_model(onnx_model)
    

    在 C# 层中,我需要创建具有正确(动态)大小的隐藏层。

        public float[] Run(Tensor<float> input) 
        {
            // package the inputs into named values to coincide with the model that
            // was traced and created in python.
            // the hidden dimensions depend on how big the batch is.
            var batch_size = input.Dimensions[0];
            var onnx_input = new List<NamedOnnxValue>
            {
                NamedOnnxValue.CreateFromTensor<float>("input", input),
                NamedOnnxValue.CreateFromTensor<float>("h0", GetHiddenTensor(batch_size)),
                NamedOnnxValue.CreateFromTensor<float>("c0", GetHiddenTensor(batch_size)),
            };
    
            var results = _inference_session.Run(onnx_input);
            // the output is the inferred values, one for each input,
            // and the hidden vectors, which we don't need for the inference
            return results.First().AsEnumerable<float>().ToArray();
        }
    

    【讨论】:

      猜你喜欢
      • 2016-11-09
      • 2019-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-23
      • 1970-01-01
      • 2021-02-10
      相关资源
      最近更新 更多