メインコンテンツへスキップ
見出し画像

Pythonでやってみた8: ExcelでPython-AIモデルを使ってみた Excel×Joblib×FastAPI

    概要

     普段の業務でExcelを多用するため、Pythonで作成したAIモデルをExcelで使用できる方法を検討しました。「いつものExcel体裁を崩さす無駄作業がなくAIモデルを使いたい」という前提を元にするとFast APIが最適と思います。
     今回は下記流れでシステムを作成しました。なお各種ライブラリの説明は個別記事をご参照ください。

    【開発の流れ】
    1.訓練済みAIモデル作成:joblib(×sklearn)
     ->Pythonで学習モデルを作成する。(今回はIrisの品種予測)
     ->AIモデルをPickle化して別の場所(Fast APIのディレクトリ内)で使う
    2.API開発:Fast API
     ->POSTメソッドでIrisのパラメータを取得できるようにする
     ->AIモデル.pklにPostされたJsonデータを入れて品種のラベルを出力する
    3.Excelでの関数作成
     ->ExcelからAPIを叩いてJSONデータを解析できるようにする
     ->取得したデータを指定のセルに出力
    4.統合
     ->1~3を統合します。

    1.訓練済みAIモデル作成:Joblib

     |Iris⦅アヤメ⦆の判別モデルを作成して学習データを転用できるようにPickleファイル化します。細かい内容は下記記事をご参照ください。

     コードをまとめて記載すると下記の通りです。

    [In]
    import pandas as pd
    import numpy as np
    from sklearn import datasets
    from sklearn.svm import SVC
    import joblib
    
    iris = datasets.load_iris() #Irisデータセット
    datas, labels = iris.data, iris.target #データとラベルを取得
    df_datas = pd.DataFrame(datas, columns=iris.feature_names) #データフレーム
    
    svm = SVC() #svmモデル作成
    svm.fit(datas, labels) #モデルの学習
    
    joblib.dump(svm, 'svm_noteKIYO.pkl') #モデルを保存
    
    [Out]
    作業ディレクトリに"svm_noteKIYO.pkl"ファイルが作成されます

    2.API開発:FastAPI

     APIを叩いて戻り値を取得できるためのAPI開発にはFast APIを使用します。コードのポイントもまとめて記載します。

    【Fast APIの設計思想】
    ●学習したAIモデルは"joblib.load(pickleファイル)"で読み込み
    ●設定したAPIが正しいことを確認するためにGETメソッドにタイトル設定Iris品種予測モデル
    ●AIモデルの結果はPOSTメソッドで取得できるようにする

    [In]
    from fastapi import FastAPI
    from pydantic import BaseModel, Field
    import joblib
    import numpy as np
    
    #AI学習モデルの読み込み
    svm = joblib.load('svm_noteKIYO.pkl') #SVMモデルでIris学習
    
    #FastAPIの設定
    class Info(BaseModel):
        sepal_L: float
        sepal_W: float
        petal_L: float
        petal_W: float
        
    app = FastAPI() #FastAPIインスタンスを生成
    
    #GETメソッド
    @app.get("/") #app.getメソッドでルーティングを設定
    async def root(): #async: 非同期処理を行う
        return 'Iris品種予測モデル'
    
    #POSTメソッド
    @app.post("/info") #app.postメソッドでルーティングを設定
    async def info_post(info: Info): #async: 非同期処理を行う
        data = np.array([[info.sepal_L, info.sepal_W, info.petal_L, info.petal_W]], dtype=np.float32) #配列に変換
        return {"label": f'{svm.predict(data)[0]}'} #JSON形式で返すため文字列変換 出力例:{'label': '0'}
        # return f'{svm.predict(data)[0]}' #こちらもOK 出力例:'0'
    

    3.Excelでの関数作成

     ExcelでFast API(HTTPクライアント)を使用できるように関数を追加します。完成品の体裁も合わせて表示しました。

    【Excelに追加する関数・プロシージャ】
    ●HTTPクライアント用(GET/POST):GET/POSTメソッド関数を作成
    ●JSON解析:Fast APIの戻り値を解析
    ●GET/POST処理:Fast APIにHTTP通信(+ データ送付)をする

    画像
    画像

    3-1.HTTPクライアント(GET/POST)

     各関数は下記の通りです。注意点としてPOSTメソッドはJSONデータを送付するためヘッダー形式は”application/json; charset=UTF-8”にしました。

    [In]※GETメソッド
    ' HTTPにGETメソッドを送信して結果を得る
    Function GetHttp(url) As String
        Dim httpObj As Object, s As String
        ' URLのページを開く
        Set httpObj = CreateObject("MSXML2.XMLHTTP")
        httpObj.Open "GET", url
        httpObj.setRequestHeader "Content-Type", "text/plain"
        httpObj.send
        ' 終了まで待機
        Do While httpObj.readyState <> 4
            DoEvents
        Loop
        ' HTTPのステータスコードが200ならば成功
        If (httpObj.Status = 200) Then
            s = httpObj.responseText
            GetHttp = "" & s
        Else
            GetHttp = ""
            Debug.Print "エラー:" & httpObj.statusCode
        End If
    End Function
    [In]※POSTメソッド
    Function PostHttp(url, json) As String
        '--------------
        'POST実行
        '--------------
        Dim xmlhttp As Object
        Set xmlhttp = CreateObject("msxml2.xmlhttp")
        xmlhttp.Open "POST", url, False
        xmlhttp.setRequestHeader "Content-Type", "application/json; charset=UTF-8" '送信形式をJSONに設定
        xmlhttp.send (json) 'JSONデータをPOST
    
        '--------------
        '応答取得
        '--------------
        Dim retCd As String
        retCd = xmlhttp.Status    '結果コード取得
        
        If retCd <> 200 Then
            Debug.Print "error:" & retCd
        Else
            Dim retHtml As String
            retHtml = StrConv(xmlhttp.responseBody, vbUnicode, 1041)    '結果HTML取得:戻り値JSON
            PostHttp = retHtml
        End If
    End Function

    3-2.JSON解析

     JSON解析は下記の通りです。

    [In]
    ' JSON文字列からキーkeyを取り出す --- (*6)
    Function GetJsonKey(JsonStr, key, enc) As String
        Dim d As Object
        If JsonStr = "" Then
            GetJsonKey = ""
            Exit Function
        End If
        Set d = CreateObject("htmlfile")
        d.Write "<meta http-equiv='X-UA-Compatible' content='IE=8' />"
        ' JavaScriptの関数を定義 --- (*7)
        d.Write "<script>" & _
                "document.getjson = function(s, key, enc){" & _
                "var vals = eval('('+s+')');" & _
                "if (enc) { return JSON.stringify(vals[key]);}" & _
                "else { return vals[key] }}" & _
                "</script>"
        ' JavaScriptの関数を呼び出す --- (*8)
        GetJsonKey = d.getjson(JsonStr, key, enc)
    End Function

    3-3.GET/POST処理

     上記の通り下記思想でコードを作成しました。

    【プロシージャの思想】
    ●Fast API側にAPIのタイトルを設定したのでGETメソッドで取得する
    ●Fast API側の戻り値のKey="label"のためJSON解析には"label"を渡す
    ●POSTするデータはJSON形式のためごり押しでJSONを記載
    ●指定セルから値を取得して指定セルに値を出力させる

    [In]GETメソッドでタイトル取得
    Sub FastAPIのタイトル()
        Dim api As String
        Dim res As String
        
        ' APIのURL(Fast APIのroot) --- (*1)
        api = "http://127.0.0.1:8000/"
        
        ' URLにアクセス --- (*2)
        res = GetHttp(api)
        
        ' シートに設定 --- (*3)
        Sheet1.Range("B9").Value = res
    End Sub
    [In]POSTメソッドで戻り値取得・解析
    Sub Irisデータ取得()
        '-----------------
        'リクエスト生成
        '-----------------
        'URL(必要に応じて変更)
        Dim url As String, res As String, result As String
        
        Dim json As String '出力:{"sepal_L": 1,"sepal_W": 1,"petal_L": 1,"petal_W": 1}
        Dim sepal_L As Double, sepal_W As Double, petal_L As Double, petal_W As Double '変数:データ型浮動小数点
        
        url = "http://127.0.0.1:8000/info" 'Fast-APIのPostエンドポイント
        
        '入力値を作成
        sepal_L = Range("A6").Value
        sepal_W = Range("B6").Value
        petal_L = Range("C6").Value
        petal_W = Range("D6").Value
        
        'POSTするパラメータをJSON形式に変更
        json = "{""sepal_L"": " & sepal_L & "," & _
                            """sepal_W"": " & sepal_W & "," & _
                            """petal_L"": " & petal_L & "," & _
                            """petal_W"": " & petal_W & "}"
            
        res = PostHttp(url, json)
            
        result = GetJsonKey(res, "label", False)
        Sheet1.Range("B10").Value = result
        
    End Sub

    3-4.体裁の調整

     後は使いやすいように体裁を調整しました。

    【Excelの体裁】
    ●入力表・出力表を作成
    ●作成したプロシージャをボタンに割り当て
    ●POSTした戻り値が整数のためラベルを割り当てる関数をB11に追加

    [セル:B11]
    =IF(B10=0,"setosa",IF(B10=1,"versicolor",IF(B10=2,"virginica","該当なし")))

    4.統合および実行

     1、2で作成したAIモデル.plkとFast APIを同じディレクトリにします。

    画像

     次にFast APIを立ち上げURL="http://127.0.0.1:8000"を確認します。

    [Terminal]
    uvicorn fastapi_iris:app --reload
    画像

     後はExcelで設定したプロシージャを処理するだけで値が抽出できます。

    画像

    5.注意点

     Fast APIの記事で書きましたが、常時は自分のPCで開発しているため問題ないですが、別PCからサーバーのようにアクセスする場合はFast APIの立ち上げはhost ip addressの設定が必要です。
     またAPIのエンドポイントは"127.0.0.1"ではなく接続先のipアドレスに変更が必要となります。

    [Terminal]
    uvicorn note_fastapi:app --reload --host 0.0.0.0 --port 8000

    参考資料:Excel-VBA

    あとがき

     2021年にこういうのが欲しくて探したけど見当たらなかったので多分初出だと思う。Python側は問題ないと思うけどExcelの関数やJSON形式はもっと美しく書けると思うけどスキル不足で今は断念。
    普段AIモデルはXGBoostを使用しているのでそれでもいけるか試してみます。
    2022年2月追記:XGBoostのモデルを用いた実装は問題なし(完了)


    この記事が参加している募集

     
     

    KIYO

     
     
    普段は製造業で企画/開発/設計しております。記事はプログラミング・機械学習、IoT関係の記事をメインで作成し、なるべく1つの記事で知りたいことを網羅していきます。内容は学術的より実装・アウトプット(ほしくなるもの)を重視して作成しています。 面白そうな仕事があればやりたいです!

    あなたへのおすすめ