\documentclass[a4paper,fontsize=14bp]{article} \input{../common-preamble} \input{../bmstu-preamble} \input{../fancy-listings-preamble} \setcounter{secnumdepth}{0} \numerationTop \begin{document} \thispagestyle{empty} \makeBMSTUHeader \makeReportTitle{домашней}{1}{Программирование нейронной сети}{Нейронные сети}{а}{Боровик И.Г.} \newpage \thispagestyle{empty} \tableofcontents \newpage \pagestyle{fancy} \section{Цель} Целью домашней работы было написание нейронной сети на языке программрования. Нейронная сеть должна распознавать изображения рукописных букв кириллического алфавита. \section{Выполнение} Для простоты реализации был выбран язык \code{Python} с применением библиотек \code{PyTorch} (нейросеть), pandas (импортирование датасета), sklearn (работа с датасетом, разделение на тестовый и обучающий). Датасеты сразу содержали чистые и зашумлённые изображения. \subsection{Подготовка} Для создания и обучения нейросети были скачены большие датасеты и установлены библиотеки для работы с нейросетями. В рамках выполнения задания был выбран многослойный персептрон с обратной связью. Работа производилась в редакторе \code{VSCode} с подключенным плагином \code{Jupyter Notebook}. Для увеличения количества вычислительных ресурсов при обучении нейросети код был перемещён на серверы Google в проект Colab (https://colab.research.google.com). \subsection{Создание датасетов} Наборы данных для нейросети создавались как набор байтов в формате \code{csv} (Comma-separated values, значения, разделённые запятой), где каждое значение - это нормированное значение байта изображения и обозначение выходного эталона для сравнения (что за буква зашифрована в байте). Код, импортирующий данные из файлов представлен в листинге \hyperref[lst:importcsv]{\ref{lst:importcsv}}, а проверочные изображения (для визуального сопоставления изображения и лейбла пользователем) на рис. \hyperref[pic:firstletters]{\ref{pic:firstletters}}. \begin{figure}[H] \centering \begin{multicols}{5} \includegraphics[width=3cm]{01-nn-hw01.png} \columnbreak \includegraphics[width=3cm]{01-nn-hw02.png} \columnbreak \includegraphics[width=3cm]{01-nn-hw03.png} \columnbreak \includegraphics[width=3cm]{01-nn-hw04.png} \columnbreak \includegraphics[width=3cm]{01-nn-hw05.png} \end{multicols} \caption{Случайно выбранные изображения из импортированных файлов} \label{pic:firstletters} \end{figure} \begin{lstlisting}[language=Python, style=PyCodeStyle, caption=Код импорта данных из файлов, label=lst:importcsv] from google.colab import drive drive.mount('/content/drive') import numpy as np import matplotlib.pyplot as plt import string import os from PIL import Image # Load images and labels X = np.loadtxt('/content/drive/MyDrive/bmstu/nn-hw/input/cyrillic_data/cyrillic_data.csv', delimiter=",") y = np.loadtxt('/content/drive/MyDrive/bmstu/nn-hw/input/cyrillic_label/cyrillic_label.csv', delimiter=",") # Display 5 random letters and their labels for _ in range(5): i = np.random.randint(X.shape[0]) my_letter = X[i].reshape(28,28) letters = "ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ" my_label = letters[int(y[i])] print(f"Displaying letter {my_label}") plt.imshow(my_letter, cmap="gray") plt.show() \end{lstlisting} После импорта изображения были риведены к нужному размеру (28*28) и упакованы в датасет для распознавания нейросетью. некоторые фрагменты этого процесса приведены в листинге \hyperref[lst:dataset]{\ref{lst:dataset}}. \begin{lstlisting}[language=Python, style=PyCodeStyle, caption=Код импорта данных из файлов, label=lst:dataset] letters = "ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ" PATH_PREF = '/content/drive/MyDrive/bmstu/nn-hw/input' PATH = PATH_PREF + '/images/images/Cyrillic/Cyrillic' sorted(os.listdir(PATH)) ratio = 0.25 def convert2csv(img_folder:str, ratio = 0.25): folders = sorted(os.listdir(img_folder)) out_img = PATH_PREF + '/cyrillic_data/my_cyrillic_data.csv' out_label = PATH_PREF + '/cyrillic_label/my_cyrillic_label.csv' img_csv = open(out_img, 'w') label_csv = open(out_label, 'w') for folder in folders: if folder == '.DS_Store': continue files = os.listdir(img_folder + '/' + folder) for file in files: if file == '.DS_Store': continue img = Image.open(img_folder + '/' + folder + '/' + file) img.load() img = img.resize((28,28), Image.ANTIALIAS) img.convert('L') pixels = list() for i in range(28): for j in range(28): pix = img.getpixel((i,j)) pixels.append(pix[3]) img_csv.write(",".join(str(pix) for pix in pixels) + '\n') label_csv.write(str(letters.index(folder)) + '\n') \end{lstlisting} \subsection{Нейросеть: Свойства и создание} При создании нейросети первое, что было сделано - это разделение общего датасета на данные для обучения и данные для тестирования. Все данные были разделены в соотношении 80/20\%, где 80 - это обучающий объём, а 20 - тестирующий. Поскольку в обучающих датасетах получилось более 15000 изображений, для более эффективного обучения, их необходимо было разделить на порции (пакеты). Каждый обучающий пакет создавался размером в 107 изображений. Подготовительный код представлен в листинге \hyperref[lst:prepare]{\ref{lst:prepare}} \begin{lstlisting}[language=Python, style=PyCodeStyle, caption=Подготовка датасетов к обучению, label=lst:prepare] train = pd.read_csv('/content/drive/MyDrive/bmstu/nn-hw/input/cyrillic_data/my_cyrillic_data.csv', delimiter=',') X = train.loc[:].values/255 train_labels = pd.read_csv('/content/drive/MyDrive/bmstu/nn-hw/input/cyrillic_label/my_cyrillic_label.csv', delimiter = ',') Y = (train_labels.to_numpy()).reshape(len(train_labels)) features_train, features_test, targets_train, targets_test = train_test_split(X, Y, test_size=0.2, random_state=42) Y_train = torch.from_numpy(targets_train).type(torch.LongTensor) Y_test = torch.from_numpy(targets_test).type(torch.LongTensor) train = torch.utils.data.TensorDataset(X_train, Y_train) test = torch.utils.data.TensorDataset(X_test, Y_test) train_batch_size =107 test_batch_size =107 train_loader = torch.utils.data.DataLoader(train, batch_size = train_batch_size, shuffle = False) test_loader = torch.utils.data.DataLoader(test, batch_size = test_batch_size, shuffle = False) \end{lstlisting} Далее была создана нейросеть со следующими свойствами: \begin{itemize} \item Входных нейронов: 784 (по количеству пикселей входящего изображения, 28*28); \item Выходных нейронов: 33 (по количеству букв современного кириллического алфавита); \item Промежуточных слоёв: 2; \item Нейронов в промежуточных слоях: 128, 22; \item Функция потерь: отрицательная функция логарифмической вероятности; \item learning rate: 0.003; \item momentum: 0.9. \end{itemize} Код, устанавливающий даные свойства и создающий нейросеть представлен в листинге \hyperref[lst:create]{\ref{lst:create}} \begin{lstlisting}[language=Python, style=PyCodeStyle, caption=Создание нейросети, label=lst:create] input_size = 784 hidden_sizes = [128, 22] output_size = 33 model = nn.Sequential(nn.Linear(input_size, hidden_sizes[0]), nn.ReLU(), nn.Linear(hidden_sizes[0], hidden_sizes[1]), nn.ReLU(), nn.Linear(hidden_sizes[1], output_size), nn.LogSoftmax(dim=1)) criterion = nn.NLLLoss() optimizer = torch.optim.SGD(model.parameters(),lr=0.003, momentum=0.9) images, labels = next(iter(train_loader)) images = images.view(images.shape[0], -1) logps = model(images.float()) loss = criterion(logps, labels) \end{lstlisting} \subsection{Нейросеть: Обучение} Обучение нейросети производилось 200 эпох, код, последовательно обучающий сеть представлен в листинге \hyperref[lst:learn]{\ref{lst:learn}}, последние сообщения из журнала обучения на рис. \hyperref[pic:learn]{\ref{pic:learn}}, а график обучения на рис. \hyperref[pic:learndiag]{\ref{pic:learndiag}}. \begin{lstlisting}[language=Python, style=PyCodeStyle, caption=Обучение нейросети, label=lst:learn] epochs = 200 time0 = time() train_losses, test_losses = [] ,[] for epoch in range(epochs): running_loss = 0 for images,labels in train_loader: images = images.view(images.shape[0], -1) labels = Variable(labels) optimizer.zero_grad() output = model(images.float()) loss = criterion(output,labels) loss.backward() optimizer.step() running_loss += loss.item() else: train_losses.append(running_loss/len(train_loader)) \end{lstlisting} \begin{figure}[h] \centering \begin{multicols}{2} \includegraphics[width=8cm]{01-nn-hw-lrl.png} \caption{Фрагмент журнала обучения нейросети} \label{pic:learn} \columnbreak \includegraphics[width=8cm]{01-nn-hw-lrd.png} \caption{Диаграмма обучения нейросети} \label{pic:learndiag} \end{multicols} \end{figure} \section{Тестирование нейросети и выводы} По результатам тестирования нейросети видно, что чаще всего рукописная буква А похожа на О, Э и Д. Буква О изредка была перепутана с А, Р, С и Ф. Но в целом, сеть ошиблась в 0,3 процентах случаев. Этот показатель можно улучшить, добавив в обучение эпох или изменив количество нейронов среднего слоя. Важно не сделать средний слой слишком большим, чтобы избежать сложностей при обучении. Код, тестирующий нейросеть представлен в листинге \hyperref[lst:test]{\ref{lst:test}}. \begin{lstlisting}[language=Python, style=PyCodeStyle, caption=Тестирование нейросети, label=lst:test] test_images = pd.read_csv("/content/drive/MyDrive/bmstu/nn-hw/input/cyrillic_data/my_cyrillic_data_test.csv") test_image = test_images.loc[:].values/255 test_dataset = torch.from_numpy(test_image) new_test_loader = torch.utils.data.DataLoader(test_dataset, batch_size = 100, shuffle = False) results = [] with torch.no_grad(): model.eval() for images in new_test_loader: test = images.view(images.shape[0], -1) output = model(test.float()) ps = torch.exp(output) top_p, top_class = ps.topk(1, dim = 1) results += top_class.numpy().tolist() predictions = np.array(results).flatten() submissions=pd.DataFrame({"Result Label": predictions}) test_label = pd.read_csv("/content/drive/MyDrive/bmstu/nn-hw/input/cyrillic_label/my_cyrillic_label_test.csv") test_label.columns = ['True label'] submissions['true'] = test_label['True label'] submissions.to_csv("/content/drive/MyDrive/bmstu/nn-hw/my_res.csv", index = False, header = True) \end{lstlisting} \end{document}