{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Loss Function\n",
    "\n",
    "Loss functions evaluate how well specific algorithm models the given data. Commonly loss functions are used to compare the target data and model's prediction. If predictions deviate too much from actual targets, loss function would output a large value. Usually, loss functions can help other optimization functions to improve the accuracy of the model.\n",
    "\n",
    "However, there’s no one-size-fits-all loss function to algorithms in machine learning. For each algorithm and machine learning projects, specifying certain loss functions could assist the user in getting better model performance. Here we will demonstrate two loss functions: `mse_loss` and `cross_entropy_loss`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Min Square Error\n",
    "\n",
    "Min square error(MSE) is the most commonly used loss function in machine learning. The intuition of MSE is straight forward: the distance between two points represents the difference between them. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$MSE = -\\sum_i{(y_i-t_i)^2/n}$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Where $y_i$ is the prediction of the ith example and $t_i$ is the target of the ith example. And n is the total number of examples.\n",
    "\n",
    "Below is a plot of an MSE function where the true target value is 100, and the predicted values range between -10,000 to 10,000. The MSE loss (Y-axis) reaches its minimum value at prediction (X-axis) = 100."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"images/mse_plot.png\" width=\"500\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cross-Entropy\n",
    "\n",
    "For most deep learning applications, we can get away with just one loss function: cross-entropy loss function. We can think of most deep learning algorithms as learning probability distributions and what we are learning is a distribution of predictions $P(y|x)$ given a series of inputs. \n",
    "\n",
    "To associate input examples x with output examples y, the parameters that maximize the likelihood of the training set should be:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\\theta^* = argmax_\\theta \\prod_{i=0}^n p(y^{(i)}/x^{(i)})$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Maxmizing the above formula equals to minimizing the negative log form of it:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\\theta^* = argmin_\\theta -\\sum_{i=0}^n logp(y^{(i)}/x^{(i)})$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It can be proven that the above formula equals to minimizing MSE loss.\n",
    "\n",
    "The majority of deep learning algorithms use cross-entropy in some way. Classiﬁers that use deep learning calculate the cross-entropy between categorical distributions over the output class. For a given class, its contribution to the loss is dependent on its probability in the following trend:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"images/corss_entropy_plot.png\" width=\"500\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Examples\n",
    "\n",
    "First let's import necessary packages."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Using TensorFlow backend.\n"
     ]
    }
   ],
   "source": [
    "import os, sys\n",
    "sys.path = [os.path.abspath(\"../../\")] + sys.path\n",
    "from deep_learning4e import *\n",
    "from notebook4e import *"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Neural Network Layers\n",
    "\n",
    "Neural networks may be conveniently described using data structures of computational graphs. A computational graph is a directed graph describing how many variables should be computed, with each variable by computed by applying a speciﬁc operation to a set of other variables. \n",
    "\n",
    "In our code, we provide class `NNUnit` as the basic structure of a neural network. The structure of `NNUnit` is simple, it only stores the following information:\n",
    "\n",
    "- **val**: the value of the current node.\n",
    "- **parent**: parents of the current node.\n",
    "- **weights**: weights between parent nodes and current node. It should be in the same size as parents.\n",
    "\n",
    "There is another class `Layer` inheriting from `NNUnit`. A `Layer` object holds a list of nodes that represents all the nodes in a layer. It also has a method `forward` to pass a value through the current layer. Here we will demonstrate several pre-defined types of layers in a Neural Network."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Output Layers\n",
    "\n",
    "Neural networks need specialized output layers for each type of data we might ask them to produce. For many problems, we need to model discrete variables that have k distinct values instead of just binary variables. For example, models of natural language may predict a single word from among of vocabulary of tens of thousands or even more choices. To represent these distributions, we use a softmax layer:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$P(y=i|x)=softmax(h(x)^TW+b)_i$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "where $W$ is matrix of learned weights of output layer $b$ is a vector of learned biases, and the softmax function is:\n",
    "\n",
    "$$softmax(z_i)=exp(z_i)/\\sum_i exp(z_i)$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It is simple to create a output layer and feed an example into it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.03205860328008499, 0.08714431874203257, 0.23688281808991013, 0.6439142598879722]\n"
     ]
    }
   ],
   "source": [
    "layer = OutputLayer(size=4)\n",
    "example = [1,2,3,4]\n",
    "print(layer.forward(example))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The output can be treated like normalized probability when the input of output layer is calculated by probability."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Input Layers\n",
    "\n",
    "Input layers can be treated like a mapping layer that maps each element of the input vector to each input layer node. The input layer acts as a storage of input vector information which can be used when doing forward propagation.\n",
    "\n",
    "In our realization of input layers, the size of the input vector and input layer should match."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1, 2, 3]\n"
     ]
    }
   ],
   "source": [
    "layer = InputLayer(size=3)\n",
    "example = [1,2,3]\n",
    "print(layer.forward(example))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Hidden Layers\n",
    "\n",
    "While processing an input vector x of the neural network, it performs several intermediate computations before producing the output y. We can think of these intermediate computations as the state of memory during the execution of a multi-step program. We call the intermediate computations hidden because the data does not specify the values of these variables.\n",
    "\n",
    "Most neural network hidden layers are based on a linear transformation followed by the application of an elementwise nonlinear function called the activation function g:\n",
    "\n",
    "$$h=g(W+b)$$\n",
    "\n",
    "where W is a learned matrix of weights and b is a learned set of bias parameters.\n",
    "\n",
    "Here we pre-defined several activation functions in `utils.py`: `sigmoid`, `relu`, `elu`, `tanh` and `leaky_relu`. They are all inherited from the `Activation` class. You can get the value of the function or its derivative at a certain point of x:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sigmoid at 0: 0.5\n",
      "Deriavation of sigmoid at 0: 0\n"
     ]
    }
   ],
   "source": [
    "s = sigmoid()\n",
    "print(\"Sigmoid at 0:\", s.f(0))\n",
    "print(\"Deriavation of sigmoid at 0:\", s.derivative(0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To create a hidden layer object, there are several attributes need to be specified:\n",
    "\n",
    "- **in_size**: the input vector size of each hidden layer node.\n",
    "- **out_size**: the size of the output vector of the hidden layer. Thus each node will hide the weight of the size of (in_size). The weights will be initialized randomly.\n",
    "- **activation**: the activation function used for this layer.\n",
    "\n",
    "Now let's demonstrate how a dense hidden layer works briefly:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.21990266877137224, 0.2038864498984756, 0.5543443697256466]\n"
     ]
    }
   ],
   "source": [
    "layer = DenseLayer(in_size=4, out_size=3, activation=sigmoid())\n",
    "example = [1,2,3,4]\n",
    "print(layer.forward(example))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This layer mapped input of size 4 to output of size 3. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Convolutional Layers\n",
    "\n",
    "The convolutional layer is similar to the hidden layer except they use a different forward strategy. The convolutional layer takes an input of multiple channels and does convolution on each channel with a pre-defined kernel function. Thus the output of the convolutional layer will still be with the same number of channels. If we image each input as an image, then channels represent its color model such as RGB. The output will still have the same color model as the input.\n",
    "\n",
    "Now let's try the one-dimensional convolution layer:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[array([3.9894228, 3.9894228, 3.9894228]), array([3.9894228, 3.9894228, 3.9894228]), array([3.9894228, 3.9894228, 3.9894228])]\n"
     ]
    }
   ],
   "source": [
    "layer = ConvLayer1D(size=3, kernel_size=3)\n",
    "example = [[1]*3 for _ in range(3)]\n",
    "print(layer.forward(example))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Which can be deemed as a one-dimensional image with three channels."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Pooling Layers\n",
    "\n",
    "Pooling layers can be treated as a special kind of convolutional layer that uses a special kind of kernel to extract a certain value in the kernel region. Here we use max-pooling to report the maximum value in each group."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[3, 4], [4, 4], [4, 4]]\n"
     ]
    }
   ],
   "source": [
    "layer = MaxPoolingLayer1D(size=3, kernel_size=3)\n",
    "example = [[1,2,3,4], [2,3,4,1],[3,4,1,2]]\n",
    "print(layer.forward(example))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see that each time kernel picks up the maximum value in its region."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
