IKEA Retailが効率的な機械学習モデルの導入のためにDockerイメージを標準化した方法

DockerとIKEA Retailの共通点は何ですか? 両社は、製品の構築、保管、出荷の方法を変更しました。 IKEA Retailの場合、フラットパック家具の市場を創出し、家具の出荷、倉庫保管、最終場所への配送からすべてをはるかに簡単かつ費用対効果の高いものにしました。 これは、Dockerが開発者のために行ったことと似ています。 Dockerは、ソフトウェアの構築、出荷、保存の方法を変更し、Docker Imagesが占有するスペースの「シェルフ」スペースを大幅に削減しました。 

この投稿では、IKEA Retailの寄稿者であるKaran Honavar氏とFernando Dorado Rueda氏が、Dockerで構築されたMLOpsソリューションについて説明します。

V2 Banner 本番環境の力を解き放つ Docker イメージの標準化により、効率的な ML モデル展開を実現

機械学習 (ML) のデプロイは、ML モデルを開発段階から実際の運用環境に移行する行為であり、複雑なアルゴリズムを実際のソリューションに変換するために最も重要です。 しかし、この複雑なプロセスには、次のような課題がないわけではありません。

  1. 複雑さと不透明度: ML モデルは複雑に包まれていることが多いため、ロジックの解読には負担がかかる可能性があります。 この曖昧さは信頼を妨げ、利害関係者への決定の説明を複雑にします。
  2. 変化するデータパターンへの適応: 実世界のデータの状況の変化は、トレーニングセットから逸脱し、「コンセプトドリフト」を引き起こす可能性があります。 これに対処するには、注意深い再トレーニングが必要であり、時間とリソースを浪費する骨の折れる作業です。
  3. リアルタイムデータ処理: 正確な予測に必要な大量のデータを処理すると、システムに負担がかかり、スケーラビリティが妨げられる可能性があります。
  4. さまざまな展開方法: ローカル、クラウド、またはWebサービスを介して展開するかどうかにかかわらず、それぞれの方法は固有の課題をもたらし、すでに複雑な手順に複雑さの層を追加します。
  5. セキュリティとコンプライアンス: ML モデルが、特に個人情報に関する厳格な規制に確実に準拠するようにするには、合法的な実装に重点を置く必要があります。
  6. 継続的なメンテナンスと監視: 旅はデプロイで終わりません。 モデルの健全性を維持し、新たな懸念に対処するには、継続的な監視が不可欠です。

これらの要因は実質的な障害を表していますが、克服できないわけではありません。 効率的な ML モデルのデプロイのために Docker イメージを標準化することで、ラボから現実世界への移動を合理化できます。 

この記事では、Dockerized ML モデルの作成、測定、デプロイ、および操作について詳しく説明します。 複雑さをわかりやすく説明し、Dockerが最先端の概念を具体的なメリットに触媒する方法を示します。

Dockerによる標準化デプロイプロセス

イケアリテールのケースのように、今日のデータドリブンな企業のダイナミックな領域では、多数のツールと展開戦略が恩恵と負担の両方として機能します。 イノベーションは繁栄しますが、複雑さも増し、矛盾と遅延を引き起こします。 解毒剤? 標準化。 それは単なる流行語以上のものです。これは、効率、コンプライアンス、シームレスな統合への道を開く方法です。

この物語の縁の下の力持ちであるDockerを入力してください。 ML デプロイの進化する分野で、Docker は俊敏性と均一性を提供します。 開発から生産まで一貫した環境を提供することで、景観を再形成しました。 Dockerの利点はコンテナ化テクノロジーにあり、開発者はライブラリやその他の依存関係など、必要なすべての部分でアプリケーションをラップし、すべてを1つのパッケージとして出荷できます。

IKEA Retailでは、ハイブリッドデータサイエンティストチームやR&Dユニットなど、多様なチームがモデルを概念化して開発し、それぞれが好みや要件に応じてドライバーとパッケージングライブラリを選択しています。 仮想環境は一定レベルのサポートを提供しますが、運用環境に移行するときに互換性の課題が生じる可能性もあります。 

これは、Dockerが日常業務に不可欠なツールになり、開発およびデプロイプロセスの簡素化と大幅な加速を提供する場所です。 主な利点は次のとおりです。

  • 移植性:Dockerを使用すると、異なるコンピューティング環境間の摩擦が解消されます。 コンテナーは、デプロイ場所に関係なく均一に実行され、パイプライン全体に堅牢性をもたらします。
  • 効率性:Dockerの軽量性により、リソースが最適に利用されるため、オーバーヘッドが削減され、パフォーマンスが向上します。
  • スケーラビリティ: Docker を使用すると、アプリケーションまたは ML モデルを水平方向に簡単にスケーリングできます。 これは、大規模な展開が要求するオーケストレーションされた交響曲と完全に一致しています。

次に、イケアリテールの先進的なMLOps(機械学習運用)チームが選んだソリューションである セルドンコアがあります。 なぜでしょうか。 モデルの出所(TensorFlow、PyTorch、H2Oなど)や言語(Python、Javaなど)に関係なく、MLモデルを本番環境に対応したマイクロサービスに変換するためです。 でもそれだけじゃないんです。 Seldon-Coreは正確にスケーリングし、高度なメトリックやロギングから説明者やA / Bテストまですべてを可能にします。

Docker と Seldon-Core のこの組み合わせは、今日の調査の中心を形成しています。 彼らは一緒に、ML デプロイの革命の青写真をスケッチします。 この相乗効果は単なる技術提携ではありません。これは、ML モデルのデプロイ、モニタリング、インタラクションを再定義する変革的なコラボレーションです。

IKEA Retailの経験を通して、この堅牢なデュオ(DockerとSeldon-Core)が複雑なタスクを合理化されたアジャイルな運用に変える方法と、リアルタイムの指標を活用して深い洞察を得る方法を明らかにします。

私たちと一緒にこの新しいMLOps時代に飛び込みましょう。 ML 制作における効率性、スケーラビリティ、戦略的優位性を解き放ちます。 イノベーションの旅はここから始まり、DockerとSeldon-Coreが先導します。 これは単なる解決策ではありません。それはパラダイムシフトです。

この記事の残りの部分では、モデルの準備、モデルの Docker イメージへのカプセル化、テストなどのデプロイ手順について説明します。 始めましょう。

前提 条件

この例を複製するには、次の項目が存在する必要があります。

モデルの準備

モデルトレーニングと簡単な評価

ML モデルのデプロイへの道のりに着手することは、傑作を作るのとよく似ています: キャンバスを準備し、すべてのブラシストロークを意図したものでなければなりません。 ただし、この調査の焦点はアートそのものではなく、それを保持するフレーム、つまり作成や使用するフレームワークに関係なく、ML モデルの標準化です。

このデモの主な目的は、モデルのパフォーマンスを向上させることではなく、ローカル開発から本番環境へのシームレスな移行を解明することです。 私たちが提示する方法論は、さまざまなモデルやフレームワークに普遍的に適用できることに注意する必要があります。 そこで、代表例として単純モデルを選択しました。 この選択は意図的なものであり、読者は基礎となるプロセスフローに集中でき、洗練されたハイパーパラメータ調整と綿密なモデル選択を必要とする可能性のあるより洗練されたモデルに容易に適応できます。 

これらの基本原則に焦点を当てることで、個々のモデルやユースケースの特異性を超えた、用途が広くアクセスしやすいガイドを提供することを目指しています。 このプロセスを詳しく見ていきましょう。

透明性と消費者のプライバシーという当社の理念に合わせ、このアプローチへの関与を促進するために、二項分類タスクには 公開データセット が採用されています。

次のコードの抜粋では、生データを実際の課題に対応できるモデルに変換する方法を反映した、トレーニングアプローチの本質を見つけることができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import os
import pickle
import numpy as np
import pandas as pd
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression, Perceptron
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
 
# Load the breast cancer dataset
X, y = datasets.load_breast_cancer(return_X_y=True)
 
# Split the dataset into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.9, random_state=0)
 
# Combine X_test and y_test into a single DataFrame
X_test_df = pd.DataFrame(X_test, columns=[f"feature_{i}" for i in range(X_test.shape[1])])
y_test_df = pd.DataFrame(y_test, columns=["target"])
 
df_test = pd.concat([X_test_df, y_test_df], axis=1)
 
# Define the path to store models
model_path = "models/"
 
# Create the folder if it doesn't exist
if not os.path.exists(model_path):
    os.makedirs(model_path)
 
# Define a list of classifier parameters
parameters = [
    {"clf": LogisticRegression(solver="liblinear", multi_class="ovr"), "name": f"{model_path}/binary-lr.joblib"},
    {"clf": Perceptron(eta0=0.1, random_state=0), "name": f"{model_path}/binary-percept.joblib"},
]
 
# Iterate through each parameter configuration
for param in parameters:
    clf = param["clf"# Retrieve the classifier from the parameter dictionary
    clf.fit(X_train, y_train)  # Fit the classifier on the training data
    # Save the trained model to a file using pickle
    model_filename = f"{param['name']}"
    with open(model_filename, 'wb') as model_file:
        pickle.dump(clf, model_file)
    print(f"Model saved in {model_filename}")
 
# Simple Model Evaluation
model_path = 'models/binary-lr.joblib'
with open(model_path, 'rb') as model_file:
    loaded_model = pickle.load(model_file)
 
# Make predictions using the loaded model
predictions = loaded_model.predict(X_test)
 
# Calculate metrics (accuracy, precision, recall, f1-score)
accuracy = accuracy_score(y_test, predictions)
precision = precision_score(y_test, predictions)
recall = recall_score(y_test, predictions)
f1 = f1_score(y_test, predictions)

モデル クラスの作成

モデル ファイルが準備されると、手元のタスクは、後で Docker イメージ内に存在する重要なアーキテクチャ要素であるモデル クラスの作成に移ります。 熟練した彫刻家のように、 セルドンが提案した厳格な基準に従って、このクラスを形作る必要があります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import joblib
import logging
 
class Score:
    """
    Class to hold metrics for binary classification, including true positives (TP), false positives (FP),
    true negatives (TN), and false negatives (FN).
    """
    def __init__(self, TP=0, FP=0, TN=0, FN=0):
        self.TP = TP  # True Positives
        self.FP = FP  # False Positives
        self.TN = TN  # True Negatives
        self.FN = FN  # False Negatives
 
 
class DockerModel:
    """
    Class for loading and predicting using a pre-trained model, handling feedback to update metrics,
    and providing those metrics.
    """
    result = {}  # Dictionary to store input data
 
    def __init__(self, model_name="models/binary-lr.joblib"):
        """
        Initialize DockerModel with metrics and model name.
        :param model_name: Path to the pre-trained model.
        """
        self.scores = Score(0, 0, 0, 0)
        self.loaded = False
        self.model_name = model_name
 
    def load(self):
        """
        Load the model from the provided path.
        """
        self.model = joblib.load(self.model_name)
        logging.info(f"Model {self.model_name} Loaded")
 
    def predict(self, X, features_names=None, meta=None):
        """
        Predict the target using the loaded model.
        :param X: Features for prediction.
        :param features_names: Names of the features, optional.
        :param meta: Additional metadata, optional.
        :return: Predicted target values.
        """
        self.result['shape_input_data'] = str(X.shape)
        logging.info(f"Received request: {X}")
        if not self.loaded:
            self.load()
            self.loaded = True
        predictions = self.model.predict(X)
        return predictions
 
    def send_feedback(self, features, feature_names, reward, truth, routing=""):
        """
        Provide feedback on predictions and update the metrics.
        :param features: Features used for prediction.
        :param feature_names: Names of the features.
        :param reward: Reward signal, not used in this context.
        :param truth: Ground truth target values.
        :param routing: Routing information, optional.
        :return: Empty list as return value is not used.
        """
        predicted = self.predict(features)
        logging.info(f"Predicted: {predicted[0]}, Truth: {truth[0]}")
        if int(truth[0]) == 1:
            if int(predicted[0]) == int(truth[0]):
                self.scores.TP += 1
            else:
                self.scores.FN += 1
        else:
            if int(predicted[0]) == int(truth[0]):
                self.scores.TN += 1
            else:
                self.scores.FP += 1
        return []  # Ignore return statement as its not used
 
    def calculate_metrics(self):
        """
        Calculate the accuracy, precision, recall, and F1-score.
        :return: accuracy, precision, recall, f1_score
        """
        total_samples = self.scores.TP + self.scores.TN + self.scores.FP + self.scores.FN
 
        # Check if there are any samples to avoid division by zero
        if total_samples == 0:
            logging.warning("No samples available to calculate metrics.")
            return 0, 0, 0, 0  # Return zeros for all metrics if no samples
 
        accuracy = (self.scores.TP + self.scores.TN) / total_samples
 
        # Check if there are any positive predictions to calculate precision
        positive_predictions = self.scores.TP + self.scores.FP
        precision = self.scores.TP / positive_predictions if positive_predictions != 0 else 0
 
        # Check if there are any actual positives to calculate recall
        actual_positives = self.scores.TP + self.scores.FN
        recall = self.scores.TP / actual_positives if actual_positives != 0 else 0
 
        # Check if precision and recall are non-zero to calculate F1-score
        if precision + recall == 0:
            f1_score = 0
        else:
            f1_score = 2 * (precision * recall) / (precision + recall)
 
        # Return the calculated metrics
        return accuracy, precision, recall, f1_score
 
 
    def metrics(self):
        """
        Generate metrics for monitoring.
        :return: List of dictionaries containing accuracy, precision, recall, and f1_score.
        """
        accuracy, precision, recall, f1_score = self.calculate_metrics()
        return [
            {"type": "GAUGE", "key": "accuracy", "value": accuracy},
            {"type": "GAUGE", "key": "precision", "value": precision},
            {"type": "GAUGE", "key": "recall", "value": recall},
            {"type": "GAUGE", "key": "f1_score", "value": f1_score},
        ]
         
    def tags(self):
        """
        Retrieve metadata when generating predictions
        :return: Dictionary the intermediate information
        """
        return self.result

これらの4つの重要な側面をカプセル化するクラス内の DockerModel 関数とクラスの詳細を詳しく見ていきましょう。

  1. 読み込みと予測:
    • load(): この関数は、指定されたパスから事前トレーニング済みモデルをインポートする役割を担います。 通常、モデルが使用可能であることを確認するために、予測を行う前に内部的に呼び出されます。
    • predict(X, features_names=None, meta=None): この関数は、読み込まれたモデルをデプロイして予測を行います。 入力特徴量 X、 オプション features_names、および オプションのメタデータ metaを受け取り、予測されたターゲット値を返します。
  2. フィードバック処理:
    • send_feedback(features, feature_names, reward, truth, routing=""): この機能は、モデルを実際のフィードバックに適応させるために不可欠です。 入力データ、真理値、およびその他のパラメーターを受け入れて、モデルのパフォーマンスを評価します。 フィードバックによってモデルの理解が更新され、メトリックが計算されてリアルタイム分析のために保存されます。 これにより、モデルの継続的な再トレーニングが容易になります。
  3. メトリックの計算:
    • calculate_metrics(): この関数は、精度、精度、再現率、および F1 スコアの基本的なメトリックを計算します。 これらのメトリックは、モデルのパフォーマンスに関する定量的な洞察を提供し、継続的な監視と潜在的な改善を可能にします。
    • Score class: この補助クラスは、真陽性 (TP)、偽陽性 (FP)、真陰性 (TN)、偽陰性 (FN) などの二項分類のメトリックを保持するために使用されます DockerModel 。 これは、前述のメトリックの計算に不可欠なこれらのパラメーターを追跡するのに役立ちます。
  4. 監視支援:
    • metrics(): この関数は、モデル監視のメトリックを生成します。 計算された精度、精度、再現率、および F1 スコアを含むディクショナリのリストを返します。 これらのメトリックはプロメテウスメトリックに準拠しており、リアルタイムの監視と分析を容易にします。
    • tags(): この関数は、予測の生成時にカスタム メタデータ データを取得するように設計されており、監視とデバッグを支援します。 要求の性質を追跡および理解するのに役立つディクショナリを返します。

これらの関数とクラスを組み合わせることで、ML モデルのライフサイクル全体をサポートするまとまりのある堅牢な構造が形成されます。 開始の瞬間 (読み込みと予測) から成長 (フィードバック処理) と評価 (メトリック計算)、継続的な警戒 (監視支援) まで、アーキテクチャは、実際の本番環境で ML モデルをデプロイおよび保守するプロセスを標準化および合理化するように設計されています。

このモデル クラスは単なるコードではありません。これは、MLモデルをローカル環境から広大な生産海に運ぶ船です。 これは、標準化のための手段であり、モデルの展開における効率と一貫性を解き放ちます。

この段階で、キャンバスを準備し、傑作の概要を説明しました。 次に、このモデルが Docker イメージにどのようにカプセル化されているか、テクノロジーと戦略を融合させて ML デプロイを再定義するアドベンチャーについて詳しく説明します。 

モデルをローカルでテストする

Docker イメージの作成に取り掛かる前に、モデルをローカルでテストすることが重要です。 このステップは、メインイベントの前のリハーサルとして機能し、モデルがテストデータで期待どおりに機能していることを確認する機会を提供します。

ローカルテストの重要性は、問題を早期に発見し、展開プロセスの後半で潜在的な複雑さを回避する能力にあります。 以下に示すコード例に従って、予想される予測が予想される形式で提供される場合、モデルが次のフェーズの準備ができていることを確認できます。

1
2
3
from DockerModel import DockerModel
demoModel = DockerModel()
demoModel.predict(X_test) # Can take the entire testing dataset or individual predictions

期待される出力は、モデルから予想されるクラス ラベルの形式と一致する必要があります。 すべてが正しく機能すれば、モデルは次の壮大なステップであるDockerイメージ内でのカプセル化に向けて十分に準備されていることが保証されます。

ローカルテストは技術的なプロセス以上のものです。これは、ゲートキーパーとして機能する品質保証手段であり、十分に準備されたモデルのみが前進することを保証します。 これは、展開プロセスで細心の注意が払われていることを示しており、コードを超越し、標準化と効率のコアバリューと共鳴する卓越性への取り組みを反映しています。

ローカル テストが完了すると、Docker イメージの作成という新しいフロンティアの入り口に立っています。 各ステップが ML デプロイの革新と習得への一歩であることを知って、このエキサイティングな旅を続けましょう。

モデルを Docker イメージにカプセル化する

イケアの小売業MLOpsの見解では、モデルは単なるコードのコレクションではありません。 むしろ、コード、依存関係、ML アーティファクトで構成される高度なアセンブリであり、すべてバージョン管理および登録された Docker イメージ内にカプセル化されています。 この構成は、物理的インフラストラクチャの綿密な計画を反映して、慎重に設計されています。

MLOps における Docker の役割は何ですか?

Docker は MLOps で重要な役割を果たし、開発から運用への移行を合理化する標準化された環境を提供します。

  • デプロイの合理化: Docker コンテナは、ML モデルの実行に必要なすべてのものをカプセル化し、デプロイ プロセスを容易にします。
  • コラボレーションの促進: データ サイエンティストとエンジニアは、Docker を使用して、モデルとその依存関係が開発のさまざまな段階で一貫していることを確認できます。
  • モデルの再現性の向上: Docker は、機械学習の重要な側面であるモデルの再現性を高める統一された環境を提供します。
  • オーケストレーション ツールとの統合: Docker は Kubernetes などのオーケストレーション プラットフォームで使用でき、コンテナー化されたアプリケーションのデプロイ、スケーリング、管理を自動化できます。

Dockerとコンテナ化は単なるテクノロジーツールではありません。MLOps のイノベーションと効率性を促進します。 一貫性、スケーラビリティ、俊敏性を確保することで、Dockerは新しい可能性を解き放ち、より俊敏で堅牢なMLデプロイプロセスへの道を開きます。 開発者、データ サイエンティスト、IT プロフェッショナルのいずれであっても、Docker を理解することは、最新のデータ駆動型アプリケーションの複雑で多面的な環境をナビゲートするために不可欠です。

ドッカーファイルの作成

Dockerfile の作成は、建物の建築計画をスケッチするようなものです。 一貫性のある分離された環境でアプリケーションを実行するための Docker イメージを作成する手順の概要を説明します。 この設計により、コード、依存関係、一意のMLアーティファクトを含むモデル全体がまとまりのあるエンティティとして扱われ、IKEA RetailのMLOpsアプローチの包括的なビジョンに沿ったものになります。

この例では、コードだけでなく、モデルの対応するすべてのアーティファクトをカプセル化するという明確な目的で Dockerfile を作成しました。 この意図的な設計により、運用環境へのスムーズな移行が容易になり、開発と展開の間のギャップを効果的に埋めることができます。

このデモでは、IKEA RetailのMLOpsアプローチが、思慮深いエンジニアリングと戦略的実装によってどのように達成されているかを示す具体的な例である次のDockerfileを使用しました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# Use an official Python runtime as a parent image.
# Using a slim image for a smaller final size and reduced attack surface.
FROM python:3.9-slim
 
# Set the maintainer label for metadata.
LABEL maintainer="fernandodorado.rueda@ingka.com"
 
# Set environment variables for a consistent build behavior.
# Disabling the buffer helps to log messages synchronously.
ENV PYTHONUNBUFFERED=1
 
# Set a working directory inside the container to store all our project files.
WORKDIR /app
 
# First, copy the requirements file to leverage Docker's cache for dependencies.
# By doing this first, changes to the code will not invalidate the cached dependencies.
COPY requirements.txt requirements.txt
 
# Install the required packages listed in the requirements file.
# It's a good practice to include the --no-cache-dir flag to prevent the caching of dependencies
# that aren't necessary for executing the application.
RUN pip install --no-cache-dir -r requirements.txt
 
# Copy the rest of the code and model files into the image.
COPY DockerModel.py DockerModel.py
COPY models/    models/
 
# Expose ports that the application will run on.
# Port 5000 for GRPC
# Port 9000 for REST
EXPOSE 5000 9000
 
# Set environment variables used by the application.
ENV MODEL_NAME DockerModel
ENV SERVICE_TYPE MODEL
 
# Change the owner of the directory to user 8888 for security purposes.
# It can prevent unauthorised write access by the application itself.
# Make sure to run the application as this non-root user later if applicable.
RUN chown -R 8888 /app
 
# Use the exec form of CMD so that the application you run will receive UNIX signals.
# This is helpful for graceful shutdown.
# Here we're using seldon-core-microservice to serve the model.
CMD exec seldon-core-microservice $MODEL_NAME --service-type $SERVICE_TYPE

この Dockerfile には、さまざまな部分が含まれています。

  • FROM python:3.9-slim: この行は、公式のPython 3.9スリムイメージを親イメージとして選択します。 サイズと攻撃対象領域が縮小され、効率とセキュリティの両方が強化されるため、好まれています。
  • LABEL maintainer="fernandodorado.rueda@ingka.com": イメージのメンテナーを指定するメタデータラベルで、連絡先情報を提供します。
  • ENV PYTHONUNBUFFERED=1: Python の出力バッファリングを無効にすると、ログメッセージが同期的に出力され、デバッグとログ分析に役立ちます。
  • WORKDIR /app: コンテナ /app内の作業ディレクトリを、すべてのプロジェクト ファイルの集中管理された場所に設定します。
  • COPY requirements.txt requirements.txt: 要件ファイルをイメージにコピーします。 コードの残りの部分をコピーする前にこれを行うと、Docker のキャッシュ メカニズムが利用され、将来のビルドが高速化されます。 このファイルには "seldon-core" パッケージが含まれている必要があります。
1
2
3
4
5
pandas==1.3.5
requests==2.28.1
numpy==1.20
seldon-core==1.14.1
scikit-learn==1.0.2
  • RUN pip install --no-cache-dir -r requirements.txt: 要件ファイルに記載されている必要なパッケージをインストールします。 このフラグ -no-cache-dir は、依存関係の不要なキャッシュを防ぎ、イメージ サイズを縮小します。
  • COPY DockerModel.py DockerModel.py: メインの Python ファイルをイメージにコピーします。
  • COPY models/ models/: モデル ファイルをイメージにコピーします。
  • EXPOSE 5000 9000: ポート 5000 (GRPC) と 9000 (REST) を公開し、コンテナー内のアプリケーションとの通信を許可します。
  • ENV MODEL_NAME DockerModel: モデル名の環境変数を設定します。
  • ENV SERVICE_TYPE MODEL: サービスタイプの環境変数を設定します。
  • RUN chown -R 8888 /app: ディレクトリの所有者をユーザー 8888 に変更します。 アプリケーションを非 root ユーザーとして実行すると、無許可の書き込みアクセスのリスクを軽減できます。
  • CMD exec seldon-core-microservice $MODEL_NAME --service-type $SERVICE_TYPE: を使用して seldon-core-microserviceサービスを開始するコマンドを実行します。 また、モデル名とサービスの種類もパラメーターとして含まれます。 を使用すると exec 、アプリケーションは UNIX シグナルを確実に受信し、グレースフル シャットダウンが容易になります。

ドッカーイメージのビルドとプッシュ

1. ドッカーデスクトップのインストール

まだインストールされていない場合は、このタスクに Docker Desktop をお勧めします。 Docker Desktop は、Docker コンテナーの構築、実行、および管理のプロセスを簡素化するグラフィカル ユーザー インターフェイスを提供します。Docker Desktop は Kubernetes もサポートしており、ローカル クラスターを簡単に作成できます。

2. プロジェクトディレクトリへの移動

ターミナルまたはコマンドプロンプトを開きます。

Dockerfile およびその他の必要なファイルが配置されているフォルダーに移動します。

3. イメージの構築

次のコマンドを実行します。 docker build . -t docker-model:1.0.0

  • docker build . 現在のディレクトリ (.) を使用してイメージをビルドするように Docker に指示します。
  • -t docker-model:1.0.0 イメージに名前 (ドッカー モデル) とタグ (1.0.0) を割り当てます。

ビルド プロセスは、Dockerfile で定義されている指示に従い、モデルの実行に必要な環境全体をカプセル化する Docker イメージを作成します。

4.画像をプッシュする

必要に応じて、イメージを Docker Hub などのコンテナー レジストリ、または組織内のプライベート レジストリにプッシュできます。

このデモでは、イメージがローカル コンテナー レジストリに保持されるため、プロセスが簡略化され、外部レジストリによる認証が不要になります。

Docker を使用して ML モデルをデプロイする: 世界に解き放つ

Docker イメージがビルドされると、その実行は比較的簡単です。 このプロセスを分解してみましょう。

1
docker run --rm --name docker-model -p 9000:9000 docker-model:1.0.0

コマンドのコンポーネント:

  1. docker run: これは、Docker コンテナーを実行するための基本コマンドです。
  2. -rm: このフラグにより、Docker コンテナーは停止後に自動的に削除されます。 これは、特にテストや短時間のタスクのためにコンテナーを実行する場合に、環境をクリーンに保つのに役立ちます。
  3. -name docker-model: 実行中のコンテナーに名前を割り当てます。
  4. p 9000:9000: これにより、ホスト マシンのポート 9000 が Docker コンテナーのポート 9000 にマップされます。 形式は p <host_port>:<container_port>です。 Dockerfile には、アプリケーションが GRPC 用にポート 5000、REST 用にポート 9000 を公開することが記載されているため、このコマンドは、ホスト上のポート 9000 を介して外部ユーザーまたはアプリケーションが REST エンドポイントを使用できるようにします。
  5. docker-model:1.0.0: これにより、実行する Docker イメージの名前とタグが指定されます。 docker-model は名前であり 1.0.0 、ビルド プロセス中に割り当てたバージョン タグです。

次に起こること

コマンドを実行すると、Docker はイメージから docker-model:1.0.0 コンテナー インスタンスを開始します。

Docker コンテナー内のアプリケーションが起動し、ポート 9000 (指定されているとおり) で要求のリッスンを開始します。

ポート マッピングを使用すると、ホスト マシンのポート 9000 での受信要求は、Docker コンテナーのポート 9000 に転送されます。

これで、アプリケーションは、ホストマシン上でネイティブに実行されているかのようにアクセスして操作できます。

Docker を使用してデプロイされたモデルをテストする

Docker イメージを配置したら、モデルの動作を確認します。

予測の生成

モデルから予測へのパスはデリケートなプロセスであり、Seldonが対応している特定の入出力タイプ(ndarray、JSONデータ、STRDATAなど)を理解する必要があります。

このシナリオでは、モデルは配列を想定しているため、ペイロードのキーは "ndarray" です。 これを調整する方法は次のとおりです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import requests
import json
 
 
def send_prediction_request(data):
     
    # Create the headers for the request
    headers = {'Content-Type': 'application/json'}
 
    try:
        # Send the POST request
        response = requests.post(URL, headers=headers, json=data)
         
        # Check if the request was successful
        response.raise_for_status() # Will raise HTTPError if the HTTP request returned an unsuccessful status code
         
        # If successful, return the JSON data
        return response.json()
    except requests.ConnectionError:
        raise Exception("Failed to connect to the server. Is it running?")
    except requests.Timeout:
        raise Exception("Request timed out. Please try again later.")
    except requests.RequestException as err:
        # For any other requests exceptions, re-raise it
        raise Exception(f"An error occurred with your request: {err}")
 
X_test
 
# Define the data payload (We can also use X_test[0:1].tolist() instead of the raw array)
data_payload = {
    "data": {
        "ndarray": [
            [
                1.340e+01, 2.052e+01, 8.864e+01, 5.567e+02, 1.106e-01, 1.469e-01,
                1.445e-01, 8.172e-02, 2.116e-01, 7.325e-02, 3.906e-01, 9.306e-01,
                3.093e+00, 3.367e+01, 5.414e-03, 2.265e-02, 3.452e-02, 1.334e-02,
                1.705e-02, 4.005e-03, 1.641e+01, 2.966e+01, 1.133e+02, 8.444e+02,
                1.574e-01, 3.856e-01, 5.106e-01, 2.051e-01, 3.585e-01, 1.109e-01
            ]
        ]
    }
}
 
 
# Get the response and print it
try:
    response = send_prediction_request(data_payload)
    pretty_json_response = json.dumps(response, indent=4
    print(pretty_json_response)
except Exception as err:
    print(err)

モデルの予測は、次の辞書のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
    "data": {
        "names": [],
        "ndarray": [
            0
        ]
    },
    "meta": {
        "metrics": [
            {
                "key": "accuracy",
                "type": "GAUGE",
                "value": 0
            },
            {
                "key": "precision",
                "type": "GAUGE",
                "value": 0
            },
            {
                "key": "recall",
                "type": "GAUGE",
                "value": 0
            },
            {
                "key": "f1_score",
                "type": "GAUGE",
                "value": 0
            }
        ],
        "tags": {
            "shape_input_data": "(1, 30)"
        }
    }
}

モデルからの応答には、いくつかのキーが含まれます。

  • "data": モデルによって生成された出力を提供します。 私たちの場合、それは予測されたクラスです。
  • "meta": メタデータとモデル メトリックが含まれます。 精度、精度、再現率、f1_scoreなど、分類メトリックの実際の値が表示されます。
  • "tags": 中間メタデータが含まれます。 これには、入力データの形状など、追跡したいものすべてを含めることができます。

上記で概説した構造により、最終的な予測を評価できるだけでなく、中間結果への洞察も得られます。 これらの分析情報は、予測を理解し、潜在的な問題をデバッグするのに役立ちます。

このステージは、モデルのトレーニングから Docker コンテナー内でのデプロイとテストまでの道のりにおける重要なマイルストーンを示します。 ML モデルを標準化する方法と、実際の予測用に設定する方法を見てきました。 この基盤により、このモデルをスケーリング、監視し、本格的な運用環境にさらに統合するための準備が整います。

リアルタイムでフィードバックを送信し、メトリックを計算する

プロビジョニングされた /feedback エンドポイントは、真理値が利用可能になったらモデルに送り返すことができるようにすることで、この学習を容易にします。 これらの真理値を受信すると、モデルのメトリックが更新され、リアルタイムの分析と監視のために他のツールでスクレイピングできます。 次のコードスニペットでは、テストデータセットを反復処理し、POST リクエストを使用して真理値を /feedback エンドポイントに送信します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import requests
import json
 
 
def send_prediction_feedback(data):
     
    # Create the headers for the request
    headers = {'Content-Type': 'application/json'}
 
    try:
        # Send the POST request
        response = requests.post(URL, headers=headers, json=data)
         
        # Check if the request was successful
        response.raise_for_status() # Will raise HTTPError if the HTTP request returned an unsuccessful status code
         
        # If successful, return the JSON data
        return response.json()
    except requests.ConnectionError:
        raise Exception("Failed to connect to the server. Is it running?")
    except requests.Timeout:
        raise Exception("Request timed out. Please try again later.")
    except requests.RequestException as err:
        # For any other requests exceptions, re-raise it
        raise Exception(f"An error occurred with your request: {err}")
 
 
 
for i in range(len(X_test)):
    payload = {'request': {'data': {'ndarray': [X_test[i].tolist()]}}, 'truth': {'data': {'ndarray': [int(y_test[i])]}}}
 
    # Get the response and print it
    try:
        response = send_prediction_feedback(payload)
        pretty_json_response = json.dumps(response, indent=4# Pretty-print JSON
        print(pretty_json_response)
    except Exception as err:
        print(err)

フィードバックを処理した後、モデルは、精度、精度、再現率、F1 スコアなどの主要なメトリックを計算して返します。 その後、これらのメトリックを分析に使用できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
    "data": {
        "ndarray": []
    },
    "meta": {
        "metrics": [
            {
                "key": "accuracy",
                "type": "GAUGE",
                "value": 0.92607003
            },
            {
                "key": "precision",
                "type": "GAUGE",
                "value": 0.9528302
            },
            {
                "key": "recall",
                "type": "GAUGE",
                "value": 0.9294478
            },
            {
                "key": "f1_score",
                "type": "GAUGE",
                "value": 0.9409938
            }
        ],
        "tags": {
            "shape_input_data": "(1, 30)"Ω
        }
    }
}

このアプローチを真に強力なものにしているのは、モデルの進化がトレーニングフェーズに限定されなくなったことです。 代わりに、実際のフィードバックに基づいて、学習、調整、および改良の継続的な状態にあります。

このようにして、静的予測エンジンを導入するだけでなく、解釈するデータの変化する状況によりよく適合できる進化するインテリジェントシステムを促進します。 これは、機械学習のデプロイに対する包括的なアプローチであり、継続的な改善とリアルタイムの適応を促進します。

結論

イケアの小売業では、Dockerは日々のMLOps活動に欠かせない要素となっており、特に本番環境への移行時にモデルの開発と展開を加速する触媒となっています。 Dockerの変革的な影響は、ワークフローを合理化するだけでなく、ワークフローを強化するさまざまな利点を通じて展開されます。

  • 標準化: Docker は、あらゆる ML モデルの開発とデプロイ中に一貫した環境をオーケストレーションし、ライフサイクル全体の均一性と一貫性を促進します。
  • 互換性:多様な環境とシームレスなマルチクラウドまたはオンプレミスの統合をサポートするDockerは、ギャップを埋め、調和のとれたワークフローを保証します。
  • 分離: Docker は、アプリケーションとリソースを確実に分離し、効率と整合性を優先する分離された環境を提供します。
  • セキュリティ:単なる分離を超えて、Dockerはアプリケーションを互いに完全に分離することでセキュリティを強化します。 この堅牢な分離により、トラフィックの流れと管理を正確に制御し、信頼の強力な基盤を築くことができます。

これらの属性は、MLOps ジャーニーにおける具体的な利点に変換され、革新的であるだけでなく堅牢なランドスケープを構築します。

  • アジャイル開発およびデプロイ環境: Docker は、応答性の高い開発およびデプロイ環境を強化し、ML モデルのシームレスな作成、更新、デプロイを可能にします。
  • リソース使用率の最適化: 共有モデル内でコンピューティング/GPU リソースを効率的に利用し、柔軟性を損なうことなくパフォーマンスを最大化します。
  • スケーラブルなデプロイ: Docker のアーキテクチャにより、ML モデルのスケーラブルなデプロイが可能になり、増大する需要に簡単に適応できます。
  • スムーズなリリース サイクル: 既存の CI/CD パイプラインとシームレスに統合する Docker は、モデルのリリース サイクルをスムーズにし、イノベーションの継続的な流れを保証します。
  • 監視ツールとの簡単な統合: Docker の互換性は、Prometheus + Grafana などの監視スタックにまで拡張され、本番環境でモデルを作成してデプロイする際に、MLOps アプローチと完全に一致するまとまりのあるエコシステムを作成します。

これらのメリットが融合することで、IKEA RetailのMLOps戦略は、効率性、セキュリティ、イノベーションのシンフォニーへと変貌します。 Dockerは単なるツールではありません:Dockerは、卓越性の追求と共鳴する哲学です。 Dockerは、創造性と現実、イノベーションと実行をつなぐ架け橋です。

ML デプロイの複雑な世界で、私たちは踏みにじられることはないが、非常にやりがいのある道を模索してきました。 標準化の変革力を活用し、ML モデルをリアルタイムでデプロイして活用するための俊敏で応答性の高い方法を解き放ちました。

しかし、これは結論ではありません。それはしきい値です。 新しい風景が手招きし、成長、探検、革新の機会に溢れています。 次の手順では、現在のアプローチを継続します。 

  • Kubernetes によるスケーリング: 柔軟性と回復力のビーコンであるKubernetesの巨大な可能性を解き放ち、無限の可能性の地平線に導きます。
  • PrometheusやGrafanaなどのオープンソーステクノロジーに基づくリアルタイムの監視およびアラートシステムを適用します
  • リアルタイム検出のためのデータドリフト検出器の接続: データの変化をリアルタイムで検出するためのドリフト検出器の展開と統合。

この探求が、あなたの道を再定義し、新しいアイデアに火をつけ、可能性の限界を押し広げる力になることを願っています。 並外れた未来への入り口は開かれており、鍵は私たちの手の中にあります。

さらに詳しく