<jupyter_start><jupyter_text>Gender Prediction, using Pre-trained Keras ModelDeep Neural Networks can be used to extract features in the input and derive higher level abstractions. This technique is used regularly in vision, speech and text analysis. In this exercise, we use a pre-trained model deep learning model that would identify low level features in texts containing people's names, and would be able to classify them in one of two categories - Male or Female.  Network ArchitectureThe problem we are trying to solve is to predict whether a given name belongs to a male or female. We will use supervised learning, where the character sequence making up the names would be `X` variable, and the flag indicating **Male(M)** or **Female(F)**  would be `Y` variable.We use a stacked 2-Layer LSTM model and a final dense layer with softmax activation as our network architecture. We use categorical cross-entropy as loss function, with an Adam optimizer. We also add a 20% dropout layer is added for regularization to avoid over-fitting.   Dependencies*  The model was built using Keras, therefore we need to include Keras deep learning library to build the network locally, in order to be able to test, prior to hosting the model.     * While running on SageMaker Notebook Instance, we choose conda_tensorflow kernel, so that Keras code is compiled to use tensorflow in the backend. * If you choose P2 and P3 class of instances for your Notebook, using Tensorflow ensures the low level code takes advantage of all available GPUs. So further dependencies needs to be installed.<jupyter_code>import os
import time
import numpy as np
import keras
from keras.models import load_model
import boto3<jupyter_output>Using TensorFlow backend.<jupyter_text>Model testingTo test the validity of the model, we do some local testing.The model was built to be able to process one-hot encoded data representing names, therefore we need to do same pre-processing on our test data (one-hot encoding using the same character indices)We feed this one-hot encoded test data to the model, and the `predict` generates a vector, similar to the training labels vector we used before. Except in this case, it contains what model thinks the gender represented by each of the test records.To present data intutitively, we simply map it back to `Male` / `Female`, from the `0` / `1` flag.<jupyter_code>!tar -zxvf ../pretrained-model/model.tar.gz -C ../pretrained-model/    
model = load_model('../pretrained-model/lstm-gender-classifier-model.h5')
char_indices = np.load('../pretrained-model/lstm-gender-classifier-indices.npy').item()
max_name_length = char_indices['max_name_length']
char_indices.pop('max_name_length', None)
alphabet_size = len(char_indices)
print(char_indices)
print(max_name_length)
print(alphabet_size)
names_test = ["Tom","Allie","Jim","Sophie","John","Kayla","Mike","Amanda","Andrew"]
num_test = len(names_test)

X_test = np.zeros((num_test, max_name_length, alphabet_size))

for i,name in enumerate(names_test):
    name = name.lower()
    for t, char in enumerate(name):
        X_test[i, t,char_indices[char]] = 1

predictions = model.predict(X_test)

for i,name in enumerate(names_test):
    print("{} ({})".format(names_test[i],"M" if predictions[i][0]>predictions[i][1] else "F"))<jupyter_output>Tom (M)
Allie (F)
Jim (M)
Sophie (F)
John (M)
Kayla (F)
Mike (M)
Amanda (F)
Andrew (M)<jupyter_text>Model savingIn order to deploy the model behind an hosted endpoint, we need to save the model fileto an S3 location.    We can obtain the name of the S3 bucket from the execution role we attached to this Notebook instance. This should work if the policies granting read permission to IAM policies was granted, as per the documentation.If for some reason, it fails to fetch the associated bucket name, it asks the user to enter the name of the bucket. If asked, use the bucket that you created in Module-3, such as 'smworkshop-firstname-lastname'.    It is important to ensure that this is the same S3 bucket, to which you provided access in the Execution role used while creating this Notebook instance.<jupyter_code>sts = boto3.client('sts')
iam = boto3.client('iam')


caller = sts.get_caller_identity()
account = caller['Account']
arn = caller['Arn']
role = arn[arn.find("/AmazonSageMaker")+1:arn.find("/SageMaker")]
timestamp = role[role.find("Role-")+5:]
policyarn = "arn:aws:iam::{}:policy/service-role/AmazonSageMaker-ExecutionPolicy-{}".format(account, timestamp)

s3bucketname = ""
policystatements = []

try:
    policy = iam.get_policy(
        PolicyArn=policyarn
    )['Policy']
    policyversion = policy['DefaultVersionId']
    policystatements = iam.get_policy_version(
        PolicyArn = policyarn, 
        VersionId = policyversion
    )['PolicyVersion']['Document']['Statement']
except Exception as e:
    s3bucketname=input("Which S3 bucket do you want to use to host training data and model? ")
    
for stmt in policystatements:
    action = ""
    actions = stmt['Action']
    for act in actions:
        if act == "s3:ListBucket":
            action = act
            break
    if action == "s3:ListBucket":
        resource = stmt['Resource'][0]
        s3bucketname = resource[resource.find(":::")+3:]

print(s3bucketname)
s3 = boto3.resource('s3')
s3.meta.client.upload_file('../pretrained-model/model.tar.gz', s3bucketname, 'model/model.tar.gz')<jupyter_output><empty_output><jupyter_text>Model hostingAmazon SageMaker provides a powerful orchestration framework that you can use to productionize any of your own machine learning algorithm, using any machine learning framework and programming languages.This is possible because SageMaker, as a manager of containers, have standarized ways of interacting with your code running inside a Docker container. Since you are free to build a docker container using whatever code and depndency you like, this gives you freedom to bring your own machinery.In the following steps, we'll containerize the prediction code and host the model behind an API endpoint.This would allow us to use the model from web-application, and put it into real use.The boilerplate code, which we affectionately call the `Dockerizer` framework, was made available on this Notebook instance by the Lifecycle Configuration that you used. Just look into the folder and ensure the necessary files are available as shown.                |    ├── container        │        ├── byoa        |   |        │   ├── train        |   |        │   ├── predictor.py        |   |        │   ├── serve        |   |        │   ├── nginx.conf        |   |        │   └── wsgi.py        |        ├── build_and_push.sh        │           ├── Dockerfile.cpu        │                └── Dockerfile.gpu<jupyter_code>os.chdir('../container')
os.getcwd()
!ls -Rl<jupyter_output>.:
total 16
-rwxrwxrwx 1 root root 1382 Aug 16 07:39 build_and_push.sh
drwxrwxrwx 2 root root 4096 Aug 16 07:39 byoa
-rw-rw-rw- 1 root root 1872 Aug 16 07:39 Dockerfile.cpu
-rw-rw-rw- 1 root root 1938 Aug 16 07:39 Dockerfile.gpu

./byoa:
total 20
-rwxrwxrwx 1 root root  687 Aug 16 07:39 nginx.conf
-rwxrwxrwx 1 root root 2887 Aug 16 07:39 predictor.py
-rwxrwxrwx 1 root root 2429 Aug 16 07:39 serve
-rwxrwxrwx 1 root root 2336 Aug 16 07:39 train
-rwxrwxrwx 1 root root  202 Aug 16 07:39 wsgi.py<jupyter_text>* `Dockerfile` describes the container image and the accompanying script `build_and_push.sh` does the heavy lifting of building the container, and uploading it into an Amazon ECR repository* Sagemaker containers that we'll be building serves prediction request using a Flask based application. `wsgi.py` is a wrapper to invoke the Flask application, while `nginx.conf` is the configuration for the nginx front end and `serve` is the program that launches the gunicorn server. These files can be used as-is, and are required to build the webserver stack serving prediction requests, following the architecture as shown:![Request serving stack](images/stack.png "Request serving stack")Request serving stack (expand to view diagram)    ![Request serving stack](images/stack.png "Request serving stack")* The file named `predictor.py` is where we need to package the code for generating inference using the trained model that was saved into an S3 bucket location by the training code during the training job run.* We'll write code into this file using Jupyter magic command - `writefile`.First part of the file would contain the necessary imports, as ususal.<jupyter_code>%%writefile byoa/predictor.py
# This is the file that implements a flask server to do inferences. It's the file that you will modify to
# implement the scoring for your own algorithm.

from __future__ import print_function

import os
import json
import pickle
from io import StringIO
import sys
import signal
import traceback

import numpy as np

import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.layers import Embedding
from keras.layers import LSTM
from keras.models import load_model
import flask

import tensorflow as tf

import pandas as pd

from os import listdir, sep
from os.path import abspath, basename, isdir
from sys import argv<jupyter_output>Overwriting byoa/predictor.py<jupyter_text>When run within an instantiated container, SageMaker makes the trained model available locally at `/opt/ml`<jupyter_code>%%writefile -a byoa/predictor.py

prefix = '/opt/ml/'
model_path = os.path.join(prefix, 'model')<jupyter_output>Appending to byoa/predictor.py<jupyter_text>The machinery to produce inference is wrapped around in a Pythonic class structure, within a `Singleton` class, aptly named - `ScoringService`.We create `Class` variables in this class to hold loaded model, character indices, tensor-flow graph, and anything else that needs to be referenced while generating prediction.<jupyter_code>%%writefile -a byoa/predictor.py

# A singleton for holding the model. This simply loads the model and holds it.
# It has a predict function that does a prediction based on the model and the input data.

class ScoringService(object):
    model_type = None           # Where we keep the model type, qualified by hyperparameters used during training
    model = None                # Where we keep the model when it's loaded
    graph = None
    indices = None              # Where we keep the indices of Alphabet when it's loaded<jupyter_output>Appending to byoa/predictor.py<jupyter_text>Generally, we have to provide class methods to load the model and related artefacts from the model path as assigned by SageMaker within the running container.Notice here that SageMaker copies the artefacts from the S3 location (as defined during model creation) into the container local file system.<jupyter_code>%%writefile -a byoa/predictor.py

    @classmethod
    def get_indices(cls):
        #Get the indices for Alphabet for this instance, loading it if it's not already loaded
        if cls.indices == None:
            model_type='lstm-gender-classifier'
            index_path = os.path.join(model_path, '{}-indices.npy'.format(model_type))
            if os.path.exists(index_path):
                cls.indices = np.load(index_path).item()
            else:
                print("Character Indices not found.")
        return cls.indices

    @classmethod
    def get_model(cls):
        #Get the model object for this instance, loading it if it's not already loaded
        if cls.model == None:
            model_type='lstm-gender-classifier'
            mod_path = os.path.join(model_path, '{}-model.h5'.format(model_type))
            if os.path.exists(mod_path):
                cls.model = load_model(mod_path)
                cls.model._make_predict_function()
                cls.graph = tf.get_default_graph()
            else:
                print("LSTM Model not found.")
        return cls.model<jupyter_output>Appending to byoa/predictor.py<jupyter_text>Finally, inside another clas method, named `predict`, we provide the code that we used earlier to generate prediction.Only difference with our previous test prediciton (in development notebook) is that in this case, the predictor will grab the data from the `input` variable, which in turn is obtained from the HTTP request payload.<jupyter_code>%%writefile -a byoa/predictor.py

    @classmethod
    def predict(cls, input):

        mod = cls.get_model()
        ind = cls.get_indices()

        result = {}

        if mod == None:
            print("Model not loaded.")
        else:
            if 'max_name_length' not in ind:
                max_name_length = 15
                alphabet_size = 26
            else:
                max_name_length = ind['max_name_length']
                ind.pop('max_name_length', None)
                alphabet_size = len(ind)

            inputs_list = input.strip('\n').split(",")
            num_inputs = len(inputs_list)

            X_test = np.zeros((num_inputs, max_name_length, alphabet_size))

            for i,name in enumerate(inputs_list):
                name = name.lower().strip('\n')
                for t, char in enumerate(name):
                    if char in ind:
                        X_test[i, t,ind[char]] = 1

            with cls.graph.as_default():
                predictions = mod.predict(X_test)

            for i,name in enumerate(inputs_list):
                result[name] = 'M' if predictions[i][0]>predictions[i][1] else 'F'
                print("{} ({})".format(inputs_list[i],"M" if predictions[i][0]>predictions[i][1] else "F"))

        return json.dumps(result)<jupyter_output>Appending to byoa/predictor.py<jupyter_text>With the prediction code captured, we move on to define the flask app, and provide a `ping`, which SageMaker uses to conduct health check on container instances that are responsible behind the hosted prediction endpoint.Here we can have the container return healthy response, with status code `200` when everythings goes well.For simplicity, we are only validating whether model has been loaded in this case. In practice, this provides opportunity extensive health check (including any external dependency check), as required.<jupyter_code>%%writefile -a byoa/predictor.py

# The flask app for serving predictions
app = flask.Flask(__name__)

@app.route('/ping', methods=['GET'])
def ping():
    #Determine if the container is working and healthy.
    # Declare it healthy if we can load the model successfully.
    health = ScoringService.get_model() is not None and ScoringService.get_indices() is not None
    status = 200 if health else 404
    return flask.Response(response='\n', status=status, mimetype='application/json')<jupyter_output>Appending to byoa/predictor.py<jupyter_text>Last but not the least, we define a `transformation` method that would intercept the HTTP request coming through to the SageMaker hosted endpoint.Here we have the opportunity to decide what type of data we accept with the request. In this particular example, we are accepting only `CSV` formatted data, decoding the data, and invoking prediction.The response is similarly funneled backed to the caller with MIME type of `CSV`.You are free to choose any or multiple MIME types for your requests and response. However if you choose to do so, it is within this method that we have to transform the back to and from the format that is suitable to passed for prediction.<jupyter_code>%%writefile -a byoa/predictor.py


@app.route('/invocations', methods=['POST'])
def transformation():
    #Do an inference on a single batch of data
    data = None

    # Convert from CSV to pandas
    if flask.request.content_type == 'text/csv':
        data = flask.request.data.decode('utf-8')
    else:
        return flask.Response(response='This predictor only supports CSV data', status=415, mimetype='text/plain')

    print('Invoked with {} records'.format(data.count(",")+1))

    # Do the prediction
    predictions = ScoringService.predict(data)

    result = ""
    for prediction in predictions:
        result = result + prediction

    return flask.Response(response=result, status=200, mimetype='text/csv')<jupyter_output>Appending to byoa/predictor.py<jupyter_text>Note that in containerizing our custom LSTM Algorithm, where we used `Keras` as our framework of our choice, we did not have to interact directly with the SageMaker API, even though SageMaker API doesn't support `Keras`.This serves to show the power and flexibility offered by containerized machine learning pipeline on SageMaker.  Container publishingIn order to host and deploy the trained model using SageMaker, we need to build the `Docker` containers, publish it to `Amazon ECR` repository, and then either use SageMaker console or API to created the endpoint configuration and deploy the stages.Conceptually, the steps required for publishing are:1. Make the`predictor.py` files executable2. Create an ECR repository within your default region3. Build a docker container with an identifieable name4. Tage the image and publish to the ECR repositoryAll of these are conveniently encapsulated inside `build_and_push` script. We simply run it with the unique name of our production run.<jupyter_code>run_type='cpu'
instance_class = "p3" if run_type.lower()=='gpu' else "c4"
instance_type = "ml.{}.8xlarge".format(instance_class)

pipeline_name = 'gender-classifier'
run=input("Enter run version: ")

run_name = pipeline_name+"-"+run
if run_type == "cpu":
    !cp "Dockerfile.cpu" "Dockerfile"

if run_type == "gpu":
    !cp "Dockerfile.gpu" "Dockerfile"
    
!sh build_and_push.sh $run_name<jupyter_output>Enter run version: 1
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Login Succeeded
Sending build context to Docker daemon   25.6kB
Step 1/13 : FROM ubuntu:16.04
16.04: Pulling from library/ubuntu

[1B9e426c26: Pulling fs layer 
[1Bb260b73b: Pulling fs layer 
[1B65fd1143: Pulling fs layer 
[1Ba07f8222: Pulling fs layer 
[1BDigest: sha256:3097ac92b852f878f802c22a38f97b097b4084dbef82893ba453ba0297d76a6a[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[5A[1K[K[4A[1K[K[3A[1K[K[2A[1K[K[1A[1K[K
Status: Downloaded newer image for ubuntu:16.04
 ---> 7aa3602ab41e
Step 2/13 : MAINTAINER <NAME> <<EMAIL>>
 ---> Running in 74a633b2ea23
Removing intermediate container 74a633b2ea23
 ---> c4265b4a021a
Step 3[...]<jupyter_text>OrchestrationAt this point, we can head to ECS console, grab the ARN for the repository where we published the docker image, and use SageMaker console to create hosted model, and endpoint.However, it is often more convenient to automate these steps. In this notebook we do exactly that using `boto3 SageMaker` API.Following are the steps:    * First we create a model hosting definition, by providing the S3 location to the model artifact, and ARN to the ECR image of the container.* Using the model hosting definition, our next step is to create configuration of a hosted endpoint that will be used to serve prediciton generation requests. * Creating the endpoint is the last step in the ML cycle, that prepares your model to serve client reqests from applications.* We wait until the provision is completed and the endpoint in service. At this point we can send request to this endpoint and obtain gender predictions.<jupyter_code>import sagemaker
sm_role = sagemaker.get_execution_role()
print("Using Role {}".format(sm_role))
acc = boto3.client('sts').get_caller_identity().get('Account')
reg = boto3.session.Session().region_name
sagemaker = boto3.client('sagemaker')

#Check if model already exists
model_name = "{}-model".format(run_name)
models = sagemaker.list_models(NameContains=model_name)['Models']
model_exists = False
if len(models) > 0:
    for model in models:
        if model['ModelName'] == model_name:
            model_exists = True
            break
#Delete model, if chosen
if model_exists == True:    
    choice = input("Model already exists, do you want to delete and create a fresh one (Y/N) ? ")
    if choice.upper()[0:1] == "Y":
        sagemaker.delete_model(ModelName = model_name)
        model_exists = False
    else:
        print("Model - {} already exists".format(model_name))

if model_exists == False:    
    model_response = sagemaker.create_model(
        ModelName=model_name,
        PrimaryContainer={
            'Image': '{}.dkr.ecr.{}.amazonaws.com/{}:latest'.format(acc, reg, run_name),
            'ModelDataUrl': 's3://{}/model/model.tar.gz'.format(s3bucketname)
        },
        ExecutionRoleArn=sm_role,
        Tags=[
            {
                'Key': 'Name',
                'Value': model_name
            }
        ]
    )
    print("{} Created at {}".format(model_response['ModelArn'], 
                                    model_response['ResponseMetadata']['HTTPHeaders']['date']))
#Check if endpoint configuration already exists
endpoint_config_name = "{}-endpoint-config".format(run_name)
endpoint_configs = sagemaker.list_endpoint_configs(NameContains=endpoint_config_name)['EndpointConfigs']
endpoint_config_exists = False
if len(endpoint_configs) > 0:
    for endpoint_config in endpoint_configs:
        if endpoint_config['EndpointConfigName'] == endpoint_config_name:
            endpoint_config_exists = True
            break
            
#Delete endpoint configuration, if chosen
if endpoint_config_exists == True:    
    choice = input("Endpoint Configuration already exists, do you want to delete and create a fresh one (Y/N) ? ")
    if choice.upper()[0:1] == "Y":
        sagemaker.delete_endpoint_config(EndpointConfigName = endpoint_config_name)
        endpoint_config_exists = False
    else:
        print("Endpoint Configuration - {} already exists".format(endpoint_config_name))
        
if endpoint_config_exists == False:           
    endpoint_config_response = sagemaker.create_endpoint_config(
        EndpointConfigName=endpoint_config_name,
        ProductionVariants=[
            {
                'VariantName': 'default',
                'ModelName': model_name,
                'InitialInstanceCount': 1,
                'InstanceType': instance_type,
                'InitialVariantWeight': 1
            },
        ],
        Tags=[
            {
                'Key': 'Name',
                'Value': endpoint_config_name
            }
        ]
    )
    print("{} Created at {}".format(endpoint_config_response['EndpointConfigArn'], 
                                    endpoint_config_response['ResponseMetadata']['HTTPHeaders']['date']))
from ipywidgets import widgets
from IPython.display import display

#Check if endpoint already exists
endpoint_name = "{}-endpoint".format(run_name)
endpoints = sagemaker.list_endpoints(NameContains=endpoint_name)['Endpoints']
endpoint_exists = False
if len(endpoints) > 0:
    for endpoint in endpoints:
        if endpoint['EndpointName'] == endpoint_name:
            endpoint_exists = True
            break
            
#Delete endpoint, if chosen
if endpoint_exists == True:    
    choice = input("Endpoint already exists, do you want to delete and create a fresh one (Y/N) ? ")
    if choice.upper()[0:1] == "Y":
        sagemaker.delete_endpoint(EndpointName = endpoint_name)
        print("Deleting Endpoint - {} ...".format(endpoint_name))
        waiter = sagemaker.get_waiter('endpoint_deleted')
        waiter.wait(EndpointName=endpoint_name,
                   WaiterConfig = {'Delay':1,'MaxAttempts':100})
        endpoint_exists = False
        print("Endpoint - {} deleted".format(endpoint_name))
        
    else:
        print("Endpoint - {} already exists".format(endpoint_name))
        
if endpoint_exists == False:  

    endpoint_response = sagemaker.create_endpoint(
        EndpointName=endpoint_name,
        EndpointConfigName=endpoint_config_name,
        Tags=[
            {
                'Key': 'string',
                'Value': endpoint_name
            }
        ]
    )
    status='Creating'
    sleep = 3

    print("{} Endpoint : {}".format(status,endpoint_name))
    bar = widgets.FloatProgress(min=0, description="Progress") # instantiate the bar
    display(bar) # display the bar

    while status != 'InService' and status != 'Failed' and status != 'OutOfService':    
        endpoint_response = sagemaker.describe_endpoint(
            EndpointName=endpoint_name
        )
        status = endpoint_response['EndpointStatus']
        time.sleep(sleep)
        bar.value = bar.value + 1 
        if bar.value >= bar.max-1:
            bar.max = int(bar.max*1.05)
        if status != 'InService' and status != 'Failed' and status != 'OutOfService':        
            print(".", end='')

    bar.max = bar.value     
    html = widgets.HTML(
        value="<H2>Endpoint <b><u>{}</b></u> - {}</H2>".format(endpoint_response['EndpointName'], status)
    )
    display(html)<jupyter_output>Endpoint already exists, do you want to delete and create a fresh one (Y/N) ? Y
Deleting Endpoint - gender-classifier-1-endpoint ...
Endpoint - gender-classifier-1-endpoint deleted
Creating Endpoint : gender-classifier-1-endpoint<jupyter_text>At the end we run a quick test to validate we are able to generate meaningful predicitions using the hosted endpoint, as we did locally using the model on the Notebbok instance.<jupyter_code>!aws sagemaker-runtime invoke-endpoint --endpoint-name "$run_name-endpoint" --body 'Tom,Allie,Jim,Sophie,John,Kayla,Mike,Amanda,Andrew' --content-type text/csv outfile
!cat outfile<jupyter_output>{
    "ContentType": "text/csv; charset=utf-8",
    "InvokedProductionVariant": "default"
}
{"Sophie": "F", "Mike": "M", "Tom": "M", "Andrew": "M", "John": "M", "Amanda": "F", "Kayla": "F", "Allie": "F", "Jim": "M"}<jupyter_text>Head back to Module-3 of the workshop now, to the section titled - `Integration`, and follow the steps described.You'll need to copy the endpoint name from the output of the cell below, to use in the Lambda function that will send request to this hosted endpoint.<jupyter_code>print(endpoint_response
      ['EndpointName'])<jupyter_output>gender-classifier-1-endpoint
{'fraction_non_alphanumeric': 0.09392223193583944, 'fraction_numerical': 0.015127902998973736, 'mean_word_length': 3.637757800105764, 'pattern_counts': {'":': 19, '<': 56, '<?xml version=': 0, '>': 66, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '5158', 'license': 'MIT-0', 'n_tokens_mistral': 7927, 'n_tokens_neox': 7407, 'n_words': 2722, 'path': '3_GenderClassification/notebooks/dockerizer-model-import-gender-classifier.ipynb', 'repo_name': 'Shuu-Ri/aws-nlp-workshop', 'size': '26322'}