INTRODUCCIÓN A TENSORFLOW
Tuve una divertida divertida que escribí un software de red neuronal en los años 90, y he estado ansioso por intentar crear algunos usando TensorFlow.
El marco de la máquina de la máquina de Google es la nueva calzada en este momento. Y cuando TensorFlow se instaló en la PI de la frambuesa, trabajar con él se hizo muy fácil de hacer. En poco tiempo hice una red neuronal que cuenta en binario. Así que pensé que pasaría por lo que he aprendido hasta ahora. Esperemos que esto sea más fácil para cualquier otra persona que quiera probarlo, o para cualquier persona que solo quiera una idea de las redes neuronales.
¿Qué es TensorFlow?
Para citar el sitio web de TensorFlow, TensorFlow es una “Biblioteca de software de código abierto para cálculo numérico utilizando los gráficos de flujo de datos”. ¿Qué queremos decir con “gráficos de flujo de datos”? Bueno, esa es la parte realmente increíble. Pero antes de que podamos responder eso, tendremos que hablar un poco sobre la estructura para una red neuronal simple.
Red Neural Counter Binary
Conceptos básicos de una red neuronal.
Una red neuronal simple tiene algunas unidades de entrada donde la entrada va. También tiene unidades ocultas, las llamadas por la perspectiva de un usuario que están literalmente ocultas. Y hay unidades de salida, desde las cuales obtenemos los resultados. A los lados también son unidades de sesgo, que están ahí para ayudar a controlar los valores emitidos de las unidades ocultas y de salida. Conectar todas estas unidades son un montón de pesas, que son solo números, cada uno de los cuales está asociado con dos unidades.
La forma en que inculpamos a la inteligencia en esta red neuronal es asignar valores a todos esos pesos. Eso es lo que hace una red neuronal, encuentra valores adecuados para esos pesos. Una vez capacitado, en nuestro ejemplo, estableceremos las unidades de entrada a los dígitos binarios 0, 0 y 0, respectivamente, TensorFlow hará cosas con todo lo que en el medio, y las unidades de salida contendrán mágicamente los dígitos binarios 0, 0 y 1 respectivamente. En caso de que te perdieras, sabía que el siguiente número después de la 000 binario era 001. Para 001, debería escupir 010, y así sucesivamente hasta 111, en el que escupirá 000. Una vez que esos pesos se establezcan adecuadamente, Sabrá saber cómo contar.
Red de contador binario neuronal con matrices.
Un paso en “Ejecutar”, la red neuronal es multiplicar el valor de cada peso por el valor de su unidad de entrada, y luego almacenar el resultado en la unidad oculta asociada.
Podemos volver a dibujar las unidades y pesas como matrices, o lo que se llaman listas en Python. Desde un punto de vista de matemáticas, son matrices. Hemos vuelto a rediberarse solo una parte de ellos en el diagrama. Multiplicar la matriz de entrada con la matriz de peso implica la multiplicación de matriz simple que resulta en la matriz de cinco elementos oculta / lista / matriz.
De matrices a tensores
En TensorFlow, esas listas se llaman tensores. Y la etapa de multiplicación de la matriz se llama operación, o OP en programador, habla, un término tendrá que acostumbrarse si planea leer la documentación de TensorFlow. Tómalo más allá, toda la red neuronal es una colección de tensores y los operadores que operan en ellos. En total, conforman un gráfico.
Gráfico completo del contador binario
Layer1 expandido
Aquí se muestran las instantáneas tomadas de Tensorboard, una herramienta para visualizar el gráfico, así como examinar los valores de tensor durante y después de la capacitación. Los tensores son las líneas, y escritas en las líneas son las dimensiones del tensor. Conectar los tensores son todas las operaciones, aunque algunas de las cosas que ve pueden hacer un vuelto a doble clic para ampliar para obtener más detalles, como lo hemos hecho para la capa1 en la segunda instantánea.
En la parte inferior es X, el nombre que hemos dado para un Placeholder OP que nos permite proporcionar valores para el tensor de entrada. La línea que sube y a la izquierda de ella es el tensor de entrada. Continúe siguiendo esa línea y encontrará el Matmul OP, que hace la multiplicación de matriz con ese tensor de entrada y el tensor, que es la otra línea que conduce al Matmul Op. Ese tensor representa los pesos.
Todo esto fue solo para darle una idea de lo que son una gráfica y sus tensores y operaciones, lo que le brinda una mejor idea de lo que queremos decir con TensorFlow siendo una “Biblioteca de software para cálculo numérico utilizando gráficos de flujo de datos”. Pero, ¿por qué querríamos crear estos gráficos?
¿Por qué crear gráficos?
La API que actualmente está estable es una para Python, un lenguaje interpretado. Las redes neuronales son complicadas intensivas y una gran parte podría tener miles o incluso millones de pesos. La computación al interpretar cada paso tomaría para siempre.
Por lo tanto, en su lugar, creamos una gráfica formada por tensores y operaciones, describiendo el diseño de la red neuronal, todas las operaciones matemáticas e incluso valores iniciales para las variables. Solo después de haber creado este gráfico, lo pasamos a lo que TensorFlow llama una sesión. Esto se conoce como ejecución diferida. La sesión ejecuta el gráfico utilizando un código muy eficiente. No solo eso, sino que muchas de las operaciones, como la multiplicación de matriz, son las que se pueden realizar en una GPU compatible (unidad de procesamiento de gráficos) y la sesión lo hará para usted. Además, TensorFlow es BUtilt para poder distribuir el procesamiento en múltiples máquinas y / o GPU. Dándole el gráfico completo le permite hacerlo.
Creando el gráfico de contador binario
Y aquí está el código para nuestra red neuronal de contador binario. Puede encontrar el código fuente completo en esta página de GitHub. Tenga en cuenta que hay un código adicional para ahorrar información para usar con Tensorboard.
Comenzaremos con el código para crear la gráfica de Tensores y OPS.
Importar TensorFlow como TF
sess = tf.interactivessionion ()
Num_inputs = 3
Num_hidden = 5
Num_outputs = 3
Primero importamos el módulo TensorFlow, creamos una sesión para usar más adelante y, para que nuestro código sea más comprensible, creamos algunas variables que contienen el número de unidades en nuestra red.
x = tf.placeherripe (tf.float32, forma = [Ninguno, Num_Inputs], Name = ‘X’)
y_ = tf.placeholder (tf.float32, forma = [Ninguno, NUM_OUTPUTS], NOMBRE = ‘Y_’)
Luego creamos marcadores de posición para nuestras unidades de entrada y salida. Un marcador de posición es un OP de TensorFlow para cosas que proporcionaremos valores para más adelante. X e Y_ ahora son tensores en un nuevo gráfico y cada uno tiene un OP de marcador de posición asociado con él.
Puede que se pregunte por qué definimos las formas como [Ninguno, NUM_INPUTS] y [Ninguno, Num_Outputs], listas de dos dimensiones y por qué ninguna para la primera dimensión? En la visión general de las redes neuronales por encima, parece que le daremos una entrada a la vez y entrenará para producir una salida dada. Sin embargo, es más eficiente, si le damos varios pares de entrada / salida a la vez, lo que se llama un lote. La primera dimensión es para el número de pares de entrada / salida en cada lote. No sabremos cuántos están en un lote hasta que realmente le damos uno más tarde. Y, de hecho, estamos usando el mismo gráfico para la capacitación, las pruebas y el uso real, por lo que el tamaño del lote no siempre será el mismo. Así que usamos el objeto Pitton Placeholder Ninguno para el tamaño de la primera dimensión por ahora.
W_fc1 = tf.truncated_normal ([num_inputs, num_hidden], media = 0.5, stddev = 0.707)
W_fc1 = tf.variable (w_fc1, nombre = ‘w_fc1’)
b_fc1 = tf.truncated_normal ([num_hidden], media = 0.5, stddev = 0.707)
b_fc1 = tf.variable (b_fc1, nombre = ‘b_fc1’)
h_fc1 = tf.nn.relu (tf.matmul (x, w_fc1) + b_fc1)
Está seguido de la creación de una capa una del gráfico de red neuronal: los pesos w_fc1, los sesgos b_fc1 y las unidades ocultas h_fc1. El “FC” es una convención que significa “totalmente conectado”, ya que los pesos conectan cada unidad de entrada a cada unidad oculta.
tf.truncated_normal resultados en una serie de operadores y tensores que luego asignarán números normalizados, aleatorios a todos los pesos.
Los OP Variables reciben un valor para realizar la inicialización con números aleatorios en este caso y mantener sus datos a través de múltiples carreras. También son útiles para guardar la red neuronal a un archivo, algo que querrá hacer una vez que esté entrenado.
Puedes ver dónde estaremos haciendo la multiplicación de matriz usando el Matmul Op. También insertamos una opción Agregar a lo que agregará los pesos de sesgo. El Relu OP realiza lo que llamamos una función de activación. La multiplicación de matriz y la adición son operaciones lineales. Hay un número muy limitado de cosas que una red neuronal puede aprender utilizando las operaciones lineales. La función de activación proporciona alguna linealidad. En el caso de la función de activación de RELU, establece cualquier valores que tengan menos de cero a cero, y todos los demás valores se dejan sin cambios. Lo creas o no, lo que hace eso abre un otro mundo de cosas que se pueden aprender.
W_fc2 = tf.truncated_normal ([num_hidden, num_outputs], media = 0.5, stddev = 0.707)
W_fc2 = tf.variable (w_fc2, nombre = ‘w_fc2’)
b_fc2 = tf.truncate_normal ([num_outputs], media = 0.5, stddev = 0.707)
b_fc2 = tf.variable (b_fc2, nombre = ‘b_fc2’)
y = tf.matmul (h_fc1, w_fc2) + b_fc2
Los pesos y los sesgos para la capa dos se configuran los mismos que para la capa, pero la capa de salida es diferente. Nuevamente hicimos una multiplicación de matriz, esta vez multiplicando los pesos y las unidades ocultas, y luego agregando los pesos de sesgo. Hemos dejado la función de activación para el siguiente bit de código.
Resultados = tf.sigmoid (y, nombre = ‘resultados’)
cross_entropy = tf.reduce_mean (
tf.nnn.sigmoid_cross_entropy_with_logits (logits = y, etiquetas = y_))
Sigmoide es otra función de activación, como el Relu que nos encontramos anteriormente, allí para proporcionar la no linealidad. Utilicé Sigmoid aquí en parte porque la ecuación sigmoidea resulta en valores entre 0 y 1, ideal para nuestro ejemplo de contador binario. También lo usé porque es bueno para las salidas donde más de una unidad de salida puede tener un valor de gran valor. En nuestro caso, para representar el número binario 111, todas las unidades de salida pueden tener valores grandes. Al hacer la clasificación de la imagen, queríamos algo bastante diferente, queríamos que solo una unidad de salida se dispara con un gran valor. Por ejemplo, querríamos que la unidad de salida que represente a las jirafas tenga un gran valor si una imagen contiene una jirafa. Algo como Softmax sería una buena opción para la clasificación de imágenes.
En una inspección cercana, parece que hay alguna duplicación. Parece que estamos insertando sigmoide dos veces. En realidad estamos creando dos diferentes, paralelos.utputs here. The cross_entropy tensor will be used during training of the neutral network. The results tensor will be used when we run our trained neural network later for whatever purpose it’s created, for fun in our case. I don’t know if this is the best way of doing this, but it’s the way I came up with.
train_step = tf.train.RMSPropOptimizer(0.25, momentum=0.5).minimize(cross_entropy)
The last piece we add to our graph is the training. This is the op or ops that will adjust all the weights based on training data. Remember, we’re still just creating a graph here. The actual training will happen later when we run the graph.
There are a few optimizers to chose from. I chose tf.train.RMSPropOptimizer because, like the sigmoid, it works well for cases where all output values can be large. For classifying things as when doing image classification, tf.train.GradientDescentOptimizer might be better.
Training and using The Binary Counter
Having created the graph, it’s time to do the training. once it’s trained, we can then use it.
inputvals = [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1],
[1, 1, 0], [1, 1, 1]]
targetvals = [[0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0],
[1, 1, 1], [0, 0, 0]]
First, we have some training data: inputvals and targetvals. inputvals contains the inputs, and for each one there’s a corresponding targetvals target value. For inputvals[0] we have [0, 0, 0], and the expected output is targetvals[0], which is [0, 0, 1], and so on.
if do_training == 1:
sess.run(tf.global_variables_initializer())
for i in range(10001):
if i%100 == 0:
train_error = cross_entropy.eval(feed_dict={x: inputvals, y_:targetvals})
print(“step %d, training error %g”%(i, train_error))
if train_error < 0.0005:
descanso
sess.run(train_step, feed_dict={x: inputvals, y_: targetvals})
if save_trained == 1:
print("Saving neural network to %s.*"%(save_file))
saver = tf.train.Saver()
saver.save(sess, save_file)
do_training and save_trained can be hardcoded, and changed for each use, or can be set using command line arguments.
We first go through all those Variable ops and have them initialize their tensors.
Then, for up to 10001 times we run the graph from the bottom up to the train_step tensor, the last thing we added to our graph. We pass inputvals and targetvals to train_step‘s op or ops, which we’d added using RMSPropOptimizer. This is the step that adjusts all the weights such that the given inputs will result in something close to the corresponding target outputs. If the error between target outputs and actual outputs gets small enough sooner, then we break out of the loop.
If you have thousands of input/output pairs then you could give it a subset of them at a time, the batch we spoke of earlier. but here we have only eight, and so we give all of them each time.
If we want to, we can also save the network to a file. Once it’s trained well, we don’t need to train it again.
else: # if we're not training then we must be loading from file
print("Loading neural network from %s"%(save_file))
saver = tf.train.Saver()
saver.restore(sess, save_file)
# Note: the restore both loads and initializes the variables
If we’re not training it then we instead load the trained network from a file. The file contains only the values for the tensors that have Variable ops. It doesn’t contain the structure of the graph. So even when running an already trained graph, we still need the code to create the graph. There is a way to save and load graphs from files using MetaGraphs but we’re not doing that here.
print('\nCounting starting with: 0 0 0')
res = sess.run(results, feed_dict={x: [[0, 0, 0]]})
print('%g %g %g'%(res[0][0], res[0][1], res[0][2]))