ABEJA Arts Blog

株式会社ABEJAのメンバーが最先端の “Mechanical Arts” と、それを実際に社会に適用する中で必要な “Liberal Arts” について発信します

サンタクロースとサンタコスを見分けるDeep Learningモデルをサービス化してみた

この記事はABEJA Platform Advent Calendar 2018の24日目です。

はじめに

どうも、緒方です。

世はAI戦国時代、皆さんの会社ではAI活用進んでいますか?😃

自分たちのプロダクトでDeep Learningを活用する際、データ収集からアノテーション、学習モデル作成やプロダクトへの組み込み(API化等)を一通りこなす必要があります。 今回はクリスマス・イブなので、 サンタクロースを題材にDeep Learningをフルスクラッチでサービスインしたら・・・?という内容でお送りしたいと思います。

やりたいこと

クリスマスといえば、サンタクロース。きっと子供の頃は待ちわびたことだと思います。 しかし世の中には様々なサンタが存在します。 はたしてDeep Learningは世の中に複数存在するサンタの属性を見分けられるのか、、、?

今回のDeep Learningのプロダクトをサービス化するにあたり、下記のタスクをこなしていきたいと思います。

  • Webから画像をクローリング
  • 教師データのタグ付
  • 学習モデルの作成
  • Web API化

Webから画像をクローリング

まずはJupyterを使ってクローリングを行います。 まずはJupyterインスタンスを立ち上げたいので Training > Job Definitionから新規のジョブ定義を作成します。 f:id:contaconta:20181217173613p:plain Notebookの画面です。 f:id:contaconta:20181217173653p:plain

開くと、Jupyterが起動します f:id:contaconta:20181217173727p:plain

普段のJupyterの使い方と同じようにNotebookを作成してコードを書きます。

ここでは、

  • キーワードに対する画像Webのクローリング
  • ABEJA PlatformのDatalakeへの画像アップロード

の2つのタスクを行います。

クローリングに関しては、神パッケージの icrawlerを利用します。キーワードを入れるとよしなにクローリングしてきてくれます。 今回は サンタサンタコス をキーワードに、画像合計1000枚程度取得します。 実行すると image/<検索ワード> に画像が保存されます。

画像がクローリングできたら、ABEJA PlatformのSDKを利用してDatalakeのChannelへ画像をアップロードします。 認証情報をSDKに渡して datalake_client.channels.create() にてClientを生成したあと、 まずはDatalakeに新規Channel(s3のbucketのようなもの)を作成します。

channel.upload_dir() 関数は、指定したディレクトリにある画像ファイルを全部Datalakeへアップロードしてくれるいいヤツです。

下記にベタ書きコードを貼っています。

from pathlib import Path
from icrawler.builtin import GoogleImageCrawler
from abeja.datalake import Client as DatalakeClient
from abeja.datalake.storage_type import StorageType

organization_id = '123456789'
credential = {
    'user_id': 'user-123456789',
    'personal_access_token': '123456789123456789123456789123456789123456789'
}

image_root_dir = Path('image')

# download images
keyword = u'サンタクロース'
max_num = 800
filters = {
    'type': 'photo'
}
crawler = GoogleImageCrawler(storage={'root_dir': image_root_dir / keyword})
crawler.crawl(keyword=keyword, max_num=max_num, filters=filters)

keyword = u'サンタコス'
max_num = 300
filters = {
    'type': 'photo'
}
crawler = GoogleImageCrawler(storage={'root_dir': image_root_dir / keyword})
crawler.crawl(keyword=keyword, max_num=max_num, filters=filters)

# create channel
datalake_client = DatalakeClient(organization_id=organization_id, credential=credential)
name = 'santa-dataset'
description = 'created by my crawler'
channel = datalake_client.channels.create(name, description, StorageType.DATALAKE.value)

# upload images
ret = channel.upload_dir(image_root_dir)

*集めた画像を眺めてみるとわかるのですが、世の中に存在するサンタ画像は サンタ画像 >> サンタコス画像 だったため、枚数を調整しています

これをJupyter上で実行することで、Datalakeに有効なファイルがUploadされました。

f:id:contaconta:20181217173908p:plain

これにてデータ収集完了です!

教師データのタグ付

教師データを作成するために次のステップへ進みます。 PlatformのAnnotationをクリックし、タグ付けのためのプロジェクトを作成します。 Annotation Toolでは各タスクに特化したテンプレートを用意しており、画面に従って入力するだけで簡単にワーカーへ依頼することが可能になります。 アカウント管理や品質管理のためのレビュー機能もついており、コードを開発すること無く教師データの作成ができます。

トップ画面はこのような形になっており、プロジェクトを管理できるようになっています。

f:id:contaconta:20181217174110p:plain

タスク準備編

Create a new Project をクリックし、タスクを作成すると次の画面がでます。

f:id:contaconta:20181217174127p:plain

現在は下記のようなテンプレートを準備しており、随時汎用的なタスクは開発・追加していきます!

  • 画像識別
  • 物体検出
  • 動画タグ付け
  • 音声書き起こし
  • テキストラベリング
  • セグメンテーション
  • 画像のテキスト化

もちろん裏ではABEJA PlatformのAPIコールしているだけなので、あなただけのオリジナルアノテーションツール も開発することが可能です。WebでもWindows EXEでもスマホアプリでも、なんでもOKです。

今回は、サンタ or サンタコス の分類問題を解いてみようと思うので、classification タスクを選択します。

f:id:contaconta:20181217174232p:plain

選択するとタスク作成画面に移ります。 ここでラベルの設計を行います(割と自由にかけます)。

f:id:contaconta:20181217174302p:plain

今回は、サンタクロースサンタコスそれ以外 の3つのタグを作成しました。

DatalakeのChannelとプロジェクトの依頼内容を設定します。 f:id:contaconta:20181217174352p:plain

最後に内容を確認してプロジェクトスタート! f:id:contaconta:20181217174418p:plain

Project SANTA 始動 f:id:contaconta:20181217174446p:plain

タスク実行編

Annotationの管理画面から、作業タスクを依頼するためにアカウントを作成します。 試しに僕のアカウントを発行します。

f:id:contaconta:20181217174515p:plain

ワーカー用のリンクをアクセスして、こんな感じでログインすると f:id:contaconta:20181217174518p:plain

アサイン可能なプロジェクト一覧を見ることができます。 f:id:contaconta:20181217174524p:plain

作業 ボタンを押すとタスク作業画面がでます。 f:id:contaconta:20181217174533p:plain

進捗状況もこんな感じで見ることができます。 f:id:contaconta:20181217175209j:plain

これ、全部1人てアノテーションするの辛いなー、辛いなー

そんな人のために、ABEJA Platform Annotationというクラウドソーシングサービスがあります。 これは、作成したタスクをアノテーターに依頼するサービスで、10万件のデータを1週間で作成するアノテーションリソース(*当社調べ) があるそうです。 早速課金しましょう(AIも課金ゲーかもしれませんね)

依頼が完了するとこのようなプログレスになります。 f:id:contaconta:20181217175240j:plain

あとは管理画面からアノテーションをダウンロードして一旦準備完了です! f:id:contaconta:20181217175323j:plain

*本来はある程度のボリュームがないと依頼できないのですが、今回は特別にお願いを聞いてもらいました、感謝

学習モデルの作成

Datasetsの整備

まずは作成した教師データをABEJA Platformの Datasets に取り込みます。 Datasetsは学習データを管理する仕組みで、Train/Valのデータセットを管理したり、データのバージョン管理をしたり、複数人で共同開発を行う場合に非常に役立ちます。

詳細はここ を見ると非常にわかりやすいです。

すると、このように Datasets 内に、利用可能なデータが用意されます。

f:id:contaconta:20181217175359j:plain

f:id:contaconta:20181217175446j:plain

便利な機能として、データセットの中身がどんなものかを確認できるPreview機能があります。

f:id:contaconta:20181217175508j:plain

タグ毎の絞り込みも可能なので、コードを書くことなくデータの確認ができます f:id:contaconta:20181217175616j:plain

f:id:contaconta:20181217175612j:plain

f:id:contaconta:20181217175605j:plain

学習モデルの作成

ここでは下記の3つをやっていきます。

  • 学習/評価データの作成
  • 学習コードの作成/実行
  • 精度評価

学習/評価データの作成

学習モデルを作成する際に重要なポイントの一つとして 学習/評価データ があります。 これは学習モデルを複数人で作成したり、後々評価する際に 僕の環境では○○だった をなくすために非常に重要なことです。

まずは Datasetsの整備 の項目を参考にデータセットを分割してスナップショットを作ります(今回は7:3の割合でデータを分割しました) 下記の画面のようにtrain / val の名前を追加し作成してます。 f:id:contaconta:20181217175832j:plain

学習コードの作成/実行

あとは普段どおりの学習コードをサンプルコードを見ながら書いていきます。 ほとんど普段Deep Learningのコードを書くのと同じようにかけます。

f:id:contaconta:20181217180515p:plain

あとは、書いたコードをアップロードして実行ボタンを押すと下記のように学習がスタートします。お茶やコーヒーを入れてプログレスバーを眺めましょう。

f:id:contaconta:20181217180617p:plain

ルール通りに書けば設定変数を外部からも変更できるので、同じコードでパラメーターを実験ごとに変えたい場合はEnvironments を学習時に変更すれば並列で実験可能です。

f:id:contaconta:20181217180633p:plain

ステータスがsuccess になっていれば学習完了です!

精度評価

AIエンジニアたるもの、識別問題をやった際にはConfusion Matrixを確認したくなるものです。 そこで登場するのがpycm です(≠pymc)。 使い方は非常に簡単で、予測値と真値のベクターを入れるだけなので使っていきましょう。

学習済みモデルをABEJA Platformから読み込み

import io
import os
import zipfile
import requests
import keras
from abeja.datasets.dataset_item import DatasetItem
from abeja.datasets import Client as DatasetClient
from abeja.train import APIClient as TrainAPIClient

# download model file
train_api_client = TrainAPIClient(credential=credential)
result = train_api_client.get_training_result(organization_id, job_definition_name, training_job_id)
res = requests.get(result['artifacts']['complete']['uri'])
binary = io.BytesIO(res.content)

# extract model file
with zipfile.ZipFile(binary) as existing_zip:
    # print(existing_zip.namelist())
    existing_zip.extractall(download_dir)

# load model
model_path = os.path.join(download_dir, 'model.h5')
model = keras.models.load_model(model_path)

推論して

def read_data_from_dataset_item(dataset_item: DatasetItem):
    img = np.array(Image.open(io.BytesIO(dataset_item.source_data[0].get_content())), dtype=np.float32)
    label = int(item.attributes['classification'][0]['label_id'])
    return img, label

dataset_client = DatasetClient(organization_id=organization_id, credential=credential)
dataset = dataset_client.get_dataset(dataset_id)
dataset_list = list(dataset.dataset_items.list(prefetch=True))

x_list, y_list = load_dataset_from_api(dataset_id)
x_arr = np.array(x_list)
y_pred = model.predict(x_arr)
y_pred = np.argmax(y_pred, axis=1)

評価すると? f:id:contaconta:20181220135202p:plain

このように精度を簡単に確認することができます。 (大体8割程度はDeep Learningでサンタとサンタコスを見分けられるっぽい?!?!?) 今回何の工夫もないモデル(ResNet50)をFinetuingしたのですが、精度を見るとサンタかサンタコスという軸ではほとんど見分けられているようです(それ以外 と間違うことが多いみたいですが)

Deploy

それなりに精度が出るモデルができたので、早速WebAPI化してみようと思います。 API化のためには下記のステップを行います。

  • 推論コードの開発
  • Model Version作成
  • Web API化

あんまりAWSやGCPとかのクラウド詳しくないという人や、Webとかロードバランサーとかよくわかんないという人でもそのあたりはABEJA Platformがマネージしてくれます。

推論コードの開発

ABEJA Platformのサンプルコードを元に雑なコードをサクッと書きます。 このコードは画像のリクエストを受け付けて、推論結果をjsonで返すコードになります。

import os
from keras.preprocessing import image
from keras.models import load_model
from keras.applications.imagenet_utils import preprocess_input
import numpy as np
from PIL import Image

img_rows, img_cols = 224, 224
model = load_model(os.path.join(os.environ.get('ABEJA_TRAINING_RESULT_DIR', '.'), 'model.h5'))


def decode_predictions(result):
    categories = {
        0: u'santa',
        1: u'santa-cos',
        2: u'others'
    }
    result_with_labels = [{"label": categories[i], "probability": score} for i, score in enumerate(result)]
    return sorted(result_with_labels, key=lambda x: x['probability'], reverse=True)


def handler(_iter, ctx):
    for img in _iter:
        img = Image.fromarray(img)
        img = img.resize((img_rows, img_cols))

        x = image.img_to_array(img)
        x = np.expand_dims(x, axis=0)
        x = preprocess_input(x, mode='tf')

        result = model.predict(x)[0]
        sorted_result = decode_predictions(result.tolist())
        yield {"result": sorted_result}

Model Version作成

学習済みモデルと推論コードを紐づけて、Model Versionというものを作成します。 これにより、あとからあの時のモデルは良かった コードとモデルの対応が取れてなくてもう動かし方がわからない などのツラみを解消できます。

f:id:contaconta:20181220141527p:plain

Web API化

先程作成したModel Versionを選択して、インスタンスタイプや台数を選んでDeployしましょう!インフラ周りは全部ABEJA Platformにお任せします。 f:id:contaconta:20181220144510p:plain

f:id:contaconta:20181220144530p:plain

APIのテストをしてみます。UIにcheck 機構があるので使ってみます。 f:id:contaconta:20181220151759g:plain

推論結果がきちんとJsonで返ってきました。 このAPIをアプリの裏側やLine Botに組み込めば、いつでもサンタクロースとサンタコスを見分ける事ができます、やったね!

考察

今回のデータセット&学習モデルだと、サンタクロースかサンタコス(その他含む)を大体8割程度見分けられるようです。

今回、フルスクラッチでデータ収集からサービス化してみて改めて勉強になりました。 特にデータ収集のところでは、大きな学びがありました。 データを集めてみて、眺めてみないとなかなかわからないこともあるものです。 例えば今回の例だと、下記のような課題が見つかりました。

  • イラスト・アニメ画像はどうするのか?
  • 若い男性のサンタ姿はサンタコス?
  • サンタとサンタコスの姿の人が同時に写ってる場合はどうするの?
  • 赤ちゃんもサンタコスに入れる?
  • 緑や黒の服装はどうする?
  • 世の中には本物のサンタの画像が少ないのでデータのバランスが悪くなるけど大丈夫?
  • そもそもすべてのサンタってコスプレなのでは

こういった課題は、アノテーションをする前のデータ収集の段階からしっかりと設計しないと、いざクラウドソーシングした後悲しい事になってしまいますね。

また、誤認識されたデータはこのようなものでした。

サンタと誤認識された画像例 f:id:contaconta:20181221141429p:plain

f:id:contaconta:20181221141417p:plain

サンタコスと誤認識された画像例 f:id:contaconta:20181221141549p:plain

f:id:contaconta:20181221141538p:plain

それ以外と誤認識された画像例

f:id:contaconta:20181221141648p:plain

f:id:contaconta:20181221141718p:plain

ラベル付けが難しそうだったり、サンタが複数人写り込んでいたりと、なかなかムズカシイものもありました。

一重にサンタとサンタコスを分けるだけでも、意外と考えなければいけないことが多く、いかにデータ精査とTry&Errorが重要かを感じられますね!

それでは良いクリスマスを!

宣伝

ABEJAではイケてるしヤバい人材募集中です!インターンとかも興味があればぜひご連絡くださいー www.wantedly.com

ABEJAの中の人と話ししたい!オフィス見学してみたいも随時受け付けておりますので、気軽にポチッとどうぞ↓↓