{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":3362,"databundleVersionId":31148,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":258963008,"sourceType":"kernelVersion"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Image classification: cats & dogs","metadata":{}},{"cell_type":"code","source":"# Handle imports up-front\nimport os\nimport sys\nimport glob\nimport random\n\n# Silence logging messages from TensorFlow, except errors\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'\n\n# PyPI imports\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport tensorflow as tf\nfrom sklearn.metrics import accuracy_score, confusion_matrix, ConfusionMatrixDisplay\n\n# Silence logging messages from TensorFlow, except errors\ntf.get_logger().setLevel('ERROR')\n\n# Import image from keras correctly based on the TensorFlow version\ntf_version = float('.'.join(tf.__version__.split('.')[0:2]))\nprint(f'Tensorflow version {tf_version}')\n\nif tf_version > 2.8:\n    import keras.utils as image\n\nelse:\n    from keras.preprocessing import image\n\n# Figure out if we are running on Kaggle or not, if so\n# add the location of utils.py to path so we can import\npath_list = os.getcwd().split(os.sep)\n\nif path_list[1] == 'kaggle':\n    sys.path.append('/kaggle/usr/lib/image_classification_functions')\n\n# Import custom helper functions from utils.py\nfrom image_classification_functions import prep_data\nfrom image_classification_functions import single_training_run\nfrom image_classification_functions import plot_single_training_run\nfrom image_classification_functions import hyperparameter_optimization_run\nfrom image_classification_functions import plot_hyperparameter_optimization_run","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1. Data preparation\n\n### 1.1. Load the data paths","metadata":{}},{"cell_type":"code","source":"# Decompress and organize the images\ntraining_data_path, validation_data_path, testing_data_path = prep_data()\n\n# Get lists of training and validation dog and cat images\ntraining_dogs = glob.glob(f'{training_data_path}/dogs/dog.*')\ntraining_cats = glob.glob(f'{training_data_path}/cats/cat.*')\nvalidation_dogs = glob.glob(f'{validation_data_path}/dogs/dog.*')\nvalidation_cats = glob.glob(f'{validation_data_path}/cats/cat.*')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 1.2. Inspect","metadata":{}},{"cell_type":"code","source":"# Plot some of the cat and dog images\nfig, axs = plt.subplots(3,2,figsize=(6, 4))\n\nfor cat, dog, row in zip(training_cats, training_dogs, axs):\n    for animal, ax in zip([cat, dog], row):\n        animal = image.load_img(animal)\n        animal = image.img_to_array(animal)\n        animal /= 255.0\n        ax.imshow(animal)\n        ax.axis('off')\n\nfig.tight_layout()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. EDA\n\nLet's take a deeper look at a few of our images to get a feel for how image data is structured.\n\n### 2.1. Image data","metadata":{}},{"cell_type":"code","source":"# Load one of the dogs\ndog = image.load_img(training_dogs[0])\n\n# And convert it to an array - this is how TensorFlow will handel the data\ndog = image.img_to_array(dog)\n\n# Take a look at some properties of the object\nprint(f'Image data is: {type(dog)}')\nprint(f'Image data shape: {dog.shape}')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The data has shape of n x n x 3? The image is n x n pixels, that makes sense. But what is the 3? Let's plot the pixel values and you will see what is going on:","metadata":{}},{"cell_type":"code","source":"plt.hist(dog[:,:,0].flatten(), bins=100, color='red', alpha=0.5, label='Red channel')\nplt.hist(dog[:,:,1].flatten(), bins=100, color='green', alpha=0.5, label='Green channel')\nplt.hist(dog[:,:,2].flatten(), bins=100, color='blue', alpha=0.5, label='Blue channel')\nplt.xlabel('Pixel value')\nplt.ylabel('Count')\nplt.legend(loc='best')\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"There are a few interesting observations we can make here:\n\n1. This array has 3 axes: n x n x 3. The first two are the dimensions of the image, the third is the three color channels: red, green and blue.\n2. n x n x 3 is over a half million individual values - this one image is 10 time more data that any of the other datasets we have worked with so far!\n3. The range of pixel values is from 0 to about 250 - in reality it is (0,255) for a total range of 256 possible values per pixel. This is defined by the JPEG image standard.\n\nThere are two things we can do with this information. First, we should scale the pixel values, this will improve the training of our neural network. We can do this directly in the model definition by adding a normalization layer. Second, we can make the images gray scale, which will decrease the input size and therefore computational burden by a factor of three. We can do this via the image dataset generator.\n\n### 2.2. Image dimensions\n\nLet's take a look at a random sample of images from the dataset and see what their dimensions are.","metadata":{}},{"cell_type":"code","source":"# Get a random sample of images, half cats and half dogs\nsample_size = 500\nsample = random.sample(training_dogs, sample_size//2)\nsample += random.sample(training_cats, sample_size//2)\n\n# Collectors for data\nheights = []\nwidths = []\n\n# Loop on the sample images\nfor sample_image in sample:\n\n    # Load the image and convert it to an array\n    sample_image = image.load_img(sample_image)\n    sample_image = image.img_to_array(sample_image)\n\n    # Get the width and height and add to collections\n    heights.append(sample_image.shape[0])\n    widths.append(sample_image.shape[1])\n\n# Plot results as a histogram\nplt.hist(heights, bins=50, alpha=0.5, label='Image heights')\nplt.hist(widths, bins=50, alpha=0.5, label='Image widths')\nplt.xlabel('Image dimension')\nplt.ylabel('Count')\nplt.legend(loc='best')\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Based on the above plot, let's set our image dimension at 64 or 128 pixels. Smaller is better for training speed and memory use, but we don't want to go too small, then the model will have a hard time learning from the data. There is no hard and fast rule here. You could do an experiment testing several different image dimensions to see how small we could make the image and still get good performance.","metadata":{}},{"cell_type":"markdown","source":"### 2.3. Image aspect ratios","metadata":{}},{"cell_type":"code","source":"# Calculate the sample image aspect ratios\naspect_ratios = np.array(widths)/np.array(heights)\n\n# Plot as histogram\nplt.hist(aspect_ratios, bins=50, color='black')\nplt.xlabel('Image aspect ratio')\nplt.ylabel('Count')\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The most common aspect ratio in the data set looks to be around 1.3 - which is the '4:3' aspect ratio that used to be the standard for computer monitors. Rather than using square input images, we can use this aspect ratio to better match the data.","metadata":{}},{"cell_type":"code","source":"image_width = 64\naspect_ratio = 4/3\nimage_height=int(image_width / aspect_ratio)\n\nprint(f'Input image dimensions: {image_width} x {image_height}')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Model\n\nNow it's time to build and train the model. We will do this in a few steps, evaluating performance by looking at the training curves along the way:\n\n1. Establish baseline performance with default settings\n2. Optimize the batch size and learning rate\n3. Optimize regularization with L1 and L2 penalties\n4. Optimize input image size for speed and/or better performance\n5. Optimize network architecture\n6. Final model and evaluation\n\nFirst, let's choose some initial settings for how much to train.","metadata":{}},{"cell_type":"code","source":"epochs = 20\nsteps_per_epoch = 50\nvalidation_steps = 50","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 3.1. Baseline model performance","metadata":{}},{"cell_type":"code","source":"%%time\n\n# Do a single training run with default settings\ntraining_results = single_training_run(\n    training_data_path,\n    validation_data_path,\n    image_height=image_width,\n    image_width=image_height,\n    epochs=epochs,\n    steps_per_epoch=steps_per_epoch,\n    validation_steps=validation_steps,\n    print_model_summary=True\n)\n\n# Plot the results\nplot_single_training_run(training_results).show()\n\nprint()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 3.2. Batch size and learning rate optimization","metadata":{}},{"cell_type":"code","source":"%%time\n\n# Define hyperparameters\nhyperparameters = {\n    'batch_sizes': [32, 64, 128],\n    'learning_rates': [0.01, 0.001, 0.0001],\n    'image_widths': [image_width],\n    'steps_per_epoch': steps_per_epoch,\n    'validation_steps': validation_steps,\n    'epochs': epochs\n}\n\n# Train the model with each set of hyperparameters\nhyperparameter_optimization_results = hyperparameter_optimization_run(\n    training_data_path,\n    validation_data_path,\n    **hyperparameters\n)\n\n# Specify which hyperparameters to include in the plot labels\nplot_labels = ['batch_sizes', 'learning_rates']\n\n# Plot the learning curves\nplot_hyperparameter_optimization_run(\n    hyperparameter_optimization_results,\n    hyperparameters,\n    plot_labels,\n    accuracy_ylims=[45,90],\n    entropy_ylims=[0.4,0.8]\n).show()\n\nprint()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"batch_size = 64\nlearning_rate = 0.001\nepochs = 150","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n\n# Set some hyperparameters for the run\nhyperparameters={\n    'batch_size': batch_size,\n    'learning_rate': learning_rate,\n    'image_width': image_width,\n    'image_height': image_height,\n    'steps_per_epoch': steps_per_epoch,\n    'validation_steps': validation_steps,\n    'epochs': epochs\n}\n\n# Do a single training run\ntraining_results=single_training_run(\n    training_data_path,\n    validation_data_path,\n    **hyperparameters\n)\n\n# Plot the results\nplot_single_training_run(training_results).show()\n\nprint()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 3.3. Regularization\n\nOptimize L1 and L2 penalties using the optimized batch size and learning rate from the previous experiment.","metadata":{}},{"cell_type":"code","source":"epochs = 150","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n\n# Define hyperparameters\nhyperparameters = {\n    'l1_penalties': [0.001, 0.0001],\n    'l2_penalties': [0.001, 0.0001],\n    'batch_sizes': [batch_size],\n    'learning_rates': [learning_rate],\n    'image_widths': [image_width],\n    'steps_per_epoch': steps_per_epoch,\n    'validation_steps': validation_steps,\n    'epochs': epochs\n}\n\n# Train the model with each set of hyperparameters\nhyperparameter_optimization_results = hyperparameter_optimization_run(\n    training_data_path,\n    validation_data_path,\n    **hyperparameters\n)\n\n# Specify which hyperparameters to include in the plot labels\nplot_labels = ['l1_penalties', 'l2_penalties']\n\n# Plot the learning curves\nplot_hyperparameter_optimization_run(\n    hyperparameter_optimization_results,\n    hyperparameters,\n    plot_labels,\n    accuracy_ylims=[45,90],\n    entropy_ylims=[0.2,1.25]\n).show()\n\nprint()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"l1_penalty = 0.0001\nl2_penalty = 0.001\nepochs = 200","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n\n# Set some hyperparameters for the run\nhyperparameters = {\n    'l1_penalty': l1_penalty,\n    'l2_penalty': l2_penalty,\n    'image_width': image_width,\n    'batch_size': batch_size,\n    'learning_rate': learning_rate,\n    'steps_per_epoch': steps_per_epoch,\n    'validation_steps': validation_steps,\n    'epochs': epochs\n}\n\n# Do a single training run\ntraining_results = single_training_run(\n    training_data_path,\n    validation_data_path,\n    **hyperparameters,\n)\n\n# Plot the results\nplot_single_training_run(training_results).show()\n\nprint()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"l1_penalty = 0.0002\nl2_penalty = 0.002","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 3.4. Input image size optimization\n\nTry some smaller input image sizes and see how the model does with the hyperparameter settings we have chosen via optimization so far.","metadata":{}},{"cell_type":"code","source":"epochs = 200","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n\n# Define hyperparameters\nhyperparameters = {\n    'l1_penalties': [l1_penalty],\n    'l2_penalties': [l2_penalty],\n    'batch_sizes': [batch_size],\n    'learning_rates': [learning_rate],\n    'image_widths': [32, 64, 128],\n    'steps_per_epoch': steps_per_epoch,\n    'validation_steps': validation_steps,\n    'epochs': epochs\n}\n\n# Train the model with each combination of hyperparameters\nhyperparameter_optimization_results = hyperparameter_optimization_run(\n    training_data_path,\n    validation_data_path,\n    **hyperparameters\n)\n\n# Specify which hyperparameters to include in the plot labels\nplot_labels = ['image_widths']\n\n# Plot the training curves\nplot_hyperparameter_optimization_run(\n    hyperparameter_optimization_results,\n    hyperparameters,\n    plot_labels,\n    accuracy_ylims=[45,90],\n    entropy_ylims=[0.4,1.4]\n).show()\n\nprint()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image_width = 128\nimage_height = int(image_width/aspect_ratio)\nepochs = 1000","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n\n# Set some hyperparameters for the run\nhyperparameters = {\n    'l1_penalty': l1_penalty,\n    'l2_penalty': l2_penalty,\n    'image_height': image_height,\n    'image_width': image_width,\n    'batch_size': batch_size,\n    'learning_rate': learning_rate,\n    'steps_per_epoch': steps_per_epoch,\n    'validation_steps': validation_steps,\n    'epochs': epochs\n}\n\n# Do a single training run\ntraining_results = single_training_run(\n    training_data_path,\n    validation_data_path,\n    **hyperparameters\n)\n\n# Plot the results\nplot_single_training_run(training_results).show()\n\nprint()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"l1_penalty = 0.0004\nl2_penalty = 0.004","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 3.5. Model architecture\n\n#### 3.5.1. Convolutional layers","metadata":{}},{"cell_type":"code","source":"epochs = 200","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n\n# Define hyperparameters\nhyperparameters = {\n    'filter_nums_list': [[16,32,64],[32,64,128],[64,128,256]],\n    'filter_sizes': [3,4,5],\n    'l1_penalties': [l1_penalty],\n    'l2_penalties': [l2_penalty],\n    'batch_sizes': [batch_size],\n    'learning_rates': [learning_rate],\n    'image_widths': [image_width],\n    'steps_per_epoch': steps_per_epoch,\n    'validation_steps': validation_steps,\n    'epochs': epochs\n}\n\n# Train the model with each combination of hyperparameters\nhyperparameter_optimization_results = hyperparameter_optimization_run(\n    training_data_path,\n    validation_data_path,\n    **hyperparameters\n)\n\n# Specify which hyperparameters to include in the plot labels\nplot_labels = ['filter_nums_list', 'filter_sizes']\n\n# Plot the training curves\nplot_hyperparameter_optimization_run(\n    hyperparameter_optimization_results,\n    hyperparameters,\n    plot_labels,\n    accuracy_ylims=[45,95],\n    entropy_ylims=[0,2]\n).show()\n\nprint()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"filter_nums = [64,128,256]\nfilter_size = 4\n\nepochs = 1000","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n\n# Set some hyperparameters for the run\nhyperparameters = {\n    'filter_nums': filter_nums,\n    'filter_size': filter_size,\n    'l1_penalty': l1_penalty,\n    'l2_penalty': l2_penalty,\n    'batch_size': batch_size,\n    'learning_rate': learning_rate,\n    'image_height': image_height,\n    'image_width': image_width,\n    'steps_per_epoch': steps_per_epoch,\n    'validation_steps': validation_steps,\n    'epochs': epochs\n}\n\n# Do a single training run\ntraining_results = single_training_run(\n    training_data_path,\n    validation_data_path,\n    **hyperparameters\n)\n\n# Plot the results\nplot_single_training_run(training_results).show()\n\nprint()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 3.6. Final hyperparameters","metadata":{}},{"cell_type":"code","source":"hyperparameters = {\n    'filter_nums': filter_nums,\n    'filter_size': filter_size,\n    'l1_penalty': l1_penalty,\n    'l2_penalty': l2_penalty,\n    'image_height': image_height,\n    'image_width': image_width,\n    'batch_size': batch_size,\n    'learning_rate': learning_rate,\n    'steps_per_epoch': steps_per_epoch,\n    'validation_steps': validation_steps,\n    'epochs': epochs\n}\n\nfor key, value in hyperparameters.items():\n    print(f'{key}: {value}')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Model performance\n\n### 4.2. Testing dataset","metadata":{}},{"cell_type":"code","source":"# Get lists of testing dog and cat images\ntesting_dogs = glob.glob(f'{testing_data_path}/dogs/dog.*')\ntesting_cats = glob.glob(f'{testing_data_path}/cats/cat.*')\n\n# Plot some of the cat and dog images\nfig, axs = plt.subplots(3,2,figsize=(6, 4))\n\nfor cat, dog, row in zip(testing_cats, testing_dogs, axs):\n    for animal, ax in zip([cat, dog], row):\n        animal = image.load_img(animal)\n        animal = image.img_to_array(animal)\n        animal /= 255.0\n        ax.imshow(animal)\n        ax.axis('off')\n\nfig.tight_layout()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"testing_dataset = tf.keras.utils.image_dataset_from_directory(\n    testing_data_path,\n    image_size = (image_height, image_width),\n    batch_size = batch_size,\n    shuffle = False\n)\n\nimages = np.concatenate([x for x, y in testing_dataset], axis=0)\nlabels = np.concatenate([y for x, y in testing_dataset], axis=0)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 4.3. Test set performance","metadata":{}},{"cell_type":"code","source":"logits = training_results.model.predict(testing_dataset).flatten()\n\nplt.hist(logits, color='black', bins=30)\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"threshold = 0.5\npredictions = np.array([1 if p > threshold else 0 for p in logits])\nlabels = np.concatenate([y for x, y in testing_dataset], axis=0)\naccuracy = accuracy_score(labels, predictions) * 100\n\n# Plot the confusion matrix\ncm = confusion_matrix(labels, predictions, normalize='true')\ncm_disp = ConfusionMatrixDisplay(confusion_matrix=cm)\n_ = cm_disp.plot()\n\nplt.title(f'Test set performance\\noverall accuracy: {accuracy:.1f}%')\nplt.xlabel('Predicted class')\nplt.ylabel('True class')\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}