|  |  | # Отчет по Лабораторной работе №1 по курсу "Интеллектуальные системы"
 | 
						
						
						
							|  |  | ## Бригада №5 (Голубев Т. Л., Ишутина Е. И.)
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ### *1. В среде GoogleColab создать новый блокнот (notebook). Импортировать необходимые для работы библиотеки и модули.*
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | tensorflow.keras — высокоуровневый API для построения и обучения нейронных сетей. Мы используем его для загрузки датасета и построения моделей.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | matplotlib.pyplot — библиотека для визуализации данных, построения графиков и отображения изображений.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | numpy — библиотека для работы с многомерными массивами и выполнения численных операций.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | sklearn (scikit-learn) — библиотека для машинного обучения, содержит инструменты для предварительной обработки данных, метрик, кластеризации и классификации.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Задание рабочей директории, импорт необходимых библиотек и модулей:
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | import os
 | 
						
						
						
							|  |  | os.chdir('/content/drive/MyDrive/Colab Notebooks')
 | 
						
						
						
							|  |  | from tensorflow import keras
 | 
						
						
						
							|  |  | import matplotlib.pyplot as plt
 | 
						
						
						
							|  |  | import numpy as np
 | 
						
						
						
							|  |  | import sklearn
 | 
						
						
						
							|  |  | ```
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ### *2. Загрузить набор данных MNIST, содержащий размеченные изображения рукописных цифр*
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Набор данных MNIST (Modified National Institute of Standards and Technology) — это стандартный набор изображений для обучения и тестирования алгоритмов распознавания рукописных цифр.
 | 
						
						
						
							|  |  | Он содержит 70 000 изображений цифр от 0 до 9, из них 60 000 для обучения (training set) и 10 000 — для тестирования (test set).
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Каждое изображение имеет размер 28×28 пикселей и представлено в градациях серого.
 | 
						
						
						
							|  |  | В TensorFlow набор данных MNIST встроен в модуль keras.datasets, что позволяет легко загрузить его одной командой.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | (X_train, y_train), (X_test, y_test) = mnist.load_data()
 | 
						
						
						
							|  |  | Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
 | 
						
						
						
							|  |  | 11490434/11490434 ━━━━━━━━━━━━━━━━━━━━ 2s 0us/step
 | 
						
						
						
							|  |  | ```
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | * X_train и X_test — массивы изображений рукописных цифр,
 | 
						
						
						
							|  |  | * y_train и y_test — соответствующие метки (от 0 до 9), обозначающие, какая цифра изображена.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ### *3. Разбить набор данных на обучающие и тестовые данные в соотношении 60000:10000 элементов. При разбиении параметр random_state выбрать равным (4k–1), где k–номер бригады. Вывести размерности полученных обучающих и тестовых массивов данных.*
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Параметр random_state обеспечивает воспроизводимость разбиения данных.
 | 
						
						
						
							|  |  | Для номера бригады = 5 получим `random_state = 19`.
 | 
						
						
						
							|  |  | Функция train_test_split() случайным образом делит данные на обучающую и тестовую выборки указанного размера.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```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 = 19)
 | 
						
						
						
							|  |  | ```
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | # вывод размерностей
 | 
						
						
						
							|  |  | 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. Вывести  первые 4 элемента обучающих данных (изображения и метки цифр).*
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Данный код отображает первые четыре изображения из обучающего набора и их метки.
 | 
						
						
						
							|  |  | Каждое изображение разворачивается обратно в форму 28×28 для корректного отображения.
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | # вывод первых 4 изображений и их меток
 | 
						
						
						
							|  |  | plt.figure(figsize=(8, 2))
 | 
						
						
						
							|  |  | for i in range(4):
 | 
						
						
						
							|  |  |     plt.subplot(1, 4, i + 1)
 | 
						
						
						
							|  |  |     plt.imshow(X_train[i].reshape(28, 28), cmap='gray')
 | 
						
						
						
							|  |  |     plt.title(f'Label: {y_train[i]}', fontsize = 6)
 | 
						
						
						
							|  |  |     plt.axis('off')
 | 
						
						
						
							|  |  | plt.show()
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ### *5. Провести предобработку данных: привести обучающие и тестовые данные к формату, пригодному для обучения нейронной сети. Входныеданные должны  принимать  значения  от  0  до  1, метки  цифрдолжны  быть закодированы  по  принципу  «one-hotencoding».Вывести  размерности предобработанных обучающих и тестовых массивов данных.*
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Перед обучением нейронной сети необходимо подготовить данные.
 | 
						
						
						
							|  |  | Каждое изображение в наборе MNIST имеет размер 28×28 пикселей, но полносвязная нейронная сеть принимает на вход вектор признаков.
 | 
						
						
						
							|  |  | Поэтому каждое изображение «вытягивается» в одномерный вектор длиной 784 (28×28), где каждый элемент соответствует яркости одного пикселя.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Кроме того, метки классов (цифры от 0 до 9) переводятся в формат one-hot encoding.
 | 
						
						
						
							|  |  | При этом каждая цифра представляется вектором длиной 10, где только один элемент равен 1 (номер класса), а остальные — 0.
 | 
						
						
						
							|  |  | Например, цифра 3 кодируется как [0, 0, 0, 1, 0, 0, 0, 0, 0, 0].
 | 
						
						
						
							|  |  | Такой формат удобен для обучения сети, потому что выходной слой содержит 10 нейронов — по одному для каждой цифры.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | # развертывание изображений 28x28 в вектор длиной 784 и нормализация
 | 
						
						
						
							|  |  | num_pixels = X_train.shape[1] * X_train.shape[2]
 | 
						
						
						
							|  |  | X_train = X_train.reshape(X_train.shape[0], num_pixels).astype('float32') / 255
 | 
						
						
						
							|  |  | X_test = X_test.reshape(X_test.shape[0], num_pixels).astype('float32') / 255
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | # кодирование меток по принципу one-hot encoding
 | 
						
						
						
							|  |  | from tensorflow.keras.utils import to_categorical
 | 
						
						
						
							|  |  | y_train = to_categorical(y_train, 10)
 | 
						
						
						
							|  |  | y_test = to_categorical(y_test, 10)
 | 
						
						
						
							|  |  | num_classes = y_train.shape[1]
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | # вывод размерностей
 | 
						
						
						
							|  |  | 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, 784)
 | 
						
						
						
							|  |  | Shape of y train: (60000, 10)
 | 
						
						
						
							|  |  | Shape of X test: (10000, 784)
 | 
						
						
						
							|  |  | Shape of y test: (10000, 10)
 | 
						
						
						
							|  |  | ```
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ### *6. Реализовать модель однослойной нейронной сети и обучить её на обучающих данных с выделением части обучающих данных в качестве валидационных. Вывести информацию об архитектуре нейронной сети. Вывести график функции ошибки на обучающих и валидационных данных по эпохам.*
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Модель представляет собой однослойную нейронную сеть без скрытых слоёв.
 | 
						
						
						
							|  |  | На вход подаются векторы длиной 784, выходной слой содержит 10 нейронов (по количеству классов).
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Используются параметры:
 | 
						
						
						
							|  |  | функция активации — `softmax`, функция ошибки — `categorical_crossentropy`, оптимизатор — `sgd`, метрика — `accuracy`.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | # создание модели однослойной нейронной сети
 | 
						
						
						
							|  |  | from keras.models import Sequential
 | 
						
						
						
							|  |  | from keras.layers import Dense
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | model0 = Sequential()
 | 
						
						
						
							|  |  | # добавляем выходной слой
 | 
						
						
						
							|  |  | model0.add(Dense(units=num_classes, input_dim=784, activation='softmax'))
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | # компиляция модели
 | 
						
						
						
							|  |  | model0.compile(loss='categorical_crossentropy',
 | 
						
						
						
							|  |  |                optimizer='sgd',
 | 
						
						
						
							|  |  |                metrics=['accuracy'])
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | # вывод информации об архитектуре модели
 | 
						
						
						
							|  |  | print(model0.summary())
 | 
						
						
						
							|  |  | /usr/local/lib/python3.12/dist-packages/keras/src/layers/core/dense.py:93: 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)
 | 
						
						
						
							|  |  | Model: "sequential"
 | 
						
						
						
							|  |  | ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
 | 
						
						
						
							|  |  | ┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
 | 
						
						
						
							|  |  | ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
 | 
						
						
						
							|  |  | │ dense (Dense)                   │ (None, 10)             │         7,850 │
 | 
						
						
						
							|  |  | └─────────────────────────────────┴────────────────────────┴───────────────┘
 | 
						
						
						
							|  |  |  Total params: 7,850 (30.66 KB)
 | 
						
						
						
							|  |  |  Trainable params: 7,850 (30.66 KB)
 | 
						
						
						
							|  |  |  Non-trainable params: 0 (0.00 B)
 | 
						
						
						
							|  |  | None
 | 
						
						
						
							|  |  | ```
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | # обучение модели
 | 
						
						
						
							|  |  | H0 = model0.fit(X_train, y_train,
 | 
						
						
						
							|  |  |                 validation_split=0.1,
 | 
						
						
						
							|  |  |                 epochs=50,
 | 
						
						
						
							|  |  |                 verbose=1)
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | # вывод графика функции ошибки
 | 
						
						
						
							|  |  | plt.plot(H0.history['loss'])
 | 
						
						
						
							|  |  | plt.plot(H0.history['val_loss'])
 | 
						
						
						
							|  |  | plt.grid()
 | 
						
						
						
							|  |  | plt.xlabel('Epochs')
 | 
						
						
						
							|  |  | plt.ylabel('Loss')
 | 
						
						
						
							|  |  | plt.legend(['train_loss', 'val_loss'])
 | 
						
						
						
							|  |  | plt.title('Loss by epochs (Model 0)')
 | 
						
						
						
							|  |  | plt.show()
 | 
						
						
						
							|  |  | Epoch 1/50
 | 
						
						
						
							|  |  | 1688/1688 ━━━━━━━━━━━━━━━━━━━━ 6s 3ms/step - accuracy: 0.6993 - loss: 1.1736 - val_accuracy: 0.8783 - val_loss: 0.5063
 | 
						
						
						
							|  |  | ...
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ### *7. Применить обученную модель к тестовым данным. Вывести значение функции ошибки и значение метрики качества классификации на тестовых данных.*
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | После обучения однослойной нейронной сети проведём оценку её работы на тестовых данных, которые не участвовали в обучении.
 | 
						
						
						
							|  |  | Для этого используется метод `evaluate()`, который возвращает значение функции ошибки и метрики качества (`accuracy`).
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | # оценка качества модели на тестовых данных
 | 
						
						
						
							|  |  | scores = model0.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.9194 - loss: 0.2818
 | 
						
						
						
							|  |  | Loss on test data: 0.28366461396217346
 | 
						
						
						
							|  |  | Accuracy on test data: 0.9205999970436096
 | 
						
						
						
							|  |  | ```
 | 
						
						
						
							|  |  | Таким образом, точность однослойной нейронной сети на тестовых данных составила примерно 92,4%,
 | 
						
						
						
							|  |  | что показывает, что даже простая модель способна достаточно эффективно распознавать изображения рукописных цифр из набора MNIST.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ### *8. Добавить в модель один скрытый слой и провести обучение и тестирование (повторить п. 6–7) при 100, 300 и 500 нейронах в скрытом слое. По метрике качества классификации на тестовых данных выбрать наилучшее количество нейронов. В качестве функции активации нейронов в скрытом слое использовать функцию sigmoid.*
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | В данном пункте добавляется один скрытый слой с функцией активации sigmoid.
 | 
						
						
						
							|  |  | Архитектура сети теперь состоит из входного слоя размерности 784, одного скрытого слоя (Dense) с количеством нейронов 100, 300 или 500, выходного слоя из 10 нейронов с активацией `softmax`. Для каждой модели выполняется компиляция, обучение и тестирование аналогично пунктам 6–7.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | from keras.models import Sequential
 | 
						
						
						
							|  |  | from keras.layers import Dense
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | neurons = [100, 300, 500]
 | 
						
						
						
							|  |  | results = {}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | for n in neurons:
 | 
						
						
						
							|  |  |     print(f'\n=== Модель со скрытым слоем {n} нейронов ===')
 | 
						
						
						
							|  |  |     
 | 
						
						
						
							|  |  |     # создание модели
 | 
						
						
						
							|  |  |     model = Sequential()
 | 
						
						
						
							|  |  |     model.add(Dense(units=n, input_dim=784, activation='sigmoid'))
 | 
						
						
						
							|  |  |     model.add(Dense(units=num_classes, activation='softmax'))
 | 
						
						
						
							|  |  |     
 | 
						
						
						
							|  |  |     # компиляция модели
 | 
						
						
						
							|  |  |     model.compile(loss='categorical_crossentropy',
 | 
						
						
						
							|  |  |                   optimizer='sgd',
 | 
						
						
						
							|  |  |                   metrics=['accuracy'])
 | 
						
						
						
							|  |  |     
 | 
						
						
						
							|  |  |     # обучение модели
 | 
						
						
						
							|  |  |     H = model.fit(X_train, y_train,
 | 
						
						
						
							|  |  |                   validation_split=0.1,
 | 
						
						
						
							|  |  |                   epochs=50,
 | 
						
						
						
							|  |  |                   verbose=1)  # чтобы не печатать все эпохи
 | 
						
						
						
							|  |  |     
 | 
						
						
						
							|  |  |     # оценка на тестовых данных
 | 
						
						
						
							|  |  |     scores = model.evaluate(X_test, y_test, verbose=1)
 | 
						
						
						
							|  |  |     results[n] = scores[1]
 | 
						
						
						
							|  |  |     
 | 
						
						
						
							|  |  |     print(f'Accuracy on test data: {scores[1]:.4f}')
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | === Модель со скрытым слоем 100 нейронов ===
 | 
						
						
						
							|  |  | Accuracy on test data: 0.9445
 | 
						
						
						
							|  |  | === Модель со скрытым слоем 300 нейронов ===
 | 
						
						
						
							|  |  | Accuracy on test data: 0.9347
 | 
						
						
						
							|  |  | === Модель со скрытым слоем 500 нейронов ===
 | 
						
						
						
							|  |  | Accuracy on test data: 0.9310
 | 
						
						
						
							|  |  | ```
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | plt.figure()
 | 
						
						
						
							|  |  | plt.plot(list(results.keys()), list(results.values()), marker='o')
 | 
						
						
						
							|  |  | plt.grid()
 | 
						
						
						
							|  |  | plt.title('Accuracy on test data depending on hidden layer size')
 | 
						
						
						
							|  |  | plt.xlabel('Number of neurons in hidden layer')
 | 
						
						
						
							|  |  | plt.ylabel('Accuracy')
 | 
						
						
						
							|  |  | plt.show()
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Результаты тестирования показывают, что с увеличением числа нейронов в скрытом слое точность модели немного снижается. По графику видно, что увеличение числа нейронов не приводит к улучшению качества классификации,
 | 
						
						
						
							|  |  | а наоборот, вызывает лёгкое переобучение и снижение точности на тестовой выборке.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Таким образом, наилучший результат достигается при 100 нейронах в скрытом слое
 | 
						
						
						
							|  |  | (accuracy ≈ 94,45%), что делает эту архитектуру оптимальной для данной задачи.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ### *9. Добавить в наилучшую архитектуру, определённую в п. 8, второй скрытый слой и провести обучение и тестирование (повторить п. 6–7) при 50 и 100 нейронах во втором скрытом слое. В качестве функции активации нейронов в скрытом слое использовать функцию sigmoid.*
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | В качестве основы берётся лучшая модель из п. 8 — сеть с одним скрытым слоем из 100 нейронов.
 | 
						
						
						
							|  |  | Теперь добавляется второй скрытый слой с 50 или 100 нейронами и функцией активации `sigmoid`.
 | 
						
						
						
							|  |  | Выходной слой остаётся таким же — 10 нейронов с активацией `softmax`.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Функция `softmax` преобразует выходные значения нейронов в вероятности:
 | 
						
						
						
							|  |  | сумма всех выходов равна 1, а каждое значение показывает уверенность модели в том,
 | 
						
						
						
							|  |  | что изображение принадлежит соответствующему классу.
 | 
						
						
						
							|  |  | Наибольшее значение определяет предсказанный класс.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | from keras.models import Sequential
 | 
						
						
						
							|  |  | from keras.layers import Dense
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | hidden2 = [50, 100]
 | 
						
						
						
							|  |  | results_2 = {}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | for n2 in hidden2:
 | 
						
						
						
							|  |  |     print(f'\n=== Модель со вторым скрытым слоем {n2} нейронов ===')
 | 
						
						
						
							|  |  |     
 | 
						
						
						
							|  |  |     # создание модели
 | 
						
						
						
							|  |  |     model2 = Sequential()
 | 
						
						
						
							|  |  |     model2.add(Dense(units=100, input_dim=784, activation='sigmoid'))  # первый скрытый слой
 | 
						
						
						
							|  |  |     model2.add(Dense(units=n2, activation='sigmoid'))                   # второй скрытый слой
 | 
						
						
						
							|  |  |     model2.add(Dense(units=num_classes, activation='softmax'))          # выходной слой
 | 
						
						
						
							|  |  |     
 | 
						
						
						
							|  |  |     # компиляция модели
 | 
						
						
						
							|  |  |     model2.compile(loss='categorical_crossentropy',
 | 
						
						
						
							|  |  |                    optimizer='sgd',
 | 
						
						
						
							|  |  |                    metrics=['accuracy'])
 | 
						
						
						
							|  |  |     
 | 
						
						
						
							|  |  |     # обучение модели
 | 
						
						
						
							|  |  |     H2 = model2.fit(X_train, y_train,
 | 
						
						
						
							|  |  |                     validation_split=0.1,
 | 
						
						
						
							|  |  |                     epochs=50,
 | 
						
						
						
							|  |  |                     verbose=1)
 | 
						
						
						
							|  |  |     
 | 
						
						
						
							|  |  |     # оценка на тестовых данных
 | 
						
						
						
							|  |  |     scores = model2.evaluate(X_test, y_test, verbose=1)
 | 
						
						
						
							|  |  |     results_2[n2] = scores[1]
 | 
						
						
						
							|  |  |     
 | 
						
						
						
							|  |  |     print(f'Accuracy on test data: {scores[1]:.4f}')
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | === Модель со вторым скрытым слоем 50 нейронов ===
 | 
						
						
						
							|  |  | Accuracy on test data: 0.9443
 | 
						
						
						
							|  |  | === Модель со вторым скрытым слоем 100 нейронов ===
 | 
						
						
						
							|  |  | Accuracy on test data: 0.9445
 | 
						
						
						
							|  |  | ```
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Добавление второго скрытого слоя не привело к заметному улучшению качества классификации — точность осталась примерно на уровне 94,4 %, как и у модели с одним скрытым слоем.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Это объясняется тем, что задача распознавания цифр из набора MNIST относительно простая, и уже один скрытый слой с 100 нейронами способен выделять необходимые закономерности.
 | 
						
						
						
							|  |  | Добавление второго слоя увеличивает количество параметров, но не добавляет новой информации, которую сеть могла бы использовать.
 | 
						
						
						
							|  |  | Кроме того, использование функции активации sigmoid ограничивает способность сети обучать глубокие слои из-за эффекта затухающего градиента, поэтому более сложная архитектура не даёт заметного прироста точности.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ### *10. Результаты исследования архитектуры нейронной сети занести в таблицу:*
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | |  №  | Количество скрытых слоёв | Нейронов в 1-м скрытом слое | Нейронов во 2-м скрытом слое | Accuracy на тестовых данных |
 | 
						
						
						
							|  |  | | :-: | :----------------------: | :-------------------------: | :--------------------------: | :-------------------------: |
 | 
						
						
						
							|  |  | |  1  |             0            |              –              |               –              |            0.923            |
 | 
						
						
						
							|  |  | |  2  |             1            |             100             |               –              |          **0.9445**         |
 | 
						
						
						
							|  |  | |  3  |             1            |             300             |               –              |            0.9347           |
 | 
						
						
						
							|  |  | |  4  |             1            |             500             |               –              |            0.9310           |
 | 
						
						
						
							|  |  | |  5  |             2            |             100             |              50              |            0.9443           |
 | 
						
						
						
							|  |  | |  6  |             2            |             100             |              100             |            0.9445           |
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Вывод:
 | 
						
						
						
							|  |  | С ростом числа нейронов и слоёв качество классификации не всегда улучшается.
 | 
						
						
						
							|  |  | Наилучший результат (accuracy ≈ 0.9445) показали модели: с одним скрытым слоем из 100 нейронов, и с двумя скрытыми слоями (100 + 100 нейронов).
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Датасет MNIST относительно простой — цифры хорошо различимы, и даже простая однослойная модель способна выделить нужные закономерности. Более сложная сеть не получает новой информации, которую можно было бы использовать для улучшения качества. К тому же, увеличение числа нейронов и слоёв повышает риск переобучения. Модель начинает «запоминать» обучающие данные, теряя способность обобщать новые примеры. Это приводит к снижению точности на тестовой выборке. Использование функции активации `sigmoid` в скрытых слоях также ограничивает обучение глубоких моделей. При большом числе слоёв градиенты становятся очень малыми (эффект `vanishing gradient`), и обучение перестаёт быть эффективным.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ### *11. Сохранить  наилучшую  нейронную  сеть  на  диск.  Данную  нейронную сеть потребуется загрузить с диска в одной из следующих лабораторных работ.*
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | model2.save('/content/drive/MyDrive/Colab Notebooks/best_model_2x100.h5')
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
 | 
						
						
						
							|  |  | ``` 
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ### *12. Для нейронной сети наилучшей архитектуры вывести два тестовых изображения, истинные метки и результат распознавания изображений.*
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Для визуальной проверки работы обученной модели выбраны два случайных изображения из тестовой выборки. Модель предсказывает класс (цифру), который с наибольшей вероятностью соответствует изображению. Сравним предсказанные значения с истинными метками.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | import numpy as np
 | 
						
						
						
							|  |  | import matplotlib.pyplot as plt
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | # выбираем индексы двух случайных изображений
 | 
						
						
						
							|  |  | indices = np.random.choice(range(X_test.shape[0]), 2, replace=False)
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | # получаем предсказания
 | 
						
						
						
							|  |  | predictions = model2.predict(X_test[indices])
 | 
						
						
						
							|  |  | predicted_labels = np.argmax(predictions, axis=1)
 | 
						
						
						
							|  |  | true_labels = np.argmax(y_test[indices], axis=1)
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | # вывод изображений и меток
 | 
						
						
						
							|  |  | plt.figure(figsize=(6, 3))
 | 
						
						
						
							|  |  | for i, idx in enumerate(indices):
 | 
						
						
						
							|  |  |     plt.subplot(1, 2, i + 1)
 | 
						
						
						
							|  |  |     plt.imshow(X_test[idx].reshape(28, 28), cmap='gray')
 | 
						
						
						
							|  |  |     plt.title(f'True: {true_labels[i]}, Pred: {predicted_labels[i]}')
 | 
						
						
						
							|  |  |     plt.axis('off')
 | 
						
						
						
							|  |  | plt.show()
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ``` 
 | 
						
						
						
							|  |  | `np.argmax()` извлекает индекс максимального значения в векторе вероятностей (то есть номер класса). `model2.predict()` возвращает вероятность принадлежности изображения к каждому из 10 классов. Для наглядности в заголовках указаны истинная метка (True) и предсказанная моделью (Pred).
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ### *13. Каждому члену бригады создать собственное изображение рукописной цифры, подобное представленным в наборе MNIST. Цифру выбрать как остаток от деления на 10 числа своего дня рождения (например, 29 февраля → 29 mod10 = 9). Сохранить изображения. Загрузить, предобработать и подать  на  вход  обученной  нейронной  сети  собственные  изображения. Вывести изображения и результаты распознавания.*
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Членам бригады соответствуют цифры 5 и 6. Они были написаны от руки, отсканированы и преобразованы в черно-белый формат размером по 28 х 28 пикселей.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Загрузка файлов:
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | from google.colab import files
 | 
						
						
						
							|  |  | uploaded = files.upload()
 | 
						
						
						
							|  |  | ```
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Предварительная подготовка аналогично началу работы:
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | from tensorflow.keras.preprocessing import image
 | 
						
						
						
							|  |  | import numpy as np
 | 
						
						
						
							|  |  | import matplotlib.pyplot as plt
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | file_names = list(uploaded.keys())
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | X_custom = []
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | for fname in file_names:
 | 
						
						
						
							|  |  |     # загружаем изображение в оттенках серого и приводим к 28×28
 | 
						
						
						
							|  |  |     img = image.load_img(fname, color_mode='grayscale', target_size=(28, 28))
 | 
						
						
						
							|  |  |     img_array = image.img_to_array(img)
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |     # инвертируем цвета, если фон белый (MNIST — белая цифра на чёрном фоне)
 | 
						
						
						
							|  |  |     img_array = 255 - img_array  
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |     # нормализация
 | 
						
						
						
							|  |  |     img_array = img_array / 255.0
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |     # разворачиваем в вектор длиной 784
 | 
						
						
						
							|  |  |     img_flat = img_array.reshape(1, 784)
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |     X_custom.append(img_flat)
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | X_custom = 1 - X_custom
 | 
						
						
						
							|  |  | predictions = model2.predict(X_custom)
 | 
						
						
						
							|  |  | print(np.argmax(predictions, axis=1))
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | [5 6]
 | 
						
						
						
							|  |  | ```
 | 
						
						
						
							|  |  | `image.load_img`(..., `color_mode='grayscale'`) — загружает изображение в градациях серого.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | `img_array / 255.0` — нормирует значения пикселей в диапазон [0, 1].
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | `X_custom = 1 - X_custom` — выполняет инвертирование яркости, чтобы фон стал тёмным, а цифра светлой — как в наборе MNIST. Без этой операции модель ошибалась, воспринимая цифру как фон.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | `np.argmax(predictions, axis=1)` — определяет итоговые распознанные цифры.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ### *14. Каждому члену бригады создать копию собственного изображения, отличающуюся от оригинала поворотом на 90 градусов в любую сторону. Сохранить изображения. Загрузить, предобработать и подать на вход обученной нейронной сети изменённые изображения. Вывести изображения и результаты распознавания.*
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Для проверки устойчивости модели к поворотам изображений были созданы версии исходных цифр, повёрнутые на 90°. Далее выполняется их загрузка, нормализация, инверсия и распознавание обученной моделью.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | from google.colab import files
 | 
						
						
						
							|  |  | uploaded = files.upload()
 | 
						
						
						
							|  |  | Число файлов: 2
 | 
						
						
						
							|  |  | 6 (1).png(image/png) - 6089 bytes, last modified: 15.10.2025 - 100% done
 | 
						
						
						
							|  |  | 5 (1).png(image/png) - 6167 bytes, last modified: 15.10.2025 - 100% done
 | 
						
						
						
							|  |  | Saving 6 (1).png to 6 (1) (1).png
 | 
						
						
						
							|  |  | Saving 5 (1).png to 5 (1).png
 | 
						
						
						
							|  |  | ```
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | from tensorflow.keras.preprocessing import image
 | 
						
						
						
							|  |  | import numpy as np
 | 
						
						
						
							|  |  | import matplotlib.pyplot as plt
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | file_names = list(uploaded.keys())
 | 
						
						
						
							|  |  | X_rotated = []
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | for fname in file_names:
 | 
						
						
						
							|  |  |     # загружаем повернутое изображение и приводим к формату 28×28
 | 
						
						
						
							|  |  |     img = image.load_img(fname, color_mode='grayscale', target_size=(28, 28))
 | 
						
						
						
							|  |  |     img_array = image.img_to_array(img) / 255.0   # нормализация
 | 
						
						
						
							|  |  |     
 | 
						
						
						
							|  |  |     # разворачиваем в вектор длиной 784
 | 
						
						
						
							|  |  |     img_flat = img_array.reshape(1, 784)
 | 
						
						
						
							|  |  |     X_rotated.append(img_flat)
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | X_rotated = np.vstack(X_rotated)
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | # инвертируем цвета (цифры — светлые, фон — тёмный)
 | 
						
						
						
							|  |  | X_rotated = 1 - X_rotated
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | # предсказания модели
 | 
						
						
						
							|  |  | predictions = model2.predict(X_rotated)
 | 
						
						
						
							|  |  | predicted_labels = np.argmax(predictions, axis=1)
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | # вывод изображений и предсказаний
 | 
						
						
						
							|  |  | plt.figure(figsize=(6, 3))
 | 
						
						
						
							|  |  | for i, fname in enumerate(file_names):
 | 
						
						
						
							|  |  |     img = image.load_img(fname, color_mode='grayscale', target_size=(28, 28))
 | 
						
						
						
							|  |  |     plt.subplot(1, len(file_names), i + 1)
 | 
						
						
						
							|  |  |     plt.imshow(img, cmap='gray')
 | 
						
						
						
							|  |  |     plt.title(f'Pred: {predicted_labels[i]}')
 | 
						
						
						
							|  |  |     plt.axis('off')
 | 
						
						
						
							|  |  | plt.show()
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | print('Распознанные цифры:', predicted_labels)
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | Распознанные цифры: [3 3]
 | 
						
						
						
							|  |  | ```
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ```python
 | 
						
						
						
							|  |  | np.set_printoptions(precision=4, suppress=True)
 | 
						
						
						
							|  |  | [[0.0003 0.     0.1308 0.8647 0.     0.0037 0.     0.     0.0006 0.    ]
 | 
						
						
						
							|  |  |  [0.0012 0.0001 0.0845 0.8197 0.     0.0825 0.     0.     0.012  0.    ]]
 | 
						
						
						
							|  |  |  ```
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | После поворота изображений на 90° нейронная сеть неверно классифицировала обе цифры, определив их как «3». Это показывает, что модель чувствительна к изменению ориентации изображений и не обладает устойчивостью к поворотам.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | ---
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | В ходе работы была реализована и исследована однослойная и многослойная нейронные сети для классификации изображений из набора MNIST.
 | 
						
						
						
							|  |  | Оптимальной оказалась модель с одним скрытым слоем на 100 нейронов, обеспечившая точность около 94 %.
 | 
						
						
						
							|  |  | Проведённый анализ показал, что увеличение числа нейронов или слоёв не всегда улучшает результат, а устойчивость к поворотам и искажениям не появляется по умолчанию и требует дополнительных методов обучения. |