by Jason Han

如何使用C#调用已训练好的 pb 模型

在深度学习领域,Python占据着举足轻重的位置,然而,由于其脚本编程的特点,在业界并没有较大的优势。相比之下,C++、Java、C#等编程语言则能更好的满足业界的各种需求。本人长期从事 C# 开发,本文的内容是探讨如何在C#编程中调用已训练好的 .pb 深度学习模型。

目前已存在多种 .Net 平台下的深度学习框架,如 Tensorflow.Net、SciSharp、TensorflowSharp等。本文以 TensorflowSharp 为例进行讲解。

1. 安装 TensorflowSharp

可直接在 Nuget 中搜索 TensorflowSharp,选择相应版本下载。下载时注意不同版本对应不同的 .Net Framework。

2. 具体代码

调用模型并预测图片的代码为:

TFGraph graph = new TFGraph();
//重点是下面的这句,把训练好的pb文件给读出来字节,然后导入
string modelPath = Directory.GetCurrentDirectory() + "\\model.pb";
byte[] model = File.ReadAllBytes(modelPath);
graph.Import(model);

var tensor = ImageUtil.CreateTensorFromImageFile(picturePath);

using (var sess = new TFSession(graph))
{
    // 计算类别概率
    var runner = sess.GetRunner();
    runner.AddInput(graph["Mul"][0], tensor);
    var r = runner.Run(graph.Softmax(graph["final_result"][0]));
    var v = (float[,])r.GetValue();
}

其中,ImageUtil 代码如下:

public static class ImageUtil
{
    public static TFTensor CreateTensorFromImageFile(byte[] contents, TFDataType destinationDataType = TFDataType.Float)
    {
        var tensor = TFTensor.CreateString(contents);

        TFOutput input, output;

        // Construct a graph to normalize the image
        using (var graph = ConstructGraphToNormalizeImage(out input, out output, destinationDataType))
        {
            // Execute that graph to normalize this one image
            using (var session = new TFSession(graph))
            {
                var normalized = session.Run(
                inputs: new[] { input },
                inputValues: new[] { tensor },
                outputs: new[] { output });

                return normalized[0];
            }
        }
    }
    // Convert the image in filename to a Tensor suitable as input to the Inception model.
    public static TFTensor CreateTensorFromImageFile(string file, TFDataType destinationDataType = TFDataType.Float)
    {
        //Thread.Sleep(500);
        var contents = File.ReadAllBytes(file);

        // DecodeJpeg uses a scalar String-valued tensor as input.
        var tensor = TFTensor.CreateString(contents);

        TFOutput input, output;

        // Construct a graph to normalize the image
        using (var g = ConstructGraphToNormalizeImage(out input, out output, destinationDataType))
        {
            // Execute that graph to normalize this one image
            using (var sess = new TFSession(g))
            {
            var normalized = sess.Run(
            inputs: new[] { input },
            inputValues: new[] { tensor },
            outputs: new[] { output });

            return normalized[0];
            }
        }
    }
    private static TFGraph ConstructGraphToNormalizeImage(out TFOutput input, out TFOutput output, TFDataType destinationDataType = TFDataType.Float)
    {
        // 以下四个参数,根据模型的输入层确定
        const int W = 299; 
        const int H = 299; 
        const float Mean = 128;
        const float Scale = 128;

        var graph = new TFGraph();
        input = graph.Placeholder(TFDataType.String);

        output = graph.Cast(
        graph.Div(x: graph.Sub(x: graph.ResizeBilinear(images: graph.ExpandDims(input: graph.Cast(graph.DecodeJpeg(contents: input, channels: 3), DstT: TFDataType.Float),
        dim: graph.Const(0, "make_batch")),
        size: graph.Const(new int[] { W, H }, "INPUT_SIZE")),
        y: graph.Const(Mean, "IMAGE_MEAN")),
        y: graph.Const(Scale, "IMAGE_STD")), destinationDataType);

        return graph;
    }

}

3. 注意事项

  1. graph.Import()函数有可能报错,如果是 netstandard相关的错误,可通过将项目的 .Net Framework升至更新版解决。

  2. 对于该行代码:

    runner.AddInput(graph["Mul"][0], tensor);
    var r = runner.Run(graph.Softmax(graph["final_result"][0]));
    

    其中的 Mul 和 final_result 是模型的输入层和输出层的 layername,因此因模型而异。对于所使用的模型,可通过:

    List<TFOperation> op_list = new List<TFOperation>(graph.GetEnumerator());
    

    获取所有层的名称。

  3. 在调试过程中,

  4. 垃圾回收机制导致的报错’CallbackOnCollectedDelegate' ,升级到1.3以后就没有了

参考

This page and its contents are copyright © 2021, Jason Han.