Introducción
Esta pregunta me parece realmente interesante, supongo que alguien habrá sacado un artículo al respecto, pero es mi día libre, así que no quiero ir persiguiendo referencias.
Así que podríamos considerarlo como una representación/codificación de la salida, lo que hago en esta respuesta. Sigo pensando que hay una forma mejor, en la que simplemente se puede utilizar una función de pérdida ligeramente diferente. (Tal vez la suma de las diferencias al cuadrado, utilizando la resta módulo 2 $\pi$ ).
Pero adelante con la respuesta real.
Método
Propongo que un ángulo $\theta$ representarse como un par de valores, su seno y su coseno.
Así que la función de codificación es: $\qquad\qquad\quad\theta \mapsto (\sin(\theta), \cos(\theta))$
y la función de decodificación es: $\qquad(y_1,y_2) \mapsto \arctan\!2(y_1,y_2)$
Para arctan2 siendo las tangentes inversas, preservando la dirección en todos los cuadrantes)
En teoría, se podría trabajar de forma equivalente directamente con los ángulos si el uso de la herramienta admite <code>atan2</code> como una función de capa (que toma exactamente 2 entradas y produce 1 salida). <a href="https://github.com/tensorflow/tensorflow/issues/6095" rel="noreferrer">TensorFlow hace esto ahora, y soporta el descenso de gradiente en él </a>aunque no está pensado para este uso. He investigado usando <code>out = atan2(sigmoid(ylogit), sigmoid(xlogit))</code> con una función de pérdida <code>min((pred - out)^2, (pred - out - 2pi)^2)</code> . Encontré que entrenaba mucho peor que usando <code>outs = tanh(ylogit), outc = tanh(xlogit))</code> con una función de pérdida <code>0.5((sin(pred) - outs)^2 + (cos(pred) - outc)^2</code> . Lo que creo que se puede atribuir a que el gradiente es discontinuo para <code>atan2</code>
Mi prueba aquí lo ejecuta como una función de preprocesamiento
Para evaluar esto, definí una tarea:
Dada una imagen en blanco y negro que representa una sola línea sobre un fondo en blanco Obtenga el ángulo que forma esa línea con respecto al "eje x positivo"
He implementado una función que genera aleatoriamente estas imágenes, con líneas en ángulos aleatorios (NB: las versiones anteriores de este post utilizaban pendientes aleatorias, en lugar de ángulos aleatorios. Gracias a @Ari Herman por señalarlo. Ahora está corregido). He construido varias redes neuronales para evaluar su rendimiento en la tarea. Los detalles completos de la implementación están en este cuaderno Jupyter . El código está todo en Julia y hago uso de la Moca biblioteca de redes neuronales.
A modo de comparación, lo presento frente a los métodos alternativos de escalado a 0,1. y de poner en 500 bins y usar soft-label softmax. No estoy especialmente contento con el último, y creo que tengo que ajustarlo. Por eso, a diferencia de los otros, sólo lo probé para 1.000 iteraciones, frente a los otros dos que se ejecutaron para 1.000 y para 10.000
Montaje experimental
Las imágenes fueron $101\times101$ píxeles, con la línea que comienza en el centro y va hacia el borde. No había ruido, etc. en la imagen, sólo una línea "negra" sobre un fondo blanco.
Para cada pista se generaron aleatoriamente 1.000 imágenes de entrenamiento y 1.000 de prueba.
La red de evaluación tenía una sola capa oculta de una anchura de 500. En la capa oculta se utilizaron neuronas sigmoides.
Se entrenó mediante el Decente Gradiente Estocástico, con una tasa de aprendizaje fija de 0,01, y un impulso fijo de 0,9.
No se utilizó ninguna regularización, ni abandono. Tampoco ningún tipo de convolución, etc. Una red simple, que espero que sugiera que estos resultados se generalicen
Es muy fácil ajustar estos parámetros en el código de prueba y animo a la gente a que lo haga. (y a buscar errores en la prueba).
Resultados
Mis resultados son los siguientes:
| | 500 bins | scaled to 0-1 | Sin/Cos | scaled to 0-1 | Sin/Cos |
| | 1,000 Iter | 1,000 Iter | 1,000 iter | 10,000 Iter | 10,000 iter |
|------------------------|--------------|----------------|--------------|----------------|--------------|
| mean_error | 0.4711263342 | 0.2225284486 | 2.099914718 | 0.1085846429 | 2.1036656318 |
| std(errors) | 1.1881991421 | 0.4878383767 | 1.485967909 | 0.2807570442 | 1.4891605068 |
| minimum(errors) | 1.83E-006 | 1.82E-005 | 9.66E-007 | 1.92E-006 | 5.82E-006 |
| median(errors) | 0.0512168533 | 0.1291033982 | 1.8440767072 | 0.0562908143 | 1.8491085947 |
| maximum(errors) | 6.0749693965 | 4.9283551248 | 6.2593307366 | 3.735884823 | 6.2704853962 |
| accurancy | 0.00% | 0.00% | 0.00% | 0.00% | 0.00% |
| accurancy_to_point001 | 2.10% | 0.30% | 3.70% | 0.80% | 12.80% |
| accurancy_to_point01 | 21.90% | 4.20% | 37.10% | 8.20% | 74.60% |
| accurancy_to_point1 | 59.60% | 35.90% | 98.90% | 72.50% | 99.90% |
Cuando me refiero al error, se trata del valor absoluto de la diferencia entre el ángulo emitido por la red neuronal y el ángulo verdadero. Así que el error medio (por ejemplo) es la media de los 1.000 casos de prueba de esta diferencia, etc. No estoy seguro de que no deba reescalar haciendo un error de digamos $\frac{7\pi}{4}$ sea igual a un error de $\frac{\pi}{4}$ ).
También presento la precisión en varios niveles de granularidad. La precisión es la porción de casos de prueba que se corrigió. Así que accuracy_to_point01
significa que se contó como correcto si la salida estaba dentro de 0,01 del ángulo verdadero. Ninguna de las representaciones obtuvo resultados perfectos, pero eso no es en absoluto sorprendente dado el funcionamiento de las matemáticas en coma flotante.
Si echas un vistazo al historial de este post, verás que los resultados tienen un poco de ruido, ligeramente diferente cada vez que lo vuelvo a ejecutar. Pero el orden general y la escala de valores siguen siendo los mismos, lo que nos permite sacar algunas conclusiones.
Discusión
El binning con softmax es el que peor funciona con diferencia, como he dicho no estoy seguro de no haber metido la pata en la implementación. Sin embargo, se desempeña marginalmente por encima de la tasa de conjetura. si fuera sólo conjetura estaríamos obteniendo un error medio de $\pi$
La codificación sin/cos se comporta significativamente mejor que la codificación escalada 0-1. La mejora es tal que a 1.000 iteraciones de entrenamiento, la codificación sin/cos tiene un rendimiento 3 veces mayor en la mayoría de las métricas que el escalado a 10.000 iteraciones.
Creo que, en parte, esto está relacionado con la mejora de la generalización, ya que ambos obtuvieron un error cuadrático medio bastante similar en el conjunto de entrenamiento, al menos una vez que se ejecutaron 10.000 iteraciones.
Ciertamente hay un límite superior en el mejor rendimiento posible en esta tarea, dado que el Ángulo podría ser más o menos cualquier número real, pero no todos esos ángulos producen líneas diferentes en la resolución de $101\times101$ píxeles. Así, por ejemplo, los ángulos 45,0 y 45,0000001 están ligados a la misma imagen en esa resolución, ningún método conseguirá que ambos sean perfectamente correctos.
También parece probable que, a escala absoluta, para superar este rendimiento se necesite una red neuronal mejor. En lugar de la muy simple esbozada anteriormente en la configuración experimental.
Conclusión.
Parece que la representación sin/cos es, con mucho, la mejor, de las representaciones que he investigado aquí. Esto tiene sentido, ya que tiene un valor suave a medida que se mueve alrededor del círculo. También me gusta que la inversa se puede hacer con arctan2 que es elegante.
Creo que la tarea presentada es suficiente en su capacidad de presentar un desafío razonable para la red. Aunque supongo que en realidad se trata de aprender a hacer un ajuste de curvas a $f(x)=\frac{y1}{y2} x$ así que tal vez sea demasiado fácil. Y lo que es peor, quizá favorezca la representación por parejas. No creo que sea así, pero se está haciendo tarde aquí, así que puede que se me haya escapado algo Le invito de nuevo a revisar mi código . Sugerir mejoras o tareas alternativas.