├── .gitignore
├── guias
├── guia_1.pdf
└── guia_2.pdf
├── clases
├── _repaso_1
│ └── repaso_1.pdf
├── _repaso_2
│ ├── repaso_2.pdf
│ ├── 0_2_Operatoria_básica_con_NumPy_+_cipher.ipynb
│ └── 0_1_Producto_cartesiano_y_conjuntos.ipynb
├── clase_1
│ └── AMIA_Comparación_velocidades.ipynb
└── clase_5
│ └── AMIA_AutoDiff_v2.ipynb
├── tp_final
├── respuestas_comentarios.md
└── TP_final_LDA_QDA.ipynb
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | Makefile
2 | internal/
3 |
--------------------------------------------------------------------------------
/guias/guia_1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FIUBA-Posgrado-Inteligencia-Artificial/analisis_matematico/HEAD/guias/guia_1.pdf
--------------------------------------------------------------------------------
/guias/guia_2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FIUBA-Posgrado-Inteligencia-Artificial/analisis_matematico/HEAD/guias/guia_2.pdf
--------------------------------------------------------------------------------
/clases/_repaso_1/repaso_1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FIUBA-Posgrado-Inteligencia-Artificial/analisis_matematico/HEAD/clases/_repaso_1/repaso_1.pdf
--------------------------------------------------------------------------------
/clases/_repaso_2/repaso_2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FIUBA-Posgrado-Inteligencia-Artificial/analisis_matematico/HEAD/clases/_repaso_2/repaso_2.pdf
--------------------------------------------------------------------------------
/tp_final/respuestas_comentarios.md:
--------------------------------------------------------------------------------
1 | # Respuestas a las preguntas técnicas
2 |
3 | Recordatorio: Si podés, no leas estas respuestas sin primero leer varias veces el código e intentar responder las preguntas por tu cuenta.
4 |
5 | ## ClassEncoder
6 |
7 | ### Q1: por qué no hace falta definir un class_to_name para el mapeo inverso?
8 | Porque el mapeo de clase a nombre (*int* a *string*) ya se puede accediendo al índice correspondiente del vector `self.names`.
9 |
10 | ### Q2: por que hace falta un reshape?
11 | Porque como NumPy no tiene una función que aplique elemento a elemento una función escalar, la aplicación se debe hacer vía una list comprehension sobre el arreglo flatteneado. Al hacer el flatten se pierde la dimensionalidad original, por lo que es necesario un reshape para recuperarla.
12 |
13 | ## BaseBayesianClassifier
14 |
15 | ### Q3: para qué sirve bincount?
16 | Sirve para contar las frecuencias absolutas de aparición de cada clase. Al dividir por `y.size`, se recuperan las frecuencias relativas, que son la estimación de las probabilidades a priori de cada clase.
17 |
18 | ### Q4: por qué el _fit_params va al final? no se puede mover a, por ejemplo, antes de la priori?
19 | Repasemos qué ocurre antes:
20 |
21 | 1. Se encodean las clases para pasarlas a valores numéricos entre 0 y k-1.
22 | 2. Se obtienen las probabilidades a priori (impuestas o estimadas). Notar que si no se encodean previamente las clases, el `bincount` de `_estimate_a_priori` rompe.
23 |
24 | El método `_fit_params` requiere que las clases estén encodeadas y utiliza el atributo `self.log_a_priori`, por lo que rompe si alguno de los dos pasos anteriores no es realizado.
25 |
26 | ## QDA
27 |
28 | ### Q5: por qué hace falta el flatten y no se puede directamente X[:,y==idx]?
29 | Porque `y` en realidad está modelado como una matriz para poder ser un vector columna, y NumPy arroja error si se intenta indexar una dimensión (la de las columnas de `X`) con un array de dos dimensiones, incluso si una de esas dos es de tamaño 1.
30 |
31 | ### Q6: por qué se usa bias=True en vez del default bias=False?
32 | Porque `bias=True` divide por *n* en vez de por *n-1*, que es el valor default, y es lo que queremos porque ese es el estimador de máxima verosimilitud.
33 |
34 | ### Q7: qué hace axis=1? por que no axis=0?
35 | Porque en nuestro esquema de observaciones como vectores columna, los datos se encuentran en formato (features, observaciones), donde una matriz X de dimensiones (n, m) indica que tiene n features y hay m observaciones. Calcular la media sobre las columnas es promediar sobre las observaciones, hacerlo sobre las features (`axis=0`) no tendría sentido.
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Análisis Matemático para Inteligencia Artificial
2 | En este repositorio vas a encontrar el material de clase utilizado en la materia Análisis Matemático para Inteligencia Artificial (CEIA-FIUBA)
3 |
4 |
5 | ## Estructura del Repositorio
6 |
7 | ```
8 | Clases
9 | ├── Clase #
10 | │ ├── Presentación
11 | │ └── Notebooks
12 | └── ...
13 | Guías
14 | ├── Enunciados
15 | └── Datos
16 | TP Final
17 | └── Notebook
18 | ```
19 |
20 | Dentro de la carpeta *Clases* también se incluye el material relativo al curso introductorio.
21 |
22 |
23 | ## Ejercicios a entregar
24 |
25 | * Entrega 1: Ejercicio 7 - Guía 1
26 | * Entrega 2: Ejercicio 5 - Guía 2
27 |
28 | La entrega se realiza subiendo a la carpeta compartida asignada los archivos correspondientes, los cuales deben estar en formato `.pdf` (matemática) o `.py` (código). Otros formatos no serán considerados como entrega válida.
29 |
30 |
31 | ## Cronograma
32 |
33 | Clase | Temas | Lanzamiento Entrega | Vencimiento Entrega
34 | :---: | :---: | :---: | :---:
35 | Repaso 1 |
Conjuntos
Relaciones
Funciones
| |
36 | Repaso 2 |
Máximos/mínimos
Matrices
NumPy
| |
37 | 1 |
Espacios vectoriales
Bases
| |
38 | 2 |
Producto interno
Ortogonalidad
Gram-Schmidt
Transf. lineales
| 1 |
39 | 3 |
Proyecciones
Cuadrados mínimos
| | 1
40 | 4 |
Avas y Aves
Diagonalización
DVS
| 2 |
41 | 5 |
Análisis multivariado
Backprop
| | 2
42 | 6 |
Opt. sin restricciones
Gradient Descent
| TP |
43 | 7 |
Opt. con restricciones
Opt. convexa y no convexa
| |
44 | 8 | Exposición TP | | TP
45 |
46 |
47 | ## Forma de Evaluación
48 |
49 | La nota final es un promedio ponderado entre las 3 notas parciales, las cuales consisten en 20%, 30% y 50% para entrega 1, entrega 2 y TP final respectivamente.
50 |
51 |
52 | ## Bibliografía
53 |
54 | * (Principal) Deisenroth, M. P., Faisal, A. A.,, Ong, C. S. (2020). *Mathematics for Machine Learning.* Cambridge University Press. [(disponible libre aquí)](https://mml-book.com/)
55 | * (Principal) Grossman, S. I. (2018). ALGEBRA LINEAL (8va. ed.). McGraw-Hill Interamericana.
56 | * (Complementario) Goodfellow, I., Bengio, Y., & Courville, A. (2016). *Deep Learning.* MIT Press. [(disponible libre aquí)](https://www.deeplearningbook.org/)
57 | * (Complementario) Jeronimo, G., Sabia, J., & Tesauri, S. (2008). *Álgebra Lineal.* Departamento de Matemática, Facultad de Ciencias Exactas y Naturales, Universidad de Buenos Aires. [(disponible libre aquí)](http://mate.dm.uba.ar/~jeronimo/algebra_lineal/AlgebraLineal.pdf)
58 | * (Complementario) Boyd, S., & Vandenberghe, L. (2004). *Convex optimization.* Cambridge university press.
59 | [(disponible libre aquí)](https://web.stanford.edu/~boyd/cvxbook/)
60 | * (Complementario) Hoffman, K., & Kunze, R. A. (1971). *Linear Algebra.* Prentice-Hall.
61 | * (Complementario) Barbolla García, R., Sanz Álvaro, P., & Tena, E.C. (2001). *Optimización: cuestiones, ejercicios y aplicaciones a la economía.* Prentice-Hall.
62 |
--------------------------------------------------------------------------------
/clases/clase_1/AMIA_Comparación_velocidades.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": []
7 | },
8 | "kernelspec": {
9 | "name": "python3",
10 | "display_name": "Python 3"
11 | },
12 | "language_info": {
13 | "name": "python"
14 | },
15 | "accelerator": "GPU",
16 | "gpuClass": "standard"
17 | },
18 | "cells": [
19 | {
20 | "cell_type": "code",
21 | "execution_count": null,
22 | "metadata": {
23 | "id": "64kZ7EiYIO3j"
24 | },
25 | "outputs": [],
26 | "source": [
27 | "import torch\n",
28 | "import numpy as np\n",
29 | "\n",
30 | "import matplotlib.pyplot as plt"
31 | ]
32 | },
33 | {
34 | "cell_type": "code",
35 | "source": [
36 | "def matmuls(mat_sz=100, use_gpu=False):\n",
37 | " device = \"cuda\" if use_gpu else \"cpu\"\n",
38 | " A = torch.randn(mat_sz, mat_sz, device=device)\n",
39 | " B = torch.randn(mat_sz, mat_sz, device=device)\n",
40 | " C = torch.randn(mat_sz, mat_sz, device=device)\n",
41 | "\n",
42 | " D = torch.matmul(A, B)\n",
43 | " E = torch.matmul(C, D)\n",
44 | "\n",
45 | " F = D + 3*E\n",
46 | " return F"
47 | ],
48 | "metadata": {
49 | "id": "1ru-amzFKtkp"
50 | },
51 | "execution_count": null,
52 | "outputs": []
53 | },
54 | {
55 | "cell_type": "code",
56 | "source": [
57 | "%%timeit\n",
58 | "matmuls(mat_sz=100, use_gpu=False)"
59 | ],
60 | "metadata": {
61 | "colab": {
62 | "base_uri": "https://localhost:8080/"
63 | },
64 | "id": "eZSKz9q6MYx_",
65 | "outputId": "6c2de4ec-0c92-4086-a53e-86112b03598e"
66 | },
67 | "execution_count": null,
68 | "outputs": [
69 | {
70 | "output_type": "stream",
71 | "name": "stdout",
72 | "text": [
73 | "379 µs ± 104 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n"
74 | ]
75 | }
76 | ]
77 | },
78 | {
79 | "cell_type": "code",
80 | "source": [
81 | "%%timeit\n",
82 | "matmuls(mat_sz=100, use_gpu=True)"
83 | ],
84 | "metadata": {
85 | "colab": {
86 | "base_uri": "https://localhost:8080/"
87 | },
88 | "id": "wCIqLRJlMfFT",
89 | "outputId": "32342beb-7d44-488d-c8ff-1f2f0da24e6b"
90 | },
91 | "execution_count": null,
92 | "outputs": [
93 | {
94 | "output_type": "stream",
95 | "name": "stdout",
96 | "text": [
97 | "92.5 µs ± 27.9 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
98 | ]
99 | }
100 | ]
101 | },
102 | {
103 | "cell_type": "code",
104 | "source": [
105 | "337/92.5"
106 | ],
107 | "metadata": {
108 | "colab": {
109 | "base_uri": "https://localhost:8080/"
110 | },
111 | "id": "Kv6o58QTYJG2",
112 | "outputId": "be1ef9a2-ea9e-4e08-a290-9357b4107f13"
113 | },
114 | "execution_count": null,
115 | "outputs": [
116 | {
117 | "output_type": "execute_result",
118 | "data": {
119 | "text/plain": [
120 | "3.6432432432432433"
121 | ]
122 | },
123 | "metadata": {},
124 | "execution_count": 5
125 | }
126 | ]
127 | },
128 | {
129 | "cell_type": "code",
130 | "source": [
131 | "%%timeit\n",
132 | "matmuls(mat_sz=1000, use_gpu=False)"
133 | ],
134 | "metadata": {
135 | "colab": {
136 | "base_uri": "https://localhost:8080/"
137 | },
138 | "id": "taRj8UbfMItW",
139 | "outputId": "d398ef6a-8fc7-4e3c-8b4f-61e1f02e408c"
140 | },
141 | "execution_count": null,
142 | "outputs": [
143 | {
144 | "output_type": "stream",
145 | "name": "stdout",
146 | "text": [
147 | "76.1 ms ± 19.4 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n"
148 | ]
149 | }
150 | ]
151 | },
152 | {
153 | "cell_type": "code",
154 | "source": [
155 | "%%timeit\n",
156 | "matmuls(mat_sz=1000, use_gpu=True)"
157 | ],
158 | "metadata": {
159 | "colab": {
160 | "base_uri": "https://localhost:8080/"
161 | },
162 | "id": "Rb7VHkU6MQ4V",
163 | "outputId": "dc8858ce-f90e-4922-eefd-31792249ca59"
164 | },
165 | "execution_count": null,
166 | "outputs": [
167 | {
168 | "output_type": "stream",
169 | "name": "stdout",
170 | "text": [
171 | "1.43 ms ± 20.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n"
172 | ]
173 | }
174 | ]
175 | },
176 | {
177 | "cell_type": "code",
178 | "source": [
179 | "76.1/1.43"
180 | ],
181 | "metadata": {
182 | "colab": {
183 | "base_uri": "https://localhost:8080/"
184 | },
185 | "id": "FTAXiqPeMgjJ",
186 | "outputId": "6826d3b3-79df-490f-9601-de1edba5c459"
187 | },
188 | "execution_count": null,
189 | "outputs": [
190 | {
191 | "output_type": "execute_result",
192 | "data": {
193 | "text/plain": [
194 | "53.21678321678321"
195 | ]
196 | },
197 | "metadata": {},
198 | "execution_count": 9
199 | }
200 | ]
201 | },
202 | {
203 | "cell_type": "code",
204 | "source": [
205 | "%%timeit\n",
206 | "matmuls(mat_sz=5000, use_gpu=False)"
207 | ],
208 | "metadata": {
209 | "colab": {
210 | "base_uri": "https://localhost:8080/"
211 | },
212 | "id": "DkWi-HERYtTu",
213 | "outputId": "976706c0-9712-48f4-ad6b-60a2e3d93f89"
214 | },
215 | "execution_count": null,
216 | "outputs": [
217 | {
218 | "output_type": "stream",
219 | "name": "stdout",
220 | "text": [
221 | "4.95 s ± 386 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
222 | ]
223 | }
224 | ]
225 | },
226 | {
227 | "cell_type": "code",
228 | "source": [
229 | "%%timeit -n 1000 -r 3\n",
230 | "matmuls(mat_sz=5000, use_gpu=True)"
231 | ],
232 | "metadata": {
233 | "colab": {
234 | "base_uri": "https://localhost:8080/"
235 | },
236 | "id": "nPodoj7hYxSu",
237 | "outputId": "15310f94-29a5-4bc5-8d60-4d0ace435c2d"
238 | },
239 | "execution_count": null,
240 | "outputs": [
241 | {
242 | "output_type": "stream",
243 | "name": "stdout",
244 | "text": [
245 | "146 ms ± 10.4 ms per loop (mean ± std. dev. of 3 runs, 1000 loops each)\n"
246 | ]
247 | }
248 | ]
249 | },
250 | {
251 | "cell_type": "code",
252 | "source": [
253 | "4.95/0.146"
254 | ],
255 | "metadata": {
256 | "colab": {
257 | "base_uri": "https://localhost:8080/"
258 | },
259 | "id": "xzbC1NjBc4NE",
260 | "outputId": "9389320d-b006-428c-f2b8-2eb6534043f5"
261 | },
262 | "execution_count": null,
263 | "outputs": [
264 | {
265 | "output_type": "execute_result",
266 | "data": {
267 | "text/plain": [
268 | "33.9041095890411"
269 | ]
270 | },
271 | "metadata": {},
272 | "execution_count": 16
273 | }
274 | ]
275 | },
276 | {
277 | "cell_type": "code",
278 | "source": [],
279 | "metadata": {
280 | "id": "HAI7cMagfDY2"
281 | },
282 | "execution_count": null,
283 | "outputs": []
284 | }
285 | ]
286 | }
--------------------------------------------------------------------------------
/tp_final/TP_final_LDA_QDA.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": []
7 | },
8 | "kernelspec": {
9 | "name": "python3",
10 | "display_name": "Python 3"
11 | },
12 | "language_info": {
13 | "name": "python"
14 | }
15 | },
16 | "cells": [
17 | {
18 | "cell_type": "markdown",
19 | "source": [
20 | "# Trabajo Práctico Final: Linear/Quadratic Discriminant Analysis (LDA/QDA)\n",
21 | "\n",
22 | "### Definición: Clasificador Bayesiano\n",
23 | "\n",
24 | "Sean $k$ poblaciones, $x \\in \\mathbb{R}^p$ puede pertenecer a cualquiera $g \\in \\mathcal{G}$ de ellas. Bajo un esquema bayesiano, se define entonces $\\pi_j \\doteq P(G = j)$ la probabilidad *a priori* de que $X$ pertenezca a la clase *j*, y se **asume conocida** la distribución condicional de cada observable dado su clase $f_j \\doteq f_{X|G=j}$.\n",
25 | "\n",
26 | "De esta manera dicha probabilidad *a posteriori* resulta\n",
27 | "$$\n",
28 | "P(G|_{X=x} = j) = \\frac{f_{X|G=j}(x) \\cdot p_G(j)}{f_X(x)} \\propto f_j(x) \\cdot \\pi_j\n",
29 | "$$\n",
30 | "\n",
31 | "La regla de decisión de Bayes es entonces\n",
32 | "$$\n",
33 | "H(x) \\doteq \\arg \\max_{g \\in \\mathcal{G}} \\{ P(G|_{X=x} = j) \\} = \\arg \\max_{g \\in \\mathcal{G}} \\{ f_j(x) \\cdot \\pi_j \\}\n",
34 | "$$\n",
35 | "\n",
36 | "es decir, se predice a $x$ como perteneciente a la población $j$ cuya probabilidad a posteriori es máxima.\n",
37 | "\n",
38 | "*Ojo, a no desesperar! $\\pi_j$ no es otra cosa que una constante prefijada, y $f_j$ es, en su esencia, un campo escalar de $x$ a simplemente evaluar.*\n",
39 | "\n",
40 | "### Distribución condicional\n",
41 | "\n",
42 | "Para los clasificadores de discriminante cuadrático y lineal (QDA/LDA) se asume que $X|_{G=j} \\sim \\mathcal{N}_p(\\mu_j, \\Sigma_j)$, es decir, se asume que cada población sigue una distribución normal.\n",
43 | "\n",
44 | "Por definición, se tiene entonces que para una clase $j$:\n",
45 | "$$\n",
46 | "f_j(x) = \\frac{1}{(2 \\pi)^\\frac{p}{2} \\cdot |\\Sigma_j|^\\frac{1}{2}} e^{- \\frac{1}{2}(x-\\mu_j)^T \\Sigma_j^{-1} (x- \\mu_j)}\n",
47 | "$$\n",
48 | "\n",
49 | "Aplicando logaritmo (que al ser una función estrictamente creciente no afecta el cálculo de máximos/mínimos), queda algo mucho más práctico de trabajar:\n",
50 | "\n",
51 | "$$\n",
52 | "\\log{f_j(x)} = -\\frac{1}{2}\\log |\\Sigma_j| - \\frac{1}{2} (x-\\mu_j)^T \\Sigma_j^{-1} (x- \\mu_j) + C\n",
53 | "$$\n",
54 | "\n",
55 | "Observar que en este caso $C=-\\frac{p}{2} \\log(2\\pi)$, pero no se tiene en cuenta ya que al tener una constante aditiva en todas las clases, no afecta al cálculo del máximo.\n",
56 | "\n",
57 | "### LDA\n",
58 | "\n",
59 | "En el caso de LDA se hace una suposición extra, que es $X|_{G=j} \\sim \\mathcal{N}_p(\\mu_j, \\Sigma)$, es decir que las poblaciones no sólo siguen una distribución normal sino que son de igual matriz de covarianzas. Reemplazando arriba se obtiene entonces:\n",
60 | "\n",
61 | "$$\n",
62 | "\\log{f_j(x)} = -\\frac{1}{2}\\log |\\Sigma| - \\frac{1}{2} (x-\\mu_j)^T \\Sigma^{-1} (x- \\mu_j) + C\n",
63 | "$$\n",
64 | "\n",
65 | "Ahora, como $-\\frac{1}{2}\\log |\\Sigma|$ es común a todas las clases se puede incorporar a la constante aditiva y, distribuyendo y reagrupando términos sobre $(x-\\mu_j)^T \\Sigma^{-1} (x- \\mu_j)$ se obtiene finalmente:\n",
66 | "\n",
67 | "$$\n",
68 | "\\log{f_j(x)} = \\mu_j^T \\Sigma^{-1} (x- \\frac{1}{2} \\mu_j) + C'\n",
69 | "$$\n",
70 | "\n",
71 | "### Entrenamiento/Ajuste\n",
72 | "\n",
73 | "Obsérvese que para ambos modelos, ajustarlos a los datos implica estimar los parámetros $(\\mu_j, \\Sigma_j) \\; \\forall j = 1, \\dots, k$ en el caso de QDA, y $(\\mu_j, \\Sigma)$ para LDA.\n",
74 | "\n",
75 | "Estos parámetros se estiman por máxima verosimilitud, de manera que los estimadores resultan:\n",
76 | "\n",
77 | "* $\\hat{\\mu}_j = \\bar{x}_j$ el promedio de los $x$ de la clase *j*\n",
78 | "* $\\hat{\\Sigma}_j = s^2_j$ la matriz de covarianzas estimada para cada clase *j*\n",
79 | "* $\\hat{\\pi}_j = f_{R_j} = \\frac{n_j}{n}$ la frecuencia relativa de la clase *j* en la muestra\n",
80 | "* $\\hat{\\Sigma} = \\frac{1}{n} \\sum_{j=1}^k n_j \\cdot s^2_j$ el promedio ponderado (por frecs. relativas) de las matrices de covarianzas de todas las clases. *Observar que se utiliza el estimador de MV y no el insesgado*\n",
81 | "\n",
82 | "Es importante notar que si bien todos los $\\mu, \\Sigma$ deben ser estimados, la distribución *a priori* puede no inferirse de los datos sino asumirse previamente, utilizándose como entrada del modelo.\n",
83 | "\n",
84 | "### Predicción\n",
85 | "\n",
86 | "Para estos modelos, al igual que para cualquier clasificador Bayesiano del tipo antes visto, la estimación de la clase es por método *plug-in* sobre la regla de decisión $H(x)$, es decir devolver la clase que maximiza $\\hat{f}_j(x) \\cdot \\hat{\\pi}_j$, o lo que es lo mismo $\\log\\hat{f}_j(x) + \\log\\hat{\\pi}_j$."
87 | ],
88 | "metadata": {
89 | "id": "bpJ7s_SIVu_I"
90 | }
91 | },
92 | {
93 | "cell_type": "markdown",
94 | "source": [
95 | "## Estructura del código"
96 | ],
97 | "metadata": {
98 | "id": "5TDWOgpJWKQa"
99 | }
100 | },
101 | {
102 | "cell_type": "markdown",
103 | "source": [
104 | "## Modelo"
105 | ],
106 | "metadata": {
107 | "id": "6yEV8WbiWl6k"
108 | }
109 | },
110 | {
111 | "cell_type": "code",
112 | "source": [
113 | "import numpy as np\n",
114 | "from numpy.linalg import det, inv"
115 | ],
116 | "metadata": {
117 | "id": "teF9O9JJmG7Z"
118 | },
119 | "execution_count": null,
120 | "outputs": []
121 | },
122 | {
123 | "cell_type": "code",
124 | "source": [
125 | "class ClassEncoder:\n",
126 | " def fit(self, y):\n",
127 | " self.names = np.unique(y)\n",
128 | " self.name_to_class = {name:idx for idx, name in enumerate(self.names)}\n",
129 | " self.fmt = y.dtype\n",
130 | " # Q1: por que no hace falta definir un class_to_name para el mapeo inverso?\n",
131 | "\n",
132 | " def _map_reshape(self, f, arr):\n",
133 | " return np.array([f(elem) for elem in arr.flatten()]).reshape(arr.shape)\n",
134 | " # Q2: por que hace falta un reshape?\n",
135 | "\n",
136 | " def transform(self, y):\n",
137 | " return self._map_reshape(lambda name: self.name_to_class[name], y)\n",
138 | "\n",
139 | " def fit_transform(self, y):\n",
140 | " self.fit(y)\n",
141 | " return self.transform(y)\n",
142 | "\n",
143 | " def detransform(self, y_hat):\n",
144 | " return self._map_reshape(lambda idx: self.names[idx], y_hat)"
145 | ],
146 | "metadata": {
147 | "id": "sDBLvbTtlwzs"
148 | },
149 | "execution_count": null,
150 | "outputs": []
151 | },
152 | {
153 | "cell_type": "code",
154 | "execution_count": null,
155 | "metadata": {
156 | "id": "m0KYC8_uSOu4"
157 | },
158 | "outputs": [],
159 | "source": [
160 | "class BaseBayesianClassifier:\n",
161 | " def __init__(self):\n",
162 | " self.encoder = ClassEncoder()\n",
163 | "\n",
164 | " def _estimate_a_priori(self, y):\n",
165 | " a_priori = np.bincount(y.flatten().astype(int)) / y.size\n",
166 | " # Q3: para que sirve bincount?\n",
167 | " return np.log(a_priori)\n",
168 | "\n",
169 | " def _fit_params(self, X, y):\n",
170 | " # estimate all needed parameters for given model\n",
171 | " raise NotImplementedError()\n",
172 | "\n",
173 | " def _predict_log_conditional(self, x, class_idx):\n",
174 | " # predict the log(P(x|G=class_idx)), the log of the conditional probability of x given the class\n",
175 | " # this should depend on the model used\n",
176 | " raise NotImplementedError()\n",
177 | "\n",
178 | " def fit(self, X, y, a_priori=None):\n",
179 | " # first encode the classes\n",
180 | " y = self.encoder.fit_transform(y)\n",
181 | "\n",
182 | " # if it's needed, estimate a priori probabilities\n",
183 | " self.log_a_priori = self._estimate_a_priori(y) if a_priori is None else np.log(a_priori)\n",
184 | "\n",
185 | " # check that a_priori has the correct number of classes\n",
186 | " assert len(self.log_a_priori) == len(self.encoder.names), \"A priori probabilities do not match number of classes\"\n",
187 | "\n",
188 | " # now that everything else is in place, estimate all needed parameters for given model\n",
189 | " self._fit_params(X, y)\n",
190 | " # Q4: por que el _fit_params va al final? no se puede mover a, por ejemplo, antes de la priori?\n",
191 | "\n",
192 | " def predict(self, X):\n",
193 | " # this is actually an individual prediction encased in a for-loop\n",
194 | " m_obs = X.shape[1]\n",
195 | " y_hat = np.empty(m_obs, dtype=self.encoder.fmt)\n",
196 | "\n",
197 | " for i in range(m_obs):\n",
198 | " encoded_y_hat_i = self._predict_one(X[:,i].reshape(-1,1))\n",
199 | " y_hat[i] = self.encoder.names[encoded_y_hat_i]\n",
200 | "\n",
201 | " # return prediction as a row vector (matching y)\n",
202 | " return y_hat.reshape(1,-1)\n",
203 | "\n",
204 | " def _predict_one(self, x):\n",
205 | " # calculate all log posteriori probabilities (actually, +C)\n",
206 | " log_posteriori = [ log_a_priori_i + self._predict_log_conditional(x, idx) for idx, log_a_priori_i\n",
207 | " in enumerate(self.log_a_priori) ]\n",
208 | "\n",
209 | " # return the class that has maximum a posteriori probability\n",
210 | " return np.argmax(log_posteriori)"
211 | ]
212 | },
213 | {
214 | "cell_type": "code",
215 | "source": [
216 | "class QDA(BaseBayesianClassifier):\n",
217 | "\n",
218 | " def _fit_params(self, X, y):\n",
219 | " # estimate each covariance matrix\n",
220 | " self.inv_covs = [inv(np.cov(X[:,y.flatten()==idx], bias=True))\n",
221 | " for idx in range(len(self.log_a_priori))]\n",
222 | " # Q5: por que hace falta el flatten y no se puede directamente X[:,y==idx]?\n",
223 | " # Q6: por que se usa bias=True en vez del default bias=False?\n",
224 | " self.means = [X[:,y.flatten()==idx].mean(axis=1, keepdims=True)\n",
225 | " for idx in range(len(self.log_a_priori))]\n",
226 | " # Q7: que hace axis=1? por que no axis=0?\n",
227 | "\n",
228 | " def _predict_log_conditional(self, x, class_idx):\n",
229 | " # predict the log(P(x|G=class_idx)), the log of the conditional probability of x given the class\n",
230 | " # this should depend on the model used\n",
231 | " inv_cov = self.inv_covs[class_idx]\n",
232 | " unbiased_x = x - self.means[class_idx]\n",
233 | " return 0.5*np.log(det(inv_cov)) -0.5 * unbiased_x.T @ inv_cov @ unbiased_x"
234 | ],
235 | "metadata": {
236 | "id": "IRamFdiGDuSR"
237 | },
238 | "execution_count": null,
239 | "outputs": []
240 | },
241 | {
242 | "cell_type": "code",
243 | "source": [
244 | "class TensorizedQDA(QDA):\n",
245 | "\n",
246 | " def _fit_params(self, X, y):\n",
247 | " # ask plain QDA to fit params\n",
248 | " super()._fit_params(X,y)\n",
249 | "\n",
250 | " # stack onto new dimension\n",
251 | " self.tensor_inv_cov = np.stack(self.inv_covs)\n",
252 | " self.tensor_means = np.stack(self.means)\n",
253 | "\n",
254 | " def _predict_log_conditionals(self,x):\n",
255 | " unbiased_x = x - self.tensor_means\n",
256 | " inner_prod = unbiased_x.transpose(0,2,1) @ self.tensor_inv_cov @ unbiased_x\n",
257 | "\n",
258 | " return 0.5*np.log(det(self.tensor_inv_cov)) - 0.5 * inner_prod.flatten()\n",
259 | "\n",
260 | " def _predict_one(self, x):\n",
261 | " # return the class that has maximum a posteriori probability\n",
262 | " return np.argmax(self.log_a_priori + self._predict_log_conditionals(x))"
263 | ],
264 | "metadata": {
265 | "id": "fRtC9HEkO5Hu"
266 | },
267 | "execution_count": null,
268 | "outputs": []
269 | },
270 | {
271 | "cell_type": "markdown",
272 | "source": [
273 | "## Código para pruebas"
274 | ],
275 | "metadata": {
276 | "id": "KS_zoK-gWkRf"
277 | }
278 | },
279 | {
280 | "cell_type": "markdown",
281 | "source": [
282 | "Seteamos los datos"
283 | ],
284 | "metadata": {
285 | "id": "nz19b6NJed2A"
286 | }
287 | },
288 | {
289 | "cell_type": "code",
290 | "source": [
291 | "# hiperparámetros\n",
292 | "rng_seed = 6543"
293 | ],
294 | "metadata": {
295 | "id": "m05KrhUDINVs"
296 | },
297 | "execution_count": null,
298 | "outputs": []
299 | },
300 | {
301 | "cell_type": "code",
302 | "source": [
303 | "from sklearn.datasets import load_iris, fetch_openml\n",
304 | "\n",
305 | "def get_iris_dataset():\n",
306 | " data = load_iris()\n",
307 | " X_full = data.data\n",
308 | " y_full = np.array([data.target_names[y] for y in data.target.reshape(-1,1)])\n",
309 | " return X_full, y_full\n",
310 | "\n",
311 | "def get_penguins():\n",
312 | " # get data\n",
313 | " df, tgt = fetch_openml(name=\"penguins\", return_X_y=True, as_frame=True)\n",
314 | "\n",
315 | " # drop non-numeric columns\n",
316 | " df.drop(columns=[\"island\",\"sex\"], inplace=True)\n",
317 | "\n",
318 | " # drop rows with missing values\n",
319 | " mask = df.isna().sum(axis=1) == 0\n",
320 | " df = df[mask]\n",
321 | " tgt = tgt[mask]\n",
322 | "\n",
323 | " return df.values, tgt.to_numpy().reshape(-1,1)\n",
324 | "\n",
325 | "# showing for iris\n",
326 | "X_full, y_full = get_iris_dataset()\n",
327 | "\n",
328 | "print(f\"X: {X_full.shape}, Y:{y_full.shape}\")"
329 | ],
330 | "metadata": {
331 | "id": "2hkXcoldXOqs",
332 | "colab": {
333 | "base_uri": "https://localhost:8080/"
334 | },
335 | "outputId": "2ce8d627-3433-4bdd-d370-85f6b703a7b9"
336 | },
337 | "execution_count": null,
338 | "outputs": [
339 | {
340 | "output_type": "stream",
341 | "name": "stdout",
342 | "text": [
343 | "X: (150, 4), Y:(150, 1)\n"
344 | ]
345 | }
346 | ]
347 | },
348 | {
349 | "cell_type": "code",
350 | "source": [
351 | "# peek data matrix\n",
352 | "X_full[:5]"
353 | ],
354 | "metadata": {
355 | "colab": {
356 | "base_uri": "https://localhost:8080/"
357 | },
358 | "id": "jAk-UQCjKecT",
359 | "outputId": "9566d67a-b78b-4809-bb94-8f605b065db6"
360 | },
361 | "execution_count": null,
362 | "outputs": [
363 | {
364 | "output_type": "execute_result",
365 | "data": {
366 | "text/plain": [
367 | "array([[5.1, 3.5, 1.4, 0.2],\n",
368 | " [4.9, 3. , 1.4, 0.2],\n",
369 | " [4.7, 3.2, 1.3, 0.2],\n",
370 | " [4.6, 3.1, 1.5, 0.2],\n",
371 | " [5. , 3.6, 1.4, 0.2]])"
372 | ]
373 | },
374 | "metadata": {},
375 | "execution_count": 66
376 | }
377 | ]
378 | },
379 | {
380 | "cell_type": "code",
381 | "source": [
382 | "# peek target vector\n",
383 | "y_full[:5]"
384 | ],
385 | "metadata": {
386 | "colab": {
387 | "base_uri": "https://localhost:8080/"
388 | },
389 | "id": "YdzMURX2KVdO",
390 | "outputId": "af5fc3ac-b391-4769-de47-44cea4f566c8"
391 | },
392 | "execution_count": null,
393 | "outputs": [
394 | {
395 | "output_type": "execute_result",
396 | "data": {
397 | "text/plain": [
398 | "array([['setosa'],\n",
399 | " ['setosa'],\n",
400 | " ['setosa'],\n",
401 | " ['setosa'],\n",
402 | " ['setosa']], dtype='\n",
586 | "\n",
587 | "Modelo | Dataset | Seed | Error (train) | Error (test)\n",
588 | ":---: | :---: | :---: | :---: | :---:\n",
589 | "QDA | Iris | 125 | 0.55 | 0.85\n",
590 | "LDA | Iris | 125 | 0.22 | 0.8\n",
591 | "\n",
592 | "\n",
593 | "\n",
594 | "## Preguntas teóricas\n",
595 | "\n",
596 | "1. En LDA se menciona que la función a maximizar puede ser, mediante operaciones, convertida en:\n",
597 | "$$\n",
598 | "\\log{f_j(x)} = \\mu_j^T \\Sigma^{-1} (x- \\frac{1}{2} \\mu_j) + C'\n",
599 | "$$\n",
600 | "Mostrar los pasos por los cuales se llega a dicha expresión.\n",
601 | "2. Explicar, utilizando las respectivas funciones a maximizar, por qué QDA y LDA son \"quadratic\" y \"linear\".\n",
602 | "3. La implementación de QDA estima la probabilidad condicional utilizando `0.5*np.log(det(inv_cov)) -0.5 * unbiased_x.T @ inv_cov @ unbiased_x` que no es *exactamente* lo descrito en el apartado teórico ¿Cuáles son las diferencias y por qué son expresiones equivalentes?\n",
603 | "\n",
604 | "El espíritu de esta componente práctica es la de establecer un mínimo de trabajo aceptable para su entrega; se invita al alumno a explorar otros aspectos que generen curiosidad, sin sentirse de ninguna manera limitado por la consigna.\n",
605 | "\n",
606 | "## Ejercicio teórico\n",
607 | "\n",
608 | "Sea una red neuronal de dos capas, la primera de 3 neuronas y la segunda de 1 con los parámetros inicializados con los siguientes valores:\n",
609 | "$$\n",
610 | "w^{(1)} =\n",
611 | "\\begin{pmatrix}\n",
612 | "0.1 & -0.5 \\\\\n",
613 | "-0.3 & -0.9 \\\\\n",
614 | "0.8 & 0.02\n",
615 | "\\end{pmatrix},\n",
616 | "b^{(1)} = \\begin{pmatrix}\n",
617 | "0.1 \\\\\n",
618 | "0.5 \\\\\n",
619 | "0.8\n",
620 | "\\end{pmatrix},\n",
621 | "w^{(2)} =\n",
622 | "\\begin{pmatrix}\n",
623 | "-0.4 & 0.2 & -0.5\n",
624 | "\\end{pmatrix},\n",
625 | "b^{(2)} = 0.7\n",
626 | "$$\n",
627 | "\n",
628 | "y donde cada capa calcula su salida vía\n",
629 | "\n",
630 | "$$\n",
631 | "y^{(i)} = \\sigma (w^{(i)} \\cdot x^{(i)}+b^{(i)})\n",
632 | "$$\n",
633 | "\n",
634 | "donde $\\sigma (z) = \\frac{1}{1+e^{-z}}$ es la función sigmoidea .\n",
635 | "\n",
636 | "\\\\\n",
637 | "Dada la observación $x=\\begin{pmatrix}\n",
638 | "1.8 \\\\\n",
639 | "-3.4\n",
640 | "\\end{pmatrix}$, $y=5$ y la función de costo $J(\\theta)=\\frac{1}{2}(\\hat{y}_\\theta-y)^2$, calcular las derivadas de J respecto de cada parámetro $w^{(1)}$, $w^{(2)}$, $b^{(1)}$, $b^{(2)}$.\n",
641 | "\n",
642 | "*Nota: Con una sigmoidea a la salida jamás va a poder estimar el 5 \"pedido\", pero eso no afecta al mecanismo de backpropagation!*\n",
643 | "\n",
644 | "## Preguntas en el código\n",
645 | "Previamente las preguntas \"técnicas\" en comentarios en el código eran parte del TP, y buscaban que el alumno logre entrar en el detalle de por qué cada linea de código es como es y en el orden en el que está. Ya no forman parte de la consigna, pero se aconseja al alumno intentar responderlas. Las respuestas a las mismas se encuentran en un archivo separado.\n",
646 | "\n",
647 | "## Opcional\n",
648 | "\n",
649 | "### QDA\n",
650 | "\n",
651 | "Debido a la forma cuadrática de QDA, no se puede predecir para *n* observaciones en una sola pasada (utilizar $X \\in \\mathbb{R}^{p \\times n}$ en vez de $x \\in \\mathbb{R}^p$) sin pasar por una matriz de *n x n* en donde se computan todas las interacciones entre observaciones. Se puede acceder al resultado recuperando sólo la diagonal de dicha matriz, pero resulta ineficiente en tiempo y (especialmente) en memoria. Aún así, es *posible* que el modelo funcione más rápido.\n",
652 | "\n",
653 | "1. Implementar el modelo `FasterQDA` (se recomienda heredarlo de TensorizedQDA) de manera de eliminar el ciclo for en el método predict.\n",
654 | "2. Comparar los tiempos de predicción de `FasterQDA` con `TensorizedQDA` y `QDA`\n",
655 | "3. Mostrar (puede ser con un print) dónde aparece la mencionada matriz de *n x n*, donde *n* es la cantidad de observaciones a predecir.\n",
656 | "4.Demostrar\n",
657 | "$$\n",
658 | "diag(A \\cdot B) = \\sum_{cols} A \\odot B^T = np.sum(A \\odot B^T, axis=1)\n",
659 | "$$ es decir, que se puede \"esquivar\" la matriz de *n x n* usando matrices de *n x p*.\n",
660 | "5.Utilizar la propiedad antes demostrada para reimplementar la predicción del modelo `FasterQDA` de forma eficiente. ¿Hay cambios en los tiempos de predicción?\n",
661 | "\n",
662 | "\n",
663 | "### LDA\n",
664 | "\n",
665 | "1. \"Tensorizar\" el modelo LDA y comparar sus tiempos de predicción con el modelo antes implementado. *Notar que, en modo tensorizado, se puede directamente precomputar $\\mu^T \\cdot \\Sigma^{-1} \\in \\mathbb{R}^{k \\times 1 \\times p}$ y guardar eso en vez de $\\Sigma^{-1}$.*\n",
666 | "2. LDA no sufre del problema antes descrito de QDA debido a que no computa productos internos, por lo que no tiene un verdadero costo extra en memoria predecir \"en batch\". Implementar el modelo `FasterLDA` y comparar sus tiempos de predicción con las versiones anteriores de LDA."
667 | ],
668 | "metadata": {
669 | "id": "1Yb1V7_yXRfO"
670 | }
671 | }
672 | ]
673 | }
--------------------------------------------------------------------------------
/clases/clase_5/AMIA_AutoDiff_v2.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": []
7 | },
8 | "kernelspec": {
9 | "name": "python3",
10 | "display_name": "Python 3"
11 | },
12 | "language_info": {
13 | "name": "python"
14 | }
15 | },
16 | "cells": [
17 | {
18 | "cell_type": "markdown",
19 | "source": [
20 | "# Diferenciación Automática (v2)\n",
21 | "\n",
22 | "En este colab vamos a explorar una implementación de un grafo de cómputo y algunas operaciones soportadas (podríamos agregarle todas las que querramos), permitiendo por diferenciación automática el cálculo de las derivadas de una función arbitrariamente compleja respecto de sus entradas."
23 | ],
24 | "metadata": {
25 | "id": "TAIge0mCOohh"
26 | }
27 | },
28 | {
29 | "cell_type": "code",
30 | "source": [
31 | "import math\n",
32 | "from graphviz import Digraph"
33 | ],
34 | "metadata": {
35 | "id": "NWmB_aQ5c6y0"
36 | },
37 | "execution_count": 63,
38 | "outputs": []
39 | },
40 | {
41 | "cell_type": "code",
42 | "source": [
43 | "# helper function for subsetting a dict\n",
44 | "def subset(X: dict, keys: set) -> dict:\n",
45 | " return {k:v for k,v in X.items() if k in keys}\n",
46 | "\n",
47 | "# an example\n",
48 | "sample_dict = {'a': 1, 'b':2, 'c':3, 'd':4, 'e':5}\n",
49 | "sample_keys = {'b','d'}\n",
50 | "subset(sample_dict, sample_keys)"
51 | ],
52 | "metadata": {
53 | "colab": {
54 | "base_uri": "https://localhost:8080/"
55 | },
56 | "id": "zGE_EknpYm56",
57 | "outputId": "affa41c1-e147-4ad9-da0f-5d11143d547b"
58 | },
59 | "execution_count": 64,
60 | "outputs": [
61 | {
62 | "output_type": "execute_result",
63 | "data": {
64 | "text/plain": [
65 | "{'b': 2, 'd': 4}"
66 | ]
67 | },
68 | "metadata": {},
69 | "execution_count": 64
70 | }
71 | ]
72 | },
73 | {
74 | "cell_type": "code",
75 | "execution_count": 65,
76 | "metadata": {
77 | "id": "pWTYugr-NxLV"
78 | },
79 | "outputs": [],
80 | "source": [
81 | "class ComputeGraph:\n",
82 | " def __init__(self, *inputs, debug=False):\n",
83 | " self.g = {input:None for input in inputs}\n",
84 | " self.inputs = set(inputs)\n",
85 | " self.debug = debug\n",
86 | "\n",
87 | " def __setitem__(self, var, operation):\n",
88 | " # do not allow placing a new op on a used name\n",
89 | " assert var not in self.g, f\"var name {var} already exists\"\n",
90 | "\n",
91 | " # check that all parents of new operation already exist in the graph\n",
92 | " # for sets, A < B holds if B contains A\n",
93 | " assert set(operation.inputs) <= set(self.g), f\"Vars {set(operation.inputs) - set(self.g)} do not exist in graph\"\n",
94 | " # add op to graph\n",
95 | " self.g[var] = operation\n",
96 | "\n",
97 | " def __call__(self, **kwords):\n",
98 | " return self.forward(**kwords)\n",
99 | "\n",
100 | " def forward(self, **kwords):\n",
101 | " # check given inputs are exactly the ones expected by the graph\n",
102 | " assert set(kwords.keys()) == self.inputs, f\"Given inputs {kwords} do not match expected inputs {self.inputs}\"\n",
103 | "\n",
104 | " # initialize values dict\n",
105 | " self.values = {var:{\"f\":0, \"df\":0} for var in self.g}\n",
106 | "\n",
107 | "\n",
108 | " # travel the graph\n",
109 | " ## Note: this exploits std dict remembering insertion order since 3.7\n",
110 | " for var, op in self.g.items():\n",
111 | "\n",
112 | " if var in self.inputs:\n",
113 | " # value of an input is the passed one\n",
114 | " value = kwords[var]\n",
115 | " else:\n",
116 | " # value of an op requires evaluating it\n",
117 | " value = op.f(self.values)\n",
118 | "\n",
119 | " # debug, possibly\n",
120 | " if self.debug:\n",
121 | " print(\"Forward:\\t\", var,\"=\",value)\n",
122 | "\n",
123 | " # save it\n",
124 | " self.values[var][\"f\"] = value\n",
125 | "\n",
126 | " # only last one will not be overwritten\n",
127 | " self.output = var\n",
128 | "\n",
129 | " # return the last one\n",
130 | " return value\n",
131 | "\n",
132 | " def backwards(self):\n",
133 | " # travel the graph in reverse order\n",
134 | " for var, op in reversed(self.g.items()):\n",
135 | "\n",
136 | " # the output is the last op, hence df is 1\n",
137 | " if var == self.output:\n",
138 | " self.values[var][\"df\"] = 1\n",
139 | "\n",
140 | " accumulated_df = self.values[var][\"df\"]\n",
141 | "\n",
142 | " # if it's an input, skip accumulating dfs (inputs have no parents)\n",
143 | " if var in self.inputs:\n",
144 | " continue\n",
145 | "\n",
146 | " # accumulate derivatives and add to parents\n",
147 | " for parent, derivative in op.df().items():\n",
148 | " # dF/dx = dF/dy * dy/dx\n",
149 | " # dF/dy is accumulated_df\n",
150 | " # dy/dx is the derivative from op\n",
151 | " self.values[parent][\"df\"] += accumulated_df * derivative\n",
152 | "\n",
153 | " # debug, possibly\n",
154 | " if self.debug:\n",
155 | " print(\"Backwards:\\t\", var,\"->\",parent,\"=\",accumulated_df,\"*\",derivative)\n",
156 | "\n",
157 | " # return derivatives for inputs\n",
158 | " return {var:values[\"df\"] for var,values in self.values.items() if var in self.inputs}\n",
159 | "\n",
160 | " def plot(self, vertical=True, use_op_names=True):\n",
161 | " d = Digraph(format='svg', graph_attr={'rankdir': 'TB' if vertical else 'LR'})\n",
162 | "\n",
163 | " for var, op in self.g.items():\n",
164 | " if var in self.inputs:\n",
165 | " d.node(var, var + \"| Input\", shape='record')\n",
166 | " continue\n",
167 | " if use_op_names:\n",
168 | " label = \"|\" + op.get_op_name()\n",
169 | " else:\n",
170 | " label = \" = \" + str(op)\n",
171 | " d.node(var, var + label, shape='record')\n",
172 | " d.edges([(parent, var) for parent in op.inputs])\n",
173 | "\n",
174 | " return d"
175 | ]
176 | },
177 | {
178 | "cell_type": "code",
179 | "source": [
180 | "class Operation:\n",
181 | " def __init__(self, *inputs):\n",
182 | " self.inputs = inputs\n",
183 | "\n",
184 | " def f(self, value_graph):\n",
185 | " self.last_inputs = [value_graph[x][\"f\"] for x in self.inputs]\n",
186 | " return self.forward(*self.last_inputs)\n",
187 | "\n",
188 | " def df(self):\n",
189 | " gradient = self.backwards(*self.last_inputs)\n",
190 | "\n",
191 | " # if it's a scalar, convert to a 1-dim gradient\n",
192 | " if not isinstance(gradient,tuple):\n",
193 | " gradient = (gradient,)\n",
194 | " return {var:derivative for var,derivative in zip(self.inputs, gradient)}\n",
195 | "\n",
196 | " def get_op_name(self):\n",
197 | " # for plotting purposes\n",
198 | " return self.__class__.__name__\n",
199 | "\n",
200 | " def __str__(self):\n",
201 | " # for plotting purposes\n",
202 | " return self.FMT % self.inputs"
203 | ],
204 | "metadata": {
205 | "id": "f50enybrUeTw"
206 | },
207 | "execution_count": 66,
208 | "outputs": []
209 | },
210 | {
211 | "cell_type": "code",
212 | "source": [
213 | "class Power(Operation):\n",
214 | " FMT = \"%s ^ %s\"\n",
215 | "\n",
216 | " def forward(self, base, exp):\n",
217 | " \"\"\"base ^ exp \"\"\"\n",
218 | " return base**exp\n",
219 | "\n",
220 | " def backwards(self, base, exp):\n",
221 | " \"\"\"\n",
222 | " derivative of x^k is k*x^{k-1}\n",
223 | " derivative of k^x is k^x * log(k)\n",
224 | " \"\"\"\n",
225 | " return (\n",
226 | " exp* base**(exp-1),\n",
227 | " base**exp * math.log(base)\n",
228 | " )"
229 | ],
230 | "metadata": {
231 | "id": "xFWW0iF6cHXW"
232 | },
233 | "execution_count": 67,
234 | "outputs": []
235 | },
236 | {
237 | "cell_type": "code",
238 | "source": [
239 | "class Square(Operation):\n",
240 | " FMT = \"%s ^ 2\"\n",
241 | "\n",
242 | " def forward(self, base):\n",
243 | " return base**2\n",
244 | "\n",
245 | " def backwards(self, base):\n",
246 | " return 2*base"
247 | ],
248 | "metadata": {
249 | "id": "f0yIh69lmnMh"
250 | },
251 | "execution_count": 68,
252 | "outputs": []
253 | },
254 | {
255 | "cell_type": "code",
256 | "source": [
257 | "class Product(Operation):\n",
258 | " FMT = \"%s * %s\"\n",
259 | "\n",
260 | " def forward(self, a, b):\n",
261 | " \"\"\"a * b\"\"\"\n",
262 | " return a * b\n",
263 | "\n",
264 | " def backwards(self, a, b):\n",
265 | " \"\"\"\n",
266 | " derivative of x*b is b\n",
267 | " derivative of a*x is a\n",
268 | " \"\"\"\n",
269 | " return (b,a)"
270 | ],
271 | "metadata": {
272 | "id": "NKXiXdDTc4jX"
273 | },
274 | "execution_count": 69,
275 | "outputs": []
276 | },
277 | {
278 | "cell_type": "code",
279 | "source": [
280 | "class Cosine(Operation):\n",
281 | " FMT = \"cos(%s)\"\n",
282 | "\n",
283 | " def forward(self, x):\n",
284 | " \"\"\"cos(x)\"\"\"\n",
285 | " return math.cos(x)\n",
286 | "\n",
287 | " def backwards(self, x):\n",
288 | " \"\"\"\n",
289 | " derivative of cos(x) is -sin(x)\n",
290 | " \"\"\"\n",
291 | " return -math.sin(x)"
292 | ],
293 | "metadata": {
294 | "id": "S3hMl-oOdLEV"
295 | },
296 | "execution_count": 70,
297 | "outputs": []
298 | },
299 | {
300 | "cell_type": "markdown",
301 | "source": [
302 | "## Código de prueba"
303 | ],
304 | "metadata": {
305 | "id": "5bsMw7Lrfh-v"
306 | }
307 | },
308 | {
309 | "cell_type": "markdown",
310 | "source": [
311 | "Empecemos con una fácil: $x^{cos(x)}$\n",
312 | "\n",
313 | "Veámosla para $x=3$, que debería dar, según [Wolfram Alpha](https://www.wolframalpha.com/input?i=derivative+of+x%5E%7Bcos%28x%29%7D+at+x%3D3&lang=es), $-0.163465$\n",
314 | "\n",
315 | "Lo vamos a plantear de la siguiente manera:\n",
316 | "```\n",
317 | "x\n",
318 | "y = cos(x)\n",
319 | "z = x^y\n",
320 | "```\n",
321 | "\n",
322 | "Donde z es la salida"
323 | ],
324 | "metadata": {
325 | "id": "AItU7-U3flae"
326 | }
327 | },
328 | {
329 | "cell_type": "code",
330 | "source": [
331 | "cg = ComputeGraph(\"x\", debug=True)\n",
332 | "cg[\"y\"] = Cosine(\"x\")\n",
333 | "cg[\"z\"] = Power(\"x\", \"y\")"
334 | ],
335 | "metadata": {
336 | "id": "A_Jxd_tVfPHT"
337 | },
338 | "execution_count": 71,
339 | "outputs": []
340 | },
341 | {
342 | "cell_type": "code",
343 | "source": [
344 | "cg(x=3)"
345 | ],
346 | "metadata": {
347 | "colab": {
348 | "base_uri": "https://localhost:8080/"
349 | },
350 | "id": "5c464jVyghEg",
351 | "outputId": "2e8fea9f-687f-4214-a748-2b94efbacf70"
352 | },
353 | "execution_count": 72,
354 | "outputs": [
355 | {
356 | "output_type": "stream",
357 | "name": "stdout",
358 | "text": [
359 | "Forward:\t x = 3\n",
360 | "Forward:\t y = -0.9899924966004454\n",
361 | "Forward:\t z = 0.3370183421202187\n"
362 | ]
363 | },
364 | {
365 | "output_type": "execute_result",
366 | "data": {
367 | "text/plain": [
368 | "0.3370183421202187"
369 | ]
370 | },
371 | "metadata": {},
372 | "execution_count": 72
373 | }
374 | ]
375 | },
376 | {
377 | "cell_type": "code",
378 | "source": [
379 | "cg.backwards()"
380 | ],
381 | "metadata": {
382 | "colab": {
383 | "base_uri": "https://localhost:8080/"
384 | },
385 | "id": "ofPjNs8Pg1WI",
386 | "outputId": "388a9e70-74be-4f72-fa3e-814cc6dab9ca"
387 | },
388 | "execution_count": 73,
389 | "outputs": [
390 | {
391 | "output_type": "stream",
392 | "name": "stdout",
393 | "text": [
394 | "Backwards:\t z -> x = 1 * -0.11121520997191278\n",
395 | "Backwards:\t z -> y = 1 * 0.3702524921598255\n",
396 | "Backwards:\t y -> x = 0.3702524921598255 * -0.1411200080598672\n"
397 | ]
398 | },
399 | {
400 | "output_type": "execute_result",
401 | "data": {
402 | "text/plain": [
403 | "{'x': -0.16346524464969328}"
404 | ]
405 | },
406 | "metadata": {},
407 | "execution_count": 73
408 | }
409 | ]
410 | },
411 | {
412 | "cell_type": "markdown",
413 | "source": [
414 | "Ahora vamos con algo un poquito más dificil, $x^{cos(y) \\cdot z^2}$. Probémoslo para $x=2, y=3, z=1.5$, que según [Wolfram Alpha](https://www.wolframalpha.com/input?i=derivatives+of+x%5E%7Bcos%28y%29*z%5E2%7D&lang=es), debería dar [-0.237818](https://www.wolframalpha.com/input?i=x%5E%28-1+%2B+z%5E2+cos%28y%29%29+z%5E2+cos%28y%29+at+x%3D2%2C+y%3D3%2C+z%3D1.5&lang=es), [-0.0469956](https://www.wolframalpha.com/input?i=-x%5E%28z%5E2+cos%28y%29%29+z%5E2+log%28x%29+sen%28y%29+at+x%3D2%2C+y%3D3%2C+z%3D1.5&lang=es), [-0.439581](https://www.wolframalpha.com/input?i=2+x%5E%28z%5E2+cos%28y%29%29+z+cos%28y%29+log%28x%29+at+x%3D2%2C+y%3D3%2C+z%3D1.5&lang=es)\n",
415 | "\n",
416 | "\n",
417 | "Lo vamos a hacer así:\n",
418 | "```\n",
419 | "x, y, z\n",
420 | "a = cos(y)\n",
421 | "b = z^2\n",
422 | "c = a*b\n",
423 | "d = x^c\n",
424 | "```\n",
425 | "\n",
426 | "Donde d es la salida"
427 | ],
428 | "metadata": {
429 | "id": "Cp3feXB-k0OY"
430 | }
431 | },
432 | {
433 | "cell_type": "code",
434 | "source": [
435 | "cg = ComputeGraph(\"x\",\"y\",\"z\")\n",
436 | "cg[\"a\"] = Cosine(\"y\")\n",
437 | "cg[\"b\"] = Square(\"z\")\n",
438 | "cg[\"c\"] = Product(\"a\",\"b\")\n",
439 | "cg[\"d\"] = Power(\"x\",\"c\")\n",
440 | "\n",
441 | "cg(x=2,y=3,z=1.5)"
442 | ],
443 | "metadata": {
444 | "colab": {
445 | "base_uri": "https://localhost:8080/"
446 | },
447 | "id": "X4Y56PCAjWcq",
448 | "outputId": "158589e3-dc11-4866-cb38-9859ded780ad"
449 | },
450 | "execution_count": 74,
451 | "outputs": [
452 | {
453 | "output_type": "execute_result",
454 | "data": {
455 | "text/plain": [
456 | "0.21353091784478878"
457 | ]
458 | },
459 | "metadata": {},
460 | "execution_count": 74
461 | }
462 | ]
463 | },
464 | {
465 | "cell_type": "code",
466 | "source": [
467 | "cg.backwards()"
468 | ],
469 | "metadata": {
470 | "colab": {
471 | "base_uri": "https://localhost:8080/"
472 | },
473 | "id": "5FrSfAkAm8R-",
474 | "outputId": "3df69733-ac1c-4f6b-d61c-18c67bd7b903"
475 | },
476 | "execution_count": 75,
477 | "outputs": [
478 | {
479 | "output_type": "execute_result",
480 | "data": {
481 | "text/plain": [
482 | "{'x': -0.23781825726586542,\n",
483 | " 'y': -0.04699561514027202,\n",
484 | " 'z': -0.43958147869203823}"
485 | ]
486 | },
487 | "metadata": {},
488 | "execution_count": 75
489 | }
490 | ]
491 | },
492 | {
493 | "cell_type": "markdown",
494 | "source": [
495 | "Ploteemos este último grafo de computo:"
496 | ],
497 | "metadata": {
498 | "id": "mUgLUnURGIRX"
499 | }
500 | },
501 | {
502 | "cell_type": "markdown",
503 | "source": [
504 | "Primero usamos los tipos de las operaciones (con `use_op_names=True`, el valor default)"
505 | ],
506 | "metadata": {
507 | "id": "MZQ4DX3tGNU-"
508 | }
509 | },
510 | {
511 | "cell_type": "code",
512 | "source": [
513 | "cg.plot(use_op_names=True)"
514 | ],
515 | "metadata": {
516 | "colab": {
517 | "base_uri": "https://localhost:8080/",
518 | "height": 373
519 | },
520 | "id": "q1j-HkzO_gXm",
521 | "outputId": "ec7c3917-d875-433a-ae6a-ffea08ea5d14"
522 | },
523 | "execution_count": 76,
524 | "outputs": [
525 | {
526 | "output_type": "execute_result",
527 | "data": {
528 | "image/svg+xml": "\n\n\n\n\n",
529 | "text/plain": [
530 | ""
531 | ]
532 | },
533 | "metadata": {},
534 | "execution_count": 76
535 | }
536 | ]
537 | },
538 | {
539 | "cell_type": "markdown",
540 | "source": [
541 | "Ahora en formato más \"humano\" con `use_op_names=False`"
542 | ],
543 | "metadata": {
544 | "id": "tCWVtsupGS-5"
545 | }
546 | },
547 | {
548 | "cell_type": "code",
549 | "source": [
550 | "cg.plot(use_op_names=False)"
551 | ],
552 | "metadata": {
553 | "colab": {
554 | "base_uri": "https://localhost:8080/",
555 | "height": 373
556 | },
557 | "id": "fsJLuVmGDV4Z",
558 | "outputId": "4991364a-a7e0-4b01-fdfc-aea1caecd231"
559 | },
560 | "execution_count": 77,
561 | "outputs": [
562 | {
563 | "output_type": "execute_result",
564 | "data": {
565 | "image/svg+xml": "\n\n\n\n\n",
566 | "text/plain": [
567 | ""
568 | ]
569 | },
570 | "metadata": {},
571 | "execution_count": 77
572 | }
573 | ]
574 | },
575 | {
576 | "cell_type": "markdown",
577 | "source": [
578 | "Si cambiamos el parámetro `vertical=False` pasa a ser horizontal"
579 | ],
580 | "metadata": {
581 | "id": "ys2zihXwGdLI"
582 | }
583 | },
584 | {
585 | "cell_type": "code",
586 | "source": [
587 | "cg.plot(use_op_names=False,vertical=False)"
588 | ],
589 | "metadata": {
590 | "colab": {
591 | "base_uri": "https://localhost:8080/",
592 | "height": 217
593 | },
594 | "id": "sr0EBtoIGcyZ",
595 | "outputId": "41771d7e-13af-4ee6-cc20-7bda18ede31c"
596 | },
597 | "execution_count": 78,
598 | "outputs": [
599 | {
600 | "output_type": "execute_result",
601 | "data": {
602 | "image/svg+xml": "\n\n\n\n\n",
603 | "text/plain": [
604 | ""
605 | ]
606 | },
607 | "metadata": {},
608 | "execution_count": 78
609 | }
610 | ]
611 | }
612 | ]
613 | }
--------------------------------------------------------------------------------
/clases/_repaso_2/0_2_Operatoria_básica_con_NumPy_+_cipher.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "name": "0-2 - Operatoria básica con NumPy + cipher",
7 | "provenance": []
8 | },
9 | "kernelspec": {
10 | "name": "python3",
11 | "display_name": "Python 3"
12 | },
13 | "language_info": {
14 | "name": "python"
15 | }
16 | },
17 | "cells": [
18 | {
19 | "cell_type": "markdown",
20 | "source": [
21 | "# NumPy\n",
22 | "\n",
23 | "**Num**erical **Py**thon es nuestra librería de cabecera, provee la estructura básica *core* que vamos a usar para casi todas las operaciones: el `numpy array`. Este es el vector/matriz/tensor que conocemos en matemática, con todas las propiedades típicas y esperables:\n",
24 | "\n",
25 | "* Todos los elementos tienen el mismo formato (real, entero, etc.)\n",
26 | "* La dimensión se fija al momento de definir la variable (pueden cambiar los valores, no las formas).\n",
27 | "* Si hacemos cosas como multiplicar por un escalar, la operación se propaga por *broadcasting* a todos los elementos del vector.\n",
28 | "\n",
29 | "Recomendamos fuertemente la lectura (parcial, no total) de la documentación oficial de la librería, tanto la [guía de uso](https://numpy.org/doc/stable/user/whatisnumpy.html) como la [lista de funcionalidades](https://numpy.org/doc/stable/reference/index.html).\n",
30 | "\n",
31 | "[Aquí](https://towardsdatascience.com/the-ultimate-beginners-guide-to-numpy-f5a2f99aef54) hay un buen resumen sobre inicios con NumPy, de mayor extensión que el presente.\n",
32 | "\n",
33 | "[Aquí](https://www.datacamp.com/community/blog/python-numpy-cheat-sheet) hay un pequeño cheatsheet sobre métodos y funciones disponibles en el paquete."
34 | ],
35 | "metadata": {
36 | "id": "rIiWvX_DvPUU"
37 | }
38 | },
39 | {
40 | "cell_type": "markdown",
41 | "metadata": {
42 | "id": "bPTR8rkYaKfz"
43 | },
44 | "source": [
45 | "## Creación de `arrays`\n",
46 | "\n",
47 | "Importante: recordemos que los arrays no son necesariamente unidimensionales, pueden ser matrices o tensores de rango superior."
48 | ]
49 | },
50 | {
51 | "cell_type": "code",
52 | "metadata": {
53 | "id": "9Jk4mfPHvvoJ",
54 | "colab": {
55 | "base_uri": "https://localhost:8080/"
56 | },
57 | "outputId": "537ceb90-594d-4d3e-b291-70fd6e615860"
58 | },
59 | "source": [
60 | "import numpy as np\n",
61 | "\n",
62 | "a = np.array([[1,2],[3,4]]) # en base a un iterable de python, como es una lista\n",
63 | "b = np.arange(start=-1,stop=5, step=2) # una sucesión de elementos, notar que el stop no es incluido\n",
64 | "c = np.linspace(start=0, stop=10, num=5) # 5 puntos uniformemente distribuidos sobre [0,10], incluido extremos\n",
65 | "d = np.empty(shape=(2,3)) # una matriz de 2x3 sin inicializar (es muy rápido)\n",
66 | "e = np.zeros((2,3)) # una matriz de 2x3 inicializada con 0s\n",
67 | "\n",
68 | "print(a)\n",
69 | "print(b)\n",
70 | "print(c)\n",
71 | "print(d)\n",
72 | "print(e)"
73 | ],
74 | "execution_count": null,
75 | "outputs": [
76 | {
77 | "output_type": "stream",
78 | "name": "stdout",
79 | "text": [
80 | "[[1 2]\n",
81 | " [3 4]]\n",
82 | "[-1 1 3]\n",
83 | "[ 0. 2.5 5. 7.5 10. ]\n",
84 | "[[5.e-324 1.e-323 0.e+000]\n",
85 | " [5.e-324 1.e-323 0.e+000]]\n",
86 | "[[0. 0. 0.]\n",
87 | " [0. 0. 0.]]\n"
88 | ]
89 | }
90 | ]
91 | },
92 | {
93 | "cell_type": "markdown",
94 | "metadata": {
95 | "id": "pxUcJIlMQAkT"
96 | },
97 | "source": [
98 | "## Caracterización de un `array`"
99 | ]
100 | },
101 | {
102 | "cell_type": "code",
103 | "metadata": {
104 | "colab": {
105 | "base_uri": "https://localhost:8080/"
106 | },
107 | "id": "ck7m5YTdQE08",
108 | "outputId": "2c2236bc-4e63-47b8-967d-79daebd73d61"
109 | },
110 | "source": [
111 | "# uso de shape para observar las dimensiones de un array\n",
112 | "print(\"a tiene shape:\",a.shape)\n",
113 | "print(\"c tiene shape:\",c.shape)\n",
114 | "print(\"d tiene shape:\",d.shape)\n",
115 | "\n",
116 | "\n",
117 | "# len nos provee el largo del array (que suele ser la cantidad de filas)\n",
118 | "print(\"a tiene len:\",len(a))\n",
119 | "\n",
120 | "# size nos provee la cantidad de elementos del array\n",
121 | "print(\"a tiene size:\",a.size)"
122 | ],
123 | "execution_count": null,
124 | "outputs": [
125 | {
126 | "output_type": "stream",
127 | "name": "stdout",
128 | "text": [
129 | "a tiene shape: (2, 2)\n",
130 | "c tiene shape: (5,)\n",
131 | "d tiene shape: (2, 3)\n",
132 | "a tiene len: 2\n",
133 | "a tiene size: 4\n"
134 | ]
135 | }
136 | ]
137 | },
138 | {
139 | "cell_type": "markdown",
140 | "metadata": {
141 | "id": "hrkjWYUIFNXo"
142 | },
143 | "source": [
144 | "## Operaciones simples con `arrays`\n",
145 | "\n",
146 | "Algunas cuestiones importantes en NumPy son que:\n",
147 | "\n",
148 | "* Las operaciones se asumen elemento a elemento (*element-wise*), por ejemplo si $x,y \\in \\mathbb{R}^5$ entonces también $x · y = (x_1 · y_1, \\dots, x_5·y_5) \\in \\mathbb{R}^5$. Si se quiere el producto escalar, el operador `@` (de producto matricial) o la función `dot` son ambas opciones correctas.\n",
149 | "* Si las dimensiones no coinciden, se intenta \"expandir\" el menor a la dimensión correcta para *propagar* la operación. A esto se lo conoce como *broadcasting* y explica por qué si $x \\in \\mathbb{R}^3$ entonces $x + 5 = (x_1+5, x_2+5,x_3+5) \\in \\mathbb{R}^3$."
150 | ]
151 | },
152 | {
153 | "cell_type": "code",
154 | "metadata": {
155 | "colab": {
156 | "base_uri": "https://localhost:8080/"
157 | },
158 | "id": "NGhlf_M9FRLF",
159 | "outputId": "a886de51-de49-42f1-c9d8-72836772324e"
160 | },
161 | "source": [
162 | "# definimos algunos arrays primero\n",
163 | "x1 = np.array([1,2,3,4])\n",
164 | "x2 = np.array([5,6,7,8])\n",
165 | "m1 = np.array([[1,2],[3,4],[5,6]])\n",
166 | "y1 = np.array([5,6])\n",
167 | "print(\"x1 =\",x1)\n",
168 | "print(\"x2 =\",x2)\n",
169 | "print(\"m1 =\",m1)\n",
170 | "print(\"y1 =\",y1)"
171 | ],
172 | "execution_count": null,
173 | "outputs": [
174 | {
175 | "output_type": "stream",
176 | "name": "stdout",
177 | "text": [
178 | "x1 = [1 2 3 4]\n",
179 | "x2 = [5 6 7 8]\n",
180 | "m1 = [[1 2]\n",
181 | " [3 4]\n",
182 | " [5 6]]\n",
183 | "y1 = [5 6]\n"
184 | ]
185 | }
186 | ]
187 | },
188 | {
189 | "cell_type": "code",
190 | "metadata": {
191 | "colab": {
192 | "base_uri": "https://localhost:8080/"
193 | },
194 | "id": "FmcKruNbTtwB",
195 | "outputId": "73dfafca-1654-4c32-d4fe-7af4ceb86596"
196 | },
197 | "source": [
198 | "# suma de vectores\n",
199 | "x1+x2"
200 | ],
201 | "execution_count": null,
202 | "outputs": [
203 | {
204 | "output_type": "execute_result",
205 | "data": {
206 | "text/plain": [
207 | "array([ 6, 8, 10, 12])"
208 | ]
209 | },
210 | "metadata": {},
211 | "execution_count": 113
212 | }
213 | ]
214 | },
215 | {
216 | "cell_type": "code",
217 | "metadata": {
218 | "colab": {
219 | "base_uri": "https://localhost:8080/"
220 | },
221 | "id": "g7gb4mmyTw_P",
222 | "outputId": "e972a534-bb03-4823-dc4c-b83b5c014115"
223 | },
224 | "source": [
225 | "# suma con broadcasting\n",
226 | "x1+5"
227 | ],
228 | "execution_count": null,
229 | "outputs": [
230 | {
231 | "output_type": "execute_result",
232 | "data": {
233 | "text/plain": [
234 | "array([6, 7, 8, 9])"
235 | ]
236 | },
237 | "metadata": {},
238 | "execution_count": 114
239 | }
240 | ]
241 | },
242 | {
243 | "cell_type": "code",
244 | "metadata": {
245 | "colab": {
246 | "base_uri": "https://localhost:8080/"
247 | },
248 | "id": "mlLy1GtVT8Jm",
249 | "outputId": "1d0507f3-99db-4cd0-b66d-0bf7e5cbfa26"
250 | },
251 | "source": [
252 | "# producto de vectores\n",
253 | "# observar que es elemento a elemento\n",
254 | "x1*x2"
255 | ],
256 | "execution_count": null,
257 | "outputs": [
258 | {
259 | "output_type": "execute_result",
260 | "data": {
261 | "text/plain": [
262 | "array([ 5, 12, 21, 32])"
263 | ]
264 | },
265 | "metadata": {},
266 | "execution_count": 115
267 | }
268 | ]
269 | },
270 | {
271 | "cell_type": "code",
272 | "metadata": {
273 | "colab": {
274 | "base_uri": "https://localhost:8080/"
275 | },
276 | "id": "GqL3zqfQT-Si",
277 | "outputId": "702f4f02-b6aa-4fde-dce1-eff049212ef6"
278 | },
279 | "source": [
280 | "# producto escalar\n",
281 | "x1 @ x2"
282 | ],
283 | "execution_count": null,
284 | "outputs": [
285 | {
286 | "output_type": "execute_result",
287 | "data": {
288 | "text/plain": [
289 | "70"
290 | ]
291 | },
292 | "metadata": {},
293 | "execution_count": 116
294 | }
295 | ]
296 | },
297 | {
298 | "cell_type": "code",
299 | "metadata": {
300 | "colab": {
301 | "base_uri": "https://localhost:8080/"
302 | },
303 | "id": "lqaif5TwB4D3",
304 | "outputId": "0b394b29-ae7f-4096-84b5-e27c63c5ce07"
305 | },
306 | "source": [
307 | "# e^x1 vectorizado\n",
308 | "np.exp(x1)"
309 | ],
310 | "execution_count": null,
311 | "outputs": [
312 | {
313 | "output_type": "execute_result",
314 | "data": {
315 | "text/plain": [
316 | "array([ 2.71828183, 7.3890561 , 20.08553692, 54.59815003])"
317 | ]
318 | },
319 | "metadata": {},
320 | "execution_count": 117
321 | }
322 | ]
323 | },
324 | {
325 | "cell_type": "code",
326 | "metadata": {
327 | "colab": {
328 | "base_uri": "https://localhost:8080/"
329 | },
330 | "id": "AUb18VK6CBki",
331 | "outputId": "147d266f-528d-48d0-c8ad-086472a7a618"
332 | },
333 | "source": [
334 | "# suma acumulativa de valores de x1\n",
335 | "np.cumsum(x1)"
336 | ],
337 | "execution_count": null,
338 | "outputs": [
339 | {
340 | "output_type": "execute_result",
341 | "data": {
342 | "text/plain": [
343 | "array([ 1, 3, 6, 10])"
344 | ]
345 | },
346 | "metadata": {},
347 | "execution_count": 118
348 | }
349 | ]
350 | },
351 | {
352 | "cell_type": "code",
353 | "metadata": {
354 | "colab": {
355 | "base_uri": "https://localhost:8080/"
356 | },
357 | "id": "TcTpMVD1UAJh",
358 | "outputId": "ba51138a-6e45-43e2-e959-81c3c9e44526"
359 | },
360 | "source": [
361 | "# suma de un vector y una matriz con broadcasting\n",
362 | "# observar que se 'propaga' fila a fila\n",
363 | "m1 + y1"
364 | ],
365 | "execution_count": null,
366 | "outputs": [
367 | {
368 | "output_type": "execute_result",
369 | "data": {
370 | "text/plain": [
371 | "array([[ 6, 8],\n",
372 | " [ 8, 10],\n",
373 | " [10, 12]])"
374 | ]
375 | },
376 | "metadata": {},
377 | "execution_count": 119
378 | }
379 | ]
380 | },
381 | {
382 | "cell_type": "code",
383 | "metadata": {
384 | "colab": {
385 | "base_uri": "https://localhost:8080/"
386 | },
387 | "id": "F50Y53EXUBtX",
388 | "outputId": "aef615d5-61f7-4723-daf7-6167e72cf132"
389 | },
390 | "source": [
391 | "# trasposición de una matriz\n",
392 | "m1.T"
393 | ],
394 | "execution_count": null,
395 | "outputs": [
396 | {
397 | "output_type": "execute_result",
398 | "data": {
399 | "text/plain": [
400 | "array([[1, 3, 5],\n",
401 | " [2, 4, 6]])"
402 | ]
403 | },
404 | "metadata": {},
405 | "execution_count": 120
406 | }
407 | ]
408 | },
409 | {
410 | "cell_type": "code",
411 | "source": [
412 | "# inversa de una matriz\n",
413 | "mm = np.array([1,2,3,4,5,7,6,8,9]).reshape((3,3))\n",
414 | "print(mm)\n",
415 | "mm_inv = np.linalg.inv(mm)\n",
416 | "print(mm_inv)"
417 | ],
418 | "metadata": {
419 | "colab": {
420 | "base_uri": "https://localhost:8080/"
421 | },
422 | "id": "ZKYXL66hv0Wm",
423 | "outputId": "288d2430-84f3-48ca-af91-3e5503e577b9"
424 | },
425 | "execution_count": null,
426 | "outputs": [
427 | {
428 | "output_type": "stream",
429 | "name": "stdout",
430 | "text": [
431 | "[[1 2 3]\n",
432 | " [4 5 7]\n",
433 | " [6 8 9]]\n",
434 | "[[-1.57142857 0.85714286 -0.14285714]\n",
435 | " [ 0.85714286 -1.28571429 0.71428571]\n",
436 | " [ 0.28571429 0.57142857 -0.42857143]]\n"
437 | ]
438 | }
439 | ]
440 | },
441 | {
442 | "cell_type": "code",
443 | "source": [
444 | "# qué pasa si multiplicamos las dos matrices?\n",
445 | "mm * mm_inv"
446 | ],
447 | "metadata": {
448 | "colab": {
449 | "base_uri": "https://localhost:8080/"
450 | },
451 | "id": "IVbI8xMyxuNQ",
452 | "outputId": "5de8c1f8-3e3c-4811-c58e-4b2ad8305d7a"
453 | },
454 | "execution_count": null,
455 | "outputs": [
456 | {
457 | "output_type": "execute_result",
458 | "data": {
459 | "text/plain": [
460 | "array([[-1.57142857, 1.71428571, -0.42857143],\n",
461 | " [ 3.42857143, -6.42857143, 5. ],\n",
462 | " [ 1.71428571, 4.57142857, -3.85714286]])"
463 | ]
464 | },
465 | "metadata": {},
466 | "execution_count": 122
467 | }
468 | ]
469 | },
470 | {
471 | "cell_type": "code",
472 | "source": [
473 | "# lo que pasa es que ese no es el producto matricial, y ahora?\n",
474 | "mm @ mm_inv"
475 | ],
476 | "metadata": {
477 | "colab": {
478 | "base_uri": "https://localhost:8080/"
479 | },
480 | "id": "Rj6BB0qExxy3",
481 | "outputId": "bb13b906-3996-49b9-b8f6-3251147524ba"
482 | },
483 | "execution_count": null,
484 | "outputs": [
485 | {
486 | "output_type": "execute_result",
487 | "data": {
488 | "text/plain": [
489 | "array([[ 1.00000000e+00, -1.11022302e-16, -1.11022302e-16],\n",
490 | " [ 0.00000000e+00, 1.00000000e+00, -1.11022302e-16],\n",
491 | " [ 0.00000000e+00, -3.33066907e-16, 1.00000000e+00]])"
492 | ]
493 | },
494 | "metadata": {},
495 | "execution_count": 123
496 | }
497 | ]
498 | },
499 | {
500 | "cell_type": "code",
501 | "source": [
502 | "# ¿qué pasa si queremos invertir una matriz que no tiene inversa?\n",
503 | "xd = np.array([1,2,3,4,5,6]).reshape((2,3))\n",
504 | "print(xd)\n",
505 | "xd_inv = np.linalg.inv(xd)"
506 | ],
507 | "metadata": {
508 | "colab": {
509 | "base_uri": "https://localhost:8080/",
510 | "height": 435
511 | },
512 | "id": "Qd5qUxZGzfkY",
513 | "outputId": "680bd41c-ff4b-4517-b0ac-77da28cae6df"
514 | },
515 | "execution_count": null,
516 | "outputs": [
517 | {
518 | "output_type": "stream",
519 | "name": "stdout",
520 | "text": [
521 | "[[1 2 3]\n",
522 | " [4 5 6]]\n"
523 | ]
524 | },
525 | {
526 | "output_type": "error",
527 | "ename": "LinAlgError",
528 | "evalue": "ignored",
529 | "traceback": [
530 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
531 | "\u001b[0;31mLinAlgError\u001b[0m Traceback (most recent call last)",
532 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mxd\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marray\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m3\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m4\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m6\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreshape\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m3\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mxd\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0mxd_inv\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlinalg\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mxd\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
533 | "\u001b[0;32m<__array_function__ internals>\u001b[0m in \u001b[0;36minv\u001b[0;34m(*args, **kwargs)\u001b[0m\n",
534 | "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/numpy/linalg/linalg.py\u001b[0m in \u001b[0;36minv\u001b[0;34m(a)\u001b[0m\n\u001b[1;32m 538\u001b[0m \u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwrap\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_makearray\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 539\u001b[0m \u001b[0m_assert_stacked_2d\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 540\u001b[0;31m \u001b[0m_assert_stacked_square\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 541\u001b[0m \u001b[0mt\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mresult_t\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_commonType\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 542\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
535 | "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/numpy/linalg/linalg.py\u001b[0m in \u001b[0;36m_assert_stacked_square\u001b[0;34m(*arrays)\u001b[0m\n\u001b[1;32m 201\u001b[0m \u001b[0mm\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0ma\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 202\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mm\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mn\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 203\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mLinAlgError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Last 2 dimensions of the array must be square'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 204\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 205\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_assert_finite\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0marrays\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
536 | "\u001b[0;31mLinAlgError\u001b[0m: Last 2 dimensions of the array must be square"
537 | ]
538 | }
539 | ]
540 | },
541 | {
542 | "cell_type": "markdown",
543 | "metadata": {
544 | "id": "WiCPSUBwVGsS"
545 | },
546 | "source": [
547 | "## Indexación y vectores lógicos\n",
548 | "\n",
549 | "Llamamos indexar a acceder a elementos de un array, a partir de diversos métodos.\n",
550 | "\n",
551 | "Llamamos vector lógico es un vector compuesto por valores booleanos (*True* o *False*)."
552 | ]
553 | },
554 | {
555 | "cell_type": "markdown",
556 | "metadata": {
557 | "id": "EHiXvUbNzGf7"
558 | },
559 | "source": [
560 | "### Indexación simple\n",
561 | "\n",
562 | "Se accede a un valor específico de una o más dimensiones utilizando corchetes [ ] para indicar el valor del índice sobre cada una.\n",
563 | "\n",
564 | "**Recordar que el primer elemento siempre lleva índice 0, no 1**"
565 | ]
566 | },
567 | {
568 | "cell_type": "code",
569 | "metadata": {
570 | "colab": {
571 | "base_uri": "https://localhost:8080/"
572 | },
573 | "id": "dr0rza0iVFlr",
574 | "outputId": "bc889ead-c95b-4436-da0e-0e6c7c79325b"
575 | },
576 | "source": [
577 | "# recordemos m1\n",
578 | "m1"
579 | ],
580 | "execution_count": null,
581 | "outputs": [
582 | {
583 | "output_type": "execute_result",
584 | "data": {
585 | "text/plain": [
586 | "array([[1, 2],\n",
587 | " [3, 4],\n",
588 | " [5, 6]])"
589 | ]
590 | },
591 | "metadata": {},
592 | "execution_count": 125
593 | }
594 | ]
595 | },
596 | {
597 | "cell_type": "code",
598 | "metadata": {
599 | "colab": {
600 | "base_uri": "https://localhost:8080/"
601 | },
602 | "id": "FbjyfRMb2Nh7",
603 | "outputId": "cc3b8e81-baf1-4019-bd59-516e9feb4873"
604 | },
605 | "source": [
606 | "# accedemos al elemento de la 2º fila, 1º columna (indices 1 y 0 respectivamente)\n",
607 | "m1[1,0]"
608 | ],
609 | "execution_count": null,
610 | "outputs": [
611 | {
612 | "output_type": "execute_result",
613 | "data": {
614 | "text/plain": [
615 | "3"
616 | ]
617 | },
618 | "metadata": {},
619 | "execution_count": 126
620 | }
621 | ]
622 | },
623 | {
624 | "cell_type": "code",
625 | "metadata": {
626 | "colab": {
627 | "base_uri": "https://localhost:8080/"
628 | },
629 | "id": "VBJhZ1px2NXy",
630 | "outputId": "c4965037-6315-4784-b04b-87997cae2027"
631 | },
632 | "source": [
633 | "# si sólo indicamos 1 índice, recortamos el eje de las filas\n",
634 | "# accedemos a la 3º fila, con índice 2\n",
635 | "m1[2]"
636 | ],
637 | "execution_count": null,
638 | "outputs": [
639 | {
640 | "output_type": "execute_result",
641 | "data": {
642 | "text/plain": [
643 | "array([5, 6])"
644 | ]
645 | },
646 | "metadata": {},
647 | "execution_count": 127
648 | }
649 | ]
650 | },
651 | {
652 | "cell_type": "code",
653 | "metadata": {
654 | "colab": {
655 | "base_uri": "https://localhost:8080/"
656 | },
657 | "id": "yjfWJzkc2NNU",
658 | "outputId": "9eee48d0-48ea-4540-beb0-fee78a8aa7f3"
659 | },
660 | "source": [
661 | "# si queremos acceder a la 1º columna podemos omitir índice en las filas\n",
662 | "m1[:,0]"
663 | ],
664 | "execution_count": null,
665 | "outputs": [
666 | {
667 | "output_type": "execute_result",
668 | "data": {
669 | "text/plain": [
670 | "array([1, 3, 5])"
671 | ]
672 | },
673 | "metadata": {},
674 | "execution_count": 128
675 | }
676 | ]
677 | },
678 | {
679 | "cell_type": "markdown",
680 | "metadata": {
681 | "id": "I68k2Sfv26O3"
682 | },
683 | "source": [
684 | "### Slicing\n",
685 | "\n",
686 | "Como se vio en el ejemplo anterior, fue necesario introducir un `:` en el índice de las filas para que la sintaxis no sea inválida. En realidad, la indexación simple es un caso particular de *slicing*, esto es, indexación utilizando *rangos de valores* y se hace de la misma manera que el slicing básico de Python, esto es por ejemplo `x[1:5, 3:5]` accede a los valores de las filas 2 a 5 y columnas 4 a 5.\n",
687 | "\n",
688 | "Observar que el rango `m:n` recorre desde *m* hasta *n-1*. Por lo que si se desean los índices *x* a *y* se debe indicar el rango `x-1:y`\n",
689 | "\n",
690 | "Volviendo al ejemplo anterior, `m1[:,0]` indica que se quiere el rango completo de las filas, para columnas de índice 0, lo cual es equivalente a acceder a la primer columna."
691 | ]
692 | },
693 | {
694 | "cell_type": "code",
695 | "metadata": {
696 | "colab": {
697 | "base_uri": "https://localhost:8080/"
698 | },
699 | "id": "hAKFcUHz4ICG",
700 | "outputId": "421426da-716d-4270-de6e-7c3e92bf4074"
701 | },
702 | "source": [
703 | "# definimos una nueva matriz más grande\n",
704 | "m2 = np.arange(24).reshape(6,4)\n",
705 | "m2"
706 | ],
707 | "execution_count": null,
708 | "outputs": [
709 | {
710 | "output_type": "execute_result",
711 | "data": {
712 | "text/plain": [
713 | "array([[ 0, 1, 2, 3],\n",
714 | " [ 4, 5, 6, 7],\n",
715 | " [ 8, 9, 10, 11],\n",
716 | " [12, 13, 14, 15],\n",
717 | " [16, 17, 18, 19],\n",
718 | " [20, 21, 22, 23]])"
719 | ]
720 | },
721 | "metadata": {},
722 | "execution_count": 129
723 | }
724 | ]
725 | },
726 | {
727 | "cell_type": "code",
728 | "metadata": {
729 | "colab": {
730 | "base_uri": "https://localhost:8080/"
731 | },
732 | "id": "JnTIpjg02NBt",
733 | "outputId": "c10160f2-d4f4-4379-e100-aa3cc24291d0"
734 | },
735 | "source": [
736 | "# accedo a las filas 1 a 3 de m2, columnas 2 a 3\n",
737 | "m2[0:3, 1:3] "
738 | ],
739 | "execution_count": null,
740 | "outputs": [
741 | {
742 | "output_type": "execute_result",
743 | "data": {
744 | "text/plain": [
745 | "array([[ 1, 2],\n",
746 | " [ 5, 6],\n",
747 | " [ 9, 10]])"
748 | ]
749 | },
750 | "metadata": {},
751 | "execution_count": 130
752 | }
753 | ]
754 | },
755 | {
756 | "cell_type": "markdown",
757 | "metadata": {
758 | "id": "FIeWjDVd5UV-"
759 | },
760 | "source": [
761 | "### Indexación por array\n",
762 | "\n",
763 | "El *slicing* es, a su vez, un caso particular de indexación por array, donde se indica con un iterable, índice a índice, cúales elementos acceder sobre un eje.\n",
764 | "\n",
765 | "Observar sin embargo que el slicing preserva las dimensiones, mientras que la indexación por array no (como es de esperarse)."
766 | ]
767 | },
768 | {
769 | "cell_type": "code",
770 | "metadata": {
771 | "colab": {
772 | "base_uri": "https://localhost:8080/"
773 | },
774 | "id": "ibMC1Ws42M1e",
775 | "outputId": "8641c49f-15a8-4042-c9f5-f474ee88204b"
776 | },
777 | "source": [
778 | "# accedo a las filas 1 y 3 (pero no 2) de m2, columnas 2 y 4 (pero no 3)\n",
779 | "m2[[0,2],[1,3]]"
780 | ],
781 | "execution_count": null,
782 | "outputs": [
783 | {
784 | "output_type": "execute_result",
785 | "data": {
786 | "text/plain": [
787 | "array([ 1, 11])"
788 | ]
789 | },
790 | "metadata": {},
791 | "execution_count": 131
792 | }
793 | ]
794 | },
795 | {
796 | "cell_type": "markdown",
797 | "metadata": {
798 | "id": "pgmh0bUZ6Hr8"
799 | },
800 | "source": [
801 | "### Indexación por vectores lógicos\n",
802 | "\n",
803 | "Otra forma de indexación por array es, en vez de un vector que indique los *índices* de los elementos a acceder, un array de la misma dimensionalidad que el original y donde se indica con *True* los valores deseados, y con *False* los no deseados.\n",
804 | "\n",
805 | "En el caso particular en que el vector lógico se construye a partir del mismo array a acceder, estamos incurriendo en un filtrado por valores, pero el mecanismo permite hacer operaciones sobre distintos arrays."
806 | ]
807 | },
808 | {
809 | "cell_type": "code",
810 | "metadata": {
811 | "colab": {
812 | "base_uri": "https://localhost:8080/"
813 | },
814 | "id": "Bf61HD6r6GJg",
815 | "outputId": "669713f3-ecfb-4586-b281-3b5511b053b2"
816 | },
817 | "source": [
818 | "# quiero sólo los valores impares de m2\n",
819 | "m2[m2 % 2 != 0]"
820 | ],
821 | "execution_count": null,
822 | "outputs": [
823 | {
824 | "output_type": "execute_result",
825 | "data": {
826 | "text/plain": [
827 | "array([ 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23])"
828 | ]
829 | },
830 | "metadata": {},
831 | "execution_count": 132
832 | }
833 | ]
834 | },
835 | {
836 | "cell_type": "code",
837 | "metadata": {
838 | "colab": {
839 | "base_uri": "https://localhost:8080/"
840 | },
841 | "id": "9hS0k2xb7CX2",
842 | "outputId": "6f9cfed5-b3a0-427e-954c-23e843e8d502"
843 | },
844 | "source": [
845 | "# observemos el vector lógico utilizado, tiene la misma forma que m2\n",
846 | "m2 % 2 != 0"
847 | ],
848 | "execution_count": null,
849 | "outputs": [
850 | {
851 | "output_type": "execute_result",
852 | "data": {
853 | "text/plain": [
854 | "array([[False, True, False, True],\n",
855 | " [False, True, False, True],\n",
856 | " [False, True, False, True],\n",
857 | " [False, True, False, True],\n",
858 | " [False, True, False, True],\n",
859 | " [False, True, False, True]])"
860 | ]
861 | },
862 | "metadata": {},
863 | "execution_count": 133
864 | }
865 | ]
866 | },
867 | {
868 | "cell_type": "code",
869 | "metadata": {
870 | "colab": {
871 | "base_uri": "https://localhost:8080/"
872 | },
873 | "id": "5L9fN7-xwAWH",
874 | "outputId": "80a04cda-7f68-4bfe-ba03-3ec18666c690"
875 | },
876 | "source": [
877 | "# qué pasa si miro sólo una columna?\n",
878 | "m2[:,1] % 3 != 0"
879 | ],
880 | "execution_count": null,
881 | "outputs": [
882 | {
883 | "output_type": "execute_result",
884 | "data": {
885 | "text/plain": [
886 | "array([ True, True, False, True, True, False])"
887 | ]
888 | },
889 | "metadata": {},
890 | "execution_count": 134
891 | }
892 | ]
893 | },
894 | {
895 | "cell_type": "code",
896 | "metadata": {
897 | "colab": {
898 | "base_uri": "https://localhost:8080/"
899 | },
900 | "id": "JCE13xJQv7cq",
901 | "outputId": "2455735c-d36c-4cba-d730-d856b161a6e2"
902 | },
903 | "source": [
904 | "# eso lo puedo utilizar para (vía broadcasting) filtrar la matriz vía valores de esa columna!\n",
905 | "m2[m2[:,1] % 3 != 0]"
906 | ],
907 | "execution_count": null,
908 | "outputs": [
909 | {
910 | "output_type": "execute_result",
911 | "data": {
912 | "text/plain": [
913 | "array([[ 0, 1, 2, 3],\n",
914 | " [ 4, 5, 6, 7],\n",
915 | " [12, 13, 14, 15],\n",
916 | " [16, 17, 18, 19]])"
917 | ]
918 | },
919 | "metadata": {},
920 | "execution_count": 135
921 | }
922 | ]
923 | },
924 | {
925 | "cell_type": "markdown",
926 | "source": [
927 | "# Jugando a encriptar\n",
928 | "\n",
929 | "Vamos a utilizar un mecanismo de cifrado *inspirado* en el [cifrado Hill](https://es.wikipedia.org/wiki/Cifrado_Hill). En este caso, vamos a utilizar un $n=4$, es decir que nuestra matriz (inversible) de cifrado $K$ va a ser de $\\mathbb{R}^{4 \\times 4}$.\n",
930 | "\n",
931 | "## Cifrado \n",
932 | "\n",
933 | "El procedimiento es el siguiente:\n",
934 | "\n",
935 | "1. A nuestro mensaje de $m$ letras le agregamos caracteres de *padding* (relleno) hasta que el largo del mensaje $m'$ es múltiplo de $n$.\n",
936 | "2. Utilizando una codificación del alfabeto a números (por ejemplo, $A \\rightarrow 1$), convertimos nuestro mensaje paddeado de $m'$ letras a un vector de $\\mathbb{R}^{m'}$\n",
937 | "3. Seccionamos el vector en *slices* de $n$ elementos, obteniendo el mensaje como una matriz $M \\in \\mathbb{R}^{n \\times \\frac{m'}{n}}$.\n",
938 | "4. Obtenemos el mensaje cifrado como $E = K \\cdot M$\n",
939 | "\n",
940 | "**Para pensar 1: ¿Dónde vive E?**\n",
941 | "\n",
942 | "**Para pensar 2: ¿Por qué es necesario el paso 2?**\n",
943 | "\n",
944 | "## Descifrado\n",
945 | "\n",
946 | "Vamos a suponer que ya disponemos de la inversa $K^{-1}$. Veamos que el procedimiento para descifrar es exactamente inverso al de cifrado:\n",
947 | "\n",
948 | "1. Obtenemos el mensaje descifrado $M_2 = K^{-1} \\cdot E$\n",
949 | "2. Aplanamos la matriz $M_2 \\in \\mathbb{R}^{n \\times \\frac{m'}{n}}$ para obtener un vector de $\\mathbb{R}^{m'}$\n",
950 | "3. Decodificamos el vector para obtener el mensaje paddeado de largo $m'$.\n",
951 | "4. Quitamos todos los caracteres de padding que encontremos al final del mensaje.\n",
952 | "\n",
953 | "\n",
954 | "**Para pensar 3: ¿Por qué estamos tan seguros que $M_2 = M$?**"
955 | ],
956 | "metadata": {
957 | "id": "BLT09_2T6MHX"
958 | }
959 | },
960 | {
961 | "cell_type": "code",
962 | "source": [
963 | "# esto no importa\n",
964 | "def print2(*args):\n",
965 | " print(*args, end='\\n\\n')"
966 | ],
967 | "metadata": {
968 | "id": "YmxgKCrtHQrR"
969 | },
970 | "execution_count": null,
971 | "outputs": []
972 | },
973 | {
974 | "cell_type": "code",
975 | "source": [
976 | "# construimos el encoder y decoder del alfabeto\n",
977 | "from string import ascii_lowercase\n",
978 | "\n",
979 | "PADDING_CHAR = '.'\n",
980 | "alfabeto = ascii_lowercase+' '+ PADDING_CHAR # las letras + espacio + caracter de padding \".\"\n",
981 | "\n",
982 | "# enumerate itera sobre un iterable pero ademas nos da el indice\n",
983 | "# es decir, (0,a)->(1,b)-> ... ->(27, .)\n",
984 | "encoder = {letter: idx for idx, letter in enumerate(alfabeto)}\n",
985 | "print(encoder)\n",
986 | "\n",
987 | "# forma fácil y rápida de invertir un diccionario, ya que items() nos da los pares (clave,valor)\n",
988 | "decoder = {v:k for k,v in encoder.items()}"
989 | ],
990 | "metadata": {
991 | "colab": {
992 | "base_uri": "https://localhost:8080/"
993 | },
994 | "id": "8BJkWIKY_5iw",
995 | "outputId": "b44ca268-bb6e-4831-ba56-98e1d8de49bd"
996 | },
997 | "execution_count": null,
998 | "outputs": [
999 | {
1000 | "output_type": "stream",
1001 | "name": "stdout",
1002 | "text": [
1003 | "{'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5, 'g': 6, 'h': 7, 'i': 8, 'j': 9, 'k': 10, 'l': 11, 'm': 12, 'n': 13, 'o': 14, 'p': 15, 'q': 16, 'r': 17, 's': 18, 't': 19, 'u': 20, 'v': 21, 'w': 22, 'x': 23, 'y': 24, 'z': 25, ' ': 26, '.': 27}\n"
1004 | ]
1005 | }
1006 | ]
1007 | },
1008 | {
1009 | "cell_type": "code",
1010 | "source": [
1011 | "# construimos las funciones para acomodar largos\n",
1012 | "def add_padding(message, padd_char, n):\n",
1013 | " \"\"\"\n",
1014 | " Add padd_char characters to the message until len(message) is a multiple of n.\n",
1015 | " n must be an integer greater than 1 and len(padd_char) must be exactly 1.\n",
1016 | " \"\"\"\n",
1017 | " # si el largo del mensaje ya es multiplo de n, devolver el mensaje tal cual\n",
1018 | " if len(message) % n == 0:\n",
1019 | " return message\n",
1020 | " # si no, le sobran len(message) % n caracteres (y por tanto le faltan n - eso para completar)\n",
1021 | " how_many_padds = n - len(message) % n \n",
1022 | "\n",
1023 | " # recordar que la multiplicacion en Python para strings es la concatenacion\n",
1024 | " # por ej. 3 * 'A' = 'AAA'\n",
1025 | " return message + how_many_padds * padd_char\n",
1026 | "\n",
1027 | "def remove_padding(message, padd_char):\n",
1028 | " return message.rstrip(padd_char)"
1029 | ],
1030 | "metadata": {
1031 | "id": "N8sepJ54CJzr"
1032 | },
1033 | "execution_count": null,
1034 | "outputs": []
1035 | },
1036 | {
1037 | "cell_type": "code",
1038 | "source": [
1039 | "# definimos el mensaje y la clave K\n",
1040 | "import numpy.linalg as npl\n",
1041 | "\n",
1042 | "mensaje = \"alo vero como andas\"\n",
1043 | "\n",
1044 | "K = np.array([\n",
1045 | " [1,2,3,4],\n",
1046 | " [5,4,4,6],\n",
1047 | " [2,1,1,3],\n",
1048 | " [1,2,1,1]\n",
1049 | "])\n",
1050 | "\n",
1051 | "N = 4\n",
1052 | "\n",
1053 | "# chequeamos que sea inversible\n",
1054 | "npl.det(K)"
1055 | ],
1056 | "metadata": {
1057 | "colab": {
1058 | "base_uri": "https://localhost:8080/"
1059 | },
1060 | "id": "P7HRW7WF_gNo",
1061 | "outputId": "e7003e0f-3e5d-4306-9b7e-e96865aa62cd"
1062 | },
1063 | "execution_count": null,
1064 | "outputs": [
1065 | {
1066 | "output_type": "execute_result",
1067 | "data": {
1068 | "text/plain": [
1069 | "-14.999999999999993"
1070 | ]
1071 | },
1072 | "metadata": {},
1073 | "execution_count": 139
1074 | }
1075 | ]
1076 | },
1077 | {
1078 | "cell_type": "code",
1079 | "source": [
1080 | "# Obtenemos la matriz M\n",
1081 | "print2(f\"Mensaje original (largo = {len(mensaje)}): {mensaje}\")\n",
1082 | "\n",
1083 | "mensaje_con_padding = add_padding(mensaje, PADDING_CHAR, N)\n",
1084 | "\n",
1085 | "print2(f\"Mensaje con padding (largo = {len(mensaje_con_padding)}): {mensaje_con_padding}\")\n",
1086 | "\n",
1087 | "mensaje_encodeado = [encoder[letra] for letra in mensaje_con_padding]\n",
1088 | "\n",
1089 | "print2(f\"Mensaje encodeado: {mensaje_encodeado}\")\n",
1090 | "\n",
1091 | "# pasamos el mensaje a matriz de n filas\n",
1092 | "\n",
1093 | "M = np.array(mensaje_encodeado, dtype=int).reshape(N, len(mensaje_encodeado)//N)\n",
1094 | "\n",
1095 | "print(\"Mensaje como matriz M:\")\n",
1096 | "print2(M)"
1097 | ],
1098 | "metadata": {
1099 | "colab": {
1100 | "base_uri": "https://localhost:8080/"
1101 | },
1102 | "id": "Z8czWbaiElYV",
1103 | "outputId": "81ba4ea6-bfc2-4f6e-b60f-0693740ff545"
1104 | },
1105 | "execution_count": null,
1106 | "outputs": [
1107 | {
1108 | "output_type": "stream",
1109 | "name": "stdout",
1110 | "text": [
1111 | "Mensaje original (largo = 19): alo vero como andas\n",
1112 | "\n",
1113 | "Mensaje con padding (largo = 20): alo vero como andas.\n",
1114 | "\n",
1115 | "Mensaje encodeado: [0, 11, 14, 26, 21, 4, 17, 14, 26, 2, 14, 12, 14, 26, 0, 13, 3, 0, 18, 27]\n",
1116 | "\n",
1117 | "Mensaje como matriz M:\n",
1118 | "[[ 0 11 14 26 21]\n",
1119 | " [ 4 17 14 26 2]\n",
1120 | " [14 12 14 26 0]\n",
1121 | " [13 3 0 18 27]]\n",
1122 | "\n"
1123 | ]
1124 | }
1125 | ]
1126 | },
1127 | {
1128 | "cell_type": "code",
1129 | "source": [
1130 | "# Encriptamos utilizando la matriz\n",
1131 | "E = K @ M\n",
1132 | "\n",
1133 | "print(\"Mensaje encriptado E:\")\n",
1134 | "print(E)"
1135 | ],
1136 | "metadata": {
1137 | "colab": {
1138 | "base_uri": "https://localhost:8080/"
1139 | },
1140 | "id": "Ym7CljBVEQCo",
1141 | "outputId": "6d140022-e140-4cb0-8948-0452e3ab98de"
1142 | },
1143 | "execution_count": null,
1144 | "outputs": [
1145 | {
1146 | "output_type": "stream",
1147 | "name": "stdout",
1148 | "text": [
1149 | "Mensaje encriptado E:\n",
1150 | "[[102 93 84 228 133]\n",
1151 | " [150 189 182 446 275]\n",
1152 | " [ 57 60 56 158 125]\n",
1153 | " [ 35 60 56 122 52]]\n"
1154 | ]
1155 | }
1156 | ]
1157 | },
1158 | {
1159 | "cell_type": "code",
1160 | "source": [
1161 | "# Para desencriptar necesitamos la inversa de K\n",
1162 | "\n",
1163 | "K_inv = npl.inv(K)\n",
1164 | "print(K_inv)"
1165 | ],
1166 | "metadata": {
1167 | "colab": {
1168 | "base_uri": "https://localhost:8080/"
1169 | },
1170 | "id": "Qg-3rKB_Hg_b",
1171 | "outputId": "1a9f4141-2be0-409e-fc29-818ed20d86e9"
1172 | },
1173 | "execution_count": null,
1174 | "outputs": [
1175 | {
1176 | "output_type": "stream",
1177 | "name": "stdout",
1178 | "text": [
1179 | "[[-0.4 0.46666667 -0.26666667 -0.4 ]\n",
1180 | " [ 0. -0.33333333 0.33333333 1. ]\n",
1181 | " [ 0.2 0.6 -1.2 -0.8 ]\n",
1182 | " [ 0.2 -0.4 0.8 0.2 ]]\n"
1183 | ]
1184 | }
1185 | ]
1186 | },
1187 | {
1188 | "cell_type": "code",
1189 | "source": [
1190 | "# Desencriptamos\n",
1191 | "\n",
1192 | "# multiplicamos por la inversa de K, comparar M2 contra M\n",
1193 | "M2_ = K_inv @ E\n",
1194 | "\n",
1195 | "print(\"M desencriptada:\")\n",
1196 | "print2(M2_)\n",
1197 | "\n",
1198 | "# EPA! hay problemas de redondeo culpa de la computadora... pero nada muy grave, redondeamos\n",
1199 | "M2 = np.rint(M2_).astype(int)\n",
1200 | "\n",
1201 | "print(\"Ahora sí, M desencriptada\")\n",
1202 | "print2(M2)\n",
1203 | "\n",
1204 | "# no nos confiemos, M2 y M son iguales?\n",
1205 | "assert np.all(M2 == M)\n",
1206 | "\n",
1207 | "# perfecto! ahora aplanamos\n",
1208 | "mensaje_aplanado = M2.flatten()\n",
1209 | "\n",
1210 | "print2(f\"M2 aplanada: {mensaje_aplanado}\")\n",
1211 | "\n",
1212 | "# desconvertimos\n",
1213 | "mensaje_desencodeado = \"\".join([decoder[num] for num in mensaje_aplanado])\n",
1214 | "\n",
1215 | "print2(f\"Mensaje recuperado con padding (largo = {len(mensaje_con_padding)}): {mensaje_con_padding}\")\n",
1216 | "\n",
1217 | "# quitamos padding\n",
1218 | "mensaje_sin_padding = remove_padding(mensaje_desencodeado, PADDING_CHAR)\n",
1219 | "\n",
1220 | "print2(f\"Mensaje recuperado (largo = {len(mensaje_sin_padding)}): {mensaje_sin_padding}\")"
1221 | ],
1222 | "metadata": {
1223 | "colab": {
1224 | "base_uri": "https://localhost:8080/"
1225 | },
1226 | "id": "WPJ9UruZHs3w",
1227 | "outputId": "01cb1c4c-ef51-46ad-c9f4-8d1d938b9918"
1228 | },
1229 | "execution_count": null,
1230 | "outputs": [
1231 | {
1232 | "output_type": "stream",
1233 | "name": "stdout",
1234 | "text": [
1235 | "M desencriptada:\n",
1236 | "[[-8.38218384e-15 1.10000000e+01 1.40000000e+01 2.60000000e+01\n",
1237 | " 2.10000000e+01]\n",
1238 | " [ 4.00000000e+00 1.70000000e+01 1.40000000e+01 2.60000000e+01\n",
1239 | " 2.00000000e+00]\n",
1240 | " [ 1.40000000e+01 1.20000000e+01 1.40000000e+01 2.60000000e+01\n",
1241 | " -1.24344979e-14]\n",
1242 | " [ 1.30000000e+01 3.00000000e+00 1.99840144e-15 1.80000000e+01\n",
1243 | " 2.70000000e+01]]\n",
1244 | "\n",
1245 | "Ahora sí, M desencriptada\n",
1246 | "[[ 0 11 14 26 21]\n",
1247 | " [ 4 17 14 26 2]\n",
1248 | " [14 12 14 26 0]\n",
1249 | " [13 3 0 18 27]]\n",
1250 | "\n",
1251 | "M2 aplanada: [ 0 11 14 26 21 4 17 14 26 2 14 12 14 26 0 13 3 0 18 27]\n",
1252 | "\n",
1253 | "Mensaje recuperado con padding (largo = 20): alo vero como andas.\n",
1254 | "\n",
1255 | "Mensaje recuperado (largo = 19): alo vero como andas\n",
1256 | "\n"
1257 | ]
1258 | }
1259 | ]
1260 | },
1261 | {
1262 | "cell_type": "markdown",
1263 | "source": [
1264 | "### ¿Qué pasa si se intenta utilizar una clave incorrecta?\n"
1265 | ],
1266 | "metadata": {
1267 | "id": "Q4yAASHzy1tW"
1268 | }
1269 | },
1270 | {
1271 | "cell_type": "code",
1272 | "source": [
1273 | "K_mala = K + 1\n",
1274 | "print(\"Clave incorrecta: \")\n",
1275 | "print2(K_mala)\n",
1276 | "\n",
1277 | "# intentamos descifrar utilizando esa clave\n",
1278 | "remove_padding(\"\".join([decoder[i] for i in np.rint(inv(K_mala) @ E).astype(int).flatten()]), PADDING_CHAR)"
1279 | ],
1280 | "metadata": {
1281 | "colab": {
1282 | "base_uri": "https://localhost:8080/",
1283 | "height": 411
1284 | },
1285 | "id": "MVU1pXuUy7di",
1286 | "outputId": "3f0fb6da-7d79-4b7e-ff19-a0f2edac4e49"
1287 | },
1288 | "execution_count": null,
1289 | "outputs": [
1290 | {
1291 | "output_type": "stream",
1292 | "name": "stdout",
1293 | "text": [
1294 | "Clave incorrecta: \n",
1295 | "[[2 3 4 5]\n",
1296 | " [6 5 5 7]\n",
1297 | " [3 2 2 4]\n",
1298 | " [2 3 2 2]]\n",
1299 | "\n"
1300 | ]
1301 | },
1302 | {
1303 | "output_type": "error",
1304 | "ename": "KeyError",
1305 | "evalue": "ignored",
1306 | "traceback": [
1307 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
1308 | "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)",
1309 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;31m# intentamos descifrar utilizando esa clave\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0mremove_padding\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"\"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mdecoder\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mK_mala\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m@\u001b[0m \u001b[0mE\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mastype\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mflatten\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mPADDING_CHAR\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
1310 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;31m# intentamos descifrar utilizando esa clave\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0mremove_padding\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"\"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mdecoder\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mK_mala\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m@\u001b[0m \u001b[0mE\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mastype\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mflatten\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mPADDING_CHAR\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
1311 | "\u001b[0;31mKeyError\u001b[0m: 37"
1312 | ]
1313 | }
1314 | ]
1315 | },
1316 | {
1317 | "cell_type": "markdown",
1318 | "source": [
1319 | "¡Ni siquiera da un string válido! E incluso si devolviera uno, no va a dar nada parecido al original."
1320 | ],
1321 | "metadata": {
1322 | "id": "7QmKdQrH0rHt"
1323 | }
1324 | },
1325 | {
1326 | "cell_type": "markdown",
1327 | "source": [
1328 | "## Para jugar en casa: versión OOP\n",
1329 | "\n",
1330 | "Es (casi) exactamente lo mismo que antes pero utilizando clases y métodos.\n",
1331 | "\n",
1332 | "No les vamos a decir cómo funciona, solamente les vamos a dar una ayuda: se puede registrar en dos modos: \n",
1333 | "1. Pasar una key al inicio y utilizar siempre esa *(ó...)*\n",
1334 | "2. Utilizar una clave aparte para cada operación de cifrado+descifrado.\n",
1335 | "\n",
1336 | "¡A descifrar (el código)!"
1337 | ],
1338 | "metadata": {
1339 | "id": "zU7rq5_ZME00"
1340 | }
1341 | },
1342 | {
1343 | "cell_type": "code",
1344 | "source": [
1345 | "import numpy as np\n",
1346 | "from numpy.linalg import inv, det\n",
1347 | "\n",
1348 | "class MatrixCipher(object):\n",
1349 | " def __init__(self, alphabet, padding_char, key=None):\n",
1350 | " \"\"\"\n",
1351 | " Params:\n",
1352 | " alphabet (str): a string with all the characters allowed in a message (they must all be different).\n",
1353 | " padding_char (str): the character to be used for padding. Must not be included in alphabet.\n",
1354 | " key (n x n matrix, optional): an invertible matrix to be used as key. \n",
1355 | " \n",
1356 | " If key is passed, key arguments in cipher and decipher will be ignored.\n",
1357 | " \"\"\"\n",
1358 | " assert len(alphabet) == len(set(alphabet)), \"Alphabet has non-unique characters\"\n",
1359 | " assert padding_char not in alphabet, \"Padding char is included in the alphabet\"\n",
1360 | " if key is not None:\n",
1361 | " assert len(key.shape) == 2, \"Key is not a matrix\"\n",
1362 | " assert key.shape[0] == key.shape[1], \"Key is not a square matrix\"\n",
1363 | " assert det(key) != 0, \"Key is not invertible\"\n",
1364 | "\n",
1365 | " self.encoder = {letter: idx for idx, letter in enumerate(alphabet + padding_char)}\n",
1366 | " self.decoder = {v:k for k,v in self.encoder.items()}\n",
1367 | " self.padding = padding_char\n",
1368 | " self.key = key\n",
1369 | "\n",
1370 | " def _try_get_key(self, key):\n",
1371 | " key = key if self.key is None else self.key\n",
1372 | " assert key is not None, \"Key was neither initialized nor passed\"\n",
1373 | " return key\n",
1374 | "\n",
1375 | " def _matrixify(self, message, n):\n",
1376 | " if len(message) % n != 0:\n",
1377 | " message += self.padding * (n - len(message) % n)\n",
1378 | " \n",
1379 | " encoded_msg = [self.encoder[x] for x in message]\n",
1380 | "\n",
1381 | " return np.array(encoded_msg).reshape(n, len(encoded_msg)//n)\n",
1382 | "\n",
1383 | " def cipher(self, message, key=None):\n",
1384 | " K = self._try_get_key(key)\n",
1385 | "\n",
1386 | " n = K.shape[0]\n",
1387 | "\n",
1388 | " M = self._matrixify(message, n)\n",
1389 | "\n",
1390 | " return K @ M\n",
1391 | " \n",
1392 | " def _dematrixify(self, matrix):\n",
1393 | " decoded_vector = [self.decoder[x] for x in np.rint(matrix.flatten())]\n",
1394 | " return \"\".join(decoded_vector).rstrip(self.padding)\n",
1395 | "\n",
1396 | " def decipher(self, ciphered_message, key=None):\n",
1397 | " K = self._try_get_key(key)\n",
1398 | "\n",
1399 | " K_inv = inv(K)\n",
1400 | "\n",
1401 | " M = K_inv @ ciphered_message\n",
1402 | "\n",
1403 | " return self._dematrixify(M)"
1404 | ],
1405 | "metadata": {
1406 | "id": "LHVIsQ0LISBQ"
1407 | },
1408 | "execution_count": null,
1409 | "outputs": []
1410 | },
1411 | {
1412 | "cell_type": "code",
1413 | "source": [
1414 | "# el ejemplo de antes tiene su equivalente en este cipher\n",
1415 | "mi_cipher = MatrixCipher(alphabet=ascii_lowercase+\" \",padding_char=PADDING_CHAR, key=K)"
1416 | ],
1417 | "metadata": {
1418 | "id": "upsmPYhwTRwL"
1419 | },
1420 | "execution_count": null,
1421 | "outputs": []
1422 | }
1423 | ]
1424 | }
--------------------------------------------------------------------------------
/clases/_repaso_2/0_1_Producto_cartesiano_y_conjuntos.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "name": "0-1 - Producto cartesiano y conjuntos.ipynb",
7 | "provenance": [],
8 | "collapsed_sections": []
9 | },
10 | "kernelspec": {
11 | "name": "python3",
12 | "display_name": "Python 3"
13 | },
14 | "language_info": {
15 | "name": "python"
16 | }
17 | },
18 | "cells": [
19 | {
20 | "cell_type": "markdown",
21 | "source": [
22 | "# Ejemplo básico aplicado de producto cartesiano, relación y conjuntos"
23 | ],
24 | "metadata": {
25 | "id": "tvaGDf6ONyoV"
26 | }
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "source": [
31 | "## Producto Cartesiano y Relación\n",
32 | "\n",
33 | "Para aquellos que utilicen SQL, la *query* (consulta)\n",
34 | "```\n",
35 | "SELECT *\n",
36 | "FROM Tabla1, Tabla2;\n",
37 | "```\n",
38 | "recupera todos los elementos de $Tabla1 \\times Tabla2$, debido a que la coma `,` representa el producto cartesiano. "
39 | ],
40 | "metadata": {
41 | "id": "jI9UpzMfOVGc"
42 | }
43 | },
44 | {
45 | "cell_type": "markdown",
46 | "source": [
47 | "### Imports, etc."
48 | ],
49 | "metadata": {
50 | "id": "sdbojsuxRlra"
51 | }
52 | },
53 | {
54 | "cell_type": "code",
55 | "execution_count": null,
56 | "metadata": {
57 | "colab": {
58 | "base_uri": "https://localhost:8080/"
59 | },
60 | "id": "puqdsMigNx2O",
61 | "outputId": "5073ef49-c67c-49b5-c4ea-82f3498f4345"
62 | },
63 | "outputs": [
64 | {
65 | "output_type": "stream",
66 | "name": "stdout",
67 | "text": [
68 | "Collecting pandasql\n",
69 | " Downloading pandasql-0.7.3.tar.gz (26 kB)\n",
70 | "Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from pandasql) (1.21.5)\n",
71 | "Requirement already satisfied: pandas in /usr/local/lib/python3.7/dist-packages (from pandasql) (1.3.5)\n",
72 | "Requirement already satisfied: sqlalchemy in /usr/local/lib/python3.7/dist-packages (from pandasql) (1.4.31)\n",
73 | "Requirement already satisfied: pytz>=2017.3 in /usr/local/lib/python3.7/dist-packages (from pandas->pandasql) (2018.9)\n",
74 | "Requirement already satisfied: python-dateutil>=2.7.3 in /usr/local/lib/python3.7/dist-packages (from pandas->pandasql) (2.8.2)\n",
75 | "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.7.3->pandas->pandasql) (1.15.0)\n",
76 | "Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.7/dist-packages (from sqlalchemy->pandasql) (1.1.2)\n",
77 | "Requirement already satisfied: importlib-metadata in /usr/local/lib/python3.7/dist-packages (from sqlalchemy->pandasql) (4.11.0)\n",
78 | "Requirement already satisfied: typing-extensions>=3.6.4 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata->sqlalchemy->pandasql) (3.10.0.2)\n",
79 | "Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata->sqlalchemy->pandasql) (3.7.0)\n",
80 | "Building wheels for collected packages: pandasql\n",
81 | " Building wheel for pandasql (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
82 | " Created wheel for pandasql: filename=pandasql-0.7.3-py3-none-any.whl size=26784 sha256=f7c5d8662a088bde53ed33ce9518b4b34fe8c3b4c4840ffa2221bb368ca5d357\n",
83 | " Stored in directory: /root/.cache/pip/wheels/5c/4b/ec/41f4e116c8053c3654e2c2a47c62b4fca34cc67ef7b55deb7f\n",
84 | "Successfully built pandasql\n",
85 | "Installing collected packages: pandasql\n",
86 | "Successfully installed pandasql-0.7.3\n"
87 | ]
88 | }
89 | ],
90 | "source": [
91 | "% pip install pandasql"
92 | ]
93 | },
94 | {
95 | "cell_type": "code",
96 | "source": [
97 | "import pandas as pd\n",
98 | "import pandasql as ps\n",
99 | "\n",
100 | "# helper\n",
101 | "q = lambda s : ps.sqldf(s, globals())\n",
102 | "\n",
103 | "# levantamos los datasets\n",
104 | "compras = pd.read_csv(\"data/cosas_a_comprar.csv\", header=0)\n",
105 | "invitados = pd.read_csv(\"data/invitados_juntada.csv\", header=0)"
106 | ],
107 | "metadata": {
108 | "id": "RZR8zzbHOx_g"
109 | },
110 | "execution_count": null,
111 | "outputs": []
112 | },
113 | {
114 | "cell_type": "markdown",
115 | "source": [
116 | "### ¿Quién trae qué?\n",
117 | "Invitados a una reunión tienen que organizarse para ver quién trae cada cosa. Para eso cuentan con los siguientes datos:\n",
118 | "* Una tabla de *compras* con cada item de la lista de cosas a comprar y su precio\n",
119 | "* Una tabla de *invitados* con la lista de invitados y, para cada uno, cuánto dinero puede disponer para comprar lo suyo."
120 | ],
121 | "metadata": {
122 | "id": "7oXfB72WRrDT"
123 | }
124 | },
125 | {
126 | "cell_type": "code",
127 | "source": [
128 | "# tabla de cosas a comprar\n",
129 | "q(\"\"\"\n",
130 | "SELECT *\n",
131 | "FROM compras;\n",
132 | "\"\"\")"
133 | ],
134 | "metadata": {
135 | "colab": {
136 | "base_uri": "https://localhost:8080/",
137 | "height": 143
138 | },
139 | "id": "EBndzdQMQQfk",
140 | "outputId": "ef21bda2-5c74-4aac-f8ad-9145932ea9b8"
141 | },
142 | "execution_count": null,
143 | "outputs": [
144 | {
145 | "output_type": "execute_result",
146 | "data": {
147 | "text/html": [
148 | "\n",
149 | "