\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}

