LoginSignup
3
3

More than 3 years have passed since last update.

Azure ML StudioとFlask連携によるWebアプリケーションサンプル

Last updated at Posted at 2019-03-25

機械学習や組合最適化の知識をブラッシュアップすべく、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 エンドポイントのテスト

MLから戻り値の処理のサンプル
    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.html
<!--*****************************************************************
*  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 %}


サーバサイドソースmain.py


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版)

クラシックAPI版

リクエストとレスポンスのjsonの形式がARM版と異なります。
また無料ワークスペースは米国中南部リージョンで作成されるので、そちらに向いています。

サーバサイドソースmain.py
#**************************************
#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>  

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3