機械学習や組合最適化の知識をブラッシュアップすべく、python・scikit-learn・Azure MLStudioを触っていますが、Azure MLStudioのモデルを実用っぽいアプリにしたいと考え、Azure MLStudioでモデルを作って、それをWebAppsアプリからREST APIを通じて呼び出してみました。
※2019/06/22 無料版のAzure MLStudioではクラシックWebAPIしか利用できません。今までは
課金される標準版で動かしていましたが、もったいないので無料版をクラシックWebAPIで呼ぶように
書き換え、そのソースも追加しました。Azureの無償/有償については、こちらで説明しています。
###demo→12
※7/12環境設定を拡張してライブラリを追加する方法で環境を再構築
#手順
1.Azure ML Studioでモデルを作成しAPIとして公開する。
2.入力項目の選択枝を得る為に、サンプルデータをダウンロードしてExcelで開いて「データ⇒重複の削除」で値を得ました。
3.Azure ML Studioでモデルに接続するPythonのクライアントアプリが生成されるので、それをローカルのAnaconda(Jupyter Notebook)にコピーして、必要な修正を施す。
4.ローカルのFlask実行環境で入力画面を作成し、3のソースをベースにサーバーサイドアプリを作成する。(今回私私自身は、4で試行錯誤してしまいましたが3の段階で、APIへ渡すjsonデータ・戻ってきたjsonデータの扱いをきちんとしてから3に進んだ方が効率的だったなと思いました。)
5.Azure WebAppsに移行する。
といった手順で行いました。
#Azure ML Studioでモデルを作成
これについては、下記サイトをそのまま踏襲して、「米国国勢調査局提供の、成人収入に関する二項分類データセット」を用いて、条件を入力して年収が5万ドル以下/大なりを判定するモデルを作成しました。Webアプリに利用したのは、ARM対応版のWebサービスで、下記例はサンプルソースC#ですが、Pythonを利用します。
Azure Machine Learningを始めよう(1)- MLStudioの使い方
#Anaconda(Jupyter Notebook)上で必要な修正を施す
3のサンプルソースを動かすだけであれば、api_keyを修正するだけで動きます。ただ戻ってきたjsonデータのハンドリングもこの段階でしていた方が良いです。ハンドリングは下記リンクのMicrosoft Docsの「Azure Machine Learning Studio」の「API Management を使用して Azure Machine Learning Studio Web サービスを管理する」のサンプルソースが参考になりました。
BES エンドポイントのテスト
body = str.encode(json.dumps(data))
url = 'https://japaneast.serv~割愛~'
api_key = '764McF0w85~割愛~'
headers = {'Content-Type':'application/json', 'Authorization':('Bearer '+ api_key)} #Bearer=無記名
req = urllib.request.Request(url, body, headers)
try:
response = urllib.request.urlopen(req)
result = response.read()
print(result)
except urllib.error.HTTPError as error:
print("The request failed with status code: " + str(error.code))
print(error.info())
print(json.loads(error.read().decode("utf8", 'ignore')))
result_dic = json.loads(result) #json形式を辞書型に変換
results = result_dic["Results"] #"Results"の値を取り出して
for outputName in results: #複数の値が返る場合"output1"・"output2"・・のインデックスが付いて戻る
result_blob_location = results[outputName]
out_sl = result_blob_location[0]['Scored Labels']
forで回しているとこは一行しかなければ「out_sl = result_dic["Results"]["output1"][0]['Scored Labels']」で良いです。私は[0]が必要な事に気づかずハマリました。
#ローカルのFlask実行環境で入力画面とサーバーサイドアプリを作成
これもhtmlから渡したjsonをサーバサイドで受け取って、MLに渡すjsonの情報を付けて渡すところでなかなかうまくいきませんでした。結局サンプルの形式のまま値だけrequest.json['キー']で置き換えるようにしていますが、htmlの時点でMLに渡すjson形式にしてしまえば良いのかもしれません。
<!--*****************************************************************
* azureMLサンプル:米国国勢調査提供の収入のサンプルの訓練結果にrestで接続
*
* 2019/03/24
*****************************************************************-->
{% extends "base.html" %}
{% block body %}
<div class="card" >
<div class="card-header" style="height:50px; font-size:1.5rem; ">
<b>azure ML webアプリサンプル</b>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-2 mb-1">年齢</div> <!--col-md-1:mdはPCのMIDDLEサイズ。mb-1はマージン(空白)をボトム(下)に設定-->
<div class="col-md-3 mb-1" >
<select class="form-control input-lg mb-1" id="age">
<option>20</option>
<option>25</option>
<option>30</option>
<option>35</option>
<option>40</option>
<option>45</option>
<option>50</option>
<option>55</option>
<option>60</option>
<option>65</option>
<option>70</option>
<option>75</option>
<option>80</option>
</select>
</div>
<div class="col-md-2 mb-1">ワーククラス</div>
<div class="col-md-5 mb-1" >
<select class="form-control input-lg mb-1" id="workclass">
<option value="Federal-gov">Federal-gov</option>
<option value="Local-gov">Local-gov</option>
<option value="Never-worked">Never-worked</option>
<option value="Private">Private</option>
<option value="Self-emp-inc">Self-emp-inc</option>
<option value="Self-emp-not-inc">Self-emp-not-inc</option>
<option value="State-gov">State-gov</option>
<option value="Without-pay">Without-pay</option>
<option value="Without-pay">Without-pay</option>
</select>
</div>
</div>
<div class="row">
<div class="col-md-2 mb-1">卒業</div>
<div class="col-md-2 mb-1" >
<select class="form-control input-lg mb-1" id="education-num">
<option value="1">Preschool</option>
<option value="2">1st-4th</option>
<option value="3">5th-6th</option>
<option value="4">7th-8th</option>
<option value="5">9th</option>
<option value="6">10th</option>
<option value="7">11th</option>
<option value="8">12th</option>
<option value="9">HS-grad</option>
<option value="10">Some-college</option>
<option value="11">Assoc-voc</option>
<option value="12">Assoc-acdm</option>
<option value="13">Bachelors</option>
<option value="14">Masters</option>
<option value="15">Prof-school</option>
<option value="16">Doctorate</option>
</select>
</div>
<div class="col-md-1 mb-1">結婚</div>
<div class="col-md-3 mb-1" >
<select class="form-control input-lg mb-1" id="marital-status">
<option value="Divorced">Divorced</option>
<option value="Married-AF-spouse">Married-AF-spouse</option>
<option value="Married-civ-spouse">Married-civ-spouse</option>
<option value="Married-spouse-absent">Married-spouse-absent</option>
<option value="Never-married">Never-married</option>
<option value="Separated">Separated</option>
<option value="Widowed">Widowed</option>
</select>
</div>
<div class="col-md-1 mb-1">関係</div>
<div class="col-md-3 mb-1" >
<select class="form-control input-lg mb-1" id="relationship">
<option value="Husband">Husband</option>
<option value="Not-in-family">Not-in-family</option>
<option value="Other-relative">Other-relative</option>
<option value="Own-child">Own-child</option>
<option value="Unmarried">Unmarried</option>
<option value="Wife">Wife</option>
</select>
</div>
</div>
<div class="row">
<div class="col-md-2 mb-1">人種</div>
<div class="col-md-2 mb-1" >
<select class="form-control input-lg mb-1" id="race">
<option value="Amer-Indian-Eskimo">Amer-Indian-Eskimo</option>
<option value="Asian-Pac-Islander">Asian-Pac-Islander</option>
<option value="Black">Black</option>
<option value="Other">Other</option>
<option value="White">White</option>
</select>
</div>
<div class="col-md-2 mb-1">性別</div>
<div class="col-md-2 mb-1" >
<select class="form-control input-lg mb-1" id="sex">
<option value="Male">Male</option>
<option value="Female">Female</option>
</select>
</div>
</div>
<div class="row">
<div class="col-md-2 mb-1">キャピタルゲイン</div>
<div class="col-md-2 mb-1" >
<input type="text" class="form-control input-lg" id="capital-gain" value=0 >
</div>
<div class="col-md-2 mb-1">キャピタルロス</div>
<div class="col-md-2 mb-1" >
<input type="text" class="form-control input-lg" id="capital-loss" value=0 >
</div>
</div>
<div class="row">
<div class="col-md-2 mb-1">労働時間</div>
<div class="col-md-2 mb-1" >
<input type="text" class="form-control input-lg" id="hours-per-week" value=40 >
</div>
<div class="col-md-1 mb-1">国</div>
<div class="col-md-3 mb-1" >
<select class="form-control input-lg mb-1" id="native-country">
<option value="Cambodia">Cambodia</option>
<option value="Canada">Canada</option>
<option value="China">China</option>
<option value="Columbia">Columbia</option>
<option value="Cuba">Cuba</option>
<option value="Dominican-Republic">Dominican-Republic</option>
<option value="Ecuador">Ecuador</option>
<option value="El-Salvador">El-Salvador</option>
<option value="England">England</option>
<option value="France">France</option>
<option value="Germany">Germany</option>
<option value="Japan">Japan</option>
<option value="Laos">Laos</option>
<option value="Mexico">Mexico</option>
<option value="United-States">United-States</option>
<option value="Vietnam">Vietnam</option>
<option value="Yugoslavia">Yugoslavia</option>
</select>
</div>
<div class="col-md-3 mb-1" >
<button class="btn btn-default" id="button" style="width: 100px; padding: 5px;">Load Data</button>
</div>
</div>
</div> <!--class="card-body"の終端-->
</div> <!--class="card"の終端-->
<script type="text/javascript">
"use strict";
var send_data = {};
$("#button").click(function() {
send_data = JSON.stringify({
"age":$("#age").val(),
"workclass":$("#workclass").val(),
"education":" ",
"education-num":$("#education-num").val(),
"marital-status":$("#marital-status").val(),
"relationship":$("#relationship").val(),
"race":$("#race").val(),
"sex":$("#sex").val(),
"capital-gain":$("#capital-gain").val(),
"capital-loss":$("#capital-loss").val(),
"hours-per-week":$("#hours-per-week").val(),
"native-country":$("#native-country").val()
});
$.ajax({
type:'POST',
url:'/azureML_cal',
data:send_data,
contentType:'application/json',
success:function(data) {
alert("結果 = " + data );},
error: function(data) {
alert('error!!!' + JSON.stringify(data) );
}
});
return false;
});
</script>
{% endblock %}
import os
import sys
import platform
sys.path.append('D:/home/site/wwwroot/Lib/site-packages')
from flask import Flask, render_template, request, jsonify, send_file, make_response, send_from_directory, request, jsonify, session, redirect, url_for
import json
import sqlite3
import werkzeug
import datetime
import time
import urllib.request
#**************************************
# ルーティング
#**************************************
#azureML機械学習
@application.route('/azureML',methods=['POST'])
def azureML():
return render_template('azureML.html')
#**************************************
#azureML機械学習結果返信
#**************************************
@application.route('/azureML_cal', methods=['POST'])
def azureML_cal():
data = {
"Inputs": {
"input1":
[
{
'age': request.json['age'],
'workclass': request.json['workclass'],
'education': " ",
'education-num': request.json['education-num'],
'marital-status': request.json['marital-status'],
'relationship': request.json['relationship'],
'race': request.json['race'],
'sex': request.json['sex'],
'capital-gain': request.json['capital-gain'],
'capital-loss': request.json['capital-loss'],
'hours-per-week': request.json['hours-per-week'],
'native-country': request.json['native-country'],
}
],
},
"GlobalParameters": {
}
}
body = str.encode(json.dumps(data))
url = 'https://japaneast.services.azureml.net/workspaces/fe89906902004c4a9eaaf4281e150~略~'
api_key = '764McF0w85eyeYk8kM7pzrCm~略~' # Replace this with the API key for the web service
headers = {'Content-Type':'application/json', 'Authorization':('Bearer '+ api_key)} #Bearer=無記名
req = urllib.request.Request(url, body, headers)
try:
response = urllib.request.urlopen(req)
result = response.read()
print('★★★★★ok★★★★★')
print(result)
except urllib.error.HTTPError as error:
print('★★★★★error★★★★★')
print("The request failed with status code: " + str(error.code))
print(error.info())
print(json.loads(error.read().decode("utf8", 'ignore')))
result_dic = json.loads(result)
results = result_dic["Results"]
for outputName in results:
result_blob_location = results[outputName]
out_sl = result_blob_location[0]['Scored Labels']
out_sl = result_dic["Results"]["output1"][0]['Scored Labels']
if out_sl is None:
out_sl = "計算不可"
return out_sl
一旦判ってしまえば、MLでいろいろ試したモデルを簡単に別サーバから呼び出せるので、マイクロソフトもMLのこういう使い方を想定しているのだと思いました。
AZUREの設定については下記を参照下さい。
[tabulatorを使って、データベースの更新にトライ(python版)](https://qiita.com/vye03702/items
/88800e38caaf38641409)
#クラシックAPI版
リクエストとレスポンスのjsonの形式がARM版と異なります。
また無料ワークスペースは米国中南部リージョンで作成されるので、そちらに向いています。
#**************************************
#12.azureML機械学習結果返信の関数
#**************************************
@application.route('/azureML_cal', methods=['POST'])
def azureML_cal():
data = {
"Inputs": {
"input1": {
"ColumnNames": [
"age",
"workclass",
"education-num",
"occupation",
"race",
"sex",
"capital-gain",
"capital-loss",
"hours-per-week",
"native-country"
],
"Values": [
[
request.json['age'],
request.json['workclass'],
request.json['education-num'],
request.json['occupation'],
request.json['race'],
request.json['sex'],
request.json['capital-gain'],
request.json['capital-loss'],
request.json['hours-per-week'],
request.json['native-country']
],
]
}
},
"GlobalParameters": {}
}
body = str.encode(json.dumps(data))
if request.json['alg'] == "1":
url = "https://ussouthcentral.services.azureml.net/workspaces/以下省略~"
api_key = "M4pihqQXhoYR以下省略~"
elif request.json['alg'] == "2":
url = "https://ussouthcentral.services.azureml.net/workspaces/以下省略~"
api_key = "DuoTIGAEQwF以下省略~"
else:
url = "https://ussouthcentral.services.azureml.net/workspaces/以下省略~"
api_key = "pFYdUvutM+以下省略~"
headers = {'Content-Type':'application/json', 'Authorization':('Bearer '+ api_key)} #Bearer=無記名
req = urllib.request.Request(url, body, headers)
try:
response = urllib.request.urlopen(req)
result = response.read()
result_dic = json.loads(result.decode('utf-8'))
log_out('azureML return OK /n' + json.dumps(result_dic , indent=2))
out_sl = result_dic["Results"]["output1"]["value"]["Values"][0][10]
except urllib.error.HTTPError as error:
log_out("The request failed with status code: " + str(error.code))
return "error"
return out_sl
--------------------------------------------------------
【azureML.htmlについては以下の部分だけ変えました】
<div class="col-md-2 mb-1">アルゴリズム</div>
<div class="col-md-4 mb-1" >
<select class="form-control input-lg mb-1" id="alg">
<option value="1">DecisionForest</option>
<option value="2">DecisionJungle</option>
<option value="3">SVN</option>
</select>
</div>