Codificación de datos de ángulo para red neuronal

20

Estoy entrenando una red neuronal (los detalles no son importantes) donde los datos objetivo son un vector de ángulos (entre 0 y 2 * pi). Estoy buscando consejos sobre cómo codificar estos datos. Esto es lo que estoy intentando actualmente (con éxito limitado):

1) Codificación 1-de-C: pongo los ángulos posibles configurados en aproximadamente 1000 ángulos discretos y luego indico un ángulo particular poniendo un 1 en el índice relevante. El problema con esto es que la red simplemente aprende a generar todos los 0 (ya que esto es casi exactamente correcto).

2) Escalado simple: escalé el rango de salida de la red ([0,1]) a [0,2 * pi]. El problema aquí es que los ángulos tienen naturalmente una topología circular (es decir, 0.0001 y 2 * pi están realmente uno al lado del otro). Con este tipo de codificación, esa información se pierde.

¡Cualquier sugerencia sera apreciada!

Ari Herman
fuente
1
No debería tener problemas con la red que genera todos los ceros, si usa una capa de salida softmax, lo que generalmente debería hacer, si está usando una salida catagorical (es decir, 1 de C).
Lyndon White
77
Una idea de codificación puramente especulativa (no lo he visto hecho o probado, pero no lo he mirado) es codificar su ángulo ( ) como un par: . Creo que sería un mapa continuo con todos los valores como y 2 \ pi cerca uno del otro. Creo que podría construir una demostración de esto y probarlo. θ ( sin ( θ ) , cos ( θ ) ) 0 2 πθθ(sin(θ),cos(θ))02π
Lyndon White
He estado pensando en esto un poco más, y creo que en realidad podría estar todo en tu función de pérdida. Quiero probar muchas cosas. Hice la demostración, pero no terminé las pruebas. Espere una respuesta detallada con respaldo experimental mañana en algún momento. (Empújame si no lo hago)
Lyndon White
Actualmente no estoy usando una capa softmax, y ese es probablemente el problema. ¡Lo implementaré hoy si tengo la oportunidad! Su idea (cos, sin) es muy interesante y me gusta especialmente que automáticamente ponga ese rango en [-1,1] (bueno si está trabajando con una función de activación de tanh). Espero ver sus resultados1
Ari Herman
Una actualización rápida: intenté implementar una capa softmax y todavía no tengo suerte. El problema, creo, es que para este problema es esencial que la "angularidad" de los datos se represente de alguna manera en la codificación. Con una codificación categórica, se pierde la topología de los datos de destino. Por lo tanto, un error de 0.5 * pi y 0.05 * pi se ve igual para la red (ve ambos como categorizaciones incorrectas).
Ari Herman

Respuestas:

18

Introducción

Encuentro esta pregunta realmente interesante, supongo que alguien ha publicado un documento sobre ella, pero es mi día libre, así que no quiero ir a buscar referencias.

Entonces podríamos considerarlo como una representación / codificación de la salida, lo que hago en esta respuesta. Sigo pensando que hay una mejor manera, donde puedes usar una función de pérdida ligeramente diferente. (Quizás la suma de las diferencias al cuadrado, utilizando el módulo de resta 2 ).π

Pero en adelante con la respuesta real.

Método

Propongo que un ángulo se represente como un par de valores, su seno y su coseno.θ

Entonces, la función de codificación es: y la función de decodificación es: Para arctan2 siendo las tangentes inversas, preservando la dirección en todos los cuadrantes)θ(sin(θ),cos(θ))
(y1,y2)arctan2(y1,y2)

En teoría, podría trabajar de manera equivalente directamente con los ángulos si su herramienta utiliza el soporte atan2como una función de capa (tomando exactamente 2 entradas y produciendo 1 salida). TensorFlow hace esto ahora y admite el descenso de gradiente en él , aunque no está destinado para este uso. Investigué el uso out = atan2(sigmoid(ylogit), sigmoid(xlogit)) con una función de pérdida min((pred - out)^2, (pred - out - 2pi)^2). Descubrí que entrenaba mucho peor que usar outs = tanh(ylogit), outc = tanh(xlogit)) con una función de pérdida 0.5((sin(pred) - outs)^2 + (cos(pred) - outc)^2. Lo cual creo que puede atribuirse a que el gradiente es discontinuo paraatan2

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, indique en qué ángulo se encuentra esa línea con el "eje x positivo"

Implementé una función para generar aleatoriamente estas imágenes, con líneas en ángulos aleatorios (NB: las versiones anteriores de esta publicación usaban pendientes aleatorias, en lugar de ángulos aleatorios. Gracias a @Ari Herman por señalarlo. Ahora está arreglado). Construí varias redes neuronales para evaluar su desempeño en la tarea. Los detalles completos de la implementación se encuentran en este cuaderno Jupyter . El código está todo en Julia , y uso la biblioteca de red neuronal Mocha .

A modo de comparación, lo presento contra los métodos alternativos de escalado a 0,1. y para poner en 500 contenedores y usar softmax de etiqueta blanda. No estoy particularmente contento con el último, y siento que necesito modificarlo. Es por eso que, a diferencia de los otros, solo lo pruebo por 1,000 iteraciones, frente a los otros dos que se ejecutaron por 1,000 y por 10,000

Configuración experimental

Las imágenes eran píxeles, con la línea en el centro y hacia el borde. No había ruido, etc. en la imagen, solo una línea "negra", sobre un fondo blanco.101×101

Para cada recorrido, se generaron 1,000 entrenamientos y 1,000 imágenes de prueba al azar.

La red de evaluación tenía una sola capa oculta de ancho 500. Se usaron neuronas sigmoideas en la capa oculta.

Fue entrenado por Stochastic Gradient Decent, con una tasa de aprendizaje fija de 0.01 y un impulso fijo de 0.9.

No se usó regularización o abandono. Tampoco hubo ningún tipo de convolución, etc. Una red simple, que espero sugiera que estos resultados se generalizarán

Es muy fácil modificar estos parámetros en el código de prueba , y animo a las personas a que lo hagan. (y busca 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, este es el valor absoluto de la diferencia entre el ángulo de salida de la red neuronal y el ángulo verdadero. Entonces, el error medio (por ejemplo) es el promedio sobre los 1,000 casos de prueba de esta diferencia, etc. No estoy seguro de que no deba volver a escalarlo haciendo un error de decir ser igual a un error de ). π7π4π4

También presento la precisión en varios niveles de granularidad. La precisión es la parte de los casos de prueba que se corrigió. Entonces accuracy_to_point01significa 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 sorprendente dado el funcionamiento de las matemáticas de coma flotante.

Si echas un vistazo al historial de esta publicación, 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

Binning con softmax es, con mucho, el peor, ya que dije que no estoy seguro de no haber estropeado algo en la implementación. Sin embargo, funciona marginalmente por encima de la tasa de conjetura. si solo estuviera adivinando, obtendríamos un error medio deπ

La codificación sin / cos funciona significativamente mejor que la codificación 0-1 escalada. La mejora es en la medida en que con 1,000 iteraciones de entrenamiento, sin / cos se está desempeñando aproximadamente 3 veces mejor en la mayoría de las métricas que la escala en 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, existe un límite superior para el mejor rendimiento posible en esta tarea, dado que el ángulo puede ser más o menos cualquier número real, pero no todos esos ángeles producen líneas diferentes con una resolución de píxeles. Entonces, dado que, por ejemplo, los ángulos 45.0 y 45.0000001 están vinculados a la misma imagen con esa resolución, ningún método obtendrá ambos perfectamente correctos.101×101

También parece probable que en una escala absoluta para ir más allá de este rendimiento, se necesite una mejor red neuronal. En lugar del muy simple descrito anteriormente en la configuración experimental.

Conclusión.

Parece que la representación sin / cos es, con mucho, la mejor de las representaciones que investigué aquí. Esto tiene sentido, ya que tiene un valor uniforme a medida que te mueves alrededor del círculo. También me gusta que lo inverso se pueda hacer con arctan2 , que es elegante.

Creo que la tarea presentada es suficiente en su capacidad para presentar un desafío razonable para la red. Aunque supongo que realmente está aprendiendo a hacer un ajuste de curva para así que tal vez sea demasiado fácil. Y quizás peor, puede estar favoreciendo la representación pareada. No creo que sea así, pero se está haciendo tarde aquí, así que podría haber perdido algo. Te invito nuevamente a que revises mi código . Sugerir mejoras o tareas alternativas.f(x)=y1y2x

Lyndon White
fuente
Esta es ciertamente la respuesta más completa que he recibido en el intercambio de pila. Como no estoy familiarizado con Julia, es difícil para mí examinar su código ... así que, en cambio, voy a tratar de replicar sus resultados usando Python. Voy a publicar mis hallazgos más tarde hoy o mañana.
Ari Herman
Si bien no me sorprendió que el binning funcionara mal, me sorprendió el grado en que el escalado (0,1) superó al método (cos, sin). Me di cuenta de que generaste tus ejemplos al elegir aleatoriamente el aumento y la ejecución de las líneas. Creo que esto generaría líneas cuyos ángulos no están distribuidos uniformemente, pero cuyas pendientes sí lo están. ¿Es posible que esta sea la razón por la cual el método (cos, sin) funcionó mucho mejor? ¿Qué pasaría si hicieras que los objetivos se bronceen (ángulo) ...?
Ari Herman
tienes razón acerca de la pendiente aleatoria frente al ángulo aleatorio. pero no creo que la orientación tan(angle)vaya tan bien, dado que el bronceado no está definido para todos los ángulos (por ejemplo, ). Lo volveré a ejecutar con ángulos generados aleatoriamente y editaré las publicaciones. π/4
Lyndon White
Debería haber un mapa cercano a uno entre julia y numpy, y entre Mocha y Caffe, si realmente desea volver a implementarlo. ¿Hay alguna parte particular del código que le resulte difícil de leer? Julia debería ser un lenguaje fácil de entender. Entonces, tal vez he hecho algo extraño.
Lyndon White
Terminé leyendo su código, y todo parece correcto. Aún así, quería escribir mi propia versión, ya que hacerlo suele ser instructivo. Mi implementación es ligeramente diferente de la suya, por lo que será interesante comparar los resultados. Los publicaré en las próximas dos horas.
Ari Herman
5

Aquí hay otra implementación de Python que compara la codificación propuesta de Lyndon White con un enfoque agrupado. El siguiente código produjo el siguiente resultado:

Training Size: 100
Training Epochs: 100
Encoding: cos_sin
Test Error: 0.017772154610047136
Encoding: binned
Test Error: 0.043398792553251526

Training Size: 100
Training Epochs: 500
Encoding: cos_sin
Test Error: 0.015376604917819397
Encoding: binned
Test Error: 0.032942592915322394

Training Size: 1000
Training Epochs: 100
Encoding: cos_sin
Test Error: 0.007544091937411164
Encoding: binned
Test Error: 0.012796594492198667

Training Size: 1000
Training Epochs: 500
Encoding: cos_sin
Test Error: 0.0038051515079569097
Encoding: binned
Test Error: 0.006180633805557207

Como puede ver, mientras que el enfoque agrupado se desempeña admirablemente en esta tarea de juguete, la codificación funciona mejor en todas las configuraciones de entrenamiento, a veces por un margen considerable. Sospecho que la tarea específica se hizo más compleja, los beneficios del uso de Lyndon blanca 's representación sería más pronunciada.( sin ( θ ) , cos ( θ ) )(sin(θ),cos(θ))(sin(θ),cos(θ))

import matplotlib.pyplot as plt
import numpy as np
import torch
import torch.nn as nn
import torch.utils.data

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")


class Net(nn.Module):
    def __init__(self, input_size, hidden_size, num_out):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_size)
        self.sigmoid = nn.Sigmoid()
        self.fc2 = nn.Linear(hidden_size, num_out)

    def forward(self, x):
        out = self.fc1(x)
        out = self.sigmoid(out)
        out = self.fc2(out)
        return out


def gen_train_image(angle, side, thickness):
    image = np.zeros((side, side))
    (x_0, y_0) = (side / 2, side / 2)
    (c, s) = (np.cos(angle), np.sin(angle))
    for y in range(side):
        for x in range(side):
            if (abs((x - x_0) * c + (y - y_0) * s) < thickness / 2) and (
                    -(x - x_0) * s + (y - y_0) * c > 0):
                image[x, y] = 1

    return image.flatten()


def gen_data(num_samples, side, num_bins, thickness):
    angles = 2 * np.pi * np.random.uniform(size=num_samples)
    X = [gen_train_image(angle, side, thickness) for angle in angles]
    X = np.stack(X)

    y = {"cos_sin": [], "binned": []}
    bin_size = 2 * np.pi / num_bins
    for angle in angles:
        idx = int(angle / bin_size)
        y["binned"].append(idx)
        y["cos_sin"].append(np.array([np.cos(angle), np.sin(angle)]))

    for enc in y:
        y[enc] = np.stack(y[enc])

    return (X, y, angles)


def get_model_stuff(train_y, input_size, hidden_size, output_sizes,
                    learning_rate, momentum):
    nets = {}
    optimizers = {}

    for enc in train_y:
        net = Net(input_size, hidden_size, output_sizes[enc])
        nets[enc] = net.to(device)
        optimizers[enc] = torch.optim.SGD(net.parameters(), lr=learning_rate,
                                          momentum=momentum)

    criterions = {"binned": nn.CrossEntropyLoss(), "cos_sin": nn.MSELoss()}
    return (nets, optimizers, criterions)


def get_train_loaders(train_X, train_y, batch_size):
    train_X_tensor = torch.Tensor(train_X)

    train_loaders = {}

    for enc in train_y:
        if enc == "binned":
            train_y_tensor = torch.tensor(train_y[enc], dtype=torch.long)
        else:
            train_y_tensor = torch.tensor(train_y[enc], dtype=torch.float)

        dataset = torch.utils.data.TensorDataset(train_X_tensor, train_y_tensor)
        train_loader = torch.utils.data.DataLoader(dataset=dataset,
                                                   batch_size=batch_size,
                                                   shuffle=True)
        train_loaders[enc] = train_loader

    return train_loaders


def show_image(image, side):
    img = plt.imshow(np.reshape(image, (side, side)), interpolation="nearest",
                     cmap="Greys")
    plt.show()


def main():
    side = 101
    input_size = side ** 2
    thickness = 5.0
    hidden_size = 500
    learning_rate = 0.01
    momentum = 0.9
    num_bins = 500
    bin_size = 2 * np.pi / num_bins
    half_bin_size = bin_size / 2
    batch_size = 50
    output_sizes = {"binned": num_bins, "cos_sin": 2}
    num_test = 1000

    (test_X, test_y, test_angles) = gen_data(num_test, side, num_bins,
                                             thickness)

    for num_train in [100, 1000]:

        (train_X, train_y, train_angles) = gen_data(num_train, side, num_bins,
                                                    thickness)
        train_loaders = get_train_loaders(train_X, train_y, batch_size)

        for epochs in [100, 500]:

            (nets, optimizers, criterions) = get_model_stuff(train_y, input_size,
                                                             hidden_size, output_sizes,
                                                             learning_rate, momentum)

            for enc in train_y:
                optimizer = optimizers[enc]
                net = nets[enc]
                criterion = criterions[enc]

                for epoch in range(epochs):
                    for (i, (images, ys)) in enumerate(train_loaders[enc]):
                        optimizer.zero_grad()

                        outputs = net(images.to(device))
                        loss = criterion(outputs, ys.to(device))
                        loss.backward()
                        optimizer.step()


            print("Training Size: {0}".format(num_train))
            print("Training Epochs: {0}".format(epochs))
            for enc in train_y:
                net = nets[enc]
                preds = net(torch.tensor(test_X, dtype=torch.float).to(device))
                if enc == "binned":
                    pred_bins = np.array(preds.argmax(dim=1).detach().cpu().numpy(),
                                         dtype=np.float)
                    pred_angles = bin_size * pred_bins + half_bin_size
                else:
                    pred_angles = torch.atan2(preds[:, 1], preds[:, 0]).detach().cpu().numpy()
                    pred_angles[pred_angles < 0] = pred_angles[pred_angles < 0] + 2 * np.pi

                print("Encoding: {0}".format(enc))
                print("Test Error: {0}".format(np.abs(pred_angles - test_angles).mean()))

            print()


if __name__ == "__main__":
    main()
airalcorn2
fuente
3

Aquí está mi versión de Python de tu experimento. Mantuve muchos de los detalles de su implementación igual, en particular uso el mismo tamaño de imagen, tamaños de capa de red, tasa de aprendizaje, impulso y métricas de éxito.

Cada red probada tiene una capa oculta (tamaño = 500) con neuronas logísticas. Las neuronas de salida son lineales o softmax como se señaló. Usé 1,000 imágenes de entrenamiento y 1,000 imágenes de prueba que se generaron de forma independiente y aleatoria (por lo que puede haber repeticiones). El entrenamiento consistió en 50 iteraciones a través del conjunto de entrenamiento.

Pude obtener una precisión bastante buena usando binning y codificación "gaussiana" (un nombre que inventé; similar a binning excepto que el vector de salida de destino tiene la forma exp (-pi * ([1,2,3, ... , 500] - idx) ** 2) donde idx es el índice correspondiente al ángulo correcto). El código está abajo; Aquí están mis resultados:

Error de prueba para codificación (cos, sin):

1,000 imágenes de entrenamiento, 1,000 imágenes de prueba, 50 iteraciones, salida lineal

  • Media: 0.0911558142071

  • Mediana: 0.0429723541743

  • Mínimo: 2.77769843793e-06

  • Máximo: 6.2608513539

  • Precisión a 0.1: 85.2%

  • Precisión a 0.01: 11.6%

  • Precisión hasta 0.001: 1.0%

Error de prueba para la codificación [-1,1]:

1,000 imágenes de entrenamiento, 1,000 imágenes de prueba, 50 iteraciones, salida lineal

  • Media: 0.234181700523

  • Mediana: 0.17460197307

  • Mínimo: 0.000473665840258

  • Máximo: 6.00637777237

  • Precisión a 0.1: 29.9%

  • Precisión a 0.01: 3.3%

  • Precisión hasta 0.001: 0.1%

Error de prueba para codificación 1 de 500:

1,000 imágenes de entrenamiento, 1,000 imágenes de prueba, 50 iteraciones, salida softmax

  • Media: 0.0298767021922

  • Mediana: 0.00388858079174

  • Mínimo: 4.08712407829e-06

  • Máximo: 6.2784479965

  • Precisión a 0.1: 99.6%

  • Precisión a 0.01: 88.9%

  • Precisión hasta 0.001: 13.5%

Error de prueba para codificación gaussiana:

1,000 imágenes de entrenamiento, 1,000 imágenes de prueba, 50 iteraciones, salida softmax

  • Media: 0.0296905377463
  • Mediana: 0.00365867335107
  • Mínimo: 4.08712407829e-06
  • Máximo: 6.2784479965
  • Precisión a 0.1: 99.6%
  • Precisión a 0.01: 90.8%
  • Precisión hasta 0.001: 14.3%

No puedo entender por qué nuestros resultados parecen estar en contradicción entre sí, pero parece que vale la pena seguir investigando.

# -*- coding: utf-8 -*-
"""
Created on Mon Jun 13 16:59:53 2016

@author: Ari
"""

from numpy import savetxt, loadtxt, round, zeros, sin, cos, arctan2, clip, pi, tanh, exp, arange, dot, outer, array, shape, zeros_like, reshape, mean, median, max, min
from numpy.random import rand, shuffle
import matplotlib.pyplot as plt

###########
# Functions
###########

# Returns a B&W image of a line represented as a binary vector of length width*height
def gen_train_image(angle, width, height, thickness):
    image = zeros((height,width))
    x_0,y_0 = width/2, height/2
    c,s = cos(angle),sin(angle)
    for y in range(height):
        for x in range(width):
            if abs((x-x_0)*c + (y-y_0)*s) < thickness/2 and -(x-x_0)*s + (y-y_0)*c > 0:
                image[x,y] = 1
    return image.flatten()

# Display training image    
def display_image(image,height, width):    
    img = plt.imshow(reshape(image,(height,width)), interpolation = 'nearest', cmap = "Greys")
    plt.show()    

# Activation function
def sigmoid(X):
    return 1.0/(1+exp(-clip(X,-50,100)))

# Returns encoded angle using specified method ("binned","scaled","cossin","gaussian")
def encode_angle(angle, method):
    if method == "binned": # 1-of-500 encoding
        X = zeros(500)
        X[int(round(250*(angle/pi + 1)))%500] = 1
    elif method == "gaussian": # Leaky binned encoding
        X = array([i for i in range(500)])
        idx = 250*(angle/pi + 1)
        X = exp(-pi*(X-idx)**2)
    elif method == "scaled": # Scaled to [-1,1] encoding
        X = array([angle/pi])
    elif method == "cossin": # Oxinabox's (cos,sin) encoding
        X = array([cos(angle),sin(angle)])
    else:
        pass
    return X

# Returns decoded angle using specified method
def decode_angle(X, method):
    if method == "binned" or method == "gaussian": # 1-of-500 or gaussian encoding
        M = max(X)
        for i in range(len(X)):
            if abs(X[i]-M) < 1e-5:
                angle = pi*i/250 - pi
                break
#        angle = pi*dot(array([i for i in range(500)]),X)/500  # Averaging
    elif method == "scaled": # Scaled to [-1,1] encoding
        angle = pi*X[0]
    elif method == "cossin": # Oxinabox's (cos,sin) encoding
        angle = arctan2(X[1],X[0])
    else:
        pass
    return angle

# Train and test neural network with specified angle encoding method
def test_encoding_method(train_images,train_angles,test_images, test_angles, method, num_iters, alpha = 0.01, alpha_bias = 0.0001, momentum = 0.9, hid_layer_size = 500):
    num_train,in_layer_size = shape(train_images)
    num_test = len(test_angles)

    if method == "binned":
        out_layer_size = 500
    elif method == "gaussian":
        out_layer_size = 500
    elif method == "scaled":
        out_layer_size = 1
    elif method == "cossin":
        out_layer_size = 2
    else:
        pass

    # Initial weights and biases
    IN_HID = rand(in_layer_size,hid_layer_size) - 0.5 # IN --> HID weights
    HID_OUT = rand(hid_layer_size,out_layer_size) - 0.5 # HID --> OUT weights
    BIAS1 = rand(hid_layer_size) - 0.5 # Bias for hidden layer
    BIAS2 = rand(out_layer_size) - 0.5 # Bias for output layer

    # Initial weight and bias updates
    IN_HID_del = zeros_like(IN_HID)
    HID_OUT_del = zeros_like(HID_OUT)
    BIAS1_del = zeros_like(BIAS1)
    BIAS2_del = zeros_like(BIAS2)

    # Train
    for j in range(num_iters):
        for i in range(num_train):
            # Get training example
            IN = train_images[i]
            TARGET = encode_angle(train_angles[i],method) 

            # Feed forward and compute error derivatives
            HID = sigmoid(dot(IN,IN_HID)+BIAS1)

            if method == "binned" or method == "gaussian": # Use softmax
                OUT = exp(clip(dot(HID,HID_OUT)+BIAS2,-100,100))
                OUT = OUT/sum(OUT)
                dACT2 = OUT - TARGET
            elif method == "cossin" or method == "scaled": # Linear
                OUT = dot(HID,HID_OUT)+BIAS2 
                dACT2 = OUT-TARGET 
            else:
                print("Invalid encoding method")

            dHID_OUT = outer(HID,dACT2)
            dACT1 = dot(dACT2,HID_OUT.T)*HID*(1-HID)
            dIN_HID = outer(IN,dACT1)
            dBIAS1 = dACT1
            dBIAS2 = dACT2

            # Update the weight updates 
            IN_HID_del = momentum*IN_HID_del + (1-momentum)*dIN_HID
            HID_OUT_del = momentum*HID_OUT_del + (1-momentum)*dHID_OUT
            BIAS1_del = momentum*BIAS1_del + (1-momentum)*dBIAS1
            BIAS2_del = momentum*BIAS2_del + (1-momentum)*dBIAS2

            # Update the weights
            HID_OUT -= alpha*dHID_OUT
            IN_HID -= alpha*dIN_HID
            BIAS1 -= alpha_bias*dBIAS1
            BIAS2 -= alpha_bias*dBIAS2

    # Test
    test_errors = zeros(num_test)
    angles = zeros(num_test)
    target_angles = zeros(num_test)
    accuracy_to_point001 = 0
    accuracy_to_point01 = 0
    accuracy_to_point1 = 0

    for i in range(num_test):

        # Get training example
        IN = test_images[i]
        target_angle = test_angles[i]

        # Feed forward
        HID = sigmoid(dot(IN,IN_HID)+BIAS1)

        if method == "binned" or method == "gaussian":
            OUT = exp(clip(dot(HID,HID_OUT)+BIAS2,-100,100))
            OUT = OUT/sum(OUT)
        elif method == "cossin" or method == "scaled":
            OUT = dot(HID,HID_OUT)+BIAS2 

        # Decode output 
        angle = decode_angle(OUT,method)

        # Compute errors
        error = abs(angle-target_angle)
        test_errors[i] = error
        angles[i] = angle

        target_angles[i] = target_angle
        if error < 0.1:
            accuracy_to_point1 += 1
        if error < 0.01: 
            accuracy_to_point01 += 1
        if error < 0.001:
            accuracy_to_point001 += 1

    # Compute and return results
    accuracy_to_point1 = 100.0*accuracy_to_point1/num_test
    accuracy_to_point01 = 100.0*accuracy_to_point01/num_test
    accuracy_to_point001 = 100.0*accuracy_to_point001/num_test

    return mean(test_errors),median(test_errors),min(test_errors),max(test_errors),accuracy_to_point1,accuracy_to_point01,accuracy_to_point001

# Dispaly results
def display_results(results,method):
    MEAN,MEDIAN,MIN,MAX,ACC1,ACC01,ACC001 = results
    if method == "binned":
        print("Test error for 1-of-500 encoding:")
    elif method == "gaussian":
        print("Test error for gaussian encoding: ")
    elif method == "scaled":
        print("Test error for [-1,1] encoding:")
    elif method == "cossin":
        print("Test error for (cos,sin) encoding:")
    else:
        pass
    print("-----------")
    print("Mean: "+str(MEAN))
    print("Median: "+str(MEDIAN))
    print("Minimum: "+str(MIN))
    print("Maximum: "+str(MAX))
    print("Accuracy to 0.1: "+str(ACC1)+"%")
    print("Accuracy to 0.01: "+str(ACC01)+"%")
    print("Accuracy to 0.001: "+str(ACC001)+"%")
    print("\n\n")


##################
# Image parameters
##################
width = 100 # Image width
height = 100 # Image heigth
thickness = 5.0 # Line thickness

#################################
# Generate training and test data
#################################
num_train = 1000
num_test = 1000
test_images = []
test_angles = []
train_images = []
train_angles = []
for i in range(num_train):
    angle = pi*(2*rand() - 1)
    train_angles.append(angle)
    image = gen_train_image(angle,width,height,thickness)
    train_images.append(image)
for i in range(num_test):
    angle = pi*(2*rand() - 1)
    test_angles.append(angle)
    image = gen_train_image(angle,width,height,thickness)
    test_images.append(image)
train_angles,train_images,test_angles,test_images = array(train_angles),array(train_images),array(test_angles),array(test_images)



###########################
# Evaluate encoding schemes
###########################
num_iters = 50

# Train with cos,sin encoding
method = "cossin"
results1 = test_encoding_method(train_images, train_angles, test_images, test_angles, method, num_iters)
display_results(results1,method)

# Train with scaled encoding
method = "scaled"
results3 = test_encoding_method(train_images, train_angles, test_images, test_angles, method, num_iters)
display_results(results3,method)

# Train with binned encoding
method = "binned"
results2 = test_encoding_method(train_images, train_angles, test_images, test_angles, method, num_iters)
display_results(results2,method)

# Train with gaussian encoding
method = "gaussian"
results4 = test_encoding_method(train_images, train_angles, test_images, test_angles, method, num_iters)
display_results(results4,method)
Ari Herman
fuente
Genial, en clave diferente. Solo estás entrenando en cada imagen una vez. Estoy entrenando en cada imagen 1,000 veces, o 10,000 veces. Las iteraciones múltiples aunque los datos de entrenamiento son normales, particularmente cuando se entrena en cantidades relativamente pequeñas de datos (y solo me tomó una tesis de pregrado no publicable para aprender esto, pero esa es otra historia). Dicho esto, debería agregar una columna de 1 iter a mi tabla. eso sería informativo
Lyndon White
Creo que el entrenamiento en imágenes similares (pero no idénticas) con objetivos similares afectaría esa red de manera similar. Si eso es cierto, debería funcionar bien simplemente aumentar el número de imágenes aleatorias en las que se entrena, en lugar de iterar muchas veces a través de un conjunto de entrenamiento más pequeño. ¿Estás diciendo que este no es el caso?
Ari Herman
Es similar, pero para esta tarea de ejemplo no tiene el problema de que eventualmente mostrará todas las imágenes posibles para que su prueba se superponga con su tren, por lo que la generalización de prueba no funcionará. Más significativamente, aunque haces 100,000 imágenes de entrenamiento, que son <1000 * 1000 imágenes de entrenamiento * iteraciones.
Lyndon White
Tienes razón, arreglaré ese problema. Hay un problema aún más significativo con mi código: estoy usando neuronas logísticas que son incapaces de producir los valores negativos requeridos por la representación (cos, sin). D'oh! Revisaré mi código y volveré a publicar lo antes posible.
Ari Herman
Es posible que (si aún no lo ha hecho) esté interesado en hacer una comprobación de Graident , que vale la pena cuando se implementan redes neuronales desde cero, ya que es muy fácil cometer un pequeño error y hacer que su red aún funcione en su mayoría. Re: Neuron: sí, tengo una capa de salida lineal, en la capa oculta sigmoidea
Lyndon White
1

Otra forma de codificar el ángulo es como un conjunto de dos valores:

y1 = max (0, theta)

y2 = max (0, -theta)

theta_out = y1 - y2

Esto tendría el problema similar a arctan2 en que el gradiente no está definido en theta = 0. No tengo tiempo para entrenar una red y compararla con las otras codificaciones, pero en este artículo la técnica parecía razonablemente exitosa.

DerekG
fuente
1
Esto parece una respuesta mezclada con otra pregunta en una publicación. Este sitio funciona de manera un poco diferente a como lo haría el foro. Aquí las respuestas deben concentrarse en responder la pregunta original. Y si tiene otra pregunta o comentario, debe publicarse como tal.
Karolis Koncevičius