DEV Community

Cover image for End-to-end CNN using TensorFlow
VIVEK PATEL
VIVEK PATEL

Posted on • Edited on

End-to-end CNN using TensorFlow

Table of content.

  1. Data gathering.
  2. Build a CNN model.
    • Getting our data ready.
    • Building a model.
    • Training a model.
    • Saving and reloading a trained model.
    • Testing Fun.
  3. Build Gui using Tkinter.
  4. Build a Flask app.
  5. Deploy your Flask app into the Heroku.

You can find the complete code repo and the Jupyter notebook below

Take a look at what we are going to build
Dog_VS_Cat
Web_demo

1. Data gathering

Data is gathered from kaggle
If you want to use Google-colab make sure you download and upload the dataset into Google drive.

2. Build the CNN model.

For building a convolution neural network we use a prebuild model from TensorFlow-Hub.

After download, we can see that data is in the below structure

|-test
    |-image

|-train
    |-Dog 
       |-image1
       |-image2
    |-Cat
       |-image1
       |-image2

Enter fullscreen mode Exit fullscreen mode

Before we jump into Jupyter notebook we have to make our data ready.

If you want to use this structure you can use TensorFlow image_dataset_from_directory

Getting our data ready

Here we make a function to rename all images and then move all images into a single folder after that we create a CSV file with filename and labels.

A rename function

import os
# 
def rename_image(path,name):
    '''
    Take the path of the folder and the name of what you want to rename.
    '''
    for count, filename in enumerate(os.listdir(path)):
        dst = name + '.' + str(count) + ".jpeg"
        src = path + filename
        dst = path + dst
        os.rename(src,dst)
    print("image change is :",count)

Enter fullscreen mode Exit fullscreen mode

Above function take path and name that you want to change.
os.listdir make a list of all files from that folder.
dst is used for the destination file and src for the source file.
here we add name + count for a unique name and then file extension.
now our loop goes through all images and change the name that you give in the function argument.

Before calling this function let's make a list of the label from that folder

label = []
for filename in os.listdir('path-to/train/'):
    label.append(filename)
Enter fullscreen mode Exit fullscreen mode

This function is to take the filename from that path we provide and append it to the label list.

Now we make another function to rename all images from both folders

def file_name():
    path='path-to/train/'
    for i, name in enumerate(label):
        real_path = path + label[i] + '/'
        rename_image(real_path,name)

#call fun
file_name()
Enter fullscreen mode Exit fullscreen mode

The above function is to call our previous rename function and applies to the subfolder as we gave labels also.

Now you can see that all images are renamed. let's move all images into a new folder with the help of the python replace function.

def move_files(old,new):
    for filename in os.listdir(old):
        os.replace(str(old) + str(filename), str(new) + str(filename))

def file_change(old_path,new_path):
    for i, name in enumerate(label):
        real_path = old_path + label[i] + '/'
        move_files(real_path,new_path)
    print("Suceess")

file_change(old_path='path-to/train/',new_path='DATA/FULL_DATA/')
Enter fullscreen mode Exit fullscreen mode

move_files takes an old and new path then moves a file from old to new.
for change files from more than one folder, we provide all folder names (our folder names stored in label list) to file_change after that function call that move_file function and move images from subfolders also.

Now all our images are in one folder let's make CSV file from that. Before that, we have to convert data into DataFrame then save it to a CSV file.

create CSV file

import pandas as pd
filenames=os.listdir("DATA/FULL_DATA/")
categories=[]
for f_name in filenames:
    category = f_name.split('.')[0]
    for i, filename in enumerate(label):
        if category == filename:
            categories.append(filename)

#create pandas DataFrame
df=pd.DataFrame({
    'filename':filenames,
    'labels':categories
})

#save to csv
df.to_csv('data.csv',index=False)
Enter fullscreen mode Exit fullscreen mode

In the above code, we have 2 lists categories and label now we make Pandas Data Frame from that. Then, we save that Data Frame into CSV with Pandasto_csvfunction.

Now our data is ready with the CSV file and filename accordingly let's build the CNN model.

Build a model

Now you can use this Jupyter notebook for complete code.

Steps

  • Import tools.
import tensorflow as tf
import tensorflow_hub as hub
Enter fullscreen mode Exit fullscreen mode

if you don't have this library use pip install to install it.

  • Read CSV file.
import pandas as pd
labels_csv = pd.read_csv("/path-to-your-csv-file/Dog_vs_cat.csv")
Enter fullscreen mode Exit fullscreen mode

The above code generates pandas DataFrame

  • Make a full filename
filenames = ["path-to-our-new-full-image-folder/" + fname for fname in labels_csv["filename"]]

# Check the first 15
filenames[:15]
Enter fullscreen mode Exit fullscreen mode
  • Let's prepare our labels.
import numpy as np
labels = labels_csv["category"].to_numpy() 
labels
Enter fullscreen mode Exit fullscreen mode

The above code converts our category into a numpy array.

  • Turn label into an array of booleans.
unique_category = np.unique(labels)
boolean_labels = [label == unique_category for label in labels]
Enter fullscreen mode Exit fullscreen mode

Now, this code converts our label into a category like here we have 2 labels so for the first item it generates an array of [True, False] if our label is 0.

  • Preprocessing Images.
IMG_SIZE = 224

# Function
def process_image(image_path, image_size=IMG_SIZE):
  """
  Takes an image file path and turns the image into a Tensor.
  """
  # Read in an image file
  image = tf.io.read_file(image_path)
  # Turn the jpg image into numerical Tensor with 3 colour channel(RGB)
  image = tf.image.decode_jpeg(image,channels=3)
  # Convert the color channel values to (0-1) values
  image = tf.image.convert_image_dtype(image,tf.float32)
  # Resize the image to (224,224)
  image = tf.image.resize(image, size=[image_size,image_size])

  return image
Enter fullscreen mode Exit fullscreen mode

The above function takes an image path.
After that with the help of tf.io.read_file fun read an image then convert it into tensors here tensor values are from 0 to 255. Now convert that into (0,1) and lastly resize it to 224, 224 sizes.

  • Turning our data into Batches.
def get_image_lable(image_path,label):
  """
  Takes an image file path name and the label,
  processes the image and return a tuple (image, label).
  """
  image = process_image(image_path)

  return image, label
Enter fullscreen mode Exit fullscreen mode

here fun takes an image path and return with its label.

  • Let's make a function to turn all of the data into batches!
BATCH_SIZE = 32

# Function to convert data into batches
def create_data_batches(X,y=None, batch_size=BATCH_SIZE,valid_data=False):
  """
  Creates batches of data of image (X) and label (y) pairs.
  Shuffle the data if it's training data but doesn't shuffle if it's validation data.
  """
  # If data is valid dataset (NO SHUFFLE)
  if valid_data:
    print("Creating valid data batches.........")
    data = tf.data.Dataset.from_tensor_slices((tf.constant(X),
                                               tf.constant(y)))
    data_batch = data.map(get_image_lable).batch(batch_size)
    return data_batch

  else:
    print("Creating train data batches.........")
    # Turn filepaths and labels into Tensors
    data = tf.data.Dataset.from_tensor_slices((tf.constant(X),
                                               tf.constant(y)))
    # Shuffling pathname and labels before mapping image processor fun
    data = data.shuffle(buffer_size=len(X))
    data_batch = data.map(get_image_lable).batch(batch_size)

    return data_batch
Enter fullscreen mode Exit fullscreen mode

This will create batches from our data.
It is good practice to convert your data into batches.

  • create batches
train_data = create_data_batches(X_train, y_train)
val_data = create_data_batches(X_val, y_val, valid_data=True)
Enter fullscreen mode Exit fullscreen mode

create our batches with shuffle if it's train data and not shuffle if it's valid data.

  • Creating our own validation set.
X = filenames
y = boolean_labels

from sklearn.model_selection import train_test_split

# Into train and valid 
X_train, X_val, y_train, y_val = train_test_split(X,y,                               test_size=0.2,random_state=42)

Enter fullscreen mode Exit fullscreen mode

It is good practice to always divided our data into train and valid. Generally, the split size is around 70-30 or 80-20 ratio.
that 70% is used to train our model and 30 is for validation.

  • Turning our data into Batches and Visualizing Data Batches
import matplotlib.pyplot as plt
# Create fun for viewing in a data batch
def show_images(images, labels):
  """
  Displays a plot of 25 images and their labels from a data batch.
  """
  plt.figure(figsize=(20, 20))
  for i in range(25):
    # Subplot
    ax = plt.subplot(5,5,i+1)
    plt.imshow(images[i])
    plt.title(unique_category[labels[i].argmax()])
    plt.axis("Off")


train_images, train_labels = next(train_data.as_numpy_iterator())
show_images(train_images,train_labels)
Enter fullscreen mode Exit fullscreen mode

Now we can see our train images with its label.

let's recap what we have done until now.

Read our data then create labels and filename after that convert data into batches with the right size and label.

Now let's build a model

  • Building a model
INPUT_SHAPE = [None, IMG_SIZE, IMG_SIZE, 3] # Batch, height, width, Colour_chanels

# Setup output shape of the model
OUTPUT_SHAPE = len(unique_category)

# Setup model URL
MODEL_URL = "https://tfhub.dev/google/imagenet/mobilenet_v2_130_224/classification/4"
Enter fullscreen mode Exit fullscreen mode

here we define our model URL with input shape and output shape

def create_model(input_shape=INPUT_SHAPE,output_shape=OUTPUT_SHAPE, model_url=MODEL_URL):
  print("Building model with:", model_url)

  # Setup the model
  model = tf.keras.Sequential(
                   [hub.KerasLayer(model_url),
                    tf.keras.layers.Dense(units=output_shape,
                    activation="softmax")]
)

  # Compile the model
  model.compile(
      loss = tf.keras.losses.BinaryCrossentropy(),
      optimizer = tf.keras.optimizers.Adam(),
      metrics = ["accuracy"]
  )

  # Build the model
  model.build(input_shape)

  return model
Enter fullscreen mode Exit fullscreen mode

This will compile our model with loss = BinaryCrossentropy() as our problem is a binary classification. If we have more than 2 labels then we use CategoricalCrossentropy().

model.summary()

early_stopping = tf.keras.callbacks.EarlyStopping(monitor="val_accuracy",
                                                  patience = 3)
Enter fullscreen mode Exit fullscreen mode

If you set patience=3 means that your training will stop after 3 epochs if it's not improving. with this callback, your model is not overfitting.
If your model is overfitting it only remembers training data and it fails on valid data. So try to reduce the model from overfitting.

  • Train a model
def train_model():
  # Fit the model
  model.fit(x=train_data,
            epochs= 100,
            validation_data=val_data,
            validation_freq = 1,
            callbacks = [early_stopping])
  return model

model = train_model()
Enter fullscreen mode Exit fullscreen mode

model.fit
here we see that after some epoch our training is stopped because of that callback we gave.

  • Making and evaluating prediction using a trained model.
predictions = model.predict(val_data, verbose=1)
predictions[0]
Enter fullscreen mode Exit fullscreen mode

here we see that predictions give a probability of label into an array of two values. If you add them then the total sum is = 1.

np.argmax(prediction[0])
Enter fullscreen mode Exit fullscreen mode

np.argmax takes an array and returns a new array with an integer value.If original value is [0.3, 0.7] then argmax return [0,1].

It means it returns 1 if its original value if is greater than 0.5

  • Saving a trained model.
model.save('model.h5')
Enter fullscreen mode Exit fullscreen mode

This will save our model. so we can use our model without retraining.

  • Predict on custom data.
def test_data(path):
  demo = imread(path)
  demo = tf.image.convert_image_dtype(demo,tf.float32)
  demo = tf.image.resize(demo,size=[224,224])
  demo = np.expand_dims(demo,axis=0)

  pred = model.predict(demo)
  result = unique_category[np.argmax(pred)]

  return result
Enter fullscreen mode Exit fullscreen mode

The above function is the same as we used to preprocess our image. additionally here we have to provide expand_dims which expands the dimension of our data. Because our model is trained with input shape = [32,224,224,3] and here we have only 1 image so we have to take our image which currently has [224,224,3] to convert to [1,224,224,3] same as our model input shape.

As of now, we have a train our CNN model and evaluating using valid data and test with some test data with a test path.

Now let's build a GUI using Tkinter so we can directly load our image and get back the result.

3. Build a GUI using Tkinter.

Read more Tkinter

Steps

  • Create a python file
  • Import libraries
  • Import model
  • Init a Gui
  • Build a function to classify an image
  • Upload function
#import lib
import tkinter as tk
from tkinter import filedialog
from tkinter import *
from matplotlib.pyplot import imread
import tensorflow as tf
import tensorflow_hub as hub
import numpy as np
from PIL import ImageTk, Image

#import model
print('_________________________________')
print('..........Start loading..........')

model = tf.keras.models.load_model('model.h5', custom_objects={
                                   "KerasLayer": hub.KerasLayer})

print('_________________________________')
print('...........Model Loaded..........')
print('_________________________________')

# dictionary to label all traffic signs class.
labels = ['Cat', 'Dog']

# initialise GUI
top = tk.Tk()
top.geometry('800x600')
top.title('Dog_VS_Cat Classification')
top.configure(background='#CDCDCD')
label = Label(top, background='#CDCDCD', font=('arial', 15, 'bold'))
sign_image = Label(top)

#To classify an image
def classify(file_path):
    global label_packed
    image = imread(file_path)
    image = tf.image.convert_image_dtype(image, tf.float32)
    image = tf.image.resize(image, size=[224, 224])
    image = np.expand_dims(image, axis=0)
    pred = model.predict(image)
    sign = labels[np.argmax(pred)]
    print(sign)
    label.configure(foreground='#011638', text=sign)

#Button
def show_classify_button(file_path):
    classify_b = Button(top, text="Classify Image",
                        command=lambda: classify(file_path),
                        padx=10, pady=5)
    classify_b.configure(background='#364156', foreground='white',
                         font=('arial', 10, 'bold'))
    classify_b.place(relx=0.79, rely=0.46)

#Upload image
def upload_image():
    try:
        file_path = filedialog.askopenfilename()
        uploaded = Image.open(file_path)
        uploaded.thumbnail(((top.winfo_width()/2.25),
                            (top.winfo_height()/2.25)))
        im = ImageTk.PhotoImage(uploaded)
        sign_image.configure(image=im)
        sign_image.image = im
        label.configure(text='')
        show_classify_button(file_path)
    except:
        pass

upload = Button(top, text="Upload an image",
                command=upload_image, padx=10, pady=5)
upload.configure(background='#364156', foreground='white',
                 font=('arial', 10, 'bold'))
upload.pack(side=BOTTOM, pady=50)
sign_image.pack(side=BOTTOM, expand=True)
label.pack(side=BOTTOM, expand=True)
heading = Label(top, text="Image Classification",
                pady=20, font=('arial', 20, 'bold'))
heading.configure(background='#CDCDCD', foreground='#364156')
heading.pack()
top.mainloop()

Enter fullscreen mode Exit fullscreen mode

Demo
Tkinter Demo

Now we have used our model in Gui it's time to make it public so everyone can use it.

For this, we have to build a web app that can get an image from the user's device and store it then predict using our model, and give the result back to the user's device.

Build a Flask app.

Use below repo for complete code

GitHub logo patelvivekdev / Dog_VS_Cat_Flask_web_app

Deploy deep learning model to Heroku


File structure of flask app

|-static
    |-js
    |-uploads
|-templates
    |-base.html
    |-index.html
|-app.py
|-model.h5

Enter fullscreen mode Exit fullscreen mode

Steps In app.py

  • Import libraries
import os
import numpy as np
from matplotlib.pyplot import imread
import tensorflow as tf
import tensorflow_hub as hub

# Flask utils
from flask import Flask, redirect, url_for, request, render_template
from werkzeug.utils import secure_filename

Enter fullscreen mode Exit fullscreen mode
  • Define a flask app
# Define a flask app
app = Flask(__name__)

STATIC_FOLDER = 'static'
# Path to the folder where we'll store the upload before prediction
UPLOAD_FOLDER = STATIC_FOLDER + '/uploads'

labels = ['Cat', 'Dog']

Enter fullscreen mode Exit fullscreen mode
  • Load model same as we did in GUI.
def load__model():
    print('[INFO] : Model loading ................')
    model = tf.keras.models.load_model('model.h5', 
                                      custom_objects{
                                     "KerasLayer": hub.KerasLayer}
    )

    return model


model = load__model()
print('[INFO] : Model loaded ................')

Enter fullscreen mode Exit fullscreen mode
  • Function to preprocess image and predict image
def preprocessing_image(path):
    img = imread(path)
    img = tf.image.convert_image_dtype(img, tf.float32)
    img = tf.image.resize(img, size=[224, 224])
    img = np.expand_dims(img, axis=0)

    return img

def predict(model, fullpath):
    image = preprocessing_image(fullpath)
    pred = model.predict(image)

    return pred

Enter fullscreen mode Exit fullscreen mode
  • Define route
@app.route('/', methods=['GET'])
def index():
    # Main page
    return render_template('index.html')


@app.route('/predict', methods=['GET', 'POST'])
def upload():
    if request.method == 'POST':
        # Get the file from post request
        file = request.files['file']
        fullname = os.path.join(UPLOAD_FOLDER, file.filename)
        file.save(fullname)

        # Make prediction
        pred = predict(model, fullname)
        result = labels[np.argmax(pred)]

        return result
    return None



if __name__ == '__main__':
    app.run(debug=True)
Enter fullscreen mode Exit fullscreen mode

We create our predict function on the predict route. So when the
form button is clicked our data is fetched from that.

Templates

Index.html

{% extends "base.html" %} {% block content %}

<h2>Dog_VS_Cat Classifier</h2>

<div>
  <form id="upload-file" method="post" enctype="multipart/form-data">
    <label for="imageUpload" class="upload-label"> Choose... </label>
    <input
      type="file"
      name="file"
      id="imageUpload"
      accept=".png, .jpg, .jpeg"
    />
  </form>

  <div class="image-section" style="display: none">
    <div class="img-preview">
      <div id="imagePreview"></div>
    </div>
    <div>
      <button type="button" class="btn btn-primary btn-lg" id="btn-predict">
        Predict!
      </button>
    </div>
  </div>

  <div class="loader" style="display: none"></div>

  <h3 id="result">
    <span> </span>
  </h3>
</div>

{% endblock %}

Enter fullscreen mode Exit fullscreen mode

Run from Command line

flask run
Enter fullscreen mode Exit fullscreen mode

This will run our flask app on localhost

Demo
Flask_demo

Now you can see on the address bar that currently, our flask app runs on localhost so not everyone can use it.

For making a model deploy into production we use Heroku as a platform as a service (PaaS). where we can store our model and build flask API to fetch prediction on images. and everyone can use it.

Deploy your Flask app into the Heroku.

  • Login into Heroku account. If you don't have any make it here. You can read more about how to use it on docs
  • Install Heroku CLI. Read more about here
  • Login into heruko using CLI
heroku login
Enter fullscreen mode Exit fullscreen mode
  • Creating Apps from the CLI
heroku create name-of-your-app
Enter fullscreen mode Exit fullscreen mode

We have to add 2 files to our project to work on Heroku.

  • requirements.txt > here we add all our libraries that are used to build our entire project. Make sure you add the right version number also.
  • Procfile > here we can add our app start point with guicorn. so you have to install guicorn as well.

Procfile

web: gunicorn app:app
Enter fullscreen mode Exit fullscreen mode

requirements.txt

flask==1.1.1
tensorflow-cpu==2.3.0
tensorflow_hub==0.10.0
matplotlib==3.3.3
numpy==1.18.0
gunicorn==20.0.4

Enter fullscreen mode Exit fullscreen mode

After adding this file we have to init our project with git. Make sure use install git on the local machine. you can download it here.

  • Init
git init
Enter fullscreen mode Exit fullscreen mode
  • Add all file
git add .
Enter fullscreen mode Exit fullscreen mode
  • Commit the change
git commit -am "Deploy our flask app"
Enter fullscreen mode Exit fullscreen mode
  • Push to Heroku for build
git push heroku master
Enter fullscreen mode Exit fullscreen mode

And it's done. you see your web app link after some build message.

Contact

Hey Readers, thank you for your time. If you liked the blog, don’t forget to appreciate it.

| Ai/ML Enthusiast | Blogger | Web Dev |

If you have any queries or suggestions feel free to contact me

Twitter Linkedin Github

Happy deep learning and have a great time learning how to make machines smarter.

Top comments (0)