Categorías
Python

Mire el mA, pero no los fines de Loops: Programación matriz con NumPy

 

Tabla de Contenidos

  • Ingeniería Los datos de prueba
  • pendiente de descenso en puro Python
  • Usando NumPy
  • Usando TensorFlow
  • Conclusión
  • Referencias

Python tiene una filosofía de diseño que las tensiones permitiendo a los programadores para expresar conceptos legible y en menos líneas de código. Esta filosofía hace que el lenguaje adecuado para un conjunto diverso de casos de uso: las secuencias de comandos simples para web, grandes aplicaciones web (como YouTube), lenguaje de script para otras plataformas (como Blender y Maya de Autodesk), y las aplicaciones científicas en diversas áreas, como la astronomía , la meteorología, la física y la ciencia de datos.

Es técnicamente posible implementar escalares y matriciales cálculos utilizando listas de Python. Sin embargo, esto puede ser difícil de manejar, y el rendimiento es pobre en comparación con los idiomas adecuados para el cálculo numérico, como el MATLAB o Fortran, o incluso algunos lenguajes de propósito general, como C o C ++.

Para eludir esta deficiencia, varias bibliotecas que han surgido a mantener la facilidad de uso de Python mismo tiempo que proporciona la capacidad de realizar cálculos numéricos de una manera eficiente. Dos de esas bibliotecas vale la pena mencionar son NumPy (una de las bibliotecas pioneras a traer el cálculo numérico eficiente para Python) y TensorFlow (una biblioteca, más recientemente, enrollada a cabo se centró más en los algoritmos de aprendizaje profundas).

  • NumPy proporciona soporte para grandes matrices multidimensionales y matrices a lo largo con una colección de funciones matemáticas para operar en estos elementos. El proyecto se basa en paquetes conocidos implementados en otros idiomas (como Fortran) para realizar cálculos eficientes, con lo que el usuario tanto la expresividad de Python y un rendimiento similar a MATLAB o Fortran.
  • TensorFlow es una biblioteca de código abierto para el cálculo numérico desarrollado originalmente por investigadores e ingenieros que trabajan en el equipo de Google cerebro. El foco principal de la biblioteca es proporcionar una API fácil de usar para implementar algoritmos de aprendizaje automático práctico y desplegarlos para funcionar en las CPU, GPU, o un clúster.

Pero, ¿Cómo se comparan estos esquemas? ¿Cuánto más rápido se ejecuta la aplicación cuando se implementa con NumPy en lugar de Python puro? ¿Qué hay de TensorFlow? El propósito de este artículo es para empezar a explorar las mejoras que se puede lograr mediante el uso de estas bibliotecas.

Para comparar el rendimiento de los tres enfoques, construirá una regresión básica con nativa de Python, NumPy y TensorFlow.

Informarte: no se pierda el seguimiento a este tutorial-Clic aquí para unirse al Boletín pitón real y sabrá cuando la próxima entrega sale.

Ingeniería de la prueba

a los datos de prueba el rendimiento de las bibliotecas, se le considera un sencillo de dos parámetros lineales problema de regresión. El modelo tiene dos parámetros: un término de intersección, w_0 y un coeficiente único, w_1.

Dada N pares de entradas x y salidas deseadas D, la idea es modelar la relación entre las salidas y las entradas utilizando un modelo lineal y = w_0 + w_1 * x donde la salida del modelo y es aproximadamente igual a la deseada salida d para cada par (x, d).

Detalle Técnica : El término de intersección, w_0, es técnicamente sólo un coeficiente como w_1, pero se puede interpretar como un coeficiente que multiplica los elementos de un vector de 1s.

Para generar el conjunto de entrenamiento del problema, utilizar el programa siguiente:

import numpy as np

np.random.seed(444)

N = 10000
sigma = 0.1
noise = sigma * np.random.randn(N)
x = np.linspace(0, 2, N)
d = 3 + 2 * x + noise
d.shape = (N, 1)

# We need to prepend a column vector of 1s to `x`.
X = np.column_stack((np.ones(N, dtype=x.dtype), x))
print(X.shape)
(10000, 2)

Este programa crea un conjunto de 10.000 x entradas distribuidas linealmente en el intervalo de 0 a 2. A continuación, crea un conjunto de salidas deseadas d = 3 + 2 * x + ruido, donde el ruido se toma de una distribución gaussiana (normal) con media cero y desviación estándar sigma = 0,1.

Al crear X y D de esta manera, usted está estipulando de manera efectiva que la solución óptima para w_0 y W_1 es 3 y 2, respectivamente.

Xplus = np.linalg.pinv(X)
w_opt = Xplus @ d
print(w_opt)
[[2.99536719]
[2.00288672]]

Existen varios métodos para estimar los parámetros w_0 y W_1 para ajustar un modelo lineal para el conjunto de entrenamiento. Uno de los más utilizados-es mínimos cuadrados ordinarios, que es una solución bien conocida para la estimación de w_0 y w_1 para reducir al mínimo el cuadrado del error e, dada por la suma de Y – d para cada muestra de entrenamiento.

Una forma de calcular fácilmente la solución ordinaria de mínimos cuadrados es mediante el uso de la Moore-Penrose pseudo-inversa de una matriz. Este enfoque se deriva del hecho de que tiene X y D y está tratando de resolver para w_m, en la ecuación d = X @ w_m. (El símbolo @ denota la multiplicación de matrices, que está soportado por tanto NumPy y Python nativo como de PEP 465 y Python 3.5+.)

Usando este enfoque, se puede estimar usando w_m w_opt = Xplus @ d, donde Xplus es propuesta por el pseudo-inversa de X, que se puede calcular usando numpy.linalg.pinv, resultando en w_0 = 2,9978 y w_1 = 2,0016, que está muy cerca de los valores esperados de w_0 = 3 y w_1 = 2.

Nota : Usando w_opt = np.linalg.inv (XT @ X) @ XT @ d produciría la misma solución. Para más información, véase A Matrix Formulación del modelo de regresión múltiple.

Aunque es posible utilizar este enfoque determinista para estimar los coeficientes del modelo lineal, no es posible para algunos otros modelos, tales como redes neuronales. En estos casos, los algoritmos iterativos se utilizan para estimar una solución para los parámetros del modelo.

uno de los algoritmos más utilizados es el descenso de gradiente, que en un nivel alto consiste en la actualización de los coeficientes de los parámetros hasta que convergen en una pérdida minimizada (o costó ). Es decir, tenemos un poco de costó función (a menudo, el error cuadrático medio-MSE), y calcular su gradiente con respecto a los coeficientes de la red (en este caso, los parámetros w_0 y W_1), considerando un mu de tamaño de paso. Mediante la realización de esta actualización muchas veces (en muchas épocas), los coeficientes convergen a una solución que minimiza el costó función .

En las siguientes secciones, se le construir y utilizar algoritmos de gradiente descendente en Python puro, NumPy y TensorFlow. Para comparar el rendimiento de los tres enfoques, vamos a ver comparaciones de tiempo de ejecución en un procesador Intel Core i7 4790K 4.0 GHz CPU.

pendiente de descenso en el inicio de

Python puro

Vamos con un enfoque puro en Python como línea de base para la comparación con los otros enfoques. La función de Python debajo de las estimaciones y los parámetros w_0 W_1 utilizando descenso de gradiente:

import itertools as it

def py_descent(x, d, mu, N_epochs):
N = len(x)
f = 2 / N

# "Empty" predictions, errors, weights, gradients.
y = [0] * N
w = [0, 0]
grad = [0, 0]

for _ in it.repeat(None, N_epochs):
# Can't use a generator because we need to
# access its elements twice.
err = tuple(i - j for i, j in zip(d, y))
grad[0] = f * sum(err)
grad[1] = f * sum(i * j for i, j in zip(err, x))
w = [i + mu * j for i, j in zip(w, grad)]
y = (w[0] + w[1] * i for i in x)
return w

arriba, todo se hace con las listas por comprensión de Python, la sintaxis de rebanado, y el incorporado en la suma () y funciones (zip). Antes de ejecutar a través de cada época, los contenedores “vacíos” de ceros se inicializan para y, w, y grad.

Detalle Técnica : py_descent anterior utilizar itertools.repeat () en lugar de para _ en el rango (N_epochs). El primero es más rápido que el último debido a la repetición () no necesita para fabricar un número entero distinto para cada bucle. Simplemente tiene que actualizar la cuenta de referencia en Ninguno. El módulo timeit contiene un ejemplo.

Ahora, usar esto para encontrar una solución:

import time

x_list = x.tolist()
d_list = d.squeeze().tolist() # Need 1d lists

# `mu` is a step size, or scaling factor.
mu = 0.001
N_epochs = 10000

t0 = time.time()
py_w = py_descent(x_list, d_list, mu, N_epochs)
t1 = time.time()

print(py_w)
[2.959859852416156, 2.0329649630002757]

print('Solve time: {:.2f} seconds'.format(round(t1 - t0, 2)))
Solve time: 18.65 seconds

Con un tamaño de paso de mu = 0.001 y 10.000 épocas, podemos obtener una estimación bastante precisa de w_0 y W_1. Dentro del bucle para, los gradientes con respecto a los parámetros se calculan y se utilizan a su vez para actualizar los pesos, moviéndose en la dirección opuesta con el fin de reducir al mínimo la función de coste MSE.

En cada época, después de la actualización, se calcula la salida del modelo. Las operaciones vectoriales se realizan usando listas por comprensión. También podríamos haber actualizado y en el lugar, pero que no habría sido beneficioso para el rendimiento.

Tiempo transcurrido del algoritmo se mide usando la biblioteca de tiempo. Toma de 18,65 segundos para estimar w_0 = 2,9598 y 2,0329 = W_1. Mientras que la biblioteca timeit puede proporcionar una estimación más exacta del tiempo de ejecución mediante la ejecución de múltiples bucles y desactivación de la recolección de basura, simplemente viendo una sola carrera con el tiempo basta en este caso, como se verá en breve.

Usando NumPy

NumPy añade soporte para grandes matrices multidimensionales y matrices, junto con una colección de funciones matemáticas para operar en ellos. Las operaciones se han optimizado para funcionar con una velocidad de vértigo, apoyándose en los BLAS y LAPACK proyectos para la implementación subyacente.

Usando NumPy, considere el siguiente programa para estimar los parámetros de la regresión:

def np_descent(x, d, mu, N_epochs):
d = d.squeeze()
N = len(x)
f = 2 / N

y = np.zeros(N)
err = np.zeros(N)
w = np.zeros(2)
grad = np.empty(2)

for _ in it.repeat(None, N_epochs):
np.subtract(d, y, out=err)
grad[:] = f * np.sum(err), f * (err @ x)
w = w + mu * grad
y = w[0] + w[1] * x
return w

np_w = np_descent(x, d, mu, N_epochs)
print(np_w)
[2.95985985 2.03296496]

El bloque de código anterior se aprovecha de operaciones vectorizada con arrays NumPy (ndarrays). La única explícita de bucle es el bucle externo sobre el que se repite la rutina de entrenamiento en sí. Las listas por comprensión están ausentes aquí porque ndarray tipo de NumPy sobrecarga los operadores aritméticos para realizar cálculos de matriz de forma optimizada.

Usted puede notar que hay algunas formas alternativas de ir sobre la solución de este problema. Por ejemplo, se puede usar simplemente f * err @ X, donde X es la matriz 2D que incluye un vector columna de unos, en lugar de nuestras x 1d.

Sin embargo, esto no es en realidad todo lo eficiente, ya que requiere un producto escalar de toda una columna de unos con otro vector (err), y se sabe que resultado será simplemente np.sum (err). Del mismo modo, w [0] + w [1] * x desechos menos cálculo que w * X, en este caso específico. La mirada de

Veamos la comparación de tiempo. Como se verá más adelante, se necesita el módulo timeit aquí para obtener una imagen más precisa del tiempo de ejecución, ya que ahora estamos hablando de fracciones de segundo en lugar de varios segundos de tiempo de ejecución:

import timeit

setup = ("from __main__ import x, d, mu, N_epochs, np_descent;"
"import numpy as np")
repeat = 5
number = 5 # Number of loops within each repeat

np_times = timeit.repeat('np_descent(x, d, mu, N_epochs)', setup=setup,
repeat=repeat, number=number)

timeit.repeat () RETUR n SA lista. Cada eleme n t es el tiempo total de tomar n ejecutar n bucles de la FICHA n t. Para conseguir una relación Si de N estimación gle de ru de N vez, que ca de N tome el tiempo promedio para una relación Si de N llamada GLE desde la parte inferior Bou de N d la lista de repeticiones:

print(min(np_times) / number)
0.31947448799983247

usando TensorFlow

TensorFlow es una biblioteca de código abierto para el cálculo numérico desarrollado originalmente por investigadores e ingenieros que trabajan en el equipo de Google cerebro.

A través de su API de Python, rutinas de TensorFlow se implementan como un gráfico de los cálculos a realizar. Los nodos en el gráfico representan operaciones matemáticas, y los bordes del gráfico representan los arreglos de datos multidimensionales (también llamados tensores) comunicados entre ellos.

En tiempo de ejecución, TensorFlow toma la gráfica de los cálculos y lo ejecuta de manera eficiente utilizando optimizado código C ++. Mediante el análisis de la gráfica de cálculos, TensorFlow es capaz de identificar las operaciones que se pueden ejecutar en paralelo. Esta arquitectura permite el uso de una única API para implementar el cálculo de una o más CPU o GPU en un escritorio, servidor o dispositivo móvil.

Usando TensorFlow, considere el siguiente programa para estimar los parámetros de la regresión:

import tensorflow as tf

def tf_descent(X_tf, d_tf, mu, N_epochs):
N = X_tf.get_shape().as_list()[0]
f = 2 / N

w = tf.Variable(tf.zeros((2, 1)), name="w_tf")
y = tf.matmul(X_tf, w, name="y_tf")
e = y - d_tf
grad = f * tf.matmul(tf.transpose(X_tf), e)

training_op = tf.assign(w, w - mu * grad)
init = tf.global_variables_initializer()

with tf.Session() as sess:
init.run()
for epoch in range(N_epochs):
sess.run(training_op)
opt = w.eval()
return opt

X_tf = tf.constant(X, dtype=tf.float32, name="X_tf")
d_tf = tf.constant(d, dtype=tf.float32, name="d_tf")

tf_w = tf_descent(X_tf, d_tf, mu, N_epochs)
print(tf_w)
[[2.9598553]
[2.032969 ]]

Cuando se utiliza TensorFlow, los datos deben ser cargados en un tipo de datos especial llamado tensor. Tensores espejo matrices NumPy de más maneras que son diferentes.

type(X_tf)

Después de que los tensores se crean a partir de los datos de entrenamiento, se define la gráfica de los cálculos:

  • primer lugar, un tensor variable de w se utiliza para almacenar los parámetros de regresión, que se actualiza en cada iteración.
  • Uso de w y X_tf, la salida y se calcula usando un producto de matriz, implementado con tf.matmul ().
  • El error se calcula y se almacena en el e tensor.
  • Los gradientes se calculan, utilizando el enfoque de la matriz, multiplicando la transpuesta de X_tf por el e.
  • Finalmente, la actualización de los parámetros de la regresión se implementa con la función tf.assign (). Se crea un nodo que implementa el descenso de gradiente de lote, la actualización de la próxima tensor de paso w para w – MU * grad.

Vale la pena notar que el código hasta la creación training_op no realiza ningún cálculo. Simplemente crea de la gráfica de los cálculos a realizar. De hecho, incluso las variables no están inicializados todavía. Para realizar los cálculos, es necesario crear una sesión y lo utilizan para inicializar las variables y ejecutar el algoritmo para evaluar los parámetros de la regresión.

Hay algunas maneras diferentes para inicializar las variables y crear la sesión para realizar los cálculos. En este programa, la línea init = tf.global_variables_initializer () crean un nodo en el gráfico que va a inicializar las variables cuando se ejecuta. La sesión se crea en el bloque con, y init.run () se utiliza para inicializar las variables realidad. En el interior del bloque con, training_op se ejecuta por el número deseado de épocas, evaluar el parámetro de la regresión, que tienen su valor final almacena en opt.

Esta es la misma estructura de código de tiempo que se utiliza con la aplicación NumPy:

setup = ("from __main__ import X_tf, d_tf, mu, N_epochs, tf_descent;"
"import tensorflow as tf")

tf_times = timeit.repeat("tf_descent(X_tf, d_tf, mu, N_epochs)", setup=setup,
repeat=repeat, number=number)

print(min(tf_times) / number)
1.1982891103994917

tardó 1.20 segundos para estimar w_0 = 2,9598553 y W_1 = 2,032969. Vale la pena notar que el cálculo se realizó sobre una CPU y el rendimiento puede ser mejorado cuando se ejecuta en una GPU.

Por último, también podría haber definido una función de coste MSE y pasó esto a gradientes de TensorFlow () función, que realiza la diferenciación automática, encontrar el vector gradiente de MSE con respecto a los pesos:

mse = tf.reduce_mean(tf.square(e), name="mse")
grad = tf.gradients(mse, w)[0]

Sin embargo, la diferencia de tiempo en este caso es insignificante.

Conclusión

El propósito de este artículo es realizar una comparación preliminar de los resultados de un Python puro, una NumPy y una aplicación TensorFlow de un simple algoritmo iterativo para estimar los coeficientes de un problema de regresión lineal.

Los resultados para el tiempo transcurrido para ejecutar el algoritmo se resumen en la siguiente tabla:

Mientras que las soluciones NumPy y TensorFlow son competitivos (en la CPU), la aplicación pura de Python es un distante tercer lugar. Aunque Python es un lenguaje de programación de propósito general robusta, sus bibliotecas dirigidas hacia el cálculo numérico va a ganar cualquier día cuando se trata de grandes operaciones por lotes en matrices.

Mientras que el ejemplo NumPy demostró ser más rápido por un pelo que TensorFlow en este caso, es importante señalar que TensorFlow realmente brilla para los casos más complejos. Con nuestro problema de regresión relativamente elementales, utilizando TensorFlow asciende podría decirse que “el uso de un martillo para romper una nuez”, como dice el dicho.

Con TensorFlow, es posible construir y entrenar las redes neuronales complejas a través de cientos o miles de servidores multi-GPU. En un futuro post, vamos a cubrir la instalación para ejecutar este ejemplo, en las GPU utilizando TensorFlow y comparar los resultados.

no se pierda el seguimiento tutorial: Haga clic aquí para unirse a la Newsletter pitón real y usted sabrá cuando la próxima entrega sale.

Referencias

  • las páginas de inicio TensorFlow NumPy y
  • Aurélien Géron: el aprendizaje práctico de la máquina con scikit-learn y TensorFlow
  • Look Ma, No Para bucles: Programación matriz con tutoriales NumPy
  • NumPy en
  • pitón real

Deja un comentario

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