Categorías
Python

Introducción a las excepciones Python

 

Tabla de Contenidos

  • ponerse en forma: Introducción a NumPy matrices
  • ¿Cuál es vectorización conteo:? Tan fácil como 1, 2, 3 … Comprar barato, vender caro
  • conteo: Tan fácil como 1, 2, 3 …
  • Comprar barato, vender caro
  • Intermezzo: Comprensión ejes notación
  • Radiodifusión
  • Programación matriz en Acción: Tablas ExamplesClustering AlgorithmsAmortization TablesImage extracción de características
  • la agrupación algoritmos
  • amortización
  • imagen de extracción de características
  • un pensamiento de despedida: no más de -Optimizar
  • Más Recursos
  • conteo: tan fácil como 1, 2, 3 …
  • Comprar barato, vender Tablas alta
  • La agrupación algoritmos
  • amortización
  • imagen de extracción de características

A veces se dice que Python, en comparación con los lenguajes de bajo nivel como C ++, mejora el tiempo de desarrollo a expensas de tiempo de ejecución. Afortunadamente, hay un puñado de formas de acelerar el tiempo de ejecución de la operación en Python sin sacrificar la facilidad de uso. Una opción adecuada para operaciones numéricas rápidas es NumPy, que merecidamente se anuncia como el paquete fundamental para la computación científica con Python.

Por supuesto, algunas personas categorizaría algo que tarda 50 microsegundos (cincuenta millonésimas de segundo) como “lento”. Sin embargo, los ordenadores pueden discrepar. El tiempo de ejecución de una operación de tomar 50 microsegundos (50 microsiemens) cae bajo el ámbito de la microperformance , que libremente se puede definir como operaciones con un tiempo de ejecución entre 1 microsegundo y 1 milisegundo.

¿Por qué importa la velocidad? La razón por la que vale la pena microperformance monitoreo es que las pequeñas diferencias en el tiempo de ejecución se vuelven amplificados con repetidas llamadas a funciones: una incrementales 50 mu s de sobrecarga, que se repiten más de 1 millón de llamadas de función, se traduce a 50 segundos de tiempo de ejecución gradual.

Cuando se trata de la computación, en realidad hay tres conceptos que se prestan NumPy su poder:

  • vectorización
  • Radiodifusión
  • Indexación

En este tutorial, verá paso a paso cómo tomar ventaja de vectorización y la difusión de , por lo que se puede utilizar NumPy a su plena capacidad. Mientras que va a utilizar alguna de indexación en la práctica aquí, los esquemas de indexación completos de NumPy, que se extienden sintaxis de rebanado de Python, son su propia bestia. Si usted está buscando para leer más sobre NumPy indexación, agarrar un poco de café y la cabeza a la sección de indexación de los documentos NumPy. Bono

gratuito: Haga clic aquí para obtener acceso a una Guía de Recursos libre NumPy que señala al los mejores tutoriales, videos y libros para mejorar sus habilidades NumPy.

ponerse en forma: Introducción a NumPy Arrays

El objeto fundamental de NumPy es su ndarray (o numpy.array), un n-dimensional array que también está presente en alguna forma en lenguajes orientados de matriz, tales como Fortran 90 , R, y MATLAB, así como predecesores APL y cosas de inicio de J.

dejar fuera mediante la formación de una matriz de 3 dimensiones con 36 elementos:

>>> import numpy as np

>>> arr = np.arange(36).reshape(3, 4, 3)
>>> arr
array([[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]],

[[12, 13, 14],
[15, 16, 17],
[18, 19, 20],
[21, 22, 23]],

[[24, 25, 26],
[27, 28, 29],
[30, 31, 32],
[33, 34, 35]]])

picturing arrays de alta dimensión en dos dimensiones puede ser difícil. Una forma intuitiva de pensar en la forma de una matriz es simplemente “leer de izquierda a derecha.” arr es un de 3 por 4 por 3 matriz:

>>> arr.shape
(3, 4, 3)

Visualmente, arr podría ser pensado como un contenedor de tres 4×3 cuadrículas (o un prisma rectangular) y se vería así:

dimensiones superiores matrices pueden ser más difíciles de imagen, pero todavía seguirá este “conjuntos dentro de una matriz” patrón.

¿Dónde podría ver los datos con más de dos dimensiones? datos

    Panel

  • pueden ser representados en tres dimensiones. Los datos que los atributos pistas de una cohorte (grupo) de los individuos en el tiempo podrían ser estructurados como (los encuestados, fechas, atributos). El 1979 Encuesta Nacional Longitudinal de la Juventud sigue 12.686 encuestados mayores de 27 años. Asumiendo que usted tiene ~ 500 puntos de datos directamente o derivados formuladas por persona y por año, estos datos tendría forma (12686, 27, 500) para un total de 177,604,000 puntos de datos. datos
  • de imágenes en color de varias imágenes se almacenan normalmente en cuatro dimensiones. Cada imagen es una matriz tridimensional de los valores de azul (RGB) (altura, anchura, canales), donde los canales son generalmente de color rojo, verde, y. Una colección de imágenes es entonces sólo (image_number, altura, anchura, canales). Mil 256×256 imágenes RGB tendrían forma (1000, 256, 256, 3). (Un extendió representación es RGBA, donde la A-alfa-denota el nivel de opacidad.)

Para más detalle en ejemplos del mundo real de datos de alta dimensión, véase el capítulo 2 de profundo de aprendizaje de François Chollet con Python .

¿Cuál es vectorización?

vectorización es una habilidad poderosa dentro NumPy para expresar las operaciones que ocurren en las matrices enteras en lugar de sus elementos individuales. He aquí una definición concisa de Wes McKinney:

Esta práctica de sustituir los bucles explícitas con expresiones de matriz se conoce comúnmente como la vectorización. En general, las operaciones de matriz vectorizado serán a menudo uno o dos (o más) órdenes de magnitud más rápido que sus equivalentes de Python, con el mayor impacto [visto] en cualquier tipo de cálculos numéricos. [Fuente]

Cuando bucle sobre una matriz o cualquier estructura de datos en Python, hay una gran cantidad de sobrecarga implicada. operaciones vectorizadas en NumPy delegan el bucle interno a las funciones altamente optimizados C y Fortran, lo que para más limpios y código Python más rápido.

conteo: Tan fácil como 1, 2, 3 …

A modo de ejemplo, considere un vector 1-dimensional de verdadero y falso para el que desea contar el número de “Falso a Verdadero” transiciones en la secuencia:

>>> np.random.seed(444)

>>> x = np.random.choice([False, True], size=100000)
>>> x
array([ True, False, True, ..., True, False, True])

con un pitón de bucle, una manera de hacer esto sería evaluar, de dos en dos, el valor de verdad de cada elemento en la secuencia junto con el elemento que viene justo después de que:

>>> def count_transitions(x) -> int:
... count = 0
... for i, j in zip(x[:-1], x[1:]):
... if j and not i:
... count += 1
... return count
...
>>> count_transitions(x)
24984

en vectorizado forma, no hay explícita para -loop o referencia directa a los elementos individuales:

>>> np.count_nonzero(x[:-1] < x[1:]) 24984

¿Cómo estas dos funciones equivalentes comparan en términos de rendimiento? En este caso particular, la llamada NumPy vectorizado gana por un factor de alrededor de 70 veces:

>>> from timeit import timeit
>>> setup = 'from __main__ import count_transitions, x; import numpy as np'
>>> num = 1000
>>> t1 = timeit('count_transitions(x)', setup=setup, number=num)
>>> t2 = timeit('np.count_nonzero(x[:-1] < x[1:])', setup=setup, number=num) >>> print('Speed difference: {:0.1f}x'.format(t1 / t2))
Speed difference: 71.0x

Detalle técnico : Otro término es el procesador vectorial , que está relacionado con el hardware de un ordenador. Cuando hablo de vectorización aquí, me refiero al concepto de sustitución explícita para bucles con expresiones de matriz, que en este caso, entonces se pueden calcular internamente con un lenguaje de bajo nivel.

Comprar barato, vender caro

He aquí otro ejemplo para abrir el apetito. Consideremos el siguiente problema entrevista técnica clásica:

Dada la historia precio de una acción como una secuencia, y suponiendo que sólo se le permite hacer una compra y una venta, ¿cuál es el beneficio máximo que se puede obtener? Por ejemplo, los precios dados = (20, 18, 14, 17, 20, 21, 15), el beneficio máximo serían 7, a partir de la compra y venta a los 14 a los 21

(A todos ustedes Finanzas: no, no se permite la venta a corto plazo.)

Hay una solución a la complejidad de tiempo n-cuadrado que consiste en tomar todas las combinaciones de dos precios en el segundo precio “viene después de” la primera y la determinación de la diferencia máxima.

Sin embargo, existe también una solución de O (n), que consiste en iterar a través de la secuencia de una sola vez y la búsqueda de la diferencia entre cada precio y un mínimo funcionamiento . Es algo parecido a esto:

>>> def profit(prices):
... max_px = 0
... min_px = prices[0]
... for px in prices[1:]:
... min_px = min(min_px, px)
... max_px = max(px - min_px, max_px)
... return max_px

>>> prices = (20, 18, 14, 17, 20, 21, 15)
>>> profit(prices)
7

se puede hacer esto en NumPy? Usted apuesta. Pero primero, vamos a construir un ejemplo cuasi-realista:

# Create mostly NaN array with a few 'turning points' (local min/max).
>>> prices = np.full(100, fill_value=np.nan)
>>> prices[[0, 25, 60, -1]] = [80., 30., 75., 50.]

# Linearly interpolate the missing values and add some noise.
>>> x = np.arange(len(prices))
>>> is_valid = ~np.isnan(prices)
>>> prices = np.interp(x=x, xp=x[is_valid], fp=prices[is_valid])
>>> prices += np.random.randn(len(prices)) * 2

Aquí está el aspecto que tiene con matplotlib. El dicho es comprar barato (verde) y vender caro (rojo):

>>> import matplotlib.pyplot as plt

# Warning! This isn't a fully correct solution, but it works for now.
# If the absolute min came after the absolute max, you'd have trouble.
>>> mn = np.argmin(prices)
>>> mx = mn + np.argmax(prices[mn:])
>>> kwargs = {'markersize': 12, 'linestyle': ''}

>>> fig, ax = plt.subplots()
>>> ax.plot(prices)
>>> ax.set_title('Price History')
>>> ax.set_xlabel('Time')
>>> ax.set_ylabel('Price')
>>> ax.plot(mn, prices[mn], color='green', **kwargs)
>>> ax.plot(mx, prices[mx], color='red', **kwargs)

en que se ve como aplicación NumPy? Si bien no hay np.cummin () “directamente” funciones universales de NumPy (ufuncs) tienen un método acumulan () que hace lo que su nombre indica:

>>> cummin = np.minimum.accumulate

La extensión de la lógica del puro ejemplo en Python, se puede encontrar la diferencia entre cada precio y un mínimo en funcionamiento (elemento a elemento) , y luego tomar el máximo de esta secuencia:

>>> def profit_with_numpy(prices):
... """Price minus cumulative minimum price, element-wise."""
... prices = np.asarray(prices)
... return np.max(prices - cummin(prices))

>>> profit_with_numpy(prices)
44.2487532293278
>>> np.allclose(profit_with_numpy(prices), profit(prices))
True

¿Cómo estas dos operaciones, que tienen la misma tiempo teórico complejidad , se comparan en tiempo de ejecución real? En primer lugar, vamos a echar una secuencia más larga. (Esto no necesariamente tiene que ser una serie temporal de precios de las acciones en este punto.)

>>> seq = np.random.randint(0, 100, size=100000)
>>> seq
array([ 3, 23, 8, 67, 52, 12, 54, 72, 41, 10, ..., 46, 8, 90, 95, 93,
28, 24, 88, 24, 49])

Ahora, para una comparación algo injusto:

>>> setup = ('from __main__ import profit_with_numpy, profit, seq;'
... ' import numpy as np')
>>> num = 250
>>> pytime = timeit('profit(seq)', setup=setup, number=num)
>>> nptime = timeit('profit_with_numpy(seq)', setup=setup, number=num)
>>> print('Speed difference: {:0.1f}x'.format(pytime / nptime))
Speed difference: 76.0x

Por encima, el tratamiento profit_with_numpy () como pseudocódigo (sin tener en cuenta la mecánica subyacente de NumPy), en realidad hay tres pasadas a través de una secuencia: comino

  • (precios) tiene O (n) tiempo de complejidad precios
  • - comino (precios) es O (n)
  • max (...) es O (n)

Esto reduce a O ( n ), porque O (3 n ) se reduce a apenas O ates ( n ) -el n “domi n ”como n enfoques i n fi n dad.

Por lo tanto, estas dos funciones tienen equivalente a tiempo peor caso . (Aunque, como nota al margen, la función NumPy viene con un número significativamente mayor complejidad espacial.) Pero eso es probablemente la comida para llevar menos importante aquí. Una lección es que, si bien la complejidad teórica de tiempo es una consideración importante, la mecánica de ejecución también pueden jugar un papel importante. No sólo puede delegar en numpy C, pero con algunas operaciones elemento a elemento y álgebra lineal, sino que también puede tomar ventaja de la informática dentro de varios subprocesos. Pero hay un montón de factores en juego aquí, incluyendo la biblioteca subyacente utilizado (BLAS / LAPACK / Atlas), y esos datos son para un conjunto ‘tro artículo completo.

Intermezzo: Understanding Ejes Notación

En NumPy, un eje refiere a una sola dimensión de una matriz multidimensional:

>>> arr = np.array([[1, 2, 3],
... [10, 20, 30]])
>>> arr.sum(axis=0)
array([11, 22, 33])
>>> arr.sum(axis=1)
array([ 6, 60])

La terminología alrededor de ejes y la manera en la que se describen puede ser un poco poco intuitivo. En la documentación de las pandas (una biblioteca construida en la cima de NumPy), es posible que con frecuencia ver algo como:

eje: { 'índice' (0), 'columnas' (1)}

Se podría argumentar que, a partir de esta descripción, los resultados anteriores deben ser “invertido”. Sin embargo, la clave es que el eje se refiere al eje largo de la cual una función se llama. Esto está bien articulado por Jake Vanderplas:

La forma en que se especifique el eje aquí puede ser confuso para los usuarios procedentes de otros idiomas. La palabra clave eje especifica la dimensión de la matriz que se colapsó, en lugar de la dimensión que será devuelto. Así, especificando eje = 0 significa que el primer eje se derrumbó: para matrices bidimensionales, esto significa que se agregarán valores dentro de cada columna. [SOURCE]

En otras palabras, la suma de una matriz para el eje = 0 colapsa las filas de la matriz con una columna en cuanto a cálculo .

Con esta distinción en mente, el movimiento de let a explorar el concepto de radiodifusión.

Radiodifusión

Radiodifusión es otra abstracción NumPy importante. Ya hemos visto que las operaciones entre dos matrices NumPy (de igual tamaño) operan elemento a elemento :

>>> a = np.array([1.5, 2.5, 3.5])
>>> b = np.array([10., 5., 1.])
>>> a / b
array([0.15, 0.5 , 3.5 ])

Pero, ¿qué pasa con las matrices de tamaño desigual? Aquí es donde entra en juego la radiodifusión:

El término radiodifusión describe cómo trata a NumPy matrices con diferentes formas durante las operaciones aritméticas. Sujeto a ciertas restricciones, la matriz es más pequeña “emisión” a través de la matriz más grande para que tengan formas compatibles. Difusión proporciona un medio de vectorización de las operaciones de la matriz de modo que se produce en bucle C en vez de Python. [Fuente]

La forma en que se implementa de radiodifusión puede llegar a ser tedioso cuando se trabaja con más de dos matrices. Sin embargo, si sólo hay dos arrays, entonces su capacidad de ser emitidos puede ser descrito con dos reglas cortos:

Cuando se opera en dos matrices, NumPy compara sus formas elemento a elemento. Se inicia con la dimensiones arrastran y se abre camino a seguir. Dos dimensiones son compatibles cuando:

Eso es todo lo que hay que hacer.

Tomemos un caso donde queremos restar cada medio por columnas de una matriz, elemento a elemento:

>>> sample = np.random.normal(loc=[2., 20.], scale=[1., 3.5],
... size=(3, 2))
>>> sample
array([[ 1.816 , 23.703 ],
[ 2.8395, 12.2607],
[ 3.5901, 24.2115]])

En la jerga estadística, muestra consta de dos muestras (las columnas) dibujados de forma independiente de dos poblaciones con medio de 2 y 20, respectivamente. Los medios de columna-sabia debe aproximarse los medios de población (aunque más o menos, porque la muestra es pequeño):

>>> mu = sample.mean(axis=0)
>>> mu
array([ 2.7486, 20.0584])

Ahora, restando los medios de columna-sabia es sencillo porque las normas de radiodifusión visita:

>>> print('sample:', sample.shape, '| means:', mu.shape)
sample: (3, 2) | means: (2,)

>>> sample - mu
array([[-0.9325, 3.6446],
[ 0.091 , -7.7977],
[ 0.8416, 4.1531]])

Aquí está un ejemplo de restar columna medios -wise, donde una matriz más pequeña se “estira” de modo que se resta de cada fila de la matriz más grande:

Detalle técnica : la matriz de menor tamaño o escalar no se estira literalmente en la memoria: es el cálculo en sí que se repite.

Esto se extiende a la normalización de cada columna, así, haciendo que cada celda de una puntuación z en relación con su respectiva columna:

>>> (sample - sample.mean(axis=0)) / sample.std(axis=0)
array([[-1.2825, 0.6605],
[ 0.1251, -1.4132],
[ 1.1574, 0.7527]])

Sin embargo, lo que si se quiere restar, por alguna razón, los mínimos de modo de fila? Se encontrará con un poco de problemas:

>>> sample - sample.min(axis=1)
ValueError: operands could not be broadcast together with shapes (3,2) (3,)

El problema aquí es que la matriz más pequeña, en su forma actual, no se puede “estirar” para estar con muestra de forma compatible. Que realmente necesita para ampliar su dimensionalidad para cumplir con las normas de radiodifusión por encima de:

>>> sample.min(axis=1)[:, None] # 3 minimums across 3 rows
array([[1.816 ],
[2.8395],
[3.5901]])

>>> sample - sample.min(axis=1)[:, None]
array([[ 0. , 21.887 ],
[ 0. , 9.4212],
[ 0. , 20.6214]])

Nota : [:, Ninguno] es un medio por el cual para expandir la dimensionalidad de una matriz, para crear un eje de longitud uno. np.newaxis es un alias para Ninguno.

hay algunos casos significativamente más complejos, también. He aquí una definición más rigurosa de cuando cualquier número arbitrario de matrices de cualquier forma NumPy puede transmitirse juntos:

Un conjunto de matrices se llama “broadcastable” de la misma forma NumPy si las siguientes reglas producen un resultado válido, es decir, una de la siguiente es cierto :

las matrices todos tienen exactamente la misma forma.

Los arrays todos tienen el mismo número de dimensiones, y la longitud de cada dimensión es o bien una longitud común o 1.

Las matrices que tienen muy pocas dimensiones pueden tener su NumPy shapes antepone con una dimensión de longitud 1 para satisfacer propiedad # 2.

[fuente]

Esto es más fácil caminar paso a paso. Digamos que usted tiene los siguientes cuatro matrices:

>>> a = np.sin(np.arange(10)[:, None])
>>> b = np.random.randn(1, 10)
>>> c = np.full_like(a, 10)
>>> d = 8

Antes de formas de cheques, NumPy primeros convertidos a escalares matrices con un elemento:

>>> arrays = [np.atleast_1d(arr) for arr in (a, b, c, d)]
>>> for arr in arrays:
... print(arr.shape)
...
(10, 1)
(1, 10)
(10, 1)
(1,)

Ahora podemos comprobar criterio # 1. Si todos los arrays tienen la misma forma, un conjunto de sus formas se condensará abajo a un elemento, porque el conjunto () constructor gotas efectivamente elementos duplicados de su entrada. Este criterio claramente no se cumple:

>>> len(set(arr.shape for arr in arrays)) == 1
False

La primera parte del criterio # 2 también falla, es decir, todo el criterio de falla:

>>> len(set((arr.ndim) for arr in arrays)) == 1
False

El criterio final es un poco más complicado:

Las matrices que tienen muy pocas dimensiones pueden tener su shapes antepuesto con una dimensión de longitud 1 para satisfacer propiedad # 2.

Para codificar esto, se puede determinar en primer lugar la dimensionalidad de la matriz de mayor dimensión y luego anteponer los a cada forma tupla NumPy hasta que todos tienen la misma dimensión:

>>> maxdim = max(arr.ndim for arr in arrays) # Maximum dimensionality
>>> shapes = np.array([(1,) * (maxdim - arr.ndim) + arr.shape
... for arr in arrays])
>>> shapes
array([[10, 1],
[ 1, 10],
[10, 1],
[ 1, 1]])

Por último, es necesario prueba que la longitud de cada dimensión o bien está (extraída de) una longitud común, o 1 . Un truco para hacer esto es para enmascarar primero la matriz de NumPy “forma-tuplas” en lugares donde igual a uno. A continuación, se puede comprobar si las diferencias en cuanto a las columnas de pico a pico (np.ptp ()) son todos cero:

>>> masked = np.ma.masked_where(shapes == 1, shapes)
>>> np.all(masked.ptp(axis=0) == 0) # ptp: max - min
True

encapsulado en una sola función, esta lógica es similar al siguiente:

>>> def can_broadcast(*arrays) -> bool:
... arrays = [np.atleast_1d(arr) for arr in arrays]
... if len(set(arr.shape for arr in arrays)) == 1:
... return True
... if len(set((arr.ndim) for arr in arrays)) == 1:
... return True
... maxdim = max(arr.ndim for arr in arrays)
... shapes = np.array([(1,) * (maxdim - arr.ndim) + arr.shape
... for arr in arrays])
... masked = np.ma.masked_where(shapes == 1, shapes)
... return np.all(masked.ptp(axis=0) == 0)
...
>>> can_broadcast(a, b, c, d)
True

Por suerte, se puede tomar un acceso directo y el uso np.broadcast () para este cordura-cheque, aunque no está explícitamente diseñado para este propósito:

>>> def can_broadcast(*arrays) -> bool:
... try:
... np.broadcast(*arrays)
... return True
... except ValueError:
... return False
...
>>> can_broadcast(a, b, c, d)
True

para los interesados ​​en excavar un poco más profundo, PyArray_Broadcast es la función subyacente C que encapsula la difusión de normas.

Programación matriz en Acción: Ejemplos

En los 3 ejemplos siguientes, se le puso la vectorización y la difusión de trabajar con algunas aplicaciones del mundo real. La agrupación de aprendizaje

Algoritmos

Máquina es un dominio que puede tomar ventaja de vectorización frecuencia y la radiodifusión. Digamos que usted tiene los vértices de un triángulo (cada fila es un X, Y de coordenadas):

>>> tri = np.array([[1, 1],
... [3, 1],
... [2, 3]])

el centro de gravedad de este “grupo” es un (x, y) coordenada que es la media aritmética de cada uno columna:

>>> centroid = tri.mean(axis=0)
>>> centroid
array([2. , 1.6667])

Es útil para visualizar esto:

>>> trishape = plt.Polygon(tri, edgecolor='r', alpha=0.2, lw=5)
>>> _, ax = plt.subplots(figsize=(4, 4))
>>> ax.add_patch(trishape)
>>> ax.set_ylim([.5, 3.5])
>>> ax.set_xlim([.5, 3.5])
>>> ax.scatter(*centroid, color='g', marker='D', s=70)
>>> ax.scatter(*tri.T, color='b', s=70)

Muchos algoritmos de agrupamiento hacen uso de las distancias euclidianas de una colección de puntos, ya sea al origen o en relación con sus centroides. coordenadas

En cartesianas, la distancia euclídea entre p UNTOS p y q es:

[Fuente: Wikipedia]

Así que para el conjunto de coordenadas en tres de arriba, la distancia euclídea de cada punto desde el origen (0, 0) sería:

>>> np.sum(tri**2, axis=1) ** 0.5 # Or: np.sqrt(np.sum(np.square(tri), 1))
array([1.4142, 3.1623, 3.6056])

es posible que reconocer que estamos realmente sólo encontrar normas euclidianas:

>>> np.linalg.norm(tri, axis=1)
array([1.4142, 3.1623, 3.6056])

lugar de hacer referencia al origen, también se puede encontrar en la norma de cada punto con respecto a la década de triángulo centroide:

>>> np.linalg.norm(tri - centroid, axis=1)
array([1.2019, 1.2019, 1.3333])

Por último, vamos a llevar esto un paso más allá: Digamos que tiene una matriz 2D X y una matriz 2D de múltiples (x, y) “propuesta” centroides. Algoritmos tales como K-means clustering trabajo mediante la asignación al azar inicial “proponen” centroides, a continuación, la reasignación de cada punto de datos para su centroide más cercano. A partir de ahí, los nuevos centroides se calculan, con el algoritmo converge en una solución una vez que las etiquetas de re-generado (una codificación de los centroides) son sin cambios entre iteraciones. Una parte de este proceso iterativo requiere calcular la distancia euclídea de cada punto de cada centroide :

>>> X = np.repeat([[5, 5], [10, 10]], [5, 5], axis=0)
>>> X = X + np.random.randn(*X.shape) # 2 distinct "blobs"
>>> centroids = np.array([[5, 5], [10, 10]])

>>> X
array([[ 3.3955, 3.682 ],
[ 5.9224, 5.785 ],
[ 5.9087, 4.5986],
[ 6.5796, 3.8713],
[ 3.8488, 6.7029],
[10.1698, 9.2887],
[10.1789, 9.8801],
[ 7.8885, 8.7014],
[ 8.6206, 8.2016],
[ 8.851 , 10.0091]])

>>> centroids
array([[ 5, 5],
[10, 10]])

En otras palabras, queremos responder a la pregunta, a la que centroide hace cada punto dentro de X pertenecen ? Tenemos que hacer algo de remodelación para permitir la difusión de aquí, con el fin de calcular la distancia euclídea entre cada punto en X y cada punto de centroides :

>>> centroids[:, None]
array([[[ 5, 5]],

[[10, 10]]])

>>> centroids[:, None].shape
(2, 1, 2)

Esto nos permite restar limpiamente una matriz de otro usando un producto combinatorio de sus filas :

>>> np.linalg.norm(X - centroids[:, None], axis=2).round(2)
array([[2.08, 1.21, 0.99, 1.94, 2.06, 6.72, 7.12, 4.7 , 4.83, 6.32],
[9.14, 5.86, 6.78, 7.02, 6.98, 0.73, 0.22, 2.48, 2.27, 1.15]])

En otras palabras, la forma NumPy de X - centroides [:, Ninguno] es (2, 10, 2), que representan esencialmente dos matrices apiladas que son cada uno el tamaño de X. a continuación, queremos que el etiqueta (número de índice) de cada centroide más cercano, la búsqueda de la distancia mínima en el eje 0 ª de la matriz anterior:

>>> np.argmin(np.linalg.norm(X - centroids[:, None], axis=2), axis=0)
array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1])

Usted puede poner todo esto junto en forma funcional: de

>>> def get_labels(X, centroids) -> np.ndarray:
... return np.argmin(np.linalg.norm(X - centroids[:, None], axis=2),
... axis=0)
>>> labels = get_labels(X, centroids)

Let inspeccionan esto visualmente, el trazado tanto los dos clusters y sus etiquetas asignadas con un color de mapeo:

>>> c1, c2 = ['#bc13fe', '#be0119'] # https://xkcd.com/color/rgb/
>>> llim, ulim = np.trunc([X.min() * 0.9, X.max() * 1.1])

>>> _, ax = plt.subplots(figsize=(5, 5))
>>> ax.scatter(*X.T, c=np.where(labels, c2, c1), alpha=0.4, s=80)
>>> ax.scatter(*centroids.T, c=[c1, c2], marker='s', s=95,
... edgecolor='yellow')
>>> ax.set_ylim([llim, ulim])
>>> ax.set_xlim([llim, ulim])
>>> ax.set_title('One K-Means Iteration: Predicted Classes')

tablas de amortización

vectorización tiene aplicaciones en las finanzas también.

Dada una tasa anual de interés, frecuencia de pago (veces por año), el equilibrio inicial del préstamo, y el plazo del préstamo, puede crear una tabla de amortización con saldos de préstamos y pagos mensuales, de una forma vectorizada. conjunto de Let algunas constantes escalares primera:

>>> freq = 12 # 12 months per year
>>> rate = .0675 # 6.75% annualized
>>> nper = 30 # 30 years
>>> pv = 200000 # Loan face value

>>> rate /= freq # Monthly basis
>>> nper *= freq # 360 months

NumPy viene precargado con un puñado de funciones financieras que, a diferencia de sus primos de Excel, son capaces de producir salidas de vectores.

El deudor (o arrendatario) paga una cantidad mensual constante que se compone de un componente principal e intereses. A medida que el saldo pendiente del préstamo disminuye, la parte del interés del pago total disminuye con él.

>>> periods = np.arange(1, nper + 1, dtype=int)
>>> principal = np.ppmt(rate, periods, nper, pv)
>>> interest = np.ipmt(rate, periods, nper, pv)
>>> pmt = principal + interest # Or: pmt = np.pmt(rate, nper, pv)

A continuación, te nee d para calcular un balance mensual, tanto ante un d después del pago de ese mes, que puede ser d efinir d como el valor futuro del saldo original menos el futuro valor de una anualidad (un flujo de pagos), utilizando un factor de iscount d d :

Funcionalmente, esto se parece a:

>>> def balance(pv, rate, nper, pmt) -> np.ndarray:
... d = (1 + rate) ** nper # Discount factor
... return pv * d - pmt * (d - 1) / rate

por último, se puede dejar esto en un formato tabular con una de las pandas trama de datos . Tenga cuidado con los signos aquí. PMT es una salida desde la perspectiva del deudor.

>>> import pandas as pd

>>> cols = ['beg_bal', 'prin', 'interest', 'end_bal']
>>> data = [balance(pv, rate, periods - 1, -pmt),
... principal,
... interest,
... balance(pv, rate, periods, -pmt)]

>>> table = pd.DataFrame(data, columns=periods, index=cols).T
>>> table.index.name = 'month'

>>> with pd.option_context('display.max_rows', 6):
... # Note: Using floats for $$ in production-level code = bad
... print(table.round(2))
...
beg_bal prin interest end_bal
month
1 200000.00 -172.20 -1125.00 199827.80
2 199827.80 -173.16 -1124.03 199654.64
3 199654.64 -174.14 -1123.06 199480.50
... ... ... ... ...
358 3848.22 -1275.55 -21.65 2572.67
359 2572.67 -1282.72 -14.47 1289.94
360 1289.94 -1289.94 -7.26 -0.00

Al final del año 30, el préstamo se paga:

>>> final_month = periods[-1]
>>> np.allclose(table.loc[final_month, 'end_bal'], 0)
True

Nota : Durante el uso de flotadores para representar el dinero puede ser útil para la ilustración del concepto en un entorno de programación, usando Python flota para los cálculos financieros en un entorno de producción podría hacer que su cálculo sea un centavo o dos en algunos casos.

Imagen de extracción de características

En un último ejemplo, vamos a trabajar con una imagen de octubre de 1941 el USS Lexington (CV-2), los restos del que fue descubierto frente a la costa de Australia en 2018. de marzo de primer lugar, podemos mapa de la imagen en una matriz de NumPy de sus valores de píxeles:

>>> from skimage import io

>>> url = ('https://www.history.navy.mil/bin/imageDownload?image=/'
... 'content/dam
hhc/our-collections/photography/images/'
... '80-G-410000/80-G-416362&rendition=cq5dam.thumbnail.319.319.png')
>>> img = io.imread(url, as_grey=True)

>>> fig, ax = plt.subplots()
>>> ax.imshow(img, cmap='gray')
>>> ax.grid(False)

Por razones de simplicidad, la imagen se carga en escala de grises, lo que resulta en una matriz 2D de 64 bits flota en lugar de una matriz MxNx4 RGBA 3-dimensional , con valores más bajos denotan manchas más oscuras:

>>> img.shape
(254, 319)

>>> img.min(), img.max()
(0.027450980392156862, 1.0)

>>> img[0, :10] # First ten cells of the first row
array([0.8078, 0.7961, 0.7804, 0.7882, 0.7961, 0.8078, 0.8039, 0.7922,
0.7961, 0.7961])
>>> img[-1, -10:] # Last ten cells of the last row
array([0.0784, 0.0784, 0.0706, 0.0706, 0.0745, 0.0706, 0.0745, 0.0784,
0.0784, 0.0824])

una técnica comúnmente empleados como un paso intermedio en el análisis de imagen es extracción parche . Como su nombre indica, este consiste en extraer más pequeños superpuestos submatrices de una matriz más grande y puede ser utilizado en casos en los que es ventajoso “eliminación de ruido” o desenfocar una imagen.

Este concepto se extiende a otros campos, también. Por ejemplo, sería hacer algo similar mediante la adopción de “rodar” ventanas de una serie de tiempo con múltiples características (variables). Es incluso útil para la construcción de Juego de la vida. (Aunque, con un núcleo de convolución 3x3 es un enfoque más directo.)

Aquí, vamos a encontrar la significa la superposición de cada parche de 10x10 dentro img. Tomando un ejemplo en miniatura, el primer grupo de parches 3x3 en la esquina superior izquierda de img sería:

>>> img[:3, :3]
array([[0.8078, 0.7961, 0.7804],
[0.8039, 0.8157, 0.8078],
[0.7882, 0.8 , 0.7961]])

>>> img[:3, :3].mean()
0.7995642701525054

El enfoque puro-Python a la creación de parches de deslizamiento implicaría un para-bucle anidado. Que había necesidad de tener en cuenta que el índice a partir de los parches más a la derecha estará en el índice n - 3 + 1, donde n es el ancho de la matriz. En otras palabras, si estuviera extrayendo parches 3x3 a partir de una matriz llamada 10x10 arr, el último parche sería tomada desde arr [07:10, 07:10]. También hay que tener en cuenta que la gama de Python () no incluye su parámetro de parada:

>>> size = 10
>>> m, n = img.shape
>>> mm, nn = m - size + 1, n - size + 1
>>>
>>> patch_means = np.empty((mm, nn))
>>> for i in range(mm):
... for j in range(nn):
... patch_means[i, j] = img[i: i+size, j: j+size].mean()

>>> fig, ax = plt.subplots()
>>> ax.imshow(patch_means, cmap='gray')
>>> ax.grid(False)

Con este bucle, que está realizando una gran cantidad de llamadas de Python.

Una alternativa que sea escalable para ampliar la imagen RGB o RGBA es stride_tricks de NumPy.

Un primer paso instructivo es visualizar, dado el tamaño del parche y la forma de la imagen, lo que es una matriz de dimensiones superiores de los parches se vería así. Tenemos un img matriz 2D con forma (254, 319) y una (10, 10) patch 2d. Esto significa que nuestra forma de salida (antes de tomar la media de cada 10x10 matriz “interior”) sería:

>>> shape = (img.shape[0] - size + 1, img.shape[1] - size + 1, size, size)
>>> shape
(245, 310, 10, 10)

También es necesario especificar el pasos de la nueva matriz. de una matriz pasos es una tupla de bytes para saltar en cada dimensión cuando se mueve a lo largo de la matriz. Cada píxel en img es un flotador 64 bits (8 bytes), es decir, el tamaño total de la imagen se 254 x 319 x 8 = 648208 bytes.

>>> img.dtype
dtype('float64')

>>> img.nbytes
648208

Internamente, img se mantiene en la memoria como un bloque contiguo de 648,208 bytes. pasos es, por tanto, una especie de “metadatos” -como atributo que nos indica la cantidad de bytes que necesitamos dar un salto adelante para pasar a la siguiente posición largo de cada eje . Nos movemos en bloques de 8 bytes a lo largo de las filas, pero necesidad de atravesar 8 x 319 = 2552 bytes para moverse “hacia abajo” de una fila a otra.

>>> img.strides
(2552, 8)

En nuestro caso, los pasos de los parches resultantes solo se repetirán los pasos de img dos veces:

>>> strides = 2 * img.strides
>>> strides
(2552, 8, 2552, 8)

Ahora, vamos a poner estas piezas juntas con stride_tricks de NumPy:

>>> from numpy.lib import stride_tricks

>>> patches = stride_tricks.as_strided(img, shape=shape, strides=strides)
>>> patches.shape
(245, 310, 10, 10)

Aquí está el primer parche 10x10 :

>>> patches[0, 0].round(2)
array([[0.81, 0.8 , 0.78, 0.79, 0.8 , 0.81, 0.8 , 0.79, 0.8 , 0.8 ],
[0.8 , 0.82, 0.81, 0.79, 0.79, 0.79, 0.78, 0.81, 0.81, 0.8 ],
[0.79, 0.8 , 0.8 , 0.79, 0.8 , 0.8 , 0.82, 0.83, 0.79, 0.81],
[0.8 , 0.79, 0.81, 0.81, 0.8 , 0.8 , 0.78, 0.76, 0.8 , 0.79],
[0.78, 0.8 , 0.8 , 0.78, 0.8 , 0.79, 0.78, 0.78, 0.79, 0.79],
[0.8 , 0.8 , 0.78, 0.78, 0.78, 0.8 , 0.8 , 0.8 , 0.81, 0.79],
[0.78, 0.77, 0.78, 0.76, 0.77, 0.8 , 0.8 , 0.77, 0.8 , 0.8 ],
[0.79, 0.76, 0.77, 0.78, 0.77, 0.77, 0.79, 0.78, 0.77, 0.76],
[0.78, 0.75, 0.76, 0.76, 0.73, 0.75, 0.78, 0.76, 0.77, 0.77],
[0.78, 0.79, 0.78, 0.78, 0.78, 0.78, 0.77, 0.76, 0.77, 0.77]])

el último paso es complicado. Para obtener una media vectorizado de cada matriz interior 10x10 , tenemos que pensar cuidadosamente acerca de la dimensionalidad de lo que tenemos ahora. El resultado debe colapsar las últimas dos dimensiones por lo que nos quedamos con una sola matriz 245x310 .

One (subóptima) forma sería la de parches reshape primero, aplanando los arrays 2D interiores a la longitud-100 vectores, y luego calculando la media sobre el eje final:

>>> veclen = size ** 2
>>> patches.reshape(*patches.shape[:2], veclen).mean(axis=-1).shape
(245, 310)

Sin embargo, también puede especificar eje como una tupla, informática una media en las últimas dos ejes, que deberían ser más eficiente que la remodelación: maquillaje de

>>> patches.mean(axis=(-1, -2)).shape
(245, 310)

Let seguro de que esto comprueba mediante la comparación de igualdad a nuestra versión en bucle. Que hace:

>>> strided_means = patches.mean(axis=(-1, -2))
>>> np.allclose(patch_means, strided_means)
True

Si el concepto de pasos que tiene babeo, no se preocupe: scikit-learn ya ha encajado muy bien todo este proceso dentro de su módulo de feature_extraction.

un pensamiento de despedida: No se exceda en Optimizar

En este artículo, se discute la optimización en tiempo de ejecución mediante el aprovechamiento de la programación de matriz en NumPy. Cuando se trabaja con grandes conjuntos de datos, es importante ser consciente de microperformance.

Sin embargo, hay un subconjunto de casos en los que evitar una nativa de Python para el bucle no es posible. Como aconsejó Donald Knuth, “La optimización prematura es la raíz de todo mal”. Los programadores pueden predecir de forma incorrecta, donde en su código aparecerá un cuello de botella, de pasar horas tratando de vectorizar totalmente una operación que daría lugar a una mejora relativamente insignificante en tiempo de ejecución.

No hay nada malo con fines de bucles roció aquí y allá. A menudo, puede ser más productivo pensar en vez acerca de cómo optimizar el flujo y la estructura de toda la secuencia de comandos en un nivel más alto de abstracción.

Más Recursos adicionales

gratuito: Haga clic aquí para obtener acceso a una Guía de Recursos libre NumPy que señala al los mejores tutoriales, videos y libros para mejorar sus habilidades NumPy.

NumPy Documentación:

  • ¿Cuál es NumPy? funciones
  • Radiodifusión
  • Universal
  • NumPy para los usuarios de MATLAB
  • El índice completo NumPy Referencia

Libros: Guía del

  • Travis Oliphant a NumPy, 2ª ed. (Travis es el creador principal de NumPy)
  • Capítulo 2 ( “Introducción a NumPy”) de Jake Vanderplas' Python Data Science Handbook
  • Capítulo 4 ( ‘NumPy Fundamentos’) y en el capítulo 12 ( ‘Advanced NumPy’) de Wes McKinney pitón de Análisis de datos 2ª ed.
  • Capítulo 2 ( “Los matemáticos bloques de construcción de redes neuronales”) de Aprendizaje de François Chollet profundo con Python
  • Robert Johansson Numerical Python
  • Ivan Idris: Guía del Principiante Numpy, 3ª ed.

Otros Recursos:

  • Wikipedia: Array Programming
  • SciPy Lecture Notes: básico y avanzado NumPy
  • EricsBroadcastingDoc: Array Radiodifusión en NumPy
  • SciPy Cookbook: Vistas frente a copias en NumPy
  • Nicolas Rougier: A partir de Python a Numpy y 100 NumPy Ejercicios docs
  • TensorFlow: docs Radiodifusión Semántica
  • Theano: Radiodifusión
  • Eli Bendersky: Radiodifusión Arrays en Numpy

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *