diff --git a/doc/LectureNotes/.ipynb_checkpoints/exercisesweek43-checkpoint.ipynb b/doc/LectureNotes/.ipynb_checkpoints/exercisesweek43-checkpoint.ipynb
index 6d6019289..f80e8787a 100644
--- a/doc/LectureNotes/.ipynb_checkpoints/exercisesweek43-checkpoint.ipynb
+++ b/doc/LectureNotes/.ipynb_checkpoints/exercisesweek43-checkpoint.ipynb
@@ -2,1492 +2,624 @@
"cells": [
{
"cell_type": "markdown",
- "id": "b2937d10",
- "metadata": {},
+ "id": "860d70d8",
+ "metadata": {
+ "editable": true
+ },
"source": [
"\n",
- ""
+ ""
]
},
{
"cell_type": "markdown",
- "id": "3dd00d19",
- "metadata": {},
+ "id": "119c0988",
+ "metadata": {
+ "editable": true
+ },
"source": [
- "# Exercises weeks 43 and 44 \n",
- "**October 23-27, 2023**\n",
+ "# Exercises week 43 \n",
+ "**October 20-24, 2025**\n",
"\n",
- "Date: **Deadline is Sunday November 5 at midnight**\n",
- "\n",
- "You can hand in the exercises from week 43 and week 44 as one exercise and get a total score of two additional points."
+ "Date: **Deadline Friday October 24 at midnight**"
]
},
{
"cell_type": "markdown",
- "id": "82a19a1d",
- "metadata": {},
+ "id": "909887eb",
+ "metadata": {
+ "editable": true
+ },
"source": [
- "# Overarching aims of the exercises weeks 43 and 44\n",
+ "# Overarching aims of the exercises for week 43\n",
"\n",
- "The aim of the exercises this week and next week is to get started with writing a neural network code\n",
- "of relevance for project 2. \n",
+ "The aim of the exercises this week is to gain some confidence with\n",
+ "ways to visualize the results of a classification problem. We will\n",
+ "target three ways of setting up the analysis. The first and simplest\n",
+ "one is the\n",
+ "1. so-called confusion matrix. The next one is the so-called\n",
"\n",
- "During week 41 we discussed three different types of gates, the\n",
- "so-called XOR, the OR and the AND gates. In order to develop a code\n",
- "for neural networks, it can be useful to set up a simpler system with\n",
- "only two inputs and one output. This can make it easier to debug and\n",
- "study the feed forward pass and the back propagation part. In the\n",
- "exercise this and next week, we propose to study this system with just\n",
- "one hidden layer and two hidden nodes. There is only one output node\n",
- "and we can choose to use either a simple regression case (fitting a\n",
- "line) or just a binary classification case with the cross-entropy as\n",
- "cost function.\n",
+ "2. ROC curve. Finally we have the\n",
"\n",
- "Their inputs and outputs can be\n",
- "summarized using the following tables, first for the OR gate with\n",
- "inputs $x_1$ and $x_2$ and outputs $y$:\n",
+ "3. Cumulative gain curve.\n",
"\n",
- "
\n",
- "\n",
- "| $x_1$ | $x_2$ | $y$ |
\n",
- "\n",
- "\n",
- "| 0 | 0 | 0 |
\n",
- "| 0 | 1 | 1 |
\n",
- "| 1 | 0 | 1 |
\n",
- "| 1 | 1 | 1 |
\n",
- "\n",
- "
"
+ "We will use Logistic Regression as method for the classification in\n",
+ "this exercise. You can compare these results with those obtained with\n",
+ "your neural network code from project 2 without a hidden layer.\n",
+ "\n",
+ "In these exercises we will use binary and multi-class data sets\n",
+ "(the Iris data set from week 41).\n",
+ "\n",
+ "The underlying mathematics is described here."
]
},
{
"cell_type": "markdown",
- "id": "f74f69af",
- "metadata": {},
+ "id": "1e1cb4fb",
+ "metadata": {
+ "editable": true
+ },
"source": [
- "## The AND and XOR Gates\n",
+ "### Confusion Matrix\n",
"\n",
- "The AND gate is defined as\n",
- "\n",
- "\n",
- "\n",
- "| $x_1$ | $x_2$ | $y$ |
\n",
- "\n",
- "\n",
- "| 0 | 0 | 0 |
\n",
- "| 0 | 1 | 0 |
\n",
- "| 1 | 0 | 0 |
\n",
- "| 1 | 1 | 1 |
\n",
- "\n",
- "
\n",
- "\n",
- "And finally we have the XOR gate\n",
- "\n",
- "\n",
- "\n",
- "| $x_1$ | $x_2$ | $y$ |
\n",
- "\n",
- "\n",
- "| 0 | 0 | 0 |
\n",
- "| 0 | 1 | 1 |
\n",
- "| 1 | 0 | 1 |
\n",
- "| 1 | 1 | 0 |
\n",
- "\n",
- "
"
+ "A **confusion matrix** summarizes a classifier’s performance by\n",
+ "tabulating predictions versus true labels. For binary classification,\n",
+ "it is a $2\\times2$ table whose entries are counts of outcomes:"
]
},
{
"cell_type": "markdown",
- "id": "1b52d47a",
- "metadata": {},
- "source": [
- "## Representing the Data Sets\n",
- "\n",
- "Our design matrix is defined by the input values $x_1$ and $x_2$. Since we have four possible outputs, our design matrix reads"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "3e6910cb",
- "metadata": {},
+ "id": "7b090385",
+ "metadata": {
+ "editable": true
+ },
"source": [
"$$\n",
- "\\boldsymbol{X}=\\begin{bmatrix} 0 & 0 \\\\\n",
- " 0 & 1 \\\\\n",
- "\t\t 1 & 0 \\\\\n",
- "\t\t 1 & 1 \\end{bmatrix},\n",
+ "\\begin{array}{l|cc} & \\text{Predicted Positive} & \\text{Predicted Negative} \\\\ \\hline \\text{Actual Positive} & TP & FN \\\\ \\text{Actual Negative} & FP & TN \\end{array}.\n",
"$$"
]
},
{
"cell_type": "markdown",
- "id": "90a3b78a",
- "metadata": {},
+ "id": "1e14904b",
+ "metadata": {
+ "editable": true
+ },
"source": [
- "while the vector of outputs is $\\boldsymbol{y}^T=[0,1,1,0]$ for the XOR gate, $\\boldsymbol{y}^T=[0,0,0,1]$ for the AND gate and $\\boldsymbol{y}^T=[0,1,1,1]$ for the OR gate.\n",
- "\n",
- "Your tasks here are\n",
- "\n",
- "1. Set up the design matrix with the inputs as discussed above and a vector containing the output, the so-called targets. Note that the design matrix is the same for all gates. You need just to define different outputs.\n",
- "\n",
- "2. Construct a neural network with only one hidden layer and two hidden nodes using the Sigmoid function as activation function.\n",
- "\n",
- "3. Set up the output layer with only one output node and use again the Sigmoid function as activation function for the output.\n",
- "\n",
- "4. Initialize the weights and biases and perform a feed forward pass and compare the outputs with the targets.\n",
- "\n",
- "5. Set up the cost function (cross entropy for classification of binary cases).\n",
- "\n",
- "6. Calculate the gradients needed for the back propagation part.\n",
- "\n",
- "7. Use the gradients to train the network in the back propagation part. Think of using automatic differentiation.\n",
- "\n",
- "8. Train the network and study your results and compare with results obtained either with **scikit-learn** or **TensorFlow**.\n",
- "\n",
- "Everything you develop here can be used directly into the code for the project."
+ "Here TP (true positives) is the number of cases correctly predicted as\n",
+ "positive, FP (false positives) is the number incorrectly predicted as\n",
+ "positive, TN (true negatives) is correctly predicted negative, and FN\n",
+ "(false negatives) is incorrectly predicted negative . In other words,\n",
+ "“positive” means class 1 and “negative” means class 0; for example, TP\n",
+ "occurs when the prediction and actual are both positive. Formally:"
]
},
{
"cell_type": "markdown",
- "id": "d6a3ab1e",
- "metadata": {},
+ "id": "e93ea290",
+ "metadata": {
+ "editable": true
+ },
"source": [
- "## Setting up the Neural Network\n",
+ "$$\n",
+ "\\text{TPR} = \\frac{\\text{TP}}{\\text{TP} + \\text{FN}}, \\quad \\text{FPR} = \\frac{\\text{FP}}{\\text{FP} + \\text{TN}},\n",
+ "$$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c80bea5b",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "where TPR and FPR are the true and false positive rates defined below.\n",
"\n",
- "We define first our design matrix and the various output vectors for the different gates."
+ "In multiclass classification with $K$ classes, the confusion matrix\n",
+ "generalizes to a $K\\times K$ table. Entry $N_{ij}$ in the table is\n",
+ "the count of instances whose true class is $i$ and whose predicted\n",
+ "class is $j$. For example, a three-class confusion matrix can be written\n",
+ "as:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a0f68f5f",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "$$\n",
+ "\\begin{array}{c|ccc} & \\text{Pred Class 1} & \\text{Pred Class 2} & \\text{Pred Class 3} \\\\ \\hline \\text{Act Class 1} & N_{11} & N_{12} & N_{13} \\\\ \\text{Act Class 2} & N_{21} & N_{22} & N_{23} \\\\ \\text{Act Class 3} & N_{31} & N_{32} & N_{33} \\end{array}.\n",
+ "$$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "869669b2",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "Here the diagonal entries $N_{ii}$ are the true positives for each\n",
+ "class, and off-diagonal entries are misclassifications. This matrix\n",
+ "allows computation of per-class metrics: e.g. for class $i$,\n",
+ "$\\mathrm{TP}_i=N_{ii}$, $\\mathrm{FN}_i=\\sum_{j\\neq i}N_{ij}$,\n",
+ "$\\mathrm{FP}_i=\\sum_{j\\neq i}N_{ji}$, and $\\mathrm{TN}_i$ is the sum of\n",
+ "all remaining entries.\n",
+ "\n",
+ "As defined above, TPR and FPR come from the binary case. In binary\n",
+ "terms with $P$ actual positives and $N$ actual negatives, one has"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2abd82a7",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "$$\n",
+ "\\text{TPR} = \\frac{TP}{P} = \\frac{TP}{TP+FN}, \\quad \\text{FPR} =\n",
+ "\\frac{FP}{N} = \\frac{FP}{FP+TN},\n",
+ "$$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2f79325c",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "as used in standard confusion-matrix\n",
+ "formulations. These rates will be used in constructing ROC curves."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0ce65a47",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "### ROC Curve\n",
+ "\n",
+ "The Receiver Operating Characteristic (ROC) curve plots the trade-off\n",
+ "between true positives and false positives as a discrimination\n",
+ "threshold varies. Specifically, for a binary classifier that outputs\n",
+ "a score or probability, one varies the threshold $t$ for declaring\n",
+ "**positive**, and computes at each $t$ the true positive rate\n",
+ "$\\mathrm{TPR}(t)$ and false positive rate $\\mathrm{FPR}(t)$ using the\n",
+ "confusion matrix at that threshold. The ROC curve is then the graph\n",
+ "of TPR versus FPR. By definition,"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d750fdff",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "$$\n",
+ "\\mathrm{TPR} = \\frac{TP}{TP+FN}, \\qquad \\mathrm{FPR} = \\frac{FP}{FP+TN},\n",
+ "$$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "561bfb2c",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "where $TP,FP,TN,FN$ are counts determined by threshold $t$. A perfect\n",
+ "classifier would reach the point (FPR=0, TPR=1) at some threshold.\n",
+ "\n",
+ "Formally, the ROC curve is obtained by plotting\n",
+ "$(\\mathrm{FPR}(t),\\mathrm{TPR}(t))$ for all $t\\in[0,1]$ (or as $t$\n",
+ "sweeps through the sorted scores). The Area Under the ROC Curve (AUC)\n",
+ "quantifies the average performance over all thresholds. It can be\n",
+ "interpreted probabilistically: $\\mathrm{AUC} =\n",
+ "\\Pr\\bigl(s(X^+)>s(X^-)\\bigr)$, the probability that a random positive\n",
+ "instance $X^+$ receives a higher score $s$ than a random negative\n",
+ "instance $X^-$ . Equivalently, the AUC is the integral under the ROC\n",
+ "curve:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5ca722fe",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "$$\n",
+ "\\mathrm{AUC} \\;=\\; \\int_{0}^{1} \\mathrm{TPR}(f)\\,df,\n",
+ "$$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "30080a86",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "where $f$ ranges over FPR (or fraction of negatives). A model that guesses at random yields a diagonal ROC (AUC=0.5), whereas a perfect model yields AUC=1.0."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9e627156",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "### Cumulative Gain\n",
+ "\n",
+ "The cumulative gain curve (or gains chart) evaluates how many\n",
+ "positives are captured as one targets an increasing fraction of the\n",
+ "population, sorted by model confidence. To construct it, sort all\n",
+ "instances by decreasing predicted probability of the positive class.\n",
+ "Then, for the top $\\alpha$ fraction of instances, compute the fraction\n",
+ "of all actual positives that fall in this subset. In formula form, if\n",
+ "$P$ is the total number of positive instances and $P(\\alpha)$ is the\n",
+ "number of positives among the top $\\alpha$ of the data, the cumulative\n",
+ "gain at level $\\alpha$ is"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3e9132ef",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "$$\n",
+ "\\mathrm{Gain}(\\alpha) \\;=\\; \\frac{P(\\alpha)}{P}.\n",
+ "$$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "75be6f5c",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "For example, cutting off at the top 10% of predictions yields a gain\n",
+ "equal to (positives in top 10%) divided by (total positives) .\n",
+ "Plotting $\\mathrm{Gain}(\\alpha)$ versus $\\alpha$ (often in percent)\n",
+ "gives the gain curve. The baseline (random) curve is the diagonal\n",
+ "$\\mathrm{Gain}(\\alpha)=\\alpha$, while an ideal model has a steep climb\n",
+ "toward 1.\n",
+ "\n",
+ "A related measure is the {\\em lift}, often called the gain ratio. It is the ratio of the model’s capture rate to that of random selection. Equivalently,"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e5525570",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "$$\n",
+ "\\mathrm{Lift}(\\alpha) \\;=\\; \\frac{\\mathrm{Gain}(\\alpha)}{\\alpha}.\n",
+ "$$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "18ff8dc2",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "A lift $>1$ indicates better-than-random targeting. In practice, gain\n",
+ "and lift charts (used e.g.\\ in marketing or imbalanced classification)\n",
+ "show how many positives can be “gained” by focusing on a fraction of\n",
+ "the population ."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c3d3fde8",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "### Other measures: Precision, Recall, and the F$_1$ Measure\n",
+ "\n",
+ "Precision and recall (sensitivity) quantify binary classification\n",
+ "accuracy in terms of positive predictions. They are defined from the\n",
+ "confusion matrix as:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f1f14c8e",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "$$\n",
+ "\\text{Precision} = \\frac{TP}{TP + FP}, \\qquad \\text{Recall} = \\frac{TP}{TP + FN}.\n",
+ "$$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "422cc743",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "Precision is the fraction of predicted positives that are correct, and\n",
+ "recall is the fraction of actual positives that are correctly\n",
+ "identified . A high-precision classifier makes few false-positive\n",
+ "errors, while a high-recall classifier makes few false-negative\n",
+ "errors.\n",
+ "\n",
+ "The F$_1$ score (balanced F-measure) combines precision and recall into a single metric via their harmonic mean. The usual formula is:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "621a2e8b",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "$$\n",
+ "F_1 =2\\frac{\\text{Precision}\\times\\text{Recall}}{\\text{Precision} + \\text{Recall}}.\n",
+ "$$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "62eee54a",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "This can be shown to equal"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7a6a2e7a",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "$$\n",
+ "\\frac{2\\,TP}{2\\,TP + FP + FN}.\n",
+ "$$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b96c9ff4",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "The F$_1$ score ranges from 0 (worst) to 1 (best), and balances the\n",
+ "trade-off between precision and recall.\n",
+ "\n",
+ "For multi-class classification, one computes per-class\n",
+ "precision/recall/F$_1$ (treating each class as “positive” in a\n",
+ "one-vs-rest manner) and then averages. Common averaging methods are:\n",
+ "\n",
+ "Micro-averaging: Sum all true positives, false positives, and false negatives across classes, then compute precision/recall/F$_1$ from these totals.\n",
+ "Macro-averaging: Compute the F$1$ score $F{1,i}$ for each class $i$ separately, then take the unweighted mean: $F_{1,\\mathrm{macro}} = \\frac{1}{K}\\sum_{i=1}^K F_{1,i}$ . This treats all classes equally regardless of size.\n",
+ "Weighted-averaging: Like macro-average, but weight each class’s $F_{1,i}$ by its support $n_i$ (true count): $F_{1,\\mathrm{weighted}} = \\frac{1}{N}\\sum_{i=1}^K n_i F_{1,i}$, where $N=\\sum_i n_i$. This accounts for class imbalance by giving more weight to larger classes .\n",
+ "\n",
+ "Each of these averages has different use-cases. Micro-average is\n",
+ "dominated by common classes, macro-average highlights performance on\n",
+ "rare classes, and weighted-average is a compromise. These formulas\n",
+ "and concepts allow rigorous evaluation of classifier performance in\n",
+ "both binary and multi-class settings."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9274bf3f",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "## Exercises\n",
+ "\n",
+ "Here is a simple code example which uses the Logistic regression machinery from **scikit-learn**.\n",
+ "At the end it sets up the confusion matrix and the ROC and cumulative gain curves.\n",
+ "Feel free to use these functionalities (we don't expect you to write your own code for say the confusion matrix)."
]
},
{
"cell_type": "code",
"execution_count": 1,
- "id": "152123b0",
- "metadata": {},
+ "id": "be9ff0b9",
+ "metadata": {
+ "collapsed": false,
+ "editable": true,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
"outputs": [],
"source": [
"%matplotlib inline\n",
"\n",
- "\"\"\"\n",
- "Simple code that tests XOR, OR and AND gates with linear regression\n",
- "\"\"\"\n",
- "\n",
- "# import necessary packages\n",
- "import numpy as np\n",
"import matplotlib.pyplot as plt\n",
- "from sklearn import datasets\n",
- "\n",
- "def sigmoid(x):\n",
- " return 1/(1 + np.exp(-x))\n",
- "\n",
- "def feed_forward(X):\n",
- " # weighted sum of inputs to the hidden layer\n",
- " z_h = np.matmul(X, hidden_weights) + hidden_bias\n",
- " # activation in the hidden layer\n",
- " a_h = sigmoid(z_h)\n",
- " \n",
- " # weighted sum of inputs to the output layer\n",
- " z_o = np.matmul(a_h, output_weights) + output_bias\n",
- " # softmax output\n",
- " # axis 0 holds each input and axis 1 the probabilities of each category\n",
- " probabilities = sigmoid(z_o)\n",
- " return probabilities\n",
- "\n",
- "# we obtain a prediction by taking the class with the highest likelihood\n",
- "def predict(X):\n",
- " probabilities = feed_forward(X)\n",
- " return np.argmax(probabilities, axis=1)\n",
- "\n",
- "# ensure the same random numbers appear every time\n",
- "np.random.seed(0)\n",
- "\n",
- "# Design matrix\n",
- "X = np.array([ [0, 0], [0, 1], [1, 0],[1, 1]],dtype=np.float64)\n",
- "\n",
- "# The XOR gate\n",
- "yXOR = np.array( [ 0, 1 ,1, 0])\n",
- "# The OR gate\n",
- "yOR = np.array( [ 0, 1 ,1, 1])\n",
- "# The AND gate\n",
- "yAND = np.array( [ 0, 0 ,0, 1])\n",
- "\n",
- "# Defining the neural network\n",
- "n_inputs, n_features = X.shape\n",
- "n_hidden_neurons = 2\n",
- "n_categories = 2\n",
- "n_features = 2\n",
- "\n",
- "# we make the weights normally distributed using numpy.random.randn\n",
- "\n",
- "# weights and bias in the hidden layer\n",
- "hidden_weights = np.random.randn(n_features, n_hidden_neurons)\n",
- "hidden_bias = np.zeros(n_hidden_neurons) + 0.01\n",
- "\n",
- "# weights and bias in the output layer\n",
- "output_weights = np.random.randn(n_hidden_neurons, n_categories)\n",
- "output_bias = np.zeros(n_categories) + 0.01\n",
- "\n",
- "probabilities = feed_forward(X)\n",
- "print(probabilities)\n",
- "\n",
- "\n",
- "predictions = predict(X)\n",
- "print(predictions)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "73319f0a",
- "metadata": {},
- "source": [
- "Not an impressive result, but this was our first forward pass with randomly assigned weights. Let us now add the full network with the back-propagation algorithm discussed above."
- ]
- },
- {
- "cell_type": "markdown",
- "id": "a7e0c47a",
- "metadata": {},
- "source": [
- "## The Code using Scikit-Learn"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "id": "dbbacc67",
- "metadata": {},
- "outputs": [],
- "source": [
- "# import necessary packages\n",
"import numpy as np\n",
- "import matplotlib.pyplot as plt\n",
- "from sklearn.neural_network import MLPClassifier\n",
- "from sklearn.metrics import accuracy_score\n",
- "import seaborn as sns\n",
+ "from sklearn.model_selection import train_test_split \n",
+ "# from sklearn.datasets import fill in the data set\n",
+ "from sklearn.linear_model import LogisticRegression\n",
"\n",
- "# ensure the same random numbers appear every time\n",
- "np.random.seed(0)\n",
+ "# Load the data, fill inn\n",
+ "mydata.data = ?\n",
"\n",
- "# Design matrix\n",
- "X = np.array([ [0, 0], [0, 1], [1, 0],[1, 1]],dtype=np.float64)\n",
+ "X_train, X_test, y_train, y_test = train_test_split(mydata.data,cancer.target,random_state=0)\n",
+ "print(X_train.shape)\n",
+ "print(X_test.shape)\n",
+ "# Logistic Regression\n",
+ "# define which type of problem, binary or multiclass\n",
+ "logreg = LogisticRegression(solver='lbfgs')\n",
+ "logreg.fit(X_train, y_train)\n",
"\n",
- "# The XOR gate\n",
- "yXOR = np.array( [ 0, 1 ,1, 0])\n",
- "# The OR gate\n",
- "yOR = np.array( [ 0, 1 ,1, 1])\n",
- "# The AND gate\n",
- "yAND = np.array( [ 0, 0 ,0, 1])\n",
+ "from sklearn.preprocessing import LabelEncoder\n",
+ "from sklearn.model_selection import cross_validate\n",
+ "#Cross validation\n",
+ "accuracy = cross_validate(logreg,X_test,y_test,cv=10)['test_score']\n",
+ "print(accuracy)\n",
+ "print(\"Test set accuracy with Logistic Regression: {:.2f}\".format(logreg.score(X_test,y_test)))\n",
"\n",
- "# Defining the neural network\n",
- "n_inputs, n_features = X.shape\n",
- "n_hidden_neurons = 2\n",
- "n_categories = 2\n",
- "n_features = 2\n",
- "\n",
- "eta_vals = np.logspace(-5, 1, 7)\n",
- "lmbd_vals = np.logspace(-5, 1, 7)\n",
- "# store models for later use\n",
- "DNN_scikit = np.zeros((len(eta_vals), len(lmbd_vals)), dtype=object)\n",
- "epochs = 100\n",
- "\n",
- "for i, eta in enumerate(eta_vals):\n",
- " for j, lmbd in enumerate(lmbd_vals):\n",
- " dnn = MLPClassifier(hidden_layer_sizes=(n_hidden_neurons), activation='logistic',\n",
- " alpha=lmbd, learning_rate_init=eta, max_iter=epochs)\n",
- " dnn.fit(X, yXOR)\n",
- " DNN_scikit[i][j] = dnn\n",
- " print(\"Learning rate = \", eta)\n",
- " print(\"Lambda = \", lmbd)\n",
- " print(\"Accuracy score on data set: \", dnn.score(X, yXOR))\n",
- " print()\n",
- "\n",
- "sns.set()\n",
- "test_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))\n",
- "for i in range(len(eta_vals)):\n",
- " for j in range(len(lmbd_vals)):\n",
- " dnn = DNN_scikit[i][j]\n",
- " test_pred = dnn.predict(X)\n",
- " test_accuracy[i][j] = accuracy_score(yXOR, test_pred)\n",
- "\n",
- "fig, ax = plt.subplots(figsize = (10, 10))\n",
- "sns.heatmap(test_accuracy, annot=True, ax=ax, cmap=\"viridis\")\n",
- "ax.set_title(\"Test Accuracy\")\n",
- "ax.set_ylabel(\"$\\eta$\")\n",
- "ax.set_xlabel(\"$\\lambda$\")\n",
+ "import scikitplot as skplt\n",
+ "y_pred = logreg.predict(X_test)\n",
+ "skplt.metrics.plot_confusion_matrix(y_test, y_pred, normalize=True)\n",
+ "plt.show()\n",
+ "y_probas = logreg.predict_proba(X_test)\n",
+ "skplt.metrics.plot_roc(y_test, y_probas)\n",
+ "plt.show()\n",
+ "skplt.metrics.plot_cumulative_gain(y_test, y_probas)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
- "id": "1cac501a",
- "metadata": {},
+ "id": "51760b3e",
+ "metadata": {
+ "editable": true
+ },
"source": [
- "## Building a neural network code\n",
+ "### Exercise a)\n",
"\n",
- "Here we present a flexible object oriented codebase\n",
- "for a feed forward neural network, along with a demonstration of how\n",
- "to use it. Before we get into the details of the neural network, we\n",
- "will first present some implementations of various schedulers, cost\n",
- "functions and activation functions that can be used together with the\n",
- "neural network.\n",
- "\n",
- "The codes here were developed by Eric Reber and Gregor Kajda during spring 2023."
+ "Convince yourself about the mathematics for the confusion matrix, the ROC and the cumlative gain curves for both a binary and a multiclass classification problem."
]
},
{
"cell_type": "markdown",
- "id": "dd153528",
- "metadata": {},
+ "id": "c1d42f5f",
+ "metadata": {
+ "editable": true
+ },
"source": [
- "### Learning rate methods\n",
+ "### Exercise b)\n",
"\n",
- "The code below shows object oriented implementations of the Constant,\n",
- "Momentum, Adagrad, AdagradMomentum, RMS prop and Adam schedulers. All\n",
- "of the classes belong to the shared abstract Scheduler class, and\n",
- "share the update_change() and reset() methods allowing for any of the\n",
- "schedulers to be seamlessly used during the training stage, as will\n",
- "later be shown in the fit() method of the neural\n",
- "network. Update_change() only has one parameter, the gradient\n",
- "($δ^l_ja^{l−1}_k$), and returns the change which will be subtracted\n",
- "from the weights. The reset() function takes no parameters, and resets\n",
- "the desired variables. For Constant and Momentum, reset does nothing."
+ "Use a binary classification data available from **scikit-learn**. As an example you can use\n",
+ "the MNIST data set and just specialize to two numbers. To do so you can use the following code lines"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "d20bb8be",
+ "metadata": {
+ "collapsed": false,
+ "editable": true,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "from sklearn.datasets import load_digits\n",
+ "digits = load_digits(n_class=2) # Load only two classes, e.g., 0 and 1\n",
+ "X, y = digits.data, digits.target"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "828ea1cd",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "Alternatively, you can use the _make$\\_$classification_\n",
+ "functionality. This function generates a random $n$-class classification\n",
+ "dataset, which can be configured for binary classification by setting\n",
+ "n_classes=2. You can also control the number of samples, features,\n",
+ "informative features, redundant features, and more."
]
},
{
"cell_type": "code",
"execution_count": 3,
- "id": "f55eea63",
- "metadata": {},
+ "id": "d271f0ba",
+ "metadata": {
+ "collapsed": false,
+ "editable": true,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
"outputs": [],
"source": [
- "import autograd.numpy as np\n",
- "\n",
- "class Scheduler:\n",
- " \"\"\"\n",
- " Abstract class for Schedulers\n",
- " \"\"\"\n",
- "\n",
- " def __init__(self, eta):\n",
- " self.eta = eta\n",
- "\n",
- " # should be overwritten\n",
- " def update_change(self, gradient):\n",
- " raise NotImplementedError\n",
- "\n",
- " # overwritten if needed\n",
- " def reset(self):\n",
- " pass\n",
- "\n",
- "\n",
- "class Constant(Scheduler):\n",
- " def __init__(self, eta):\n",
- " super().__init__(eta)\n",
- "\n",
- " def update_change(self, gradient):\n",
- " return self.eta * gradient\n",
- " \n",
- " def reset(self):\n",
- " pass\n",
- "\n",
- "\n",
- "class Momentum(Scheduler):\n",
- " def __init__(self, eta: float, momentum: float):\n",
- " super().__init__(eta)\n",
- " self.momentum = momentum\n",
- " self.change = 0\n",
- "\n",
- " def update_change(self, gradient):\n",
- " self.change = self.momentum * self.change + self.eta * gradient\n",
- " return self.change\n",
- "\n",
- " def reset(self):\n",
- " pass\n",
- "\n",
- "\n",
- "class Adagrad(Scheduler):\n",
- " def __init__(self, eta):\n",
- " super().__init__(eta)\n",
- " self.G_t = None\n",
- "\n",
- " def update_change(self, gradient):\n",
- " delta = 1e-8 # avoid division ny zero\n",
- "\n",
- " if self.G_t is None:\n",
- " self.G_t = np.zeros((gradient.shape[0], gradient.shape[0]))\n",
- "\n",
- " self.G_t += gradient @ gradient.T\n",
- "\n",
- " G_t_inverse = 1 / (\n",
- " delta + np.sqrt(np.reshape(np.diagonal(self.G_t), (self.G_t.shape[0], 1)))\n",
- " )\n",
- " return self.eta * gradient * G_t_inverse\n",
- "\n",
- " def reset(self):\n",
- " self.G_t = None\n",
- "\n",
- "\n",
- "class AdagradMomentum(Scheduler):\n",
- " def __init__(self, eta, momentum):\n",
- " super().__init__(eta)\n",
- " self.G_t = None\n",
- " self.momentum = momentum\n",
- " self.change = 0\n",
- "\n",
- " def update_change(self, gradient):\n",
- " delta = 1e-8 # avoid division ny zero\n",
- "\n",
- " if self.G_t is None:\n",
- " self.G_t = np.zeros((gradient.shape[0], gradient.shape[0]))\n",
- "\n",
- " self.G_t += gradient @ gradient.T\n",
- "\n",
- " G_t_inverse = 1 / (\n",
- " delta + np.sqrt(np.reshape(np.diagonal(self.G_t), (self.G_t.shape[0], 1)))\n",
- " )\n",
- " self.change = self.change * self.momentum + self.eta * gradient * G_t_inverse\n",
- " return self.change\n",
- "\n",
- " def reset(self):\n",
- " self.G_t = None\n",
- "\n",
- "\n",
- "class RMS_prop(Scheduler):\n",
- " def __init__(self, eta, rho):\n",
- " super().__init__(eta)\n",
- " self.rho = rho\n",
- " self.second = 0.0\n",
- "\n",
- " def update_change(self, gradient):\n",
- " delta = 1e-8 # avoid division ny zero\n",
- " self.second = self.rho * self.second + (1 - self.rho) * gradient * gradient\n",
- " return self.eta * gradient / (np.sqrt(self.second + delta))\n",
- "\n",
- " def reset(self):\n",
- " self.second = 0.0\n",
- "\n",
- "\n",
- "class Adam(Scheduler):\n",
- " def __init__(self, eta, rho, rho2):\n",
- " super().__init__(eta)\n",
- " self.rho = rho\n",
- " self.rho2 = rho2\n",
- " self.moment = 0\n",
- " self.second = 0\n",
- " self.n_epochs = 1\n",
- "\n",
- " def update_change(self, gradient):\n",
- " delta = 1e-8 # avoid division ny zero\n",
- "\n",
- " self.moment = self.rho * self.moment + (1 - self.rho) * gradient\n",
- " self.second = self.rho2 * self.second + (1 - self.rho2) * gradient * gradient\n",
- "\n",
- " moment_corrected = self.moment / (1 - self.rho**self.n_epochs)\n",
- " second_corrected = self.second / (1 - self.rho2**self.n_epochs)\n",
- "\n",
- " return self.eta * moment_corrected / (np.sqrt(second_corrected + delta))\n",
- "\n",
- " def reset(self):\n",
- " self.n_epochs += 1\n",
- " self.moment = 0\n",
- " self.second = 0"
+ "from sklearn.datasets import make_classification\n",
+ "X, y = make_classification(n_samples=1000, n_features=20, n_informative=10, n_redundant=5, n_classes=2, random_state=42)"
]
},
{
"cell_type": "markdown",
- "id": "1a9bcb3e",
- "metadata": {},
+ "id": "0068b032",
+ "metadata": {
+ "editable": true
+ },
"source": [
- "### Usage of the above learning rate schedulers\n",
+ "You can use this option for the multiclass case as well, see the next exercise.\n",
+ "If you prefer to study other binary classification datasets, feel free\n",
+ "to replace the above suggestions with your own dataset.\n",
"\n",
- "To initalize a scheduler, simply create the object and pass in the\n",
- "necessary parameters such as the learning rate and the momentum as\n",
- "shown below. As the Scheduler class is an abstract class it should not\n",
- "called directly, and will raise an error upon usage."
+ "Make plots of the confusion matrix, the ROC curve and the cumulative gain curve."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c45f5b41",
+ "metadata": {
+ "editable": true
+ },
+ "source": [
+ "### Exercise c) week 43\n",
+ "\n",
+ "As a multiclass problem, we will use the Iris data set discussed in\n",
+ "the exercises from weeks 41 and 42. This is a three-class data set and\n",
+ "you can set it up using **scikit-learn**,"
]
},
{
"cell_type": "code",
"execution_count": 4,
- "id": "86013cb4",
- "metadata": {},
+ "id": "3b045d56",
+ "metadata": {
+ "collapsed": false,
+ "editable": true,
+ "jupyter": {
+ "outputs_hidden": false
+ }
+ },
"outputs": [],
"source": [
- "momentum_scheduler = Momentum(eta=1e-3, momentum=0.9)\n",
- "adam_scheduler = Adam(eta=1e-3, rho=0.9, rho2=0.999)"
+ "from sklearn.datasets import load_iris\n",
+ "iris = load_iris()\n",
+ "X = iris.data # Features\n",
+ "y = iris.target # Target labels"
]
},
{
"cell_type": "markdown",
- "id": "535331f6",
- "metadata": {},
+ "id": "14cc859c",
+ "metadata": {
+ "editable": true
+ },
"source": [
- "Here is a small example for how a segment of code using schedulers\n",
- "could look. Switching out the schedulers is simple."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "id": "7e0f6b5a",
- "metadata": {},
- "outputs": [],
- "source": [
- "weights = np.ones((3,3))\n",
- "print(f\"Before scheduler:\\n{weights=}\")\n",
- "\n",
- "epochs = 10\n",
- "for e in range(epochs):\n",
- " gradient = np.random.rand(3, 3)\n",
- " change = adam_scheduler.update_change(gradient)\n",
- " weights = weights - change\n",
- " adam_scheduler.reset()\n",
- "\n",
- "print(f\"\\nAfter scheduler:\\n{weights=}\")"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "f018ae57",
- "metadata": {},
- "source": [
- "### Cost functions\n",
- "\n",
- "Here we discuss cost functions that can be used when creating the\n",
- "neural network. Every cost function takes the target vector as its\n",
- "parameter, and returns a function valued only at $x$ such that it may\n",
- "easily be differentiated."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "id": "c13507bf",
- "metadata": {},
- "outputs": [],
- "source": [
- "import autograd.numpy as np\n",
- "\n",
- "def CostOLS(target):\n",
- " \n",
- " def func(X):\n",
- " return (1.0 / target.shape[0]) * np.sum((target - X) ** 2)\n",
- "\n",
- " return func\n",
- "\n",
- "\n",
- "def CostLogReg(target):\n",
- "\n",
- " def func(X):\n",
- " \n",
- " return -(1.0 / target.shape[0]) * np.sum(\n",
- " (target * np.log(X + 10e-10)) + ((1 - target) * np.log(1 - X + 10e-10))\n",
- " )\n",
- "\n",
- " return func\n",
- "\n",
- "\n",
- "def CostCrossEntropy(target):\n",
- " \n",
- " def func(X):\n",
- " return -(1.0 / target.size) * np.sum(target * np.log(X + 10e-10))\n",
- "\n",
- " return func"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "6dab17bc",
- "metadata": {},
- "source": [
- "Below we give a short example of how these cost function may be used\n",
- "to obtain results if you wish to test them out on your own using\n",
- "AutoGrad's automatics differentiation."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "id": "a5dbba01",
- "metadata": {},
- "outputs": [],
- "source": [
- "from autograd import grad\n",
- "\n",
- "target = np.array([[1, 2, 3]]).T\n",
- "a = np.array([[4, 5, 6]]).T\n",
- "\n",
- "cost_func = CostCrossEntropy\n",
- "cost_func_derivative = grad(cost_func(target))\n",
- "\n",
- "valued_at_a = cost_func_derivative(a)\n",
- "print(f\"Derivative of cost function {cost_func.__name__} valued at a:\\n{valued_at_a}\")"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "b55d31d4",
- "metadata": {},
- "source": [
- "### Activation functions\n",
- "\n",
- "Finally, before we look at the neural network, we will look at the\n",
- "activation functions which can be specified between the hidden layers\n",
- "and as the output function. Each function can be valued for any given\n",
- "vector or matrix X, and can be differentiated via derivate()."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 8,
- "id": "b3e045a6",
- "metadata": {},
- "outputs": [],
- "source": [
- "import autograd.numpy as np\n",
- "from autograd import elementwise_grad\n",
- "\n",
- "def identity(X):\n",
- " return X\n",
- "\n",
- "\n",
- "def sigmoid(X):\n",
- " try:\n",
- " return 1.0 / (1 + np.exp(-X))\n",
- " except FloatingPointError:\n",
- " return np.where(X > np.zeros(X.shape), np.ones(X.shape), np.zeros(X.shape))\n",
- "\n",
- "\n",
- "def softmax(X):\n",
- " X = X - np.max(X, axis=-1, keepdims=True)\n",
- " delta = 10e-10\n",
- " return np.exp(X) / (np.sum(np.exp(X), axis=-1, keepdims=True) + delta)\n",
- "\n",
- "\n",
- "def RELU(X):\n",
- " return np.where(X > np.zeros(X.shape), X, np.zeros(X.shape))\n",
- "\n",
- "\n",
- "def LRELU(X):\n",
- " delta = 10e-4\n",
- " return np.where(X > np.zeros(X.shape), X, delta * X)\n",
- "\n",
- "\n",
- "def derivate(func):\n",
- " if func.__name__ == \"RELU\":\n",
- "\n",
- " def func(X):\n",
- " return np.where(X > 0, 1, 0)\n",
- "\n",
- " return func\n",
- "\n",
- " elif func.__name__ == \"LRELU\":\n",
- "\n",
- " def func(X):\n",
- " delta = 10e-4\n",
- " return np.where(X > 0, 1, delta)\n",
- "\n",
- " return func\n",
- "\n",
- " else:\n",
- " return elementwise_grad(func)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "c0189342",
- "metadata": {},
- "source": [
- "Below follows a short demonstration of how to use an activation\n",
- "function. The derivative of the activation function will be important\n",
- "when calculating the output delta term during backpropagation. Note\n",
- "that derivate() can also be used for cost functions for a more\n",
- "generalized approach."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 9,
- "id": "640aa861",
- "metadata": {},
- "outputs": [],
- "source": [
- "z = np.array([[4, 5, 6]]).T\n",
- "print(f\"Input to activation function:\\n{z}\")\n",
- "\n",
- "act_func = sigmoid\n",
- "a = act_func(z)\n",
- "print(f\"\\nOutput from {act_func.__name__} activation function:\\n{a}\")\n",
- "\n",
- "act_func_derivative = derivate(act_func)\n",
- "valued_at_z = act_func_derivative(a)\n",
- "print(f\"\\nDerivative of {act_func.__name__} activation function valued at z:\\n{valued_at_z}\")"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "1007ccdd",
- "metadata": {},
- "source": [
- "### The Neural Network\n",
- "\n",
- "Now that we have gotten a good understanding of the implementation of\n",
- "some important components, we can take a look at an object oriented\n",
- "implementation of a feed forward neural network. The feed forward\n",
- "neural network has been implemented as a class named FFNN, which can\n",
- "be initiated as a regressor or classifier dependant on the choice of\n",
- "cost function. The FFNN can have any number of input nodes, hidden\n",
- "layers with any amount of hidden nodes, and any amount of output nodes\n",
- "meaning it can perform multiclass classification as well as binary\n",
- "classification and regression problems. Although there is a lot of\n",
- "code present, it makes for an easy to use and generalizeable interface\n",
- "for creating many types of neural networks as will be demonstrated\n",
- "below."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 10,
- "id": "9584a2da",
- "metadata": {},
- "outputs": [],
- "source": [
- "import math\n",
- "import autograd.numpy as np\n",
- "import sys\n",
- "import warnings\n",
- "from autograd import grad, elementwise_grad\n",
- "from random import random, seed\n",
- "from copy import deepcopy, copy\n",
- "from typing import Tuple, Callable\n",
- "from sklearn.utils import resample\n",
- "\n",
- "warnings.simplefilter(\"error\")\n",
- "\n",
- "\n",
- "class FFNN:\n",
- " \"\"\"\n",
- " Description:\n",
- " ------------\n",
- " Feed Forward Neural Network with interface enabling flexible design of a\n",
- " nerual networks architecture and the specification of activation function\n",
- " in the hidden layers and output layer respectively. This model can be used\n",
- " for both regression and classification problems, depending on the output function.\n",
- "\n",
- " Attributes:\n",
- " ------------\n",
- " I dimensions (tuple[int]): A list of positive integers, which specifies the\n",
- " number of nodes in each of the networks layers. The first integer in the array\n",
- " defines the number of nodes in the input layer, the second integer defines number\n",
- " of nodes in the first hidden layer and so on until the last number, which\n",
- " specifies the number of nodes in the output layer.\n",
- " II hidden_func (Callable): The activation function for the hidden layers\n",
- " III output_func (Callable): The activation function for the output layer\n",
- " IV cost_func (Callable): Our cost function\n",
- " V seed (int): Sets random seed, makes results reproducible\n",
- " \"\"\"\n",
- "\n",
- " def __init__(\n",
- " self,\n",
- " dimensions: tuple[int],\n",
- " hidden_func: Callable = sigmoid,\n",
- " output_func: Callable = lambda x: x,\n",
- " cost_func: Callable = CostOLS,\n",
- " seed: int = None,\n",
- " ):\n",
- " self.dimensions = dimensions\n",
- " self.hidden_func = hidden_func\n",
- " self.output_func = output_func\n",
- " self.cost_func = cost_func\n",
- " self.seed = seed\n",
- " self.weights = list()\n",
- " self.schedulers_weight = list()\n",
- " self.schedulers_bias = list()\n",
- " self.a_matrices = list()\n",
- " self.z_matrices = list()\n",
- " self.classification = None\n",
- "\n",
- " self.reset_weights()\n",
- " self._set_classification()\n",
- "\n",
- " def fit(\n",
- " self,\n",
- " X: np.ndarray,\n",
- " t: np.ndarray,\n",
- " scheduler: Scheduler,\n",
- " batches: int = 1,\n",
- " epochs: int = 100,\n",
- " lam: float = 0,\n",
- " X_val: np.ndarray = None,\n",
- " t_val: np.ndarray = None,\n",
- " ):\n",
- " \"\"\"\n",
- " Description:\n",
- " ------------\n",
- " This function performs the training the neural network by performing the feedforward and backpropagation\n",
- " algorithm to update the networks weights.\n",
- "\n",
- " Parameters:\n",
- " ------------\n",
- " I X (np.ndarray) : training data\n",
- " II t (np.ndarray) : target data\n",
- " III scheduler (Scheduler) : specified scheduler (algorithm for optimization of gradient descent)\n",
- " IV scheduler_args (list[int]) : list of all arguments necessary for scheduler\n",
- "\n",
- " Optional Parameters:\n",
- " ------------\n",
- " V batches (int) : number of batches the datasets are split into, default equal to 1\n",
- " VI epochs (int) : number of iterations used to train the network, default equal to 100\n",
- " VII lam (float) : regularization hyperparameter lambda\n",
- " VIII X_val (np.ndarray) : validation set\n",
- " IX t_val (np.ndarray) : validation target set\n",
- "\n",
- " Returns:\n",
- " ------------\n",
- " I scores (dict) : A dictionary containing the performance metrics of the model.\n",
- " The number of the metrics depends on the parameters passed to the fit-function.\n",
- "\n",
- " \"\"\"\n",
- "\n",
- " # setup \n",
- " if self.seed is not None:\n",
- " np.random.seed(self.seed)\n",
- "\n",
- " val_set = False\n",
- " if X_val is not None and t_val is not None:\n",
- " val_set = True\n",
- "\n",
- " # creating arrays for score metrics\n",
- " train_errors = np.empty(epochs)\n",
- " train_errors.fill(np.nan)\n",
- " val_errors = np.empty(epochs)\n",
- " val_errors.fill(np.nan)\n",
- "\n",
- " train_accs = np.empty(epochs)\n",
- " train_accs.fill(np.nan)\n",
- " val_accs = np.empty(epochs)\n",
- " val_accs.fill(np.nan)\n",
- "\n",
- " self.schedulers_weight = list()\n",
- " self.schedulers_bias = list()\n",
- "\n",
- " batch_size = X.shape[0] // batches\n",
- "\n",
- " X, t = resample(X, t)\n",
- "\n",
- " # this function returns a function valued only at X\n",
- " cost_function_train = self.cost_func(t)\n",
- " if val_set:\n",
- " cost_function_val = self.cost_func(t_val)\n",
- "\n",
- " # create schedulers for each weight matrix\n",
- " for i in range(len(self.weights)):\n",
- " self.schedulers_weight.append(copy(scheduler))\n",
- " self.schedulers_bias.append(copy(scheduler))\n",
- "\n",
- " print(f\"{scheduler.__class__.__name__}: Eta={scheduler.eta}, Lambda={lam}\")\n",
- "\n",
- " try:\n",
- " for e in range(epochs):\n",
- " for i in range(batches):\n",
- " # allows for minibatch gradient descent\n",
- " if i == batches - 1:\n",
- " # If the for loop has reached the last batch, take all thats left\n",
- " X_batch = X[i * batch_size :, :]\n",
- " t_batch = t[i * batch_size :, :]\n",
- " else:\n",
- " X_batch = X[i * batch_size : (i + 1) * batch_size, :]\n",
- " t_batch = t[i * batch_size : (i + 1) * batch_size, :]\n",
- "\n",
- " self._feedforward(X_batch)\n",
- " self._backpropagate(X_batch, t_batch, lam)\n",
- "\n",
- " # reset schedulers for each epoch (some schedulers pass in this call)\n",
- " for scheduler in self.schedulers_weight:\n",
- " scheduler.reset()\n",
- "\n",
- " for scheduler in self.schedulers_bias:\n",
- " scheduler.reset()\n",
- "\n",
- " # computing performance metrics\n",
- " pred_train = self.predict(X)\n",
- " train_error = cost_function_train(pred_train)\n",
- "\n",
- " train_errors[e] = train_error\n",
- " if val_set:\n",
- " \n",
- " pred_val = self.predict(X_val)\n",
- " val_error = cost_function_val(pred_val)\n",
- " val_errors[e] = val_error\n",
- "\n",
- " if self.classification:\n",
- " train_acc = self._accuracy(self.predict(X), t)\n",
- " train_accs[e] = train_acc\n",
- " if val_set:\n",
- " val_acc = self._accuracy(pred_val, t_val)\n",
- " val_accs[e] = val_acc\n",
- "\n",
- " # printing progress bar\n",
- " progression = e / epochs\n",
- " print_length = self._progress_bar(\n",
- " progression,\n",
- " train_error=train_errors[e],\n",
- " train_acc=train_accs[e],\n",
- " val_error=val_errors[e],\n",
- " val_acc=val_accs[e],\n",
- " )\n",
- " except KeyboardInterrupt:\n",
- " # allows for stopping training at any point and seeing the result\n",
- " pass\n",
- "\n",
- " # visualization of training progression (similiar to tensorflow progression bar)\n",
- " sys.stdout.write(\"\\r\" + \" \" * print_length)\n",
- " sys.stdout.flush()\n",
- " self._progress_bar(\n",
- " 1,\n",
- " train_error=train_errors[e],\n",
- " train_acc=train_accs[e],\n",
- " val_error=val_errors[e],\n",
- " val_acc=val_accs[e],\n",
- " )\n",
- " sys.stdout.write(\"\")\n",
- "\n",
- " # return performance metrics for the entire run\n",
- " scores = dict()\n",
- "\n",
- " scores[\"train_errors\"] = train_errors\n",
- "\n",
- " if val_set:\n",
- " scores[\"val_errors\"] = val_errors\n",
- "\n",
- " if self.classification:\n",
- " scores[\"train_accs\"] = train_accs\n",
- "\n",
- " if val_set:\n",
- " scores[\"val_accs\"] = val_accs\n",
- "\n",
- " return scores\n",
- "\n",
- " def predict(self, X: np.ndarray, *, threshold=0.5):\n",
- " \"\"\"\n",
- " Description:\n",
- " ------------\n",
- " Performs prediction after training of the network has been finished.\n",
- "\n",
- " Parameters:\n",
- " ------------\n",
- " I X (np.ndarray): The design matrix, with n rows of p features each\n",
- "\n",
- " Optional Parameters:\n",
- " ------------\n",
- " II threshold (float) : sets minimal value for a prediction to be predicted as the positive class\n",
- " in classification problems\n",
- "\n",
- " Returns:\n",
- " ------------\n",
- " I z (np.ndarray): A prediction vector (row) for each row in our design matrix\n",
- " This vector is thresholded if regression=False, meaning that classification results\n",
- " in a vector of 1s and 0s, while regressions in an array of decimal numbers\n",
- "\n",
- " \"\"\"\n",
- "\n",
- " predict = self._feedforward(X)\n",
- "\n",
- " if self.classification:\n",
- " return np.where(predict > threshold, 1, 0)\n",
- " else:\n",
- " return predict\n",
- "\n",
- " def reset_weights(self):\n",
- " \"\"\"\n",
- " Description:\n",
- " ------------\n",
- " Resets/Reinitializes the weights in order to train the network for a new problem.\n",
- "\n",
- " \"\"\"\n",
- " if self.seed is not None:\n",
- " np.random.seed(self.seed)\n",
- "\n",
- " self.weights = list()\n",
- " for i in range(len(self.dimensions) - 1):\n",
- " weight_array = np.random.randn(\n",
- " self.dimensions[i] + 1, self.dimensions[i + 1]\n",
- " )\n",
- " weight_array[0, :] = np.random.randn(self.dimensions[i + 1]) * 0.01\n",
- "\n",
- " self.weights.append(weight_array)\n",
- "\n",
- " def _feedforward(self, X: np.ndarray):\n",
- " \"\"\"\n",
- " Description:\n",
- " ------------\n",
- " Calculates the activation of each layer starting at the input and ending at the output.\n",
- " Each following activation is calculated from a weighted sum of each of the preceeding\n",
- " activations (except in the case of the input layer).\n",
- "\n",
- " Parameters:\n",
- " ------------\n",
- " I X (np.ndarray): The design matrix, with n rows of p features each\n",
- "\n",
- " Returns:\n",
- " ------------\n",
- " I z (np.ndarray): A prediction vector (row) for each row in our design matrix\n",
- " \"\"\"\n",
- "\n",
- " # reset matrices\n",
- " self.a_matrices = list()\n",
- " self.z_matrices = list()\n",
- "\n",
- " # if X is just a vector, make it into a matrix\n",
- " if len(X.shape) == 1:\n",
- " X = X.reshape((1, X.shape[0]))\n",
- "\n",
- " # Add a coloumn of zeros as the first coloumn of the design matrix, in order\n",
- " # to add bias to our data\n",
- " bias = np.ones((X.shape[0], 1)) * 0.01\n",
- " X = np.hstack([bias, X])\n",
- "\n",
- " # a^0, the nodes in the input layer (one a^0 for each row in X - where the\n",
- " # exponent indicates layer number).\n",
- " a = X\n",
- " self.a_matrices.append(a)\n",
- " self.z_matrices.append(a)\n",
- "\n",
- " # The feed forward algorithm\n",
- " for i in range(len(self.weights)):\n",
- " if i < len(self.weights) - 1:\n",
- " z = a @ self.weights[i]\n",
- " self.z_matrices.append(z)\n",
- " a = self.hidden_func(z)\n",
- " # bias column again added to the data here\n",
- " bias = np.ones((a.shape[0], 1)) * 0.01\n",
- " a = np.hstack([bias, a])\n",
- " self.a_matrices.append(a)\n",
- " else:\n",
- " try:\n",
- " # a^L, the nodes in our output layers\n",
- " z = a @ self.weights[i]\n",
- " a = self.output_func(z)\n",
- " self.a_matrices.append(a)\n",
- " self.z_matrices.append(z)\n",
- " except Exception as OverflowError:\n",
- " print(\n",
- " \"OverflowError in fit() in FFNN\\nHOW TO DEBUG ERROR: Consider lowering your learning rate or scheduler specific parameters such as momentum, or check if your input values need scaling\"\n",
- " )\n",
- "\n",
- " # this will be a^L\n",
- " return a\n",
- "\n",
- " def _backpropagate(self, X, t, lam):\n",
- " \"\"\"\n",
- " Description:\n",
- " ------------\n",
- " Performs the backpropagation algorithm. In other words, this method\n",
- " calculates the gradient of all the layers starting at the\n",
- " output layer, and moving from right to left accumulates the gradient until\n",
- " the input layer is reached. Each layers respective weights are updated while\n",
- " the algorithm propagates backwards from the output layer (auto-differentation in reverse mode).\n",
- "\n",
- " Parameters:\n",
- " ------------\n",
- " I X (np.ndarray): The design matrix, with n rows of p features each.\n",
- " II t (np.ndarray): The target vector, with n rows of p targets.\n",
- " III lam (float32): regularization parameter used to punish the weights in case of overfitting\n",
- "\n",
- " Returns:\n",
- " ------------\n",
- " No return value.\n",
- "\n",
- " \"\"\"\n",
- " out_derivative = derivate(self.output_func)\n",
- " hidden_derivative = derivate(self.hidden_func)\n",
- "\n",
- " for i in range(len(self.weights) - 1, -1, -1):\n",
- " # delta terms for output\n",
- " if i == len(self.weights) - 1:\n",
- " # for multi-class classification\n",
- " if (\n",
- " self.output_func.__name__ == \"softmax\"\n",
- " ):\n",
- " delta_matrix = self.a_matrices[i + 1] - t\n",
- " # for single class classification\n",
- " else:\n",
- " cost_func_derivative = grad(self.cost_func(t))\n",
- " delta_matrix = out_derivative(\n",
- " self.z_matrices[i + 1]\n",
- " ) * cost_func_derivative(self.a_matrices[i + 1])\n",
- "\n",
- " # delta terms for hidden layer\n",
- " else:\n",
- " delta_matrix = (\n",
- " self.weights[i + 1][1:, :] @ delta_matrix.T\n",
- " ).T * hidden_derivative(self.z_matrices[i + 1])\n",
- "\n",
- " # calculate gradient\n",
- " gradient_weights = self.a_matrices[i][:, 1:].T @ delta_matrix\n",
- " gradient_bias = np.sum(delta_matrix, axis=0).reshape(\n",
- " 1, delta_matrix.shape[1]\n",
- " )\n",
- "\n",
- " # regularization term\n",
- " gradient_weights += self.weights[i][1:, :] * lam\n",
- "\n",
- " # use scheduler\n",
- " update_matrix = np.vstack(\n",
- " [\n",
- " self.schedulers_bias[i].update_change(gradient_bias),\n",
- " self.schedulers_weight[i].update_change(gradient_weights),\n",
- " ]\n",
- " )\n",
- "\n",
- " # update weights and bias\n",
- " self.weights[i] -= update_matrix\n",
- "\n",
- " def _accuracy(self, prediction: np.ndarray, target: np.ndarray):\n",
- " \"\"\"\n",
- " Description:\n",
- " ------------\n",
- " Calculates accuracy of given prediction to target\n",
- "\n",
- " Parameters:\n",
- " ------------\n",
- " I prediction (np.ndarray): vector of predicitons output network\n",
- " (1s and 0s in case of classification, and real numbers in case of regression)\n",
- " II target (np.ndarray): vector of true values (What the network ideally should predict)\n",
- "\n",
- " Returns:\n",
- " ------------\n",
- " A floating point number representing the percentage of correctly classified instances.\n",
- " \"\"\"\n",
- " assert prediction.size == target.size\n",
- " return np.average((target == prediction))\n",
- " def _set_classification(self):\n",
- " \"\"\"\n",
- " Description:\n",
- " ------------\n",
- " Decides if FFNN acts as classifier (True) og regressor (False),\n",
- " sets self.classification during init()\n",
- " \"\"\"\n",
- " self.classification = False\n",
- " if (\n",
- " self.cost_func.__name__ == \"CostLogReg\"\n",
- " or self.cost_func.__name__ == \"CostCrossEntropy\"\n",
- " ):\n",
- " self.classification = True\n",
- "\n",
- " def _progress_bar(self, progression, **kwargs):\n",
- " \"\"\"\n",
- " Description:\n",
- " ------------\n",
- " Displays progress of training\n",
- " \"\"\"\n",
- " print_length = 40\n",
- " num_equals = int(progression * print_length)\n",
- " num_not = print_length - num_equals\n",
- " arrow = \">\" if num_equals > 0 else \"\"\n",
- " bar = \"[\" + \"=\" * (num_equals - 1) + arrow + \"-\" * num_not + \"]\"\n",
- " perc_print = self._format(progression * 100, decimals=5)\n",
- " line = f\" {bar} {perc_print}% \"\n",
- "\n",
- " for key in kwargs:\n",
- " if not np.isnan(kwargs[key]):\n",
- " value = self._format(kwargs[key], decimals=4)\n",
- " line += f\"| {key}: {value} \"\n",
- " sys.stdout.write(\"\\r\" + line)\n",
- " sys.stdout.flush()\n",
- " return len(line)\n",
- "\n",
- " def _format(self, value, decimals=4):\n",
- " \"\"\"\n",
- " Description:\n",
- " ------------\n",
- " Formats decimal numbers for progress bar\n",
- " \"\"\"\n",
- " if value > 0:\n",
- " v = value\n",
- " elif value < 0:\n",
- " v = -10 * value\n",
- " else:\n",
- " v = 1\n",
- " n = 1 + math.floor(math.log10(v))\n",
- " if n >= decimals - 1:\n",
- " return str(round(value))\n",
- " return f\"{value:.{decimals-n-1}f}\""
- ]
- },
- {
- "cell_type": "markdown",
- "id": "9ccd1fc1",
- "metadata": {},
- "source": [
- "Before we make a model, we will quickly generate a dataset we can use\n",
- "for our linear regression problem as shown below"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 11,
- "id": "7f3a5b31",
- "metadata": {},
- "outputs": [],
- "source": [
- "import autograd.numpy as np\n",
- "from sklearn.model_selection import train_test_split\n",
- "\n",
- "def SkrankeFunction(x, y):\n",
- " return np.ravel(0 + 1*x + 2*y + 3*x**2 + 4*x*y + 5*y**2)\n",
- "\n",
- "def create_X(x, y, n):\n",
- " if len(x.shape) > 1:\n",
- " x = np.ravel(x)\n",
- " y = np.ravel(y)\n",
- "\n",
- " N = len(x)\n",
- " l = int((n + 1) * (n + 2) / 2) # Number of elements in beta\n",
- " X = np.ones((N, l))\n",
- "\n",
- " for i in range(1, n + 1):\n",
- " q = int((i) * (i + 1) / 2)\n",
- " for k in range(i + 1):\n",
- " X[:, q + k] = (x ** (i - k)) * (y**k)\n",
- "\n",
- " return X\n",
- "\n",
- "step=0.5\n",
- "x = np.arange(0, 1, step)\n",
- "y = np.arange(0, 1, step)\n",
- "x, y = np.meshgrid(x, y)\n",
- "target = SkrankeFunction(x, y)\n",
- "target = target.reshape(target.shape[0], 1)\n",
- "\n",
- "poly_degree=3\n",
- "X = create_X(x, y, poly_degree)\n",
- "\n",
- "X_train, X_test, t_train, t_test = train_test_split(X, target)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "1ac05bb6",
- "metadata": {},
- "source": [
- "Now that we have our dataset ready for the regression, we can create\n",
- "our regressor. Note that with the seed parameter, we can make sure our\n",
- "results stay the same every time we run the neural network. For\n",
- "inititialization, we simply specify the dimensions (we wish the amount\n",
- "of input nodes to be equal to the datapoints, and the output to\n",
- "predict one value)."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 12,
- "id": "0f857604",
- "metadata": {},
- "outputs": [],
- "source": [
- "input_nodes = X_train.shape[1]\n",
- "output_nodes = 1\n",
- "\n",
- "linear_regression = FFNN((input_nodes, output_nodes), output_func=identity, cost_func=CostOLS, seed=2023)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "eeff4315",
- "metadata": {},
- "source": [
- "We then fit our model with our training data using the scheduler of our choice."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 13,
- "id": "46246810",
- "metadata": {},
- "outputs": [],
- "source": [
- "linear_regression.reset_weights() # reset weights such that previous runs or reruns don't affect the weights\n",
- "\n",
- "scheduler = Constant(eta=1e-3)\n",
- "scores = linear_regression.fit(X_train, t_train, scheduler)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "8c6f9954",
- "metadata": {},
- "source": [
- "Due to the progress bar we can see the MSE (train_error) throughout\n",
- "the FFNN's training. Note that the fit() function has some optional\n",
- "parameters with defualt arguments. For example, the regularization\n",
- "hyperparameter can be left ignored if not needed, and equally the FFNN\n",
- "will by default run for 100 epochs. These can easily be changed, such\n",
- "as for example:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 14,
- "id": "2661939c",
- "metadata": {},
- "outputs": [],
- "source": [
- "linear_regression.reset_weights() # reset weights such that previous runs or reruns don't affect the weights\n",
- "\n",
- "scores = linear_regression.fit(X_train, t_train, scheduler, lam=1e-4, epochs=1000)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "74c5624c",
- "metadata": {},
- "source": [
- "We see that given more epochs to train on, the regressor reaches a lower MSE.\n",
- "\n",
- "Let us then switch to a binary classification. We use a binary\n",
- "classification dataset, and follow a similar setup to the regression\n",
- "case."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 15,
- "id": "4b8eb115",
- "metadata": {},
- "outputs": [],
- "source": [
- "from sklearn.datasets import load_breast_cancer\n",
- "from sklearn.preprocessing import MinMaxScaler\n",
- "\n",
- "wisconsin = load_breast_cancer()\n",
- "X = wisconsin.data\n",
- "target = wisconsin.target\n",
- "target = target.reshape(target.shape[0], 1)\n",
- "\n",
- "X_train, X_val, t_train, t_val = train_test_split(X, target)\n",
- "\n",
- "scaler = MinMaxScaler()\n",
- "scaler.fit(X_train)\n",
- "X_train = scaler.transform(X_train)\n",
- "X_val = scaler.transform(X_val)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 16,
- "id": "2c0f92bd",
- "metadata": {},
- "outputs": [],
- "source": [
- "input_nodes = X_train.shape[1]\n",
- "output_nodes = 1\n",
- "\n",
- "logistic_regression = FFNN((input_nodes, output_nodes), output_func=sigmoid, cost_func=CostLogReg, seed=2023)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "49201ae4",
- "metadata": {},
- "source": [
- "We will now make use of our validation data by passing it into our fit function as a keyword argument"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 17,
- "id": "55b5e426",
- "metadata": {},
- "outputs": [],
- "source": [
- "logistic_regression.reset_weights() # reset weights such that previous runs or reruns don't affect the weights\n",
- "\n",
- "scheduler = Adam(eta=1e-3, rho=0.9, rho2=0.999)\n",
- "scores = logistic_regression.fit(X_train, t_train, scheduler, epochs=1000, X_val=X_val, t_val=t_val)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "b51762fb",
- "metadata": {},
- "source": [
- "Finally, we will create a neural network with 2 hidden layers with activation functions."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 18,
- "id": "6b59e27d",
- "metadata": {},
- "outputs": [],
- "source": [
- "input_nodes = X_train.shape[1]\n",
- "hidden_nodes1 = 100\n",
- "hidden_nodes2 = 30\n",
- "output_nodes = 1\n",
- "\n",
- "dims = (input_nodes, hidden_nodes1, hidden_nodes2, output_nodes)\n",
- "\n",
- "neural_network = FFNN(dims, hidden_func=RELU, output_func=sigmoid, cost_func=CostLogReg, seed=2023)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 19,
- "id": "72c87921",
- "metadata": {},
- "outputs": [],
- "source": [
- "neural_network.reset_weights() # reset weights such that previous runs or reruns don't affect the weights\n",
- "\n",
- "scheduler = Adam(eta=1e-4, rho=0.9, rho2=0.999)\n",
- "scores = neural_network.fit(X_train, t_train, scheduler, epochs=1000, X_val=X_val, t_val=t_val)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "ed40d7d2",
- "metadata": {},
- "source": [
- "### Multiclass classification\n",
- "\n",
- "Finally, we will demonstrate the use case of multiclass classification\n",
- "using our FFNN with the famous MNIST dataset, which contain images of\n",
- "digits between the range of 0 to 9."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 20,
- "id": "315ef3fe",
- "metadata": {},
- "outputs": [],
- "source": [
- "from sklearn.datasets import load_digits\n",
- "\n",
- "def onehot(target: np.ndarray):\n",
- " onehot = np.zeros((target.size, target.max() + 1))\n",
- " onehot[np.arange(target.size), target] = 1\n",
- " return onehot\n",
- "\n",
- "digits = load_digits()\n",
- "\n",
- "X = digits.data\n",
- "target = digits.target\n",
- "target = onehot(target)\n",
- "\n",
- "input_nodes = 64\n",
- "hidden_nodes1 = 100\n",
- "hidden_nodes2 = 30\n",
- "output_nodes = 10\n",
- "\n",
- "dims = (input_nodes, hidden_nodes1, hidden_nodes2, output_nodes)\n",
- "\n",
- "multiclass = FFNN(dims, hidden_func=LRELU, output_func=softmax, cost_func=CostCrossEntropy)\n",
- "\n",
- "multiclass.reset_weights() # reset weights such that previous runs or reruns don't affect the weights\n",
- "\n",
- "scheduler = Adam(eta=1e-4, rho=0.9, rho2=0.999)\n",
- "scores = multiclass.fit(X, target, scheduler, epochs=1000)"
+ "Make plots of the confusion matrix, the ROC curve and the cumulative\n",
+ "gain curve for this (or other) multiclass data set."
]
}
],
@@ -1507,7 +639,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.9.10"
+ "version": "3.9.15"
}
},
"nbformat": 4,
diff --git a/doc/LectureNotes/exercisesweek43.ipynb b/doc/LectureNotes/exercisesweek43.ipynb
index 737d0d60c..f80e8787a 100644
--- a/doc/LectureNotes/exercisesweek43.ipynb
+++ b/doc/LectureNotes/exercisesweek43.ipynb
@@ -32,15 +32,15 @@
"editable": true
},
"source": [
- "# Overarching aims of the exercises weeks 43 and 44\n",
+ "# Overarching aims of the exercises for week 43\n",
"\n",
"The aim of the exercises this week is to gain some confidence with\n",
"ways to visualize the results of a classification problem. We will\n",
"target three ways of setting up the analysis. The first and simplest\n",
"one is the\n",
- "1. so-called confusion matrix, and the next is the\n",
+ "1. so-called confusion matrix. The next one is the so-called\n",
"\n",
- "2. ROC curve and finally the\n",
+ "2. ROC curve. Finally we have the\n",
"\n",
"3. Cumulative gain curve.\n",
"\n",
@@ -446,7 +446,10 @@
"id": "be9ff0b9",
"metadata": {
"collapsed": false,
- "editable": true
+ "editable": true,
+ "jupyter": {
+ "outputs_hidden": false
+ }
},
"outputs": [],
"source": [
@@ -518,7 +521,10 @@
"id": "d20bb8be",
"metadata": {
"collapsed": false,
- "editable": true
+ "editable": true,
+ "jupyter": {
+ "outputs_hidden": false
+ }
},
"outputs": [],
"source": [
@@ -547,7 +553,10 @@
"id": "d271f0ba",
"metadata": {
"collapsed": false,
- "editable": true
+ "editable": true,
+ "jupyter": {
+ "outputs_hidden": false
+ }
},
"outputs": [],
"source": [
@@ -589,7 +598,10 @@
"id": "3b045d56",
"metadata": {
"collapsed": false,
- "editable": true
+ "editable": true,
+ "jupyter": {
+ "outputs_hidden": false
+ }
},
"outputs": [],
"source": [
@@ -611,7 +623,25 @@
]
}
],
- "metadata": {},
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.15"
+ }
+ },
"nbformat": 4,
"nbformat_minor": 5
}