#はじめに
MicrosoftのCustomVisionというサービスで、
はやい!(試行錯誤含めて3時間くらい)
やすい!(無料アカウント)
うまい!(お寿司はおいしい)
で、めちゃくちゃ気軽に機会学習ができるとのことなので、LINEbotを作ってみました。
事前準備
CustomVisionの登録
MicrosoftアカウントのOutlookメールアドレスを取得する方法
Microsoft Learn | Microsoft Docs にアクセスしてサインインします
CustomVisionの準備
学習用の画像を準備します
google-images-downloadを使うとそれ用のフォルダを勝手に作ってダウンロードしてくれるのでめっちゃ便利でした
参考 -> LINE動物図鑑の作り方
画像が準備できたらこちらのトレーニングを参考に画像判定用のモデルを作成します
こちら (ただし、うまくAPIを発行できない場合があるので事前にこちらを参考に、事前にAzure Portal側でCustom Vision用リソースグループやCustom Visionリソースを作っておくと上手くいきました(2019/08/29 現在))
今回は、「お寿司屋さんで写真を取ってLINEbotに送る」ことを想定してたので、そういう感じの画像に選別します
だんだん、「これは本当にマグロなのか?マグロとはなんなのか?」とゲシュタルト崩壊してきて興味深いです
タグを登録し、先ほどダウンロードした画像たちをアップロードします
登録し終えたら、学習させます。
テストしてみると、イクラはかなりの精度で判定できましたが、
ハマチは少し微妙でした。
Prediction APIをクリックすると以下のようなポップアップが表示されます
今回使うのは赤枠の部分のURLとPrediction-keyの二つなので控えておきます
他参考
https://azure-recipe.kc-cloud.jp/2017/12/custom_vision_2017adcal/
https://azure.microsoft.com/ja-jp/services/cognitive-services/custom-vision-service/
実装
LINEbotの準備
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefestを参考にLINEのbotの作成を行います
APIは個人で使う想定(金額的な意味で)なのでBotはngrokを使って公開します
'use strict';
//必要なものは都度 npm i してください
const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3002;
const cv = require('customvision-api');
const fs = require('fs');
const bodyParser = require('body-parser');
const Request = require('request');
const line_config = {
channelSecret: 'botのチャンネルシークレット',
channelAccessToken: 'botのアクセストークン'
};
const app = express();
const client = new line.Client(line_config);
app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない)
app.post('/webhook', line.middleware(line_config), (req, res) => {
console.log(req.body.events);
// botに送られる画像にアクセスするための設定
const options = {
url: `https://api.line.me/v2/bot/message/${req.body.events[0].message.id}/content`,
method: 'get',
headers: {
'Authorization': 'Bearer ' + line_config.channelAccessToken,
},
encoding: null
};
Request(options, function(error, response, body) {
if (!error && response.statusCode == 200) {
//画像を保存します
fs.writeFileSync(`/tmp/` + req.body.events[0].message.id + `.jpg`, new Buffer(body), 'binary');
console.log('file saved');
const filePath = `/tmp/` + req.body.events[0].message.id + `.jpg`;
const cv_config = {
"predictionEndpoint": "先ほどpublishしたPrediction APIのURL",
"predictionKey": "先ほどpublishしたPrediction APIのpredictionKey"
};
cv.sendImage(
filePath,
cv_config,
(data) => {
console.log(data);
// => 出力結果
// { id: 'xxxxxxxxxxxxxxxxxxxxxxxxx',
// project: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
// iteration: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
// created: '2019-08-28T15:01:24.855Z',
// predictions:
// [ { probability: 0.833851159,
// tagId: 'a5f078e9-490d-4040-afbf-6918d7744011',
// tagName: 'hamachi' },
// { probability: 0.09781464,
// tagId: '35660c82-9df6-4511-9bd6-0e26db54ad4c',
// tagName: 'maguro' },
// { probability: 0.013582103,
// tagId: '5d170ee1-9d02-40f6-ab8c-addeaec6cefc',
// tagName: 'engawa' },
// { probability: 0.011500218,
// tagId: '37f42876-ce01-4cc3-8951-d7169e41b936',
// tagName: 'tako' },
// { probability: 0.0102164745,
// tagId: 'c46ae2a3-d532-4b5f-8287-2ebd5537583d',
// tagName: 'salmon' },
// { probability: 0.00753958151,
// tagId: 'b1a939d5-2a61-4518-90bf-dfd8542e646d',
// tagName: 'hotate' },
// { probability: 0.00548384944,
// tagId: '0d944222-e4e5-4b95-9084-8c17c52c6ea2',
// tagName: 'tobikko' },
// { probability: 0.005075191,
// tagId: '8f7b69ab-877e-464c-91d8-f5faf2d32633',
// tagName: 'ikura' },
// { probability: 0.004676578,
// tagId: 'c1a43c10-6bcd-4893-b5b0-3016816e3c91',
// tagName: 'ebi' },
// { probability: 0.00426511373,
// tagId: 'fbe51510-a6f3-4fba-a141-afa2ed6fcec0',
// tagName: 'ika' },
// { probability: 0.00379423774,
// tagId: '730eb665-a967-444f-9a0f-7cd16527e249',
// tagName: 'tamago' },
// { probability: 0.00220071618,
// tagId: '9e1789d3-dd45-48b9-b116-5aa8f711afec',
// tagName: 'hokkigai' } ] }
let tagName = data.predictions[0].tagName;
let probability = data.predictions[0].probability;
client.replyMessage(req.body.events[0].replyToken, {
type: 'text',
text: tagName + 'の確率、' + probability
});
}
);
}
})
Promise
.all(req.body.events.map(handleEvent))
.then((result) => res.json(result));
})
app.listen(PORT);
console.log(`Server running at ${PORT}`);