{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "_ZCogkNcQhAB" }, "source": [ "# Gaussian Maximum Likelihood\n", "\n", "## MLE of a Gaussian $p_{model}(x|w)$\n", "\n", "You are given an array of data points called `data`. Your course site plots the negative log-likelihood function for several candidate hypotheses. Estimate the parameters of the Gaussian $p_{model}$ by coding an implementation that estimates its optimal parameters (15 points) and explaining what it does (10 points). You are free to use any Gradient-based optimization method you like. " ] }, { "cell_type": "code", "source": [ "import numpy as np\n", "import pandas as pd\n", "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", "\n", "np.random.seed(0)\n", "sns.set_theme(style='whitegrid', palette='pastel')\n", "\n", "import warnings\n", "warnings.filterwarnings('ignore')\n", "\n", "from sklearn.linear_model import LinearRegression" ], "metadata": { "id": "9tEZiMYncrvb" }, "execution_count": 73, "outputs": [] }, { "cell_type": "markdown", "source": [ "\n", "- $lnL(\\hat \\mu,\\sigma^2|Y) = {-N \\over 2} ln(2\\pi) - [\\sum_{i=1}^N{1\\over 2}ln \\sigma ^2 - {1 \\over 2 \\sigma ^2} (Y_i - \\mu)^2]$\n", "\n", "- ${\\partial ln L \\over \\partial \\mu} = {1 \\over \\sigma^2} \\sum_{i=1}^N(Y_i - \\mu)$\n", "\n", "- ${\\partial ln L \\over \\partial \\sigma ^2} = {1 \\over 2 \\sigma ^2} (-N + {1 \\over \\sigma^2} \\sum_{n=1}^N(Y_i - \\mu)^2)$" ], "metadata": { "id": "6y7UTqAlYqa8" } }, { "cell_type": "code", "execution_count": 74, "metadata": { "id": "_fbYCmRRQhAF" }, "outputs": [], "source": [ "data = [4, 5, 7, 8, 8, 9, 10, 5, 2, 3, 5, 4, 8, 9]\n", "\n", "# This function calculates the partial derivates of a negative log-likelihood function for the mean and variance\n", "def gradient(mean, var, x):\n", " N = len(x)\n", " mean_gradient = (1 / var) * np.sum(x - mean) \n", " var_gradient = (1 / (2 * var)) * (-N + (1 / var) * np.sum(np.subtract(x, mean) ** 2))\n", " return mean_gradient, var_gradient\n", " \n", "\n", "# Performs a gradient descent using data, starting params, learning rate, and number of iterations.\n", "# Each iteration changes theta partially based on the learning rate and eventually would converge towards\n", "# the true params.\n", "def gradient_descent(data, theta0, learning_rate, max_iter):\n", " mean, var = theta0\n", " x = np.array(data) # transform to numpy array for easier functions\n", " for _ in range(max_iter): \n", " g = gradient(mean, var, x)\n", " \n", " # update params with calculated partial derivatives\n", " mean = mean + learning_rate * g[0] \n", " var = var + learning_rate * g[1]\n", " return mean, var" ] }, { "cell_type": "code", "source": [ "iterations = 1000 # number of times we want to descent\n", "theta = (0, 1) # (mean, var)\n", "alpha = 0.01 # learning rate\n", "\n", "# calculate params\n", "e_mean, e_var = gradient_descent(data, theta, alpha, iterations)\n", "mean = np.mean(data) # true mean from data\n", "var = np.var(data) # true variance from data\n", "\n", "print(f\"Estimated params: mean={e_mean} variance={e_var}\")\n", "print(f\"True params: mean={mean} variance={var}\")" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "9-VFAZtKpwvM", "outputId": "77594a4f-4c7d-4201-b56a-b0fda92c93c3" }, "execution_count": 75, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Estimated params: mean=6.214285714179054 variance=5.851817293989179\n", "True params: mean=6.214285714285714 variance=5.882653061224489\n" ] } ] }, { "cell_type": "markdown", "metadata": { "id": "SqVOZaiAQhAI" }, "source": [ "## MLE of a conditional Gaussian $p_{model}(y|x,w)$\n", "\n", "You are given a problem that involves the relationship between $x$ and $y$. Estimate the parameters of a $p_{model}$ that fit the dataset (x,y) shown below. You are free to use any Gradient-based optimization method you like. \n" ] }, { "cell_type": "markdown", "source": [ "$MSE = {1 \\over n} \\sum_{i=1}^n (y_i - \\hat y_i)^2$ \n", "\n", "- $f(m, b) = {1 \\over n} \\sum_{i=1}^n (y_i - (mx_i+b))^2$\n", "\n", "${\\partial f \\over \\partial m} = {1 \\over n} \\sum_{i=1}^n -2x_i(y_i - (mx_i+b))$\n", "\n", "${\\partial f \\over \\partial b} = {1 \\over n} \\sum_{i=1}^n -2(y_i - (mx_i+b))$\n", "\n" ], "metadata": { "id": "OIdosdhMxn3D" } }, { "cell_type": "code", "execution_count": 76, "metadata": { "id": "4xoYaZCBQhAL" }, "outputs": [], "source": [ "x = np.array([8, 16, 22, 33, 50, 51])\n", "y = np.array([5, 20, 14, 32, 42, 58])\n", "\n", "# The goal here is to generate a p_model that is optimized for the following \n", "# linear regression: y = m * x + b\n", "# Because we are provided x and y, we can use gradient descent and mean-squared\n", "# error to optimize m and b. The code is attached below.\n", "# An additional implementation using sklearn's Linear Regression is also included.\n", "# y = m * x + b\n", "\n", "def conditional_gradient(x, y, m, b):\n", " n = len(x)\n", " m_gradient = -2 * np.sum(x * (y - (m * x + b))) / n\n", " b_gradient = -2 * np.sum(y - (m * x + b)) / n\n", " return m_gradient, b_gradient\n", " \n", "def conditional_gradient_descent(x, y, params, learning_rate, max_iter):\n", " m, b = params\n", " for _ in range(max_iter):\n", " g = conditional_gradient(x, y, m, b)\n", "\n", " m = m - learning_rate * g[0]\n", " b = b + learning_rate * g[1]\n", " return m, b" ] }, { "cell_type": "code", "source": [ "# The code below generates paramters from a self-implemented gradient descent\n", "# as well as through `sklearn`'s LinearRegression package. It takes the calculated\n", "# `m` and `b` and places them into the respective `estimate` and `actual` \n", "# functions\n", "\n", "params = (0, 5)\n", "iterations = 100\n", "alpha = 0.0001\n", "\n", "m, b = conditional_gradient_descent(x, y, params, alpha, iterations)\n", "\n", "def estimate(x):\n", " return m * x + b\n", "\n", "model = LinearRegression()\n", "model.fit(x.reshape(-1, 1), y)\n", "\n", "def actual(x):\n", " return model.coef_[0] * x + model.intercept_" ], "metadata": { "id": "Lu-6_UHKclJS" }, "execution_count": 79, "outputs": [] }, { "cell_type": "code", "source": [ "# The code below uses the previously calculated `estimate` and `actual` functions\n", "# to generate a graph with the original data points as well as the estimated and\n", "# actual lines.\n", "\n", "sns.scatterplot(x=x, y=y)\n", "\n", "start = min(x)\n", "end = max(x)\n", "\n", "pp1, pp2 = (start, estimate(start)), (end, estimate(end))\n", "ap1, ap2 = (start, actual(start)), (end, actual(end))\n", "\n", "plt.plot([pp1[0], pp2[0]], [pp1[1], pp2[1]], label='Estimated')\n", "plt.plot([ap1[0], ap2[0]], [ap1[1], ap2[1]], label='Actual')\n", "plt.legend(loc=\"upper left\")\n", "\n", "plt.show()" ], "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 269 }, "id": "126Kj3r-gBYL", "outputId": "4081c5e0-2d67-46c0-ae52-4f5195fb89b5" }, "execution_count": 80, "outputs": [ { "output_type": "display_data", "data": { "text/plain": [ "
" ], "image/png": "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\n" }, "metadata": {} } ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.10.9" }, "orig_nbformat": 4, "vscode": { "interpreter": { "hash": "7d6993cb2f9ce9a59d5d7380609d9cb5192a9dedd2735a011418ad9e827eb538" } }, "colab": { "provenance": [] } }, "nbformat": 4, "nbformat_minor": 0 }