orient_express 0.3.2__tar.gz → 0.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: orient_express
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: A library to simplify model deployment to Vertex AI
5
5
  Author: Alexey Zankevich
6
6
  Author-email: alex.zankevich@shiftsmart.com
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
13
  Requires-Dist: google-cloud-aiplatform
14
14
  Requires-Dist: google-cloud-storage
15
+ Requires-Dist: jupyter (>=1.1.1,<2.0.0)
15
16
  Requires-Dist: pandas
16
17
  Description-Content-Type: text/markdown
17
18
 
@@ -1,4 +1,6 @@
1
1
  import os
2
+ from typing import Optional
3
+
2
4
  import joblib
3
5
  import logging
4
6
  import pandas as pd
@@ -9,20 +11,20 @@ from google.cloud import aiplatform
9
11
  class ModelExpress:
10
12
  def __init__(
11
13
  self,
12
- model_name,
13
- project_name,
14
- bucket_name,
15
- model_version=None,
16
- model=None,
17
- region="us-central1",
18
- serialized_model_path="model.joblib",
19
- serving_container_image_uri="us-west1-docker.pkg.dev/shiftsmart-api/orient-express/xgboost-scikit-learn:latest",
20
- serving_container_predict_route="/v1/models/orient-express-model:predict",
21
- serving_container_health_route="/v1/models/orient-express-model",
22
- endpoint_name=None,
14
+ model_name: str,
15
+ project_name: str,
16
+ bucket_name: str,
17
+ model_version: Optional[int] = None,
18
+ model: object = None,
19
+ region: str = "us-central1",
20
+ serialized_model_path: str = "model.joblib",
21
+ serving_container_image_uri: str = "us-west1-docker.pkg.dev/shiftsmart-api/orient-express/xgboost-scikit-learn:latest",
22
+ serving_container_predict_route: str = "/v1/models/orient-express-model:predict",
23
+ serving_container_health_route: str = "/v1/models/orient-express-model",
24
+ endpoint_name: Optional[str] = None,
23
25
  machine_type="n1-standard-4",
24
- min_replica_count=1,
25
- max_replica_count=1,
26
+ min_replica_count: int = 1,
27
+ max_replica_count: int = 1,
26
28
  ):
27
29
  self.model = model
28
30
  self.model_name = model_name
@@ -44,15 +46,22 @@ class ModelExpress:
44
46
  else:
45
47
  self.endpoint_name = endpoint_name
46
48
 
49
+ self._vertex_initialized = False
50
+
47
51
  def colab_auth(self):
48
52
  from google.colab import auth
49
53
 
50
54
  auth.authenticate_user()
51
55
 
52
56
  def _vertex_init(self):
53
- aiplatform.init(project=self.project_name, location=self.region)
54
-
55
- def get_latest_vertex_model(self, model_name):
57
+ if not self._vertex_initialized:
58
+ aiplatform.init(project=self.project_name, location=self.region)
59
+ self._vertex_initialized = True
60
+
61
+ def get_latest_vertex_model(self, model_name: str):
62
+ """If there are a few models with the same name, load the most recent one.
63
+ It's highly recommended to keep only 1 model with the same name to avoid the confusion
64
+ """
56
65
  self._vertex_init()
57
66
 
58
67
  # Search for models with the specified display name
@@ -89,7 +98,7 @@ class ModelExpress:
89
98
 
90
99
  return self.create_model_version(new_version, last_model)
91
100
 
92
- def get_artifacts_path(self, version, file_name=None):
101
+ def get_artifacts_path(self, version: int, file_name: str = None):
93
102
  dir_name = f"models/{self.model_name}/{version}"
94
103
  if file_name:
95
104
  return f"{dir_name}/{file_name}"
@@ -152,7 +161,9 @@ class ModelExpress:
152
161
  traffic_percentage=100,
153
162
  )
154
163
 
155
- def remote_predict(self, input_df):
164
+ def remote_predict(self, input_df: pd.DataFrame):
165
+ self._vertex_init()
166
+
156
167
  if not self.endpoint:
157
168
  endpoint = self.get_endpoint()
158
169
  if not endpoint:
@@ -165,27 +176,31 @@ class ModelExpress:
165
176
  predictions = self.endpoint.predict(instances=instances)
166
177
  return predictions.predictions
167
178
 
168
- def local_predict(self, input_df):
179
+ def local_predict(self, input_df: pd.DataFrame):
180
+ self._vertex_init()
181
+
169
182
  if not self.model:
170
183
  self.load_model_from_registry()
171
184
 
172
185
  return self.model.predict(input_df)
173
186
 
174
- def local_predict_proba(self, input_df):
187
+ def local_predict_proba(self, input_df: pd.DataFrame):
175
188
  if not self.model:
176
189
  self.load_model_from_registry()
177
190
 
178
191
  return self.model.predict_proba(input_df)
179
192
 
180
193
  def load_model_from_registry(self):
194
+ self._vertex_init()
195
+
196
+ vertex_model = self.get_latest_vertex_model(self.model_name)
197
+
181
198
  if self.model_version:
199
+ # reload the model using a specific model version
182
200
  vertex_model = aiplatform.Model(
183
- model_name=self.model_name, version=self.model_version
201
+ model_name=vertex_model.resource_name, version=str(self.model_version)
184
202
  )
185
203
 
186
- else:
187
- vertex_model = self.get_latest_vertex_model(self.model_name)
188
-
189
204
  if not vertex_model:
190
205
  raise Exception(f"Model '{self.model_name}' not found in the registry.")
191
206
 
@@ -194,7 +209,7 @@ class ModelExpress:
194
209
 
195
210
  self.model = joblib.load(self.serialized_model_path)
196
211
 
197
- def download_artifacts(self, artifact_uri):
212
+ def download_artifacts(self, artifact_uri: str):
198
213
  storage_client = storage.Client()
199
214
  bucket_name, artifact_path = artifact_uri.replace("gs://", "").split("/", 1)
200
215
  bucket = storage_client.bucket(bucket_name)
@@ -0,0 +1,81 @@
1
+ from sklearn.base import BaseEstimator, TransformerMixin
2
+ from sklearn.preprocessing import LabelEncoder
3
+
4
+
5
+ class LabelEncoderTransformer(BaseEstimator, TransformerMixin):
6
+ """
7
+ A wrapper class that integrates a model and a label encoder to handle
8
+ encoded predictions and their corresponding probabilities.
9
+
10
+ Attributes:
11
+ model (object): A machine learning model with `fit`, `predict`, and `predict_proba` methods.
12
+ label_encoder (object): A label encoder that maps encoded class labels to their original string labels.
13
+
14
+ Methods:
15
+ fit(X, y):
16
+ Trains the model on the provided features and labels.
17
+ predict(X):
18
+ Predicts the classes for the given features and returns the original string labels.
19
+ predict_proba(X):
20
+ Returns the probabilities for each class for the given features, along with their original class labels.
21
+ """
22
+
23
+ def __init__(self, model: BaseEstimator, label_encoder: LabelEncoder):
24
+ """
25
+ Initializes the LabelEncoderTransformer.
26
+
27
+ Args:
28
+ model (BaseEstimator): A machine learning model.
29
+ label_encoder (LabelEncoder): A label encoder with `fit` and `inverse_transform` methods.
30
+ """
31
+ self.model = model
32
+ self.label_encoder = label_encoder
33
+
34
+ def fit(self, X, y):
35
+ """
36
+ Fits the model to the training data.
37
+
38
+ Args:
39
+ X (array-like): Feature matrix.
40
+ y (array-like): Target labels.
41
+
42
+ Returns:
43
+ self: Fitted LabelEncoderTransformer instance.
44
+ """
45
+ self.model.fit(X, y)
46
+ return self
47
+
48
+ def predict(self, X):
49
+ """
50
+ Predicts the target labels and returns the original string labels.
51
+
52
+ Args:
53
+ X (array-like): Feature matrix for predictions.
54
+
55
+ Returns:
56
+ array-like: Predicted class labels in their original form.
57
+ """
58
+ encoded_predictions = self.model.predict(X)
59
+ return self.label_encoder.inverse_transform(encoded_predictions)
60
+
61
+ def predict_proba(self, X):
62
+ """
63
+ Returns class probabilities along with their original labels.
64
+
65
+ Args:
66
+ X (array-like): Feature matrix for predictions.
67
+
68
+ Returns:
69
+ list: A list of lists, where each inner list contains `[class_name, probability]` pairs.
70
+ """
71
+ # Get raw probabilities
72
+ probabilities = self.model.predict_proba(X)
73
+
74
+ # Get all class labels
75
+ class_names = self.label_encoder.classes_
76
+ # Combine class names with probabilities
77
+ combined_output = [
78
+ [[class_name, prob] for class_name, prob in zip(class_names, sample_probs)]
79
+ for sample_probs in probabilities
80
+ ]
81
+ return combined_output
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "orient_express"
3
- version = "0.3.2"
3
+ version = "0.3.4"
4
4
  description = "A library to simplify model deployment to Vertex AI"
5
5
  authors = ["Alexey Zankevich <alex.zankevich@shiftsmart.com>"]
6
6
  readme = "README.md"
@@ -10,6 +10,7 @@ python = ">=3.9,<3.13"
10
10
  google-cloud-aiplatform = "*"
11
11
  google-cloud-storage = "*"
12
12
  pandas = "*"
13
+ jupyter = "^1.1.1"
13
14
 
14
15
  [tool.poetry.dev-dependencies]
15
16
  black = "24.10.0"
File without changes