CoolFace
Apppublic

marimo-team/marimo-learn

sourceHugging Faceupdated 5mo agoView on Hugging Face
3likes
19_maximum_likelihood_estimation.py1117 linesDownload Raw Back to probability
1# /// script2# requires-python = ">=3.10"3# dependencies = [4#     "marimo",5#     "matplotlib==3.10.8",6#     "scipy==1.17.1",7#     "numpy==2.4.3",8#     "polars==1.24.0",9#     "plotly==5.18.0",10# ]11# ///12 13import marimo14 15__generated_with = "0.18.4"16app = marimo.App(width="medium", app_title="Maximum Likelihood Estimation")17 18 19@app.cell(hide_code=True)20def _(mo):21    mo.md(r"""22    # Maximum Likelihood Estimation23 24    _This notebook is a computational companion to ["Probability for Computer Scientists"](https://chrispiech.github.io/probabilityForComputerScientists/en/part5/mle/), by Stanford professor Chris Piech._25 26    Maximum Likelihood Estimation (MLE) is a fundamental method in statistics for estimating parameters of a probability distribution. The central idea is elegantly simple: **choose the parameters that make the observed data most likely**.27 28    In this notebook, we'll try to understand MLE, starting with the core concept of likelihood and how it differs from probability. We'll explore how to formulate MLE problems mathematically and then solve them for various common distributions. Along the way, I've included some interactive visualizations to help build your intuition about these concepts. You'll see how MLE applies to real-world scenarios like linear regression, and hopefully gain a deeper appreciation for why this technique is so widely used in statistics and machine learning. Think of MLE as detective work - we have some evidence (our data) and we're trying to figure out the most plausible explanation (our parameters) for what we've observed.29    """)30    return31 32 33@app.cell(hide_code=True)34def _(mo):35    mo.md(r"""36    ## Likelihood: The Core Concept37 38    Before diving into MLE, we need to understand what "likelihood" means in a statistical context.39 40    ### Data and Parameters41 42    Suppose we have collected some data $X_1, X_2, \ldots, X_n$ that are independent and identically distributed (IID). We assume these data points come from a specific type of distribution (like Normal, Bernoulli, etc.) with unknown parameters $\theta$.43 44    ### What is Likelihood?45 46    Likelihood measures how probable our observed data is, given specific values of the parameters $\theta$.47 48    /// note49    **Probability vs. Likelihood**50 51    - **Probability**: Given parameters $\theta$, what's the chance of observing data $X$?52    - **Likelihood**: Given observed data $X$, how likely are different parameter values $\theta$?53    ///54 55    To simplify notation, we'll use $f(X=x|\Theta=\theta)$ to represent either the PMF or PDF of our data, conditioned on the parameters.56    """)57    return58 59 60@app.cell(hide_code=True)61def _(mo):62    mo.md(r"""63    ### The Likelihood Function64 65    Since we assume our data points are independent, the likelihood of all our data is the product of the likelihoods of each individual data point:66 67    $$L(\theta) = \prod_{i=1}^n f(X_i = x_i|\Theta = \theta)$$68 69    This function $L(\theta)$ gives us the likelihood of observing our entire dataset for different parameter values $\theta$.70 71    /// tip72    **Key Insight**: Different parameter values produce different likelihoods for the same data. Better parameter values will make the observed data more likely.73    ///74    """)75    return76 77 78@app.cell(hide_code=True)79def _(mo):80    mo.md(r"""81    ## Maximum Likelihood Estimation82 83    The core idea of MLE is to find the parameter values $\hat{\theta}$ that maximize the likelihood function:84 85    $$\hat{\theta} = \underset{\theta}{\operatorname{argmax}} \, L(\theta)$$86 87    The notation $\hat{\theta}$ represents our best estimate of the true parameters based on the observed data.88 89    ### Working with Log-Likelihood90 91    In practice, we usually work with the **log-likelihood** instead of the likelihood directly. Since logarithm is a monotonically increasing function, the maximum of $L(\theta)$ occurs at the same value of $\theta$ as the maximum of $\log L(\theta)$.92 93    Taking the logarithm transforms our product into a sum, which is much easier to work with:94 95    $$LL(\theta) = \log L(\theta) = \log \prod_{i=1}^n f(X_i=x_i|\Theta = \theta) = \sum_{i=1}^n \log f(X_i = x_i|\Theta = \theta)$$96 97    /// warning98    Working with products of many small probabilities can lead to numerical underflow. Taking the logarithm converts these products to sums, which is numerically more stable.99    ///100    """)101    return102 103 104@app.cell(hide_code=True)105def _(mo):106    mo.md(r"""107    ### Finding the Maximum108 109    To find the values of $\theta$ that maximize the log-likelihood, we typically:110 111    1. Take the derivative of $LL(\theta)$ with respect to each parameter112    2. Set each derivative equal to zero113    3. Solve for the parameters114 115    Let's see this approach in action with some common distributions.116    """)117    return118 119 120@app.cell(hide_code=True)121def _(mo):122    mo.md(r"""123    ## MLE for Bernoulli Distribution124 125    > _Note:_ The following derivation is included as reference material. The credit for this mathematical formulation belongs to ["Probability for Computer Scientists"](https://chrispiech.github.io/probabilityForComputerScientists/en/part5/mle/) by Chris Piech.126 127    Let's start with a simple example: estimating the parameter $p$ of a Bernoulli distribution.128 129    ### The Model130 131    A Bernoulli distribution has a single parameter $p$ which represents the probability of success (getting a value of 1). Its probability mass function (PMF) can be written as:132 133    $$f(x|p) = p^x(1-p)^{1-x}, \quad x \in \{0, 1\}$$134 135    This elegant formula works because:136 137    - When $x = 1$: $f(1|p) = p^1(1-p)^0 = p$138    - When $x = 0$: $f(0|p) = p^0(1-p)^1 = 1-p$139 140    ### Deriving the MLE141 142    Given $n$ independent Bernoulli trials $X_1, X_2, \ldots, X_n$, we want to find the value of $p$ that maximizes the likelihood of our observed data.143 144    Step 1: Write the likelihood function145    $$L(p) = \prod_{i=1}^n p^{x_i}(1-p)^{1-x_i}$$146 147    Step 2: Take the logarithm to get the log-likelihood148    $$\begin{align*}149    LL(p) &= \sum_{i=1}^n \log(p^{x_i}(1-p)^{1-x_i}) \\150    &= \sum_{i=1}^n \left[x_i \log(p) + (1-x_i)\log(1-p)\right] \\151    &= \left(\sum_{i=1}^n x_i\right) \log(p) + \left(n - \sum_{i=1}^n x_i\right) \log(1-p) \\152    &= Y\log(p) + (n-Y)\log(1-p)153    \end{align*}$$154 155    where $Y = \sum_{i=1}^n x_i$ is the total number of successes.156 157    Step 3: Find the value of $p$ that maximizes $LL(p)$ by setting the derivative to zero158    $$\begin{align*}159    \frac{d\,LL(p)}{dp} &= \frac{Y}{p} - \frac{n-Y}{1-p} = 0 \\160    \frac{Y}{p} &= \frac{n-Y}{1-p} \\161    Y(1-p) &= p(n-Y) \\162    Y - Yp &= pn - pY \\163    Y &= pn \\164    \hat{p} &= \frac{Y}{n} = \frac{\sum_{i=1}^n x_i}{n}165    \end{align*}$$166 167    /// tip168    The MLE for the parameter $p$ in a Bernoulli distribution is simply the **sample mean** - the proportion of successes in our data!169    ///170    """)171    return172 173 174@app.cell(hide_code=True)175def _(controls):176    controls.center()177    return178 179 180@app.cell(hide_code=True)181def _(generate_button, mo, np, plt, sample_size_slider, true_p_slider):182    # generate bernoulli samples when button is clicked183    bernoulli_button_value = generate_button.value184 185    # get parameter values186    bernoulli_true_p = true_p_slider.value187    bernoulli_n = sample_size_slider.value188 189    # generate data190    bernoulli_data = np.random.binomial(1, bernoulli_true_p, size=bernoulli_n)191    bernoulli_Y = np.sum(bernoulli_data)192    bernoulli_p_hat = bernoulli_Y / bernoulli_n193 194    # create visualization195    bernoulli_fig, (bernoulli_ax1, bernoulli_ax2) = plt.subplots(1, 2, figsize=(12, 5))196 197    # plot data histogram198    bernoulli_ax1.hist(bernoulli_data, bins=[-0.5, 0.5, 1.5], rwidth=0.8, color='lightblue')199    bernoulli_ax1.set_xticks([0, 1])200    bernoulli_ax1.set_xticklabels(['Failure (0)', 'Success (1)'])201    bernoulli_ax1.set_title(f'Bernoulli Data: {bernoulli_n} samples')202    bernoulli_ax1.set_ylabel('Count')203    bernoulli_y_counts = [bernoulli_n - bernoulli_Y, bernoulli_Y]204    for bernoulli_idx, bernoulli_count in enumerate(bernoulli_y_counts):205        bernoulli_ax1.text(bernoulli_idx, bernoulli_count/2, f"{bernoulli_count}", 206                 ha='center', va='center', 207                 color='white' if bernoulli_idx == 0 else 'black', 208                 fontweight='bold')209 210    # calculate log-likelihood function211    bernoulli_p_values = np.linspace(0.01, 0.99, 100)212    bernoulli_ll_values = np.zeros_like(bernoulli_p_values)213 214    for bernoulli_i, bernoulli_p in enumerate(bernoulli_p_values):215        bernoulli_ll_values[bernoulli_i] = bernoulli_Y * np.log(bernoulli_p) + (bernoulli_n - bernoulli_Y) * np.log(1 - bernoulli_p)216 217    # plot log-likelihood218    bernoulli_ax2.plot(bernoulli_p_values, bernoulli_ll_values, 'b-', linewidth=2)219    bernoulli_ax2.axvline(x=bernoulli_p_hat, color='r', linestyle='--', label=f'MLE: $\\hat{{p}} = {bernoulli_p_hat:.3f}$')220    bernoulli_ax2.axvline(x=bernoulli_true_p, color='g', linestyle='--', label=f'True: $p = {bernoulli_true_p:.3f}$')221    bernoulli_ax2.set_xlabel('$p$ (probability of success)')222    bernoulli_ax2.set_ylabel('Log-Likelihood')223    bernoulli_ax2.set_title('Log-Likelihood Function')224    bernoulli_ax2.legend()225 226    plt.tight_layout()227    plt.gca()228 229    # Create markdown to explain the results230    bernoulli_explanation = mo.md(231        f"""232        ### Bernoulli MLE Results233 234        **True parameter**: $p = {bernoulli_true_p:.3f}$  235        **Sample statistics**: {bernoulli_Y} successes out of {bernoulli_n} trials  236        **MLE estimate**: $\\hat{{p}} = \\frac{{{bernoulli_Y}}}{{{bernoulli_n}}} = {bernoulli_p_hat:.3f}$237 238        The plot on the right shows the log-likelihood function $LL(p) = Y\\log(p) + (n-Y)\\log(1-p)$. 239        The red dashed line marks the maximum likelihood estimate $\\hat{{p}}$, and the green dashed line 240        shows the true parameter value.241 242        /// note243        Try increasing the sample size to see how the MLE estimate gets closer to the true parameter value!244        ///245        """246    )247 248    # Display plot and explanation together249    mo.vstack([250        bernoulli_fig,251        bernoulli_explanation252    ])253    return254 255 256@app.cell(hide_code=True)257def _(mo):258    mo.md(r"""259    ## MLE for Normal Distribution260 261    Next, let's look at a more complex example: estimating the parameters $\mu$ and $\sigma^2$ of a Normal distribution.262 263    ### The Model264 265    A Normal (Gaussian) distribution has two parameters:266    - $\mu$: the mean267    - $\sigma^2$: the variance268 269    Its probability density function (PDF) is:270 271    $$f(x|\mu, \sigma^2) = \frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{(x - \mu)^2}{2\sigma^2}\right)$$272 273    ### Deriving the MLE274 275    Given $n$ independent samples $X_1, X_2, \ldots, X_n$ from a Normal distribution, we want to find the values of $\mu$ and $\sigma^2$ that maximize the likelihood of our observed data.276 277    Step 1: Write the likelihood function278    $$L(\mu, \sigma^2) = \prod_{i=1}^n \frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{(x_i - \mu)^2}{2\sigma^2}\right)$$279 280    Step 2: Take the logarithm to get the log-likelihood281    $$\begin{align*}282    LL(\mu, \sigma^2) &= \log\prod_{i=1}^n \frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{(x_i - \mu)^2}{2\sigma^2}\right) \\283    &= \sum_{i=1}^n \log\left[\frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{(x_i - \mu)^2}{2\sigma^2}\right)\right] \\284    &= \sum_{i=1}^n \left[-\frac{1}{2}\log(2\pi\sigma^2) - \frac{(x_i - \mu)^2}{2\sigma^2}\right] \\285    &= -\frac{n}{2}\log(2\pi\sigma^2) - \frac{1}{2\sigma^2}\sum_{i=1}^n (x_i - \mu)^2286    \end{align*}$$287 288    Step 3: Find the values of $\mu$ and $\sigma^2$ that maximize $LL(\mu, \sigma^2)$ by setting the partial derivatives to zero.289 290    For $\mu$:291    $$\begin{align*}292    \frac{\partial LL(\mu, \sigma^2)}{\partial \mu} &= \frac{1}{\sigma^2}\sum_{i=1}^n (x_i - \mu) = 0 \\293    \sum_{i=1}^n (x_i - \mu) &= 0 \\294    \sum_{i=1}^n x_i &= n\mu \\295    \hat{\mu} &= \frac{1}{n}\sum_{i=1}^n x_i296    \end{align*}$$297 298    For $\sigma^2$:299    $$\begin{align*}300    \frac{\partial LL(\mu, \sigma^2)}{\partial \sigma^2} &= -\frac{n}{2\sigma^2} + \frac{1}{2(\sigma^2)^2}\sum_{i=1}^n (x_i - \mu)^2 = 0 \\301    \frac{n}{2\sigma^2} &= \frac{1}{2(\sigma^2)^2}\sum_{i=1}^n (x_i - \mu)^2 \\302    n\sigma^2 &= \sum_{i=1}^n (x_i - \mu)^2 \\303    \hat{\sigma}^2 &= \frac{1}{n}\sum_{i=1}^n (x_i - \hat{\mu})^2304    \end{align*}$$305 306    /// tip307    The MLE for a Normal distribution gives us:308 309    - $\hat{\mu}$ = sample mean310    - $\hat{\sigma}^2$ = sample variance (using $n$ in the denominator, not $n-1$)311    ///312    """)313    return314 315 316@app.cell(hide_code=True)317def _(normal_controls):318    normal_controls.center()319    return320 321 322@app.cell(hide_code=True)323def _(324    mo,325    normal_generate_button,326    normal_sample_size_slider,327    np,328    plt,329    true_mu_slider,330    true_sigma_slider,331):332    # generate normal samples when button is clicked333    normal_button_value = normal_generate_button.value334 335    # get parameter values336    normal_true_mu = true_mu_slider.value337    normal_true_sigma = true_sigma_slider.value338    normal_true_var = normal_true_sigma**2339    normal_n = normal_sample_size_slider.value340 341    # generate random data342    normal_data = np.random.normal(normal_true_mu, normal_true_sigma, size=normal_n)343 344    # calculate mle estimates345    normal_mu_hat = np.mean(normal_data)346    normal_sigma2_hat = np.mean((normal_data - normal_mu_hat)**2)  # mle variance using n347    normal_sigma_hat = np.sqrt(normal_sigma2_hat)348 349    # create visualization350    normal_fig, (normal_ax1, normal_ax2) = plt.subplots(1, 2, figsize=(12, 5))351 352    # plot histogram and density curves353    normal_bins = np.linspace(min(normal_data) - 1, max(normal_data) + 1, 30)354    normal_ax1.hist(normal_data, bins=normal_bins, density=True, alpha=0.6, color='lightblue', label='Data Histogram')355 356    # plot range for density curves357    normal_x = np.linspace(min(normal_data) - 2*normal_true_sigma, max(normal_data) + 2*normal_true_sigma, 1000)358 359    # plot true and mle densities360    normal_true_pdf = (1/(normal_true_sigma * np.sqrt(2*np.pi))) * np.exp(-0.5 * ((normal_x - normal_true_mu)/normal_true_sigma)**2)361    normal_ax1.plot(normal_x, normal_true_pdf, 'g-', linewidth=2, label=f'True: N({normal_true_mu:.2f}, {normal_true_var:.2f})')362 363    normal_mle_pdf = (1/(normal_sigma_hat * np.sqrt(2*np.pi))) * np.exp(-0.5 * ((normal_x - normal_mu_hat)/normal_sigma_hat)**2)364    normal_ax1.plot(normal_x, normal_mle_pdf, 'r--', linewidth=2, label=f'MLE: N({normal_mu_hat:.2f}, {normal_sigma2_hat:.2f})')365 366    normal_ax1.set_xlabel('x')367    normal_ax1.set_ylabel('Density')368    normal_ax1.set_title(f'Normal Distribution: {normal_n} samples')369    normal_ax1.legend()370 371    # create contour plot of log-likelihood372    normal_mu_range = np.linspace(normal_mu_hat - 2, normal_mu_hat + 2, 100)373    normal_sigma_range = np.linspace(max(0.1, normal_sigma_hat - 1), normal_sigma_hat + 1, 100)374 375    normal_mu_grid, normal_sigma_grid = np.meshgrid(normal_mu_range, normal_sigma_range)376    normal_ll_grid = np.zeros_like(normal_mu_grid)377 378    # calculate log-likelihood for each grid point379    for normal_i in range(normal_mu_grid.shape[0]):380        for normal_j in range(normal_mu_grid.shape[1]):381            normal_mu = normal_mu_grid[normal_i, normal_j]382            normal_sigma = normal_sigma_grid[normal_i, normal_j]383            normal_ll = -normal_n/2 * np.log(2*np.pi*normal_sigma**2) - np.sum((normal_data - normal_mu)**2)/(2*normal_sigma**2)384            normal_ll_grid[normal_i, normal_j] = normal_ll385 386    # plot log-likelihood contour387    normal_contour = normal_ax2.contourf(normal_mu_grid, normal_sigma_grid, normal_ll_grid, levels=50, cmap='viridis')388    normal_ax2.set_xlabel('μ (mean)')389    normal_ax2.set_ylabel('σ (standard deviation)')390    normal_ax2.set_title('Log-Likelihood Contour')391 392    # mark mle and true params393    normal_ax2.plot(normal_mu_hat, normal_sigma_hat, 'rx', markersize=10, label='MLE Estimate')394    normal_ax2.plot(normal_true_mu, normal_true_sigma, 'g*', markersize=10, label='True Parameters')395    normal_ax2.legend()396 397    plt.colorbar(normal_contour, ax=normal_ax2, label='Log-Likelihood')398    plt.tight_layout()399    plt.gca()400 401    # relevant markdown for the results402    normal_explanation = mo.md(403        rf"""404        ### Normal MLE Results405 406        **True parameters**: $\mu = {normal_true_mu:.3f}$, $\sigma^2 = {normal_true_var:.3f}$  407        **MLE estimates**: $\hat{{\mu}} = {normal_mu_hat:.3f}$, $\hat{{\sigma}}^2 = {normal_sigma2_hat:.3f}$408 409        The left plot shows the data histogram with the true Normal distribution (green) and the MLE-estimated distribution (red dashed).410 411        The right plot shows the log-likelihood function as a contour map in the $(\mu, \sigma)$ parameter space. The maximum likelihood estimates are marked with a red X, while the true parameters are marked with a green star.412 413        /// note414        Notice how the log-likelihood contour is more stretched along the σ axis than the μ axis. This indicates that we typically estimate the mean with greater precision than the standard deviation.415        ///416 417        /// tip418        Increase the sample size to see how the MLE estimates converge to the true parameter values!419        ///420        """421    )422 423    # plot and explanation together424    mo.vstack([425        normal_fig,426        normal_explanation427    ])428    return429 430 431@app.cell(hide_code=True)432def _(mo):433    mo.md(r"""434    ## MLE for Linear Regression435 436    Now let's look at a more practical example: using MLE to derive linear regression.437 438    ### The Model439 440    Consider a model where:441    - We have pairs of observations $(X_1, Y_1), (X_2, Y_2), \ldots, (X_n, Y_n)$442    - The relationship between $X$ and $Y$ follows: $Y = \theta X + Z$443    - $Z \sim N(0, \sigma^2)$ is random noise444    - Our goal is to estimate the parameter $\theta$445 446    This means that for a given $X_i$, the conditional distribution of $Y_i$ is:447 448    $$Y_i | X_i \sim N(\theta X_i, \sigma^2)$$449 450    ### Deriving the MLE451 452    Step 1: Write the likelihood function for each data point $(X_i, Y_i)$453    $$f(Y_i | X_i, \theta) = \frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{(Y_i - \theta X_i)^2}{2\sigma^2}\right)$$454 455    Step 2: Write the likelihood for all data456    $$\begin{align*}457    L(\theta) &= \prod_{i=1}^n f(Y_i, X_i | \theta) \\458    &= \prod_{i=1}^n f(Y_i | X_i, \theta) \cdot f(X_i)459    \end{align*}$$460 461    Since $f(X_i)$ doesn't depend on $\theta$, we can simplify:462    $$L(\theta) = \prod_{i=1}^n \frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{(Y_i - \theta X_i)^2}{2\sigma^2}\right) \cdot f(X_i)$$463 464    Step 3: Take the logarithm to get the log-likelihood465    $$\begin{align*}466    LL(\theta) &= \log \prod_{i=1}^n \frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{(Y_i - \theta X_i)^2}{2\sigma^2}\right) \cdot f(X_i) \\467    &= \sum_{i=1}^n \log\left[\frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{(Y_i - \theta X_i)^2}{2\sigma^2}\right)\right] + \sum_{i=1}^n \log f(X_i) \\468    &= -\frac{n}{2} \log(2\pi\sigma^2) - \frac{1}{2\sigma^2} \sum_{i=1}^n (Y_i - \theta X_i)^2 + \sum_{i=1}^n \log f(X_i)469    \end{align*}$$470 471    Step 4: Since we only care about maximizing with respect to $\theta$, we can drop terms that don't contain $\theta$:472    $$\hat{\theta} = \underset{\theta}{\operatorname{argmax}} \left[ -\frac{1}{2\sigma^2} \sum_{i=1}^n (Y_i - \theta X_i)^2 \right]$$473 474    This is equivalent to:475    $$\hat{\theta} = \underset{\theta}{\operatorname{argmin}} \sum_{i=1}^n (Y_i - \theta X_i)^2$$476 477    Step 5: Find the value of $\theta$ that minimizes the sum of squared errors by setting the derivative to zero:478    $$\begin{align*}479    \frac{d}{d\theta} \sum_{i=1}^n (Y_i - \theta X_i)^2 &= 0 \\480    \sum_{i=1}^n -2X_i(Y_i - \theta X_i) &= 0 \\481    \sum_{i=1}^n X_i Y_i - \theta X_i^2 &= 0 \\482    \sum_{i=1}^n X_i Y_i &= \theta \sum_{i=1}^n X_i^2 \\483    \hat{\theta} &= \frac{\sum_{i=1}^n X_i Y_i}{\sum_{i=1}^n X_i^2}484    \end{align*}$$485 486    /// tip487    **Key Insight**: MLE for this simple linear model gives us the least squares estimator! This is an important connection between MLE and regression.488    ///489    """)490    return491 492 493@app.cell(hide_code=True)494def _(linear_controls):495    linear_controls.center()496    return497 498 499@app.cell(hide_code=True)500def _(501    linear_generate_button,502    linear_sample_size_slider,503    mo,504    noise_sigma_slider,505    np,506    plt,507    true_theta_slider,508):509    # linear model data calc when button is clicked510    linear_button_value = linear_generate_button.value511 512    # get parameter values513    linear_true_theta = true_theta_slider.value514    linear_noise_sigma = noise_sigma_slider.value515    linear_n = linear_sample_size_slider.value516 517    # generate x data (uniformly between -3 and 3)518    linear_X = np.random.uniform(-3, 3, size=linear_n)519 520    # generate y data according to the model y = θx + z521    linear_Z = np.random.normal(0, linear_noise_sigma, size=linear_n)522    linear_Y = linear_true_theta * linear_X + linear_Z523 524    # calculate mle estimate525    linear_theta_hat = np.sum(linear_X * linear_Y) / np.sum(linear_X**2)526 527    # calculate sse for different theta values528    linear_theta_range = np.linspace(linear_true_theta - 1.5, linear_true_theta + 1.5, 100)529    linear_sse_values = np.zeros_like(linear_theta_range)530 531    for linear_i, linear_theta in enumerate(linear_theta_range):532        linear_y_pred = linear_theta * linear_X533        linear_sse_values[linear_i] = np.sum((linear_Y - linear_y_pred)**2)534 535    # convert sse to log-likelihood (ignoring constant terms)536    linear_ll_values = -linear_sse_values / (2 * linear_noise_sigma**2)537 538    # create visualization539    linear_fig, (linear_ax1, linear_ax2) = plt.subplots(1, 2, figsize=(12, 5))540 541    # plot scatter plot with regression lines542    linear_ax1.scatter(linear_X, linear_Y, color='blue', alpha=0.6, label='Data points')543 544    # plot range for regression lines545    linear_x_line = np.linspace(-3, 3, 100)546 547    # plot true and mle regression lines548    linear_ax1.plot(linear_x_line, linear_true_theta * linear_x_line, 'g-', linewidth=2, label=f'True: Y = {linear_true_theta:.2f}X')549    linear_ax1.plot(linear_x_line, linear_theta_hat * linear_x_line, 'r--', linewidth=2, label=f'MLE: Y = {linear_theta_hat:.2f}X')550 551    linear_ax1.set_xlabel('X')552    linear_ax1.set_ylabel('Y')553    linear_ax1.set_title(f'Linear Regression: {linear_n} data points')554    linear_ax1.grid(True, alpha=0.3)555    linear_ax1.legend()556 557    # plot log-likelihood function558    linear_ax2.plot(linear_theta_range, linear_ll_values, 'b-', linewidth=2)559    linear_ax2.axvline(x=linear_theta_hat, color='r', linestyle='--', label=f'MLE: θ = {linear_theta_hat:.3f}')560    linear_ax2.axvline(x=linear_true_theta, color='g', linestyle='--', label=f'True: θ = {linear_true_theta:.3f}')561    linear_ax2.set_xlabel('θ (slope parameter)')562    linear_ax2.set_ylabel('Log-Likelihood')563    linear_ax2.set_title('Log-Likelihood Function')564    linear_ax2.grid(True, alpha=0.3)565    linear_ax2.legend()566 567    plt.tight_layout()568    plt.gca()569 570    # relevant markdown to explain results571    linear_explanation = mo.md(572        f"""573        ### Linear Regression MLE Results574 575        **True parameter**: $\\theta = {linear_true_theta:.3f}$  576        **MLE estimate**: $\\hat{{\\theta}} = {linear_theta_hat:.3f}$577 578        The left plot shows the scatter plot of data points with the true regression line (green) and the MLE-estimated regression line (red dashed).579 580        The right plot shows the log-likelihood function for different values of $\\theta$. The maximum likelihood estimate is marked with a red dashed line, and the true parameter is marked with a green dashed line.581 582        /// note583        The MLE estimate $\\hat{{\\theta}} = \\frac{{\\sum_{{i=1}}^n X_i Y_i}}{{\\sum_{{i=1}}^n X_i^2}}$ minimizes the sum of squared errors between the predicted and actual Y values.584        ///585 586        /// tip587        Try increasing the noise level to see how it affects the precision of the estimate!588        ///589        """590    )591 592    # show plot and explanation593    mo.vstack([594        linear_fig,595        linear_explanation596    ])597    return598 599 600@app.cell(hide_code=True)601def _(mo):602    mo.md(r"""603    ## Interactive Concept: Density/Mass Functions vs. Likelihood604 605    To better understand the distinction between likelihood and density/mass functions, let's create an interactive visualization. This concept is crucial for understanding why MLE works.606    """)607    return608 609 610@app.cell(hide_code=True)611def _(concept_controls):612    concept_controls.center()613    return614 615 616@app.cell(hide_code=True)617def _(concept_dist_type, mo, np, perspective_selector, plt, stats):618    # current distribution type619    concept_dist_type_value = concept_dist_type.value620 621    # view mode from dropdown622    concept_view_mode = "likelihood" if perspective_selector.value == "Likelihood Perspective" else "probability"623 624    # visualization based on distribution type625    concept_fig, concept_ax = plt.subplots(figsize=(10, 6))626 627    if concept_dist_type_value == "Normal":628        if concept_view_mode == "probability":629            # density function perspective: fixed params, varying data630            concept_mu = 0      # fixed parameter631            concept_sigma = 1   # fixed parameter632 633            # generate x values for the pdf634            concept_x = np.linspace(-4, 4, 1000)635 636            # plot pdf637            concept_pdf = stats.norm.pdf(concept_x, concept_mu, concept_sigma)638            concept_ax.plot(concept_x, concept_pdf, 'b-', linewidth=2, label='PDF: N(0, 1)')639 640            # highlight specific data values641            concept_data_points = [-2, -1, 0, 1, 2]642            concept_colors = ['#FF9999', '#FFCC99', '#99FF99', '#99CCFF', '#CC99FF']643 644            for concept_i, concept_data in enumerate(concept_data_points):645                concept_prob = stats.norm.pdf(concept_data, concept_mu, concept_sigma)646                concept_ax.plot([concept_data, concept_data], [0, concept_prob], concept_colors[concept_i], linewidth=2)647                concept_ax.scatter(concept_data, concept_prob, color=concept_colors[concept_i], s=50, 648                           label=f'PDF at x={concept_data}: {concept_prob:.3f}')649 650            concept_ax.set_xlabel('Data (x)')651            concept_ax.set_ylabel('Probability Density')652            concept_ax.set_title('Density Function Perspective: Fixed Parameters (μ=0, σ=1), Different Data Points')653 654        else:  # likelihood perspective655            # likelihood perspective: fixed data, varying parameters656            concept_data_point = 1.5  # fixed observed data657 658            # different possible parameter values (means)659            concept_mus = [-1, 0, 1, 2, 3]660            concept_sigma = 1661 662            # generate x values for multiple pdfs663            concept_x = np.linspace(-4, 6, 1000)664 665            concept_colors = ['#FF9999', '#FFCC99', '#99FF99', '#99CCFF', '#CC99FF']666 667            for concept_i, concept_mu in enumerate(concept_mus):668                concept_pdf = stats.norm.pdf(concept_x, concept_mu, concept_sigma)669                concept_ax.plot(concept_x, concept_pdf, color=concept_colors[concept_i], linewidth=2, alpha=0.7,670                        label=f'N({concept_mu}, 1)')671 672                # mark the likelihood of the data point for this param673                concept_likelihood = stats.norm.pdf(concept_data_point, concept_mu, concept_sigma)674                concept_ax.plot([concept_data_point, concept_data_point], [0, concept_likelihood], concept_colors[concept_i], linewidth=2)675                concept_ax.scatter(concept_data_point, concept_likelihood, color=concept_colors[concept_i], s=50, 676                           label=f'L(μ={concept_mu}|X=1.5) = {concept_likelihood:.3f}')677 678            # add vertical line at the observed data point679            concept_ax.axvline(x=concept_data_point, color='black', linestyle='--', 680                       label=f'Observed data: X=1.5')681 682            concept_ax.set_xlabel('Data (x)')683            concept_ax.set_ylabel('Probability Density / Likelihood')684            concept_ax.set_title('Likelihood Perspective: Fixed Data Point (X=1.5), Different Parameter Values')685 686    elif concept_dist_type_value == "Bernoulli":687        if concept_view_mode == "probability":688            # probability perspective: fixed parameter, two possible data values689            concept_p = 0.3  # fixed parameter690 691            # bar chart for p(x=0) and p(x=1)692            concept_ax.bar([0, 1], [1-concept_p, concept_p], width=0.4, color=['#99CCFF', '#FF9999'], 693                   alpha=0.7, label=f'PMF: Bernoulli({concept_p})')694 695            # text showing probabilities696            concept_ax.text(0, (1-concept_p)/2, f'P(X=0|p={concept_p}) = {1-concept_p:.3f}', ha='center', va='center', fontweight='bold')697            concept_ax.text(1, concept_p/2, f'P(X=1|p={concept_p}) = {concept_p:.3f}', ha='center', va='center', fontweight='bold')698 699            concept_ax.set_xlabel('Data (x)')700            concept_ax.set_ylabel('Probability')701            concept_ax.set_xticks([0, 1])702            concept_ax.set_xticklabels(['X=0', 'X=1'])703            concept_ax.set_ylim(0, 1)704            concept_ax.set_title('Probability Perspective: Fixed Parameter (p=0.3), Different Data Values')705 706        else:  # likelihood perspective707            # likelihood perspective: fixed data, varying parameter708            concept_data_point = 1  # fixed observed data (success)709 710            # different possible parameter values711            concept_p_values = np.linspace(0.01, 0.99, 100)712 713            # calculate likelihood for each p value714            if concept_data_point == 1:715                # for x=1, likelihood is p716                concept_likelihood = concept_p_values717                concept_ax.plot(concept_p_values, concept_likelihood, 'b-', linewidth=2, 718                        label=f'L(p|X=1) = p')719 720                # highlight specific values721                concept_highlight_ps = [0.2, 0.5, 0.8]722                concept_colors = ['#FF9999', '#99FF99', '#99CCFF']723 724                for concept_i, concept_p in enumerate(concept_highlight_ps):725                    concept_ax.plot([concept_p, concept_p], [0, concept_p], concept_colors[concept_i], linewidth=2)726                    concept_ax.scatter(concept_p, concept_p, color=concept_colors[concept_i], s=50, 727                               label=f'L(p={concept_p}|X=1) = {concept_p:.3f}')728 729                concept_ax.set_title('Likelihood Perspective: Fixed Data Point (X=1), Different Parameter Values')730 731            else:  # x=0732                # for x = 0, likelihood is (1-p)733                concept_likelihood = 1 - concept_p_values734                concept_ax.plot(concept_p_values, concept_likelihood, 'r-', linewidth=2, 735                        label=f'L(p|X=0) = (1-p)')736 737                # highlight some specific values738                concept_highlight_ps = [0.2, 0.5, 0.8]739                concept_colors = ['#FF9999', '#99FF99', '#99CCFF']740 741                for concept_i, concept_p in enumerate(concept_highlight_ps):742                    concept_ax.plot([concept_p, concept_p], [0, 1-concept_p], concept_colors[concept_i], linewidth=2)743                    concept_ax.scatter(concept_p, 1-concept_p, color=concept_colors[concept_i], s=50, 744                               label=f'L(p={concept_p}|X=0) = {1-concept_p:.3f}')745 746                concept_ax.set_title('Likelihood Perspective: Fixed Data Point (X=0), Different Parameter Values')747 748            concept_ax.set_xlabel('Parameter (p)')749            concept_ax.set_ylabel('Likelihood')750            concept_ax.set_xlim(0, 1)751            concept_ax.set_ylim(0, 1)752 753    elif concept_dist_type_value == "Poisson":754        if concept_view_mode == "probability":755            # probability perspective: fixed parameter, different data values756            concept_lam = 2.5  # fixed parameter757 758            # pmf for different x values plot759            concept_x_values = np.arange(0, 10)760            concept_pmf_values = stats.poisson.pmf(concept_x_values, concept_lam)761 762            concept_ax.bar(concept_x_values, concept_pmf_values, width=0.4, color='#99CCFF', 763                   alpha=0.7, label=f'PMF: Poisson({concept_lam})')764 765            # highlight a few specific values766            concept_highlight_xs = [1, 2, 3, 4]767            concept_colors = ['#FF9999', '#99FF99', '#FFCC99', '#CC99FF']768 769            for concept_i, concept_x in enumerate(concept_highlight_xs):770                concept_prob = stats.poisson.pmf(concept_x, concept_lam)771                concept_ax.scatter(concept_x, concept_prob, color=concept_colors[concept_i], s=50, 772                           label=f'P(X={concept_x}|λ={concept_lam}) = {concept_prob:.3f}')773 774            concept_ax.set_xlabel('Data (x)')775            concept_ax.set_ylabel('Probability')776            concept_ax.set_xticks(concept_x_values)777            concept_ax.set_title('Probability Perspective: Fixed Parameter (λ=2.5), Different Data Values')778 779        else:  # likelihood perspective780            # likelihood perspective: fixed data, varying parameter781            concept_data_point = 4  # fixed observed data782 783            # different possible param values784            concept_lambda_values = np.linspace(0.1, 8, 100)785 786            # calc likelihood for each lambda value787            concept_likelihood = stats.poisson.pmf(concept_data_point, concept_lambda_values)788 789            concept_ax.plot(concept_lambda_values, concept_likelihood, 'b-', linewidth=2, 790                    label=f'L(λ|X={concept_data_point})')791 792            # highlight some specific values793            concept_highlight_lambdas = [1, 2, 4, 6]794            concept_colors = ['#FF9999', '#99FF99', '#99CCFF', '#FFCC99']795 796            for concept_i, concept_lam in enumerate(concept_highlight_lambdas):797                concept_like_val = stats.poisson.pmf(concept_data_point, concept_lam)798                concept_ax.plot([concept_lam, concept_lam], [0, concept_like_val], concept_colors[concept_i], linewidth=2)799                concept_ax.scatter(concept_lam, concept_like_val, color=concept_colors[concept_i], s=50, 800                           label=f'L(λ={concept_lam}|X={concept_data_point}) = {concept_like_val:.3f}')801 802            concept_ax.set_xlabel('Parameter (λ)')803            concept_ax.set_ylabel('Likelihood')804            concept_ax.set_title(f'Likelihood Perspective: Fixed Data Point (X={concept_data_point}), Different Parameter Values')805 806    concept_ax.legend(loc='best', fontsize=9)807    concept_ax.grid(True, alpha=0.3)808    plt.tight_layout()809    plt.gca()810 811    # relevant explanation based on view mode812    if concept_view_mode == "probability":813        concept_explanation = mo.md(814            f"""815            ### Density/Mass Function Perspective816 817            In the **density/mass function perspective**, the parameters of the distribution are **fixed and known**, and we evaluate the function at **different possible data values**.818 819            For the {concept_dist_type_value} distribution, we've fixed the parameter{'s' if concept_dist_type_value == 'Normal' else ''} and shown the {'density' if concept_dist_type_value == 'Normal' else 'probability mass'} function evaluated at different data points.820 821            This is the typical perspective when:822 823            - We know the true parameters of a distribution824            - We want to evaluate the {'density' if concept_dist_type_value == 'Normal' else 'probability mass'} at different observations825            - We make predictions based on our model826 827            **Mathematical notation**: $f(x | \theta)$828            """829        )830    else:  # likelihood perspective831        concept_explanation = mo.md(832            f"""833            ### Likelihood Perspective834 835            In the **likelihood perspective**, the observed data is **fixed and known**, and we calculate how likely different parameter values are to have generated that data.836 837            For the {concept_dist_type_value} distribution, we've fixed the observed data point{'s' if concept_dist_type_value == 'Normal' else ''} and shown the likelihood of different parameter values.838 839            This is the perspective used in MLE:840 841            - We have observed data842            - We don't know the true parameters843            - We want to find parameters that best explain our observations844 845            **Mathematical notation**: $L(\theta | X = x)$846 847            /// tip848            The value of $\\theta$ that maximizes this likelihood function is the MLE estimate $\\hat{{\\theta}}$!849            ///850            """851        )852 853    # Display plot and explanation together854    mo.vstack([855        concept_fig,856        concept_explanation857    ])858    return859 860 861@app.cell(hide_code=True)862def _(mo):863    mo.md(r"""864    ## 🤔 Test Your Understanding865 866    Which of the following statements about Maximum Likelihood Estimation are correct? Click each statement to check your answer.867 868    /// details | Probability and likelihood have different interpretations: probability measures the chance of data given parameters, while likelihood measures how likely parameters are given data.869    ✅ **Correct!**870 871    Probability measures how likely it is to observe particular data when we know the parameters. Likelihood measures how likely particular parameter values are, given observed data.872 873    Mathematically, probability is $P(X=x|\theta)$ while likelihood is $L(\theta|X=x)$.874    ///875 876    /// details | We use log-likelihood instead of likelihood because it's mathematically simpler and numerically more stable.877    ✅ **Correct!**878 879    We work with log-likelihood for several reasons:880    1. It converts products into sums, which is easier to work with mathematically881    2. It avoids numerical underflow when multiplying many small probabilities882    3. Logarithm is a monotonically increasing function, so the maximum of the likelihood occurs at the same parameter values as the maximum of the log-likelihood883    ///884 885    /// details | For a Bernoulli distribution, the MLE for parameter p is the sample mean of the observations.886    ✅ **Correct!**887 888    For a Bernoulli distribution with parameter $p$, given $n$ independent samples $X_1, X_2, \ldots, X_n$, the MLE estimator is:889 890    $$\hat{p} = \frac{\sum_{i=1}^n X_i}{n}$$891 892    This is simply the sample mean, or the proportion of successes (1s) in the data.893    ///894 895    /// details | For a Normal distribution, MLE gives unbiased estimates for both mean and variance parameters.896    ❌ **Incorrect.**897 898    While the MLE for the mean ($\hat{\mu} = \frac{1}{n}\sum_{i=1}^n X_i$) is unbiased, the MLE for variance:899 900    $$\hat{\sigma}^2 = \frac{1}{n}\sum_{i=1}^n (X_i - \hat{\mu})^2$$901 902    is a biased estimator. It uses $n$ in the denominator rather than $n-1$ used in the unbiased estimator.903    ///904 905    /// details | MLE estimators are always unbiased regardless of the distribution.906    ❌ **Incorrect.**907 908    MLE is not always unbiased, though it often is asymptotically unbiased (meaning the bias approaches zero as the sample size increases).909 910    A notable example is the MLE estimator for the variance of a Normal distribution:911    $$\hat{\sigma}^2 = \frac{1}{n}\sum_{i=1}^n (X_i - \hat{\mu})^2$$912 913    This estimator is biased, which is why we often use the unbiased estimator:914    $$s^2 = \frac{1}{n-1}\sum_{i=1}^n (X_i - \hat{\mu})^2$$915 916    Despite occasional bias, MLE estimators have many desirable properties, including consistency and asymptotic efficiency.917    ///918    """)919    return920 921 922@app.cell(hide_code=True)923def _(mo):924    mo.md(r"""925    ## Summary926 927    Maximum Likelihood Estimation really is one of those elegant ideas that sits at the core of modern statistics. When you get down to it, MLE is just about finding the most plausible explanation for the data we've observed. It's like being a detective - you have some clues (your data), and you're trying to piece together the most likely story (your parameters) that explains them.928 929    We've seen how this works with different distributions. For the Bernoulli, it simply gives us the sample proportion. For the Normal, it gives us the sample mean and a slightly biased estimate of variance. And for linear regression, it provides a mathematical justification for the least squares method that everyone learns in basic stats classes.930 931    What makes MLE so useful in practice is that it tends to give us estimates with good properties. As you collect more data, the estimates generally get closer to the true values (consistency) and do so efficiently. That's why MLE is everywhere in statistics and machine learning - from simple regression models to complex neural networks.932 933    The most important takeaway? Next time you're fitting a model to data, remember that you're not just following a recipe - you're finding the parameters that make your observed data most likely to have occurred. That's the essence of statistical inference.934    """)935    return936 937 938@app.cell(hide_code=True)939def _(mo):940    mo.md(r"""941    ## Further Reading942 943    If you're curious to dive deeper into this topic, check out "Statistical Inference" by Casella and Berger - it's the classic text that many statisticians learned from. For a more machine learning angle, Bishop's "Pattern Recognition and Machine Learning" shows how MLE connects to more advanced topics like EM algorithms and Bayesian methods.944 945    Beyond the basics we've covered, you might explore Bayesian estimation (which incorporates prior knowledge), Fisher Information (which tells us how precisely we can estimate parameters), or the EM algorithm (for when we have missing data or latent variables). Each of these builds on the foundation of likelihood that we've established here.946    """)947    return948 949 950@app.cell(hide_code=True)951def _(mo):952    mo.md(r"""953    ## Appendix (helper functions and imports)954    """)955    return956 957 958@app.cell959def _():960    import marimo as mo961    return (mo,)962 963 964@app.cell965def _():966    import numpy as np967    import matplotlib.pyplot as plt968    from scipy import stats969    import plotly.graph_objects as go970    import polars as pl971    from matplotlib import cm972 973    # Set a consistent random seed for reproducibility974    np.random.seed(42)975 976    # Set a nice style for matplotlib977    plt.style.use('seaborn-v0_8-darkgrid')978    return np, plt, stats979 980 981@app.cell(hide_code=True)982def _(mo):983    # Create interactive elements984    true_p_slider = mo.ui.slider(985        start =0.01, 986        stop =0.99, 987        value=0.3, 988        step=0.01, 989        label="True probability (p)"990    )991 992    sample_size_slider = mo.ui.slider(993        start =10, 994        stop =1000, 995        value=100, 996        step=10, 997        label="Sample size (n)"998    )999 1000    generate_button = mo.ui.button(label="Generate New Sample", kind="success")1001 1002    controls = mo.vstack([1003        mo.vstack([true_p_slider, sample_size_slider]), 1004        generate_button1005    ], justify="space-between")1006    return controls, generate_button, sample_size_slider, true_p_slider1007 1008 1009@app.cell(hide_code=True)1010def _(mo):1011    # Create interactive elements for Normal distribution1012    true_mu_slider = mo.ui.slider(1013        start =-5, 1014        stop =5, 1015        value=0, 1016        step=0.1, 1017        label="True mean (μ)"1018    )1019 1020    true_sigma_slider = mo.ui.slider(1021        start =0.5, 1022        stop =3, 1023        value=1, 1024        step=0.1, 1025        label="True standard deviation (σ)"1026    )1027 1028    normal_sample_size_slider = mo.ui.slider(1029        start =10, 1030        stop =500, 1031        value=50, 1032        step=10, 1033        label="Sample size (n)"1034    )1035 1036    normal_generate_button = mo.ui.button(label="Generate New Sample", kind="warn")1037 1038    normal_controls = mo.hstack([1039        mo.vstack([true_mu_slider, true_sigma_slider, normal_sample_size_slider]), 1040        normal_generate_button1041    ], justify="space-between")1042    return (1043        normal_controls,1044        normal_generate_button,1045        normal_sample_size_slider,1046        true_mu_slider,1047        true_sigma_slider,1048    )1049 1050 1051@app.cell(hide_code=True)1052def _(mo):1053    # Create interactive elements for linear regression1054    true_theta_slider = mo.ui.slider(1055        start =-2, 1056        stop =2, 1057        value=0.5, 1058        step=0.1, 1059        label="True slope (θ)"1060    )1061 1062    noise_sigma_slider = mo.ui.slider(1063        start =0.1, 1064        stop =2, 1065        value=0.5, 1066        step=0.1, 1067        label="Noise level (σ)"1068    )1069 1070    linear_sample_size_slider = mo.ui.slider(1071        start =10, 1072        stop =200, 1073        value=50, 1074        step=10, 1075        label="Sample size (n)"1076    )1077 1078    linear_generate_button = mo.ui.button(label="Generate New Sample", kind="warn")1079 1080    linear_controls = mo.hstack([1081        mo.vstack([true_theta_slider, noise_sigma_slider, linear_sample_size_slider]), 1082        linear_generate_button1083    ], justify="space-between")1084    return (1085        linear_controls,1086        linear_generate_button,1087        linear_sample_size_slider,1088        noise_sigma_slider,1089        true_theta_slider,1090    )1091 1092 1093@app.cell(hide_code=True)1094def _(mo):1095    # Interactive elements for likelihood vs probability demo1096    concept_dist_type = mo.ui.dropdown(1097        options=["Normal", "Bernoulli", "Poisson"],1098        value="Normal",1099        label="Distribution"1100    )1101 1102    # Replace buttons with a simple dropdown selector1103    perspective_selector = mo.ui.dropdown(1104        options=["Probability Perspective", "Likelihood Perspective"],1105        value="Probability Perspective",1106        label="View"1107    )1108 1109    concept_controls = mo.vstack([1110        mo.hstack([concept_dist_type, perspective_selector])1111    ])1112    return concept_controls, concept_dist_type, perspective_selector1113 1114 1115if __name__ == "__main__":1116    app.run()1117