8

From course to production, The painful path 😆 of taking your model to production.

May be if you have done some Artificial intelligence courses here ❤️ You are able to design, train, evaluate and some make predictions with our own models, but until now there’s not a course how to deploy to production your own ML models (👋Hi! I like Platzi’s culture).

Well… here we’re going to learn how to do it, look at it here!

Start by the first

We’re going to suppose yo have your own trained model, if you don’t please take and complete this course Curso de deep learning con pytorch, at the end you will have your model.

Otherwise I’ll bring you this dataset and this code to generate this Model.

<h3>Set requirements</h3>

The project was developed with the follow:

  • Python 3. Course Here.
  • Django. Course Here.
  • torch. Course Here.
  • AWS S3. Course Here.
  • Heroku. Course Here.

! This is a lite version of same project for educational purposes only. If you want to see the full version here.

Look the net infrastructure

For this tutorial we’re going to use a model that was trained to classify motorcycles images in one of ten categories.

The Net used for this was the following:

#Create NetclassNet(nn.Module):def__init__(self, num_channels):super(Net,self).__init__()

        self.num_channels = num_channels
        self.conv1 = nn.Conv2d(3, self.num_channels, 3, stride=1, padding=1)
        self.bn1 = nn.BatchNorm2d(self.num_channels)
        self.conv2 = nn.Conv2d(self.num_channels, self.num_channels*2, 3, stride=1, padding=1)
        self.bn2 = nn.BatchNorm2d(self.num_channels*2)
        self.conv3 = nn.Conv2d(self.num_channels*2, self.num_channels*4, 3, stride=1, padding=1)
        self.bn3 = nn.BatchNorm2d(self.num_channels*4)

        #full conectedself.fc1 =  nn.Linear(self.num_channels*4*4*4*4, self.num_channels*4)
        self.fcbn1 =  nn.BatchNorm1d(self.num_channels*4)
        self.fc2 =  nn.Linear(self.num_channels*4, 10)

    defforward(self, x):#starts 3x64x64
            x = self.bn1(self.conv1(x)) #num_channelsx64x64
            x = F.relu(F.max_pool2d(x, 2)) #num_channelsx32x32
            x = self.bn2(self.conv2(x)) #num_channels*2x32x32
            x = F.relu(F.max_pool2d(x, 2)) #num_channels*2x16x16
            x = self.bn3(self.conv3(x)) #num_channels*4x16x16
            x = F.relu(F.max_pool2d(x, 2)) #num_channels*4x8x8#flatten
            x = x.view(-1, self.num_channels*4*4*4*4)
            # fc
            x = self.fc1(x)
            x = self.fcbn1(x)
            x = F.relu(x)
            x = F.dropout(x, p=0.8, training=True)
            x = self.fc2(x)

            #log_softmax
            x = F.log_softmax(x, dim=1)
            return x

The Net was designed to work with images that had the next image processing:

preprocess = transforms.Compose ([
                                    transforms.Resize(64),
                                    transforms.CenterCrop(64),
                                    transforms.ToTensor(),
                                    transforms.Normalize(
                                        mean = [0.485, 0.456, 0.406],
                                        std = [0.229, 0.224, 0.225]
                                    )])

#Open image & convert to rgb
img = Image.open(BytesIO(SOME_IMAGE_URL)).convert('RGB')
#Pass through preprocess
img_t = preprocess(img)

This is important because the model only works with the same infrastructure it was trained.

Lets go to code

<h3>Start with the basics</h3>
  • Go to GitHub and start a new project. Course Here.

Then in terminal

#Create root project folder
mkdir my_new_project
cd  my_new_project
virtualenv venv
source venv/bin/activate

#django
pip3 install django
django-admin startproject mysite
cd mysite
python3 manage.py startapp home

#
touch requirements.txt

#Add frontend templates
mkdir templates
touch templates/index.html
mkdir static

#Add folder to code all extra functions we will need
mkdir utils
mkdir utils/__init__.py
touch utils/utilities.pymkdir utils/ml
mkdir utils/ml/__init__.py
touch utils/ml/net.py

#git
git init
touch .gitignore
git remote add origin 'YOUR GITHUB PROJECT URL'
git pull origin master
git add .
git commit -m"First commit"
git push origin master

Until this point our project looks like:

my_new_project/
     venv
     mysite/
         requirements.txt
         .gitignore
manage.py
         mysite/
init.py
asgi.py
setting.py
urls.py
                 wsgi
         home/
init.py
admin.py
apps.py
                 migrations/
init.py
models.py
tests.py
views.py
         static/
         templates/
                 Index.html
         utils/
init.py
utilities.py
                 ml/
init.py
net.py

<h3>Spoiler XD at requirements.txt</h3>

Those are all libraries we’re going to use along the project

django
whitenoise
gunicorn
pillow
#This libraryis a lite version of torch. Works only with cpu.
https://download.pytorch.org/whl/cpu/torch-1.7.0%2Bcpu-cp36-cp36m-linux_x86_64.whl
torchvision
boto3
requests
dj-database-url
psycopg2
psycopg2-binary

Run in terminal

pip3 install -r requirements.txt
<h3>/mysite/setting.py</h3>
#New libraries
import os
import dj_database_url
.
.
.
Debug = False
.
.
.
ALLOWED_HOSTS = ['*']
.
.
.
INSTALLED_APPS = [
	
#Add'home', 
]

.
.
.
MIDDLEWARE = [
         ...
     'whitenoise.middleware.WhiteNoiseMiddleware',
] 
.
.
.
#Modifyif DEBUG:
	DATABASES = {
		 'default': {
		 'ENGINE': 'django.db.backends.sqlite3',
		 'NAME': BASE_DIR / 'db.sqlite3',
	     }
	
else: 
	DATABASES = {
	    'default': dj_database_url.config(
		default=os.environ.get('DATABASE_URL')
	    )
	}
.
.
.
#Add
STATIC_URL = '/static/'
STATICFILES_DIRS=(os.path.join(BASE_DIR,'static'),)
STATIC_ROOT = os.path.normpath(os.path.join(BASE_DIR, 'staticfiles'))

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
<h3>/mysite/urls.py</h3>
#Add#Librariesfrom django.urls import include
from django.conf import settings
from django.conf.urls.static import static
from django.shortcuts import render, redirect

#Redirect from root to home appdefhome_redirect(request):return redirect('home/')

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', home_redirect), #redirect
    path('home/', include(('apps.home.urls','home'), namespace='home')),
]

#manage Media if Debug=Trueif settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
<h3>Changes in ‘home’ app</h3> <h3>/mysite/home/model.py</h3>

Add Database model

#AddclassImage(models.Model):
    image_id = models.AutoField(primary_key=True)
    #Save the Image URL stored in S3
    bucket_url = models.TextField()
    #Save the prediction made by our ML model
    category = models.CharField(null=True, blank=True, max_length=50)

    def__str__(self):return'{}'.format(self.image_id)
<h3>/mysite/home/admin.py</h3>

Register Database model in app

from apps.home.models import Image

admin.site.register(Image)
<h3>model.pth</h3>

Paste your ml model inside mysite/utils/ml/
In order to have the model like mysite/utils/ml/model.pth

<h3>net.py</h3>

mysite/utils/ml/net.py

Copy/Paste the Net used for train the model.

<h3>utilities.py</h3>

mysite/utils/utilities.py

#Pytorchimport torch
import torchvision
from torchvision import transforms
#ML netfrom utils.ml.net import Net
#image Managmentfrom PIL import Image
import requests
from io import BytesIO
#awsimport boto3

defload_model():#pretrained model path
    PATH = 'ml_models/model.pth'# Initializate model
    model = Net(64)
    #Call pretrained model from path
    model.load_state_dict(torch.load(PATH))
    model.eval()
    return model


deftransform_image(path):'''
    Make specific transformations to an image
    in order to pass through the net.
    '''#Transformations
    preprocess = transforms.Compose ([
                                    transforms.Resize(64),
                                    transforms.CenterCrop(64),
                                    transforms.ToTensor(),
                                    transforms.Normalize(
                                        mean = [0.485, 0.456, 0.406],
                                        std = [0.229, 0.224, 0.225]
                                    )])
    #get response from url
    response = requests.get(path)
    #Open image & convert to rgb
    img = Image.open(BytesIO(response.content)).convert('RGB')
    #Pass through preprocess
    img_t = preprocess(img)
    #Flat tensor
    processed_img = torch.unsqueeze(img_t, 0)

    return processed_img

defpredict(img, model):#Stablishing categories(labels)#! Make sure you stablish your own categories if it's needed
    CATHEGORIES = ['off_road',
                    'naked',
                    'scooter',
                    'scrambler',
                    'cafe_racer',
                    'cruiser',
                    'sport',
                    'touring',
                    'bike',
                    'quad']
    #Call predicton from model
    outputs = model(img)
    _, predicted = torch.max(outputs, 1)

    return CATHEGORIES[predicted]


defupload_to_aws(img):#Set AWS keys
    AWS_ACCESS_KEY = os.environ.get('AWS_ACCESS_KEY')
    AWS_SECRET_KEY = os.environ.get('AWS_SECRET_KEY')
    #Call client
    s3 = boto3.client('s3', aws_access_key_id=AWS_ACCESS_KEY,
                      aws_secret_access_key=AWS_SECRET_KEY)
    #Set bucket
    S3_BUCKET = os.environ.get('S3_BUCKET')
    #Upload image to bucket with type jpeg & public properties
    s3.upload_fileobj(img, S3_BUCKET,'images/'+img.name, ExtraArgs={ "ContentType": "image/jpeg",
                                                                     'ACL':'public-read'})
<h3>/mysite/home/views.py</h3>
#Standard librariesimport os
#djangofrom django.shortcuts import render, redirect
from django.http import HttpResponse
from django.conf import settings
#appsfrom home.models import Image
#ml functionsfrom utils.utilities import load_model, transform_image, predict
#awsfrom utils.utilities import upload_to_aws

# /homedefhome(request):#Call all element from DB
    images = Image.objects.all()

    if request.method == 'POST':

        # Get image from request
        img = request.FILES['image']

        # storage image to aws
        upload_to_aws(img)

        #Create new object in db Image
        new_image = Image.objects.create()

        # Get aws bucket url
        BUCKET_URL = os.environ.get('BUCKET_URL')

        '''
        Call image-processing to transform image with required characteristics
        for model
        '''
        processed_img =  transform_image(BUCKET_URL + img.name)
        #Call ML model
        model = load_model()
        #Call prediction from the ML net
        prediction = predict(processed_img, model)
        #set bucket url
        bucket_url = BUCKET_URL + img.name
        new_image.bucket_url = bucket_url
        #Set prediction into db from net prediction
        new_image.category = prediction
        #Save the new object
        new_image.save()

        context = {'images' : images}
        return redirect('/')

    else:
        context = {'images' : images}
        return render(request, 'index.html', context)
<h3>templates.html</h3>

/mysite/templates/index.html

the scope for this tutorial isn’t frontend stuffs but it’s needed to a complete deploy that’s why here there is the complete html. For go deeper please visit fontend architecture career from Platzi.

AWS

Until now we have almost our code complete, so let’s start to configure our AWS bucket

If you want to go deeper into AWS take this Platzi’s carrer

  • Start creating or logging you AWS account (!Is needed a credit card accout)
  • Go to S3 Service
  • Create Bucket
    Screenshot from 2020-11-08 13-42-46.png
  • Type bucket name
    Screenshot from 2020-11-08 13-55-16.png
  • Unlock all public access
    Screenshot from 2020-11-08 13-55-42.png
  • click on Create Bucket

Now you have created your bucket!
Screenshot from 2020-11-08 13-56-33.png

-Click in your bucket and select Permissions
Screenshot from 2020-11-09 09-30-39.png

-Go to Cross-origin resource sharing (CORS)
Screenshot from 2020-11-09 09-31-04.png
and add:

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": []
    }
]

At least we need to generate the security credentials to make the connection between the app and our bucket.

  • Go to your Identity access management AIM
  • Go to Security credentials
  • Go to Access keys (access key ID and secret access key)
  • Click in Create New Access Key and download (!Make sure keep safe this keys, and never share or type them in production code)

That’s all we need here in AWS!

Heroku

To go deeper in Heroku please take this course.

  • If you don’t, go to Heroku and create an account.
<h3>Prerequisites</h3>
  • Install Heroku CLI
sudo snap install--classic heroku

-Verify your installation

heroku --version
<h3>Create Heroku app</h3>
  • Establishing your working directory in terminal at …/mysite/
  • type in terminal
heroku login
heroku create YOUR_NAME_APP

In your browser at Heroku’s dashboard will appear your created app [YOUR_NAME_APP]

  • Go to setting -> Config vars click in reveal config vars and type your secret keys:
    ! This is the only place you should have your production keys
AWS_ACCESS_KEY : 'YOUR SECRET KEY'
AWS_SECRET_KEY :  'YOUR SECRET KEY'
S3_BUCKET : 'YOUR BUCKET NAME'
BUCKET_URL : 'THE URL THAT AWS GENERATES FOR YOUR BUCKET'
SECRETKEY : 'THE SECRET KEY GENERATED BY DJANGO IN setting.py'

When you deploy your app, Heroku automatically generates another secret key:

DATABASE_URL :'AUTOMATICALLY GENERATED BY HEROKU'

Final steps

at …/mysite/ in terminal:

touch Procfile

add at /mysite/Procfile:

web: gunicorn mysite.wsgi --log-file -

In terminal:

#django
python3 manage.py collectstatic --noinput
python3 manage.py makemigrations
python3 manage.py migrate

#git
git add .
git commit -m "Project ready for deploy"
git push origin master
#Link github to heroku
heroku git:remote -a YOUR_NAME_APP
#Create postgres DB
heroku addons:create heroku-postgresql:hobby-dev
#deploy
git push heroku master

When Heroku finishes successfully the deploy just type, to make the migration to the new postgres DB:

heroku run python manage.py migrate
<h3>Congrats 🎉🎉</h3>

Finally, to see your app running:

heroku open

Extras

motoclassifier
Complete project
ML Model design & train Here
Current model running Here
moto-images dataset

Related posts Everything is ok but… How do I create my own dataset?

<h3>Author</h3> <h3>Ricardo Moreno | 2020</h3>
Escribe tu comentario
+ 2