cloud-submit 0.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.
- cloud_submit-0.1/.gitignore +8 -0
- cloud_submit-0.1/LICENSE +19 -0
- cloud_submit-0.1/PKG-INFO +12 -0
- cloud_submit-0.1/README.md +29 -0
- cloud_submit-0.1/docs/examples/aws/src/csub/build_config.py +153 -0
- cloud_submit-0.1/docs/examples/aws/src/fit.py +42 -0
- cloud_submit-0.1/docs/examples/aws/src/generate.py +32 -0
- cloud_submit-0.1/docs/examples/aws/src/parameters.py +8 -0
- cloud_submit-0.1/docs/examples/aws/userconfig/template.yaml +112 -0
- cloud_submit-0.1/docs/examples/basic/src/csub/build_config.py +102 -0
- cloud_submit-0.1/docs/examples/basic/src/fit.py +41 -0
- cloud_submit-0.1/docs/examples/basic/src/generate.py +24 -0
- cloud_submit-0.1/docs/examples/basic/src/parameters.py +8 -0
- cloud_submit-0.1/docs/examples/basic/userconfig/template.yaml +7 -0
- cloud_submit-0.1/docs/examples/multi-worker/src/csub/build_config.py +105 -0
- cloud_submit-0.1/docs/examples/multi-worker/src/fit.py +70 -0
- cloud_submit-0.1/docs/examples/multi-worker/src/generate.py +27 -0
- cloud_submit-0.1/docs/examples/multi-worker/src/parameters.py +10 -0
- cloud_submit-0.1/docs/examples/multi-worker/userconfig/template.yaml +7 -0
- cloud_submit-0.1/pyproject.toml +29 -0
- cloud_submit-0.1/src/cloud_submit/__init__.py +30 -0
- cloud_submit-0.1/src/cloud_submit/cli.py +1014 -0
- cloud_submit-0.1/src/cloud_submit/config.py +164 -0
- cloud_submit-0.1/src/cloud_submit/controller.py +375 -0
- cloud_submit-0.1/src/cloud_submit/environment_handler.py +398 -0
- cloud_submit-0.1/src/cloud_submit/envs/aws/__init__.py +2 -0
- cloud_submit-0.1/src/cloud_submit/envs/aws/local_environment_handler.py +478 -0
- cloud_submit-0.1/src/cloud_submit/envs/aws/local_execution_handler.py +5 -0
- cloud_submit-0.1/src/cloud_submit/envs/aws/remote_environment_handler.py +401 -0
- cloud_submit-0.1/src/cloud_submit/envs/aws/remote_execution_handler.py +87 -0
- cloud_submit-0.1/src/cloud_submit/envs/aws/s3_tools.py +46 -0
- cloud_submit-0.1/src/cloud_submit/envs/local/environment_handler.py +121 -0
- cloud_submit-0.1/src/cloud_submit/envs/local/execution_handler.py +5 -0
- cloud_submit-0.1/src/cloud_submit/execution/base_execution_handler.py +49 -0
- cloud_submit-0.1/src/cloud_submit/execution/config.py +248 -0
- cloud_submit-0.1/src/cloud_submit/execution/execute.py +108 -0
- cloud_submit-0.1/src/cloud_submit/execution/utils.py +98 -0
- cloud_submit-0.1/src/cloud_submit/images.py +58 -0
- cloud_submit-0.1/src/cloud_submit/utils.py +67 -0
cloud_submit-0.1/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2018 The Python Packaging Authority
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cloud-submit
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: A tool for building processing pipelines in Python and executing them locally or in the cloud.
|
|
5
|
+
Author: Martin Wiebusch
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
|
+
Requires-Dist: click
|
|
12
|
+
Requires-Dist: pyyaml
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# cloud-submit
|
|
2
|
+
|
|
3
|
+
A tool for packaging Python code with Docker and executing it in local and
|
|
4
|
+
cloud environments.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
$ pip install cloud-submit
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Documentation
|
|
13
|
+
|
|
14
|
+
This package provides a Python module called `cloud_submit` as well as a
|
|
15
|
+
command-line tool called `csub`.
|
|
16
|
+
|
|
17
|
+
Have a look at the
|
|
18
|
+
[examples](https://github.com/mwiebusch78/cloud-submit/tree/main/docs/examples)
|
|
19
|
+
to see how to set up a project directory. To run any of the examples you will
|
|
20
|
+
have to copy `userconfig/template.yaml` to `userconfig/default.yaml` and fill
|
|
21
|
+
in the blanks.
|
|
22
|
+
|
|
23
|
+
To run the AWS example you need to have the AWS CLI (v2) installed locally
|
|
24
|
+
and you need an AWS account. The comments in `userconfig/template.yaml` tell
|
|
25
|
+
you how you need to configure your AWS account so that the example can run.
|
|
26
|
+
|
|
27
|
+
Run `csub --help` to explore the functionality of `csub` and its subcommands.
|
|
28
|
+
|
|
29
|
+
Proper documentation will be added as soon as I have the time. (Promise!)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import datetime as dt
|
|
2
|
+
import cloud_submit as cs
|
|
3
|
+
|
|
4
|
+
# import AWS-specific environments that ship with cloud_submit.
|
|
5
|
+
import cloud_submit.envs.aws
|
|
6
|
+
from cloud_submit.envs.aws import LocalAWSEnv, RemoteAWSEnv
|
|
7
|
+
|
|
8
|
+
# csub automatically adds your project's src directory to sys.path, so
|
|
9
|
+
# you can import any modules defined there.
|
|
10
|
+
from parameters import PARAMS
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
PROJECT_NAME = 'basic-aws'
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def build_config(project_root, userconfig):
|
|
17
|
+
"""Create an instance of cloud_submit.Config describing your project.
|
|
18
|
+
|
|
19
|
+
Note:
|
|
20
|
+
The csub command expects a function with this name defined in
|
|
21
|
+
src/csub/build_config.py. It calls this function to obtain a
|
|
22
|
+
cloud_submit.Config object which describes the environments, images,
|
|
23
|
+
artifacts and pipelines defined in your project.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
project_root (str): The root directory of your project.
|
|
27
|
+
userconfig (dict): A dict holding user-specific information. This
|
|
28
|
+
corresponds to the content of the file userconfigs/default.yaml
|
|
29
|
+
in your project directory (or some other yaml file in that directory
|
|
30
|
+
if csub was called with the --user option).
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
config (cloud_submit.Config): The config object describing the project.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# Images
|
|
37
|
+
|
|
38
|
+
images = [
|
|
39
|
+
cs.BaseImage(
|
|
40
|
+
name='base',
|
|
41
|
+
instructions=r"""
|
|
42
|
+
FROM python
|
|
43
|
+
RUN \
|
|
44
|
+
cd /opt && \
|
|
45
|
+
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" \
|
|
46
|
+
-o "awscliv2.zip" && \
|
|
47
|
+
unzip awscliv2.zip && \
|
|
48
|
+
./aws/install && \
|
|
49
|
+
rm -rf awscliv2.zip aws
|
|
50
|
+
RUN pip install --root-user-action=ignore polars numpy
|
|
51
|
+
""",
|
|
52
|
+
),
|
|
53
|
+
cs.ExecutionImage(
|
|
54
|
+
name='exec',
|
|
55
|
+
parent='base',
|
|
56
|
+
),
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
# Artifacts
|
|
60
|
+
|
|
61
|
+
artifacts = [
|
|
62
|
+
cs.Artifact('train_data.parquet', kind='file', scope='run'),
|
|
63
|
+
cs.Artifact('coefficients.json', kind='file', scope='run'),
|
|
64
|
+
cs.Artifact('predictions.parquet', kind='file', scope='run'),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
# Pipelines
|
|
68
|
+
|
|
69
|
+
train_pipeline = cs.Pipeline(
|
|
70
|
+
name='train',
|
|
71
|
+
steps=[
|
|
72
|
+
cs.Step(
|
|
73
|
+
name='generate',
|
|
74
|
+
spec=cs.Spec(
|
|
75
|
+
cpu=2,
|
|
76
|
+
memory=6.5,
|
|
77
|
+
disk=20,
|
|
78
|
+
),
|
|
79
|
+
function='generate:generate_step',
|
|
80
|
+
image='exec',
|
|
81
|
+
params={
|
|
82
|
+
'random_seed': PARAMS['random_seed'],
|
|
83
|
+
'num_samples': PARAMS['num_samples'],
|
|
84
|
+
'alpha': PARAMS['alpha'],
|
|
85
|
+
'beta': PARAMS['beta'],
|
|
86
|
+
'sigma': PARAMS['sigma'],
|
|
87
|
+
},
|
|
88
|
+
outputs={
|
|
89
|
+
'train_data_path': cs.local('train_data.parquet'),
|
|
90
|
+
},
|
|
91
|
+
),
|
|
92
|
+
cs.Step(
|
|
93
|
+
name='fit',
|
|
94
|
+
spec=cs.Spec(
|
|
95
|
+
cpu=2,
|
|
96
|
+
memory=6.5,
|
|
97
|
+
disk=20,
|
|
98
|
+
),
|
|
99
|
+
function='fit:fit_step',
|
|
100
|
+
image='exec',
|
|
101
|
+
inputs={
|
|
102
|
+
'train_data_path': cs.local('train_data.parquet'),
|
|
103
|
+
},
|
|
104
|
+
outputs={
|
|
105
|
+
'coefficients_path': cs.local('coefficients.json'),
|
|
106
|
+
'predictions_path': cs.local('predictions.parquet'),
|
|
107
|
+
},
|
|
108
|
+
),
|
|
109
|
+
],
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
config = cs.Config(
|
|
113
|
+
project_name=PROJECT_NAME,
|
|
114
|
+
user_name=userconfig['username'],
|
|
115
|
+
project_root=project_root,
|
|
116
|
+
images=images,
|
|
117
|
+
artifacts=artifacts,
|
|
118
|
+
pipelines=[train_pipeline],
|
|
119
|
+
environments=[
|
|
120
|
+
LocalAWSEnv(
|
|
121
|
+
name='local',
|
|
122
|
+
project=PROJECT_NAME,
|
|
123
|
+
user=userconfig['username'],
|
|
124
|
+
aws_account_id=userconfig['aws_account_id'],
|
|
125
|
+
aws_region=userconfig['aws_region'],
|
|
126
|
+
aws_profile=userconfig['aws_profile'],
|
|
127
|
+
docker_namespace=userconfig['docker_namespace'],
|
|
128
|
+
s3_bucket=userconfig['s3_bucket'],
|
|
129
|
+
s3_prefix=userconfig['s3_prefix'],
|
|
130
|
+
),
|
|
131
|
+
RemoteAWSEnv(
|
|
132
|
+
name='remote',
|
|
133
|
+
project=PROJECT_NAME,
|
|
134
|
+
user=userconfig['username'],
|
|
135
|
+
aws_account_id=userconfig['aws_account_id'],
|
|
136
|
+
aws_region=userconfig['aws_region'],
|
|
137
|
+
aws_profile=userconfig['aws_profile'],
|
|
138
|
+
docker_namespace=userconfig['docker_namespace'],
|
|
139
|
+
s3_bucket=userconfig['s3_bucket'],
|
|
140
|
+
s3_prefix=userconfig['s3_prefix'],
|
|
141
|
+
ecs_cluster_arn=userconfig['ecs_cluster_arn'],
|
|
142
|
+
ecs_capacity_provider=userconfig['ecs_capacity_provider'],
|
|
143
|
+
ecs_infrastructure_role_arn=
|
|
144
|
+
userconfig['ecs_infrastructure_role_arn'],
|
|
145
|
+
ecs_execution_role_arn=userconfig['ecs_execution_role_arn'],
|
|
146
|
+
ecs_task_role_arn=userconfig['ecs_task_role_arn'],
|
|
147
|
+
stepfunctions_role_arn=userconfig['stepfunctions_role_arn'],
|
|
148
|
+
),
|
|
149
|
+
],
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return config
|
|
153
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import polars as pl
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def fit_step(
|
|
9
|
+
train_data_path,
|
|
10
|
+
coefficients_path,
|
|
11
|
+
predictions_path,
|
|
12
|
+
):
|
|
13
|
+
print(f'Fitting model.')
|
|
14
|
+
|
|
15
|
+
train_data = pl.read_parquet(os.path.join(train_data_path, '*.parquet'))
|
|
16
|
+
x = train_data.get_column('x').to_numpy()
|
|
17
|
+
y = train_data.get_column('y').to_numpy()
|
|
18
|
+
|
|
19
|
+
# add intercept term
|
|
20
|
+
intercept = np.ones(len(x))
|
|
21
|
+
x = np.stack([intercept, x], axis=1)
|
|
22
|
+
|
|
23
|
+
# fit linear regression model
|
|
24
|
+
coeffs = np.linalg.solve(np.dot(x.T, x), np.dot(x.T, y))
|
|
25
|
+
|
|
26
|
+
# compute in-sample predictions
|
|
27
|
+
preds = np.dot(x, coeffs)
|
|
28
|
+
preds = train_data.with_columns(pl.Series('pred', preds))
|
|
29
|
+
|
|
30
|
+
# save coefficients
|
|
31
|
+
with open(coefficients_path, 'w') as stream:
|
|
32
|
+
json.dump(
|
|
33
|
+
{
|
|
34
|
+
'alpha': float(coeffs[0]),
|
|
35
|
+
'beta': float(coeffs[1]),
|
|
36
|
+
},
|
|
37
|
+
stream,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# save in-sample predictions
|
|
41
|
+
preds.write_parquet(predictions_path)
|
|
42
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import polars as pl
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def generate_step(
|
|
8
|
+
random_seed,
|
|
9
|
+
num_samples,
|
|
10
|
+
alpha,
|
|
11
|
+
beta,
|
|
12
|
+
sigma,
|
|
13
|
+
train_data_path,
|
|
14
|
+
):
|
|
15
|
+
print(f'Generating training data.')
|
|
16
|
+
|
|
17
|
+
rng = np.random.default_rng(random_seed)
|
|
18
|
+
x = rng.normal(size=num_samples)
|
|
19
|
+
y = alpha + beta*x + sigma*rng.normal(size=num_samples)
|
|
20
|
+
|
|
21
|
+
train_data = pl.DataFrame([
|
|
22
|
+
pl.Series('x', x),
|
|
23
|
+
pl.Series('y', y),
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
os.mkdir(train_data_path)
|
|
27
|
+
split_index = len(train_data)//2
|
|
28
|
+
train_data[:split_index].write_parquet(
|
|
29
|
+
os.path.join(train_data_path, 'part0.parquet'))
|
|
30
|
+
train_data[split_index:].write_parquet(
|
|
31
|
+
os.path.join(train_data_path, 'part1.parquet'))
|
|
32
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
PARAMS = {
|
|
2
|
+
# Parameters for data generation.
|
|
3
|
+
'random_seed': 42, # The random seed.
|
|
4
|
+
'num_samples': 100, # The number of training samples to generate.
|
|
5
|
+
'alpha': 1.0, # The true value of the intercept coefficient.
|
|
6
|
+
'beta': 2.0, # The true value of the linear coefficient.
|
|
7
|
+
'sigma': 0.1, # The standard deviation of the error term.
|
|
8
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Your username is used for keeping your artifacts, images and cloud resources
|
|
2
|
+
# separate from those of other users.
|
|
3
|
+
username: # TODO: choose a user name.
|
|
4
|
+
|
|
5
|
+
# This is the ID of your AWS account. Make sure you specify it as a string,
|
|
6
|
+
# e.g. "123456789012".
|
|
7
|
+
aws_account_id: # TODO: add you AWS account ID.
|
|
8
|
+
|
|
9
|
+
# This is the region where your AWS resources were/are created
|
|
10
|
+
# (e.g. eu-north-1).
|
|
11
|
+
aws_region: # TODO: add your AWS region.
|
|
12
|
+
|
|
13
|
+
# This is the name of the profile that you want to use to authenticate with
|
|
14
|
+
# AWS. Look at ~/.aws/config to see what profiles you have configured.
|
|
15
|
+
# Normally there should be one called "default" which is the one that's used
|
|
16
|
+
# when you run aws commands without a --profile option.
|
|
17
|
+
aws_profile: default
|
|
18
|
+
|
|
19
|
+
# This is the docker namespace that will be used to store docker images
|
|
20
|
+
# needed in your project. You have to set up an Elastic Container Registy
|
|
21
|
+
# in AWS in the region you specified under `aws_region`. Images under this
|
|
22
|
+
# namespace must be mutable and you must allow repo creation on push for
|
|
23
|
+
# this namespace. It is also recommended to enable blob mounting on the
|
|
24
|
+
# registry.
|
|
25
|
+
docker_namespace: csub
|
|
26
|
+
|
|
27
|
+
# This is the name of the S3 bucket where your artifacts will be stored.
|
|
28
|
+
s3_bucket: # TODO: add the name of your S3 bucket.
|
|
29
|
+
|
|
30
|
+
# This is the S3 prefix under which your artifacts will be stored.
|
|
31
|
+
s3_prefix: csub
|
|
32
|
+
|
|
33
|
+
# If you want to run pipelines in AWS you have to set up a cluster in
|
|
34
|
+
# Elastic Container Service (ECS). The cluster must allow the use
|
|
35
|
+
# of managed EC2 instances (second option in the cluster creation form
|
|
36
|
+
# in the AWS console).
|
|
37
|
+
#
|
|
38
|
+
# If you only want to do local runs you can use an string value for this
|
|
39
|
+
# and all following parameters.
|
|
40
|
+
ecs_cluster_arn: n/a # TODO: add the ARN of your ECS cluster.
|
|
41
|
+
|
|
42
|
+
# The capacity provider should have been created automatically when you
|
|
43
|
+
# created the cluster. To find its name, view your cluster in the AWS console
|
|
44
|
+
# and look at the "Infrastructure" tab. Note that we need the *name* of the
|
|
45
|
+
# capacity provider, not its ARN.
|
|
46
|
+
ecs_capacity_provider: n/a # TODO: add the name of your capacity provider.
|
|
47
|
+
|
|
48
|
+
# The infrastructure role is the role used to manage the managed EC2 instances
|
|
49
|
+
# in your cluster. You should have created this role when you created the
|
|
50
|
+
# cluster.
|
|
51
|
+
ecs_infrastructure_role_arn: n/a # TODO: add the ARN of your infrastructure role
|
|
52
|
+
|
|
53
|
+
# To create the next two roles you can open the form for creating a task
|
|
54
|
+
# definition in ECS. In the "Task roles" sections it asks you to enter a
|
|
55
|
+
# "Task role" and a "Task execution role". Next to the dropdowns there are
|
|
56
|
+
# "Create new role" buttons which let you create those roles on the fly.
|
|
57
|
+
# You do not need to complete the task definition after you have created
|
|
58
|
+
# them.
|
|
59
|
+
ecs_task_role_arn: n/a # TODO: add the ARN of your task role
|
|
60
|
+
ecs_execution_role_arn: n/a # TODO: add the ARN of your task execution role
|
|
61
|
+
|
|
62
|
+
# The stepfunction role is used to orchestrate your ECS tasks via AWS
|
|
63
|
+
# Stepfunctions. This role should have the AWSLambdaRole policy and the
|
|
64
|
+
# following custom inline policy:
|
|
65
|
+
#
|
|
66
|
+
# {
|
|
67
|
+
# "Version": "2012-10-17",
|
|
68
|
+
# "Statement": [
|
|
69
|
+
# {
|
|
70
|
+
# "Sid": "ECSOrchestrationPermissions",
|
|
71
|
+
# "Effect": "Allow",
|
|
72
|
+
# "Action": [
|
|
73
|
+
# "ecs:RegisterTaskDefinition",
|
|
74
|
+
# "ecs:DeregisterTaskDefinition",
|
|
75
|
+
# "ecs:RunTask",
|
|
76
|
+
# "ecs:DescribeTasks",
|
|
77
|
+
# "ecs:StopTask"
|
|
78
|
+
# ],
|
|
79
|
+
# "Resource": "*"
|
|
80
|
+
# },
|
|
81
|
+
# {
|
|
82
|
+
# "Sid": "PassRolesToECSInstances",
|
|
83
|
+
# "Effect": "Allow",
|
|
84
|
+
# "Action": "iam:PassRole",
|
|
85
|
+
# "Resource": [
|
|
86
|
+
# "{INSTANCE_ROLE_ARN}",
|
|
87
|
+
# "{INFRASTRUCTURE_ROLE_ARN}",
|
|
88
|
+
# "{TASK_EXECUTION_ROLE_ARN}",
|
|
89
|
+
# "{TASK_ROLE_ARN}"
|
|
90
|
+
# ]
|
|
91
|
+
# },
|
|
92
|
+
# {
|
|
93
|
+
# "Sid": "EventBridgeSyncTracking",
|
|
94
|
+
# "Effect": "Allow",
|
|
95
|
+
# "Action": [
|
|
96
|
+
# "events:PutTargets",
|
|
97
|
+
# "events:PutRule",
|
|
98
|
+
# "events:DescribeRule"
|
|
99
|
+
# ],
|
|
100
|
+
# "Resource": "arn:aws:events:*:*:rule/StepFunctionsGetEventsForECSTaskRule"
|
|
101
|
+
# }
|
|
102
|
+
# ]
|
|
103
|
+
# }
|
|
104
|
+
#
|
|
105
|
+
# Here {INFRASTUCTURE_ROLE_ARN}, {TASK_EXECUTION_ROLE_ARN} and {TASK_ROLE_ARN}
|
|
106
|
+
# are the role ARNs you specified earlier and {INSTANCE_ROLE_ARN} is the ARN
|
|
107
|
+
# of another role that you set up when you created your cluster.
|
|
108
|
+
#
|
|
109
|
+
# Also note that the user you authenticate as when you submit a job (i.e. the
|
|
110
|
+
# user of the profile you specified under `aws_profile`) must have permission
|
|
111
|
+
# to assume the stepfunction role you specify here.
|
|
112
|
+
stepfunctions_role_arn: n/a # TODO: add your stepfunctions role ARN.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import datetime as dt
|
|
2
|
+
import cloud_submit as cs
|
|
3
|
+
|
|
4
|
+
# csub automatically adds your project's src directory to sys.path, so
|
|
5
|
+
# you can import any modules defined there.
|
|
6
|
+
from parameters import PARAMS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
PROJECT_NAME = 'basic'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_config(project_root, userconfig):
|
|
13
|
+
"""Create an instance of cloud_submit.Config describing your project.
|
|
14
|
+
|
|
15
|
+
Note:
|
|
16
|
+
The csub command expects a function with this name defined in
|
|
17
|
+
src/csub/build_config.py. It calls this function to obtain a
|
|
18
|
+
cloud_submit.Config object which describes the environments, images,
|
|
19
|
+
artifacts and pipelines defined in your project.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
project_root (str): The root directory of your project.
|
|
23
|
+
userconfig (dict): A dict holding user-specific information. This
|
|
24
|
+
corresponds to the content of the file userconfigs/default.yaml
|
|
25
|
+
in your project directory (or some other yaml file in that directory
|
|
26
|
+
if csub was called with the --user option).
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
config (cloud_submit.Config): The config object describing the project.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
# Images
|
|
33
|
+
|
|
34
|
+
images = [
|
|
35
|
+
cs.BaseImage(
|
|
36
|
+
name='base',
|
|
37
|
+
instructions="""
|
|
38
|
+
FROM python
|
|
39
|
+
RUN pip install --root-user-action=ignore polars numpy
|
|
40
|
+
""",
|
|
41
|
+
),
|
|
42
|
+
cs.ExecutionImage(
|
|
43
|
+
name='exec',
|
|
44
|
+
parent='base',
|
|
45
|
+
),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
# Artifacts
|
|
49
|
+
|
|
50
|
+
artifacts = [
|
|
51
|
+
cs.Artifact('train_data.parquet', kind='file', scope='run'),
|
|
52
|
+
cs.Artifact('coefficients.json', kind='file', scope='run'),
|
|
53
|
+
cs.Artifact('predictions.parquet', kind='file', scope='run'),
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
# Pipelines
|
|
57
|
+
|
|
58
|
+
train_pipeline = cs.Pipeline(
|
|
59
|
+
name='train',
|
|
60
|
+
steps=[
|
|
61
|
+
cs.Step(
|
|
62
|
+
name='generate',
|
|
63
|
+
function='generate:generate_step',
|
|
64
|
+
image='exec',
|
|
65
|
+
params={
|
|
66
|
+
'random_seed': PARAMS['random_seed'],
|
|
67
|
+
'num_samples': PARAMS['num_samples'],
|
|
68
|
+
'alpha': PARAMS['alpha'],
|
|
69
|
+
'beta': PARAMS['beta'],
|
|
70
|
+
'sigma': PARAMS['sigma'],
|
|
71
|
+
},
|
|
72
|
+
outputs={
|
|
73
|
+
'train_data_path': cs.local('train_data.parquet'),
|
|
74
|
+
},
|
|
75
|
+
),
|
|
76
|
+
cs.Step(
|
|
77
|
+
name='fit',
|
|
78
|
+
function='fit:fit_step',
|
|
79
|
+
image='exec',
|
|
80
|
+
inputs={
|
|
81
|
+
'train_data_path': cs.local('train_data.parquet'),
|
|
82
|
+
},
|
|
83
|
+
outputs={
|
|
84
|
+
'coefficients_path': cs.local('coefficients.json'),
|
|
85
|
+
'predictions_path': cs.local('predictions.parquet'),
|
|
86
|
+
},
|
|
87
|
+
),
|
|
88
|
+
],
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
config = cs.Config(
|
|
93
|
+
project_name=PROJECT_NAME,
|
|
94
|
+
user_name=userconfig['username'],
|
|
95
|
+
project_root=project_root,
|
|
96
|
+
images=images,
|
|
97
|
+
artifacts=artifacts,
|
|
98
|
+
pipelines=[train_pipeline],
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return config
|
|
102
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import polars as pl
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def fit_step(
|
|
8
|
+
train_data_path,
|
|
9
|
+
coefficients_path,
|
|
10
|
+
predictions_path,
|
|
11
|
+
):
|
|
12
|
+
print(f'Fitting model.')
|
|
13
|
+
|
|
14
|
+
train_data = pl.read_parquet(train_data_path)
|
|
15
|
+
x = train_data.get_column('x').to_numpy()
|
|
16
|
+
y = train_data.get_column('y').to_numpy()
|
|
17
|
+
|
|
18
|
+
# add intercept term
|
|
19
|
+
intercept = np.ones(len(x))
|
|
20
|
+
x = np.stack([intercept, x], axis=1)
|
|
21
|
+
|
|
22
|
+
# fit linear regression model
|
|
23
|
+
coeffs = np.linalg.solve(np.dot(x.T, x), np.dot(x.T, y))
|
|
24
|
+
|
|
25
|
+
# compute in-sample predictions
|
|
26
|
+
preds = np.dot(x, coeffs)
|
|
27
|
+
preds = train_data.with_columns(pl.Series('pred', preds))
|
|
28
|
+
|
|
29
|
+
# save coefficients
|
|
30
|
+
with open(coefficients_path, 'w') as stream:
|
|
31
|
+
json.dump(
|
|
32
|
+
{
|
|
33
|
+
'alpha': float(coeffs[0]),
|
|
34
|
+
'beta': float(coeffs[1]),
|
|
35
|
+
},
|
|
36
|
+
stream,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# save in-sample predictions
|
|
40
|
+
preds.write_parquet(predictions_path)
|
|
41
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import polars as pl
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def generate_step(
|
|
6
|
+
random_seed,
|
|
7
|
+
num_samples,
|
|
8
|
+
alpha,
|
|
9
|
+
beta,
|
|
10
|
+
sigma,
|
|
11
|
+
train_data_path,
|
|
12
|
+
):
|
|
13
|
+
print(f'Generating training data.')
|
|
14
|
+
|
|
15
|
+
rng = np.random.default_rng(random_seed)
|
|
16
|
+
x = rng.normal(size=num_samples)
|
|
17
|
+
y = alpha + beta*x + sigma*rng.normal(size=num_samples)
|
|
18
|
+
|
|
19
|
+
train_data = pl.DataFrame([
|
|
20
|
+
pl.Series('x', x),
|
|
21
|
+
pl.Series('y', y),
|
|
22
|
+
])
|
|
23
|
+
train_data.write_parquet(train_data_path)
|
|
24
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
PARAMS = {
|
|
2
|
+
# Parameters for data generation.
|
|
3
|
+
'random_seed': 42, # The random seed.
|
|
4
|
+
'num_samples': 100, # The number of training samples to generate.
|
|
5
|
+
'alpha': 1.0, # The true value of the intercept coefficient.
|
|
6
|
+
'beta': 2.0, # The true value of the linear coefficient.
|
|
7
|
+
'sigma': 0.1, # The standard deviation of the error term.
|
|
8
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Your username is used for keeping your artifacts, images and cloud resources
|
|
2
|
+
# separate from those of other users.
|
|
3
|
+
#
|
|
4
|
+
# (In this example we only use a single local environment, so there are no
|
|
5
|
+
# other users here. But the username becomes very important when you're running
|
|
6
|
+
# jobs in the cloud on an account that you share with other people.)
|
|
7
|
+
userconfig: # TODO: choose a user name.
|