{"id":28260036,"url":"https://github.com/rosnavigator/ann-blood-cells","last_synced_at":"2026-04-16T10:02:08.755Z","repository":{"id":286663624,"uuid":"884945153","full_name":"RosNaviGator/ANN-blood-cells","owner":"RosNaviGator","description":"Competition in Artificial Neural Networks and Deep Learning course (2024-2025). The task is to develop a multi-class classification model to classify 96x96 RGB images of blood cells into eight classes, each representing a distinct cell state. ","archived":false,"fork":false,"pushed_at":"2024-11-28T14:24:25.000Z","size":8956,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-01T09:13:05.405Z","etag":null,"topics":["airlab","artificial-neural-networks","blood-cells","challenge","classification","cnn","cnn-classification","computer-vision","convolutional-neural-networks","keras","machine-learning","neural-network","neural-networks","polimi","python","tensorflow"],"latest_commit_sha":null,"homepage":"","language":"Jupyter Notebook","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RosNaviGator.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-11-07T17:05:35.000Z","updated_at":"2025-07-31T13:15:12.000Z","dependencies_parsed_at":null,"dependency_job_id":"c73142c9-a4af-4807-8bd9-c6ad3e3b7ded","html_url":"https://github.com/RosNaviGator/ANN-blood-cells","commit_stats":null,"previous_names":["rosnavigator/ann-blood-cells"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/RosNaviGator/ANN-blood-cells","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RosNaviGator%2FANN-blood-cells","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RosNaviGator%2FANN-blood-cells/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RosNaviGator%2FANN-blood-cells/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RosNaviGator%2FANN-blood-cells/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RosNaviGator","download_url":"https://codeload.github.com/RosNaviGator/ANN-blood-cells/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RosNaviGator%2FANN-blood-cells/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31880883,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-16T09:23:21.276Z","status":"ssl_error","status_checked_at":"2026-04-16T09:23:15.028Z","response_time":69,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["airlab","artificial-neural-networks","blood-cells","challenge","classification","cnn","cnn-classification","computer-vision","convolutional-neural-networks","keras","machine-learning","neural-network","neural-networks","polimi","python","tensorflow"],"created_at":"2025-05-20T04:08:58.775Z","updated_at":"2026-04-16T10:02:08.739Z","avatar_url":"https://github.com/RosNaviGator.png","language":"Jupyter Notebook","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Artificial Neural Networks and Deep Learning 2024\n\n---\n\n## Homework 1: Blood-cell classification\n\n### Team Members\n- Maria Aurora Bertasini*\n- Marco Cioci*\n- Francesco Rosnati*\n- Luca Tramacere*\n\n*Master's candidate in High Performance Computing Engineering at Politecnico di Milano\n\n## Content\nThe homework was focused on classifying eight specific classes of blood cells. We adopted a systematic trial-and-error approach to iteratively develop a convolutional neural network (CNN) model capable of achieving high accuracy. \n\n### [Report](./Report_homework1_YNWA.pdf)\nRefer to this document for additional information.\n\n![classes](./img/classes.png)\n\n## Introduction\n\n---\n\n### Template Notebook\nThis notebook serves as a foundational template that outlines the workflow followed throughout the project. It provides an overview of the core steps involved in our process and includes links to more detailed, dedicated notebooks that focus on the key aspects of the project. Please note that **this template is not intended to be executed directly**. Instead, it serves as a reference to guide the reader through the structure and approach used. \n\n### Final model\n[Final Model Notebook](./FinalModel.ipynb) presents the final version of our model, that resulted in a 0.94 accuracy on Codabench Leaderbord.\n\nThe model consist on a Transfer Learning approach that uses `EfficientNetB2` as backbone feature extractor. The classificator is composed of three medium-light dense layers (512, 256, 128), with proper batch normalizations and regularizations. \n\nThe good results depend mainly on two focal aspects:\n- heavy application of **augmentations**, especially from KerasCV library, to allow our models to generalize well beyond the limit imposed by the starting dataset\n- a thorough journey in the choice of the best **fine-tuning** so that it could learn the best weights possible for the problem at hand\n\n\n\n## Work environment\n\n---\n### Manage Colab Environment\n```python\nCOLAB = False\n\nif COLAB:\n    !pip install keras_cv -qq\n    from google.colab import drive\n    drive.mount('/gdrive')\n    %cd /gdrive/My Drive/ANN_new\n```\n\n\n### Imports\n\n```python\nimport numpy as np\nimport tensorflow as tf\nimport keras_cv as kcv\nfrom tensorflow.keras.applications import \"\"\"BASE MODEL\"\"\"\nfrom tensorflow.keras.applications.\"\"\"BASE MODEL\"\"\" import preprocess_input\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Dense, Input, GlobalAveragePooling2D, Dropout, BatchNormalization, Resizing, Rescaling, LeakyReLU, ELU\nfrom sklearn.model_selection import train_test_splitBuild, Create, Compile\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint\nfrom sklearn.utils.class_weight import compute_class_weight\n\n\nSEED = 42\n\n```\n## Preprocessing\n\n[Preprocessing Notebook](./DatasetPreparation.ipynb): thorough description of the steps applied are shown in a dedicated environment, available at the link. \n\n---\n\nData required preprocessing before the actual ideation of the Neural Network could begin. In particular the dataset had a large number of *unwanted data*.\n- Starting data, input of the preprocessing notebook: `data/training_set.npz`\n- Processed data, output: `data/training_set_clean.npz`\n\nIn the present notebook data are uploaded **already preprocessed** in the following cell.\n### Load the dataset already cleaned from unwanted data\n\n```python\ndata = np.load('data/training_set_clean.npz')\nX = data['images']\ny = data['labels']\n```\n## Augmentations\n\n[Augmentations Notebook](./Augmentations.ipynb) provides a detailed overview of all the *keras_cv* augmentations tested.\n\n---\nDuring the development of the project a large number of augmentations were experimented with. They come from two sources:\n- *Keras Image augmentation layers* (included in Keras by default)\n- *Keras_cv Augmentation Layers* (additional library)\n#### Keras Image Augmentation Layers\nFollowing empirical testing to determine which ones would consistently prove useful, a group of five layers was selected and established as the foundational augmentations. \nFrom that point onward, all additional augmentations were applied on top of these base layers:\n\n```python\naugmentation = tf.keras.Sequential([\n    tf.keras.layers.RandomFlip('horizontal'),\n    tf.keras.layers.RandomRotation(0.7),\n    tf.keras.layers.RandomBrightness(0.2),\n    tf.keras.layers.RandomTranslation(height_factor=0.15, width_factor=0.15),\n    tf.keras.layers.RandomZoom(0.3)\n])\n```\n\n#### Keras_cv Augmentation Layers\nFor more complex and heavily distorting augmentations, the task was delegated to the more versatile and feature-rich *keras_cv* library, which provided greater flexibility and variety in data augmentation. Watch the dedicated notebook previously linked to find out more. It allows users to visualize the effects of each augmentation individually, examine the outcomes of various combinations, and review the pipelines actively employed throughout the project's development. \n\n\nThe augmentations are conveniently stored in a proper [python module](./py_modules/KerascvAug.py), once defined the desired ones they are to be inserted in the augment function below, which will later be applied during *Data Preparation* step.\n# import all augmentations and defined pipelines\nfrom py_modules.kerascv_aug import *\n\n```python\n\n# ------------------------- #\n# Define pipeline/s\n# ------------------------- #\n# ...\n# ...\n   \n\n\ndef augment(images, labels):\n    \n    \n    # Ensure images are tensors of the desired type\n    images = tf.cast(images, tf.float32)\n\n\n    # ------------------------- #\n    # Apply augmentations here\n    # ------------------------- #\n    # ...\n    # ...\n    \n\n    return images, labels\n\n```\n\n## Transfer Learning\n\n---\n### Before Transfer Learning: custom CNNs\nInitially, our approach was to build models from scratch, but it quickly became apparent that this strategy was much less efficient and powerful compared to leveraging pre-trained base models available in Keras. As a result, custom CNNs were soon abandoned in favor of more effective transfer learning methods. \n\nHowever, we have decided to present a couple of our early attempts for the sake of documentation and insight into the development process. These can be found in the [Custom CNNs notebook](./CustomCNNs.ipynb).\n#### Prepare dataset\n```python\n# set autotune\nAUTOTUNE = tf.data.AUTOTUNE\n\n# Normalize and preprocess images\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n\nX_train = tf.convert_to_tensor(X_train, dtype=tf.float32)\nX_test = tf.convert_to_tensor(X_test, dtype=tf.float32)\n\n# One-hot encode labels\ny_train = tf.keras.utils.to_categorical(y_train, num_classes=8).astype(np.float32)\ny_test = tf.keras.utils.to_categorical(y_test, num_classes=8).astype(np.float32)\n```\n\n```python\n# ---------------- #\n# Preparation\n# ---------------- #\n\ndef prepare_dataset(images, labels, is_training=True, batch_size=32):\n\n    # Create the base dataset\n    dataset = tf.data.Dataset.from_tensor_slices((images, labels))\n\n    if is_training:\n        dataset = dataset.shuffle(buffer_size=1024)\n\n    # Apply EfficientNet preprocessing\n    def preprocess(images, labels):\n        images = preprocess_input(images)\n        return images, labels\n\n    dataset = dataset.map(preprocess, num_parallel_calls=AUTOTUNE)\n\n    # Batch before augmentation\n    dataset = dataset.batch(batch_size)\n\n    if is_training:\n\n        # It is possbile to have different augments in different batches\n        def augment_with_index(batch_index, data):\n            images, labels = data\n            return augment(images, labels, batch_index)\n\n        dataset = dataset.enumerate().map(\n            augment_with_index, num_parallel_calls=AUTOTUNE\n        )\n\n    return dataset.prefetch(buffer_size=AUTOTUNE)\n\n\n# Prepare datasets\ntrain_dataset = prepare_dataset(X_train, y_train, is_training=True, batch_size=32)\nval_dataset = prepare_dataset(X_test, y_test, is_training=False, batch_size=32)\n```\n\n#### Build Model\n\n```python\n# -------------------- #\n# Build, Create, Compile\n# -------------------- #\n\ndef create_model(input_shape=(96, 96, 3), num_classes=8, augmentation=None):\n    input_layer = Input(shape=input_shape)\n\n    # Resizing layer for prediction to resize images to 224x224\n    x = Resizing(260, 260)(input_layer)\n\n    # Base model\n    base_model = \"\"\"BASE MODEL\"\"\"\"\n    base_model.trainable = False\n\n    # Model architecture\n    # with Activation Function LeakyReLU\n    x = augmentation(x)\n    # x = Rescaling(scale=1./127.5, offset=-1)(x)\n    x = base_model(x, training=False)\n    x = GlobalAveragePooling2D()(x)\n    x = Dropout(0.2)(x)\n    x = BatchNormalization()(x)\n    x = Dense(512, activation=None)(x)\n    x = LeakyReLU(negative_slope=0.05)(x)\n    x = Dropout(0.1)(x)\n    x = BatchNormalization()(x)\n    x = Dense(256, activation=None)(x)\n    x = LeakyReLU(negative_slope=0.05)(x)\n    x = Dropout(0.1)(x)\n    x = BatchNormalization()(x)\n    x = Dense(128, activation=None)(x)\n    x = LeakyReLU(negative_slope=0.05)(x)\n    output_layer = Dense(num_classes, activation='softmax')(x)\n\n    return Model(inputs=input_layer, outputs=output_layer)\n\n\n# ---------------- #\n# Create \u0026 Compile\n# ---------------- #\n\nmodel = create_model(augmentation=augmentation)\nlr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(\n    initial_learning_rate=0.001,\n    decay_steps=1000,\n    decay_rate=0.95\n)\noptimizer = tf.keras.optimizers.Adam(learning_rate=lr_schedule)\nmodel.compile(\n    optimizer=optimizer,\n    loss='categorical_crossentropy',\n    metrics=['accuracy']\n)\n\n\n# ---------------- #\n# Other settings\n# ---------------- #\n\n# Callbacks\ncallbacks = [\n    EarlyStopping(\n        monitor='val_accuracy',\n        patience=15,\n        restore_best_weights=True\n    ),\n    ModelCheckpoint(\n        \"\"\"MODEL NAME\"\"\",\n        monitor='val_accuracy',\n        save_best_only=True,\n        mode='max'\n    )\n]\n\n# Compute class weights to fight class imbalance\nclass_weights = compute_class_weight(\n    class_weight='balanced',\n    classes=np.unique(np.argmax(y_train, axis=1)),\n    y=np.argmax(y_train, axis=1)\n)\nclass_weights = dict(enumerate(class_weights))\n```\n\n```python\n# ---------------- #\n# Train model\n# ---------------- #\n\nhistory = model.fit(\n    train_dataset,\n    validation_data=val_dataset,\n    epochs=100,\n    callbacks=callbacks,\n    class_weight=class_weights\n)\n```\n\n## Fine-tuning\n\n---\n### Visualize the Base Model architecture\nEvery Keras pre-trained model has a different architecture, some of them have layers structured in blocks of layers and stages of blocks. It is advisable to unfreeze whole blocks, in order to do so it is necessary to visualize the feature extractor layers.\n```python\n# Print layer indices, names, and trainability status\nfor i, layer in enumerate(model.get_layer(\"\"\"BASE MODEL\"\"\").layers):\n    print(f\"Layer {i}: {layer.name}, Type: {type(layer).__name__}, Trainable: {layer.trainable}\")\n```\n\n### Fine-tune the desired amount of blocks\nOnce established the number of layers to keep freezed, it's possible to set such number with parameter `N` and procede with the fine tuning, as shown in the cell below.\n\n```python\n# Reload model\nmodel = tf.keras.models.load_model(\"\"\"MODEL NAME\"\"\")\n\n\n# ---------------- #\n# Unfreeze\n# ---------------- #\n\nN = \"\"\"CHOOSE NUMBER\"\"\" # Number of layers to freeze\n\nfor i, layer in enumerate(model.get_layer(\"\"\"BASE MODEL\"\"\").layers):\n    layer.trainable = True\n\nfor i, layer in enumerate(model.get_layer(\"\"\"BASE MODEL\"\"\").layers):\n    layer.trainable = False\n\n\nfor i, layer in enumerate(model.get_layer(\"\"\"BASE MODEL\"\"\").layers):\n    if isinstance(layer, tf.keras.layers.Conv2D) or isinstance(layer, tf.keras.layers.DepthwiseConv2D):\n        layer.trainable = True\n\n\n# Set the first N layers as non-trainable\nfor i, layer in enumerate(model.get_layer(\"\"\"BASE MODEL\"\"\").layers[:N]):\n    layer.trainable = False\n\n# Print layer indices, names, and trainability status\nfor i, layer in enumerate(model.get_layer(\"\"\"BASE MODEL\"\"\").layers):\n    print(f\"Layer {i}: {layer.name}, Type: {type(layer).__name__}, Trainable: {layer.trainable}\")\n\n\n\n# -------------------- #\n# Fine-tune settings\n# -------------------- #\n\n# Use a lower learning rate for fine-tuning\nfine_tune_lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(\n    initial_learning_rate=0.00001,  # Small learning rate for fine-tuning\n    decay_steps=1000,\n    decay_rate=0.95\n)\nfine_tune_optimizer = tf.keras.optimizers.Adam(learning_rate=fine_tune_lr_schedule)\nmodel.compile(optimizer=fine_tune_optimizer, loss='categorical_crossentropy', metrics=['accuracy'])\n\n# Additional callbacks\nfine_tune_early_stopping = EarlyStopping(monitor='val_accuracy', patience=10, restore_best_weights=True)\nfine_tune_checkpoint = ModelCheckpoint(\"\"\"MODEL NAME FINE TUNED\"\"\", monitor='val_accuracy', save_best_only=True, mode='max')\n```\n\n```python\n# -------------------- #\n# Fine-tune\n# -------------------- #\n\nfine_tune_history = model.fit(\n    train_dataset,\n    batch_size=16, # Smaller batch size for fine-tuning\n    validation_data=val_dataset,\n    epochs=20,\n    callbacks=[fine_tune_early_stopping, fine_tune_checkpoint],\n    class_weight=class_weights\n).history\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frosnavigator%2Fann-blood-cells","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frosnavigator%2Fann-blood-cells","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frosnavigator%2Fann-blood-cells/lists"}