Overview of this blog

This blog post contains a simple fast AI model that I created by following the lesson 1 of the latest Fast AI course. I have used a different dataset here and have almost replicated the steps used by Jeremy to train and predict an image classifier model. Note:

  1. This Blog post is written with an intention of learning how to use Jupyter Notebooks with fastpages to create blog posts.
  2. This is not a very verbose tutorial, as my aim was to just get a hands on into the FastAi's code, and create a simple blog post out of it.
  3. Future blogs that I'll write will focus more on the actual definitions of concepts/techniques and code by code walkthrough of the notebook that I'll create as I'll follow the course along.
  4. I have added "Commands" learned section at the bottom of the blog, to provide under useful (and can be basic) commands that I have learned as part of the notebook.

Step 1 : Installing fast's latest version

pip install -Uqq fastbook
Note: you may need to restart the kernel to use updated packages.

Step 2 : Defining an Image Search Function

Function Signature : search_images(term, max_images)

Input :

  • term - The search term to be provided into the search engine
  • max_images - Number of images for which URLs to be generated

Output : URLs of the images searched in the duckduckgo search engine

from fastcore.all import *
import time

def search_images(term, max_images=200):
    url = 'https://duckduckgo.com/'
    res = urlread(url,data={'q':term})
    searchObj = re.search(r'vqd=([\d-]+)\&', res)
    requestUrl = url + 'i.js'
    params = dict(l='us-en', o='json', q=term, vqd=searchObj.group(1), f=',,,', p='1', v7exp='a')
    urls,data = set(),{'next':1}
    while len(urls)<max_images and 'next' in data:
        data = urljson(requestUrl,data=params)
        urls.update(L(data['results']).itemgot('image'))
        requestUrl = url + data['next']
        time.sleep(0.2)
    return L(urls)[:max_images]

Step 3 : Downloading & Viewing a Searched image

  1. We'll be importing download_url from fastdownload to download the list of URLs in a destination path provided as function argument
  2. We'll then import fastai's vision, and open an Image with a thumbnail of 256*256
from fastdownload import download_url
dest = 'person_sad.jpg'
download_url(urls[0], dest, show_progress=False)

from fastai.vision.all import *
im = Image.open(dest)
im.to_thumb(256,256)
download_url(search_images('happy person photos', max_images=1)[0], 'person_happy.jpg', show_progress=False)
Image.open('person_happy.jpg').to_thumb(256,256)

Cleaning the directories

We will now remove any existing content that is present in the directories which we'll use for downloading and saving the images. Note: This will only work if you already have a directory with the below path. If not, then it will produce an error.

import shutil
downloaded_path = Path('happy_sad_angry_downloaded')
resized_path = Path("happy_sad_angry_resized")
shutil.rmtree(downloaded_path)
shutil.rmtree(resized_path)

Step 4 - Organising the data

  1. Download the different categories of images in happy_sad_angry_downloaded directory
  2. Resizing all the images downloaded and saving the resized images in happy_sad_angry_resized directory
  3. Some photos might not download correctly which could cause our model training to fail, hence remove them:
searches = 'happy person','sad person','angry person'
downloaded_path = Path('happy_sad_angry_downloaded')
resized_path = Path("happy_sad_angry_resized")

for o in searches:
    dest_downloaded = (downloaded_path/o)
    dest_downloaded.mkdir(exist_ok=True, parents=True)
    download_images(dest_downloaded, urls=search_images(f'{o} photo'))
    resize_images(downloaded_path/o, max_size=400, dest=resized_path/o)
/root/mambaforge/lib/python3.9/site-packages/PIL/Image.py:992: UserWarning: Palette images with Transparency expressed in bytes should be converted to RGBA images
  warnings.warn(
/root/mambaforge/lib/python3.9/site-packages/PIL/Image.py:992: UserWarning: Palette images with Transparency expressed in bytes should be converted to RGBA images
  warnings.warn(
failed = verify_images(get_image_files(resized_path))
failed.map(Path.unlink)
len(failed)
0

Step 5 - Creating the DataBlock

To train a model, we'll need DataLoaders, which is an object that contains a :

  1. Training set (the images used to create a model) and a ;
  2. Validation set (the images used to check the accuracy of a model -- not used during training). In fastai we can create that easily using a DataBlock, and view sample images from it:

DataBlock API

  1. The inputs are going to be images “ImageBlock” and the outputs are going to be categories “CategoryBlock”.
  2. get_image_files is used to get the items we require
  3. We define a splitter to split the dataset into Training & Validation Set. In this case, we are using a RandomSplitter with 20% data for validation
  4. get_y takes the label for the images. Here, parent_label is the name of the parent (or folder) for each image, i.e., happy person, sad person, angry person
  5. Before training, resize each image to 192x192 pixels by "squishing" it (as opposed to cropping it).
dls = DataBlock(
    blocks=(ImageBlock, CategoryBlock), 
    get_items=get_image_files, 
    splitter=RandomSplitter(valid_pct=0.2, seed=42),
    get_y=parent_label,
    item_tfms=[Resize(192, method='squish')]
).dataloaders(resized_path)

dls.show_batch(max_n=6)

Step 6 - Creating the Vision Learner

Here is where the actual magic happens. Yes, we're not aware about the fun calculations underneath at the moment, hence, let's call it as magic ;).

  1. We're now training the model using the dataloader that we created in the previous step.
  2. We define error_rate as our metrics, which is nothing but the mean squared error
  3. We provide resnet18 as the architecture(pre-trained model) to train our model. This is the basis of transfer learning, which will be covered in the later blogs.
learn = vision_learner(dls, resnet18, metrics=error_rate)
learn.fine_tune(3)
Downloading: "https://download.pytorch.org/models/resnet18-f37072fd.pth" to /root/.cache/torch/hub/checkpoints/resnet18-f37072fd.pth
epoch train_loss valid_loss error_rate time
0 1.550776 0.940344 0.353982 00:05
epoch train_loss valid_loss error_rate time
0 0.927735 0.834148 0.274336 00:05
1 0.689470 0.893372 0.300885 00:05
2 0.513495 0.876687 0.292035 00:05

Step 7 - Predictions

We'll now use the model to predict the images we downloaded initially to categorize one of the three categories

is_happy,x,probs = learn.predict(PILImage.create('person_happy.jpg'))
print(f"This is a: {is_happy}.")
print(f"Probability the person is happy: {probs[1]:.4f}")
This is a: happy person.
Probability the person is happy: 1.0000
print(learn.predict(PILImage.create('person_happy.jpg')))
('happy person', TensorBase(1), TensorBase([4.7284e-06, 9.9999e-01, 6.0729e-07]))
print(learn.predict(PILImage.create('person_sad.jpg')))
('sad person', TensorBase(2), TensorBase([3.2509e-03, 1.1917e-04, 9.9663e-01]))

End

As you can see, we have successfully trained a model and predicted results in well under 10 mins. This was a very brief introduction blog post to the image classification model in the fastAI, with almost no tweaks in the parameters, or pre-trained models, or data augmentations. The below section is completely optional, and just provide some additional commands that are tend to be useful in general, or which I have learned as completely new while doing this project.

Optional Cells

  • ##### This cell can be run if you want to delete the whole folder along with the contents. This will delete any/all directories and files that are present inside path object.
import shutil
path = Path('resized_fruits')
shutil.rmtree(path)
  • ##### Deleting only Files in a directory
flag_search = 'happy person'
path = Path('happy_sad_angry')

dest_flag_search = (path/flag_search) # Path to the "happy person" folder

files = os.listdir(dest_flag_search)
for fi in files:
    print(fi)
    os.unlink(dest_flag_search/fi)
  • ##### Deleting a Folder (Note: This will only work when the folders are empty)
searches = 'happy person','sad person','angry person'
path = Path('happy_sad_angry')

for o in searches:
    os.rmdir(path/o)

Challenges/Errors Faced

  1. Same File error - See post : https://forums.fast.ai/t/same-file-path-error-while-resizing-images-lesson-1/97601 Description : While running the resize_images() method, the resized images were created with the same file name and path as of downloaded images, and hence the error was producing. Although, I'm not sure, why this error didn't appear in the original notebook for lesson 1.

Useful Commands learned

OS Specific

  1. To create a path to a folder or file :-
    path = Path(happy_sad_angry/abc.jpg)
    
  2. To delete a directory :-
    os.rmdir(<path>)
    
  3. To delete files in a folder :-
    os.unlink(<filepath>)
    
  4. To list directories and files in a folder
    os.listdir(<path>)
    
  5. To install latest versions of a library
    !pip install -Uqq fastbook
    
  6. To get the os environment
    os.environ()
    
    You can also use os.environ().get(<key>)

FastAI Specific

  1. To download using URL
    from fastdownload import download_url
    download_url(<url>, <dest_file>, show_progress=False)
    
  2. FastAI's vision imports
    from fastai.vision.all import *
    
  3. To download and resize images to equal resolution
    download_images(<download_path>, urls= <list of url>)
    resize_images(<downloaded_path>, max_size=400, dest=<resized_path>)
    
  4. To open an image from the path
    PILImage.create('person_sad.jpg')
    
  5. To predict provided an item : Returns label(or category), index to look from the probability tensor, probabilities for all category (as a tensor)
    learn.predict(<item>)
    
    Output : ('sad person', TensorBase(2), TensorBase([3.2509e-03, 1.1917e-04, 9.9663e-01]))

Pythonic Image

  1. To open an image in lazy manner (i.e., it identifies the file, but the file remains open and the actual image data is not read from the file until you try to process the data) and set thumbnail to 256*256
    im = Image.open(<dest_file>)
    im.to_thumb(256,256)
    

Pythonic Strings

  1. Formatted Strings
    o = 'happy person
    f'{o} photo'
    
    Output : happy person photo

Further Learnings (Todo)

  1. Using Regex
  2. Define Datablock
  3. Define Learner