837 KiB
Начало лабораторной работы, юху(-оперативка в конце)¶
Классификация¶
Загрузка набора данных для задачи классификации¶
В данной лабораторной работе используется набор данных с классификацией некотрых персонажей вселенной Marvel
Набор данных состоит из 5 папок с изображениями 5 персонажей сооответственно: Капитан Омерика, Мертвый Бассейн, Соколиный Глаз, Халк, Росомаха
Ссылка на датасет: https://www.kaggle.com/datasets/codyfrederick/simple-marvel-characters?resource=download
Какова задача¶
Задача: классификация перечисленных персонажей вселенной Marvel
import numpy as np
import matplotlib.pyplot as plt
import os
from PIL import Image
from sklearn.model_selection import train_test_split
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, BatchNormalization
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.preprocessing.image import ImageDataGenerator
# Устанавливаем seed для воспроизводимости
np.random.seed(42)
# Укажите путь к вашему датасету
dataset_path = "./static/images/data"
# Список классов (персонажей)
characters = os.listdir(dataset_path)
num_classes = len(characters)
print(f"Всего классов: {num_classes}")
print("Список персонажей:", characters)
Отображение данных¶
Вывод примеров классов
plt.figure(figsize=(12, 8))
for i, character in enumerate(characters[:5]):
character_dir = os.path.join(dataset_path, character)
img_files = os.listdir(character_dir)[:3]
for j, img_file in enumerate(img_files):
img_path = os.path.join(character_dir, img_file)
img = Image.open(img_path)
plt.subplot(5, 3, i*3 + j + 1)
plt.imshow(img)
plt.title(character)
plt.axis('off')
plt.tight_layout()
plt.show()
Предобработка изображений¶
- Конвертация в RGB на случай черно-белых изображений
- Ресайз до 128x128 пикселей
- Нормализация значений пикселей в диапазон [0, 1]
- Преобразование меток в one-hot encoding
- Разделение данных на выборки
# Путь к датасету
dataset_path = "./static/images/data"
# Получаем список персонажей
characters = sorted(os.listdir(dataset_path))
num_classes = len(characters)
# Параметры изображений
img_width, img_height = 128, 128
input_shape = (img_width, img_height, 3) # используем 2D-изображения для CNN
# Загрузка и предобработка изображений
X = []
y = []
for i, character in enumerate(characters):
character_dir = os.path.join(dataset_path, character)
for img_file in os.listdir(character_dir):
try:
img_path = os.path.join(character_dir, img_file)
img = Image.open(img_path).convert('RGB') # Убедимся, что 3 канала
img = img.resize((img_width, img_height))
img_array = np.array(img) / 255.0 # Нормализация
X.append(img_array)
y.append(i)
except Exception as e:
print(f"Ошибка при загрузке {img_path}: {e}")
X = np.array(X)
y = np.array(y)
# Преобразование меток в one-hot encoding
y = to_categorical(y, num_classes=num_classes)
# Разделение данных
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
print(f"Размер обучающей выборки: {X_train.shape}")
print(f"Размер тестовой выборки: {X_test.shape}")
print(f"Количество классов: {num_classes}")
Создание модели CNN¶
Архитектура CNN¶
Слои и их назначение:
Свёрточные блоки (Conv2D + BatchNorm + MaxPooling):
Выделяют пространственные признаки
BatchNorm ускоряет обучение
Dropout:
- Борется с переобучением (0.2-0.5)
Полносвязные слои:
- Анализируют извлечённые признаки
Выходной слой:
- softmax для многоклассовой классификации
def create_cnn_model(input_shape, num_classes):
model = Sequential()
# Первый сверточный блок
model.add(Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=input_shape))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), activation='relu', padding='same'))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2)))
model.add(Dropout(0.2))
# Второй сверточный блок
model.add(Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(BatchNormalization())
model.add(Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2)))
model.add(Dropout(0.3))
# Третий сверточный блок
model.add(Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(BatchNormalization())
model.add(Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2)))
model.add(Dropout(0.4))
# Полносвязные слои
model.add(Flatten())
model.add(Dense(256, activation='relu'))
model.add(BatchNormalization())
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))
# Компиляция модели
model.compile(optimizer=Adam(learning_rate=0.0001),
loss='categorical_crossentropy',
metrics=['accuracy'])
return model
model = create_cnn_model(input_shape, num_classes)
model.summary()
Аугментация данных и обучение¶
Эффекты для аугментации:
Повороты на ±20°
Сдвиги по ширине/высоте на 20%
Горизонтальное отражение
Для обучения возьмем 50 эпох
# Создаем генератор аугментированных данных
train_datagen = ImageDataGenerator(
rotation_range=20,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest'
)
# Генератор для тестовых данных (только нормализация)
test_datagen = ImageDataGenerator()
# Подготовка генераторов
batch_size = 32
train_generator = train_datagen.flow(X_train, y_train, batch_size=batch_size)
test_generator = test_datagen.flow(X_test, y_test, batch_size=batch_size)
# Коллбэки для обучения
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau
callbacks = [
EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True),
ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=5)
]
# Обучение модели
epochs = 50
history = model.fit(
train_generator,
steps_per_epoch=len(X_train) // batch_size,
epochs=epochs,
validation_data=test_generator,
validation_steps=len(X_test) // batch_size,
callbacks=callbacks,
verbose=1
)
Оценка и визуализация результатов¶
# Оценка модели на тестовых данных
test_loss, test_acc = model.evaluate(test_generator, verbose=0)
print(f"\nФинальная точность на тестовых данных: {test_acc:.4f}")
print(f"Финальные потери на тестовых данных: {test_loss:.4f}")
# Графики обучения
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'], label='Обучающая точность')
plt.plot(history.history['val_accuracy'], label='Валидационная точность')
plt.title('Точность модели')
plt.ylabel('Точность')
plt.xlabel('Эпоха')
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(history.history['loss'], label='Обучающие потери')
plt.plot(history.history['val_loss'], label='Валидационные потери')
plt.title('Потери модели')
plt.ylabel('Потери')
plt.xlabel('Эпоха')
plt.legend()
plt.tight_layout()
plt.show()
Реузльтаты обученной модели оказались не самыми лучшими - 18.5% точности.
Анализ проблемы:
Недостаточная сложность модели для датасета
Так как в кадом классе датасета персонаж предосталвен во всех возможных вариациях(в супер-костюме - без костюма, в полный рост - в анфаз и т.д.), модели достаточно тяжело кооректно предугадать персонажа по изборажению
Переобучение
Слишком большая разница между train и val accuracy
Проблемы с данными
Неправильная предобработка или несбалансированные классы
Попытка №2¶
На основе недочетов прошлой модели попробуем создать новую, более подходящую для решения поставленной задачи
Ключевые улучшения:¶
Увеличенная емкость модели:
Добавлено больше фильтров (64 → 128 → 256)
Увеличен размер полносвязного слоя (512 нейронов)
Улучшенная регуляризация:
Добавлен L2-регуляризатор для всех слоев
Увеличен Dropout (до 0.6 в последнем слое)
Более агрессивная аугментация данных
Оптимизация обучения:
Уменьшена скорость обучения с целью рассмотрения наибольшего количества минимумов - learning rate (0.00001)
Добавлено сохранение лучшей модели
Увеличен patience для EarlyStopping
Работа с дисбалансом классов:
Добавлена визуализация распределения классов
Реализовано автоматическое взвешивание классов
Дополнительные улучшения:
Вертикальное отражение в аугментации
Регулировка яркости изображений
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, BatchNormalization
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint
from tensorflow.keras.regularizers import l2
# Улучшенная архитектура модели
def create_improved_model(input_shape, num_classes):
model = Sequential([
# Увеличение количества фильтров и добавление L2 регуляризации
Conv2D(64, (3, 3), activation='relu', padding='same',
input_shape=input_shape, kernel_regularizer=l2(0.001)),
BatchNormalization(),
Conv2D(64, (3, 3), activation='relu', padding='same'),
BatchNormalization(),
MaxPooling2D((2, 2)),
Dropout(0.3),
Conv2D(128, (3, 3), activation='relu', padding='same', kernel_regularizer=l2(0.001)),
BatchNormalization(),
Conv2D(128, (3, 3), activation='relu', padding='same'),
BatchNormalization(),
MaxPooling2D((2, 2)),
Dropout(0.4),
Conv2D(256, (3, 3), activation='relu', padding='same', kernel_regularizer=l2(0.001)),
BatchNormalization(),
Conv2D(256, (3, 3), activation='relu', padding='same'),
BatchNormalization(),
MaxPooling2D((2, 2)),
Dropout(0.5),
Flatten(),
Dense(512, activation='relu', kernel_regularizer=l2(0.001)),
BatchNormalization(),
Dropout(0.6),
Dense(num_classes, activation='softmax')
])
# Измененный оптимизатор с более низким learning rate
optimizer = Adam(learning_rate=0.00001)
model.compile(optimizer=optimizer,
loss='categorical_crossentropy',
metrics=['accuracy'])
return model
# Улучшенная аугментация данных
def create_datagen():
return ImageDataGenerator(
rotation_range=30,
width_shift_range=0.3,
height_shift_range=0.3,
shear_range=0.3,
zoom_range=0.3,
horizontal_flip=True,
vertical_flip=True,
brightness_range=[0.7, 1.3],
fill_mode='nearest'
)
# Улучшенные callback-функции
def create_callbacks():
return [
EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True),
ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=10, min_lr=1e-7),
ModelCheckpoint('best_model.h5', save_best_only=True, monitor='val_accuracy')
]
# Проверка баланса классов
def check_class_balance(y):
class_counts = np.sum(y, axis=0)
plt.figure(figsize=(10, 5))
plt.bar(range(len(class_counts)), class_counts)
plt.title('Распределение классов')
plt.xlabel('Класс')
plt.ylabel('Количество образцов')
plt.show()
return class_counts
# Проверяем баланс классов
class_counts = check_class_balance(y_train)
print("Количество образцов в классах:", class_counts)
# Если классы несбалансированы, добавляем взвешивание классов
class_weights = {i: 1./class_counts[i] for i in range(num_classes)}
# Создаем и обучаем модель
model = create_improved_model(input_shape, num_classes)
model.summary()
train_datagen = create_datagen()
test_datagen = ImageDataGenerator()
train_generator = train_datagen.flow(X_train, y_train, batch_size=32)
test_generator = test_datagen.flow(X_test, y_test, batch_size=32)
history = model.fit(
train_generator,
steps_per_epoch=len(X_train) // 32,
epochs=50,
validation_data=test_generator,
validation_steps=len(X_test) // 32,
callbacks=create_callbacks(),
class_weight=class_weights if min(class_counts) < max(class_counts) * 0.7 else None,
verbose=1
)
from tensorflow.keras.models import load_model
best_model = load_model('best_model.keras')
test_loss, test_acc = best_model.evaluate(test_generator, verbose=0)
print(f"\nЛучшая точность на тестовых данных: {test_acc:.4f}")
print(f"Лучшие потери на тестовых данных: {test_loss:.4f}")
Оценим проделанную работу¶
# Визуализация результатов обучения
plt.figure(figsize=(14, 5))
# График точности
plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'], label='Точность на обучении')
plt.plot(history.history['val_accuracy'], label='Точность на валидации')
plt.title('График точности модели')
plt.ylabel('Точность')
plt.xlabel('Эпоха')
plt.ylim([0, 1]) # Ограничиваем ось Y от 0 до 1 для точности
plt.legend(loc='lower right')
# График потерь
plt.subplot(1, 2, 2)
plt.plot(history.history['loss'], label='Потери на обучении')
plt.plot(history.history['val_loss'], label='Потери на валидации')
plt.title('График потерь модели')
plt.ylabel('Потери')
plt.xlabel('Эпоха')
plt.legend(loc='upper right')
plt.tight_layout()
plt.show()
# Дополнительная информация
final_train_acc = history.history['accuracy'][-1]
final_val_acc = history.history['val_accuracy'][-1]
print(f"\nФинальная точность на обучении: {final_train_acc:.4f}")
print(f"Финальная точность на валидации: {final_val_acc:.4f}")
# Сохранение графиков
plt.savefig('training_metrics.png', dpi=300, bbox_inches='tight')
Модель эффективнее распознает персонажей, но всё еще не с самой высокой точностью. В качестве причин таких результатов можно указать одну из описанных ранее(1-ю причину в блоке описания причин полученных результатов у прошлой модели) и достаточно агрессивную регуляризацию