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/