{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "1115a7f0",
   "metadata": {},
   "source": [
    "# Addendum A - regresja liniowa w `tensorflow` + Lista 5"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "943901dd-1068-4d1a-a607-2ca5bc11f63c",
   "metadata": {},
   "source": [
    "Gradient descent w `tensorflow`\n",
    "```bash\n",
    "!pip install tensorflow\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4facbcc0",
   "metadata": {},
   "source": [
    "Regresja liniowa w `tensorflow`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "58c2336d",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d5c7f31d",
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9bc7fb98",
   "metadata": {},
   "outputs": [],
   "source": [
    "N = 200\n",
    "\n",
    "x = tf.linspace(-2.0, 2.0, N) # jak numpy.linspace\n",
    "\n",
    "a_real = 2.5\n",
    "b_real = -1.0\n",
    "\n",
    "y = a_real * x + b_real\n",
    "y += 0.3 * tf.random.normal((N,))\n",
    "\n",
    "a = tf.Variable(tf.random.normal(())) # zamiast tensora z requires_grad\n",
    "b = tf.Variable(tf.random.normal(()))\n",
    "\n",
    "optimizer = tf.keras.optimizers.SGD(learning_rate=1e-2) # por. torch.optim.SGD - nie ma a, b\n",
    "\n",
    "for step in range(501):\n",
    "    with tf.GradientTape() as tape: # \"taśma\" zapisująca obliczenia\n",
    "        predicted_y = a * x + b\n",
    "        loss = tf.reduce_mean((predicted_y - y) ** 2)\n",
    "\n",
    "    grads = tape.gradient(loss, [a, b]) # taśma -> [a, b] -> gradient\n",
    "    optimizer.apply_gradients(zip(grads, [a, b])) # dodanie gradientu (a -= lr*grads[0], b -= lr*grads[1])\n",
    "\n",
    "    if step % 50 == 0:\n",
    "        print(f\"step {step:4d} loss {loss.numpy():.6f}\")\n",
    "\n",
    "print(\"\\nParametry:\")\n",
    "print(\"a =\", a.numpy())\n",
    "print(\"b =\", b.numpy())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "33f422d9",
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.scatter(x, y)\n",
    "plt.plot(x, a.numpy() * x + b.numpy(), color=\"red\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "135a9d43",
   "metadata": {},
   "source": [
    "# Lista 5"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f08c19e8",
   "metadata": {},
   "source": [
    "## Zadanie 1 (1 punkt)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40d82766-d3c3-4b29-8a7b-334d169b34fd",
   "metadata": {},
   "source": [
    "- Zainstaluj `tensorflow`.\n",
    "- Przerób przykłady regresji z notatnika do wykładu na `tensorflow`.\n",
    "- Opcjonalnie: to samo dla rekonstrukcji obrazu."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b6ac60da",
   "metadata": {},
   "source": [
    "## Zadanie 2 (2 punkty)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ebdddf10",
   "metadata": {},
   "source": [
    "<font color=\"red\"><b>Uwaga:</b></font> we wszystkich zadaniach stosuj MSE (średni błąd kwadratowy, nie sumę błędów kwadratowych) jako funkcję straty. Zadanie możesz zrobić z użyciem `torch` lub `tensorflow`. Zamiast optymalizatora `SGD` możesz użyć `Adam` (może to poprawić szybkość zbieżności)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9faf9d14",
   "metadata": {},
   "source": [
    "Mamy 100 punktów danych:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "56ae17c1",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e549b155",
   "metadata": {},
   "outputs": [],
   "source": [
    "N = 100\n",
    "x = np.linspace(0, 6 * np. pi, N)\n",
    "y = 5 * np.sin(x + 2) + 0.3 * x + 4 + np.random.randn(N) / 3\n",
    "\n",
    "plt.scatter(x, y, s=2)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f945fbb0",
   "metadata": {},
   "source": [
    "Dane reprezentują *szereg czasowy*, w którym wyraźnie widać sezonowość (cyklyczne wzrosty i spadki) oraz liniowy trend."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ff3b799",
   "metadata": {},
   "source": [
    "### Krok 1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2825ca74",
   "metadata": {},
   "source": [
    "Dopasuj parametry regresji liniowej, tzn. znajdź $a, b$ dla których przybliżenie\n",
    "\n",
    "$$y \\sim ax + b$$\n",
    "\n",
    "daje najmniejsze MSE. Postaraj się uzyskać MSE nie większe, niż `13.0`. Następnie narysuj punkty danych i uzyskane przybliżenia. Regresja powinna uchwycić trend wzrostu.\n",
    "\n",
    "<font color=\"red\">Zapisz znaleziony parametr $a$.</font>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a223dfd7",
   "metadata": {},
   "outputs": [],
   "source": [
    "# miejsce na kod ..."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd970138",
   "metadata": {},
   "source": [
    "### Krok 2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bac17998",
   "metadata": {},
   "source": [
    "Dopasuj parametry uproszczonego modelu sinusoidalnego, tzn. znajdź $A, \\phi, b$ dla których przybliżenie\n",
    "\n",
    "$$y \\sim A \\sin(x + \\phi)  +b$$\n",
    "\n",
    "daje najmniejsze MSE. Postaraj się uzyskać MSE nie większe, niż `3.0`. Następnie narysuj punkty danych i uzyskane przybliżenia. Model powinien dobrze uchwycić zmiany sezonowe.\n",
    "\n",
    "<font color=\"red\">Zapisz znalezione parametry $A, \\phi, b$.</font>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dbe9e5d2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# miejsce na kod ..."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d461a76b",
   "metadata": {},
   "source": [
    "### Krok 3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e3fa49a0",
   "metadata": {},
   "source": [
    "Dopasuj parametry (uproszczonego) modelu sinusoidalnego połączonego z regresją liniową, tzn. znajdź $A, \\phi, a, b$ dla których przybliżenie\n",
    "\n",
    "$$y \\sim A \\sin(x + \\phi) + ax + b$$\n",
    "\n",
    "daje dajmniejsze MSE. <font color=\"red\">Za początkową wartość $a$ przyjmij wartość uzyskaną w Kroku 1, a za początkowe wartości $A, \\phi, b$ przyjmij wartości uzyskane w Kroku 2.</font> Porównaj końcowe MSE z MSE z obu tych kroków. Następnie narysuj punkty danych i uzyskane przybliżenia. Model powinien dobrze reprezentować zarówno zmiany sezonowe, jak i trend."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "345ecf92",
   "metadata": {},
   "outputs": [],
   "source": [
    "# miejsce na kod ..."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a00f48f9",
   "metadata": {},
   "source": [
    "Powyższy sposób prowadzi do (szybkiego) wytrenowania modelu poprzez wytrenowanie parametrów jego uproszczonych wersji (modele z Kroków 1 i 2 to szczególne przypadki modeli z Kroku 3), a następnie \"dotrenowanie\" ich we właściwym modelu."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "626c1611",
   "metadata": {},
   "source": [
    "### Krok 4 (opcjonalny)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b1dec1bd",
   "metadata": {},
   "source": [
    "Możesz przetestować alternatywny sposób wytrenowania modelu, gdzie od początku trenujemy model z Kroku 3, ale:\n",
    "- Po wylosowaniu początkowych wartości parametrów, trenujemy wyłącznie $a$, natomiast $A, \\phi, b$ traktujemy jako stałe (nie wymagające gradientu).\n",
    "- Po wytrenowaniu $a$, \"zamrażamy\" jego wartość, \"odmrażamy\" $A, \\phi, b$ i je trenujemy, traktując $a$ jako stałą.\n",
    "- Ponownie zamrażamy $A, \\phi, b$, odmrażamy  i trenujemy $a$.\n",
    "- Kontynuujemy naprzemienne trenowanie trójki $(A, \\phi, b)$ oraz $a$.\n",
    "\n",
    "Proces ten można granularyzować jeszcze bardziej, za każdym razem trenując tylko jeden z czterech parametrów (zamrażając pozostałe)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d77dfc7",
   "metadata": {},
   "source": [
    "## Zadanie 3 (nieobowiązkowe) - kolorowe obrazy"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e57848e",
   "metadata": {},
   "source": [
    "W modelu kolorów RGB (Red, Green, Blue), każdy kolor składa się z trzech podstawowych składowych barw addytywnych (czerwonej, zielonej, niebieskiej). Kolor jest typowo reprezentowany przez trójkę $(r, g, b)$, reprezentującą natężenie tych kolejnych składowych (wartościami $r,g,b$ mogą być liczby `float` z zakresu $[0,1]$ lub liczby całkowite z zakresu $[0,255]$, analogicznie jak dla obrazów monochromatycznych)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a049788a",
   "metadata": {},
   "outputs": [],
   "source": [
    "import imageio"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "95100771",
   "metadata": {},
   "source": [
    "Wczytanie obrazu w kolorze (bez parametru `mode='L'`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "98d69599",
   "metadata": {},
   "outputs": [],
   "source": [
    "image = imageio.v2.imread(\"https://www.math.uni.wroc.pl/~jagiella/files/geografia_ai/baboon_color.bmp\") # lub http i v3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b4c36ad5",
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.imshow(image)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ed216320",
   "metadata": {},
   "outputs": [],
   "source": [
    "image.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c9b3acd8",
   "metadata": {},
   "source": [
    "Obraz to tablica $h \\times w \\times 3$, której każda warstwa (indeksowana ostatnią współrzędną) to opis pojedynczej składowej każdego piksela:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8cca6006",
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axes = plt.subplots(1, 3, figsize=(15,5))\n",
    "for ax, i, name in zip(axes, range(3), (\"red\", \"green\", \"blue\")):\n",
    "    ax.set_title(name)\n",
    "    ax.imshow(image[:,:,i], vmin=0, vmax=255)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a6e8a86c",
   "metadata": {},
   "source": [
    "Poniżej składowe wyświetlane we własnym kolorze:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "98e9423d",
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axes = plt.subplots(1, 3, figsize=(15,5))\n",
    "axes[0].imshow(image * np.array([1, 0, 0])) # mnoży warstwę \"czerwoną\" przez 1 i pozostałe przez 0\n",
    "axes[1].imshow(image * np.array([0, 1, 0])) # to samo dla zielonej\n",
    "axes[2].imshow(image * np.array([0, 0, 1])) # i niebieskiej\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5f08446b",
   "metadata": {},
   "source": [
    "Obraz monochromatyczny można było \"skompresować\" przez iloczyn pary macierzy $W, H$. W przypadku obrazu kolorowego można skompresować każdą składową oddzielnie. Metoda ta ma jednak wadę: rekonstruując daną składową nie wykorzystujemy informacji o pozostałych składowych. Natężenie danej składowej bywa jednak skorelowane z natężeniem pozostałych."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3d0d830e",
   "metadata": {},
   "source": [
    "Przykładowo, współczynnik korelacji dla składowej zielonej i niebieskiej z powyższego obrazu:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24822638",
   "metadata": {},
   "outputs": [],
   "source": [
    "np.corrcoef(image[:,:,1].ravel(), image[:,:,2].ravel())[0, 1]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b10284b",
   "metadata": {},
   "source": [
    "Alternatywne podejście: zmienić kształt tablicy na dwuwymiarową, dokonać rozkładu na parę macierzy, dla odzyskanej macierzy zmienić kształ z powrotem na trójwymiarową. Przykładowe sposoby:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3726a829",
   "metadata": {},
   "outputs": [],
   "source": [
    "h, w, _ = image.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d455a788",
   "metadata": {},
   "outputs": [],
   "source": [
    "image1 = image.reshape(h, 3*w)\n",
    "plt.imshow(image1)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "095705aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "image2 = image.reshape(3 * h, w)\n",
    "plt.imshow(image2)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0fc881b7",
   "metadata": {},
   "outputs": [],
   "source": [
    "image3 = image.T.reshape(w * 3, h).T\n",
    "plt.imshow(image3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9ca59770",
   "metadata": {},
   "source": [
    "Zadanie (pro forma): dla różnych sposobów reprezentowania kolorowego obrazu jako macierzy (dwuwymiarowej), przetestuj efekty odtwarzania macierzy jako produktu $WH$ oraz konwersji z powrotem do obrazu kolorowego."
   ]
  }
 ],
 "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.11.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
