\def\pathToRoot{../../}
\def\DLbook{https://www.deeplearningbook.org/}
\def\LAchapter{https://www.deeplearningbook.org/contents/linear_algebra.html}
\def\post{https://towardsdatascience.com/linear-algebra-for-deep-learning-f21d7e7d7f23}
\input{HW1/mainheader}
\input{HW1/uebungsheader}
\def\issolution{}

\begin{document}

% {Sheet number}{headline}{deadline}
\exercisehead{1}{Linear Algebra Basics}{17.11.2020, 23:59}

\section*{Instructions}

A good understanding of linear algebra is essential for understanding and working with many machine learning algorithms, especially deep learning algorithms.
The purpose of the following exercises is to create the base for understanding concepts and algorithms introduced in the following lectures.

The exercises are based on the \href{\post}{\textit{Basic Linear Algebra for Deep Learning} post} by Niklas Donges and the \href{\LAchapter}{\textit{Linear Algebra} chapter} in the \href{\DLbook}{\textit{Deep Learning} book} by Ian Goodfellow, Yoshua Bengio and Aaron Courville.

\section*{Exercises}

\begin{exercise}[Mathematical objects][1]
 Consulting \href{\post}{Niklas's post} or \href{\LAchapter}{Deep Learning book}, give short definitions and your own examples.

 Note the conventional way to define different mathematical objects (you can find them in the \href{\LAchapter}{DL book}), e.g. we write scalars in italics and usually give them lowercase variable names.
 Throughout all the assignments (and of course outside of this course) stick to the conventional way of writing scalars, vectors etc\footnote{See how to make letters bold in LaTex math mode  \href{https://tex.stackexchange.com/questions/14395/bold-italic-vectors}{here}.}.

 \begin{enumerate}
     \item scalar
     \item vector
     \item matrix
     \item tensor
 \end{enumerate}

\end{exercise}

\begin{solution}
   % write the solution here
   \color{blue}
   \begin{enumerate}
       \item A \textbf{scalar} is just a single number. We write scalars in italics and usually give them lowercase variable names.
        \math
        s \in R , s = 0.2
        \endmath

        \item A \textbf{vector} is an ordered array of numbers. Typically we give vectors lowercase names in bold typeface. The elements of the vector are identified by writing its name in italic typeface.
        \math
            \textbf{x} = \begin{bmatrix}
            x_1 & x_2 & x_3
            \end{bmatrix}
        \endmath

        \item A \textbf{matrix} is a 2-D array of numbers, where each element is identiﬁed by indices instead of just one. We usually give matrices uppercase variable names with bold typeface, such as \textbf{A}. The elements of the matrix are identified by writing its name in italic typeface.

        \math
            \textbf{A} = \begin{bmatrix}
            x_1 & x_2 & x_3 \\
            x_4 & x_5 & x_6 \\
            x_7 & x_8 & x_9
            \end{bmatrix}
        \endmath

        \item A \textbf{tensor} is an array of numbers, arranged on a regular grid, with a variable number of axes. A tensor has three indices, where the first one points to the row, the second to the column and the third one to the axis. We denote a tensor named “A” with this typeface: A.

        We identify the element of A at coordinates $ (i,j,k)$  by writing  $ \textbf{A}_{i,j,k}$.

 \end{enumerate}
\end{solution}

\begin{exercise}[Vectors in machine learning][2]

In machine learning we deal with data in multidimensional space.
Each data point is characterised by a number of features: for example, a lecture can be characterised by the number of CoLi students, CS students and students from other departments (i.e. 3 features).

\begin{figure}[h]
    \centering
    \includegraphics[width=.8\textwidth]{figures/vectors.png}
    \caption{Different vector representations. Note: those are not representations of the same vector}
    \label{fig:vector_representations}
\end{figure}

\begin{enumerate}
\item Create your own data set of \textbf{three} data points where each data point is described by \textbf{two} features.
Report:
    \begin{enumerate}
        \item what are the data points in your data set (e.g. lectures);
        \item what are the features of the data points (e.g. number of CoLi students).
    \end{enumerate}

\item Represent the data set in physical space (as in Figure \ref{fig:vector_representations}).
Label the axes according to the features you defined.

\item Represent the data in form of vectors $\vect{x_1}, \vect{x_2}, \vect{x_3}$.

\item Represent the data in form of a matrix $\vect{X} \in \mathbb{R}^{n\times m}$, where $n$ is the number of data points and $m$ is the number of features.

\end{enumerate}

\end{exercise}

\begin{solution}
   % write the solution here
   \color{blue}

   \begin{enumerate}
       \item This dataset comprises number of letters in the alphabet vs. number of phonemes in three languages: Arabic, Hindi and Lithuanian.

       \begin{enumerate}
            \item Data points - Arabic, Hindi, Lithuanian.
           \item Features of the data points - number of letters in alphabet, number of phonemes.
       \end{enumerate}

       \item
    \begin{tikzpicture}
  \draw[<->] (0,0)--(6.1,0) node[right]{$x, letters$};
  \draw[<->] (0,0)--(0,6.1) node[above]{$y, phonemes$};
  \draw[line width=2pt,blue,-stealth](0,0)--(2.8,3.6) node[anchor=south west]{$\boldsymbol{Arabic(28,36)}$};
  \draw[line width=2pt,red,-stealth](0,0)--(4.7,6.1) node[anchor=south west]{$\boldsymbol{Hindi(47,61)}$};
  \draw[line width=2pt,green,-stealth](0,0)--(3.2,5.6) node[anchor=north east]{$\boldsymbol{Lithuanian(32,56)}$};
\end{tikzpicture}

       \item
       Arabic:\\
     $$\vect{x1} = \begin{bmatrix} \texttt{28} \\  \texttt{36} \end{bmatrix} $$ \\
     Hindi:\\
     $$\vect{x2} = \begin{bmatrix} \texttt{47} \\  \texttt{61} \end{bmatrix} $$ \\
     Lithuanian:\\
     $$\vect{x3} = \begin{bmatrix} \texttt{32} \\  \texttt{56} \end{bmatrix} $$

       \item
    $\vect{X} \in \mathbb{R}^{n\times m}$\\
    $\vect{X} = \begin{bmatrix} 28 & 47 & 32 \\ 36 & 61 & 56 \end{bmatrix}$
   \end{enumerate}
\end{solution}

\begin{exercise}[Operations on vectors and matrices][2.5]

We can perform mathematical operations such as addition, subtraction, multiplication on vectors, matrices and scalars.
Carefully read the \textit{Computational rules} part of the \href{\post}{post\footnote{Pay attention to the dimensions and use the cheat sheets provided by Niklas.}} and perform the following exercises.
If the computation is impossible, write \textit{impossible} and argue why.

\begin{enumerate}
    \item $\begin{bmatrix} 3 & 4 & 1 \\ 0 & 2 & 3 \end{bmatrix} \div 2 = $
    \item $\begin{bmatrix} 3 & 4 & 1 \\ 0 & 2 & 3 \end{bmatrix} \ast \begin{bmatrix} 2 \\ 1 \\ 5  \end{bmatrix} = $
    \item $\begin{bmatrix} 3 & 4 & 1 \\ 0 & 2 & 3 \end{bmatrix} - \begin{bmatrix} -1 & 2 & 10 \\ 3 & -6 & 3 \end{bmatrix} = $
    \item $\begin{bmatrix} 3 & 4 & 1 \\ 0 & 2 & 3 \end{bmatrix} \ast \begin{bmatrix} 0 & 2 \\ 1 & -2 \\ 4 & 0 \end{bmatrix} = $
    \item $\begin{bmatrix} 3 & 4 & 1 \\ 0 & 2 & 3 \end{bmatrix} \ast \begin{bmatrix} 10 & 5 \\ 0 & 2 \end{bmatrix} = $
\end{enumerate}

If you are interested in the physical meaning of vectors, matrices and operations on them, watch videos from the \href{https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab}{Linear Algebra} series by \href{https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw}{3Blue1Brown}.

\end{exercise}

\begin{solution}
   % write the solution here
   \color{blue}
    \begin{enumerate}
    \item   \math
                \begin{bmatrix}
                    1.5 & 2 & 0.5 \\
                    0 & 1 & 1.5
                \end{bmatrix}
            \endmath

    \item   \math
                \begin{bmatrix}
                    15\\
                    17
                \end{bmatrix}
            \endmath

    \item   \math
                \begin{bmatrix}
                    4 & 2 & -9 \\
                    -3 & 8 & 0
                \end{bmatrix}
         \endmath

    \item   \math
                \begin{bmatrix}
                    8 & -2 \\
                    14 & -4
                \end{bmatrix}
            \endmath

        \item Impossible. As number of columns in first matrix doesn't match number of rows in the second one.

    \end{enumerate}

\end{solution}

\begin{exercise}[Multiplication properties and types of matrices][3.5]

Read chapters \textit{2.2 Multiplying Matrices and Vectors} and \textit{2.3 Identity and Inverse Matrices} of the \href{https://www.deeplearningbook.org/contents/linear_algebra.html}{DL book} or corresponding parts of the \href{https://towardsdatascience.com/linear-algebra-for-deep-learning-f21d7e7d7f23}{Niklas's post}

On the internet, find the following definitions and write them down:
\begin{enumerate}
    \item symmetric matrix
    \item orthogonal matrix
    \item unit vector
    \item orthogonal vectors
\end{enumerate}

Let $\vect{A}^{n \times n}$ be an orthogonal matrix, $\vect{B}^{m \times m}$ -- a symmetric matrix and $\vect{C}^{m \times n}$ -- a regular matrix, $\vect{I}$ -- identity matrix, and $\lambda$ - a scalar. \\
    For each expression choose its equivalent, show the intermediate steps, give the dimensions of the result. \vspace*{0.5em} \\
    $(\vect{B} \lambda \vect{I})^T\vect{C} = $ \\
    \begin{enumerate*}
        \item $\lambda \vect{CB}$ \hspace*{3em}
        \item $\vect{CB}^T  \lambda$ \hspace*{3em}
        \item $\lambda \vect{BCI}$
    \end{enumerate*}
    \vspace*{0.5em} \\
    $\vect{A}^{-1}(\vect{CA}^{-1})^T \lambda = $ \\
    \begin{enumerate*}
        \item $\lambda \vect{C}^T$ \hspace*{3em}
        \item $\lambda \vect{C}$ \hspace*{4.5em}
        \item $\vect{A}^T\vect{C}^T \lambda$
    \end{enumerate*}
    \vspace*{0.5em} \\
    $\vect{AA}^T\vect{B}^T\vect{C} = $ \\
    \begin{enumerate*}
        \item $\vect{CB}$ \hspace*{3.2em}
        \item $\vect{B}^{-1}\vect{C}$ \hspace*{3.8em}
        \item $\vect{BC}$
    \end{enumerate*}

\end{exercise}

\begin{solution}
   \color{blue}
   \begin{enumerate}
       \item A \textbf{symmetric matrix} is a square matrix that satisfies $ A^T = A$. The entries of a symmetric matrix are symmetric with respect to the main diagonal. If $A$ is a symmetric matrix, then for every $i$,$j$ $$ a_{ij} = a_{ji}$$

        \item An \textbf{orthogonal matrix} is a real square matrix whose columns and rows are orthogonal unit vectors. When matrix $Q$ is orthogonal, the given is true:
        $$
        Q\mathord{\cdot}Q^T = Q^T\mathord{\cdot}Q = I
        $$

        \item A \textbf{unit vector} is a vector of length 1 , and is denoted by circumflex or “hat”: $\hat{u}$
        \item \textbf{Orthogonal vectors} are two vectors $u$ and $v$ whose dot product- $$u\mathord{\cdot}v = 0$$
   \end{enumerate}

   \begin{enumerate}
       \item The answer is \textbf{c}.\\
        $ (B_{m \times m}\lambda I_{m \times m})^{T}C_{m \times n}$\\
        $= \lambda B_{m \times m}^{T}C_{m \times n}$ (Property used- $B^T=B$)\\
        $= \lambda B_{m \times m}C_{m \times n}I_{n \times n} $\\
        Matrix dimension - $m \times n$

        \item The answer is \textbf{a}.\\
        $ A^{-1}(CA^{-1})^{T}\lambda$\\
        $= A^{-1}\mathord{\cdot}(A^{-1})^{T}\mathord{\cdot}C^{T}\lambda$  (Property used- $AA^T=I$)\\
        $= IC^{T}\lambda$ \\
        $= \lambda C^{T}$ \\
        Matrix dimension - $n \times m$

        \item
        $ (A_{n \times n}A^{T}_{n \times n})B^{T}_{m \times m}C_{m \times n}\\$
        $= I_{n \times n}B_{m \times m}C_{m \times n}$\\
        There is an ambiguity in the expression above as $ AA^{T}$ results an identity matrix $\vect{I}$ of size $n \times n$. However, proceeding further we get $\vect{I}\vect{B}\vect{C}$ which is an undefined expression as size of $\vect{B}$ is $m \times m$ while the size of identity matrix $\vect{I}$ is $n \times n$.  \\
        Matrix dimension - undefined.
   \end{enumerate}

\end{solution}

\begin{exercise}[Vector norms][1]

Read the chapter \textit{2.5 Norms} of the \href{https://www.deeplearningbook.org/contents/linear_algebra.html}{DL book}.

Calculate $L^2$ and $L^1$ norms of the following vector: $\vect{a} = \begin{bmatrix} 2 \\ -3 \\ 1 \end{bmatrix}$.

\vspace{3em}

\textit{Bonus exercise (2 points)}: Draw all the vectors $\vect{x} \in \mathbb{R}^2$ for which
\begin{enumerate}
    \item $||\vect{x}||_1 = 1$;
    \item $||\vect{x}||_2 = 1$.
\end{enumerate}

\end{exercise}

\begin{solution}
   \color{blue}
    \begin{flalign*}
        L^{2}   & = {\left \| x \right \|}_{2} = (\sum_{i} \left | x_{i} \right |^{2})^{\frac{1}{2}}\\
                & = \sqrt{4+9+1} = \sqrt{14} = 3.741
    \end{flalign*}

    \begin{flalign*}
        L^{1}   & = {\left \| x \right \|}_{1} = \sum_{i} \left | x_{i} \right |\\
                & = 2+3+1 = 6
    \end{flalign*}
    \textbf{Bonus exercise}\\
    \\
    Let $\vect{x} = \begin{bmatrix} x_1 \\ x_2 \end{bmatrix}$ represent a 2-dimensional vector. Given that,
    \begin{flalign}
        ||\vect{x}||_1 = x_1 + x_2 = 1 \\
        ||\vect{x}||_2 = \sqrt{x_1 + x_2} = 1
    \end{flalign}
    Squaring equation 1 both sides-
    \begin{flalign*}
        &\implies (x_1 + x_2)^2 = 1 \\
        &\implies x_1^2 + x_2^2 + 2x_1x_2 = 1
    \end{flalign*}
    Squaring equation 2 both sides and replacing it in the above equation-
    \begin{flalign*}
    &\implies x_1^2 + x_2^2 = 1\\
    &\implies 1 + 2x_1x_2 = 1\\
    &\implies x_1x_2 = 0
    \end{flalign*}
    Therefore, all possible vectors are-
    $$\vect{x} = \begin{bmatrix} x_1 \\ 0 \end{bmatrix}, \vect{x} = \begin{bmatrix} 0 \\ x_2 \end{bmatrix}$$
    $$\vect{x} = \begin{bmatrix} x_1 & 0 \end{bmatrix}, \vect{x} = \begin{bmatrix} 0 & x_2 \end{bmatrix}$$

\end{solution}

\section*{Submission instructions}

\framebox{
	\begin{minipage}{\linewidth}
		The following instructions are mandatory. If you are not following them, tutors can
		decide to not correct your exercise.
	\end{minipage}
}

\begin{itemize}
    \item You have to submit the solutions of this assignment sheet as a team of 2-3 students.
    \item  Hand in a \textbf{single} PDF file with your solutions.
    \item Therefore Make sure to write the student ID and the name of each
    member of your team on your submission.
    \item Your assignment solution must be uploaded by only \textbf{one} of your team members to the course website.
    \item If you have any trouble with the submission, contact your tutor \textbf{before} the deadline.
\end{itemize}

\end{document}


\chapter{Background}
\section{Machine Learning}
Machine learning is a sub-field of AI, which studies the development of systems that learn from experience without being explicitly programmed. There are different machine learning approaches, including supervised, unsupervised, and reinforcement learning, which describe what information is provided to the system and the method by which it learns from that information. This section gives an overview of supervised and reinforcement learning, as well as the basics of neural networks and their interpretability limitations.

\subsection{Supervised Learning}
Supervised learning is the most popular and successful machine learning approach, in which an algorithm learns to solve a problem by being exposed to example solutions to that problem. Specifically, the algorithm is given a collection of inputs and corresponding outputs (labels). This collection is called training data and each input-output pair is called a training example. The algorithm, then, automatically infers a mapping between those inputs and outputs through a process called training, after which it is able to assign labels to inputs it has not seen before. Different algorithms use different training methods, and this section will focus on Neural Networks, one of the most popular class of such algorithms.

\subsection{Neural Networks}
A Neural Network (NN), is a ML algorithm used widely for supervised learning. This section provides an overview of the standard, feed-forward NN architecture, but note that there are various types of NNs with different architectures and methods of training.

\subsubsection{High-Level Representation}
Neural networks are commonly represented as a set of successive layers: one input layer, one or more hidden layers, and one output layer. Each of those layers consists of a set of nodes and each of those nodes is connected to every node of the successive layer. A graphical representation of this structure can be seen in figure \ref{fig:neural_network}. The nodes in the input layer of such a network represent the set of properties (features) that describe the object that the NN is meant to classify. For example, in the classic problem of recognising handwritten digits (see, for example, \cite{handwritten}) images of handwritten digits are 'shown' to the network, by assigning a numerical value to each pixel in an image and using those as features. Each node in the input layer, then, corresponds to one of those features. Every node in the input layer passes its value, along with a numerical weight that represents the significance of that value, to every node of the first hidden layer. Each node in a hidden layer represents a mathematical function, which performs an arithmetic operation on the inputs and weights it receives from the previous layer and produces an output, which it passes on to every node of the next layer, along with a new corresponding weight. This process repeats until the initial input values have propagated through and been manipulated by each of the hidden layers in succession. The values produced by the final hidden layer pass on to the output layer, which performs the final calculation before providing an output which represents the network prediction about the input it was shown. In the case of the handwritten digit recognition problem, there are 10 nodes in the output layer (one for each digit from 0-9), and the value of each node represents the network's confidence in the handwritten digit it was shown being the one represented by the corresponding node. The digit represented by the node with the highest value is chosen as the network's decision.

The goal of the training phase of a NN is to compute a set of weights such that the network makes as few wrong predictions as possible. The weights are initially set to arbitrary numerical values (usually random) and are slowly tuned to minimise the number of errors the NN makes. The result of this process is a set of optimal weights, which the NN can then use in the process described above to assign labels to inputs it has not seen before.

\begin{figure}[ht]
    \centering
    \includegraphics[width=12cm]{images/neural_network.png}
    \caption{Graphical representation of a feed-forward neural network with 3 hidden layers}
    \label{fig:neural_network}
\end{figure}

\subsection{Reinforcement Learning}
Reinforcement learning describes a process in which an algorithm (such as a neural network) plays the role of an agent that attempts to learn to operate in an environment by repeatedly interacting with it. The term "reinforcement" comes from the fact that the agent is provided with feedback from the environment, which comes in the form of a positive or negative reward depending on whether the agent is behaving in a way that brings it closer to achieving whatever goal the environment in question has set. The most intuitive example of such a setup is a video game, where the reward is the score the agent achieves by playing the game.

Similarly to supervised learning, the algorithm is provided with some input (the environment state) and learns to map it to some output (an action) such that some success metric (the reward) is optimised. However, in reinforcement learning, the algorithm is not provided with examples of correct input-output pairs. Instead, it has to actively interact with the environment to discover new inputs and learn to respond to them.

The process of reinforcement learning takes the form of an iterative experiment with some termination criteria. At the beginning of the experiment, the agent receives an encoded version of the initial state of the environment as input and outputs a random action, as it has no notion of which actions are going to yield maximum reward yet, which updates the state of the environment. Then, the environment provides the agent with a numerical reward (a positive or negative number representing reinforcement and punishment respectively) and the updated state. This process repeats until the agent achieves some predefined goal (e.g. wins the game) or one of the termination criteria is met (e.g. a timer runs out).

Like in supervised learning, neural networks are the most widely used algorithmic technique in reinforcement learning. In fact, the use of neural networks has given rise to a sub-field of reinforcement learning referred to as Deep Reinforcement Learning, which is characterised by the use of neural networks with multiple hidden layers.

\subsection{Uninterpretability of Machine Learning Models}
Since this project aims to propose a solution to the uninterpretability problem of current ML models, it is necessary to articulate clearly what uninterpretability means in this context and why these models suffer from it.

The simplest algorithm that is considered to belong to the set of ML models is linear regression. Linear regression is a statistical technique that attempts to infer a linear correlation between one or more variables. The result of applying this technique to a collection of concrete values for the variables in question is a linear model that "fits" the given data such that the error generated by predictions on the data using this model is minimal. The problem with this technique and, in fact, any statistical technique, is that it tells us nothing about the cause of the inferred relationship between the variables. That is to say, we know how the variables are correlated, but we don't know why that is, or what the exact relationship between them is. Take the popular problem of predicting house prices \cite{housing} as an example. Given a set of house characteristics along with the price each house was sold for, a model can be trained to predict the prices of houses it has not seen before. However, the model can tell us nothing about why, say, increasing the number of bedrooms tends to be correlated with an increase in price; it can merely tell us that, based on the available data, this seems to be the case.

Neural networks are essentially more complex versions of traditional statistical techniques. This added complexity only serves to compound the problem of uninterpretability by increasing the number of transformations the original data undergoes to produce predictions, as well as the number of parameters the model requires to tune to make those predictions more accurate. Put simply, the only information the model provides us with is a table of floating-point numbers and a series of algebraic calculations that produce the prediction. This makes it impossible for humans to understand the reasoning behind the "decisions" a neural network makes, since they always take the form of statistical predictions.

We might often be tempted to attribute reasoning to neural networks that perform high-level tasks, such as a neural network learning to play a video game via reinforcement learning, but this is an illusion: all the network does is receive encoded inputs, perform some calculations on them, and output the result, which the neural network predicts will produce an action in the video game that will bring it closer to the predefined "win" condition.

\section{Genetic Programming}
Genetic programming (GP) is a technique that borrows concepts from biological evolution to automatically generate programs that solve a specific problem. The algorithm generates an initial population of random programs and progressively modifies them until a program that can solve the problem in question emerges. GP takes the form of an iterative process in which the programs are repeatedly evaluated using a procedure (fitness function) to compute a measure of their performance. Then, a set of operations called genetic operations are performed on the population to produce a new generation of programs that are likely to perform better than their predecessors.

The characteristics of GP algorithms can be mapped to the components of a reinforcement learning agent to allow GP to be used on reinforcement learning tasks. Specifically, programs in a population can take the form of functions that take an environment state as input and produce an action as output. Subsequently, the reward provided by the environment can be used as the fitness measure for the programs, using which the algorithm can pick programs that perform well and discard the ones who don't. This mapping makes it possible to apply GP to reinforcement learning tasks.

\subsection{Program Structure}
In order to generate programs, the GP algorithm uses a domain-specific language (DSL) which is a language designed with the characteristics of the given problem in mind, as opposed to a general-purpose language which is designed to describe programs for any problem. The DSL consists of a function set and a terminal set. The function set contains the names of all the functions that can be used to construct programs and the terminal set contains variables and constants (terminals) that can be given as inputs to these functions.

In the case of a typed DSL, each function takes a fixed number of arguments, each of which has to be of a specific type (e.g. 'number' or 'string'). The amount of arguments a function takes is known as its arity. Therefore, when constructing programs using this language, the GP algorithm has to ensure that the number of terminals chosen as the arguments to a function matches its arity, and that the type of each terminal matches that of the corresponding argument. If a function produces an output, then that also has a type, which must be checked when a function is passed as an argument to another function. If the DSL is untyped, any function can take any terminal or other function as an argument without any restrictions.

\subsection{Tree-Based Program Representation}
Programs generated by a GP algorithm can be represented as trees. The root (top) node of the tree represents a function, and each of its branches represents an argument to that function. If a branch leads to a node with more branches (sub-tree), then this is also a function. Eventually, every sub-tree has to reach a node that has no branches, called a leaf, and each leaf represents a terminal (e.g. a number) that is passed as an argument to the function represented by the root of the sub-tree that leaf is connected to. For example, figure \ref{fig:example_program_tree} shows a visual representation of a program that consists of a function, \verb+IFLTE+ (if-less-than-or-equal), which takes four arguments: three terminals (\verb+pa+, \verb+0+, and \verb+R+) and another \verb+IFLTE+ function which takes four terminals (\verb+pv+, \verb+0+, \verb+L+, and \verb+R+) as its arguments.

\begin{figure}[ht]
    \centering
    \includegraphics[width=10cm]{images/complex_iflte_program.png}
    \caption{Tree representation of an example program produced by a GP algorithm.}
    \label{fig:example_program_tree}
\end{figure}

\subsection{Genetic Operators}
Genetic operators are algorithms that take one or more programs from a population as input and generate a new program as output. The three main types of genetic operators are selection, mutation and crossover. Selection is the simplest of the three and is commonly used to pick programs from the population to be used by the other two operators. There are many different selection algorithms, two of the most popular being fitness proportionate selection (aka roulette wheel selection) and tournament selection. Fitness proportionate selection uses a pseudorandom number generator to select programs from a population such that the higher the fitness of a program, the higher the probability that program will be chosen. Tournament selection randomly selects a subset of the programs in a population (the size of the subset is called tournament size) and picks the program with the highest fitness; the higher a program's fitness, the higher its probability of "winning" any of the tournaments it's placed in. Tournament selection provides more flexibility than fitness proportionate selection because it's possible to adjust the probability of weak individuals being selected (selection pressure) by increasing or decreasing the tournament size. Decreasing the selection pressure can be useful to avoid early convergence, i.e. the fittest programs of the first few generations dominating the population and not allowing better programs to emerge.

The mutation operator randomly selects a proportion of the programs in the population, and changes one of the program's sub-trees (in the case of tree-based representation) with a randomly generated sub-tree. The exact value of the proportion of the population that is selected for mutation is called the mutation rate of the algorithm.

Finally, crossover attempts to simulate a simple version of biological sexual reproduction. It selects two programs (parents) from the population using the selection operator, randomly picks a node on each of them (crossover point) and then combines the sub-trees that have the chosen node as their root to create a new program (offspring). The proportion of programs generated using crossover is called the crossover rate. Crossover was not used in this project for the sake of simplicity, as it did not prove necessary for the generation of programs that served as good solutions to the problems tackled.

\section{Related Work}
The problem of the uninterpretability of ML models has been known for a long time and a lot of research has been done to address the issue. Additionally, Genetic Programming has been a useful optimisation technique since its establishment with papers such as \cite{koza}, from which this project borrowed the idea for the \verb+IFLTE+ function, which served as the primary building block for the solutions produced and discussed in this report. Recently, there have been quite a few attempts to integrate Genetic Algorithms (of which GP algorithms are a subset) into the ML workflow, as seen for example in \cite{relatedwork1}, \cite{relatedwork2} and \cite{relatedwork3}, as well as attempts to use GP (e.g. \cite{gprl}, \cite{pirl}, \cite{atari}) to make solutions to ML problems more interpretable. To my knowledge, however, all of these approaches either use neural networks in some fashion, thus inevitably trading interpretability for performance, or use GP as a black-box algorithm whose solutions are no more interpretable than those produced by a neural network.

In this project, GP was used directly to attempt to solve problems that are traditionally solved using black-box ML models. The solutions produced take the form of human-readable programs that use familiar logical structures and high-level representations of the problem parameters to make them interpretable. The hope is that this will allow us to reason about solutions to these problems, instead of merely relying on the robustness of the black-box models that produce them.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% UMB-CS114-2015F: Introduction to Programming in Java
% Copyright 2015 Pejman Ghorbanzade <pejman@ghorbanzade.com>
% Creative Commons Attribution-ShareAlike 4.0 International License
% More info: https://github.com/ghorbanzade/UMB-CS114-2015F
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\def \topDirectory {.}
\def \texDirectory {\topDirectory/src/main/tex}

\documentclass[12pt,letterpaper,twoside]{article}
\usepackage{\texDirectory/template/style/directives}
\usepackage{\texDirectory/template/style/assignment}
\input{\texDirectory/template/config}

\begin{document}

\doc{title}{Solution to Midterm Practice Exam}
\doc{points}{20}

\prepare{header}

\section*{Question 1}

The following programs have each a \textbf{single} compilation error given below. You are expected to find and fix the error so that given command-line arguments will lead to expected output as indicated.

\begin{enumerate}[label=\textbf{(\alph*)}]

\item Execution Script \hfill Expected Output\\
\texttt{java HelloWorld} \hfill \texttt{Hello World!}
\begin{lstlisting}
public class HelloWorld {
	public static void main(String[] args) {
		System.println("Hello World!");
	}
}
\end{lstlisting}

\begin{terminal}
HelloWorld.java:3: error: cannot find symbol
		System.println("Hello World!");
1 error
\end{terminal}

\newpage

\item Execution Script \hfill Expected Output\\
\texttt{java Quadratic 1 -2 -4} \hfill \texttt{-1.236 and 3.237}
\begin{lstlisting}
public class Quadratic {
	public static void main(String[] args) {
		int a = Integer.parseInt(args[0]);
		int b = Integer.parseInt(args[1]);
		int c = Integer.parseInt(args[2]);
		double discriminant = b*b - 4*a*c;
		if (double discriminant > 0) {
			double sol1 = (- b - Math.sqrt(discriminant))/(2*a);
			double sol2 = (- b + Math.sqrt(discriminant))/(2*a);
			System.out.printf("%.3f and %.3f\n", sol1, sol2);
		}
	}
}
\end{lstlisting}

\begin{terminal}
Quadratic.java:7: error: '.class' expected
		if (double discriminant > 0) {
Quadratic.java:7: error: illegal start of expression
		if (double discriminant > 0) {
Quadratic.java:7: error: ';' expected
		if (double discriminant > 0) {
Quadratic.java:7: error: illegal start of expression
		if (double discriminant > 0) {
Quadratic.java:7: error: ';' expected
		if (double discriminant > 0) {
Quadratic.java:13: error: class, interface, or enum expected
}
6 errors
\end{terminal}

\newpage

\item Execution Script \hfill Expected Output\\
\texttt{java TwoCities Texas Boston} \hfill \texttt{Boston, Texas}
\begin{lstlisting}
public class TwoCities {
	public static void main(String[] args) {
		if (args[0].compareTo(args[1]) < 0) {
			System.out.println(args[0] + ", " + args[1]);
		else
			System.out.println(args[1] + ", " + args[0]);
		}
	}
}
\end{lstlisting}

\begin{terminal}
TwoCities.java:5: error: 'else' without 'if'
	else
1 error
\end{terminal}

\item Execution Script \hfill Expected Output\\
\texttt{java ShowNum 5} \hfill \texttt{1, 2, 3, 4, 5, }

\begin{lstlisting}
public class ShowNum {
	public static void main(String[] args) {
		int limit = Integer.parseInt(args[0]);
		for (i < limit; i++) {
			System.out.printf("%d, ", i);
		}
	}
}
\end{lstlisting}

\begin{terminal}
ShowNum.java:4: error: > expected
		for (i < limit; i++) {
ShowNum.java:4: error: not a statement
		for (i < limit; i++) {
ShowNum.java:4: error: ';' expected
		for (i < limit; i++) {
3 errors
\end{terminal}

\end{enumerate}

\newpage

\subsection*{Solution}

\begin{enumerate}[label=\textbf{(\alph*)}]

\item Execution Script \hfill Expected Output\\
\texttt{java HelloWorld} \hfill \texttt{Hello World!}

\lstset{language=Java,tabsize=2}
\begin{lstlisting}
public class HelloWorld {
	public static void main(String[] args) {
		System.out.println("Hello World!");
	}
}
\end{lstlisting}

\item Execution Script \hfill Expected Output\\
\texttt{java Quadratic 1 -2 -4} \hfill \texttt{-1.236 and 3.237}

\lstset{language=Java,tabsize=2}
\begin{lstlisting}
public class Quadratic {
	public static void main(String[] args) {
		int a = Integer.parseInt(args[0]);
		int b = Integer.parseInt(args[1]);
		int c = Integer.parseInt(args[2]);
		double discriminant = b*b - 4*a*c;
		if (discriminant > 0) {
			double sol1 = (- b - Math.sqrt(discriminant))/(2*a);
			double sol2 = (- b + Math.sqrt(discriminant))/(2*a);
			System.out.printf("%.3f and %.3f\n", sol1, sol2);
		}
	}
}
\end{lstlisting}

\item Execution Script \hfill Expected Output\\
\texttt{java TwoCities Texas Boston} \hfill \texttt{Boston, Texas}

\lstset{language=Java,tabsize=2}
\begin{lstlisting}
public class TwoCities {
	public static void main(String[] args) {
		if (args[0].compareTo(args[1]) < 0)
			System.out.println(args[0] + ", " + args[1]);
		else
			System.out.println(args[1] + ", " + args[0]);
	}
}
\end{lstlisting}

\newpage

\item Execution Script \hfill Expected Output\\
\texttt{java ShowNum 5} \hfill \texttt{1, 2, 3, 4, 5, }

\lstset{language=Java,tabsize=2}
\begin{lstlisting}
public class ShowNum {
	public static void main(String[] args) {
		int limit = Integer.parseInt(args[0]);
		for (int i = 0; i < limit; i++) {
			System.out.printf("%d, ", i);
		}
	}
}
\end{lstlisting}

\end{enumerate}

\section*{Question 2}

The following program is written to take $n$ as a command line argument and print the first $n$ prime numbers.
The program has no compilation error and functions as expected.
A Java programmer, however, argues that it is badly written and that it has major design problem.
Read the code snippet, explain how it works and why it is deemed as \textbf{inefficient}.
Modify the program to resolve the argued problem.

\begin{lstlisting}
public class NPrimes {
	public static void main(String[] args) {
		int count = Integer.parseInt(args[0]);
		int number = 2;
		for (int i = 0; i < count; i++) {
			boolean isPrime = false;
			do {
				boolean flag = true;
				for (int j = 2; j < number; j++) {
					if (number % j == 0)
						flag = false;
				}
				if (flag) {
					System.out.println(number);
					isPrime = true;
				}
				number++;
			}
			while (isPrime == false);
		}
	}
}
\end{lstlisting}

\newpage

\subsection*{Solution}

The program takes a command line argument and parses it into an integer.
A \texttt{for} loop is used whose each iteration prints a single prime number.
A nested \texttt{do-while} loop iterates over numbers and checks whether they are prime.
The solution proposed is complicated, hard-to-follow and inefficient.
The program is slow because the task to check if a number $x$ is prime or not requires $x - 2$ iterations.
The following program slightly modifies \texttt{NPrimes.java} to exit the inner \texttt{for} loop, the first time variable \texttt{flag} is assigned \texttt{false}.
For better code readability, the \texttt{do-while} loop is changed to a \texttt{while} loop.

\lstset{language=Java,tabsize=4}
\begin{lstlisting}
public class NPrimes {
	public static void main(String[] args) {
		int count = Integer.parseInt(args[0]);
		int number = 1;
		for (int i = 0; i < count; i++) {
			while (true) {
				number++;
				boolean isPrime = true;
				for (int j = 2; j < number; j++) {
					if (number % j == 0) {
						isPrime = false;
						break;
					}
				}
				if (isPrime) {
					break;
				}
			}
			System.out.println(number);
		}
	}
}
\end{lstlisting}

\newpage

\section*{Question 3}

A palindrome is a sequence of characters which reads the same backward or forward.
Write a program \texttt{Palindrome.java} that prompts user for a phrase and checks if the phrase is a palindrome or not.
For simplicity, a palindrome is both case- and whitespace-sensitive.
Following are two expected sample runs of your program.

\begin{terminal}
$ javac Palindrome.java
$ java Palindrome
Enter a String? hello olleh
Your String is a Palindrome.
$ java Palindrome
Enter a String? radar
Your String is a Palindrome.
$ java Palindrome
Enter a String? race car
Your String is not a Palindrome.
\end{terminal}

\subsection*{Solution}
\lstset{language=Java,tabsize=2}
\begin{lstlisting}
import java.util.Scanner;
public class Palindrome {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		String phrase = input.nextLine();
		int limit = (int) (phrase.length() / 2);
		boolean isPalindrome = true;
		for (int i = 0; i < limit; i++) {
			if (phrase.charAt(i) != phrase.charAt(phrase.length() - i - 1)) {
				isPalindrome = false;
				break;
			}
		}
		if (isPalindrome)
			System.out.println("Your String is a Palindrome.");
		else
			System.out.println("Your String is not a Palindrome.");
	}
}
\end{lstlisting}

\end{document}