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)