#| default_exp app

Putting Models in Production with Hugging Face and Fastai#

%load_ext autoreload
%autoreload 2
The autoreload extension is already loaded. To reload it, use:
  %reload_ext autoreload
from fastai.vision.widgets import *
from fastai.text.all import *
from fastai.tabular.all import *
from fastai.collab import *
from aiking.data.external import *

import contextlib
import pathlib
import os
from huggingface_hub import notebook_login, create_repo, Repository, HfApi
#| export
from fastai.vision.all import *
from huggingface_hub import push_to_hub_fastai, from_pretrained_fastai
import gradio as gr

Construct a Dataset#

# !rm -rf /home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2
# !ls /home/rahul.saraf/rahuketu/programming/AIKING_HOME/data
clstypes = ["Impressionism", "Cubism", "Fauvism", "Graffiti", "Fantasy", "Contemporary"]
dest = "Artsie2"
path = construct_image_dataset(clstypes, dest, key=os.getenv("BING_KEY"), loc=None, count=300, engine='bing'); path
Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2')
list_ds()
(#4) ['Artsie2','Artsie','DoppelGanger','BirdsVsForests']
path = get_ds('Artsie2'); path
Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2')
path.ls()
(#6) [Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Contemporary'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Graffiti'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Fantasy'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Impressionism'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Cubism'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Fauvism')]
(path/"Impressionism").ls()
(#137) [Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Impressionism/4898e171-f17f-466c-bbe3-9f889df10a27.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Impressionism/6f16828a-e223-4f2f-8f82-7fe95c8e6c80.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Impressionism/c0d44517-4312-4510-bbd9-c44a09403026.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Impressionism/75e7c39e-7fce-4f9a-9747-95b69a6d099d.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Impressionism/d32d5e30-3076-426d-9a6e-a7d40d41606f.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Impressionism/3284b8b1-6650-436f-bc44-205bfa1eb28b.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Impressionism/59c27982-0259-427b-8e6c-5a742e9d1c4c.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Impressionism/f39a20d8-e22f-4d71-8a72-3e9f3186fccf.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Impressionism/fa3a514d-f0d7-4197-9d5b-14bfaeb4d52d.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Impressionism/33d194d1-3a4e-4f68-9f8c-c385f32aefb5.jpg')...]
doc(DataBlock)

DataBlock

DataBlock(blocks:list=None, dl_type:TfmdDL=None, getters:list=None, n_inp:int=None, item_tfms:list=None, batch_tfms:list=None, get_items=None, splitter=None, get_y=None, get_x=None)

Generic container to quickly build `Datasets` and `DataLoaders`.

doc(get_image_files)

get_image_files

get_image_files(path, recurse=True, folders=None)

Get image files in `path` recursively, only in `folders`, if specified.

get_image_files(path)
(#851) [Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Contemporary/82a61733-57e0-40fc-9ec8-acfb751784b2.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Contemporary/d794bed2-6b9f-4097-8a41-8eab47e9f1c9.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Contemporary/f4750f6d-71ba-470f-bf1b-37474e536d44.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Contemporary/927f965b-907f-416e-98fc-b8f15d49ce68.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Contemporary/681af49b-beba-4978-9d8a-4d1cb363a24d.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Contemporary/d61cbea0-439c-4aee-9f95-d58fe1008ed7.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Contemporary/1534f4ce-b7eb-420b-87fb-da5b1414199c.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Contemporary/4943a087-c240-408d-840d-b7ccbe2fe787.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Contemporary/c50d6b17-ad83-49ea-a69d-df90e83a091d.jpg'),Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/data/Artsie2/Contemporary/ca0c74e2-1502-471f-b3e8-774badb6047d.jpg')...]
doc(RandomSplitter)

RandomSplitter

RandomSplitter(valid_pct=0.2, seed=None)

Create function that splits `items` between train/val with `valid_pct` randomly.

doc(RandomResizedCrop)

RandomResizedCrop

RandomResizedCrop(size:int|tuple, min_scale:float=0.08, ratio=(0.75, 1.3333333333333333), resamples=(, ), val_xtra:float=0.14, max_scale:float=1.0, **kwargs)

Picks a random scaled crop of an image and resize it to `size`

Model Training#

dls = DataBlock(
    blocks=(ImageBlock, CategoryBlock),
    get_items=get_image_files,
    splitter=RandomSplitter(valid_pct=0.2, seed=42),
    get_y=parent_label,
    item_tfms=[RandomResizedCrop(size=192)]
).dataloaders(path); dls
<fastai.data.core.DataLoaders at 0x15545500a0a0>
dls.show_batch(max_n=9)
../../_images/02a_prod_artsie_20_0.png
learner = vision_learner(dls, resnet18, metrics=[error_rate, accuracy])
learner.fine_tune(6)
epoch train_loss valid_loss error_rate accuracy time
0 2.159975 0.826005 0.288235 0.711765 00:19
epoch train_loss valid_loss error_rate accuracy time
0 0.748183 0.503624 0.200000 0.800000 00:19
1 0.604021 0.454181 0.141176 0.858824 00:20
2 0.493107 0.420894 0.135294 0.864706 00:20
3 0.425259 0.395624 0.123529 0.876471 00:20
4 0.381026 0.417063 0.117647 0.882353 00:20
5 0.332991 0.411139 0.111765 0.888235 00:20

Classification Interpretation#

doc(ClassificationInterpretation)

ClassificationInterpretation

ClassificationInterpretation(learn:Learner, dl:DataLoader, losses:TensorBase, act=None)

Interpretation methods for classification models.

interp = ClassificationInterpretation.from_learner(learner); interp
<fastai.interpret.ClassificationInterpretation at 0x155437d91b50>
interp.plot_confusion_matrix()
../../_images/02a_prod_artsie_25_2.png
interp.plot_top_losses(20, nrows=5, figsize=(15,15))
../../_images/02a_prod_artsie_26_2.png

Export Model#

learner.export(aiking_path('learner')/"artsie.pkl")
aiking_path('learner').ls()
(#1) [Path('/home/rahul.saraf/rahuketu/programming/AIKING_HOME/learners/artsie.pkl')]

Hugging Face Api#

notebook_login()

Create Huggingface Spaces#

repo_name = 'artsie'
repo_local_path = pathlib.Path(os.environ.get('HUGGINGFACE_HUB_DIR'))/repo_name
repo_url = create_repo(repo_id=f"rahuketu86/{repo_name}", repo_type="space", space_sdk='gradio')
repo = Repository(local_dir=repo_local_path, clone_from=repo_url)
Cloning https://huggingface.co/spaces/rahuketu86/artsie into local empty directory.
@contextlib.contextmanager
def preserve_cwd(new_dir):
    curdir= os.getcwd()
    os.chdir(new_dir)
    try: yield
    finally: os.chdir(curdir)

Create Model Repository#

#| export
repo_model_name = 'artsie-model'
with preserve_cwd(os.environ.get('HUGGINGFACE_HUB_DIR')):
    push_to_hub_fastai(learner=learner, repo_id=f"rahuketu86/{repo_model_name}")
/home/rahul.saraf/rahuketu/programming/huggingface_hub/rahuketu86/artsie-model is already a clone of https://huggingface.co/rahuketu86/artsie-model. Make sure you pull the latest changes with `repo.git_pull()`.
remote: Scanning LFS files for validity, may be slow...        
remote: LFS file scan complete.        
To https://huggingface.co/rahuketu86/artsie-model
   958e2de..c059d64  main -> main

Test Remote learner#

#| export
remote_learner = from_pretrained_fastai(f"rahuketu86/{repo_model_name}"); remote_learner
<fastai.learner.Learner at 0x1553f02e8070>

Pull a url from net with impressionist painting

uploader = widgets.FileUpload(); uploader
img = PILImage.create(uploader.data[0]); img
../../_images/02a_prod_artsie_43_0.png
cls_name, i, probs = remote_learner.predict(img)
print(f"Probability of class {cls_name} is {probs[i]:0.3f}")
Probability of class Impressionism is 0.707

Gradio App#

#| export
labels = remote_learner.dls.vocab

def classify_img(img):
    img = PILImage.create(img)
    pred, pred_idx, probs = remote_learner.predict(img)
    return {labels[i]:float(probs[i]) for i in range(len(labels))}
classify_img(uploader.data[0])
{'Contemporary': 0.012162644416093826,
 'Cubism': 0.003161832457408309,
 'Fantasy': 0.14627183973789215,
 'Fauvism': 0.0085138576105237,
 'Graffiti': 0.12294895201921463,
 'Impressionism': 0.7069408893585205}
gr.Label(classify_img(uploader.data[0]))
label
#| export
demo = gr.Interface(fn=classify_img, 
                    inputs=gr.Image(), 
                    outputs=gr.Label(num_top_classes=len(labels)))
demo.launch(server_name="0.0.0.0", share=True)
Running on local URL:  http://0.0.0.0:7879
Running on public URL: https://26506.gradio.app

This share link expires in 72 hours. For free permanent hosting, check out Spaces: https://huggingface.co/spaces
(<gradio.routes.App at 0x1553eb242610>,
 'http://localhost:7879/',
 'https://26506.gradio.app')
#| export
demo.launch(inline=False)
Rerunning server... use `close()` to stop if you need to change `launch()` parameters.
----
Running on local URL:  http://0.0.0.0:7879

To create a public link, set `share=True` in `launch()`.
(<gradio.routes.App at 0x1553eb242610>, 'http://localhost:7879/', None)

Export#

from nbdev.export import *
nb_export("02a_prod_artsie.ipynb", lib_path=".")
%%writefile requirements.txt
fastai
huggingface_hub[fastai] 
gradio
Overwriting requirements.txt
!cat app.py
# AUTOGENERATED! DO NOT EDIT! File to edit: 02a_prod_artsie.ipynb.

# %% auto 0
__all__ = ['repo_model_name', 'remote_learner', 'labels', 'demo', 'classify_img']

# %% 02a_prod_artsie.ipynb 4
from fastai.vision.all import *
from huggingface_hub import push_to_hub_fastai, from_pretrained_fastai
import gradio as gr

# %% 02a_prod_artsie.ipynb 37
repo_model_name = 'artsie-model'

# %% 02a_prod_artsie.ipynb 40
remote_learner = from_pretrained_fastai(f"rahuketu86/{repo_model_name}"); remote_learner

# %% 02a_prod_artsie.ipynb 47
labels = remote_learner.dls.vocab

def classify_img(img):
    img = PILImage.create(img)
    pred, pred_idx, probs = remote_learner.predict(img)
    return {labels[i]:float(probs[i]) for i in range(len(labels))}

# %% 02a_prod_artsie.ipynb 50
demo = gr.Interface(fn=classify_img, 
                    inputs=gr.Image(), 
                    outputs=gr.Label(num_top_classes=len(labels)))

# %% 02a_prod_artsie.ipynb 52
demo.launch(inline=False)
!cat requirements.txt
fastai
huggingface_hub[fastai] 
gradio
api = HfApi()
api.upload_file(
    path_or_fileobj="app.py",
    path_in_repo="app.py",
    repo_id=f"rahuketu86/{repo_name}",
    repo_type="space",
)
'https://huggingface.co/spaces/rahuketu86/artsie/blob/main/app.py'
api.upload_file(
    path_or_fileobj="requirements.txt",
    path_in_repo="requirements.txt",
    repo_id=f"rahuketu86/{repo_name}",
    repo_type="space",
)
'https://huggingface.co/spaces/rahuketu86/artsie/blob/main/requirements.txt'