Quantcast
Channel: Google Cloud Platform その1 Advent Calendarの記事 - Qiita
Viewing all articles
Browse latest Browse all 23

AutoML Vision用の学習用画像収集スクリプト

$
0
0

皆さんこんにちは。@best_not_bestです。

Google Cloud Platform(GCP)のプロダクトの1つに、Cloud AutoML Visionとういうものがあり、ノンプログラミングで画像認識モデルの作成が可能です。

学習用の画像は以下の方法でアップロード可能です。
(cf. https://cloud.google.com/vision/automl/docs/create#upload_your_images

  • Web UI上からアップロード(zipファイルで複数ファイルアップロードも可能)
  • Google Cloud Storage(GCS)からの読み込み

今回は後者の方法を用い、画像検索APIで画像を収集 → GCSへファイルをアップロードするスクリプトを作成します。
モデルは犬画像の認識モデルを作成します!

AutoML Visionに加え、GCS、Stackdriver Loggingを使用しますので、必要に応じてGCP上から各種APIをオンにしてください。

環境

マシン/OS

  • MacBook Pro (13-inch, 2017, Four Thunderbolt 3 Ports)
  • OS 10.12.6
  • pyenv: 1.2.8
  • pyenv-virtualenv: 1.1.3

Python

pyenv、pyenv-virtualenvで 3.7.1 の環境を構築します。

$ mkdir hogehoge
$ cd hogehoge

$ pyenv local 3.7.1
$ pyenv virtualenv 3.7.1 hogehoge
$ pyenv local hogehoge

$ pip install -U pip

以下のライブラリをインストールします。

requirements.txt
cachetools==3.0.0
certifi==2018.11.29
chardet==3.0.4
dill==0.2.8.2
docopt==0.6.2
flake8==3.6.0
flake8-docstrings==1.1.0
flake8-polyfill==1.0.1
future==0.16.0
gapic-google-cloud-logging-v2==0.91.3
google-api-core==1.7.0
google-api-python-client==1.7.7
google-auth==1.6.2
google-auth-httplib2==0.0.3
google-cloud-core==0.29.1
google-cloud-logging==1.9.1
google-cloud-storage==1.13.2
google-gax==0.15.16
google-resumable-media==0.3.2
googleapis-common-protos==1.5.5
grpcio==1.17.1
httplib2==0.12.0
idna==2.8
mccabe==0.6.1
numpy==1.15.4
oauth2client==3.0.0
pandas==0.23.4
ply==3.8
proto-google-cloud-logging-v2==0.91.3
protobuf==3.6.1
pyasn1==0.4.4
pyasn1-modules==0.2.2
pycodestyle==2.4.0
pydocstyle==3.0.0
pyflakes==2.0.0
python-dateutil==2.7.5
pytz==2018.7
PyYAML==3.13
requests==2.21.0
rsa==4.0
six==1.12.0
snowballstemmer==1.2.1
uritemplate==3.0.0
urllib3==1.24.1

ディレクトリ構成

scripts
 └─ scripts.py
model
 └─ get_images.py
config
 └─ conf.yml
outputs

処理概要

  1. 画像検索APIで画像を収集
  2. ローカルに画像を保存
  3. 画像ファイルをGCSへアップロード
  4. 画像のURIとラベルを含むCSVファイルの作成
  5. CSVファイルをGCSへアップロード

画像検索APIはGoogle Custom Search APIを使いたい所ですが、リクエスト制限があるため、Bing Image Search APIを使用します。
Microsoftアカウントの作成等は、以下の記事を参考にさせて頂きました。

ファイル説明

conf.yml

APIキーや、検索ワードを設定します。
検索ワードはみんなの犬図鑑から日本犬をピックアップしています。(全犬種やりたかったけど時間がなかった・・・。)

conf.yml
api_key: 'xxxxx'
gcs_bucket_name: 'yyyyy'
images_per_requests: 50
request_count: 10
dir_name: 'dog'
search_words:
  -
    label: 'akita'
    word: '秋田犬'
  -
    label: 'kai'
    word: '甲斐犬'
  -
    label: 'kishu'
    word: '紀州犬'
  -
    label: 'shikoku'
    word: '四国犬'
  -
    label: 'shiba'
    word: '柴犬'
  -
    label: 'tosa'
    word: '土佐犬'
  -
    label: 'spitz'
    word: '日本スピッツ'
  -
    label: 'japanese-terrier'
    word: '日本テリア'
  -
    label: 'hokkaido'
    word: '北海道犬'
  • api_key: Bingの画像検索APIキー
  • gcs_bucket_name: アップロードするGCSのバケット名(バケット名は<GCPのプロジェクトID>-vcmにする必要があります。)
  • images_per_requests: 1リクエストあたりの要求画像数
  • request_count: リクエスト数
  • dir_name: ローカル、及びGCSの保存ディレクトリ名
  • search_words.label: 画像に付けるラベル名
  • search_words.word: 画像検索ワード

get_images.py

Bing画像検索APIから画像の取得、GCSへのファイルアップロードを行います。

get_images.py
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

"""Get images from Bing Search."""

from google.cloud import storage
import hashlib
import os
import pandas as pd
import requests
import urllib

class CommonError(Exception):
    """common error class."""

    pass

class GetImagesFromBingSearch(object):
    """Get images from Bing Search."""

    def __init__(
        self,
        api_key: str,
        gcs_bucket_name: str,
        gcs_dir_name: str,
    ):
        """init."""
        self.headers = {
            'Content-Type': 'multipart/form-data',
            'Ocp-Apim-Subscription-Key': api_key,
        }
        gcs_client = storage.Client()
        self.gcs_bucket_name = gcs_bucket_name
        self.gcs_bucket = gcs_client.get_bucket(gcs_bucket_name)
        self.csv_data = []
        self.gcs_dir_name = gcs_dir_name

    def get_image_url(
        self,
        search_word: str,
        images_per_requests: int = 50,
        request_count: int = 20,
    ) -> list:
        """get image url."""
        image_url_list = []

        # offset
        for offset in range(0, (images_per_requests * request_count), images_per_requests):
            # query parameters
            params = urllib.parse.urlencode(
                {
                    'q': search_word,
                    'mkt': 'ja-JP',
                    'count': images_per_requests,
                    'offset': offset,
                }
            )

            try:
                # get
                response = requests.get(
                    'https://api.cognitive.microsoft.com/bing/v7.0/images/search',
                    headers=self.headers,
                    params=params
                )
                response.raise_for_status()
                search_results = response.json()
            except Exception as e:
                raise CommonError(e)

            # append url
            for values in search_results['value']:
                image_url_list.append(values['contentUrl'])

        return list(set(image_url_list))

    def get_image(
        self,
        image_url: str,
        output_dir_path: str,
        blob_dir: str,
    ) -> bool:
        """get image."""
        opener = urllib.request.build_opener()
        urllib.request.install_opener(opener)

        # check extension
        parsed_url_path = urllib.parse.urlparse(image_url).path.split(':')[0]
        extension = os.path.splitext(parsed_url_path)[-1].lower()
        if extension not in ('.jpg', '.jpeg', '.gif', '.png', '.bmp'):
            msg = 'extension error. url:"%s".' % (image_url)
            raise CommonError(msg)

        try:
            # get
            response = requests.get(image_url, allow_redirects=True, timeout=5)
        except Exception as e:
            raise CommonError(e)

        # check content
        if len(response.content) == 0:
            msg = 'content error. url:"%s".' % (image_url)
            raise CommonError(msg)

        # save
        hashed_url = hashlib.sha256(image_url.encode('utf-8')).hexdigest()
        output_file_path = os.path.join(
            output_dir_path,
            hashed_url + extension,
        )
        with open(output_file_path, 'wb') as fp:
            fp.write(response.content)

        # upload to GCS
        blob_name = 'automl_vision/' \
            + self.gcs_dir_name \
            + '/' \
            + blob_dir \
            + '/' \
            + hashed_url \
            + extension
        blob = self.gcs_bucket.blob(blob_name)
        blob.upload_from_filename(output_file_path)

        # add data
        self.csv_data.append(
            {
                'gcs_uri': 'gs://' + self.gcs_bucket_name + '/' + blob_name,
                'label': blob_dir,
            }
        )

        return True

    def make_csv(
        self,
        output_file_path: str,
    ) -> bool:
        """make CSV file."""
        df = pd.DataFrame.from_dict(self.csv_data)
        df = df.ix[
            :,
            [
                'gcs_uri',
                'label',
            ]
        ]
        df.to_csv(output_file_path, index=False, header=False)

        # upload to GCS
        blob_name = 'automl_vision/' \
            + self.gcs_dir_name \
            + '/' \
            + os.path.basename(output_file_path)
        blob = self.gcs_bucket.blob(blob_name)
        blob.upload_from_filename(output_file_path)

        return True
  • get_image_url: Bing画像検索APIから画像URLの取得を行います。
  • get_image: 画像URLから画像の取得 → ローカルに保存 → GCSへのファイルアップロードを行います。
    • 盛り込み過ぎたのでUnitテストしづらい・・・。
    • 特定の拡張子を持つファイルのみ保存されます。また、ファイルサイズが0のファイルは保存されません。
  • make_csv: 前述した「画像のURIとラベルを含むCSVファイル」を作成します。

scripts.py

スクリプト部分になります。ログはStackdriver Loggingに送られます。

scripts.py
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

"""Get images from Bing Search.

Usage:
    scripts.py
        --conf_file_path=<conf_file_path>
        --output_dir_path=<output_dir_path>
    scripts.py -h | --help
Options:
    -h --help show this screen and exit.
"""

from docopt import docopt
import google.cloud.logging
import logging
import os
import shutil
import sys
import yaml

try:
    import get_images
except ImportError:
    sys.path.append(os.path.abspath(os.path.dirname(__file__)) + '/../model')
    import get_images

if __name__ == '__main__':
    # logging config
    logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s')

    # logging
    logging_client = google.cloud.logging.Client()
    logging_client.setup_logging()
    logging.info('%s start.' % (__file__))

    # get parameters
    args = docopt(__doc__)
    conf_file_path = args['--conf_file_path']
    output_dir_path = args['--output_dir_path']

    # config
    with open(conf_file_path) as f:
        conf_data = yaml.load(f)
    api_key = conf_data['api_key']
    search_words = conf_data['search_words']
    gcs_bucket_name = conf_data['gcs_bucket_name']
    images_per_requests = conf_data['images_per_requests']
    request_count = conf_data['request_count']
    dir_name = conf_data['dir_name']

    # create model
    gifbs = get_images.GetImagesFromBingSearch(
        api_key,
        gcs_bucket_name,
        dir_name,
    )

    for search_word in search_words:
        label = search_word['label']
        word = search_word['word']

        # make dir
        output_tmp_dir_path = os.path.join(
            os.path.abspath(output_dir_path),
            dir_name,
            label,
        )
        if os.path.isdir(output_tmp_dir_path):
            # remove dir
            shutil.rmtree(output_tmp_dir_path)
        os.mkdir(output_tmp_dir_path)

        try:
            # get image url
            image_url_list = gifbs.get_image_url(
                word,
                images_per_requests=images_per_requests,
                request_count=request_count,
            )
        except get_images.CommonError as e:
            logging.warning(e)

        for image_url in image_url_list:
            # get image, save and upload GCS
            try:
                gifbs.get_image(
                    image_url,
                    output_tmp_dir_path,
                    label,
                )
            except get_images.CommonError as e:
                logging.warning(e)
                continue

    # make CSV file
    output_file_path = os.path.join(
        os.path.abspath(output_dir_path),
        dir_name,
        'data.csv',
    )
    gifbs.make_csv(output_file_path)

    logging.info('%s end.' % (__file__))
    sys.exit(0)

実行方法

conf_file_pathoutput_dir_pathがコマンド引数となります。それぞれ<設定ファイルのパス><画像の保存ディレクトリのパス>を指定ください。

実行例
$ python scripts/scripts.py  \
  --conf_file_path=./config/conf.yml \
  --output_dir_path=./outputs/

実行結果

結構な数のWarningログが出力されますが、プログラム内で意図的に出力しているログであり、必要な画像はGCSへとアップロードされています。

実行結果
2018-12-22 23:47:05,421 INFO: scripts/scripts.py start.
scripts/scripts.py start.
2018-12-22 23:47:21,630 WARNING: extension error. url:"https://item-shopping.c.yimg.jp/i/j/usual_irish3".
extension error. url:"https://item-shopping.c.yimg.jp/i/j/usual_irish3".
(中略)
2018-12-22 23:51:12,407 INFO: scripts/scripts.py end.
scripts/scripts.py end.
Program shutting down, attempting to send 1 queued log entries to Stackdriver Logging...
Waiting up to 5 seconds.
Sent all pending logs.

gs://<gcs_bucket_name>/automl_vision/<dir_name>/<search_words.label>/配下にラベル毎に画像ファイルが、gs://<gcs_bucket_name>/automl_vision/<dir_name>/data.csvに、「画像のURIとラベルを含むCSVファイル」がアップロードされます。

AutoML Vision

では、実際にAutoML Visionを使用してみます。

学習用画像のアップロード

ページ上部の「ADD IMAGES」→「Select CSV file on Cloud Storage」から、上記の「画像のURIとラベルを含むCSVファイル」のGCS上のURIを入力し、「IMPORT」をクリックします。
以下の画面に遷移すればアップロード完了です。

1.png

同一画像が別ラベルでアップロードされている、ファイルが読み込めない等のエラーが出ていますが、今回は後述の作業にクリティカルな影響が無いため無視します。

3.png
4.png

不要画像の削除

その犬種ではない画像、そもそも犬ではない画像はWeb UIから削除します。
これが結構大変です。

5.png

結果、以下のようなデータ数となりました。

6.png

学習

画像を学習させます。土佐犬の画像を増やした方が良いと言われてますが、このまま続けてみます。

7.png

学習方法は無料の「1 compute hour」を選択します。

8.png

学習には15分から1時間程度掛かります。完了すると自分のGCPアカウント宛てにメールが届きます。

9.png

評価

評価結果は以下のようになりました。

14.png
15.png

過学習している気もするので新規画像で予測してみます。

予測

Web UIからいくつか画像をアップロードしてみます。

  • 柴犬
    10.png

  • 土佐犬
    12.png

  • 北海道犬
    11.png

  • 紀州犬
    13.png

深く検証したわけではないですが、学習データが少なかった土佐犬、パッと見判別し辛い北海道犬と紀州犬も、かなりの精度で判別出来ているようです!

料金

超概算ですが以下になります。

  • AutoML Vision: ¥0
  • GCS: ¥10未満
  • Bing Image Search API: ¥300程度(検証で結構叩いたので実際はもう少し安いかも)

まとめ

  • AutoML Vision用の学習データを収集するスクリプトを(ざっくりと)作成しました。
  • AutoML Visionで犬画像の認識モデルを作成しました。
  • AutoML Vision最高!

Viewing all articles
Browse latest Browse all 23

Trending Articles