BMSTU/01-nn-hw-01-report.tex

246 lines
13 KiB
TeX
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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