Делаем нейронную сеть, которая будет распознавать цифры, написанные от руки
Обязательно изучите введение в нейронные сети . Чтобы решить данную задачу нужно будет создать многослойный персепрон. В прошлый раз я рассказывал как обучить нейронную сеть операции XOR . Нейронная сеть будет очень похожей.
Решение для PyTorch
Для начала, подключим необходимые библиотеки:
_x000D_import torch, math_x000D_import numpy as np_x000D_import matplotlib.pyplot as plt_x000D__x000D_from torch import nn_x000D_from torchsummary import summary_x000D_from torch.utils.data import DataLoader, TensorDataset_x000D__x000D_tensor_device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')_x000D_print ("Device:", tensor_device) Датасет будем брать из базы MNIST. MNIST — это база цифр от 0 до 9, нарисованных отруки.
Скачать ее можно следующей командой:
_x000D_wget https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz -O "mnist.npz"Загрузим датасет:
_x000D_data_orig = np.load("mnist.npz", allow_pickle=True)_x000D_data_orig = {_x000D_ "train": {_x000D_ "x": data_orig["x_train"],_x000D_ "y": data_orig["y_train"],_x000D_ },_x000D_ "test": {_x000D_ "x": data_orig["x_test"],_x000D_ "y": data_orig["y_test"],_x000D_ }_x000D_}Дата сет содержит обучающую и контрольную выборку, а также сами изображения и правильные ответы.
Выведите на экран информации о датасете:
_x000D_print ("Train images", data_orig["train"]["x"].shape)_x000D_print ("Train answers", data_orig["train"]["y"].shape)_x000D_print ("Test images", data_orig["test"]["x"].shape)_x000D_print ("Test answers", data_orig["test"]["y"].shape)Должно вывести следующую информацию:
_x000D_Train images (60000, 28, 28)_x000D_Train answers (60000,)_x000D_Test images (10000, 28, 28)_x000D_Test answers (10000,)Давайте убедимся, что там действительно цифры. Загрузим из датасета определенное фото и отобразим его на экране. Должна отобразиться цифра, которая находится на позиции photo_number.
_x000D_photo_number=256_x000D_print ("Number:", data_orig["train"]["y"][photo_number])_x000D_plt.imshow(data_orig["train"]["x"][photo_number], cmap='gray')_x000D_plt.show()Нормализация данных
Перед тем, как обучать нейронную сеть, нужно нормализовать изображения. Фотографии 28×28 нужно преобразовать в вектора. Также нужно все числа вектора поделить на 255, чтобы они были в диапазоне от 0 до 1 формата float32.
А также правильные ответы должны быть в векторе из 10 цифр. Например:
- число 1 нужно преобразовать в вектор [0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
- число 5 в [0, 0, 0, 0, 0, 1, 0, 0, 0, 0]
_x000D_def get_vector_by_number(count):_x000D_ _x000D_ r"""_x000D_ Преобразует число в списке в выходной вектор_x000D_ Например:_x000D_ 1 -> [0, 1, 0, 0, 0, 0, 0, 0, 0, 0]_x000D_ 5 -> [0, 0, 0, 0, 0, 1, 0, 0, 0, 0]_x000D_ """_x000D_ _x000D_ def f(number):_x000D_ res = [0.0] * count_x000D_ _x000D_ if (number >=0 and number < count):_x000D_ res[number] = 1.0_x000D_ _x000D_ return res_x000D_ _x000D_ return fОбъявим две функции, которые будут нормализовать входящие данные и ответы.
_x000D_def data_normalize_x(data_x):_x000D_ r"""_x000D_ Нормализация датасета по x_x000D_ """_x000D_ data_x = torch.from_numpy(data_x)_x000D_ data_x_shape_len = len(data_x.shape)_x000D_ _x000D_ if data_x_shape_len == 3:_x000D_ data_x = data_x.reshape(data_x.shape[0], -1)_x000D_ elif data_x_shape_len == 2:_x000D_ data_x = data_x.reshape(-1)_x000D_ _x000D_ data_x = data_x.to(torch.float32) / 255.0_x000D_ return data_x_x000D_ _x000D__x000D_def data_normalize_y(data_y):_x000D_ r"""_x000D_ Нормализация датасета по y_x000D_ """_x000D_ data_y = list(map(get_vector_by_number(10), data_y))_x000D_ data_y = torch.tensor( data_y )_x000D_ return data_yСоздадим нормализованный датасет:
_x000D_batch_size = 128_x000D__x000D_data = {_x000D_ "train": {_x000D_ "x": data_normalize_x(data_orig["train"]["x"]),_x000D_ "y": data_normalize_y(data_orig["train"]["y"]),_x000D_ },_x000D_ "test": {_x000D_ "x": data_normalize_x(data_orig["test"]["x"]),_x000D_ "y": data_normalize_y(data_orig["test"]["y"]),_x000D_ }_x000D_}_x000D__x000D_train_dataset = TensorDataset( data["train"]["x"], data["train"]["y"] )_x000D_test_dataset = TensorDataset( data["test"]["x"], data["test"]["y"] )_x000D__x000D_train_count = data["train"]["x"].shape[0]_x000D_test_count = data["test"]["x"].shape[0]_x000D__x000D_train_loader = DataLoader(_x000D_ train_dataset,_x000D_ batch_size=batch_size,_x000D_ drop_last=True,_x000D_ shuffle=True_x000D_)_x000D_test_loader = DataLoader(_x000D_ test_dataset,_x000D_ batch_size=batch_size,_x000D_ drop_last=True,_x000D_ shuffle=False_x000D_)Создание и обучение нейронной сети
Архитектура модели:
_x000D_def create_model():_x000D_ input_shape = 784_x000D_ output_shape = 10_x000D__x000D_ model = nn.Sequential(_x000D_ nn.Linear(input_shape, 128),_x000D_ nn.ReLU(),_x000D_ nn.Linear(128, output_shape),_x000D_ #nn.Softmax()_x000D_ )_x000D__x000D_ # Adam optimizer_x000D_ optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, betas=(0.9, 0.99))_x000D__x000D_ # mean squared error_x000D_ loss = nn.MSELoss()_x000D__x000D_ return {_x000D_ "input_shape": input_shape,_x000D_ "output_shape": output_shape,_x000D_ "model": model,_x000D_ "optimizer": optimizer,_x000D_ "loss": loss,_x000D_ }_x000D__x000D_Выведем информацию о модели на экран:
_x000D_model_info = create_model()_x000D__x000D_# Show model info_x000D_summary(model_info["model"], (model_info["input_shape"],))Будет выведено:
_x000D_----------------------------------------------------------------_x000D_ Layer (type) Output Shape Param #_x000D_================================================================_x000D_ Linear-1 [-1, 128] 100,480_x000D_ ReLU-2 [-1, 128] 0_x000D_ Linear-3 [-1, 10] 1,290_x000D_================================================================_x000D_Total params: 101,770_x000D_Trainable params: 101,770_x000D_Non-trainable params: 0_x000D_----------------------------------------------------------------_x000D_Input size (MB): 0.00_x000D_Forward/backward pass size (MB): 0.00_x000D_Params size (MB): 0.39_x000D_Estimated Total Size (MB): 0.39_x000D_----------------------------------------------------------------Обучение модели
_x000D_epochs = 20_x000D__x000D_model_info = create_model()_x000D__x000D_model = model_info["model"]_x000D_optimizer = model_info["optimizer"]_x000D_loss = model_info["loss"]_x000D__x000D_model = model.to(tensor_device)_x000D__x000D_history = {_x000D_ "loss_train": [],_x000D_ "loss_test": [],_x000D_}_x000D__x000D_for step_index in range(epochs):_x000D_ _x000D_ loss_train = 0_x000D_ loss_test = 0_x000D_ _x000D_ batch_iter = 0_x000D_ _x000D_ # Обучение_x000D_ for batch_x, batch_y in train_loader:_x000D_ _x000D_ batch_x = batch_x.to(tensor_device)_x000D_ batch_y = batch_y.to(tensor_device)_x000D_ _x000D_ # Вычислим результат модели_x000D_ model_res = model(batch_x)_x000D_ _x000D_ # Найдем значение ошибки между ответом модели и правильными ответами_x000D_ loss_value = loss(model_res, batch_y)_x000D_ loss_train = loss_value.item()_x000D_ _x000D_ # Вычислим градиент_x000D_ optimizer.zero_grad()_x000D_ loss_value.backward()_x000D_ _x000D_ # Оптимизируем_x000D_ optimizer.step()_x000D_ _x000D_ # Очистим кэш CUDA_x000D_ if torch.cuda.is_available():_x000D_ torch.cuda.empty_cache()_x000D_ _x000D_ del batch_x, batch_y_x000D_ _x000D_ batch_iter = batch_iter + batch_size_x000D_ batch_iter_value = round(batch_iter / train_count * 100)_x000D_ print (f"rStep {step_index+1}, {batch_iter_value}%", end='')_x000D_ _x000D_ _x000D_ # Вычислим ошибку на тестовом датасете_x000D_ for batch_x, batch_y in test_loader:_x000D_ _x000D_ batch_x = batch_x.to(tensor_device)_x000D_ batch_y = batch_y.to(tensor_device)_x000D_ _x000D_ # Вычислим результат модели_x000D_ model_res = model(batch_x)_x000D_ _x000D_ # Найдем значение ошибки между ответом модели и правильными ответами_x000D_ loss_value = loss(model_res, batch_y)_x000D_ loss_test = loss_value.item()_x000D_ _x000D_ _x000D_ # Отладочная информация_x000D_ #if i % 10 == 0:_x000D_ print ("r", end='')_x000D_ print (f"Step {step_index+1}, loss: {loss_train},tloss_test: {loss_test}")_x000D_ _x000D_ # Остановим обучение, если ошибка меньше чем 0.01_x000D_ if loss_test < 0.015 and step_index > 5:_x000D_ break_x000D_ _x000D_ # Добавим значение ошибки в историю, для дальнейшего отображения на графике_x000D_ history["loss_train"].append(loss_train)_x000D_ history["loss_test"].append(loss_test)Запустим обучение, и выведем его на экран:
_x000D_Step 1, loss: 0.007191469427198172, loss_test: 0.020943233743309975_x000D_Step 2, loss: 0.005624017678201199, loss_test: 0.016437651589512825_x000D_Step 3, loss: 0.004931427538394928, loss_test: 0.014247491955757141_x000D_Step 4, loss: 0.0045996420085430145, loss_test: 0.013283359818160534_x000D_Step 5, loss: 0.004507290665060282, loss_test: 0.012732605449855328_x000D_Step 6, loss: 0.004471090622246265, loss_test: 0.01236715167760849_x000D_Step 7, loss: 0.004237602464854717, loss_test: 0.011990693397819996Покажем график обучения:
_x000D_import matplotlib.pyplot as plt_x000D__x000D_plt.plot( np.multiply(history['loss_train'], 100), label='Ошибка обучения')_x000D_plt.plot( np.multiply(history['loss_test'], 100), label='Ошибка на тестах')_x000D_plt.ylabel('Процент')_x000D_plt.xlabel('Эпоха')_x000D_plt.legend()_x000D_plt.show()Результат обучения. Как видно из графика процент ошибки стремится к нуль, а правильные ответы к 100%. Делаем вывод, что нейронная сеть обучилась корректно.

Проверка модели
Напишем функцию, которая по вектору, которая отвечает модель, будет возвращаться ответ ввиде числа, а не вектора.
_x000D_def get_answer_from_vector(vector):_x000D_ r"""_x000D_ Returns answer from vector_x000D_ """_x000D_ value_max = -math.inf_x000D_ value_index = 0_x000D_ for i in range(0, len(vector)):_x000D_ value = vector[i]_x000D_ if value_max < value:_x000D_ value_index = i_x000D_ value_max = value_x000D_ _x000D_ return value_indexПроверим как правильно отвечает модель:
_x000D_photo_number = 200_x000D_photo = data_orig["test"]["x"][photo_number]_x000D_correct_answer = data_orig["test"]["y"][photo_number]_x000D__x000D_tensor_x = data_normalize_x(photo)_x000D_tensor_x = tensor_x[None, :]_x000D_tensor_y = model(tensor_x)_x000D__x000D_model_answer = get_answer_from_vector(tensor_y[0].tolist())_x000D_ _x000D_print ("Model answer", model_answer)_x000D_print ("Correct answer", correct_answer)_x000D__x000D_plt.imshow(photo, cmap='gray')_x000D_plt.show()