{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "3HfhkfWi122_"
   },
   "source": [
    "# Exercise 6: PyTorch Introduction with Logistic Regression"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "AEOOH_2Q3JYb"
   },
   "source": [
    "## 1. Setup: Install dependencies and download data\n",
    "- Download the image by running the next cell"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from google.colab import drive\n",
    "\n",
    "drive.mount(\"/content/drive\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "-2l-S3DP1zyV",
    "outputId": "aa4bfaf5-de50-4ab2-bafd-e2cda2d9d10a"
   },
   "outputs": [],
   "source": [
    "!pip install -q torch torchvision matplotlib tqdm gdown\n",
    "\n",
    "import gdown\n",
    "import os\n",
    "\n",
    "if not os.path.exists(\"/content/drive/My Drive/IPEO/Sentinel_2_LakeGeneva.png\"):\n",
    "    gdown.download(id=\"1xtiTdNepRB5BcPpZPvmqt64bMkqFMLND\", output=\"/content/drive/My Drive/IPEO/Sentinel_2_LakeGeneva.png\", quiet=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Alternatively, download the images from [here](https://drive.google.com/file/d/1xtiTdNepRB5BcPpZPvmqt64bMkqFMLND/view?usp=sharing)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Numpy to Pytorch\n",
    "\n",
    "**Pytorch is numpy with gradients** and syntax and handling of variables is designed to be very similar.\n",
    "\n",
    "\n",
    "We start with loading an image with Pillow, convert it to numpy, and then to torch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from PIL import Image\n",
    "\n",
    "# Pillow Image (we are not interested in the alpha channel, thats why we convert to RGB only)\n",
    "image = Image.open('/content/drive/My Drive/IPEO/Sentinel_2_LakeGeneva.png').convert('RGB')\n",
    "\n",
    "image # jupyter plots a pillow image when it is the last line in the cell"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "# TODO convert the pillow image to numpy array with np.array\n",
    "numpy_array_image = np.array(image)\n",
    "\n",
    "# TODO display the values of the array\n",
    "numpy_array_image"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Conversion numpy to torch tensor\n",
    "\n",
    "and compare the results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "\n",
    "# TODO convert the numpy array to a pytorch tensor \n",
    "# by calling torch.tensor(nparray) or torch.from_numpy(nparray)\n",
    "torch_tensor_image = torch.tensor(numpy_array_image)\n",
    "\n",
    "# TODO print tensor\n",
    "print(torch_tensor_image)\n",
    "\n",
    "# TODO bring tensor back to numpy by calling .numpy()\n",
    "numpy_array_image = torch_tensor_image.numpy()\n",
    "\n",
    "# TODO print numpy array\n",
    "print(numpy_array_image)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Casting to different datatypes\n",
    "\n",
    "outputs should look like\n",
    "```\n",
    "torch.uint8\n",
    "torch.int64\n",
    "torch.float32\n",
    "torch.float64\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO print datatype of torch_tensor_image: syntax: tensor.dtype\n",
    "print(torch_tensor_image.dtype)\n",
    "\n",
    "# TODO cast the integer image to 32bit integer numbers by calling .long() and print the datatype\n",
    "print(torch_tensor_image.long().dtype)\n",
    "\n",
    "# TODO cast the integer image to 32bit floating point numbers by calling .float() and print the datatype\n",
    "print(torch_tensor_image.float().dtype)\n",
    "\n",
    "# TODO cast the integer image to 64bit floating point numbers by calling .double() and print the datatype\n",
    "print(torch_tensor_image.double().dtype)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Gradients\n",
    "\n",
    "let's calculate some gradients on the sum function $S = \\sum_i p_i$, with $p_i$ as single pixel in the image $I$\n",
    "\n",
    "**TODO** what is the gradient of the sum $S$ with respect to an individual summands $p_i$:\n",
    "\n",
    "$\\frac{\\partial S}{\\partial p_i} = 1$\n",
    "\n",
    "Hint: ask us if you need a hint (it may be simpler than you think). Also, the next cell will output the solution if you implemented it correctly\n",
    "\n",
    "Side note: If you receive a `UserWarning: To copy construct from a tensor ...`, dont worry about it. \n",
    "We want to demonstrate the `requires_grad=True` option. The computationally efficient way `tensor_with_grad = tensor_without_grad.clone().detach().requires_grad_(True)` is not very intuitive"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO check if the torch_tensor_image requires gradients\n",
    "print(torch_tensor_image.requires_grad)\n",
    "\n",
    "# TODO cast torch_tensor_image to float (only floating point tensor can have gradients) \n",
    "torch_tensor_image_float = torch_tensor_image.float()\n",
    "\n",
    "# TODO initialize a tensor with gradients (set requires_grad option to True)\n",
    "torch_tensor_image_with_grad = torch.tensor(torch_tensor_image_float.clone().detach(), requires_grad=True)\n",
    "\n",
    "# TODO check if torch_tensor_image_with_grad now has gradients enabled\n",
    "print(torch_tensor_image_with_grad.requires_grad)\n",
    "\n",
    "# just a rename to be consistent with equation \n",
    "I = torch_tensor_image_with_grad\n",
    "\n",
    "# TODO sum over all values in the A tensor\n",
    "mu = I.sum()\n",
    "\n",
    "# TODO calculate gradients by calling .backward()  \n",
    "mu.backward()\n",
    "\n",
    "# TODO print gradients with .grad \n",
    "print(I.grad)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3 Logistic Regression with Stochastic Gradient Descent\n",
    "\n",
    "let's first select some training data for classes `land` and `water`\n",
    "\n",
    "### 3.1 Generate training data\n",
    "\n",
    "we need to get training data describing the classes `land` and `water`, we do this by \n",
    "having input features `x = (green, red, nir)` and labels `y = 0` for `land` and `y = 1` for `water`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "#If plot is not showing, try uncomment the command below\n",
    "#%notebook inline\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "image = Image.open('/content/drive/My Drive/IPEO/Sentinel_2_LakeGeneva.png').convert(\"RGB\")\n",
    "\n",
    "# let's convert to a tensor image with floating point numbers [0,1]\n",
    "image = torch.from_numpy(np.array(image) / 255.)\n",
    "\n",
    "# TODO select five training points for each class from the pixels coordinates in the image\n",
    "water_coords = torch.tensor([(500, 1750), (1000,1000), (1500, 750), (2500, 1000), (2000,800)])\n",
    "land_coords = torch.tensor([(200, 250), (500, 750), (1000, 2000), (2500, 1750), (2500,500)])\n",
    "\n",
    "fig, ax = plt.subplots()\n",
    "ax.imshow(image)\n",
    "ax.plot(water_coords[:,0], water_coords[:,1], \"r*\")\n",
    "ax.plot(land_coords[:,0], land_coords[:,1], \"wo\")\n",
    "ax.legend([\"water\",\"land\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# here, we extract the pixel values for each pixel annotated as water and store it into a water_pixels tensor\n",
    "water_pixels = []\n",
    "for point in water_coords:\n",
    "    r,c = point\n",
    "    pixel_values = image[c,r]\n",
    "    water_pixels.append(pixel_values)\n",
    "water_pixels = torch.stack(water_pixels)\n",
    "\n",
    "# TODO: do the same for land_pixels\n",
    "land_pixels = []\n",
    "for point in land_coords:\n",
    "    r,c = point\n",
    "    pixel_values = image[c,r]\n",
    "    land_pixels.append(pixel_values)\n",
    "land_pixels = torch.stack(land_pixels)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "let's now combine the water and land features with labels and store them \n",
    "\n",
    "into \n",
    "* `x = [(g1,r1,nir1), (g2,r2,nir2), ...]` (input) and \n",
    "* `y = [1,1,1,...,0,0,0,...]` (target/ground truth) \n",
    "\n",
    "variables"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO torch.vstack (vertical stack) water pixels and land pixels to a single tensor of size (N x 3)\n",
    "x = torch.vstack([water_pixels, land_pixels])\n",
    "\n",
    "# TODO convert x to .float()\n",
    "x = x.float()\n",
    "\n",
    "# TODO generate labels of size (N) with zeros for land pixels and ones for water pixels\n",
    "y_water = torch.ones(water_pixels.shape[0])\n",
    "y_land = torch.zeros(land_pixels.shape[0])\n",
    "\n",
    "y = torch.hstack([y_water, y_land])\n",
    "print(y.shape)\n",
    "y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.2 Plotting the Feature Space"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "from mpl_toolkits import mplot3d\n",
    "\n",
    "bands = np.array([\"green\", \"red\", \"nir\"])\n",
    "x_idx = 0\n",
    "y_idx = 1\n",
    "\n",
    "classes = np.array([\"water\", \"land\"])\n",
    "\n",
    "fig = plt.figure()\n",
    "ax = plt.axes(projection='3d')\n",
    "\n",
    "scatter = ax.scatter(x[:,0], x[:,1], x[:,2], c=y, cmap=\"RdBu\")\n",
    "el, annot = scatter.legend_elements()\n",
    "legend1 = ax.legend(el, classes,\n",
    "                    loc=\"lower left\", title=\"Classes\")\n",
    "ax.add_artist(legend1)\n",
    "ax.set_xlabel(f\"pixel value {bands[0]} band\")\n",
    "ax.set_ylabel(f\"pixel value {bands[1]} band\")\n",
    "ax.set_zlabel(f\"pixel value {bands[2]} band\")\n",
    "ax.set_title(f\"feature space: green - red - nir\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.3 Implement linear logistic regression classifier\n",
    "\n",
    "$y = \\sigma(\\mathbf{Ax} + b)$\n",
    "\n",
    "that consists of two steps:\n",
    "1. a linear decision plane $\\mathbf{Ax} + b$\n",
    "2. a sigmoid activation function $\\sigma(z) = \\frac{1}{1+\\exp(-z)}$\n",
    "\n",
    "#### 3.3.1 Matrix Multiplication and Sigmoid\n",
    "\n",
    "**TODO** implement step 1: the linear decision plane with random `A` and `b` and our data `x`\n",
    "\n",
    "we use `nn.Parameter` instead of `torch.Tensor` as they have gradients enabled natively and represent trainable weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from torch import nn\n",
    "torch.random.manual_seed(0) # we set a random seed to have identical \"random\" outputs\n",
    "\n",
    "in_features = 3 # RGB\n",
    "out_features = 1 # 1 probability\n",
    "\n",
    "# TODO randomly initialize the learnable parameters\n",
    "A = nn.Parameter(torch.rand([in_features, out_features]))\n",
    "b = nn.Parameter(torch.rand([out_features]))\n",
    "\n",
    "# TODO implement Ax + b and store into h\n",
    "# (you can use the identical torch.matmul(A,B), torch.mm, \n",
    "# or \"A @ B\" for the matrix multiplication)\n",
    "h = x @ A + b\n",
    "\n",
    "h # prints h, it represents the distance of each point from this (randomly initialized) decision plane"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**TODO** implement step 2, the sigmoid function $\\sigma(z) = \\frac{1}{1+\\exp(-z)}$\n",
    "\n",
    "Note, you can use torch.exp(tensor) to implement the exponential"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sigmoid(z):\n",
    "    return 1 / (1 + torch.exp(-z))\n",
    "\n",
    "# this output represents the probability of each point. \n",
    "# Points on one side will have a positive probability, \n",
    "# points on the other a negative.\n",
    "print(sigmoid(torch.tensor(-100)))\n",
    "print(sigmoid(torch.tensor(-1)))\n",
    "print(sigmoid(torch.tensor(0)))\n",
    "print(sigmoid(torch.tensor(1)))\n",
    "print(sigmoid(torch.tensor(100)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "let's run the sigmoid function on `h` as the output of $\\mathbf{Ax} + b$\n",
    "this gives us a complete linear layer $\\sigma(\\mathbf{Ax} + b)$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sigmoid(h)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 3.3.2 Package your code into a Torch Module\n",
    "\n",
    "FYI: background on Python classes\n",
    "* the module is a class called `Linear` that \n",
    "* has a constructor `__init__` and a member function `forward`\n",
    "* `self` references the instance of this class and `self.A = ...` initializes a variable of this instance.\n",
    "* It inherits from the class `nn.Module` (`super().__init__()` is the constructor of the parent class)\n",
    "\n",
    "**TODO** implement the linear decision plane as `nn.Module` and initialize the `nn.Parameter` `A` and `b` randomly with `torch.rand`\n",
    "\n",
    "the final output should be identical to `sigmoid(h)` from above"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from torch import nn\n",
    "torch.random.manual_seed(0) # we set a random seed to have identical \"random\" outputs\n",
    "\n",
    "class Linear(nn.Module):\n",
    "    def __init__(self, in_features, out_features):\n",
    "        super().__init__()\n",
    "        # TODO initialize the learnable parameters A and b with random values of the correct shape\n",
    "        self.A = nn.Parameter(torch.rand([in_features, out_features]))\n",
    "        self.b = nn.Parameter(torch.rand([out_features]))\n",
    "        \n",
    "        \n",
    "    def forward(self, x):\n",
    "        # TODO implement the multiplication Ax + b here\n",
    "        return torch.matmul(x, self.A) + self.b\n",
    "\n",
    "# nothing to do here, but note that we need to package sigmoid into \n",
    "# a nn.Module as well for nn.Sequential in the next cell\n",
    "class Sigmoid(nn.Module):\n",
    "    def forward(self, x):\n",
    "        return sigmoid(x)\n",
    "    \n",
    "    \n",
    "in_features = 3 # RGB\n",
    "out_features = 1 # 1 probability\n",
    "\n",
    "linear_module = Linear(in_features,out_features) # calls __init__() of Linear\n",
    "sigmoid_function = Sigmoid()\n",
    "\n",
    "h = linear_module(x) # calls forward(x)\n",
    "sigmoid_function(h) # should be similar to the previous cell"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 3.3.3 Single Logistic Regression Model\n",
    "\n",
    "let's build and initialize a single `model` that converts pixel vales `x` into class probabilities `y`by chaining `Linear` and `sigmoid` together with `nn.Sequential`\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import OrderedDict\n",
    "model = nn.Sequential(\n",
    "        OrderedDict(\n",
    "            {\"linear\": Linear(in_features,out_features), \n",
    "            \"sigmoid\": Sigmoid()}\n",
    "            )\n",
    "        )\n",
    "\n",
    "model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 3.3.3.4 Optimization with Gradient Descent\n",
    "\n",
    "In the next cell you will implement the gradient descent\n",
    "\n",
    "$A \\leftarrow A - \\eta \\frac{\\partial e}{\\partial A}$, and $b \\leftarrow b - \\eta \\frac{\\partial e}{\\partial A}$\n",
    "\n",
    "of parameters `A`, `b` with `error` $e$ and learning rate $\\eta$\n",
    "\n",
    "Note: execute the previous cell to restart from a random model. The expected result is a plot of decreasing training error over time (epochs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "from tqdm.auto import tqdm\n",
    "\n",
    "# number of epochs (times the model sees the training dataset)\n",
    "num_epochs = 2000\n",
    "learning_rate = 1\n",
    "\n",
    "errors = []\n",
    "with tqdm(range(num_epochs)) as progress_bar:\n",
    "    for epoch in progress_bar:\n",
    "        probability = model(x)\n",
    "\n",
    "        # Binary Cross Entropy (BCE) error function\n",
    "        error = torch.nn.functional.binary_cross_entropy(probability, y.unsqueeze(-1).float())\n",
    "\n",
    "        # gradient back propagation (built into Torch)\n",
    "        error.backward()\n",
    "\n",
    "        # TODO implement gradient descent for A and b: e.g. A = A - lr * A.grad \n",
    "        # note that this needs to be wrapped in nn.Parameter\n",
    "        model.linear.A = nn.Parameter(model.linear.A - learning_rate * model.linear.A.grad)\n",
    "        model.linear.b = nn.Parameter(model.linear.b - learning_rate * model.linear.b.grad)\n",
    "\n",
    "        errors.append(error.detach().numpy())\n",
    "        progress_bar.set_description(f\"epoch {epoch} BCE error: {error:.2f}\")\n",
    "        \n",
    "%matplotlib inline\n",
    "fig, ax = plt.subplots(figsize=(6,2))\n",
    "ax.plot(np.arange(num_epochs), errors)\n",
    "ax.set_xlabel(\"epoch\")\n",
    "ax.set_ylabel(\"training error\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.3.4 Test Trained Model\n",
    "\n",
    "now we apply the trained model on all pixels of the image\n",
    "\n",
    "to do so, we need to \n",
    "1. reshape (flatten) the image of size (H,W,C=3) to a list of pixels (HW,C=3)\n",
    "2. estimate a probability for each pixel\n",
    "3. reshape back the probabilities of shape (HW,1) to a probability image (H,W)\n",
    "\n",
    "**TODO** implement these steps\n",
    "\n",
    "final expected output is\n",
    "```\n",
    "torch.Size([2232, 2998, 3])\n",
    "torch.Size([6691536, 3])\n",
    "torch.Size([6691536, 1])\n",
    "torch.Size([2232, 2998])\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "image = Image.open('/content/drive/My Drive/IPEO/Sentinel_2_LakeGeneva.png').convert(\"RGB\")\n",
    "image = torch.from_numpy(np.array(image) / 255.).float()\n",
    "\n",
    "print(image.shape)\n",
    "H,W,C = image.shape # stores original sizes in the variables height H, width W, and channels C\n",
    "\n",
    "# TODO reshape the image to a list of pixels with .reshape \n",
    "image_pixels = image.reshape(H*W,C)\n",
    "print(image_pixels.shape)\n",
    "\n",
    "# TODO predict a probability for each pixel\n",
    "pixels_probabilities = model(image_pixels)\n",
    "print(pixels_probabilities.shape)\n",
    "\n",
    "# TODO .reshape the list of probabilities (HW,1) to (H,W)\n",
    "probability_image = pixels_probabilities.reshape(H,W)\n",
    "print(probability_image.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Congratulations! You reached the end of the exercise\n",
    "\n",
    "**no more TODOs here execute the next cells for plotting the results**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from mpl_toolkits.axes_grid1 import make_axes_locatable\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(12,12))\n",
    "im = ax.imshow(probability_image.detach().numpy(), vmin=0, vmax=1, cmap=\"viridis\")\n",
    "divider = make_axes_locatable(ax)\n",
    "cax = divider.append_axes('right', size='5%', pad=0.05)\n",
    "fig.colorbar(im, cax=cax, orientation='vertical', label=\"probability water\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Visualization of the pixels in the feature space and and learned decision plane\n",
    "\n",
    "This cell is for your interest. Nothing to do here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "from mpl_toolkits import mplot3d\n",
    "import matplotlib.cm as cm\n",
    "\n",
    "fig = plt.figure()\n",
    "ax = plt.axes(projection='3d')\n",
    "\n",
    "# select only a random subset of pixels for plotting\n",
    "idxs = np.random.randint(len(image_pixels), size=1000)\n",
    "colors = cm.viridis(pixels_probabilities[idxs].detach().numpy())\n",
    "\n",
    "#scatter = ax.scatter(image_pixels[idxs,0], image_pixels[idxs,1], image_pixels[idxs,2], c=image_pixels[idxs])\n",
    "scatter = ax.scatter(image_pixels[idxs,0], image_pixels[idxs,1], image_pixels[idxs,2], c=colors)\n",
    "\n",
    "# define decision boundary using A, b\n",
    "point  = np.array([0, 0, 0])\n",
    "normal = model.linear.A.detach().squeeze().cpu().numpy()\n",
    "d = -point.dot(normal) + model.linear.b.detach().squeeze().cpu().numpy()\n",
    "xx, yy = np.meshgrid(np.linspace(0,1,2), np.linspace(0,1,2))\n",
    "z = (-normal[0] * xx - normal[1] * yy - d) * 1. /normal[2]\n",
    "\n",
    "# plot decision boundary\n",
    "ax.plot_surface(xx, yy, z, alpha=0.5, color=\"#0065bd\")##0065bd\n",
    "\n",
    "# definition of axis and labels\n",
    "ax.set_xlabel(f\"pixel value {bands[0]} band\")\n",
    "ax.set_ylabel(f\"pixel value {bands[1]} band\")\n",
    "ax.set_zlabel(f\"pixel value {bands[2]} band\")\n",
    "ax.set_title(f\"feature space: green - red - nir\")\n"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "collapsed_sections": [],
   "name": "[ex4] Solution",
   "provenance": []
  },
  "interpreter": {
   "hash": "d87036bccc21f2c1ac8988b25c74afd5927b201ca402dbbb169419cef9fe6115"
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.8.8"
  },
  "widgets": {
   "application/vnd.jupyter.widget-state+json": {
    "0978057c5c074e419ecca708c638f35d": {
     "model_module": "@jupyter-widgets/controls",
     "model_module_version": "1.5.0",
     "model_name": "DescriptionStyleModel",
     "state": {
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "DescriptionStyleModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "StyleView",
      "description_width": ""
     }
    },
    "148371a9fcb3453fa95ee7624d9cdf63": {
     "model_module": "@jupyter-widgets/controls",
     "model_module_version": "1.5.0",
     "model_name": "DescriptionStyleModel",
     "state": {
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "DescriptionStyleModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "StyleView",
      "description_width": ""
     }
    },
    "245cddfd5ce047faa6f545badfcd5c4c": {
     "model_module": "@jupyter-widgets/controls",
     "model_module_version": "1.5.0",
     "model_name": "HTMLModel",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "HTMLModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "HTMLView",
      "description": "",
      "description_tooltip": null,
      "layout": "IPY_MODEL_ecce0096b3c34e5a9c5ef35cdf947eb9",
      "placeholder": "​",
      "style": "IPY_MODEL_148371a9fcb3453fa95ee7624d9cdf63",
      "value": " 100/100 [00:00&lt;00:00, 163.32it/s]"
     }
    },
    "3f6b816ca70b43d4b0fbee734c9dae1f": {
     "model_module": "@jupyter-widgets/base",
     "model_module_version": "1.2.0",
     "model_name": "LayoutModel",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    },
    "431b96d328c24eb6822f635cf2c60ce9": {
     "model_module": "@jupyter-widgets/base",
     "model_module_version": "1.2.0",
     "model_name": "LayoutModel",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    },
    "50764556a6ec453080992537237705ea": {
     "model_module": "@jupyter-widgets/controls",
     "model_module_version": "1.5.0",
     "model_name": "FloatProgressModel",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "FloatProgressModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "ProgressView",
      "bar_style": "success",
      "description": "",
      "description_tooltip": null,
      "layout": "IPY_MODEL_3f6b816ca70b43d4b0fbee734c9dae1f",
      "max": 100,
      "min": 0,
      "orientation": "horizontal",
      "style": "IPY_MODEL_7f2cc28c6259431c9b2d0d212dd6e291",
      "value": 100
     }
    },
    "7d227c84e4844d06b60a6ab2c17bec2a": {
     "model_module": "@jupyter-widgets/controls",
     "model_module_version": "1.5.0",
     "model_name": "HTMLModel",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "HTMLModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "HTMLView",
      "description": "",
      "description_tooltip": null,
      "layout": "IPY_MODEL_431b96d328c24eb6822f635cf2c60ce9",
      "placeholder": "​",
      "style": "IPY_MODEL_0978057c5c074e419ecca708c638f35d",
      "value": "100%"
     }
    },
    "7f2cc28c6259431c9b2d0d212dd6e291": {
     "model_module": "@jupyter-widgets/controls",
     "model_module_version": "1.5.0",
     "model_name": "ProgressStyleModel",
     "state": {
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "ProgressStyleModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "StyleView",
      "bar_color": null,
      "description_width": ""
     }
    },
    "cee89cf3da084598b5f8a715125bef95": {
     "model_module": "@jupyter-widgets/controls",
     "model_module_version": "1.5.0",
     "model_name": "HBoxModel",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "HBoxModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "HBoxView",
      "box_style": "",
      "children": [
       "IPY_MODEL_7d227c84e4844d06b60a6ab2c17bec2a",
       "IPY_MODEL_50764556a6ec453080992537237705ea",
       "IPY_MODEL_245cddfd5ce047faa6f545badfcd5c4c"
      ],
      "layout": "IPY_MODEL_d957a654504348609879c0a6bc1c2875"
     }
    },
    "d957a654504348609879c0a6bc1c2875": {
     "model_module": "@jupyter-widgets/base",
     "model_module_version": "1.2.0",
     "model_name": "LayoutModel",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    },
    "ecce0096b3c34e5a9c5ef35cdf947eb9": {
     "model_module": "@jupyter-widgets/base",
     "model_module_version": "1.2.0",
     "model_name": "LayoutModel",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    }
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
