coremltools常用操作

1.建立spec,查看各层信息,保存模型

import coremltools as ct

# 载入模型
model = ct.models.MLModel("yolov5s.mlmodel")

'''
With thespec you can add new layers, remove layers, rename inputs and outputs, 
and change the model in pretty much any way you please.
'''
# 获取spec的三种方式:


# 方式一:这种方式对mlmodel做了深拷贝,任何编辑不会影响原模型
spec = model.get_spec()

# 方式二:如果想直接对原模型编辑使用如下方式
# spec = model._spec

# 方式三:直接加载
# spec = ct.utils.load_spec("YourModel.mlmodel")

# 查看是否加载成功,直接打印输出即可
# 这种方式只会打印处输入和输出相关的信息
# 注意:避免使用print(spec),这样会输出整个模型和参数
# print(spec.description)


# 利用protobuf API更改模型
# 查看类型
# print(type(spec))   # <class 'Model_pb2.Model'>
# 注:_pb2后缀意味着这是Model.proto类型文件 ,所有的“message” objects在spec中有相同的类型
# print(type(spec.description))

# 添加模型描述
spec.description.metadata.shortDescription = "This is my awesome model!"

# 设置为默认值
spec.description.metadata.ClearField("shortDescription")

# 查看模型是否有某种类型的message
# print(spec.description.HasField("metadata"))

# 如果是oneof类型,使用下面的语句查看到底是哪种具体的类型,不同的模型显示结果不同
# print(spec.WhichOneof("Type"))   # neuralNetwork

# print(spec.neuralNetwork.someProperty)

# 查看每一层的命名
# for layer in spec.neuralNetwork.layers:
#     print(layer.name)

# 只查看卷积层的命名
# for layer in spec.neuralNetwork.layers:
#     if layer.HasField("convolution"):
#         print(layer.name)


# 查看某一种类型的层的命名
# for layer in spec.neuralNetwork.layers:
#     if layer.WhichOneof("layer") == "convolution":
#         print(layer.name)


# 查看特定的某一层的信息
layer = spec.neuralNetwork.layers[0]  # 第一层
# print("name", layer.name)
# print("inputs", layer.input)
# print("outputs", layer.output)
# 在这一层再增加一个输入口
layer.input.append("another_input") # 如果此时保存模型,xcode无法加载,因为并未在任何地方定义第二个输入
# print(len(layer.input))
# 删除第二个输入
del layer.input[-1]


# 增加一个层到old_output层后边
new_layer = spec.neuralNetwork.layers.add()
new_layer.name = "My new layer"
new_layer.input.append("old_output")
new_layer.output.append("new_output")

# 添加线性激活层并,设置激活层的参数
new_layer.activation.linear.alpha = 1
print(new_layer)

# 将这一层的的激活函数更改为Relu,方式一:
new_layer.activation.ReLU.SetInParent()
# 将这一层的的激活函数更改为Relu,方式二:
layer.activation.ReLU.MergeFromString(b"")

# 将这一层改为完全不同的类型,如设置为Coreml的全连接层
new_layer.innerProduct.inputChannels = 100

# 查看当前模型有哪些命令可以使用
# print(dir(ct.proto.Model_pb2.Model))

# 删除刚刚创建的层
del spec.neuralNetwork.layers[-1]

# 保存模型方式一:直接保存
ct.utils.save_spec(spec, "YourNewModel.mlmodel")

# 保存模型方式二:如果是model._spec打开的话,用这种方式保存
# model.save("YourNewModel.mlmodel")

# 保存模型方式三:从此前创建的spec保存
new_model = ct.models.MLModel(spec)
new_model.save("YourNewModel.mlmodel")

2.查看各层的详细信息

import coremltools as ct

# 载入模型
spec = ct.utils.load_spec("yolov5s.mlmodel")

# 查看输入和输出的信息
print(spec.description)
print("-------------------")

# 查看输入的类型
# print(spec.description.input[0].type.WhichOneof("Type"))

# 查看输出的shape
# print(spec.description.output[0].type.multiArrayType.shape)


# 查看模型的类型
print(spec.WhichOneof("Type"))    # neuralNetwork

# 查看模型有哪些属性
# where someProperty is the name of the thing you want to look at.
# print(spec.neuralNetwork.someProperty)

# 查看模型的类型
def get_nn(spec):
    if spec.WhichOneof("Type") == "neuralNetwork":
        return spec.neuralNetwork
    elif spec.WhichOneof("Type") == "neuralNetworkClassifier":
        return spec.neuralNetworkClassifier
    elif spec.WhichOneof("Type") == "neuralNetworkRegressor":
        return spec.neuralNetworkRegressor
    else:
        raise ValueError("MLModel does not have a neural network")
nn = get_nn(spec)
# print(nn)
print(type(nn))   # <class 'NeuralNetwork_pb2.NeuralNetwork'>

# from helpers import get_nn
# nn = get_nn(spec)
#
# from coremltools.models.neural_network.builder import _get_nn_spec as get_nn
# nn = get_nn(spec)


# 查看有多少层
# print(len(nn.layers))

# 查看第一层的输入的fields
# print(nn.layers[0].input)
# print(spec.description.input[0].name)

# 对于NeuralNetworkClassifier
# print(spec.description.predictedProbabilitiesName)

# 查看最后一层输出层的命名
# print(nn.layers[-1].output)

# 查看特定的层,由于只能通过数字索引的方式找到对应的层,首先需要拿到每一层的索引值
# 取索引:方式一
for i, layer in enumerate(nn.layers):
    print(i, layer.name)

# 取索引:方式二:通过字典的方式{名称:索引值}
layer_dict = {layer.name:i for i, layer in enumerate(nn.layers)}
print(layer_dict)

# 例如:查看"input.113"这一层的信息
layer = nn.layers[layer_dict["input.113"]]

# 查看这一层的类型
print(layer.WhichOneof("layer"))# convolution

# 如果是卷积层则可以进行下面的操作
print(layer.convolution.outputChannels)
print(layer.convolution.kernelChannels)
print(layer.convolution.kernelSize)
print(layer.convolution.stride)
print(len(layer.convolution.weights.floatValue))
# 如果是半精度类型
print(len(layer.convolution.weights.float16Value))
# 如果是量化过的模型
print(len(layer.convolution.weights.rawValue))

# 以numpy存储w
import numpy as np
cout = layer.convolution.outputChannels
cin = layer.convolution.kernelChannels // layer.convolution.nGroups
kh = layer.convolution.kernelSize[0]
kw = layer.convolution.kernelSize[1]
W = np.array(layer.convolution.weights.floatValue)
W = W.reshape(cout, cin, kh, kw)

# 查看这一层是否有bias
# 方式一:
print(layer.convolution.HasField("bias"))
# 方式二:
print(layer.convolution.hasBias)


# 查看所有层的相关信息
ct.models.neural_network.printer.print_network_spec(spec)

#只查看输入层和输出层的信息
ct.models.neural_network.printer.print_network_spec(spec, True)

3.从零开始创建mlmodel

import coremltools as ct
from coremltools.models import datatypes
from coremltools.models import neural_network

# 指定输入
input_features = [ ("image", datatypes.Array(3, 32, 32)) ]
# 自动计算输出,所以不用指定输出
output_features = [ ("labelProbs", None) ]
# 如果知道了输出也可以填上
# output_features = [ ("labelProbs", datatypes.Array(10)) ]
# 由于 mode="classifier"所以输出是字典,类别对应他的可能性大小
builder = neural_network.NeuralNetworkBuilder(input_features, output_features, mode="classifier")
# print(builder.spec)

# 查看输入和输出信息
# print(builder.spec.description)

# 指明输入的是一张图片
builder.set_pre_processing_parameters(image_input_names=["image"],
                                    is_bgr=False,
                                    red_bias=-125.3,
                                    green_bias=-122.95,
                                    blue_bias=-113.87)

# 查看输入信息
# print(builder.spec.description.input)

# 对于分类模型指定分类信息
cifar10_labels = ["airplane", "automobile", "bird", "cat", "deer",
"dog", "frog", "horse", "ship", "truck"]
builder.set_class_labels(class_labels=cifar10_labels,
                         predicted_feature_name="label",
                         prediction_blob="labelProbs")

# print(builder.spec.description.output)

# 添加第一个卷积层
W, b = get_weights("data/conv1")
# W, b = 0, 0

builder.add_convolution(name="conv1",
                        kernel_channels=3,
                        output_channels=32,
                        height=5,
                        width=5,
                        stride_height=1,
                        stride_width=1,
                        border_mode="valid",
                        groups=1,
                        W=W,
                        b=b,
                        has_bias=True,
                        input_name="image",
                        output_name="conv1_output",
                        padding_top=2,
                        padding_bottom=2,
                        padding_left=2,
                        padding_right=2)

# 添加池化层
builder.add_pooling(name="pool1",
                    height=3,
                    width=3,
                    stride_height=2,
                    stride_width=2,
                    layer_type="MAX",
                    padding_type="INCLUDE_LAST_PIXEL",
                    input_name="conv1_output",
                    output_name="pool1_output")
# 添加激活函数
builder.add_activation( name="fake",
                        non_linearity="LINEAR",
                        input_name="pool1_output",
                        output_name="labelProbs")
# 保存刚刚创建的模型
mlmodel = ct.models.MLModel(builder.spec)
mlmodel.save("temp.mlmodel")

# 删除最后一层
del builder.spec.neuralNetworkClassifier.layers[-1]

# 如果不是分类模型则使用
# builder.spec.neuralNetwork.layers

# 增加第二个激活层
builder.add_activation(name="relu1",
                        non_linearity="RELU",
                        input_name="pool1_output",
                        output_name="relu1_output")

# The output from pool3 is a (64, 4, 4) tensor. Flattened this becomes a vector of 1024 elements.
builder.add_flatten(name="flatten1",
                    mode=0,
                    input_name="pool3_output",
                    output_name="flatten1_output")

# 增加第一个全卷积层
W, b = get_weights("data/ip1")
builder.add_inner_product(name="ip1",
                            W=W,
                            b=b,
                            input_channels=1024,
                            output_channels=64,
                            has_bias=True,
                            input_name="flatten1_output",
                            output_name="ip1_output")
# 增加第二个全卷积层
W, b = get_weights("data/ip2")
builder.add_inner_product(name="ip2",
                            W=W,
                            b=b,
                            input_channels=64,
                            output_channels=10,
                            has_bias=True,
                            input_name="ip1_output",
                            output_name="ip2_output")

# 增加softmax层
builder.add_softmax(name="softmax",
                    input_name="ip2_output",
                    output_name="labelProbs")


# print(builder.spec.neuralNetworkClassifier.layers[-1].name)
# del builder.spec.neuralNetworkClassifier.layers[-1]

mlmodel = ct.models.MLModel(builder.spec)

mlmodel.short_description = "cifar10_quick"
mlmodel.author = "https://github.com/BVLC/caffe/tree/master/examples/cifar10"
mlmodel.license = "https://github.com/BVLC/caffe/blob/master/LICENSE"
mlmodel.input_description["image"] = "The input image"
mlmodel.output_description["labelProbs"] = "The predicted probabilities"
mlmodel.output_description["label"] = "The class with the highest score"
mlmodel.save("CIFAR10.mlmodel")

# 预测图片,目前预测图片只能在macos上进行
import PIL
img = PIL.Image.open("boat.jpg")
img = img.resize((32, 32), PIL.Image.BILINEAR)
prediction = mlmodel.predict({"image": img}, useCPUOnly=True)
print(prediction)

4.查看并更行某些层的信息

import coremltools as ct

# 载入模型
existing_model = ct.utils.load_spec("YourModel.mlmodel")
builder = ct.models.neural_network.NeuralNetworkBuilder(spec=existing_model)

# 查看某些层的信息
builder.inspect_input_features()
builder.inspect_output_features()
builder.inspect_layers(last=3)
builder.inspect_conv_channels("layer_name")
builder.inspect_innerproduct_channels("layer_name")

# 更新某些层
builder.make_updatable(["layer1", ...])
training_features = [ ("input", datatypes.Array(3)),("target", Double) ]
builder.set_training_input(training_features)
builder.set_categorical_cross_entropy_loss(...)
builder.set_mean_squared_error_loss(...)
builder.set_sgd_optimizer(...)
builder.set_adam_optimizer(...)
builder.set_epochs(...)


# 查看相关层的属性
builder.inspect_updatable_layers()
builder.inspect_loss_layers()
builder.inspect_optimizer()

# 修改完成之后进行保存
ct.utils.save_spec(builder.spec, "YourNewModel.mlmodel")

5.在input后面添加缩放层

import coremltools as ct

def get_nn(spec):
    if spec.WhichOneof("Type") == "neuralNetwork":
        return spec.neuralNetwork
    elif spec.WhichOneof("Type") == "neuralNetworkClassifier":
        return spec.neuralNetworkClassifier
    elif spec.WhichOneof("Type") == "neuralNetworkRegressor":
        return spec.neuralNetworkRegressor
    else:
        raise ValueError("MLModel does not have a neural network")


spec = ct.utils.load_spec("test.mlmodel")
nn = get_nn(spec)


# 添加缩放层
###################  每个通道不同的缩放比大小
import copy

# 复制到新的spec
old_layers = copy.deepcopy(nn.layers)
# 删除模型中的层,因为已经复制了一份,原模型已经吧不需要了
del nn.layers[:]

# 在CoreML中,所有的层之间都是通过输入和输出的命名来进行链接
# 由于新的缩放层是在模型的输入和之前的第一层之间,所以需要找到模型的输入层
input_name = old_layers[0].input[0]
print(input_name)
# 设置新的这一缩放层的输出命名
new_layer_output = input_name + "_scaled"

# 设计缩放层
# 增加层
new_layer = nn.layers.add()
# 设置属性
new_layer.name = "scale_layer"
# 设置input可以使他链接到模型的输入层
new_layer.input.append(input_name)
# 设置output可以使他链接到后面的层
new_layer.output.append(new_layer_output)
# 将这一层设置为缩放层比并分配缩放因子, new_layer.scale产生ScaleLayerParams层
new_layer.scale.scale.floatValue.extend([1/0.229, 1/0.224, 1/0.225])
# 设置shape属性
new_layer.scale.shapeScale.extend([3, 1, 1]) #cwh
print(new_layer)
# 已经完成缩放层的设置接下来添加后面的层
nn.layers.extend(old_layers)
# 更改后面层的输入为缩放层的输出
nn.layers[1].input[0] = new_layer_output

# 保存并查看模型是否设置成功
ct.utils.save_spec(spec, "NewModel.mlmodel")
# 查看模型可以用netron也可以使用过下面的语句,如果没有报错则说明添加成功
new_mlmodel = ct.models.MLModel(spec)

6.查看模型类型并转为float16

import coremltools as ct
from coremltools.models.neural_network import quantization_utils
# 定义查看模型类型的函数
def get_nn(spec):
    if spec.WhichOneof("Type") == "neuralNetwork":
        return spec.neuralNetwork
    elif spec.WhichOneof("Type") == "neuralNetworkClassifier":
        return spec.neuralNetworkClassifier
    elif spec.WhichOneof("Type") == "neuralNetworkRegressor":
        return spec.neuralNetworkRegressor
    else:
        raise ValueError("MLModel does not have a neural network")

# 检查权重的类型
def examine_weights(weights):
    if len(weights.floatValue) > 0:
        print("Weights are 32-bit")
    elif len(weights.float16Value) > 0:
        print("Weights are 16-bit")
    elif len(weights.rawValue) > 0:
        print("Weights are quantized or custom layer")
    else:
        print("This layer has no weights")

spec = ct.utils.load_spec("MobileNet.mlmodel")
nn = get_nn(spec)

# import numpy as np
# # 将权重转化为numpy数组
# weights = np.array(nn.layers[0].convolution.weights.floatValue)

# 查看权重类型
examine_weights(nn.layers[i].convolution.weights)
model = ct.models.MLModel("MobileNet.mlmodel")
model_fp16 = quantization_utils.quantize_weights(model, nbits=16)
model_fp16.save("MobileNet_fp16.mlmodel")

7.模型量化

import coremltools as ct
from coremltools.models.neural_network import quantization_utils

# 加载需要量化的模型
model = ct.models.MLModel("MobileNet.mlmodel")
# 进行量化
quant_model = quantization_utils.quantize_weights(model, 4, "linear")
# quant_model = quantization_utils.quantize_weights(model, 8, "kmeans")
# 保存量化后的模型
quant_model.save("MobileNet_q.mlmodel")

# 对量化的模型进行解码(并非是对其进行还原)
model = ct.models.MLModel("MobileNet_q.mlmodel")
dequant_model = quantization_utils.quantize_weights(model,"dequantization")
dequant_model.save("demodel.mlmodel")

8.更改输入和输出为图片类型

import coremltools as ct
import coremltools.proto.FeatureTypes_pb2 as ft
'''
使用情况:当得到mlmodel之后吧,如果输入的类型是“MultiArray” 而不是 “image”但是需要“image”就会很不方便,此时需要更改
'''
# 将输入的类型改为图片
spec = ct.utils.load_spec("YourModel.mlmodel")
input = spec.description.input[0]
input.type.imageType.colorSpace = ft.ImageFeatureType.RGB
input.type.imageType.height = 224
input.type.imageType.width = 224
ct.utils.save_spec(spec, "YourNewModel.mlmodel")
import coremltools as ct
import coremltools.proto.FeatureTypes_pb2 as ft

spec = ct.utils.load_spec("MobileNet.mlmodel")
output = spec.description.output[0]
output.type.imageType.colorSpace = ft.ImageFeatureType.RGB
output.type.imageType.height = 300
output.type.imageType.width = 150

# 如果模型的输出是【1,3,h, w】,则可以删除一个维度
del output.type.multiArrayType.shape[0]
# 也可以更改输入
# spec.description.input[i] .

# 将数组转为图片类型
# helpers.py https://github.com/hollance/coreml-survival-guide/blob/master/Scripts/helpers.py
# from helpers import convert_multiarray_to_image
# convert_multiarray_to_image(spec.description.output[0], is_bgr=False)

9.输入和输出从MultiArray改为浮点型或者整型

import coremltools as ct
import coremltools.proto.FeatureTypes_pb2 as ft

spec = ct.utils.load_spec("yolov5s.mlmodel")
print(spec.description)


def update_multiarray_to_float32(feature):
    if feature.type.HasField("multiArrayType"):
        feature.type.multiArrayType.dataType = ft.ArrayFeatureType.FLOAT32


for feature in spec.description.output:
    update_multiarray_to_float32(feature)

# 注意:
# 如果要输出整型则可以使用 ft.ArrayFeatureType.INT32
# 如果模型使用MultiArray作为输入的类型,但需要更改为浮点型时:
for feature in spec.description.input:
    update_multiarray_to_float32(feature)
# 查看是否更改成功
print(spec.description)

10.更改输出层的shape

import coremltools as ct
import coremltools.proto.FeatureTypes_pb2 as ft

spec = ct.utils.load_spec("test.mlmodel")

# 添加shape
spec.description.output[0].type.multiArrayType.shape.append(91)
spec.description.output[0].type.multiArrayType.shape.append(1917)

# 删除第二个输出的最后一个维度
# del spec.description.output[2].type.multiArrayType.shape[-1]

print(spec.description)

11.插入新的层

import coremltools as ct

def get_nn(spec):
    if spec.WhichOneof("Type") == "neuralNetwork":
        return spec.neuralNetwork
    elif spec.WhichOneof("Type") == "neuralNetworkClassifier":
        return spec.neuralNetworkClassifier
    elif spec.WhichOneof("Type") == "neuralNetworkRegressor":
        return spec.neuralNetworkRegressor
    else:
        raise ValueError("MLModel does not have a neural network")

spec = ct.utils.load_spec("test.mlmodel")
nn = get_nn(spec)
# print(nn.layers[-2].output[0])
# 在末尾添加一层
new_layer = nn.layers.add()
new_layer.name = "test_layer"
new_layer.activation.linear.alpha = 1.0
new_layer.output.append(nn.layers[-2].output[0])

nn.layers[-2].output[0] = "test_layer_input"
new_layer.input.append(nn.layers[-2].output[0])
print(new_layer)


# 在开始添加一层
import copy
old_layers = copy.deepcopy(nn.layers)
del nn.layers[:]
new_layer = nn.layers.add()
new_layer.name = "test_layer"
new_layer.activation.linear.alpha = 1.0
new_layer.input.append(old_layers[0].input[0])
new_layer.output.append("test_layer_output")
nn.layers.extend(old_layers)
nn.layers[1].input[0] = "test_layer_output"


#   在中间插入一层
#  建立字典索引
layer_dict = {layer.name: i for i,layer in enumerate(nn.layers)}
print(layer_dict)
# 找到对应的索引
layer_idx = layer_dict["conv6"]
import copy
# 拷贝后续的层
old_layers = copy.deepcopy(nn.layers[layer_idx:])
# 删除后续的层
del nn.layers[layer_idx:]
# 将后续的层连接到刚刚添加的层上
nn.layers.extend(old_layers)
nn.layers[layer_idx + 1].input[0] = "test_layer_output"

12.删除某一层

import coremltools as ct

spec = ct.utils.load_spec("Inceptionv3.mlmodel")
def get_nn(spec):
    if spec.WhichOneof("Type") == "neuralNetwork":
        return spec.neuralNetwork
    elif spec.WhichOneof("Type") == "neuralNetworkClassifier":
        return spec.neuralNetworkClassifier
    elif spec.WhichOneof("Type") == "neuralNetworkRegressor":
        return spec.neuralNetworkRegressor
    else:
        raise ValueError("MLModel does not have a neural network")
# 找到这层
nn = get_nn(spec)
layer_dict = {layer.name:i for i, layer in enumerate(nn.layers)}
print(layer_dict)
layer_to_delete = nn.layers[layer_dict["convolution2d_36"]]
# 用常规操作替换他
layer_to_delete.activation.linear.alpha = 1
layer_to_delete.activation.linear.beta = 0


# 删除某一层:
# 找到位置
nn = get_nn(spec)
layer_dict = {layer.name:i for i, layer in enumerate(nn.layers)}
print(layer_dict)
layer_index =layer_dict["convolution2d_36"]

# 将后续层的输入链接到
nn.layers[layer_index + 1].input[0] = nn.layers[layer_index].input[0]
# 删除这一层
del nn.layers[layer_index]

参考资料

https://coremltools.readme.io/docs

https://github.com/apple/coremltools/tree/master/mlmodel

https://developer.apple.com/machine-learning/models/

https://github.com/hollance/coreml-survival-guide

https://github.com/apple/coremltools

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值