ぱたへね

はてなダイアリーはrustの色分けができないのでこっちに来た

Google Cloud AutoMLとAzure Custom Visionを比較してみた(識別編)

始めに(大事な話)

始めに言いたいことは、AutoMLは会社のお金で回しましょう。大事な事なので二回言います。AutoMLは会社のお金で回しましょう。

今回初めてAzure Custom Visionを使ってみたのですが、先にやっていた同僚から識別モデルなら無料枠に収まりますよという言葉を信じてやってみたところ、なんとAzure Custom Visionは2 プロジェクトまでは無料枠が使えるのですが3プロジェクト目からは無料枠を使えません。つまり、無料枠(金額)が残っていても請求が来ます。とりあえず試すとき4プロジェクト同時に動かした僕が悪いです。はい。

詳しくはこちらで確認してください。

azure.microsoft.com

というわけで、うっかり自腹で回してしまったのでブログのネタにしようと思います。

Google Cloud AutoMLとAzure Custom Visionとは

説明はいろいろあると思うので省略します。Azure Custom Visionは、転移学習ベースになっていて50枚程度の少ない枚数でも良い結果になると聞いています。

こちらの良くある質問より。

azure.microsoft.com

Custom Vision サービスは、画像間の主な違いをすばやく認識するように最適化されているので、少量のデータを含むモデルでプロトタイプの作成を始めることができます。ラベルあたり 50 個の画像から開始することをお勧めします。問題の複雑さと必要な精度に応じて、最終的なモデルに数百または数千個のサンプルが必要になる場合があります。

やったこと

両者のAutoML技術を使って、エッジ向けに出力されるモデルの比較を行いました。比較のためにデータは同じにしました。 Google Cloud AutoMLの方は、エッジ向け、速度優先、残りはデフォルトです。Azure Computer Visionは、General (compact)の設定にして、tfliteでexportしています。

f:id:natsutan:20200318213329p:plain:w300

学習時間はGoogle Cloud AutoMLはデフォルトのまま、Azure Custom VisionはMAX二時間の設定にしました。これはGoogle Cloud AutoMLがだいたい二時間くらいで終わるのでそれに合わせています。

エッジに持っていた時の計算量の違い等は次に調べたいです。

使ったデータ

Kaglleのdog vs catのデータを使いました。

www.kaggle.com

学習(train)データとして100枚、200枚、1000枚、2000枚の4パターンを試しました。犬、猫同じ数です。100枚は少ないのは十分承知ですがAzure Custom Visionが各クラス50枚でもいけると言っているのでお手並み拝見です。trainとvalidationの分割は特に指定せずデフォルトのままです。

testデータとして別途1000枚(犬500枚、猫500枚)を用意しました。

学習結果 その1

Google Cloud AutoML、Azure Custom Visionのレポートの値です。

Google Cloud AutoML

枚数 precision recall
100 90.0 90.0
200 85.0 85.0
1000 93.0 93.0
2000 97.5 97.5

Azure Custom Vision

枚数 precision recall
100 95.0 95.0
200 100.0 100.0
1000 97.0 97.0
2000 99.0 99.0

学習結果 その2

僕たちが知りたいのは汎化能力なので、testデータ1000枚でも調査しました。Google Cloud AutoML、Azure Custom Visionで、学習データもテストデータも同じデータで数字出しています。計算は単純に精度(accuracy)を出しています。データが千枚なので、正解数/1000です。

枚数 Google Cloud AutoML Azure Custom Vision
100 0.777 0.918
200 0.900 0.942
1000 0.947 0.914
2000 0.947 0.952

これはちょっと面白い結果になっています。Azure Custom Visionはデータ数が少ないときは、Google Cloud AutoMLよりも精度が高く、データ数が増えてくると、だいたい同じような結果になります。これだけで判断するのは難しいですが、やはり各クラス1000枚くらいあった方がよいんじゃないでしょうか。

あと学習結果その1と比べてみるとわかりますが、Google Cloud AutoML、Azure Custom Visionどちらを使うにしても、訓練データ(バリデーションデータ)への過学習は避けられないと思います。特にAzure Custom Visionは99%, 100%とレポートされますが、実際はもう少し現実的な値におさまりそうです。

今回は自然画像が多くなかなか100%に近づけるのは難しいと思いますが、背景を揃えたりできるとGoogle Cloud AutoML、Azure Custom Visionで差がでてくる可能性もあります。もう少しいろんなデータセットで試してみたいところです。

例えば少ない学習データで、この画像で猫はきつい気がします。

f:id:natsutan:20200318213043j:plain:w200

Azure Custom Visionが1000枚で一度精度が下がるのも、もう少し条件振って検証したいです。(お金がかかるのでやりません)

実装上の注意

推論エンジンはQumicoのサンプルを参考に、どちらもtf.lite.Interpreterを使いました。

github.com

Google Cloud AutoML、Azure Custom Visionで変更したのは2つです。

入力フォーマット

Google Cloud AutoMLは画像をuint8で入力しますが、Azure Cumputer Visionはfloat32で入力します。

ラベル

Azure Custom Visionは4回やって猫が0、犬が1でしたが、Google Cloud AutoMLは犬、猫がランダムにくるので毎回確認する必要がありました。

まとめ

AutoMLは会社のお金で回そう。

推論用ソースコード(参考)

ダウンロードしたtfliteを動かしたときのサンプルコードです。参考になるかも。

import numpy as np
import os
import sys
import tensorflow as tf
from PIL import Image

WIDTH = 224
HEIGHT = 224

def infer(interpreter, image_path):

    image = Image.open(image_path)
    resized_image = image.resize((WIDTH, HEIGHT), Image.BICUBIC)

    image_data = np.array(resized_image, dtype='uint8')
    inputs = np.ascontiguousarray(np.expand_dims(image_data, axis=0))

    interpreter.set_tensor(interpreter.get_input_details()[0]["index"], inputs)
    interpreter.invoke()
    predictions = interpreter.get_tensor(interpreter.get_output_details()[0]["index"])
    np.set_printoptions(threshold=sys.maxsize)
    label_index = np.argmax(predictions)
    return label_index


def calc_accuracy(model_path, img_dir, log_name, n=1000, cat=0):
    cat_num = n // 2
    dog_num = n - cat_num

    interpreter = tf.lite.Interpreter(model_path=str(model_path))
    interpreter.allocate_tensors()

    err = 0
    with open(log_name, 'w') as fp:
        for i in range(cat_num):
            fname = "cat.%4d.jpg" % (i + 1000)
            image_path = os.path.join(img_dir, 'cats', fname)
            result = infer(interpreter, image_path)
            if result != cat:
                fp.write("ERROR:%s\n" % fname)
                err += 1

        for i in range(dog_num):
            fname = "dog.%4d.jpg" % (i + 1000)
            image_path = os.path.join(img_dir, 'dogs', fname)
            result = infer(interpreter, image_path)
            if result == cat:
                fp.write("ERROR:%s\n" % fname)
                err += 1

        acc = (n - err) / n
        fp.write("ACC:%f\n" % acc)

    print(acc)
    return acc


if __name__ == "__main__":

    image_root = 'D:/data/dog_and_cat_small/dog_and_cat_small/dog_and_cat_small/validation/'
    model_50 = 'C:/home/myproj/automl_azure_gcp/gcp/50/50_model-export_icn_tflite-dog_cat_50_20200316013707-2020-03-16T06_53_44.875Z_model.tflite'
    model_100 = 'C:/home/myproj/automl_azure_gcp/gcp/100/100_model-export_icn_tflite-dog_cat_100_20200316013837-2020-03-16T06_55_15.299Z_model.tflite'
    model_500 = 'C:/home/myproj/automl_azure_gcp/gcp/500/500_model-export_icn_tflite-dog_cat_500_20200316014602-2020-03-16T06_56_26.836Z_model.tflite'
    model_1000 = 'C:/home/myproj/automl_azure_gcp/gcp/1000/1000_model-export_icn_tflite-dog_cat_1000_20200316020838-2020-03-16T06_57_48.485Z_model.tflite'
    models = [model_50, model_100, model_500, model_1000]
    logs = ['log/gcp_50.log', 'log/gcp_100.log', 'log/gcp_500.log', 'log/gcp_1000.log']
    cat_numbers = [0, 1, 1, 0]

    for model, log, cat in zip(models, logs, cat_numbers):
        calc_accuracy(model, image_root, log, n=1000, cat=cat)

    print("finish")