kakakakakku blog

Weekly Tech Blog: Keep on Learning!

データエンジニアリングライフサイクルのステージと底流とは /「データエンジニアリングの基礎」を読んだ

2024年3月に出版された「データエンジニアリングの基礎」を読んだ📕

仕事で取り組んでいることに関係していて,何かしら新しい気付きや発見があれば良いな〜と思って読んでみたけど,期待以上に素晴らしい一冊だった❗️データを取り扱うときに考慮すべきポイントが詳細にまとまっていて,一人で読むのもヨシ!データプロジェクトのメンバーと輪読会をして改善点を洗い出すのもヨシ!という感じで幅広く活用できると思う.

特に本書で重要なのは「データエンジニアリングライフサイクル」というフレームワーク(コンセプト)で,データを活用してプロダクトの価値に変えていくための「ステージ」「底流」から構成されている👌(図を引用できると紹介しやすいんだけど...!)特にデータエンジニアリングライフサイクルのあらゆる側面をサポートする横断的な観点を本書では「底流 (undercurrents)」と表現していて,以下の6種類から構成されていた.本書は全体を通して,データエンジニアリングライフサイクルの観点から解説されていて印象的だった.

  • セキュリティ
  • データ管理
  • DataOps
  • データアーキテクチャ
  • オーケストレーション
  • ソフトウェアエンジニアリング

技術的利害関係者

1章にデータエンジニアに関連する技術的利害関係者の紹介が出てくる.また11章の「職種名と担当範囲は変化する」では担当範囲の境界はますます曖昧になっているとも書いてある.データエンジニアリングライフサイクルを実現するために多くの役割が必要になることは理解できるけど,ある程度の企業規模じゃないとそこまで専任を置くことは難しく,今いるメンバーでどうオーバーラップして前に進めていくかを考えることも重要なのかなと個人的には思った😀また役割のトピックを読んでいて,AWS Well-Architected Machine Learning Lens の「MLOE-04: Establish ML roles and responsibilities」を思い出した.同じく Machine Learning Lens でも役割と責任の分離は明確ではなくオーバーラップすると書いてある.どういう役割が必要なのかを把握することはとても重要❗️

docs.aws.amazon.com

データエンジニアが ML について知っておくべきこと

役割という観点だと,9章で紹介されていた「データエンジニアが ML について知っておくべきこと」というトピックも良かった😃僕自身はアプリケーション・インフラ・DevOps(定義は曖昧だけど)など,ソフトウェア開発に必要なある程度の領域を幅広く得意としているけど,機械学習 (ML) の専門性はなく,ちょうど苦戦しているところだった.

もちろん本書の「まえがき」にデータサイエンティストでは対処できない問題があると書いてある通り,データエンジニアリングライフサイクルを実現するためには特に底流まわりを支援する必要があって,僕自身のスキルセットはフィットしそうだけど,さらに僕自身が機械学習 (ML) の理解を深めることでプロジェクトの価値にさらに貢献できるかなと思っていた.とは言え,どこまで深く理解するべきなのか・そもそも理解できるのかという不安もあり,まずは本書に載っている「知っておくべきこと」の中から理解が浅いところを抑えておきたいなと感じた.

良いデータアーキテクチャの原則

3章に出てくる「良いデータアーキテクチャの原則」は AWS Well-Architected Framework や Google Cloud の 5 Principles for Cloud-Native Architecture にインスパイアされたと書いてあったけど,データエンジニアリングに限らず重要な観点で,紹介されていて良かった👏 逆に普段からソフトウェア開発をしながらこういう原則を意識できていれば,データプロジェクトでも応用しやすいと思う.あと原則7に Jeff Bezos の「Two-Way Door(双方向ドア)」という表現が紹介されていたのもなつかしくて良かった❗️

  • 原則1: 共通コンポーネントを賢く選択する
  • 原則2: 障害に備える
  • 原則3: スケーラビリティ設計
  • 原則4: アーキテクチャはリーダーシップだ
  • 原則5: 常に設計し続ける
  • 原則6: 疎結合システムを構築する
  • 原則7: 可逆な決定をする
  • 原則8: セキュリティを優先する
  • 原則9: FinOps を活用する

aws.amazon.com

読書メモ

他に読書メモに残したことの一部を箇条書きにしておく❗️

  • タイプAデータエンジニアとタイプBデータエンジニア
  • CAO (Chief Analytics Officer): 最高分析責任者
  • CAO-2 (Chief Algorithms Officer): 最高アルゴリズム責任者
  • リバース ETL
  • DMBOK (Data Management Body of Knowledge)
  • The DataOps Manifesto
  • 履歴書駆動開発 (Resume Driven Development)
  • ゼロスケール (Scale to Zero)
  • データカスケード
  • アプリケーションと ML 間での緊密なフィードバック

誤植

  • P13: DMM (Data Management Maturity) のリンクが 404 になっていた
  • P23: 図1-12 の データアーキテクチャデータアーキテクト では?(原著では Data architects と書かれていた)

X ポスト🔗

PyTorch Tutorials「(optional) Exporting a Model from PyTorch to ONNX and Running it using ONNX Runtime」を試した

PyTorch のチュートリアル「(optional) Exporting a Model from PyTorch to ONNX and Running it using ONNX Runtime」を試した❗️

pytorch.org

PyTorch に低解像度の画像を高解像度の画像に変換する「超解像モデル」のサンプルがあって,今回のチュートリアルではそのモデルを ONNX (Open Neural Network eXchange) にエクスポートして,ONNX Runtime で推論する流れをサクッと体験できる.完全に入門者の僕にピッタリの内容だった👌

github.com

学べたこと

PyTorch のモデルは torch.onnx.export() 関数で ONNX にエクスポートできる.

pytorch.org

そして onnxruntime.InferenceSession クラスの run() 関数で推論できる.今回のチュートリアルでは providersCPUExecutionProvider を指定しているけど,CUDA (Compute Unified Device Architecture) 環境があれば CUDAExecutionProvider を指定することもできる.

ort_session = onnxruntime.InferenceSession("super_resolution.onnx", providers=["CPUExecutionProvider"])

onnxruntime.ai

実行結果

PyTorch モデルを ONNX モデルにエクスポートしてから推論した.今回は猫画像🐱を超解像にする内容だった.

実行前

実行後

その他

チュートリアルの範囲外ではあるけど,Netron を使うと ONNX モデルを可視化できる.

netron.app

今回エクスポートした super_resolution.onnx を可視化してみた💡

関連チュートリアル

pytorch.org

実験管理を便利に行う MLflow Tracking に入門した

実験管理やモデルレジストリなど,機械学習ライフサイクルをうまく管理するプラットフォームとして有名な「MLflow」に入門する.GitHub リポジトリの Star は 12000 もあってスゴイ!MLflow は MLOps の文脈でもよく聞くので,1度試しておこうと思った.

現在,MLflow には大きく「4種類」のコンポーネントがある.

  • MLflow Tracking : 実験管理 / ハイパーパラメータや評価メトリクスなどを記録したり比較できたりする
  • MLflow Projects : パッケージング / 機械学習を実行するための設定情報などを再現可能にする
  • MLflow Models : モデル化 / さまざまなデプロイツールをサポートする汎用的なモデル形式を提供する
  • MLflow Model Registry : モデルレジストリ / レジストリとしてモデルを管理する

github.com

MLflow Tutorial

今回は MLflow のドキュメントに載っている「Tutorial」「Quickstart」を参考にしながら,MLflow の中で基本となるコンポーネント「MLflow Tracking」を試す.ドキュメントを読みながら進めて,特にハマるところもなく試せた.

mlflow.org

mlflow.org

準備

準備として MLflow と scikit-learn をインストールする.さらに MLflow の GitHub リポジトリにある examples を使うため git clone もしておく.今回は macOS を使って環境を構築する.それぞれのバージョンは以下の通り.

  • MLflow : 1.26.1
  • scikit-learn : 1.1.1
$ pip install mlflow
$ pip install scikit-learn
$ git clone https://github.com/mlflow/mlflow

次に mlflow ui コマンドを実行して,実験結果を表示する画面を起動する.デフォルトでは http://127.0.0.1:5000/ で表示できる.準備 OK!

$ mlflow/examples
$ mlflow ui

MLflow 用語

今から出てくる MLflow 用語の関係性を以下にまとめておく.Experiment(実験)に複数の Run(実行)が紐付く.

  • Experiment(実験)
  • Run(実行)

MLflow Tracking サンプルコード

MLflow Tracking のイメージを掴むために,まず「Quickstart」に載っている quickstart/mlflow_tracking.py を実行する.コードのポイントは大きく3点ある.

  • log_param() 関数を使って「パラメータ : param1を登録する
  • log_metric() 関数を使って「メトリクス : fooを登録する
  • log_artifacts() 関数を使って「アーティファクト : test.txtを登録する
import os
from random import random, randint

from mlflow import log_metric, log_param, log_artifacts

if __name__ == "__main__":
    print("Running mlflow_tracking.py")

    log_param("param1", randint(0, 100))

    log_metric("foo", random())
    log_metric("foo", random() + 1)
    log_metric("foo", random() + 2)

    if not os.path.exists("outputs"):
        os.makedirs("outputs")
    with open("outputs/test.txt", "w") as f:
        f.write("hello world!")

    log_artifacts("outputs")

MLflow の関数は以下のドキュメントに詳細に載っている.

今回は「計3回」実行する.

$ python quickstart/mlflow_tracking.py
$ python quickstart/mlflow_tracking.py
$ python quickstart/mlflow_tracking.py

実行後に画面を更新すると実行結果を確認できる.また param1foo など登録したパラメータとメトリクスも一覧できる.

個別に実行を選択するとアーティファクトも確認できる.便利!

MLflow Tracking と scikit-learn を組み合わせる

次は「Tutorial」に載っている sklearn_elasticnet_wine/train.py を使って実践的なトレーニングを実行する.今回のお題として,ワインの品質を予測する.アルゴリズムとしては scikit-learn の ElasticNet を使って回帰分析をする.ハイパーパラメータとしては alpha(L1, L2 正規化項の合計)と l1_ratio(L1 正則化項の割合)を変えながらパフォーマンスを確認する.モデルの評価メトリクスとしては RMSE(二乗平均平方根誤差) を使う.

そして,今回は「Tutorial」の手順を一部変更する.そのまま実行すると Default Experiments に紐付くため,新しく sklearn-elasticnet-wine Experiments を作って紐付ける.今回は id=1 の Experiments になった.

import mlflow

experiment_id = mlflow.create_experiment('sklearn-elasticnet-wine')
print(experiment_id)

コードのポイントは大きく4点ある.その他のコードはデータセットを分割したり,scikit-learn を使ってトレーニングをしたり,一般的な機械学習コードそのものなので,紹介は割愛する.

  • 実行を sklearn-elasticnet-wine Experiments に紐付けるために mlflow.start_run()mlflow.start_run(experiment_id=1) に変更する
  • log_param() 関数を使って「ハイパーパラメータ : alphal1_ratioを登録する
  • log_metric() 関数を使って「メトリクス : rmser2maeを登録する
  • log_model() 関数を使って「モデル」を登録する
# The data set used in this example is from http://archive.ics.uci.edu/ml/datasets/Wine+Quality
# P. Cortez, A. Cerdeira, F. Almeida, T. Matos and J. Reis.
# Modeling wine preferences by data mining from physicochemical properties. In Decision Support Systems, Elsevier, 47(4):547-553, 2009.

import os
import warnings
import sys

import pandas as pd
import numpy as np
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from sklearn.model_selection import train_test_split
from sklearn.linear_model import ElasticNet
from urllib.parse import urlparse
import mlflow
import mlflow.sklearn

import logging

logging.basicConfig(level=logging.WARN)
logger = logging.getLogger(__name__)


def eval_metrics(actual, pred):
    rmse = np.sqrt(mean_squared_error(actual, pred))
    mae = mean_absolute_error(actual, pred)
    r2 = r2_score(actual, pred)
    return rmse, mae, r2


if __name__ == "__main__":
    warnings.filterwarnings("ignore")
    np.random.seed(40)

    # Read the wine-quality csv file from the URL
    csv_url = (
        "http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv"
    )
    try:
        data = pd.read_csv(csv_url, sep=";")
    except Exception as e:
        logger.exception(
            "Unable to download training & test CSV, check your internet connection. Error: %s", e
        )

    # Split the data into training and test sets. (0.75, 0.25) split.
    train, test = train_test_split(data)

    # The predicted column is "quality" which is a scalar from [3, 9]
    train_x = train.drop(["quality"], axis=1)
    test_x = test.drop(["quality"], axis=1)
    train_y = train[["quality"]]
    test_y = test[["quality"]]

    alpha = float(sys.argv[1]) if len(sys.argv) > 1 else 0.5
    l1_ratio = float(sys.argv[2]) if len(sys.argv) > 2 else 0.5

    with mlflow.start_run(experiment_id=1):
        lr = ElasticNet(alpha=alpha, l1_ratio=l1_ratio, random_state=42)
        lr.fit(train_x, train_y)

        predicted_qualities = lr.predict(test_x)

        (rmse, mae, r2) = eval_metrics(test_y, predicted_qualities)

        print("Elasticnet model (alpha=%f, l1_ratio=%f):" % (alpha, l1_ratio))
        print("  RMSE: %s" % rmse)
        print("  MAE: %s" % mae)
        print("  R2: %s" % r2)

        mlflow.log_param("alpha", alpha)
        mlflow.log_param("l1_ratio", l1_ratio)
        mlflow.log_metric("rmse", rmse)
        mlflow.log_metric("r2", r2)
        mlflow.log_metric("mae", mae)

        tracking_url_type_store = urlparse(mlflow.get_tracking_uri()).scheme

        # Model registry does not work with file store
        if tracking_url_type_store != "file":

            # Register the model
            # There are other ways to use the Model Registry, which depends on the use case,
            # please refer to the doc for more information:
            # https://mlflow.org/docs/latest/model-registry.html#api-workflow
            mlflow.sklearn.log_model(lr, "model", registered_model_name="ElasticnetWineModel")
        else:
            mlflow.sklearn.log_model(lr, "model")

今回はハイパーパラメータを変更しながら「計5回」実行する.

  • 実行 1. alpha=0.5 / l1_ratio=0.5
  • 実行 2. alpha=0.4 / l1_ratio=0.4
  • 実行 3. alpha=0.3 / l1_ratio=0.3
  • 実行 4. alpha=0.2 / l1_ratio=0.2
  • 実行 5. alpha=0.1 / l1_ratio=0.1
$ python sklearn_elasticnet_wine/train.py
Elasticnet model (alpha=0.500000, l1_ratio=0.500000):
  RMSE: 0.7931640229276851
  MAE: 0.6271946374319586
  R2: 0.10862644997792614

$ python sklearn_elasticnet_wine/train.py 0.4 0.4
Elasticnet model (alpha=0.400000, l1_ratio=0.400000):
  RMSE: 0.7644619587468349
  MAE: 0.5966303605775048
  R2: 0.17197111491474282

$ python sklearn_elasticnet_wine/train.py 0.3 0.3
Elasticnet model (alpha=0.300000, l1_ratio=0.300000):
  RMSE: 0.7443224557281489
  MAE: 0.5754825491733004
  R2: 0.2150247343683439

$ python sklearn_elasticnet_wine/train.py 0.2 0.2
Elasticnet model (alpha=0.200000, l1_ratio=0.200000):
  RMSE: 0.7336400911821402
  MAE: 0.5643841279275428
  R2: 0.23739466063584158

$ python sklearn_elasticnet_wine/train.py 0.1 0.1
Elasticnet model (alpha=0.100000, l1_ratio=0.100000):
  RMSE: 0.7128829045893679
  MAE: 0.5462202174984664
  R2: 0.2799376066653344

実行後に画面を更新すると実行結果を確認できる.実行結果は sklearn-elasticnet-wine Experiments に紐付いている.

MLflow では metrics.rmse < 0.74 のようなクエリを書くことで実行結果をフィルタできる.

クエリ構文は以下のドキュメントに詳細に載っている.

www.mlflow.org

さらに実行結果(今回だと5件)を選択して評価メトリクス RMSE を比較できる.

さらにハイパーパラメータと評価メトリクスの関係性も比較できる.以下の例では,ハイパーパラメータ alpha と評価メトリクス RMSE の関係性を比較している.

そして,アーティファクトを確認するとモデル model.pkl も含まれている.

モデルをデプロイして推論する

MLflow CLI で mlflow models serve コマンドを実行すると,指定したモデルをデプロイした推論エンドポイントを起動できる.

www.mlflow.org

今回は最も RMSE の値が低くパフォーマンスが高かった実行 e8901371a1494c34bd0a26e8180c5441 のモデルをデプロイする.アーティファクトからパスを取得して,以下のように実行する.

$ mlflow models serve -m file:///Users/kakakakakku/mlflow/examples/mlruns/1/e8901371a1494c34bd0a26e8180c5441/artifacts/model -p 1234

推論エンドポイント http://127.0.0.1:1234 に以下のように curl コマンドを実行すると推論結果を取得できる.mlflow models serve コマンドのログを眺めていたら HTTP Server は Gunicorn を使っていた.

$ curl -X POST -H 'Content-Type:application/json; format=pandas-split' http://127.0.0.1:1234/invocations\
  --data '{"columns":["alcohol", "chlorides", "citric acid", "density", "fixed acidity", "free sulfur dioxide", "pH", "residual sugar", "sulphates", "total sulfur dioxide", "volatile acidity"],"data":[[12.8, 0.029, 0.48, 0.98, 6.2, 29, 3.33, 1.2, 0.39, 75, 0.66]]}' 
[10.652032783691832]

$ curl -X POST -H 'Content-Type:application/json; format=pandas-split' http://127.0.0.1:1234/invocations\
  --data '{"columns":["alcohol", "chlorides", "citric acid", "density", "fixed acidity", "free sulfur dioxide", "pH", "residual sugar", "sulphates", "total sulfur dioxide", "volatile acidity"],"data":[[15.8, 0.029, 0.48, 0.98, 6.2, 29, 3.33, 1.2, 0.39, 75, 0.66]]}' 
[10.83593352318585]

まとめ

今回は MLflow のドキュメントに載っている「Tutorial」「Quickstart」を参考にしながら,MLflow の中で基本となるコンポーネント「MLflow Tracking」を試した.実験管理がしやすく便利だった.他にもグラフ画像などを登録する log_figure() 関数や簡単にパラメータとメトリクスを登録する Automatic Logging など,まだまだ気になる機能がある.引き続き試していくぞ!

www.mlflow.org

Pandas で指数表記を無効化する

Jupyter NotebookPandas のコードを実装しているときに「指数表記を無効化」する場合は pd.optionsdisplay.float_format を設定する.以下にサンプルとして「桁数 2」「桁数 6」の例を載せておく.

# 小数点以下 桁数 2
pd.options.display.float_format = '{:.2f}'.format

# 小数点以下 桁数 6
pd.options.display.float_format = '{:.6f}'.format

pandas.pydata.org

DataFrame で動作確認する 🎯

random.uniform で取得した乱数を3乗した適当な値を DataFrame にする.デフォルト設定だと e+08 のように指数表記になる.

import pandas as pd
import random

df = pd.DataFrame(
    {
        'id': [1, 2, 3],
        'dest': [random.uniform(100, 1000) ** 3, random.uniform(100, 1000) ** 3, random.uniform(100, 1000) ** 3]
    }
)
df

#    id          dest
# 0   1  5.343391e+08
# 1   2  1.095209e+08
# 2   3  5.267437e+08

次に pd.optionsdisplay.float_format'{:.2f}'.format を設定する.桁数 2 で指数表記を無効化できた!

pd.options.display.float_format = '{:.2f}'.format
df

#    id         dest
# 0   1 534339073.50
# 1   2 109520894.40
# 2   3 526743661.86

今度は pd.optionsdisplay.float_format'{:.6f}'.format を設定する.桁数 6 で指数表記を無効化できた!

pd.options.display.float_format = '{:.6f}'.format
df

#    id             dest
# 0   1 534339073.500472
# 1   2 109520894.397633
# 2   3 526743661.855074

Jupyter Notebook の実行結果も載せておく!

関連記事

kakakakakku.hatenablog.com

Pandas で NDJSON (.jsonl) を読み込む

Pandas で NDJSON (Newline Delimited JSON) を読み込む場合 read_json() 関数に lines=True パラメータを設定すれば OK!

pandas.pydata.org

NDJSON サンプル dataset.jsonl

{ "id": 1, "name": "Alice" }
{ "id": 2, "name": "Bob" }
{ "id": 3, "name": "Kakakakakku", "blog":  "https://kakakakakku.hatenablog.com/" }

サンプルコード ndjson.py

import pandas as pd

df = pd.read_json('./dataset.jsonl', lines=True)
print(df)

実行すると期待通りに DataFrame を表示できた!

$ python ndjson.py
   id         name                                 blog
0   1        Alice                                  NaN
1   2          Bob                                  NaN
2   3  Kakakakakku  https://kakakakakku.hatenablog.com/