Deploying Sklearn Machine Learning on AWS Lambda with SAM | by GreekDataGuy | Towards Data Science

Building machine learning models on a laptop is straightforward.
Deploying models so they’re available in production is not.
Productionizing ML is hard. Not only does it combine SDE, ML and DevOps. But there’s no one-size fits all approach.
A company’s setup depends on a combination of business requirements, financial constraints and technical skills.
Today we’ll learn to deploy serverless ML on Lambda.
While most ML is not a good fit for serverless, it can make sense on occasion. I’ll explain when it makes sense in a moment.
Background informationTrain a model locallyPush the model to S3Build a SAM applicationTest the application locallyDeploy the application to AWSTest the APIConclusion
Background information
Train a model locally
Push the model to S3
Build a SAM application
Test the application locally
Deploy the application to AWS
Test the API
Conclusion
Your ML is a good fit for Lambda if...
Your model is small. Lambda reloads your model on each request so a large model is is going to result in slow response times.
Data vectors are small. Lambda comes with a 3GB memory ceiling. So if your input data is massive, it’s just going to crash.
You model will be sporadically accessed. Lambda scales out (not up) to infinite and charges you per request. So it’s well suited to intermittent “on-the-fly” demand.
Low-cost is important. Lambda charges $0.20 per million requests.
Otherwise... consider deploying on EC2, or Sagemaker (if money is not a concern).
SAM stands for “Serverless Application Model”. It’s an open-source framework for provisioning AWS services via code.
As a rule, always choose infrastructure-as-code over setting up infrastructure in the AWS console. The former is more maintainable, reproducible, and allows storing versions in Github.
You’ll need a few things setup on your machine.
AWS Shell (downloaded and configured)
AWS SAM Client
Docker
The focus of this tutorial is on deployment. So we won’t concern ourselves with training an accurate model. But you can easily replace this with your own more complex model.
In Jupyter notebook, start by importing required libraries.
from sklearn.datasets import load_wineimport pandas as pdimport numpy as npimport pickle
Import data and generate a dataframe.
data = load_wine() # import datasetdf = pd.DataFrame(data['data'], columns=data['feature_names']) # build dataframedf['target'] = data['target'] # add dependent variabledf = df.sample(frac=1) # randomize the datadf.head(3)
Split data into test and train sets.
print("row count:",len(df))train_df = df[:150]test_df = df[150:]#=> row count: 178
Prepare data to train a model.
def X_and_y_from_df(df, y_column, X_columns = []):    '''Extract data from the dataframe'''    X = {}    for feature in X_columns:        X[feature] = df[feature].tolist()    y = df[y_column].tolist()    return X, yX_train, y_train = X_and_y_from_df(train_df, 'target', ['alcohol'])X_test, y_test = X_and_y_from_df(test_df, 'target', ['alcohol'])X_train = np.array(X_train['alcohol']).reshape(-1,1)X_test = np.array(X_test['alcohol']).reshape(-1,1)
Fit the model.
from sklearn.linear_model import LogisticRegressionmodel = LogisticRegression()model.fit(X_train, y_train)
Pickle the model.
import picklepickle.dump( model, open( "pickled_model.p", "wb" ) )
Great! Now it’s in a format that can be easily stored in S3.
Open your command line in the same directory as your notebook above.
Create an S3 bucket.
$ aws s3 mb s3://sam-sklearn-lambda-123
Note: You’ll need your own globally unique bucket name.
Push your pickled model to S3.
$ aws s3 cp pickled_model.p s3://lambda-app-bucket-123
Your model is now accessible to AWS services and can be updated or changed without redeploying any other code.
On the command line, initialize a SAM application.
$ sam init
This will ask you 4 questions which will be used to build a starting template for our SAM application.
Answer the questions as below.
Which template source would you like to use? 1 — AWS Quick Start TemplatesWhich runtime would you like to use? 2 — python3.8Project name [sam-app]: [press “enter” to keep the default name]AWS quick start application templates: 1 — Hello World Example
Which template source would you like to use? 1 — AWS Quick Start Templates
Which runtime would you like to use? 2 — python3.8
Project name [sam-app]: [press “enter” to keep the default name]
AWS quick start application templates: 1 — Hello World Example
Now cd into the directory that was just created, and open it with your favorite text editor. I’m using Atom.
The directory structure should look like below.
Next, let’s change the name of our the subdirectory where our lambda code lives from /hello_world to code/. You can do this with the following command.
$ mv hello_world/ code/ 
Now it’s time to update files.
This is the file which tells CloudFormation which services should be provisioned.
Update it so it looks like below.
AWSTemplateFormatVersion: '2010-09-09'Transform: AWS::Serverless-2016-10-31Description: 'Serverless wine classifier'Globals:  Function:    Timeout: 10Resources:  WineClassifierFunction:    Type: AWS::Serverless::Function    Properties:      CodeUri: WineClassifierFunction      Handler: app.lambda_handler      Runtime: python3.8      MemorySize: 1024      Role: arn:aws:iam::421242169512:role/LambdaCanReadS3      Environment:        Variables:          s3_bucket: lambda-app-bucket-123          model_name: pickled_model.p      Events:        WineClassifier:          Type: Api          Properties:            Path: /classify            Method: postOutputs:  WineClassifierApi:    Description: API Gateway endpoint URL for Prod stage for WineClassifier function    Value:      Fn::Sub: https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/classify/
Note: You’ll need to create an IAM role in AWS which permits Lambda to read files in S3. Mine is called “arn:aws:iam::421242169512:role/LambdaCanReadS3” but you’ll need to replace this with your role’s name.
The above provisions both an API Gateway endpoint and a Lambda.
It also specifies the language running on the Lambda as Python3.8, sets a few variables that can be accessed in our lambda code, and configures the API Gateway endpoint.
This is where our Lambda code lives.
import jsonimport sklearnimport boto3import osimport jsonimport pickles3 = boto3.client('s3')s3_bucket = os.environ['s3_bucket']model_name = os.environ['model_name']temp_file_path = '/tmp/' + model_namefrom sklearn.linear_model import LogisticRegressiondef lambda_handler(event, context):    # Parse input    body = event['body']    input = json.loads(body)['data']    input = float(input )    # Download pickled model from S3 and unpickle    s3.download_file(s3_bucket, model_name, temp_file_path)    with open(temp_file_path, 'rb') as f:        model = pickle.load(f)    # Predict class    prediction = model.predict([[input]])[0]    return {        "statusCode": 200,        "body": json.dumps({            "prediction": str(prediction),        }),    }
In a nutshell, this imports libraries, pulls your model from S3, parses the input request, classifies it, and returns a response.
This is where we specify what libraries should be installed on our Lambda. Below is all we need.
requestsboto3sklearn
Now run the following on your command line.
$ sam build
This creates a .aws-sam/build directory, which contains code that will be deployed to AWS.
Before deploying, let’s make sure the application works locally. Do not continue until you get a successful response here.
Run below to provision the app locally.
$ sam local start-api
Now hit the endpoint with a curl.
$ curl -XPOST http://127.0.0.1:3000/classify -H 'Content-Type: application/json' -d '{"data":".10"}'
The response should look like below.
{"prediction": "1"}
If you’re response looks like above, continue!
cd into the .aws-sam/build/ directory.
$ cd .aws-sam$ cd build
Now run below. This creates a zip file with dependencies that will be deployed (in a moment). This will take a minute.
Note: I’m using the name of my S3 bucket but you should use the name of yours.
$ sam package --template-file template.yaml --s3-bucket lambda-app-bucket-123 --output-template-file packaged.yaml
Then run below to deploy your application.
$ sam deploy --template-file packaged.yaml --stack-name SklearnLambdaStack --capabilities CAPABILITY_IAM
You should get a confirmation that your application has been deployed.
In the previous response from deployment, you’ll be provided with a URL.
If everything worked, sending a request to this URL should return the same response as making a request to your app running locally.
$ curl -XPOST https://6lsk6c6vw2.execute-api.us-east-1.amazonaws.com/Prod/classify/ -H 'Content-Type: application/json' -d '{"data":".10"}'
So the above should return.
{"prediction": "1"}
Congratulations! You’ve deployed your first serverless ML application!
As you can see, deploying ML on serverless architecture is anything but straightforward.
The example we walked through is about as simple as it gets; we even skipped API authentication because I didn’t want to detract from the core lesson.
There aren’t many great examples on productionizing serverless ML, so I hope this helps speed up your learning process.
I fully expect we’ll see more and better tool for productionizing ML in future. But until then, infrastructure is going to be tightly coupled with model deployment.
I hope you found this useful. As always, let me know if you get stuck and I’ll try to help!