├── requirements.txt ├── model ├── checkpoint ├── model.ckpt.index ├── model.ckpt.meta └── model.ckpt.data-00000-of-00001 ├── settings.ini ├── settings.ini.template ├── vendored └── README.md ├── serverless.yml ├── README.md ├── run_model.py ├── .gitignore ├── tf_regression.py └── handler.py /requirements.txt: -------------------------------------------------------------------------------- 1 | tensorflow==0.12.1 2 | -------------------------------------------------------------------------------- /model/checkpoint: -------------------------------------------------------------------------------- 1 | model_checkpoint_path: "model.ckpt" 2 | all_model_checkpoint_paths: "model.ckpt" 3 | -------------------------------------------------------------------------------- /settings.ini: -------------------------------------------------------------------------------- 1 | [model] 2 | LOCAL_MODEL_FOLDER = model/ 3 | LEARNING_RATE = 0.01 4 | TRAINING_EPOCHS = 5000 -------------------------------------------------------------------------------- /settings.ini.template: -------------------------------------------------------------------------------- 1 | [model] 2 | LOCAL_MODEL_FOLDER = '' 3 | LEARNING_RATE = 0 4 | TRAINING_EPOCHS = 0 -------------------------------------------------------------------------------- /model/model.ckpt.index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacopotagliabue/tensorflow_to_lambda_serverless/HEAD/model/model.ckpt.index -------------------------------------------------------------------------------- /model/model.ckpt.meta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacopotagliabue/tensorflow_to_lambda_serverless/HEAD/model/model.ckpt.meta -------------------------------------------------------------------------------- /vendored/README.md: -------------------------------------------------------------------------------- 1 | In this folder you need to put all the dependencies. 2 | You can find a pre-compiled zip for Lambdas in the Medium blog post. -------------------------------------------------------------------------------- /model/model.ckpt.data-00000-of-00001: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacopotagliabue/tensorflow_to_lambda_serverless/HEAD/model/model.ckpt.data-00000-of-00001 -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | frameworkVersion: ">=1.2.1" 2 | 3 | service: tensorflow-lambda-regression 4 | 5 | provider: 6 | name: aws 7 | runtime: python2.7 8 | stage: dev 9 | region: us-west-2 10 | 11 | functions: 12 | predict: 13 | handler: handler.predict 14 | events: 15 | - http: 16 | path: /predict 17 | method: get 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tensorflow to AWS lambda 2 | This project is the companion code for my Medium post on deploying tensorflow models to AWS lambda (using [Serverless](https://serverless.com/)): in a few minutes and a few lines of Python code you can serve predictions from a trained model through a lambda-powered endpoint. 3 | 4 | Please refer to the Medium blog [post](https://medium.com/@jacopotagliabue/serving-tensorflow-predictions-with-python-and-aws-lambda-facb4ab87ddd#.v01eyg8kh) for a full explanation on the code structure and how to deploy it. 5 | -------------------------------------------------------------------------------- /run_model.py: -------------------------------------------------------------------------------- 1 | """ 2 | Small script to run the regression model as a standalone code for training and testing purposes 3 | """ 4 | import ConfigParser 5 | import os 6 | import numpy 7 | from tf_regression import TensorFlowRegressionModel 8 | 9 | # get config file 10 | HERE = os.path.dirname(os.path.realpath(__file__)) 11 | Config = ConfigParser.ConfigParser() 12 | Config.read(HERE + '/settings.ini') 13 | # settings for the training 14 | MODEL_DIR = Config.get('model', 'LOCAL_MODEL_FOLDER') 15 | LEARNING_RATE = float(Config.get('model', 'LEARNING_RATE')) 16 | TRAINING_EPOCHS = int(Config.get('model', 'TRAINING_EPOCHS')) 17 | 18 | 19 | def main(): 20 | # training data 21 | train_X = numpy.asarray([3.3, 4.4, 5.5, 6.71, 6.93, 4.168, 9.779, 6.182, 7.59, 2.167, 22 | 7.042, 10.791, 5.313, 7.997, 5.654, 9.27, 3.1]) 23 | train_Y = numpy.asarray([1.7, 2.76, 2.09, 3.19, 1.694, 1.573, 3.366, 2.596, 2.53, 1.221, 24 | 2.827, 3.465, 1.65, 2.904, 2.42, 2.94, 1.3]) 25 | # Uncomment here for training again the model 26 | # r = TensorFlowRegressionModel(Config) 27 | # r.train(train_X, train_Y, LEARNING_RATE, TRAINING_EPOCHS, MODEL_DIR) 28 | # make some predictions with the stored model 29 | test_val = 6.83 30 | r = TensorFlowRegressionModel(Config, is_training=False) 31 | y_pred = r.predict(test_val) 32 | print y_pred 33 | assert y_pred > 2.48 and y_pred < 2.52 34 | 35 | return 36 | 37 | 38 | if __name__ == "__main__": 39 | main() 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | 91 | .serveless/ 92 | 93 | .idea/ 94 | .idea/inspectionProfiles 95 | 96 | .idea/ 97 | *.zip 98 | -------------------------------------------------------------------------------- /tf_regression.py: -------------------------------------------------------------------------------- 1 | # refactored from the examples at https://github.com/aymericdamien/TensorFlow-Examples 2 | import tensorflow as tf 3 | import numpy 4 | 5 | 6 | class TensorFlowRegressionModel: 7 | 8 | def __init__(self, config, is_training=True): 9 | # store the model variables into a class object 10 | self.vars = self.set_vars() 11 | self.model = self.build_model(self.vars) 12 | # if it is not training, restore the model and store the session in the class 13 | if not is_training: 14 | self.sess = self.restore_model(config.get('model', 'LOCAL_MODEL_FOLDER')) 15 | 16 | return 17 | 18 | def set_vars(self): 19 | """ 20 | Define the linear regression model through the variables 21 | """ 22 | return { 23 | # placeholders 24 | 'X': tf.placeholder("float"), 25 | 'Y': tf.placeholder("float"), 26 | # model weight and bias 27 | 'W': tf.Variable(numpy.random.randn(), name="weight"), 28 | 'b': tf.Variable(numpy.random.randn(), name="bias") 29 | } 30 | 31 | def build_model(self, vars): 32 | """ 33 | Define the linear regression model through the variables 34 | """ 35 | return tf.add(tf.mul(vars['X'], vars['W']), vars['b']) 36 | 37 | def restore_model(self, model_dir): 38 | sess = tf.Session() 39 | saver = tf.train.Saver() 40 | ckpt = tf.train.get_checkpoint_state(model_dir) 41 | if ckpt and ckpt.model_checkpoint_path: 42 | saver.restore(sess, ckpt.model_checkpoint_path) 43 | 44 | return sess 45 | 46 | def train(self, train_X, train_Y, learning_rate, training_epochs, model_output_dir=None): 47 | n_samples = train_X.shape[0] 48 | # Mean squared error 49 | cost = tf.reduce_sum(tf.pow(self.model - self.vars['Y'], 2)) / (2 * n_samples) 50 | # Gradient descent 51 | optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost) 52 | # Launch the graph 53 | with tf.Session() as sess: 54 | sess.run(tf.global_variables_initializer()) 55 | saver = tf.train.Saver(tf.global_variables()) 56 | # Fit all training data 57 | for epoch in range(training_epochs): 58 | for x, y in zip(train_X, train_Y): 59 | sess.run(optimizer, feed_dict={self.vars['X']: x, self.vars['Y']: y}) 60 | # Save model locally 61 | saver.save(sess, model_output_dir + 'model.ckpt') 62 | 63 | return 64 | 65 | def predict(self, x_val): 66 | return self.sess.run(self.vars['W']) * x_val + self.sess.run(self.vars['b']) 67 | -------------------------------------------------------------------------------- /handler.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import ConfigParser 5 | """ 6 | This is needed so that the script running on AWS will pick up the pre-compiled dependencies 7 | from the vendored folder 8 | """ 9 | HERE = os.path.dirname(os.path.realpath(__file__)) 10 | sys.path.append(os.path.join(HERE, "vendored")) 11 | """ 12 | Now that the script knows where to look, we can safely import our objects 13 | """ 14 | from tf_regression import TensorFlowRegressionModel 15 | """ 16 | Declare here global objects living across requests 17 | """ 18 | # use Pythonic ConfigParser to handle settings 19 | Config = ConfigParser.ConfigParser() 20 | Config.read(HERE + '/settings.ini') 21 | # instantiate the tf_model in "prediction mode" 22 | tf_model = TensorFlowRegressionModel(Config, is_training=False) 23 | # just print a message so we can verify in AWS the loading of dependencies was correct 24 | print "loaded done!" 25 | 26 | 27 | def validate_input(input_val): 28 | """ 29 | Helper function to check if the input is indeed a float 30 | 31 | :param input_val: the value to check 32 | :return: the floating point number if the input is of the right type, None if it cannot be converted 33 | """ 34 | try: 35 | float_input = float(input_val) 36 | return float_input 37 | except ValueError: 38 | return None 39 | 40 | 41 | def get_param_from_url(event, param_name): 42 | """ 43 | Helper function to retrieve query parameters from a Lambda call. Parameters are passed through the 44 | event object as a dictionary. 45 | 46 | :param event: the event as input in the Lambda function 47 | :param param_name: the name of the parameter in the query string 48 | :return: the parameter value 49 | """ 50 | params = event['queryStringParameters'] 51 | return params[param_name] 52 | 53 | 54 | def return_lambda_gateway_response(code, body): 55 | """ 56 | This function wraps around the endpoint responses in a uniform and Lambda-friendly way 57 | 58 | :param code: HTTP response code (200 for OK), must be an int 59 | :param body: the actual content of the response 60 | """ 61 | return {"statusCode": code, "body": json.dumps(body)} 62 | 63 | 64 | def predict(event, context): 65 | """ 66 | This is the function called by AWS Lambda, passing the standard parameters "event" and "context" 67 | When deployed, you can try it out pointing your browser to 68 | 69 | {LambdaURL}/{stage}/predict?x=2.7 70 | 71 | where {LambdaURL} is Lambda URL as returned by serveless installation and {stage} is set in the 72 | serverless.yml file. 73 | 74 | """ 75 | try: 76 | param = get_param_from_url(event, 'x') 77 | x_val = validate_input(param) 78 | if x_val: 79 | value = tf_model.predict(x_val) 80 | else: 81 | raise "Input parameter has invalid type: float expected" 82 | except Exception as ex: 83 | error_response = { 84 | 'error_message': "Unexpected error", 85 | 'stack_trace': str(ex) 86 | } 87 | return return_lambda_gateway_response(503, error_response) 88 | 89 | return return_lambda_gateway_response(200, {'value': value}) 90 | 91 | 92 | 93 | --------------------------------------------------------------------------------