この記事は、「Automate detection of broken utility poles using the Amazon Rekognition Custom Labels SDK」を翻訳したものになります。
国内インフラの問題というのは関係者全員の悩みの種であると言えます。顧客満足度に悪影響を及ぼすだけでなく、その企業の事業や財務上の売上にも連鎖的に影響を及ぼします。例えば、電柱は対処すべき面倒なインフラ問題の一例です。通常一般的な木製の配電柱は、およそ50年の寿命があるとされています。しかし、事故、悪天候災害、あるいは送電線の移設など、予期せぬ出来事により、これらの電柱を早期に交換する必要が生じることがあります。そこで、ドローンや街頭カメラを使って、折損した電柱を撮影し、画像化することが現在の業界共通の取組みとなっています。そして、画像に写っている電柱を手作業で検査し、修理や交換が必要ない状態であることを確認するのです。ご想像の通り、これらの電柱の交換が必要かどうかを判断するプロセスは、人為的なミスや作業遅延の影響を受けやすく、時間のかかる手作業です。
このような問題に対処するために、私たちは、Amazon Rekognition Custom Labels を使うソリューションを提案しています。 街頭カメラやドローンから撮影した電柱の画像を、Amazon Rekognition Custom Labels で学習させた機械コンピュータビジョンモデルに与えれば、電柱の状態が良いのか、破損しているのかを自動的に検出することができます。
Amazon Rekognition は、AWS の AI/ML スタックに含まれるコンピュータビジョンサービスです。画像や動画の解析の自動化を可能にします。Amazon Rekognition を使用すると、画像やビデオ内のオブジェクト、人、テキスト、シーン、不適切なコンテンツ、アクティビティを識別することができます。
我々のソリューションでは、画像を分析するためのカスタム機械学習(ML)モデルを作成することができる Amazon Rekognition Custom Labels を使用しています。Amazon Rekognition Custom Labels を使えば、何千枚もの画像に対して、数枚の画像で堅牢で展開可能なモデルをトレーニングすることができます。
我々のユースケースでは、電柱の画像を利用します。以下は、通常の電柱の例です。
以下の画像は折損した電柱の例です。
もし画像がすでにラベル付けされている場合、Amazon Rekognition Custom Labels は、Amazon Rekognition コンソール上で数回クリックするだけでトレーニングを開始することができます。そうでない場合は、Amazon Rekognition Custom Labels のラベル付けユーザーインターフェース内で直接ラベル付けするか、Amazon SageMaker Ground Truth などの別のサービスを使ってラベル付けすることが可能です。Amazon Rekognition Custom Labels で画像のセットをトレーニングすると、わずか数時間でカスタム画像のコンピュータビジョンモデルを作成することができます。このカスタムモデルをトレーニングした後、新しい画像に対して推論を行うためのエンドポイントとして使用することができます。
この記事では、Amazon Rekognition Custom Labels API と AWS SDK を使用して、この技術をいかに簡単にアプリケーションに統合できるかを紹介します。
画像を格納するデータセットのバケットの準備
他のMLモデルと同様に、まずはいくつかのデータから始めます。この記事では、壊れた電柱と壊れていない電柱の画像を扱います。このデータセットは全てラベル付けされ、Amazon Simple Storage Service (Amazon S3) に保存します。このデータセットの格納場所は、モデルを学習する時の SDK 内で指定されます。
モデルの学習
それでは、ラベル付けされたデータが S3 バケットに格納されたので、データのモデルを作ってみましょう。
- 必要なライブラリのインポート
- Amazon Rekognition Custom Labels では、ユースケースに応じたプロジェクトを作成する必要があるため、以下の関数でプロジェクトを作成します:
def create_project(project_name):
client=boto3.client('rekognition')
#Create a project
print('Creating project:' + project_name)
response=client.create_project(ProjectName=project_name)
arn = response['ProjectArn']
print('project ARN: ' + response['ProjectArn'])
return arn
この関数はプロジェクトのARNを返します。これは後でモデルの学習時に使用するので、メモするか変数に保存しておく必要があります。
次に、Amazon Rekognition Custom Labels を使ったモデルの学習方法を定義します。このメソッドでは、入力として project_arn
(先ほど保存したプロジェクトの ARN 変数)、このバージョンのモデルに固有の version_name
、学習結果を保存する場所を定義した out_config
、および学習データとテストデータのマニフェストファイルの場所を指定します。
- 以下の
train_model()
メソッドを実行し、表示されたプロジェクトのバージョン ARN を後で使用するためにメモしておきます。
# Train model helper method
import json
def train_model(project_arn, version_name, output_config, training_dataset,testing_dataset):
client=boto3.client('rekognition')
print('Starting training of: ' + version_name)
try:
response=client.create_project_version(ProjectArn=project_arn,
VersionName=version_name,
OutputConfig=output_config,
TrainingData=training_dataset,
TestingData=testing_dataset)
# Wait for the project version training to complete
project_version_training_completed_waiter = client.get_waiter('project_version_training_completed')
project_version_training_completed_waiter.wait(ProjectArn=project_arn,
VersionNames=[version_name])
#Get the completion status
describe_response=client.describe_project_versions(ProjectArn=project_arn,
VersionNames=[version_name])
for model in describe_response['ProjectVersionDescriptions']:
print('Project Version ARN: ' + model['ProjectVersionArn'])
print("Status: " + model['Status'])
print("Message: " + model['StatusMessage'])
except Exception as e:
print(e)
print('Done...')
- 次に、以下のコードで表示されているように
train_model()
メソッドを呼び出します:
project_arn='arn:aws:rekognition:us-east-1:<12桁のAWSアカウント>:project/project_utility_pole_blog/<表示されたARNと同じ値>'
version_name='v1'
output_config = json.loads('{"S3Bucket":"my-bucket", "S3KeyPrefix":"blog/REK-CL/utilitypoles/"}')
training_dataset= json.loads('{"Assets": [{ "GroundTruthManifest": { "S3Object": { "Bucket": "my-bucket", "Name": "datasets/cables-ds/manifests/output/output.manifest" } } } ] }')
testing_dataset= json.loads('{"AutoCreate":true}')
train_model(project_arn, version_name, output_config, training_dataset, testing_dataset)
今回は Amazon Rekognition Custom Labels を使って学習データを 80:20 にランダムに分割するので、testing_dataset
には "AutoCreate":true
を設定します。また、テストデータセットが別にある場合は、前述のコードで training_dataset
に対して行ったように、testing_dataset
をマニフェストファイルとして指定することも可能です。
モデル学習は、2−3時間ほどで完了します
DescribeProjectVersions
を呼び出して現在の状態を把握、完了したら ProjectVersionsDescriptions
を呼んで学習結果を取得し、モデルを評価することができます:
def describe_model(project_arn, version_name):
client=boto3.client('rekognition')
response=client.describe_project_versions(ProjectArn=project_arn,
VersionNames=[version_name])
for model in response['ProjectVersionDescriptions']:
print(json.dumps(model,indent=4,default=str))
モデルの結果と拡張
本投稿では、2つのモデルを学習します。最初のモデルは、良好な電柱と壊れた電柱の画像を80枚、均等に取り込みます。これらの画像は、Amazon Rekognition Custom Labels に供給され、モデルメトリクスが評価されます。
2つ目のモデルでは、生画像をそのまま与えるのではなく、コンピュータビジョンの課題でよく見られるように、これらの画像に対して data augmentation(訳註:データの増強、拡張)を行います。Amazon Rekognition Custom Labels 自体では提供される画像について十分判断できないため data augmentation を行いません。そのためモデルのメトリクスをさらに改善させたいシナリオでは、明示的に image augmentation(訳註:画像データの増強、拡張)を行うことをお勧めします。
そして、精度やAUCスコアなどのモデルメトリクスを、オリジナルモデルと拡張モデルで比較します。
以下のコードによって、image augmentation をおこないます:
import random
import numpy as np
from scipy import ndarray
import skimage as sk
from skimage import transform
from skimage import util
def random_rotation(image_array: ndarray):
# pick a random degree of rotation between 25% on the left and 25% on the right
random_degree = random.uniform(-25, 25)
return sk.transform.rotate(image_array, random_degree, preserve_range=True)
def horizontal_flip(image_array: ndarray):
# horizontal flip doesn't need skimage, it's easy as flipping the image array of pixels !
return image_array[:, ::-1]
以下はオリジナルの画像です。
画像をランダムに回転させると以下のような回転画像となります。
80枚の画像からなる元データを、ランダムに60枚の学習用データと20枚のテスト用データに分割します。学習用データの60枚の画像に対して、Amazon Rekognition Custom Labelsを用いてオリジナルモデルを構築します。
拡張モデルを構築するには、回転と水平反転を含む前述のコードを実行することで、60枚の学習データ画像を追加増強しました。これにより、学習データのサイズは60枚から180枚に増加し、これらのデータセットを使って、新しいモデルを構築します。
この2つのモデルを構築した後、これらのモデルのテストデータを実行し、両方の結果を比較します。この結果を得るために以下のコードを使用します:
# Helper method that makes a inference on a Custom Labels trained model for a binary classification problem
import boto3
import io
from botocore.exceptions import ClientError
def getLabelAndConfidenceBinary(bucket, image, model):
"""
:param bucket: The name of the S3 bucket that contains the image that you want to analyze.
:param image: The name of the image that you want to analyze.
:param model: The ARN of the Amazon Rekognition Custom Labels model that you want to use.
"""
rek_client=boto3.client('rekognition')
s3_connection = boto3.resource('s3')
# set minimum confidence to 0 as we are interested in results for all condidence levels
min_confidence = 0
try:
#Get image from S3 bucket.
s3_object = s3_connection.Object(bucket, image)
s3_response = s3_object.get()
#Call DetectCustomLabels
response = rek_client.detect_custom_labels(Image={'S3Object': {'Bucket': bucket, 'Name': image}},
MinConfidence=min_confidence,
ProjectVersionArn=model)
except ClientError as err:
print("Exception in get_custom_labels()")
raise
if response['CustomLabels'][0]["Confidence"] >= response['CustomLabels'][1]["Confidence"]:
return response['CustomLabels'][0]["Name"], response['CustomLabels'][0]["Confidence"]
else:
return response['CustomLabels'][1]["Name"], response['CustomLabels'][1]["Confidence"]
# Helper method that iterates through an S3 bucket and retrieves image labels
def getImageNames(bucket, prefix):
import boto3
client = boto3.client('s3')
paginator = client.get_paginator('list_objects_v2')
result = paginator.paginate(Bucket=bucket, Prefix=prefix)
images = []
for page in result:
if "Contents" in page:
for key in page[ "Contents" ]:
keyString = key[ "Key" ]
name = keyString.split("/")[-1]
iclass = name.split("-")[0]
if len(iclass)>0:
images.append([keyString, iclass])
return images
# This code loops through all images and creates a y_true list objects based on image labels
image_folder = "blog/REK-CL/utilitypoles/new_test_images/"
y_true = []
names = []
test_images = getImageNames('my-bucket', image_folder)
for image in test_images:
iclass = image[1]
name = image[0]
names.append(name)
if iclass=="bad":
y_true.append(1)
else:
y_true.append(0)
# The helper method below makes predictions on a deploy Custom Labels model and returns predictions
# Custom Labels associates confidence level to the label it predicts
# In the code below, we are turning that to a [0 to 1] probability scale, where 1 indicates a broken pole
def getPredictions(model, bucket, images):
y_pred = []
y_prob = []
for image in images:
labelconf = getLabelAndConfidenceBinary(bucket, image[0], model)
if labelconf[0]=="broken":
y_pred.append(1)
prob = labelconf[1]/100.0
y_prob.append(prob)
if labelconf[0]=="good":
y_pred.append(0)
prob = 1.0 - labelconf[1]/100.0
y_prob.append(prob)
if labelconf[0]=="":
raise Exception("Invalid label")
return y_pred, y_prob
bucket = "my-bucket"
# Assign Project Version ARN returned by train_model() below for both the models
original_model = "arn:aws:rekognition:us-east-1:xxxxxxxxxxxx:project/upole-new-aug14/version/upole-new-aug14.2021-08-14T17.20.06/1628976006624"
enhanced_model = "arn:aws:rekognition:us-east-1: xxxxxxxxxxxx:project/upole-new-aug14/version/upole-new-aug14.2021-08-14T18.36.45/1628980605880"
y_pred_original, y_prob_original = getPredictions(original_model, bucket, test_images)
y_pred_enhanced, y_prob_enhanced = getPredictions(enhanced_model, bucket, test_images)
from sklearn.metrics import accuracy_score
print("Original model accuracy = ", round(accuracy_score(y_true, y_pred_original), 2))
print("Enhanced model accuracy = ", round(accuracy_score(y_true, y_pred_enhanced), 2))
------ 上記コードの出力結果 -------
Original model accuracy = 0.79
Enhanced model accuracy = 0.89
--------------------------------
import numpy as np
from sklearn import metrics
def calculateAUC(y_true, y_prob, pos_label):
fpr, tpr, thresholds = metrics.roc_curve(y_true, np.array(y_prob), pos_label=pos_label)
return metrics.auc(fpr, tpr)
print("Original model AUC = ", round(calculateAUC(y_true, y_prob_original, 1),2))
print("Enhanced model AUC = ", round(calculateAUC(y_true, y_prob_enhanced, 1),2))
---- 上記コードの出力結果 ----
Original model AUC = 0.92
Enhanced model AUC = 0.96
---------------------------
パフォーマンスレビュー
モデルの結果からわかるように、image augmentation を行うことで、モデルの精度が0.79から0.89に、モデルのAUCが0.92から0.96に大幅に改善されました。
元のモデルはそのままでも良い結果が出ましたが、image augmentation をすることでさらに結果が良くなりました。必ずしも常に image augmentation をする必要はありませんが、このテクニックを使って、さらにモデルを改善できるかどうか試してみてください。
クリーンアップ
このソリューションの使用を終了した後、課金を停止するためにモデルを停止することが重要です。プロジェクトを削除するには、次の関数を実行します。
def delete_project(project_arn):
client = boto3.client(‘rekognition’)
print(‘Deleting project: ’ + project_arn)
response = client.delete_project(ProjectArn=project_arn)
print(‘Status: ’ + response[‘Status’])
print(‘Done…’)
まとめ
本投稿では、壊れたり損傷した電柱を検出するためのカスタムMLモデルの学習、評価、推論に成功しました。この時間のかかる手動タスクは、通常、人為的なミスや作業遅延の影響を非常に受けやすいものでしたが、Amazon Rekognition Custom Labels を使用することで、精度を維持しながらこのプロセスをより速くすることができました。
カスタムラベルについてさらに詳しい情報は、「What Is Amazon Rekognition Custom Labels?」をご覧ください。
本ブログは、ソリューションアーキテクトの丹羽が翻訳しました。原文はこちらです。
著者について:
Winston Nwanne は、AWSソリューションアーキテクトとして、公共部門のパートナーと仕事をしています。AI/MLを専門とし、お客様やパートナーのAWSクラウド内での機能拡張を支援しています。顧客サポート以外では、読書、金融リテラシーに関するYouTube動画の作成、バスケットボール、そして家族と過ごすことが好きです。
Raju Penmatcha はAWSのシニアAI/MLスペシャリスト・ソリューション・アーキテクトです。教育、政府、非営利団体のお客様を対象に、機械学習や人工知能関連のプロジェクトに取り組み、AWSを使ったソリューションの構築を支援しています。顧客支援をしていないときは、新しい場所に旅行するのが好きです。