Files
AIM-PIbd-32-kuznetsov-A-V/lab11/lab11.ipynb
2025-05-16 21:08:29 +04:00

4.2 MiB
Raw Blame History

Лабораторная 11

Задача: Мультиклассовая классификация изображений на 5 категорий (daisy, dandelion, rose, sunflower, tulip)

Ссылка на датасет: https://www.kaggle.com/datasets/rahmasleam/flowers-dataset

In [24]:
import os
import cv2
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

def load_images_from_folder(folder, target_size=(512, 512)):
    images = []
    labels = []
    for label in os.listdir(folder):
        if label in ['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']:
            label_folder = os.path.join(folder, label)
            if os.path.isdir(label_folder):
                for filename in os.listdir(label_folder):
                    img_path = os.path.join(label_folder, filename)
                    img = cv2.imread(img_path)
                    if img is not None:
                        img_resized = cv2.resize(img, target_size)
                        images.append(img_resized)
                        labels.append(label)
    return images, labels

folder_path = "./static/csv/dataset_flower"
images, labels = load_images_from_folder(folder_path)
num_images_to_display = min(8, len(images))

def display_images(images, labels, max_images=10):
    if not images:
        print("Нет изображений для отображения.")
        return
    
    count = min(max_images, len(images))
    cols = 4
    rows = (count + cols - 1) // cols

    plt.figure(figsize=(15, 5 * rows))
    for i in range(count):
        plt.subplot(rows, cols, i + 1)
        plt.imshow(cv2.cvtColor(images[i], cv2.COLOR_BGR2RGB))
        plt.title(labels[i])
        plt.axis('off')
    plt.tight_layout()
    plt.show()

images, labels = load_images_from_folder(folder_path)
display_images(images, labels)

# Преобразование в массивы
images = np.array(images)  
labels = np.array(labels)
No description has been provided for this image

Предобработка изображений

In [25]:
import cv2
import numpy as np
import matplotlib.pyplot as plt

def preprocess_images(images):
    processed_images = []
    for img in images:
        img_resized = cv2.resize(img, (128, 128))
        img_gray = cv2.cvtColor(img_resized, cv2.COLOR_BGR2GRAY)
        img_eq = cv2.equalizeHist(img_gray)
        img_bin = cv2.adaptiveThreshold(img_eq, 255,  cv2.ADAPTIVE_THRESH_GAUSSIAN_C, 
    cv2.THRESH_BINARY, 11, 2)
        processed_images.append(img_bin)
    return np.array(processed_images)

processed_images = preprocess_images(images)

def display_single_image(original, processed, index):
    plt.figure(figsize=(10, 5))
    
    plt.subplot(1, 2, 1)
    plt.imshow(cv2.cvtColor(original[index], cv2.COLOR_BGR2RGB))
    plt.title('Оригинальное изображение')
    plt.axis('off')

    plt.subplot(1, 2, 2)
    plt.imshow(processed[index], cmap='gray')
    plt.title('Обработанное изображение')
    plt.axis('off')

    plt.show()

index = 0  
display_single_image(images, processed_images, index)
No description has been provided for this image

Глубокое обучение

In [26]:
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from tensorflow.keras.utils import to_categorical
import tensorflow as tf
import matplotlib.pyplot as plt
from matplotlib.image import imread
import os
from pathlib import Path
import numpy as np
import pandas as pd
import cv2

PIC_SIZE = 128
BATCH_SIZE = 8

def load_and_label_images_from_directory(directory, target_size=(PIC_SIZE, PIC_SIZE), allowed_labels=None):
    images = []
    labels = []
    
    if allowed_labels is None:
        allowed_labels = ['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']
    
    for label in os.listdir(directory):
        if label in allowed_labels:
            label_folder = os.path.join(directory, label)
            if os.path.isdir(label_folder):
                for filename in os.listdir(label_folder):
                    img_path = os.path.join(label_folder, filename)
                    if img_path.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')):
                        img = cv2.imread(img_path)
                        if img is not None:
                            img_resized = cv2.resize(img, target_size, interpolation=cv2.INTER_AREA)
                            images.append(img_resized)
                            labels.append(label)
    
    return np.array(images), np.array(labels)


train_images_raw, train_labels_raw = load_and_label_images_from_directory(folder_path)

def preprocess_images(images):
    processed_images = []
    
    for image in images:
        normalized_image = image.astype(np.float32) / 255.0
        processed_images.append(normalized_image)

    return np.array(processed_images)

processed_images = preprocess_images(train_images_raw)

label_encoder = LabelEncoder()
labels_encoded = label_encoder.fit_transform(train_labels_raw)
labels_categorical = tf.keras.utils.to_categorical(labels_encoded)

X_train, X_test, y_train, y_test = train_test_split(processed_images, labels_categorical, test_size=0.2, random_state=42)

train_ds = tf.data.Dataset.from_tensor_slices((X_train, y_train))
val_ds = tf.data.Dataset.from_tensor_slices((X_test, y_test))

def convert_to_grayscale(image, label):
    image = tf.image.rgb_to_grayscale(image)
    return image, label

train_ds = train_ds.map(convert_to_grayscale)
val_ds = val_ds.map(convert_to_grayscale)

train_ds = train_ds.shuffle(1000).batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)
val_ds = val_ds.batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)

print("Train dataset and validation dataset successfully prepared.")
print(f"Train shape: {X_train.shape}, Validation shape: {X_test.shape}")
print(f"Классы: {label_encoder.classes_}")
Train dataset and validation dataset successfully prepared.
Train shape: (917, 128, 128, 3), Validation shape: (230, 128, 128, 3)
Классы: ['daisy' 'dandelion']

Обучение модели

In [27]:
from keras_tuner import HyperModel, RandomSearch
from tensorflow.keras import layers # type: ignore
import tensorflow as tf


class ImageClassifier(HyperModel):
    def build(self, hp):
        model = tf.keras.Sequential([ # type: ignore
            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)),
            layers.MaxPooling2D(pool_size=(2, 2)),

            layers.Conv2D(hp.Int('filters_2', 32, 128, step=32), (3, 3), activation='relu', padding='same', kernel_initializer="he_normal"),
            layers.MaxPooling2D(pool_size=(2, 2)),

            layers.Conv2D(hp.Int('filters_3', 64, 256, step=64), (3, 3), activation='relu', padding='same', kernel_initializer="he_normal"),
            layers.MaxPooling2D(pool_size=(2, 2)),

            layers.GlobalAveragePooling2D(),
            layers.Dense(hp.Int('units', 64, 256, step=64), activation='relu', kernel_initializer="he_normal"),
            layers.Dropout(hp.Float('dropout', 0.1, 0.5, step=0.1)),
            layers.Dense(len(label_encoder.classes_), activation='softmax')
        ])

        model.compile(
            optimizer=tf.keras.optimizers.Adam(hp.Float('learning_rate', 1e-4, 1e-2, sampling='LOG')), # type: ignore
            loss='categorical_crossentropy',
            metrics=['accuracy']
        )

        return model

tuner = RandomSearch(
    ImageClassifier(),
    objective="val_accuracy",
    max_trials=5,
    executions_per_trial=1,
    directory="D:/ktune",
    project_name="image_classifier",
)

tuner.search(X_train, y_train, epochs=5, validation_split=0.2)

best_model = tuner.get_best_models(num_models=1)[0]

test_loss, test_acc = best_model.evaluate(X_test, y_test)
print(f"Тестовая точность: {test_acc * 100:.2f}%")
Reloading Tuner from D:/ktune\image_classifier\tuner0.json
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.
  super().__init__(activity_regularizer=activity_regularizer, **kwargs)
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. 
  saveable.load_own_variables(weights_store.get(inner_path))
8/8 ━━━━━━━━━━━━━━━━━━━━ 2s 159ms/step - accuracy: 0.7665 - loss: 0.5344
Тестовая точность: 77.39%

Информация о лучшей модели

In [28]:
best_model = tuner.get_best_models(num_models=1)[0]
best_model.summary()
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                     Output Shape                  Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d (Conv2D)                 │ (None, 128, 128, 128)  │         3,584 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d (MaxPooling2D)    │ (None, 64, 64, 128)    │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_1 (Conv2D)               │ (None, 64, 64, 96)     │       110,688 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_1 (MaxPooling2D)  │ (None, 32, 32, 96)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_2 (Conv2D)               │ (None, 32, 32, 192)    │       166,080 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_2 (MaxPooling2D)  │ (None, 16, 16, 192)    │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ global_average_pooling2d        │ (None, 192)            │             0 │
│ (GlobalAveragePooling2D)        │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense (Dense)                   │ (None, 64)             │        12,352 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout (Dropout)               │ (None, 64)             │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_1 (Dense)                 │ (None, 2)              │           130 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 292,834 (1.12 MB)
 Trainable params: 292,834 (1.12 MB)
 Non-trainable params: 0 (0.00 B)

Качество предсказаний для каждого класса

In [29]:
X_train, X_val, y_train, y_val = train_test_split(processed_images, labels_categorical, test_size=0.2, random_state=42)

tuner.search(X_train, y_train, epochs=20, validation_data=(X_val, y_val))

best_model = tuner.get_best_models(num_models=1)[0]

y_pred = best_model.predict(X_val, batch_size=32)

prediction = y_pred[0:5]

images_list = X_val[0:5]
plt.figure(figsize=(20, 5))  

predict_label = []

for i in range(5):
    plt.subplot(1, 5, i + 1)  
    image = images_list[i]  
    
    plt.imshow(image)  

    prob = prediction[i]  
    class_idx = np.argmax(prob)
    label = label_encoder.inverse_transform([class_idx])[0]  
    confidence = prob[class_idx]  
    
    predict_label.append(f"{label}: {confidence * 100:.2f}%")
    plt.title(predict_label[i], fontsize=16)
    plt.axis('off')

plt.tight_layout()
plt.show()
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.
8/8 ━━━━━━━━━━━━━━━━━━━━ 1s 175ms/step
No description has been provided for this image

Оценка модели

In [31]:
tuner.search(X_train, y_train, epochs=20, validation_split=0.2)
history = best_model.fit(X_train, y_train, epochs=20, validation_data=(X_val, y_val))

best_model = tuner.get_best_models(num_models=1)[0]

test_loss, test_acc = best_model.evaluate(X_test, y_test)
print(f"Тестовая точность: {test_acc * 100:.2f}%")

train_loss, train_acc = best_model.evaluate(X_train, y_train)
print(f"Train Loss: {train_loss:.4f}, Train Accuracy: {train_acc:.4f}")

val_loss, val_acc = best_model.evaluate(X_val, y_val)
print(f"Validation Loss: {val_loss:.4f}, Validation Accuracy: {val_acc:.4f}")

predictions = best_model.predict(X_val)

predicted_classes = np.argmax(predictions, axis=1)
predicted_confidences = np.max(predictions, axis=1)  
pd.DataFrame(history.history).plot(figsize=(15,8)) # type: ignore
plt.grid(True)
plt.gca().set_ylim(0,4)
plt.show()
Epoch 1/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 32s 1s/step - accuracy: 0.7739 - loss: 0.4661 - val_accuracy: 0.7826 - val_loss: 0.4797
Epoch 2/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 29s 1s/step - accuracy: 0.7816 - loss: 0.4553 - val_accuracy: 0.8348 - val_loss: 0.3860
Epoch 3/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 30s 1s/step - accuracy: 0.8118 - loss: 0.3863 - val_accuracy: 0.8000 - val_loss: 0.4420
Epoch 4/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 28s 980ms/step - accuracy: 0.8402 - loss: 0.3704 - val_accuracy: 0.8217 - val_loss: 0.3635
Epoch 5/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 29s 996ms/step - accuracy: 0.8669 - loss: 0.3318 - val_accuracy: 0.8478 - val_loss: 0.3594
Epoch 6/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 29s 987ms/step - accuracy: 0.8699 - loss: 0.3314 - val_accuracy: 0.8609 - val_loss: 0.3477
Epoch 7/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 32s 1s/step - accuracy: 0.8842 - loss: 0.2958 - val_accuracy: 0.8391 - val_loss: 0.3366
Epoch 8/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 31s 1s/step - accuracy: 0.8505 - loss: 0.3168 - val_accuracy: 0.8174 - val_loss: 0.4497
Epoch 9/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 29s 1s/step - accuracy: 0.8643 - loss: 0.3027 - val_accuracy: 0.8087 - val_loss: 0.4310
Epoch 10/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 33s 1s/step - accuracy: 0.8578 - loss: 0.3034 - val_accuracy: 0.8348 - val_loss: 0.3297
Epoch 11/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 29s 1s/step - accuracy: 0.8906 - loss: 0.2508 - val_accuracy: 0.8391 - val_loss: 0.3221
Epoch 12/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 29s 1s/step - accuracy: 0.8818 - loss: 0.2755 - val_accuracy: 0.8652 - val_loss: 0.3187
Epoch 13/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 28s 979ms/step - accuracy: 0.8925 - loss: 0.2798 - val_accuracy: 0.8565 - val_loss: 0.3755
Epoch 14/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 29s 1s/step - accuracy: 0.8694 - loss: 0.2912 - val_accuracy: 0.8565 - val_loss: 0.3089
Epoch 15/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 29s 1s/step - accuracy: 0.8829 - loss: 0.2856 - val_accuracy: 0.8739 - val_loss: 0.3000
Epoch 16/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 29s 1s/step - accuracy: 0.9090 - loss: 0.2478 - val_accuracy: 0.8261 - val_loss: 0.4229
Epoch 17/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 27s 926ms/step - accuracy: 0.8945 - loss: 0.2768 - val_accuracy: 0.8739 - val_loss: 0.2947
Epoch 18/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 27s 945ms/step - accuracy: 0.8962 - loss: 0.2618 - val_accuracy: 0.8696 - val_loss: 0.3015
Epoch 19/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 28s 951ms/step - accuracy: 0.9160 - loss: 0.2169 - val_accuracy: 0.8739 - val_loss: 0.3186
Epoch 20/20
29/29 ━━━━━━━━━━━━━━━━━━━━ 28s 958ms/step - accuracy: 0.8984 - loss: 0.2407 - val_accuracy: 0.8652 - val_loss: 0.3282
8/8 ━━━━━━━━━━━━━━━━━━━━ 2s 196ms/step - accuracy: 0.7665 - loss: 0.5344
Тестовая точность: 77.39%
29/29 ━━━━━━━━━━━━━━━━━━━━ 6s 203ms/step - accuracy: 0.7918 - loss: 0.4727
Train Loss: 0.4662, Train Accuracy: 0.7972
8/8 ━━━━━━━━━━━━━━━━━━━━ 2s 196ms/step - accuracy: 0.7665 - loss: 0.5344
Validation Loss: 0.5100, Validation Accuracy: 0.7739
8/8 ━━━━━━━━━━━━━━━━━━━━ 2s 201ms/step
No description has been provided for this image