GoogleAppEngine(GAE)スタンダード環境でscikit-learnを動かしてみた(機械学習、予測)


はじめに

以下の記事で、GoogleAppEngineスタンダード環境で、scikit-learnを利用することができるようになったとのことで、動作させてみました。
学習したモデルはGCSへ保存したり、モデルを読み込んで予測タスクも実行しています。

Python 3.7 が動作する第2世代ランタイムでは、gVisor をベースとしたアイソレーション環境により安全性が担保できるため、内部的にCで書かれた Pythonライブラリもロードして使用することができます。

とのこと。

環境

  • GoogleAppEngine StandardEnvironment (Python3.7(ベータ版))
  • GoogleCloudStorage
  • scikit-learn 0.20.0
  • Pickle

簡易構成図と流れは、以下です。

  1. 事前準備(学習データ読み込みと、モデル保存用に、GCSバケット準備)
  2. GAEがGCSから学習データを取得
  3. 機械学習実行
  4. 学習モデルはGCSへ保存
  5. 学習モデルを読み込み、予測

構成図

説明

1. 事前準備(GAE設定、学習データ読み込みと、モデル保存用に、GCSバケット準備)

GCS上に学習データを配置しておきます。GAEはそれを読み込んで学習することとします。

そして、GAE実行に、必要なライブラリを読み込んだり、環境準備をしておきます。

requirements.txt
Flask==1.0.2
google-cloud-storage== 1.7.0
gunicorn==19.8.1
WTForms-Appengine==0.1
pandas==0.23.4
scikit-learn==0.20.0
numpy==1.15.2

runtimeでpython37を指定します。また、学習タスクで負荷が高く、デフォルトのF1だとServerError504が発生したので、F2を指定しました。

app.yaml
runtime: python37
instance_class: F2

2. GAEがGCSから学習データを取得

GAEがGCSから学習データを取得します。取得したデータはsource_dataに格納します。
(今回、source_filenameはPOST request内のFORMデータに格納するようにしています。)

main.py(抜粋)
def download_blob(source_blob_name):
    """Downloads a blob from the bucket."""

    storage_client = storage.Client(project=PROJECT_ID)
    bucket = storage_client.get_bucket(CLOUD_STORAGE_BUCKET)
    blob = bucket.blob(source_blob_name)
    file = blob.download_as_string()
    # <class 'bytes'>
    return file

def multi_ml():
    source_filename = request.form['source_file']
    ## source_filename : alldata.csv
    source_data = download_blob(source_filename)

3. 機械学習実行

まず、取得したデータは、最後の要素が目的変数で、それ以外が説明変数である形式であることを前提とします。

説明変数1 説明変数2 目的変数
10 10 100
20 20 200
30 30 300

上記のデータを学習用。テスト用のデータフレームへ分けます。パラメータはデフォルト。

main.py
    import sklearn.model_selection
    df = pd.read_csv(BytesIO(source_data))
    X = df.drop(df.columns.values[len(df.columns)-1], axis=1)
    Y = df.iloc[:, [len(df.columns)-1]]
    X_train, X_test, Y_train, Y_test = sklearn.model_selection.train_test_split(X,Y)

今回テストとして、scikit-learnでランダムフォレストアルゴリズムを使います。パラメータはデフォルト。

main.py(抜粋)
    from sklearn.ensemble import RandomForestRegressor
    rf_reg = RandomForestRegressor()
    rf_reg.fit(X_train, Y_train)

4. 学習モデルはGCSへ保存

上記で学習したモデルを、Pickleでdumpして、名前を指定してGCSへアップロードしておきます。予測で利用します。

main.py(抜粋)
    model = pickle.dumps(rf_reg)
    url = upload_file(model, 'RandomForestModel.pkl', 'application/octet-stream')
main.py(upload_file機能
def upload_file(file_stream, filename, content_type):
    storage_client = _get_storage_client()
    bucket = storage_client.bucket(CLOUD_STORAGE_BUCKET)
    blob = bucket.blob(filename)

    blob.upload_from_string(
        file_stream,
        content_type=content_type)

    url = 'gs://{}/{}'.format(CLOUD_STORAGE_BUCKET, filename)

    return url

学習フェーズはここまで。予測フェーズは以下。

5. 予測のために、GCSから学習モデルを読み込み

GAEがGCSからモデルを取得し、Pickleでモデルをloadします。
(今回、モデルが格納されているURI(gs://)はPOST request内のFORMデータに格納するようにしています。)

main.py
    model_uri = request.form['model']
    ## gs://<uploadurl>/Model.pkl

    model_data = download_blob(model_uri)
    model = pickle.loads(model_data)

予測するための入力データもPOST requestで取得できる前提とします。カンマ区切り。
※この辺りは、学習したデータに合わせてください。

input_data
40,40,40

今回テストで、stringデータを入力した関係もあり、str~list~np.arrayへ変換しています。
このあたりは、入力データに合わせて適宜作り替えてください。

main.py
    input_data = request.form['input']
    input_list = input_data.split(',')
    input_array = np.array([input_list]).astype(np.float64)

6. 予測を実行

あとは、読み込んだモデルを利用して、predictをするだけです。

main.py
    predict_kekka = model.predict(input_array)

結果は適宜利用しましょう。テスト実装では、結果をユーザに返しています。

main.py
    return render_template('predict_kekka.html', predict_kekka=predict_kekka, message=message)

参考

GAE スタンダード環境で scikit-learn を使う
GAE StandardEnvironmentのinstance class

おわりに

基本的には機械学習のアーキテクチャではMachineLearningEngine(MLE)を使うものですし、GAEはinstance_classとして、GPUは選択できないので、学習タスクを行うことはあまり適していないと思われます。
ただ、予測タスク(5,6項)を行う分には、GAEは価格も安いので十分に使えるものではないかと思いました。

コードはgithubに公開しました。