自定义CoreML输出层,用于对多维数组输出求和

请耐心等待。我对CoreML和机器学习还比较陌生。我有一个从Caffe转换而来的CoreML模型,这个模型是基于一篇研究论文的实现,模型类型是CSRNet,主要用于人群计数。经过一番努力,我成功地使用Coremltools在Python中加载了这个MLmodel,并使用Pillow预处理图像并预测输出。结果是一个多维数组(来自密度图),我进一步处理了这个多维数组以得出实际的数值预测结果。

我该如何在模型中添加一个自定义输出层,该层可以接收当前输出并执行以下功能?我已经阅读了许多文章,但仍然感到困惑。(本质上,它是将多维数组中的所有值求和)我希望能够保存这个模型/层,并将其导入到Xcode中,使MLModel的结果为单一数值,而不是多维数组。

这是我目前在Python中用于将模型输出转换为数字的代码:

# 预测输出
output = model.predict({'data': img})
summed_output = sum(output.values())
prediction = np.sum(summed_output)
print("prediction: ", prediction)

完整(简化版)代码:

import coremltools as ct
from PIL import Image
import numpy as np

# 实例化模型(CSRNet)
model = ct.models.MLModel('shanghai_b.mlmodel')

# 调整图像大小的函数
def load_image(path, resize_to=None):
    img = PIL.Image.open(path)
    if resize_to is not None:
        img = img.resize(resize_to, PIL.Image.ANTIALIAS)
    img_np = np.array(img).astype(np.float32)
    return img_np, img

# 选择图像
image = 'IMG_173.jpg'

# 调整图像大小
_, img = load_image(image, resize_to=(900, 675))

# 预测输出
output = model.predict({'data': img})
summed_output = sum(output.values())
prediction = np.sum(summed_output)
print("prediction: ", prediction)

Xcode显示MLModel的输出为:“MultiArray (Double 1 x 168 x 225)”。当我使用Coremltools将其导入Python时,同一个模型的规格描述如下:

<bound method MLModel.predict of input {
  name: "data"
  type {
    imageType {
      width: 224
      height: 224
      colorSpace: RGB
    }
  }
}
output {
  name: "estdmap"
  type {
    multiArrayType {
      dataType: DOUBLE
    }
  }
}>

感谢任何帮助!如果有用,我很乐意分享过程中使用的其他代码。

P.S. 我还将我在Xcode项目中的代码作为参考添加如下。

private func detectImage(_ image: CIImage) {
    guard let model = try? VNCoreMLModel(for: HundredsPredictor().model) else {
        fatalError("Loading to CoreML failed")
    }

    let modelRequest = VNCoreMLRequest(model: model) { (request, error) in
        if error != nil {
            print(error?.localizedDescription ?? "Error")
        } else {
            guard let result = request.results as? [VNObservation] else {fatalError("Error")}

            if #available(iOS 14.0, *) {
                print(result)

                // 输出: [<VNCoreMLFeatureValueObservation: 0x282069da0> 344A87BC-B13E-4195-922E-7381694C91FF requestRevision=1 confidence=1.000000 timeRange={{0/1 = 0.000}, {0/1 = 0.000}} "density_map" - "MultiArray: Double 1 × 168 × 225 array" (1.000000)]

            } else {
                // Fallback on earlier versions
            }
            if let firstResult = result.first {
                print(firstResult)
                // 输出: [<VNCoreMLFeatureValueObservation: 0x282069da0> 344A87BC-B13E-4195-922E-7381694C91FF requestRevision=1 confidence=1.000000 timeRange={{0/1 = 0.000}, {0/1 = 0.000}} "density_map" - "MultiArray : Double 1 × 168 × 225 array" (1.000000)]
            }
        }
    }
    let handler = VNImageRequestHandler(ciImage: image)
    do {
        try handler.perform([modelRequest])
        print(handler)
    }
    catch let error as NSError {
        print(error)
    }
}

更新:解决方案

在Python中:

from helpers import get_nn
# 帮助文件来源于Matthijs Hollemans的GitHub
# url: https://github.com/hollance/coreml-survival-guide/blob/master/Scripts/helpers.py
# 加载原始模型
spec = ct.utils.load_spec("HundredsPredictor.mlmodel")
nn = get_nn(spec)

# 构建新层
new_layer = nn.layers.add()
new_layer.name = "summingLayer"
params = ct.proto.NeuralNetwork_pb2.ReduceLayerParams
new_layer.reduce.mode = params.SUM
new_layer.reduce.axis = params.CHW

# 将新层添加到模型中
new_layer.output.append(nn.layers[-2].output[0])
nn.layers[-2].output[0] = nn.layers[-2].name + "_output"
new_layer.input.append(nn.layers[-2].output[0])
spec.description.output[0].type.multiArrayType.shape[0] = 1

# 保存新模型
ct.models.utils.save_spec(spec, "HundredPredictorSummed.mlmodel")

在Swift中,导入新的更新后的模型后:

private func detectImage(_ image: CIImage) {
    guard let model = try? VNCoreMLModel(for: HundredPredictorSummed().model) else {
        fatalError("Loading to CoreML failed")
    }

    let request = VNCoreMLRequest(model: model) { [weak self] request, error in
        guard let results = request.results as? [VNCoreMLFeatureValueObservation],
              let topResult = results.first else {
            fatalError("Unexpected result type from VNCoreMLRequest")
        }

        DispatchQueue.main.async {
            guard let data = topResult.featureValue.multiArrayValue else { return }
            let ptr = data.dataPointer.assumingMemoryBound(to: Double.self)
            let sum = ptr[0]
            print("SUM: ", sum)

            self?.detectLabel.text = "~\(String(Int(round(sum)))) ppl"
        }
    }

    let handler = VNImageRequestHandler(ciImage: image)

    DispatchQueue.global(qos: .userInteractive).async {
        do {
            try handler.perform([request])
        } catch {
            print(error)
        }
    }
}

回答:

您可以在模型的末尾添加一个ReduceSumLayerParams。您需要手动在Python中完成此操作。如果将其reduceAll参数设置为true,它将计算整个张量的总和。

然而,在我看来,使用现有的模型同样简单,并且在您的Swift代码中获取MLMultiArray的数据指针,并使用vDSP.sum(a)计算总和也是可行的。

Related Posts

使用LSTM在Python中预测未来值

这段代码可以预测指定股票的当前日期之前的值,但不能预测…

如何在gensim的word2vec模型中查找双词组的相似性

我有一个word2vec模型,假设我使用的是googl…

dask_xgboost.predict 可以工作但无法显示 – 数据必须是一维的

我试图使用 XGBoost 创建模型。 看起来我成功地…

ML Tuning – Cross Validation in Spark

我在https://spark.apache.org/…

如何在React JS中使用fetch从REST API获取预测

我正在开发一个应用程序,其中Flask REST AP…

如何分析ML.NET中多类分类预测得分数组?

我在ML.NET中创建了一个多类分类项目。该项目可以对…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注