{"id":"l-ml-5-2","courseId":"ml-crash-course","lang":"en","kind":"lesson","summary":"Explain what layers, activations, epochs and backpropagation are, train a small neural network with MLPClassifier, and judge honestly when deep learning beats simpler models.","html":"<p>“Deep learning” gets used as a synonym for AI, and it powers image recognition, speech-to-text and the large language models in the next lesson. Underneath, it’s built from pieces you already know: weighted sums like linear regression, a squashing function like the sigmoid, and gradient descent. This lesson assembles them, trains a small network, and then puts it next to logistic regression to see what it’s really worth.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">From one neuron to a network</h2>\n<p>A <strong>neuron</strong> does what logistic regression does without the final squash: multiply each input by a weight, add them up, add a bias. Then it applies an <strong>activation function</strong>. The most common is <strong>ReLU</strong>, which outputs the sum if it’s positive and 0 otherwise. A <strong>layer</strong> is a set of neurons that all read the same inputs, each with its own weights. A <strong>network</strong> stacks layers: the outputs of one become the inputs of the next.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 640 300\" role=\"img\" aria-labelledby=\"l-ml-5-2-t1\"><defs><marker id=\"arrow-l-ml-5-2-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-ml-5-2-t1\">Three input nodes connect to every one of four hidden nodes, which connect to every one of three output nodes. Hidden nodes apply ReLU; outputs apply softmax to give class probabilities.</title>\n  <text class=\"d-label-strong\" x=\"90\" y=\"24\" text-anchor=\"middle\">inputs</text>\n  <text class=\"d-label-strong\" x=\"320\" y=\"24\" text-anchor=\"middle\">hidden layer (ReLU)</text>\n  <text class=\"d-label-strong\" x=\"550\" y=\"24\" text-anchor=\"middle\">outputs (softmax)</text>\n  <g class=\"d-line\">\n    <path d=\"M110 90 L300 60 M110 90 L300 125 M110 90 L300 190 M110 90 L300 255\"/>\n    <path d=\"M110 155 L300 60 M110 155 L300 125 M110 155 L300 190 M110 155 L300 255\"/>\n    <path d=\"M110 220 L300 60 M110 220 L300 125 M110 220 L300 190 M110 220 L300 255\"/>\n    <path d=\"M340 60 L530 90 M340 60 L530 155 M340 60 L530 220\"/>\n    <path d=\"M340 125 L530 90 M340 125 L530 155 M340 125 L530 220\"/>\n    <path d=\"M340 190 L530 90 M340 190 L530 155 M340 190 L530 220\"/>\n    <path d=\"M340 255 L530 90 M340 255 L530 155 M340 255 L530 220\"/>\n  </g>\n  <circle class=\"d-box\" cx=\"90\" cy=\"90\" r=\"20\"/><circle class=\"d-box\" cx=\"90\" cy=\"155\" r=\"20\"/><circle class=\"d-box\" cx=\"90\" cy=\"220\" r=\"20\"/>\n  <circle class=\"d-box-primary\" cx=\"320\" cy=\"60\" r=\"20\"/><circle class=\"d-box-primary\" cx=\"320\" cy=\"125\" r=\"20\"/><circle class=\"d-box-primary\" cx=\"320\" cy=\"190\" r=\"20\"/><circle class=\"d-box-primary\" cx=\"320\" cy=\"255\" r=\"20\"/>\n  <circle class=\"d-box-accent\" cx=\"550\" cy=\"90\" r=\"20\"/><circle class=\"d-box-accent\" cx=\"550\" cy=\"155\" r=\"20\"/><circle class=\"d-box-accent\" cx=\"550\" cy=\"220\" r=\"20\"/>\n  <text class=\"d-label-muted\" x=\"430\" y=\"290\" text-anchor=\"middle\">every line is one weight</text>\n</svg><figcaption>A small fully connected network</figcaption></figure>\n<p>The activation is the whole trick. Without it, a layer is a linear function, and a linear function of a linear function is still linear: ten layers would be no more powerful than one logistic regression. With ReLU in between, each layer can bend and combine what the previous one produced, so the network can represent curves and interactions you never wrote down. In effect, the hidden layers <strong>learn their own features</strong>.</p>\n<p>For classification, the output layer has one neuron per class and a <strong>softmax</strong>, which turns the outputs into probabilities that sum to 1, the multi-class cousin of the sigmoid.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">How a network learns</h2>\n<p>Training repeats four steps:</p>\n<ol>\n<li><strong>Forward pass</strong>: run a batch of examples through the network to get predictions.</li>\n<li><strong>Loss</strong>: measure how wrong they are, with log loss for classification.</li>\n<li><strong>Backpropagation</strong>: compute the gradient of the loss with respect to every weight, working backwards from the output layer with the chain rule.</li>\n<li><strong>Optimiser step</strong>: nudge every weight against its gradient. <code dir=\"ltr\">adam</code>, the default, is gradient descent with a per-weight adaptive learning rate.</li>\n</ol>\n<p>One pass through the whole training set is an <strong>epoch</strong>. Training runs for many epochs, and you watch the loss fall exactly as it did in your hand-written gradient descent in section 2, just across thousands of weights instead of two.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">A network on handwritten digits</h2>\n<p>The digits dataset holds 1,797 images of handwritten digits, each 8×8 pixels flattened into 64 numbers. scikit-learn’s <code dir=\"ltr\">MLPClassifier</code> (multi-layer perceptron) is a small fully connected network.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"0\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.datasets </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> load_digits</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.linear_model </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> LogisticRegression</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.model_selection </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> train_test_split</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.neural_network </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> MLPClassifier</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.pipeline </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> make_pipeline</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.preprocessing </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> StandardScaler</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">X, y </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> load_digits(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">return_X_y</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">True</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">X_train, X_test, y_train, y_test </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> train_test_split(X, y, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">test_size</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.25</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">random_state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">stratify</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">y)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">net </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> make_pipeline(StandardScaler(),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                    MLPClassifier(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">hidden_layer_sizes</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">64</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,), </span><span style=\"color:#953800;--shiki-dark:#FFA657\">max_iter</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">500</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">random_state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">net.fit(X_train, y_train)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">mlp </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> net[</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"weight matrices:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, [w.shape </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> w </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> mlp.coefs_])</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"parameters:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">sum</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(w.size </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> w </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> mlp.coefs_) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> sum</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(b.size </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> mlp.intercepts_))</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"epochs run:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, mlp.n_iter_, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"| loss first, 10th, last:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">float</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(mlp.loss_curve_[i]), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> i </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">9</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)])</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"network test accuracy: \"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(net.score(X_test, y_test), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">linear </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> make_pipeline(StandardScaler(), LogisticRegression(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">max_iter</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)).fit(X_train, y_train)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"logistic test accuracy:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(linear.score(X_test, y_test), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>One hidden layer of 64 neurons means two weight matrices: 64×64 from pixels to hidden units and 64×10 from hidden units to digits, 4,810 parameters with the biases. The loss falls from 2.31 (a uniform guess over ten classes) to under 0.01, and training stops on its own after 164 epochs because the improvement has stalled. Test accuracy: 98%.</p>\n<p>And logistic regression? Also about 98%. On 8×8 images with a few hundred examples per class, a straight boundary is already enough. The network’s extra power has almost nothing to work on. Try it on churn and the result is the same, slightly worse: around 0.79 ROC AUC against 0.81 for logistic regression.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Expecting a neural network to beat everything</span></p>\n<p>Networks have more parameters than rows in many tabular datasets, need scaled inputs, and are sensitive to settings (layer sizes, learning rate, <code dir=\"ltr\">alpha</code> for L2 regularisation, epochs). On small or medium tables with designed features, regularised linear models and gradient boosting are usually as good and far easier to run and explain. Reach for deep learning when the data is large and raw.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Where deep learning wins</h2>\n<p>The networks behind modern AI differ from <code dir=\"ltr\">MLPClassifier</code> in scale and in architecture. <strong>Convolutional networks</strong> reuse the same small filters across an image, so they learn edges, then shapes, then objects. <strong>Transformers</strong>, behind today’s language models, let every word in a text weigh every other word. Both are trained on millions or billions of examples, on GPUs, with libraries such as PyTorch, Keras or JAX, not scikit-learn.</p>\n<p>What they share with your 4,810-weight network is everything conceptual: layers, activations, a loss, backpropagation, epochs, overfitting, a validation set. When a deep learning paper says “we trained for 20 epochs with early stopping on validation loss”, you now know what every word means.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>When to switch tools</span></p>\n<p>If your inputs are pixels, audio samples or raw text, or you have hundreds of thousands of labelled examples, it’s time for a deep learning framework or, increasingly, a pretrained model you adapt. If your inputs are a table of customer facts, stay with scikit-learn.</p>\n</aside>\n<p>Pretrained models are the bridge between the two worlds, and the most visible of them are large language models. Next lesson explains what they do, how embeddings turn text into the kind of vectors you’ve been feeding models all course, and where they fit next to a churn model.</p>\n","toc":[{"id":"sec-1","text":"From one neuron to a network","level":2},{"id":"sec-2","text":"How a network learns","level":2},{"id":"sec-3","text":"A network on handwritten digits","level":2},{"id":"sec-4","text":"Where deep learning wins","level":2}],"takeaways":["A neuron computes a weighted sum of its inputs plus a bias and passes it through an activation function such as ReLU; a layer is many neurons side by side.","Without non-linear activations, any stack of layers collapses into a single linear model.","Training repeats forward pass, loss, backpropagation (gradients for every weight) and an optimiser step; one pass over the training data is an epoch.","<code dir=\"ltr\">MLPClassifier</code> is a small fully connected network for tabular-sized problems; real deep learning uses PyTorch, Keras or JAX on GPUs.","Neural networks shine on large amounts of raw, unstructured data such as images, audio and text; on small tables, linear models and boosted trees usually match or beat them."],"further":[{"title":"Neural network models (supervised)","url":"https://scikit-learn.org/stable/modules/neural_networks_supervised.html"},{"title":"MLPClassifier","url":"https://scikit-learn.org/stable/modules/generated/sklearn.neural_network.MLPClassifier.html"}],"quiz":[{"q":"<p>Why do neural networks need non-linear activation functions between layers?</p>","options":[{"text":"They make training faster on GPUs.","why":"Speed isn’t the reason; the reason is what the network can represent at all."},{"text":"They keep the outputs between 0 and 1.","why":"That’s only true of some activations (sigmoid); ReLU outputs can be any non-negative number."},{"text":"They stop the weights from growing too large.","why":"That’s regularisation’s job (<code dir=\"ltr\">alpha</code> in <code dir=\"ltr\">MLPClassifier</code>), not the activation’s."},{"text":"Without them, several linear layers multiply out to one linear function, so depth adds nothing.","why":"Correct. The non-linearity is what lets stacked layers model curves and interactions."}],"answer":3},{"q":"<p>A network trains on 1,347 images with <code dir=\"ltr\">max_iter=500</code>, and <code dir=\"ltr\">n_iter_</code> reports 164. What happened?</p>","options":[{"text":"Training stopped after 164 epochs because the loss stopped improving by more than the tolerance.","why":"Correct. For the <code dir=\"ltr\">adam</code> and <code dir=\"ltr\">sgd</code> solvers, <code dir=\"ltr\">max_iter</code> caps the number of epochs, and training stops early once progress stalls."},{"text":"It saw only 164 of the 1,347 images.","why":"An epoch is a full pass over all training rows; each of the 164 epochs used every image."},{"text":"It crashed at epoch 164 and kept the last good weights.","why":"Stopping early is normal convergence, not a crash; a failure to converge would raise a <code dir=\"ltr\">ConvergenceWarning</code> at 500."}],"answer":0},{"q":"<p>On the churn table, a small MLP reaches CV ROC AUC 0.79 while logistic regression reaches 0.81. What’s the most sensible reading?</p>","options":[{"text":"The MLP needs more layers; deep learning always wins with enough depth.","why":"With 298 rows and eight features, extra capacity mostly adds variance. Depth isn’t free."},{"text":"On a small table of informative features, a simple model is hard to beat; the network adds complexity without signal to use.","why":"Correct. Networks earn their keep when there’s a lot of data and raw inputs whose features are hard to design by hand."},{"text":"Neural networks can’t be used for binary classification.","why":"They handle binary classification routinely; the issue is data size and type."}],"answer":1}],"exercise":{"kind":"python","starter":"from sklearn.datasets import load_digits\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.neural_network import MLPClassifier\nfrom sklearn.pipeline import make_pipeline\nfrom sklearn.preprocessing import StandardScaler\n\nX, y = load_digits(return_X_y=True)\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)\n\n# 1. Pipeline: StandardScaler, then an MLPClassifier with ONE hidden layer of 32 neurons,\n#    max_iter=500, random_state=42. Fit it on the training data.\nnet = MLPClassifier(hidden_layer_sizes=(32, 32), max_iter=500, random_state=42)\n\n# 2. Facts about the trained network\nn_params = None      # total number of weights + biases\nepochs = None        # epochs actually run\ntest_acc = None      # test accuracy\n","solution":"from sklearn.datasets import load_digits\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.neural_network import MLPClassifier\nfrom sklearn.pipeline import make_pipeline\nfrom sklearn.preprocessing import StandardScaler\n\nX, y = load_digits(return_X_y=True)\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)\n\nnet = make_pipeline(StandardScaler(),\n                    MLPClassifier(hidden_layer_sizes=(32,), max_iter=500, random_state=42))\nnet.fit(X_train, y_train)\n\nmlp = net[-1]\nn_params = sum(w.size for w in mlp.coefs_) + sum(b.size for b in mlp.intercepts_)\nepochs = mlp.n_iter_\ntest_acc = net.score(X_test, y_test)\nprint(n_params, epochs, round(test_acc, 3))\n","checks":[{"id":"pipeline","test":"hasattr(net, 'steps') and type(net[0]).__name__ == 'StandardScaler' and type(net[-1]).__name__ == 'MLPClassifier'"},{"id":"architecture","test":"tuple(net[-1].hidden_layer_sizes) == (32,) and [w.shape for w in net[-1].coefs_] == [(64, 32), (32, 10)]"},{"id":"params","test":"n_params is not None and int(n_params) == 64 * 32 + 32 + 32 * 10 + 10"},{"id":"trained","test":"epochs is not None and int(epochs) == net[-1].n_iter_ and net[-1].loss_curve_[-1] < net[-1].loss_curve_[0]"},{"id":"accuracy","test":"test_acc is not None and 0.95 < float(test_acc) <= 1.0"}],"title":"Train and inspect a small network","prompt":"<ol>\n<li><code dir=\"ltr\">net</code>: a pipeline of <code dir=\"ltr\">StandardScaler</code> then <code dir=\"ltr\">MLPClassifier</code> with <strong>one</strong> hidden layer of 32 neurons (<code dir=\"ltr\">max_iter=500, random_state=42</code>), fitted on the digits training data.</li>\n<li>From the fitted network, set <code dir=\"ltr\">n_params</code> (all weights plus all biases), <code dir=\"ltr\">epochs</code> (how many epochs training actually ran) and <code dir=\"ltr\">test_acc</code> (accuracy on the test set).</li>\n</ol>\n","hints":["One hidden layer of 32 is <code dir=\"ltr\">hidden_layer_sizes=(32,)</code>; the trailing comma makes it a tuple.","<code dir=\"ltr\">net[-1]</code> is the fitted <code dir=\"ltr\">MLPClassifier</code>. Its <code dir=\"ltr\">coefs_</code> and <code dir=\"ltr\">intercepts_</code> are lists of NumPy arrays; add up their <code dir=\"ltr\">.size</code>.","<code dir=\"ltr\">n_iter_</code> holds the number of epochs run."],"explanation":"<p>The network has a 64×32 weight matrix into the hidden layer and a 32×10 matrix out to the ten digits, plus 32 and 10 biases: 2,410 parameters. Training stops by itself once the loss stops improving, well before 500 epochs, and the test accuracy lands around 97–98%, about the same as logistic regression on these small images. Without the scaler, pixel values from 0 to 16 make training slower and less stable.</p>\n","checkLabels":{"pipeline":"net scales, then runs an MLPClassifier","architecture":"One hidden layer of 32 neurons","params":"n_params counts every weight and bias","trained":"epochs matches the epochs run, and the loss fell","accuracy":"test_acc is above 0.95"}},"words":803,"readMinutes":4}