0
0

More than 1 year has passed since last update.

CoreML変換で入力を正規化する方法まとめ(inputにmean stdを使う)

Posted at

CoreMLの入力正規化ってどうすればいいの?

一般的に機械学習モデルで画像入力を使う場合、入力のピクセル値は小さく正規化されます。
具体的には、元々画像のピクセル値は RGBそれぞれ
0~255
で表現されますが、機械学習モデルの入力として使うときは
0~1

-1~1
などの小さな値に変換されます。

0~1 であればpythonでは

# original python code
input = img / 255

-1~1 であればpythonでは

# original python code
input = img / 127.5 -1

のように計算されています。

また、mean(平均)を引いてstd(標準偏差)で割るという正規化されることもあります。
以下のようなImageNetのmean(平均)とstd(標準偏差)がよく用いられます。

# original python code
mean = [0.406, 0.456, 0.485] #BGR
std = [0.225, 0.224, 0.229]

input = img.astype(np.float32) / 255.
input -= mean
input /= std

これらの入力をCoreMLで表現するにはどうすればいいのでしょうか?

変換時に設定する

CoreMLToolsの変換メソッドにはscaleとbiasという引数があり、そこで正規化を設定できます。
これにより上記のpythonによる正規化計算と同等の計算をRGBそれぞれのピクセル値に行えます。

0~1 正規化の場合

import coremltools as ct
mlmodel = ct.convert(traced_model, inputs=[ct.ImageType(shape=input.shape,scale=1/255)])

-1~1 正規化の場合

import coremltools as ct
mlmodel = ct.convert(traced_model, inputs=[ct.ImageType(shape=input.shape,scale=1/127.5,bias=[-1,-1,-1])])

平均と標準偏差を用いた正規化の場合(ImageNet)

import coremltools as ct
mlmodel = ct.convert(traced_model, inputs=[ct.ImageType(shape=input.shape,bias=[-0.485/0.229,-0.456/0.224,-0.406/0.225],scale=1.0/255.0/0.226)])

CoreMLToolsのImageTypeを使う場合は上記の設定でCoreMLモデルが正規化を行なってくれます。

TensorType入力をSwiftで正規化する場合

何らかの事情でCoreMLのImageType入力を使わずTensorType入力に正規化を行いたい場合、
変換時には正規化の前処理を設定せず、SwiftでMLMultiArrayの操作を行います。

UIImageをMLMultiArrayに変換する以下のコードスニペット(僕著)でCoreMLToolsと似た感じで前処理を設定できます。

0~1 正規化の場合

let preProcessedMlMultiArray:MLMultiArray = uiImage.mlMultiArray(scale: 1/255)

-1~1 正規化の場合

let preProcessedMlMultiArray:MLMultiArray = uiImage.mlMultiArray(scale: 1/127.5, rBias: -1, gBias: -1, bBias: -1)

平均と標準偏差を用いた正規化の場合(ImageNet)

let x_1 = image_masked?.mlMultiArray(scale: 1/255.0/0.226,rBias: -0.485/0.229,gBias: -0.456/0.224,bBias: -0.406/0.225)

出力を非正規化する

もしも元のpythonコードが出力を非正規化する(正規化した小さな値から0~255に戻す)以下のような処理を行っている場合

# original python code
output = model_output * 255 # 0~1 to 0~255
# output = (model_output+1) * 127.5 # -1~1 to 0~255

CoreMLの出力も非正規化する必要があります。
(平均と標準偏差を用いた入力正規化の場合も、出力は0~1か-1~1になっていることが多いと僕は思います)

変換時に非正規化を追加する

activation_params = [255,0] # 0~1から0~255に非正規化している場合
activation_params = [127.5,127.5] # -1~1から0~255に非正規化している場合

import coremltools as ct
from coremltools.proto import FeatureTypes_pb2 as ft
from coremltools.models.utils import save_spec

mlmodel = ct.models.MLModel('your_model.mlmodel')
spec = mlmodel.get_spec()
original_model_output_name = spec.description.output.name
builder = ct.models.neural_network.NeuralNetworkBuilder(spec=spec)

builder.add_squeeze(name="squeeze", input_name=original_model_output_name, output_name="squeeze_out", axes = None, squeeze_all = True)
builder.add_activation(name="activation",non_linearity="LINEAR",input_name="squeeze_out",output_name="activation_out",params=activation_params)

builder.spec.description.output.pop()
builder.spec.description.output.add()
output = builder.spec.description.output[0]
output.name = "activation_out"
output.type.imageType.colorSpace = ft.ImageFeatureType.ColorSpace.Value('RGB')
output.type.imageType.width = {your_model_output_width} 
output.type.imageType.height = {your_model_output_height} 

save_spec(builder.spec, 'yourModel.mlmodel')

CoreMLHelpersで画像出力を得る際に非正規化する

あるいは、MLMultiArrayの出力のままでCoreMLHelpersを用いてCGImageなどに変える際に非正規化することもできます。

let out = try model.prediction(from: input)
let outArray = out.featureValue(for: "var_2132")?.multiArrayValue
let outImage = outArray?.cgImage(min: 0,max: 1, axes: (1,2,3)) // 0~1 to 0~255
// let outImage = outArray?.cgImage(min: -1,max: 1, axes: (1,2,3)) // -1~1 to 0~255

Happy Machine Learning!

🐣


フリーランスエンジニアです。
お仕事のご相談こちらまで
rockyshikoku@gmail.com

機械学習、ARアプリ(Web/iOS)を作っています。
機械学習/AR関連の情報を発信しています。

Twitter
Medium
GitHub

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0