autogluon.cloud 0.3.0__tar.gz → 0.3.1__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.
- {autogluon.cloud-0.3.0/src/autogluon.cloud.egg-info → autogluon.cloud-0.3.1}/PKG-INFO +1 -1
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/setup.py +3 -2
- autogluon.cloud-0.3.1/src/autogluon/cloud/endpoint/__init__.py +0 -0
- autogluon.cloud-0.3.1/src/autogluon/cloud/scripts/ray_scripts/__init__.py +0 -0
- autogluon.cloud-0.3.1/src/autogluon/cloud/scripts/ray_scripts/train.py +124 -0
- autogluon.cloud-0.3.1/src/autogluon/cloud/scripts/sagemaker_scripts/__init__.py +0 -0
- autogluon.cloud-0.3.1/src/autogluon/cloud/scripts/sagemaker_scripts/multimodal_serve.py +155 -0
- autogluon.cloud-0.3.1/src/autogluon/cloud/scripts/sagemaker_scripts/tabular_serve.py +121 -0
- autogluon.cloud-0.3.1/src/autogluon/cloud/scripts/sagemaker_scripts/timeseries_serve.py +93 -0
- autogluon.cloud-0.3.1/src/autogluon/cloud/scripts/sagemaker_scripts/train.py +173 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/version.py +1 -1
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1/src/autogluon.cloud.egg-info}/PKG-INFO +1 -1
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon.cloud.egg-info/SOURCES.txt +8 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/LICENSE +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/NOTICE +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/README.md +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/pyproject.toml +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/setup.cfg +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/__init__.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/__init__.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/backend/__init__.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/backend/backend.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/backend/backend_factory.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/backend/constant.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/backend/multimodal_sagemaker_backend.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/backend/ray_aws_backend.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/backend/ray_backend.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/backend/sagemaker_backend.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/backend/tabular_sagemaker_backend.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/backend/timeseries_sagemaker_backend.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/cluster/__init__.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/cluster/cluster_config_generator.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/cluster/cluster_manager.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/cluster/constants.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/cluster/ray_aws_cluster_config_generator.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/cluster/ray_aws_cluster_manager.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/cluster/ray_cluster_config_generator.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/cluster/ray_cluster_manager.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/data/__init__.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/data/format_converter.py +0 -0
- {autogluon.cloud-0.3.0/src/autogluon/cloud/endpoint → autogluon.cloud-0.3.1/src/autogluon/cloud/default_cluster_configs}/__init__.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/endpoint/endpoint.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/endpoint/sagemaker_endpoint.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/job/__init__.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/job/ray_job.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/job/remote_job.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/job/sagemaker_job.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/predictor/__init__.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/predictor/cloud_predictor.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/predictor/multimodal_cloud_predictor.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/predictor/tabular_cloud_predictor.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/predictor/timeseries_cloud_predictor.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/scripts/__init__.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/scripts/script_manager.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/utils/__init__.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/utils/ag_sagemaker.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/utils/aws_utils.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/utils/constants.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/utils/deserializers.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/utils/dlc_utils.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/utils/ec2.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/utils/iam.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/utils/misc.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/utils/ray_aws_iam.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/utils/s3_utils.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/utils/sagemaker_iam.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/utils/serializers.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/utils/utils.py +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon.cloud.egg-info/dependency_links.txt +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon.cloud.egg-info/namespace_packages.txt +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon.cloud.egg-info/requires.txt +3 -3
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon.cloud.egg-info/top_level.txt +0 -0
- {autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon.cloud.egg-info/zip-safe +0 -0
|
@@ -67,7 +67,8 @@ def default_setup_args(*, version):
|
|
|
67
67
|
package_data={
|
|
68
68
|
AUTOGLUON: [
|
|
69
69
|
"LICENSE",
|
|
70
|
-
]
|
|
70
|
+
],
|
|
71
|
+
'autogluon.cloud': ['default_cluster_configs/*.yaml'],
|
|
71
72
|
},
|
|
72
73
|
classifiers=[
|
|
73
74
|
"Development Status :: 4 - Beta",
|
|
@@ -103,7 +104,7 @@ def default_setup_args(*, version):
|
|
|
103
104
|
return setup_args
|
|
104
105
|
|
|
105
106
|
|
|
106
|
-
version = "0.3.
|
|
107
|
+
version = "0.3.1"
|
|
107
108
|
version = update_version(version, use_file_if_exists=False, create_file=True)
|
|
108
109
|
|
|
109
110
|
install_requires = [
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import pickle
|
|
4
|
+
import shutil
|
|
5
|
+
import time
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import boto3
|
|
10
|
+
import ray
|
|
11
|
+
|
|
12
|
+
from autogluon.common.utils.s3_utils import s3_path_to_bucket_prefix
|
|
13
|
+
from autogluon.tabular import TabularDataset, TabularPredictor
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def wait_for_nodes_to_be_ready():
|
|
17
|
+
expected_num_nodes = int(os.environ.get("AG_NUM_NODES"))
|
|
18
|
+
ray.init("auto")
|
|
19
|
+
print("Waiting for worker nodes to be ready")
|
|
20
|
+
while len(ray.nodes()) < expected_num_nodes:
|
|
21
|
+
time.sleep(5)
|
|
22
|
+
ray.shutdown()
|
|
23
|
+
print("All nodes ready")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_utc_timestamp_now():
|
|
27
|
+
return datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def upload_log_file(job_id: str, model_output_path: str):
|
|
31
|
+
from ray.job_submission import JobSubmissionClient
|
|
32
|
+
|
|
33
|
+
client = JobSubmissionClient("http://127.0.0.1:8265")
|
|
34
|
+
logs = client.get_job_logs(job_id=job_id)
|
|
35
|
+
log_file = "training.log"
|
|
36
|
+
with open(log_file, "w") as f:
|
|
37
|
+
f.write(logs)
|
|
38
|
+
cloud_bucket, cloud_prefix = s3_path_to_bucket_prefix(model_output_path)
|
|
39
|
+
upload_file(file_name=log_file, bucket=cloud_bucket, prefix=cloud_prefix)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def tear_down_cluster(cluster_config_file: str):
|
|
43
|
+
import subprocess
|
|
44
|
+
|
|
45
|
+
print("Will tear down the cluster in 10 secs")
|
|
46
|
+
time.sleep(10)
|
|
47
|
+
cmd = f"ray stop --force; ray down {cluster_config_file} -y"
|
|
48
|
+
subprocess.Popen(cmd, shell=True, preexec_fn=os.setpgrp) # Avoid being terminated because ray runtime is down
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def upload_file(file_name: str, bucket: str, prefix: Optional[str] = None):
|
|
52
|
+
"""
|
|
53
|
+
Upload a file to an S3 bucket
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
file_name: str,
|
|
58
|
+
File to upload
|
|
59
|
+
bucket: str,
|
|
60
|
+
Bucket to upload to
|
|
61
|
+
prefix: Optional[str], default = None
|
|
62
|
+
S3 prefix. If not specified then will upload to the root of the bucket
|
|
63
|
+
"""
|
|
64
|
+
object_name = os.path.basename(file_name)
|
|
65
|
+
if prefix is not None and len(prefix) == 0:
|
|
66
|
+
prefix = None
|
|
67
|
+
if prefix is not None:
|
|
68
|
+
object_name = prefix + "/" + object_name
|
|
69
|
+
|
|
70
|
+
# Upload the file
|
|
71
|
+
s3_client = boto3.client("s3")
|
|
72
|
+
s3_client.upload_file(file_name, bucket, object_name)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
parser = argparse.ArgumentParser()
|
|
77
|
+
parser.add_argument("--ag_args_path", type=str, required=True)
|
|
78
|
+
parser.add_argument("--train_data", type=str, required=True)
|
|
79
|
+
parser.add_argument("--tune_data", type=str, required=False)
|
|
80
|
+
parser.add_argument("--model_output_path", type=str, required=True)
|
|
81
|
+
parser.add_argument("--ray_job_id", type=str, required=True)
|
|
82
|
+
parser.add_argument("--leaderboard", action="store_true")
|
|
83
|
+
parser.add_argument("--cluster_config_file", type=str, required=False)
|
|
84
|
+
parser.set_defaults(leaderboard=False)
|
|
85
|
+
args = parser.parse_args()
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
wait_for_nodes_to_be_ready()
|
|
89
|
+
train_data = TabularDataset(args.train_data)
|
|
90
|
+
tune_data = None
|
|
91
|
+
if args.tune_data is not None:
|
|
92
|
+
tune_data = TabularDataset(args.tune_data)
|
|
93
|
+
with open(args.ag_args_path, "rb") as f:
|
|
94
|
+
ag_args = pickle.load(f)
|
|
95
|
+
predictor_init_args = ag_args["predictor_init_args"]
|
|
96
|
+
predictor_fit_args = ag_args["predictor_fit_args"]
|
|
97
|
+
save_path = f"ag_distributed_training_{get_utc_timestamp_now()}"
|
|
98
|
+
predictor_init_args["path"] = save_path
|
|
99
|
+
|
|
100
|
+
print("Start training TabularPredictor")
|
|
101
|
+
predictor = TabularPredictor(**predictor_init_args).fit(
|
|
102
|
+
train_data=train_data, tuning_data=tune_data, **predictor_fit_args
|
|
103
|
+
)
|
|
104
|
+
print("Compressing the model artifacts")
|
|
105
|
+
model_artifact = shutil.make_archive("model", "zip", save_path)
|
|
106
|
+
model_output_path = os.path.dirname(args.model_output_path)
|
|
107
|
+
cloud_bucket, cloud_prefix = s3_path_to_bucket_prefix(model_output_path)
|
|
108
|
+
print(f"Uploading model artifact to {model_output_path}")
|
|
109
|
+
upload_file(file_name=model_artifact, bucket=cloud_bucket, prefix=cloud_prefix)
|
|
110
|
+
|
|
111
|
+
if args.leaderboard:
|
|
112
|
+
lb = predictor.leaderboard(silent=False)
|
|
113
|
+
leaderboard_file = "leaderboard.csv"
|
|
114
|
+
lb.to_csv(leaderboard_file)
|
|
115
|
+
upload_file(file_name=leaderboard_file, bucket=cloud_bucket, prefix=cloud_prefix)
|
|
116
|
+
print("Training finished")
|
|
117
|
+
except Exception as e:
|
|
118
|
+
raise e
|
|
119
|
+
finally:
|
|
120
|
+
upload_log_file(job_id=args.ray_job_id, model_output_path=os.path.dirname(args.model_output_path))
|
|
121
|
+
|
|
122
|
+
cluster_config_file = args.cluster_config_file
|
|
123
|
+
if cluster_config_file is not None:
|
|
124
|
+
tear_down_cluster(cluster_config_file)
|
|
File without changes
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# flake8: noqa
|
|
2
|
+
import base64
|
|
3
|
+
import copy
|
|
4
|
+
import os
|
|
5
|
+
import pickle
|
|
6
|
+
from io import BytesIO, StringIO
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
from autogluon.core.constants import BINARY, MULTICLASS
|
|
12
|
+
from autogluon.core.utils import get_pred_from_proba_df
|
|
13
|
+
from autogluon.multimodal import MultiModalPredictor
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _cleanup_images():
|
|
17
|
+
files = os.listdir(".")
|
|
18
|
+
for file in files:
|
|
19
|
+
if file.endswith(".png"):
|
|
20
|
+
os.remove(file)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def model_fn(model_dir):
|
|
24
|
+
"""Loads model from previously saved artifact"""
|
|
25
|
+
model = MultiModalPredictor.load(model_dir)
|
|
26
|
+
|
|
27
|
+
# Check for _learner class which is available post 1.0.0 version of AutoGluon
|
|
28
|
+
if hasattr(model, "_learner") and hasattr(model._learner, "_label_column"):
|
|
29
|
+
label_column = model._learner._label_column
|
|
30
|
+
column_types = copy.copy(model._learner._column_types)
|
|
31
|
+
else:
|
|
32
|
+
# Fallback for older versions
|
|
33
|
+
label_column = model._label_column
|
|
34
|
+
column_types = copy.copy(model._column_types)
|
|
35
|
+
column_types.pop(label_column)
|
|
36
|
+
globals()["column_names"] = list(column_types.keys())
|
|
37
|
+
|
|
38
|
+
return model
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def transform_fn(model, request_body, input_content_type, output_content_type="application/json"):
|
|
42
|
+
image_bytearrays = None
|
|
43
|
+
inference_kwargs = {}
|
|
44
|
+
if input_content_type == "application/x-parquet":
|
|
45
|
+
buf = BytesIO(request_body)
|
|
46
|
+
data = pd.read_parquet(buf)
|
|
47
|
+
|
|
48
|
+
elif input_content_type == "text/csv":
|
|
49
|
+
buf = StringIO(request_body)
|
|
50
|
+
data = pd.read_csv(buf)
|
|
51
|
+
|
|
52
|
+
elif input_content_type == "application/json":
|
|
53
|
+
buf = StringIO(request_body)
|
|
54
|
+
data = pd.read_json(buf)
|
|
55
|
+
|
|
56
|
+
elif input_content_type == "application/jsonl":
|
|
57
|
+
buf = StringIO(request_body)
|
|
58
|
+
data = pd.read_json(buf, orient="records", lines=True)
|
|
59
|
+
|
|
60
|
+
elif input_content_type == "application/x-npy":
|
|
61
|
+
buf = BytesIO(request_body)
|
|
62
|
+
data = np.load(buf, allow_pickle=True)
|
|
63
|
+
image_bytearrays = []
|
|
64
|
+
for _bytes in data:
|
|
65
|
+
im_bytes = base64.b85decode(_bytes)
|
|
66
|
+
image_bytearrays.append(im_bytes)
|
|
67
|
+
|
|
68
|
+
elif input_content_type == "application/x-autogluon-parquet":
|
|
69
|
+
buf = bytes(request_body)
|
|
70
|
+
payload = pickle.loads(buf)
|
|
71
|
+
data = pd.read_parquet(BytesIO(payload["data"]))
|
|
72
|
+
inference_kwargs = payload["inference_kwargs"]
|
|
73
|
+
if inference_kwargs is None:
|
|
74
|
+
inference_kwargs = {}
|
|
75
|
+
|
|
76
|
+
elif input_content_type == "application/x-autogluon-npy":
|
|
77
|
+
buf = bytes(request_body)
|
|
78
|
+
payload = pickle.loads(buf)
|
|
79
|
+
data = np.load(BytesIO(payload["data"]), allow_pickle=True)
|
|
80
|
+
image_bytearrays = []
|
|
81
|
+
for _bytes in data:
|
|
82
|
+
im_bytes = base64.b85decode(_bytes)
|
|
83
|
+
image_bytearrays.append(im_bytes)
|
|
84
|
+
inference_kwargs = payload["inference_kwargs"]
|
|
85
|
+
if inference_kwargs is None:
|
|
86
|
+
inference_kwargs = {}
|
|
87
|
+
|
|
88
|
+
elif input_content_type == "application/x-image":
|
|
89
|
+
image_bytearrays = []
|
|
90
|
+
image_bytearrays.append(request_body)
|
|
91
|
+
|
|
92
|
+
else:
|
|
93
|
+
raise ValueError(f"{input_content_type} input content type not supported.")
|
|
94
|
+
|
|
95
|
+
if image_bytearrays is not None:
|
|
96
|
+
data = dict(image=image_bytearrays) # multimodal image prediction takes in a dict containing image bytearrays
|
|
97
|
+
else:
|
|
98
|
+
# no header
|
|
99
|
+
test_columns = sorted(list(data.columns))
|
|
100
|
+
train_columns = sorted(column_names)
|
|
101
|
+
if test_columns != train_columns:
|
|
102
|
+
num_cols = len(data.columns)
|
|
103
|
+
|
|
104
|
+
if num_cols != len(column_names):
|
|
105
|
+
raise Exception(
|
|
106
|
+
f"Invalid data format. Input data has {num_cols} while the model expects {len(column_names)}"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
else:
|
|
110
|
+
new_row = pd.DataFrame(data.columns).transpose()
|
|
111
|
+
old_rows = pd.DataFrame(data.values)
|
|
112
|
+
data = pd.concat([new_row, old_rows]).reset_index(drop=True)
|
|
113
|
+
data.columns = column_names
|
|
114
|
+
# find image column
|
|
115
|
+
image_column = None
|
|
116
|
+
# Check for _learner class which is available post 1.0.0 version of AutoGluon
|
|
117
|
+
if hasattr(model, "_learner") and hasattr(model._learner, "_column_types"):
|
|
118
|
+
column_types = model._learner._column_types
|
|
119
|
+
else:
|
|
120
|
+
# Fallback for older versions
|
|
121
|
+
column_types = model._column_types
|
|
122
|
+
for column_name, column_type in column_types.items():
|
|
123
|
+
if column_type in ("image_path", "image", "image_bytearray"):
|
|
124
|
+
image_column = column_name
|
|
125
|
+
break
|
|
126
|
+
if image_column is not None:
|
|
127
|
+
print(f"Detected image column {image_column}")
|
|
128
|
+
data[image_column] = [base64.b85decode(_bytes) for _bytes in data[image_column]]
|
|
129
|
+
|
|
130
|
+
if model.problem_type == BINARY or model.problem_type == MULTICLASS:
|
|
131
|
+
pred_proba = model.predict_proba(data, as_pandas=True, **inference_kwargs)
|
|
132
|
+
pred = get_pred_from_proba_df(pred_proba, problem_type=model.problem_type)
|
|
133
|
+
pred_proba.columns = [str(c) + "_proba" for c in pred_proba.columns]
|
|
134
|
+
pred.name = model.label
|
|
135
|
+
prediction = pd.concat([pred, pred_proba], axis=1)
|
|
136
|
+
else:
|
|
137
|
+
prediction = model.predict(data, as_pandas=True, **inference_kwargs)
|
|
138
|
+
if isinstance(prediction, pd.Series):
|
|
139
|
+
prediction = prediction.to_frame()
|
|
140
|
+
|
|
141
|
+
if "application/x-parquet" in output_content_type:
|
|
142
|
+
output = prediction.to_parquet()
|
|
143
|
+
output_content_type = "application/x-parquet"
|
|
144
|
+
elif "application/json" in output_content_type:
|
|
145
|
+
output = prediction.to_json()
|
|
146
|
+
output_content_type = "application/json"
|
|
147
|
+
elif "text/csv" in output_content_type:
|
|
148
|
+
output = prediction.to_csv(index=None)
|
|
149
|
+
output_content_type = "text/csv"
|
|
150
|
+
else:
|
|
151
|
+
raise ValueError(f"{output_content_type} content type not supported")
|
|
152
|
+
|
|
153
|
+
_cleanup_images()
|
|
154
|
+
|
|
155
|
+
return output, output_content_type
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# flake8: noqa
|
|
2
|
+
import base64
|
|
3
|
+
import hashlib
|
|
4
|
+
import os
|
|
5
|
+
import pickle
|
|
6
|
+
from io import BytesIO, StringIO
|
|
7
|
+
|
|
8
|
+
import pandas as pd
|
|
9
|
+
from PIL import Image
|
|
10
|
+
|
|
11
|
+
from autogluon.core.constants import QUANTILE, REGRESSION
|
|
12
|
+
from autogluon.core.utils import get_pred_from_proba_df
|
|
13
|
+
from autogluon.tabular import TabularPredictor
|
|
14
|
+
|
|
15
|
+
image_dir = os.path.join("/tmp", "ag_images")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _save_image_and_update_dataframe_column(bytes):
|
|
19
|
+
os.makedirs(image_dir, exist_ok=True)
|
|
20
|
+
im_bytes = base64.b85decode(bytes)
|
|
21
|
+
# nosec B303 - not a cryptographic use
|
|
22
|
+
im_hash = hashlib.sha1(im_bytes).hexdigest()
|
|
23
|
+
im = Image.open(BytesIO(im_bytes))
|
|
24
|
+
im_name = f"tabular_image_{im_hash}.png"
|
|
25
|
+
im_path = os.path.join(image_dir, im_name)
|
|
26
|
+
im.save(im_path)
|
|
27
|
+
print(f"Image saved as {im_path}")
|
|
28
|
+
|
|
29
|
+
return im_path
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def model_fn(model_dir):
|
|
33
|
+
"""loads model from previously saved artifact"""
|
|
34
|
+
model = TabularPredictor.load(model_dir)
|
|
35
|
+
model.persist_models()
|
|
36
|
+
globals()["column_names"] = model.original_features
|
|
37
|
+
|
|
38
|
+
return model
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def transform_fn(model, request_body, input_content_type, output_content_type="application/json"):
|
|
42
|
+
inference_kwargs = {}
|
|
43
|
+
if input_content_type == "application/x-parquet":
|
|
44
|
+
buf = BytesIO(request_body)
|
|
45
|
+
data = pd.read_parquet(buf)
|
|
46
|
+
|
|
47
|
+
elif input_content_type == "text/csv":
|
|
48
|
+
buf = StringIO(request_body)
|
|
49
|
+
data = pd.read_csv(buf)
|
|
50
|
+
|
|
51
|
+
elif input_content_type == "application/json":
|
|
52
|
+
buf = StringIO(request_body)
|
|
53
|
+
data = pd.read_json(buf)
|
|
54
|
+
|
|
55
|
+
elif input_content_type == "application/jsonl":
|
|
56
|
+
buf = StringIO(request_body)
|
|
57
|
+
data = pd.read_json(buf, orient="records", lines=True)
|
|
58
|
+
|
|
59
|
+
elif input_content_type == "application/x-autogluon":
|
|
60
|
+
buf = bytes(request_body)
|
|
61
|
+
payload = pickle.loads(buf)
|
|
62
|
+
data = pd.read_parquet(BytesIO(payload["data"]))
|
|
63
|
+
inference_kwargs = payload["inference_kwargs"]
|
|
64
|
+
if inference_kwargs is None:
|
|
65
|
+
inference_kwargs = {}
|
|
66
|
+
|
|
67
|
+
else:
|
|
68
|
+
raise ValueError(f"{input_content_type} input content type not supported.")
|
|
69
|
+
|
|
70
|
+
test_columns = sorted(list(data.columns))
|
|
71
|
+
train_columns = sorted(column_names)
|
|
72
|
+
if test_columns != train_columns:
|
|
73
|
+
num_cols = len(data.columns)
|
|
74
|
+
|
|
75
|
+
if num_cols != len(column_names):
|
|
76
|
+
raise Exception(
|
|
77
|
+
f"Invalid data format. Input data has {num_cols} while the model expects {len(column_names)}"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
else:
|
|
81
|
+
new_row = pd.DataFrame(data.columns).transpose()
|
|
82
|
+
old_rows = pd.DataFrame(data.values)
|
|
83
|
+
data = pd.concat([new_row, old_rows]).reset_index(drop=True)
|
|
84
|
+
data.columns = column_names
|
|
85
|
+
|
|
86
|
+
# find image column
|
|
87
|
+
image_column = None
|
|
88
|
+
for column_name, special_types in model.feature_metadata.get_type_map_special().items():
|
|
89
|
+
if "image_path" in special_types:
|
|
90
|
+
image_column = column_name
|
|
91
|
+
break
|
|
92
|
+
# save image column bytes to disk and update the column with saved path
|
|
93
|
+
if image_column is not None:
|
|
94
|
+
print(f"Detected image column {image_column}")
|
|
95
|
+
data[image_column] = [_save_image_and_update_dataframe_column(bytes) for bytes in data[image_column]]
|
|
96
|
+
|
|
97
|
+
if model.problem_type not in [REGRESSION, QUANTILE]:
|
|
98
|
+
pred_proba = model.predict_proba(data, as_pandas=True, **inference_kwargs)
|
|
99
|
+
pred = get_pred_from_proba_df(pred_proba, problem_type=model.problem_type)
|
|
100
|
+
pred_proba.columns = [str(c) + "_proba" for c in pred_proba.columns]
|
|
101
|
+
pred.name = model.label
|
|
102
|
+
prediction = pd.concat([pred, pred_proba], axis=1)
|
|
103
|
+
else:
|
|
104
|
+
prediction = model.predict(data, as_pandas=True, **inference_kwargs)
|
|
105
|
+
if isinstance(prediction, pd.Series):
|
|
106
|
+
prediction = prediction.to_frame()
|
|
107
|
+
|
|
108
|
+
if "application/x-parquet" in output_content_type:
|
|
109
|
+
prediction.columns = prediction.columns.astype(str)
|
|
110
|
+
output = prediction.to_parquet()
|
|
111
|
+
output_content_type = "application/x-parquet"
|
|
112
|
+
elif "application/json" in output_content_type:
|
|
113
|
+
output = prediction.to_json()
|
|
114
|
+
output_content_type = "application/json"
|
|
115
|
+
elif "text/csv" in output_content_type:
|
|
116
|
+
output = prediction.to_csv(index=None)
|
|
117
|
+
output_content_type = "text/csv"
|
|
118
|
+
else:
|
|
119
|
+
raise ValueError(f"{output_content_type} content type not supported")
|
|
120
|
+
|
|
121
|
+
return output, output_content_type
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# flake8: noqa
|
|
2
|
+
import os
|
|
3
|
+
import pickle
|
|
4
|
+
import shutil
|
|
5
|
+
from io import BytesIO, StringIO
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
from autogluon.timeseries import TimeSeriesDataFrame, TimeSeriesPredictor
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def model_fn(model_dir):
|
|
13
|
+
"""loads model from previously saved artifact"""
|
|
14
|
+
# TSPredictor will write to the model file during inference while the default model_dir is read only
|
|
15
|
+
# Copy the model file to a writable location as a temporary workaround
|
|
16
|
+
tmp_model_dir = os.path.join("/tmp", "model")
|
|
17
|
+
try:
|
|
18
|
+
shutil.copytree(model_dir, tmp_model_dir, dirs_exist_ok=False)
|
|
19
|
+
except:
|
|
20
|
+
# model already copied
|
|
21
|
+
pass
|
|
22
|
+
model = TimeSeriesPredictor.load(tmp_model_dir)
|
|
23
|
+
print("MODEL LOADED")
|
|
24
|
+
return model
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def prepare_timeseries_dataframe(df, predictor):
|
|
28
|
+
target = predictor.target
|
|
29
|
+
cols = df.columns.to_list()
|
|
30
|
+
id_column = cols[0]
|
|
31
|
+
timestamp_column = cols[1]
|
|
32
|
+
df[timestamp_column] = pd.to_datetime(df[timestamp_column])
|
|
33
|
+
static_features = None
|
|
34
|
+
if target != cols[-1]:
|
|
35
|
+
# target is not the last column, then there are static features being merged in
|
|
36
|
+
target_index = cols.index(target)
|
|
37
|
+
static_columns = cols[target_index + 1 :]
|
|
38
|
+
static_features = df[[id_column] + static_columns].groupby([id_column], sort=False).head(1)
|
|
39
|
+
static_features.set_index(id_column, inplace=True)
|
|
40
|
+
df.drop(columns=static_columns, inplace=True)
|
|
41
|
+
df = TimeSeriesDataFrame.from_data_frame(df, id_column=id_column, timestamp_column=timestamp_column)
|
|
42
|
+
if static_features is not None:
|
|
43
|
+
df.static_features = static_features
|
|
44
|
+
return df
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def transform_fn(model, request_body, input_content_type, output_content_type="application/json"):
|
|
48
|
+
inference_kwargs = {}
|
|
49
|
+
if input_content_type == "application/x-parquet":
|
|
50
|
+
buf = BytesIO(request_body)
|
|
51
|
+
data = pd.read_parquet(buf)
|
|
52
|
+
|
|
53
|
+
elif input_content_type == "text/csv":
|
|
54
|
+
buf = StringIO(request_body)
|
|
55
|
+
data = pd.read_csv(buf)
|
|
56
|
+
|
|
57
|
+
elif input_content_type == "application/json":
|
|
58
|
+
buf = StringIO(request_body)
|
|
59
|
+
data = pd.read_json(buf)
|
|
60
|
+
|
|
61
|
+
elif input_content_type == "application/jsonl":
|
|
62
|
+
buf = StringIO(request_body)
|
|
63
|
+
data = pd.read_json(buf, orient="records", lines=True)
|
|
64
|
+
|
|
65
|
+
elif input_content_type == "application/x-autogluon":
|
|
66
|
+
buf = bytes(request_body)
|
|
67
|
+
payload = pickle.loads(buf)
|
|
68
|
+
data = pd.read_parquet(BytesIO(payload["data"]))
|
|
69
|
+
inference_kwargs = payload["inference_kwargs"]
|
|
70
|
+
if inference_kwargs is None:
|
|
71
|
+
inference_kwargs = {}
|
|
72
|
+
|
|
73
|
+
else:
|
|
74
|
+
raise ValueError(f"{input_content_type} input content type not supported.")
|
|
75
|
+
|
|
76
|
+
data = prepare_timeseries_dataframe(data, model)
|
|
77
|
+
prediction = model.predict(data, **inference_kwargs)
|
|
78
|
+
prediction = pd.DataFrame(prediction)
|
|
79
|
+
|
|
80
|
+
if "application/x-parquet" in output_content_type:
|
|
81
|
+
prediction.columns = prediction.columns.astype(str)
|
|
82
|
+
output = prediction.to_parquet()
|
|
83
|
+
output_content_type = "application/x-parquet"
|
|
84
|
+
elif "application/json" in output_content_type:
|
|
85
|
+
output = prediction.to_json()
|
|
86
|
+
output_content_type = "application/json"
|
|
87
|
+
elif "text/csv" in output_content_type:
|
|
88
|
+
output = prediction.to_csv(index=None)
|
|
89
|
+
output_content_type = "text/csv"
|
|
90
|
+
else:
|
|
91
|
+
raise ValueError(f"{output_content_type} content type not supported")
|
|
92
|
+
|
|
93
|
+
return output, output_content_type
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# isort: skip_file
|
|
2
|
+
# flake8: noqa
|
|
3
|
+
# The import order of autogluon sub module here could cause seg fault. Ignore isort for now
|
|
4
|
+
# https://github.com/autogluon/autogluon/issues/2042
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import shutil
|
|
9
|
+
from pprint import pprint
|
|
10
|
+
|
|
11
|
+
import pickle
|
|
12
|
+
|
|
13
|
+
from autogluon.common.loaders import load_pd
|
|
14
|
+
from autogluon.tabular import TabularPredictor, TabularDataset
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_input_path(path):
|
|
18
|
+
file = os.listdir(path)[0]
|
|
19
|
+
if len(os.listdir(path)) > 1:
|
|
20
|
+
raise ValueError(f"WARN: more than one file is found in {channel} directory")
|
|
21
|
+
print(f"Using {file}")
|
|
22
|
+
filename = f"{path}/{file}"
|
|
23
|
+
return filename
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_env_if_present(name):
|
|
27
|
+
result = None
|
|
28
|
+
if name in os.environ:
|
|
29
|
+
result = os.environ[name]
|
|
30
|
+
return result
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def prepare_timeseries_dataframe(df, predictor_init_args):
|
|
34
|
+
target = predictor_init_args["target"]
|
|
35
|
+
cols = df.columns.to_list()
|
|
36
|
+
id_column = cols[0]
|
|
37
|
+
timestamp_column = cols[1]
|
|
38
|
+
df[timestamp_column] = pd.to_datetime(df[timestamp_column])
|
|
39
|
+
static_features = None
|
|
40
|
+
if target != cols[-1]:
|
|
41
|
+
# target is not the last column, then there are static features being merged in
|
|
42
|
+
target_index = cols.index(target)
|
|
43
|
+
static_columns = cols[target_index + 1 :]
|
|
44
|
+
static_features = df[[id_column] + static_columns].groupby([id_column], sort=False).head(1)
|
|
45
|
+
static_features.set_index(id_column, inplace=True)
|
|
46
|
+
df.drop(columns=static_columns, inplace=True)
|
|
47
|
+
df = TimeSeriesDataFrame.from_data_frame(df, id_column=id_column, timestamp_column=timestamp_column)
|
|
48
|
+
if static_features is not None:
|
|
49
|
+
print(static_features)
|
|
50
|
+
df.static_features = static_features
|
|
51
|
+
return df
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def prepare_data(data_file, predictor_type, predictor_init_args=None):
|
|
55
|
+
if predictor_type == "timeseries":
|
|
56
|
+
assert predictor_init_args is not None
|
|
57
|
+
data = load_pd.load(data_file)
|
|
58
|
+
data = prepare_timeseries_dataframe(data, predictor_init_args)
|
|
59
|
+
else:
|
|
60
|
+
data = TabularDataset(data_file)
|
|
61
|
+
return data
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
# Disable Autotune
|
|
66
|
+
os.environ["MXNET_CUDNN_AUTOTUNE_DEFAULT"] = "0"
|
|
67
|
+
|
|
68
|
+
# ------------------------------------------------------------ Args parsing
|
|
69
|
+
print("Starting AG")
|
|
70
|
+
parser = argparse.ArgumentParser()
|
|
71
|
+
|
|
72
|
+
# Data, model, and output directories
|
|
73
|
+
parser.add_argument("--output-data-dir", type=str, default=get_env_if_present("SM_OUTPUT_DATA_DIR"))
|
|
74
|
+
parser.add_argument("--model-dir", type=str, default=get_env_if_present("SM_MODEL_DIR"))
|
|
75
|
+
parser.add_argument("--n_gpus", type=str, default=get_env_if_present("SM_NUM_GPUS"))
|
|
76
|
+
parser.add_argument("--train_dir", type=str, default=get_env_if_present("SM_CHANNEL_TRAIN"))
|
|
77
|
+
parser.add_argument("--tune_dir", type=str, required=False, default=get_env_if_present("SM_CHANNEL_TUNE"))
|
|
78
|
+
parser.add_argument(
|
|
79
|
+
"--train_images", type=str, required=False, default=get_env_if_present("SM_CHANNEL_TRAIN_IMAGES")
|
|
80
|
+
)
|
|
81
|
+
parser.add_argument(
|
|
82
|
+
"--tune_images", type=str, required=False, default=get_env_if_present("SM_CHANNEL_TUNE_IMAGES")
|
|
83
|
+
)
|
|
84
|
+
parser.add_argument("--ag_args", type=str, default=get_env_if_present("SM_CHANNEL_AG_ARGS"))
|
|
85
|
+
parser.add_argument("--serving_script", type=str, default=get_env_if_present("SM_CHANNEL_SERVING"))
|
|
86
|
+
|
|
87
|
+
args, _ = parser.parse_known_args()
|
|
88
|
+
|
|
89
|
+
print(f"Args: {args}")
|
|
90
|
+
|
|
91
|
+
# See SageMaker-specific environment variables: https://sagemaker.readthedocs.io/en/stable/overview.html#prepare-a-training-script
|
|
92
|
+
os.makedirs(args.output_data_dir, mode=0o777, exist_ok=True)
|
|
93
|
+
|
|
94
|
+
ag_args_file = get_input_path(args.ag_args)
|
|
95
|
+
with open(ag_args_file, "rb") as f:
|
|
96
|
+
ag_args = pickle.load(f) # AutoGluon-specific args
|
|
97
|
+
|
|
98
|
+
if args.n_gpus:
|
|
99
|
+
ag_args["num_gpus"] = int(args.n_gpus)
|
|
100
|
+
|
|
101
|
+
print("Running training job with the config:")
|
|
102
|
+
pprint(ag_args)
|
|
103
|
+
|
|
104
|
+
# ---------------------------------------------------------------- Training
|
|
105
|
+
save_path = os.path.normpath(args.model_dir)
|
|
106
|
+
predictor_type = ag_args["predictor_type"]
|
|
107
|
+
predictor_init_args = ag_args["predictor_init_args"]
|
|
108
|
+
predictor_init_args["path"] = save_path
|
|
109
|
+
predictor_fit_args = ag_args["predictor_fit_args"]
|
|
110
|
+
valid_predictor_types = ["tabular", "multimodal", "timeseries"]
|
|
111
|
+
assert (
|
|
112
|
+
predictor_type in valid_predictor_types
|
|
113
|
+
), f"predictor_type {predictor_type} not supported. Valid options are {valid_predictor_types}"
|
|
114
|
+
if predictor_type == "tabular":
|
|
115
|
+
predictor_cls = TabularPredictor
|
|
116
|
+
elif predictor_type == "multimodal":
|
|
117
|
+
from autogluon.multimodal import MultiModalPredictor
|
|
118
|
+
|
|
119
|
+
predictor_cls = MultiModalPredictor
|
|
120
|
+
elif predictor_type == "timeseries":
|
|
121
|
+
from autogluon.timeseries import TimeSeriesPredictor, TimeSeriesDataFrame
|
|
122
|
+
|
|
123
|
+
predictor_cls = TimeSeriesPredictor
|
|
124
|
+
|
|
125
|
+
train_file = get_input_path(args.train_dir)
|
|
126
|
+
training_data = prepare_data(train_file, predictor_type, predictor_init_args)
|
|
127
|
+
|
|
128
|
+
if predictor_type == "tabular" and "image_column" in ag_args:
|
|
129
|
+
feature_metadata = predictor_fit_args.get("feature_metadata", None)
|
|
130
|
+
assert (
|
|
131
|
+
feature_metadata is not None
|
|
132
|
+
), f"Detected image_column: {ag_args['image_column']} while feature metadata is not included"
|
|
133
|
+
feature_metadata = feature_metadata.add_special_types({ag_args["image_column"]: ["image_path"]})
|
|
134
|
+
predictor_fit_args["feature_metadata"] = feature_metadata
|
|
135
|
+
|
|
136
|
+
tuning_data = None
|
|
137
|
+
if args.tune_dir:
|
|
138
|
+
tune_file = get_input_path(args.tune_dir)
|
|
139
|
+
tuning_data = prepare_data(tune_file, predictor_type)
|
|
140
|
+
|
|
141
|
+
if args.train_images:
|
|
142
|
+
train_image_compressed_file = get_input_path(args.train_images)
|
|
143
|
+
train_images_dir = "train_images"
|
|
144
|
+
shutil.unpack_archive(train_image_compressed_file, train_images_dir)
|
|
145
|
+
image_column = ag_args["image_column"]
|
|
146
|
+
training_data[image_column] = training_data[image_column].apply(
|
|
147
|
+
lambda path: os.path.join(train_images_dir, path)
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if args.tune_images:
|
|
151
|
+
tune_image_compressed_file = get_input_path(args.tune_images)
|
|
152
|
+
tune_images_dir = "tune_images"
|
|
153
|
+
shutil.unpack_archive(tune_image_compressed_file, tune_images_dir)
|
|
154
|
+
image_column = ag_args["image_column"]
|
|
155
|
+
tuning_data[image_column] = tuning_data[image_column].apply(lambda path: os.path.join(tune_images_dir, path))
|
|
156
|
+
|
|
157
|
+
predictor = predictor_cls(**predictor_init_args).fit(training_data, tuning_data=tuning_data, **predictor_fit_args)
|
|
158
|
+
|
|
159
|
+
# When use automm backend, predictor needs to be saved with standalone flag to avoid need of internet access when loading
|
|
160
|
+
# This is required because of https://discuss.huggingface.co/t/error-403-when-downloading-model-for-sagemaker-batch-inference/12571/6
|
|
161
|
+
if predictor_type == "multimodal":
|
|
162
|
+
predictor.save(path=save_path, standalone=True)
|
|
163
|
+
|
|
164
|
+
if predictor_cls == TabularPredictor:
|
|
165
|
+
if ag_args.get("leaderboard", False):
|
|
166
|
+
lb = predictor.leaderboard(silent=False)
|
|
167
|
+
lb.to_csv(f"{args.output_data_dir}/leaderboard.csv")
|
|
168
|
+
|
|
169
|
+
print("Saving serving script")
|
|
170
|
+
serving_script_saving_path = os.path.join(save_path, "code")
|
|
171
|
+
os.mkdir(serving_script_saving_path)
|
|
172
|
+
serving_script_path = get_input_path(args.serving_script)
|
|
173
|
+
shutil.move(serving_script_path, os.path.join(serving_script_saving_path, os.path.basename(serving_script_path)))
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""This is the autogluon.cloud version file."""
|
|
2
|
-
__version__ = '0.3.
|
|
2
|
+
__version__ = '0.3.1'
|
|
@@ -34,6 +34,7 @@ src/autogluon/cloud/cluster/ray_cluster_config_generator.py
|
|
|
34
34
|
src/autogluon/cloud/cluster/ray_cluster_manager.py
|
|
35
35
|
src/autogluon/cloud/data/__init__.py
|
|
36
36
|
src/autogluon/cloud/data/format_converter.py
|
|
37
|
+
src/autogluon/cloud/default_cluster_configs/__init__.py
|
|
37
38
|
src/autogluon/cloud/endpoint/__init__.py
|
|
38
39
|
src/autogluon/cloud/endpoint/endpoint.py
|
|
39
40
|
src/autogluon/cloud/endpoint/sagemaker_endpoint.py
|
|
@@ -48,6 +49,13 @@ src/autogluon/cloud/predictor/tabular_cloud_predictor.py
|
|
|
48
49
|
src/autogluon/cloud/predictor/timeseries_cloud_predictor.py
|
|
49
50
|
src/autogluon/cloud/scripts/__init__.py
|
|
50
51
|
src/autogluon/cloud/scripts/script_manager.py
|
|
52
|
+
src/autogluon/cloud/scripts/ray_scripts/__init__.py
|
|
53
|
+
src/autogluon/cloud/scripts/ray_scripts/train.py
|
|
54
|
+
src/autogluon/cloud/scripts/sagemaker_scripts/__init__.py
|
|
55
|
+
src/autogluon/cloud/scripts/sagemaker_scripts/multimodal_serve.py
|
|
56
|
+
src/autogluon/cloud/scripts/sagemaker_scripts/tabular_serve.py
|
|
57
|
+
src/autogluon/cloud/scripts/sagemaker_scripts/timeseries_serve.py
|
|
58
|
+
src/autogluon/cloud/scripts/sagemaker_scripts/train.py
|
|
51
59
|
src/autogluon/cloud/utils/__init__.py
|
|
52
60
|
src/autogluon/cloud/utils/ag_sagemaker.py
|
|
53
61
|
src/autogluon/cloud/utils/aws_utils.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/backend/backend_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/backend/ray_aws_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
{autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/backend/sagemaker_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/cluster/cluster_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/cluster/ray_cluster_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
{autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/data/format_converter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/endpoint/sagemaker_endpoint.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/predictor/cloud_predictor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon/cloud/scripts/script_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon.cloud.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{autogluon.cloud-0.3.0 → autogluon.cloud-0.3.1}/src/autogluon.cloud.egg-info/namespace_packages.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|