この記事は更新から24ヶ月以上経過しているため、最新の情報を別途確認することを推奨いたします。
【90分】AzureのAI を活用して野生のシロクマを追跡する
DA-100という資格を取る過程で勉強したラーニングパスでとても面白いものがあったので紹介します。
以下のラーニングパスを実勢した内容をこの記事で紹介していきます。
出典:https://docs.microsoft.com/ja-jp/learn/modules/build-ml-model-with-azure-stream-analytics/
このラーニングパスでのハンズオンの成果物
仮想カメラで撮影した写真をAzure Custom Visionで解析し
写真に写っている動物を分類します。 写真のメタデータから撮影場所の情報を取得し、Power BIで白くま生息地を分析するためのレポートを作成します。
必要なAzureサービスの構成と説明
色々なAzureサービスを駆使して、ラーニングパスで成果物を作成していくのですが
各サービスの構成と利用目的については以下のように理解しています。
このラーニングパスを実勢するご利益
Azure FunctionやAzure Custom VisionやAzure Storare/SQL Database等の主要なAzureサービスに一通り触れて理解することができる。
Power BIの基本機能から外部データソースとの連携/視覚化の装飾等を学ぶことができると思います。
Azure Functionではフローの自動化を学ぶことができました。
Azure Custom Visionでは写真の分類に必要な学習モデルの作成方法から分類まで、他サービスとの連携方法を広く学ぶことができました。
Azure Storare/SQL Databaseでは基本な機能から、他サービスとの連携方法を学ぶことができました。
Power BIについても基本機能のから外部データソースとの連携/視覚化の装飾等を学ぶことができると思います。
実践していく
基本的にラーニングパスに沿ってやったことを覚書としてかいています。このハンズオンに興味がある方は是非、本家のラーニングパスを実勢してみてください。
本家はこちら:https://docs.microsoft.com/ja-jp/learn/modules/build-ml-model-with-azure-stream-analytics/
アカウントを作成し、仮想カメラをデプロイしていきます。
Azure Portalに接続して、Cloud Shell を使っていきます。
polar-bear-rgという名前のリソースグループを作成します。
az group create --name polar-bear-rg --location southcentralus
sirokumasanというアカウント名の変数を作成します。
ACCOUNT_NAME=sirokumasan
先ほど作成したリソースグループにsirokumasanというアカウント名のストレージを作成します。
az storage account create --name $ACCOUNT_NAME --resource-group polar-bear-rg --location southcentralus --sku Standard_LRS
ストレージにphotosというコンテナを作成します。
az storage container create --name photos --account-name $ACCOUNT_NAME
アカウントキーを取得して、メモに控えておきます
az storage account keys list --account-name $ACCOUNT_NAME
VSCodeでnode環境を作成する
ターミナルを開いて、作業用フォルダを作成し、フォルダに移動します。
mkdir C:\sirokumasan
cd C:\sirokumasan
プロジェクトを作成し、先ほどPortalで作成したストレージへアクセスするためのライブラリを取得します。
npm init -y
npm install azure-storage --save
プロジェクトにphotosというフォルダを作成します。
作成したフォルダに以下から取得した画像を格納していきます。
プロジェクトにcameras.json を作成します。
[
{
"deviceId" : "polar_cam_0001",
"latitude" : 75.401451,
"longitude" : -95.722518
},
{
"deviceId" : "polar_cam_0002",
"latitude" : 75.027715,
"longitude" : -96.041859
},
{
"deviceId" : "polar_cam_0003",
"latitude" : 74.996653,
"longitude" : -96.601780
},
{
"deviceId" : "polar_cam_0004",
"latitude" : 75.247701,
"longitude" : -96.074436
},
{
"deviceId" : "polar_cam_0005",
"latitude" : 75.044926,
"longitude" : -93.651951
},
{
"deviceId" : "polar_cam_0006",
"latitude" : 75.601571,
"longitude" : -95.294407
},
{
"deviceId" : "polar_cam_0007",
"latitude" : 74.763102,
"longitude" : -95.091160
},
{
"deviceId" : "polar_cam_0008",
"latitude" : 75.473988,
"longitude" : -94.069432
},
{
"deviceId" : "polar_cam_0009",
"latitude" : 75.232307,
"longitude" : -96.277683
},
{
"deviceId" : "polar_cam_0010",
"latitude" : 74.658811,
"longitude" : -93.783787
}
]
プロジェクトにrun.jsを作成します。
'use strict';
// Connect to the storage account
var storage = require('azure-storage');
var blobService = storage.createBlobService(
process.env.ACCOUNT_NAME,
process.env.ACCOUNT_KEY
);
// Load image file names and create an array of cameras
var fs = require('fs');
fs.readdir('photos', (err, files) => {
var cameras = JSON.parse(fs.readFileSync('cameras.json', 'utf8')).map(
camera => new Camera(
camera.deviceId,
camera.latitude,
camera.longitude,
blobService,
files
)
);
// Start the cameras
cameras.forEach(camera => {
camera.start();
});
});
class Camera {
constructor(id, latitude, longitude, blobService, files) {
this._id = id;
this._latitude = latitude;
this._longitude = longitude;
this._blobService = blobService;
this._files = files.slice(0);
this._interval = 300000;
}
start() {
// Register the first callback
setTimeout(this.timer, Math.random() * this._interval, this);
console.log('Started ' + this._id);
}
timer(self) {
// Randomly select a photo
var index = Math.floor(Math.random() * self._files.length);
var filename = self._files[index]
// Define the metadata to be written to the blob
var metadata = {
'latitude': self._latitude,
'longitude': self._longitude,
'id': self._id
};
// Upload the blob
self._blobService.createBlockBlobFromLocalFile('photos', filename, 'photos/' + filename, { 'metadata': metadata }, (err, result) => {
if (!err) {
console.log(self._id + ': Uploaded ' + filename);
}
else {
console.log(self._id + ': Error uploading ' + filename);
}
});
// Register the next callback
setTimeout(self.timer, Math.random() * self._interval, self);
}
}
VSCodeのターミナルでストレージアカウントへの接続情報を設定します。
set ACCOUNT_NAME=sirokumasan
set ACCOUNT_KEY=<事前にメモしたアカウントキー>
VSCodeのターミナルでストレージアカウントへの接続情報を設定します。
set ACCOUNT_NAME=sirokumasan
set ACCOUNT_KEY=<事前にメモしたアカウントキー>
run.jsを実行します。
node run.js
※うまくいかない場合はrun.jsにアクセス情報を直書きしてみると良いかもしれません。
シロクマを認識するように機械学習モデルをトレーニングしてみる
Custom Visionポータルにアクセスしてサインインします。
新しいプロジェクトを作成し、設定していきます。
新しいプロジェクトを作成します
プロジェクトを設定していきます。
リソース: polar-bear-vision
プロジェクトの種類: “分類”
分類の種類: Multiclass (Single tab per image) (マルチクラス (画像ごとに 1 つのタブ))
ドメイン: General (一般)
プロジェクトで使用するホッキョクギツネの画像を取得して設定していきます。
画像をアップロードしていきます。
ホッキョクギツネとして『arctic-fox』とタグづけます。
プロジェクトで使用する白くまの画像を取得して設定していきます。
画像をアップロードして白くまとして『polar-bear』とタグづけます。
プロジェクトで使用するセイウチの画像を取得して設定していきます。
画像をアップロードしてセイウチとして『walrus』とタグづけます。
モデルをトレーニングしていきます。
トレーニングタイプはクイックを指定します
トレーニングが終わると以下のような画面が表示されます。 完了までに少し時間がかかります。
クイックテストを行っていきます。
クイックテストの実行
テストで使用する画像は以下から取得します。
https://github.com/MicrosoftDocs/mslearn-build-ml-model-with-azure-stream-analytics/raw/master/testing-images/testing-images.zip
クイックテストの実行結果
画像にシロクマが含まれるかどうかのモデルによる予測が十分満足できるレベルに熟達するまで
これらの画像を活用して繰り返しテストを行います。
モデルを発行します
Publishを選択します
Prediction resourceを選択してモデルを発行します
Prediction URLから接続情報を取得します
赤枠の部分を控えます
Azure Portalでファンクションを作成します。
リソースの作成から関数アプリを選択します
関数アプリを作成します
関数を作成します
BlobTriggerのindex.jsを編集していきます。
ストレージコネクションストリングはストレージアカウントの接続文字列を設定します。
※Pathの部分は以下のようにphotos/{name}として監視するコンテナ名を指定する必要があります
module.exports = function (context, myBlob) {
var predictionUrl = process.env.PREDICTION_URL;
var predictionKey = process.env.PREDICTION_KEY;
var storageConnectionString = process.env.<CONNECTION_STRING_NAME>;
var storage = require('azure-storage');
var blobService = storage.createBlobService(storageConnectionString);
var blobName = context.bindingData.name;
var blobUri = context.bindingData.uri;
// Read the blob's metadata
blobService.getBlobMetadata('photos', blobName, (err, result, response) => {
if (!err) {
var latitude = result.metadata.latitude;
var longitude = result.metadata.longitude;
var id = result.metadata.id;
// Generate a shared access signature for the Custom Vision service
var now = new Date();
var expiry = new Date(now).setMinutes(now.getMinutes() + 3);
var policy = {
AccessPolicy: {
Permissions: storage.BlobUtilities.SharedAccessPermissions.READ,
Start: now,
Expiry: expiry
},
};
var sas = blobService.generateSharedAccessSignature('photos', blobName, policy);
// Pass the blob URL to the Custom Vision service
var request = require('request');
var options = {
url: predictionUrl,
method: 'POST',
headers: {
'Prediction-Key': predictionKey
},
body: {
'Url': blobUri + '?' + sas
},
json: true
};
request(options, (err, result, body) => {
if (!err) {
var probability = body.predictions.find(p => p.tagName.toLowerCase() === 'polar-bear').probability;
var isPolarBear = probability > 0.8; // 80% threshold
if (isPolarBear) {
context.log('POLAR BEAR detected by ' + id + ' at ' + latitude + ', ' + longitude);
}
else {
context.log('Other wildlife detected by ' + id + ' at ' + latitude + ', ' + longitude);
}
context.done();
}
else {
context.log(err);
context.done();
}
});
}
else {
context.log(err);
context.done();
}
});
};
関数アプリのコンソールで以下のコマンドを実行します。
npm install request
npm install azure-storage
警告は無視していいらしい。
関数アプリのに前工程で取得したPrediction-URLとPrediction-Key を設定していきます。
設定後にブロブに新たに画像を追加すると、トリガーが作動して画像を自動で認識して分類することを確認することができます。
画像は以下の画像をアップロードしました。
https://docs.microsoft.com/ja-jp/learn/modules/build-ml-model-with-azure-stream-analytics/media/image-12.jpg
ログを確認し、ログ上でカスタムビジョンと連携し、画像が正しく認識され分類されたことを確認します。
※何回か試したので、ファイル名はimage-16.jpgになってます。
(インクリメントしながら確認しました)
VScodeでもう一度run.jsを実行して、ストレージに写真を送信してみます。
カスタムビジョンが実行され、VScodeから送信した画像から白くまを検出していることがわかります!
Azure SQL Databaseをデプロイする
Azure Portalからクラウドシェルを開きます
作成するデータベース情報を変数セットしておきます
SERVER_NAME="<server-name>"
ADMIN_USERNAME="<admin-username>"
ADMIN_PASSWORD="<admin-password>"
DATABASE_NAME="<database-name>"
※パスワードはパスワードバリデーションチェックにひっかからないようにやや複雑目なパスワードを設定することを推奨します。
データベースサーバーを作成します
az sql server create --name $SERVER_NAME --resource-group polar-bear-rg --location westus --admin-user $ADMIN_USERNAME --admin-password $ADMIN_PASSWORD
※デフォルトのロケーションはnorthcentralusになっていますが対象ロケーションだと作成エラーになるのでロケーションはwestusに変更しています。
データベースを作成します
az sql db create --resource-group polar-bear-rg --server $SERVER_NAME --name $DATABASE_NAME --service-objective S0
リソースグループにSQLデータベースができていることを確認します
SQLサーバーのファイアウォールの設定を変更し、あとでPower BIデスクトップから接続できるようにしておきます
SQLデータベースのクエリエディタでSQLを実行して、テーブルを作成します
SQL
CREATE TABLE [dbo].[PolarBears]
(
[Id] [uniqueidentifier] NOT NULL,
[CameraId] [nvarchar](16) NULL,
[Latitude] [real] NULL,
[Longitude] [real] NULL,
[Url] [varchar](max) NULL,
[Timestamp] [datetime] NULL,
[IsPolarBear] [bit] NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
)
ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE [dbo].[PolarBears] ADD DEFAULT (newid()) FOR [Id]
GO
ALTER TABLE [dbo].[PolarBears] ADD DEFAULT (getdate()) FOR [Timestamp]
GO
ALTER TABLE [dbo].[PolarBears] ADD DEFAULT ((0)) FOR [IsPolarBear]
GO
テーブルが作成できていることを確認します。
関数を変更します。
関数アプリのコンソールでnpm installを実行します
npm install tedious
tedious パッケージにより、Node.js アプリから SQL Server や Azure SQL Database と接続が可能になります。
最初のほうに作ったBlob Triggerのコードとテストを開きます。
コードにデータベースへの接続情報を追記します。 ダブルクォーテーション内は自身が設定した接続情報で置き換えます。
var databaseUserName = process.env.DATABASE_USER_NAME;
var databasePassword = process.env.DATABASE_PASSWORD;
var databaseServer = 'DATABASE_SERVER_NAME.database.windows.net';
var databaseName = 'DATABASE_NAME';
関数を一部差し換えます
◆差し換え前
if (isPolarBear) {
context.log('POLAR BEAR detected by ' + id + ' at ' + latitude + ', ' + longitude);
}
else {
context.log('Other wildlife detected by ' + id + ' at ' + latitude + ', ' + longitude);
}
context.done();
◆差し換え後
以下、本家のコードに足りてない部分があり追加しました。
dbConnection.connect();
上記のコードが抜けているとSQLDatabaseへの接続エラーが出てしまいます。
2021/9/13時点では本家のコードだけではエラーになります。
// Update the database
var Connection = require('tedious').Connection;
var Request = require('tedious').Request;
var config =
{
authentication:
{
type: 'default',
options:
{
userName: databaseUserName,
password: databasePassword
}
},
server: databaseServer,
options:
{
database: databaseName,
encrypt: true
}
};
var dbConnection = new Connection(config);
dbConnection.on('connect', err => {
if (!err) {
var query = "INSERT INTO dbo.PolarBears (CameraId, Latitude, Longitude, URL, Timestamp, IsPolarBear) " +
"VALUES ('" + id + "', " + latitude + ", " + longitude + ", '" + blobUri + "', GETDATE(), " + (isPolarBear ? "1" : "0") + ")";
var dbRequest = new Request(query, err => {
// Called when request completes, with or without error
if (err) {
context.log(err);
}
dbConnection.close();
context.done();
});
dbConnection.execSql(dbRequest);
}
// 以下、本家のコードに足りてない部分があり追加しました。
dbConnection.connect();
else {
context.log(err);
context.done();
}
});
VSCodeで再度run.jsを起動します
クエリエディタでデータが蓄積できていることを確認します
Power BI レポートを作製する
Power BIデスクトップを起動してSQLデータベースに接続します
Power BIデスクトップを起動してSQLデータベースに接続します
SQLステートメント
SELECT TOP 20 Id, CameraId, Latitude, Longitude, Url, Timestamp, FORMAT(Timestamp,'MM/dd/yyyy h:mm:ss tt') AS TimestampLabel, IsPolarBear FROM dbo.PolarBears ORDER BY Timestamp DESC
接続の方法をデータベースに切り替えて、接続していきます。
いよいよデータベースの情報を視覚化していきます!!
視覚化で白くま判定と経度情報を設定していきます。
各経度の情報を集計しないように設定していきます。
少しずつ形になっています
空きスペースにCameraId、IsPolarBear、TimestampLabelの表を作成します
空きスペースにIsPolarBear と Latitudeの円グラフを作成します
視覚化の表示の値をカウントに切り替えます
IsPolarBearのスライサーを作成します
装飾していきます
動作確認していく
SQLデータベースの情報を削除します
DELETE FROM dbo.PolarBears
VSCodeでデータを再投入します
node run.js
Power BIのデータを更新します
データが更新され、白くまの生息地を追うことができるようになります
非常に面白いラーニングパスでした。
これからAzureを学ばれる方に是非おすすめしたいラーニングパスだと思います。