├── .gitignore.txt ├── ChatGPT Image Mar 30, 2025, 11_17_06 AM.png ├── ChatGPT Image Small.png ├── Course Image.png ├── Homeworks ├── Logistic Regression_Homework_Public.ipynb ├── Scikit-Learn - Linear Regression Homework_Public.ipynb ├── Scikit-Learn - Logistic Regression Homework_Public.ipynb ├── Week 1.ipynb └── Week 3 - Homework 2 - Public .pdf ├── LICENSE ├── Notebooks ├── Autoencoder.ipynb ├── CNN Lab.ipynb ├── Decision Tree.ipynb ├── Feature Engineering.ipynb ├── Lab 3 - Gradient Descent.ipynb ├── Linear Regression.ipynb ├── Logistic Regression.ipynb ├── Neural Network from Scratch.ipynb ├── PyTorch_Tutorial.ipynb └── Regularization and Cross-Validation.ipynb ├── README.md ├── Reading Materials ├── Autoencoders.pdf ├── Convolutional_Neural_Networks.pdf ├── Feature Representation.pdf ├── Gradient_Descent.pdf ├── Introduction.pdf ├── Linear Classification.pdf ├── Linear Regression.pdf ├── Neural_Networks.pdf ├── Neural_Networks_Part 2.pdf └── Non-parametric methods.pdf ├── Topic 1 └── Intro_General.pdf ├── Topic 2 └── Lecture 2 - Linear Regression, Regularization and Cross-Validation.pdf ├── Topic 3 └── Lecture 3 - Gradient Descent.pdf ├── Topic 4 └── Lecture 4 - Linear Classification.pdf ├── Topic 5 ├── Addtional Material - Scikit-Learn.pdf └── Lecture 5 - Feature Engineering.pdf ├── Topic 6 └── Lecture 6 -Neural Network.pdf ├── Topic 7 └── Lecture 7 - Neural Network - AutoEncoder.pdf ├── Topic 8 └── Lecture 8 - Convolutional Neural Network.pdf ├── Topic 9 └── Lecture 9 - Non-Parametric Models.pdf └── requirements.txt /.gitignore.txt: -------------------------------------------------------------------------------- 1 | Intro_General.pptx 2 | Lecture 2 - Linear Regression, Regularization and Cross-Validation.pptx -------------------------------------------------------------------------------- /ChatGPT Image Mar 30, 2025, 11_17_06 AM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/ChatGPT Image Mar 30, 2025, 11_17_06 AM.png -------------------------------------------------------------------------------- /ChatGPT Image Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/ChatGPT Image Small.png -------------------------------------------------------------------------------- /Course Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Course Image.png -------------------------------------------------------------------------------- /Homeworks/Logistic Regression_Homework_Public.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Homework 3 \n", 8 | "\n", 9 | "PAML 2024,\n", 10 | "\n", 11 | "Rina Buoy, PhD" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "YOUR FULL NAME: " 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "import numpy as np\n", 28 | "import pandas as pd\n", 29 | "import matplotlib.pyplot as plt\n", 30 | "from sklearn.model_selection import train_test_split\n", 31 | "#from sklearn.preprocessing import LabelEncoder\n", 32 | "\n", 33 | "# Logistic Regression class with Gradient Descent (same as above)\n", 34 | "class LogisticRegressionGD:\n", 35 | " def __init__(self, learning_rate=0.01, iterations=1000):\n", 36 | " self.learning_rate = learning_rate\n", 37 | " self.iterations = iterations\n", 38 | "\n", 39 | " def sigmoid(self, z):\n", 40 | " \"\"\"Sigmoid function to map predictions to probabilities.\"\"\"\n", 41 | " return 1 / (1 + np.exp(-z))\n", 42 | "\n", 43 | " def fit(self, X, y):\n", 44 | " \"\"\"Training the logistic regression model using gradient descent.\"\"\"\n", 45 | " self.m, self.n = X.shape\n", 46 | " self.theta = np.zeros(self.n)\n", 47 | " self.bias = 0\n", 48 | "\n", 49 | " for i in range(self.iterations):\n", 50 | " z = np.dot(X, self.theta) + self.bias\n", 51 | " y_pred = self.sigmoid(z)\n", 52 | " dw = (1 / self.m) * np.dot(X.T, (y_pred - y))\n", 53 | " db = (1 / self.m) * np.sum(y_pred - y)\n", 54 | " self.theta -= self.learning_rate * dw\n", 55 | " self.bias -= self.learning_rate * db\n", 56 | " \n", 57 | " def predict(self, X):\n", 58 | " z = np.dot(X, self.theta) + self.bias\n", 59 | " y_pred = self.sigmoid(z)\n", 60 | " return [1 if i > 0.5 else 0 for i in y_pred]\n", 61 | "\n", 62 | "\n", 63 | "\n" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": {}, 69 | "source": [ 70 | "### GOAL\n", 71 | "\n", 72 | "Apply the provided logistic regression solver to a Congressional Voting Records dataset." 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": 3, 78 | "metadata": {}, 79 | "outputs": [], 80 | "source": [ 81 | "# Load the Congressional Voting Records dataset\n", 82 | "url = \"https://archive.ics.uci.edu/ml/machine-learning-databases/voting-records/house-votes-84.data\"\n", 83 | "columns = ['party', 'handicapped-infants', 'water-project', 'budget', 'physician-fee-freeze',\n", 84 | " 'el-salvador-aid', 'religious-groups', 'anti-satellite-ban', 'aid-to-contras',\n", 85 | " 'mx-missile', 'immigration', 'synfuels', 'education', 'superfund-sue', 'crime',\n", 86 | " 'duty-free', 'south-africa']" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "metadata": {}, 92 | "source": [ 93 | "#### TASK 1:\n", 94 | "\n", 95 | "(a) Load the above dataset using Pandas, \n", 96 | "\n", 97 | "(b) Replace 'y' with 1 and 'n' with 0, \n", 98 | "\n", 99 | "(c) Handle missing data by filling it with the mode of each column\n", 100 | "\n", 101 | "\n", 102 | "\n", 103 | "\n" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 2, 109 | "metadata": {}, 110 | "outputs": [], 111 | "source": [ 112 | "### YOUR CODES" 113 | ] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "metadata": {}, 118 | "source": [ 119 | "#### TASK 2:\n", 120 | "\n", 121 | "(a) Extract the feature matrix, X, and the target vector, y\n", 122 | "\n", 123 | "(b) Split it into train and test sets using train_test_split" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": null, 129 | "metadata": {}, 130 | "outputs": [], 131 | "source": [ 132 | "### YOUR CODES\n", 133 | "\n" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "metadata": {}, 139 | "source": [ 140 | "#### TASK 3:\n", 141 | "\n", 142 | "(a) Create and train the model on the train set \n", 143 | "\n", 144 | "(b) Compute the accuracy on the test set" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": null, 150 | "metadata": {}, 151 | "outputs": [], 152 | "source": [ 153 | "### YOUR CODES" 154 | ] 155 | } 156 | ], 157 | "metadata": { 158 | "kernelspec": { 159 | "display_name": "base", 160 | "language": "python", 161 | "name": "python3" 162 | }, 163 | "language_info": { 164 | "codemirror_mode": { 165 | "name": "ipython", 166 | "version": 3 167 | }, 168 | "file_extension": ".py", 169 | "mimetype": "text/x-python", 170 | "name": "python", 171 | "nbconvert_exporter": "python", 172 | "pygments_lexer": "ipython3", 173 | "version": "3.11.5" 174 | } 175 | }, 176 | "nbformat": 4, 177 | "nbformat_minor": 2 178 | } 179 | -------------------------------------------------------------------------------- /Homeworks/Scikit-Learn - Linear Regression Homework_Public.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Homework 5\n", 8 | "\n", 9 | "PAML 2024,\n", 10 | "\n", 11 | "Rina Buoy, PhD" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "YOUR FULL NAME: " 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 13, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "# Import necessary libraries\n", 28 | "from sklearn.model_selection import train_test_split\n", 29 | "from sklearn.linear_model import LinearRegression\n", 30 | "from sklearn.datasets import fetch_california_housing\n", 31 | "from sklearn.metrics import r2_score" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "### Applying Linear Regression with Scikit-Learn" 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": {}, 44 | "source": [ 45 | "#### Task 1: Applying Linear Regression with Scikit-Learn\n" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": 14, 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [ 54 | "\n", 55 | "# Load the California Housing dataset from Scikit-Learn dataset\n", 56 | "# Store Feature Matrix as X, and target variable as y\n" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "#### Task 2: Split X, y into train and test sets\n" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 15, 69 | "metadata": {}, 70 | "outputs": [], 71 | "source": [ 72 | "\n", 73 | "# Split the dataset into training and testing sets\n" 74 | ] 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "metadata": {}, 79 | "source": [ 80 | "#### Task 3: Train a Linear regression using LinearRegression sklearn.linear_model" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 16, 86 | "metadata": {}, 87 | "outputs": [], 88 | "source": [ 89 | "\n", 90 | "\n", 91 | "# Create a logistic regression model\n", 92 | "# Train the model on the training data\n" 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "metadata": {}, 98 | "source": [ 99 | "#### Task 4: Compute R-squared on the test set using r2_square from sklearn.metrics" 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": 17, 105 | "metadata": {}, 106 | "outputs": [], 107 | "source": [ 108 | "# Compute the R-quared on the test set" 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "metadata": {}, 114 | "source": [ 115 | "#### Task 5: What can be done further to improve the R-sqaured ?" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": 18, 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [ 124 | "# Frame your answer in the context of feature engineering lecture." 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": null, 130 | "metadata": {}, 131 | "outputs": [], 132 | "source": [] 133 | } 134 | ], 135 | "metadata": { 136 | "kernelspec": { 137 | "display_name": "base", 138 | "language": "python", 139 | "name": "python3" 140 | }, 141 | "language_info": { 142 | "codemirror_mode": { 143 | "name": "ipython", 144 | "version": 3 145 | }, 146 | "file_extension": ".py", 147 | "mimetype": "text/x-python", 148 | "name": "python", 149 | "nbconvert_exporter": "python", 150 | "pygments_lexer": "ipython3", 151 | "version": "3.11.5" 152 | } 153 | }, 154 | "nbformat": 4, 155 | "nbformat_minor": 2 156 | } 157 | -------------------------------------------------------------------------------- /Homeworks/Scikit-Learn - Logistic Regression Homework_Public.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Homework 4\n", 8 | "\n", 9 | "PAML 2024,\n", 10 | "\n", 11 | "Rina Buoy, PhD" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "YOUR FULL NAME: " 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 1, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "# Import necessary libraries\n", 28 | "import numpy as np\n", 29 | "from sklearn.model_selection import train_test_split\n", 30 | "from sklearn.linear_model import LogisticRegression\n", 31 | "from sklearn import datasets\n", 32 | "from sklearn.metrics import accuracy_score" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "### Applying Logistic Regression with Scikit-Learn" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "#### Task 1: Applying Logistic Regression with Scikit-Learn\n" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 2, 52 | "metadata": {}, 53 | "outputs": [], 54 | "source": [ 55 | "\n", 56 | "# Load the Breast Cancer dataset from Scikit-Learn dataset\n", 57 | "# Store Feature Matrix as X, and target variable as y\n" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "#### Task 2: Split X, y into train and test sets\n" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 3, 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "\n", 74 | "# Split the dataset into training and testing sets\n" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": {}, 80 | "source": [ 81 | "#### Task 3: Train a logistic regression using LogisticRegression sklearn.linear_model" 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": 4, 87 | "metadata": {}, 88 | "outputs": [], 89 | "source": [ 90 | "\n", 91 | "\n", 92 | "# Create a logistic regression model\n", 93 | "# Train the model on the training data\n" 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "metadata": {}, 99 | "source": [ 100 | "#### Task 4: Compute accuracy on the test set using accuracy_score from sklearn.metrics" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": 5, 106 | "metadata": {}, 107 | "outputs": [], 108 | "source": [ 109 | "# Compute the accuracy on the test set" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "metadata": {}, 115 | "source": [ 116 | "#### Task 5: What can be done further to improve the accuracy ?" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": 6, 122 | "metadata": {}, 123 | "outputs": [], 124 | "source": [ 125 | "# Frame your answer in the context of feature engineering lecture." 126 | ] 127 | }, 128 | { 129 | "cell_type": "markdown", 130 | "metadata": {}, 131 | "source": [] 132 | } 133 | ], 134 | "metadata": { 135 | "kernelspec": { 136 | "display_name": "base", 137 | "language": "python", 138 | "name": "python3" 139 | }, 140 | "language_info": { 141 | "codemirror_mode": { 142 | "name": "ipython", 143 | "version": 3 144 | }, 145 | "file_extension": ".py", 146 | "mimetype": "text/x-python", 147 | "name": "python", 148 | "nbconvert_exporter": "python", 149 | "pygments_lexer": "ipython3", 150 | "version": "3.11.5" 151 | } 152 | }, 153 | "nbformat": 4, 154 | "nbformat_minor": 2 155 | } 156 | -------------------------------------------------------------------------------- /Homeworks/Week 1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Week 1: Homework" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## Instructions for Students:\n", 15 | "\n", 16 | "Complete the code cells with the exercises indicated by comments and print out the outputs.\n" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 5, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "\n", 26 | "# Import the necessary libraries\n" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "### Part 1: NumPy Exercises" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 6, 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "name": "stdout", 43 | "output_type": "stream", 44 | "text": [ 45 | "### Part 1: Linear Algebra with NumPy ###\n" 46 | ] 47 | } 48 | ], 49 | "source": [ 50 | "\n", 51 | "print(\"### Part 1: Linear Algebra with NumPy ###\")\n", 52 | "\n", 53 | "\n" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": 7, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "\n", 63 | "# 1. Create a 3x3 matrix with values ranging from 1 to 9\n" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 8, 69 | "metadata": {}, 70 | "outputs": [], 71 | "source": [ 72 | "# 2. Create a 3x1 vector with values ranging from 10 to 12\n", 73 | "\n" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 9, 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "# 3. Perform matrix multiplication between the matrix and vector\n" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 10, 88 | "metadata": {}, 89 | "outputs": [], 90 | "source": [ 91 | "# 4. Find the determinant of the matrix\n" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": 11, 97 | "metadata": {}, 98 | "outputs": [], 99 | "source": [ 100 | "# 5. Find the inverse of the matrix (if it exists)\n" 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "metadata": {}, 106 | "source": [ 107 | "### Part 2: Working with DataFrames and Pandas" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": 12, 113 | "metadata": {}, 114 | "outputs": [ 115 | { 116 | "name": "stdout", 117 | "output_type": "stream", 118 | "text": [ 119 | "\n", 120 | "### Part 2: Linear Algebra with Pandas ###\n" 121 | ] 122 | } 123 | ], 124 | "source": [ 125 | "\n", 126 | "print(\"\\n### Part 2: Linear Algebra with Pandas ###\")" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": 13, 132 | "metadata": {}, 133 | "outputs": [], 134 | "source": [ 135 | "# 6. Create a DataFrame from a Numpy Matrix\n" 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": 14, 141 | "metadata": {}, 142 | "outputs": [], 143 | "source": [ 144 | "# 7. Find the row-wise and column-wise mean of the DataFrame\n" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": 15, 150 | "metadata": {}, 151 | "outputs": [], 152 | "source": [ 153 | "# 8. Convert the DataFrame back to a NumPy array\n", 154 | "\n" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": 16, 160 | "metadata": {}, 161 | "outputs": [], 162 | "source": [ 163 | "# 9. Find the eigenvalues and eigenvectors of a Numpy Matrix" 164 | ] 165 | } 166 | ], 167 | "metadata": { 168 | "kernelspec": { 169 | "display_name": "base", 170 | "language": "python", 171 | "name": "python3" 172 | }, 173 | "language_info": { 174 | "codemirror_mode": { 175 | "name": "ipython", 176 | "version": 3 177 | }, 178 | "file_extension": ".py", 179 | "mimetype": "text/x-python", 180 | "name": "python", 181 | "nbconvert_exporter": "python", 182 | "pygments_lexer": "ipython3", 183 | "version": "3.11.5" 184 | } 185 | }, 186 | "nbformat": 4, 187 | "nbformat_minor": 2 188 | } 189 | -------------------------------------------------------------------------------- /Homeworks/Week 3 - Homework 2 - Public .pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Homeworks/Week 3 - Homework 2 - Public .pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Rina Buoy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Notebooks/Autoencoder.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## An Autoencoder From Scratch\n", 8 | "\n", 9 | "by Rina Buoy, PhD" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "1. Network Structure:\n", 17 | "\n", 18 | "The encoder layer compresses the input image (784 dimensions) into a smaller hidden last representation (64 dimensions).\n", 19 | "The decoder reconstructs the image from this compressed representation.\n", 20 | "\n", 21 | "2. Training:\n", 22 | "\n", 23 | "The model is trained on the MNIST training set, flattened and normalized to values between 0 and 1.\n", 24 | "We use Mean Squared Error as the loss and print it every 10 epochs.\n", 25 | "\n", 26 | "3. Reconstruction Visualization:\n", 27 | "\n", 28 | "The visualize_reconstruction function uses matplotlib to display original and reconstructed images side-by-side for comparison.\n", 29 | "\n", 30 | "4. Running and Observing:\n", 31 | "\n", 32 | "You should see the loss decrease over epochs, and the reconstructed images should look more like the originals as training progresses." 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "#### Step 1: Set up the imports" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 1, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "import numpy as np\n", 49 | "import torch\n", 50 | "from torchvision import datasets, transforms\n", 51 | "import matplotlib.pyplot as plt" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "### Step 2: Helper Functions" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 2, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "\n", 68 | "# Define the activation functions\n", 69 | "def sigmoid(x):\n", 70 | " x= np.clip( x, -500, 500 )\n", 71 | " return 1 / (1 + np.exp(-x))\n", 72 | "\n", 73 | "def sigmoid_derivative(x):\n", 74 | " return x * (1 - x)\n", 75 | "\n", 76 | "# Mean squared error loss function\n", 77 | "def mean_squared_error(y_true, y_pred):\n", 78 | " return np.mean((y_true - y_pred) ** 2)" 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "metadata": {}, 84 | "source": [ 85 | "### Step 3: Define the Neural Network Structure, Training Loops, Forward, and Backward Pass\n" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": 3, 91 | "metadata": {}, 92 | "outputs": [], 93 | "source": [ 94 | "# Define autoencoder parameters\n", 95 | "input_size = 28 * 28 # 784 for MNIST images\n", 96 | "hidden_size = 64 # Size of hidden layer\n", 97 | "output_size = input_size\n", 98 | "\n", 99 | "# Initialize weights and biases\n", 100 | "np.random.seed(42)\n", 101 | "W1 = np.random.randn(input_size, hidden_size) * 0.01\n", 102 | "b1 = np.zeros((1, hidden_size))\n", 103 | "W2 = np.random.randn(hidden_size, output_size) * 0.01\n", 104 | "b2 = np.zeros((1, output_size))\n", 105 | "\n", 106 | "# Activation and its derivative\n", 107 | "def relu(x):\n", 108 | " return np.maximum(0, x)\n", 109 | "\n", 110 | "def relu_derivative(x):\n", 111 | " return np.where(x > 0, 1, 0)\n", 112 | "\n", 113 | "# Forward pass\n", 114 | "def forward(X):\n", 115 | " Z1 = np.dot(X, W1) + b1\n", 116 | " A1 = relu(Z1)\n", 117 | " Z2 = np.dot(A1, W2) + b2\n", 118 | " return Z1, A1, Z2\n", 119 | "\n", 120 | "# Loss function\n", 121 | "def mse_loss(y_true, y_pred):\n", 122 | " return np.mean((y_true - y_pred) ** 2)\n", 123 | "\n", 124 | "# Backward pass\n", 125 | "def backward(X, Z1, A1, Z2, learning_rate=0.001):\n", 126 | " global W1, b1, W2, b2\n", 127 | " m = X.shape[0]\n", 128 | "\n", 129 | " # Gradients for output layer\n", 130 | " dZ2 = (Z2 - X) / m\n", 131 | " dW2 = np.dot(A1.T, dZ2)\n", 132 | " db2 = np.sum(dZ2, axis=0, keepdims=True)\n", 133 | "\n", 134 | " # Gradients for hidden layer\n", 135 | " dA1 = np.dot(dZ2, W2.T)\n", 136 | " dZ1 = dA1 * relu_derivative(Z1)\n", 137 | " dW1 = np.dot(X.T, dZ1)\n", 138 | " db1 = np.sum(dZ1, axis=0, keepdims=True)\n", 139 | "\n", 140 | " # Update weights and biases\n", 141 | " W1 -= learning_rate * dW1\n", 142 | " b1 -= learning_rate * db1\n", 143 | " W2 -= learning_rate * dW2\n", 144 | " b2 -= learning_rate * db2" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "### Step 4: Load and Pepare the dataset" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": 4, 157 | "metadata": {}, 158 | "outputs": [], 159 | "source": [ 160 | "# Load MNIST data using torchvision\n", 161 | "transform = transforms.Compose([transforms.ToTensor()])\n", 162 | "train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)\n", 163 | "test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)\n", 164 | "\n", 165 | "# Prepare data\n", 166 | "X_train = train_dataset.data.numpy().reshape(-1, 28 * 28) / 255.0 # Normalize and flatten to 784-dimensional vectors\n", 167 | "X_test = test_dataset.data.numpy().reshape(-1, 28 * 28) / 255.0" 168 | ] 169 | }, 170 | { 171 | "cell_type": "markdown", 172 | "metadata": {}, 173 | "source": [ 174 | "### Step 5: Model Instanciation and Training" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": 6, 180 | "metadata": {}, 181 | "outputs": [ 182 | { 183 | "name": "stdout", 184 | "output_type": "stream", 185 | "text": [ 186 | "Epoch 1, Loss: 0.0503\n", 187 | "Epoch 2, Loss: 0.0373\n", 188 | "Epoch 3, Loss: 0.0313\n", 189 | "Epoch 4, Loss: 0.0274\n", 190 | "Epoch 5, Loss: 0.0245\n", 191 | "Epoch 6, Loss: 0.0223\n", 192 | "Epoch 7, Loss: 0.0206\n", 193 | "Epoch 8, Loss: 0.0192\n", 194 | "Epoch 9, Loss: 0.0180\n", 195 | "Epoch 10, Loss: 0.0170\n", 196 | "Epoch 11, Loss: 0.0162\n", 197 | "Epoch 12, Loss: 0.0155\n" 198 | ] 199 | }, 200 | { 201 | "ename": "KeyboardInterrupt", 202 | "evalue": "", 203 | "output_type": "error", 204 | "traceback": [ 205 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 206 | "\u001b[1;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", 207 | "Cell \u001b[1;32mIn[6], line 15\u001b[0m\n\u001b[0;32m 13\u001b[0m \u001b[38;5;66;03m# Calculate loss on training data\u001b[39;00m\n\u001b[0;32m 14\u001b[0m _, _, output \u001b[38;5;241m=\u001b[39m forward(X_train)\n\u001b[1;32m---> 15\u001b[0m loss \u001b[38;5;241m=\u001b[39m mse_loss(X_train, output)\n\u001b[0;32m 16\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mEpoch \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mepoch\u001b[38;5;250m \u001b[39m\u001b[38;5;241m+\u001b[39m\u001b[38;5;250m \u001b[39m\u001b[38;5;241m1\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m, Loss: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mloss\u001b[38;5;132;01m:\u001b[39;00m\u001b[38;5;124m.4f\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m'\u001b[39m)\n", 208 | "Cell \u001b[1;32mIn[3], line 28\u001b[0m, in \u001b[0;36mmse_loss\u001b[1;34m(y_true, y_pred)\u001b[0m\n\u001b[0;32m 25\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m Z1, A1, Z2\n\u001b[0;32m 27\u001b[0m \u001b[38;5;66;03m# Loss function\u001b[39;00m\n\u001b[1;32m---> 28\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mmse_loss\u001b[39m(y_true, y_pred):\n\u001b[0;32m 29\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m np\u001b[38;5;241m.\u001b[39mmean((y_true \u001b[38;5;241m-\u001b[39m y_pred) \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39m \u001b[38;5;241m2\u001b[39m)\n\u001b[0;32m 31\u001b[0m \u001b[38;5;66;03m# Backward pass\u001b[39;00m\n", 209 | "\u001b[1;31mKeyboardInterrupt\u001b[0m: " 210 | ] 211 | } 212 | ], 213 | "source": [ 214 | "# Training the autoencoder\n", 215 | "# Training the autoencoder\n", 216 | "epochs = 20\n", 217 | "learning_rate = 0.001\n", 218 | "batch_size = 64\n", 219 | "\n", 220 | "for epoch in range(epochs):\n", 221 | " for i in range(0, X_train.shape[0], batch_size):\n", 222 | " X_batch = X_train[i:i+batch_size]\n", 223 | " Z1, A1, Z2 = forward(X_batch)\n", 224 | " backward(X_batch, Z1, A1, Z2, learning_rate)\n", 225 | " \n", 226 | " # Calculate loss on training data\n", 227 | " _, _, output = forward(X_train)\n", 228 | " loss = mse_loss(X_train, output)\n", 229 | " print(f'Epoch {epoch + 1}, Loss: {loss:.4f}')\n" 230 | ] 231 | }, 232 | { 233 | "cell_type": "markdown", 234 | "metadata": {}, 235 | "source": [ 236 | "### Step 6: Model Testing by Reconstruction" 237 | ] 238 | }, 239 | { 240 | "cell_type": "code", 241 | "execution_count": 7, 242 | "metadata": {}, 243 | "outputs": [ 244 | { 245 | "data": { 246 | "image/png": "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AABZNGww9f333x8qs+65557Q+jt16uTKxo4dW1hWYSDjx48Prd964YUXXNnChQtdmQ3aVmEjKowRLdepp55aWL7mmmtcnTe96U2ubN26dYXlK6+80tV5/vnnq2wd9lb9+/d3ZYcddlhhWY1hO3bsyNUk1MFxxx1XWD744INdHRXiVjbYTQVl2TC7rVu3ujonnHCCK/vCF75Q8e/9n//zf1zZjTfeWPF3yOuqq64qLKuQQxtsmZIOLW9u6r7NnkcEHyISUqzY8RCN6dvf/rYr+/d//3dXZp81f//732drU60dc8wxrqx79+6F5V/84heuzq9//etcTUJAv379Cssf/vCHQ797+umnXdnatWsLyyeddFJoXR07diwsq3Ds3/zmN67s2WefDa0fzUe9o/jtb3/rymwQ9de//nVXJxJsr6gQamXFihWl1o+W68c//nFhWYWfH3jggaF12XfRs2bNcnU+//nPuzL1Htg66qijXJl6Rv3Zz35WWLbvr1Py43JKKd1www2F5T/84Q+uzvr16ys1s2b4nxAAAAAAAAAAACALPkIAAAAAAAAAAIAs+AgBAAAAAAAAAACy4CMEAAAAAAAAAADIomGDqXPbvHmzK3vggQcq/i4Sjh2lQulsYLYKPLn11ltr1gbUnw37VQFPiu0HDz30UM3aBNggVaU5A4yQnwoj/93vfldYjoZ3KcuXLy8sq1Csr3zlK67s+eeff8PrTimlCy+80JV17dq1sPzNb37T1Xnzm9/syn74wx8Wll9++eWKbULM2Wef7cpOOeWUwvKiRYtcnWnTpmVrUzVUILoNon7wwQddnS1btmRqERrRscceW7HOSy+95MpU/0Lj2bVrlytTgfSrV68uLKtj3tzatGnjylTY5sc//nFXZrf7/PPPr13DUBM2yLR9+/auziOPPOLK1HOBvV963/ve5+qovjNo0KDCco8ePVydP//5z67sHe94hyvbtGmTK0M+7dq1KyxfeeWVrs6pp57qyjZs2FBY/ta3vuXqRO73gZT0s9rll1/uyj7ykY8Ullu1auXqqPcZN954oyu77rrrCss7duyo2M6oLl26uLLWrVu7squvvrqwfM8997g6/fr1q1m7cuF/QgAAAAAAAAAAgCz4CAEAAAAAAAAAALLgIwQAAAAAAAAAAMiCjxAAAAAAAAAAACCLvTaYurl169bNlf3o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", 247 | "text/plain": [ 248 | "
" 249 | ] 250 | }, 251 | "metadata": {}, 252 | "output_type": "display_data" 253 | } 254 | ], 255 | "source": [ 256 | "# Testing and visualizing the reconstructed images\n", 257 | "def plot_reconstructed_images(original, reconstructed, n=10):\n", 258 | " plt.figure(figsize=(20, 4))\n", 259 | " for i in range(n):\n", 260 | " # Original images\n", 261 | " ax = plt.subplot(2, n, i + 1)\n", 262 | " plt.imshow(original[i].reshape(28, 28), cmap=\"gray\")\n", 263 | " plt.axis(\"off\")\n", 264 | "\n", 265 | " # Reconstructed images\n", 266 | " ax = plt.subplot(2, n, i + 1 + n)\n", 267 | " plt.imshow(reconstructed[i].reshape(28, 28), cmap=\"gray\")\n", 268 | " plt.axis(\"off\")\n", 269 | " plt.show()\n", 270 | "\n", 271 | "# Test on x_test data\n", 272 | "_, _, x_test_reconstructed = forward(X_test)\n", 273 | "plot_reconstructed_images(X_test, x_test_reconstructed)" 274 | ] 275 | } 276 | ], 277 | "metadata": { 278 | "kernelspec": { 279 | "display_name": "base", 280 | "language": "python", 281 | "name": "python3" 282 | }, 283 | "language_info": { 284 | "codemirror_mode": { 285 | "name": "ipython", 286 | "version": 3 287 | }, 288 | "file_extension": ".py", 289 | "mimetype": "text/x-python", 290 | "name": "python", 291 | "nbconvert_exporter": "python", 292 | "pygments_lexer": "ipython3", 293 | "version": "3.11.5" 294 | } 295 | }, 296 | "nbformat": 4, 297 | "nbformat_minor": 2 298 | } 299 | -------------------------------------------------------------------------------- /Notebooks/Feature Engineering.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Feature Engineering\n", 8 | "\n", 9 | "by Rina Buoy, PhD" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "### 1. Generating dummy data" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 3, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "# Import necessary libraries\n", 26 | "import pandas as pd\n", 27 | "import numpy as np\n", 28 | "\n", 29 | "# Sample dataset creation\n", 30 | "data = {\n", 31 | " 'age': [25, 32, 47, 51, 62],\n", 32 | " 'salary': [50000, 60000, 120000, 90000, 140000],\n", 33 | " 'gender': ['Male', 'Female', 'Female', 'Male', 'Female'],\n", 34 | " 'city': ['Osaka', 'Tokyo', 'Tokyo', 'Osaka', 'Kyoto'],\n", 35 | " 'bought_product': [1, 0, 1, 0, 1] # Target variable\n", 36 | "}\n", 37 | "\n", 38 | "\n", 39 | "# Create a DataFrame\n", 40 | "df = pd.DataFrame(data)\n" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": {}, 46 | "source": [ 47 | "### 2. Feature Engineering" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 4, 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "\n", 57 | "\n", 58 | "# Step 1: Feature Engineering - Adding new features\n", 59 | "# Add a new feature: salary per age\n", 60 | "df['salary_per_age'] = df['salary'] / df['age']\n", 61 | "\n", 62 | "# Step 2: Encoding Categorical Data\n", 63 | "# Manually encode 'gender' (Male -> 1, Female -> 0)\n", 64 | "df['gender_encoded'] = df['gender'].map({'Male': 1, 'Female': 0})\n", 65 | "\n", 66 | "# Manually encode 'city' using One-Hot Encoding\n", 67 | "df = pd.get_dummies(df, columns=['city'], drop_first=True) # Drop the first to avoid multicollinearity\n", 68 | "\n", 69 | "# Step 3: Normalization (Min-Max Scaling)\n", 70 | "def min_max_scaling(series):\n", 71 | " return (series - series.min()) / (series.max() - series.min())\n", 72 | "\n", 73 | "# Apply normalization to 'salary' and 'salary_per_age'\n", 74 | "df['salary'] = min_max_scaling(df['salary'])\n", 75 | "df['salary_per_age'] = min_max_scaling(df['salary_per_age'])\n", 76 | "\n", 77 | "# Step 4: Generating Polynomial Features\n", 78 | "def add_polynomial_features(df, columns, degree=2):\n", 79 | " for col in columns:\n", 80 | " for power in range(2, degree + 1):\n", 81 | " df[f'{col}_pow_{power}'] = df[col] ** power\n", 82 | " return df\n", 83 | "\n", 84 | "# Add polynomial features for 'age' and 'salary'\n", 85 | "df = add_polynomial_features(df, ['age', 'salary'], degree=2)\n", 86 | "\n", 87 | "\n" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": {}, 93 | "source": [ 94 | "### 3. Final Features" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 5, 100 | "metadata": {}, 101 | "outputs": [ 102 | { 103 | "name": "stdout", 104 | "output_type": "stream", 105 | "text": [ 106 | "Transformed Dataset:\n", 107 | " age salary gender bought_product salary_per_age gender_encoded \\\n", 108 | "0 25 0.000000 Male 1 0.298413 1 \n", 109 | "1 32 0.111111 Female 0 0.139881 0 \n", 110 | "2 47 0.777778 Female 1 1.000000 0 \n", 111 | "3 51 0.444444 Male 0 0.000000 1 \n", 112 | "4 62 1.000000 Female 1 0.625704 0 \n", 113 | "\n", 114 | " city_Osaka city_Tokyo age_pow_2 salary_pow_2 \n", 115 | "0 True False 625 0.000000 \n", 116 | "1 False True 1024 0.012346 \n", 117 | "2 False True 2209 0.604938 \n", 118 | "3 True False 2601 0.197531 \n", 119 | "4 False False 3844 1.000000 \n", 120 | "\n", 121 | "Features (X):\n", 122 | " age salary gender_encoded city_Osaka city_Tokyo age_pow_2 \\\n", 123 | "0 25 0.000000 1 True False 625 \n", 124 | "1 32 0.111111 0 False True 1024 \n", 125 | "2 47 0.777778 0 False True 2209 \n", 126 | "3 51 0.444444 1 True False 2601 \n", 127 | "4 62 1.000000 0 False False 3844 \n", 128 | "\n", 129 | " salary_pow_2 \n", 130 | "0 0.000000 \n", 131 | "1 0.012346 \n", 132 | "2 0.604938 \n", 133 | "3 0.197531 \n", 134 | "4 1.000000 \n", 135 | "\n", 136 | "Target (y):\n", 137 | "0 1\n", 138 | "1 0\n", 139 | "2 1\n", 140 | "3 0\n", 141 | "4 1\n", 142 | "Name: bought_product, dtype: int64\n" 143 | ] 144 | } 145 | ], 146 | "source": [ 147 | "# Final dataset after feature engineering, normalization, and encoding\n", 148 | "print(\"Transformed Dataset:\")\n", 149 | "print(df)\n", 150 | "\n", 151 | "# Optional: Split features (X) and target (y)\n", 152 | "X = df.drop(columns=['bought_product', 'gender', 'salary_per_age']) # Removing raw and unneeded columns\n", 153 | "y = df['bought_product']\n", 154 | "\n", 155 | "print(\"\\nFeatures (X):\")\n", 156 | "print(X)\n", 157 | "\n", 158 | "print(\"\\nTarget (y):\")\n", 159 | "print(y)" 160 | ] 161 | } 162 | ], 163 | "metadata": { 164 | "kernelspec": { 165 | "display_name": "base", 166 | "language": "python", 167 | "name": "python3" 168 | }, 169 | "language_info": { 170 | "codemirror_mode": { 171 | "name": "ipython", 172 | "version": 3 173 | }, 174 | "file_extension": ".py", 175 | "mimetype": "text/x-python", 176 | "name": "python", 177 | "nbconvert_exporter": "python", 178 | "pygments_lexer": "ipython3", 179 | "version": "3.11.5" 180 | } 181 | }, 182 | "nbformat": 4, 183 | "nbformat_minor": 2 184 | } 185 | -------------------------------------------------------------------------------- /Notebooks/Lab 3 - Gradient Descent.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Rina Buoy, PhD" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## Generate Training Datasets" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "$\\hat{y}= \\theta_0 + \\theta_1x_1 + \\epsilon$\n", 22 | "\n", 23 | "$\\theta_0 = 4$\n", 24 | "\n", 25 | "$\\theta_1 = 3$\n" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": 5, 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "import numpy as np\n", 35 | "import matplotlib.pyplot as plt\n", 36 | "\n", 37 | "# Generate some random data for demonstration\n", 38 | "np.random.seed(0)\n", 39 | "X = 2 * np.random.rand(100, 1) # epsilon \n", 40 | "y = 4 + 3 * X + np.random.randn(100, 1) # y_hat\n", 41 | "\n", 42 | "\n" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "\n", 52 | "plt.scatter(X, y)\n", 53 | "plt.xlabel('X')\n", 54 | "plt.ylabel('y')" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "## Gradient Descent" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "# Define the learning rate and number of iterations\n", 76 | "learning_rate = 0.1\n", 77 | "n_iterations = 10\n", 78 | "\n", 79 | "# Add a column of ones to X for the bias term\n", 80 | "X_b = np.c_[np.ones((100, 1)), X]\n", 81 | "\n", 82 | "# Function to compute the loss (mean squared error)\n", 83 | "def compute_loss(theta, X, y):\n", 84 | " error = y - X.dot(theta)\n", 85 | " loss = np.mean(error**2)\n", 86 | " return loss\n", 87 | "\n", 88 | "# Function to compute the gradients\n", 89 | "def compute_gradients(theta, X, y):\n", 90 | " gradients = -2/X.shape[0] * X.T.dot(y - X.dot(theta))\n", 91 | " return gradients\n", 92 | "\n", 93 | "# Function to perform gradient descent\n", 94 | "def gradient_descent(X, y, learning_rate, n_iterations):\n", 95 | " theta = np.random.randn(2,1) # Random initialization of parameters\n", 96 | " theta_trajectory = []\n", 97 | " losses = []\n", 98 | " for iteration in range(n_iterations):\n", 99 | " gradients = compute_gradients(theta, X, y)\n", 100 | " theta = theta - learning_rate * gradients\n", 101 | " theta_trajectory.append(theta)\n", 102 | " losses.append(compute_loss(theta, X, y))\n", 103 | "\n", 104 | " plt.figure()\n", 105 | "\n", 106 | " plt.scatter(X[:,-1], y)\n", 107 | " plt.plot(X[:,-1], X.dot(theta), color='red')\n", 108 | " plt.xlabel('X')\n", 109 | " plt.ylabel('y')\n", 110 | " plt.title(f'Gradient Descent Linear Regression : Iter {iteration}')\n", 111 | " return theta,theta_trajectory,losses\n", 112 | "\n", 113 | "# Perform gradient descent\n", 114 | "theta,theta_trajectory,losses = gradient_descent(X_b, y, learning_rate, n_iterations)\n", 115 | "\n", 116 | "# Print the parameters obtained by gradient descent\n", 117 | "print(\"Parameters obtained by gradient descent:\", theta)\n", 118 | "\n", 119 | "# Plot the data and the linear regression line\n", 120 | "\n", 121 | "#plt.show()\n" 122 | ] 123 | }, 124 | { 125 | "cell_type": "markdown", 126 | "metadata": {}, 127 | "source": [ 128 | "## Loss Surface" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": null, 134 | "metadata": {}, 135 | "outputs": [], 136 | "source": [ 137 | "\n", 138 | "\n", 139 | "theta0_vals = np.linspace(-10, 10, 100)\n", 140 | "theta1_vals = np.linspace(-10, 10, 100)\n", 141 | "theta0_mesh, theta1_mesh = np.meshgrid(theta0_vals, theta1_vals)\n", 142 | "loss_surface = np.zeros_like(theta0_mesh)\n", 143 | "for i in range(len(theta0_vals)):\n", 144 | " for j in range(len(theta1_vals)):\n", 145 | " theta_ij = np.array([[theta0_mesh[i,j]], [theta1_mesh[i,j]]])\n", 146 | " loss_surface[i,j] = compute_loss(theta_ij, X_b, y)\n", 147 | "\n", 148 | "fig = plt.figure()\n", 149 | "ax = fig.add_subplot(111, projection='3d')\n", 150 | "ax.plot_surface(theta0_mesh, theta1_mesh, loss_surface, cmap='viridis')\n", 151 | "ax.set_xlabel('Theta 0')\n", 152 | "ax.set_ylabel('Theta 1')\n", 153 | "ax.set_zlabel('Loss')\n", 154 | "ax.set_title('Loss Surface')\n", 155 | "plt.show()" 156 | ] 157 | }, 158 | { 159 | "cell_type": "markdown", 160 | "metadata": {}, 161 | "source": [ 162 | "## Learning Trajectory" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": null, 168 | "metadata": {}, 169 | "outputs": [], 170 | "source": [ 171 | "# Plot the loss surface\n", 172 | "theta0_vals = np.linspace(-10, 10, 100)\n", 173 | "theta1_vals = np.linspace(-10, 10, 100)\n", 174 | "theta0_mesh, theta1_mesh = np.meshgrid(theta0_vals, theta1_vals)\n", 175 | "loss_surface = np.zeros_like(theta0_mesh)\n", 176 | "for i in range(len(theta0_vals)):\n", 177 | " for j in range(len(theta1_vals)):\n", 178 | " theta_ij = np.array([[theta0_mesh[i,j]], [theta1_mesh[i,j]]])\n", 179 | " loss_surface[i,j] = compute_loss(theta_ij, X_b, y)\n", 180 | "\n", 181 | "# Plot the trajectory\n", 182 | "theta_trajectory = np.array(theta_trajectory).squeeze()\n", 183 | "fig = plt.figure()\n", 184 | "ax = fig.add_subplot(111, projection='3d')\n", 185 | "ax.plot_surface(theta0_mesh, theta1_mesh, loss_surface, cmap='viridis', alpha=0.5)\n", 186 | "ax.plot(theta_trajectory[:,0], theta_trajectory[:,1], losses, color='red', marker='o')\n", 187 | "ax.set_xlabel('Theta 0')\n", 188 | "ax.set_ylabel('Theta 1')\n", 189 | "ax.set_zlabel('Loss')\n", 190 | "ax.set_title('Gradient Descent Trajectory')\n", 191 | "plt.show()" 192 | ] 193 | }, 194 | { 195 | "cell_type": "markdown", 196 | "metadata": {}, 197 | "source": [ 198 | "## Mini-Batch Training" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": null, 204 | "metadata": {}, 205 | "outputs": [], 206 | "source": [ 207 | "\n", 208 | "\n", 209 | "# Define the learning rate and number of iterations\n", 210 | "learning_rate = 0.1\n", 211 | "n_iterations = 2\n", 212 | "batch_size = 20 # Mini-batch size\n", 213 | "\n", 214 | "# Function to compute the loss (mean squared error)\n", 215 | "def compute_loss(theta, X, y):\n", 216 | " error = y - X.dot(theta)\n", 217 | " loss = np.mean(error**2)\n", 218 | " return loss\n", 219 | "\n", 220 | "# Function to compute the gradients using a mini-batch\n", 221 | "def compute_gradients(theta, X_batch, y_batch):\n", 222 | " gradients = -2/X_batch.shape[0] * X_batch.T.dot(y_batch - X_batch.dot(theta))\n", 223 | " return gradients\n", 224 | "\n", 225 | "# Function to perform gradient descent with mini-batch training\n", 226 | "def gradient_descent(X, y, learning_rate, n_iterations, batch_size):\n", 227 | " theta = np.random.randn(2,1) # Random initialization of parameters\n", 228 | " theta_trajectory = [theta]\n", 229 | " loss = compute_loss(theta, X, y)\n", 230 | " losses = [loss ]\n", 231 | " m = len(X)\n", 232 | " for iteration in range(n_iterations):\n", 233 | " shuffled_indices = np.random.permutation(m)\n", 234 | " X_shuffled = X[shuffled_indices]\n", 235 | " y_shuffled = y[shuffled_indices]\n", 236 | " for i in range(0, m, batch_size):\n", 237 | " X_batch = X_shuffled[i:i+batch_size]\n", 238 | " y_batch = y_shuffled[i:i+batch_size]\n", 239 | " gradients = compute_gradients(theta, X_batch, y_batch)\n", 240 | " theta = theta - learning_rate * gradients\n", 241 | " loss = compute_loss(theta, X, y)\n", 242 | " theta_trajectory.append(theta)\n", 243 | " losses.append(loss)\n", 244 | " plt.figure()\n", 245 | "\n", 246 | " plt.scatter(X[:,-1], y)\n", 247 | " plt.plot(X[:,-1], X.dot(theta), color='red')\n", 248 | " plt.xlabel('X')\n", 249 | " plt.ylabel('y')\n", 250 | " plt.title(f'Gradient Descent Linear Regression : Iteration {iteration} Batch {i}')\n", 251 | " \n", 252 | " return theta_trajectory, losses\n", 253 | "\n", 254 | "# Perform gradient descent with mini-batch training\n", 255 | "theta_trajectory,losses = gradient_descent(X_b, y, learning_rate, n_iterations, batch_size)\n", 256 | "\n", 257 | "\n" 258 | ] 259 | }, 260 | { 261 | "cell_type": "code", 262 | "execution_count": null, 263 | "metadata": {}, 264 | "outputs": [], 265 | "source": [ 266 | "# Plot the loss surface\n", 267 | "theta0_vals = np.linspace(-10, 10, 100)\n", 268 | "theta1_vals = np.linspace(-10, 10, 100)\n", 269 | "theta0_mesh, theta1_mesh = np.meshgrid(theta0_vals, theta1_vals)\n", 270 | "loss_surface = np.zeros_like(theta0_mesh)\n", 271 | "for i in range(len(theta0_vals)):\n", 272 | " for j in range(len(theta1_vals)):\n", 273 | " theta_ij = np.array([[theta0_mesh[i,j]], [theta1_mesh[i,j]]])\n", 274 | " loss_surface[i,j] = compute_loss(theta_ij, X_b, y)\n", 275 | "\n", 276 | "# Plot the trajectory\n", 277 | "theta_trajectory = np.array(theta_trajectory).squeeze()\n", 278 | "fig = plt.figure()\n", 279 | "ax = fig.add_subplot(111, projection='3d')\n", 280 | "ax.plot_surface(theta0_mesh, theta1_mesh, loss_surface, cmap='viridis', alpha=0.5)\n", 281 | "ax.plot(theta_trajectory[:,0], theta_trajectory[:,1], losses, color='red', marker='o')\n", 282 | "ax.set_xlabel('Theta 0')\n", 283 | "ax.set_ylabel('Theta 1')\n", 284 | "ax.set_zlabel('Loss')\n", 285 | "ax.set_title('Gradient Descent Trajectory with Mini-Batch Training')\n", 286 | "plt.show()" 287 | ] 288 | } 289 | ], 290 | "metadata": { 291 | "kernelspec": { 292 | "display_name": "base", 293 | "language": "python", 294 | "name": "python3" 295 | }, 296 | "language_info": { 297 | "codemirror_mode": { 298 | "name": "ipython", 299 | "version": 3 300 | }, 301 | "file_extension": ".py", 302 | "mimetype": "text/x-python", 303 | "name": "python", 304 | "nbconvert_exporter": "python", 305 | "pygments_lexer": "ipython3", 306 | "version": "3.11.5" 307 | } 308 | }, 309 | "nbformat": 4, 310 | "nbformat_minor": 2 311 | } 312 | -------------------------------------------------------------------------------- /Notebooks/Neural Network from Scratch.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Neural Network From Scratch\n", 8 | "\n", 9 | "by Rina Buoy, PhD" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "This simple neural network model demonstrates:\n", 17 | "\n", 18 | "1. Network design with one hidden layer.\n", 19 | "\n", 20 | "2. Forward propagation to compute activations.\n", 21 | "\n", 22 | "3. Binary cross-entropy loss computation.\n", 23 | "\n", 24 | "4. Backward propagation for gradient computation.\n", 25 | "\n", 26 | "5. Parameter updates using gradient descent.\n", 27 | "\n", 28 | "This setup can classify points into two classes based on the relationship between X[:, 0] and X[:, 1]. Adjusting the number of nodes, layers, or epochs can improve accuracy." 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "metadata": {}, 34 | "source": [ 35 | "#### Step 1: Set up the imports" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 2, 41 | "metadata": {}, 42 | "outputs": [], 43 | "source": [ 44 | "import numpy as np\n", 45 | "import matplotlib.pyplot as plt" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "metadata": {}, 51 | "source": [ 52 | "### Step 2: Generate Sample Data" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 3, 58 | "metadata": {}, 59 | "outputs": [], 60 | "source": [ 61 | "np.random.seed(42) # For reproducibility\n", 62 | "\n", 63 | "# Generate 100 random points for two classes\n", 64 | "X = np.random.randn(100, 2)\n", 65 | "y = (X[:, 0] * X[:, 1] > 0).astype(int) # Points where x*y > 0 are in class 1\n", 66 | "y = y.reshape(-1, 1)" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 4, 72 | "metadata": {}, 73 | "outputs": [ 74 | { 75 | "data": { 76 | "text/plain": [ 77 | "Text(0.5, 1.0, 'A binary classification with 2 features')" 78 | ] 79 | }, 80 | "execution_count": 4, 81 | "metadata": {}, 82 | "output_type": "execute_result" 83 | }, 84 | { 85 | "data": { 86 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjUAAAHFCAYAAAAKbwgcAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAACASklEQVR4nO3ddZxUVRvA8d+5M9vJ7tKxICAgDSLSKa2CiRiooIiAgYlBWNgtoShYqK+KKIooJaCAhISggjRK13bN3PP+MbCy7M5sTu7z/XxGnHvPvfeZnd25z5xUWmuNEEIIIYSfM7wdgBBCCCFEWZCkRgghhBABQZIaIYQQQgQESWqEEEIIERAkqRFCCCFEQJCkRgghhBABQZIaIYQQQgQESWqEEEIIERAkqRFCCCFEQJCkRnjM66+/jlKKJk2aFOu42rVrM2DAgELL7dmzB6UUs2bNKmGEvm/ixIkopbwag1KKiRMn5tm2ePFiLrzwQiIiIlBKMXfuXGbNmoVSij179rgljj/++IOJEycWeP6bb76Z2rVru+W67lDQz2r27Nm8+uqr+cqe+T1/8cUXS3St9evXM2rUKJo2bUpUVBSVK1emZ8+eLFmypMjn+Oyzz2jcuDFhYWEopdi4cWOJYnElPT2diRMn8tNPP5X5uUXgkqRGeMx7770HwNatW/n111/L/PxVq1Zl1apV9O/fv8zPLf6zatUqhg8fnvtca80111xDUFAQ33zzDatWraJLly7079+fVatWUbVqVbfE8ccffzBp0qQCk5rHH3+cr776yi3XdYeCflbOkprS+uSTT1izZg233norX3/9NTNmzCAkJIQePXrwwQcfFHr80aNHufHGG6lbty4LFixg1apVnH/++WUeZ3p6OpMmTZKkRhSL1dsBiPJh3bp1bNq0if79+/Pdd9/x7rvv0rZt2zK9RkhICBdffHGZnrOo0tPTCQ8P98q1Pe3cn/GBAwc4ceIEgwYNokePHnn2VaxY0ZOh5apbt65XrltSFStW9NjP6sEHH8xXy9OvXz9atWrFE088wU033eTy+O3bt5OTk8MNN9xAly5d3BmqW2ityczMJCwszNuhCDeQmhrhEe+++y4Azz77LO3bt+fTTz8lPT29WOf46quvaNasGaGhoZx33nm8/vrrefYX1Px0prlm69atXHfddcTExFC5cmVuvfVWkpKS8hz/1ltv0blzZypVqkRERARNmzbl+eefJycnJ0+5rl270qRJE5YvX0779u0JDw/n1ltvZdiwYcTFxRX4urp3707jxo0LfY0LFiygR48exMTEEB4eTqNGjZg8ebLLYz777DN69epF1apVCQsLo1GjRjz88MOkpaXlKbdr1y4GDx5MtWrVCAkJoXLlyvTo0SNP08GSJUvo2rUr8fHxhIWFUatWLa688so8r+ns5qeJEydSo0YNAB566CGUUrnNPs6anwp7jevWrWPw4MHUrl2bsLAwateuzXXXXcfevXtzy8yaNYurr74agG7duqGUyvPeF9T8lJmZybhx46hTpw7BwcFUr16dUaNGcerUqTzlzjR3LliwgFatWhEWFkbDhg1zaxpdadOmTb6awqZNm6KUYu3atbnb5syZg1KK33//vcCfVdeuXfnuu+/Yu3dv7msrqNnx5Zdfpk6dOkRGRtKuXTtWr15daIyVKlXKt81isdC6dWv279/v8tibb76Zjh07AnDttdeilKJr1665+9etW8dll11GXFwcoaGhtGzZkv/97395znH06FHuvPNOLrjgAiIjI6lUqRLdu3dnxYoVuWX27NmTm+RNmjQp9/XffPPNuXEU1LxYUPOsUorRo0czbdo0GjVqREhICO+//z4Af//9N0OGDKFSpUqEhITQqFEj3nrrrTzHm6bJU089RYMGDQgLCyM2NpZmzZrx2muvufxZCe+QmhrhdhkZGXzyySe0adOGJk2acOuttzJ8+HA+//xzhg4dWqRzbNy4kXvuuYeJEydSpUoVPv74Y+6++26ys7O5//77Cz3+yiuv5Nprr2XYsGH8/vvvjBs3DiDPjWrnzp0MGTIk96a3adMmnn76af766698N7SDBw9yww038OCDD/LMM89gGAaxsbG89957zJ49O0/zzB9//MHSpUvzfVie69133+W2226jS5cuTJs2jUqVKrF9+3a2bNni8ri///6bfv36cc899xAREcFff/3Fc889x5o1a/L0k+jXrx92u53nn3+eWrVqcezYMVauXJl7U9+zZw/9+/enU6dOvPfee8TGxvLvv/+yYMECsrOzC6yJGj58OM2bN+eKK65gzJgxDBkyhJCQkFK9xj179tCgQQMGDx5MXFwcBw8eZOrUqbRp04Y//viDhIQE+vfvzzPPPMMjjzzCW2+9RatWrQDnNTRaawYOHMjixYsZN24cnTp1YvPmzUyYMIFVq1axatWqPHFv2rSJ++67j4cffpjKlSszY8YMhg0bRr169ejcubPT19ezZ0/efPNNcnJyCAoK4vDhw2zZsoWwsDAWLlxImzZtAFi0aBGVK1emadOmBZ5nypQp3H777ezcudNpM9pbb71Fw4YNc5uoHn/8cfr168fu3buJiYlxGmNBbDYbK1asKDTxfvzxx7nooosYNWoUzzzzDN26dSM6OhqApUuX0qdPH9q2bcu0adOIiYnh008/5dprryU9PT03ITlx4gQAEyZMoEqVKqSmpvLVV1/RtWtXFi9eTNeuXalatSoLFiygT58+DBs2LPfvqaS1WXPnzmXFihWMHz+eKlWqUKlSJf744w/at29PrVq1eOmll6hSpQo//PADd911F8eOHWPChAkAPP/880ycOJHHHnuMzp07k5OTw19//ZUvGRY+QgvhZh988IEG9LRp07TWWqekpOjIyEjdqVOnIh2fmJiolVJ648aNebZfcsklOjo6WqelpWmttd69e7cG9MyZM3PLTJgwQQP6+eefz3PsnXfeqUNDQ7VpmgVe026365ycHP3BBx9oi8WiT5w4kbuvS5cuGtCLFy/Od1yXLl10ixYt8mwbOXKkjo6O1ikpKU5fY0pKio6OjtYdO3Z0GtPZr8cZ0zR1Tk6OXrZsmQb0pk2btNZaHzt2TAP61VdfdXrsF198oYF8P+dzAXrChAm5z8/83F944YU85WbOnKkBvXv37mK9xnPZbDadmpqqIyIi9GuvvZa7/fPPP9eAXrp0ab5jhg4dqhMTE3OfL1iwoMDfg88++0wD+u23387dlpiYqENDQ/XevXtzt2VkZOi4uDg9YsQIl7EuWrRIA3r58uVaa60/+ugjHRUVpe+8807drVu33HL169fXQ4YMyX1+7s9Ka6379++f5zWccebn3bRpU22z2XK3r1mzRgP6k08+cRljQR599FEN6Llz5xZadunSpRrQn3/+eZ7tDRs21C1bttQ5OTl5tg8YMEBXrVpV2+32As9ns9l0Tk6O7tGjhx40aFDu9qNHj+b7XTvj3Pf3jIL+PgAdExOT529Ya6179+6ta9SooZOSkvJsHz16tA4NDc0tP2DAgHx/08J3SfOTcLt3332XsLAwBg8eDEBkZCRXX301K1as4O+//y7SORo3bkzz5s3zbBsyZAjJycn89ttvhR5/2WWX5XnerFkzMjMzOXLkSO62DRs2cNlllxEfH4/FYiEoKIibbroJu93O9u3b8xxfoUIFunfvnu86d999Nxs3buSXX34BIDk5mQ8//JChQ4cSGRnpNL6VK1eSnJzMnXfeWezRTbt27WLIkCFUqVIlN+4zfR3+/PNPAOLi4qhbty4vvPACL7/8Mhs2bMA0zTznadGiBcHBwdx+++28//777Nq1q1hxFKaorzE1NZWHHnqIevXqYbVasVqtREZGkpaWlvt6iutMjdWZ2oIzrr76aiIiIli8eHGe7S1atKBWrVq5z0NDQzn//PPzNIEVpEOHDoSGhrJo0SIAFi5cSNeuXenTpw8rV64kPT2d/fv38/fff9OzZ88SvZYz+vfvj8ViyX3erFkzgEJjPNeMGTN4+umnue+++7j88stLFMuOHTv466+/uP766wFHzc+ZR79+/Th48CDbtm3LLT9t2jRatWpFaGgoVquVoKAgFi9eXOL3tzDdu3enQoUKuc8zMzNZvHgxgwYNIjw8PF+8mZmZuU15F110EZs2beLOO+/khx9+IDk52S0xirIhSY1wqx07drB8+XL69++P1ppTp05x6tQprrrqKoAi9VMAqFKlitNtx48fL/T4+Pj4PM/PNDVkZGQAsG/fPjp16sS///7La6+9xooVK1i7dm1uk9GZcmc4G9Fz+eWXU7t27dzjZs2aRVpaGqNGjXIZ39GjRwFy+6cUVWpqKp06deLXX3/lqaee4qeffmLt2rXMmTMnT9xKKRYvXkzv3r15/vnnadWqFRUrVuSuu+4iJSUFcDTdLFq0iEqVKjFq1Cjq1q1L3bp1y6zvQFFf45AhQ3jzzTcZPnw4P/zwA2vWrGHt2rVUrFgx3/tQVMePH8dqteZrvlBKUaVKlXy/Q+f+voDjd6aw64eGhtKhQ4fcpGbx4sVccskldO3aFbvdzooVK1i4cCFAqZOawn6ni2LmzJmMGDGC22+/nRdeeKHEsRw+fBiA+++/n6CgoDyPO++8E4Bjx44Bjn5AI0eOpG3btnz55ZesXr2atWvX0qdPnxK/v4U59+/1+PHj2Gw23njjjXzx9uvXL0+848aN48UXX2T16tX07duX+Ph4evTowbp169wSqygd6VMj3Oq9995Da80XX3zBF198kW//+++/z1NPPZXnG2dBDh065HRbQTeg4po7dy5paWnMmTOHxMTE3O3O5t9wVtNgGAajRo3ikUce4aWXXmLKlCn06NGDBg0auLz+mZvtP//8U6y4lyxZwoEDB/jpp5/yjEQpqL0/MTExt8P29u3b+d///sfEiRPJzs5m2rRpAHTq1IlOnTpht9tZt24db7zxBvfccw+VK1fOrWkrqaK8xqSkJL799lsmTJjAww8/nLs9Kysrty9GScTHx2Oz2Th69GiexEZrzaFDh3L7upSFHj16MH78eNasWcM///zDJZdcQlRUFG3atGHhwoUcOHCA888/n5o1a5bZNUti5syZDB8+nKFDhzJt2rRSzX+UkJAAOBKAK664osAyZ/4GPvroI7p27crUqVPz7D+TXBdFaGgoWVlZ+bafSUTOde5rq1ChAhaLhRtvvNHpF446deoAYLVaGTt2LGPHjuXUqVMsWrSIRx55hN69e7N///5yM+rRX0hNjXAbu93O+++/T926dVm6dGm+x3333cfBgwf5/vvvCz3X1q1b2bRpU55ts2fPJioqKreTaGmc+dA7u7Oo1pp33nmn2OcaPnw4wcHBXH/99Wzbto3Ro0cXekz79u2JiYlh2rRpaK1LFTfA9OnTXR53/vnn89hjj9G0adMCm+8sFgtt27bNrXEqShNfYYryGpVSaK3zvZ4ZM2Zgt9vzbCtOzcSZoeYfffRRnu1ffvklaWlp+Yail0bPnj2x2Ww8/vjj1KhRg4YNG+ZuX7RoEUuWLClSLU1RaoZKatasWQwfPpwbbriBGTNmlHpCxwYNGlC/fn02bdrEhRdeWOAjKioKcLzH576/mzdvZtWqVXm2uXp/a9euzZEjR3JriACys7P54YcfihRveHg43bp1Y8OGDTRr1qzAeAv6shQbG8tVV13FqFGjOHHihNsmlhQlJzU1wm2+//57Dhw4wHPPPZdn2OcZTZo04c033+Tdd98tdMbgatWqcdlllzFx4kSqVq3KRx99xMKFC3nuuefK5JvSJZdcQnBwMNdddx0PPvggmZmZTJ06lZMnTxb7XLGxsdx0001MnTqVxMRELr300kKPiYyM5KWXXmL48OH07NmT2267jcqVK7Njxw42bdrEm2++WeBx7du3p0KFCtxxxx1MmDCBoKAgPv7443wJ4ObNmxk9ejRXX3019evXJzg4mCVLlrB58+bcGpFp06axZMkS+vfvT61atcjMzMxtHixtU0lRX2N0dDSdO3fmhRdeICEhgdq1a7Ns2TLeffddYmNj85zvzMzUb7/9NlFRUYSGhlKnTp0Cb0aXXHIJvXv35qGHHiI5OZkOHTrkjn5q2bIlN954Y6lf3xmtW7emQoUK/Pjjj9xyyy2523v27MmTTz6Z+/+Fadq0KXPmzGHq1Km0bt0awzC48MILSx3f559/zrBhw2jRogUjRoxgzZo1efa3bNnS5Qg2Z6ZPn07fvn3p3bs3N998M9WrV+fEiRP8+eef/Pbbb3z++ecADBgwgCeffJIJEybQpUsXtm3bxhNPPEGdOnWw2Wy554uKiiIxMZGvv/6aHj16EBcXl/s7ce211zJ+/HgGDx7MAw88QGZmJq+//nq+xNeV1157jY4dO9KpUydGjhxJ7dq1SUlJYceOHcybNy+3H9all15KkyZNuPDCC6lYsSJ79+7l1VdfJTExkfr16xf75yTczIudlEWAGzhwoA4ODtZHjhxxWmbw4MHaarXqQ4cOOS2TmJio+/fvr7/44gvduHFjHRwcrGvXrq1ffvnlPOVcjX46evRonrIFjTaZN2+ebt68uQ4NDdXVq1fXDzzwgP7+++/zjbDp0qWLbty4scvX/tNPP2lAP/vssy7LnWv+/Pm6S5cuOiIiQoeHh+sLLrhAP/fcc/lez9lWrlyp27Vrp8PDw3XFihX18OHD9W+//ZbnZ3H48GF9880364YNG+qIiAgdGRmpmzVrpl955ZXcETSrVq3SgwYN0omJiTokJETHx8frLl266G+++SbP9Sjh6KeivsZ//vlHX3nllbpChQo6KipK9+nTR2/ZskUnJibqoUOH5jnXq6++quvUqaMtFkue11vQ6JiMjAz90EMP6cTERB0UFKSrVq2qR44cqU+ePJmn3Jnft3N16dJFd+nSJd/2ggwaNEgD+uOPP87dlp2drSMiIrRhGPmuWdDP6sSJE/qqq67SsbGxWimV+747+3lrnf+9KcjQoUM14PRx7vt1Lmejn7TWetOmTfqaa67RlSpV0kFBQbpKlSq6e/fuuSMftdY6KytL33///bp69eo6NDRUt2rVSs+dO7fA92zRokW6ZcuWOiQkRAN53v/58+frFi1a6LCwMH3eeefpN9980+nop1GjRhX4Wnbv3q1vvfVWXb16dR0UFKQrVqyo27dvr5966qncMi+99JJu3769TkhI0MHBwbpWrVp62LBhes+ePS5/TsI7lNbFqOsWQhTJfffdx9SpU9m/f3+Z9PkRQghROGl+EqIMrV69mu3btzNlyhRGjBghCY0QQniQ1NQIUYaUUoSHh9OvXz9mzpzpcm4aIYQQZUtqaoQoQ/IdQQghvEeGdAshhBAiIEhSI4QQQoiAIEmNEEIIIQJCuepTY5omBw4cICoqqtQzaAohhBDCM7TWpKSkUK1aNQzDeX1MuUpqDhw44PX1VoQQQghRMvv373e5KG65SmrOrD2yf/9+oqOjvRyNEEIIIYoiOTmZmjVr5t7HnSlXSc2ZJqfo6GhJaoQQQgg/U1jXEekoLIQQQoiAIEmNEEIIIQKCJDVCCCGECAiS1AghhBAiIEhSI4QQQoiAIEmNEEIIIQKCJDVCCCGECAiS1AghhBAiIEhSI4QQQoiAUK5mFBZClF9bfvmLOa9+x8YlW0BBy+5NGHR3f5p0aOjt0IQQZURprbW3g/CU5ORkYmJiSEpKkmUShChH5rz2HVPvnYXFamC3mQC5/z/qtVsZOKavlyMUQrhS1Pu3ND8JIQLa9vU7mXrvLIDchObs/3/r7vfYsWG3N0ITQpQxSWqEEAHt6zcXYLE6/6izWA2+fmuBByMSQriLJDVCiID2+4o/89TQnMtuM/l9+R8ejEgI4S6S1AghApqrWprcMkEWD0QihHA3SWqEEAHtor6tMCzOP+oMi0GbPi09GJEQwl0kqRFCBLTLRvXGMBSoAnYqR1Jz2Z29PR6XEKLsSVIjhAho1etVZfwX92MNsuapsTEsBtYgKxO+uJ9qdat4MUIhRFmReWqEEOXC0X+O893bC9n001YAWnRrQr/belKxRryXIxNCFKao929JaoQQQgjh02TyPSGEEEKUK5LUCCGEECIgSFIjhBBCiIAgSY0QQgghAoIkNUIIIYQICJLUCCGEECIgSFIjhBBCiIBg9XYAQghRXmjbP5D5Ndp+BGWpCKGXoay1vB2WEAFDkhohhHAzrU10yvOQPhNHBblCoyH1DXT4jaioR1BKKs6FKC35KxJCCHdLmw7p7wEasAO20/9qSP8A0t7yanhCBApJaoQQwo20zkCnve26TNoMtJnmoYiECFyS1AghhDtlrQZdSMKiMyB7tWfiESKASVIjhBDupDOKWC7dvXEIUQ5IUiOEEO5krVfEcnXdG4cQ5YAkNUII4UYq6HwIag5YnJQwwNoYFXSBJ8MSIiBJUiOEEG6mop8BFU7+xMYCKgwVM9kbYQkRcCSpEUIIN1NB9VHxcyB0AP9ND2aF0P6o+DmooIbeDE+IgCGT7wkhhAcoayIq9gW0fhLMU2DEoFSYt8MSIqBIUiOEEB6kVChYqng7DCECkjQ/CSGEECIgSFIjhBBCiIAgSY0QQgghAoIkNUIIIYQICJLUCCGEECIgSFIjhBBCiIAgSY0QQgghAoIkNUIIIUpFmyfQ9oNonePtUEQ5J5PvCSGEKBGduRSdNgVyNjk2qBh0+GBUxB0oI8K7wYlySWpqhBBCFJtOn40+NQJyfj9rYxKkvYM+cSPaTPdecKLckqRGCCFEsWj7EXTyk6efmefsNcH2B6TP9HRYQkhSI4QQopgyvgS0iwImOv1jtHZVRoiyJ0mNEEKIYtG2nYByXcg8BjrNI/EIcYYkNUIIIYpHRVBoUoMBKsQT0QiRS5IaIYQQxaJCewE2FyUsENIdpYI8FZIQgCQ1Qgghiiu4HQQ1BywF7HTU4KiIER4NSQiQpEYIIUQxKWWgKrwNQS1Ob7GSO+2ZCkPFvokKbu6l6ER5JpPvCSGEKDZlVIC42ZCzEZ21CHQmytoAQgegjHBvhyfKKUlqhBBClIhSCoJbooJbejsUIQBpfhJCCCFEgJCkRgghhBABQZIaIYQQQgQEv0lqJk+eTJs2bYiKiqJSpUoMHDiQbdu2eTssIYQQQvgIv0lqli1bxqhRo1i9ejULFy7EZrPRq1cv0tJkGm4hhBBCgNJ+uuLY0aNHqVSpEsuWLaNz585FOiY5OZmYmBiSkpKIjo52c4RCCCGEKAtFvX/77ZDupKQkAOLi4pyWycrKIisrK/d5cnKy2+MSQgghhHf4TfPT2bTWjB07lo4dO9KkSROn5SZPnkxMTEzuo2bNmh6MUgghhBCe5JfNT6NGjeK7777j559/pkaNGk7LFVRTU7NmTWl+EkIIIfxIwDY/jRkzhm+++Ybly5e7TGgAQkJCCAkJ8VBkQgghypo208E8CCocZanq7XCEj/ObpEZrzZgxY/jqq6/46aefqFOnjrdDEkII4SbaPIlOeQ0yvgQcNe7a2ggVOQYV2tO7wQmf5Td9akaNGsVHH33E7NmziYqK4tChQxw6dIiMjAxvhyaEEKIMafMU+vi1kPEZZxIaAGx/oU/diU7/1GuxCd/mN31qlFIFbp85cyY333xzkc4hQ7qFEML3mcnPQPqHgN1JCSuq0s8ow/noVxFYAq5PjZ/kXkKIc+z9Yz9fvPwtyz5fSVZ6NjXOr8rlo/rSd3h3goKDvB2e8DFa50DG5zhPaABMyPgaIm7xVFjCT/hNUiOE8D8blvzOo/2fwbSb2G0mAPv/+pc3x8zg569+5alvxxEcIomNOIt5AnRhM8UbaNseCq6/F+WZ3/SpEUL4l6yMLJ64+iVsOfbchAZAa8dj49ItfP7CN16MUPgkFQGFpisajEhPRCP8jCQ1Qgi3WP75alJPpqHNgpuOtamZ++b32O2umhlEeaOMSAjuAFhclLKjQvt6KiThRySpEUK4xfZ1O7EGuboxwakjSZw4eMozAQm/oSJHAZqCa2wMCO6CCnI+m7wovySpEUK4hTXYSlH691uDpWufyEsFt0bFvnm6KQoc3T9PJ8gh3VCxr3opMuHr5NNECOEWbfq25IuX5zndrwzFeU1rEVtRplcQ+anQnhCyEjK/R9t2oFQ4hPRCBdX3dmjCh0lSI4Rwi5bdm3Be80T2bt2fp6PwGdrUXDfuCqdzUAmhVCiEDZJRTqLIpPlJCOEWSime/nYc1epWAcCwOG5NFqvjY+eWp66jyzXtvRafECLwSE2NEMJtEqrHM33Ti6ycu5YVc1aTnpJJYqMa9L+9JzXOr+bt8IQQAcZvlkkoC7JMghBCCOF/inr/luYnIYQQQgQESWqEEEIIERAkqRFCCCFEQJCkRgghhBABQZIaIYQQQgQESWqEEEIIERAkqRFCCCFEQJCkRgghhBABQZIaIYQQQgQEWSZBCCFKSWvN1pXb2LZmBxarhda9mlGzQXVvhyVEuSNJjRDlQHZmNmsXbOTk4SQSqsfRulczgoKDvB1WQNj31788ec1L7NmyH8NQaO1Ici7q15KHP7yLqAqR3g5RiHJD1n4SIsDNn7GYdx78kNRTabnbouOjGPXaLXQf0smLkfm/4wdPMqL5faScTMO0m3n2GRaD+q3O47VfnsJitXgpQgdtJkHGPLR9H8qIgdB+KGsdr8YkRHEU9f4tNTVCBLAF7y3hldun5duefDyFyTe8jsVqocs17b0QWWCY+8b3BSY0AKbdZNvaHaz+dj0dBl7khegcdPqn6OSngBzAgkZD6mvo0MtRMU+jVLB7rms/AJmLQKeBtQ6EdHfbtYQ4QzoKCxGgcrJzeOehj1yWmf7AB5hm/huyKJqF7/9UYEJzhmExWPzxcg9GlJfO/AGdPB7IBjRgA+yOnZnz0EkTyv6aOhsz6VH00W7olKfRqW+gT92FPtIJnbm0zK8nxNkkqREiQP226HeSj6e4LHN0/3H+WLnNQxEFnpSTqS73m3aTU0eTPRRNXlprdMprgHJSwoTMOWj7v2V73aTxkPEFjiTqTCIF6FPoUyPR2evK9HpCnE2SGiEC1KkjSUUqd/KId266gaByYkXnOQNgsRpUO6+K5wI6m30P2HfgSCycUZC5sMwuqW17IHOOk2s6tunU18vsekKcS5IaIQJUxZoJRSpXqWa8myMJXANG9EK5yGrsNpM+w7p7MKKz6LTCy2CAdl3bVCyZ83F9WzEhezXaPFF21xTiLJLUCBGgmne9gIQa8Sgn91xlKGo2rMb5F9b1bGABpN/tPanbojaGJf9HaVCIyeiXqnJBs/no1KnoHA8381lqAIWNurKB5bwyu6Q2kyjSbcV03SwqRElJUiNEgLJYLIx5cxigUOdkNspwbBv9xvB8+0TRhYaH8OLSifQd1p2gkP8Gk3bob+PLP3dy6XULIG0GOvV19PFLMU/chjbLsGbEBWXEQmhfnCc2ClQshPYsu2taapLbEdmpIDCKVosoRHHJPDVCBLhf5//GtLGz+Gf7wdxttZvU5M5Xb6Fl96ZejCywpJ5KY8/W/YSH7yax6n0o7OTvW2KB4ItQFWZ5JJnU9kPo41eBeZy8yYbj+6yKnYIKLbvmMW0moY90wDHaqiAWCL0cI/bZMrumKB+Kev+WpEaIckBrzd+/7eLkoVPEV4+jbvPaUkPjJubJ0ZC1GFc1FiruE1Rwa4/Eo+2H0SmvQuY8cpON4ItRkWNQwW3K/nrpn6CTJ+DoQX327cUCRhwq/kuUxUudp4Xfksn3hBC5lFKc31r6zrib1tmQtQhwNfePFZ0532NJjbJURsVORpuPg3kMjEiUEee+64VfB0asYzi5fdfprRYI6YWKflgSGuFWktQIIURZ0Rm4TmgANHioX83ZlBEORi3PXCu0L4T0cSQ1Og0sNdyaSAlxhiQ1QghRVlQUqBjQruYI0ihrbU9F5DVKKbBK7aDwLElqhBCijChloMMHQ9o7uKyxCRvksZj8lTbTIPNbtG0bqFBUSA8IaiV9wYRLktQIIUQZUhG3ozMXOWb0zdNZ2NFxVkU9JP1KCqEzF6GT7gedjuM2pdFpMyCoFVSYIk1ZwimZp0YIIcqQMqJQ8Z9C+LVA6H87LHVRMS+jIm7xWmz+QGdvRJ8afbp/EuRZhDNnE/rk7ZSjQbuimKSmRgghypgyYlDRE9GRD4J5AAhxdJYto6YTrTNBZ4KKRqnA+m6q06bjqNUqqPnODjmbIXsVhLT3cGTCH0hSI4QQbuIYcVSvzM6ns9eiU6dC9i+ABiMOHTYEFXkbSoWV2XW8xTEkfimFD4n/ASVJjShAYKX4QggRoHTGt+gTNzhqKc5MameegLQp6OM3os10r8ZXJnQ2RRoSX6TFOkV5JEmNEEL4OG0moZPGnX527kzFJti2oNPe8XRYZU9FFGFdKI2yll3tlwgsktQIIYSvy/gaxxIHzjrImpA+G60LW0zStymlUOHX4/rWpCDsSk+FJPyMJDVCCOHjtG07zlfbPlPoZCGT/vmJ8FvAegH5b0+nF+GMHo+yVPR4WMI/SFIjhBC+rsidgIPdGoYnKCMcFfchRNzumJ35jKBmqNjpjrWlhHBCRj8JIYSPUyE90envuyhhQPBFKCPSYzG5kzIiUFFj0ZFjwDwOKlgm3BNFIjU1Qgjh64IvgqDmOG+C0qiIOzwZkUcoFYSyVJGERhSZJDVCCOHjlFKoCtMhqMnpLVYcCY4CglAxz8q8LUIgzU9CCOEXlBEHcf+D7F/RWT+CznQMbQ4bhDIqeDs8IXyCJDVCCOEnlFIQcjEq5GJvhyKET5LmJyGEEEIEBElqhBBCCBEQJKkRQgghRECQpEYIIYQQAUGSGiGEEEIEBElqhBBCCBEQZEi3EMJnaa3Z99e/pJxIpXJiRSrWiPd2SEIIHyZJjRDCJ/363XpmjPuYPVv2OzYoaN2zGXe8fDO1G9f0bnBCCJ8kzU9CCJ+z7H8reeyyZ9m79Z//NmrYsGQLd7V/hD1b93svOCGEz5KkRgjhU7Izs3n1jrcBR/PT2Uy7SVZ6NtPGzvJCZEIIXyfNT0IIn7Jq3npST6U53W/aTdYv2szRf457tY9N8okU1n6/kYzUTGo2rEazzhc4ljEQQniNXyU1y5cv54UXXmD9+vUcPHiQr776ioEDB3o7LCFEGTq0+wiGxcC0m84LaTi854hXkhq73c57j8xmzmvzsWXbcrdXr1+VB2eN4oJ2DTwekxDCwa+an9LS0mjevDlvvvmmt0MRQrhJVFwkpukioTlTLj7KA9Hk99Zd7/H5i9/kSWgADu48zAM9nmDX5r1eietcWtvRWT+j02ejM75Dm6neDkkIt/Ormpq+ffvSt29fb4chhHCjjoMu4o1R72DLsRe4XylF7SY1qdWwuocjgwM7DzFv2o+g8+8zTRNbjo0PJv2PiV8+UOi5tG0POv1DyPwBdBYENUKF3wAhl5S6GUtnLUMnPQbm4bO2hkLknRAxQprJRMDyq5oaIUTgi46P4poHLi94pwKN5tanh3jlxrxk9s8YhvOPTdNusurrtaQlp7s8j876BX1sAKTPBvMI6CTIXoM+NRqd/Fi+DtLFobN+RZ8c4ThvHpno1Jch7a0Sn1sIXxfQSU1WVhbJycl5HkII3zf0iWu5btwgrEEWUGBYHB9VETHhPPLxPVw8oLVX4jp1JAnDcJ1MmaYm5YTzph5tpqJPjQJygLNro043uWV8DhlzShyjTnnhzP8VvD91Kto8VeLzBwJtP+BI/nL+KFUCKXyPXzU/FdfkyZOZNGmSt8MQQhSTYRjc+vQQrrx3AL/MXUvy8RSq1qlEu8suJDg02GtxVayZ4LoDM2ANshBTMdp5gYy5oDNwlnSAQqfPRIVfWez4tG0f2DYXUsrmaPIKv7bY5/d32rYDnfw0ZP/y30ZLLYgciwrr573ARJkJ6JqacePGkZSUlPvYv18m7BLCn8QkRNNveA8GPzSQLte092pCA9Djhk7gotnLYjXodl1HwiJCnZbRORsBV7U9Gmzb0Tqr+AGaJ4pQyChiucCibbvQx6+F7NV5d9j3o5PuQad/7p3ARJkK6KQmJCSE6OjoPA8hhCiphGpx3Dj+6gL3GRaD8KhwbpxQ8P6zSuI6qTmjBH2GLFWKUMgOlqrFP7ef0ykvgE4nb5MfnKkx08lPoU3n8yMJ/+BXSU1qaiobN25k48aNAOzevZuNGzeyb98+7wYmhCg3rn/sSka/MYzYSjF5tjfv2pjXVz9D1TqVXR6vQtqT/8Z6NgOCWqFU8WullKUKBLfH5Ue7CoOQXsU+tz/T9uOQtQTXP/cMR7Oc8GtK+1EvqZ9++olu3brl2z506FBmzZpV6PHJycnExMSQlJQktTZCiFKx5dj4c/XfjhmFG1Sj6nmuk5kztM5CH+1+ugnIybD12Kmo0B4likvn/OloZiGb3M7HZ587+glU+OASndtf6Zyt6OODCillhYg7MKLu8khMoniKev/2q47CXbt2lZ7qQpQjWmtWzVvHN28t4O/fdhMSFkynKy9m4Ji+RU4i3MUaZKVpp0bFPk6pEKjwLvrEUNCn+K/DsAWwoyLHljihAVBBjSD+E3TyJMjZ+N8Ooyoq6n5U2KUlPrffMmKLUMhEGRXcHYlwM7+qqSktqakRwn9orXllxHS+n7E4z7IJhtXAarXyzPxHaN61sZejLDltJkPGXHTmD6AzIagJKnywIykpq2vYdoF9P6hYCGqKUn7V46BMmcevgZzNFFR75WCgKq5AWSp6MixRREW9f0tSU0pJx5JZ9c060pLSqV6/Km36tMBitZTJuYUozxbMXMpLw6YUuE8ZirDIUD7ZP53wqDAPR+ZbtP0YZHyGzlx0embipqjwIajg5t4OzaforNXokzfjqBkr4LYXMRwj6kEPRyWKKiCbn3yJ3W7n3XGz+eq177Dl2HO/SVaoEssDM0fRpncLb4cohF/78pV5KKUKbHLWpiY9JYMls39mwIhLvBCdb9DZG9Enbz09qud0DYR9NzrzK4i8GxU5yqvx+RIVcjHETkUnPwLmcRydqU0gCCJuQUWO9XKEoiyU37rIUpp+3wd88dI3uevTnKkaP3U4iccvncyWn//0ZnhC+LXM9Cz2bNnvsg+dYRhsXfmXB6PyLdpMQ58cnjehAc50Ptapr6Ezl3glNl+lQruhKi53dMSOeggVPRlV6WeMqPvLddNcIJF3sQSO/nOcuW98T0Gft1prtIaZj3/q+cBEuffr/N94qNcTDIi8gcuib2TSlS/4ZYJd2FIE4JgD78zyCeVS5jzQybjqI6LT3vVkRH5BqSBUaA9UxC2o8Culc3CAKcefCCX302crXc6LZdpNNi/7g+MHT3ouKFHuzXr8Ux4bMJmNS7eSlZ5FRmomq+at494u4x0rS/uR4NBgGrU732VyY7eZtOze1INR+Q6tTXTae4WUMiFnHVq7mptFBBJtP4xOexcz+Rl06jto+yFvh+RxktSUQPLxFCxF+IaYfDzFA9EIAb8t/p2Pn/4SIM/aRHabCRpeH/UOe//8x1vhlcg191+GaRbc/GRYDOKqxNL56nYejspHpL0F9j3ejiLgaa3R2WvQaTPRaR+hbb651I7WGjPlNfTRLo6Zk9M/Qqe+hD7aFTPlhXI1FYokNSVQObEiNpvrbz+GxSC+mlRrCs+Y+8Z8LFbnf84Wi8G3U/2rtqbjoLYMneRYdPHs16aUIjI2gskLHiM4JMhb4XmNNtPQqTOKUNKAoOYoJaMxS0Ln/IU+1gd94gZ0ynPolCfRx3pinrzL95ZTSH/Pkehinn7Y/vv/tHcgbZpXw/MkSWpKoOu17Qly8WFqsRp0vOIiouOiPBiVKM/+XL3dUSvjhN1msnXlNg9GVDZuePwqpqx7jl43d6N+q/No0rEhI168iVnbX+e8ZoneDs87slcCGUUoaKIibnF3NAFJ2/5Bn7ge7GeW4DHJHQqe9SP61Eifqf3QOgudWvDUB7ll0qajzXQPReRdMqS7BCJjIxj50lBeH5X/25JhMQiLCuPWp4d4ITJRXhVlbiRrsH/+uddvdR5j376jzM+beiqNhR8sY+2CDWRn5dDoovr0H3EJVWpXKvNrlSldlIQGCOkOIX3cG0uA0unvOln8EsB0rPSdvQpC2ns6tPyyfwVdSFcHne6ItxQzVfsLqakpoUtH9mbcR3dRpc5ZH4AKWvVsxhurnqF6vfK3Cq7wnosHXOiy+UkZirb9W3kwIt+2ff1Obqo7mqn3zmLtDxvZtHQr/3vxG4bWH8OP7//k7fBcs9YtUjEVORalSrDSt4CMr3G9+KUFnTnPU9G4povYFKZT3RuHj/DPr24+ovuQTnQd3IGdG/eQnpxB1bqVqVQzwdthiXJo4Ji+LHhviWNU3jm14oahCAkPod/wwP+WVhRpSWk83Psp0pLT8zQhnOlg/eKtU6jRoBoXXHy+t0J0SQU1RlsbgW0bBQ/ntjhmFQ7yzfh9ndZmERIAO5inPBFO4Sx1ilbOep574/ARUlNTSoZhUL/VeTTv2lgSGuE1tRvX5LHP7sUaZM0zd4syFCERoTz93SNUqBzrvQB9yMIPl5N6Mi3PKLGzGRbFnFe+9XBUxaNingUVhmMRzLNZQIWjop/2RlgBQSkDjMKaIC1gqe6ReAqjghqCtSnOb+cGWBuAtYknw/IaqakRwgtM02TD4t/5ec6vZKRlUqthDXrf0o34qiUfMddxUFs+3PUW37+zmN9//hOLxaBVz2b0vqUb0fHSaf2MdT9sRBe09s9pdpvJr/N/82BExedYiftLdOobkLkAR1OJFUL7oSJHo6y1vRyhf1Phg9Gpb+J8YkM7KuwqT4bkkop5Cn3iOsfaX3mazSxAMCpmcrlpipQFLYXwsKRjyTzafzLb1u7AYrU4mkC0BqUY/cYwLr2jl7dDDGjj+jzFuh83uSwTFBrE/PTZHoqodLSZDjoJVAzKCPd2OAFBmyno41eDfS8F9q0JuxEj5nGPx+WKtu1Ap7wOWT/iSMYMCOmJirwrIJoiZUFLIXyQ1prxlz/H37/tAsCeZ74jzet3vkPFGvFcPKC1dwIsBxq2rc9vi3930fxk0PCieh6OquQciYwkM2VJGVEQ/wk6+RnI/A7HvC+AEYeKuA3Cb/VqfAVR1nqoCq+jzRQwT4BRAWWUvy/vktQI4UFbV27jj1Xbne5XhmL25Dl+l9SYpslvi35n35//EBoRysUDWhFXxTcnn+x3W08+mTzH6X7TbjJoTD8PRiR8kTIqoGJfQJuPgG0HEAxBF6CUb0/4qIwoMMpvc7MkNUJ40Mq5a7BYLefU0PxHm5o/V20n+XiK3/SD2fLzn0y+8Q2O7D2KMhTa1BgWg/4jLuHOV27GGuRbHzMVa8Tz4KzRPHvTGxiGyp200LAYmHaTy0f3oeMVbb0cpfAVyqgAwW28HYYoIt/6tBEiwGVlZFOU/npZGdnuD6YM7Ni4mwd7PYkt21E9r0+v1WTaTb6d+iMZKRk89P4Yb4ZYoO5DOlH9/Gp89dp3rP52PXabnQZt6jFwTF86DLyo3HSqFCLQSFIjhAfVbV670HXDoipEUKFyjIciKp2PnvgCe449N5k5m9aaRR8uZ/DDg0hsVMML0bnW4MK6PPzhXd4OQwhRhmSeGiE8qNt1HQiLCHVaW2NYDAbc0cvnmmwKkpGawapv1jrtcAuOddCWzv7Zg1EJIcozSWqE8KCwyDAe/vAulMXIt6yBMhT1Wtbhukeu8FJ0xZOWlI5ZQA1NHkqRdCy5ROdPPp7CvGk/8v6Ez5g37UeSjxeyvo0Qotzz/a+DQgSY9pe34dUVT/HZc3MdNR2mpkKVWC6/sw9X3NufsIhQb4dYJNHxUQSFBpGTmeO0jLabVE6sWKzzaq357Lm5vD/xf9hz7BhWA9NmMuWemdw04RoGPzxQ+rwIIQokk+8J4UW2HBvZmTmERYb65Y361RHT+X7mEkyb8zlfPt47lYRqcU7PkZWRxZr5Gzh5OImE6nEc2HmI6fd/4LT8na/ewqC7ZMi1EOWJTL4nhB+wBln9ov+MMzeMv4qV36wl6VhKgX1rhk661mVC8+30hcx4+CPSktJRyjGxMoXkdh9M/B/9R1xCcIhvzxcihPA86VMjhJ/QWrP3z3/4Y/V2Th5J8nY4ACRUj+eN1ZNp279VnpqmhOpx3Dt9BENc9A/67u2FvDbybdKS0oHTCQ3kW2X8XKmn0ti0dEtpQxdCBCD//YooRDmy4svVzBr/Kfv+/NexQUG9FnV48IPR1Glcy6uxVU6syBNzH+LYv8f5Z/tBwiJDqdeqDhbLuStI/yc7K4d3x31c4mumnkov8bFCiLKhtYbsVeispaCzHCuGh16GMiK9FpMkNUL4uPkzFvPK7dPybtSwY8Nubm92H/e9eyd9bu7mneDOklA9noTq8UUq+9vCzaScTCvxtarVrVziY4UQpaftR9EnbwfbVs6kEjrDDsnPQeyLqNBLvBKXND8J4cPSktJ46653nRfQ8NKwKezYsNtzQZWBU0dLNszbMBSJjWty/oV1yzgiIURRaW1Hn7wVbH+d3mI7/dBAJvrUXejsTV6JTZIaIXzYT5+tJNvFkGkANHz89BeeCaiMVKpZtBqdsxkWA0uQhbHv3OGXI8WECBhZy8G2DShodnRHpzid9o5HQzpDkhohfNjBXYdRRuE38FXfrMOfZmdo3q0xCdXjnM6srJQiNCIkz7aW3Zvw6s9PccHF53sgQuHvtJmMzl6Pzt6E1v6xlpq/0FkLAed95sAOWYvR2vWSMO4gfWqE8GGRFSILXFfpXHabid1m95vh4RaLhbum3MaEgc+fHsr932tUhsIwFE/NG0eNBtU4eegUFarEEl+1gkdi01rz15od/PTpL6QmpVGtbhV639y1yP2FhHdpMwWd8hxkzAVOJzMqBiJuhYgRKCXf5UtNZ1DoMEXsOJqkXCU/ZU8m3xPChx3cfZib6o4utFzlxIp8tHuKByIqW2u+38D0+9//b1QXULd5IiNfvYXmXRp7PJ6M1AyevOZl1i7YiMVqAbRjqLnWDH/uRq6+71KPxqPth8C2A1QIBDVHqWCPXt/faDMdfWIw2P6mwKaR0KtQMU9L82Up6dQp6NTXAefrvmFUxai0rMyu6ZbJ96ZMmcKcOXOIi4vjjjvuoHv37rn7jh07xkUXXcSuXbtKHrUQ5di2tTtY/vkq0pIzqNmgGj1v7EzVOpXpfl0Hlnzyi8tjLx/Vx0NRlq2L+rakTZ8W7Ny4h5OHT5FQPY46TRO9Fs9zQ99k/cLNANjPWU397Qc+IL5qLN2HdHJ7HNp+CJ08CbKWkPuNWMVAxHCIuE1qG5zJ+OR0Xw8n39Uzv4DwqyG4pUfDCjhhV0Lq6y4KKFTEjR4L52xFTmpef/11xo0bxy233EJSUhL9+vVjwoQJjBs3DgC73c7evXvdFqgQgSo9JYMnr3mJdT9sctQOKDDtJjMe/ogxbw7ngVmjObzvGFt/2Zb/YAVNOjTk8tH+mdSAo/9MvZZ1vHJtrTV7tu7nxMGTZGVm88tXa5yWVQo+fPILul3X0a3f9LV5An38WjCPkOfmrJPQqS+B/Qgq5nG3Xd+f6fRPcd0sYkFnfI6SpKZUlKUyRE9AJ0/A0TX37BobA4JaQriPJzXTp0/nnXfeYciQIQDceeedDBw4kIyMDJ544gm3BShEoHtmyKv8tuh3IG/tgM2088qI6VSoEsurK55iwcwlfDjpc47sOwZAdHwkl4/qy7UPXU5wqDRLFNeGJb8zbez77NpctC9jWsM/2w5wYOchqterSnZWDicOniQkPIQKlWLKLC6dNuN0QuOkk2XGh+iI68CoBJnz0fbDKCMOQvuhLOW834/9YGEFwL7fI6EEOhV+HViqoVOnQc56x0YjHhV+/enaxBDXJ3CTIic1u3fvpn379rnP27Vrx5IlS+jRowc5OTncc8897ohPiIC2c9Mefv3uN6f7DUPx0ZNf0O7SC+lzS3f63NKd5OMpZGdmU6Fy7Ol+H6K41i/cxCP9ninRiLFTh5P4dtpC5s9YRHpyBgANL6rHDY9fRdv+rUsVl9Ya0v+H04QGAAs66QnI+Q3IcTzHDinPoCNGoCLvLnd9RrTOgpw/QIWBy5FOFlDO1yITxaNCuqBCuqDNVNBZYMSilHc/k4qc1CQkJLB//35q166du61x48YsWbKE7t278++//zo/WAhRoF++WoNhNZyucm2amu3rdnLswInchSGj46PylLHb7Oz94x9ysm3UbFCN8Kgwt8ftz7TWvDbyHbSpi53UWIOtvHLHdPb/dSDPAp7b1u3ksUuf5d7pI+h3W89SRJcFurCJCU3IWX3Wc9vpf+2QNsXRqThyZCli8B9a2yBtKjrt/SL83ADsqLDL3B5XeeNYFsF7SyOcrci9zTp27MiXX36Zb/sFF1zA4sWLWbBgQZkGJkR5kJmWiVGEb9WZaVn5tmmtmfPqdwxJHMmIFvcz+qKHuabKcN4YPYP0lAx3hBsQtq7cxsFdh4ud0BgWg8RGNfIlNEDusPvXR8/g1NHSLDYaAoQWUsZ13Dp1Ktos+RIU/kJrjU56CJ36ZhETGgsEtYCQrm6OTHhTkZOahx9+mObNmxe4r3HjxixdupTHH5fOa0IUR60LamLLcT1BVUh4CBVr5K8yf+vu95g6dhYnDp7M3ZaVkc230xdyf7eJZKbnT4QEuX2SisOwGFSpU4lDe4/kS2jOZtpNFr5f8mGsSikIG0jp5vbIhOzlpTg+L62z0WkfYR7ti3noAszDrTGTHkfbvDzSNXsNZM7DdZKnTj+AkC6oCjO83jwi3KvISc0XX3zBjTc6780cFRXFL7+4HnYqhMir67XtCYsKy/3cPZdhMehzSzdCwvJ2uvv7t118/WbBtaOm3WTHht18O+3Hsg43IMQkRBVe6CxhUaFcde8Anv5uHGmFrA5uGAb7/ipdU7yKuA1UOAUnNkXsK2OmlCqGM7TOQp8Yhk55Euy7ABvoFMj4An3scnT22jK5Toliy/icQpM/Ix4V9Rgq4QeMCtNQhsxPFuiKnNTMmjWLiy66iN9//z3fvrfffpsmTZpgtfrHbKZC+IrQ8BAeen80hmFgWPL+ORoWg+r1qnDTpGvyHff9jMVYrM7/fDWaeVMlqSlI866NiS3iaCXDYtBjSCdue/5Gx4zCRcgpStunSVlrouJmg/XcRTstENKrSOfQth3onK2ligM4PbJlLY7akLNrROxADvrkaO8tQWDfh+sO1YBOR0XciLJ6Z8oA4XlFTmq2bNlCkyZNaNOmDZMnT8Y0Tfbt20fPnj158MEHefnll/n+++/dGasQAanDwIt46adJtOrZLPemGR4dzpX39Oe1lU8THZe/ZuHAzkPYnXQuBkDD4X1H3RSxf7MGWbntuRuKVNa0myz8YBm2HBuh4SG06dMyX/J5NrvNTqerLi51jCqoASp+Hiruf6joSajoZ1EVl2NUeMPRL6Swj+70WejjgzCPXYm2lWz+MK1zIP1jnM8aa4I+CZleSp6NOAr9OahYT0QifEiRq1aio6P54IMPuPLKKxkxYgSfffYZu3fvpl27dvz+++/UrFnTnXEKEdCadGjI5O8fJT0lg8y0TKLjo1yu4xQVH4VhMVz274iIDndHqAWy5djYsGQLSUeTqVQrgSYdG2IYvjvrba+hXbFl23hj9IxC+zRlZWSTlpROTEI01z96Jet/3ORIPs/pymFYDJp0bEjj9g3KJEalFAS3cDzO3h49EX3iutNDlwupqbD94Sgb/w3KklC8AOyHQJ8qpJAVnfM7KmxA8c5dBlToZeisJS5KGBA2yGPxCN9Q7E+dtm3b0rRpUzZv3oxpmjz44IOS0AhRRsKjwoirUqHQhSm7XdvBZUJjWA163tC5rMMr0IKZSxlcYwSP9H2a5256g/u6TuCmeqNZ8/0Gj1y/pPrd1pOBY/piFLIKujXIQni0o0mpcfsGPP6/sYRFhIJy7DvTDNi8a2MmffWg2+eIUUEXoOI+h+BOFN4eZgfzBDr9w5JcqGzLlbXQS8DamIL71VjAiEOFF61GTgSOYiU1n3zyCY0bN8Y0Tf78809GjhxJ3759ufvuu8nIkCGkQnhK2/6taNCmboFNIYbFICwilCvu6e/2OObPWMxLw6aQdDTvkNoje4/x2KWTWb9wk9tjKI3et3THdLEKusVq0O26jgQF/3fj7jioLZ8dfIexb9/BZXf24ZoHLufNNc/y/MLxRMZGeCJsVND5GHFvoyr+AkEdcf1RbkJG/uk4CmVUBktdXCdONlRIl+KfuwwoFYSKmwnBHc5sIffnYD0fFTdbZlguh4q8SvdVV13FDz/8wDPPPMOYMWNyt69atYqbb74ZrTXvv/8+7dq1c1uwpSWrdIuyZrfZ+XnOr8yfsYjDe44SWzmWXkO70uP6jvlGLJW15BMpPDPkNdb/uAnDYqAU2G0mVc+rzPgv7qNeC/d2jszOzOaaqreRllTwiCBlKBIvqMHbm17y6RluX7jlLRZ+sCzfvDWGxSAkPJgp656nRv2qXoqucOaJGyH710JKBWNU2VLsc+uMr9FJDzjZawFrI1T8l15/f7VtJ2StBOyOPkdBzb0ekyhbZb5K98GDB9mwYQP16tXLs71du3Zs2rSJhx56iC5dupCd7aWe8EIAmelZfDd9IfOm/8iRvUeJjI2g5w2dueKe/o7RK2UoKyOLxy99lg1LtuT2bzmw6zBbf/mLOa9+y4tLJxJbsezWBDpXdFwUzy54jN1b9rFuwUZysm00aFOXlj2aeqQ/y6/zNzhNaMAxId2eLfvZ/fs+zmvmvZW3CzP2nTuIiovk67cWYMu25W6v1ag6D30wxqcTGgAs1XE0wbjoX2OpXKJTq7DLHR2N09486xqnFzC01EZVmFbq5MGRTJqlmj9GWesWMFpMeJLWdrD9CToDLOd5rZasyDU1pmkW+kG5fPlyOnf2TDt+SUhNTWBLS07nge6T2LFhNxqd25HTsBhExITz8rInqN247Pp/vXXXe3w9ZUHubLJnM6wGrXo0ZfL3j5XZ9XzN128t4K273it0Zt5nvn+UNr1beCaoUkg+kcL6HzeTlZ5FYuOaNLyonl9829fZa9EnrndRwkBF3oeKvK3k17DtQKf/D2w7wIhChfaBkJ6oUvSn0bZ9jsU7M7923AiNiqjwwRB+M8oo3lxCwnu01pDxGTr1LTAPn95qQEhvVPQjjhW9y0CZ19QU5ZufLyc0IvC9+/DH7Ny0J99N1rSbpCWl88TVL/Lu1lfL5EaVlpTG/BmLCkxoAEybybofNrF/27/UbFC91NcrSycPn+L7d5fw26LNaK1p1vkC+t/es9g1WfHVKhRpqYGEahVKGqpHRcdF0W1wh8IL+pqgCyH0Usj8lvyz61rAUhvCryvVJZS1Hir6kVKd42w6Zwv6xI2gM8mtYTKPOm6MGfMh/hOUEVtm1xNulPYWOvX1czaakPUj+vgGiJ9T/JF3peC7Yy6FKIa05HQWzFrqdESQaTfZ/9cBNi//o0yut339LrIzcwott3lZ2VyvrPy2aDM3njeKWeM/ZdNPW9m87A9mPzOHG+uO5pe5a4p1rov6tXLZMVYZivOaJVK7Sa3Shi1cUEqhYp6DiDtAnf1+WCC0Hyp+9ukFB32D1ib61N2O2pl8TWYm2Pegk5/zRmiimLT9ADr1DSd77Y5ENW2aR2OSpEYEhH1//ktOIUmGYTHYtmZHmVyvqGshFnPNRLc6+s9xHr/8ObIzc/LUMJl2E3uOnSeveblYU/wHhwRxx8tDC9ynlEIpxR0vD/WLJhx/p5QVI+peVKWVqAofoirMRFX8GSP2JZThYzVl2avAvh/nk/rZIfMbtFmahUGFR2R8hevRcXbHkhra5qJM2ZJ1DURAcLVkwBla60Lnfymq81ufR1CIlZws13+sTTs1LJPrlYXvpi/Elm0rsMnIsU3zzVsLGP3GsCKfs/fN3bAGWXn7wQ/zLKxZrV4V7nprOC27N3V6bOqpNH58/ydH7ZnWNOvcmEuGdiGqgu/UKvgbpcIgpK23w3At5w8K7dhMDth255t4UPgWbf+HQudK0umOVdRV/kV53UGSGhEQzmuWSExCFEnHnC/kp01N694FrzRfXJGxEfS+pTvz316Eaeb/xmmxGjTr0pjEC3xnYspf5//mcsI+u81k1bfri5XUAPS4vhNdr23P7yv+JOloMhVrJdCobX2XNTSblm3l8cueIzM1M3fbyq/XMWv8pzzx9UO06NakWDEIP6KCcV5Lc2454dOKtAyFBZTnvqhI85MICNYgK1fdd5nT/YbF4MI+LUhsVKPMrnn7CzdyQfvzHec/Myutcjyq1KnMwx+OcX6wF9hthUypX8QyBbFYLbTo1oQu17TngovPd5nQHP3nOI/2n0xmWiZa6zyPzPQsHhswmSOyblXgCulC/g7N5zAqgrVslpsQ7qPCLsV1jZsFQvugPJigSlIjAsY1D1xGv9t6AP81R52Zcbdeyzr0vrkbL9z6lmMU1COzObjrsNNzFUVYRCjPLxrPQx+M4YL2DahYI576rc5j9OvDmLr+OeKq+FZfhiYdGrpsprNYHWsXudu8qT+Qk5VT4MgxbWpysm3Mm7bQ7XEI71DW2hByCa5uPypiRKnmrRGeoYIugJA+FPxeGoAVFTHSszEVdZ6aQCDz1JQPf/76N9/PWMyBnYeISYji4ksv5KvX5/P3+l1YrI5J8pRhYJomtz41hOvGlY9F73Zv2cftze9z+SX5lRVP0qSDexOb4U3uZe8f/7gsU6tRdd7d+qpb4xDeo81U9MmRkPMrjv41Jo6boB3Cb0VFPSQdzP2E1lnopAmQ+dXpLQowwaiGin0JFdy6TK5T5vPUCOEvGrWtT6O29XOf39d1Ajs37QEc/UYA9Om+Je89OpvKiQl0H9LJ43F6Wp0mtRjzxnDeGD0Di9XI/VkYVgPTZnLr00PcntAAhXauBoo0XF74L2VEQtwHkL0anTkPzFNgqYEKvwZlrVfo8cJ3KBWCin0Wbb8HspY6hupbz4fg9ijl+cYgSWpEQNu2dofLuWmUUsx+Zg7drutYLr4ZXnZnb85rnsicV7/LM/neoLv706qH85FKZanRxfU5vPdIblJ1LovVyJOUisCklIKQdqgQ310vUBSdslQp9SSPZUGSGhHQVs1bl6dW4lxaa/b+8Q9H/zlOpZqem/XSm5p0aOiRGhlnLruzN4s/XuF0v91mctmoPh6MSAgRKKSjsAhoOVk2KEINjDR3eM4F7Rpw08RrgP86cp/9/zdNuMarSZcQwn/5XVIzZcoU6tSpQ2hoKK1bt2bFCuff+ISo17IO9hzXw5TDo8OoVKt81NL4ihvHX81T8x6mWecLsFgNLFaDpp0a8cTXD3HjhKu9HZ4Qwk/5VfPTZ599xj333MOUKVPo0KED06dPp2/fvvzxxx/UqiXry/gKbaZCxpfojC/APAZGFVT41RB2BUqFejSWDoMuIjo+ipSTqQWvpm0xGHD7JQSHlHy1YVEybfu3pm3/1rkzHJeHPk1CCPfyqyHdbdu2pVWrVkydOjV3W6NGjRg4cCCTJ08u9HgZ0u1+2n4MfWII2Pee2YJjiJ8Ga0NU3Ecow7M/+41Lt/BI/2cwbfY8fWuUoajf6jxeXDKBsMgwj8YkhBCi6Ip6//ab5qfs7GzWr19Pr1698mzv1asXK1euLPCYrKwskpOT8zyEe+mkh08vVqf5b0KU0//a/kYnT/J4TC26NeGtNc/S7bqOBJ2ukalYM55bn7qOl36aJAmNEEIECL9pfjp27Bh2u53KlSvn2V65cmUOHTpU4DGTJ09m0iTP30TLK23bC9nLXZSwQ+Z8tP1hlKWix+ICxxwtD70/hgdnjcZus5fZwpZCeNORfUdZ+c06MtOySLygBhf1bYnFWrqZeDPTs/jp01/4+7ddBIUEcfGA1jTv2liaB4Vf8LtP9nP/sLTWTv/Yxo0bx9ixY3OfJycnU7Om7ywwGHByNhShkB1yfgdLd7eHUxCllCQ0wu9lZ+Xw2si3Wfj+MlCO32vTbhJXtQLjPrqrxAuCrl2wgacGv0J6cgbWIAtaw5evfEv9VnV4ct444qv61tIfQpzLb5qfEhISsFgs+Wpljhw5kq/25oyQkBCio6PzPIQ7FfXXSb7xCVEaL9z8Jgs/WOZYCNTUuauvnzx8inF9n+bv33YV+5w7N+1h/OXPkZHiWDndlmPPXeB01+a9PNTryRIveCqEp/hNUhMcHEzr1q1ZuDDvQncLFy6kffv2XopK5BHchsITliAIbumJaIQISLt/38tPn610uiCoaTf56Mkvin3ez1/8Jne19HPZbSZ7t+7n1+9+K1HMQniK3yQ1AGPHjmXGjBm89957/Pnnn9x7773s27ePO+64w9uhCUBZqp5esdVZm77hGNZtxHowKiECy5JPfnG52rppN1k1bx0ZqRlFPqfWmhVfrnY68zY4pj9Y/uWqYsUqhKf5VeeCa6+9luPHj/PEE09w8OBBmjRpwvz580lMTPR2aOI0FfMU+sS/YNuMI2c2//s3uA0q+hHvBiiEn0s5kVroLNna1KQlZxR5ZJ/WutBZtU27yb9/H3LZj1EIb/OrmhqAO++8kz179pCVlcX69evp3Lmzt0MSZ1FGFCp+NirmZQhuD9b6ENwJFfsGqsJMlJLh00KURpU6ldB219OLhYSHEB0fVeRzGoZB1bqVC209/uvXv3l15NsFNlEJ4Qv8LqkRvk+pYFTYAIy49zASvsOIewcV2hul/KpiUAifdMlNXVwmHxarQa+hXYs9S/bld/YpUg3M/LcXsfCDZcU6txCeIkmNEMLnaPMEOu0DzJQX0Gkz0fYj3g7JZ8RXrcCwZ4YUuM9iNahQpQI3PH5lsc976Z29adKxYaHrvypDMefVb4t9fiE8QZIaIYTP0FqjU6ehj3REpzwNaTPRKc+hj3bGTHlZmj1Ou+aBy3lg5igq1/5vEkvDatDxyot5Y9XTxFUp/nwywSFBPLvgMRKbuJ7LS5uanZv2kpMtK9sL3yPtAUII35H+MTr15bM22P7737RpoMIgcqTHw/JFvYZ2peeNndmzZT+ZaZlUq1eF2IoxpTpncGgwdZvXZu/WfwocMn42w5DvxML3SFIjhPAJWmejU99wXSZtGoQPRRnhZXNNMw3QoCLcMqLnTG1GULB7VoE3DIPzmpXt6M9WPZqx+KMVzq9pMWjcvkGpl2MQhcvJzmHFl7+y5vvfsGXbqNfyPPrc2q3UyWsg86tVuktLVukWwnfprFXok0MLLadi30CF9i75dbSGzO/QaTPA9odjo6UOKuIWCLsGpUpXA6G15qfPVvLFS9+wfb1jZt8Gbepx9X2X0vnqdj4/HDorI4sbzxtF0rGU3JmKz/XkNw9z8YDWHo6sfPl3x0EeuuRJDu89imExcpteLVYLD394F12ubuflCD0r4FbpFkIEOJ1StHJmWukuk/oqOmks2P78b6N9Dzp5PDrp0VL323nnwQ95Zsir7NiwO3fb3+t38tTgV3jvkdmlOrcnhISFMHnBY0TG5q29OjPh37DJ10tC42bZmdk82PMJjv57HHDMEaRNx5IYthwbzwx5lW1rd3g5St8kSY0QwjdY6hStnLXkzS06exOkTT3z7Ow9jn8yv4SsxSU+/8alW/j8pXkAmGf1STnz/58+N5ffV/xZ4LG+pG7z2sza/jp3vDSUpp0b0aBNXfoO68H0jS8y+KGB3g4v4C37fBVH9h3DLGiGZ+2Ye/Hzl77xfGB+QPrUCCF8ggqqj7Y2A9sWHDNRn8sASyIEtSrxNXT6JziW8XC2MKMFnf4RKrRnic7/zZQfsFgNp8sNWKwG30xdQNNOjUp0fk+KqhDJFff054p7+ns7lHJn9bfrMQwD0yz498huM1n5zToPR+UfJKkRQriVth9Cp70PGXNBJ4OlOir8OggfnG+GaRXzJPrEYNDZ5E08LIAFFTO5dH1SbH/gPKHBsc+2zcnrOHb6eAsENUcZkfnKbF+30+X6SXabyd/rHP1sjuw7yprvN5KdkU3dFrVp1uUCn+9vIzwjJyvHaUJzhj3bJktWFECSGiHKOa015KxDZ3wF9sNgqYwKGwRBF5b6A1PnbEefuB50KrnJhH0vOuVZyPgG4j7MkxyooEYQ9wU69ZXTzUAaUBDcARV1LyqocaniQRVl1FRo3tdgnkInPwmZ8/97DYSiI25ARd6DUsG5ZUPCQwo9e1BoEM/d9AaLP16BxnFT0qamev2qPDL7bs5vXbfor0cEpLrNa/Prd7857aitDEVik5qS0BRAkhohyjGts9Gn7oGsRfzXLGNBZ3wBwR3RQc0hewVoGwS3QoUPQVnr/Xe8eRKyfgadAdbzHTUYpz9otdboU6PzJjSOPY5/bH+iU19ERU/ME5MKqo+qMMVxbvM4GHEoI65MXq8K7Y3O2UDe/jRns0BY37NeX5ojKbPtOuc1ZELau2jbXoh9M/c1dxx0EZ9uP+D0ZmQYBjnZNpbMXpHbIfnMvwd3Heb+7hOZuv55qterWroXKvxa3+E9mP3MHKf7takZNKafByPyH9JRWIhyTCc/B1lLTj+z5/03+2dImwI5mx3NLumfoI/1R6d/itY5mMlPo490QCfdh05+DH3iGvTxS9E5pzvCZv8K9j04b+4xIf1LtJla4F5lVEBZ65VZQgNA2BVgVKDgjz4DVDAq/Pr/NmV8BrYdFPwaNGQthOzVuVsuHdmLkLBgDCP/N2jDYhAcGsS/2w/m6UR8hmk3yc7I5rPn5hb3VYkAU6lmAvdOHwHK8XtzhlKAgs5XXUyvm7t6LT5fJkmNEOWUNk9BxqcU3Ck3t9RZ/28HNDp5AvrkHZD+AXlm/AWw7USfGIK27YGc3yn8IyYLbDuLHXtJKSMGVeEDMM4sL2DFUUMFqChUhfdQluq55XX6Zziv1YHcWq3TEqrH8+wPjxEe42jmMixG7k0pIiacC/u0cDlpnd1msvjjFYX2pxCBr8+t3Xlx8UQu7N08N0mu2bA6d711G498cg8Wi0x+WBBpfhKivMpeA5Rk/R7D0SRVIDvoTHTqNFTQ+UU7nXLPbLvOL3c+VFwMmT+is1cDJiqoNYT1y9dxGfNwIWezg/2fPFsuaNeA2fumsXT2z2xe/gcoaNa5Md2u68DLw6eiC0lYsjNzyM7MIbQI/XNEYGvetTHNuzbGNE1Mu4k1SG7ZhZGfkBDllS7pgoSuRg+d3p85Dx3+Oa5rgQAjHqz1SxhHySkVDGEDUGEDCilYAXS6iwKWs2p9/hMWEUq/23rS77a8Q8Mr1aqIMhS4WFcpqkIEIWHBTvcX1cnDp/j7t90YFoML2p1PeFRY4QcJn2QYhqy1VUSS1AhRXgU1cePJc1DWqujgTpC9EmeJkIoYhvJwTU1xqPAr0alv4jw5sztGihVRn1u78b8Xvna637AY9LutZ6lGtSSfSOHN0e+y7PNVuR2WQ8KCuXRkb2595jq3rUMlhC+Q1E+IckpZEyG4Pbl9Sop+ZBHKhDr6qMS+BNYLTm8783Fz+nphgyH81mJe28PCh4BRiYJ/RoZjIsCQrkU+Xc0G1bn6vksL3GdYDCrVSuCaBy4vUagAGakZjO0yIU9CA5CVkc2Xr37Lk9e8XOplIITwZZLUCOGCtu3CTH4G8/hgzBND0Wnvo81kb4dVZlTM5NPNJ8X5KCjspmiB8CtQyooyYlHxn6Fip0BIbwi+GMKuQsV9jhHzRKkXj3Q3ZcSh4j+BoObn7oHQPqgKM1CqeEnhbc/fyMiXbya20n8rLRsWg05XXczrK58mOj6qxPF+9/Yi9v35T4FDyrWpWfXNOjYs/r3E5xfC18kq3UI4odM+RKc8heOGbye3hkLFoOJmoYIucHG0/9DmCXTah5Dxv9PzwsRDUGvHcGXgv6Yjxzw2KnIM2kyD9PcKOJsFjFhU/FcoSxXPvAAP0TnbIGcTYIWQdihL6eaSseXY+Pu33WRnZlOrUQ0qnJXklNStje5m/7YDTvcbVoPOV7Xj0dn3lPpaQnhSUe/f0qdGiALorFXolCdPPztzUz+d/+tk9MlbIWEJyijKDLW+TRlxqKi7IeruPNt1zt/o9I9Oz2Njg6CWqPCbUCEXg9ZgxKPT3gaddOZMENwOFT0p4BIaABXUAIIalNn5rEFWGrUt207Sx/494XK/aTM5vOdomV5TCF8iSY0QBdBp7+J84UMTzBOQ+S2EX+PhyDxHBdVHxUwCJuXfpxRE3gYRQyH7t9MzCtdDWWuW2fUdyzesR2etAGyooGYQ0t2nOxZ7W0zFaDJSM53uNywG8dViPReQEB4mSY0Q59BaQ/YvuB66bKCzf0EFcFJTFEoFQ8jFZX5ebT+MPjny9IrdFkChsTn6/8ROQQWf28dFAPS5pTsfTPyswBmLwTFr8SU3dfVsUEJ4kG/30hPCawqb0VU71kMSZU7rbPSJoWA7vdwCdnJnLjaPo08ORdv2eys8n3bpyF7EV4/DYs3/0W5YDBp3aEDb/q28EJkQniFJjRDnUEqBtQmF/XmooBYeiceTtJmKTp2OeaQ75qELMI+0w0x5AW0/4rkgMn8A+7kLSJ5hgs5Cp3/guXjcJDM9i+/eXshd7R/lxvNGcV+3CSz6aDk52SWdFBGi46N4dcWTXNAub98fpRSdr27HM/Mflen1RUCT0U9CFEBnzEMn3edkrwKCUJWWl+1ii16mzZPo49edXoTy7JoqCxgxqLhPUNY6bo/DPHnn6c7JLmrLVBxG5dXO9/u4pGPJ3N9tInv+2I9CobXGMBSmqWncoQGTv3+UsMjSzQC8e8s+/vr1byxWCy26N6FSzYQyil54UnZmNks//YXFH68g+Vgy1etXpd9tPWnVs1mpJmn0N0W9f0tSI0QBtHYs3OhY8NHgvxuso3+Hin0dFdrT+Qn8kHnqPsicT8E1JBawNsRI+Mr9cZy44fS6VK6EYlTZ7PZY3OWxSyez9oeNmLb8iZthMeh9SzfGvn2HFyITvuTEoZPc330S+//6F2UotKmxWA3sNpOu17bn4Q/vcrlAaiAp6v1bmp+EKIBSyjE0OfYtCGoDKsKxDlDYFaj4uQGX0Gj7cRcJDY7ttq3oHA8kEpZ6uJ7lWIE10f1xuMm/Ow7y63e/FZjQgKMz78IPlpF8PMXDkQlf89S1r/DvjoOAY/JEcKzkDrDsfyv5ZLL7v2T4G0lqhM/TZhI6bRbmqfswkx5GZ36PLvFijEWnlEKFXoIR/yFG5Q0YlX/FiHm66KtPe5jWWejMpeiMuejs34o3Hb7tLwpfqFJBjvtno3WMKHMVi0aFX+/2ONxly89/FVrGlm1j29odHohG+KodG3fz+4o/nSa/WsOcV78lO8v9n4X+RIZ0C5+mMxehT90LZOPoy6LQGXPAUh0qzERZa3s3QB+gtYb099Gpr4NO/W+HpTbEPI0KblP4SVRRVoXWgPvniFFBF6AjhkPaDBzv+dnJmQHBF0HYFW6PQwhv2rB4C4ZhYJrO+5alnExjz5Z9nN+6rgcj821SUyN8ls75A31qDI6ERuPo13L6G7z9EPrEzWjtfKKxciPtbXTKM3kTGgD7PvSJoejsjYWfI6gpqMhCCikI6VDSKItFRT6Ain4KLGdN5qdiIGLk6fWWipKE+aamnRoVWsYabKVBm3oeiEb4KtNuFmnt2ILW+SrPJKkRPkunzTzzfwXstYN5ADK/92RIPkebSY4amgKZgIlOeaHQ8ygVCuE34/xT1IDQvihL9ZIFWkxKKVT4NaiEhaiKSx3/VvoFI+puv05oAKrVrULb/q0wCphLBhwdhS+5qUupFrYU/q9x+/MLTVhCI0NJbFx2s3gHAklqhO/K+pFCZ/XNXOypaHxT5gJyJ6YrkAk5a9F254scnqEiR0HoZaefWfL+G9TaUXPiYUoplKU6ypro98nM2R6YOYpaDRwJ4plhuYbh+LfRxfUZ+crN3gpN+IjGHRpSp2mtAidSBDAMg/639SQsItTDkfk26VMjfJfOLqSACeW9+ck8hiPxKGR2Y/MYWKq5LKKUBWKeh/Dr0Rmfg/0fMBJQYZdDcEeUku9AZSUmIZo310xm8UcrWDBzKScPnaJSYgL9hvekyzXtsAbJR3N5p5Ri/Bf3c1+X8Zw8kpQ7+unM0O4mnRpyy1ODvRyl75F5aoTPMo8NPD0qx1kVrAUihmFE3e/BqHyLTv8cnfwYBTfR/UdVXB6QK2eLotm+fifz31nM/m3/ElUhgq7XdqDDoIsICpbFQX1d0rFkvp2+kIUfLCPlRCpV61ZmwO2X0OOGTuXq/ZPJ9wogSY1/0elfopPHuSihUAmLynRlaH+jzRT0kXY4OlMXxDFayIjz/2UFRPFprZk29n3mvPZd7qRtZ0bU1G5ck+cXjadC5VhvhylEoWTyPeH/wgZCSG/ODOX+j+PXVkWPL9cJDYAyolBRzpZzMAArKupBT4YkfMi8qT8y57XvgP8mbTszRHjfX/8y6coXvRabEO4gSY3wWUpZULGvoqLHO+ZccWyF4LaoCu/59QRsZUlF3IKKngTqnHWorPVRcR+hgpp4JzDhVaZp8tnzc53vt5tsXblNJvkTAUV6owmfppQFwq9HhV9/ek4aC0qVn3bkolLh10HYVY41k3SKY34X6wXlasE7kdeBHYc4su+YyzIWq8HaBRtlThwRMCSpEX5DKfcNXdQ6G7J+BvMwGPEQ0gWlQtx2PXdQKshjk+MJ37N9/U6+nfYj29fvIiQsmAvaFWU5D4Utp5CRc0L4EUlqRLmnM75BJz8F+tR/G1U0RD14eh0iIXzbx099yazxn+Z2Bgb4c/XfhR5nt9lpeFF9d4cnhMdIUiPKNZ0xH51UwJBwnXx6qLSBCr/K43EJUVQrv17LrPGfAv91BgbHyCellNOFTQ2LQUL1ONr0beGJMIXwCOkoLMotre3olGddl0l5wdE0JQKC1hqduQDz+GDMQ40wDzXGPDkCnfWrt0Mrsc9e+BrDUvBH+dkJzdndqwyLQWhECBO+vB+LxVLAkUL4J6mpEeVXzm9gHnJdRp+E7JUQ0tUjIQn30Vo7Fv5Mfx/H97nTC6RmLUdnLYXoCX43os6WY+OPldtcljEsBvVbn0dmaiYHdx0mPCqMHtd3YtDd/amcWNFDkQrhGZLUiPLLPFG0cvYilhO+Leun0wkN5J2l2rG+mE5+AoLboazneTqyEjPNos2dWqN+VR7+8C43RyOE90nzkyi/LFXLtpzwOp3zJzp1CmbKK+jMBWid89++9A/4b6HOghjo9E/cHmNZCg4Jom7zRJThfOi+aZpc0K6BB6MSwnukpkaUX9amYKkD9j0UvHaSAqMyBF/k4cDcS9sPQsY8tHkMZakMoZehLP7dDKHNZPSpeyD7ZxyJi0JjAyMOYl5DhbSFnN9xveq7HXI2eiLcMnXlvZfy/M1vFrhPGYrQiFB63tjZw1EFnoO7D5N8LIWEGvHEV63g7XCEE5LUiHJLKQXRE9Anbz295ezExvHNV0VPcEwAGAC0NtEpz0P6TByvz4LGDikvQORoiBjll5P1aW2iT94OOZtObzkrcTFPoU8Og/g5oIIKW/cTCHZTlO7T88bO/LF6O99O+xHDYmDaHU1rhsXAGmzlibkPEh4V5uUo/dfGpVuY8fDH/828rODCXi0Y8eJN1G5cvpdp8UWyoKUo93TWKnTK02Db/t9GSx1U9DhUAHUQNlNeh7SCv9EDqKhHURFDPRhR2dBZv6BP3uKihAVCB4AKgYwvcV5bo1BRD6IihrkhSvfSWrNm/m/MfXMBOzbsJjg0iE5XtOXy0X2pel5lb4fnt379bj3jBz7v6GR+Vv8lw2IQEhbMqz8/xXnNEr0YYfkhq3QXQJIa4YzWGmx/nZ5RuGLALTGgzVT0kfZApvNCKhZV6WeU8q/aCjPpMciYA7iaGTcI4r+E41fgSGrO/dgzQEWgKi5GGbHuCjXg2e12fv3uN9b/uAm7zaRh2/p0vbY9oeH+NTs3OCYmvK7WHZw6nFTgXD+GxaBxhwa8/NMTXoiu/Cnq/Vuan4TgdFNUUCOgkbdDcY/sX3CZ0IBjRuXsdRDS3hMRlR2dQt7RTAXJQVnrQOzrjr432E4fczpxVVGouHcloSmFf3cc5JF+z3BgxyEsVkeT7XdvL2T6/e8zcc4DNO/S2MsRFs+6Hzdx8tApp/tNu8nvy//kwM5DVKtbxXOBCZdk9JMQ5YGZVrRyuojlfImlNrnJiTNGAhCMCu2JqvgTKvIeCO4GIT1Q0eNRFZeigpq5P9YAlZGWyQM9JnFo9xHAUcthtzma+dKT0nm03zP8u+OgN0MstoM7DxeptvbMaxa+QZIaIcoDa52yLedDVNhVuK6pMVDh1+feoJQlARV5B0bcdIwKUxz7jEiPxBqoln7yC0f3H8/tpHw209TYcmzMff17L0RWcpEVIpwuMXG2iNgID0QjikqSGiHKg6AWYDkP53/yFghqhbLW82BQZUNZazpqXhzPztlrgLU+hPtfB2h/8vOc1S5rNew2k58++6XQ89htdpZ88jNju4zn2uq3MbzJvXz63FyST6SUZbhF0u7S1gSFBrksU6VOJeq38r8vAoFMkhohygGlFCrmOSCI/BPQWUCFoaL9t8OjihyJinkeLGeNRFFhEH4DKu4TqYlxs4zUzEJrNbIyXK+hlp2Vw2MDJjP5+tfY+stfnDh4ir1//MN7j87m9mb3cWBnIUualLGImAiue2iQyzK3Pj0Ew5DbqC+Rd0OIckIFN0fFfw4h3fivRsMCIb1R8V+igs73ZnilpsIGohJ+QCUsQiXMR1VajRH9mCQ0HlCnaSIWq/PbiWEoEguZ0+XDSZ+zftFmIO/yD9rUnDycxKQrXyxSc1BZuv7xK7n+0SuxBllQSuV2gA6NCOHet++g2+AOHo1HFE6GdAtRDmkzGcyTYMTLTV+U2q7NexnR4n6XZR76YAw9byh4ZuPszGyurnIb6cnpLs/xyvInaNLR8yMUTx1NYsWXv5J0NJnKiRXpeGVbwiJCPR5HeSZDuoUQTikjGgxJ7EXZOK9ZIjdNvIYPJv4PZag8E9Uppegw6CK6Xee8VmPfn/8WmtAYFoPfV/zllaQmtmIMl97Ry+PXFcUnSY0QQohSu3H81dRqWJ3Pnp/L37/tBqBSYkWuvLs/l4/pg8XiYrmRIs5zGUDzYQo3kaRGCCFEmehyTXu6XNOe1FNp2G12ouOjijTXS+IFNYiKiyTlRKrTMqbdpEX3JmUZrghAftNR+Omnn6Z9+/aEh4cTGxvr7XCEEEI4ERkbQUxCdJGXGgkKDmLQXf2clrdYDRpeVI+GF9UvyzBFAPKbpCY7O5urr76akSNHejsUIYQQZWzII1fQ6cq2gKP/DJxevkRB5cSKjP/CdUdkIcCPmp8mTZoEwKxZs7wbiBBCiDJnsVp49NN76bVgI/PfWcQ/2w8SHR9Jj+s70+OGTjLaSBSJ3yQ1JZGVlUVWVlbu8+TkZC9GI4QoD7TOgOwNQA5YG6Eslbwdkt8wDIO2/VrRtl8rb4ci/FRAJzWTJ0/OreERQgh30tqGTn0D0t8HfWZ4soEO6YWKnoCyxHs1PiHKA6/2qZk4caJj+nYXj3Xr1pX4/OPGjSMpKSn3sX///jKMXgghHLTW6KSHIG3aWQkNgAlZC9EnBjsmPBRCuJVXa2pGjx7N4MGDXZapXbt2ic8fEhJCSEhIiY8XQogiydkImfOc7LSDfT+kfwyRMtBBCHfyalKTkJBAQkKCN0MQQohS0xlf4Fgo1O6khIlO/xQlSY0QbuU3fWr27dvHiRMn2LdvH3a7nY0bNwJQr149IiNl7RohhBfZD+I8oTnNPOKRUIQoz/wmqRk/fjzvv/9+7vOWLVsCsHTpUrp27eqlqIQQAjDicV1TAxixaK35Z/sB0lMyqVK7IjEJsv6WEGVJVukWQohS0lm/oE/e4qKEhZ8XD+KDyRns/eMfwDHBXKcr23L78zdSqVZFzwQqhJ8q6v3bb2YUFkIInxXcDoJaU/DKjBa+/6QaT974N/v+/Dd3q2k3WTHnV0a3HceR/cc8FqoQgUySGiGEKAVtJqFP3QU5vwH5K77T0hvz1qOOCfjOrRg3bSZJx1OY9finnghViIAnSY0QPkprjc5ej07/FJ0xF22e8HZI4hxaZ6FP3ARZi8mf0CiwJPLTD7dgy3Le18a0mSz99BfSUzLcGqsQ5YHfdBQWojzROVvRp+4H+86ztlrRYYNR0eNQKshrsYmzZMwD259Odmqw7+XA9pVYrAa2HOeJjS3bxomDJwmPCnNPnEKUE5LUCOFjtG0X+sT1oDPP2WODjI/ROhUV+7xXYhN56YwvcfSjcTbeQhEZsR3TLLxSPCImvCxDCwgnjyTxw8yl7P59L8GhwbS/vA0X9WuJxWLxdmjCR0lSI4SP0alTQWcBZkF7IXMuOmc4Kuh8T4cmzmUewXlCA6DpctlxZj3jfN0nw1A06dSICpVjyzo6v6W1ZvoDH/DlK9/+9+NVsOC9JSQ2rsmzCx4lobqspSXykz41QvgQrbMh8ztcT+RmQWd+7amQhCtGVVx/jBpUq1uZXkO7ooz8I6OUcvznponXFHqprSu38cyQVxmSOJKb6o3irbve45/tB0ocuq/SWvPMkFf58uVv8+aLp/9/35//8HCfp7HbC5nsUJRLktQI4Ut0KmArvJx0GvYJKvwqCq5RO8NEhV3NPdNvp8+t3R0L9RoKi9XRfBIZG8mEL++neZfGLq/z6XNzuafjYyz/YhVH9x/j4K4jfDPtB25rOpaV36wtuxfkA1bNW8dPn610ul+bmr1b97NuwUbPBSX8hjQ/CeFLVBQQCpzbn+YcRhVPRCMKE9oP0mdDzmbyJzcGWJtA2ACCVBBj376DGx6/il++WkN6cgY1zq9Ku8vbEBziutP3hiW/8+64jwGw2/67hmkzMRU8ec3LfLjrLRKqxZXxi/OOL1/5ttAySilWfr2Wtv1beyAi4U+kpkYIH6JUEIRfgWPKfWdMVNggT4UkXFAqGFXhPQgdSN7viBYIvRQVNwulgnO3VqqZwKC7+nH9Y1fS5Zr2hSY0AHNe/Q7D6uSjWjuSm/lvLyrV6/Al29bsKLSM1prsrBwPRCP8jdTUCOFjVMQd6MwfwTxJgX1rIoajrLU8HpcomDIiUbHPou0PQM5GQENQC5QloUzOv3n5H5g2501cpmmyadnWMrmWLzAsRfuufV7TRDdHIvyR1NQI4QHaTEOnf46Z8gI6dQrattNpWWWpgor7HwR3IM+0+yoWFfUQKvJ+9wcsik1Z4lGhPVChPcssoQFHU0tZlPEXbfq0KLBT9dkMi0Gvm7t6JiDhV6SmRgg30xnfopMfBZ0BWNFoSH0VHdoPFfMsSoXmO0ZZa6DiZqDt/4JtJ6gwCGqepylDlA8tuzdh5bx1TmtrlKFo2aOph6NynyvvHcDyL1e7LHPX1NtkhXNRIKmpEcKNdNYv6KT7zppIz0Zuk1LmAvSph1weryzVUSGdUcFtJKEpp668d4DLhCY4JIh+w3t4OCr3uaBdA+6bcSfKUPlqbCxWg7um3Eb/4T29FJ3wdUqfu8JaACvq0uVClBXz+LWQswlXw35VwnyUtZ7nghJ+Z+6b3/PW3e9hsRi5I6AMi4E1yMITXz9E60uaeznCsndg5yG+nfYjf6z+G4vV4KK+Lek7rAfR8VHeDk14QVHv35LUCOEm2n4UfbRDIaUsqMjRqMhRHolJ+K+dm/Ywb8oP/P7LX1itFtr2b8WAEZdQqVZFb4cmhNsV9f4tfWqEcBedWoRCCm2mEjjdPIW71G1em3umj/B2GEL4NElqhHAXozIQDGS7KGRDWWt7Jh4hhE86vPco305fyPZ1OwkKsdK2Xyt63NBZVm0vAWl+EsKNzKTHIeMLnK/lFIaq9AvKiHRrHNpMg/SP0emfgHnQMXNx2EBUxC0oSzW3Xlvkl5acTnZGNtEJUbLidDm3YOZSXrl9GgCm3UQpxzJXMQnRPL9wPOc1k/l4QPrUFEiSGuFp2n4MffwqMA+TN7ExAI2KeQEVdpl7YzCT0SeGgG0HeTssW0CFo+I+RgU1dGsMwuG3xb8z++kv2fSTY7K8qLhILr2jF4MfHkhYpHwrL2+2/PIX93Z+vMCF3g2LQXR8FB/sfJOwiPzTPpQ3Rb1/y5BuIdxIWRJQ8V9A2JU4mqJOC2qKqjDD7QkNgE553jHXTb4RWHbQ6ehTd1OOvtt4zcIPl/FQryf4fcWfudtSTqTy6XNzua/rRDJSM7wYnfCGL16ah8XJDMqm3eTUkSSWfvKLh6Pyb5LUCOFmypKAEfMUqtKvqIQFqIorMOI/R4V0cvu1tZkMGXNx3vxlB/tuyF7j9ljKmtYZ6Kzl6MwfXM7Q7AuSj6c4mhi042Z1NtNusnPTHj57/msvRSe8Zc2CDXkWKT2XUoq1CzZ4MCL/J0mNEB6ijAiU9TyUpbLnLmrbgeuOygAG2H73RDRlQmsTnToVfaQ9+uRw9Kkx6GN9MY8PRtt2eTu8Ai36cDm2HGeJpSOxmTf1R+x252VE4LG7+J0Ax8Kdtmybh6IJDJLUCBHIVOGrQDsa9ItSzjfolGfRqa+ATsu7I2cT+vi1aNs/3gnMhT1b92MYrj9uk4+nkHKiKNMAiEBRv9V5GC7WuTIMRYM2MjFncUhSI0QgszYEI66QQhpCOnsknNLStn2Q/r6TvXbQqejUKR6NqShCI0KKVC4kvGjlRGC44u5+mKbz/mzKYtBnWHcPRuT/JKkRIoApFYSKGO6ihAWC20Pmt5in7sZMGofOXIrWvtkMojO+wvXHlh0yv0HrwprcPKvjFW2x25z/TA2LQaueTWWUSznT7bqO9D29bpdxVodhi9XAsBg8/MEYEqoV9qVEnE0m3xMi0IXfCrZ/IeNjwIKj0/Dpf41qkL0anb0aRzOUgc740lHDU+E9lCXBm5HnZx6BQudfzgYzGXwo9qadGtG4fQP+/PXvfB2FAbSpGfLIlV6ITHiTUop7p4+gVY+mfPX6fP7esBtrkIV2l17IlfcO4PzWdb0dot+ReWqEKCd0zlZ0xudg2w9GBbDUgrQ3nZS2gPUCVPwXKOU7iziYKa9C2nScj+YCsKIqb0Ap32rKST6ewvjLn2Prym1YrBZQYNpMgkKs3PfunXS/rqO3QxTCZ8naT0KIPFRQY1RQ49zn5vFrcDTlFDSk1O4YEZW9BkLaeirEQqmwy9FprvrMWCB0gM8lNADR8VG8suJJtvz8F7989SuZ6dnUblyTnjd2JjI2wtvhCREQJKkRohzS5inI2VhIKSs6awnKl5Iaax102HWQ8UkBey2gwnx6xXOlFE07NaJpp0beDkWIgCQdhYUoj4rckTbLrWGUhIoeDxF3AOfUxlgboOI+QVllrRwhyiupqRGiPDLiQVUAfdJFIRvK6ns1CkpZUFFj0RG3Q/YvoDPBWi9P05oQonySpEaIckgpCzr8ekibQsF9ahSoMAgd4OnQikwZkRDa29thCCF8iDQ/CVFOqcjbIagF+YdIWwALKuYVlCEdWIUQ/kOSGiHKKaVCUXHvo6IeAKPq6a0WCLkEFf8/VGg3r8YnhBDFJc1PQpRjSoVAxHBUxPDTs/BaUUq+6wgh/JMkNUIIAJQK9nYIQghRKvKVTAghhBABQZIaIYQQQgQESWqEEEIIERAkqRFCCCFEQJCkRgghhBABQZIaIYQQQgQEGdIthBABSGvNz1+tYe4b89m+bifWIAttB7TmynsGUL/Ved4OTwi3UFpr7e0gPCU5OZmYmBiSkpKIjo72djhCCOEWWmveGPMu86b8gGExMO2O9b0sVgOt4ZGP76bLNe29HKUQRVfU+7c0PwkhRIBZ/vkq5k35ASA3oQGw20xM02Tyja9z7MAJb4UnhNtIUiOEEAFmzmvfYVicfLxr0HaT72cs9mxQQniAJDVCCBFgtq/bmaeG5lymqflrzd8ejEgIz5CkRgghAozTWprTlFJYrBYPRSOE50hSI4QQAaZNn5ZYrM4/3jWaNn1aejAiITxDkhohhAgwV913KXYnzU+GxSA6Looe13fycFRCuJ8kNUIIEWCadGjI2LfvwDDUf01RyvGIiAnn2R8eIzwqzKsxCuEOMvmeEEIEoL7DetCiWxO+nb6Qv9b8TXBIEG37t+aSmzoTERPh7fCEcAuZfE8IIfzQycOnOLjrMGFRYdRuXBOllLdDEsJtinr/lpoaIYTwI4f2HGHqvbNYNW8d2nR8J61evyo3P3EtXa/t4OXohPAuSWqEEMJPHN57lNFtx5FyIjU3oQH4d8dBnr7uVVJOpHLpyN5ejFAI75KOwkII4SdmPvYJqSdT80+sdzq/mTp2Fqmn0jwfmBA+QpIaIYTwcaeOJvHuIx+z+OMV2G3OZwq2ZdtZMvtnD0YmhG+R5ichhPBh//x9kLGdH+fU0eRCy1qsBod2H/ZAVEL4JklqhBDCw3Kyc1j04XLmTf2Bf3ccIiImnB7Xd+by0X1IqBaXW05rzZNXv0TS8ZQ8fWicMU1NVFyUO0MXwqdJUiOEEB6UnZnNo/0ns3HpFpSh0KYmPTmD/73wNd9N/5GXfppEnaaJAGxduY1dm/cW+dymadJ1cHt3hS6Ez/OLPjV79uxh2LBh1KlTh7CwMOrWrcuECRPIzs72dmhCCFEsH076nE3LtgLkqX0x7SZpyRlMGPQCpunoN/Pnqu2FLk55hlKKfsN7ULVO5bIPWgg/4Rc1NX/99RemaTJ9+nTq1avHli1buO2220hLS+PFF1/0dnhCCFEk2ZnZzJv2o9OmJNNucnDXYdYv3Eyb3i0wLAZFmR9VGYpLR/Zi5Ms3l3HEQvgXv0hq+vTpQ58+fXKfn3feeWzbto2pU6dKUiOE8Bv/7jhEWlK6yzIWq8Gfq7bTpncLWvVsWmhfmpDwYGZseYUqtSuVZahC+CW/aH4qSFJSEnFxcS7LZGVlkZycnOchhBDeUpSmJK3BYrUAUKdpIi26NcGwFnycUjD4oUGS0Ahxml8mNTt37uSNN97gjjvucFlu8uTJxMTE5D5q1qzpoQiFECK/GudXJb5aBZdlTLtJy55Nc58/8sk9JDaqATiamcBRmwPQdXBHrhs3yE3RCuF/vLqg5cSJE5k0aZLLMmvXruXCCy/MfX7gwAG6dOlCly5dmDFjhstjs7KyyMrKyn2enJxMzZo1ZUFLIYTXfPnKt0y77/0C91msBvVb1+X1lU/nWaAyOyuHn79czaKPlpN0NJlq9avQd1hPWnZvIgtZinKhqAtaejWpOXbsGMeOHXNZpnbt2oSGhgKOhKZbt260bduWWbNmYRjFq2iSVbqFEN5mmiav3/kO3729CIvVwG4zc4d212xQjReWTCS+quvaHCHKG79Iaorj33//pVu3brRu3ZqPPvoIi8VS7HNIUiOE8AVaa7au3Mb8dxaxf9u/RFWIpNt1HelydTuCQ4O9HZ4QPiegkpozTU61atXigw8+yJPQVKlSpcjnkaRGCCGE8D9FvX/7xZDuH3/8kR07drBjxw5q1KiRZ58f5GRCCCGE8AC/GP108803o7Uu8CGEEEIIAX6S1AghhBBCFEaSGiGEEEIEBElqhBBCCBEQJKkRQgghRECQpEYIIYQQAUGSGiGEEEIEBElqhBBCCBEQJKkRQgghREDwixmFy8qZyfqSk5O9HIkQQgghiurMfbuwSXfLVVKTkpICQM2aNb0ciRBCCCGKKyUlhZiYGKf7/WJBy7JimiYHDhwgKioKpZS3w/F7ycnJ1KxZk/3798sCoT5A3g/fIu+H75H3xLcU5/3QWpOSkkK1atUwDOc9Z8pVTY1hGPkWxBSlFx0dLR8QPkTeD98i74fvkffEtxT1/XBVQ3OGdBQWQgghRECQpEYIIYQQAUGSGlFiISEhTJgwgZCQEG+HIpD3w9fI++F75D3xLe54P8pVR2EhhBBCBC6pqRFCCCFEQJCkRgghhBABQZIaIYQQQgQESWqEEEIIERAkqRFlYs+ePQwbNow6deoQFhZG3bp1mTBhAtnZ2d4Ordx6+umnad++PeHh4cTGxno7nHJnypQp1KlTh9DQUFq3bs2KFSu8HVK5tXz5ci699FKqVauGUoq5c+d6O6Rya/LkybRp04aoqCgqVarEwIED2bZtW5mdX5IaUSb++usvTNNk+vTpbN26lVdeeYVp06bxyCOPeDu0cis7O5urr76akSNHejuUcuezzz7jnnvu4dFHH2XDhg106tSJvn37sm/fPm+HVi6lpaXRvHlz3nzzTW+HUu4tW7aMUaNGsXr1ahYuXIjNZqNXr16kpaWVyfllSLdwmxdeeIGpU6eya9cub4dSrs2aNYt77rmHU6dOeTuUcqNt27a0atWKqVOn5m5r1KgRAwcOZPLkyV6MTCil+Oqrrxg4cKC3QxHA0aNHqVSpEsuWLaNz586lPp/U1Ai3SUpKIi4uztthCOFR2dnZrF+/nl69euXZ3qtXL1auXOmlqITwTUlJSQBldq+QpEa4xc6dO3njjTe44447vB2KEB517Ngx7HY7lStXzrO9cuXKHDp0yEtRCeF7tNaMHTuWjh070qRJkzI5pyQ1wqWJEyeilHL5WLduXZ5jDhw4QJ8+fbj66qsZPny4lyIPTCV5P4R3KKXyPNda59smRHk2evRoNm/ezCeffFJm57SW2ZlEQBo9ejSDBw92WaZ27dq5/3/gwAG6detGu3btePvtt90cXflT3PdDeF5CQgIWiyVfrcyRI0fy1d4IUV6NGTOGb775huXLl1OjRo0yO68kNcKlhIQEEhISilT233//pVu3brRu3ZqZM2diGFIRWNaK834I7wgODqZ169YsXLiQQYMG5W5fuHAhl19+uRcjE8L7tNaMGTOGr776ip9++ok6deqU6fklqRFl4sCBA3Tt2pVatWrx4osvcvTo0dx9VapU8WJk5de+ffs4ceIE+/btw263s3HjRgDq1atHZGSkd4MLcGPHjuXGG2/kwgsvzK213Ldvn/Qx85LU1FR27NiR+3z37t1s3LiRuLg4atWq5cXIyp9Ro0Yxe/Zsvv76a6KionJrNGNiYggLCyv9BbQQZWDmzJkaKPAhvGPo0KEFvh9Lly71dmjlwltvvaUTExN1cHCwbtWqlV62bJm3Qyq3li5dWuDfwtChQ70dWrnj7D4xc+bMMjm/zFMjhBBCiIAgnR6EEEIIERAkqRFCCCFEQJCkRgghhBABQZIaIYQQQgQESWqEEEIIERAkqRFCCCFEQJCkRgghhBABQZIaIYQQQgQESWqEEH7BbrfTvn17rrzyyjzbk5KSqFmzJo899hgAd999N61btyYkJIQWLVp4IVIhhLdIUiOE8AsWi4X333+fBQsW8PHHH+duHzNmDHFxcYwfPx5wLJh36623cu2113orVCGEl8iClkIIv1G/fn0mT57MmDFj6NatG2vXruXTTz9lzZo1BAcHA/D6668DcPToUTZv3uzNcIUQHiZJjRDCr4wZM4avvvqKm266id9//53x48dLM5MQApCkRgjhZ5RSTJ06lUaNGtG0aVMefvhhb4ckhPAR0qdGCOF33nvvPcLDw9m9ezf//POPt8MRQvgISWqEEH5l1apVvPLKK3z99de0a9eOYcOGobX2dlhCCB8gSY0Qwm9kZGQwdOhQRowYQc+ePZkxYwZr165l+vTp3g5NCOEDJKkRQviNhx9+GNM0ee655wCoVasWL730Eg888AB79uwBYMeOHWzcuJFDhw6RkZHBxo0b2bhxI9nZ2V6MXAjhCUpLva0Qwg8sW7aMHj168NNPP9GxY8c8+3r37o3NZmPRokV069aNZcuW5Tt+9+7d1K5d20PRCiG8QZIaIYQQQgQEaX4SQgghRECQpEYIIYQQAUGSGiGEEEIEBElqhBBCCBEQJKkRQgghRECQpEYIIYQQAUGSGiGEEEIEBElqhBBCCBEQJKkRQgghRECQpEYIIYQQAUGSGiGEEEIEBElqhBBCCBEQ/g+B1MqlGYJWWwAAAABJRU5ErkJggg==", 87 | "text/plain": [ 88 | "
" 89 | ] 90 | }, 91 | "metadata": {}, 92 | "output_type": "display_data" 93 | } 94 | ], 95 | "source": [ 96 | "import matplotlib.pyplot as plt\n", 97 | "\n", 98 | "plt.scatter(X[:,0],X[:,1], c=y)\n", 99 | "plt.xlabel('X1')\n", 100 | "plt.ylabel('X2')\n", 101 | "plt.title('A binary classification with 2 features')\n", 102 | "\n" 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "metadata": {}, 108 | "source": [ 109 | "### Step 3: Define the Neural Network Structure\n", 110 | "\n", 111 | "We’ll use a neural network with:\n", 112 | "\n", 113 | "Input layer of 2 nodes (for 2 features),\n", 114 | "1 hidden layer with 4 nodes,\n", 115 | "Output layer with 1 node (for binary classification)." 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": 5, 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [ 124 | "# Define network dimensions\n", 125 | "input_dim = 2\n", 126 | "hidden_dim = 4\n", 127 | "output_dim = 1\n", 128 | "\n", 129 | "# Initialize weights and biases with small random values\n", 130 | "W1 = np.random.randn(input_dim, hidden_dim) * 0.01\n", 131 | "b1 = np.zeros((1, hidden_dim))\n", 132 | "W2 = np.random.randn(hidden_dim, output_dim) * 0.01\n", 133 | "b2 = np.zeros((1, output_dim))\n" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "metadata": {}, 139 | "source": [ 140 | "#### Step 4: Define Activation and Loss Functions\n", 141 | "\n", 142 | "We’ll use the ReLU activation for the hidden layer and sigmoid for the output layer." 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": 6, 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [ 151 | "def sigmoid(x):\n", 152 | " return 1 / (1 + np.exp(-x))\n", 153 | "\n", 154 | "def sigmoid_derivative(x):\n", 155 | " return sigmoid(x) * (1 - sigmoid(x))\n", 156 | "\n", 157 | "def relu(x):\n", 158 | " return np.maximum(0, x)\n", 159 | "\n", 160 | "def relu_derivative(x):\n", 161 | " return (x > 0).astype(float)\n", 162 | "\n", 163 | "def binary_cross_entropy(predictions, targets):\n", 164 | " # Binary cross-entropy loss\n", 165 | " m = targets.shape[0]\n", 166 | " return -np.mean(targets * np.log(predictions + 1e-9) + (1 - targets) * np.log(1 - predictions + 1e-9))\n" 167 | ] 168 | }, 169 | { 170 | "cell_type": "markdown", 171 | "metadata": {}, 172 | "source": [ 173 | "#### Step 5: Forward Propagation\n", 174 | "\n", 175 | "Compute the activations for each layer in the network." 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": 7, 181 | "metadata": {}, 182 | "outputs": [], 183 | "source": [ 184 | "def forward_propagation(X):\n", 185 | " # Hidden layer\n", 186 | " Z1 = np.dot(X, W1) + b1\n", 187 | " A1 = relu(Z1)\n", 188 | " \n", 189 | " # Output layer\n", 190 | " Z2 = np.dot(A1, W2) + b2\n", 191 | " A2 = sigmoid(Z2)\n", 192 | " \n", 193 | " return Z1, A1, Z2, A2\n" 194 | ] 195 | }, 196 | { 197 | "cell_type": "markdown", 198 | "metadata": {}, 199 | "source": [ 200 | "#### Step 6: Backward Propagation\n", 201 | "Compute gradients of weights and biases." 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": 8, 207 | "metadata": {}, 208 | "outputs": [], 209 | "source": [ 210 | "def backward_propagation(X, y, Z1, A1, Z2, A2):\n", 211 | " m = X.shape[0]\n", 212 | " \n", 213 | " # Output layer gradients\n", 214 | " dZ2 = A2 - y\n", 215 | " dW2 = (1 / m) * np.dot(A1.T, dZ2)\n", 216 | " db2 = (1 / m) * np.sum(dZ2, axis=0, keepdims=True)\n", 217 | " \n", 218 | " # Hidden layer gradients\n", 219 | " dA1 = np.dot(dZ2, W2.T)\n", 220 | " dZ1 = dA1 * relu_derivative(Z1)\n", 221 | " dW1 = (1 / m) * np.dot(X.T, dZ1)\n", 222 | " db1 = (1 / m) * np.sum(dZ1, axis=0, keepdims=True)\n", 223 | " \n", 224 | " return dW1, db1, dW2, db2\n" 225 | ] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "metadata": {}, 230 | "source": [ 231 | "#### Step 7: Gradient Descent Update" 232 | ] 233 | }, 234 | { 235 | "cell_type": "code", 236 | "execution_count": 9, 237 | "metadata": {}, 238 | "outputs": [], 239 | "source": [ 240 | "learning_rate = 0.1\n", 241 | "\n", 242 | "def update_parameters(W1, b1, W2, b2, dW1, db1, dW2, db2, learning_rate):\n", 243 | " W1 -= learning_rate * dW1\n", 244 | " b1 -= learning_rate * db1\n", 245 | " W2 -= learning_rate * dW2\n", 246 | " b2 -= learning_rate * db2\n", 247 | " return W1, b1, W2, b2\n" 248 | ] 249 | }, 250 | { 251 | "cell_type": "markdown", 252 | "metadata": {}, 253 | "source": [ 254 | "#### Step 8: Training Loop\n", 255 | "\n", 256 | "Now we’ll combine everything and run a training loop for several epochs." 257 | ] 258 | }, 259 | { 260 | "cell_type": "code", 261 | "execution_count": 10, 262 | "metadata": {}, 263 | "outputs": [ 264 | { 265 | "name": "stdout", 266 | "output_type": "stream", 267 | "text": [ 268 | "Epoch 0, Loss: 0.6932\n", 269 | "Epoch 100, Loss: 0.6922\n", 270 | "Epoch 200, Loss: 0.6912\n", 271 | "Epoch 300, Loss: 0.6854\n", 272 | "Epoch 400, Loss: 0.6610\n", 273 | "Epoch 500, Loss: 0.6253\n", 274 | "Epoch 600, Loss: 0.5914\n", 275 | "Epoch 700, Loss: 0.5485\n", 276 | "Epoch 800, Loss: 0.5043\n", 277 | "Epoch 900, Loss: 0.4531\n" 278 | ] 279 | } 280 | ], 281 | "source": [ 282 | "epochs = 1000\n", 283 | "\n", 284 | "for epoch in range(epochs):\n", 285 | " # Forward propagation\n", 286 | " Z1, A1, Z2, A2 = forward_propagation(X)\n", 287 | " \n", 288 | " # Compute loss\n", 289 | " loss = binary_cross_entropy(A2, y)\n", 290 | " \n", 291 | " # Backward propagation\n", 292 | " dW1, db1, dW2, db2 = backward_propagation(X, y, Z1, A1, Z2, A2)\n", 293 | " \n", 294 | " # Update parameters\n", 295 | " W1, b1, W2, b2 = update_parameters(W1, b1, W2, b2, dW1, db1, dW2, db2, learning_rate)\n", 296 | " \n", 297 | " # Print loss every 100 epochs\n", 298 | " if epoch % 100 == 0:\n", 299 | " print(f\"Epoch {epoch}, Loss: {loss:.4f}\")\n" 300 | ] 301 | }, 302 | { 303 | "cell_type": "markdown", 304 | "metadata": {}, 305 | "source": [ 306 | "#### Step 9: Testing the Model\n", 307 | "\n", 308 | "After training, let’s test the model's accuracy." 309 | ] 310 | }, 311 | { 312 | "cell_type": "code", 313 | "execution_count": 11, 314 | "metadata": {}, 315 | "outputs": [ 316 | { 317 | "name": "stdout", 318 | "output_type": "stream", 319 | "text": [ 320 | "Training Accuracy: 84.00%\n" 321 | ] 322 | } 323 | ], 324 | "source": [ 325 | "def predict(X):\n", 326 | " _, _, _, A2 = forward_propagation(X)\n", 327 | " return (A2 > 0.5).astype(int)\n", 328 | "\n", 329 | "# Test accuracy on training data\n", 330 | "predictions = predict(X)\n", 331 | "accuracy = np.mean(predictions == y) * 100\n", 332 | "print(f\"Training Accuracy: {accuracy:.2f}%\")\n" 333 | ] 334 | }, 335 | { 336 | "cell_type": "markdown", 337 | "metadata": {}, 338 | "source": [ 339 | "#### Additional: Model Parameter Count and FLOPs\n", 340 | "\n", 341 | "1. Parameter Count: The total number of parameters in a neural network is the sum of the number of weights and biases for each layer.\n", 342 | "\n", 343 | "2. FLOP Count: The FLOPs for a fully connected layer are calculated based on the number of multiplications and additions. Each weight requires a multiplication with an input and an addition to the output for each neuron in the layer.\n", 344 | "\n" 345 | ] 346 | }, 347 | { 348 | "cell_type": "code", 349 | "execution_count": 12, 350 | "metadata": {}, 351 | "outputs": [ 352 | { 353 | "name": "stdout", 354 | "output_type": "stream", 355 | "text": [ 356 | "Total Parameters: 17\n", 357 | "Total FLOPs per forward pass: 32\n" 358 | ] 359 | } 360 | ], 361 | "source": [ 362 | "def count_parameters():\n", 363 | " total_params = 0\n", 364 | " total_params += W1.size + b1.size # Parameters for first layer\n", 365 | " total_params += W2.size + b2.size # Parameters for second layer\n", 366 | " return total_params\n", 367 | "\n", 368 | "def count_flops(X):\n", 369 | " # FLOPs for fully connected layers = 2 * (input_size * output_size)\n", 370 | " # This accounts for multiplications and additions\n", 371 | " \n", 372 | " flops = 0\n", 373 | " \n", 374 | " # FLOPs for first layer\n", 375 | " input_size = X.shape[1]\n", 376 | " hidden_size = W1.shape[1]\n", 377 | " flops += 2 * input_size * hidden_size # Multiplications and additions\n", 378 | " \n", 379 | " # FLOPs for ReLU activations (just comparisons, typically count as 1 FLOP each)\n", 380 | " flops += hidden_size\n", 381 | " \n", 382 | " # FLOPs for second layer\n", 383 | " flops += 2 * hidden_size * W2.shape[1]\n", 384 | " \n", 385 | " # FLOPs for sigmoid activation in output layer\n", 386 | " flops += W2.shape[1] * 4 # Sigmoid (multiplications, divisions, exponentiation)\n", 387 | " \n", 388 | " return flops\n", 389 | "\n", 390 | "# Example usage:\n", 391 | "print(f\"Total Parameters: {count_parameters()}\")\n", 392 | "print(f\"Total FLOPs per forward pass: {count_flops(X)}\")\n" 393 | ] 394 | }, 395 | { 396 | "cell_type": "markdown", 397 | "metadata": {}, 398 | "source": [ 399 | "Generally, the FLOPs for backpropagation are about twice as much as the forward pass because:\n", 400 | "\n", 401 | "Gradient Computation: The gradients for weights, biases, and activations require both multiplications and additions similar to the forward pass.\n", 402 | "Chain Rule Application: Additional operations are needed to propagate the error backward.\n", 403 | "Here’s an approximate breakdown of the FLOPs required for each layer in the backward pass:\n", 404 | "\n", 405 | "1. Output Layer:\n", 406 | "\n", 407 | "Gradient of Loss w.r.t Output: Similar to forward pass FLOPs.\n", 408 | "Gradient w.r.t Weights and Biases: Roughly equivalent to forward pass for each parameter.\n", 409 | "Propagation of Error to Previous Layer: Similar in complexity to forward pass.\n", 410 | "\n", 411 | "2. Hidden Layer:\n", 412 | "\n", 413 | "Gradient w.r.t Activations: Similar to the forward pass, plus the application of the derivative of the activation function (e.g., ReLU or sigmoid).\n", 414 | "Gradient w.r.t Weights and Biases: Similar to forward pass.\n", 415 | "Propagation of Error: Similar to forward pass.\n", 416 | "Using these principles, here’s an approximate calculation of FLOPs for the backward pass:" 417 | ] 418 | }, 419 | { 420 | "cell_type": "code", 421 | "execution_count": 13, 422 | "metadata": {}, 423 | "outputs": [ 424 | { 425 | "name": "stdout", 426 | "output_type": "stream", 427 | "text": [ 428 | "Total FLOPs for backward pass: 53\n" 429 | ] 430 | } 431 | ], 432 | "source": [ 433 | "def count_backward_flops(X):\n", 434 | " # Backward FLOPs calculation for each layer\n", 435 | " flops = 0\n", 436 | "\n", 437 | " # Output layer FLOPs\n", 438 | " hidden_size = W1.shape[1]\n", 439 | " output_size = W2.shape[1]\n", 440 | " \n", 441 | " # Gradient of loss with respect to A2\n", 442 | " flops += output_size # For binary cross-entropy loss\n", 443 | "\n", 444 | " # Gradient of output layer weights and biases\n", 445 | " flops += 2 * hidden_size * output_size # Gradient of W2 and bias (multiplies and additions)\n", 446 | "\n", 447 | " # Propagating gradient back to hidden layer (similar to forward pass)\n", 448 | " flops += 2 * hidden_size * output_size\n", 449 | "\n", 450 | " # Hidden layer FLOPs\n", 451 | " input_size = X.shape[1]\n", 452 | " \n", 453 | " # Gradient with respect to A1, involving the derivative of ReLU\n", 454 | " flops += hidden_size # ReLU derivatives (1 comparison per neuron)\n", 455 | "\n", 456 | " # Gradient of hidden layer weights and biases\n", 457 | " flops += 2 * input_size * hidden_size # Gradient of W1 and bias (multiplies and additions)\n", 458 | " \n", 459 | " # Propagation of gradient back to input layer\n", 460 | " flops += 2 * input_size * hidden_size\n", 461 | "\n", 462 | " return flops\n", 463 | "\n", 464 | "# Example usage:\n", 465 | "print(f\"Total FLOPs for backward pass: {count_backward_flops(X)}\")\n" 466 | ] 467 | }, 468 | { 469 | "cell_type": "markdown", 470 | "metadata": {}, 471 | "source": [ 472 | "#### Next ?\n", 473 | "\n", 474 | "What if we add additional hidden layers ?\n", 475 | "\n", 476 | "What if we use different activation function ?\n", 477 | "\n", 478 | "What if we use a linear regression loss ?" 479 | ] 480 | } 481 | ], 482 | "metadata": { 483 | "kernelspec": { 484 | "display_name": "base", 485 | "language": "python", 486 | "name": "python3" 487 | }, 488 | "language_info": { 489 | "codemirror_mode": { 490 | "name": "ipython", 491 | "version": 3 492 | }, 493 | "file_extension": ".py", 494 | "mimetype": "text/x-python", 495 | "name": "python", 496 | "nbconvert_exporter": "python", 497 | "pygments_lexer": "ipython3", 498 | "version": "3.11.5" 499 | } 500 | }, 501 | "nbformat": 4, 502 | "nbformat_minor": 2 503 | } 504 | -------------------------------------------------------------------------------- /Notebooks/Regularization and Cross-Validation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Linear Regression with Cross-Validation and Regularization\n", 15 | "\n", 16 | "In this demo, we will explore two important concepts in machine learning: **Cross-Validation** and **Regularization**. We will use the `scikit-learn` package to perform these tasks.\n", 17 | "\n", 18 | "### Cross-Validation:\n", 19 | "Cross-validation is a technique for evaluating machine learning models by splitting the dataset into training and test sets multiple times to ensure that the model's performance generalizes well to unseen data. One common approach is **k-fold cross-validation**, where the data is split into `k` subsets, and the model is trained on `k-1` subsets while the remaining subset is used for validation. This process is repeated `k` times, and the average performance is taken.\n", 20 | "\n", 21 | "### Regularization:\n", 22 | "Regularization techniques add a penalty to the cost function of a machine learning model to prevent overfitting. We focus on two types of regularization:\n", 23 | "- **Ridge Regression (L2 Regularization)**: Adds a penalty proportional to the square of the coefficients.\n", 24 | " \n", 25 | " $\n", 26 | " J(\\theta) = \\frac{1}{2m} \\sum_{i=1}^{m} \\left( \\hat{y}^{(i)} - y^{(i)} \\right)^2 + \\lambda \\sum_{j=1}^{n} \\theta_j^2\n", 27 | "$\n", 28 | "\n", 29 | "- **Lasso Regression (L1 Regularization)**: Adds a penalty proportional to the absolute value of the coefficients.\n", 30 | "\n", 31 | " $\n", 32 | " J(\\theta) = \\frac{1}{2m} \\sum_{i=1}^{m} \\left( \\hat{y}^{(i)} - y^{(i)} \\right)^2 + \\lambda \\sum_{j=1}^{n} |\\theta_j|\n", 33 | " $\n", 34 | "\n", 35 | "The parameter $\\lambda$ (also known as `alpha` in `scikit-learn`) controls the strength of regularization: higher values increase the penalty and thus shrink the model coefficients.\n" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 1, 41 | "metadata": {}, 42 | "outputs": [ 43 | { 44 | "name": "stdout", 45 | "output_type": "stream", 46 | "text": [ 47 | "Linear Regression (no regularization) 5-fold cross-validation R^2 scores: [1. 1. 1. 1. 1.]\n", 48 | "Average R^2: 1.0\n", 49 | "\n", 50 | "Ridge Regression (L2) 5-fold cross-validation R^2 scores: [0.99840327 0.9979706 0.99798774 0.99762316 0.99803145]\n", 51 | "Average R^2: 0.9980032443503974\n", 52 | "\n", 53 | "Lasso Regression (L1) 5-fold cross-validation R^2 scores: [0.99212177 0.98990394 0.98996858 0.9882734 0.99021121]\n", 54 | "Average R^2: 0.9900957787734809\n" 55 | ] 56 | }, 57 | { 58 | "data": { 59 | "text/html": [ 60 | "
LinearRegression()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" 465 | ], 466 | "text/plain": [ 467 | "LinearRegression()" 468 | ] 469 | }, 470 | "execution_count": 1, 471 | "metadata": {}, 472 | "output_type": "execute_result" 473 | } 474 | ], 475 | "source": [ 476 | "import numpy as np\n", 477 | "import matplotlib.pyplot as plt\n", 478 | "from sklearn.linear_model import LinearRegression, Ridge, Lasso\n", 479 | "from sklearn.model_selection import cross_val_score, train_test_split\n", 480 | "\n", 481 | "# Generate synthetic data for the demo\n", 482 | "np.random.seed(0)\n", 483 | "X = 2 * np.random.rand(100, 1)\n", 484 | "y = 4 + 3 * X + np.random.randn(100, 1).ravel()\n", 485 | "\n", 486 | "# Split the data into training and testing sets\n", 487 | "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n", 488 | "\n", 489 | "# Cross-validation with a simple Linear Regression (no regularization)\n", 490 | "lin_reg = LinearRegression()\n", 491 | "\n", 492 | "# Perform 5-fold cross-validation and compute the average R^2 score\n", 493 | "scores = cross_val_score(lin_reg, X_train, y_train, cv=5, scoring='r2')\n", 494 | "print(f\"Linear Regression (no regularization) 5-fold cross-validation R^2 scores: {scores}\")\n", 495 | "print(f\"Average R^2: {scores.mean()}\")\n", 496 | "\n", 497 | "# Ridge Regression (L2 Regularization)\n", 498 | "ridge_reg = Ridge(alpha=1.0) # alpha is the regularization strength\n", 499 | "\n", 500 | "# Perform cross-validation for Ridge\n", 501 | "ridge_scores = cross_val_score(ridge_reg, X_train, y_train, cv=5, scoring='r2')\n", 502 | "print(f\"\\nRidge Regression (L2) 5-fold cross-validation R^2 scores: {ridge_scores}\")\n", 503 | "print(f\"Average R^2: {ridge_scores.mean()}\")\n", 504 | "\n", 505 | "# Lasso Regression (L1 Regularization)\n", 506 | "lasso_reg = Lasso(alpha=0.1) # alpha is the regularization strength\n", 507 | "\n", 508 | "# Perform cross-validation for Lasso\n", 509 | "lasso_scores = cross_val_score(lasso_reg, X_train, y_train, cv=5, scoring='r2')\n", 510 | "print(f\"\\nLasso Regression (L1) 5-fold cross-validation R^2 scores: {lasso_scores}\")\n", 511 | "print(f\"Average R^2: {lasso_scores.mean()}\")\n", 512 | "\n", 513 | "# Train and compare models on the test set\n", 514 | "lin_reg\n" 515 | ] 516 | } 517 | ], 518 | "metadata": { 519 | "kernelspec": { 520 | "display_name": "base", 521 | "language": "python", 522 | "name": "python3" 523 | }, 524 | "language_info": { 525 | "codemirror_mode": { 526 | "name": "ipython", 527 | "version": 3 528 | }, 529 | "file_extension": ".py", 530 | "mimetype": "text/x-python", 531 | "name": "python", 532 | "nbconvert_exporter": "python", 533 | "pygments_lexer": "ipython3", 534 | "version": "3.11.5" 535 | } 536 | }, 537 | "nbformat": 4, 538 | "nbformat_minor": 2 539 | } 540 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Course Image](https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/main/Course%20Image.png) 2 | 3 | 4 | # Practical Applications in Machine Learning 5 | This is a repository for PALM students at the Royal University of Phnom Penh (2024). The materials for this course are adapted from https://introml.mit.edu/. The contents are solely for educational purposes. 6 | 7 | (The below contents will be updated from time to time.) 8 | 9 | 10 | # Practical Applications in Machine Learning (PAML 2024) 11 | 12 | Welcome to the **PAML 2024**! This course will cover the foundation and practical applications in machine learning for undergraduate students. Below is the weekly breakdown of the course content, assignments, and key deliverables. 13 | 14 | ## Table of Contents 15 | 16 | | **Week** | **Topic** | **Description** | **Readings/Resources** | **Labs** | **Assignments** | 17 | |----------|------------|-----------------|-----------------------|-----------------|-----------------| 18 | | Topic 1 | Overview of ML | [Sldie](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Week%201/Intro_General.pdf) | [Link to Readings/Resources](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Reading%20Materials/Introduction.pdf)| [Python](https://faculty.washington.edu/otoomet/machinelearning-py/python.html); [Numpy & Pandas](https://faculty.washington.edu/otoomet/machinelearning-py/numpy-and-pandas.html); [Linear Algebra](https://faculty.washington.edu/otoomet/machinelearning-py/la.html) | [Week 1: Homework](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Homeworks/Week%201.ipynb) (Deadline: 30th Sept. 2024)| 19 | | Topic 2 | Linear Regression | [Slide](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Week%202/Lecture%202%20-%20Linear%20Regression%2C%20Regularization%20and%20Cross-Validation.pdf) | [Link to Readings/Resources](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Reading%20Materials/Linear%20Regression.pdf)| [Linear Regression](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Notebooks/Linear%20Regression.ipynb);[Regularization](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Notebooks/Regularization%20and%20Cross-Validation.ipynb) | Assignment 1 (Kaggle Competition)| 20 | | Topic 3 | Gradient Descent | [Slide](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Week%203/Lecture%203%20-%20Gradient%20Descent.pdf) |[Link to Readings/Resources](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Reading%20Materials/Gradient_Descent.pdf)| [Gradient Descent Lab](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Notebooks/Lab%203%20-%20Gradient%20Descent.ipynb) | [Homework 2](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Homeworks/Week%203%20-%20Homework%202%20-%20Public%20.pdf)| 21 | | Topic 4 | Linear Classification | [Slide](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Week%204/Lecture%204%20-%20Linear%20Classification.pdf) |[Link to Readings/Resources](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Reading%20Materials/Linear%20Classification.pdf)| [Linear Classification Lab](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Notebooks/Logistic%20Regression.ipynb) | [Homework 3](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Homeworks/Logistic%20Regression_Homework_Public.ipynb)| 22 | | Topic 5 | Feature Engineering | [Slide](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Week%205/Lecture%205%20-%20Feature%20Engineering.pdf) | [Link to Readings/Resources](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Reading%20Materials/Feature%20Representation.pdf); [Scikit-Learn](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Week%205/Addtional%20Material%20-%20Scikit-Learn.pdf) | [Feature Engineering Lab](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Notebooks/Feature%20Engineering.ipynb) | [Homework 4](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Homeworks/Scikit-Learn%20-%20Logistic%20Regression%20Homework_Public.ipynb); [Homework 5](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Homeworks/Scikit-Learn%20-%20Linear%20Regression%20Homework_Public.ipynb)| 23 | | Topic 6 | Neural Network | [Slide](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Week%206/Lecture%206%20-Neural%20Network.pdf) | [Link to Readings/Resources](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Reading%20Materials/Neural_Networks.pdf)| [Neural Network Lab](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Notebooks/Neural%20Network%20from%20Scratch.ipynb) | TBA| 24 | | Topic 7 | Autoencoder | [Slide](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Week%207/Lecture%207%20-%20Neural%20Network%20-%20AutoEncoder.pdf) | [Neural Net. Pt.2](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Reading%20Materials/Neural_Networks_Part%202.pdf)/[Link to Readings/Resources](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Reading%20Materials/Autoencoders.pdf) | [Autoencoder Lab](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Notebooks/Autoencoder.ipynb)| TBA| 25 | | Topic 8 | [Pytorch Tutorial](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Notebooks/PyTorch_Tutorial.ipynb) | 26 | | Topic 9 | Convolutional Neural Networks | [Slide](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Week%208/Lecture%208%20-%20Convolutional%20Neural%20Network.pdf) | [Reading Material](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Reading%20Materials/Convolutional_Neural_Networks.pdf) |[CNN Lab](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Notebooks/CNN%20Lab.ipynb) | TBA| 27 | | Topic 10 | Non-parametric Models | [Slide](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Topic%209/Lecture%209%20-%20Non-Parametric%20Models.pdf) | [Reading Material](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Reading%20Materials/Non-parametric%20methods.pdf) | [Decision Tree Lab](https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning/blob/main/Notebooks/Decision%20Tree.ipynb) | TBA| 28 | --- 29 | 30 | ## How to Use This Repository 31 | 32 | 1. **Clone the repository**: 33 | ```bash 34 | git clone https://github.com/rinabuoy/Practical-Applications-in-Machine-Learning.git 35 | cd Practical-Applications-in-Machine-Learning 36 | 37 | 38 | ## Additional Materials: 39 | 40 | **Textbooks** 41 | 42 | (1) [Speech and Language Processing (3rd ed. draft)](https://web.stanford.edu/~jurafsky/slp3/) 43 | 44 | (2) [Understanding Deep Learning](https://udlbook.github.io/udlbook/) 45 | 46 | (3) [Deep Learning: Foundations and Concepts](https://link.springer.com/book/10.1007/978-3-031-45468-4) 47 | 48 | (4) [Probabilistic Machine Learning: An Introduction](https://probml.github.io/pml-book/book1.html) 49 | 50 | (5) [Machine Learning with PyTorch and Scikit-Learn Book](https://github.com/rasbt/machine-learning-book) 51 | 52 | **Probability** 53 | 54 | (1) [CS229](https://cs229.stanford.edu/lectures-spring2022/cs229-probability_review.pdf) 55 | 56 | (2) [3Blue1Brown](https://www.youtube.com/watch?v=8idr1WZ1A7Q&list=PLZHQObOWTQDOjmo3Y6ADm0ScWAlEXf-fp) 57 | 58 | **Linear Algebra** 59 | 60 | (1) [CS229](https://cs229.stanford.edu/notes2024summer/cs229-linear_algebra.pdf) 61 | 62 | (2) [3Blue1Brown](https://www.youtube.com/watch?v=fNk_zzaMoSs&list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab) 63 | 64 | **Python** 65 | 66 | (1) [Python](https://colab.research.google.com/github/cs231n/cs231n.github.io/blob/master/python-colab.ipynb) 67 | 68 | (2) [Numpy](https://cs231n.github.io/python-numpy-tutorial/) 69 | 70 | (3) [Pytorch](https://colab.research.google.com/drive/1FERNv6t8xpX9Nly_JdnePWEPllI7F3Fx?usp=sharing) 71 | 72 | (4) [CS50](https://cs50.harvard.edu/python/2022/) 73 | 74 | (5) [How to Create and Publish Your Own Python Package](https://medium.com/@nydas/how-to-create-and-publish-your-own-python-package-8e4f3fd70506) 75 | 76 | **Neural Networks** 77 | 78 | (1) [3Blue1Brown](https://www.youtube.com/watch?v=aircAruvnKk&list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi) 79 | 80 | (2) [Zero-to-Hero by Andrey Kaparthy](https://www.youtube.com/playlist?list=PLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ) 81 | 82 | (3) [Visualizing transformers and attention | Talk for TNG Big Tech Day '24](https://www.youtube.com/watch?v=KJtZARuO3JY&t=313s) 83 | 84 | **HuggingFace** 85 | 86 | (1) [CS224N: Hugging Face Transformers Tutorial (Spring '24) ](https://colab.research.google.com/drive/13r94i6Fh4oYf-eJRSi7S_y_cen5NYkBm#scrollTo=9EhWoZef-X8u) 87 | 88 | 89 | 90 | **Other tools** 91 | 92 | (1) [Missing Semester (Git, Command-line)](https://missing.csail.mit.edu/) 93 | 94 | (2) [Llama 3.2 Running Locally in VSCode: How to Set It Up with CodeGPT and Ollama](https://medium.com/@dan.avila7/llama-3-2-running-locally-in-vscode-how-to-set-it-up-with-codegpt-and-olla-8d33fd29c195) 95 | 96 | (3) [How to run Ollama in Windows via WSL](https://medium.com/@Tanzim/how-to-run-ollama-in-windows-via-wsl-8ace765cee12) 97 | 98 | 99 | **Inspirational/ General Knowledge Videos** 100 | 101 | (1) [Invention of Blue LED](https://www.youtube.com/watch?v=AF8d72mA41M) 102 | 103 | (2) [Invention of QR Code](https://www.youtube.com/watch?v=w5ebcowAJD8) 104 | 105 | **License** 106 | 107 | This course content is licensed under the MIT License. 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /Reading Materials/Autoencoders.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Reading Materials/Autoencoders.pdf -------------------------------------------------------------------------------- /Reading Materials/Convolutional_Neural_Networks.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Reading Materials/Convolutional_Neural_Networks.pdf -------------------------------------------------------------------------------- /Reading Materials/Feature Representation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Reading Materials/Feature Representation.pdf -------------------------------------------------------------------------------- /Reading Materials/Gradient_Descent.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Reading Materials/Gradient_Descent.pdf -------------------------------------------------------------------------------- /Reading Materials/Introduction.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Reading Materials/Introduction.pdf -------------------------------------------------------------------------------- /Reading Materials/Linear Classification.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Reading Materials/Linear Classification.pdf -------------------------------------------------------------------------------- /Reading Materials/Linear Regression.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Reading Materials/Linear Regression.pdf -------------------------------------------------------------------------------- /Reading Materials/Neural_Networks.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Reading Materials/Neural_Networks.pdf -------------------------------------------------------------------------------- /Reading Materials/Neural_Networks_Part 2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Reading Materials/Neural_Networks_Part 2.pdf -------------------------------------------------------------------------------- /Reading Materials/Non-parametric methods.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Reading Materials/Non-parametric methods.pdf -------------------------------------------------------------------------------- /Topic 1/Intro_General.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Topic 1/Intro_General.pdf -------------------------------------------------------------------------------- /Topic 2/Lecture 2 - Linear Regression, Regularization and Cross-Validation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Topic 2/Lecture 2 - Linear Regression, Regularization and Cross-Validation.pdf -------------------------------------------------------------------------------- /Topic 3/Lecture 3 - Gradient Descent.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Topic 3/Lecture 3 - Gradient Descent.pdf -------------------------------------------------------------------------------- /Topic 4/Lecture 4 - Linear Classification.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Topic 4/Lecture 4 - Linear Classification.pdf -------------------------------------------------------------------------------- /Topic 5/Addtional Material - Scikit-Learn.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Topic 5/Addtional Material - Scikit-Learn.pdf -------------------------------------------------------------------------------- /Topic 5/Lecture 5 - Feature Engineering.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Topic 5/Lecture 5 - Feature Engineering.pdf -------------------------------------------------------------------------------- /Topic 6/Lecture 6 -Neural Network.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Topic 6/Lecture 6 -Neural Network.pdf -------------------------------------------------------------------------------- /Topic 7/Lecture 7 - Neural Network - AutoEncoder.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Topic 7/Lecture 7 - Neural Network - AutoEncoder.pdf -------------------------------------------------------------------------------- /Topic 8/Lecture 8 - Convolutional Neural Network.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Topic 8/Lecture 8 - Convolutional Neural Network.pdf -------------------------------------------------------------------------------- /Topic 9/Lecture 9 - Non-Parametric Models.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinabuoy/Practical-Applications-in-Machine-Learning/828573933300184fc0d888b901a1a69de5658dbd/Topic 9/Lecture 9 - Non-Parametric Models.pdf -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | torch >= 2.0.1 # all 2 | jupyterlab >= 4.0 # all 3 | matplotlib >= 3.7.1 # ch04; ch05 4 | tqdm >= 4.66.1 # ch05; ch07 5 | numpy >= 1.25, < 2.0 # dependency of several other libraries like torch and pandas 6 | pandas >= 2.2.1 # ch06 7 | psutil >= 5.9.5 # ch07; already installed automatically as dependency of torch 8 | --------------------------------------------------------------------------------