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

246 lines
13 KiB
TeX
Raw Permalink Normal View History

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