Subir el brillo de una foto enorme
Vectorizar es sustituir un bucle por una sola operación sobre un array entero. Es la base de la computación paralela con datos: una instrucción, muchos números.
Para subir el brillo de una foto hay que sumar la misma cantidad a 12 millones de píxeles. Ningún píxel depende de otro, así que todos se pueden tratar a la vez.
Al final de esta lección
Nota
Puedo sustituir un bucle por una sola operación sobre un array entero.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Array | una fila de números del mismo tipo, guardados juntos |
| Vectorizar | operar con el array entero en lugar de un bucle |
| Elemento a elemento | la misma operación en cada posición del array |
| Producto escalar | la suma de multiplicar dos arrays posición a posición |
| Dependencia | un paso que necesita el resultado del paso anterior |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un bucle trata los elementos uno a uno
Un bucle for toma un precio, hace la cuenta y pasa al siguiente. Cada vuelta es una orden que Python lee y ejecuta.
Con cuatro precios no se nota. Con cuatro millones, el tiempo se va en leer órdenes, no en hacer cuentas.
Añadir el IVA con un bucle
# Precios en céntimos. Se suma el 21 % de IVA a cada uno, con un bucle.
precios = [1200, 3000, 800, 2500]
con_iva = []
operaciones = 0
for precio in precios:
con_iva.append(precio * 121 // 100)
operaciones = operaciones + 2
print("Con IVA:", con_iva)
print("Operaciones de Python en el bucle:", operaciones)
# Añade un quinto precio, 4000, y vuelve a ejecutar: ¿cuántas operaciones salen?
Una operación sobre el array entero
Con NumPy, una biblioteca de arrays, escribes precios * 121 // 100 y la cuenta se aplica a todos los elementos. El bucle sigue existiendo, pero ya no lo escribes en Python: lo hace código compilado, mucho más rápido.
A este modo de trabajar se le llama elemento a elemento. Una GPU lo lleva al extremo.
La misma cuenta con NumPy
import numpy as np
precios = np.array([1200, 3000, 800, 2500])
con_iva = precios * 121 // 100 # una sola operación sobre todo el array
print(con_iva) # [1452 3630 968 3025]
caros = precios[precios > 1000] # una máscara elige los elementos
print(caros) # [1200 3000 2500]
Ejemplo: tres formas de sumar 1.000 cuadrados
| Forma | Quién repite | Órdenes de Python | Resultado |
|---|---|---|---|
| Un bucle for | Python, una vuelta por número | 2.000 | 333833500 |
| Una operación con NumPy | Código compilado | 1 | 333833500 |
| Una GPU de verdad | Miles de hilos a la vez | 1 | el mismo |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Qué imprime el script del laboratorio
El laboratorio trae steps/vectors.py. Suma los cuadrados del 1 al 1.000 de las dos formas: con un bucle y con una operación de NumPy. Imprime los dos resultados, si son iguales y cuántas operaciones de Python hizo cada forma.
Lo que imprime está en inglés. Fíjate en los números. No mide ningún tiempo y siempre imprime lo mismo.
Sumar cuadrados de dos formas
docker exec jupyter python3 steps/vectors.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Sumar del 1 al 100 con una orden
docker exec jupyter python3 -c "print(sum(range(1, 101)))"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Un error muy común
Advertencia
Mucha gente cree que vectorizar hace menos cuentas. No es así: las cuentas son las mismas. Lo que cambia es quién repite: un código compilado, no Python. Y solo se puede vectorizar si ningún paso necesita el resultado del anterior.
Cuándo no se puede vectorizar
Sumar 5 a cada elemento sí se vectoriza: cada resultado depende solo de su elemento. Un total acumulado, no: cada posición necesita la anterior.
Esa dependencia obliga a ir en orden. Al repartir un trabajo, lo primero es buscar los pasos que no dependen unos de otros.