{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Лабораторная 11"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Задача: Мультиклассовая классификация изображений на 5 категорий (daisy, dandelion, rose, sunflower, tulip)\n",
    "\n",
    "Ссылка на датасет: https://www.kaggle.com/datasets/rahmasleam/flowers-dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 2890656 characters]",
      "text/plain": [
       "<Figure size 1500x1500 with 10 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import os\n",
    "import cv2\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def load_images_from_folder(folder, target_size=(512, 512)):\n",
    "    images = []\n",
    "    labels = []\n",
    "    for label in os.listdir(folder):\n",
    "        if label in ['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']:\n",
    "            label_folder = os.path.join(folder, label)\n",
    "            if os.path.isdir(label_folder):\n",
    "                for filename in os.listdir(label_folder):\n",
    "                    img_path = os.path.join(label_folder, filename)\n",
    "                    img = cv2.imread(img_path)\n",
    "                    if img is not None:\n",
    "                        img_resized = cv2.resize(img, target_size)\n",
    "                        images.append(img_resized)\n",
    "                        labels.append(label)\n",
    "    return images, labels\n",
    "\n",
    "folder_path = \"./static/csv/dataset_flower\"\n",
    "images, labels = load_images_from_folder(folder_path)\n",
    "num_images_to_display = min(8, len(images))\n",
    "\n",
    "def display_images(images, labels, max_images=10):\n",
    "    if not images:\n",
    "        print(\"Нет изображений для отображения.\")\n",
    "        return\n",
    "    \n",
    "    count = min(max_images, len(images))\n",
    "    cols = 4\n",
    "    rows = (count + cols - 1) // cols\n",
    "\n",
    "    plt.figure(figsize=(15, 5 * rows))\n",
    "    for i in range(count):\n",
    "        plt.subplot(rows, cols, i + 1)\n",
    "        plt.imshow(cv2.cvtColor(images[i], cv2.COLOR_BGR2RGB))\n",
    "        plt.title(labels[i])\n",
    "        plt.axis('off')\n",
    "    plt.tight_layout()\n",
    "    plt.show()\n",
    "\n",
    "images, labels = load_images_from_folder(folder_path)\n",
    "display_images(images, labels)\n",
    "\n",
    "# Преобразование в массивы\n",
    "images = np.array(images)  \n",
    "labels = np.array(labels)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Предобработка изображений"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 523560 characters]",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def preprocess_images(images):\n",
    "    processed_images = []\n",
    "    for img in images:\n",
    "        img_resized = cv2.resize(img, (128, 128))\n",
    "        img_gray = cv2.cvtColor(img_resized, cv2.COLOR_BGR2GRAY)\n",
    "        img_eq = cv2.equalizeHist(img_gray)\n",
    "        img_bin = cv2.adaptiveThreshold(img_eq, 255,  cv2.ADAPTIVE_THRESH_GAUSSIAN_C, \n",
    "    cv2.THRESH_BINARY, 11, 2)\n",
    "        processed_images.append(img_bin)\n",
    "    return np.array(processed_images)\n",
    "\n",
    "processed_images = preprocess_images(images)\n",
    "\n",
    "def display_single_image(original, processed, index):\n",
    "    plt.figure(figsize=(10, 5))\n",
    "    \n",
    "    plt.subplot(1, 2, 1)\n",
    "    plt.imshow(cv2.cvtColor(original[index], cv2.COLOR_BGR2RGB))\n",
    "    plt.title('Оригинальное изображение')\n",
    "    plt.axis('off')\n",
    "\n",
    "    plt.subplot(1, 2, 2)\n",
    "    plt.imshow(processed[index], cmap='gray')\n",
    "    plt.title('Обработанное изображение')\n",
    "    plt.axis('off')\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "index = 0  \n",
    "display_single_image(images, processed_images, index)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Глубокое обучение"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train dataset and validation dataset successfully prepared.\n",
      "Train shape: (917, 128, 128, 3), Validation shape: (230, 128, 128, 3)\n",
      "Классы: ['daisy' 'dandelion']\n"
     ]
    }
   ],
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.preprocessing import LabelEncoder\n",
    "from tensorflow.keras.utils import to_categorical\n",
    "import tensorflow as tf\n",
    "import matplotlib.pyplot as plt\n",
    "from matplotlib.image import imread\n",
    "import os\n",
    "from pathlib import Path\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import cv2\n",
    "\n",
    "PIC_SIZE = 128\n",
    "BATCH_SIZE = 8\n",
    "\n",
    "def load_and_label_images_from_directory(directory, target_size=(PIC_SIZE, PIC_SIZE), allowed_labels=None):\n",
    "    images = []\n",
    "    labels = []\n",
    "    \n",
    "    if allowed_labels is None:\n",
    "        allowed_labels = ['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']\n",
    "    \n",
    "    for label in os.listdir(directory):\n",
    "        if label in allowed_labels:\n",
    "            label_folder = os.path.join(directory, label)\n",
    "            if os.path.isdir(label_folder):\n",
    "                for filename in os.listdir(label_folder):\n",
    "                    img_path = os.path.join(label_folder, filename)\n",
    "                    if img_path.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')):\n",
    "                        img = cv2.imread(img_path)\n",
    "                        if img is not None:\n",
    "                            img_resized = cv2.resize(img, target_size, interpolation=cv2.INTER_AREA)\n",
    "                            images.append(img_resized)\n",
    "                            labels.append(label)\n",
    "    \n",
    "    return np.array(images), np.array(labels)\n",
    "\n",
    "\n",
    "train_images_raw, train_labels_raw = load_and_label_images_from_directory(folder_path)\n",
    "\n",
    "def preprocess_images(images):\n",
    "    processed_images = []\n",
    "    \n",
    "    for image in images:\n",
    "        normalized_image = image.astype(np.float32) / 255.0\n",
    "        processed_images.append(normalized_image)\n",
    "\n",
    "    return np.array(processed_images)\n",
    "\n",
    "processed_images = preprocess_images(train_images_raw)\n",
    "\n",
    "label_encoder = LabelEncoder()\n",
    "labels_encoded = label_encoder.fit_transform(train_labels_raw)\n",
    "labels_categorical = tf.keras.utils.to_categorical(labels_encoded)\n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(processed_images, labels_categorical, test_size=0.2, random_state=42)\n",
    "\n",
    "train_ds = tf.data.Dataset.from_tensor_slices((X_train, y_train))\n",
    "val_ds = tf.data.Dataset.from_tensor_slices((X_test, y_test))\n",
    "\n",
    "def convert_to_grayscale(image, label):\n",
    "    image = tf.image.rgb_to_grayscale(image)\n",
    "    return image, label\n",
    "\n",
    "train_ds = train_ds.map(convert_to_grayscale)\n",
    "val_ds = val_ds.map(convert_to_grayscale)\n",
    "\n",
    "train_ds = train_ds.shuffle(1000).batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\n",
    "val_ds = val_ds.batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\n",
    "\n",
    "print(\"Train dataset and validation dataset successfully prepared.\")\n",
    "print(f\"Train shape: {X_train.shape}, Validation shape: {X_test.shape}\")\n",
    "print(f\"Классы: {label_encoder.classes_}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Обучение модели"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Reloading Tuner from D:/ktune\\image_classifier\\tuner0.json\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "d:\\3_КУРС_ПИ\\МИИ\\aisenv\\Lib\\site-packages\\keras\\src\\layers\\convolutional\\base_conv.py:107: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.\n",
      "  super().__init__(activity_regularizer=activity_regularizer, **kwargs)\n",
      "d:\\3_КУРС_ПИ\\МИИ\\aisenv\\Lib\\site-packages\\keras\\src\\saving\\saving_lib.py:757: UserWarning: Skipping variable loading for optimizer 'adam', because it has 2 variables whereas the saved optimizer has 22 variables. \n",
      "  saveable.load_own_variables(weights_store.get(inner_path))\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[1m8/8\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m2s\u001b[0m 159ms/step - accuracy: 0.7665 - loss: 0.5344\n",
      "Тестовая точность: 77.39%\n"
     ]
    }
   ],
   "source": [
    "from keras_tuner import HyperModel, RandomSearch\n",
    "from tensorflow.keras import layers # type: ignore\n",
    "import tensorflow as tf\n",
    "\n",
    "\n",
    "class ImageClassifier(HyperModel):\n",
    "    def build(self, hp):\n",
    "        model = tf.keras.Sequential([ # type: ignore\n",
    "            layers.Conv2D(hp.Int('filters_1', 32, 128, step=32), (3, 3), activation='relu', padding='same', kernel_initializer=\"he_normal\", input_shape=(PIC_SIZE, PIC_SIZE, 3)),\n",
    "            layers.MaxPooling2D(pool_size=(2, 2)),\n",
    "\n",
    "            layers.Conv2D(hp.Int('filters_2', 32, 128, step=32), (3, 3), activation='relu', padding='same', kernel_initializer=\"he_normal\"),\n",
    "            layers.MaxPooling2D(pool_size=(2, 2)),\n",
    "\n",
    "            layers.Conv2D(hp.Int('filters_3', 64, 256, step=64), (3, 3), activation='relu', padding='same', kernel_initializer=\"he_normal\"),\n",
    "            layers.MaxPooling2D(pool_size=(2, 2)),\n",
    "\n",
    "            layers.GlobalAveragePooling2D(),\n",
    "            layers.Dense(hp.Int('units', 64, 256, step=64), activation='relu', kernel_initializer=\"he_normal\"),\n",
    "            layers.Dropout(hp.Float('dropout', 0.1, 0.5, step=0.1)),\n",
    "            layers.Dense(len(label_encoder.classes_), activation='softmax')\n",
    "        ])\n",
    "\n",
    "        model.compile(\n",
    "            optimizer=tf.keras.optimizers.Adam(hp.Float('learning_rate', 1e-4, 1e-2, sampling='LOG')), # type: ignore\n",
    "            loss='categorical_crossentropy',\n",
    "            metrics=['accuracy']\n",
    "        )\n",
    "\n",
    "        return model\n",
    "\n",
    "tuner = RandomSearch(\n",
    "    ImageClassifier(),\n",
    "    objective=\"val_accuracy\",\n",
    "    max_trials=5,\n",
    "    executions_per_trial=1,\n",
    "    directory=\"D:/ktune\",\n",
    "    project_name=\"image_classifier\",\n",
    ")\n",
    "\n",
    "tuner.search(X_train, y_train, epochs=5, validation_split=0.2)\n",
    "\n",
    "best_model = tuner.get_best_models(num_models=1)[0]\n",
    "\n",
    "test_loss, test_acc = best_model.evaluate(X_test, y_test)\n",
    "print(f\"Тестовая точность: {test_acc * 100:.2f}%\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Информация о лучшей модели"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\">Model: \"sequential\"</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[1mModel: \"sequential\"\u001b[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
       "┃<span style=\"font-weight: bold\"> Layer (type)                    </span>┃<span style=\"font-weight: bold\"> Output Shape           </span>┃<span style=\"font-weight: bold\">       Param # </span>┃\n",
       "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
       "│ conv2d (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Conv2D</span>)                 │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">128</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">128</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">128</span>)  │         <span style=\"color: #00af00; text-decoration-color: #00af00\">3,584</span> │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ max_pooling2d (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">MaxPooling2D</span>)    │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">64</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">64</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">128</span>)    │             <span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ conv2d_1 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Conv2D</span>)               │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">64</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">64</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">96</span>)     │       <span style=\"color: #00af00; text-decoration-color: #00af00\">110,688</span> │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ max_pooling2d_1 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">MaxPooling2D</span>)  │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">32</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">32</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">96</span>)     │             <span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ conv2d_2 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Conv2D</span>)               │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">32</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">32</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">192</span>)    │       <span style=\"color: #00af00; text-decoration-color: #00af00\">166,080</span> │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ max_pooling2d_2 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">MaxPooling2D</span>)  │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">16</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">16</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">192</span>)    │             <span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ global_average_pooling2d        │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">192</span>)            │             <span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> │\n",
       "│ (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">GlobalAveragePooling2D</span>)        │                        │               │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ dense (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Dense</span>)                   │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">64</span>)             │        <span style=\"color: #00af00; text-decoration-color: #00af00\">12,352</span> │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ dropout (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Dropout</span>)               │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">64</span>)             │             <span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ dense_1 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Dense</span>)                 │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">2</span>)              │           <span style=\"color: #00af00; text-decoration-color: #00af00\">130</span> │\n",
       "└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
       "</pre>\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",
       "│ conv2d (\u001b[38;5;33mConv2D\u001b[0m)                 │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m128\u001b[0m, \u001b[38;5;34m128\u001b[0m, \u001b[38;5;34m128\u001b[0m)  │         \u001b[38;5;34m3,584\u001b[0m │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ max_pooling2d (\u001b[38;5;33mMaxPooling2D\u001b[0m)    │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m64\u001b[0m, \u001b[38;5;34m64\u001b[0m, \u001b[38;5;34m128\u001b[0m)    │             \u001b[38;5;34m0\u001b[0m │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ conv2d_1 (\u001b[38;5;33mConv2D\u001b[0m)               │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m64\u001b[0m, \u001b[38;5;34m64\u001b[0m, \u001b[38;5;34m96\u001b[0m)     │       \u001b[38;5;34m110,688\u001b[0m │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ max_pooling2d_1 (\u001b[38;5;33mMaxPooling2D\u001b[0m)  │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m32\u001b[0m, \u001b[38;5;34m32\u001b[0m, \u001b[38;5;34m96\u001b[0m)     │             \u001b[38;5;34m0\u001b[0m │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ conv2d_2 (\u001b[38;5;33mConv2D\u001b[0m)               │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m32\u001b[0m, \u001b[38;5;34m32\u001b[0m, \u001b[38;5;34m192\u001b[0m)    │       \u001b[38;5;34m166,080\u001b[0m │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ max_pooling2d_2 (\u001b[38;5;33mMaxPooling2D\u001b[0m)  │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m16\u001b[0m, \u001b[38;5;34m16\u001b[0m, \u001b[38;5;34m192\u001b[0m)    │             \u001b[38;5;34m0\u001b[0m │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ global_average_pooling2d        │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m192\u001b[0m)            │             \u001b[38;5;34m0\u001b[0m │\n",
       "│ (\u001b[38;5;33mGlobalAveragePooling2D\u001b[0m)        │                        │               │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ dense (\u001b[38;5;33mDense\u001b[0m)                   │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m64\u001b[0m)             │        \u001b[38;5;34m12,352\u001b[0m │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ dropout (\u001b[38;5;33mDropout\u001b[0m)               │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m64\u001b[0m)             │             \u001b[38;5;34m0\u001b[0m │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ dense_1 (\u001b[38;5;33mDense\u001b[0m)                 │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m2\u001b[0m)              │           \u001b[38;5;34m130\u001b[0m │\n",
       "└─────────────────────────────────┴────────────────────────┴───────────────┘\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\"> Total params: </span><span style=\"color: #00af00; text-decoration-color: #00af00\">292,834</span> (1.12 MB)\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m292,834\u001b[0m (1.12 MB)\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\"> Trainable params: </span><span style=\"color: #00af00; text-decoration-color: #00af00\">292,834</span> (1.12 MB)\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m292,834\u001b[0m (1.12 MB)\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\"> Non-trainable params: </span><span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> (0.00 B)\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "best_model = tuner.get_best_models(num_models=1)[0]\n",
    "best_model.summary()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Качество предсказаний для каждого класса"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:5 out of the last 17 calls to <function TensorFlowTrainer.make_predict_function.<locals>.one_step_on_data_distributed at 0x00000202A83FF1A0> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.\n",
      "\u001b[1m8/8\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 175ms/step\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 903788 characters]",
      "text/plain": [
       "<Figure size 2000x500 with 5 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "X_train, X_val, y_train, y_val = train_test_split(processed_images, labels_categorical, test_size=0.2, random_state=42)\n",
    "\n",
    "tuner.search(X_train, y_train, epochs=20, validation_data=(X_val, y_val))\n",
    "\n",
    "best_model = tuner.get_best_models(num_models=1)[0]\n",
    "\n",
    "y_pred = best_model.predict(X_val, batch_size=32)\n",
    "\n",
    "prediction = y_pred[0:5]\n",
    "\n",
    "images_list = X_val[0:5]\n",
    "plt.figure(figsize=(20, 5))  \n",
    "\n",
    "predict_label = []\n",
    "\n",
    "for i in range(5):\n",
    "    plt.subplot(1, 5, i + 1)  \n",
    "    image = images_list[i]  \n",
    "    \n",
    "    plt.imshow(image)  \n",
    "\n",
    "    prob = prediction[i]  \n",
    "    class_idx = np.argmax(prob)\n",
    "    label = label_encoder.inverse_transform([class_idx])[0]  \n",
    "    confidence = prob[class_idx]  \n",
    "    \n",
    "    predict_label.append(f\"{label}: {confidence * 100:.2f}%\")\n",
    "    plt.title(predict_label[i], fontsize=16)\n",
    "    plt.axis('off')\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Оценка модели\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m32s\u001b[0m 1s/step - accuracy: 0.7739 - loss: 0.4661 - val_accuracy: 0.7826 - val_loss: 0.4797\n",
      "Epoch 2/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m29s\u001b[0m 1s/step - accuracy: 0.7816 - loss: 0.4553 - val_accuracy: 0.8348 - val_loss: 0.3860\n",
      "Epoch 3/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m30s\u001b[0m 1s/step - accuracy: 0.8118 - loss: 0.3863 - val_accuracy: 0.8000 - val_loss: 0.4420\n",
      "Epoch 4/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m28s\u001b[0m 980ms/step - accuracy: 0.8402 - loss: 0.3704 - val_accuracy: 0.8217 - val_loss: 0.3635\n",
      "Epoch 5/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m29s\u001b[0m 996ms/step - accuracy: 0.8669 - loss: 0.3318 - val_accuracy: 0.8478 - val_loss: 0.3594\n",
      "Epoch 6/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m29s\u001b[0m 987ms/step - accuracy: 0.8699 - loss: 0.3314 - val_accuracy: 0.8609 - val_loss: 0.3477\n",
      "Epoch 7/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m32s\u001b[0m 1s/step - accuracy: 0.8842 - loss: 0.2958 - val_accuracy: 0.8391 - val_loss: 0.3366\n",
      "Epoch 8/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m31s\u001b[0m 1s/step - accuracy: 0.8505 - loss: 0.3168 - val_accuracy: 0.8174 - val_loss: 0.4497\n",
      "Epoch 9/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m29s\u001b[0m 1s/step - accuracy: 0.8643 - loss: 0.3027 - val_accuracy: 0.8087 - val_loss: 0.4310\n",
      "Epoch 10/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m33s\u001b[0m 1s/step - accuracy: 0.8578 - loss: 0.3034 - val_accuracy: 0.8348 - val_loss: 0.3297\n",
      "Epoch 11/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m29s\u001b[0m 1s/step - accuracy: 0.8906 - loss: 0.2508 - val_accuracy: 0.8391 - val_loss: 0.3221\n",
      "Epoch 12/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m29s\u001b[0m 1s/step - accuracy: 0.8818 - loss: 0.2755 - val_accuracy: 0.8652 - val_loss: 0.3187\n",
      "Epoch 13/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m28s\u001b[0m 979ms/step - accuracy: 0.8925 - loss: 0.2798 - val_accuracy: 0.8565 - val_loss: 0.3755\n",
      "Epoch 14/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m29s\u001b[0m 1s/step - accuracy: 0.8694 - loss: 0.2912 - val_accuracy: 0.8565 - val_loss: 0.3089\n",
      "Epoch 15/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m29s\u001b[0m 1s/step - accuracy: 0.8829 - loss: 0.2856 - val_accuracy: 0.8739 - val_loss: 0.3000\n",
      "Epoch 16/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m29s\u001b[0m 1s/step - accuracy: 0.9090 - loss: 0.2478 - val_accuracy: 0.8261 - val_loss: 0.4229\n",
      "Epoch 17/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m27s\u001b[0m 926ms/step - accuracy: 0.8945 - loss: 0.2768 - val_accuracy: 0.8739 - val_loss: 0.2947\n",
      "Epoch 18/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m27s\u001b[0m 945ms/step - accuracy: 0.8962 - loss: 0.2618 - val_accuracy: 0.8696 - val_loss: 0.3015\n",
      "Epoch 19/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m28s\u001b[0m 951ms/step - accuracy: 0.9160 - loss: 0.2169 - val_accuracy: 0.8739 - val_loss: 0.3186\n",
      "Epoch 20/20\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m28s\u001b[0m 958ms/step - accuracy: 0.8984 - loss: 0.2407 - val_accuracy: 0.8652 - val_loss: 0.3282\n",
      "\u001b[1m8/8\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m2s\u001b[0m 196ms/step - accuracy: 0.7665 - loss: 0.5344\n",
      "Тестовая точность: 77.39%\n",
      "\u001b[1m29/29\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m6s\u001b[0m 203ms/step - accuracy: 0.7918 - loss: 0.4727\n",
      "Train Loss: 0.4662, Train Accuracy: 0.7972\n",
      "\u001b[1m8/8\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m2s\u001b[0m 196ms/step - accuracy: 0.7665 - loss: 0.5344\n",
      "Validation Loss: 0.5100, Validation Accuracy: 0.7739\n",
      "\u001b[1m8/8\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m2s\u001b[0m 201ms/step\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 51812 characters]",
      "text/plain": [
       "<Figure size 1500x800 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "tuner.search(X_train, y_train, epochs=20, validation_split=0.2)\n",
    "history = best_model.fit(X_train, y_train, epochs=20, validation_data=(X_val, y_val))\n",
    "\n",
    "best_model = tuner.get_best_models(num_models=1)[0]\n",
    "\n",
    "test_loss, test_acc = best_model.evaluate(X_test, y_test)\n",
    "print(f\"Тестовая точность: {test_acc * 100:.2f}%\")\n",
    "\n",
    "train_loss, train_acc = best_model.evaluate(X_train, y_train)\n",
    "print(f\"Train Loss: {train_loss:.4f}, Train Accuracy: {train_acc:.4f}\")\n",
    "\n",
    "val_loss, val_acc = best_model.evaluate(X_val, y_val)\n",
    "print(f\"Validation Loss: {val_loss:.4f}, Validation Accuracy: {val_acc:.4f}\")\n",
    "\n",
    "predictions = best_model.predict(X_val)\n",
    "\n",
    "predicted_classes = np.argmax(predictions, axis=1)\n",
    "predicted_confidences = np.max(predictions, axis=1)  \n",
    "pd.DataFrame(history.history).plot(figsize=(15,8)) # type: ignore\n",
    "plt.grid(True)\n",
    "plt.gca().set_ylim(0,4)\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "aisenv",
   "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.12.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
