Skip to content

Commit d087f9b

Browse files
lukasz-artificialjesterhazy
authored andcommitted
export_saved_model: copy asset files (#100)
* export_saved_model: copy asset files
1 parent d4716b9 commit d087f9b

File tree

10 files changed

+696
-20
lines changed

10 files changed

+696
-20
lines changed

src/tf_container/serve.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# permissions and limitations under the License.
1313

1414
import json
15+
import re
1516
import shutil
1617
import subprocess
1718
import boto3
@@ -54,34 +55,33 @@ def export_saved_model(checkpoint_dir, model_path, s3=boto3.client('s3', region_
5455
raise e
5556
# Select most recent saved_model.pb
5657
saved_model_path = saved_model_path_array[-1]
57-
58-
variables_path = [x['Key'] for x in contents if 'variables/variables' in x['Key']]
59-
variable_names_to_paths = {v.split('/').pop(): v for v in variables_path}
58+
saved_model_base_path = os.path.dirname(saved_model_path)
6059

6160
prefixes = key_prefix.split('/')
62-
folders = saved_model_path.split('/')[len(prefixes):]
63-
saved_model_filename = folders.pop()
61+
folders = saved_model_path.split('/')[len(prefixes):-1]
6462
path_to_save_model = os.path.join(model_path, *folders)
6563

66-
path_to_variables = os.path.join(path_to_save_model, 'variables')
67-
68-
os.makedirs(path_to_variables)
69-
70-
target = os.path.join(path_to_save_model, saved_model_filename)
71-
s3.download_file(bucket_name, saved_model_path, target)
72-
logger.info("Downloaded saved model at {}".format(target))
64+
def file_filter(x): return x['Key'].startswith(saved_model_base_path) and not x['Key'].endswith("/")
65+
paths_to_copy = [x['Key'] for x in contents if file_filter(x)]
7366

74-
for filename, full_path in variable_names_to_paths.items():
75-
key = full_path
76-
target = os.path.join(path_to_variables, filename)
67+
for key in paths_to_copy:
68+
target = re.sub(r"^"+saved_model_base_path, path_to_save_model, key)
69+
_makedirs_for_file(target)
7770
s3.download_file(bucket_name, key, target)
71+
logger.info("Downloaded saved model at {}".format(path_to_save_model))
7872
else:
7973
if os.path.exists(checkpoint_dir):
8074
_recursive_copy(checkpoint_dir, model_path)
8175
else:
8276
logger.error("Failed to copy saved model. File does not exist in {}".format(checkpoint_dir))
8377

8478

79+
def _makedirs_for_file(file_path):
80+
directory = os.path.dirname(file_path)
81+
if not os.path.exists(directory):
82+
os.makedirs(directory)
83+
84+
8585
def _recursive_copy(src, dst):
8686
for root, dirs, files in os.walk(src):
8787
root = os.path.relpath(root, src)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import json
2+
import os
3+
import requests
4+
5+
6+
def test_json_request():
7+
data = ["great", "fantastic", "movie"]
8+
9+
serialized_output = requests.post("http://localhost:8080/invocations",
10+
data=json.dumps(data),
11+
headers={'Content-type': 'application/json',
12+
'Accept': 'application/json'}).content
13+
14+
prediction_result = json.loads(serialized_output)
15+
classes = prediction_result['result']['classifications'][0]['classes']
16+
assert len(classes) == 2
17+
assert classes[0].keys() == [u'score', u'label']
18+
19+
20+
def test_assets_were_restored():
21+
found_vocabulary_files = _find_files("/opt/ml/model/export/Servo",
22+
dir_predicate=lambda d: d.endswith("/assets"),
23+
file_predicate=lambda f: f == "vocabulary.txt")
24+
assert len(found_vocabulary_files) > 0, 'At least one "vocabulary.txt" asset file is expected'
25+
26+
27+
def _find_files(root_dir, dir_predicate=lambda _: True, file_predicate=lambda _: True):
28+
found_files = []
29+
for root, _, files in os.walk(root_dir):
30+
if dir_predicate(root):
31+
found_files += [root + "/" + file for file in files if file_predicate(file)]
32+
return found_files
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import os
2+
3+
from sagemaker import fw_utils
4+
5+
from test.integ.docker_utils import train, HostingContainer
6+
from test.integ.utils import copy_resource, create_config_files
7+
from test.integ.conftest import SCRIPT_PATH
8+
9+
import uuid
10+
11+
12+
def test_save_restore_assets(docker_image, sagemaker_session, opt_ml, processor):
13+
resource_path = os.path.join(SCRIPT_PATH, '../resources/sentiment')
14+
15+
default_bucket = sagemaker_session.default_bucket()
16+
17+
copy_resource(resource_path, opt_ml, 'data', 'input/data')
18+
19+
s3_source_archive = fw_utils.tar_and_upload_dir(session=sagemaker_session.boto_session,
20+
bucket=sagemaker_session.default_bucket(),
21+
s3_key_prefix='test_job',
22+
script='sentiment.py',
23+
directory=os.path.join(resource_path, 'code'))
24+
25+
checkpoint_s3_path = 's3://{}/save_restore_assets/output-{}'.format(default_bucket, uuid.uuid4())
26+
27+
additional_hyperparameters = dict(
28+
training_steps=1000,
29+
evaluation_steps=100,
30+
checkpoint_path=checkpoint_s3_path)
31+
create_config_files('sentiment.py', s3_source_archive.s3_prefix, opt_ml, additional_hyperparameters)
32+
os.makedirs(os.path.join(opt_ml, 'model'))
33+
34+
train(docker_image, opt_ml, processor)
35+
36+
with HostingContainer(opt_ml=opt_ml, image=docker_image, script_name='sentiment.py', processor=processor) as c:
37+
c.execute_pytest('test/integ/container_tests/sentiment_classification.py')

test/resources/sentiment/__init__.py

Whitespace-only changes.

test/resources/sentiment/code/__init__.py

Whitespace-only changes.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from __future__ import print_function
2+
3+
import tensorflow as tf
4+
5+
INPUT_TENSOR_NAME = 'inputs'
6+
CHANNEL_BASEDIR = '/opt/ml/input/data/'
7+
8+
9+
def estimator_fn(run_config, hyperparameters):
10+
feature_columns = [tf.feature_column.embedding_column(
11+
tf.feature_column.categorical_column_with_vocabulary_file(
12+
INPUT_TENSOR_NAME, CHANNEL_BASEDIR + "vocabulary.txt"),
13+
combiner="mean",
14+
dimension=32)]
15+
16+
return tf.estimator.DNNClassifier(feature_columns=feature_columns,
17+
hidden_units=[10],
18+
n_classes=2,
19+
config=run_config)
20+
21+
22+
def train_input_fn(training_dir, hyperparameters):
23+
return _generate_input_fn("train")
24+
25+
26+
def eval_input_fn(training_dir, hyperparameters):
27+
return _generate_input_fn("test")
28+
29+
30+
def serving_input_fn(hyperparameters):
31+
feature_spec = {INPUT_TENSOR_NAME: tf.VarLenFeature(dtype=tf.string)}
32+
return tf.estimator.export.build_parsing_serving_input_receiver_fn(feature_spec)()
33+
34+
35+
def _generate_input_fn(data_dir):
36+
dataset = tf.data.Dataset.from_generator(lambda: _input_data_reader(data_dir), (tf.string, tf.int32)).repeat(3)
37+
doc, label = dataset.make_one_shot_iterator().get_next()
38+
return {INPUT_TENSOR_NAME: doc}, label
39+
40+
41+
def _input_data_reader(subdir):
42+
data_file = CHANNEL_BASEDIR + subdir + ".txt"
43+
with open(data_file, "rt") as f:
44+
for line in f.readlines():
45+
if line.startswith("#"):
46+
continue
47+
label, doc = line.strip().split("\t")
48+
yield [doc.lower().split(" ")], [[int(label)]]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# samples from Large Movie Review Dataset (http://ai.stanford.edu/~amaas/data/sentiment/ , http://www.aclweb.org/anthology/P11-1015)
2+
1 One of the funniest movies made in recent years. Good characterization, plot and exceptional chemistry make this one a classic
3+
0 Hated it with all my being. Worst movie ever. Mentally- scarred. Help me. It was that bad.TRUST ME!!!
4+
0 This is quite possibly the worst sequel ever made. The script is unfunny and the acting stinks. The exact opposite of the original.
5+
0 This movie is terrible. It's about some no brain surfin dude that inherits some company. Does Carrot Top have no shame?<br /><br />
6+
1 This is the greatest movie ever. If you have written it off with out ever seeing it. You must give it a second try.
7+
1 Just love the interplay between two great characters of stage & screen - Veidt & Barrymore
8+
0 Don't waste your time and money on it. It's not quite as bad as "Adrenalin", by the same director but that's not saying much.
9+
1 Absolutely fantastic! Whatever I say wouldn't do this underrated movie the justice it deserves. Watch it now! FANTASTIC!
10+
0 This movie is so bad it's almost good. Bad story, bad acting, bad music, you name it. O.K., who are the jokers that gave this flick a '10'?
11+
1 Add this little gem to your list of holiday regulars. It is<br /><br />sweet, funny, and endearing
12+
0 Ten minutes of people spewing gallons of pink vomit. Recurring scenes of enormous piles of dog excrement - need one say more???
13+
0 Primary plot!Primary direction!Poor interpretation.
14+
0 Read the book, forget the movie!
15+
1 This is a good film. This is very funny. Yet after this film there were no good Ernest films!
16+
1 If you like Pauly Shore, you'll love Son in Law. If you hate Pauly Shore, then, well...I liked it!
17+
1 Brilliant and moving performances by Tom Courtenay and Peter Finch.
18+
1 A touching movie. It is full of emotions and wonderful acting. I could have sat through it a second time.
19+
1 Without a doubt, one of Tobe Hoppor's best! Epic storytellng, great special effects, and The Spacegirl (vamp me baby!).
20+
0 Widow hires a psychopath as a handyman. Sloppy film noir thriller which doesn't make much of its tension promising set-up. (3/10)
21+
1 For pure gothic vampire cheese nothing can compare to the Subspecies films. I highly recommend each and every one of them.
22+
0 More suspenseful, more subtle, much, much more disturbing....
23+
1 a mesmerizing film that certainly keeps your attention... Ben Daniels is fascinating (and courageous) to watch.
24+
0 What a script, what a story, what a mess!
25+
1 If you've ever had a mad week-end out with your mates then you'll appreciate this film. Excellent fun and a laugh a minute.
26+
0 I caught this film late at night on HBO. Talk about wooden acting, unbelievable plot, et al. Very little going in its favor. Skip it.
27+
1 Brilliant. Ranks along with Citizen Kane, The Matrix and Godfathers. Must see, at least for basset in her early days. Watch it.
28+
0 The plot was really weak and confused. This is a true Oprah flick. (In Oprah's world, all men are evil and all women are victims.)
29+
0 I hope this group of film-makers never re-unites.
30+
1 This is a great movie. Too bad it is not available on home video.
31+
0 This is a terrible movie, don't waste your money on it. Don't even watch it for free. That's all I have to say.

0 commit comments

Comments
 (0)