{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[Table of Contents](table_of_contents.ipynb)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Topic 1. Vector Spaces\n",
    "Author: Jonathan Skaggs\n",
    "\n",
    "Email:  jbskaggs12@gmail.com\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Introduction\n",
    "Give a high level (i.e., no equations) explanation of the topic, and why it is important."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vector spaces are widely used in electical engineering, computer science, physics, chemistry, and mathematics.  They are a fundamental concept in abstract algebra and are consequently used all over the place.  Defining a vector space involves defining a mathematical system withs two operators, an additive operation and scalar multiplication operation usually denoted + and \\*.  Vector spaces can be defined over discrete-time signals (things like $\\mathbb{R}^n$, $\\mathbb{C}^n$) but they can also be defined over continuous-time signals (things like $P_n$, the set of all polynomials of degree n or less).  Once a vector space is defined then it satisfies certain mathematical properties and any transformation in that vector space can be guaranteed to follow the properties of vector spaces.\n",
    "\n",
    "There are some special vector spaces that always seem to come up so they have special names.  A couple examples of these special vector spaces are Hilbert and Banach Spaces.  (These special spaces are covered in more detail in later topics)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Explanation of the theory"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following is the definition of a vector space where $x$ and $y$ are elements of a vector space $V$ where 0 is the 0 vector; and c and d are arbitrary scalar values:\n",
    "\n",
    " #### Vector Space:\n",
    "\n",
    "1. $\\forall{x, y}$: $x+y = y+x \\in{V}$\n",
    "2. $\\forall{x, y, z}$: $(x+y)+z = x+(y+z) \\in{V}$\n",
    "3. $\\forall{x}$: $0+x = x+0 = x$\n",
    "4. $\\forall{x, y}$: $(-x) + x = x + (-x) = 0$\n",
    "5. $\\forall{x}$: $0x = 0$\n",
    "6. $\\forall{x}$: $1x = x$\n",
    "7. $\\forall{x, c, d}$: $(cd)x = c(dx) \\in{V}$\n",
    "8. $\\forall{x, y, c, d}$: $c(x+y) = cx + cy \\in{V}$\n",
    "9. $\\forall{x, c, d}$: $(c+d)x = cx +dx \\in{V}$\n",
    "\n",
    "When any two operators that can be defined as + and * that satisfy the above constraints is a vector space."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Axiom 1-4 describes how a vector space must be closed under addition.  In order to be closed under addition, any arbitrary elements from a vector space must be commutative, associative, contain an additive identity (the 0 vector), and contain an additive inverse.  Both commutative and associative are commonly understood properties.  An additive identity simply means that there exists an element that when operating on two elements always results in the other element.  An additive inverse means that for every element there exists another element that added together is the 0 vector.\n",
    "\n",
    "Axioms 5-7 state that a vector space is closed under scalar multiplication. To be closed under scalar multiplication means that there is a 0 vector, a multiplicative identity, and scalar multiplication is associative.  Containing a 0 vector simply means that there is an element where any scalar times this element is the itself the 0 vector.  A multiplicative identity means that there exists a scalar where any element times this scalar is the same element.  Finally scalar multiplications being associative means that it is associative.\n",
    "\n",
    "Axioms 8-9 state that the distributive property holds.  In addition, it might be helpful to note that x, y and z must exist in the vector space so any amount of adding or scaling must result in a vector that is also in the vector space."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## Simple Numerical Examples"
   ]
  },
  {
   "cell_type": "markdown",
   "source": [
    "The first vector space we are going to show is the real numbers $\\mathbb{R}$.  In order to do this we need to prove all 9 axioms above.  Rather than prove we will check numerically that the above statment is true."
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "# we will use this definition for equivilance to deal with floating point calculation errors\n",
    "def equal(x, y):\n",
    "    return x - y > -0.00000001 and x - y < 0.00000001\n",
    "\n",
    "def print_result(a, b, c, d):\n",
    "    print(a, b, c, d)\n",
    "    print()\n",
    "\n",
    "\n",
    "print(\"We will show below that each of the conditions are met for real numbers\")\n",
    "# first we initialize three random variables in R\n",
    "x = np.random.sample(1)[0] * 100\n",
    "y = np.random.sample(1)[0] * 100\n",
    "z = np.random.sample(1)[0] * 100\n",
    "# now we need to initialize a few random scalars\n",
    "c = np.random.sample(1)[0] * 100\n",
    "d = np.random.sample(1)[0] * 100\n",
    "print(\"x = \", x)\n",
    "print(\"y = \", y)\n",
    "print(\"z = \", z)\n",
    "print(\"c = \", c)\n",
    "print(\"d = \", d)\n",
    "print()\n",
    "\n",
    "def check_if_is_valid_vector_space(x, y, z, c, d):\n",
    "    # 1. $\\forall{x, y}$: $x+y = y+x$\n",
    "    print_result(\"1. x + y = y + x =\", x + y, \":\", equal(x + y, y + x))\n",
    "\n",
    "    # 2. $\\forall{x, y, z}$: $(x+y)+z = x+(y+z)$\n",
    "    print_result(\"2. (x + y) + z = y + (x + z) =\",(x + y) + z, \":\", equal((x + y) + z, x + (y + z)))\n",
    "\n",
    "    # 3. $\\forall{x, y}$: $0+x = x+0 = x$\n",
    "    print_result(\"3. 0 + x = x =\", x + 0, \":\", equal(x + 0, x) )\n",
    "\n",
    "    # 4. $\\forall{x, y}$: $(-x) + x = x + (-x) = 0$\n",
    "    print_result(\"4. x + -x = 0 =\", x + -x, \":\", equal(x + -x, 0) )\n",
    "\n",
    "    # 5. $\\forall{x}$: $0x = 0$\n",
    "    print_result(\"5. x * 0 = 0 =\", x * 0, \":\", equal(x * 0, 0) )\n",
    "\n",
    "    # 6. $\\forall{x}$: $1x = x$\n",
    "    print_result(\"6. x * 1 = x =\", x * 1, \":\", equal(x * 1, x) )\n",
    "\n",
    "    # 7. $\\forall{x, c, d}$: $(cd)x = c(dx)$\n",
    "    print_result(\"7. (cd)x = c(dx) =\", (c * d) * x, \":\", equal((c * d) * x, c * (d * x)) )\n",
    "\n",
    "    # 8. $\\forall{x, y, c, d}$: $c(x+y) = cx + cy$\n",
    "    print_result(\"8. c(x + y) = cx + cy =\", c * (x  + y), \":\", equal(c * (x  + y), (c * x) + (c * y)) )\n",
    "\n",
    "    # 9. $\\forall{x, c, d}$: $(c+d)x = cx +dx$\n",
    "    print_result(\"9. (c + d)x = cx + dx =\", (c + d) * x, \":\", equal((c + d) * x, (c * x) + (d * x)) )\n",
    "\n",
    "check_if_is_valid_vector_space(x, y, z, c, d)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "We have shown that $\\mathbb{R}$ can be defined as a vector space.  A vector space can be defined over any $\\mathbb{R}^n$.  As an example we will show that $\\mathbb{R}^{10}$ can be defined as a vector space."
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "# we will use this definition for equivilance to deal with floating point calculation errors\n",
    "def equal(x, y):\n",
    "    return np.sum(x - y) > -0.00000001 and np.sum(x - y) < 0.00000001\n",
    "\n",
    "print(\"We will show below that each of the conditions are met for numbers in R^n\")\n",
    "# first we initialize three random variables in R^n\n",
    "n = 10\n",
    "x = np.random.sample(n) * 100\n",
    "y = np.random.sample(n) * 100\n",
    "z = np.random.sample(n) * 100\n",
    "# now we need to initialize a few random scalars\n",
    "c = np.random.sample(1)[0] * 100\n",
    "d = np.random.sample(1)[0] * 100\n",
    "print(\"x = \", x)\n",
    "print(\"y = \", y)\n",
    "print(\"z = \", z)\n",
    "print(\"c = \", c)\n",
    "print(\"d = \", d)\n",
    "print()\n",
    "\n",
    "check_if_is_valid_vector_space(x, y, z, c, d)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Hopefully now, you can see how this could be applied to any $\\mathbb{R}^n$.  A simple extension would be to define a vector space over $\\mathbb{C}^n$.  We can even define over a set of functions.  Below we define $P_3$ as a vector space.  $P_3$ is all polynomials of degree 3 or less."
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "n = 3\n",
    "p1 = np.random.sample(n + 1) * 100\n",
    "p2 = np.random.sample(n + 1) * 100\n",
    "p3 = np.random.sample(n + 1) * 100\n",
    "c = np.random.sample(1)[0] * 100\n",
    "d = np.random.sample(1)[0] * 100\n",
    "\n",
    "# here we define how to print a polynomial\n",
    "def polynomial_to_string(p1):\n",
    "    m = p1.shape[0]\n",
    "    string = ''\n",
    "    for i in range(m - 1, 0, -1):\n",
    "        string += str(p1[i]) + \"v^\" + str(i) + \" + \"\n",
    "    string += str(p1[0])\n",
    "    return string\n",
    "\n",
    "# now we will want to print out our nice polynomial\n",
    "def print_result(a, b, c, d):\n",
    "    print(a, polynomial_to_string(b), c, d)\n",
    "    print()\n",
    "\n",
    "# And finally we check to make sure that the polynomials are valid\n",
    "check_if_is_valid_vector_space(p1, p2, p3, c, d)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## An Engineering Application\n",
    "Provide a more sophisticated example showing one engineering example of the topic, complete with python code."
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Neural networks are powerful examples that often use vector representations.  One of the first examples of neural networks to come out of research is called a Perceptron.  If you are unfamiliar a perceptron is a single layer neural network that is used in one famous example is using the perceptron to identify the type of flower from a dataset called the Iris dataset.  Iris dataset contains 3 types of flowers: Iris-setosa, Iris-versicolor, and Iris-virginica.  We will ignore Iris-virginica in order to simplify the problem.  Each instance of a flower contains certain measurements: sepal length, sepal width, petal length, and petal width.  All measurements are in cm."
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def load_data():\n",
    "    URL_='https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'\n",
    "    data = pd.read_csv(URL_, header = None)\n",
    "\n",
    "    data = data[:100]\n",
    "    print(data)\n",
    "    data[4] = np.where(data.iloc[:, -1]=='Iris-setosa', 0, 1)\n",
    "    return np.array(data)\n",
    "\n",
    "data = load_data()\n"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "The task in this problem is to try to identify the type of flower from the features.  In order to do this we will represent each feature of flowers as a dimension in our Iris flower vector space.  In machine learning this technique of creating a vector space to represent a list of attributes of an object for identification is also called a feature space.  For the iris dataset we will end up with a vector space defined in $\\mathbb{R}^4$.  The first flower would be represented in 4 dimensional space as the vector, x, \\[5.1,  3.5,  1.4, 0.2\\]$^T$. We want to define the problem so that we input x and we get out the class, y, or in this case Iris-setosa.  We will represent Iris-setosa as class 0, Iris-versicolor as class 1."
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Below is an implemented perceptron.  For anyone unfamiliar I tried to comment my code a lot so that it is easy to understand.  First during training the model will run through each example and if it is wrong it will take a step in the right direction (using gradient descent) so that it will be right next time.  Each run through the training data is called an epoch (pronounced like epic).  It is important to run enough epochs so that the data converges. If you have a hard time understanding what is going on in the Perceptron Class try looking at the next segment first."
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "# Throughout this code the following symbols are used a is the learning rate,\n",
    "# x is the feature vector, y is the class label, w is a weight vector\n",
    "class Perceptron():\n",
    "    ys = []\n",
    "\n",
    "    def __init__(self):\n",
    "        self.w = []\n",
    "        self.a = .1\n",
    "        self.num_epochs = 1000\n",
    "        pass\n",
    "\n",
    "    # calculates the accuracy of the machine learning model\n",
    "    def measure_accuracy(self, xs, ys):\n",
    "        correct_count = 0\n",
    "        prediction = []\n",
    "        print(\"Test Set Results\")\n",
    "        for i in range(np.size(xs, axis=0)):\n",
    "            x = xs[i]\n",
    "            y = int(ys[i])\n",
    "            self.predict(x, prediction)\n",
    "            pred = int(prediction[0])\n",
    "            if pred == y:\n",
    "                correct_count += 1\n",
    "            print(\"  Test Sample\", i + 1)\n",
    "            print(\"  Input:\", xs[i])\n",
    "            print(\"  Predicted Class:\", pred)\n",
    "            print(\"  True Class:\", int(ys[i]))\n",
    "            print()\n",
    "        return correct_count / np.size(xs, axis=0)\n",
    "\n",
    "    # does the prediction at test time\n",
    "    def test(self, x, w):\n",
    "        n = np.dot(x, w)\n",
    "        z = 1 if n > 0 else 0\n",
    "        return z\n",
    "\n",
    "    # calculates the gradient and the change is w\n",
    "    def delta_w(self, a, x, y, w):\n",
    "        n = np.dot(x, w)\n",
    "        z = 1 if n > 0 else 0\n",
    "        delta = (a * (y - z)) * x\n",
    "        return w + delta\n",
    "\n",
    "    # one run through the training set is also called one epoch\n",
    "    def epoch(self, a, x, y, w, N):\n",
    "        for i in range(N):\n",
    "            w = self.delta_w(a, x[i], y[i], w)\n",
    "        return w\n",
    "\n",
    "    # trains the model\n",
    "    def train(self, x, y):\n",
    "        self.w = np.random.randn(np.size(x, axis=1) + 1)\n",
    "        x = np.squeeze(np.asarray(x))\n",
    "        x = np.hstack((x, np.ones((np.size(x, axis=0), 1))))\n",
    "        for _ in range(self.num_epochs):\n",
    "            self.w = self.epoch(self.a, x, np.squeeze(y), self.w, np.size(x, axis=0))\n",
    "        return self.w\n",
    "\n",
    "    # a wrapper function for test (it predicts the output at test time)\n",
    "    def predict(self, x, y):\n",
    "        x = np.append(x, 1)\n",
    "        del y[:]\n",
    "        y += [(self.test(x, self.w))]\n",
    "        return y\n"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Now that we have defined our model we need to shuffle the data, split it into training and testing sets, train our model, and finally test out model."
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "# load the model\n",
    "learner = Perceptron()\n",
    "\n",
    "# shufle the data\n",
    "np.random.shuffle(data)\n",
    "\n",
    "# we will split our data into training and test sets\n",
    "num_trn_examples = 80 # this means there are 20 tst examples b/c there are 100 total examples\n",
    "trn_data = data[:num_trn_examples]\n",
    "tst_data = data[num_trn_examples:]\n",
    "\n",
    "# split the input from the class labels\n",
    "trn_x = trn_data[:, :-1]\n",
    "trn_y = trn_data[:, -1]\n",
    "tst_x = tst_data[:, :-1]\n",
    "tst_y = tst_data[:, -1]\n",
    "\n",
    "# train the perceptron\n",
    "learner.train(trn_x, trn_y)\n",
    "\n",
    "# test our model\n",
    "accuracy = learner.measure_accuracy(tst_x, tst_y)\n",
    "print(\"Test Set Accuracy\", str(int(accuracy * 100)) + \"%\")\n"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "We were able to model the different Iris flowers according to a vector space model and consequently we were able to identify the type of Iris flower based on several sepal/petal measurements. YAY!  In machine learning there are many sophisticated examples of when modeling things using vector spaces is very useful. Hopefully now you can see other instances where vector spaces would also be useful."
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Challenge Problem"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Write to numerically verify that the integer class $\\mathbb{Z}^n$ does not satisfy all of the conditions for a vector space? (hint: Are the results always in the same space?)"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "# todo: add your code here!\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}