# Отчёт по лабораторной работе №3 **Троянов Д.С., Чернов Д.Е. — А-01-22** --- ## Задание 1 ### 1) Подготовка рабочей среды и импорт библиотек Инициализируем рабочую среду и подключаем необходимые библиотеки для работы с нейронными сетями и обработки данных. Также настраиваем SSL для корректной загрузки датасетов. ```python # Подключение необходимых библиотек и модулей import os import ssl # Обход проблемы с SSL сертификатами на macOS ssl._create_default_https_context = ssl._create_unverified_context # Для работы в Google Colab раскомментируйте следующую строку: # os.chdir('/content/drive/MyDrive/Colab Notebooks/is_lab3') from tensorflow import keras from tensorflow.keras import layers from tensorflow.keras.models import Sequential import matplotlib.pyplot as plt import numpy as np from sklearn.metrics import classification_report, confusion_matrix from sklearn.metrics import ConfusionMatrixDisplay ``` ### 2) Загрузка датасета MNIST Загружаем стандартный набор данных MNIST, который содержит изображения рукописных цифр от 0 до 9 с соответствующими метками. ```python # Импорт и загрузка датасета MNIST from keras.datasets import mnist (X_train, y_train), (X_test, y_test) = mnist.load_data() ``` ### 3) Разделение данных на обучающую и тестовую выборки Производим собственное разбиение датасета в соотношении 60 000:10 000. Для воспроизводимости результатов используем параметр random_state = 3 (вычисляется как 4k - 1, где k = 1 - номер нашей бригады). Выводим размерности полученных массивов данных. ```python # Создание собственного разбиения датасета from sklearn.model_selection import train_test_split # Объединение исходных обучающей и тестовой выборок в единый набор X = np.concatenate((X_train, X_test)) y = np.concatenate((y_train, y_test)) # Разделение на обучающую и тестовую выборки согласно заданию X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 10000, train_size = 60000, random_state = 3) # Вывод размерностей полученных массивов print('Shape of X train:', X_train.shape) print('Shape of y train:', y_train.shape) print('Shape of X test:', X_test.shape) print('Shape of y test:', y_test.shape) ``` ``` Shape of X train: (60000, 28, 28) Shape of y train: (60000,) Shape of X test: (10000, 28, 28) Shape of y test: (10000,) ``` ### 4) Предобработка данных Выполняем нормализацию пикселей изображений (приведение к диапазону [0, 1]) и преобразование меток в формат one-hot encoding для корректной работы с категориальной функцией потерь. Выводим размерности предобработанных массивов данных. ```python # Определение параметров данных и модели num_classes = 10 input_shape = (28, 28, 1) # Нормализация значений пикселей: приведение к диапазону [0, 1] X_train = X_train / 255 X_test = X_test / 255 # Добавление размерности канала для корректной работы с Conv2D слоями # Преобразование из (высота, ширина) в (высота, ширина, каналы) X_train = np.expand_dims(X_train, -1) X_test = np.expand_dims(X_test, -1) print('Shape of transformed X train:', X_train.shape) print('Shape of transformed X test:', X_test.shape) # Преобразование меток в формат one-hot encoding y_train = keras.utils.to_categorical(y_train, num_classes) y_test = keras.utils.to_categorical(y_test, num_classes) print('Shape of transformed y train:', y_train.shape) print('Shape of transformed y test:', y_test.shape) ``` ``` Shape of transformed X train: (60000, 28, 28, 1) Shape of transformed X test: (10000, 28, 28, 1) Shape of transformed y train: (60000, 10) Shape of transformed y test: (10000, 10) ``` ### 5) Построение и обучение сверточной нейронной сети Создаем архитектуру сверточной нейронной сети с использованием сверточных слоев, пулинга и регуляризации. Обучаем модель на подготовленных данных с выделением части данных для валидации. Выводим информацию об архитектуре нейронной сети. ```python # Создание модели сверточной нейронной сети model = Sequential() model.add(layers.Conv2D(32, kernel_size=(3, 3), activation="relu", input_shape=input_shape)) model.add(layers.MaxPooling2D(pool_size=(2, 2))) model.add(layers.Conv2D(64, kernel_size=(3, 3), activation="relu")) model.add(layers.MaxPooling2D(pool_size=(2, 2))) model.add(layers.Dropout(0.5)) model.add(layers.Flatten()) model.add(layers.Dense(num_classes, activation="softmax")) model.summary() ``` **Model: "sequential"** | Layer (type) | Output Shape | Param # | |--------------------------------|---------------------|--------:| | conv2d (Conv2D) | (None, 26, 26, 32) | 320 | | max_pooling2d (MaxPooling2D) | (None, 13, 13, 32) | 0 | | conv2d_1 (Conv2D) | (None, 11, 11, 64) | 18,496 | | max_pooling2d_1 (MaxPooling2D) | (None, 5, 5, 64) | 0 | | dropout (Dropout) | (None, 5, 5, 64) | 0 | | flatten (Flatten) | (None, 1600) | 0 | | dense (Dense) | (None, 10) | 16,010 | **Total params:** 34,826 (136.04 KB) **Trainable params:** 34,826 (136.04 KB) **Non-trainable params:** 0 (0.00 B) ```python # Компиляция и обучение модели batch_size = 512 epochs = 15 model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"]) model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, validation_split=0.1) ``` ### 6) Оценка качества модели на тестовых данных Проводим финальную оценку обученной модели на независимой тестовой выборке, получая значения функции потерь и точности классификации. ```python # Оценка качества работы обученной модели на тестовой выборке scores = model.evaluate(X_test, y_test) print('Loss on test data:', scores[0]) print('Accuracy on test data:', scores[1]) ``` ``` 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - accuracy: 0.9884 - loss: 0.0409 Loss on test data: 0.04092026501893997 Accuracy on test data: 0.9883999824523926 ``` ### 7) Демонстрация работы модели на отдельных примерах Визуализируем результаты распознавания для двух тестовых изображений, сравнивая предсказания модели с истинными метками. ```python # Визуализация результатов распознавания для двух тестовых изображений for n in [3,26]: result = model.predict(X_test[n:n+1]) print('NN output:', result) plt.imshow(X_test[n].reshape(28,28), cmap=plt.get_cmap('gray')) plt.show() print('Real mark: ', np.argmax(y_test[n])) print('NN answer: ', np.argmax(result)) ``` ![MNIST тестовое изображение - цифра 6](images/1.png) ``` Real mark: 6 NN answer: 6 ``` ![MNIST тестовое изображение - цифра 3](images/2.png) ``` Real mark: 3 NN answer: 3 ``` ### 8) Детальный анализ качества классификации Генерируем подробный отчет о качестве классификации и строим матрицу ошибок для визуального анализа работы модели по каждому классу. ```python # Получение истинных и предсказанных меток для всех тестовых данных true_labels = np.argmax(y_test, axis=1) # Предсказанные метки классов predicted_labels = np.argmax(model.predict(X_test), axis=1) # Вывод подробного отчета о качестве классификации print(classification_report(true_labels, predicted_labels)) # Построение и визуализация матрицы ошибок conf_matrix = confusion_matrix(true_labels, predicted_labels) # Отрисовка матрицы ошибок в виде "тепловой карты" display = ConfusionMatrixDisplay(confusion_matrix=conf_matrix) display.plot() plt.show() ``` ``` 313/313 ━━━━━━━━━━━━━━━━━━━━ 2s 5ms/step precision recall f1-score support 0 1.00 0.99 1.00 1001 1 0.99 1.00 0.99 1143 2 0.99 0.99 0.99 987 3 0.99 0.99 0.99 1023 4 0.99 0.99 0.99 974 5 1.00 0.98 0.99 907 6 0.99 0.99 0.99 974 7 0.98 0.99 0.99 1032 8 0.98 0.98 0.98 1006 9 0.98 0.99 0.98 953 accuracy 0.99 10000 macro avg 0.99 0.99 0.99 10000 weighted avg 0.99 0.99 0.99 10000 ``` ![Матрица ошибок для MNIST](images/3.png) ### 9) Тестирование на собственных изображениях Загружаем и обрабатываем собственные изображения цифр, созданные ранее, и проверяем способность модели их корректно распознавать. ```python # Загрузка и обработка собственных изображений from PIL import Image for name_image in ['2.png', '7.png']: file_data = Image.open(name_image) file_data = file_data.convert('L') # Перевод в градации серого test_img = np.array(file_data) # Вывод собственного изображения plt.imshow(test_img, cmap=plt.get_cmap('gray')) plt.show() # Предобработка test_img = test_img / 255 test_img = np.reshape(test_img, (1,28,28,1)) # Распознавание result = model.predict(test_img) print('I think it\'s', np.argmax(result)) ``` ![Собственное изображение - цифра 2](images/4.png) ``` I think it's 2 ``` ![Собственное изображение - цифра 7](images/5.png) ``` I think it's 7 ``` ### 10) Сравнение с моделью из предыдущей лабораторной работы Загружаем сохраненную полносвязную нейронную сеть из лабораторной работы №1 и оцениваем ее производительность на тех же тестовых данных для последующего сравнения. ```python model_lr1 = keras.models.load_model("best_mnist_model.keras") model_lr1.summary() ``` **Model: "sequential"** | Layer (type) | Output Shape | Param # | |------------------|-------------:|--------:| | dense (Dense) | (None, 10) | 7,850 | **Total params:** 7,852 (30.68 KB) **Trainable params:** 7,850 (30.66 KB) **Non-trainable params:** 0 (0.00 B) **Optimizer params:** 2 (12.00 B) ```python # Подготовка данных для полносвязной сети (преобразование изображений в векторы) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 10000, train_size = 60000, random_state = 3) num_pixels = X_train.shape[1] * X_train.shape[2] X_train = X_train.reshape(X_train.shape[0], num_pixels) / 255 X_test = X_test.reshape(X_test.shape[0], num_pixels) / 255 print('Shape of transformed X train:', X_train.shape) print('Shape of transformed X train:', X_test.shape) # Преобразование меток в формат one-hot encoding y_train = keras.utils.to_categorical(y_train, num_classes) y_test = keras.utils.to_categorical(y_test, num_classes) print('Shape of transformed y train:', y_train.shape) print('Shape of transformed y test:', y_test.shape) ``` ``` Shape of transformed X train: (60000, 784) Shape of transformed X train: (10000, 784) Shape of transformed y train: (60000, 10) Shape of transformed y test: (10000, 10) ``` ```python # Оценка качества работы модели на тестовых данных scores = model_lr1.evaluate(X_test, y_test) print('Loss on test data:', scores[0]) print('Accuracy on test data:', scores[1]) ``` ``` 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - accuracy: 0.9233 - loss: 0.2863 Loss on test data: 0.28625616431236267 Accuracy on test data: 0.92330002784729 ``` ### 11) Сравнительный анализ моделей Сравниваем сверточную нейронную сеть с полносвязной сетью по ключевым показателям: количеству параметров, времени обучения и качеству классификации. Делаем выводы по результатам применения сверточной нейронной сети для распознавания изображений. **Таблица сравнения моделей:** | Модель | Количество настраиваемых параметров | Количество эпох обучения | Качество классификации тестовой выборки | |----------|-------------------------------------|---------------------------|-----------------------------------------| | Сверточная | 34 826 | 15 | accuracy: 0.988; loss: 0.041 | | Полносвязная | 7 852 | 50 | accuracy: 0.923; loss: 0.286 | **Выводы:** На основе проведенного анализа можно заключить, что сверточная нейронная сеть демонстрирует существенные преимущества перед полносвязной сетью при решении задач распознавания изображений: 1. **Эффективность параметров**: Сверточная сеть имеет больше параметров (34 826 против 7 852), но при этом показывает значительно лучшие результаты, что говорит о более эффективном использовании параметров для извлечения пространственных признаков. 2. **Скорость обучения**: Для достижения высокого качества сверточной сети требуется в 3.3 раза меньше эпох обучения (15 против 50), что существенно сокращает время обучения. 3. **Точность классификации**: Сверточная сеть показывает более высокую точность (98.8% против 92.3%) и значительно меньшую функцию потерь (0.041 против 0.286). Разница в точности составляет 6.5 процентных пункта, что является существенным улучшением. 4. **Обобщающая способность**: Сверточная сеть демонстрирует лучшую способность к обобщению, что видно из более низкой функции потерь на тестовых данных. Эти результаты подтверждают, что архитектура сверточных сетей, учитывающая пространственную структуру изображений через операции свертки и пулинга, является более подходящим выбором для задач компьютерного зрения, несмотря на большее количество параметров. ## Задание 2 ### Работа с датасетом CIFAR-10 Повторяем основные этапы задания 1, но используем датасет CIFAR-10, содержащий цветные изображения объектов 10 различных классов. Особенности выполнения: - Разделение данных производится в соотношении 50 000:10 000 - После разделения визуализируем 25 изображений из обучающей выборки - При демонстрации работы модели выбираем примеры так, чтобы одно изображение распознавалось корректно, а другое - ошибочно ### 1) Загрузка датасета CIFAR-10 Загружаем набор данных CIFAR-10, который содержит цветные изображения размером 32x32 пикселя, разделенные на 10 классов: самолет, автомобиль, птица, кошка, олень, собака, лягушка, лошадь, корабль, грузовик. ```python # Импорт и загрузка датасета CIFAR-10 from keras.datasets import cifar10 (X_train, y_train), (X_test, y_test) = cifar10.load_data() ``` ### 2) Разделение данных на обучающую и тестовую выборки Создаем собственное разбиение датасета CIFAR-10 в соотношении 50 000:10 000. Используем random_state = 3 для воспроизводимости результатов (k = 1 - номер нашей бригады). Выводим размерности полученных массивов данных. ```python # Создание собственного разбиения датасета # Объединение исходных выборок X = np.concatenate((X_train, X_test)) y = np.concatenate((y_train, y_test)) # Разделение на обучающую и тестовую выборки X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 10000, train_size = 50000, random_state = 3) # Вывод размерностей print('Shape of X train:', X_train.shape) print('Shape of y train:', y_train.shape) print('Shape of X test:', X_test.shape) print('Shape of y test:', y_test.shape) ``` ``` Shape of X train: (50000, 32, 32, 3) Shape of y train: (50000, 1) Shape of X test: (10000, 32, 32, 3) Shape of y test: (10000, 1) ``` ### Визуализация примеров из обучающей выборки Отображаем сетку из 25 изображений из обучающей выборки с подписями соответствующих классов для визуального ознакомления с данными. ```python class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'] plt.figure(figsize=(10,10)) for i in range(25): plt.subplot(5,5,i+1) plt.xticks([]) plt.yticks([]) plt.grid(False) plt.imshow(X_train[i]) plt.xlabel(class_names[y_train[i][0]]) plt.show() ``` ![Сетка из 25 изображений CIFAR-10](images/6.png) ### 3) Предобработка данных CIFAR-10 Нормализуем значения пикселей и преобразуем метки в формат one-hot encoding для работы с категориальной функцией потерь. Выводим размерности предобработанных массивов данных. ```python # Определение параметров данных и модели num_classes = 10 input_shape = (32, 32, 3) # Нормализация значений пикселей: приведение к диапазону [0, 1] X_train = X_train / 255 X_test = X_test / 255 print('Shape of transformed X train:', X_train.shape) print('Shape of transformed X test:', X_test.shape) # Преобразование меток в формат one-hot encoding y_train = keras.utils.to_categorical(y_train, num_classes) y_test = keras.utils.to_categorical(y_test, num_classes) print('Shape of transformed y train:', y_train.shape) print('Shape of transformed y test:', y_test.shape) ``` ``` Shape of transformed X train: (50000, 32, 32, 3) Shape of transformed X test: (10000, 32, 32, 3) Shape of transformed y train: (50000, 10) Shape of transformed y test: (10000, 10) ``` ### 4) Построение и обучение сверточной сети для CIFAR-10 Создаем более сложную архитектуру сверточной сети с использованием батч-нормализации и нескольких блоков свертки для работы с цветными изображениями. Обучаем модель на подготовленных данных с выделением части данных для валидации. ```python # Создание модели сверточной нейронной сети model = Sequential() # Блок 1 model.add(layers.Conv2D(32, (3, 3), padding="same", activation="relu", input_shape=input_shape)) model.add(layers.BatchNormalization()) model.add(layers.Conv2D(32, (3, 3), padding="same", activation="relu")) model.add(layers.BatchNormalization()) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Dropout(0.25)) # Блок 2 model.add(layers.Conv2D(64, (3, 3), padding="same", activation="relu")) model.add(layers.BatchNormalization()) model.add(layers.Conv2D(64, (3, 3), padding="same", activation="relu")) model.add(layers.BatchNormalization()) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Dropout(0.25)) # Блок 3 model.add(layers.Conv2D(128, (3, 3), padding="same", activation="relu")) model.add(layers.BatchNormalization()) model.add(layers.Conv2D(128, (3, 3), padding="same", activation="relu")) model.add(layers.BatchNormalization()) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Dropout(0.4)) model.add(layers.Flatten()) model.add(layers.Dense(128, activation='relu')) model.add(layers.Dropout(0.5)) model.add(layers.Dense(num_classes, activation="softmax")) model.summary() ``` **Model: "sequential_9"** | Layer (type) | Output Shape | Param # | |--------------------------------------------|-------------------|---------:| | conv2d_41 (Conv2D) | (None, 32, 32, 32) | 896 | | batch_normalization_6 (BatchNormalization) | (None, 32, 32, 32) | 128 | | conv2d_42 (Conv2D) | (None, 32, 32, 32) | 9,248 | | batch_normalization_7 (BatchNormalization) | (None, 32, 32, 32) | 128 | | max_pooling2d_26 (MaxPooling2D) | (None, 16, 16, 32) | 0 | | dropout_24 (Dropout) | (None, 16, 16, 32) | 0 | | conv2d_43 (Conv2D) | (None, 16, 16, 64) | 18,496 | | batch_normalization_8 (BatchNormalization) | (None, 16, 16, 64) | 256 | | conv2d_44 (Conv2D) | (None, 16, 16, 64) | 36,928 | | batch_normalization_9 (BatchNormalization) | (None, 16, 16, 64) | 256 | | max_pooling2d_27 (MaxPooling2D) | (None, 8, 8, 64) | 0 | | dropout_25 (Dropout) | (None, 8, 8, 64) | 0 | | conv2d_45 (Conv2D) | (None, 8, 8, 128) | 73,856 | | batch_normalization_10 (BatchNormalization)| (None, 8, 8, 128) | 512 | | conv2d_46 (Conv2D) | (None, 8, 8, 128) | 147,584 | | batch_normalization_11 (BatchNormalization)| (None, 8, 8, 128) | 512 | | max_pooling2d_28 (MaxPooling2D) | (None, 4, 4, 128) | 0 | | dropout_26 (Dropout) | (None, 4, 4, 128) | 0 | | flatten_9 (Flatten) | (None, 2048) | 0 | | dense_17 (Dense) | (None, 128) | 262,272 | | dropout_27 (Dropout) | (None, 128) | 0 | | dense_18 (Dense) | (None, 10) | 1,290 | **Total params:** 552,362 (2.11 MB) **Trainable params:** 551,466 (2.10 MB) **Non-trainable params:** 896 (3.50 KB) ```python # Компиляция и обучение модели batch_size = 64 epochs = 50 model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"]) model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, validation_split=0.1) ``` ### 5) Оценка качества модели на тестовых данных Оцениваем финальную производительность обученной модели на тестовой выборке CIFAR-10. ```python # Оценка качества работы модели на тестовых данных scores = model.evaluate(X_test, y_test) print('Loss on test data:', scores[0]) print('Accuracy on test data:', scores[1]) ``` ``` 313/313 ━━━━━━━━━━━━━━━━━━━━ 7s 22ms/step - accuracy: 0.8553 - loss: 0.5210 Loss on test data: 0.5209607481956482 Accuracy on test data: 0.8553000092506409 ``` ### 6) Демонстрация работы модели на отдельных примерах Визуализируем результаты распознавания для двух тестовых изображений: одно должно быть распознано корректно, другое - ошибочно. ```python # Визуализация результатов распознавания для двух тестовых изображений for n in [3,14]: result = model.predict(X_test[n:n+1]) print('NN output:', result) plt.imshow(X_test[n].reshape(32,32,3), cmap=plt.get_cmap('gray')) plt.show() print('Real mark: ', np.argmax(y_test[n])) print('NN answer: ', np.argmax(result)) ``` ![CIFAR-10 тестовое изображение](images/7.png) ``` Real mark: 6 NN answer: 6 ``` ![CIFAR-10 тестовое изображение - олень (ошибочно распознано)](images/8.png) ``` Real mark: 4 NN answer: 5 ``` ### 7) Детальный анализ качества классификации CIFAR-10 Генерируем подробный отчет о качестве классификации и строим матрицу ошибок для анализа работы модели по каждому классу. ```python # Получение истинных и предсказанных меток для всех тестовых данных true_labels = np.argmax(y_test, axis=1) # Предсказанные метки классов predicted_labels = np.argmax(model.predict(X_test), axis=1) # Вывод подробного отчета о качестве классификации print(classification_report(true_labels, predicted_labels, target_names=class_names)) # Построение и визуализация матрицы ошибок conf_matrix = confusion_matrix(true_labels, predicted_labels) # Отрисовка матрицы ошибок в виде "тепловой карты" fig, ax = plt.subplots(figsize=(6, 6)) disp = ConfusionMatrixDisplay(confusion_matrix=conf_matrix,display_labels=class_names) disp.plot(ax=ax, xticks_rotation=45) # Поворот подписей по X и приятная палитра plt.tight_layout() # Чтобы всё влезло plt.show() ``` ``` 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step precision recall f1-score support airplane 0.84 0.91 0.87 1007 automobile 0.95 0.91 0.93 1037 bird 0.83 0.79 0.81 1030 cat 0.77 0.65 0.70 990 deer 0.83 0.82 0.82 966 dog 0.72 0.83 0.77 1009 frog 0.90 0.89 0.89 972 horse 0.87 0.89 0.88 991 ship 0.95 0.92 0.93 990 truck 0.89 0.93 0.91 1008 accuracy 0.85 10000 macro avg 0.86 0.85 0.85 10000 weighted avg 0.86 0.85 0.85 10000 ``` ![Матрица ошибок для CIFAR-10](images/9.png) **Выводы по результатам классификации CIFAR-10:** Разработанная сверточная нейронная сеть показала хорошие результаты при классификации цветных изображений из датасета CIFAR-10. Модель достигла точности классификации 85.5% (accuracy: 0.855, loss: 0.521) на тестовой выборке, а в детальном отчете о классификации показала accuracy 0.85, что является достойным результатом для данной задачи, учитывая сложность различения объектов в низком разрешении (32x32 пикселя) и наличие 10 различных классов. Использование батч-нормализации и dropout-регуляризации позволило улучшить обобщающую способность модели и предотвратить переобучение. Архитектура с тремя блоками сверточных слоев эффективно извлекает иерархические признаки из изображений, что подтверждается полученными метриками качества.