%\newcommand*\pagecountmode{} % auskommentieren, um geschriebene Textseiten zählen zu können
%\newcommand*\draftmode{} % auskommentieren für "Entwurf - Stand xx" in der Fußzeile

% Datei ist UTF8-enkodiert: Äöß
% WICHTIG: draft durch final ersetzen, wenn fertig
\documentclass[bibliography=totocnumbered,11pt,a4paper,oneside,final]{scrartcl}

% Kommantare '% chktex ##' unterdrücken lint-Warnungen

% Deutsche Zeichen
\usepackage[utf8]{inputenc}
% Deutsche Silbentrennung
\usepackage[ngerman]{babel}

% Ränder
\usepackage[left=2.50cm, right=4.00cm, top=2.00cm, bottom=2.00cm]{geometry}
% "1.5pt" Word-Krepel-Zeilenabstand
\usepackage{setspace}
\makeatletter
\newcommand{\MSonehalfspacing}{%
    \setstretch{1.44}%
    \ifcase\@ptsize\relax % 10 pt
        \setstretch{1.448}%
    \or%11pt
        \setstretch{1.399}%
    \or%12 pt
        \setstretch{1.433}%
    \fi
}
\makeatother
\MSonehalfspacing{}

% Schriftart Arial
\usepackage{helvet}
\renewcommand{\familydefault}{\sfdefault}

% Nie viel Platz zwischen Listenitems
\usepackage{enumitem}
\setlist{noitemsep}

% Quellen
\usepackage[
    style=mla,
    citestyle=verbose-trad1,
    dashed=false,
    backend=biber
]{biblatex}
\def\labelitemi{--}
\setlength{\emergencystretch}{3em}
\defbibenvironment{bibliography} % Aufzählung
{\itemize
        {}
        {\setlength{\leftmargin}{\bibhang}%
         \setlength{\itemindent}{-\leftmargin}%
         \setlength{\itemsep}{\bibitemsep}%
         \setlength{\parsep}{\bibparsep}
        }
    }
    {\enditemize}
    {\item}
\DeclareNameAlias{author}{last-first}
\DeclareFieldFormat{title:online}{#1}
\DeclareFieldFormat{url}{\url{#1}}
\DeclareFieldFormat{title:article}{#1}
\renewcommand*{\labelnamepunct}{\addcolon\space}
% Todo: Was folgt, ist ein ekliger Hack.
% http://ctan.space-pro.be/tex-archive/macros/latex/contrib/biblatex-contrib/biblatex-mla/mla.bbx
\renewbibmacro*{url+urldate}{%
    \printfield{url}
    \newunit
    \setunit{\addcomma\addspace}
    \printtext{\printurldate}
    %\printtext{\stripzeros{\printfield{urlday}} \mkbibmonth{\thefield{urlmonth}} \printfield{urlyear}} % Todo
}
\DeclareBibliographyDriver{article}{%
  \usebibmacro{bibindex}%
  \usebibmacro{author/editor+others/translator+others}%
  \usebibmacro{mlabelnamepunct}%
  \usebibmacro{title}%
  \newunit\newblock
  \renewcommand*{\newunitpunct}{\addcomma\space} % AUTSCH :(
  \iffieldundef{note}{}
  {%
      \usebibmacro{in:}
      \newunit
      \printfield{note}
      \newunit
  }
  \iffieldundef{pages}{}
  {%
      \printtext{S\adddot}
      \printfield{pages}%
  }
  \newunit\newblock
  \iffieldundef{url}{}
  {%
      \usebibmacro{publimedium}%\newblock
      \newunit
      \printtext{Stand}\addcolon
      \usebibmacro{mla:article:journal+issuetitle}%
      \renewcommand*{\newunitpunct}{\addcolon\space}
  }
  \usebibmacro{finentry}
}
% Info: alles ist @online, auch Bücher.
% urldate: Datum, date: Abrufdatum, note: Seitentitel
\addbibresource{bibliography.bib}

% Bilder einbetten
\usepackage[final]{graphicx}
\usepackage{wrapfig}

% Diagramme
\usepackage{pgfplots}
\pgfplotsset{compat=1.12} % Todo migrieren

% draft-Modus-Bedingung ifdraft
\usepackage{ifdraft}

% Aktuelle Uhrzeit
\usepackage{datetime}
%\def\datengerman{\def\today{\number\day.\number\month.\number\year}}

\usepackage{draftwatermark}
\SetWatermarkText{Entwurf}
\SetWatermarkLightness{0.9}

% Seitenzahlen unten rechts
\usepackage{fancyhdr} % Todo nicht empfohlen mit KOMA
\pagestyle{fancy}
\renewcommand{\headrulewidth}{0pt}
\renewcommand{\footrulewidth}{0pt}
\setlength{\footskip}{3em}
\lhead{}
\rhead{}
\ifx\draftmode\undefined{}
    \cfoot{}{}
\else
    \cfoot{{Entwurf -- Stand \today, \currenttime}}{} % chktex 8
\fi
\rfoot{\thepage}

% Standardschriftgröße auch für (alle) Überschriften
\usepackage{titlesec} % Todo nicht empfohlen mit KOMA
\titleformat*{\section}{\normalfont\normalsize\bfseries}
\titleformat*{\subsection}{\normalfont\normalsize\bfseries}
\titleformat*{\subsubsection}{\normalfont\normalsize\bfseries}
\titleformat*{\paragraph}{\normalfont\normalsize\bfseries}
\titleformat*{\subparagraph}{\normalfont\normalsize\bfseries}

% 10 pt für footnotes
\renewcommand{\footnotesize}{\small}

% Keine Einzüge, sieht blocksatziger aus
\setlength{\parindent}{0em}

% Zitate: li/re 0.5 cm Einzug
% Zitate: einzeiliger Abstand in Blöcken
% Todo - noch keine Zitate gebraucht

% eigenes Titelblatt
\usepackage{titling}
\usepackage{array}
\usepackage{tabularx}

% erweiterte Tabellen
\usepackage{tabulary}

% Links darstellen
\usepackage{hyperref}
\urlstyle{same}

% Source Code Highlighting
\usepackage[final]{minted}
\newminted{python}{python3,autogobble,breaklines,fontsize=\small}

% Formeln
\usepackage[nosumlimits]{mathtools}

% vertikale Linien für das Deckblatt
\newcommand{\horizontalline}{%
    \noindent
    \makebox[\linewidth]{\rule{\textwidth}{0.2pt}}
}

% Definitionen für Metatags
\author{schneefux}
\title{Wie trifft ein künstliches neuronales Netz eine Voraussage?}
\date{\today}

\begin{document}
\ifx\pagecountmode\undefined{}
    \begin{titlepage}
        \newgeometry{left=2.50cm, right=2.50cm, top=2cm, bottom=2cm}
        \begin{center}
            \Large{\textbf{Gymnasium}}

            \Large{\textbf{Straße 1}}

            \Large{\textbf{12345 Stadt}}
        \end{center}
        \vfill
        \begin{center}
            \LARGE{\textbf{Facharbeit im}}

            \LARGE{\textbf{Seminarfach}}
        \end{center}
        \vfill
        \horizontalline{}
        \begin{center}
            \Large{\textbf{Künstliche neuronale Netze}}

            \Large{\textbf{\thetitle}}
        \end{center}
        \horizontalline{}
        \vfill
        \vfill
        \begin{center}
            \begin{tabularx}{\textwidth}{>{\raggedright\arraybackslash}X >{\raggedright\arraybackslash}X >{\raggedleft\arraybackslash}X}
                \Large{Verfasser:}&\Large{\textbf{\theauthor{}}}&\\
                \Large{Fachlehrer:}&\Large{\textbf{xxx}}&\\
                \Large{Abgabedatum:}&\Large{\textbf{15. März 2016}}&\\
                                     &\ifdraft{\currenttime}{}&\\
            \end{tabularx}
        \end{center}
        \restoregeometry{}
        \vfill
    \end{titlepage}
    \tableofcontents
    \vspace{\parskip}
    \listoffigures
    \pagenumbering{gobble}
    \clearpage
    \pagenumbering{arabic}
\fi
    
    \setcounter{page}{1}
    \section{Einleitung}
        Wie trifft ein künstliches neuronales Netz eine Voraussage?

        Ein künstliches neuronales Netz ist durch immer schneller werdende Computer die de facto Lösungsmethodik, um von einem Problem zu einer Lösung zu kommen, ohne den direkten Weg dorthin zu kennen. Mit riesigen Datenmengen können Firmen wie Google und Facebook Computergehirne trainieren, um anschließend das Verhalten ihrer Nutzer vorherzusagen. Der Traum eines jeden Menschen ist es, das Unbekannte und die Zukunft zu erfahren. Künstliche neuronale Netze bringen die Technologie ein Stück weiter in die Richtung, in der Regressionen und strukturelle Algorithmik an ihre Grenzen gelangt sind.

        In dieser Facharbeit soll es um die Funktionsweise eines kleinen Feedforward-Netzes gehen, welches darauf trainiert wird, anhand verschiedener Kriterien eine Marktpreiseinschätzung zu geben. Feedforward-Netze sind die einfachsten Typen künstlicher neuronaler Netze, die sich für eine Vorhersage von Zahlenwerten eignen. Das künstliche neuronale Netz wird mit Python und dem Tensorflow-Framework implementiert.
        Dahinführend, wird zunächst die biologische Grundlage der natürlichen neuronalen Netze aufgezeigt. Anschließend wird von dieser Grundlage ausgegangen, und die wesentlichen Funktionselemente eines biologischen Neurons werden auf ein künstliches Neuron übertragen, welches durch einfache mathematische Operationen im Verbund mit anderen zum Lösen von Problemen verwendet werden kann.
        Schließlich wird an der selbst geschriebenen Beispielimplementierung eines künstlichen neuronalen Netzes demonstriert, wie dieses dem Netz unbekannte Hauspreismittelwerte in der Region Bostons anhand einer Reihe von Umgebungsfaktoren auf bis zu 500\$ genau bestimmt. Anhand dieses Programmes werden die Auswirkungen der Veränderung verschiedener Konfigurationsparameter des künstlichen neuronalen Netzes untersucht, um zu verstehen, wie sie die Vorhersagequalität beeinflussen.
        Als Abschluss wird auf die weiteren Möglichkeiten künstlicher neuronaler Netze eingegangen.

    \subsection{Biologische Grundlage}
        % TODO fachliche Korrektheit überprüfen
        Neuronen (Abbildung~\ref{fig:neuron}) bestehen aus Dendriten und Axonen. Ein Signal wird über die Dendriten aufgenommen und bei Überschreiten des Schwellwerts des sogenannten Axonhügels am Axon als elektrisches Signal zur nächsten Zelle weitergeleitet. Die Verbindung zur nächsten Zelle wird Synapse genannt.
        Neuronale Netze bestehen aus einer großen Menge an Neuronen und Synapsen. Das menschliche Gehirn besitzt rund 100 Milliarden Neuronen, von denen jedes einzelne mit durchschnittlich 1000 anderen verbunden ist\footcite[Vgl.][]{welt}.

\ifx\pagecountmode\undefined{}
        \begin{figure}
            \centering
            \includegraphics[width=0.7\linewidth]{res/neuron}
            \caption[Schema eines Neurons]{Schema eines Neurons\label{fig:neuron}\protect\footnotemark}
        \end{figure}
        \footnotetext{\cite{cancer}}
\fi

        Die von den Dendriten aufgenommenen Eingangssignale können Ausgangssignale eines anderen Neurons sein oder Signale eines Rezeptors. Rezeptoren sind zum Beispiel Photorezeptoren im Auge oder Dehnungsrezeptoren an Sehnen\footcite[Vgl.][]{antwerpes}. Geht ein Ausgangssignal nicht zu einem weiteren Neuron, geht es zu einem Effektor. Ein Effektor kann eine Muskelzelle sein\footcite[Vgl.][]{spektrum}.
        Die Funktion eines Neurons lässt sich am Patellarsehnenreflex beobachten. Unterhalb der Kniescheibe sitzen Dehnungsrezeptoren, die einen Schlag registrieren und ein Signal zum Rückenmark senden, von wo aus es direkt über eine einzelne Synapse an den Effektor Muskel geht und eine Streckung des Beins verursacht\footcite[Vgl.][]{weber}.
        Wenn des Eingangssignal zu schwach ist, um den Axonhügel zu passieren, wird das Bein nicht gestreckt.

        In einem neuronalen Netz sind die Informationen durch die Eigenschaft der Synapsenverbindungen, bei jeder Nutzung stärker zu werden, in diesen gespeichert. Ein biologisches Gehirn lernt von alleine durch Wiederholungen.

    \subsection{Entwicklung künstlicher neuronaler Netze}
        Zwischen 1942 und~1955 gab es die ersten Gedanken zu künstlichen neuronalen Netzen, 1943 von Walter Pitts und Warren Mc Culloch eine theorethische Umsetzung, allerdings ohne Anwendungsbezug.
        Bis etwa 1969 wurden viele Grundbausteine der Forschung gelegt.
        Das erste tatsächlich funktionsfähige künstliche neuronale Netz wurde 1957/1958 von Frank Rosenblatt und Charles Wightman entwickelt, das Mark~I~Perceptron war eine Ziffernerkennung gebaut mit motorbetriebenen Potentiometern.
        Weitere Forschung verlief bis 1985 schleichend. 1974 entwickelte Paul Werbos das Fehlerrückführungsverfahren. Durch die Innovation der Hopfield-Netze, die in der Lage waren, schwierige Optimierungsaufgaben zu lösen, kam das Thema wieder in den Fokus vieler Forscher und sorgt bis heute für Innovationen.\footcite[Vgl.][]{kriesel}

        Künstliche neuronale Netze gehören zur Klasse der neuronalen Netze, ebenso wie ein biologisches neuronales Netz. Ein Gehirn ist ein besonders großes biologisches neuronales Netz.
        Künstliche neuronale Netze haben nicht das Primärziel, die Funktionsweise eines biologischen neuronalen Netzes exakt nachzubilden, sind also nicht Untersuchungsgegenstand der Hirnforschung.
        Stattdessen haben künstliche neuronale Netze biologische als Vorbild. Ein künstliches Neuron simuliert die wesentlichen Elemente eines biologischen Neurons.

    \subsection{Funktionsweise künstlicher neuronaler Netze}
\ifx\pagecountmode\undefined{}
        \begin{figure}
            \centering
            \includegraphics[width=0.8\linewidth]{res/kuenstliches-neuron}
            \caption[Schema eines künstlichen Neurons]{Schema eines künstlichen Neurons\label{fig:kneuron}\protect\footnotemark}
        \end{figure}
        \footnotetext{\cite{chrislb}}
\fi
        Im folgendem Abschnitt wird die Funktionsweise eines biologischen Neurons auf ein künstliches Neuron abstrahiert.

        Ein Schema eines einzelnen Neurons ist in Abbildung~\ref{fig:kneuron} dargestellt.
        Die Dendriten werden durch Eingaben~\(x_1, x_2, \dots x_n\) dargestellt. Ein biologisches Neuron hat stärkere und schwächere Synapsen, deren Stärke die Priorität eines Signals beeinflusst. In einem künstlichen Neuron wird das durch die Gewichte~\(w_1, w_2, \dots w_n\) abgebildet. Die Werte der Eingaben werden mit den Gewichten multipliziert und anschließend summiert. Diese gewichtete Summe wird Netzeingabe gennannt und berechnet sich als \(net = \sum_n (x_n \cdot w_n)\). In einem biologischen Neuron entscheidet der Schwellwert über die Weitergabe eines elektrischen Signals. Bei künstlichen neuronalen Netzen existieren verschiedene Varianten, die sich als Aktivierungsfunktion zusammenfassen lassen. Es ist möglich, dass die Aktivierungsfunktion nur dann eine Ausgabe liefert, wenn ein Schwellenwert überschritten wird -- dann ist es eine Schrittfunktion, meistens wird aber als Aktivierungsfunktion eine lineare Funktion, eine Sigmoidfunktion oder eine Softmaxfunktion genutzt.\footcite[Vgl.][]{hand11neur}
        Die Ausgabe~\(o\) berechnet sich mit der Aktivierungsfunktion~\(\varphi\) als~\(o = \varphi(\sum_n (x_n \cdot w_n))\).\footcite[Vgl][]{gershenson} % TODO fachlichen Biobezug überprüfen

        Ein künstliches neuronales Netz besteht aus mehreren Neuronen in mindestens zwei Schichten.
        Die erste Schicht ist die Eingabeschicht. Die Neuronen der Eingabeschicht werden mit den Eingabedaten aktiviert. Oft ist es sinnvoll, vor jedes Neuron der Eingabeschicht jeweils ein weiteres Neuron mit der Ausgabe~\(1\) zu schalten. Diese ,,nullte'' Schicht, die aus den Bias besteht, ist ein technischer Trick, der verhindert, dass ein Eingabedatum von~\(0\) das Eingabeneuron unbenutzbar macht, weil so ein Nullprodukt entsteht: \(net = 0 \cdot w_n = 0\). Ein künstliches neuronales Netz würde eine Eingabe mit~\(0\) als unwichtig werten, was nicht im Sinne des Entwicklers ist. % chktex 26

        Es existieren verschiedene Varianten künstlicher neuronaler Netze. Die einfachste Variante ist das Feedforward-Netz. In einem Feedforward-Netzwerk sind alle Neuronen der einen Schicht vollständig und ausschließlich mit den Neuronen der nächsten Schicht verbunden.\footcite[Vgl.][]{praat}
        Daneben existieren rekurrente künstliche neuronale Netze oder andere Netztypen, bei denen Neuronen mit sich selbst oder unter den Schichten verbunden sind, was dazu führt, dass das Netz zum Beispiel ein Kurzzeitgedächtnis bekommt. In dieser Facharbeit wird es um ein dreischichtiges Feedforward-Netz gehen, da es der simpelste Netztyp ist und für die Anwendung ausreicht. Eine Erläuterung größerer Netze würde über den Umfang dieser Facharbeit hinausgehen.

        Feedforward-Netze können einschichtig oder mehrschichtig sein. Sind sie einschichtig, ist die Eingabeschicht direkt mit der Ausgabeschicht verbunden. In der Ausgabeschicht stellen die Ausgaben der Ausgabeneuronen die Ausgaben des Netzes dar. Mehrschichtige Feedforward-Netze besitzen zusätzlich noch ein oder mehrere versteckte Schichten zwischen der Eingabe- und der ausgabeschicht. In Abbildung~\ref{fig:feedforward} ist ein Feedforward-Netz mit drei Eingabeneuronen (rot), vier versteckten Neuronen (braun) und einem Ausgabeneuron (grün) schematisiert.

        Möchte man nun die Ausgabe des Netzes bei gegebenen Eingaben berechnen, so könnte man die Ausgabe jedes Neurons einzeln berechnen. Um das zu vereinfachen, wird aber jede Schicht durch einen Vektor dargestellt, der so viele Dimensionen wie die Schicht Neuronen hat. Die Gewichte befinden sich in einer Matrix, die in der ersten Dimension die Anzahl an Neuronen der betroffenen Schicht hat, und als zweite Dimension die Anzahl Neuronen der nächsten Schicht. Nimmt man das Schema als Beispiel, ergibt sich für die Eingabeschicht folgender Vektor und folgende Gewichtsmatrix:
        \[
            eingabe = \begin{pmatrix} x_1 \\ x_2 \\ x_3 \end{pmatrix}\qquad
            gewichte_1 = \begin{bmatrix}
                w_{1,1} & w_{1,2} & w_{1,3} \\
                w_{2,1} & w_{2,2} & w_{2,3} \\
                w_{3,1} & w_{3,2} & w_{3,3} \\
                w_{4,1} & w_{4,2} & w_{4,3}
            \end{bmatrix}
        \]
\ifx\pagecountmode\undefined{}
        \begin{figure}
            \centering
            \includegraphics[width=0.65\linewidth]{res/mlp}
            \caption[Schema eines mehrschichtigen Feedforward-Netzes]{Schema eines mehrschichtigen Feedforward-Netzes\label{fig:feedforward}\protect\footnotemark}
        \end{figure}
        \footnotetext{\cite{sky99}}
\fi

        Multipliziert man~\(eingabe \cdot gewichte_1\), erhält man die Netzeingabe für die versteckte Schicht als vierdimensionalen Vektor. Für die Ausgabe der versteckten Schicht wird für jedes Element des Vektors die Aktivierungsfunktion berechnet. Anschließend wird die Ausgabe der versteckten Schicht mit der Gewichtsmatrix der nächsten Schicht multipliziert und man erhält im Schema einen eindimensionalen Vektor. Für diesen Vektor wird wieder die Aktivierungsfunktion der Ausgabeschicht berechnet das Ergebnis wird als Ausgabe des gesamten Netzes betrachtet.
        Das Wissen eines künstlichen neuronalen Netzes ist auch in den Neuronenverbindungen, also in den Gewichten, gespeichert. Es gibt zwei Methoden, ein künstliches neuronales Netz zu trainieren. Die eine ist das unbeaufsichtigte Lernen und funktioniert wie beim biologischen Vorbild durch die Benutzung der Gewichte. Die andere, weit häufiger benutzte Methode ist das beaufsichtigte Lernen. Beim beaufsichtigten Lernen wird das künstliche neuronale Netz zunächst mit Eingaben gefüttert, zu denen es bekannte Ausgaben ausgeben soll. Die Gewichte des Netzes werden so angepasst, dass die Zielausgabe möglichst gut erreicht wird. Ist eine Menge von Eingabe- und Zieldaten gegeben, macht es mehr Sinn, beaufsichtigt zu Lehren, da unbeaufsichtigtes Lernen aufwändiger ist, länger dauert und sich das Ergebnis nicht kontrollieren lässt. In der noch folgenden Anwendung ist das Beispiel so gewählt, dass es beaufsichtigt gelehrt werden kann.
        Um möglichst effektiv beaufsichtigt zu lehren, werden die Gewichte anfangs auf zufällige Werte gesetzt. Im anschließenden Training werden sie so modifiziert, dass der durch die Kostenfunktion, oder auch Fehlerfunktion berechnete Fehler so gering wie möglich wird. Nach dem Training folgt der Test mit Eingabedaten, zu denen die gewünschten Ausgabedaten ebenfalls bekannt sind. Der Test zeigt, ob das künstliche neuronale Netz die Muster in den Eingabedaten erkannt hat und damit in der Lage ist, bei unbekannten Daten zuverlässige Einschätzungen zu treffen.\footcite[Vgl.][]{hand11alg}
        Es gibt zwei Szenarien für die Ergebnisse des Tests: Das neuronale Netz kann überangepasst sein, was bedeutet, dass es zwar trainierte Daten akkurat verarbeitet, bei untrainierten Daten aber absurde Ergebnisse liefert. Das neuronale Netz kann unterangepasst sein, was dazu führt, dass es die Trainingsdaten nicht gut genug gelernt hat und weder bei den Trainings- noch bei den Testdaten gute Ausgaben liefert.
        Das Ziel des Lehrers ist es, das Netz durch geschickte Netzkonfiguration so zu trainieren, dass es möglichst wenig über- und unterangepasst ist.

        Bei einem Feedforward-Netz bietet sich eine Lerntechnik an, die Fehlerrückführung genannt wird.
        In einem künstlichen neuronalen Netz ist jedes Neuron an dem Fehler, den das Netz erzeugt, beteiligt.
        Im Bewusstsein dieser Tatsache funktioniert die Fehlerrückführung, die den Fehler eines einzelnen Neurons mit dem Gradientverfahren berechnet und die Gewichte dieses Neurons verändert, sodass der Fehler geringer wird.
        % TODO diesen Absatz inhaltlich noch einmal überprüfen
        Um das Gradientverfahren anwenden zu können, muss die Aktivierungsfunktion differenzierbar und kontinuierlich sein. Sei also die Aktivierungsfunktion eine Sigmoidfunktion:
        \[o = \varphi(net) = \frac{1}{1 + e^{-net}}\]
        Der Fehler sei berechnet als die Differenz zwischen tatsächlicher und gewünschter Gesamtausgabe: \(\Delta = t - o\). Die Deltaregel der Fehlerrückführung zur Berechnung des Fehlers eines einzelnen Neurons lautet~\(\delta = l \cdot \Delta \cdot \varphi'(net)\). \(l\)~ist das Lerntempo, eine kleine Zahl, oft~\(0.01\). Sie spielt bei der Über- und Unteranpassung eine große Rolle. Die Ableitung der Sigmoidfunktion ist~\(\varphi(net)' = net (1 - net)\). Nach dem Einsetzen ergibt sich~\(\delta = l \cdot (t - o) \cdot (1 - net)\).
        Daraus sollen nun die Gewichtsveränderungen zwischen zwei Neuronen berechnet werden. Die Veränderung~\(\Delta w_{j,k}\) ist proportional zum Fehler des Neurons~\(j\) und abhängig von der Aktivierung des Neurons~\(k\). Um das Gewicht zwischen~\(k\) und~\(j\) nun anzupassen, wird die Differenz berechnet: \(\Delta w_{k,j} = \delta_k \cdot x_k\). In einer Eingabeschicht ist~\(x_k\) eine Eingabe, in anderen Schichten die gewichtete Summe~\(net\).
        Die Gewichtsveränderung zwischen zwei Neuronen in einem Feedforward-Netz mit der Sigmoidfunktion als Aktivierungsfunktion in einer Ausgabeschicht berechnet sich also:
        \[\Delta w_{k,j} = l \cdot (t_k - o_k) \cdot (1 - net_k) \cdot x_j\]

        Es besteht nun das Problem, dass der Fehler einer versteckten Schicht oder einer Eingabeschicht vom Fehler der nächsten Schicht abhängig ist. Indem man die Gewichtsveränderung nicht mit der Ableitung der Aktivierungsfunktion, sondern mit der gewichteten Summe der Fehler der nächsten Schicht berechnet, löst man dieses Abhängigkeitsproblem:
        \[\Delta w_{k,j} = l \cdot (t_k - o_k) \sum_l {w_{l,k} \cdot \delta_l} \cdot x_j \]

        Das Netz wird so von hinten nach vorne optimiert, daher kommt der Name Fehler\emph{rückführung}.\footcite[Vgl.][]{dongsoo}

    \section{Anwendung auf die Boston-Datenreihe}
        Mit künstlichen neuronalen Netzen können Probleme gelöst werden, zu denen keine algorithmische Lösung bekannt ist. Feedforward-Netze eignen sich, um zum Beispiel Eingabedaten zu klassifizieren, da sie in der Lage sind, Rauschen zu filtern. Bilderkennung und künstliche Intelligenz sind häufige Anwendungsgebiete, oder Voraussagen und Modellierungen.\footcite[Vgl.][]{hand11c}
        Mit voraussagenden künstlichen neuronalen Netzen lassen sich zum Beispiel Entscheidungen im Aktienhandel treffen.\footcite[Vgl.][]{facklam}
        Neben der Prognose der Veränderungsrichtung von Aktienkursen, der Prognose von Wechselkursen, der kurzfristigen Prognose von Aktienkursen, werden mit künstlichen neuronalen Netzen auch Kreditwürdigkeitsanalysen getätigt.\footcite[Vgl.][]{sbernd}

        Um herauszufinden, wie ein künstliches neuronales Netz eine Voraussage trifft, wird das von mir in der Programmiersprache Python geschriebene künstliche neuronale Netz auf ein Problem der sogenannten Boston-Datenreihe trainiert und angewendet.
        Sie ist erhältlich über die Internetseite des \textsc{Center for Machine Learning and Intelligent Systems}. Die Datenreihe wurde 1978 erstellt von D. Harrison und D. L. Rubinfeld und enthält mittlere Hauspreise und Umgebungsfaktoren von Vororten Bostons. Die Daten sind vom \textsc{U. S. Census Service} erhoben worden.
        Die Boston-Datenreihe wird in vielen Programmiereinführungen mit dem Fokus auf maschinelles Lernen verwendet, um die Funktionsweise einer linearen Regression an einem einfachen Beispiel zu demonstrieren. In dieser Arbeit wird die Datenreihe mit Hilfe eines künstlichen neuronalen Netzes untersucht.

        \subsection{Aufbau und Ziel der Datenreihe}
            Die Datenreihe enthält 506~Elemente mit jeweils 14~Attributen.\footcite{harrison}

            \vspace{\topsep}
            \begin{tabulary}{\textwidth}{lLl}
                Attribut        & Beschreibung & Beispielwert \\ \hline % chktex 44
                \texttt{CRIM}   & Die Prokopfkriminalitätsrate der Stadt & \(0.1415\) \\
                \texttt{ZN}     & Anteil an Parzellen, die für Wohngebiete markiert sind und größer als 25000 Quadratfuß & \(0\) \\
                \texttt{INDUS}  & Anteil an Grundbesitzen ohne Einzelhandelsunternehmen pro Stadt & \(6.91\) \\
                \texttt{CHAS}   & Charles-River-Variable, die 1 ist, wenn das Gebiet an einen Fluss grenzt, sonst 0 & \(0\) \\
                \texttt{NOX}    & Stickoxidkonzentration in parts per 10 million & \(0.448\) \\
                \texttt{RM}     & Durchschnittliche Anzahl an Räumen pro Haus & \(6.169\) \\
                \texttt{AGE}    & Anteil an bewohnten Gebäuden, die vor 1940 gebaut wurden & \(6.6\) \\
                \texttt{DIS}    & Gewichtete Distanz zu fünf Arbeitszentren Bostons & \(5.7209\) \\
                \texttt{RAD}    & Index der Erreichbarkeit von Einfallsstraßen & \(3\) \\
                \texttt{TAX}    & Vollwert des Eigentumssteueranteils pro 10000 Dollar & \(233\) \\
                \texttt{PTRATIO}& Verhältnis Schüleranzahl zu Lehreranzahl pro Stadt & \(17.9\) \\
                \texttt{B}      & Der Wert \(1000 \cdot {(B_k - 0.63)}^2\), wobei~\(B_k\) der Anteil an Schwarzen ist & \(383.37\) \\
                \texttt{LSTAT}  & Prozent der Unterschicht pro Stadt & \(5.81\) \\
                \texttt{MEDV}   & Mittelwert bewohnter Häuser in 1000 Dollar & \(25.3\) \\
            \end{tabulary}
            \vspace{\topsep}

            Nach einem Training soll ein Algorithmus den \texttt{MEDV}-Wert einer Stadt durch die gegebenen anderen 13~Attribute möglichst genau berechnen. Mit der Boston-Datenreihe lassen sich verschiedene Lösungsmethoden für ähnliche Probleme vergleichen. Ein Lösungsalgorithmus, der eine lineare Regression verwendet, ist in den Anlagen beigefügt, liegt aber nicht im Fokus dieser Arbeit.

        \subsection{Implementierung}
            \subsubsection{Grundlage}
            Um ein künstliches neuronales Netz zu entwickeln, wird Tensorflow verwendet. Tensorflow ist eine von Google entwickelte und genutzte Bibliothek für die Programmiersprache Python, mit der Berechnungen schnell und effizient durchgeführt werden können.
            Python ist eine einfache und trotzdem schnelle Programmiersprache, die auf vielen Plattformen läuft. Sowohl Tensorflow als auch Python sind Open Source Software und damit frei nutzbar.

            Verstanden, wie die Voraussage eines künstlichen neuronalen Netzes funktioniert, wird am besten anhand der Untersuchung eines funktionsfähigen Netzes. Mit Tensorflow und Python habe ich ein Feedforward-Netz erschaffen, welches in der Lage ist, das Problem der Boston-Datenreihe zu lösen. In der Implementierung findet die zuvor erklärte Mathematik Anwendung. Nach Übertragung der Formeln in algorithmische Logik werden die Ergebnisse der Umsetzung untersucht.
            Die Erklärung ist auf die Lösung des Problems der Boston-Datenreihe bezogen, die Umsetzungen lassen sich auf beliebige Probleme, die mit dreischichtigen Feedforward-Netzen gelöst werden können, übertragen.

            Um das Training zu erleichtern, werden vor den Berechnungen alle Eingangsdaten und Zieldaten skaliert, sodass sie sich im Intervall~\([0;1]\) befinden. Die Anpassung beschleunigt den Lernvorgang.
            Die Eingangsdaten sind 506~Objekte mit 13~Attributen. Die erwarteten 506~Zieldaten sind die mittleren Hauspreise. Aus einem Eingabeobjekt wird ein Eingabevektor erstellt, der in der Programmierung als Liste gesehen wird. Die Listen enthalten die Attributenwerte in der oben aufgezählten Reihenfolge.
            Eingangsdaten und zugehörige Ausgangsdaten werden in zwei Sets aufgeteilt. Ein Set, bestehend aus zufälligen 85\%~der Eingangs- und zugehörigen Zieldaten, wird zum Training verwendet. Mit dem anderen Set wird das künstliche neuronale Netz bewertet.
            Das implementierte künstliche neuronale Netz besteht aus drei Schichten. In der ersten Schicht befinden sich die 13~Eingabeneuronen, von denen jedes ein Element der Eingabeliste als Eingabe erhält. Die zweite, versteckte Schicht, besteht aus 9 Neuronen. Die Zahl ist willkürlich gewählt und orientiert sich an der Faustformel, die Anzahl auf ungefähr zwei Drittel der Summe der Eingangs- und Ausgangsneuronen festzulegen, was in den Tests gute Ergebnisse bringt. Die Ausgabeschicht enthält ein einzelnes Neuron, dessen Ausgabe möglichst nahe an den Zielwert kommen soll.

            Die Eingabeneuronen werden mit den 13 Eingangsdaten gefüttert und zu jeder Eingabe wird das Bias addiert. Diese Werte werden mit den Gewichten, die die erste und die zweite Schicht verbinden, multipliziert. Die zweite Schicht enthält 9 Neuronen, sodass die Gewichtsmatrix die Dimensionen \(13 \times 9\) hat. Die Aktivierungsfunktion wird auf jedes Element der Netzeingabe angewendet.
            \begin{pythoncode}
                vecVersteckt = sigmoid((vecEingaben + vecBias) * matGewichteEingabe)
            \end{pythoncode}
            Der erhaltene Vektor hat neun Dimensionen, beziehungsweise die erhaltene Liste hat eine Länge von neun, pro verstecktem Neuron ein Element. Da das Produkt aus Vektor und Matrix ein Vektor mit den gewichteten Summen als Elementen ist, muss nicht zusätzlich eine Summe berechnet werden.
            Die Ausgaben der versteckten Schicht werden mit der zweiten Gewichtsmatrix multipliziert. Diese hat die Dimensionen \(9 \times 1\). Wieder wird für den Ergebnisvektor der Dimension 1 die Sigmoidfunktion angewandt. Die Ausgabe dieses einen Ausgabeneurons ist die Gesamtausgabe des Netzes.
            \begin{pythoncode}
                scalarAusgabe = alsSkalar(sigmoid(vecVersteckt * matGewichteAusgabe))
            \end{pythoncode}

            Die Fehlerfunktion ist die Differenz zwischen der Ausgabe und dem Ziel.
            \begin{pythoncode}
                scalarFehler = scalarZiel - scalarAusgabe
            \end{pythoncode}

            Es wäre für den Computer uneffizient, nur eine Liste von Eingaben mit einem Ziel auf einmal zu berechnen. TensorFlow ist darauf ausgelegt, viele Berechnungen auf einmal durchzuführen, und braucht wesentlich weniger Zeit für eine große Berechnung als für viele kleine. Um das auszunutzen, werden bis zu 50 Eingabeobjekte und 50 Zieldaten gleichzeitig verarbeitet. So wird der Vektor der Quelldaten zu einer Matrix der Dimension \(50 \times 13\) und das Zielskalar zu einem \(50\)-dimensionalen Vektor. Für die Berechnung ergibt sich kein großer Unterschied, aber die Fehlerfunktion wird angepasst. Es wird die Summe aller 50 Differenzen genommen. Da es passieren kann, dass einzelne große Fehlerdifferenzen in der Summe 0 ergeben, wird der Betrag der Differenzen summiert. So verändert sich der Pseudocode zu:
            \begin{pythoncode}
                matBias = dupliziere(vecBias, 50)
                matVersteckt = sigmoid((matEingaben + matBias) * matGewichteEingabe)
                vecAusgabe = alsVec(sigmoid(matVersteckt * matGewichteAusgabe))
            \end{pythoncode}

            Jede Gruppe von Eingabeobjekten und Zieldaten wird 100 mal trainiert, um dem Optimierungsalgorithmus genug Gelegenheit zum Anpassen der Netzparameter zu geben.  Wird ein neuronales Netz zu wenig trainiert, werden im Test ungenaue Ergebnisse erzielt, es ist unterangepasst; wird es zu viel trainiert, ist das Netz zu sehr auf die Trainingsdaten fixiert und kann auf die Testdaten nicht eingehen, es ist überangepasst.
            Um das Risiko der Überanpassung zu minimieren, wird während des gesamten Trainings das Lerntempo der Fehlerrückführung verringert, sodass die Optimierung bei unbekannten Daten die Parameter langsamer verändert. Es startet bei dem Wert \(0.1\) und fällt in jedem weiteren Lernschritt exponentiell. Das Lerntempo eines Schrittes berechnet sich als \(\text{aktuellesLerntempo} = \text{lerntempo} \cdot \text{falltempo} ^ {\text{schritt} / \text{fallschritt}}\). Die Variable \(\text{schritt}\) wird während des Lernens hochgezählt.\footcite[Vgl.][]{gbrainteam}

        \subsubsection{Programmcode}
            Das Programm ist komplett in Eigenentwicklung entstanden.
            Der vollständige Quellcode des Programms befindet sich als Konsolenanwendung in den Anlagen.
            Umgesetzt in Python-Code besteht das Programm aus diesen wesentlichen Elementen:

            \begin{pythoncode}
# Vereinfachte Darstellung der Implementierung eines Feedforward-Netzes
# mit einer versteckten Schicht.
#
# Copyright (c) 2016, schneefux
#
# Parameter der Netzkonfiguration:
# Die Anzahl an Eingabeneuronen, an versteckten Neuronen
# und an Ausgabeneuronen
IN = 13
HIDDEN = 9
OUT = 1
# Die anfängliche Lerngeschwindigkeit und die Parameter
# für die Berechnung der fallenden Geschwindigkeit
LEARNINGRATE = 0.1
DECAYRATE = 0.95
DECAYSTEPS = 10

# Die Eingabeschicht
with tf.name_scope('input_layer'):
    # Die placeholder werden später von Tensorflow mit den Quelldaten
    # und den gewünschten Zieldaten gefüllt.
    matInput = tf.placeholder(tf.float32, [None, IN], name='input')
    vecTarget = tf.placeholder(tf.float32, [None], name='target')

    # Der Vektor Bias wird als Variable mit zufälligen Werten
    # initialisiert und so vermehrt, dass eine Matrix entsteht,
    # die auf die Eingaben addiert wird.
    scalarNumelements = tf.shape(vecTarget, name='batch_length')
    vecBias = tf.Variable(tf.random_uniform([IN], -1.0, 1.0), name='bias')
    vecsBias = tf.tile(vecBias, scalarNumelements)
    vecsBias = tf.reshape(vecsBias, [-1, IN])
    matBiasedInput = tf.add(matInput, vecsBias, name='biased_input')

# Die versteckte Schicht
with tf.name_scope('hidden_layer'):
    # Die Gewichtsmatrix wird als Variable
    # mit zufälligen Werten initialisiert,
    matWeights = tf.Variable(tf.random_uniform([IN, HIDDEN], -1.0, 1.0),
                             name='weights')
    # mit der Eingabe+Bias multipliziert,
    matLayerin = tf.matmul(matBiasedInput, matWeights, name='layer_input')
    # und durch die Sigmoidfunktion geführt.
    matLayerout = tf.sigmoid(matLayerin, name='layer_output')

# Die Ausgabeschicht
with tf.name_scope('output_layer'):
    # Wie bei der versteckten Schicht.
    # Matrix mit zufälligen Werten initialisieren,
    matWeightsHidden = tf.Variable(
            tf.random_uniform([HIDDEN, OUT], -1.0, 1.0),
            name='hidden-weights')
    # mit der Ausgabe der letzten Schicht multiplizieren,
    matLayerinHidden = tf.matmul(matLayerout, matWeightsHidden,
                                 name='hidden_input')
    # in die Sigmoidfunktion füttern
    matLayeroutHidden = tf.sigmoid(matLayerinHidden,
                                   name='hidden_output')
    # und anschließend von einer n*1-Matrix
    # in einen n-Dimensionalen Vektor umwandeln.
    vecLayeroutHidden = tf.reshape(matLayeroutHidden, [-1])

# Die Fehlerfunktion
with tf.name_scope('cost'):
    # Der Fehler ist die Summe der Beträge aller Differenzen
    # zwischen Zielwerten und ermittelten Werten.
    vecDifference = tf.sub(vecTarget, vecLayeroutHidden, name='diff')
    scalarError = tf.reduce_sum(tf.abs(vecDifference))

# Die Trainingsalgorithmik
with tf.name_scope('train'):
    # Der Zähler 'global_step' zählt die Trainingsschritte hoch,
    # in den Diagrammen die X-Achse.
    global_step = tf.Variable(0, trainable=False)
    # Das Lerntempo wird berechnet
    learning_rate = tf.train.exponential_decay(LEARNINGRATE,
                                               global_step, DECAYSTEPS,
                                               DECAYRATE,
                                               staircase=True)
    # Das Gradientverfahren wird mit dem
    # durch die Fehlerfunktion gefundenen
    # Fehler und der eben berechneten learning rate
    # angewiesen, den Fehler zu verringern.
    train_step = tf.train.GradientDescentOptimizer(
        learning_rate).minimize(scalarError, global_step=global_step)
            \end{pythoncode}

        \subsection{Evaluation verschiedener Netzkonfigurationen}
            Das Problem, dass das implementierte künstliche neuronale Netz löst, lässt sich auch mit einer linearen Regression lösen.
            Um die Genauigkeit einer Vorhersage einer linearen Regression und der künstlicher neuronaler Netze zu vergleichen, kann man den durchschnittlichen quadratischen Fehler, englisch mean squared error oder kurz MSE der Testdaten mit der Formel \(\text{mse} = {(\text{ziel} - \text{vorhersage})}^2\) berechnen.
            Bei einer linearen Regression\footnote{Ein Programm zur Berechnung der linearen Regression befindet sich in den Anlagen.} erhält man einen Wert zwischen 10 und 30.
            Das künstliche neuronale Netz konnte in den Tests Werte vorhersagen, die genauso gut wie die einer linearen Regression waren oder noch besser.

            In der Tabelle ist ein Vergleich zwischen fünf mittleren Hauspreisen, die das künstliche neuronale Netz vorhergesagt hat, und den tatsächlichen Werten. Die Differenzen sind größtenteils kleiner als~1000\$.

            \begin{center}
                \begin{tabular}{ccc}
                    Tatsächlicher Wert & Vorhergesagter Wert & Differenz \\ \hline % chktex 44
                    \(23.6\) & \(28.05794144\) & \(~5\) \\
                    \(32.4\) & \(31.51463318\) & \(<1\) \\
                    \(13.6\) & \(14.05183411\) & \(<1\) \\
                    \(22.8\) & \(23.38663673\) & \(<1\) \\
                    \(16.1\) & \(16.86310959\) & \(<1\)
                \end{tabular}
            \end{center}

            Nun wird analysiert, wie sich unterschiedliche Konfigurationen des neuronalen Netzes auf die Vorhersagegenauigkeit auswirken, um besser zu verstehen, wie es funktioniert.
            Für alle folgenden Tests werden jeweils die gleichen Eingangs- und Zieldaten verwendet, damit das Ergebnis vergleichbar ist. Die obere Tabelle ist mit Ergebnissen der Konfiguration, wie sie am Anfang des Codes der Implementierung beschrieben ist, entstanden. Das künstliche neuronale Netz wurde in 50er-Paketen 100 mal trainiert. Es ergab sich ein MSE von \(11.44\).

\ifx\pagecountmode\undefined{}
            \begin{figure}
                \centering
                \begin{minipage}{0.45\textwidth}
                    \centering
                    \begin{tikzpicture}
                        \begin{axis}[
                            ylabel={Fehler},
                            xlabel={Schritt},
                            width=\textwidth
                        ]
                            \addplot table [
                                mark=,
                                x=Step,
                                y=Value,
                                col sep=comma,
                            ] {fehler.csv};
                        \end{axis}
                    \end{tikzpicture}
                    \caption{Fehler im Verlauf des Trainings\label{fig:fehlerverlauf}}
                \end{minipage}\hfill
                \begin{minipage}{0.45\textwidth}
                    \centering
                    \begin{tikzpicture}
                        \begin{axis}[
                            ylabel={Fehler},
                            xlabel={Schritt},
                            width=\textwidth
                        ]
                            \addplot table [
                                mark=,
                                x=Step,
                                y=Value,
                                col sep=comma,
                            ] {fehler-lrfix.csv};
                        \end{axis}
                    \end{tikzpicture}
                    \caption{Fehler mit einem fixen Lerntempo\label{fig:lerntempo-fix}}
            \end{minipage}
            \end{figure}
\fi

            In der Abbildung~\ref{fig:fehlerverlauf} ist dargestellt, wie sich der Fehler der Fehlerfunktion im Verlaufe des Trainings ändert. Nach jeweils 100~Schritten gibt es einen Sprung des Fehlers, der anschließend geglättet wird. Dieser Sprung entsteht, weil das künstliche neuronale Netz dann mit neuen, unbekannten Daten trainiert wird und sich langsam anpasst.
            Zu Beginn ist der Fehler am höchsten, da die zufälligen Gewichte und Bias das Netz Werte vorhersagen lassen, die weit an dem gewünschten Wert vorbei gehen. Die Optimierungsfunktion gleicht das schnell aus.

            \subsubsection{Auswirkung des Lerntempos und der Anzahl versteckter Neuronen}
                Abbildung~\ref{fig:lerntempo-fix} zeigt, wie sich der Fehler über die Zeit verhalten würde, wäre das Lerntempo fix auf~0.1 und würde nicht langsam absinken. Anders als in~\ref{fig:fehlerverlauf} sieht man keine Glättung der Fehlerkurve gegen Ende des Lernvorgangs, stattdessen wird deutlich, dass das Netz der Überanpassung unterliegt, weil es sich nicht richtig an die Trainingsdaten anpasst und der Fehler sehr stark schwankt. Die Gradientfunktion verfehlt das Fehlerminimum. Der MSE der Testdaten beträgt etwa 32~und ist damit zu hoch, als dass die Ergebnisse des Netzes nutzbar wären. Ein künstliches neuronales Netz lernt also besser, je entspannter es sich nach einem längeren Training gegenüber neuen Daten verhält, und trifft dann bessere Voraussagen.

\ifx\pagecountmode\undefined{}
                \begin{figure}
                    \centering
                    \begin{tikzpicture}
                        \begin{axis}[
                            ylabel={Fehler},
                            xlabel={Schritt},
                            width=0.5\textwidth
                        ]
                            \addplot [violet] table [
                                mark=,
                                x=Step,
                                y=Value,
                                col sep=comma,
                            ] {fehler-1hidden.csv};
                            \addplot [teal] table [
                                mark=,
                                x=Step,
                                y=Value,
                                col sep=comma,
                            ] {fehler-20hidden.csv};
                            \addplot [red] table [
                                mark=,
                                x=Step,
                                y=Value,
                                col sep=comma,
                            ] {fehler-200hidden.csv};
                        \end{axis}
                    \end{tikzpicture}
                    \caption[Fehler bei 1, 20, 200 versteckten Neuronen]{Fehler bei \textcolor{violet}{1},~\textcolor{teal}{20},~\textcolor{red}{200} versteckten Neuronen\label{fig:varianten-versteckte-neuronen}}
                \end{figure}
\fi

                Die Anzahl versteckter Neuronen hat großen Einfluss auf die Genauigkeit der Vorhersagen. Wie man in Abbildung~\ref{fig:varianten-versteckte-neuronen} sieht, ist die Optimierungsfunktion mit 200~versteckten Neuronen überfordert, kann sich aber mit 20~versteckten Neuronen besser auf die Boston-Daten anpassen, als mit nur einem. 200~versteckte Neuronen sind zu viele, als dass das Netz noch lernen könnte, aber mit einem versteckten Neuron ist es noch nicht ausgereizt. Hat man nur ein verstecktes Neuron, ist der Unterschied zu keiner versteckten Schicht nur sehr gering.
                Künstliche neuronale Netze treffen bessere Voraussagen, wenn die Eingangsdaten durch eine versteckte Schicht gehen, die nicht zu komplex ist.

            \subsubsection{Auswirkung der Anzahl Trainingsschritte}
\ifx\pagecountmode\undefined{}
                \begin{figure}
                    \centering
                    \begin{minipage}{0.49\textwidth}
                        \centering
                        \begin{tikzpicture}
                            \begin{axis}[
                                ylabel={Fehler},
                                xlabel={Schritt},
                                width=\textwidth
                            ]
                                \addplot table [
                                    mark=,
                                    x=Step,
                                    y=Value,
                                    col sep=comma,
                                ] {fehler-10steps.csv};
                            \end{axis}
                        \end{tikzpicture}
                        \caption{Fehler bei 10~Trainingsschritten\label{fig:zehnschritte}}
                    \end{minipage}\hfill
                    \begin{minipage}{0.49\textwidth}
                        \centering
                        \begin{tikzpicture}
                            \begin{axis}[
                                ylabel={Fehler},
                                xlabel={Schritt},
                                width=\textwidth
                            ]
                                \addplot table [
                                    mark=,
                                    x=Step,
                                    y=Value,
                                    col sep=comma,
                                ] {fehler-500steps-50decay.csv};
                            \end{axis}
                        \end{tikzpicture}
                        \caption{Fehler bei 500~Trainingsschritten\label{fig:viele-trainingsschritte}}
                    \end{minipage}
                \end{figure}
\fi

                Die Folgen von Unteranpassung sieht man, wenn man die Anzahl an Trainingsschritten zu stark verringert. Die Optimierungsfunktion bekommt zu wenige Gelegenheiten, das künstliche neuronale Netz an die Trainingsdaten anzupassen. In Abbildung~\ref{fig:zehnschritte} ist es zu wenig auf die Trainingsdaten angepasst, was man daran sehen kann, dass der Fehler gegen Ende des Trainings immer noch über~5 liegt. 500~Trainingsschritte, gezeigt in Abbildung~\ref{fig:viele-trainingsschritte}, würden zu einer Überanpassung führen, würde das Lerntempo nicht schnell genug sinken. Zu Anfang des Trainings mit 500~Schritten sieht man, wie der Fehler noch stark springt, aber dann gegen Ende gleichmäßig sinkt, was zu Ergebnissen führt, die mit der Anfangskonfiguration vergleichbar sind.

                Ein künstliches neuronales Netz trifft bessere Voraussagen, wenn es lange genug trainiert wurde.

            \subsubsection{Anpassung des Lerntempos und der Anzahl versteckter Neuronen}
\ifx\pagecountmode\undefined{}
                \begin{figure}
                    \centering
                    \begin{minipage}{0.49\textwidth}
                        \begin{tikzpicture}
                            \begin{axis}[
                                ylabel={Fehler},
                                xlabel={Schritt},
                                width=\textwidth
                            ]
                                \addplot [violet] table [
                                    mark=,
                                    x=Step,
                                    y=Value,
                                    col sep=comma,
                                ] {fehler-optimize1.csv};
                                \addplot [blue] table [
                                    mark=,
                                    x=Step,
                                    y=Value,
                                    col sep=comma,
                                ] {fehler-optimize2.csv};
                                \addplot [red] table [
                                    mark=,
                                    x=Step,
                                    y=Value,
                                    col sep=comma,
                                ] {fehler-optimize3.csv};
                            \end{axis}
                        \end{tikzpicture}
                        \caption{Ergebnisse der Optimierungen für 300~Trainingsschritte\label{fig:dreihundertschritte}}
                    \end{minipage}\hfill
                    \begin{minipage}{0.49\textwidth}
                        \centering
                        \begin{tikzpicture}
                            \begin{axis}[
                                ylabel=MSE,
                                xlabel=Anzahl versteckter Neuronen,
                                width=\textwidth,
                                enlargelimits=0.15,
                                bar width=4pt,
                                ybar
                            ]
                                \addplot table[meta=label] {%
                                    x   y           label
                                    2   11.9116048498 mse
                                    3   11.1524385699 mse
                                    4   11.743970118  mse
                                    5   9.89823469173 mse
                                    6   13.9700044151 mse
                                    7   10.8518897635 mse
                                    8   11.7992252605 mse
                                    9   9.86165519225 mse
                                    10  9.06410344363 mse
                                    11  8.7162225255  mse
                                    12  9.31919342458 mse
                                    13  10.8683007971 mse
                                    14  9.81450188138 mse
                                    15  9.21076581389 mse
                                    16  10.5126583211 mse
                                    17  11.9231341442 mse
                                    18  9.67562051661 mse
                                    19  10.0841650993 mse
                                    20  10.6535950672 mse
                                };
                            \end{axis}
                        \end{tikzpicture}
                        \caption{Mean squared error bei verschiedenen Anzahlen versteckter Neuronen\label{fig:mse}}
                    \end{minipage}
                \end{figure}
\fi

                Nun wird das sinkende Lerntempo untersucht, da es offensichtlich eine große Rolle zur Verhinderung von Überanpassung spielt und die Qualität der Voraussagen damit beeinflusst.
                In den vorherigen Tests wurden, wenn nicht anders angegeben, die Trainingsdaten 100~mal trainiert. Das Lerntempo ist berechnet worden als~\(0.1 \cdot 0.95^{\frac{s}{10}}\) für jeden Schritt~\(s\).
                Wie in dem letzten Test sichtbar war, ist es besser, etwas länger zu trainieren, als zu wenig. Also wird die Anzahl an Trainingsschritten des Testnetzes auf~300 erhöht. Damit das Lerntempo nicht zu schnell sinkt, wird der Fallschritt verdreifacht. Der Fehlergraph ist in Abbildung~\ref{fig:dreihundertschritte} violett, im Test ergibt sich ein MSE von~\(8.61\). Der Fehler springt am Anfang sehr stark und ändert sich gegen Ende kaum noch. Verringert man das Anfangslerntempo auf~\(0.01\) und erhöht man das Falltempo auf~\(0.9\) und den Fallschritt auf~100, ergibt sich der wesentlich ruhigere blaue Graph und ein MSE von~\(10.39\). Bei einem Falltempo von~\(0.96\) und einem Fallschritt von~\(50\) ist das Netz am Ende noch etwas anpassungsfreudiger -- dargestellt im roten Graphen -- und erzielt einen MSE von~\(9.86\). Auch wenn der MSE damit etwas höher als bei der Konfiguration zum violetten Graphen ist, wird sie bevorzugt, da sie in den letzten beiden Blöcken sich noch stärker anpasst, und durch diese Anpassungsfähigkeit besser bei anderen Zusammenstellungen der Trainingsdaten abschneiden würde. % chktex 8

                Die optimale Anzahl an versteckten Neuronen für diese Netzkonfiguration soll jetzt systematisch gefunden werden.
                In Abbildung~\ref{fig:mse} sind die MSE für 2 bis~20 versteckte Neuronen in der versteckten Schicht abgebildet. Der MSE eines einzelnen Neurons, \(28.52\), ist weit von den anderen Werten entfernt.
                11~versteckte Neuronen bringen das beste Ergebnis mit einem MSE von~\(8.72\). Diese Netzkonfiguration bringt ein um~4~Einheiten besseres Ergebnis als das einer linearen Regression mit \(13.73\)~Einheiten.

    \section{Fazit}
        Wie trifft ein künstliches neuronales Netz eine Voraussage?

        Ein künstliches neuronales Netz gewichtet Eingabedaten. Die Genauigkeit der Gewichtung hängt von der Qualität der Konfiguration und des Trainings ab. Die optimale Konfiguration lässt sich nicht berechnen, sondern muss ausprobiert werden. Das ,,Computergehirn'' erkennt die für den Anwender verborgenen Zusammenhänge mit bestimmten Konfigurationen besser, als mit anderen. Es erklärt dem Anwender jedoch nicht den Lösungsweg. Ein künstliches neuronales Netz zeigt dem Anwender statistische Zusammenhänge, die dieser selbst interpretieren muss. % chktex 26

        Das erklärte und implementierte künstliche neuronale Netz ist klein und einfach. Mit größeren künstlichen neuronalen Netzen, die auch unterschiedliche Netztypen kombinieren, ist es möglich, dass Computer komplizierte Probleme besser lösen, als ein Mensch es kann. Anders als bei einem Algorithmus besitzt ein künstliches neuronales Netz ein gewisses Maß an Intelligenz. Die Reaktion auf Eingabedaten kann zwar nachvollzogen, aber nicht unbedingt erklärt werden.

        Es existieren verschiedene Projekte und Spielereien mit künstlichen neuronalen Netzen, die das für Computer Unmögliche möglich zu machen scheinen.
        Mit neuraltalk2\footcite{neuraltalk} werden Bilder beschrieben, mit char-rnn\footcite{charrnn} imitiert ein rekurrentes künstliches neuronales Netz Shakespeare.
        neural-storyteller\footcite{storyteller} erzählt Geschichten angeregt durch Fotos, mit sogenannten deep convolutional neural networks überträgt ein Forscherteam den Zeichenstil berühmter Künstler auf beliebige Bilder\footcite{twominutepapers}. % chktex 26

        Künstliche neuronale Netze spielen Computerspiele perfekt, wie zum Beispiel MarI/O das Spiel Super Mario\footcite{mario}.
        Ein Google-Team hat mit der künstlichen Intelligenz AlphaGo die weltbesten Go-Spieler schlagen können\footcite{alphago}. 
        Zuvor glaubte man, ein Computer wäre dem Menschen in diesem Brettspiel noch lange unterlegen, da, anders als beim Schach, die \(10^{761}\)~Zugmöglichkeiten die Kapazität jedes Rechenzentrums übersteigen und daher nicht alle vorausberechnet werden können. AlphaGo ist in der Lage, den Zug vorherzusagen, der am wahrscheinlichsten zum Sieg führt.

        Das Forschungsgebiet künstlicher neuronaler Netze ist relevanter denn je, es ist zu erwarten, dass in nicht allzu langer Zeit künstliche neuronale Netze auch auf mobilen Geräten Einzug halten werden und vielleicht eines Tages das Gehirn des Menschen erweitern können.
        
\ifx\pagecountmode\undefined{}

    \clearpage

    \printbibliography[title=Literaturverzeichnis] % LITERATURVERZEICHNIS

    \clearpage

    \section{Anlagen}
        \subsection*{Vollständige Implementierung des künstlichen neuronalen Netzes}
            \begin{pythoncode}
#!/usr/bin/python3
#
# Copyright (c) 2016, schneefux
#
from sklearn import datasets
from sklearn import preprocessing
from sklearn.cross_validation import train_test_split
import tensorflow as tf
import numpy.random
import numpy as np

# Siehe Kommentar in dem Programm der linearen Regression
SEED = 42
numpy.random.seed(SEED)
tf.set_random_seed(SEED)

# Datenreihe laden
dataset = datasets.load_boston()
data, target = dataset.data, dataset.target

# Datenwerte für ein effektiveres Lernen zwischen 0 und 1 skalieren
data_scaler = preprocessing.MinMaxScaler()
data = data_scaler.fit_transform(data)
target_scaler = preprocessing.MinMaxScaler()
target = target_scaler.fit_transform(target)

# Datenwerte in Trainings- und Testreihe aufteilen
x_train, x_test, y_train, y_test = train_test_split(
    data, target, train_size=0.85
)

# Die Netzkonfiguration
BATCH = 50
STEPS = 300
HIDDEN = 11
IN = 13
OUT = 1
LEARNINGRATE = 0.01
DECAYRATE = 0.96
DECAYSTEPS = 50

# Der Lernverlauf wird in einem individuellem Dateinamen gespeichert,
# der je nach Konfiguration variiert.
RUNNAME = 'B'+str(BATCH)+'S'+str(STEPS)+'H'+str(HIDDEN)\
        + 'LR'+str(LEARNINGRATE) + 'DR'+str(DECAYRATE)\
        + 'DS'+str(DECAYSTEPS)+'SD'+str(SEED)

sess = tf.InteractiveSession()
# Ausführliche Kommentare und ein vereinfachter Ablauf
# finden sich in Abschnitt 2.2.2 der Facharbeit.

with tf.name_scope('input_layer'):
    matInput = tf.placeholder(tf.float32, [None, IN], name='input')
    vecTarget = tf.placeholder(tf.float32, [None], name='target')
    scalarNumelements = tf.shape(vecTarget, name='batch_length')
    vecBias = tf.Variable(tf.random_uniform([IN], -1.0, 1.0), name='bias')
    summaryBias = tf.histogram_summary("bias", vecBias)
    vecsBias = tf.tile(vecBias, scalarNumelements)
    vecsBias = tf.reshape(vecsBias, [-1, IN])
    matBiasedInput = tf.add(matInput, vecsBias, name='biased_input')

with tf.name_scope('hidden_layer'):
    matWeights = tf.Variable(tf.random_uniform([IN, HIDDEN], -1.0, 1.0),
                             name='weights')
    summaryWeights = tf.histogram_summary('weights', matWeights)
    matLayerin = tf.matmul(matBiasedInput, matWeights, name='layer_input')
    matLayerout = tf.sigmoid(matLayerin, name='layer_output')

with tf.name_scope('output_layer'):
    vecTarget = vecTarget
    matWeightsHidden = tf.Variable(
            tf.random_uniform([HIDDEN, OUT], -1.0, 1.0),
            name='hidden-weights')
    summaryWeightsHidden = tf.histogram_summary('hiddenweights',
                                                matWeightsHidden)
    matLayerinHidden = tf.matmul(matLayerout, matWeightsHidden,
                                 name='hidden_input')
    matLayeroutHidden = tf.sigmoid(matLayerinHidden, name='hidden_output')
    # nur vec, wenn OUT=1
    vecLayeroutHidden = tf.reshape(matLayeroutHidden, [-1])

with tf.name_scope('cost'):
    vecDifference = tf.sub(vecTarget, vecLayeroutHidden, name='diff')
    scalarError = tf.reduce_sum(tf.abs(vecDifference), name='error')
    summaryError = tf.scalar_summary('error', scalarError)

with tf.name_scope('train'):
    global_step = tf.Variable(0, trainable=False)
    learning_rate = tf.train.exponential_decay(LEARNINGRATE,
                                               global_step, DECAYSTEPS,
                                               DECAYRATE, staircase=True)
    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(
            scalarError, global_step=global_step)

summaries = tf.merge_all_summaries()
writer = tf.train.SummaryWriter("/tmp/boston_logs/%s" % RUNNAME,
                                sess.graph_def)
# Alle Tensorflow-Variablen müssen bis hier spezifiziert sein!
sess.run(tf.initialize_all_variables())

# Training
for count in range(0, int(len(x_train) / BATCH)):
    print("batch " + str(count))
    for i in range(0, STEPS):
        result = sess.run([summaries, train_step],
                          feed_dict={
                              matInput: x_train[count*BATCH:(count+1)*BATCH],
                              vecTarget: y_train[count*BATCH:(count+1)*BATCH]
                          })
        writer.add_summary(result[0], count * STEPS + i)

print("Training beendet")

print("Testergebnisse - Differenz zu echten Daten:")
result = sess.run([vecLayeroutHidden],
                    feed_dict={
                        matInput: x_test,
                        vecTarget: y_test
                    })
print("================================")
print("erwartete Daten:")
print("--------------------------------")
# Ausgabedaten sind skaliert, zurückskalieren
print(target_scaler.inverse_transform(y_test))
print("--------------------------------")
print("vorhergesagte Daten:")
print(target_scaler.inverse_transform(result[0]))
print("--------------------------------")
print("")
print("Differenz:")
print("--------------------------------")
# MSE aus der Differenz unskalierter Daten berechnen,
# nicht MSE aus Differenz skalierter Daten unskalieren:
# !!! (a - b) * f + o != (a * f + o) - (b * f + o)
# <=> af - bf + o     != af - bf
diff = target_scaler.inverse_transform(result[0]) -
           target_scaler.inverse_transform(y_test)
print(diff)
print("--------------------------------")
print("")
print("MSE")
# Entsprechend (af - bf + o)^2 != (af - bf)^2…
print(np.mean(diff ** 2))
            \end{pythoncode}
        \subsection*{Vollständige Implementierung einer linearen Regression}
            \begin{pythoncode}
#!/usr/bin/python3
#
# Copyright (c) 2016, schneefux
#
from sklearn.datasets import load_boston
from sklearn.linear_model import LinearRegression
from sklearn.cross_validation import train_test_split
import numpy as np

# Ein gleicher Wert SEED bei der linearen Regression
# und bei der Implementierung des kNN bewirkt,
# dass Trainings- und Testreihe gleich sind
# und damit das Ergebnis vergleichbar.
SEED = 42
np.random.seed(SEED)

# Datenreihe laden und in Trainings- und Testreihe aufteilen
data = load_boston()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, train_size=0.85)

# Die lineare Regression auf die Trainingsdaten anwenden
model = LinearRegression()
model.fit(X_train, y_train)

# Die Testdaten vorhersagen
prediction = model.predict(X_test)
# Das Quadrat aller Differenzen berechnen
mse = np.mean([
    (y_test[n] - prediction[n]) ** 2
    for n in range(len(prediction))
])

print(mse)
                \end{pythoncode}
\ifx\draftmode\undefined{}
\else
        \subsection*{Lizenzhinweise}
            Dieses Werk ist lizenziert unter einer Creative Commons Namensnennung -- Nicht kommerziell -- Keine Bearbeitungen 4.0 International Lizenz\footnote{\url{https://creativecommons.org/licenses/by-nc-nd/4.0/}}.\\ % chktex 8

            Hiermit wird unentgeltlich jeder Person, die eine Kopie der Software und der zugehörigen Dokumentationen (die ,,Software'') erhält, die Erlaubnis erteilt, sie uneingeschränkt zu nutzen, inklusive und ohne Ausnahme mit dem Recht, sie zu verwenden, zu kopieren, zu verändern, zusammenzufügen, zu veröffentlichen, zu verbreiten, zu unterlizenzieren und/oder zu verkaufen, und Personen, denen diese Software überlassen wird, diese Rechte zu verschaffen, unter den folgenden Bedingungen: % chktex 26

            Der obige Urheberrechtsvermerk und dieser Erlaubnisvermerk sind in allen Kopien oder Teilkopien der Software beizulegen.

            \uppercase{Die Software wird ohne jede Ausdrückliche oder implizierte Garantie bereitgestellt, einschließlich der Garantie zur Benutzung für den vorggesehenen oder einem bestimmten Zweck sowie jeglicher Rechtsverletzung, jedoch nicht darauf beschränkt. In keinem Fall sind die Autoren oder Copyrightinhaber für jeglichen Schaden oder sonstige Ansprüche haftbbar zu machen, ob infolge der Erfüllung eines Vertrages, eines Deliktes oder anders im Zusammenhang mit der Software oder sonstiger Verwendung der Software entstanden.\footnote{\url{https://opensource.org/licenses/MIT}}}
\fi

    \clearpage
    \section{Selbstständigkeitserklärung}
        Hiermit versichere ich, dass ich die vorliegende Arbeit ohne unzulässige Hilfe Dritter und ohne Benutzung anderer als der angegebenen Hilfsmittel angefertigt habe; die aus fremden Quellen direkt oder indirekt übernommenen Gedanken sind als solche kenntlich gemacht.

        \vspace{2cm}

        Stadt, \today

        \vspace{2cm}

        \textsc{schneefux}
\fi
\end{document}
