{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Found 2199 images belonging to 2 classes.\n", "Found 549 images belonging to 2 classes.\n", "Training samples: 2199\n", "Validation samples: 549\n" ] } ], "source": [ "import tensorflow as tf\n", "from tensorflow.keras.preprocessing.image import ImageDataGenerator\n", "\n", "dataset_dir = 'C:/Users/vsavelev/GITHUB/DS_projet/jan24_cds_mushrooms/data'\n", "\n", "# Create ImageDataGenerator with validation split\n", "datagen = ImageDataGenerator(rescale=1.0/255, validation_split=0.2)\n", "\n", "train_generator = datagen.flow_from_directory(\n", " dataset_dir,\n", " target_size=(224, 224),\n", " batch_size=32,\n", " class_mode='categorical',\n", " subset='training' # Set as training data\n", ")\n", "\n", "validation_generator = datagen.flow_from_directory(\n", " dataset_dir,\n", " target_size=(224, 224),\n", " batch_size=32,\n", " class_mode='categorical',\n", " subset='validation' # Set as validation data\n", ")\n", "\n", "print(f'Training samples: {train_generator.samples}')\n", "print(f'Validation samples: {validation_generator.samples}')" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Model: \"sequential\"\n",
"
\n"
],
"text/plain": [
"\u001b[1mModel: \"sequential\"\u001b[0m\n"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n", "┃ Layer (type) ┃ Output Shape ┃ Param # ┃\n", "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n", "│ resnet50 (Functional) │ ? │ 23,587,712 │\n", "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", "│ global_average_pooling2d │ ? │ 0 (unbuilt) │\n", "│ (GlobalAveragePooling2D) │ │ │\n", "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", "│ dense (Dense) │ ? │ 0 (unbuilt) │\n", "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", "│ dropout (Dropout) │ ? │ 0 (unbuilt) │\n", "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", "│ dense_1 (Dense) │ ? │ 0 (unbuilt) │\n", "└─────────────────────────────────┴────────────────────────┴───────────────┘\n", "\n" ], "text/plain": [ "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n", "┃\u001b[1m \u001b[0m\u001b[1mLayer (type) \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m Param #\u001b[0m\u001b[1m \u001b[0m┃\n", "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n", "│ resnet50 (\u001b[38;5;33mFunctional\u001b[0m) │ ? │ \u001b[38;5;34m23,587,712\u001b[0m │\n", "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", "│ global_average_pooling2d │ ? │ \u001b[38;5;34m0\u001b[0m (unbuilt) │\n", "│ (\u001b[38;5;33mGlobalAveragePooling2D\u001b[0m) │ │ │\n", "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", "│ dense (\u001b[38;5;33mDense\u001b[0m) │ ? │ \u001b[38;5;34m0\u001b[0m (unbuilt) │\n", "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", "│ dropout (\u001b[38;5;33mDropout\u001b[0m) │ ? │ \u001b[38;5;34m0\u001b[0m (unbuilt) │\n", "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", "│ dense_1 (\u001b[38;5;33mDense\u001b[0m) │ ? │ \u001b[38;5;34m0\u001b[0m (unbuilt) │\n", "└─────────────────────────────────┴────────────────────────┴───────────────┘\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Total params: 23,587,712 (89.98 MB)\n", "\n" ], "text/plain": [ "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m23,587,712\u001b[0m (89.98 MB)\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Trainable params: 0 (0.00 B)\n", "\n" ], "text/plain": [ "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Non-trainable params: 23,587,712 (89.98 MB)\n", "\n" ], "text/plain": [ "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m23,587,712\u001b[0m (89.98 MB)\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from tensorflow.keras.applications import ResNet50\n", "from tensorflow.keras import layers, models\n", "\n", "# Load and Configure the Pre-trained ResNet50 Model\n", "base_model = ResNet50(weights='imagenet', include_top=False, input_shape=(224, 224, 3))\n", "\"\"\"\n", "weights='imagenet': Loads the pre-trained weights from the ImageNet dataset.\n", "include_top=False: Excludes the top fully-connected layers of the ResNet50 model, enabling you to add your own custom layers.\n", "input_shape=(224, 224, 3): Specifies the input shape of the images (224x224 pixels, with 3 color channels - RGB).\n", "\"\"\"\n", "\n", "# Freeze the base model (to freeze the pre-trained layers)\n", "base_model.trainable = False\n", "\n", "# Add custom layers on top of the base model\n", "model = models.Sequential([ #allows to stack layers linearly\n", " base_model,\n", " layers.GlobalAveragePooling2D(),\n", " layers.Dense(1024, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(0.01)), # L2 regularization\n", " layers.Dropout(0.5),\n", " layers.Dense(train_generator.num_classes, activation='softmax')\n", "])\n", "\n", "\"\"\"\n", "GlobalAveragePooling2D(): Reduces each feature map to a single number by taking the average, \n", "which helps to reduce the size of the model and prevent overfitting.\n", "Dense(1024, activation='relu'): Adds a fully connected layer with 1024 units and ReLU activation function.\n", "Dropout(0.5): Adds a dropout layer with a 50% dropout rate to prevent overfitting by randomly setting half of the input units \n", "to 0 at each update during training.\n", "Dense(train_generator.num_classes, activation='softmax'): \n", "Adds the final output layer with units equal to the number of classes in your dataset, using the softmax activation function for multi-class classification.\n", "\"\"\"\n", "model.compile(optimizer=tf.keras.optimizers.Adam(),\n", " loss='categorical_crossentropy',\n", " metrics=['accuracy'])\n", "\n", "\"\"\"\n", "optimizer=tf.keras.optimizers.Adam(): Uses the Adam optimizer, which is an adaptive learning rate optimization algorithm.\n", "loss='categorical_crossentropy': Uses categorical cross-entropy as the loss function, suitable for multi-class classification.\n", "metrics=['accuracy']: Tracks accuracy as the metric to evaluate the model's performance during training and testing.\n", "\"\"\"\n", "model.summary()\n", "\n" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Epoch 1/10\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "c:\\Users\\vsavelev\\AppData\\Local\\anaconda3\\Lib\\site-packages\\keras\\src\\trainers\\data_adapters\\py_dataset_adapter.py:121: UserWarning: Your `PyDataset` class should call `super().__init__(**kwargs)` in its constructor. `**kwargs` can include `workers`, `use_multiprocessing`, `max_queue_size`. Do not pass these arguments to `fit()`, as they will be ignored.\n", " self._warn_if_super_not_called()\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m143s\u001b[0m 2s/step - accuracy: 0.8540 - loss: 6.8043 - val_accuracy: 0.8860 - val_loss: 0.6636\n", "Epoch 2/10\n", "\u001b[1m 1/68\u001b[0m \u001b[37m━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[1m2:09\u001b[0m 2s/step - accuracy: 0.8438 - loss: 0.8550" ] }, { "name": "stderr", "output_type": "stream", "text": [ "c:\\Users\\vsavelev\\AppData\\Local\\anaconda3\\Lib\\contextlib.py:158: UserWarning: Your input ran out of data; interrupting training. Make sure that your dataset or generator can generate at least `steps_per_epoch * epochs` batches. You may need to use the `.repeat()` function when building your dataset.\n", " self.gen.throw(typ, value, traceback)\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 11ms/step - accuracy: 0.8438 - loss: 0.8550 - val_accuracy: 1.0000 - val_loss: 0.4526\n", "Epoch 3/10\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m146s\u001b[0m 2s/step - accuracy: 0.8873 - loss: 0.6507 - val_accuracy: 0.8879 - val_loss: 0.4938\n", "Epoch 4/10\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m2s\u001b[0m 6ms/step - accuracy: 0.8750 - loss: 0.5112 - val_accuracy: 0.8000 - val_loss: 0.6924\n", "Epoch 5/10\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m149s\u001b[0m 2s/step - accuracy: 0.8857 - loss: 0.5040 - val_accuracy: 0.8879 - val_loss: 0.4471\n", "Epoch 6/10\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m2s\u001b[0m 6ms/step - accuracy: 0.8750 - loss: 0.4322 - val_accuracy: 0.8000 - val_loss: 0.6413\n", "Epoch 7/10\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m166s\u001b[0m 2s/step - accuracy: 0.8926 - loss: 0.4378 - val_accuracy: 0.8860 - val_loss: 0.4396\n", "Epoch 8/10\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m2s\u001b[0m 7ms/step - accuracy: 0.9375 - loss: 0.3477 - val_accuracy: 1.0000 - val_loss: 0.2697\n", "Epoch 9/10\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m173s\u001b[0m 3s/step - accuracy: 0.8890 - loss: 0.4132 - val_accuracy: 0.8879 - val_loss: 0.4444\n", "Epoch 10/10\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m2s\u001b[0m 7ms/step - accuracy: 0.9688 - loss: 0.3492 - val_accuracy: 0.8000 - val_loss: 0.5367\n" ] } ], "source": [ "history = model.fit(\n", " train_generator,\n", " steps_per_epoch=train_generator.samples // train_generator.batch_size,\n", " validation_data=validation_generator,\n", " validation_steps=validation_generator.samples // validation_generator.batch_size,\n", " epochs=10\n", ")\n", "\n", "#This specifies the number of complete passes through the training dataset. Here, the model will train for 10 epochs." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Model compiled successfully.\n", "Callbacks created successfully.\n", "Epoch 1/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m267s\u001b[0m 4s/step - accuracy: 0.8686 - loss: 0.6860 - val_accuracy: 0.8897 - val_loss: 0.5719\n", "Epoch 2/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 9ms/step - accuracy: 0.8750 - loss: 0.6368 - val_accuracy: 0.6000 - val_loss: 0.7365\n", "Epoch 3/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m286s\u001b[0m 4s/step - accuracy: 0.8856 - loss: 0.6046 - val_accuracy: 0.8897 - val_loss: 0.6145\n", "Epoch 4/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m5s\u001b[0m 9ms/step - accuracy: 0.8438 - loss: 0.5484 - val_accuracy: 0.6000 - val_loss: 0.7114\n", "Epoch 5/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m323s\u001b[0m 5s/step - accuracy: 0.8840 - loss: 0.5082 - val_accuracy: 0.8860 - val_loss: 0.5360\n", "Epoch 6/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 7ms/step - accuracy: 0.8750 - loss: 0.4655 - val_accuracy: 1.0000 - val_loss: 0.4314\n", "Epoch 7/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m299s\u001b[0m 4s/step - accuracy: 0.8770 - loss: 0.4487 - val_accuracy: 0.8879 - val_loss: 0.4546\n", "Epoch 8/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 6ms/step - accuracy: 0.9375 - loss: 0.3386 - val_accuracy: 0.8000 - val_loss: 0.5507\n", "Epoch 9/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m276s\u001b[0m 4s/step - accuracy: 0.8946 - loss: 0.3906 - val_accuracy: 0.8879 - val_loss: 0.4199\n", "Epoch 10/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 7ms/step - accuracy: 0.8438 - loss: 0.4413 - val_accuracy: 0.8000 - val_loss: 0.5716\n", "Epoch 11/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m293s\u001b[0m 4s/step - accuracy: 0.8840 - loss: 0.3867 - val_accuracy: 0.8860 - val_loss: 0.4153\n", "Epoch 12/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 7ms/step - accuracy: 0.9565 - loss: 0.2604 - val_accuracy: 1.0000 - val_loss: 0.2164\n", "Epoch 13/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m290s\u001b[0m 4s/step - accuracy: 0.8951 - loss: 0.3485 - val_accuracy: 0.8915 - val_loss: 0.3989\n", "Epoch 14/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 7ms/step - accuracy: 0.9375 - loss: 0.2921 - val_accuracy: 0.4000 - val_loss: 1.3309\n", "Epoch 15/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m273s\u001b[0m 4s/step - accuracy: 0.8950 - loss: 0.3413 - val_accuracy: 0.8860 - val_loss: 0.4095\n", "Epoch 16/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 7ms/step - accuracy: 0.9062 - loss: 0.3136 - val_accuracy: 1.0000 - val_loss: 0.1990\n", "Epoch 17/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m270s\u001b[0m 4s/step - accuracy: 0.8919 - loss: 0.3295 - val_accuracy: 0.8860 - val_loss: 0.4054\n", "Epoch 18/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 8ms/step - accuracy: 0.8750 - loss: 0.3683 - val_accuracy: 1.0000 - val_loss: 0.1749\n", "Epoch 19/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m279s\u001b[0m 4s/step - accuracy: 0.8906 - loss: 0.3170 - val_accuracy: 0.8860 - val_loss: 0.4067\n", "Epoch 20/20\n", "\u001b[1m68/68\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m4s\u001b[0m 8ms/step - accuracy: 0.9375 - loss: 0.2421 - val_accuracy: 1.0000 - val_loss: 0.2140\n" ] } ], "source": [ "from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint\n", "\n", "\n", "# Unfreeze some layers\n", "base_model.trainable = True\n", "fine_tune_at = 100 # fine-tune from this layer onwards\n", "\n", "for layer in base_model.layers[:fine_tune_at]:\n", " layer.trainable = False\n", "\n", "# # Unfreeze more layers gradually\n", "# for layer in base_model.layers[:-10]: # Unfreeze all layers except the last 10 layers\n", "# layer.trainable = False\n", "\n", "model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=1e-6), #learning_rate=1e-5\n", " loss='categorical_crossentropy', #The categorical cross-entropy loss function is used because this is a multi-class classification problem\n", " metrics=['accuracy'])\n", "\n", "print(\"Model compiled successfully.\")\n", "\n", "\"\"\"\n", "The Adam optimizer is used with a very small learning rate (1e-5). Fine-tuning typically \n", "uses a smaller learning rate to prevent large updates to the weights, which could potentially destroy the learned features in the pre-trained model.\n", "\"\"\"\n", "\n", "early_stopping = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)\n", "model_checkpoint = ModelCheckpoint('best_model.keras', save_best_only=True, monitor='val_loss')\n", "\n", "print(\"Callbacks created successfully.\")\n", "\n", "\n", "history_fine = model.fit(\n", " train_generator,\n", " steps_per_epoch=train_generator.samples // train_generator.batch_size,\n", " validation_data=validation_generator,\n", " validation_steps=validation_generator.samples // validation_generator.batch_size,\n", " epochs=20,\n", " #callbacks=[early_stopping, model_checkpoint]\n", ")\n" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[1m18/18\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m22s\u001b[0m 1s/step - accuracy: 0.8704 - loss: 0.4312\n", "Validation loss: 0.4050602614879608\n", "Validation accuracy: 0.8870673775672913\n" ] } ], "source": [ "loss, accuracy = model.evaluate(validation_generator)\n", "print(f'Validation loss: {loss}')\n", "print(f'Validation accuracy: {accuracy}')\n" ] } ], "metadata": { "kernelspec": { "display_name": "base", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.7" } }, "nbformat": 4, "nbformat_minor": 2 }