|
|
|
@ -0,0 +1,529 @@
|
|
|
|
|
|
|
|
## Лабораторная работа №3 ИС. Распознавание изображений
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### Выполнили: Ишутина Е. И., Голубев Т. Л.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
В работе проводится исследование моделей глубокого обучения при классификации изображений. Рассматривались два набора данных: MNIST с черно-белыми изображениями цифр, и CIFAR-10 с цветными изображениями десяти классов (cat, deer, truck и т.д.) размерности 32×32 пикселя. Для обоих наборов была выполнена нормализация и приведение меток классов к формату one-hot.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
One-hot - кодирование данных в виде вектора, содержащего столько элементов, сколько существует классов. Все элементы равны нулю (или близки) кроме значения на позиции, соответствующей истинному классу (там значение ближе к единице). Такой формат нужен в нейронных сетях, где выходной слой формирует распределение вероятностей по классам.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Для набора MNIST обучена сверточная нейронная сеть, а затем произведено её сравнение с лучшей полносвязной моделью из ЛР1. Для набора CIFAR-10 была реализована модель сверточной нейронной сети и оценена результативность ее работы.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Задание 1
|
|
|
|
|
|
|
|
#### *1. В среде Google Colab создать новый блокнот (notebook). Импортировать необходимые для работы библиотеки и модули*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Подключены библиотеки. Создана рабочая директория на Google Диске и зафиксированы генераторы случайных чисел для обеспечения воспроизводимости результатов.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
from google.colab import drive
|
|
|
|
|
|
|
|
drive.mount('/content/drive')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
os.chdir('/content/drive/MyDrive/Colab Notebooks/is_lab3')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from tensorflow import keras
|
|
|
|
|
|
|
|
from tensorflow.keras import layers
|
|
|
|
|
|
|
|
from tensorflow.keras.models import Sequential
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay
|
|
|
|
|
|
|
|
from sklearn.model_selection import train_test_split
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import tensorflow as tf
|
|
|
|
|
|
|
|
tf.random.set_seed(123)
|
|
|
|
|
|
|
|
np.random.seed(123)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
Mounted at /content/drive
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
#### *2. Загрузить набор данных MNIST, содержащий размеченные изображения рукописных цифр.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Загружен набор данных MNIST, включающий 70 000 размеченных изображений рукописных цифр размерностью 28×28 пикселей. Набор состоял из 60 000 изображений обучающей выборки и 10 000 изображений тестовой выборки, при этом каждой матрице пикселей соответствовала метка класса от 0 до 9. После загрузки обе части набора были объединены в единые массивы данных, чтобы потом выполнить разбиение согласно варианту задания.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
from keras.datasets import mnist
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(X_train_full, y_train_full), (X_test_full, y_test_full) = mnist.load_data()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
X = np.concatenate((X_train_full, X_test_full), axis=0)
|
|
|
|
|
|
|
|
y = np.concatenate((y_train_full, y_test_full), axis=0)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
|
|
|
|
|
|
|
|
11490434/11490434 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### *3. Разбить набор данных на обучающие и тестовые данные в соотношении 60000:10000 элементов. При разбиении параметр random_state выбрать равным (4k–1), где k – номер бригады. Вывести размерности полученных обучающих и тестовых массивов данных.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
k = 5
|
|
|
|
|
|
|
|
random_state = 4 * k - 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
X_train, X_test, y_train, y_test = train_test_split(
|
|
|
|
|
|
|
|
X, y, train_size=60000, test_size=10000, random_state=random_state, shuffle=True
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
Выведенные размерности подтвердили корректность проведённого разбиения и соответствие полученных массивов заданным параметрам.
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
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». Вывести размерности предобработанных обучающих и тестовых массивов данных.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Значения пикселей приведены к диапазону [0, 1], метки классов были преобразованы в формат one-hot, где каждый класс представлен вектором длины десять.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
num_classes = 10
|
|
|
|
|
|
|
|
input_shape = (28, 28, 1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# приведение значений к диапазону [0,1]
|
|
|
|
|
|
|
|
X_train = X_train.astype('float32') / 255.0
|
|
|
|
|
|
|
|
X_test = X_test.astype('float32') / 255.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# добавление размерности каналов
|
|
|
|
|
|
|
|
X_train = np.expand_dims(X_train, -1)
|
|
|
|
|
|
|
|
X_test = np.expand_dims(X_test, -1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# one-hot кодирование меток
|
|
|
|
|
|
|
|
y_train_cat = keras.utils.to_categorical(y_train, num_classes)
|
|
|
|
|
|
|
|
y_test_cat = keras.utils.to_categorical(y_test, num_classes)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print('Shape of transformed X_train:', X_train.shape)
|
|
|
|
|
|
|
|
print('Shape of transformed y_train:', y_train_cat.shape)
|
|
|
|
|
|
|
|
print('Shape of transformed X_test:', X_test.shape)
|
|
|
|
|
|
|
|
print('Shape of transformed y_test:', y_test_cat.shape)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
Выведенные размерности подтвердили корректное преобразование изображений в тензоры формы 28×28×1 и меток в матрицы 60000×10 и 10000×10 для обучающей и тестовой выборок соответственно.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
Shape of transformed X_train: (60000, 28, 28, 1)
|
|
|
|
|
|
|
|
Shape of transformed y_train: (60000, 10)
|
|
|
|
|
|
|
|
Shape of transformed X_test: (10000, 28, 28, 1)
|
|
|
|
|
|
|
|
Shape of transformed y_test: (10000, 10)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### *5. Реализовать модель сверточной нейронной сети и обучить ее на обучающих данных с выделением части обучающих данных в качестве валидационных. Вывести информацию об архитектуре нейронной сети.*
|
|
|
|
|
|
|
|
Пояснения по коду:
|
|
|
|
|
|
|
|
* batch_size – размер батча (количество изображений, обрабатываемых одновременно за один шаг градиентного спуска). Используется Sequential API, где слои добавляются один за другим. Это удобно для простых последовательных моделей CNN.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Conv2D(32, (3,3)): 32 фильтра размером 3×3, которые будут сканировать изображение.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* input_shape=input_shape: форма входных данных (например, (28,28,1) для серых изображений MNIST).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* MaxPooling2D(2,2) уменьшает размерность признаков в 2 раза, выбирая максимум в каждом окне 2×2.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Dropout(0.5) случайным образом отключает 50% нейронов во время обучения, чтобы уменьшить переобучение.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
batch_size = 512
|
|
|
|
|
|
|
|
epochs = 15
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
|
|
|
|
|
|
|
|
model.summary()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
history = model.fit(X_train, y_train_cat, batch_size=batch_size, epochs=epochs, validation_split=0.1)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
/usr/local/lib/python3.12/dist-packages/keras/src/layers/convolutional/base_conv.py:113: 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 # |
|
|
|
|
|
|
|
|
|----------------------------|----------------------|---------|
|
|
|
|
|
|
|
|
| conv2d (Conv2D) | (None, 26, 26, 32) | 320 |
|
|
|
|
|
|
|
|
| max_pooling2d (MaxPooling) | (None, 13, 13, 32) | 0 |
|
|
|
|
|
|
|
|
| conv2d_1 (Conv2D) | (None, 11, 11, 64) | 18,496 |
|
|
|
|
|
|
|
|
| max_pooling2d_1 (MaxPooling) | (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)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### *6. Оценить качество обучения на тестовых данных. Вывести значение функции ошибки и значение метрики качества классификациина тестовых данных.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
scores = model.evaluate(X_test, y_test_cat, verbose=2)
|
|
|
|
|
|
|
|
print('Loss on test data:', scores[0])
|
|
|
|
|
|
|
|
print('Accuracy on test data:', scores[1])
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
313/313 - 3s - 8ms/step - accuracy: 0.9879 - loss: 0.0402
|
|
|
|
|
|
|
|
Loss on test data: 0.04024936258792877
|
|
|
|
|
|
|
|
Accuracy on test data: 0.9879000186920166
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
Vодель обучена хорошо и показывает высокое качество на тестовой выборке.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### *7. Подать на вход обученной модели два тестовых изображения. Вывести изображения, истинные метки и результаты распознавания.*
|
|
|
|
|
|
|
|
Пояснения по коду:
|
|
|
|
|
|
|
|
* Берём два изображения из тестового набора (с индексами 0 и 1).
|
|
|
|
|
|
|
|
* X_test[n:n+1] — формируем батч из одного изображения.
|
|
|
|
|
|
|
|
* model.predict() возвращает вектор вероятностей для каждого класса (10 элементов для цифр 0–9).
|
|
|
|
|
|
|
|
* NN output vector показывает вероятности для всех 10 классов.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
indices = [0, 1]
|
|
|
|
|
|
|
|
for n in indices:
|
|
|
|
|
|
|
|
result = model.predict(X_test[n:n+1])
|
|
|
|
|
|
|
|
plt.figure()
|
|
|
|
|
|
|
|
plt.imshow(X_test[n].reshape(28,28), cmap='gray')
|
|
|
|
|
|
|
|
plt.title(f"Real: {y_test[n]} Pred: {np.argmax(result)}")
|
|
|
|
|
|
|
|
plt.axis('off')
|
|
|
|
|
|
|
|
plt.show()
|
|
|
|
|
|
|
|
print('NN output vector:', result)
|
|
|
|
|
|
|
|
print('Real mark:', y_test[n])
|
|
|
|
|
|
|
|
print('NN answer:', np.argmax(result))
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
NN output vector: [[3.5711860e-08 3.5435047e-11 6.5117740e-07 7.4699518e-09 5.9110135e-08
|
|
|
|
|
|
|
|
1.4115658e-03 9.9851364e-01 2.6488631e-12 7.4022493e-05 2.6488609e-10]]
|
|
|
|
|
|
|
|
Real mark: 6
|
|
|
|
|
|
|
|
NN answer: 6
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
NN output vector: [[9.2878885e-08 3.3229617e-06 4.1963812e-04 3.1485452e-04 1.7722991e-09
|
|
|
|
|
|
|
|
2.6501787e-09 5.7302459e-13 9.9888808e-01 1.0063148e-05 3.6401587e-04]]
|
|
|
|
|
|
|
|
Real mark: 7
|
|
|
|
|
|
|
|
NN answer: 7
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
Как видно, модель верно распознала случайно выбранные две цифры. В выходном векторе у всех значений, кроме позиции верного класса, были значения порядка 10^-4 - 10^-13. Значение для верного класса близко к единице.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### *8. Вывести отчет о качестве классификации тестовой выборки и матрицу ошибок для тестовой выборки.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Precision = 0.99 для класса 0 означает, что почти все объекты, которые сеть предсказала как «0», действительно 0. Recall 1.00 для класса 0 означает, что сеть нашла все объекты «0» в тестовой выборке.
|
|
|
|
|
|
|
|
Accuracy (общая точность) = 0.99 дает понять, что модель правильно классифицирует 99% изображений.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* В матрице ошибок основная часть значений находится на диагонали, а значит, большинство предсказаний верные. С помощью небольших ошибок вне диагоналей можно понять, какие числа нейросеть «путает». Например, сеть может перепутать «4» и «9», или «3» и «5», если они визуально похожи.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
true_labels = y_test
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
precision recall f1-score support
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
0 0.99 1.00 0.99 969
|
|
|
|
|
|
|
|
1 0.99 0.99 0.99 1155
|
|
|
|
|
|
|
|
2 0.99 0.98 0.98 969
|
|
|
|
|
|
|
|
3 0.99 0.99 0.99 1032
|
|
|
|
|
|
|
|
4 1.00 0.98 0.99 1016
|
|
|
|
|
|
|
|
5 0.98 0.99 0.98 898
|
|
|
|
|
|
|
|
6 0.99 0.99 0.99 990
|
|
|
|
|
|
|
|
7 0.98 0.99 0.99 1038
|
|
|
|
|
|
|
|
8 0.99 0.98 0.99 913
|
|
|
|
|
|
|
|
9 0.99 0.98 0.98 1020
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
accuracy 0.99 10000
|
|
|
|
|
|
|
|
macro avg 0.99 0.99 0.99 10000
|
|
|
|
|
|
|
|
weighted avg 0.99 0.99 0.99 10000
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### *9. Загрузить, предобработать и подать на вход обученной нейронной сети собственное изображение, созданное при выполнении лабораторной работы №1. Вывести изображение и результат распознавания.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
from PIL import Image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
img_path = '../5.png'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
file_data = Image.open(img_path)
|
|
|
|
|
|
|
|
file_data = file_data.convert('L') # перевод в градации серого
|
|
|
|
|
|
|
|
test_img = np.array(file_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
plt.imshow(test_img, cmap='gray')
|
|
|
|
|
|
|
|
plt.axis('off')
|
|
|
|
|
|
|
|
plt.show()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# нормализация и изменение формы
|
|
|
|
|
|
|
|
test_proc = test_img.astype('float32') / 255.0
|
|
|
|
|
|
|
|
test_proc = np.reshape(test_proc, (1, 28, 28, 1))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
result = model.predict(test_proc)
|
|
|
|
|
|
|
|
print("NN output vector:", result)
|
|
|
|
|
|
|
|
print("I think it's", np.argmax(result))
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
NN output vector: [[1.5756325e-12 5.2755486e-15 1.4891595e-10 7.3797599e-07 1.8559115e-12
|
|
|
|
|
|
|
|
9.9998915e-01 3.5407410e-08 5.2025315e-12 1.5018414e-06 8.6681475e-06]]
|
|
|
|
|
|
|
|
I think it's 5
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 10. Загрузить с диска модель, сохраненную при выполнении лабораторной работы №1. Вывести информацию об архитектуре модели.Повторить для этой модели п.6.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
При работе с моделью из ЛР1 необходимо взять данные в исходном формате, иначе получится двойная нормализация. В CNN данные нормализовались на этапе подготовки к сети (X/255.0 и reshape к (28,28,1)), но модель из ЛР1 ожидала плоский вектор 784 элементов на изображение.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
# возьмём оригинальные X, y — до всех преобразований для CNN
|
|
|
|
|
|
|
|
(X_train_full, y_train_full), (X_test_full, y_test_full) = mnist.load_data()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# объединим, чтобы сделать то же разбиение, что и в ЛР1
|
|
|
|
|
|
|
|
X_all = np.concatenate((X_train_full, X_test_full), axis=0)
|
|
|
|
|
|
|
|
y_all = np.concatenate((y_train_full, y_test_full), axis=0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from sklearn.model_selection import train_test_split
|
|
|
|
|
|
|
|
X_train_l1, X_test_l1, y_train_l1, y_test_l1 = train_test_split(
|
|
|
|
|
|
|
|
X_all, y_all, train_size=60000, test_size=10000, random_state=19
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# теперь — подготовка данных ЛР1
|
|
|
|
|
|
|
|
X_test_lr1 = X_test_l1.reshape((X_test_l1.shape[0], 28*28)).astype('float32') / 255.0
|
|
|
|
|
|
|
|
y_test_lr1 = keras.utils.to_categorical(y_test_l1, 10)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# оценка модели
|
|
|
|
|
|
|
|
scores_lr1 = model_lr1.evaluate(X_test_lr1, y_test_lr1, verbose=2)
|
|
|
|
|
|
|
|
print(scores_lr1)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
Точность уменьшилась, так как в ЛР1 использовалась полносвязная сеть, а не сверточная. Сверточные сети лучше извлекают признаки у изображений, а потому дают большую точность.
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
313/313 - 2s - 6ms/step - accuracy: 0.9445 - loss: 0.1969
|
|
|
|
|
|
|
|
[0.1968761384487152, 0.9445000290870667]
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 11. Сравнить обученную модель сверточной сети и наилучшую модель полносвязной сети из лабораторной работы №1 по следующим показателям: количество настраиваемых параметров в сети, количество эпох обучения, качество классификации тестовой выборки. Сделать выводы по результатам применения сверточной нейронной сети для распознавания изображений.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
# загрузка сохранённой модели ЛР1
|
|
|
|
|
|
|
|
model_lr1_path = '../best_model_2x100.h5'
|
|
|
|
|
|
|
|
model_lr1 = load_model(model_lr1_path)
|
|
|
|
|
|
|
|
model_lr1.summary()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# подготовка тестового набора для модели ЛР1
|
|
|
|
|
|
|
|
X_test_l1 = X_test_l1.reshape((X_test_l1.shape[0], 28 * 28)).astype('float32') / 255.0
|
|
|
|
|
|
|
|
y_test_l1_cat = keras.utils.to_categorical(y_test_l1, 10)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# оценка модели ЛР1
|
|
|
|
|
|
|
|
scores_lr1 = model_lr1.evaluate(X_test_l1, y_test_l1_cat, verbose=2)
|
|
|
|
|
|
|
|
print('LR1 model - Loss:', scores_lr1[0])
|
|
|
|
|
|
|
|
print('LR1 model - Accuracy:', scores_lr1[1])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# оценка сверточной модели ЛР3
|
|
|
|
|
|
|
|
scores_conv = model.evaluate(X_test, y_test_cat, verbose=2)
|
|
|
|
|
|
|
|
print('Conv model - Loss:', scores_conv[0])
|
|
|
|
|
|
|
|
print('Conv model - Accuracy:', scores_conv[1])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# вывод числа параметров обеих моделей
|
|
|
|
|
|
|
|
print('LR1 model parameters:', model_lr1.count_params())
|
|
|
|
|
|
|
|
print('Conv model parameters:', model.count_params())
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
В MLP (ЛР1) количество параметров = 89610, а в CNN (ЛР3) оно равно 34826. CNN имеет значительно меньше параметров, примерно в 2,5 раза меньше, чем MLP. Это произошло потому, что в сверточных слоях параметры делятся по ядрам свертки и применяются к локальным областям изображения, что снижает избыточность. MLP полностью соединяет все нейроны между слоями, а значит, имеет больше весов. Меньшее число параметров влечет к меньшей вероятности переобучения и более экономное использование памяти.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Значение функции потерь у CNN почти в 5 раз меньше, что указывает на лучшее соответствие предсказаний истинным меткам.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
WARNING:absl:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.
|
|
|
|
|
|
|
|
Model: "sequential_9"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| Layer (type) | Output shape | Param # |
|
|
|
|
|
|
|
|
|--------------|--------------|---------|
|
|
|
|
|
|
|
|
| dense_18 | (None, 100) | 78,500 |
|
|
|
|
|
|
|
|
| dense_19 | (None, 100) | 10,100 |
|
|
|
|
|
|
|
|
| dense_20 | (None, 10) | 1,010 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Total params: 89,612 (350.05 KB)
|
|
|
|
|
|
|
|
Trainable params: 89,610 (350.04 KB)
|
|
|
|
|
|
|
|
Non-trainable params: 0 (0.00 B)
|
|
|
|
|
|
|
|
Optimizer params: 2 (12.00 B)
|
|
|
|
|
|
|
|
313/313 - 3s - 9ms/step - accuracy: 0.9445 - loss: 0.1969
|
|
|
|
|
|
|
|
LR1 model - Loss: 0.1968761384487152
|
|
|
|
|
|
|
|
LR1 model - Accuracy: 0.9445000290870667
|
|
|
|
|
|
|
|
313/313 - 6s - 20ms/step - accuracy: 0.9879 - loss: 0.0402
|
|
|
|
|
|
|
|
Conv model - Loss: 0.04024936258792877
|
|
|
|
|
|
|
|
Conv model - Accuracy: 0.9879000186920166
|
|
|
|
|
|
|
|
LR1 model parameters: 89610
|
|
|
|
|
|
|
|
Conv model parameters: 34826
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Задание 2.
|
|
|
|
|
|
|
|
#### *1–3. Загрузка CIFAR-10 и разбиение 50 000 : 10 000, вывод 25 изображений*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CIFAR-10 — это стандартный набор цветных изображений маленького размера (32×32 пикселя) с 10 классами объектов, включая транспорт, животных и птиц, предназначенный для задач классификации изображений.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Аналогично заданию №1, данные нормализуются и преобразовываются в формат one-hot.
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
from keras.datasets import cifar10
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(X_train_c, y_train_c), (X_test_c, y_test_c) = cifar10.load_data()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print('Shapes (original):', X_train_c.shape, y_train_c.shape, X_test_c.shape, y_test_c.shape)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
|
|
|
|
|
|
|
|
'dog', 'frog', 'horse', 'ship', 'truck']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# вывод 25 изображений
|
|
|
|
|
|
|
|
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_c[i])
|
|
|
|
|
|
|
|
plt.xlabel(class_names[y_train_c[i][0]])
|
|
|
|
|
|
|
|
plt.show()
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
|
|
|
|
|
|
|
|
170498071/170498071 ━━━━━━━━━━━━━━━━━━━━ 4s 0us/step
|
|
|
|
|
|
|
|
Shapes (original): (50000, 32, 32, 3) (50000, 1) (10000, 32, 32, 3) (10000, 1)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### *4. Предобработка CIFAR-10 (нормализация и one-hot)*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
num_classes = 10
|
|
|
|
|
|
|
|
input_shape_cifar = (32, 32, 3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
X_train_c = X_train_c.astype('float32') / 255.0
|
|
|
|
|
|
|
|
X_test_c = X_test_c.astype('float32') / 255.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
y_train_c_cat = keras.utils.to_categorical(y_train_c, num_classes)
|
|
|
|
|
|
|
|
y_test_c_cat = keras.utils.to_categorical(y_test_c, num_classes)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print('Transformed shapes:', X_train_c.shape, y_train_c_cat.shape, X_test_c.shape, y_test_c_cat.shape)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
Transformed shapes: (50000, 32, 32, 3) (50000, 10) (10000, 32, 32, 3) (10000, 10)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### *5. Реализация и обучение сверточной сети для CIFAR-10*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Используются три слоя Conv2D с увеличивающимся числом фильтров (32 → 64 → 128) для извлечения признаков с изображений CIFAR-10. Между сверточными слоями используются MaxPooling2D для уменьшения размерности и концентрации на важных признаках.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
model_cifar = Sequential()
|
|
|
|
|
|
|
|
model_cifar.add(layers.Conv2D(32, (3,3), activation='relu', input_shape=input_shape_cifar))
|
|
|
|
|
|
|
|
model_cifar.add(layers.MaxPooling2D((2,2)))
|
|
|
|
|
|
|
|
model_cifar.add(layers.Conv2D(64, (3,3), activation='relu'))
|
|
|
|
|
|
|
|
model_cifar.add(layers.MaxPooling2D((2,2)))
|
|
|
|
|
|
|
|
model_cifar.add(layers.Conv2D(128, (3,3), activation='relu'))
|
|
|
|
|
|
|
|
model_cifar.add(layers.MaxPooling2D((2,2)))
|
|
|
|
|
|
|
|
model_cifar.add(layers.Flatten())
|
|
|
|
|
|
|
|
model_cifar.add(layers.Dense(128, activation='relu'))
|
|
|
|
|
|
|
|
model_cifar.add(layers.Dropout(0.5))
|
|
|
|
|
|
|
|
model_cifar.add(layers.Dense(num_classes, activation='softmax'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
model_cifar.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
|
|
|
|
|
|
|
|
model_cifar.summary()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
batch_size = 512
|
|
|
|
|
|
|
|
epochs = 20
|
|
|
|
|
|
|
|
history_cifar = model_cifar.fit(X_train_c, y_train_c_cat, batch_size=batch_size, epochs=epochs, validation_split=0.1)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
/usr/local/lib/python3.12/dist-packages/keras/src/layers/convolutional/base_conv.py:113: 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_1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| Layer (type) | Output Shape | Param # |
|
|
|
|
|
|
|
|
| ------------------------------ | ------------------ | ------- |
|
|
|
|
|
|
|
|
| conv2d_2 (Conv2D) | (None, 30, 30, 32) | 896 |
|
|
|
|
|
|
|
|
| max_pooling2d_2 (MaxPooling2D) | (None, 15, 15, 32) | 0 |
|
|
|
|
|
|
|
|
| conv2d_3 (Conv2D) | (None, 13, 13, 64) | 18,496 |
|
|
|
|
|
|
|
|
| max_pooling2d_3 (MaxPooling2D) | (None, 6, 6, 64) | 0 |
|
|
|
|
|
|
|
|
| conv2d_4 (Conv2D) | (None, 4, 4, 128) | 73,856 |
|
|
|
|
|
|
|
|
| max_pooling2d_4 (MaxPooling2D) | (None, 2, 2, 128) | 0 |
|
|
|
|
|
|
|
|
| flatten_1 (Flatten) | (None, 512) | 0 |
|
|
|
|
|
|
|
|
| dense_1 (Dense) | (None, 128) | 65,664 |
|
|
|
|
|
|
|
|
| dropout_1 (Dropout) | (None, 128) | 0 |
|
|
|
|
|
|
|
|
| dense_2 (Dense) | (None, 10) | 1,290 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Total params: 160,202 (625.79 KB)
|
|
|
|
|
|
|
|
Trainable params: 160,202 (625.79 KB)
|
|
|
|
|
|
|
|
Non-trainable params: 0 (0.00 B)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
#### *6. Оценка качества на тестовой выборке CIFAR-10*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
scores_cifar = model_cifar.evaluate(X_test_c, y_test_c_cat, verbose=2)
|
|
|
|
|
|
|
|
print('CIFAR - Loss on test data:', scores_cifar[0])
|
|
|
|
|
|
|
|
print('CIFAR - Accuracy on test data:', scores_cifar[1])
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
313/313 - 8s - 26ms/step - accuracy: 0.6855 - loss: 0.8885
|
|
|
|
|
|
|
|
CIFAR - Loss on test data: 0.8884508609771729
|
|
|
|
|
|
|
|
CIFAR - Accuracy on test data: 0.6855000257492065
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### *7-8. Подать два тестовых изображения: одно верно, другое ошибочно. Вывести отчет о качестве классификации тестовой выборки и матрицу ошибок для тестовой выборки*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
print(classification_report(true_cifar, preds_cifar, target_names=class_names))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
conf_matrix_cifar = confusion_matrix(true_cifar, preds_cifar)
|
|
|
|
|
|
|
|
display = ConfusionMatrixDisplay(confusion_matrix=conf_matrix_cifar,
|
|
|
|
|
|
|
|
display_labels=class_names)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
plt.figure(figsize=(10,10)) # figsize задаётся здесь
|
|
|
|
|
|
|
|
display.plot(cmap='Blues', colorbar=False) # без figsize
|
|
|
|
|
|
|
|
plt.xticks(rotation=45)
|
|
|
|
|
|
|
|
plt.show()
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
precision recall f1-score support
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
airplane 0.78 0.66 0.71 1000
|
|
|
|
|
|
|
|
automobile 0.82 0.81 0.81 1000
|
|
|
|
|
|
|
|
bird 0.61 0.55 0.58 1000
|
|
|
|
|
|
|
|
cat 0.49 0.43 0.46 1000
|
|
|
|
|
|
|
|
deer 0.62 0.67 0.64 1000
|
|
|
|
|
|
|
|
dog 0.51 0.71 0.59 1000
|
|
|
|
|
|
|
|
frog 0.81 0.73 0.77 1000
|
|
|
|
|
|
|
|
horse 0.72 0.71 0.71 1000
|
|
|
|
|
|
|
|
ship 0.77 0.82 0.80 1000
|
|
|
|
|
|
|
|
truck 0.80 0.76 0.78 1000
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
accuracy 0.69 10000
|
|
|
|
|
|
|
|
macro avg 0.69 0.69 0.69 10000
|
|
|
|
|
|
|
|
weighted avg 0.69 0.69 0.69 10000
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<Figure size 1000x1000 with 0 Axes>
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Для CIFAR-10 точность составила ~68.55%, что ниже, чем для MNIST, из-за большей сложности изображений (цветные, более сложные объекты).
|
|
|
|
|
|
|
|
Видно, что классы cat и dog хуже распознаются (точность 0.49 и 0.51), а automobile, ship, truck распознаются лучше (~0.8).
|
|
|
|
|
|
|
|
Матрица ошибок (ConfusionMatrixDisplay) позволяет визуально увидеть, какие классы чаще путаются между собой (например, cat и dog).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Вывод
|
|
|
|
|
|
|
|
Проведенное исследование показало, что сверточные нейронные сети превосходят полносвязные модели в задачах распознавания изображений. Для MNIST сверточная сеть достигла точности 98,79% при меньшем числе параметров (34 826 против 89 610 у MLP), что обеспечивает более экономное использование памяти и снижает риск переобучения.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
На более сложном наборе CIFAR-10 сеть показала точность 68,55%, при этом объекты с визуально схожими признаками, например кошки и собаки, распознаются хуже, чем однозначные объекты, такие как автомобили и корабли. Результаты демонстрируют, что сверточные сети эффективно извлекают признаки из изображений и обеспечивают высокое качество классификации, особенно на структурированных данных, хотя для сложных цветных изображений требуется более глубокая архитектура или дополнительные методы улучшения обучения.
|