├── .gitignore
├── 2-1-1-dictvectorizer.ipynb
├── 2-1-2-feature-hashing.ipynb
├── 2-1-3-trabajando-con-texto.ipynb
├── 2-2-1-escalamiento-de-numeros.ipynb
├── 2-2-2-discretizacion.ipynb
├── 2-2-3-codificacion-de-categorias.ipynb
├── 2-2-4-simple-imputer.ipynb
├── 3-1-1-cross-validation.ipynb
├── 3-1-2-hyper-parameter-tunning.ipynb
├── 3-1-3-metrics.ipynb
├── 3-1-6-visualizaciones.ipynb
├── 3-1-8-train-test-split.ipynb
├── 4-1-1-regresion-lineal.ipynb
├── 4-1-2-ridge-and-lasso.ipynb
├── 4-2-1-regresion-logistica.ipynb
├── 4-2-2-support-vector-classifier.ipynb
├── 4-2-3-random-forest.ipynb
├── 4-2-4-k-nearest-neighbors.ipynb
├── 5-1-1-kmeans.ipynb
├── 5-1-2-dbscan.ipynb
├── 5-2-1-principal-component-analysis.ipynb
├── 6-1-1-pipelines.ipynb
├── 6-1-2-pipelines-parte-2.ipynb
├── 6-2-1-persistencia-de-modelos.ipynb
├── complex.csv
├── custom_houses.txt
├── requirements.txt
└── utils.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .venv/
2 |
3 | .ipynb_checkpoints
4 | */.ipynb_checkpoints/*
5 |
6 | # IPython
7 | profile_default/
8 | ipython_config.py
9 |
10 | # Remove previous ipynb_checkpoints
11 | # git rm -r .ipynb_checkpoints/
12 |
13 | ### Python ###
14 | # Byte-compiled / optimized / DLL files
15 | __pycache__/
16 | *.py[cod]
17 | *$py.class
18 |
19 |
20 | *.joblib
21 | *.pickle
22 | *.sio
23 |
--------------------------------------------------------------------------------
/2-1-1-dictvectorizer.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "0d64ba02",
6 | "metadata": {},
7 | "source": [
8 | "# Vectorizando diccionarios\n",
9 | "\n",
10 | "Llegó el momento de comenzar a hablar sobre feature engineering y pre-procesamiento de datos. En la gran mayoría de los casos, el pre-procesamiento de datos consiste en transofmar nuestras variables a números para que nuestro modelo los pueda procesar. Vamos a comenzar.\n",
11 | "\n",
12 | "No es nada fuera de lo común el trabajar con información contenida en diccionarios de Python, después de todo es uno de los tipos soportados por default en el lenguaje. \n",
13 | "\n",
14 | "Para lidiar con este tipo de datos, scikit-learn nos ofrece un transformador de datos, llamado DictVectorizer
para convertir diccionarios con características categóricas y numéricas a representaciones vectoriales.\n",
15 | "\n",
16 | "Para demostrarte un ejemplo, vamos a crear un conjunto de datos en la forma de una lista de diccionarios:"
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "execution_count": null,
22 | "id": "7255d8bb",
23 | "metadata": {},
24 | "outputs": [],
25 | "source": [
26 | "data = [\n",
27 | " {'name': 'Hugo', 'age': 25, 'city': 'Bogotá'},\n",
28 | " {'name': 'Paco', 'age': 30, 'city': 'Tlaxcala'},\n",
29 | " {'name': 'Luis', 'age': 22, 'city': 'Buenos Aires'}\n",
30 | "]"
31 | ]
32 | },
33 | {
34 | "cell_type": "markdown",
35 | "id": "3c969dbc",
36 | "metadata": {},
37 | "source": [
38 | "Importamos la clase:"
39 | ]
40 | },
41 | {
42 | "cell_type": "code",
43 | "execution_count": null,
44 | "id": "7ad98d92",
45 | "metadata": {},
46 | "outputs": [],
47 | "source": [
48 | "from sklearn.feature_extraction import DictVectorizer"
49 | ]
50 | },
51 | {
52 | "cell_type": "markdown",
53 | "id": "b349941b",
54 | "metadata": {},
55 | "source": [
56 | "Inicializamos un objeto:"
57 | ]
58 | },
59 | {
60 | "cell_type": "code",
61 | "execution_count": null,
62 | "id": "b8482deb",
63 | "metadata": {},
64 | "outputs": [],
65 | "source": [
66 | "vectorizer = DictVectorizer(sparse=False)"
67 | ]
68 | },
69 | {
70 | "cell_type": "markdown",
71 | "id": "ce945afc",
72 | "metadata": {},
73 | "source": [
74 | "Y entrenamos el vectorizador con nuestros datos de entrada, e inmediatamente procedemos a transformar el mismo arreglo de entrada:"
75 | ]
76 | },
77 | {
78 | "cell_type": "code",
79 | "execution_count": null,
80 | "id": "1562932d",
81 | "metadata": {},
82 | "outputs": [],
83 | "source": [
84 | "vectorizer.fit(data)\n",
85 | "vectorized_data = vectorizer.transform(data)"
86 | ]
87 | },
88 | {
89 | "cell_type": "markdown",
90 | "id": "a29f48e7",
91 | "metadata": {},
92 | "source": [
93 | "Al hacer esto, y gracias al argumento sparse=False
, obtenemos un arreglo bidimensional en NumPy:"
94 | ]
95 | },
96 | {
97 | "cell_type": "code",
98 | "execution_count": null,
99 | "id": "638b29af",
100 | "metadata": {},
101 | "outputs": [],
102 | "source": [
103 | "vectorized_data"
104 | ]
105 | },
106 | {
107 | "cell_type": "markdown",
108 | "id": "a13c0712",
109 | "metadata": {},
110 | "source": [
111 | "Si tienes curiosidad de conocer el orden de las columnas en este arreglo bidimensional, puedes usar la propiedad feature_names_
o la propiedad vocabulary_
:"
112 | ]
113 | },
114 | {
115 | "cell_type": "code",
116 | "execution_count": null,
117 | "id": "d2f54bc0",
118 | "metadata": {},
119 | "outputs": [],
120 | "source": [
121 | "print(vectorizer.feature_names_)\n",
122 | "print(vectorizer.vocabulary_)"
123 | ]
124 | },
125 | {
126 | "cell_type": "markdown",
127 | "id": "87aa21a2",
128 | "metadata": {},
129 | "source": [
130 | "Una te entrega una lista ordenada de las columnas, mientras que la otra te da un diccionario que mapea el nombre de una columna con el número que le corresponde dentro del arreglo bidimiensional resultante.\n",
131 | "\n",
132 | "Así podemos ver que las columnas de texto han sido codificadas utilizando la técnica one-hot encoding, es decir, un uno en donde corresponde al valor y cero en el resto de las columnas. Por otro lado, la propiedad “age” ha permanecido como el valor numérico que ya era."
133 | ]
134 | },
135 | {
136 | "cell_type": "markdown",
137 | "id": "2c25fd38",
138 | "metadata": {},
139 | "source": [
140 | "## Parámetros extra\n",
141 | "\n",
142 | "En cuanto a los parámetros que le pasamos al constructor, el más relevante es uno que ya utilizamos: sparse
que por default es igual a True
, y cuando este argumento es verdadero, el vectorizador en lugar de regresar un arreglo de NumPy, nos devolverá una matriz dispersa de SciPy:\n",
143 | "\n",
144 | "Quizá el argumento más relevante es sparse
, que permite especificar el tipo de la salida:"
145 | ]
146 | },
147 | {
148 | "cell_type": "code",
149 | "execution_count": null,
150 | "id": "5bee24c5",
151 | "metadata": {},
152 | "outputs": [],
153 | "source": [
154 | "vectorizer = DictVectorizer()\n",
155 | "vectorized_data = vectorizer.fit_transform(data)\n",
156 | "vectorized_data"
157 | ]
158 | },
159 | {
160 | "cell_type": "markdown",
161 | "id": "dc8429c4",
162 | "metadata": {},
163 | "source": [
164 | " > 📚 De tarea, te dejo que experimentes pasándole diccionarios con llaves y valores que no hayas visto antes. Dime en los comentarios, ¿qué es lo que sucede?"
165 | ]
166 | },
167 | {
168 | "cell_type": "markdown",
169 | "id": "6cf57c6b",
170 | "metadata": {},
171 | "source": [
172 | "## Conclusión\n",
173 | "\n",
174 | "DictVectorizer
es una herramienta poderosa, sin embargo, no es siempre la mejor forma de codificar tus datos. \n",
175 | "\n",
176 | "Utilízalo cuando tengas que lidiar con datos estructurados en la forma de diccionarios, y cuando las propiedades de estos sean valores categóricos en forma de cadenas o números.\n",
177 | "\n",
178 | "También debes tener cuidado de usarlo cuando tienes una alta cardinalidad en valores categóricos, en nuestro ejemplo anterior, podrías considerar la propiedad “name” como una con una alta cardinalidad, después de todo puede existir un número infinito de nombres.\n",
179 | "\n",
180 | "Otra cosa que hay que tener en cuenta es que DictVectorizer
es un tanto genérico, y hay veces en las que requerirás tener más control sobre cómo es que la transformación entre datos de entrada y características sucede."
181 | ]
182 | }
183 | ],
184 | "metadata": {
185 | "kernelspec": {
186 | "display_name": "Python 3 (ipykernel)",
187 | "language": "python",
188 | "name": "python3"
189 | },
190 | "language_info": {
191 | "codemirror_mode": {
192 | "name": "ipython",
193 | "version": 3
194 | },
195 | "file_extension": ".py",
196 | "mimetype": "text/x-python",
197 | "name": "python",
198 | "nbconvert_exporter": "python",
199 | "pygments_lexer": "ipython3",
200 | "version": "3.8.11"
201 | },
202 | "notion_metadata": {
203 | "archived": false,
204 | "cover": null,
205 | "created_by": {
206 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
207 | "object": "user"
208 | },
209 | "created_time": "2023-03-14T19:56:00.000Z",
210 | "icon": null,
211 | "id": "286afa10-f5b3-49ae-aae0-5a46da144287",
212 | "last_edited_by": {
213 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
214 | "object": "user"
215 | },
216 | "last_edited_time": "2023-04-21T17:04:00.000Z",
217 | "object": "page",
218 | "parent": {
219 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
220 | "type": "database_id"
221 | },
222 | "properties": {
223 | "Assign": {
224 | "id": "%5DtZZ",
225 | "people": [],
226 | "type": "people"
227 | },
228 | "Code name": {
229 | "id": "PT%5CP",
230 | "rich_text": [],
231 | "type": "rich_text"
232 | },
233 | "Name": {
234 | "id": "title",
235 | "title": [
236 | {
237 | "annotations": {
238 | "bold": false,
239 | "code": false,
240 | "color": "default",
241 | "italic": false,
242 | "strikethrough": false,
243 | "underline": false
244 | },
245 | "href": null,
246 | "plain_text": "2.1.1 DictVectorizer",
247 | "text": {
248 | "content": "2.1.1 DictVectorizer",
249 | "link": null
250 | },
251 | "type": "text"
252 | }
253 | ],
254 | "type": "title"
255 | },
256 | "Order": {
257 | "id": "k_Cb",
258 | "number": 3.11,
259 | "type": "number"
260 | },
261 | "Real order": {
262 | "id": "%7Dn_k",
263 | "rich_text": [
264 | {
265 | "annotations": {
266 | "bold": false,
267 | "code": false,
268 | "color": "default",
269 | "italic": false,
270 | "strikethrough": false,
271 | "underline": false
272 | },
273 | "href": null,
274 | "plain_text": "06",
275 | "text": {
276 | "content": "06",
277 | "link": null
278 | },
279 | "type": "text"
280 | }
281 | ],
282 | "type": "rich_text"
283 | },
284 | "Status": {
285 | "id": "s%7D%5Ea",
286 | "status": {
287 | "color": "green",
288 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
289 | "name": "Done"
290 | },
291 | "type": "status"
292 | }
293 | },
294 | "url": "https://www.notion.so/2-1-1-DictVectorizer-286afa10f5b349aeaae05a46da144287"
295 | }
296 | },
297 | "nbformat": 4,
298 | "nbformat_minor": 5
299 | }
300 |
--------------------------------------------------------------------------------
/2-1-2-feature-hashing.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "0e21c369",
6 | "metadata": {},
7 | "source": [
8 | "# Feature hashing\n",
9 | "\n",
10 | "En esta lección te hablaré de cómo es que uno puede lidiar con datos categorícos con alta cardinalidad, es decir, aquellas variables que pueden tomar muchos valores.\n",
11 | "\n",
12 | "En machine learning existe una técnica muy conocida llamada feature hashing o acá entre amigos, el hashing trick. Esta técnica consiste en aplicarle una función de hash al valor de una característica para asociarlo con la posición dentro de un arreglo.\n",
13 | "\n",
14 | "La idea básica detrás del hashing es convertir una entrada en una forma más compacta y fácil de procesar. En lugar de almacenar una lista completa de todas las características en su forma original, la entrada se transforma en una representación numérica más simple.\n",
15 | "\n",
16 | "Vamos a verlo con un ejemplo, comenzamos por crear un arreglo de diccionarios:"
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "execution_count": null,
22 | "id": "f589e3b2",
23 | "metadata": {},
24 | "outputs": [],
25 | "source": [
26 | "data = [{'apple': 2, 'banana': 1, 'orange': 3},\n",
27 | " {'banana': 4, 'orange': 1},\n",
28 | " {'kiwi': 3, 'pineapple': 5}]"
29 | ]
30 | },
31 | {
32 | "cell_type": "markdown",
33 | "id": "a79b1fa7",
34 | "metadata": {},
35 | "source": [
36 | "Después importamos la clase FeatureHasher
del módulo sklearn.feature_extraction
:"
37 | ]
38 | },
39 | {
40 | "cell_type": "code",
41 | "execution_count": null,
42 | "id": "7825e2c0",
43 | "metadata": {},
44 | "outputs": [],
45 | "source": [
46 | "from sklearn.feature_extraction import FeatureHasher"
47 | ]
48 | },
49 | {
50 | "cell_type": "markdown",
51 | "id": "3ea60a62",
52 | "metadata": {},
53 | "source": [
54 | "Y creamos un objeto de la clase, estableciendo el parámetro n_features
, que a su vez representará el número de entradas del vector resultante:"
55 | ]
56 | },
57 | {
58 | "cell_type": "code",
59 | "execution_count": null,
60 | "id": "c0c7ba9b",
61 | "metadata": {},
62 | "outputs": [],
63 | "source": [
64 | "hasher = FeatureHasher(n_features=10)"
65 | ]
66 | },
67 | {
68 | "cell_type": "markdown",
69 | "id": "e8c45736",
70 | "metadata": {},
71 | "source": [
72 | "Y después podemos llamar al método fit_transform
para transformar en una sola acción nuestro dataset:"
73 | ]
74 | },
75 | {
76 | "cell_type": "code",
77 | "execution_count": null,
78 | "id": "a9a8c61c",
79 | "metadata": {},
80 | "outputs": [],
81 | "source": [
82 | "hashed_data = hasher.fit_transform(data)"
83 | ]
84 | },
85 | {
86 | "cell_type": "markdown",
87 | "id": "33cf8221",
88 | "metadata": {},
89 | "source": [
90 | "El resultado de ejecutar transform con nuestros datos es siempre una matriz dispersa dado el uso que normalmente se le da a la clase FeatureHasher
, es por eso que aquí la estoy convirtiendo de vuelta a un arreglo de NumPy utilizando el método todense
:"
91 | ]
92 | },
93 | {
94 | "cell_type": "code",
95 | "execution_count": null,
96 | "id": "6d8dcab7",
97 | "metadata": {},
98 | "outputs": [],
99 | "source": [
100 | "hashed_data.todense()"
101 | ]
102 | },
103 | {
104 | "cell_type": "markdown",
105 | "id": "d91b92e1",
106 | "metadata": {},
107 | "source": [
108 | "Si los resultados no son lo que esperabas, te comprendo, a primera vista es difícil interpretar qué está haciendo el hasher. Por el momento, quédate solamente con que obtuvimos vectores de 4 dimensiones, justo como le especificamos en el constructor con n_features
igual a 4."
109 | ]
110 | },
111 | {
112 | "cell_type": "markdown",
113 | "id": "99109221",
114 | "metadata": {},
115 | "source": [
116 | "## Parámetros extra\n",
117 | "\n",
118 | "En el ejemplo anterior, utilizamos diccionarios como valores de entrada, sin embargo también es común el usar cadenas como entradas, para esto podemos establecer el argumento input_type
como string
:"
119 | ]
120 | },
121 | {
122 | "cell_type": "code",
123 | "execution_count": null,
124 | "id": "1a8fb0fb",
125 | "metadata": {},
126 | "outputs": [],
127 | "source": [
128 | "hasher = FeatureHasher(n_features=4, input_type='string')\n",
129 | "hashed_data = hasher.transform([\n",
130 | " ['cat', 'dog', 'bird'],\n",
131 | " ['cat', 'bird'],\n",
132 | " ['fish', 'dog'],\n",
133 | "])\n",
134 | "hashed_data.todense()"
135 | ]
136 | },
137 | {
138 | "cell_type": "markdown",
139 | "id": "dd90604d",
140 | "metadata": {},
141 | "source": [
142 | "## Explicación de los valores\n",
143 | "\n",
144 | "Volviendo a los valores resultantes tan confusos, esto sucede porque cuando hay funciones de hasheo involucradas en un proceso, estamos destinados a sufrir colisiones, particularmente si contamos un un número de características lo suficientemente bajo, como en nuestro caso con n_features
igual a 4. Lo cual hace que a valores distintos se les asigne la misma posición dentro del vector. Para mitigar los efectos de esta colisión, FeatureHasher
tiene otra función encargada de determinar el signo del valor a sumar, esto con la finalidad de que las colisiones se eliminen entre ellas, es por eso que de pronto también ves valores negativos.\n",
145 | "\n",
146 | " > 📚 De tarea, te dejo que experimentes con el valor de n_features
, elige un valor suficientemente grande para prevenir colisiones. La recomendación de scikit-learn es que este valor sea una siempre una potencia de dos."
147 | ]
148 | },
149 | {
150 | "cell_type": "markdown",
151 | "id": "5fb53637",
152 | "metadata": {},
153 | "source": [
154 | "## Conclusión\n",
155 | "\n",
156 | "Si bien el feature hashing es una técnica técnica poderosa utilizada en ML, no es tan benéfica aplicarla en todos los escenarios, en particular cuando tenemos atributos con baja cardinalidad, puesto que como lo vimos en el ejemplo, cuando n_features
tiene un valor bajo, usar hashing puede causarnos pérdida de información.\n",
157 | "\n",
158 | "En estos casos, pueden ser más apropiadas otras técnicas como la codificación one-hot o label encoding."
159 | ]
160 | }
161 | ],
162 | "metadata": {
163 | "kernelspec": {
164 | "display_name": "Python 3 (ipykernel)",
165 | "language": "python",
166 | "name": "python3"
167 | },
168 | "language_info": {
169 | "codemirror_mode": {
170 | "name": "ipython",
171 | "version": 3
172 | },
173 | "file_extension": ".py",
174 | "mimetype": "text/x-python",
175 | "name": "python",
176 | "nbconvert_exporter": "python",
177 | "pygments_lexer": "ipython3",
178 | "version": "3.8.11"
179 | },
180 | "notion_metadata": {
181 | "archived": false,
182 | "cover": null,
183 | "created_by": {
184 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
185 | "object": "user"
186 | },
187 | "created_time": "2023-03-14T19:57:00.000Z",
188 | "icon": null,
189 | "id": "d79cbfdd-b35f-4e2c-8820-2e7615d1b589",
190 | "last_edited_by": {
191 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
192 | "object": "user"
193 | },
194 | "last_edited_time": "2023-04-21T17:05:00.000Z",
195 | "object": "page",
196 | "parent": {
197 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
198 | "type": "database_id"
199 | },
200 | "properties": {
201 | "Assign": {
202 | "id": "%5DtZZ",
203 | "people": [],
204 | "type": "people"
205 | },
206 | "Code name": {
207 | "id": "PT%5CP",
208 | "rich_text": [],
209 | "type": "rich_text"
210 | },
211 | "Name": {
212 | "id": "title",
213 | "title": [
214 | {
215 | "annotations": {
216 | "bold": false,
217 | "code": false,
218 | "color": "default",
219 | "italic": false,
220 | "strikethrough": false,
221 | "underline": false
222 | },
223 | "href": null,
224 | "plain_text": "2.1.2 Feature hashing",
225 | "text": {
226 | "content": "2.1.2 Feature hashing",
227 | "link": null
228 | },
229 | "type": "text"
230 | }
231 | ],
232 | "type": "title"
233 | },
234 | "Order": {
235 | "id": "k_Cb",
236 | "number": 3.12,
237 | "type": "number"
238 | },
239 | "Real order": {
240 | "id": "%7Dn_k",
241 | "rich_text": [
242 | {
243 | "annotations": {
244 | "bold": false,
245 | "code": false,
246 | "color": "default",
247 | "italic": false,
248 | "strikethrough": false,
249 | "underline": false
250 | },
251 | "href": null,
252 | "plain_text": "07",
253 | "text": {
254 | "content": "07",
255 | "link": null
256 | },
257 | "type": "text"
258 | }
259 | ],
260 | "type": "rich_text"
261 | },
262 | "Status": {
263 | "id": "s%7D%5Ea",
264 | "status": {
265 | "color": "green",
266 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
267 | "name": "Done"
268 | },
269 | "type": "status"
270 | }
271 | },
272 | "url": "https://www.notion.so/2-1-2-Feature-hashing-d79cbfddb35f4e2c88202e7615d1b589"
273 | }
274 | },
275 | "nbformat": 4,
276 | "nbformat_minor": 5
277 | }
278 |
--------------------------------------------------------------------------------
/2-2-1-escalamiento-de-numeros.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "80af35b8",
6 | "metadata": {},
7 | "source": [
8 | "# Escalamiento de números\n",
9 | "\n",
10 | "Cuando trabajamos con datos, es común encontrarnos con variables que tienen diferentes escalas o magnitudes. Por ejemplo, imagina un dataset de datos médicos en donde podemos encontrar información relacionada con el peso y altura de las personas. En este dataset el peso varía entre 50 y 150 kilogramos, mientras que la altura varía entre 1.50 y 1.90 metros.\n",
11 | "\n"
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": null,
17 | "id": "8737654d",
18 | "metadata": {},
19 | "outputs": [],
20 | "source": [
21 | "import numpy as np\n",
22 | "import matplotlib.pyplot as plt\n",
23 | "import pandas as pd\n",
24 | "\n",
25 | "def make_dataset(n):\n",
26 | " min_w, max_w = 50, 150\n",
27 | " noise_w = np.random.normal(0, 10, n)\n",
28 | " weights = np.random.uniform(min_w, max_w, n)\n",
29 | " \n",
30 | " min_h, max_h = 1.50, 1.90\n",
31 | " noise_h = np.random.normal(0, 10, n)\n",
32 | " heights = np.random.uniform(min_h, max_h, n)\n",
33 | " return np.vstack([weights, heights]).T\n",
34 | " \n",
35 | "def plot(ax, dataset, title):\n",
36 | " weights, heights = dataset[:,0], dataset[:,1]\n",
37 | " noise = np.random.uniform(-0.2, 0.2, len(weights))\n",
38 | " ax.scatter(\n",
39 | " weights,\n",
40 | " np.full(len(weights), 1) + noise,\n",
41 | " )\n",
42 | " ax.scatter(\n",
43 | " heights,\n",
44 | " np.full(len(heights), 2) + noise,\n",
45 | " )\n",
46 | " ax.set_ylim(0.5, 2.5)\n",
47 | " ax.set_yticks([1, 2], ['Weight','Height'])\n",
48 | " ax.set_title(title)\n",
49 | " return ax\n",
50 | "\n",
51 | "def show_dataframe(dataset):\n",
52 | " return pd.DataFrame(dataset, columns=['Weight', 'Height'])\n",
53 | "\n",
54 | "def plot_dataset(*objects):\n",
55 | " objects = [(objects[i], objects[i+1]) for i in range(0, len(objects) -1, 2)]\n",
56 | " plots = len(objects)\n",
57 | " fig, axs = plt.subplots(1, plots, figsize=(5 * plots, 5))\n",
58 | " if len(objects) == 1:\n",
59 | " axs = [axs]\n",
60 | " for (dataset, title), ax in zip(objects, axs):\n",
61 | " plot(ax, dataset, title)\n",
62 | " fig.tight_layout()"
63 | ]
64 | },
65 | {
66 | "cell_type": "code",
67 | "execution_count": null,
68 | "id": "f3d8a4a1",
69 | "metadata": {},
70 | "outputs": [],
71 | "source": [
72 | "original_dataset = make_dataset(100)\n",
73 | "show_dataframe(original_dataset)"
74 | ]
75 | },
76 | {
77 | "cell_type": "code",
78 | "execution_count": null,
79 | "id": "38b4b32a",
80 | "metadata": {},
81 | "outputs": [],
82 | "source": [
83 | "fig = plt.figure()\n",
84 | "ax = fig.gca()\n",
85 | "plot(ax, original_dataset, \"Original data\")"
86 | ]
87 | },
88 | {
89 | "cell_type": "markdown",
90 | "id": "26aa98ef",
91 | "metadata": {},
92 | "source": [
93 | "Nuestro modelo de machine learning no tiene noción de que unas cosas están medidas en kilogramos y otras en metros y si los datos no están escalados, algunos atributos pueden tener más peso que otros debido a sus escalas, lo que puede llevar a decisiones incorrectas del modelo.\n",
94 | "\n",
95 | "Volviendo a nuestro ejemplo, puede que el algoritmo “se fije” más en el peso puesto que es la que tiene un rango de varianza mayor, 100, mientras que el peso de solo 0.4. Es aquí en donde entra la importancia de escalar nuestras variables."
96 | ]
97 | },
98 | {
99 | "cell_type": "markdown",
100 | "id": "ec57c8e3",
101 | "metadata": {},
102 | "source": [
103 | "## ¿Por qué es importante?\n",
104 | "\n",
105 | "En concreto, podemos pensar en tres razones por las cuales vale la pena escalar los valores:\n",
106 | "\n",
107 | " 1. Facilita el entrenamiento: Al tener todas las características en la misma escala, los algoritmos de Machine Learning convergen más rápido hacia un mínimo en la función de pérdida.\n",
108 | "\n",
109 | " 1. Mejora el rendimiento: Algunos algoritmos, como SVM o KNN, que se basan en distancias, son muy sensibles a la escala de los datos y pueden dar resultados incorrectos si las características no están estandarizadas.\n",
110 | "\n",
111 | " 1. Permite una interpretación más fácil: Al estandarizar, podemos comparar la importancia relativa de las características en nuestro modelo."
112 | ]
113 | },
114 | {
115 | "cell_type": "markdown",
116 | "id": "d2e5ed76",
117 | "metadata": {},
118 | "source": [
119 | "## ¿Cómo realizarlo?\n",
120 | "\n",
121 | "Existen diversas técnicas para lograr el escalado de variables, y podemos usar las más comunes con scikit-learn."
122 | ]
123 | },
124 | {
125 | "cell_type": "markdown",
126 | "id": "9e18bbaa",
127 | "metadata": {},
128 | "source": [
129 | "### Estandarización\n",
130 | "\n",
131 | "La estandarziación es tal vez la más común de las transformaciones de escalamiento, consiste en centrar todos los datos de un atributo dado del conjunto en 0 y hacer que su varianza sea 1.\n",
132 | "\n"
133 | ]
134 | },
135 | {
136 | "cell_type": "code",
137 | "execution_count": null,
138 | "id": "606d674a",
139 | "metadata": {},
140 | "outputs": [],
141 | "source": [
142 | "from sklearn.preprocessing import StandardScaler\n",
143 | "scaler = StandardScaler()\n",
144 | "standard_scaled = scaler.fit_transform(original_dataset)\n",
145 | "\n",
146 | "plot_dataset(\n",
147 | " original_dataset, \"Original data\",\n",
148 | " standard_scaled, \"Standardized data\")"
149 | ]
150 | },
151 | {
152 | "cell_type": "markdown",
153 | "id": "3e8972a2",
154 | "metadata": {},
155 | "source": [
156 | "Este escalador se es utilizado comúnmente cuando tienes datos que están distribuidos normalmente y quieres que todos tus datos tengan escalas similares. También ten en cuenta que el rango de las características es variable. Es también usado cuando estás preparando datos para una regresión o redes neuronales."
157 | ]
158 | },
159 | {
160 | "cell_type": "markdown",
161 | "id": "033eb5f5",
162 | "metadata": {},
163 | "source": [
164 | "### Escalamiento min-max\n",
165 | "\n",
166 | "Esta técnica de escalamiento nos ayuda a transformar los valores de nuestro dataset de tal forma que queden dentro de un rango conocido.\n",
167 | "\n"
168 | ]
169 | },
170 | {
171 | "cell_type": "code",
172 | "execution_count": null,
173 | "id": "b22cf083",
174 | "metadata": {},
175 | "outputs": [],
176 | "source": [
177 | "from sklearn.preprocessing import MinMaxScaler\n",
178 | "scaler = MinMaxScaler()\n",
179 | "minmax_dataset = scaler.fit_transform(original_dataset)\n",
180 | "\n",
181 | "plot_dataset(\n",
182 | " original_dataset, \"Original data\",\n",
183 | " minmax_dataset, \"Min-max scaled data\")"
184 | ]
185 | },
186 | {
187 | "cell_type": "markdown",
188 | "id": "a111c909",
189 | "metadata": {},
190 | "source": [
191 | "Utiliza este escalador cuando quieras que los datos queden entre un rango determinado, en este caso, entre 0 y 1 por default - especialmente útil para modelos basados en distancia, como el modelo de k-Nearest neighbours o SVM. En este caso, no importa mucho que tus características estén distribuidas normalmente."
192 | ]
193 | },
194 | {
195 | "cell_type": "markdown",
196 | "id": "321f53c5",
197 | "metadata": {},
198 | "source": [
199 | "### Escalamiento máximo absoluto\n",
200 | "\n",
201 | "Este escalador transforma los datos dividiéndolos por el valor máximo absoluto de cada variable. Esto es útil cuando se trabaja con datos que tienen valores muy grandes o muy pequeños.\n",
202 | "\n"
203 | ]
204 | },
205 | {
206 | "cell_type": "code",
207 | "execution_count": null,
208 | "id": "2fe147d1",
209 | "metadata": {},
210 | "outputs": [],
211 | "source": [
212 | "from sklearn.preprocessing import MaxAbsScaler\n",
213 | "scaler = MaxAbsScaler()\n",
214 | "maxabs_scaled = scaler.fit_transform(original_dataset)\n",
215 | "\n",
216 | "plot_dataset(\n",
217 | " original_dataset, \"Original data\",\n",
218 | " maxabs_scaled, \"Max-abs scaled data\")"
219 | ]
220 | },
221 | {
222 | "cell_type": "markdown",
223 | "id": "43279b8a",
224 | "metadata": {},
225 | "source": [
226 | "MaxAbsScaler
es una buena opción cuando las características son dispersas o en su mayoría cero y tienen escalas variables. También es útil cuando se utilizan redes neuronales o modelos lineales dispersos como Logistic Regression o SVM."
227 | ]
228 | },
229 | {
230 | "cell_type": "markdown",
231 | "id": "3563c06e",
232 | "metadata": {},
233 | "source": [
234 | "### Otras formas de escalar valores\n",
235 | "\n",
236 | "Scikit-learn cuenta además con otros escaladores que no podremos cubrir aquí, pero que son más especializados para trabajar con datos con otras distribuciones que ayudan a transformar datos a valores escalados y con distribuciones normales para ser procesados.\n",
237 | "\n",
238 | "from sklearn.preprocessing import QuantileTransformer\n",
239 | "from sklearn.preprocessing import RobustScaler\n",
240 | "from sklearn.preprocessing import PowerTransform\n",
241 | "\n",
242 | "Y pues ahí lo tienes, espero que hayas entendido el valor de escalar tus datos y que de ahora en adelante hagas uso de esta técnica en tus proyectos. "
243 | ]
244 | }
245 | ],
246 | "metadata": {
247 | "kernelspec": {
248 | "display_name": "Python 3 (ipykernel)",
249 | "language": "python",
250 | "name": "python3"
251 | },
252 | "language_info": {
253 | "codemirror_mode": {
254 | "name": "ipython",
255 | "version": 3
256 | },
257 | "file_extension": ".py",
258 | "mimetype": "text/x-python",
259 | "name": "python",
260 | "nbconvert_exporter": "python",
261 | "pygments_lexer": "ipython3",
262 | "version": "3.8.11"
263 | },
264 | "notion_metadata": {
265 | "archived": false,
266 | "cover": null,
267 | "created_by": {
268 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
269 | "object": "user"
270 | },
271 | "created_time": "2023-03-14T20:00:00.000Z",
272 | "icon": null,
273 | "id": "32a8af92-d8c6-421f-a4e7-23a5e5cd3f64",
274 | "last_edited_by": {
275 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
276 | "object": "user"
277 | },
278 | "last_edited_time": "2023-04-21T17:05:00.000Z",
279 | "object": "page",
280 | "parent": {
281 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
282 | "type": "database_id"
283 | },
284 | "properties": {
285 | "Assign": {
286 | "id": "%5DtZZ",
287 | "people": [],
288 | "type": "people"
289 | },
290 | "Code name": {
291 | "id": "PT%5CP",
292 | "rich_text": [],
293 | "type": "rich_text"
294 | },
295 | "Name": {
296 | "id": "title",
297 | "title": [
298 | {
299 | "annotations": {
300 | "bold": false,
301 | "code": false,
302 | "color": "default",
303 | "italic": false,
304 | "strikethrough": false,
305 | "underline": false
306 | },
307 | "href": null,
308 | "plain_text": "2.2.1 Escalamiento de números",
309 | "text": {
310 | "content": "2.2.1 Escalamiento de números",
311 | "link": null
312 | },
313 | "type": "text"
314 | }
315 | ],
316 | "type": "title"
317 | },
318 | "Order": {
319 | "id": "k_Cb",
320 | "number": 3.21,
321 | "type": "number"
322 | },
323 | "Real order": {
324 | "id": "%7Dn_k",
325 | "rich_text": [
326 | {
327 | "annotations": {
328 | "bold": false,
329 | "code": false,
330 | "color": "default",
331 | "italic": false,
332 | "strikethrough": false,
333 | "underline": false
334 | },
335 | "href": null,
336 | "plain_text": "09",
337 | "text": {
338 | "content": "09",
339 | "link": null
340 | },
341 | "type": "text"
342 | }
343 | ],
344 | "type": "rich_text"
345 | },
346 | "Status": {
347 | "id": "s%7D%5Ea",
348 | "status": {
349 | "color": "green",
350 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
351 | "name": "Done"
352 | },
353 | "type": "status"
354 | }
355 | },
356 | "url": "https://www.notion.so/2-2-1-Escalamiento-de-n-meros-32a8af92d8c6421fa4e723a5e5cd3f64"
357 | }
358 | },
359 | "nbformat": 4,
360 | "nbformat_minor": 5
361 | }
362 |
--------------------------------------------------------------------------------
/2-2-2-discretizacion.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "15ec2bc0",
6 | "metadata": {},
7 | "source": [
8 | "# Descretización\n",
9 | "\n",
10 | "Por más extraño que esto te suene, hay ocasiones en las que querrás pasar de una variable con un valor continuo numérico a una variable categórica.\n",
11 | "\n",
12 | "Entre las razones por las que querrías hacer algo así están:\n",
13 | "\n",
14 | " - Para intentar mejorar las capacidades de generalización del modelo\n",
15 | "\n",
16 | " - Reducir la complejidad de los datos\n",
17 | "\n",
18 | " - Reducir el impacto de valores extremos (outliers)\n",
19 | "\n",
20 | " - Por privacidad, anonimizar un poco más nuestros datos (aunque no es una técnica eficaz por si sola)"
21 | ]
22 | },
23 | {
24 | "cell_type": "markdown",
25 | "id": "52224e88",
26 | "metadata": {},
27 | "source": [
28 | "## Bining\n",
29 | "\n",
30 | "Una forma de hacerlo es a través de una técnica conocida como binning que nos ayuda a dividir una variable numérica continua en un número fijo de intervalos (o cubetas, que de ahí viene el nombre binning) de tamaño más o menos igual.\n",
31 | "\n",
32 | "Vamos a ver este proceso con una variable que represente las edades de algunas personas:"
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": null,
38 | "id": "3e8ae92f",
39 | "metadata": {},
40 | "outputs": [],
41 | "source": [
42 | "import pandas as pd\n",
43 | "\n",
44 | "dataset = pd.DataFrame({\n",
45 | " 'age': [20, 25, 30, 35, 40, 45, 50, 55, 60, 65],\n",
46 | " 'savings': [1100, 3000, 5000, 20, 5312, 0, 502, 20, 0, 22000]\n",
47 | "})\n",
48 | "\n",
49 | "dataset"
50 | ]
51 | },
52 | {
53 | "cell_type": "markdown",
54 | "id": "aa08f7c8",
55 | "metadata": {},
56 | "source": [
57 | "En scikit-learn podemos llevar a cabo este proceso a través de la clase KBinsDiscretizer
.\n",
58 | "\n",
59 | "Comenzamos por importarla de sklearn.preprocessing
:"
60 | ]
61 | },
62 | {
63 | "cell_type": "code",
64 | "execution_count": null,
65 | "id": "91875454",
66 | "metadata": {},
67 | "outputs": [],
68 | "source": [
69 | "from sklearn.preprocessing import KBinsDiscretizer"
70 | ]
71 | },
72 | {
73 | "cell_type": "markdown",
74 | "id": "9aa36775",
75 | "metadata": {},
76 | "source": [
77 | "Entre los parámetros más importantes, KBinsDiscretizer
nos permite establecer el número de cubetas que queremos crear, por default el valor es 5. En este caso vamos a crear tres a través del argumento n_bins
.\n",
78 | "\n",
79 | "También otra cosa muy importante, como vimos en una sección pasada del curso, es que los valores categóricos, como nuestras cubetas, deben ser convertidos a valores numéricos para ser procesados por un algoritmo. Para especificar qué tipo de codificación queremos, podemos utilizar el argumento encode
, en este caso vamos a elegir ordinal
, pero también puedes utilizar onehot
y onehot-dense
:"
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": null,
85 | "id": "5ee3f10b",
86 | "metadata": {},
87 | "outputs": [],
88 | "source": [
89 | "discretizer = KBinsDiscretizer(n_bins=3, encode='ordinal')\n",
90 | "discretizer.fit(dataset[['age']])"
91 | ]
92 | },
93 | {
94 | "cell_type": "code",
95 | "execution_count": null,
96 | "id": "63efe5d2",
97 | "metadata": {},
98 | "outputs": [],
99 | "source": [
100 | "discretizer.transform(dataset[['age']])"
101 | ]
102 | },
103 | {
104 | "cell_type": "markdown",
105 | "id": "618b8d6d",
106 | "metadata": {},
107 | "source": [
108 | "### Otros argumentos a la función\n",
109 | "\n",
110 | "La clase KBinsDiscretizer
también nos permite especificar la estrategia que debe seguir para definir la anchura del intervalo cubierto por cada una de nuestras cubetas.\n",
111 | "\n",
112 | "Podemos utilizar el argumento strategy
igual a uniform
si queremos que los intervalos sean del mismo tamaño. Podemos establecer este valor igual a quantile
si queremos que cada cubeta contenga la misma cantidad de elementos y por último podemos utilizar kmeans
si queremos que los elementos se acomoden por si solos de acuerdo a un algoritmo similar a k-means."
113 | ]
114 | },
115 | {
116 | "cell_type": "markdown",
117 | "id": "e43c995d",
118 | "metadata": {},
119 | "source": [
120 | "### Otros casos para la discretización\n",
121 | "\n",
122 | "La discretización se puede aplicar en edades, datos sobre el consumo de energía, niveles de satisfación, precios de hogares... etcétera."
123 | ]
124 | },
125 | {
126 | "cell_type": "markdown",
127 | "id": "8b3c3795",
128 | "metadata": {},
129 | "source": [
130 | "## Binarización\n",
131 | "\n",
132 | "Otra forma de convertir de valores continuos a valores discretos es a través de un proceso llamado binarización. Como el nombre lo indica, esta técnica consiste en convertir un valor continuo en uno de dos valores a partir de cierto umbral o threshold.\n",
133 | "\n",
134 | "Scikit-learn nos ofrece la clase Binarizer
:"
135 | ]
136 | },
137 | {
138 | "cell_type": "code",
139 | "execution_count": null,
140 | "id": "4519152c",
141 | "metadata": {},
142 | "outputs": [],
143 | "source": [
144 | "from sklearn.preprocessing import Binarizer"
145 | ]
146 | },
147 | {
148 | "cell_type": "markdown",
149 | "id": "a0e9fcc5",
150 | "metadata": {},
151 | "source": [
152 | "Esta clase toma únicamente un solo argumento, el umbral que queremos que tome en cuenta para la binarización, supon que queremos binarizar la columna savings
en nuestro dataset, y establecer el umbral en 1000
:"
153 | ]
154 | },
155 | {
156 | "cell_type": "code",
157 | "execution_count": null,
158 | "id": "671ecd64",
159 | "metadata": {},
160 | "outputs": [],
161 | "source": [
162 | "binarizer = Binarizer(threshold=1000)"
163 | ]
164 | },
165 | {
166 | "cell_type": "markdown",
167 | "id": "4d3372a4",
168 | "metadata": {},
169 | "source": [
170 | "Llamamos al método fit_transform
aunque en este caso no tiene mucho sentido llamar a fit
porque en realidad no hay nada que aprender:"
171 | ]
172 | },
173 | {
174 | "cell_type": "code",
175 | "execution_count": null,
176 | "id": "305077f7",
177 | "metadata": {},
178 | "outputs": [],
179 | "source": [
180 | "savings_binarized = binarizer.fit_transform(dataset[['savings']])\n",
181 | "savings_binarized"
182 | ]
183 | },
184 | {
185 | "cell_type": "markdown",
186 | "id": "535b111c",
187 | "metadata": {},
188 | "source": [
189 | "Otros casos para la binarización\n",
190 | "\n",
191 | "La binarización nos ayuda a cuando solamente nos interesa saber si algo pasó o no, y no necesariamente cuantas veces sucedió. Por ejemplo: en lugar de contar cuantas veces un usuario visitó una página, solamente queremos saber si la visitó o no. También nos puede resultar util para diferenciar entre valores extremos y aquellos que no lo son, por ejemplo en edades, podríamos decir que alguien que tenga más de 100 años es un caso extremo y marcarlo con un binarizer.\n",
192 | "\n",
193 | "Y pues ahí lo tienes. Ya cuentas con algunas herramientas para pasar de un espacio continuo de valores a uno discreto."
194 | ]
195 | }
196 | ],
197 | "metadata": {
198 | "kernelspec": {
199 | "display_name": "Python 3 (ipykernel)",
200 | "language": "python",
201 | "name": "python3"
202 | },
203 | "language_info": {
204 | "codemirror_mode": {
205 | "name": "ipython",
206 | "version": 3
207 | },
208 | "file_extension": ".py",
209 | "mimetype": "text/x-python",
210 | "name": "python",
211 | "nbconvert_exporter": "python",
212 | "pygments_lexer": "ipython3",
213 | "version": "3.8.11"
214 | },
215 | "notion_metadata": {
216 | "archived": false,
217 | "cover": null,
218 | "created_by": {
219 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
220 | "object": "user"
221 | },
222 | "created_time": "2023-03-14T20:07:00.000Z",
223 | "icon": null,
224 | "id": "1a658574-6476-4293-b710-0a36ce82a785",
225 | "last_edited_by": {
226 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
227 | "object": "user"
228 | },
229 | "last_edited_time": "2023-04-21T17:05:00.000Z",
230 | "object": "page",
231 | "parent": {
232 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
233 | "type": "database_id"
234 | },
235 | "properties": {
236 | "Assign": {
237 | "id": "%5DtZZ",
238 | "people": [],
239 | "type": "people"
240 | },
241 | "Code name": {
242 | "id": "PT%5CP",
243 | "rich_text": [],
244 | "type": "rich_text"
245 | },
246 | "Name": {
247 | "id": "title",
248 | "title": [
249 | {
250 | "annotations": {
251 | "bold": false,
252 | "code": false,
253 | "color": "default",
254 | "italic": false,
255 | "strikethrough": false,
256 | "underline": false
257 | },
258 | "href": null,
259 | "plain_text": "2.2.2 Discretización",
260 | "text": {
261 | "content": "2.2.2 Discretización",
262 | "link": null
263 | },
264 | "type": "text"
265 | }
266 | ],
267 | "type": "title"
268 | },
269 | "Order": {
270 | "id": "k_Cb",
271 | "number": 3.23,
272 | "type": "number"
273 | },
274 | "Real order": {
275 | "id": "%7Dn_k",
276 | "rich_text": [
277 | {
278 | "annotations": {
279 | "bold": false,
280 | "code": false,
281 | "color": "default",
282 | "italic": false,
283 | "strikethrough": false,
284 | "underline": false
285 | },
286 | "href": null,
287 | "plain_text": "10",
288 | "text": {
289 | "content": "10",
290 | "link": null
291 | },
292 | "type": "text"
293 | }
294 | ],
295 | "type": "rich_text"
296 | },
297 | "Status": {
298 | "id": "s%7D%5Ea",
299 | "status": {
300 | "color": "green",
301 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
302 | "name": "Done"
303 | },
304 | "type": "status"
305 | }
306 | },
307 | "url": "https://www.notion.so/2-2-2-Discretizaci-n-1a65857464764293b7100a36ce82a785"
308 | }
309 | },
310 | "nbformat": 4,
311 | "nbformat_minor": 5
312 | }
313 |
--------------------------------------------------------------------------------
/2-2-4-simple-imputer.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "f19e8ebd",
6 | "metadata": {},
7 | "source": [
8 | "# Imputación de características\n",
9 | "\n",
10 | "En machine learning es común el encontrarte con que el dataset que estás utilizando tiene información faltante en algunas de sus columnas. La información faltante en el mundo de Python científico está a veces codificada como NaN
s o valores nulos.\n",
11 | "\n",
12 | "El hecho de que falte información se puede dar gracias a varios factores: hubo un error en la recolección de datos, los datos se corrompieron en alguna parte o simplemente nunca se recolectaron.\n",
13 | "\n",
14 | "Bajo ciertas condiciones es mejor calcular valores para que esos valores faltantes no afecten el desempeño de tu modelo causando predicciones sesgadas o incorrectas. Y de hecho, muchos de los algoritmos que ofrece scikit-learn requieren de que tu dataset no contenga valores nulos.\n",
15 | "\n",
16 | "Para esta tarea, scikit-learn nos ofrece una clase llamada SimpleImputer
que nos ayuda con la tarea de rellenar los valores faltantes a través de diferentes estrategias.\n",
17 | "\n",
18 | "Vamos a crear un dataset con algunos datos faltantes:"
19 | ]
20 | },
21 | {
22 | "cell_type": "code",
23 | "execution_count": null,
24 | "id": "7cce4dc5",
25 | "metadata": {},
26 | "outputs": [],
27 | "source": [
28 | "import pandas as pd\n",
29 | "\n",
30 | "data =pd.DataFrame([\n",
31 | " ('red', 1, 1.0, -1), ('blue', 2, None, -3), (None, 3, 3.0, -5),\n",
32 | " ('red', 4, 4.0, -2), ('red', None, 5.0, -5), ('blue', 6, 6.0, -1),\n",
33 | " ('red', 7, None), ('blue', 8, 8.0, None), ('green', 9, 9.0, None),\n",
34 | " ('red', 10, 10.0, None),\n",
35 | "], columns=['color', 'number', 'value', 'other'])\n",
36 | "\n",
37 | "data"
38 | ]
39 | },
40 | {
41 | "cell_type": "markdown",
42 | "id": "afc21ced",
43 | "metadata": {},
44 | "source": [
45 | "Para utilizarlo, primero hay que importarlo de sklearn.impute
"
46 | ]
47 | },
48 | {
49 | "cell_type": "code",
50 | "execution_count": null,
51 | "id": "5a726e4c",
52 | "metadata": {},
53 | "outputs": [],
54 | "source": [
55 | "from sklearn.impute import SimpleImputer"
56 | ]
57 | },
58 | {
59 | "cell_type": "markdown",
60 | "id": "c91d3adf",
61 | "metadata": {},
62 | "source": [
63 | "Primero vamos a trabajar con valores numéricos, y con los argumentos por default de la clase, que utilizará el promedio:"
64 | ]
65 | },
66 | {
67 | "cell_type": "code",
68 | "execution_count": null,
69 | "id": "d8e88e3a",
70 | "metadata": {},
71 | "outputs": [],
72 | "source": [
73 | "\n",
74 | "imputer = SimpleImputer()\n",
75 | "imputer.fit(data[['value']])\n",
76 | "data['value'] = imputer.transform(data[['value']])\n",
77 | "data"
78 | ]
79 | },
80 | {
81 | "cell_type": "markdown",
82 | "id": "d0431fd3",
83 | "metadata": {},
84 | "source": [
85 | "Digamos que para otra columna, lo que quieres es utilizar la media, en lugar de el promedio:"
86 | ]
87 | },
88 | {
89 | "cell_type": "code",
90 | "execution_count": null,
91 | "id": "ac479fa6",
92 | "metadata": {},
93 | "outputs": [],
94 | "source": [
95 | "imputer = SimpleImputer(strategy='median')\n",
96 | "imputer.fit(data[['number']])\n",
97 | "data['number'] = imputer.transform(data[['number']])\n",
98 | "data"
99 | ]
100 | },
101 | {
102 | "cell_type": "markdown",
103 | "id": "fc73a01c",
104 | "metadata": {},
105 | "source": [
106 | "También es posible rellenar valores a partir del elemento más frecuente, por ejemplo, para el valor faltante en la columna color
podemos elegir esta opción puesto que las dos anteriores solamente funcionan con datos numéricos:"
107 | ]
108 | },
109 | {
110 | "cell_type": "code",
111 | "execution_count": null,
112 | "id": "35efa2ac",
113 | "metadata": {},
114 | "outputs": [],
115 | "source": [
116 | "imputer = SimpleImputer(missing_values=pd.NA, strategy='most_frequent')\n",
117 | "imputer.fit(data[['color']])\n",
118 | "data['color'] = imputer.transform(data[['color']]).squeeze()\n",
119 | "data"
120 | ]
121 | },
122 | {
123 | "cell_type": "markdown",
124 | "id": "074ec7f0",
125 | "metadata": {},
126 | "source": [
127 | "La cuarta y última estrategia es la de establecer un valor constante. Útil cuando tu has calculado este valor de antemano, para esto es ideal utilizar dos argumentos strategy='constant'
y fill_value
con el valor que quieres poner:"
128 | ]
129 | },
130 | {
131 | "cell_type": "code",
132 | "execution_count": null,
133 | "id": "4106dd63",
134 | "metadata": {},
135 | "outputs": [],
136 | "source": [
137 | "imputer = SimpleImputer(strategy='constant', fill_value=10)\n",
138 | "imputer.fit(data[['other']])\n",
139 | "data['other'] = imputer.transform(data[['other']])\n",
140 | "data"
141 | ]
142 | },
143 | {
144 | "cell_type": "markdown",
145 | "id": "2799b3f2",
146 | "metadata": {},
147 | "source": [
148 | "Y así es como podemos tener un dataset sin valores faltantes, listo para ser procesado y usado para entrenar un modelo de machine learning utilizando scikit-learn."
149 | ]
150 | }
151 | ],
152 | "metadata": {
153 | "kernelspec": {
154 | "display_name": "Python 3 (ipykernel)",
155 | "language": "python",
156 | "name": "python3"
157 | },
158 | "language_info": {
159 | "codemirror_mode": {
160 | "name": "ipython",
161 | "version": 3
162 | },
163 | "file_extension": ".py",
164 | "mimetype": "text/x-python",
165 | "name": "python",
166 | "nbconvert_exporter": "python",
167 | "pygments_lexer": "ipython3",
168 | "version": "3.8.11"
169 | },
170 | "notion_metadata": {
171 | "archived": false,
172 | "cover": null,
173 | "created_by": {
174 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
175 | "object": "user"
176 | },
177 | "created_time": "2023-03-14T20:24:00.000Z",
178 | "icon": null,
179 | "id": "232c0d13-c3ab-4335-81aa-86b36896e450",
180 | "last_edited_by": {
181 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
182 | "object": "user"
183 | },
184 | "last_edited_time": "2023-04-21T17:05:00.000Z",
185 | "object": "page",
186 | "parent": {
187 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
188 | "type": "database_id"
189 | },
190 | "properties": {
191 | "Assign": {
192 | "id": "%5DtZZ",
193 | "people": [],
194 | "type": "people"
195 | },
196 | "Code name": {
197 | "id": "PT%5CP",
198 | "rich_text": [],
199 | "type": "rich_text"
200 | },
201 | "Name": {
202 | "id": "title",
203 | "title": [
204 | {
205 | "annotations": {
206 | "bold": false,
207 | "code": false,
208 | "color": "default",
209 | "italic": false,
210 | "strikethrough": false,
211 | "underline": false
212 | },
213 | "href": null,
214 | "plain_text": "2.2.4 Simple imputer",
215 | "text": {
216 | "content": "2.2.4 Simple imputer",
217 | "link": null
218 | },
219 | "type": "text"
220 | }
221 | ],
222 | "type": "title"
223 | },
224 | "Order": {
225 | "id": "k_Cb",
226 | "number": 3.31,
227 | "type": "number"
228 | },
229 | "Real order": {
230 | "id": "%7Dn_k",
231 | "rich_text": [
232 | {
233 | "annotations": {
234 | "bold": false,
235 | "code": false,
236 | "color": "default",
237 | "italic": false,
238 | "strikethrough": false,
239 | "underline": false
240 | },
241 | "href": null,
242 | "plain_text": "12",
243 | "text": {
244 | "content": "12",
245 | "link": null
246 | },
247 | "type": "text"
248 | }
249 | ],
250 | "type": "rich_text"
251 | },
252 | "Status": {
253 | "id": "s%7D%5Ea",
254 | "status": {
255 | "color": "green",
256 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
257 | "name": "Done"
258 | },
259 | "type": "status"
260 | }
261 | },
262 | "url": "https://www.notion.so/2-2-4-Simple-imputer-232c0d13c3ab433581aa86b36896e450"
263 | }
264 | },
265 | "nbformat": 4,
266 | "nbformat_minor": 5
267 | }
268 |
--------------------------------------------------------------------------------
/3-1-1-cross-validation.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "42cdf0ca",
6 | "metadata": {},
7 | "source": [
8 | "# Validación cruzada\n",
9 | "\n",
10 | "La validación cruzada o cross-validation es una técnica que permite evaluar el desempeño de un modelo de aprendizaje automático.\n",
11 | "\n",
12 | "Ya sabemos que el entrenar un modelo con un conjunto de datos y probarlos en el mismo conjunto de datos es un error, es por eso que usualmente dividimos los datos en dos conjuntos: entrenamiento y prueba.\n",
13 | "\n",
14 | "Es útil porque en lugar de dividir los datos en dos conjuntos estáticos de entrenamiento y prueba como normalmente estamos acostumbrados, esta técnica divide los datos en múltiples conjuntos llamados “folds” en inglés. \n",
15 | "\n",
16 | "Supongamos que tienes estos datos. Lo que hace crossvalidation es dividirlos en N grupos de tamaño más o menos similar.\n",
17 | "\n",
18 | "Entonces la distribución de los datos queda así:\n",
19 | "\n",
20 | "Después se va iterando sobre todas las combinaciones de estos datos, utilizando un conjunto de entrenamiento diferente para cada iteración y evaluando también sobre un conjunto diferente a cada vez. Esto nos va a dar como resultado N medidas de qué tan bueno es nuestro algoritmo.\n",
21 | "\n",
22 | "Este método de entrenamiento es útil para tener una mejor estimación del modelo en datos no vistos, esto cobra mayor relevancia cuando no tenemos un conjunto de datos de gran tamaño.\n",
23 | "\n",
24 | "El método por excelencia para aplicar y calcular la validación cruzada en scikit-learn es la función cross_val_score
."
25 | ]
26 | },
27 | {
28 | "cell_type": "markdown",
29 | "id": "24c60287",
30 | "metadata": {},
31 | "source": [
32 | "## La función cross_val_score
\n",
33 | "\n",
34 | "Una de las formas más fáciles de utilizar la validación cruzada es a través de la función cross_val_score
. Esta es una función que entrena y prueba un modelo en todos y cada uno de los folds generados por la validación cruzada.\n",
35 | "\n",
36 | "Lo que sucede dentro de la función es que a cada iteración se crea un y se entrena un nuevo modelo, luego se evalúa y la calificación que recibe se guarda en un arreglo. Arreglo que será regresado como resultado de la función.\n",
37 | "\n",
38 | "Para verlo con un ejemplo, vamos a cargar un dataset y un modelo de machine learning. En una lección a futuro vamos a ver modelos mas a detalle, por ahora, simplemente ejecuta el código:"
39 | ]
40 | },
41 | {
42 | "cell_type": "code",
43 | "execution_count": null,
44 | "id": "876ac4bb",
45 | "metadata": {},
46 | "outputs": [],
47 | "source": [
48 | "from sklearn.datasets import load_iris\n",
49 | "from sklearn.ensemble import RandomForestClassifier"
50 | ]
51 | },
52 | {
53 | "cell_type": "code",
54 | "execution_count": null,
55 | "id": "8d3c469a",
56 | "metadata": {},
57 | "outputs": [],
58 | "source": [
59 | "\n",
60 | "\n",
61 | "iris_dataset = load_iris()\n",
62 | "X, y = iris_dataset.data, iris_dataset.target\n",
63 | "\n",
64 | "X[:10]"
65 | ]
66 | },
67 | {
68 | "cell_type": "markdown",
69 | "id": "384c7eb0",
70 | "metadata": {},
71 | "source": [
72 | "Ahora si, para usar cross_val_score
hay que importar la función de sklearn.model_selection
:"
73 | ]
74 | },
75 | {
76 | "cell_type": "code",
77 | "execution_count": null,
78 | "id": "4a764a8b",
79 | "metadata": {},
80 | "outputs": [],
81 | "source": [
82 | "from sklearn.model_selection import cross_val_score"
83 | ]
84 | },
85 | {
86 | "cell_type": "markdown",
87 | "id": "c75e0786",
88 | "metadata": {},
89 | "source": [
90 | "Y procedemos a usarla pasando como argumentos el modelo sin entrenar, los datos de entrada y la etiqueta:"
91 | ]
92 | },
93 | {
94 | "cell_type": "code",
95 | "execution_count": null,
96 | "id": "1b1310f7",
97 | "metadata": {},
98 | "outputs": [],
99 | "source": [
100 | "model = RandomForestClassifier()\n",
101 | "\n",
102 | "scores = cross_val_score(model, X, y, cv=5)"
103 | ]
104 | },
105 | {
106 | "cell_type": "markdown",
107 | "id": "1b8a373b",
108 | "metadata": {},
109 | "source": [
110 | "Dependiendo del tamaño de tus datos la función se puede tardar un poco de tiempo.\n",
111 | "\n",
112 | "Recuerda lo que está sucediendo internamente, tus datos están siendo divididos en 5 segmentos, valor que en este caso estamos estableciendo con el argumento cv
. Cada uno de estos segmentos será usado para probar el desempeño de un modelo entrenado en los segmentos restantes.\n",
113 | "\n",
114 | "Si visualizamos el resultado de la función verás 5 valores."
115 | ]
116 | },
117 | {
118 | "cell_type": "code",
119 | "execution_count": null,
120 | "id": "b29ed3ba",
121 | "metadata": {},
122 | "outputs": [],
123 | "source": [
124 | "scores"
125 | ]
126 | },
127 | {
128 | "cell_type": "markdown",
129 | "id": "8ec115a9",
130 | "metadata": {},
131 | "source": [
132 | "Cada uno de estos valores representa el puntaje del modelo en cada uno de estos segmentos de validación cruzada. En este caso, el modelo obtuvo un puntaje alto en la mayoría de los segmentos, lo que sugiere que el modelo que le pasamos a la función tiene un desempeño aceptable."
133 | ]
134 | },
135 | {
136 | "cell_type": "markdown",
137 | "id": "18eabb78",
138 | "metadata": {},
139 | "source": [
140 | "### ¿Cómo le hago para entrenar un modelo con cross_val_score
?\n",
141 | "\n",
142 | "Si bien la función entrena modelos, solamente lo realiza con la finalidad de evaluar el desempeño. Si todas las puntuaciones que te devolvió la función son aceptables, puedes proceder a entrenar un modelo final utilizando todos tus datos de entrenamiento, ese sería el modelo que tendrías que probar con tu conjunto de pruebas y posteriormente pasar a producción."
143 | ]
144 | },
145 | {
146 | "cell_type": "code",
147 | "execution_count": null,
148 | "id": "3d0daa64",
149 | "metadata": {},
150 | "outputs": [],
151 | "source": [
152 | "model.fit(X, y)"
153 | ]
154 | },
155 | {
156 | "cell_type": "markdown",
157 | "id": "58ae67de",
158 | "metadata": {},
159 | "source": [
160 | "## Otros argumentos\n",
161 | "\n",
162 | "Los argumentos base de la función son el estimador, o el modelo a probar, las variables de entrada y la etiqueta. Opcionalmente podemos especificar cuántos segmentos queremos utilizando el argumento cv
pasándole un número entero.\n",
163 | "\n",
164 | "Te estarás preguntando, ¿qué métrica está utilizando para medir el desempeño? – por default, cross_val_score
\n",
165 | " utiliza una métrica de evaluación específica del estimador en cuestión. Por ejemplo, para un clasificador de regresión logística, la métrica predeterminada es la exactitud (accuracy
), mientras que para un modelo de regresión, la métrica predeterminada es el coeficiente de determinación R-cuadrado (R^2
). Sin embargo, se puede especificar una métrica diferente a través del argumento scoring
si se desea utilizar una medida de evaluación diferente a la predeterminada.\n",
166 | "\n",
167 | "Por ejemplo, digamos que quieres utilizar 6 segmentos y en lugar de utilizar accuracy
, nos interesa más saber la precisión del modelo. Tendríamos que llamar a cross_val_score
así:"
168 | ]
169 | },
170 | {
171 | "cell_type": "code",
172 | "execution_count": null,
173 | "id": "cce6f5de",
174 | "metadata": {},
175 | "outputs": [],
176 | "source": [
177 | "model = RandomForestClassifier()\n",
178 | "\n",
179 | "scores = cross_val_score(model, X, y, cv = 6, scoring='precision_macro', verbose = 3)"
180 | ]
181 | },
182 | {
183 | "cell_type": "markdown",
184 | "id": "999c53a7",
185 | "metadata": {},
186 | "source": [
187 | "Si te das cuenta, también estoy especificando verbose
igual a 3, que controla la cantidad de información que se imprime durante la validación cruzada. Un valor mayor de verbose
imprime más información, mientras que un valor menor imprime menos información. Útil para cuando quieres saber qué es lo que está sucediendo."
188 | ]
189 | },
190 | {
191 | "cell_type": "markdown",
192 | "id": "f41006ed",
193 | "metadata": {},
194 | "source": [
195 | "## La función cross_validate
\n",
196 | "\n",
197 | "Existe otra función aún más genérica que cross_val_score
pero que recibe los mismos argumentos. La gran diferencia entre ellos es que cross_validate
permite especificar varias métricas para evaluar y regresa más información:"
198 | ]
199 | },
200 | {
201 | "cell_type": "code",
202 | "execution_count": null,
203 | "id": "4b196ed7",
204 | "metadata": {},
205 | "outputs": [],
206 | "source": [
207 | "from sklearn.model_selection import cross_validate"
208 | ]
209 | },
210 | {
211 | "cell_type": "code",
212 | "execution_count": null,
213 | "id": "d817d789",
214 | "metadata": {},
215 | "outputs": [],
216 | "source": [
217 | "model = RandomForestClassifier()\n",
218 | "\n",
219 | "scores = cross_validate(model, X, y, cv = 6, \n",
220 | "\tscoring=[\n",
221 | "\t\t'precision_macro', \n",
222 | "\t\t'precision_micro',\n",
223 | "\t\t'accuracy'\n",
224 | "\t],\n",
225 | "\tverbose = 3)"
226 | ]
227 | },
228 | {
229 | "cell_type": "markdown",
230 | "id": "3a3dca4d",
231 | "metadata": {},
232 | "source": [
233 | "Y el resultado es un diccionario con más información:"
234 | ]
235 | },
236 | {
237 | "cell_type": "code",
238 | "execution_count": null,
239 | "id": "2febd538",
240 | "metadata": {},
241 | "outputs": [],
242 | "source": [
243 | "for key, values in scores.items():\n",
244 | " print(key)\n",
245 | " print(values)"
246 | ]
247 | },
248 | {
249 | "cell_type": "markdown",
250 | "id": "16e89955",
251 | "metadata": {},
252 | "source": [
253 | "Y pues ahí lo tienen, esta lección se trató sobre la validación cruzada y cómo es que se puede usar en scikit-learn, recuerda que esta es una técnica importante en aprendizaje automático y se recomienda utilizarla siempre que sea posible. Sin embargo, es importante tener en cuenta la complejidad computacional y los recursos que se consumirían cuando estemos trabajando con una gran cantidad de información. \n",
254 | "\n",
255 | "Es por eso que la validación cruzada es especialmente útil cuando se dispone de un conjunto de datos pequeño y se desea estimar el rendimiento de un modelo de forma fiable."
256 | ]
257 | }
258 | ],
259 | "metadata": {
260 | "kernelspec": {
261 | "display_name": "Python 3 (ipykernel)",
262 | "language": "python",
263 | "name": "python3"
264 | },
265 | "language_info": {
266 | "codemirror_mode": {
267 | "name": "ipython",
268 | "version": 3
269 | },
270 | "file_extension": ".py",
271 | "mimetype": "text/x-python",
272 | "name": "python",
273 | "nbconvert_exporter": "python",
274 | "pygments_lexer": "ipython3",
275 | "version": "3.8.11"
276 | },
277 | "notion_metadata": {
278 | "archived": false,
279 | "cover": null,
280 | "created_by": {
281 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
282 | "object": "user"
283 | },
284 | "created_time": "2023-03-14T20:34:00.000Z",
285 | "icon": null,
286 | "id": "84756db3-1a8f-48c7-bbaf-6eff93f0d749",
287 | "last_edited_by": {
288 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
289 | "object": "user"
290 | },
291 | "last_edited_time": "2023-04-21T17:05:00.000Z",
292 | "object": "page",
293 | "parent": {
294 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
295 | "type": "database_id"
296 | },
297 | "properties": {
298 | "Assign": {
299 | "id": "%5DtZZ",
300 | "people": [],
301 | "type": "people"
302 | },
303 | "Code name": {
304 | "id": "PT%5CP",
305 | "rich_text": [],
306 | "type": "rich_text"
307 | },
308 | "Name": {
309 | "id": "title",
310 | "title": [
311 | {
312 | "annotations": {
313 | "bold": false,
314 | "code": false,
315 | "color": "default",
316 | "italic": false,
317 | "strikethrough": false,
318 | "underline": false
319 | },
320 | "href": null,
321 | "plain_text": "3.1.1 Cross-validation",
322 | "text": {
323 | "content": "3.1.1 Cross-validation",
324 | "link": null
325 | },
326 | "type": "text"
327 | }
328 | ],
329 | "type": "title"
330 | },
331 | "Order": {
332 | "id": "k_Cb",
333 | "number": 4.1,
334 | "type": "number"
335 | },
336 | "Real order": {
337 | "id": "%7Dn_k",
338 | "rich_text": [
339 | {
340 | "annotations": {
341 | "bold": false,
342 | "code": false,
343 | "color": "default",
344 | "italic": false,
345 | "strikethrough": false,
346 | "underline": false
347 | },
348 | "href": null,
349 | "plain_text": "13",
350 | "text": {
351 | "content": "13",
352 | "link": null
353 | },
354 | "type": "text"
355 | }
356 | ],
357 | "type": "rich_text"
358 | },
359 | "Status": {
360 | "id": "s%7D%5Ea",
361 | "status": {
362 | "color": "green",
363 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
364 | "name": "Done"
365 | },
366 | "type": "status"
367 | }
368 | },
369 | "url": "https://www.notion.so/3-1-1-Cross-validation-84756db31a8f48c7bbaf6eff93f0d749"
370 | }
371 | },
372 | "nbformat": 4,
373 | "nbformat_minor": 5
374 | }
375 |
--------------------------------------------------------------------------------
/3-1-2-hyper-parameter-tunning.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "c36887c2",
6 | "metadata": {},
7 | "source": [
8 | "# Búsqueda de hiperparámetros\n",
9 | "\n",
10 | "Sabes que para entrenar un modelo de machine learning es necesario establecer algunos valores y configuraciones para modificar el comportamiento del entrenamiento, estos son conocidos como hiperparámetros. Por poner un ejemplo de estos hiperparámetros, toma la clase RandomForestRegressor
(más adelante veremos los algoritmos de machine learning que sklear nos ofrece, por el momento no te preocupes):"
11 | ]
12 | },
13 | {
14 | "cell_type": "code",
15 | "execution_count": null,
16 | "id": "29d1de95",
17 | "metadata": {},
18 | "outputs": [],
19 | "source": [
20 | "from sklearn.ensemble import RandomForestRegressor\n",
21 | "\n",
22 | "models = RandomForestRegressor(\n",
23 | " n_estimators = 10,\n",
24 | " criterion = \"gini\",\n",
25 | " max_depth = 10,\n",
26 | " max_leaf_nodes = 100\n",
27 | ")"
28 | ]
29 | },
30 | {
31 | "cell_type": "markdown",
32 | "id": "bdfecf23",
33 | "metadata": {},
34 | "source": [
35 | "En donde los hiperarámetros son: el número de árboles, el criterio de división, la profundidad máxima y la cantidad mínima de muestras por hoja.\n",
36 | "\n",
37 | "Estos valores tienen un impacto significativo en el desempeño del modelo, y pueden ser la diferencia entre un mal modelo y uno que funciona a la perfección.\n",
38 | "\n",
39 | "A pesar de que los hiperparámetros por default que las clases de scikit-learn tienen son valores razonables, no son necesariamente óptimos para todos los conjuntos de datos o para todos los problemas de aprendizaje automático. Por lo tanto, es importante hacer una búsqueda de hiperparámetros para encontrar los valores óptimos que maximicen el desempeño del modelo en todos nuestros conjuntos de datos.\n",
40 | "\n",
41 | "Realizar esta búsqueda lleva tiempo y esfuerzo, pero es una inversión que vale la pena hacer por la mejora que representan en nuestro modelo estos parámetros.\n",
42 | "\n",
43 | "Scikit-learn nos ofrece varias opciones para cuando se trata de hacer la búsqueda de estos hiperparámetros de forma sistemática en lugar de manual.\n",
44 | "\n",
45 | "Las técnicas son: grid search o búsqueda en cuadrícula y búsqueda aleatoria o random search en inglés. Cada una tiene sus ventajas y desventajas, en esta lección yo te hablaré de la búsqueda aleatoria:\n",
46 | "\n",
47 | "Solo una pequeña nota, en scikit-learn las búsquedas de hiperparámetros están siempre conectadas con al validación cruzada, para garantizar que los valores elegidos sean una elección correcta para el conjunto de datos."
48 | ]
49 | },
50 | {
51 | "cell_type": "markdown",
52 | "id": "30da6f91",
53 | "metadata": {},
54 | "source": [
55 | "## Random search\n",
56 | "\n",
57 | "Ahora si, vamos a ver un ejemplo en un problema de regresión.\n",
58 | "\n",
59 | "Primero, carguemos el dataset y dividámoslo en los conjuntos de entrenamiento y prueba:"
60 | ]
61 | },
62 | {
63 | "cell_type": "code",
64 | "execution_count": null,
65 | "id": "5de22911",
66 | "metadata": {},
67 | "outputs": [],
68 | "source": [
69 | "from sklearn.datasets import fetch_california_housing\n",
70 | "from sklearn.model_selection import train_test_split\n",
71 | "\n",
72 | "housing_dataset = fetch_california_housing()\n",
73 | "\n",
74 | "X_train, X_test, y_train, y_test = train_test_split(\n",
75 | "\thousing_dataset.data,\n",
76 | "\thousing_dataset.target,\n",
77 | "\trandom_state=42\n",
78 | ")"
79 | ]
80 | },
81 | {
82 | "cell_type": "markdown",
83 | "id": "a53fa0e9",
84 | "metadata": {},
85 | "source": [
86 | "Luego vamos a crear un modelo de regresión:"
87 | ]
88 | },
89 | {
90 | "cell_type": "code",
91 | "execution_count": null,
92 | "id": "dcc40d3c",
93 | "metadata": {},
94 | "outputs": [],
95 | "source": [
96 | "from sklearn.ensemble import RandomForestRegressor\n",
97 | "\n",
98 | "model = RandomForestRegressor()"
99 | ]
100 | },
101 | {
102 | "cell_type": "markdown",
103 | "id": "eefa3252",
104 | "metadata": {},
105 | "source": [
106 | "Debemos definir el espacio de parámetros en el que vamos a buscar - este espacio de búsqueda será utilizado por Random Search para generar combinaciones de hiperparámetros de forma aleatoria, estas combinaciones serán utilizadas para crear nuevas instancias de nuestro RandomForestRegressor y ejecutar validación cruzada sobre ellos, evaluando así qué tan buenos son para encontrar la mejor combinación."
107 | ]
108 | },
109 | {
110 | "cell_type": "code",
111 | "execution_count": null,
112 | "id": "6cb7f63a",
113 | "metadata": {},
114 | "outputs": [],
115 | "source": [
116 | "param_distributions = {\n",
117 | " # 'n_estimators': [100, 1000, 2000],\n",
118 | " # 'criterion': [\"squared_error\", \"absolute_error\", \"friedman_mse\"],\n",
119 | " # 'max_depth': [None, 10, 100],\n",
120 | " 'max_features': [\"sqrt\", \"log2\"],\n",
121 | " 'max_leaf_nodes': [None, 10, 100, 1000]\n",
122 | "}"
123 | ]
124 | },
125 | {
126 | "cell_type": "markdown",
127 | "id": "1f91b54c",
128 | "metadata": {},
129 | "source": [
130 | "Y finalmente, importamos la clase RandomizedSearchCV
:"
131 | ]
132 | },
133 | {
134 | "cell_type": "code",
135 | "execution_count": null,
136 | "id": "800ca2b6",
137 | "metadata": {},
138 | "outputs": [],
139 | "source": [
140 | "from sklearn.model_selection import RandomizedSearchCV"
141 | ]
142 | },
143 | {
144 | "cell_type": "markdown",
145 | "id": "a997cc26",
146 | "metadata": {},
147 | "source": [
148 | "Creamos una instancia, pasándole el modelo, el conjunto de parámetros. Después especificamos el número de iteraciones, recuerda que la búsqueda is aleatoria, el número de iteraciones especifica cuántos intentos haremos para encontrar los mejores hiperparámetros. Con cv
especificamos el número de subconjuntos para la validación cruzada y por último, fijamos el estado aleatorio en 42 para que el resultado sea reproducible."
149 | ]
150 | },
151 | {
152 | "cell_type": "code",
153 | "execution_count": null,
154 | "id": "26578951",
155 | "metadata": {},
156 | "outputs": [],
157 | "source": [
158 | "search = RandomizedSearchCV(model, param_distributions, n_iter=50, cv=5, random_state=42)"
159 | ]
160 | },
161 | {
162 | "cell_type": "markdown",
163 | "id": "4ac3a1f8",
164 | "metadata": {},
165 | "source": [
166 | "Por último llamamos a fit
para comenzar la búsqueda, este recibe los datos de entrenamiento:"
167 | ]
168 | },
169 | {
170 | "cell_type": "code",
171 | "execution_count": null,
172 | "id": "031b2f0d",
173 | "metadata": {},
174 | "outputs": [],
175 | "source": [
176 | "search.fit(X_train, y_train)"
177 | ]
178 | },
179 | {
180 | "cell_type": "markdown",
181 | "id": "409de42c",
182 | "metadata": {},
183 | "source": [
184 | "Este se va a tardar un poco, pero al terminar vamos a poder acceder a los mejores parámetros utilizando el atributo best_params_
y podemos evaluar el mejor modelo conseguido a través del método score
:"
185 | ]
186 | },
187 | {
188 | "cell_type": "code",
189 | "execution_count": null,
190 | "id": "d93753c3",
191 | "metadata": {},
192 | "outputs": [],
193 | "source": [
194 | "print(\"Mejores hiperparámetros: \", search.best_params_)\n",
195 | "print(\"Puntuación de prueba: \", search.score(X_test, y_test))"
196 | ]
197 | },
198 | {
199 | "cell_type": "markdown",
200 | "id": "77bab256",
201 | "metadata": {},
202 | "source": [
203 | "## Entrenando un modelo con los mejores parámetros\n",
204 | "\n",
205 | "Para entrenar el modelo final, podemos tomar los mejores hiperparámetros y pasarlos al constructor, esto crea un modelo fresco con la configuración ideal que acabamos de conseguir y lo entrena con la totalidad de nuestros datos de entrenamiento:"
206 | ]
207 | },
208 | {
209 | "cell_type": "code",
210 | "execution_count": null,
211 | "id": "bcffa359",
212 | "metadata": {},
213 | "outputs": [],
214 | "source": [
215 | "best_model = RandomForestRegressor(**search.best_params_)\n",
216 | "\n",
217 | "best_model.fit(X_train, y_train)"
218 | ]
219 | },
220 | {
221 | "cell_type": "markdown",
222 | "id": "59b4c1b2",
223 | "metadata": {},
224 | "source": [
225 | " > 📚 De tarea, practica usando una búsqueda en cuadrícula, utilizando GridSearchCV
. Cuidado con utilizar muchos parámetros porque grid search toma tiempo para ejecutarse."
226 | ]
227 | },
228 | {
229 | "cell_type": "markdown",
230 | "id": "cf33c156",
231 | "metadata": {},
232 | "source": [
233 | "## No garantiza la mejor solución\n",
234 | "\n",
235 | "Es importante tener en cuenta que la búsqueda de hiperparámetros no garantiza encontrar el conjunto óptimo de hiperparámetros para un modelo dado. Es posible que la combinación óptima de hiperparámetros no se encuentre en el espacio de búsqueda especificado manualmente. Por lo tanto, es importante considerar la búsqueda de hiperparámetros como un proceso iterativo que puede requerir varias iteraciones para alcanzar un conjunto óptimo de hiperparámetros para un modelo dado."
236 | ]
237 | },
238 | {
239 | "cell_type": "markdown",
240 | "id": "4f7ec668",
241 | "metadata": {},
242 | "source": [
243 | "## En conclusión\n",
244 | "\n",
245 | "La búsqueda de hiperparámetros es un paso crucial cuando quieres sacarle el máximo provecho a los datos. En scikit-learn esta búsqueda está fuertemente ligada con la validación cruzada aunque en la teoría son dos conceptos independientes el uno del otro.\n",
246 | "\n",
247 | "scikit-learn ofrece dos métodos de búsqueda de hiperparámetros: GridSearchCV y RandomizedSearchCV. El primero realiza una búsqueda exhaustiva sobre todas las combinaciones posibles de valores de hiperparámetros especificados, mientras que el segundo realiza una búsqueda aleatoria de un subconjunto de combinaciones. En general, RandomizedSearchCV puede ser más eficiente que GridSearchCV cuando el espacio de búsqueda de hiperparámetros es grande.\n",
248 | "\n",
249 | "También recuerda que no es una solución mágica, y que a veces debes iterar en la elección del mejor espacio de búsqueda."
250 | ]
251 | }
252 | ],
253 | "metadata": {
254 | "kernelspec": {
255 | "display_name": "Python 3 (ipykernel)",
256 | "language": "python",
257 | "name": "python3"
258 | },
259 | "language_info": {
260 | "codemirror_mode": {
261 | "name": "ipython",
262 | "version": 3
263 | },
264 | "file_extension": ".py",
265 | "mimetype": "text/x-python",
266 | "name": "python",
267 | "nbconvert_exporter": "python",
268 | "pygments_lexer": "ipython3",
269 | "version": "3.8.11"
270 | },
271 | "notion_metadata": {
272 | "archived": false,
273 | "cover": null,
274 | "created_by": {
275 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
276 | "object": "user"
277 | },
278 | "created_time": "2023-03-14T20:34:00.000Z",
279 | "icon": null,
280 | "id": "8da91980-e5e1-49cb-8e7a-4b74406ffda5",
281 | "last_edited_by": {
282 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
283 | "object": "user"
284 | },
285 | "last_edited_time": "2023-04-21T17:06:00.000Z",
286 | "object": "page",
287 | "parent": {
288 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
289 | "type": "database_id"
290 | },
291 | "properties": {
292 | "Assign": {
293 | "id": "%5DtZZ",
294 | "people": [],
295 | "type": "people"
296 | },
297 | "Code name": {
298 | "id": "PT%5CP",
299 | "rich_text": [],
300 | "type": "rich_text"
301 | },
302 | "Name": {
303 | "id": "title",
304 | "title": [
305 | {
306 | "annotations": {
307 | "bold": false,
308 | "code": false,
309 | "color": "default",
310 | "italic": false,
311 | "strikethrough": false,
312 | "underline": false
313 | },
314 | "href": null,
315 | "plain_text": "3.1.2 Hyper-parameter tunning",
316 | "text": {
317 | "content": "3.1.2 Hyper-parameter tunning",
318 | "link": null
319 | },
320 | "type": "text"
321 | }
322 | ],
323 | "type": "title"
324 | },
325 | "Order": {
326 | "id": "k_Cb",
327 | "number": 4.2,
328 | "type": "number"
329 | },
330 | "Real order": {
331 | "id": "%7Dn_k",
332 | "rich_text": [
333 | {
334 | "annotations": {
335 | "bold": false,
336 | "code": false,
337 | "color": "default",
338 | "italic": false,
339 | "strikethrough": false,
340 | "underline": false
341 | },
342 | "href": null,
343 | "plain_text": "14",
344 | "text": {
345 | "content": "14",
346 | "link": null
347 | },
348 | "type": "text"
349 | }
350 | ],
351 | "type": "rich_text"
352 | },
353 | "Status": {
354 | "id": "s%7D%5Ea",
355 | "status": {
356 | "color": "green",
357 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
358 | "name": "Done"
359 | },
360 | "type": "status"
361 | }
362 | },
363 | "url": "https://www.notion.so/3-1-2-Hyper-parameter-tunning-8da91980e5e149cb8e7a4b74406ffda5"
364 | }
365 | },
366 | "nbformat": 4,
367 | "nbformat_minor": 5
368 | }
369 |
--------------------------------------------------------------------------------
/3-1-6-visualizaciones.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "fd22bbd2",
6 | "metadata": {},
7 | "source": [
8 | "# Visualización de resultados\n",
9 | "\n",
10 | "Las visualización de los datos no solamente se realiza antes de entrenar un modelo durante el análisis exploratorio de datos, sino que también son es herramienta muy útil para entender el desempeño de nuestros modelos de una manera más intuitiva y fácil de interpretar. Afortunadamente, scikit-learn también nos ofrece varias funciones para visualizar los resultados de nuestros modelos.\n",
11 | "\n",
12 | "Las visualizaciones de las que te voy a hablar aplican específicamente para modelos de clasificación.\n",
13 | "\n",
14 | "Para crear estas visualizaciones necesitas tener un modelo ya entrenado, así que eso es lo que estoy haciendo en esta celda."
15 | ]
16 | },
17 | {
18 | "cell_type": "code",
19 | "execution_count": null,
20 | "id": "8b457208",
21 | "metadata": {},
22 | "outputs": [],
23 | "source": [
24 | "# Crea un dataset\n",
25 | "from sklearn.datasets import make_moons\n",
26 | "from sklearn.model_selection import train_test_split\n",
27 | "\n",
28 | "X, y = make_moons(n_samples=1000, random_state=42, noise=0.40)\n",
29 | "X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)\n",
30 | "\n",
31 | "print(f\"Total number of samples: {len(X)}\")\n",
32 | "print(f\"Samples on the test set {len(X_test)}\")"
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": null,
38 | "id": "4f47f9cb",
39 | "metadata": {},
40 | "outputs": [],
41 | "source": [
42 | "# Visualización del dataset\n",
43 | "import matplotlib.pyplot as plt\n",
44 | "\n",
45 | "plt.scatter(X[:, 0], X[:, 1], c=y, edgecolor=\"k\")"
46 | ]
47 | },
48 | {
49 | "cell_type": "code",
50 | "execution_count": null,
51 | "id": "4da1f745",
52 | "metadata": {},
53 | "outputs": [],
54 | "source": [
55 | "# Creamos un clasificador básico para esta lección\n",
56 | "from sklearn.ensemble import RandomForestClassifier\n",
57 | "\n",
58 | "model = RandomForestClassifier()\n",
59 | "model.fit(X_train, y_train)"
60 | ]
61 | },
62 | {
63 | "cell_type": "markdown",
64 | "id": "a6b0160d",
65 | "metadata": {},
66 | "source": [
67 | "## Matriz de confusión\n",
68 | "\n",
69 | "Una matriz de confusión se utiliza para evaluar el desempeño de un modelo de clasificación. Es una matriz cuadrada que muestra la cantidad de verdaderos positivos (TP), falsos positivos (FP), verdaderos negativos (TN) y falsos negativos (FN) para cada clase en un conjunto de datos de prueba."
70 | ]
71 | },
72 | {
73 | "cell_type": "code",
74 | "execution_count": null,
75 | "id": "ebf397c9",
76 | "metadata": {},
77 | "outputs": [],
78 | "source": [
79 | "from sklearn.metrics import ConfusionMatrixDisplay\n",
80 | "\n",
81 | "ConfusionMatrixDisplay.from_estimator(model, X_test, y_test)"
82 | ]
83 | },
84 | {
85 | "cell_type": "markdown",
86 | "id": "95dda091",
87 | "metadata": {},
88 | "source": [
89 | "TP | FP\n",
90 | "-------\n",
91 | "FN | TN\n",
92 | "\n",
93 | "La matriz de confusión funciona comparando las predicciones del modelo con las etiquetas de clase reales en el conjunto de datos de prueba. La diagonal principal de la matriz de confusión muestra los verdaderos positivos y verdaderos negativos, que son las predicciones correctas del modelo. Las otras entradas de la matriz muestran los falsos positivos y falsos negativos, que son las predicciones incorrectas del modelo.\n",
94 | "\n",
95 | "Es recomendable usarla para ver en qué tipo de predicciones está fallando tu modelo, si en los falsos positivos o en los falsos negativos. Estos errores pueden tener consecuencias importantes dependiendo del problema que se esté resolviendo, por lo que es importante utilizar la matriz de confusión para entender y evaluar el desempeño del modelo."
96 | ]
97 | },
98 | {
99 | "cell_type": "markdown",
100 | "id": "48dc290f",
101 | "metadata": {},
102 | "source": [
103 | "## Visualización de las fronteras de decisión\n",
104 | "\n",
105 | "La función DecisionBoundaryDisplay
de scikit-learn es una herramienta útil para visualizar las fronteras de decisión de un modelo de clasificación. Esta función nos permite visualizar las regiones del espacio de características donde el modelo predice cada clase, lo que nos ayuda a entender mejor cómo el modelo está haciendo las clasificaciones.\n",
106 | "\n",
107 | "La visualización de las fronteras de decisión es particularmente útil en casos en los que las clases no se pueden separar perfectamente en el espacio de características. En estos casos, el modelo puede tener dificultades para hacer clasificaciones precisas, y las fronteras de decisión pueden ser irregulares o complejas."
108 | ]
109 | },
110 | {
111 | "cell_type": "code",
112 | "execution_count": null,
113 | "id": "9b264c04",
114 | "metadata": {},
115 | "outputs": [],
116 | "source": [
117 | "from sklearn.inspection import DecisionBoundaryDisplay\n",
118 | "\n",
119 | "DecisionBoundaryDisplay.from_estimator(\n",
120 | " estimator = model, \n",
121 | " X = X,\n",
122 | " alpha=0.5,\n",
123 | ")\n",
124 | "\n",
125 | "plt.scatter(X[:, 0], X[:, 1], c=y, edgecolor=\"k\")"
126 | ]
127 | },
128 | {
129 | "cell_type": "markdown",
130 | "id": "2d143687",
131 | "metadata": {},
132 | "source": [
133 | "## Curva de precisión-recall\n",
134 | "\n",
135 | "La curva de precisión-recall es una herramienta útil para evaluar el desempeño de un modelo de clasificación en términos de precisión y recall para diferentes umbrales de clasificación. La curva se genera trazando la precisión en el eje y y el recall en el eje x para diferentes umbrales de clasificación."
136 | ]
137 | },
138 | {
139 | "cell_type": "code",
140 | "execution_count": null,
141 | "id": "a7d534d0",
142 | "metadata": {},
143 | "outputs": [],
144 | "source": [
145 | "from sklearn.metrics import PrecisionRecallDisplay\n",
146 | "\n",
147 | "PrecisionRecallDisplay.from_estimator(model, X_test, y_test)"
148 | ]
149 | },
150 | {
151 | "cell_type": "markdown",
152 | "id": "a5b7678e",
153 | "metadata": {},
154 | "source": [
155 | "En otras palabras, muestra el tradeoff entre la precisión y el recall, un área grande debajo de la curva representa tanto un alto recall como una alta precisión, donde una alta precisión se relaciona con una baja tasa de falsos positivos, y un alto recall se relaciona con una baja tasa de falsos negativos.\n",
156 | "\n",
157 | "Esta gráfica es particularmente útil cuando nuestro dataset está desbalanceado."
158 | ]
159 | },
160 | {
161 | "cell_type": "markdown",
162 | "id": "2cadd8b5",
163 | "metadata": {},
164 | "source": [
165 | "## ROC Curve\n",
166 | "\n",
167 | "La curva ROC (Receiver Operating Characteristic) es una visualización útil para evaluar el desempeño de un modelo de clasificación en términos de su capacidad para distinguir entre clases. \n",
168 | "\n",
169 | "La curva se genera trazando la tasa de verdaderos positivos (TPR) en el eje y y la tasa de falsos positivos (FPR) en el eje x para diferentes umbrales de clasificación."
170 | ]
171 | },
172 | {
173 | "cell_type": "code",
174 | "execution_count": null,
175 | "id": "52bc0ae7",
176 | "metadata": {},
177 | "outputs": [],
178 | "source": [
179 | "from sklearn.metrics import RocCurveDisplay\n",
180 | "\n",
181 | "RocCurveDisplay.from_estimator(model, X_test, y_test)"
182 | ]
183 | },
184 | {
185 | "cell_type": "markdown",
186 | "id": "75234f0d",
187 | "metadata": {},
188 | "source": [
189 | "Esta permite evaluar la capacidad de un modelo de clasificación para distinguir entre las clases, incluso cuando las distribuciones de clase son desiguales. Es útil porque representa el trade-off entre la tasa de verdaderos positivos y la tasa de falsos positivos. Un clasificador ideal se sitúa en la esquina superior izquierda del gráfico, lo que indica una alta tasa de verdaderos positivos y una baja tasa de falsos positivos. En este caso, el modelo puede distinguir perfectamente entre las dos clases."
190 | ]
191 | },
192 | {
193 | "cell_type": "markdown",
194 | "id": "855306a2",
195 | "metadata": {},
196 | "source": [
197 | "## Conclusión\n",
198 | "\n",
199 | "En resumen, las visualizaciones son una herramienta poderosa para entender y comunicar los resultados de nuestros modelos de una manera más efectiva. Scikit-learn nos ofrece varias funciones para visualizar los resultados de nuestros modelos de clasificación y regresión, y es importante considerar estas herramientas al evaluar y presentar los resultados de nuestros modelos."
200 | ]
201 | }
202 | ],
203 | "metadata": {
204 | "kernelspec": {
205 | "display_name": "Python 3 (ipykernel)",
206 | "language": "python",
207 | "name": "python3"
208 | },
209 | "language_info": {
210 | "codemirror_mode": {
211 | "name": "ipython",
212 | "version": 3
213 | },
214 | "file_extension": ".py",
215 | "mimetype": "text/x-python",
216 | "name": "python",
217 | "nbconvert_exporter": "python",
218 | "pygments_lexer": "ipython3",
219 | "version": "3.8.11"
220 | },
221 | "notion_metadata": {
222 | "archived": false,
223 | "cover": null,
224 | "created_by": {
225 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
226 | "object": "user"
227 | },
228 | "created_time": "2023-03-14T20:36:00.000Z",
229 | "icon": null,
230 | "id": "e8b073bd-cd10-4a43-9e7b-560b5fdc100b",
231 | "last_edited_by": {
232 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
233 | "object": "user"
234 | },
235 | "last_edited_time": "2023-04-21T17:26:00.000Z",
236 | "object": "page",
237 | "parent": {
238 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
239 | "type": "database_id"
240 | },
241 | "properties": {
242 | "Assign": {
243 | "id": "%5DtZZ",
244 | "people": [],
245 | "type": "people"
246 | },
247 | "Code name": {
248 | "id": "PT%5CP",
249 | "rich_text": [],
250 | "type": "rich_text"
251 | },
252 | "Name": {
253 | "id": "title",
254 | "title": [
255 | {
256 | "annotations": {
257 | "bold": false,
258 | "code": false,
259 | "color": "default",
260 | "italic": false,
261 | "strikethrough": false,
262 | "underline": false
263 | },
264 | "href": null,
265 | "plain_text": "3.1.6 Visualizaciones",
266 | "text": {
267 | "content": "3.1.6 Visualizaciones",
268 | "link": null
269 | },
270 | "type": "text"
271 | }
272 | ],
273 | "type": "title"
274 | },
275 | "Order": {
276 | "id": "k_Cb",
277 | "number": 4.4,
278 | "type": "number"
279 | },
280 | "Real order": {
281 | "id": "%7Dn_k",
282 | "rich_text": [
283 | {
284 | "annotations": {
285 | "bold": false,
286 | "code": false,
287 | "color": "default",
288 | "italic": false,
289 | "strikethrough": false,
290 | "underline": false
291 | },
292 | "href": null,
293 | "plain_text": "18 - Visualizaciones",
294 | "text": {
295 | "content": "18 - Visualizaciones",
296 | "link": null
297 | },
298 | "type": "text"
299 | }
300 | ],
301 | "type": "rich_text"
302 | },
303 | "Status": {
304 | "id": "s%7D%5Ea",
305 | "status": {
306 | "color": "green",
307 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
308 | "name": "Done"
309 | },
310 | "type": "status"
311 | }
312 | },
313 | "url": "https://www.notion.so/3-1-6-Visualizaciones-e8b073bdcd104a439e7b560b5fdc100b"
314 | }
315 | },
316 | "nbformat": 4,
317 | "nbformat_minor": 5
318 | }
319 |
--------------------------------------------------------------------------------
/3-1-8-train-test-split.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "2d04b0c9",
6 | "metadata": {},
7 | "source": [
8 | "# Train-test split\n",
9 | "\n",
10 | "Es hora de hablar del que es quizá el método más popular de scikit-learn, aquel que nos ayuda a dividir un dataset en diferentes conjuntos de información.\n",
11 | "\n",
12 | "Para este entonces ya deberías tener clara la importancia de realizar esta división, así que no nos detendremos demasiado en el por qué, sino más bien en el cómo.\n",
13 | "\n",
14 | "Comenzamos por importar el método"
15 | ]
16 | },
17 | {
18 | "cell_type": "code",
19 | "execution_count": null,
20 | "id": "6a0a619b",
21 | "metadata": {},
22 | "outputs": [],
23 | "source": [
24 | "from sklearn.model_selection import train_test_split"
25 | ]
26 | },
27 | {
28 | "cell_type": "markdown",
29 | "id": "51f7a7a1",
30 | "metadata": {},
31 | "source": [
32 | "En realidad el método es bastante sencillo de utilizar, pero hay algunos trucos que debes tener en cuenta para sacarle el máximo jugo."
33 | ]
34 | },
35 | {
36 | "cell_type": "markdown",
37 | "id": "f088a251",
38 | "metadata": {},
39 | "source": [
40 | "### Interfaz\n",
41 | "\n",
42 | "Esta función tiene una interfaz un poco peculiar, puesto que está hecho para recibir una cantidad variable de argumentos, para ejemplificar, mira lo siguiente:"
43 | ]
44 | },
45 | {
46 | "cell_type": "code",
47 | "execution_count": null,
48 | "id": "bfeb4857",
49 | "metadata": {},
50 | "outputs": [],
51 | "source": [
52 | "import numpy as np\n",
53 | "\n",
54 | "# Generate some example data\n",
55 | "X1 = np.arange(0, 100)\n",
56 | "X2 = np.arange(100, 200)\n",
57 | "X3 = np.arange(200, 300)\n",
58 | "\n",
59 | "print(f\"Shapes: {X1.shape}, {X2.shape}, {X3.shape}\")\n",
60 | "\n",
61 | "# Split the data into training and testing sets\n",
62 | "X1_train, X1_test, X2_train, X2_test, X3_train, X3_test = train_test_split(X1, X2, X3)\n",
63 | "\n",
64 | "\n",
65 | "\n",
66 | "print(\"Shapes after splitting:\")\n",
67 | "print(f\"X1_train: {X1_train.shape}, X1_test: {X1_test.shape}\")\n",
68 | "print(f\"X2_train: {X2_train.shape}, X2_test: {X2_test.shape}\")\n",
69 | "print(f\"X3_train: {X3_train.shape}, X3_test: {X3_test.shape}\")"
70 | ]
71 | },
72 | {
73 | "cell_type": "markdown",
74 | "id": "89ee32ab",
75 | "metadata": {},
76 | "source": [
77 | "Lo más común es que lo veas de la siguiente forma, en donde se le pasa un conjunto de datos y las etiquetas correspondientes:"
78 | ]
79 | },
80 | {
81 | "cell_type": "code",
82 | "execution_count": null,
83 | "id": "6666c4ba",
84 | "metadata": {},
85 | "outputs": [],
86 | "source": [
87 | "X = np.random.rand(100, 2)\n",
88 | "y = np.random.randint(0, 2, 100)\n",
89 | "\n",
90 | "X_train, X_test, y_train, y_test = train_test_split(X, y)"
91 | ]
92 | },
93 | {
94 | "cell_type": "markdown",
95 | "id": "10503ac4",
96 | "metadata": {},
97 | "source": [
98 | "### Argumentos\n",
99 | "\n",
100 | "Tamaños de los sets\n",
101 | "\n",
102 | "Por default, y sin más argumentos, el tamaño de los datasets estará dividido en 75% para el conjunto de entrenamiento y 25% para el conjunto de prueba.\n",
103 | "\n",
104 | "Estos valores son modificables, desde luego, puedes utilizar los parámetros test_size
o train_size
para modificar el tamaño (recuerda establecer solamente uno), puedes utilizar tanto valores enteros como flotantes.\n",
105 | "\n",
106 | "Si utilizas un valor entero, se utilizará ese número exacto, por ejemplo:"
107 | ]
108 | },
109 | {
110 | "cell_type": "code",
111 | "execution_count": null,
112 | "id": "3e166904",
113 | "metadata": {},
114 | "outputs": [],
115 | "source": [
116 | "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=10)\n",
117 | "\n",
118 | "print(\"Shapes after splitting:\")\n",
119 | "print(f\"X_train: {X_train.shape}, X_test: {X_test.shape}\")\n",
120 | "print(f\"y_train: {y_train.shape}, y_test: {y_test.shape}\")"
121 | ]
122 | },
123 | {
124 | "cell_type": "markdown",
125 | "id": "6c98a890",
126 | "metadata": {},
127 | "source": [
128 | "Pero también puedes utilizar flotantes, que servirán como porcentajes:"
129 | ]
130 | },
131 | {
132 | "cell_type": "code",
133 | "execution_count": null,
134 | "id": "b47f51e3",
135 | "metadata": {},
136 | "outputs": [],
137 | "source": [
138 | "X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5)\n",
139 | "\n",
140 | "print(\"Shapes after splitting:\")\n",
141 | "print(f\"X_train: {X_train.shape}, X_test: {X_test.shape}\")\n",
142 | "print(f\"y_train: {y_train.shape}, y_test: {y_test.shape}\")"
143 | ]
144 | },
145 | {
146 | "cell_type": "markdown",
147 | "id": "c1dd0f4f",
148 | "metadata": {},
149 | "source": [
150 | "Semilla aleatoria\n",
151 | "\n",
152 | "Por default, la función asigna de forma aleatoria los datos a cualquiera de los dos conjuntos, así que dos ejecuciones no nos entregarán los mismos resultados:"
153 | ]
154 | },
155 | {
156 | "cell_type": "code",
157 | "execution_count": null,
158 | "id": "12800f0e",
159 | "metadata": {},
160 | "outputs": [],
161 | "source": [
162 | "X1 = np.arange(0, 100)\n",
163 | "\n",
164 | "X_train, X_test = train_test_split(X1, train_size=0.5)\n",
165 | "print(\"First 10 elements of X_train:\", X_train[:10])\n",
166 | "\n",
167 | "X_train, X_test = train_test_split(X1, train_size=0.5)\n",
168 | "print(\"First 10 elements of X_train:\", X_train[:10])"
169 | ]
170 | },
171 | {
172 | "cell_type": "markdown",
173 | "id": "37291a6e",
174 | "metadata": {},
175 | "source": [
176 | "Si lo que quieres es reproducibilidad, puedes establecer una semilla aleatoria utilizando el argumento random_state
:"
177 | ]
178 | },
179 | {
180 | "cell_type": "code",
181 | "execution_count": null,
182 | "id": "4a28745c",
183 | "metadata": {},
184 | "outputs": [],
185 | "source": [
186 | "X1 = np.arange(0, 100)\n",
187 | "\n",
188 | "X_train, X_test = train_test_split(X1, train_size=0.5, random_state=42)\n",
189 | "print(\"First 10 elements of X_train:\", X_train[:10])\n",
190 | "\n",
191 | "X_train, X_test = train_test_split(X1, train_size=0.5, random_state=42)\n",
192 | "print(\"First 10 elements of X_train:\", X_train[:10])"
193 | ]
194 | },
195 | {
196 | "cell_type": "markdown",
197 | "id": "ff8e6382",
198 | "metadata": {},
199 | "source": [
200 | "Estratificación\n",
201 | "\n",
202 | "Cuando estés trabajando con conjuntos de datos desbalanceados (aquellos que cuenten con más datos de una clase que de otras) puedes establecer el argumento stratify
para que los datos sean distribuidos equitativamente en los dos sets:"
203 | ]
204 | },
205 | {
206 | "cell_type": "code",
207 | "execution_count": null,
208 | "id": "d2ec1be8",
209 | "metadata": {},
210 | "outputs": [],
211 | "source": [
212 | "def show_counts(y):\n",
213 | " unique, counts = np.unique(y, return_counts=True)\n",
214 | " counts = dict(zip(unique, counts))\n",
215 | " for class_, count in counts.items():\n",
216 | " print(f\"Class {class_}:\\t{count:>5} ({count/len(y)*100:00.2f}%)\")"
217 | ]
218 | },
219 | {
220 | "cell_type": "markdown",
221 | "id": "54888d8e",
222 | "metadata": {},
223 | "source": [
224 | "Creamos un dataset de ejemplo, presta atención a las cuentas de las etiquetas de “apple” y “orange”:"
225 | ]
226 | },
227 | {
228 | "cell_type": "code",
229 | "execution_count": null,
230 | "id": "bca850ec",
231 | "metadata": {},
232 | "outputs": [],
233 | "source": [
234 | "sample_size = 1000\n",
235 | "X = np.random.rand(sample_size, 2)\n",
236 | "y = np.random.choice([\"apple\", \"orange\"], sample_size, p=[0.9, 0.1])\n",
237 | "\n",
238 | "show_counts(y)"
239 | ]
240 | },
241 | {
242 | "cell_type": "markdown",
243 | "id": "0916db44",
244 | "metadata": {},
245 | "source": [
246 | "Si las dividimos sin estratificación, presta atención a lo que sucede con las cuentas:"
247 | ]
248 | },
249 | {
250 | "cell_type": "code",
251 | "execution_count": null,
252 | "id": "1912140a",
253 | "metadata": {},
254 | "outputs": [],
255 | "source": [
256 | "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=60)\n",
257 | "\n",
258 | "show_counts(y_train)\n",
259 | "show_counts(y_test)"
260 | ]
261 | },
262 | {
263 | "cell_type": "markdown",
264 | "id": "7b2343ab",
265 | "metadata": {},
266 | "source": [
267 | "Pero si lo hacemos estratificando con y
:"
268 | ]
269 | },
270 | {
271 | "cell_type": "code",
272 | "execution_count": null,
273 | "id": "e5dc474f",
274 | "metadata": {},
275 | "outputs": [],
276 | "source": [
277 | "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=60, stratify=y)\n",
278 | "\n",
279 | "show_counts(y_train)\n",
280 | "show_counts(y_test)"
281 | ]
282 | },
283 | {
284 | "cell_type": "markdown",
285 | "id": "a81c98fa",
286 | "metadata": {},
287 | "source": [
288 | "No aleatorizar\n",
289 | "\n",
290 | "Por default, la función separa los datos de forma aleatoria, pero habrá ocasiones en las que esto no es lo ideal, por ejemplo, cuando estés trabajando con datos en series de tiempo. En estos casos el tomar datos de forma aleatoria causaría un problema de data leakage. Scikit-learn nos permite desactivar la aleatorización pasando el argumento shuffle
igual a falso:"
291 | ]
292 | },
293 | {
294 | "cell_type": "code",
295 | "execution_count": null,
296 | "id": "40a120b9",
297 | "metadata": {},
298 | "outputs": [],
299 | "source": [
300 | "X_train, X_test = train_test_split(X, shuffle=False)\n",
301 | "\n",
302 | "print(\"First 10 elements of X_train:\", X_train[:10])\n",
303 | "print(\"First 10 elements of X_test:\", X_test[:10])"
304 | ]
305 | },
306 | {
307 | "cell_type": "markdown",
308 | "id": "5c6de790",
309 | "metadata": {},
310 | "source": [
311 | "Pero si lo llamamos como sin shuffle
:"
312 | ]
313 | },
314 | {
315 | "cell_type": "code",
316 | "execution_count": null,
317 | "id": "debab5d3",
318 | "metadata": {},
319 | "outputs": [],
320 | "source": [
321 | "X_train, X_test = train_test_split(X)\n",
322 | "\n",
323 | "print(\"First 10 elements of X_train:\", X_train[:10])\n",
324 | "print(\"First 10 elements of X_test:\", X_test[:10])"
325 | ]
326 | },
327 | {
328 | "cell_type": "markdown",
329 | "id": "c635d012",
330 | "metadata": {},
331 | "source": [
332 | "Espero que ahora tengas una comprensión clara del método train_test_split()
y cómo ajustar los argumentos para satisfacer las necesidades de tu conjunto de datos. Recuerda que este es un paso importante en la creación de modelos de aprendizaje automático precisos y efectivos."
333 | ]
334 | }
335 | ],
336 | "metadata": {
337 | "kernelspec": {
338 | "display_name": "Python 3 (ipykernel)",
339 | "language": "python",
340 | "name": "python3"
341 | },
342 | "language_info": {
343 | "codemirror_mode": {
344 | "name": "ipython",
345 | "version": 3
346 | },
347 | "file_extension": ".py",
348 | "mimetype": "text/x-python",
349 | "name": "python",
350 | "nbconvert_exporter": "python",
351 | "pygments_lexer": "ipython3",
352 | "version": "3.8.11"
353 | },
354 | "notion_metadata": {
355 | "archived": false,
356 | "cover": null,
357 | "created_by": {
358 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
359 | "object": "user"
360 | },
361 | "created_time": "2023-03-14T20:36:00.000Z",
362 | "icon": null,
363 | "id": "e892c132-744e-48b6-9e63-ed38a08a6c2a",
364 | "last_edited_by": {
365 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
366 | "object": "user"
367 | },
368 | "last_edited_time": "2023-04-21T17:43:00.000Z",
369 | "object": "page",
370 | "parent": {
371 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
372 | "type": "database_id"
373 | },
374 | "properties": {
375 | "Assign": {
376 | "id": "%5DtZZ",
377 | "people": [],
378 | "type": "people"
379 | },
380 | "Code name": {
381 | "id": "PT%5CP",
382 | "rich_text": [],
383 | "type": "rich_text"
384 | },
385 | "Name": {
386 | "id": "title",
387 | "title": [
388 | {
389 | "annotations": {
390 | "bold": false,
391 | "code": false,
392 | "color": "default",
393 | "italic": false,
394 | "strikethrough": false,
395 | "underline": false
396 | },
397 | "href": null,
398 | "plain_text": "3.1.8 Train-test split",
399 | "text": {
400 | "content": "3.1.8 Train-test split",
401 | "link": null
402 | },
403 | "type": "text"
404 | }
405 | ],
406 | "type": "title"
407 | },
408 | "Order": {
409 | "id": "k_Cb",
410 | "number": 4.5,
411 | "type": "number"
412 | },
413 | "Real order": {
414 | "id": "%7Dn_k",
415 | "rich_text": [
416 | {
417 | "annotations": {
418 | "bold": false,
419 | "code": false,
420 | "color": "default",
421 | "italic": false,
422 | "strikethrough": false,
423 | "underline": false
424 | },
425 | "href": null,
426 | "plain_text": "19",
427 | "text": {
428 | "content": "19",
429 | "link": null
430 | },
431 | "type": "text"
432 | }
433 | ],
434 | "type": "rich_text"
435 | },
436 | "Status": {
437 | "id": "s%7D%5Ea",
438 | "status": {
439 | "color": "green",
440 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
441 | "name": "Done"
442 | },
443 | "type": "status"
444 | }
445 | },
446 | "url": "https://www.notion.so/3-1-8-Train-test-split-e892c132744e48b69e63ed38a08a6c2a"
447 | }
448 | },
449 | "nbformat": 4,
450 | "nbformat_minor": 5
451 | }
452 |
--------------------------------------------------------------------------------
/4-1-2-ridge-and-lasso.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "58b5d874",
6 | "metadata": {},
7 | "source": [
8 | "# Otro tipo de regresiones\n",
9 | "\n",
10 | "Existen otros dos tipos de regresiones en scikit-learn: Lasso y Ridge. Estas se caracterizan por poner restricciones a la magnitud de los coeficientes de la regresión."
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "id": "3f695c90",
16 | "metadata": {},
17 | "source": [
18 | "## Ridge\n",
19 | "\n",
20 | "La primera de las que les quiero hablar es conocida como Ridge.\n",
21 | "\n",
22 | "Esta regresión, a diferencia de la regresión lineal tradicional, se penaliza el la magnitud de los coeficientes aprendidos. Esto a su vez reduce la varianza de los coeficientes estimados y mejora la estabilidad del modelo cuando existe co-linealidad, es decir, la correlación entre nuestras variables predictoras.\n",
23 | "\n",
24 | "La penalización que utiliza Ridge es conocida como L2, te dejo más detalles sobre esta penalización en los recursos de la sesión para que conozcas más.\n",
25 | "\n",
26 | "Antes de comenzar, vamos a generar algunos datos:"
27 | ]
28 | },
29 | {
30 | "cell_type": "code",
31 | "execution_count": null,
32 | "id": "f23b5a7c",
33 | "metadata": {},
34 | "outputs": [],
35 | "source": [
36 | "from sklearn.datasets import make_regression\n",
37 | "from sklearn.model_selection import train_test_split\n",
38 | "\n",
39 | "X, y = make_regression(n_samples=100, n_features=10, bias=2.0, noise=5.0, random_state=42)\n",
40 | "X_train, X_test, y_train, y_test = train_test_split(X, y)"
41 | ]
42 | },
43 | {
44 | "cell_type": "markdown",
45 | "id": "f841ad8b",
46 | "metadata": {},
47 | "source": [
48 | "Para usar ridge, hay que importarla de linear_model
, y llamar al constructor:"
49 | ]
50 | },
51 | {
52 | "cell_type": "code",
53 | "execution_count": null,
54 | "id": "75b8cc67",
55 | "metadata": {},
56 | "outputs": [],
57 | "source": [
58 | "from sklearn.linear_model import Ridge\n",
59 | "\n",
60 | "ridge = Ridge()"
61 | ]
62 | },
63 | {
64 | "cell_type": "markdown",
65 | "id": "4f822c39",
66 | "metadata": {},
67 | "source": [
68 | "Y como todo otro estimador en scikit-learn, tiene los métodos fit
y predict
para interactuar con ella:"
69 | ]
70 | },
71 | {
72 | "cell_type": "code",
73 | "execution_count": null,
74 | "id": "f16382d3",
75 | "metadata": {},
76 | "outputs": [],
77 | "source": [
78 | "ridge.fit(X_train, y_train)\n",
79 | "y_pred = ridge.predict(X_test)"
80 | ]
81 | },
82 | {
83 | "cell_type": "markdown",
84 | "id": "036f39ec",
85 | "metadata": {},
86 | "source": [
87 | "### Argumentos\n",
88 | "\n",
89 | "La clase comparte un par de argumentos con LinearRegression
, estos son fit_intercept
y normalize
. Pero además incluye algunos específicos:\n",
90 | "\n",
91 | " - alpha
: Es un parámetro de tipo flotante que especifica el nivel de regularización en el modelo. Un valor más alto de alpha da como resultado coeficientes más pequeños y, por lo tanto, un modelo más simplificado. El valor predeterminado es 1.0 – este es un hiperparámetro que es recomendable tunear.\n",
92 | "\n",
93 | " - solver
: Es una cadena que indica el solucionador utilizado en el problema de optimización subyacente. Los valores posibles son \"auto\", \"svd\", \"cholesky\", \"lsqr\" y \"sparse_cg\". El valor predeterminado es \"auto\", y en general funciona bien.\n",
94 | "\n",
95 | " - max_iter
: Es un entero que especifica el número máximo de iteraciones permitidas en el solucionador – algunos solucionadores funcionan de manera iterativa. El valor predeterminado es None, lo que significa que se utiliza un valor razonable en función del tamaño del conjunto de datos."
96 | ]
97 | },
98 | {
99 | "cell_type": "markdown",
100 | "id": "2e23bd42",
101 | "metadata": {},
102 | "source": [
103 | "## Lasso\n",
104 | "\n",
105 | "Esta regresión, a diferencia de la regresión lineal tradicional, se penaliza el la magnitud de los coeficientes aprendidos – parecido a la regresión Ridge.\n",
106 | "\n",
107 | "La penalización que utiliza Lasso es conocida como L1, te dejo más detalles sobre esta penalización en los recursos de la sesión para que conozcas más. Pero algo a notar es que Lasso puede forzar a que algunos coeficientes se vuelvan cero, excluyendo así algunas de las variables de entrada de los cálculos del modelo, reduciendo así la complejidad de este.\n",
108 | "\n",
109 | "Las variables \"castigadas\" son aquellas que el modelo considere irrelevantes o con alta colinearidad.\n",
110 | "\n",
111 | "El algoritmo de Lasso funciona de forma iterativa por definición, esa es otra diferencia con la regresión lineal tradicional que tiene una solución analítica cerrada.\n",
112 | "\n",
113 | "Ridge también está disponible en el módulo linear_model
de sklearn
:"
114 | ]
115 | },
116 | {
117 | "cell_type": "code",
118 | "execution_count": null,
119 | "id": "db9aa85f",
120 | "metadata": {},
121 | "outputs": [],
122 | "source": [
123 | "from sklearn.linear_model import Lasso\n",
124 | "\n",
125 | "lasso = Lasso()"
126 | ]
127 | },
128 | {
129 | "cell_type": "markdown",
130 | "id": "071b310f",
131 | "metadata": {},
132 | "source": [
133 | "Y desde luego, comparte la interfaz de otros estimadores en scikit-learn:"
134 | ]
135 | },
136 | {
137 | "cell_type": "code",
138 | "execution_count": null,
139 | "id": "2f9fd943",
140 | "metadata": {},
141 | "outputs": [],
142 | "source": [
143 | "ridge.fit(X_train, y_train)\n",
144 | "y_pred = ridge.predict(X_test)"
145 | ]
146 | },
147 | {
148 | "cell_type": "markdown",
149 | "id": "bde4cf84",
150 | "metadata": {},
151 | "source": [
152 | "### Argumentos\n",
153 | "\n",
154 | "Al igual que la regresión Ridge, también tiene los argumentos alpha
para controlar la fuerza con la que aplica la penalización, así como el parámetro max_iter
que tiene mayor importancia aquí porque este sí es un algoritmo completamente iterativo.\n",
155 | "\n",
156 | "Además tiene los siguientes argumentos que te pueden ayudar en el entrenamiento:\n",
157 | "\n",
158 | " - tol
: Es la tolerancia para la convergencia del algoritmo de optimización. Si la diferencia entre dos iteraciones consecutivas es menor que tol
, se considera que el algoritmo ha convergido. El valor predeterminado es 1e-4.\n",
159 | "\n",
160 | " - warm_start
: Este parámetro es booleano y especifica si se deben utilizar los coeficientes de la regresión anterior como punto de partida para la regresión actual. Si es True
, se utiliza la solución anterior como punto de partida para la optimización, lo que puede acelerar el proceso de ajuste. El valor predeterminado es False
."
161 | ]
162 | },
163 | {
164 | "cell_type": "markdown",
165 | "id": "7f399db1",
166 | "metadata": {},
167 | "source": [
168 | "## Atributos\n",
169 | "\n",
170 | "Ambas clases ofrecen los atributos de la regresión lineal que nos ayudan a entender un poco más sobre nuestros valores de entrada. Si recuerdas, en la sesión anterior sobre regresión lineal vimos cómo es que los atributos coef_
e intercept_
pueden ser usados para interpretar los resultados. Lasso y Ridge cuentan con ellos también. "
171 | ]
172 | },
173 | {
174 | "cell_type": "markdown",
175 | "id": "98b4f268",
176 | "metadata": {},
177 | "source": [
178 | "### Comparison"
179 | ]
180 | },
181 | {
182 | "cell_type": "code",
183 | "execution_count": null,
184 | "id": "953d7b9b",
185 | "metadata": {},
186 | "outputs": [],
187 | "source": [
188 | "import numpy as np\n",
189 | "import matplotlib.pyplot as plt\n",
190 | "from sklearn.model_selection import train_test_split\n",
191 | "from sklearn.datasets import make_regression\n",
192 | "from sklearn.linear_model import LinearRegression, Lasso, Ridge\n",
193 | "\n",
194 | "# Generate a random regression dataset\n",
195 | "X, y = make_regression(n_samples=1000, n_features=10, n_informative=5, noise=10, random_state=42)\n",
196 | "\n",
197 | "# Fit Linear Regression\n",
198 | "lr = LinearRegression()\n",
199 | "ridge = Ridge()\n",
200 | "lasso = Lasso()\n",
201 | "\n",
202 | "# Fit regressions\n",
203 | "lr.fit(X, y)\n",
204 | "ridge.fit(X, y)\n",
205 | "lasso.fit(X, y)\n",
206 | "\n",
207 | "# Plot the coefficients\n",
208 | "fig, ax = plt.subplots(figsize=(10, 6))\n",
209 | "models = ['Linear Regression', 'Ridge Regression', 'Lasso Regression']\n",
210 | "coefficients = [lr.coef_, ridge.coef_, lasso.coef_]\n",
211 | "colors = ['blue', 'green', 'red']\n",
212 | "\n",
213 | "for i, (model, coef) in enumerate(zip(models, coefficients)):\n",
214 | " ax.bar(np.arange(len(coef)) + i*0.25, coef, color=colors[i], width=0.25, label=model)\n",
215 | "\n",
216 | "ax.set_xticks(np.arange(len(coef)))\n",
217 | "ax.set_xticklabels(['Feature '+str(i) for i in range(len(coef))])\n",
218 | "ax.set_ylabel(\"Coeficiente\")\n",
219 | "ax.legend()"
220 | ]
221 | },
222 | {
223 | "cell_type": "markdown",
224 | "id": "3c182254",
225 | "metadata": {},
226 | "source": [
227 | "## ¿Cuando usar cada una?\n",
228 | "\n",
229 | " - La regresión lineal es una buena opción cuando la relación entre las variables independientes y la variable dependiente es aproximadamente lineal, siempre es un buen método a considerar, aunque sea para establecer un baseline.\n",
230 | "\n",
231 | " - La regresión de ridge es una buena opción cuando hay muchas características y se espera que algunas de ellas tengan efectos pequeños o moderados en la variable dependiente. Ridge ayuda en la regularización al encoger los coeficientes de algunas características hacia cero, pero no a cero como Lasso.\n",
232 | "\n",
233 | " - La regresión de lasso es una buena opción cuando hay muchas características y se espera que algunas de ellas sean irrelevantes o redundantes. Lasso ayuda en la selección de características y la regularización al convertir algunos coeficientes de características en cero, efectivamente desapareciéndolas del modelo.\n",
234 | "\n",
235 | "Y pues ahí lo tienes, otros dos tipos de regresión muy parecidos a la regresión lineal pero que incluyen un nivel de penalización para los coeficientes que nos ayuda a reducir overfitting y la complejidad general del modelo.\n"
236 | ]
237 | }
238 | ],
239 | "metadata": {
240 | "kernelspec": {
241 | "display_name": "Python 3 (ipykernel)",
242 | "language": "python",
243 | "name": "python3"
244 | },
245 | "language_info": {
246 | "codemirror_mode": {
247 | "name": "ipython",
248 | "version": 3
249 | },
250 | "file_extension": ".py",
251 | "mimetype": "text/x-python",
252 | "name": "python",
253 | "nbconvert_exporter": "python",
254 | "pygments_lexer": "ipython3",
255 | "version": "3.8.11"
256 | },
257 | "notion_metadata": {
258 | "archived": false,
259 | "cover": null,
260 | "created_by": {
261 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
262 | "object": "user"
263 | },
264 | "created_time": "2023-03-14T20:30:00.000Z",
265 | "icon": null,
266 | "id": "9cdbe641-ca87-495d-b530-d5975154bbd1",
267 | "last_edited_by": {
268 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
269 | "object": "user"
270 | },
271 | "last_edited_time": "2023-04-21T17:07:00.000Z",
272 | "object": "page",
273 | "parent": {
274 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
275 | "type": "database_id"
276 | },
277 | "properties": {
278 | "Assign": {
279 | "id": "%5DtZZ",
280 | "people": [],
281 | "type": "people"
282 | },
283 | "Code name": {
284 | "id": "PT%5CP",
285 | "rich_text": [],
286 | "type": "rich_text"
287 | },
288 | "Name": {
289 | "id": "title",
290 | "title": [
291 | {
292 | "annotations": {
293 | "bold": false,
294 | "code": false,
295 | "color": "default",
296 | "italic": false,
297 | "strikethrough": false,
298 | "underline": false
299 | },
300 | "href": null,
301 | "plain_text": "4.1.2 Ridge and Lasso",
302 | "text": {
303 | "content": "4.1.2 Ridge and Lasso",
304 | "link": null
305 | },
306 | "type": "text"
307 | }
308 | ],
309 | "type": "title"
310 | },
311 | "Order": {
312 | "id": "k_Cb",
313 | "number": 5.12,
314 | "type": "number"
315 | },
316 | "Real order": {
317 | "id": "%7Dn_k",
318 | "rich_text": [
319 | {
320 | "annotations": {
321 | "bold": false,
322 | "code": false,
323 | "color": "default",
324 | "italic": false,
325 | "strikethrough": false,
326 | "underline": false
327 | },
328 | "href": null,
329 | "plain_text": "21 - LassoAndRidge",
330 | "text": {
331 | "content": "21 - LassoAndRidge",
332 | "link": null
333 | },
334 | "type": "text"
335 | }
336 | ],
337 | "type": "rich_text"
338 | },
339 | "Status": {
340 | "id": "s%7D%5Ea",
341 | "status": {
342 | "color": "green",
343 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
344 | "name": "Done"
345 | },
346 | "type": "status"
347 | }
348 | },
349 | "url": "https://www.notion.so/4-1-2-Ridge-and-Lasso-9cdbe641ca87495db530d5975154bbd1"
350 | }
351 | },
352 | "nbformat": 4,
353 | "nbformat_minor": 5
354 | }
355 |
--------------------------------------------------------------------------------
/4-2-1-regresion-logistica.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "92da298c",
6 | "metadata": {},
7 | "source": [
8 | "# Regresión logística\n",
9 | "\n",
10 | "Ya sabes que existe una regresión que en lugar de predecir valores numéricos continuos nos ayuda a hacer clasificaciones. Esa regresión es la regresión logística, y en scikit-learn, la implementación de este algoritmo se encuentra en la clase LogisticRegression
.\n",
11 | "\n",
12 | "La regresión logística es un modelo de aprendizaje supervisado que se utiliza comúnmente para problemas de clasificación.\n",
13 | "\n",
14 | "Dado un conjunto de características, la regresión logística estima la probabilidad de que una instancia pertenezca a una clase en particular. Esta probabilidad se transforma en una etiqueta de clase utilizando un umbral de decisión."
15 | ]
16 | },
17 | {
18 | "cell_type": "code",
19 | "execution_count": null,
20 | "id": "f27865d3",
21 | "metadata": {},
22 | "outputs": [],
23 | "source": [
24 | "# Crea un dataset\n",
25 | "from sklearn.datasets import make_moons\n",
26 | "from sklearn.model_selection import train_test_split\n",
27 | "\n",
28 | "X, y = make_moons(n_samples=1000, random_state=42, noise=0.40)\n",
29 | "X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)"
30 | ]
31 | },
32 | {
33 | "cell_type": "markdown",
34 | "id": "96f151bb",
35 | "metadata": {},
36 | "source": [
37 | "Luego, se instancia un objeto de la clase LogisticRegression
:"
38 | ]
39 | },
40 | {
41 | "cell_type": "code",
42 | "execution_count": null,
43 | "id": "5e143371",
44 | "metadata": {},
45 | "outputs": [],
46 | "source": [
47 | "from sklearn.linear_model import LogisticRegression\n",
48 | "\n",
49 | "lr = LogisticRegression()"
50 | ]
51 | },
52 | {
53 | "cell_type": "markdown",
54 | "id": "675ce1fb",
55 | "metadata": {},
56 | "source": [
57 | "Y se ajusta a los datos de entrenamiento utilizando el método fit
. "
58 | ]
59 | },
60 | {
61 | "cell_type": "code",
62 | "execution_count": null,
63 | "id": "0d3334fb",
64 | "metadata": {},
65 | "outputs": [],
66 | "source": [
67 | "lr.fit(X_train, y_train)"
68 | ]
69 | },
70 | {
71 | "cell_type": "markdown",
72 | "id": "22d61aa7",
73 | "metadata": {},
74 | "source": [
75 | "Una vez entrenado, el modelo se puede utilizar para hacer predicciones en los datos de prueba utilizando el método predict
."
76 | ]
77 | },
78 | {
79 | "cell_type": "code",
80 | "execution_count": null,
81 | "id": "516c1846",
82 | "metadata": {},
83 | "outputs": [],
84 | "source": [
85 | "y_pred = lr.predict(X_test)"
86 | ]
87 | },
88 | {
89 | "cell_type": "markdown",
90 | "id": "409d71ad",
91 | "metadata": {},
92 | "source": [
93 | "La verdad es que no hay mucha ciencia en eso."
94 | ]
95 | },
96 | {
97 | "cell_type": "markdown",
98 | "id": "f3ac5c23",
99 | "metadata": {},
100 | "source": [
101 | "## Predict proba\n",
102 | "\n",
103 | "En problemas de clasificación los clasificadores de scikit-learn tienen un método llamado predict_proba
que puedes utilizar para obtener un estimado de qué tan probable es que una instancia pertenezca a una clase u otra.\n",
104 | "\n",
105 | "Por ejemplo, puedes llamar al método predict proba sobre nuestro modelo y nuestros datos de entrada:"
106 | ]
107 | },
108 | {
109 | "cell_type": "code",
110 | "execution_count": null,
111 | "id": "c52d73e0",
112 | "metadata": {},
113 | "outputs": [],
114 | "source": [
115 | "probabilities = lr.predict_proba(X_test)\n",
116 | "probabilities"
117 | ]
118 | },
119 | {
120 | "cell_type": "markdown",
121 | "id": "fa9e7de3",
122 | "metadata": {},
123 | "source": [
124 | "En este caso, como estamos hablando de un problema de clasificación binaria, probabilities
es una matriz de dos columnas, en donde la primera columna representa la probabilidad de que la muestra pertenezca a la clase negativa y la segunda a la positiva.\n",
125 | "\n",
126 | "El predecir las probabilidades en lugar de obtener una clasificación dura es útil en algunos casos, para conocer más, te invito a que veas los recursos de esta lección.\n",
127 | "\n",
128 | "Recuerda además que todos los clasificadores de scikit-learn tienen este método, y no solo la regresión lineal."
129 | ]
130 | },
131 | {
132 | "cell_type": "markdown",
133 | "id": "0414427a",
134 | "metadata": {},
135 | "source": [
136 | "## Argumentos\n",
137 | "\n",
138 | "La clase LogisticRegression
en scikit-learn tiene una gran cantidad de parámetros que permiten personalizar el modelo según las necesidades específicas del problema, algunos de los comunes y con los que te recomiendo que juegues al momento de trabajar\n",
139 | "\n",
140 | " - penalty
: especifica la norma de regularización a utilizar en el modelo. Las opciones comunes son “L1”, “L2” y “elasticnet”. El valor por default es “L2”. En general, mi recomendación es que trates de no usar “L1” con la regresión logística. \n",
141 | "\n",
142 | " - tol
: especifica la tolerancia para la detección de convergencia del algoritmo de optimización. Al ser este un algoritmo iterativo, es importante establecer un valor de tolerancia, en caso de que el algoritmo llegue a un punto en el que los valores no cambien lo suficiente, poder detener el entrenamiento.\n",
143 | "\n",
144 | " - max_iter
: siguiendo en el tema de las iteraciones, también es posible establecer un número máximo de estas.\n",
145 | "\n",
146 | " - C
: es un valor que controla la fuerza con la que la regularización es aplicada. C
tiene la peculiaridad de ser un valor que afecta inversamente a la regularización, entre más pequeño sea este valor, más fuerte será la regularización aplicada.\n",
147 | "\n",
148 | " - class_weight
: este argumento es útil cuando estás lidiando con un problema en donde haya un desbalance en los datos. "
149 | ]
150 | },
151 | {
152 | "cell_type": "markdown",
153 | "id": "d4ae53f8",
154 | "metadata": {},
155 | "source": [
156 | "## Ejemplo de uso de class_weight
:"
157 | ]
158 | },
159 | {
160 | "cell_type": "code",
161 | "execution_count": null,
162 | "id": "33a09f0e",
163 | "metadata": {},
164 | "outputs": [],
165 | "source": [
166 | "from utils import classification_report_comparison\n",
167 | "\n",
168 | "import numpy as np\n",
169 | "from sklearn.datasets import make_classification\n",
170 | "from sklearn.model_selection import train_test_split\n",
171 | "from sklearn.metrics import classification_report\n",
172 | "from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score\n",
173 | "\n",
174 | "\n",
175 | "# Create an imbalanced classification dataset\n",
176 | "X, y = make_classification(n_samples=1000, n_features=10, n_informative=5, n_redundant=5,\n",
177 | " weights=[0.9], random_state=42)\n",
178 | "\n",
179 | "# Split the dataset into training and testing sets\n",
180 | "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)\n",
181 | "\n",
182 | "# Fit Logistic Regression with class_weight='balanced'\n",
183 | "balanced_lr = LogisticRegression(class_weight='balanced')\n",
184 | "balanced_lr.fit(X_train, y_train)\n",
185 | "\n",
186 | "vanilla_lr = LogisticRegression()\n",
187 | "vanilla_lr.fit(X_train, y_train)\n",
188 | "\n",
189 | "\n",
190 | "# Make predictions on the testing set\n",
191 | "balanced_y_pred = balanced_lr.predict(X_test)\n",
192 | "vanilla_y_pred = vanilla_lr.predict(X_test)\n",
193 | "\n",
194 | "classification_report_comparison(y_test, {\"Balanced\": balanced_y_pred, \"No balance\": vanilla_y_pred})"
195 | ]
196 | },
197 | {
198 | "cell_type": "markdown",
199 | "id": "293c88a8",
200 | "metadata": {},
201 | "source": [
202 | " > 📚 De tarea, ¿por qué no intentas jugar un poco más con los parámetros? utiliza la función classification_report_comparison
"
203 | ]
204 | }
205 | ],
206 | "metadata": {
207 | "kernelspec": {
208 | "display_name": "Python 3 (ipykernel)",
209 | "language": "python",
210 | "name": "python3"
211 | },
212 | "language_info": {
213 | "codemirror_mode": {
214 | "name": "ipython",
215 | "version": 3
216 | },
217 | "file_extension": ".py",
218 | "mimetype": "text/x-python",
219 | "name": "python",
220 | "nbconvert_exporter": "python",
221 | "pygments_lexer": "ipython3",
222 | "version": "3.8.11"
223 | },
224 | "notion_metadata": {
225 | "archived": false,
226 | "cover": null,
227 | "created_by": {
228 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
229 | "object": "user"
230 | },
231 | "created_time": "2023-03-14T20:31:00.000Z",
232 | "icon": null,
233 | "id": "64f4d55c-a830-47cb-8825-7baa26afbd1b",
234 | "last_edited_by": {
235 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
236 | "object": "user"
237 | },
238 | "last_edited_time": "2023-04-21T17:07:00.000Z",
239 | "object": "page",
240 | "parent": {
241 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
242 | "type": "database_id"
243 | },
244 | "properties": {
245 | "Assign": {
246 | "id": "%5DtZZ",
247 | "people": [],
248 | "type": "people"
249 | },
250 | "Code name": {
251 | "id": "PT%5CP",
252 | "rich_text": [],
253 | "type": "rich_text"
254 | },
255 | "Name": {
256 | "id": "title",
257 | "title": [
258 | {
259 | "annotations": {
260 | "bold": false,
261 | "code": false,
262 | "color": "default",
263 | "italic": false,
264 | "strikethrough": false,
265 | "underline": false
266 | },
267 | "href": null,
268 | "plain_text": "4.2.1 Regresión logística",
269 | "text": {
270 | "content": "4.2.1 Regresión logística",
271 | "link": null
272 | },
273 | "type": "text"
274 | }
275 | ],
276 | "type": "title"
277 | },
278 | "Order": {
279 | "id": "k_Cb",
280 | "number": 5.21,
281 | "type": "number"
282 | },
283 | "Real order": {
284 | "id": "%7Dn_k",
285 | "rich_text": [
286 | {
287 | "annotations": {
288 | "bold": false,
289 | "code": false,
290 | "color": "default",
291 | "italic": false,
292 | "strikethrough": false,
293 | "underline": false
294 | },
295 | "href": null,
296 | "plain_text": "22 - RegresionLogistica",
297 | "text": {
298 | "content": "22 - RegresionLogistica",
299 | "link": null
300 | },
301 | "type": "text"
302 | }
303 | ],
304 | "type": "rich_text"
305 | },
306 | "Status": {
307 | "id": "s%7D%5Ea",
308 | "status": {
309 | "color": "green",
310 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
311 | "name": "Done"
312 | },
313 | "type": "status"
314 | }
315 | },
316 | "url": "https://www.notion.so/4-2-1-Regresi-n-log-stica-64f4d55ca83047cb88257baa26afbd1b"
317 | }
318 | },
319 | "nbformat": 4,
320 | "nbformat_minor": 5
321 | }
322 |
--------------------------------------------------------------------------------
/4-2-2-support-vector-classifier.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "65f8ab8e",
6 | "metadata": {},
7 | "source": [
8 | "# Support Vector Classifiers\n",
9 | "\n",
10 | "SVM es un algoritmo que nos sirve para realizar clasificación de datos. Ya sabes que el funcionamiento básico de SVM consiste en encontrar un hiperplano que pueda separar las diferentes categorías de datos, \n",
11 | "\n",
12 | "Scikit-Learn nos ofrece una implementación de SVM para clasificación en la clase SVC
dentro del módulo sklearn.svm
:"
13 | ]
14 | },
15 | {
16 | "cell_type": "code",
17 | "execution_count": null,
18 | "id": "37f7e085",
19 | "metadata": {},
20 | "outputs": [],
21 | "source": [
22 | "from sklearn.svm import SVC\n",
23 | "\n",
24 | "svc = SVC()"
25 | ]
26 | },
27 | {
28 | "cell_type": "markdown",
29 | "id": "1320bfcc",
30 | "metadata": {},
31 | "source": [
32 | "Pero primero, vamos a generar un dataset para probar el clasificador:"
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": null,
38 | "id": "b6bf65ac",
39 | "metadata": {},
40 | "outputs": [],
41 | "source": [
42 | "from sklearn.datasets import make_classification\n",
43 | "from sklearn.model_selection import train_test_split\n",
44 | "\n",
45 | "X, y = make_classification(n_samples=500, n_features=15, n_classes=2, random_state=0)\n",
46 | "\n",
47 | "X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)"
48 | ]
49 | },
50 | {
51 | "cell_type": "markdown",
52 | "id": "3fced032",
53 | "metadata": {},
54 | "source": [
55 | "Y después podemos entrenar el modelo"
56 | ]
57 | },
58 | {
59 | "cell_type": "code",
60 | "execution_count": null,
61 | "id": "22ec896e",
62 | "metadata": {},
63 | "outputs": [],
64 | "source": [
65 | "svc.fit(X_train, y_train)\n",
66 | "y_pred = svc.predict(X_test)"
67 | ]
68 | },
69 | {
70 | "cell_type": "markdown",
71 | "id": "bdca8caf",
72 | "metadata": {},
73 | "source": [
74 | "Y podemos evaluar el desmpeño usando la función de precisión:"
75 | ]
76 | },
77 | {
78 | "cell_type": "code",
79 | "execution_count": null,
80 | "id": "cb4a0e00",
81 | "metadata": {},
82 | "outputs": [],
83 | "source": [
84 | "from sklearn.metrics import accuracy_score\n",
85 | "\n",
86 | "accuracy = accuracy_score(y_test, y_pred)\n",
87 | "print(\"Precisión del modelo SVM: {:.2f}\".format(accuracy))"
88 | ]
89 | },
90 | {
91 | "cell_type": "markdown",
92 | "id": "ce648760",
93 | "metadata": {},
94 | "source": [
95 | "## Argumentos de la función\n",
96 | "\n",
97 | "La clase SVC tiene una gran cantidad de argumentos, y ya sabes que su importancia varía de problema a problema, sin embargo, los primeros que deberías considerar ajustar son los siguientes:\n",
98 | "\n",
99 | " - C
: Este parámetro controla la penalización de errores de clasificación. Un valor adecuado de C puede ayudar a evitar tanto el overfitting como el underfitting.\n",
100 | "\n",
101 | " - kernel
: Este parámetro determina la transformación que se le va a aplicar a los datos antes de ejecutar el algoritmos de SVC. Te recomiendo que intentes con varios para encontrar el mejor para tu modelo - particularmente si no son separables linealmente.\n",
102 | "\n",
103 | " - degree
: Este parámetro se utiliza con el kernel poly
y controla el grado del polinomio que se utiliza para la transformación de los datos, un grado mayor a 1 proporciona una frontera más compleja pero corre el riesgo de sufrir de overfitting.\n",
104 | "\n",
105 | " - gamma
: Este parámetro se utiliza con los kernels poly
, rbf
y sigmoid
y controla cómo es que se comporta. Un valor adecuado de gamma puede ayudar a evitar tanto el overfitting como el underfitting.\n",
106 | "\n",
107 | " - class_weight
: Este parámetro se utiliza para abordar el desequilibrio de clases en el conjunto de datos. Si el conjunto de datos tiene clases desequilibradas, el ajuste de los pesos de las clases puede mejorar significativamente el rendimiento del modelo. Ya vimos la importancia que este argumento tiene en problemas de clasificación en la lección sobre la regresión logística."
108 | ]
109 | },
110 | {
111 | "cell_type": "markdown",
112 | "id": "13dfbdbf",
113 | "metadata": {},
114 | "source": [
115 | "## Comportamiento de algunos argumentos\n",
116 | "\n",
117 | "Vamos a ver el efecto que tienen algunos argumentos sobre SVC, lo que haremos es visualizar la frontera de decisión. \n",
118 | "\n",
119 | "Primero necesitamos crear un dataset de juguete, este tendrá dos dimensiones o features:"
120 | ]
121 | },
122 | {
123 | "cell_type": "code",
124 | "execution_count": null,
125 | "id": "61e7739e",
126 | "metadata": {},
127 | "outputs": [],
128 | "source": [
129 | "from sklearn.datasets import make_moons\n",
130 | "\n",
131 | "X, y = make_moons(n_samples=100, random_state=42, noise=0.10)"
132 | ]
133 | },
134 | {
135 | "cell_type": "markdown",
136 | "id": "a8115a1d",
137 | "metadata": {},
138 | "source": [
139 | "### C
- Regularización\n",
140 | "\n",
141 | "En cierto modo podemos pensar en este valor como uno que nos deja elegir qué tan estricto tiene que ser el margen entre la línea y los elementos de nuestro dataset, aquí debes tener en cuenta que entre menor sea el valor, menor será el efecto sobre el margen - por default el valor es 1:"
142 | ]
143 | },
144 | {
145 | "cell_type": "code",
146 | "execution_count": null,
147 | "id": "975617d6",
148 | "metadata": {},
149 | "outputs": [],
150 | "source": [
151 | "from utils import plot_boundaries\n",
152 | "\n",
153 | "C_values = [0.001, 0.01,0.1, 1, 10]\n",
154 | "svcs = []\n",
155 | "\n",
156 | "for C_value in C_values:\n",
157 | " svcs.append(\n",
158 | " (f\"C = {C_value}\", SVC(kernel='linear', C=C_value))\n",
159 | " )\n",
160 | "\n",
161 | "plot_boundaries(X, y, svcs)"
162 | ]
163 | },
164 | {
165 | "cell_type": "markdown",
166 | "id": "212fc828",
167 | "metadata": {},
168 | "source": [
169 | "## kernel
\n",
170 | "\n",
171 | "Este argumento controla la transformación interna que se le aplica a los datos para intentar obtener un hiperplano que los separe, por default el valor RBF que proviene de radial basis function:"
172 | ]
173 | },
174 | {
175 | "cell_type": "code",
176 | "execution_count": null,
177 | "id": "d5204e6a",
178 | "metadata": {},
179 | "outputs": [],
180 | "source": [
181 | "kernels = ['linear', 'poly', 'rbf', 'sigmoid']\n",
182 | "\n",
183 | "svcs = []\n",
184 | "for kernel in kernels:\n",
185 | " svcs.append(\n",
186 | " (f\"Kernel = {kernel}\", SVC(kernel=kernel, C=1))\n",
187 | " )\n",
188 | "plot_boundaries(X, y, svcs)"
189 | ]
190 | },
191 | {
192 | "cell_type": "markdown",
193 | "id": "e0ff8539",
194 | "metadata": {},
195 | "source": [
196 | "## degree
\n",
197 | "\n",
198 | "Este controla el grado del polinomio cuando se elige el kernel poly
:"
199 | ]
200 | },
201 | {
202 | "cell_type": "code",
203 | "execution_count": null,
204 | "id": "b906afde",
205 | "metadata": {},
206 | "outputs": [],
207 | "source": [
208 | "degree_values = [1, 2, 3, 4, 5, 6]\n",
209 | "\n",
210 | "svcs = [\n",
211 | " (f\"Degree = {degree}\", SVC(kernel='poly', degree=degree, C=1))\n",
212 | " for degree in degree_values\n",
213 | "]\n",
214 | "\n",
215 | "plot_boundaries(X, y, svcs)"
216 | ]
217 | },
218 | {
219 | "cell_type": "markdown",
220 | "id": "2ea13491",
221 | "metadata": {},
222 | "source": [
223 | "## LinearSVC
\n",
224 | "\n",
225 | "Existe otra clase dentro del módulo svm
de Scikit-Learn, llamada LinearSVC
que deberías considerar si tu conjunto de datos es grande o tiene muchas características. LinearSVC
es una versión de SVM que utiliza un kernel lineal y está optimizado para la clasificación lineal. Puede ser mucho más rápido en grandes conjuntos de datos que SVC con un kernel no lineal, y consume menos memoria.\n",
226 | "\n",
227 | "La diferencia entre SVC
y LinearSVC
es que SVC
puede utilizar cualquier kernel (por ejemplo, lineal, polinómico, RBF), mientras que LinearSVC
sólo puede utilizar un kernel lineal. Como resultado, SVC
puede ser más potente y preciso que LinearSVC
para problemas no lineales, pero también puede ser más lento y consumir más memoria. Por otro lado, LinearSVC
es más rápido y consume menos memoria, pero sólo puede resolver problemas lineales.\n",
228 | "\n",
229 | "En general es buena idea probar con ambas opciones y diferentes configuraciones para encontrar la mejor combinación que funcione para tus necesidades, siempre y cuando tengas en cuenta el costo beneficio entre ellos. \n",
230 | "\n",
231 | "Y pues ahí lo tienes, espero que ahora te queden má claros en qué tipo de problemas puede uno aplicar SVM y cuáles son los argumentos más importantes para comenzar a tunear los hiperparámetros."
232 | ]
233 | }
234 | ],
235 | "metadata": {
236 | "kernelspec": {
237 | "display_name": "Python 3 (ipykernel)",
238 | "language": "python",
239 | "name": "python3"
240 | },
241 | "language_info": {
242 | "codemirror_mode": {
243 | "name": "ipython",
244 | "version": 3
245 | },
246 | "file_extension": ".py",
247 | "mimetype": "text/x-python",
248 | "name": "python",
249 | "nbconvert_exporter": "python",
250 | "pygments_lexer": "ipython3",
251 | "version": "3.8.11"
252 | },
253 | "notion_metadata": {
254 | "archived": false,
255 | "cover": null,
256 | "created_by": {
257 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
258 | "object": "user"
259 | },
260 | "created_time": "2023-03-14T20:31:00.000Z",
261 | "icon": null,
262 | "id": "6afe71d5-7482-4602-8cda-bc5012843e4a",
263 | "last_edited_by": {
264 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
265 | "object": "user"
266 | },
267 | "last_edited_time": "2023-04-23T09:31:00.000Z",
268 | "object": "page",
269 | "parent": {
270 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
271 | "type": "database_id"
272 | },
273 | "properties": {
274 | "Assign": {
275 | "id": "%5DtZZ",
276 | "people": [],
277 | "type": "people"
278 | },
279 | "Code name": {
280 | "id": "PT%5CP",
281 | "rich_text": [
282 | {
283 | "annotations": {
284 | "bold": false,
285 | "code": false,
286 | "color": "default",
287 | "italic": false,
288 | "strikethrough": false,
289 | "underline": false
290 | },
291 | "href": null,
292 | "plain_text": "SupportVectorClassifier",
293 | "text": {
294 | "content": "SupportVectorClassifier",
295 | "link": null
296 | },
297 | "type": "text"
298 | }
299 | ],
300 | "type": "rich_text"
301 | },
302 | "Name": {
303 | "id": "title",
304 | "title": [
305 | {
306 | "annotations": {
307 | "bold": false,
308 | "code": false,
309 | "color": "default",
310 | "italic": false,
311 | "strikethrough": false,
312 | "underline": false
313 | },
314 | "href": null,
315 | "plain_text": "4.2.2 Support Vector Classifier",
316 | "text": {
317 | "content": "4.2.2 Support Vector Classifier",
318 | "link": null
319 | },
320 | "type": "text"
321 | }
322 | ],
323 | "type": "title"
324 | },
325 | "Order": {
326 | "id": "k_Cb",
327 | "number": 5.22,
328 | "type": "number"
329 | },
330 | "Real order": {
331 | "id": "%7Dn_k",
332 | "rich_text": [
333 | {
334 | "annotations": {
335 | "bold": false,
336 | "code": false,
337 | "color": "default",
338 | "italic": false,
339 | "strikethrough": false,
340 | "underline": false
341 | },
342 | "href": null,
343 | "plain_text": "23",
344 | "text": {
345 | "content": "23",
346 | "link": null
347 | },
348 | "type": "text"
349 | }
350 | ],
351 | "type": "rich_text"
352 | },
353 | "Status": {
354 | "id": "s%7D%5Ea",
355 | "status": {
356 | "color": "green",
357 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
358 | "name": "Done"
359 | },
360 | "type": "status"
361 | }
362 | },
363 | "url": "https://www.notion.so/4-2-2-Support-Vector-Classifier-6afe71d5748246028cdabc5012843e4a"
364 | }
365 | },
366 | "nbformat": 4,
367 | "nbformat_minor": 5
368 | }
369 |
--------------------------------------------------------------------------------
/4-2-4-k-nearest-neighbors.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "5ad9becf",
6 | "metadata": {},
7 | "source": [
8 | "# k-Nearest Neighbors\n",
9 | "\n",
10 | "Otra forma de clasificar elementos es a través del algoritmo de k-Nearest Neighbors. Este funciona clasificando los datos basándose a las etiquetas de los datos que tiene más cercanos en el espacio de características – para cada una nueva muestra se buscan los “k” vecinos más cercanos y dependiendo de estas etiquetas se decide a que clase pertenece dada nuevo elemento.\n",
11 | "\n",
12 | "La forma de utilizarlo en scikit-learn es simple como cualquier otro clasificador, lo importamos del módulo:"
13 | ]
14 | },
15 | {
16 | "cell_type": "code",
17 | "execution_count": null,
18 | "id": "3ccfff8d",
19 | "metadata": {},
20 | "outputs": [],
21 | "source": [
22 | "from sklearn.neighbors import KNeighborsClassifier"
23 | ]
24 | },
25 | {
26 | "cell_type": "markdown",
27 | "id": "d6d9876c",
28 | "metadata": {},
29 | "source": [
30 | "Y tiene los métodos fit
y predict
usuales:"
31 | ]
32 | },
33 | {
34 | "cell_type": "code",
35 | "execution_count": null,
36 | "id": "f12c0717",
37 | "metadata": {},
38 | "outputs": [],
39 | "source": [
40 | "from sklearn.datasets import make_moons\n",
41 | "from sklearn.model_selection import train_test_split\n",
42 | "\n",
43 | "X, y = make_moons(n_samples=1000, random_state=42, noise=0.1)\n",
44 | "\n",
45 | "X_train, X_test, y_train, y_test = train_test_split(X, y)"
46 | ]
47 | },
48 | {
49 | "cell_type": "code",
50 | "execution_count": null,
51 | "id": "a64181bb",
52 | "metadata": {},
53 | "outputs": [],
54 | "source": [
55 | "knn = KNeighborsClassifier()\n",
56 | "\n",
57 | "knn.fit(X_train, y_train)\n",
58 | "\n",
59 | "print(knn.predict(X_test))\n",
60 | "print(knn.score(X_test, y_test))"
61 | ]
62 | },
63 | {
64 | "cell_type": "markdown",
65 | "id": "3cb16bed",
66 | "metadata": {},
67 | "source": [
68 | "Y también tiene el método predict_proba
aunque la probabilidad aquí nos ayuda a definir cuantos vecinos cercanos tenía: "
69 | ]
70 | },
71 | {
72 | "cell_type": "code",
73 | "execution_count": null,
74 | "id": "807a72df",
75 | "metadata": {},
76 | "outputs": [],
77 | "source": [
78 | "print(knn.predict_proba(X_test)[:20])"
79 | ]
80 | },
81 | {
82 | "cell_type": "markdown",
83 | "id": "d74646d9",
84 | "metadata": {},
85 | "source": [
86 | "Como sea, lo interesante está en los argumentos, los hiperparámetros de la clase."
87 | ]
88 | },
89 | {
90 | "cell_type": "markdown",
91 | "id": "d87b11f4",
92 | "metadata": {},
93 | "source": [
94 | "## Argumentos\n",
95 | "\n",
96 | "Al igual que muchos otros modelos de machine learning, la clase KNeighborsClassifier
tiene algunos argumentos para modificar su comportamiento:\n",
97 | "\n",
98 | " - n_neighbors
: Este hiperparámetro determina la cantidad de vecinos que se utilizarán en la clasificación. Si el valor de n_neighbors
es demasiado bajo, el modelo puede sobreajustar los datos, mientras que si el valor es demasiado alto, el modelo puede subajustar los datos. El valor por default 5.\n",
99 | "\n",
100 | " - weights
: Este hiperparámetro determina cómo se ponderan las distancias entre las muestras de entrenamiento y la muestra de prueba. Las opciones son 'uniform', donde todas las muestras tienen el mismo peso en la clasificación, y 'distance', donde las muestras más cercanas tienen un mayor peso. Usualmente, la opción por defecto es 'uniform'.\n",
101 | "\n",
102 | " - metric
: Este hiperparámetro determina la métrica de distancia utilizada para calcular las distancias entre las muestras. Algunas opciones comunes son 'euclidean', 'manhattan' y 'minkowski'.\n",
103 | "\n",
104 | " - algorithm
: Este hiperparámetro determina el algoritmo utilizado para encontrar los vecinos más cercanos. Las opciones son 'brute', que busca los vecinos más cercanos calculando todas las distancias entre todas las muestras, y 'kd_tree' o 'ball_tree', que utilizan estructuras de datos para buscar los vecinos más cercanos de manera más eficiente.\n",
105 | "\n",
106 | "Vamos a ver el comportamiento del modelo cuando modificamos su , comenzando por la que tal vez sea la más importante:\n",
107 | "\n",
108 | "Primero vamos a crear un dataset de ejemplo:"
109 | ]
110 | },
111 | {
112 | "cell_type": "code",
113 | "execution_count": null,
114 | "id": "fd063859",
115 | "metadata": {},
116 | "outputs": [],
117 | "source": [
118 | "from utils import plot_boundaries\n",
119 | "\n",
120 | "X, y = make_moons(n_samples=1000, random_state=42, noise=0.15)"
121 | ]
122 | },
123 | {
124 | "cell_type": "markdown",
125 | "id": "81ead054",
126 | "metadata": {},
127 | "source": [
128 | "## n_neighbors
"
129 | ]
130 | },
131 | {
132 | "cell_type": "code",
133 | "execution_count": null,
134 | "id": "7dd882f1",
135 | "metadata": {},
136 | "outputs": [],
137 | "source": [
138 | "plot_boundaries(\n",
139 | " X, y, \n",
140 | " [\n",
141 | " ('n_neighbors = 1', KNeighborsClassifier(n_neighbors=1)),\n",
142 | " ('n_neighbors = 10', KNeighborsClassifier(n_neighbors=10)),\n",
143 | " ('n_neighbors = 100', KNeighborsClassifier(n_neighbors=100)),\n",
144 | " ('n_neighbors = 999', KNeighborsClassifier(n_neighbors=999)),\n",
145 | " ]\n",
146 | ")"
147 | ]
148 | },
149 | {
150 | "cell_type": "markdown",
151 | "id": "63eee9c0",
152 | "metadata": {},
153 | "source": [
154 | "## La importancia de escalar las características\n",
155 | "\n",
156 | "k-Nearest Neighbors es un algoritmo basado completamente en las distancias entre características, es de vital importancia que estas estén escaladas antes de pasarlas al modelo, si no, vas a sufrir de problemas al momento de entrenar y obtener predicciones.\n",
157 | "\n",
158 | "Para demostrarlo, aquí estoy creando un dataset y estoy sacando una de sus características fuera de la escala al multiplicarla por 5:"
159 | ]
160 | },
161 | {
162 | "cell_type": "code",
163 | "execution_count": null,
164 | "id": "7f9e6717",
165 | "metadata": {},
166 | "outputs": [],
167 | "source": [
168 | "X, y = make_moons(n_samples=100, random_state=42, noise=.1)\n",
169 | "X[:,1] = X[:,1] * 5\n",
170 | "\n",
171 | "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)"
172 | ]
173 | },
174 | {
175 | "cell_type": "markdown",
176 | "id": "6a587e47",
177 | "metadata": {},
178 | "source": [
179 | "Después entreno un dataset con los datos sin escalar:"
180 | ]
181 | },
182 | {
183 | "cell_type": "code",
184 | "execution_count": null,
185 | "id": "83506c02",
186 | "metadata": {},
187 | "outputs": [],
188 | "source": [
189 | "knn_unscaled = KNeighborsClassifier(n_neighbors=5)\n",
190 | "knn_unscaled.fit(X_train, y_train)\n",
191 | "accuracy_unscaled = knn_unscaled.score(X_test, y_test)"
192 | ]
193 | },
194 | {
195 | "cell_type": "markdown",
196 | "id": "9f3c397b",
197 | "metadata": {},
198 | "source": [
199 | "Y entreno uno escalando las características previamente:"
200 | ]
201 | },
202 | {
203 | "cell_type": "code",
204 | "execution_count": null,
205 | "id": "4af32644",
206 | "metadata": {},
207 | "outputs": [],
208 | "source": [
209 | "from sklearn.preprocessing import StandardScaler\n",
210 | "\n",
211 | "scaler = StandardScaler()\n",
212 | "X_train_scaled = scaler.fit_transform(X_train)\n",
213 | "X_test_scaled = scaler.transform(X_test)\n",
214 | "knn_scaled = KNeighborsClassifier(n_neighbors=5)\n",
215 | "knn_scaled.fit(X_train_scaled, y_train)\n",
216 | "accuracy_scaled = knn_scaled.score(X_test_scaled, y_test)"
217 | ]
218 | },
219 | {
220 | "cell_type": "markdown",
221 | "id": "66883c34",
222 | "metadata": {},
223 | "source": [
224 | "De entrada podemos ver la diferencia entre el desempeño de uno y otro:"
225 | ]
226 | },
227 | {
228 | "cell_type": "code",
229 | "execution_count": null,
230 | "id": "7faec7b8",
231 | "metadata": {},
232 | "outputs": [],
233 | "source": [
234 | "print(f\"Sin escalar\\t{accuracy_unscaled:.4f}\")\n",
235 | "print(f\"Escaladas\\t{accuracy_scaled:.4f}\")"
236 | ]
237 | },
238 | {
239 | "cell_type": "markdown",
240 | "id": "9c6b441f",
241 | "metadata": {},
242 | "source": [
243 | "Pero se ve puede apreciar mejor con una gráfica en dos dimensiones:"
244 | ]
245 | },
246 | {
247 | "cell_type": "code",
248 | "execution_count": null,
249 | "id": "079edd20",
250 | "metadata": {},
251 | "outputs": [],
252 | "source": [
253 | "from utils import plot_knn_boundaries\n",
254 | "\n",
255 | "plot_knn_boundaries(knn_unscaled,knn_scaled, X_train, X_train_scaled, y_train)"
256 | ]
257 | },
258 | {
259 | "cell_type": "markdown",
260 | "id": "ff446ae1",
261 | "metadata": {},
262 | "source": [
263 | "## Tamaño\n",
264 | "\n",
265 | "El tamaño de un modelo de kNN en disco y en memoria varía con respecto al tamaño de su dataset de entrenamiento:"
266 | ]
267 | },
268 | {
269 | "cell_type": "code",
270 | "execution_count": null,
271 | "id": "a15b5d53",
272 | "metadata": {},
273 | "outputs": [],
274 | "source": [
275 | "import joblib\n",
276 | "import os\n",
277 | "from sklearn.datasets import make_classification\n",
278 | "\n",
279 | "n_samples = [100, 1000, 10000, 100000]\n",
280 | "\n",
281 | "for n in n_samples:\n",
282 | " X, y = make_classification(n_samples=n, n_features=20)\n",
283 | " knn = KNeighborsClassifier()\n",
284 | " knn.fit(X, y)\n",
285 | " joblib.dump(knn, f\"/tmp/knn_model_{n}.joblib\")\n",
286 | " model_size = os.path.getsize(f\"/tmp/knn_model_{n}.joblib\")\n",
287 | "\n",
288 | " print(f\"Tamaño del modelo (n={n}):\\t{model_size:>10} bytes\")"
289 | ]
290 | },
291 | {
292 | "cell_type": "markdown",
293 | "id": "dc1fcf47",
294 | "metadata": {},
295 | "source": [
296 | "## En conclusión\n",
297 | "\n",
298 | "El modelo de k-NN es uno que puedes utilizar en problemas de clasificación, especialmente en conjuntos de datos pequeños o de tamaño moderado, problemas con múltiples clases, datos ruidosos o valores faltantes, y en problemas con una dimensionalidad baja a moderada.\n",
299 | "\n",
300 | "Pero considera no utilizarlo en problemas con conjuntos de datos grandes, problemas con una dimensionalidad alta, problemas en los que la velocidad es crítica, problemas con datos muy dispersos, y en problemas en los que la precisión es más importante que la simplicidad y la interpretabilidad. En estos casos, puede ser necesario considerar otros algoritmos de aprendizaje automático más adecuados para el problema específico.\n"
301 | ]
302 | }
303 | ],
304 | "metadata": {
305 | "kernelspec": {
306 | "display_name": "Python 3 (ipykernel)",
307 | "language": "python",
308 | "name": "python3"
309 | },
310 | "language_info": {
311 | "codemirror_mode": {
312 | "name": "ipython",
313 | "version": 3
314 | },
315 | "file_extension": ".py",
316 | "mimetype": "text/x-python",
317 | "name": "python",
318 | "nbconvert_exporter": "python",
319 | "pygments_lexer": "ipython3",
320 | "version": "3.8.11"
321 | },
322 | "notion_metadata": {
323 | "archived": false,
324 | "cover": null,
325 | "created_by": {
326 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
327 | "object": "user"
328 | },
329 | "created_time": "2023-03-14T20:32:00.000Z",
330 | "icon": null,
331 | "id": "48ca8b03-f148-42a3-a5a1-2c88cf4b4e6e",
332 | "last_edited_by": {
333 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
334 | "object": "user"
335 | },
336 | "last_edited_time": "2023-04-23T09:31:00.000Z",
337 | "object": "page",
338 | "parent": {
339 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
340 | "type": "database_id"
341 | },
342 | "properties": {
343 | "Assign": {
344 | "id": "%5DtZZ",
345 | "people": [],
346 | "type": "people"
347 | },
348 | "Code name": {
349 | "id": "PT%5CP",
350 | "rich_text": [
351 | {
352 | "annotations": {
353 | "bold": false,
354 | "code": false,
355 | "color": "default",
356 | "italic": false,
357 | "strikethrough": false,
358 | "underline": false
359 | },
360 | "href": null,
361 | "plain_text": "kNN",
362 | "text": {
363 | "content": "kNN",
364 | "link": null
365 | },
366 | "type": "text"
367 | }
368 | ],
369 | "type": "rich_text"
370 | },
371 | "Name": {
372 | "id": "title",
373 | "title": [
374 | {
375 | "annotations": {
376 | "bold": false,
377 | "code": false,
378 | "color": "default",
379 | "italic": false,
380 | "strikethrough": false,
381 | "underline": false
382 | },
383 | "href": null,
384 | "plain_text": "4.2.4 k-Nearest neighbors",
385 | "text": {
386 | "content": "4.2.4 k-Nearest neighbors",
387 | "link": null
388 | },
389 | "type": "text"
390 | }
391 | ],
392 | "type": "title"
393 | },
394 | "Order": {
395 | "id": "k_Cb",
396 | "number": 5.24,
397 | "type": "number"
398 | },
399 | "Real order": {
400 | "id": "%7Dn_k",
401 | "rich_text": [
402 | {
403 | "annotations": {
404 | "bold": false,
405 | "code": false,
406 | "color": "default",
407 | "italic": false,
408 | "strikethrough": false,
409 | "underline": false
410 | },
411 | "href": null,
412 | "plain_text": "25",
413 | "text": {
414 | "content": "25",
415 | "link": null
416 | },
417 | "type": "text"
418 | }
419 | ],
420 | "type": "rich_text"
421 | },
422 | "Status": {
423 | "id": "s%7D%5Ea",
424 | "status": {
425 | "color": "green",
426 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
427 | "name": "Done"
428 | },
429 | "type": "status"
430 | }
431 | },
432 | "url": "https://www.notion.so/4-2-4-k-Nearest-neighbors-48ca8b03f14842a3a5a12c88cf4b4e6e"
433 | }
434 | },
435 | "nbformat": 4,
436 | "nbformat_minor": 5
437 | }
438 |
--------------------------------------------------------------------------------
/5-1-2-dbscan.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "425eca0a",
6 | "metadata": {},
7 | "source": [
8 | "# DBSCAN\n",
9 | "\n",
10 | "Siguendo con los algoritmos de clústering, existe otro que a diferencia de k-Means no requiere que especifiques de antemano el número de clústers.\n",
11 | "\n",
12 | "Este algoritmo es conocido como DBSCAN, o Density-Based Spatial Clustering of Applications with Noise. \n",
13 | "\n",
14 | "Este algoritmo agrupa elementos dentro de un conjunto en función de su densidad en el espacio. Puntos que estén cercanos entre si se considerarán parte del mismo cluster, mientras que puntos que estén muy alejados se consideararán como ruido.\n",
15 | "\n",
16 | "La implementación de DBSCAN
está dentro del módulo sklearn.cluster
:"
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "execution_count": null,
22 | "id": "8d1d6ef0",
23 | "metadata": {},
24 | "outputs": [],
25 | "source": [
26 | "from sklearn.cluster import DBSCAN"
27 | ]
28 | },
29 | {
30 | "cell_type": "markdown",
31 | "id": "733ae788",
32 | "metadata": {},
33 | "source": [
34 | "Vamos a generar un dataset con 5 blobs de datos, 5 clusters originalmente:"
35 | ]
36 | },
37 | {
38 | "cell_type": "code",
39 | "execution_count": null,
40 | "id": "4c4a72c0",
41 | "metadata": {},
42 | "outputs": [],
43 | "source": [
44 | "from sklearn.datasets import make_blobs\n",
45 | "\n",
46 | "X, y_true = make_blobs(n_samples=500, centers=5, cluster_std=0.8, random_state=0)"
47 | ]
48 | },
49 | {
50 | "cell_type": "markdown",
51 | "id": "4bc36c21",
52 | "metadata": {},
53 | "source": [
54 | "Como con DBSCAN
no requiere que se especifique el número de clusters, podemos inicializarlo con sus valores por default – y luego usaremos fit_predict
para conseguir los clusters asignados:"
55 | ]
56 | },
57 | {
58 | "cell_type": "code",
59 | "execution_count": null,
60 | "id": "dd8a753e",
61 | "metadata": {},
62 | "outputs": [],
63 | "source": [
64 | "dbscan = DBSCAN()\n",
65 | "labels = dbscan.fit_predict(X)"
66 | ]
67 | },
68 | {
69 | "cell_type": "markdown",
70 | "id": "c55c034d",
71 | "metadata": {},
72 | "source": [
73 | "Si revisamos las etiquetas, verás que hay algunas con el valor -1
, estas son las que fueron identificadas como ruido:"
74 | ]
75 | },
76 | {
77 | "cell_type": "code",
78 | "execution_count": null,
79 | "id": "b9f70ce7",
80 | "metadata": {},
81 | "outputs": [],
82 | "source": [
83 | "labels"
84 | ]
85 | },
86 | {
87 | "cell_type": "markdown",
88 | "id": "d6a2bb71",
89 | "metadata": {},
90 | "source": [
91 | "Podemos visualizar los clusters con la siguiente función."
92 | ]
93 | },
94 | {
95 | "cell_type": "code",
96 | "execution_count": null,
97 | "id": "26150ad6",
98 | "metadata": {},
99 | "outputs": [],
100 | "source": [
101 | "from utils import view_dbscan\n",
102 | "\n",
103 | "view_dbscan(X, y_true, [(\"Etiquetas predichas\", dbscan)])"
104 | ]
105 | },
106 | {
107 | "cell_type": "markdown",
108 | "id": "4c26b550",
109 | "metadata": {},
110 | "source": [
111 | "## Argumentos de DBSCAN
\n",
112 | "\n",
113 | "DBSCAN tiene varios argumentos, pero los más importantes a considerar son:\n",
114 | "\n",
115 | " - eps
: El radio de vecindad que define la distancia máxima entre dos puntos para que se consideren vecinos. \n",
116 | "\n",
117 | " - min_samples
: El número mínimo de puntos requeridos para formar un clúster. Valores de min_samples
demasiado pequeños pueden resultar en clústeres muy pequeños y ruido no deseado, mientras que valores demasiado grandes pueden hacer que se agrupen menos puntos."
118 | ]
119 | },
120 | {
121 | "cell_type": "markdown",
122 | "id": "7bbb76c6",
123 | "metadata": {},
124 | "source": [
125 | "## Visualización de los argumentos"
126 | ]
127 | },
128 | {
129 | "cell_type": "markdown",
130 | "id": "98c09d2e",
131 | "metadata": {},
132 | "source": [
133 | "### eps
\n",
134 | "\n",
135 | "El radio de vecindad que define la distancia máxima entre dos puntos para que se consideren vecinos. Valores de eps
demasiado pequeños pueden hacer que se agrupen menos puntos o incluso que todos los puntos sean clasificados como ruido, mientras que valores demasiado grandes pueden agrupar puntos que no deberían estar juntos."
136 | ]
137 | },
138 | {
139 | "cell_type": "code",
140 | "execution_count": null,
141 | "id": "8da432fa",
142 | "metadata": {},
143 | "outputs": [],
144 | "source": [
145 | "dbscan = DBSCAN()\n",
146 | "labels = dbscan.fit_predict(X)\n",
147 | "\n",
148 | "eps_list = [0.01, 0.1, 0.3, 0.5, 1]\n",
149 | "\n",
150 | "trained_dbscans = []\n",
151 | "for eps_value in eps_list:\n",
152 | " dbscan = DBSCAN(eps = eps_value)\n",
153 | " dbscan.fit(X)\n",
154 | " trained_dbscans.append((f\"eps = {eps_value}\", dbscan))\n",
155 | "\n",
156 | "view_dbscan(X, y_true, trained_dbscans)"
157 | ]
158 | },
159 | {
160 | "cell_type": "markdown",
161 | "id": "f43b588b",
162 | "metadata": {},
163 | "source": [
164 | "### min_samples
\n",
165 | "\n",
166 | "El número mínimo de puntos requeridos para formar un clúster. Valores de min_samples
demasiado pequeños pueden resultar en clústeres muy pequeños y ruido no deseado, mientras que valores demasiado grandes pueden hacer que se agrupen menos puntos."
167 | ]
168 | },
169 | {
170 | "cell_type": "code",
171 | "execution_count": null,
172 | "id": "3e9d1e3e",
173 | "metadata": {},
174 | "outputs": [],
175 | "source": [
176 | "dbscan = DBSCAN()\n",
177 | "labels = dbscan.fit_predict(X)\n",
178 | "\n",
179 | "min_samples_list = [1, 3, 5, 20, 50]\n",
180 | "\n",
181 | "trained_dbscans = []\n",
182 | "for min_samples_value in min_samples_list:\n",
183 | " dbscan = DBSCAN(min_samples = min_samples_value)\n",
184 | " dbscan.fit(X)\n",
185 | " trained_dbscans.append((f\"min_samples = {min_samples_value}\", dbscan))\n",
186 | "\n",
187 | "view_dbscan(X, y_true, trained_dbscans)"
188 | ]
189 | },
190 | {
191 | "cell_type": "markdown",
192 | "id": "3ddce004",
193 | "metadata": {},
194 | "source": [
195 | "## Para elegir los hiperparámetros\n",
196 | "\n",
197 | "Para medir la calidad de nuestra elección de hiperparámetros en dbscan podemos utilizar las métricas que ya vimos previamente como el coeficiente de Silhouette, el índice de Calinski-Harabasz o el índice de Davies-Bouldin para encontrar la mejor configuración de hiperparámetros.\n",
198 | "\n",
199 | "También puedes usar métricas secundarias, de negocio, para definir los mejores valores."
200 | ]
201 | },
202 | {
203 | "cell_type": "markdown",
204 | "id": "f87660f9",
205 | "metadata": {},
206 | "source": [
207 | "## En comparación con k-Means\n",
208 | "\n",
209 | "DBSCAN es más adecuado que K-means en situaciones donde el número de clusters es desconocido, los clusters tienen formas no convexas o diferentes densidades, y los datos contienen ruido o valores atípicos. En general, DBSCAN es una buena opción cuando se desea una solución más automatizada y menos sensible a suposiciones ad-hoc acerca del número de clusters y la forma de los datos.\n",
210 | "\n",
211 | "Ahora ya conoces dos algoritmos de clústering disponibles en Scikit Learn."
212 | ]
213 | }
214 | ],
215 | "metadata": {
216 | "kernelspec": {
217 | "display_name": "Python 3 (ipykernel)",
218 | "language": "python",
219 | "name": "python3"
220 | },
221 | "language_info": {
222 | "codemirror_mode": {
223 | "name": "ipython",
224 | "version": 3
225 | },
226 | "file_extension": ".py",
227 | "mimetype": "text/x-python",
228 | "name": "python",
229 | "nbconvert_exporter": "python",
230 | "pygments_lexer": "ipython3",
231 | "version": "3.8.11"
232 | },
233 | "notion_metadata": {
234 | "archived": false,
235 | "cover": null,
236 | "created_by": {
237 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
238 | "object": "user"
239 | },
240 | "created_time": "2023-04-19T20:17:00.000Z",
241 | "icon": null,
242 | "id": "03e9caa7-3f2a-47b2-a9da-3a515c58d3dd",
243 | "last_edited_by": {
244 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
245 | "object": "user"
246 | },
247 | "last_edited_time": "2023-04-23T09:31:00.000Z",
248 | "object": "page",
249 | "parent": {
250 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
251 | "type": "database_id"
252 | },
253 | "properties": {
254 | "Assign": {
255 | "id": "%5DtZZ",
256 | "people": [],
257 | "type": "people"
258 | },
259 | "Code name": {
260 | "id": "PT%5CP",
261 | "rich_text": [],
262 | "type": "rich_text"
263 | },
264 | "Name": {
265 | "id": "title",
266 | "title": [
267 | {
268 | "annotations": {
269 | "bold": false,
270 | "code": false,
271 | "color": "default",
272 | "italic": false,
273 | "strikethrough": false,
274 | "underline": false
275 | },
276 | "href": null,
277 | "plain_text": "5.1.2 DBSCAN",
278 | "text": {
279 | "content": "5.1.2 DBSCAN",
280 | "link": null
281 | },
282 | "type": "text"
283 | }
284 | ],
285 | "type": "title"
286 | },
287 | "Order": {
288 | "id": "k_Cb",
289 | "number": 6.12,
290 | "type": "number"
291 | },
292 | "Real order": {
293 | "id": "%7Dn_k",
294 | "rich_text": [],
295 | "type": "rich_text"
296 | },
297 | "Status": {
298 | "id": "s%7D%5Ea",
299 | "status": {
300 | "color": "green",
301 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
302 | "name": "Done"
303 | },
304 | "type": "status"
305 | }
306 | },
307 | "url": "https://www.notion.so/5-1-2-DBSCAN-03e9caa73f2a47b2a9da3a515c58d3dd"
308 | }
309 | },
310 | "nbformat": 4,
311 | "nbformat_minor": 5
312 | }
313 |
--------------------------------------------------------------------------------
/5-2-1-principal-component-analysis.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "f5488846",
6 | "metadata": {},
7 | "source": [
8 | "# Principal Component Analysis\n",
9 | "\n",
10 | "El PCA, por sus siglas en inglés, es una técnica de reducción que se utiliza para preparar datos para ser visualizados o para ser utilizados en análisis y modelado.\n",
11 | "\n",
12 | "PCA busca reducir la dimensionalidad de los datos al proyectarlos en un nuevo espacio de características que esté compuesto por las direcciones de máxima varianza de los datos originales.\n",
13 | "\n",
14 | "El objetivo de PCA es encontrar una nueva representación de los datos que permita preservar la mayor cantidad de información posible a pesar de reducir el número de variables. Esto se logra mediante la identificación de las componentes principales, que son combinaciones lineales de las variables originales que explican la mayor cantidad de varianza en los datos.\n",
15 | "\n",
16 | "Vamos a trabajar con el dataset de las flores Iris, este dataset tiene 4 dimensiones en donde cada una de ellas tiene una relación con el mundo real:"
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "execution_count": null,
22 | "id": "594b11a6",
23 | "metadata": {},
24 | "outputs": [],
25 | "source": [
26 | "from sklearn.datasets import load_iris\n",
27 | "\n",
28 | "iris = load_iris()\n",
29 | "X = iris.data\n",
30 | "labels = iris.target\n",
31 | "\n",
32 | "print(X.shape)\n",
33 | "X[:10]"
34 | ]
35 | },
36 | {
37 | "cell_type": "markdown",
38 | "id": "960ac68a",
39 | "metadata": {},
40 | "source": [
41 | "Los puedes visualizar con la siguiente función:"
42 | ]
43 | },
44 | {
45 | "cell_type": "code",
46 | "execution_count": null,
47 | "id": "103f1471",
48 | "metadata": {},
49 | "outputs": [],
50 | "source": [
51 | "from utils import visualize_iris_pairplot\n",
52 | "\n",
53 | "visualize_iris_pairplot(iris)"
54 | ]
55 | },
56 | {
57 | "cell_type": "markdown",
58 | "id": "fc47914a",
59 | "metadata": {},
60 | "source": [
61 | "En Scikit-learn, para aplicar PCA a un conjunto de datos, se crea una instancia de la clase PCA
la cual hay que importar del módulo sklearn.decomposition
:"
62 | ]
63 | },
64 | {
65 | "cell_type": "code",
66 | "execution_count": null,
67 | "id": "59333c53",
68 | "metadata": {},
69 | "outputs": [],
70 | "source": [
71 | "from sklearn.decomposition import PCA"
72 | ]
73 | },
74 | {
75 | "cell_type": "markdown",
76 | "id": "3730aa28",
77 | "metadata": {},
78 | "source": [
79 | "Uno de los hiperparámetros más importantes de PCA es la cantidad de componentes que queremos, este número, dado por el argumento n_components
que te recomiendo especificar en la mayoría de los casos. Digamos que queremos reducir nuestro dataset a dos dimensiones únicamente:"
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": null,
85 | "id": "9f6cb31a",
86 | "metadata": {},
87 | "outputs": [],
88 | "source": [
89 | "pca = PCA(n_components=2)"
90 | ]
91 | },
92 | {
93 | "cell_type": "markdown",
94 | "id": "6695f368",
95 | "metadata": {},
96 | "source": [
97 | "Esto significa que de 4 dimensiones, vamos a convertirlo a dos – llamando al método fit
y luego transform
:"
98 | ]
99 | },
100 | {
101 | "cell_type": "code",
102 | "execution_count": null,
103 | "id": "6ef35a27",
104 | "metadata": {},
105 | "outputs": [],
106 | "source": [
107 | "pca.fit(X)\n",
108 | "X_reduced = pca.transform(X)\n",
109 | "\n",
110 | "print(X_reduced.shape)\n",
111 | "X_reduced[:10]"
112 | ]
113 | },
114 | {
115 | "cell_type": "markdown",
116 | "id": "522b860a",
117 | "metadata": {},
118 | "source": [
119 | "Y ahora podemos visualizar este nuevo dataset, que es una versión en baja dimension pero que captura las diferencias de los datos originales:"
120 | ]
121 | },
122 | {
123 | "cell_type": "code",
124 | "execution_count": null,
125 | "id": "823b0669",
126 | "metadata": {},
127 | "outputs": [],
128 | "source": [
129 | "import matplotlib.pyplot as plt\n",
130 | "\n",
131 | "# Graficar los datos transformados\n",
132 | "plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=labels)\n",
133 | "plt.xlabel('Componente Principal 1')\n",
134 | "plt.ylabel('Componente Principal 2')\n",
135 | "plt.show()"
136 | ]
137 | },
138 | {
139 | "cell_type": "markdown",
140 | "id": "835cb8e2",
141 | "metadata": {},
142 | "source": [
143 | "Algo importante a señalar es que después de las transformaciones, estas dos nuevas dimensiones, estos valores no tienen relación alguna con ninguna propiedad física. Son solo “componentes”, aquí no podemos hablar de centímetros o pétalos, nada de eso."
144 | ]
145 | },
146 | {
147 | "cell_type": "markdown",
148 | "id": "cabb832f",
149 | "metadata": {},
150 | "source": [
151 | "## ¿Cómo medir qué tan bueno es PCA?\n",
152 | "\n",
153 | "Es difícil por si solo poder cuantificar qué tan bueno es nuestra elección de hiperparámetros de PCA. A veces se suele medir el desempeño de PCA en conjunto con que tan bien es capaz de ayudar a mejorar el rendimiento de un modelo de aprendizaje automático que se entrena con los datos que salen de PCA, o si las gráficas que generamos con este son buenas o no.\n",
154 | "\n",
155 | "Y pues ahí lo tienen el algoritmo de PCA es útil cuando necesitamso reducir la dimensión de nuestros datos ya sea para entrenar un nuevo modelo o simplemente visualizar datos.\n",
156 | "\n",
157 | "Y pues ahí lo tienen, PCA es un algoritmo que tal vez por si mismo su utilidad no es tan evidente, pero cuando lo pones en conjunto con una gráfica o un modelo de machine learning, comienza a cobrar más importancia y su utilidad se hace evidente."
158 | ]
159 | }
160 | ],
161 | "metadata": {
162 | "kernelspec": {
163 | "display_name": "Python 3 (ipykernel)",
164 | "language": "python",
165 | "name": "python3"
166 | },
167 | "language_info": {
168 | "codemirror_mode": {
169 | "name": "ipython",
170 | "version": 3
171 | },
172 | "file_extension": ".py",
173 | "mimetype": "text/x-python",
174 | "name": "python",
175 | "nbconvert_exporter": "python",
176 | "pygments_lexer": "ipython3",
177 | "version": "3.8.11"
178 | },
179 | "notion_metadata": {
180 | "archived": false,
181 | "cover": null,
182 | "created_by": {
183 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
184 | "object": "user"
185 | },
186 | "created_time": "2023-03-14T20:32:00.000Z",
187 | "icon": null,
188 | "id": "a95c216a-2ca7-4a9b-b823-2afca9ef9840",
189 | "last_edited_by": {
190 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
191 | "object": "user"
192 | },
193 | "last_edited_time": "2023-05-14T06:33:00.000Z",
194 | "object": "page",
195 | "parent": {
196 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
197 | "type": "database_id"
198 | },
199 | "properties": {
200 | "Assign": {
201 | "id": "%5DtZZ",
202 | "people": [],
203 | "type": "people"
204 | },
205 | "Code name": {
206 | "id": "PT%5CP",
207 | "rich_text": [],
208 | "type": "rich_text"
209 | },
210 | "Name": {
211 | "id": "title",
212 | "title": [
213 | {
214 | "annotations": {
215 | "bold": false,
216 | "code": false,
217 | "color": "default",
218 | "italic": false,
219 | "strikethrough": false,
220 | "underline": false
221 | },
222 | "href": null,
223 | "plain_text": "5.2.1 Principal Component Analysis",
224 | "text": {
225 | "content": "5.2.1 Principal Component Analysis",
226 | "link": null
227 | },
228 | "type": "text"
229 | }
230 | ],
231 | "type": "title"
232 | },
233 | "Order": {
234 | "id": "k_Cb",
235 | "number": 6.21,
236 | "type": "number"
237 | },
238 | "Real order": {
239 | "id": "%7Dn_k",
240 | "rich_text": [
241 | {
242 | "annotations": {
243 | "bold": false,
244 | "code": false,
245 | "color": "default",
246 | "italic": false,
247 | "strikethrough": false,
248 | "underline": false
249 | },
250 | "href": null,
251 | "plain_text": "28",
252 | "text": {
253 | "content": "28",
254 | "link": null
255 | },
256 | "type": "text"
257 | }
258 | ],
259 | "type": "rich_text"
260 | },
261 | "Status": {
262 | "id": "s%7D%5Ea",
263 | "status": {
264 | "color": "green",
265 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
266 | "name": "Done"
267 | },
268 | "type": "status"
269 | }
270 | },
271 | "url": "https://www.notion.so/5-2-1-Principal-Component-Analysis-a95c216a2ca74a9bb8232afca9ef9840"
272 | }
273 | },
274 | "nbformat": 4,
275 | "nbformat_minor": 5
276 | }
277 |
--------------------------------------------------------------------------------
/6-1-1-pipelines.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "a119ff88",
6 | "metadata": {},
7 | "source": [
8 | "# Pipelines (Parte 1)\n",
9 | "\n",
10 | "Los pipelines son una secuencia de pasos para procesar información.\n",
11 | "\n",
12 | "Así mismo, siguiendo este concepto, un pipeline en Scikit-Learn es una forma de aplicar secuencialmente una lista de transformaciones o predicciones a un conjunto de datos. \n",
13 | "\n",
14 | "En lugar de llevar a cabo la ejecución y almacenamiento de cada paso manualmente, los pipelines te permiten organizar el pre-procesamiento, extracción de características y entrenamiento en un solo lugar. Y después, puedes reutilizarlos para cuando tienes que realizar nuevas predicciones.\n",
15 | "\n",
16 | "Esto simplifica tu código, dota de consistencia en tus proyectos y hace muy sencilla la tarea de compartir y reutilizar el código.\n",
17 | "\n",
18 | "Los pipelines siguen exactamente la misma interfaz que ya hemos visto que comparten muchos objetos en Scikit-Learn"
19 | ]
20 | },
21 | {
22 | "cell_type": "markdown",
23 | "id": "bda07262",
24 | "metadata": {},
25 | "source": [
26 | "## La clase Pipeline
\n",
27 | "\n",
28 | "La clase al rededor de la que se centra todo es la clase Pipeline
:"
29 | ]
30 | },
31 | {
32 | "cell_type": "code",
33 | "execution_count": null,
34 | "id": "deeb8091",
35 | "metadata": {},
36 | "outputs": [],
37 | "source": [
38 | "from sklearn.pipeline import Pipeline"
39 | ]
40 | },
41 | {
42 | "cell_type": "markdown",
43 | "id": "6ba8af77",
44 | "metadata": {},
45 | "source": [
46 | "Esta recibe una lista de tuplas de transformadores asociados con un nombre, por ejemplo, vamos a crear un pipeline con dos pasos, uno que escale unas variables y otro que reduzca las dimensiones a un dataset – dos transformaciones que ya vimos en este curso:"
47 | ]
48 | },
49 | {
50 | "cell_type": "code",
51 | "execution_count": null,
52 | "id": "3caeaa3f",
53 | "metadata": {},
54 | "outputs": [],
55 | "source": [
56 | "from sklearn.preprocessing import StandardScaler\n",
57 | "from sklearn.decomposition import PCA\n",
58 | "\n",
59 | "pipeline = Pipeline([\n",
60 | "\t('scaler', StandardScaler()),\n",
61 | "\t('pca', PCA(n_components=2)),\n",
62 | "])"
63 | ]
64 | },
65 | {
66 | "cell_type": "markdown",
67 | "id": "21a695d7",
68 | "metadata": {},
69 | "source": [
70 | "Y ahora vamos a cargar unos datos para demostrar cómo es que funciona – nota que X_train
es un una matriz de 4 columnas:"
71 | ]
72 | },
73 | {
74 | "cell_type": "code",
75 | "execution_count": null,
76 | "id": "67493ff6",
77 | "metadata": {},
78 | "outputs": [],
79 | "source": [
80 | "from utils import load_split_iris\n",
81 | "\n",
82 | "X_train, X_test, y_train, y_test = load_split_iris()"
83 | ]
84 | },
85 | {
86 | "cell_type": "markdown",
87 | "id": "a48dac08",
88 | "metadata": {},
89 | "source": [
90 | "Con esto, ya podemos entrenar a nuestro pipeline:"
91 | ]
92 | },
93 | {
94 | "cell_type": "code",
95 | "execution_count": null,
96 | "id": "fd8826cc",
97 | "metadata": {},
98 | "outputs": [],
99 | "source": [
100 | "pipeline.fit(X_train)"
101 | ]
102 | },
103 | {
104 | "cell_type": "markdown",
105 | "id": "e450675f",
106 | "metadata": {},
107 | "source": [
108 | "Después podemos transformar nuestros dos conjuntos de datos – si ves los valores resultantes, verás que ahora solo son dos dimensiones gracias a la reducción de dimensiones que agregamos:"
109 | ]
110 | },
111 | {
112 | "cell_type": "code",
113 | "execution_count": null,
114 | "id": "d67c6177",
115 | "metadata": {},
116 | "outputs": [],
117 | "source": [
118 | "X_train_transformed = pipeline.transform(X_train)\n",
119 | "X_test_transformed = pipeline.transform(X_test)"
120 | ]
121 | },
122 | {
123 | "cell_type": "markdown",
124 | "id": "8f60214c",
125 | "metadata": {},
126 | "source": [
127 | "Y ahora sí, estos datos podemos usarlos en un clasificador, por ejemplo:"
128 | ]
129 | },
130 | {
131 | "cell_type": "code",
132 | "execution_count": null,
133 | "id": "9ae3a5e7",
134 | "metadata": {},
135 | "outputs": [],
136 | "source": [
137 | "from sklearn.linear_model import LogisticRegression\n",
138 | "\n",
139 | "lr = LogisticRegression()\n",
140 | "\n",
141 | "lr.fit(X_train_transformed, y_train)\n",
142 | "y_pred = lr.predict(X_test_transformed)\n",
143 | "score = lr.score(X_test_transformed, y_test)\n",
144 | "print(f'Test accuracy: {score:.2f}')"
145 | ]
146 | },
147 | {
148 | "cell_type": "markdown",
149 | "id": "a30f8124",
150 | "metadata": {},
151 | "source": [
152 | "¿Excelente no? ahora ya no tenemos que preocuparnos por tener que guardar el scaler y pca por separado. Y ahora podemos utilizar el mismo pipeline cuando pongamos nuestros datos en producción…"
153 | ]
154 | },
155 | {
156 | "cell_type": "markdown",
157 | "id": "3fe29a34",
158 | "metadata": {},
159 | "source": [
160 | "## Los pipelines como modelos de machine learning\n",
161 | "\n",
162 | "Pero, ¿qué me dirías si te dijera que podemos incluir nuestro modelo como parte del pipeline en lugar de tenerlo por separado?\n",
163 | "\n",
164 | " Vamos a definir exactamente eso:"
165 | ]
166 | },
167 | {
168 | "cell_type": "code",
169 | "execution_count": null,
170 | "id": "76c0d599",
171 | "metadata": {},
172 | "outputs": [],
173 | "source": [
174 | "pipeline = Pipeline([\n",
175 | "\t('scaler', StandardScaler()),\n",
176 | "\t('pca', PCA(n_components=2)),\n",
177 | "\t('lr', LogisticRegression()),\n",
178 | "])\n",
179 | "\n",
180 | "pipeline.fit(X_train, y_train)"
181 | ]
182 | },
183 | {
184 | "cell_type": "markdown",
185 | "id": "02c0ecdf",
186 | "metadata": {},
187 | "source": [
188 | "Así como lo vez, el último paso de un Pipeline
puede ser un modelo de machine learning. Y luego lo podemos utilizar para predecir nuevos valores:"
189 | ]
190 | },
191 | {
192 | "cell_type": "code",
193 | "execution_count": null,
194 | "id": "2a5a9064",
195 | "metadata": {},
196 | "outputs": [],
197 | "source": [
198 | "y_pred = pipeline.predict(X_test)\n",
199 | "score = pipeline.score(X_test, y_test)\n",
200 | "print(f'Test accuracy: {score:.2f}')"
201 | ]
202 | },
203 | {
204 | "cell_type": "markdown",
205 | "id": "d20343aa",
206 | "metadata": {},
207 | "source": [
208 | "## Son compatibles con otras herramientas de Scikit-Learn\n",
209 | "\n",
210 | "Los Pipelines
también son compatibles con otras herramientas disponibles en Scikit-Learn, por ejemplo las herramientas de validación cruzada que ya vimos previamente:"
211 | ]
212 | },
213 | {
214 | "cell_type": "code",
215 | "execution_count": null,
216 | "id": "18b093af",
217 | "metadata": {},
218 | "outputs": [],
219 | "source": [
220 | "import numpy as np\n",
221 | "from sklearn.model_selection import cross_val_score\n",
222 | "\n",
223 | "pipeline = Pipeline([\n",
224 | "\t('scaler', StandardScaler()),\n",
225 | "\t('pca', PCA(n_components=2)),\n",
226 | "\t('lr', LogisticRegression()),\n",
227 | "])\n",
228 | "\n",
229 | "cv = 5\n",
230 | "cv_scores = cross_val_score(pipeline, X_train, y_train, cv=cv)\n",
231 | "\n",
232 | "# Mostrar los resultados\n",
233 | "print(f'Scores de validación cruzada ({cv} folds): {cv_scores}')\n",
234 | "print(f'Score promedio: {np.mean(cv_scores):0.2f}')"
235 | ]
236 | },
237 | {
238 | "cell_type": "markdown",
239 | "id": "7d9370d0",
240 | "metadata": {},
241 | "source": [
242 | "Y también con la búsqueda de hiperparámetros:"
243 | ]
244 | },
245 | {
246 | "cell_type": "code",
247 | "execution_count": null,
248 | "id": "6ae76225",
249 | "metadata": {},
250 | "outputs": [],
251 | "source": [
252 | "pipeline = Pipeline([\n",
253 | " ('scaler', StandardScaler()), # Paso 1: Escalar los datos\n",
254 | " ('pca', PCA()), # Paso 2: Reducción de dimensionalidad\n",
255 | " ('lr', LogisticRegression()), # Paso 3: Modelo de regresión logística\n",
256 | "])\n",
257 | "\n",
258 | "param_grid = {\n",
259 | " 'pca__n_components': [1, 2, 3],\n",
260 | " 'lr__penalty': ['l1', 'l2', 'elasticnet', None],\n",
261 | " 'lr__C': np.logspace(-3, 3, 7),\n",
262 | "}"
263 | ]
264 | },
265 | {
266 | "cell_type": "markdown",
267 | "id": "87311e0f",
268 | "metadata": {},
269 | "source": [
270 | "La peculiaridad está en cómo definimos la cuadrícula de parámetros, tienes que ponerle el nombre con el que asociaste el transformador seguido de dos guiones bajos, seguido por el nombre del argumento."
271 | ]
272 | },
273 | {
274 | "cell_type": "code",
275 | "execution_count": null,
276 | "id": "77e10524",
277 | "metadata": {},
278 | "outputs": [],
279 | "source": [
280 | "from sklearn.model_selection import GridSearchCV\n",
281 | "\n",
282 | "grid_search = GridSearchCV(pipeline, param_grid, cv=5, n_jobs=-1)\n",
283 | "grid_search.fit(X_train, y_train)"
284 | ]
285 | },
286 | {
287 | "cell_type": "code",
288 | "execution_count": null,
289 | "id": "159db332",
290 | "metadata": {},
291 | "outputs": [],
292 | "source": [
293 | "# Mostrar los resultados\n",
294 | "print(f'Mejores parámetros: {grid_search.best_params_}')\n",
295 | "print(f'Mejor puntaje: {grid_search.best_score_:.2f}')"
296 | ]
297 | }
298 | ],
299 | "metadata": {
300 | "kernelspec": {
301 | "display_name": "Python 3 (ipykernel)",
302 | "language": "python",
303 | "name": "python3"
304 | },
305 | "language_info": {
306 | "codemirror_mode": {
307 | "name": "ipython",
308 | "version": 3
309 | },
310 | "file_extension": ".py",
311 | "mimetype": "text/x-python",
312 | "name": "python",
313 | "nbconvert_exporter": "python",
314 | "pygments_lexer": "ipython3",
315 | "version": "3.8.11"
316 | },
317 | "notion_metadata": {
318 | "archived": false,
319 | "cover": null,
320 | "created_by": {
321 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
322 | "object": "user"
323 | },
324 | "created_time": "2023-03-14T20:37:00.000Z",
325 | "icon": null,
326 | "id": "72d0bbeb-ea5c-4d3c-a1e4-dbd726295b3d",
327 | "last_edited_by": {
328 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
329 | "object": "user"
330 | },
331 | "last_edited_time": "2023-04-21T17:09:00.000Z",
332 | "object": "page",
333 | "parent": {
334 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
335 | "type": "database_id"
336 | },
337 | "properties": {
338 | "Assign": {
339 | "id": "%5DtZZ",
340 | "people": [],
341 | "type": "people"
342 | },
343 | "Code name": {
344 | "id": "PT%5CP",
345 | "rich_text": [],
346 | "type": "rich_text"
347 | },
348 | "Name": {
349 | "id": "title",
350 | "title": [
351 | {
352 | "annotations": {
353 | "bold": false,
354 | "code": false,
355 | "color": "default",
356 | "italic": false,
357 | "strikethrough": false,
358 | "underline": false
359 | },
360 | "href": null,
361 | "plain_text": "6.1.1 Pipelines",
362 | "text": {
363 | "content": "6.1.1 Pipelines",
364 | "link": null
365 | },
366 | "type": "text"
367 | }
368 | ],
369 | "type": "title"
370 | },
371 | "Order": {
372 | "id": "k_Cb",
373 | "number": 7.1,
374 | "type": "number"
375 | },
376 | "Real order": {
377 | "id": "%7Dn_k",
378 | "rich_text": [
379 | {
380 | "annotations": {
381 | "bold": false,
382 | "code": false,
383 | "color": "default",
384 | "italic": false,
385 | "strikethrough": false,
386 | "underline": false
387 | },
388 | "href": null,
389 | "plain_text": "29 - Pipelines",
390 | "text": {
391 | "content": "29 - Pipelines",
392 | "link": null
393 | },
394 | "type": "text"
395 | }
396 | ],
397 | "type": "rich_text"
398 | },
399 | "Status": {
400 | "id": "s%7D%5Ea",
401 | "status": {
402 | "color": "green",
403 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
404 | "name": "Done"
405 | },
406 | "type": "status"
407 | }
408 | },
409 | "url": "https://www.notion.so/6-1-1-Pipelines-72d0bbebea5c4d3ca1e4dbd726295b3d"
410 | }
411 | },
412 | "nbformat": 4,
413 | "nbformat_minor": 5
414 | }
415 |
--------------------------------------------------------------------------------
/6-2-1-persistencia-de-modelos.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "bf823e7d",
6 | "metadata": {},
7 | "source": [
8 | "# Persistencia de modelos\n",
9 | "\n",
10 | "Ya tienes todo listo, estás contento o contenta con tu modelo y todos los vectorizadores y transformadores que entrenaste para hacerlo funcionar. Pero, ¿qué sigue? es hora de guardarlo en el disco para distribuirlo y ponerlo en producción.\n",
11 | "\n",
12 | "Para continuar en esta lección, voy a cargar y crear y entrenar un pipeline de scikit learn que para fines prácticos, es un modelo de scikit-learn. Si quieres conocer más detalles, revisa la lección sobre pipelines."
13 | ]
14 | },
15 | {
16 | "cell_type": "code",
17 | "execution_count": null,
18 | "id": "44417c75",
19 | "metadata": {},
20 | "outputs": [],
21 | "source": [
22 | "import numpy as np\n",
23 | "from sklearn.linear_model import LinearRegression\n",
24 | "from sklearn.model_selection import train_test_split\n",
25 | "from sklearn.metrics import mean_squared_error\n",
26 | "\n",
27 | "def load_trained_model():\n",
28 | "# Generate synthetic data\n",
29 | "\tnp.random.seed(0)\n",
30 | "\tX = np.random.rand(100, 1)\n",
31 | "\ty = 2 * X + 1 + 0.1 * np.random.randn(100, 1)\n",
32 | "\t\n",
33 | "\t# Split the data into training and testing sets\n",
34 | "\tX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n",
35 | "\t\n",
36 | "\t# Create a linear regression model\n",
37 | "\tmodel = LinearRegression()\n",
38 | "\t\n",
39 | "\t# Train the model using the training data\n",
40 | "\tmodel.fit(X_train, y_train)\n",
41 | "\n",
42 | "\treturn model\n",
43 | "\n",
44 | "model = load_trained_model()"
45 | ]
46 | },
47 | {
48 | "cell_type": "markdown",
49 | "id": "9141c6d1",
50 | "metadata": {},
51 | "source": [
52 | "Recuerda que este ya es un modelo entrenado."
53 | ]
54 | },
55 | {
56 | "cell_type": "markdown",
57 | "id": "0ffc0aa6",
58 | "metadata": {},
59 | "source": [
60 | "## Pickle\n",
61 | "\n",
62 | "Tradicionalmente los modelos eran serializados en disco utilizando pickle
la biblioteca por default para serializar objetos en Python.\n",
63 | "\n",
64 | "Guardar un modelo es sencillo con pickle, lo que tienes que hacer es:"
65 | ]
66 | },
67 | {
68 | "cell_type": "code",
69 | "execution_count": null,
70 | "id": "7139c6d3",
71 | "metadata": {},
72 | "outputs": [],
73 | "source": [
74 | "import pickle\n",
75 | "\n",
76 | "with open(\"model.pickle\", \"wb\") as wb:\n",
77 | "\tpickle.dump(model, wb)"
78 | ]
79 | },
80 | {
81 | "cell_type": "markdown",
82 | "id": "9b628407",
83 | "metadata": {},
84 | "source": [
85 | "Esto serializará el modelo en el archivo “model.pickle”, este ya es un archivo que podemos compartir con alguien más o poner en producción nosotros mismos. Para leerlo de disco es necesario hacer lo siguiente:"
86 | ]
87 | },
88 | {
89 | "cell_type": "code",
90 | "execution_count": null,
91 | "id": "ab409b96",
92 | "metadata": {},
93 | "outputs": [],
94 | "source": [
95 | "with open(\"model.pickle\", \"rb\") as rb:\n",
96 | "\tunpickled_model = pickle.load(rb)"
97 | ]
98 | },
99 | {
100 | "cell_type": "markdown",
101 | "id": "18d60d34",
102 | "metadata": {},
103 | "source": [
104 | "Podemos corroborar que sea del tipo que estamos esperando:"
105 | ]
106 | },
107 | {
108 | "cell_type": "code",
109 | "execution_count": null,
110 | "id": "c129b607",
111 | "metadata": {},
112 | "outputs": [],
113 | "source": [
114 | "type(unpickled_model)"
115 | ]
116 | },
117 | {
118 | "cell_type": "markdown",
119 | "id": "259c40b9",
120 | "metadata": {},
121 | "source": [
122 | "Y hacer predicciones sobre nuevos datos:"
123 | ]
124 | },
125 | {
126 | "cell_type": "code",
127 | "execution_count": null,
128 | "id": "98fd3196",
129 | "metadata": {},
130 | "outputs": [],
131 | "source": []
132 | },
133 | {
134 | "cell_type": "markdown",
135 | "id": "6969d348",
136 | "metadata": {},
137 | "source": [
138 | "### Desventajas\n",
139 | "\n",
140 | "Sin embargo, pickle tiene serios fallos de seguridad y no es el modo recomendable de persistir tus modelos ya que al momento de deserializar este permite la ejecución de código de forma arbitraria. Yo te recomiendo que uses pickle si, y solo si, no tienes otra alternativa."
141 | ]
142 | },
143 | {
144 | "cell_type": "markdown",
145 | "id": "5372c22f",
146 | "metadata": {},
147 | "source": [
148 | "## Joblib\n",
149 | "\n",
150 | "Existe otra biblioteca llamada joblib
que está optimizada para serializar grandes arreglos de NumPy. Pero que internamente termina utilizando pickle para muchas de sus tareas, así que comparten los mismos problemas.\n",
151 | "\n",
152 | "Yo te voy a mostrar cómo se usa por completitud en este curso:"
153 | ]
154 | },
155 | {
156 | "cell_type": "code",
157 | "execution_count": null,
158 | "id": "b90ddf64",
159 | "metadata": {},
160 | "outputs": [],
161 | "source": [
162 | "from joblib import dump\n",
163 | "\n",
164 | "dump(model, \"model.joblib\")"
165 | ]
166 | },
167 | {
168 | "cell_type": "markdown",
169 | "id": "44f0c786",
170 | "metadata": {},
171 | "source": [
172 | "Y para cargarlo, se hace así:"
173 | ]
174 | },
175 | {
176 | "cell_type": "code",
177 | "execution_count": null,
178 | "id": "fd5f3aac",
179 | "metadata": {},
180 | "outputs": [],
181 | "source": [
182 | "from joblib import load\n",
183 | "\n",
184 | "model_joblibbed = load(\"model.joblib\")"
185 | ]
186 | },
187 | {
188 | "cell_type": "markdown",
189 | "id": "41b3becb",
190 | "metadata": {},
191 | "source": [
192 | "Revisa el tipo y haz algunas predicciones para que veas que si funciona:"
193 | ]
194 | },
195 | {
196 | "cell_type": "code",
197 | "execution_count": null,
198 | "id": "cee56d0b",
199 | "metadata": {},
200 | "outputs": [],
201 | "source": [
202 | "type(model_joblibbed)"
203 | ]
204 | },
205 | {
206 | "cell_type": "markdown",
207 | "id": "b54078ca",
208 | "metadata": {},
209 | "source": [
210 | "Si bien Joblib es una mejora sobre Pickle, esta mejora es en aspectos de velocidad y tamaño del modelo almacenado, pero nada cambia en el ámbito de seguridad. Es por eso que te recomiendo que no utilices Joblib a menos de que no tengas otra alternativa."
211 | ]
212 | },
213 | {
214 | "cell_type": "markdown",
215 | "id": "b972a6db",
216 | "metadata": {},
217 | "source": [
218 | "## Skops\n",
219 | "\n",
220 | "Recientemente surgió una nueva biblioteca llamada skops, cuyo objetivo es ayudar a guardar modelos de machine learning y ponerlos en producción. \n",
221 | "\n",
222 | "Esta biblioteca forma parte de scikit-learn, sino que es \n",
223 | "\n",
224 | "Es sencilla de utilizar:"
225 | ]
226 | },
227 | {
228 | "cell_type": "code",
229 | "execution_count": null,
230 | "id": "43e07084",
231 | "metadata": {},
232 | "outputs": [],
233 | "source": [
234 | "import skops.io as sio\n",
235 | "\n",
236 | "sio.dump(model, \"model.sio\")"
237 | ]
238 | },
239 | {
240 | "cell_type": "markdown",
241 | "id": "a99487cb",
242 | "metadata": {},
243 | "source": [
244 | "Y para cargarlo:"
245 | ]
246 | },
247 | {
248 | "cell_type": "code",
249 | "execution_count": null,
250 | "id": "41543f1d",
251 | "metadata": {},
252 | "outputs": [],
253 | "source": [
254 | "skopted_model = sio.load(\"model.sio\")"
255 | ]
256 | },
257 | {
258 | "cell_type": "markdown",
259 | "id": "94499297",
260 | "metadata": {},
261 | "source": [
262 | "Revisa el tipo y haz algunas predicciones para que veas que si funciona:"
263 | ]
264 | },
265 | {
266 | "cell_type": "code",
267 | "execution_count": null,
268 | "id": "4a47af77",
269 | "metadata": {},
270 | "outputs": [],
271 | "source": [
272 | "type(skopted_model)"
273 | ]
274 | },
275 | {
276 | "cell_type": "markdown",
277 | "id": "5c6e4cbb",
278 | "metadata": {},
279 | "source": [
280 | "## Sobre las extensiones\n",
281 | "\n",
282 | "En realidad no importan para nada las extensiones que le asignes a tu modelo, puesto que a final de cuentas para leerlos o escribirlos al disco deberás especificar la dirección completa hacia el archivo."
283 | ]
284 | },
285 | {
286 | "cell_type": "markdown",
287 | "id": "766f3531",
288 | "metadata": {},
289 | "source": [
290 | "## Otras recomendaciones\n",
291 | "\n",
292 | "Guardar los modelos es solo una parte de poner nuestros modelos en producción, otras cosas que debes hacer es \n",
293 | "\n",
294 | "Guardar los archivos es solo una parte de poner nuestros modelos en producción. Otras cosas que debes hacer son:\n",
295 | "\n",
296 | " 1. Mantener versiones consistentes de las bibliotecas: Asegurarte de utilizar las mismas versiones, o algunas compatibles de scikit-learn y sus dependencias al guardar y cargar los modelos. Esto garantiza la compatibilidad y un comportamiento consistente del modelo.\n",
297 | "\n",
298 | " 1. Incluir pasos de preprocesamiento: Guardar los transformadores de preprocesamiento entrenados junto con el modelo. Puedes lograr esto mediante la creación de un pipeline que incluya todos los pasos y guardar todo el pipeline.\n",
299 | "\n",
300 | " 1. Documentar tu modelo: Considera documentar el propósito del modelo, las métricas de rendimiento, el conjunto de datos utilizado para el entrenamiento, featur engineering y cualquier otra información relevante. Esta documentación te ayudará a ti y a otros a comprender el contexto, las limitaciones y los casos de uso del modelo.\n",
301 | "\n",
302 | " 1. Usar un sistema de control de versiones: Almacena tus modelos guardados y los archivos asociados (por ejemplo, scripts de preprocesamiento de datos, archivos de configuración y documentación) en un sistema de control de versiones como Git. \n",
303 | "\n",
304 | " 1. Realizar copias de seguridad de tus modelos: Asegúrate de que tus modelos guardados estén respaldados en un sistema de almacenamiento seguro y confiable. Esto podría implicar guardar modelos en almacenamiento en la nube o utilizar una solución de copia de seguridad dedicada.\n",
305 | "\n",
306 | " 1. Asegúrate que el modelo funciona después de guardarlo y abrirlo: Después de guardar un modelo, prueba cargarlo y realizar predicciones para asegurarte de que el proceso de serialización y deserialización funcione según lo esperado. \n",
307 | "\n",
308 | "Y pues eso es todo, espero estos consejos te sirvan para poner tu modelo en producción de forma exitosa."
309 | ]
310 | }
311 | ],
312 | "metadata": {
313 | "kernelspec": {
314 | "display_name": "Python 3 (ipykernel)",
315 | "language": "python",
316 | "name": "python3"
317 | },
318 | "language_info": {
319 | "codemirror_mode": {
320 | "name": "ipython",
321 | "version": 3
322 | },
323 | "file_extension": ".py",
324 | "mimetype": "text/x-python",
325 | "name": "python",
326 | "nbconvert_exporter": "python",
327 | "pygments_lexer": "ipython3",
328 | "version": "3.8.11"
329 | },
330 | "notion_metadata": {
331 | "archived": false,
332 | "cover": null,
333 | "created_by": {
334 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
335 | "object": "user"
336 | },
337 | "created_time": "2023-03-14T21:00:00.000Z",
338 | "icon": null,
339 | "id": "b7fd46d7-5816-4b3b-af09-04840af8c73c",
340 | "last_edited_by": {
341 | "id": "84951847-6e2b-487e-acba-6838f60f1102",
342 | "object": "user"
343 | },
344 | "last_edited_time": "2023-04-21T17:09:00.000Z",
345 | "object": "page",
346 | "parent": {
347 | "database_id": "97ecfa4e-50d1-4827-8791-2199c709d680",
348 | "type": "database_id"
349 | },
350 | "properties": {
351 | "Assign": {
352 | "id": "%5DtZZ",
353 | "people": [],
354 | "type": "people"
355 | },
356 | "Code name": {
357 | "id": "PT%5CP",
358 | "rich_text": [],
359 | "type": "rich_text"
360 | },
361 | "Name": {
362 | "id": "title",
363 | "title": [
364 | {
365 | "annotations": {
366 | "bold": false,
367 | "code": false,
368 | "color": "default",
369 | "italic": false,
370 | "strikethrough": false,
371 | "underline": false
372 | },
373 | "href": null,
374 | "plain_text": "6.2.1 Persistencia de modelos",
375 | "text": {
376 | "content": "6.2.1 Persistencia de modelos",
377 | "link": null
378 | },
379 | "type": "text"
380 | }
381 | ],
382 | "type": "title"
383 | },
384 | "Order": {
385 | "id": "k_Cb",
386 | "number": 7.3,
387 | "type": "number"
388 | },
389 | "Real order": {
390 | "id": "%7Dn_k",
391 | "rich_text": [
392 | {
393 | "annotations": {
394 | "bold": false,
395 | "code": false,
396 | "color": "default",
397 | "italic": false,
398 | "strikethrough": false,
399 | "underline": false
400 | },
401 | "href": null,
402 | "plain_text": "31 - Persistencia",
403 | "text": {
404 | "content": "31 - Persistencia",
405 | "link": null
406 | },
407 | "type": "text"
408 | }
409 | ],
410 | "type": "rich_text"
411 | },
412 | "Status": {
413 | "id": "s%7D%5Ea",
414 | "status": {
415 | "color": "green",
416 | "id": "b6060d48-45e7-42e9-a8a0-8bd803231f7f",
417 | "name": "Done"
418 | },
419 | "type": "status"
420 | }
421 | },
422 | "url": "https://www.notion.so/6-2-1-Persistencia-de-modelos-b7fd46d758164b3baf0904840af8c73c"
423 | }
424 | },
425 | "nbformat": 4,
426 | "nbformat_minor": 5
427 | }
428 |
--------------------------------------------------------------------------------
/custom_houses.txt:
--------------------------------------------------------------------------------
1 | 1.810120951474600588e+03 1.613098948116576765e+06
2 | 1.867712103380688632e+03 1.666057667414530646e+06
3 | 1.753326974414202368e+03 1.409978691969105275e+06
4 | 1.893005211613963638e+03 1.336039661958248587e+06
5 | 2.404469826500847375e+03 1.964901333277512109e+06
6 | 2.453455996240692002e+03 1.142487136580080027e+06
7 | 2.653771559885210536e+03 1.687040135042126989e+06
8 | 2.847065181276282601e+03 1.701725091046435526e+06
9 | 3.597752900191665049e+03 1.918297265122287208e+06
10 | 3.899055901545349116e+03 1.639646643427925650e+06
11 | 3.854625451603862984e+03 2.098436666822076775e+06
12 | 4.481199931289616870e+03 1.714184185324641643e+06
13 | 4.223005507010944712e+03 1.973000743128600996e+06
14 | 4.684710185355212161e+03 1.936862084886921104e+06
15 | 4.625814903768279692e+03 2.190061572484398261e+06
16 | 4.513105800766992616e+03 1.932899136322357692e+06
17 | 4.268187116683106979e+03 1.947776381772362627e+06
18 | 3.994316765967707397e+03 1.449135458736267872e+06
19 | 4.468723277429286100e+03 2.313567576319508720e+06
20 | 4.717806036057159872e+03 2.234565893150302116e+06
21 | 4.518292601557593116e+03 2.442959930258815177e+06
22 | 4.120979442718144128e+03 1.906688516827645712e+06
23 | 4.699620063495669456e+03 2.524717889086782001e+06
24 | 4.755203624675813444e+03 2.630847231373633724e+06
25 | 4.508998038073023963e+03 2.507336334523105994e+06
26 | 4.644481843995535201e+03 2.784958565897839144e+06
27 | 4.780131713717496496e+03 2.812147891800433397e+06
28 | 4.534397358380396327e+03 3.039519338868414052e+06
29 | 5.256724015209994832e+03 2.863441164931272157e+06
30 | 5.102610672947936109e+03 2.677655859906574711e+06
31 | 5.860914748466561832e+03 3.173104193786484655e+06
32 | 5.416293621328508380e+03 3.390947803852088284e+06
33 | 5.820214863971269551e+03 3.245603294664805755e+06
34 | 5.498692367157302215e+03 3.337397426747394726e+06
35 | 5.471758848624500388e+03 3.504422289929317310e+06
36 | 5.351130853774843672e+03 3.149648518579395022e+06
37 | 4.983023727552616037e+03 3.489199686506818980e+06
38 | 5.683099086312430700e+03 3.506731208901585080e+06
39 | 5.371764595593894228e+03 3.573685616625252645e+06
40 | 5.353310399080975003e+03 3.283796746111967601e+06
41 | 6.002125635566038909e+03 4.001487321965371259e+06
42 | 2.984086569030671853e+03 1.470153893629318336e+06
43 | 3.106327201545907883e+03 1.071032673010225873e+06
44 | 2.395527080685265901e+03 1.426073540878117085e+06
45 | 2.791736107833956339e+03 1.893059634943370009e+06
46 | 3.052213327032052803e+03 1.572380637063383125e+06
47 | 2.582101597108478472e+03 1.915558068266563583e+06
48 | 3.307097578460966815e+03 1.384264893668767996e+06
49 | 2.988933029210479162e+03 1.721893809314253740e+06
50 | 2.923130405598300968e+03 1.608064519916175166e+06
51 | 3.320226062213946534e+03 1.527422314112653723e+06
52 | 3.111534099571165825e+03 1.796179424831853947e+06
53 | 3.461330321943119998e+03 1.106298081372697838e+06
54 | 3.709216241347872710e+03 1.386649486275410280e+06
55 | 2.898039042049892487e+03 1.564994232089303434e+06
56 | 4.054334571961155689e+03 1.453200506526318844e+06
57 | 3.950349273811302282e+03 1.674257819772283314e+06
58 | 3.564725424474545434e+03 1.773087574045566842e+06
59 | 3.616519587021394727e+03 1.791095074919553008e+06
60 | 5.868270512666340437e+03 2.562670410611306317e+06
61 | 5.676832704968497637e+03 2.719705390333041549e+06
62 | 5.511529544564102252e+03 3.036892077838510741e+06
63 | 5.907412882153399551e+03 2.852712327780236956e+06
64 | 5.905776316054807467e+03 2.541482114371186588e+06
65 | 6.229354318724594123e+03 2.333303455635476857e+06
66 | 6.637271273838596244e+03 2.837108103941265494e+06
67 | 6.067916462041393061e+03 2.525382793876131997e+06
68 | 6.393056206691053376e+03 2.951191804958967958e+06
69 | 7.051106532983802936e+03 3.171032152189831715e+06
70 | 6.803969981739473951e+03 3.060113667042931076e+06
71 | 7.323732081120561816e+03 2.911040620524955913e+06
72 | 6.868811743734387164e+03 2.894872388629327528e+06
73 | 7.095301479650861438e+03 2.711202642182202078e+06
74 | 7.237827109955038395e+03 2.954760604403499048e+06
75 | 7.375570970430803754e+03 2.630477446644174401e+06
76 | 6.885511173308143043e+03 2.848909387438863982e+06
77 | 7.502589717692853810e+03 3.016118541728213895e+06
78 | 6.977248055966993888e+03 3.329352433363795746e+06
79 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | scikit-learn
2 | pandas
3 | numpy
4 | scipy
5 | matplotlib
6 |
--------------------------------------------------------------------------------
/utils.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | import pandas as pd
4 | import matplotlib.pyplot as plt
5 | from sklearn.datasets import make_blobs, load_iris
6 | from sklearn.cluster import KMeans
7 | from sklearn.model_selection import train_test_split
8 | from sklearn.linear_model import LinearRegression
9 | from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
10 | import numpy as np
11 | from itertools import combinations
12 | from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score
13 |
14 |
15 | def make_dataset(n):
16 | min_w, max_w = 50, 150
17 | noise_w = np.random.normal(0, 10, n)
18 | weights = np.random.uniform(min_w, max_w, n)
19 |
20 | min_h, max_h = 1.50, 1.90
21 | noise_h = np.random.normal(0, 10, n)
22 | heights = np.random.uniform(min_h, max_h, n)
23 | return np.vstack([weights, heights]).T
24 |
25 |
26 | def plot(ax, dataset, title):
27 | weights, heights = dataset[:, 0], dataset[:, 1]
28 | noise = np.random.uniform(-0.2, 0.2, len(weights))
29 | ax.scatter(
30 | weights,
31 | np.full(len(weights), 1) + noise,
32 | )
33 | ax.scatter(
34 | heights,
35 | np.full(len(heights), 2) + noise,
36 | )
37 | ax.set_ylim(0.5, 2.5)
38 | ax.set_yticks([1, 2], ["Weight", "Height"])
39 | ax.set_title(title)
40 | return ax
41 |
42 |
43 | def show_dataframe(dataset):
44 | return pd.DataFrame(dataset, columns=["Weight", "Height"])
45 |
46 |
47 | def plot_dataset(*objects):
48 | objects = [(objects[i], objects[i + 1]) for i in range(0, len(objects) - 1, 2)]
49 | plots = len(objects)
50 | fig, axs = plt.subplots(1, plots, figsize=(5 * plots, 5))
51 | if len(objects) == 1:
52 | axs = [axs]
53 | for (dataset, title), ax in zip(objects, axs):
54 | plot(ax, dataset, title)
55 | fig.tight_layout()
56 |
57 |
58 | def make_clusters():
59 | X, y_true = make_blobs(n_samples=1000, centers=4, cluster_std=1, random_state=42)
60 | y_pred_6 = KMeans(n_clusters=6, random_state=42, n_init="auto").fit_predict(X)
61 | y_pred_5 = KMeans(n_clusters=5, random_state=42, n_init="auto").fit_predict(X)
62 | y_pred_4 = KMeans(n_clusters=4, random_state=42, n_init="auto").fit_predict(X)
63 | y_pred_3 = KMeans(n_clusters=3, random_state=42, n_init="auto").fit_predict(X)
64 | y_wrong = np.random.randint(4, size=1000)
65 |
66 | return X, y_wrong, [y_pred_3, y_pred_4, y_pred_5, y_pred_6]
67 |
68 |
69 | def plot_clusters(X, wrong_data, *predicted):
70 | fig, axs = plt.subplots(1, 2 + len(predicted), figsize=(25, 5))
71 |
72 | axs[0].scatter(X[:, 0], X[:, 1], c="k", alpha=0.5)
73 | axs[0].set_title("Datos originales")
74 |
75 | for idx, y_preds in enumerate(predicted, 1):
76 | axs[idx].scatter(X[:, 0], X[:, 1], c=y_preds)
77 | axs[idx].set_title(f"{idx+2} clusters encontrados")
78 | axs[-1].scatter(X[:, 0], X[:, 1], c=wrong_data)
79 | axs[-1].set_title("Mal clusttering")
80 |
81 |
82 | def plot_regression_results(y_true, y_pred):
83 | fig, ax = plt.subplots(figsize=(7, 4))
84 | for idx, (yp, yt) in enumerate(zip(y_pred, y_true)):
85 | ax.plot((idx, idx), (yp, yt), "k--")
86 | ax.scatter(np.arange(0, len(y_true)), y_true, s=15, label="True value")
87 | ax.scatter(np.arange(0, len(y_pred)), y_pred, s=15, label="Predicted value")
88 |
89 | ax.set_xlabel("Data")
90 | ax.set_ylabel("Predictions")
91 | ax.set_title("True vs Predicted Values")
92 | ax.legend()
93 |
94 |
95 | def load_split_iris():
96 | iris = load_iris()
97 | return train_test_split(iris.data, iris.target, test_size=0.2, random_state=42)
98 |
99 |
100 | def load_custom_houses():
101 | data = np.loadtxt("custom_houses.txt")
102 | return data[:, :1], data[:, 1]
103 |
104 |
105 | def load_complex_data():
106 | data = pd.read_csv("complex.csv")
107 | return data
108 |
109 |
110 | def show_transformed_data(dataset, columns):
111 | cols = np.concatenate(columns).ravel()
112 |
113 | return pd.DataFrame(dataset, columns=cols)
114 |
115 |
116 | def classification_report_comparison(y_true, model_predictions: dict):
117 | # accuracy_score, precision_score, recall_score, f1_score
118 | scores = (
119 | ("Accuracy", accuracy_score),
120 | ("Precision", precision_score),
121 | ("Recall", recall_score),
122 | ("F1", f1_score),
123 | )
124 | metrics_per_model = {}
125 | for model_name, y_pred in model_predictions.items():
126 | metrics_per_model[model_name] = {metric_name: metric(y_true, y_pred) for metric_name, metric in scores}
127 |
128 | return pd.DataFrame(metrics_per_model)
129 |
130 |
131 | def load_trained_model():
132 | # Generate synthetic data
133 | np.random.seed(0)
134 | X = np.random.rand(100, 1)
135 | y = 2 * X + 1 + 0.1 * np.random.randn(100, 1)
136 |
137 | # Split the data into training and testing sets
138 | X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
139 |
140 | # Create a linear regression model
141 | model = LinearRegression()
142 |
143 | # Train the model using the training data
144 | model.fit(X_train, y_train)
145 |
146 | return model, X_test, y_test
147 |
148 |
149 |
150 | def plot_boundary(classifier, X, y, title, ax):
151 | """Plot the decision boundary for a classifier."""
152 | # Train the classifier with the given hyperparameters
153 | classifier.fit(X, y)
154 |
155 | # Plot the original datapoints
156 | ax.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Paired)
157 | ax.axis('tight')
158 |
159 | # Plot decision boundary
160 | x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
161 | y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
162 | xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
163 | np.arange(y_min, y_max, 0.02))
164 | Z = classifier.predict(np.c_[xx.ravel(), yy.ravel()])
165 | Z = Z.reshape(xx.shape)
166 | ax.contourf(xx, yy, Z, cmap=plt.cm.Paired, alpha=0.2)
167 |
168 | if hasattr(classifier, "support_vectors_"):
169 | # If the SVC is a kernel SVM, plot the support vectors
170 | ax.scatter(classifier.support_vectors_[:, 0], classifier.support_vectors_[:, 1], s=80,
171 | facecolors='none', edgecolors='k')
172 |
173 | # Hide the ticks on the axes and set the corresponding title
174 | ax.set_xticks([])
175 | ax.set_yticks([])
176 | ax.set_title(title)
177 |
178 | def plot_boundaries(X, y, classifiers):
179 | # Compute the number of rows and columns based on the number of hyperparameter combinations
180 | num_plots = len(classifiers)
181 | num_cols = 3 # int(np.ceil(np.sqrt(num_plots)))
182 | num_rows = int(np.ceil(num_plots / num_cols))
183 |
184 | size_per_plot = 3
185 |
186 | # Create a figure with the desired number of subplots
187 | fig, axes = plt.subplots(num_rows, num_cols, figsize=(num_cols * size_per_plot, num_rows*size_per_plot))
188 |
189 | # Flatten the axes array to simplify indexing
190 | axes = axes.ravel()
191 |
192 | for i, (title, svc) in enumerate(classifiers):
193 | plot_boundary(svc, X, y, title, axes[i])
194 |
195 | for i in range(num_plots, num_rows * num_cols):
196 | axes[i].set_visible(False)
197 |
198 | fig.tight_layout()
199 |
200 |
201 | def plot_knn_boundaries(knn, knn_scaled, X, X_scaled, y):
202 | xx, yy = np.meshgrid(np.linspace(X[:,0].min()-1, X[:,0].max()+1, 100),
203 | np.linspace(X[:,1].min()-1, X[:,1].max()+1, 100))
204 | scaled_xx, scaled_yy = np.meshgrid(np.linspace(X_scaled[:,0].min()-1, X_scaled[:,0].max()+1, 100),
205 | np.linspace(X_scaled[:,1].min()-1, X_scaled[:,1].max()+1, 100))
206 |
207 |
208 | Z = knn.predict(np.c_[xx.ravel(), yy.ravel()])
209 | Z = Z.reshape(xx.shape)
210 |
211 | Z_scaled = knn_scaled.predict(np.c_[scaled_xx.ravel(), scaled_yy.ravel()])
212 | Z_scaled = Z_scaled.reshape(scaled_xx.shape)
213 |
214 | fig, axs = plt.subplots(1, 2, figsize=(10, 4))
215 |
216 | axs[0].contourf(xx, yy, Z, cmap=plt.cm.RdBu, alpha=0.5)
217 | axs[0].scatter(X[:,0], X[:,1], c=y, cmap=plt.cm.RdBu)
218 | axs[0].set_title('KNN sin escalar')
219 |
220 | axs[1].contourf(scaled_xx, scaled_yy, Z_scaled, cmap=plt.cm.RdBu, alpha=0.5)
221 | axs[1].scatter(X_scaled[:,0], X_scaled[:,1], c=y, cmap=plt.cm.RdBu)
222 | axs[1].set_title('KNN escalado')
223 |
224 |
225 | axs[0].set_xlabel('Característica 1')
226 | axs[0].set_ylabel('Característica 2')
227 | axs[0].set_xlim(xx.min(), xx.max())
228 | axs[0].set_ylim(yy.min(), yy.max())
229 |
230 |
231 | axs[1].set_xlabel('Característica 1')
232 | axs[1].set_ylabel('Característica 2')
233 | axs[1].set_xlim(scaled_xx.min(), scaled_xx.max())
234 | axs[1].set_ylim(scaled_yy.min(), scaled_yy.max())
235 |
236 | plt.tight_layout()
237 | plt.show()
238 |
239 |
240 | def view_centroids(kmeans, input_features,
241 | title="Centroides de los clústers", xlabel="Característica 1", ylabel="Característica 2", ax=None):
242 | labels = kmeans.labels_
243 | centroids = kmeans.cluster_centers_
244 |
245 | no_ax = False
246 | if ax is None:
247 | fig, ax = plt.subplots(1, 1, figsize=(6, 4))
248 | no_ax = True
249 |
250 | ax.scatter(input_features[:, 0], input_features[:, 1], c=labels)
251 | ax.scatter(centroids[:, 0], centroids[:, 1], marker='v', c='red', label='Centroids')
252 | ax.set_xlabel(xlabel)
253 | ax.set_ylabel(ylabel)
254 | ax.set_title(title)
255 |
256 | # Calculate all the clustering metrics
257 | sscore = silhouette_score(input_features, labels)
258 | cscore = calinski_harabasz_score(input_features, labels)
259 | dscore = davies_bouldin_score(input_features, labels)
260 |
261 | # Add the clustering metrics as a text annotation
262 | annotation_text = "Silhouette score: {:.2f},\nCalinski-Harabasz score: {:.2f},\nDavies-Bouldin score: {:.2f}".format(sscore, cscore, dscore)
263 | ax.annotate(annotation_text, xy=(0.95, 0.15), xycoords='axes fraction', fontsize=8, ha='right', va='top')
264 |
265 |
266 | # Ajustar y mostrar la figura
267 | ax.legend()
268 | if no_ax:
269 | fig.tight_layout()
270 |
271 |
272 | def plot_centroids(input_features, trained_kmeans, titles, xlabel="Característica 1", ylabel="Característica 2"):
273 | # Compute the number of rows and columns based on the number of hyperparameter combinations
274 | num_plots = len(trained_kmeans)
275 | num_cols = 3 # int(np.ceil(np.sqrt(num_plots)))
276 | num_rows = int(np.ceil(num_plots / num_cols))
277 |
278 | size_per_plot = 4
279 |
280 | # Create a figure with the desired number of subplots
281 | fig, axes = plt.subplots(num_rows, num_cols, figsize=(num_cols * size_per_plot, num_rows*size_per_plot))
282 |
283 | # Flatten the axes array to simplify indexing
284 | axes = axes.ravel()
285 |
286 | for i, (title, kmeans) in enumerate(zip(titles, trained_kmeans)):
287 | view_centroids(kmeans, input_features, title=title, xlabel=xlabel, ylabel=ylabel, ax=axes[i])
288 |
289 | for i in range(num_plots, num_rows * num_cols):
290 | axes[i].set_visible(False)
291 |
292 | fig.tight_layout()
293 |
294 |
295 | def view_dbscan(original_features, y_true, dbscans):
296 |
297 | num_plots = len(dbscans) + 1
298 | num_cols = 3
299 | num_rows = int(np.ceil(num_plots / num_cols))
300 |
301 |
302 | size_per_plot = 4
303 |
304 | # Create a figure with the desired number of subplots
305 | fig, axes = plt.subplots(num_rows, num_cols, figsize=(num_cols * size_per_plot, num_rows*size_per_plot))
306 |
307 | axes = axes.ravel()
308 |
309 | axes[0]
310 |
311 | # Gráfico de etiquetas predichas
312 | axes[0].scatter(original_features[:, 0], original_features[:, 1], c=y_true, cmap='viridis')
313 | axes[0].set_xlabel('X1')
314 | axes[0].set_ylabel('X2')
315 | axes[0].set_title('Etiquetas "originales"')
316 |
317 | for idx, (title, dbscan) in enumerate(dbscans, 1):
318 | labels = dbscan.labels_
319 | n_clusters = len(set(labels)) - (1 if -1 in labels else 0)
320 | cluster_labels = list(set(labels))
321 |
322 | ax2 = axes[idx]
323 |
324 | ax2.scatter(original_features[:, 0], original_features[:, 1], c=labels, cmap='viridis')
325 | ax2.set_xlabel('X1')
326 | ax2.set_ylabel('X2')
327 | ax2.set_title(f"{title} - {n_clusters} clusters")
328 |
329 | ruido_indices = np.where(labels == -1)[0]
330 | ax2.scatter(original_features[ruido_indices, 0], original_features[ruido_indices, 1], c='red')
331 |
332 |
333 |
334 | for i in range(num_plots, num_rows * num_cols):
335 | axes[i].set_visible(False)
336 |
337 | plt.tight_layout()
338 | plt.show()
339 |
340 |
341 | def view_centroids_iris(kmeans, input_features):
342 |
343 | view_centroids(kmeans, input_features,
344 | title="Centroides de los clústers", xlabel="Longitud del sépalo", ylabel="Ancho del sépalo")
345 |
346 | def visualize_iris_pairplot(iris):
347 |
348 | bombs = list(combinations(range(4), 2))
349 |
350 | _, axes = plt.subplots(nrows=2, ncols=3, figsize=(12, 8))
351 |
352 | axes = axes.ravel()
353 | for i, comb in enumerate(bombs):
354 | ax = axes[i]
355 | ax.scatter(iris.data[:, comb[0]], iris.data[:, comb[1]], c=iris.target)
356 | ax.set_xlabel(iris.feature_names[comb[0]])
357 | ax.set_ylabel(iris.feature_names[comb[1]])
358 |
359 | plt.tight_layout()
360 | plt.show()
361 |
--------------------------------------------------------------------------------