{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","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":"none","dataSources":[{"sourceId":3978,"databundleVersionId":32719,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Introduction\nNational Data Science Bowl competition is about plankton images classification. Plankton are crutial to our ecosystem approx half the total carbon fixed in the global carbon cycle. \nMethods for measuring and monitoring plankton population are time consuming and cannot scope necessary for large scale studies. \nUnderwater imagery system ensures number of images to analize plankton species and competition is aim to build an algorithm to automate the image identification process. \n\nWe utilize image classification library MobileNetV2, visualize accuracy and evaluate the model. You can check and comment the results. \n\n\n<font color = 'green'>\n\nContent:\n\n1. [Load & Check Data & Library](#1)\n2. [MobileNet Model Preperation & Fit](#2)\n3. [Visualization](#3)\n4. [Predict & Submission](#4)\n5. [Second MobileNet Model Preperation & Fit](#5)\n6. [Second MobileNet Model Visualization](#6)\n7. [Second MobileNet Model Predict & Submission](#7)\n8. [Conclusion & Comment](#5)\n","metadata":{}},{"cell_type":"markdown","source":"<a id = '1'></a><br>\n# 1-Load & Check Data & Library","metadata":{}},{"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport matplotlib.pyplot as plt # visualization \nfrom PIL import Image # Image editing\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator # Image Transformation\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, Input ,BatchNormalization\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\nfrom tensorflow.keras.applications import MobileNetV2\nfrom tensorflow.keras.layers import GlobalAveragePooling2D\nfrom sklearn.utils import class_weight\nimport tensorflow as tf\nimport zipfile # Zip archives\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-01T12:38:10.743636Z","iopub.execute_input":"2025-08-01T12:38:10.745704Z","iopub.status.idle":"2025-08-01T12:38:10.777840Z","shell.execute_reply.started":"2025-08-01T12:38:10.745634Z","shell.execute_reply":"2025-08-01T12:38:10.776517Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train images are in the zip files, Extract the files\nzip_path = \"/kaggle/input/datasciencebowl/train.zip\"\nextract_path = \"/kaggle/working/train\"\n\nwith zipfile.ZipFile(zip_path, 'r') as zip_ref:\n    zip_ref.extractall(extract_path)\n\nprint(\"File classes: \",os.listdir(extract_path)[:5])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T12:38:10.779892Z","iopub.execute_input":"2025-08-01T12:38:10.780377Z","iopub.status.idle":"2025-08-01T12:38:18.553106Z","shell.execute_reply.started":"2025-08-01T12:38:10.780349Z","shell.execute_reply":"2025-08-01T12:38:18.552194Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Image show example from train file\nextract_path = \"/kaggle/working/train/train\"\n\nclass_folder = os.listdir(extract_path)[0]\nclass_path = os.path.join(extract_path, class_folder)\n\nimg_file = [f for f in os.listdir(class_path) if f.endswith(('.png', '.jpg', '.jpeg'))][0]\nimg_path = os.path.join(class_path, img_file)\n\nimg = Image.open(img_path)\nplt.imshow(img, cmap='gray')\nplt.title(f\"Class: {class_folder}\")\nplt.axis(\"off\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T12:38:18.554413Z","iopub.execute_input":"2025-08-01T12:38:18.554734Z","iopub.status.idle":"2025-08-01T12:38:18.671218Z","shell.execute_reply.started":"2025-08-01T12:38:18.554701Z","shell.execute_reply":"2025-08-01T12:38:18.670139Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<a id = '2'></a><br>\n# 2-MobileNet Model Preperation & Fit","metadata":{}},{"cell_type":"code","source":"train_dir = \"/kaggle/working/train/train\"\nnum_classes = len(os.listdir(\"/kaggle/working/train/train\"))\nimg_size = 80 # size of image\nbatch_size = 64 # loaded image numbers","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T12:38:18.673276Z","iopub.execute_input":"2025-08-01T12:38:18.673570Z","iopub.status.idle":"2025-08-01T12:38:18.679491Z","shell.execute_reply.started":"2025-08-01T12:38:18.673548Z","shell.execute_reply":"2025-08-01T12:38:18.678268Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# MobilNetV2 base model\nbase_model = MobileNetV2(\n    input_shape=(img_size, img_size, 3), \n    include_top=False,\n    weights='imagenet'\n)\n\nbase_model.trainable = False # freezing the model\n\ninputs = Input(shape=(img_size, img_size, 3))  \nx = base_model(inputs, training=False)\nx = GlobalAveragePooling2D()(x)\nx = Dropout(0.3)(x)\noutputs = Dense(num_classes, activation='softmax')(x)\n\nmodel = Model(inputs, outputs)\n\nmodel.compile(optimizer=Adam(learning_rate=1e-4),\n              loss='categorical_crossentropy',\n              metrics=['accuracy'])\n\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T12:38:18.680696Z","iopub.execute_input":"2025-08-01T12:38:18.681073Z","iopub.status.idle":"2025-08-01T12:38:20.352864Z","shell.execute_reply.started":"2025-08-01T12:38:18.681050Z","shell.execute_reply":"2025-08-01T12:38:20.352030Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Data generator and Normalization\ndatagen = ImageDataGenerator(\n    rescale = 1./255,\n    validation_split = 0.2,\n    rotation_range=10,\n    zoom_range = 0.1,\n    width_shift_range=0.1,\n    height_shift_range=0.1\n)\n\ntrain_gen = datagen.flow_from_directory(\n    train_dir,\n    target_size=(img_size, img_size),\n    color_mode='rgb',  \n    batch_size=batch_size,\n    class_mode='categorical',\n    subset='training',\n    shuffle=True\n)\n\nval_gen = datagen.flow_from_directory(\n    train_dir,\n    target_size=(img_size, img_size),\n    color_mode='rgb',\n    batch_size=batch_size,\n    class_mode='categorical',\n    subset='validation',\n    shuffle=False\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T12:38:20.353987Z","iopub.execute_input":"2025-08-01T12:38:20.354527Z","iopub.status.idle":"2025-08-01T12:38:21.233067Z","shell.execute_reply.started":"2025-08-01T12:38:20.354495Z","shell.execute_reply":"2025-08-01T12:38:21.231993Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Callbacks\n\nearly_stop = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True, verbose=1)\n\nreduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=2, verbose=1, min_lr=1e-6)\n\ncheckpoint = ModelCheckpoint(\"best_mobilenetv2.h5\", monitor='val_accuracy', save_best_only=True, verbose=1)\n\ncallbacks = [early_stop, reduce_lr, checkpoint]\n\n# Model fit\nhistory = model.fit(\n    train_gen,\n    validation_data=val_gen,\n    epochs=20,\n    callbacks=callbacks\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T12:38:21.234059Z","iopub.execute_input":"2025-08-01T12:38:21.234326Z","iopub.status.idle":"2025-08-01T13:21:06.458138Z","shell.execute_reply.started":"2025-08-01T12:38:21.234306Z","shell.execute_reply":"2025-08-01T13:21:06.457021Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<a id = '3'></a><br>\n# 3-Visualization","metadata":{}},{"cell_type":"code","source":"# Visualization\n\nplt.figure(figsize=(12, 5))\n\nplt.subplot(1, 2, 1)\nplt.plot(history.history['accuracy'], label='Train Accuracy')\nplt.plot(history.history['val_accuracy'], label='Validation Accuracy')\nplt.title(\"Model Accuracy\")\nplt.legend()\n\nplt.subplot(1, 2, 2)\nplt.plot(history.history['loss'], label='Train Loss')\nplt.plot(history.history['val_loss'], label='Validation Loss')\nplt.title(\"Model Loss\")\nplt.legend()\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T13:21:06.459572Z","iopub.execute_input":"2025-08-01T13:21:06.459829Z","iopub.status.idle":"2025-08-01T13:21:06.916468Z","shell.execute_reply.started":"2025-08-01T13:21:06.459809Z","shell.execute_reply":"2025-08-01T13:21:06.915613Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Test images are in the zip files, Extract the files\n\nimport zipfile\ntest_zip_path = \"/kaggle/input/datasciencebowl/test.zip\"\nextract_test_path = \"/kaggle/working/test\"\n\nwith zipfile.ZipFile(test_zip_path, 'r') as zip_ref:\n    zip_ref.extractall(extract_test_path)\n\ntest_dir = extract_test_path + \"/test\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T13:21:06.917611Z","iopub.execute_input":"2025-08-01T13:21:06.918056Z","iopub.status.idle":"2025-08-01T13:21:28.031915Z","shell.execute_reply.started":"2025-08-01T13:21:06.918027Z","shell.execute_reply":"2025-08-01T13:21:28.030744Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<a id = '4'></a><br>\n# 4-Predict & Submission","metadata":{}},{"cell_type":"code","source":"# Test ImageDataGenerator\ntest_datagen = ImageDataGenerator(rescale=1./255)\n\n# Test generator\ntest_generator = test_datagen.flow_from_directory(\n    directory=extract_test_path,\n    target_size=(img_size, img_size),\n    batch_size=64,\n    class_mode=None,\n    shuffle=False\n)\n\n# Predict\npreds = model.predict(test_generator, verbose=1)\ntest_preds = np.argmax(preds, axis=1)\n\n# image filenames\nimage_files = test_generator.filenames\nimage_files = [os.path.basename(f) for f in image_files]  \n\n# Class indices\nidx_to_label = {v: k for k, v in train_gen.class_indices.items()}\npred_labels = [idx_to_label[i] for i in test_preds]\n\nclass_names = list(train_gen.class_indices.keys())\n\n# Submission \nsubmission = pd.DataFrame(preds, columns=class_names)\nsubmission.insert(0, \"image\", image_files)\n\nsubmission.to_csv(\"submission.csv\", index=False) # Score: 1.60754 Private score: 1.64601\nsubmission.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T13:21:28.035146Z","iopub.execute_input":"2025-08-01T13:21:28.035466Z","iopub.status.idle":"2025-08-01T13:28:51.340571Z","shell.execute_reply.started":"2025-08-01T13:21:28.035442Z","shell.execute_reply":"2025-08-01T13:28:51.339605Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<a id = '5'></a><br>\n# 5-Second MobileNet Model Preperation & Fit","metadata":{}},{"cell_type":"code","source":"img_size = 160\nbatch_size = 32\n\ndatagen = ImageDataGenerator(\n    rescale=1./255,\n    rotation_range=15,\n    width_shift_range=0.1,\n    height_shift_range=0.1,\n    zoom_range=0.2,\n    horizontal_flip=True,\n    validation_split=0.2\n)\n\ntrain_gen = datagen.flow_from_directory(\n    train_dir,\n    target_size=(img_size, img_size),\n    color_mode='grayscale',\n    class_mode='categorical',\n    subset='training',\n    batch_size=batch_size,\n    shuffle=True\n)\n\nval_gen = datagen.flow_from_directory(\n    train_dir,\n    target_size=(img_size, img_size),\n    color_mode='grayscale',\n    class_mode='categorical',\n    subset='validation',\n    batch_size=batch_size,\n    shuffle=False\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T13:28:51.341843Z","iopub.execute_input":"2025-08-01T13:28:51.342590Z","iopub.status.idle":"2025-08-01T13:28:52.235384Z","shell.execute_reply.started":"2025-08-01T13:28:51.342562Z","shell.execute_reply":"2025-08-01T13:28:52.234022Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def build_model(input_shape, num_classes):\n    inputs = Input(shape=input_shape)\n\n    # Convert grayscale to RGB\n    x = tf.keras.layers.Lambda(lambda x: tf.image.grayscale_to_rgb(x))(inputs)\n        \n    base_model = MobileNetV2(include_top=False, weights='imagenet', input_tensor=x)\n    base_model.trainable = False  # freeze\n    \n    x = base_model.output\n    x = GlobalAveragePooling2D()(x)\n    x = Dropout(0.3)(x)\n    outputs = Dense(num_classes, activation='softmax')(x)\n    \n    return Model(inputs, outputs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T13:28:52.236782Z","iopub.execute_input":"2025-08-01T13:28:52.237150Z","iopub.status.idle":"2025-08-01T13:28:52.244858Z","shell.execute_reply.started":"2025-08-01T13:28:52.237119Z","shell.execute_reply":"2025-08-01T13:28:52.243657Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = build_model((img_size, img_size, 1), train_gen.num_classes)\n\nmodel.compile(\n    optimizer=Adam(learning_rate=1e-4),\n    loss='categorical_crossentropy',\n    metrics=['accuracy']\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T13:28:52.246063Z","iopub.execute_input":"2025-08-01T13:28:52.246430Z","iopub.status.idle":"2025-08-01T13:28:53.215015Z","shell.execute_reply.started":"2025-08-01T13:28:52.246399Z","shell.execute_reply":"2025-08-01T13:28:53.213970Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels = train_gen.classes\ncw = class_weight.compute_class_weight(class_weight='balanced', classes=np.unique(labels), y=labels)\nclass_weights = dict(enumerate(cw))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T13:28:53.216238Z","iopub.execute_input":"2025-08-01T13:28:53.216655Z","iopub.status.idle":"2025-08-01T13:28:53.275491Z","shell.execute_reply.started":"2025-08-01T13:28:53.216624Z","shell.execute_reply":"2025-08-01T13:28:53.274507Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"callbacks = [\n    ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=3, verbose=1),\n    EarlyStopping(monitor='val_loss', patience=7, restore_best_weights=True),\n    ModelCheckpoint(\"best_model.h5\", monitor='val_loss', save_best_only=True)\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T13:28:53.276716Z","iopub.execute_input":"2025-08-01T13:28:53.277102Z","iopub.status.idle":"2025-08-01T13:28:53.282469Z","shell.execute_reply.started":"2025-08-01T13:28:53.277073Z","shell.execute_reply":"2025-08-01T13:28:53.281272Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history = model.fit(\n    train_gen,\n    validation_data=val_gen,\n    epochs=30,\n    callbacks=callbacks,\n    class_weight=class_weights \n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T13:28:53.283543Z","iopub.execute_input":"2025-08-01T13:28:53.283882Z","iopub.status.idle":"2025-08-01T17:08:26.997697Z","shell.execute_reply.started":"2025-08-01T13:28:53.283856Z","shell.execute_reply":"2025-08-01T17:08:26.995067Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<a id = '6'></a><br>\n# 6-Second MobileNet Model Visualization","metadata":{}},{"cell_type":"code","source":"# Visualization\n\nplt.figure(figsize=(12, 5))\n\nplt.subplot(1, 2, 1)\nplt.plot(history.history['accuracy'], label='Train Accuracy')\nplt.plot(history.history['val_accuracy'], label='Validation Accuracy')\nplt.title(\"Model Accuracy\")\nplt.legend()\n\nplt.subplot(1, 2, 2)\nplt.plot(history.history['loss'], label='Train Loss')\nplt.plot(history.history['val_loss'], label='Validation Loss')\nplt.title(\"Model Loss\")\nplt.legend()\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T17:08:27.003329Z","iopub.execute_input":"2025-08-01T17:08:27.004032Z","iopub.status.idle":"2025-08-01T17:08:27.579233Z","shell.execute_reply.started":"2025-08-01T17:08:27.003961Z","shell.execute_reply":"2025-08-01T17:08:27.578076Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<a id = '7'></a><br>\n# 7-Second MobileNet Model Predict & Submission","metadata":{}},{"cell_type":"code","source":"# Test ImageDataGenerator\ntest_datagen = ImageDataGenerator(rescale=1./255)\n\n# Test generator\ntest_generator = test_datagen.flow_from_directory(\n    directory=extract_test_path,\n    target_size=(img_size, img_size),\n    batch_size=32,\n    class_mode=None,\n    shuffle=False,\n    color_mode='grayscale'\n)\n\n# Predict\npreds = model.predict(test_generator, verbose=1)\ntest_preds = np.argmax(preds, axis=1)\n\n# image filenames\nimage_files = test_generator.filenames\nimage_files = [os.path.basename(f) for f in image_files]  \n\n# Class indices\nidx_to_label = {v: k for k, v in train_gen.class_indices.items()}\npred_labels = [idx_to_label[i] for i in test_preds]\n\nclass_names = list(train_gen.class_indices.keys())\n\n# Submission \nsubmission = pd.DataFrame(preds, columns=class_names)\nsubmission.insert(0, \"image\", image_files)\n\nsubmission.to_csv(\"submission.csv\", index=False) # Score: 5.54002 Private score: 5.53441\nsubmission.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-01T17:08:27.580731Z","iopub.execute_input":"2025-08-01T17:08:27.581107Z","iopub.status.idle":"2025-08-01T17:38:04.134783Z","shell.execute_reply.started":"2025-08-01T17:08:27.581084Z","shell.execute_reply":"2025-08-01T17:38:04.133518Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<a id = '8'></a><br>\n# 8-Conclusion & Comment","metadata":{}},{"cell_type":"markdown","source":"Image classification with two MobileNet models are shown above. First model score is higher than second model. Second model we used grayscale to rgb and higher image_size and lower batch. Could you please check and comment models how can we decrease score? ","metadata":{}}]}