[Amazon SageMaker] 30種類の商品を分類するモデルで誤検知ゼロを目指してみました

2020.07.01

1 はじめに

CX事業本部の平内(SIN)です。

Amazon SageMaker(以下、SageMaker)のイメージ分類(組み込みアルゴリズム)で30種類の商品を分類するモデルを作成しました。

イメージ分類で、ある程度の精度を出すのは、比較的簡単ですが、誤りの少ない、精度の高いものとなると、それなりに苦労します。

今回は、贅沢にも誤検知ゼロを目指してみました。

下記は、最終的に出来上がったモデルで判定している様子です。30種類の商品を間違わずに判定できている様子を確認できます。(注:状況によっては、誤判定が発生する事もあると思います)

2 データ作成

(1) 動画撮影

元となるデータは、商品を回転台に乗せ、照明を変化させながら3回転ほどWebカメラで撮影しています。30種類の撮影時間は、小1時間程度です。


参考:[Amazon SageMaker] 回転台を使って撮影した動画で、Amazon SageMaker Ground Truth形式のデータセット(Image Classification)を作ってみました

動画は、800*600 24fpsで撮影されており、各データのサイズは、20MByte程度になっています。

(2) 解像度の変換

動画は、解像度を320*240に変換しました。

変換した理由は、以前、色々確認してみたところ、モデルの入力である、224 * 224に近いほうが、精度が上がる結果となった為です。


参考:[Amazon SageMaker] イメージ分類(Image Classification)において、データセットの解像度が、学習及び、検出結果に与える影響について確認してみました

変換後のサイズは、1.5MByte程度になりました。

import os
import glob
import subprocess

# 設定
WIDTH = 320
HEIGHT = 240
INPUT_PATH = "/tmp/"
OUTPUT_PATH = "/tmp/{}-{}/".format(WIDTH, HEIGHT)

# 出力先ディレクトリ作成
os.makedirs(OUTPUT_PATH, exist_ok=True)

# 入力パスにある*.mp4を列挙
files = glob.glob("{}/*.mp4".format(INPUT_PATH))

for src in files:
    # ffmpegで解像度変換
    subprocess.call('ffmpeg -i "{}" -vf scale={}:{} "{}{}"'.format(src, WIDTH, HEIGHT, OUTPUT_PATH, os.path.basename(src)), shell=True)

(3) 解像度の変更

動画は、200枚づつ切り出し、Ground Truth形式のデータセットとし、その後、イメージ形式に変換しています。
参考:[Amazon SageMaker] Amazon SageMaker Ground Truth で作成したデータをイメージ分類で利用可能なイメージ形式に変換してみました

各200件で、30種類なので、全部で6,000件となり、8:2で分割して、学習用は、4,800件となりました。

全データ: 6000件 
PORIPPY(GREEN) (200件) => 160:40
OREO (200件) => 160:40
CUNTRY_MAM (200件) => 160:40
PORIPPY(RED) (200件) => 160:40
CHEDDER_CHEESE (200件) => 160:40
PRETZEL(YELLOW) (200件) => 160:40
UMIAJISEN (200件) => 160:40
KAKINOTANE(NORMAL) (200件) => 160:40
FURUGURA(BROWN) (200件) => 160:40
NOIR (200件) => 160:40
BANANA(BLOWN) (200件) => 160:40
CHEESE_ARARE (200件) => 160:40
ORENO_OYATU (200件) => 160:40
PRIME (200件) => 160:40
CRATZ(RED) (200件) => 160:40
CRATZ(GREEN) (200件) => 160:40
PORIPPY(YELLOW) (200件) => 160:40
KOTUBUKKO (200件) => 160:40
ASPARAGUS (200件) => 160:40
NORI_PI (200件) => 160:40
KAKINOTANE(UME) (200件) => 160:40
PRETZEL(BLACK) (200件) => 160:40
CRATZ(ORANGE) (200件) => 160:40
CHOCO_MERIZE (200件) => 160:40
POTATO(RED) (200件) => 160:40
BANANA(BLUE) (200件) => 160:40
DENROKU (200件) => 160:40
FURUGURA(RED) (200件) => 160:40
PRETZEL(GREEN) (200件) => 160:40
POTATO(BLUE) (200件) => 160:40

train:4800 validation:1200

3 学習

設定したパラメータは、以下のとおりです。

early_stopping  false
epochs  6
image_shape 3,224,224
learning_rate   0.001
mini_batch_size 32
multi_label 0
num_classes 30
num_layers  152
num_training_samples    4800
optimizer   sgd
precision_dtype float32
use_pretrained_model    1
use_weighted_loss   0

学習の経過です。

epoch   Train-accuracy  Validation-accuracy
-----------------------------------------
0       0.233   0.123
1       0.394   0.384
2       0.758   0.607
3       0.924   0.804
4       0.98    0.919
5       0.996   0.917

4 結果

実は、この6000件のデータセットで作成したモデルは、30件中6件の誤検知がありました。間違えていたのは、以下の6種類です。

  • BANANA(BLOWN)が、、BANANA(BLUE)と判定されている
  • CRATZ(RED)が、CRATZ(ORANGE)と判定されている
  • ポリッピー(YELLOW)が、ポリッピー(RED)と判定されている
  • PRETZEL(YELLOW)が、PRETZEL(GREEN)と判定されている
  • ポリッピー(GREEN)が、海鮮味と判定されている
  • フライドポテト(じゃがバター味)が、フライドポテト(しお味)と判定されている

言語化すると「(色違い的な)よく似た商品に誤って判定されてしまっている」と言えそうです。 そして、以下の商品が、ちょっと 「強すぎる」 って感じです。

  • BANANA(BLUE)
  • CRATZ(ORANGE)
  • ポリッピー(RED)
  • PRETZEL(GREEN)
  • 海鮮味
  • フライドポテト(しお味)

5 改善

当初、すべての商品を200件としましたが、改善のために、上記の 「強すぎる」 商品のデータ数を180件に削減しました。

NUMS={"PORIPPY(GREEN)":200,"OREO":200,"CUNTRY_MAM":200,"PORIPPY(RED)":180,"CHEDDER_CHEESE":200,"PRETZEL(YELLOW)":200,"UMIAJISEN":180,"KAKINOTANE(NORMAL)":200,"FURUGURA(BROWN)":200,"NOIR":200,"BANANA(BLOWN)":200,"CHEESE_ARARE":200,"ORENO_OYATU":200,"PRIME":200,"CRATZ(RED)":200,"CRATZ(GREEN)":200,"PORIPPY(YELLOW)":200,"KOTUBUKKO":200,"ASPARAGUS":200,"NORI_PI":200,"KAKINOTANE(UME)":200,"PRETZEL(BLACK)":200,"CRATZ(ORANGE)":180,"CHOCO_MERIZE":200,"POTATO(RED)":200,"BANANA(BLUE)":180,"DENROKU":200,"FURUGURA(RED)":200,"PRETZEL(GREEN)":180,"POTATO(BLUE)":180}

削減したデータをイメージ形式に変換すると、学習データは、4704件となります。

全データ: 5880件 
PORIPPY(RED) (180件) => 144:36
UMIAJISEN (180件) => 144:36
CRATZ(ORANGE) (180件) => 144:36
BANANA(BLUE) (180件) => 144:36
PRETZEL(GREEN) (180件) => 144:36
POTATO(BLUE) (180件) => 144:36
PORIPPY(GREEN) (200件) => 160:40
OREO (200件) => 160:40
CUNTRY_MAM (200件) => 160:40
CHEDDER_CHEESE (200件) => 160:40
PRETZEL(YELLOW) (200件) => 160:40
KAKINOTANE(NORMAL) (200件) => 160:40
FURUGURA(BROWN) (200件) => 160:40
NOIR (200件) => 160:40
BANANA(BLOWN) (200件) => 160:40
CHEESE_ARARE (200件) => 160:40
ORENO_OYATU (200件) => 160:40
PRIME (200件) => 160:40
CRATZ(RED) (200件) => 160:40
CRATZ(GREEN) (200件) => 160:40
PORIPPY(YELLOW) (200件) => 160:40
KOTUBUKKO (200件) => 160:40
ASPARAGUS (200件) => 160:40
NORI_PI (200件) => 160:40
KAKINOTANE(UME) (200件) => 160:40
PRETZEL(BLACK) (200件) => 160:40
CHOCO_MERIZE (200件) => 160:40
POTATO(RED) (200件) => 160:40
DENROKU (200件) => 160:40
FURUGURA(RED) (200件) => 160:40

train:4704 validation:1176

このデータセットを、下記のパラメータで学習すると、最初に紹介した動画のような、30勝0敗のモデルを得ることができました。

early_stopping  false
epochs  7
image_shape 3,224,224
learning_rate   0.001
mini_batch_size 32
multi_label 0
num_classes 30
num_layers  152
num_training_samples    4704
optimizer   sgd
precision_dtype float32
use_pretrained_model    1
use_weighted_loss   0
epoch   Train-accuracy  Validation-accuracy
-----------------------------------------
0       0.9     0.835
1       0.945   0.937
2       0.983   0.985
3       0.991   0.957
4       0.997   0.963
5       0.996   0.964
6       1.0     0.985

6 最後に

実は、改善のための最初のアプローチは、増加学習でした。最初のモデルを引き継いで、誤っていた商品のみのデータセットで増加学習をしました。

しかし、追加した商品は、完全に検出できるようになるのですが、元々、正確に検出出来ていた商品も、追加した商品に引きずられて間違ってしまう結果となりました。(データ数を非常に少数にしても同じでした)

そして、最終的に、うまく言ったのは、今回紹介した、強くなっている商品のデータを削減するアプローチでした。

この方法が、万能であると言う訳では決してありませんが、一つの結果として紹介できれば幸いです。