\chapter*{Выбор архитектуры нейронной сети}
\addcontentsline{toc}{chapter}{Выбор архитектуры нейронной сети} % Добавляем введение в оглавление

\section{Super Resolution Convolutional Neural Network (SRCNN)}

Использование сверточных нейронных сетей для данной работы является стандартом, поэтому и начать стоит с нее. Super Resolution Convolutional Neural Network\cite{DongLHT15} является одним из популярных решений для данной задачи. Архитектура сверточной нейронной сети (рис. \ref{fig:screenshot001})представлена в виде 3 слоев, входного слоя с 3 входами для каждого цвета и 64 фичами, после которых идет один скрытый сверточный слой и один выходной слой. В данном случае, первый слой извлекает набор фич из изображения, в то время, как второй слой нелинейно размечает данные фичи на патчи с высоким разрешением, а последний слой комбинирует предсказания с оценкой окружающих  патчей и отдает на выход изображение с повышенным разрешением. 

\begin{figure}[h!]
	\centering
	\includegraphics[width=0.8\linewidth]{screenshot001}
	\caption{Архитектура нейронной сети SRCNN \cite{DongLHT15}}
	\label{fig:screenshot001}
\end{figure}


Стоит отметить, что в данной архитектуре входное изображение уже должно быть нужного разрешения, увеличивая вычислительную сложность нейронной сети. Что является потенциальным решающим фактором в пользу более продвинутых реализаций методов повышения разрешения.

\begin{lstlisting}[
	style=compactpython,
	caption=Протестированная реализация SRCNN,
	label={lst:SRCNN}
	]
class SimpleSuperResolutionNet(nn.Module):
	def __init__(self, kernel_size=3, n_feats=64, colors=3): 
		super(SimpleSuperResolutionNet, self).__init__()
		self.conv = nn.Conv2d(
		colors, n_feats, kernel_size=kernel_size, padding=kernel_size//2)
		
		half_feats = n_feats//2
		self.conv2 = nn.Conv2d(
		n_feats, half_feats, kernel_size=kernel_size, padding=kernel_size//2)
		
		self.conv3 = nn.Conv2d(
		half_feats, colors, kernel_size=kernel_size, padding=kernel_size//2)
	
	def forward(self, x):
		x = torch.relu(self.conv(x))
		x = torch.relu(self.conv2(x))
		x = self.conv3(x)
		return x

\end{lstlisting}

Можно увидеть в листинге \ref{lst:SRCNN}, что размер ядра был выбран отличным от представленных в работе, связано это с тем, что при обучении на наших тестовых данных, одинаковое ядро показало лучшие результаты, в сравнении с оригинальными.

\section{Fast Super Resolution Convolutional Neural Network (FSRCNN)}

sdada

\section{Enhanced Deep Super Resolution (EDSR)}

Некоторым продвижением в задаче повышения разрешения можно считать применение в сверточных нейронных сетях остаточных блоков.\cite{he2016deep} В ключевой реализации \cite{LimSKNL17} была предложена архитектура состоящая из большого количества скрытых остаточных блоков (рис. \ref{fig:resblockedsr}), после начального входного слоя.

% TODO: \usepackage{graphicx} required
\begin{figure}[h!]
	\centering
	\includegraphics[width=0.2\linewidth]{res_block_edsr}
	\caption[Структура остаточного блока в сети EDSR]{}
	\label{fig:resblockedsr}
\end{figure}

В отличии от стандартных остаточных блоков, использованных в ResNet \cite{HeZRS15}, в EDSR из остаточного блока удален блок пакетной нормализации, так как данный блок может быть нежелательным в данной задаче

% TODO: \usepackage{graphicx} required
\begin{figure}[h!]
	\centering
	\includegraphics[width=0.6\linewidth]{edsr_arch}
	\caption[Архитектура нейронной сети EDSR \cite{LimSKNL17}]{}
	\label{fig:edsrarch}
\end{figure}

Были проверены две реализации: "state-of-the-art", предоставленная авторами статьи, уже предобученная модель, а также своя реализация по образу и подобию предоставленной авторами.

Сеть состояла из 8 остаточных блоков, в каждом из которых было по 64 фичи, с ядром размера 3х3, и значением множителя 0.1

\begin{lstlisting}[
	style=compactpython,
	caption=Структура остаточного блока,
	label={lst:ResBlock}
	]
class ResidualBlock(nn.Module):
	def __init__(self, n_feats, kernel_size, bias=True, bn=False, act=nn.ReLU(True), res_scale=1.0):
		super(ResidualBlock, self).__init__()
		m = []
		for i in range(2):
			m.append(nn.Conv2d(n_feats, n_feats, kernel_size,
			padding=(kernel_size//2), bias=bias))
		if bn:
			m.append(nn.BatchNorm2d(n_feats))
		if i == 0:
			m.append(act)
		self.body = nn.Sequential(*m)
		self.res_scale = res_scale
	
	def forward(self, x):
		res = self.body(x).mul(self.res_scale)
		res += x
		return res
\end{lstlisting}