Kodowanie danych kąta dla sieci neuronowej

20

Uczę sieci neuronowej (szczegóły nieważne), gdzie dane docelowe to wektor kątów (od 0 do 2 * pi). Szukam porady, jak zakodować te dane. Oto, co obecnie próbuję (z ograniczonym sukcesem):

1) Kodowanie 1-of-C: I bin ustawiam możliwe kąty na około 1000 dyskretnych kątów, a następnie wskazuję konkretny kąt, umieszczając 1 przy odpowiednim indeksie. Problem polega na tym, że sieć po prostu uczy się wyprowadzać wszystkie zera (ponieważ jest to prawie dokładnie poprawne).

2) Proste skalowanie: skalowałem zakres wyjściowy sieci ([0,1]) do [0,2 * pi]. Problem polega na tym, że kąty mają naturalnie topologię kołową (tj. 0,0001 i 2 * pi faktycznie znajdują się obok siebie). Przy tego rodzaju kodowaniu informacje te są tracone.

Wszelkie sugestie będą mile widziane!

Ari Herman
źródło
1
Nie powinieneś mieć problemu z wyświetlaniem przez sieć wszystkich zer, jeśli używasz warstwy wyjściowej softmax - co ogólnie powinieneś zrobić, jeśli używasz wyjścia katagorycznego (tj. 1-of-C).
Lyndon White
7
Ideą czysto spekulatywnego kodowania (nie widziałem, żeby to zrobiono lub przetestowałem, ale nie spojrzałem) to zakodowanie swojego kąta ( ) jako pary: . Myślę, że byłaby to ciągła mapa ze wszystkimi wartościami takimi jak i blisko siebie. Myślę, że mógłbym zbudować wersję demo tego i przetestować. θθ(grzech(θ),sałata(θ))02)π
Lyndon White,
zastanawiałem się nad tym trochę i myślę, że może to być wszystko w twojej funkcji utraty. Chcę spróbować wielu rzeczy. Zrobiłem demo, ale nie ukończyłem testów. Oczekuj jutro szczegółowej odpowiedzi z eksperymentalnym wsparciem. (Poke mnie, jeśli nie)
Lyndon White
Obecnie nie używam warstwy softmax i jest to prawdopodobnie problem. Zaimplementuję to dzisiaj, jeśli będę miał szansę! Twój pomysł (cos, sin) jest bardzo interesujący, a szczególnie podoba mi się to, że automatycznie ustawia ten zakres na [-1,1] (dobrze, jeśli pracujesz z funkcją aktywacji tanh). Z niecierpliwością czekam na twoje wyniki1
Ari Herman
Szybka aktualizacja: próbowałem zaimplementować warstwę softmax i nadal nie mam szczęścia. Wydaje mi się, że problem polega na tym, że w przypadku tego problemu istotna jest reprezentacja „kątowości” danych w kodowaniu. W przypadku kodowania kategorycznego topologia danych docelowych zostaje utracona. Stąd błąd 0,5 * pi i 0,05 * pi wyglądają tak samo dla sieci (traktuje oba jako niepoprawne kategoryzacje).
Ari Herman

Odpowiedzi:

18

Wprowadzenie

Uważam to pytanie za bardzo interesujące, zakładam, że ktoś opublikował na nim artykuł, ale to mój dzień wolny, więc nie chcę gonić za referencjami.

Możemy więc uznać to za reprezentację / kodowanie danych wyjściowych, co robię w tej odpowiedzi. Nadal myślę, że istnieje lepszy sposób, w którym można po prostu użyć nieco innej funkcji utraty. (Być może suma kwadratowych różnic przy użyciu odejmowania modulo 2 ).π

Ale z rzeczywistą odpowiedzią.

metoda

I proponują, że kąt być przedstawiony w postaci pary, jej wartości sinusa i kosinusa jej.θ

Zatem funkcja kodowania to: a funkcja dekodowania to: Dla arctan2, które są odwrotnymi stycznymi, zachowując kierunek we wszystkich ćwiartkach)θ(grzech(θ),sałata(θ))
(y1,y2))arctan2)(y1,y2))

Teoretycznie możesz równo pracować bezpośrednio z kątami, jeśli twoje narzędzie jest obsługiwane atan2jako funkcja warstwy (biorąc dokładnie 2 dane wejściowe i generując 1 wynik). TensorFlow robi to teraz i obsługuje opadanie gradientu , choć nie jest to przeznaczone do tego zastosowania. Badałem za out = atan2(sigmoid(ylogit), sigmoid(xlogit)) pomocą funkcji straty min((pred - out)^2, (pred - out - 2pi)^2). Przekonałem się, że trenował o wiele gorzej niż używanie outs = tanh(ylogit), outc = tanh(xlogit)) z funkcją utraty 0.5((sin(pred) - outs)^2 + (cos(pred) - outc)^2. Wydaje mi się, że można to przypisać nieciągłości gradientuatan2

Moje testy tutaj uruchamiają go jako funkcję przetwarzania wstępnego

Aby to ocenić, zdefiniowałem zadanie:

Biorąc pod uwagę czarno-biały obraz przedstawiający pojedynczą linię na pustym tle Wyprowadzić, pod jakim kątem ta linia jest pod „dodatnią osią x”

Zaimplementowałem funkcję losowego generowania tych obrazów, z liniami pod losowymi kątami (Uwaga: wcześniejsze wersje tego postu używały losowych nachyleń, a nie losowych kątów. Dzięki @Ari Herman za zwrócenie na to uwagi. Teraz jest naprawione). Zbudowałem kilka sieci neuronowych, aby ocenić wydajność na tym zadaniu. Pełne szczegóły implementacji znajdują się w tym notatniku Jupyter . Cały kod jest w Julii , a ja korzystam z biblioteki sieci neuronowej Mocha .

Dla porównania przedstawiam go w stosunku do alternatywnych metod skalowania do 0,1. oraz wkładanie do 500 pojemników i stosowanie softmax soft-label. Nie jestem szczególnie zadowolony z ostatniego i czuję, że muszę go ulepszyć. Dlatego, w przeciwieństwie do innych, testuję go tylko na 1000 iteracji, w porównaniu do dwóch pozostałych, które przeprowadzono na 1000 i na 10 000

Zestaw doświadczalny

Obrazy miały pikseli, a linia przesuwała się na środku i dochodziła do krawędzi. Na obrazie nie było szumu, tylko „czarna” linia na białym tle.101×101

Dla każdego szlaku losowo wygenerowano 1000 treningów i 1000 zdjęć testowych.

Sieć oceny miała jedną ukrytą warstwę o szerokości 500. W ukrytej warstwie zastosowano neurony sigmoidalne.

Został przeszkolony przez Stochastic Gradient Decent, ze stałą szybkością uczenia się 0,01 i stałym pędem 0,9.

Nie zastosowano regulacji ani rezygnacji. Nie było też żadnego rodzaju splotu itp. Prosta sieć, która, mam nadzieję, sugeruje, że wyniki te się uogólnią

Dostosowanie tych parametrów w kodzie testowym jest bardzo łatwe i zachęcam ludzi do tego. (i poszukaj błędów w teście).

Wyniki

Moje wyniki są następujące:

|                        |  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%       |

Gdy odnoszę się do błędu, jest to wartość bezwzględna różnicy między kątem wyjściowym sieci neuronowej a kątem rzeczywistym. Zatem średni błąd (na przykład) jest średnio ponad 1000 przypadków testowych tej różnicy itp nie jestem pewien, że nie powinno się go poprzez przeskalowanie błąd powiedzmy jest równa na błąd ). π7π4π4

Przedstawiam również dokładność na różnych poziomach szczegółowości. Dokładność jest częścią przypadków testowych, które uzyskały. Więc accuracy_to_point01oznacza, że został on liczony jako poprawne, jeśli wyjście było w 0,01 prawdziwego kątem. Żadne z przedstawień nie przyniosło żadnych doskonałych wyników, ale nie jest to wcale zaskakujące, biorąc pod uwagę, jak działa matematyka zmiennoprzecinkowa.

Jeśli spojrzysz na historię tego postu, zobaczysz, że wyniki mają trochę hałasu, nieco inny za każdym razem, gdy go ponownie uruchamiam. Ale ogólny porządek i skala wartości pozostają takie same; co pozwala nam wyciągnąć pewne wnioski.

Dyskusja

Binning z softmaxem działa zdecydowanie najgorzej, ponieważ powiedziałem, że nie jestem pewien, czy coś nie spieprzyłem w implementacji. Działa jednak nieznacznie powyżej wskaźnika zgadywania. Gdyby tylko zgadywał, otrzymalibyśmy średni błądπ

Kodowanie sin / cos działa znacznie lepiej niż skalowane kodowanie 0-1. Poprawa polega na tym, że przy 1000 iteracjach treningowych sin / cos radzi sobie około 3 razy lepiej na większości metryk niż skalowanie przy 10.000 iteracji.

Myślę, że częściowo wiąże się to z poprawą uogólnienia, ponieważ oba miały dość podobny średni błąd kwadratowy na zestawie treningowym, co najmniej raz po uruchomieniu 10 000 iteracji.

Z pewnością istnieje górna granica najlepszej możliwej wydajności w tym zadaniu, biorąc pod uwagę, że Kąt może być mniej więcej dowolną liczbą rzeczywistą, ale nie wszystkie takie anioły wytwarzają różne linie w rozdzielczości pikseli. Ponieważ na przykład oba kąty 45.0 i 45.0000001 są powiązane z tym samym obrazem w tej rozdzielczości, żadna metoda nigdy nie uzyska obu poprawnych poprawności.101×101

Wydaje się również prawdopodobne, że w skali absolutnej, aby wyjść poza tę wydajność, potrzebna jest lepsza sieć neuronowa. Zamiast tego bardzo prostego opisanego powyżej w konfiguracji eksperymentalnej.

Wniosek.

Wydaje się, że reprezentacja sin / cos jest zdecydowanie najlepsza z reprezentacji, które badałem tutaj. Ma to sens, ponieważ ma płynną wartość podczas poruszania się po okręgu. Podoba mi się również, że odwrotność można wykonać za pomocą arctan2 , który jest elegancki.

Uważam, że przedstawione zadanie jest wystarczające, aby móc przedstawić rozsądne wyzwanie dla sieci. Chociaż tak naprawdę myślę, że to tylko nauka dopasowywania krzywej do więc być może jest to zbyt łatwe. A może gorzej może sprzyjać sparowanej reprezentacji. Nie sądzę, że tak jest, ale robi się już późno, więc mogłem coś przeoczyć. Zapraszam ponownie do przejrzenia mojego kodu . Zaproponuj ulepszenia lub alternatywne zadania.fa(x)=y1y2)x

Lyndon White
źródło
Jest to z pewnością najdokładniejsza odpowiedź, jaką kiedykolwiek otrzymałem przy wymianie stosu. Ponieważ nie jestem zaznajomiony z Julią, trudno jest mi sprawdzić twój kod ... więc zamiast tego spróbuję powtórzyć twoje wyniki przy użyciu Pythona. Prześlę mi wyniki później lub dziś.
Ari Herman
Chociaż nie byłem zaskoczony, że binowanie działało słabo, byłem zaskoczony stopniem, w jakim skalowanie (0,1) było lepsze niż metoda (cos, sin). Zauważyłem, że wygenerowałeś swoje przykłady, losowo wybierając wzrost i przebieg linii. Myślę, że wygenerowałoby to linie, których kąty nie są równomiernie rozmieszczone, ale których nachylenia są. Czy to możliwe, że właśnie dlatego metoda (cos, sin) działała o wiele lepiej? Co by się stało, gdybyś opalił cele (kąt) ...?
Ari Herman
tan(angle)π/4
Pomiędzy Julią i Numpy oraz między Mocha i Caffe powinna znajdować się mapa jeden do jednego, jeśli naprawdę chcesz ją zaimplementować. Czy jest jakaś część kodu, którą trudno odczytać? Julia powinna być łatwym do zrozumienia językiem. Więc może zrobiłem coś dziwnego.
Lyndon White
W końcu przeczytałem twój kod i wszystko wydaje się prawidłowe. Mimo to chciałem napisać własną wersję, ponieważ jest to zwykle pouczające. Moja implementacja różni się nieco od twojej, więc porównanie wyników będzie interesujące. Zamieszczę je w ciągu najbliższych kilku godzin.
Ari Herman
5

Oto kolejna implementacja Pythona porównująca proponowane kodowanie Lyndona White'a z podejściem binowanym. Poniższy kod wygenerował następujące dane wyjściowe:

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

(grzech(θ),sałata(θ))(grzech(θ),sałata(θ))

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
źródło
3

Oto moja wersja eksperymentu w języku Python. Zachowałem wiele szczegółów twojej implementacji bez zmian, w szczególności używam tego samego rozmiaru obrazu, rozmiarów warstwy sieci, szybkości uczenia się, tempa i wskaźników sukcesu.

Każda badana sieć ma jedną ukrytą warstwę (rozmiar = 500) z neuronami logistycznymi. Neurony wyjściowe są liniowe lub softmax, jak wspomniano. Użyłem 1000 obrazów treningowych i 1000 obrazów testowych, które zostały niezależnie, losowo wygenerowane (więc mogą być powtórzenia). Trening składał się z 50 iteracji w zestawie treningowym.

Byłem w stanie uzyskać całkiem dobrą dokładność za pomocą binowania i kodowania „gaussowskiego” (nazwa, którą wymyśliłem; podobny do binowania, z tym wyjątkiem, że docelowy wektor wyjściowy ma postać exp (-pi * ([1,2,3, ... , 500] - idx) ** 2) gdzie idx to indeks odpowiadający prawidłowemu kątowi). Kod znajduje się poniżej; oto moje wyniki:

Błąd testu dla kodowania (cos, sin):

1000 obrazów szkoleniowych, 1000 obrazów testowych, 50 iteracji, wyjście liniowe

  • Średnia: 0,0911558142071

  • Mediana: 0,0429723541743

  • Minimum: 2,77769843793e-06

  • Maksymalnie: 6,2608513539

  • Dokładność do 0,1: 85,2%

  • Dokładność do 0,01: 11,6%

  • Dokładność do 0,001: 1,0%

Błąd testu dla kodowania [-1,1]:

1000 obrazów szkoleniowych, 1000 obrazów testowych, 50 iteracji, wyjście liniowe

  • Średnia: 0,234181700523

  • Mediana: 0,17460197307

  • Minimum: 0,000473665840258

  • Maksymalnie: 6,00637777237

  • Dokładność do 0,1: 29,9%

  • Dokładność do 0,01: 3,3%

  • Dokładność do 0,001: 0,1%

Błąd testu dla kodowania 1 na 500:

1000 obrazów treningowych, 1000 obrazów testowych, 50 iteracji, wyjście softmax

  • Średnia: 0,0298767021922

  • Mediana: 0,00388858079174

  • Minimum: 4.08712407829e-06

  • Maksymalnie: 6,2784479965

  • Dokładność do 0,1: 99,6%

  • Dokładność do 0,01: 88,9%

  • Dokładność do 0,001: 13,5%

Błąd testu dla kodowania gaussowskiego:

1000 obrazów treningowych, 1000 obrazów testowych, 50 iteracji, wyjście softmax

  • Średnia: 0,0296905377463
  • Mediana: 0,00365867335107
  • Minimum: 4.08712407829e-06
  • Maksymalnie: 6,2784479965
  • Dokładność do 0,1: 99,6%
  • Dokładność do 0,01: 90,8%
  • Dokładność do 0,001: 14,3%

Nie mogę zrozumieć, dlaczego nasze wyniki wydają się być ze sobą sprzeczne, ale wydaje się, że warto to zbadać.

# -*- 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
źródło
Fajne, z innym klawiszem. Trenujesz na każdym obrazie tylko raz. Trenuję każdy obraz 1000 razy lub 10000 razy. Wielokrotne iteracje choć dane treningowe są normalne, szczególnie podczas treningu na stosunkowo niewielkich ilościach danych (i zajęło mi to tylko jedną niepublikowaną pracę licencjacką, aby się tego nauczyć, ale to już inna historia). Powiedziawszy to, powinienem dodać 1-iterową kolumnę do mojej tabeli. to byłoby pouczające
Lyndon White
Myślę, że szkolenie na podobnych (ale nie identycznych) obrazach z podobnymi celami wpłynęłoby podobnie na tę sieć. Jeśli to prawda, powinno działać dobrze, aby po prostu zwiększyć liczbę trenowanych losowych obrazów, a raczej wielokrotnie powtarzać przez mniejszy zestaw treningowy. Mówisz, że tak nie jest?
Ari Herman
Jest podobnie, ale w tym przykładowym zadaniu nie ma problemu, że ostatecznie pokażesz wszystkie możliwe obrazy, aby twój test zachodził na twój pociąg, więc testowanie uogólnienia nie zadziała. Co ważniejsze, wykonujesz 100 000 obrazów treningowych, czyli <1000 * 1000 obrazów treningowych * Iteracje.
Lyndon White
Masz rację, naprawię ten problem. Istnieje jeszcze poważniejszy problem z moim kodem: używam neuronów logistycznych, które nie są w stanie wytworzyć ujemnych wartości wymaganych przez reprezentację (cos, sin). Nie! Jak najszybciej poprawię kod i opublikuję go ponownie.
Ari Herman
Możesz (jeśli jeszcze tego nie zrobiłeś) być zainteresowany wykonaniem Graident Check , co jest przydatne , gdy implementujesz sieci neuronowe od zera, ponieważ bardzo łatwo jest popełnić niewielki błąd, a twoja sieć nadal działa. Re: Neuron: tak, mam liniową warstwę wyjściową na sigmoidalną ukrytą warstwę
Lyndon White
1

Innym sposobem zakodowania kąta jest zestaw dwóch wartości:

y1 = max (0, theta)

y2 = max (0, -teta)

theta_out = y1 - y2

Miałoby to podobny problem do arctan2, ponieważ gradient jest niezdefiniowany przy theta = 0. Nie mam czasu na szkolenie sieci i porównywanie z innymi kodowaniami, ale w tym artykule technika wydawała się dość skuteczna.

DerekG
źródło
1
To wydaje się być odpowiedzią pomieszaną z innym pytaniem w jednym poście. Ta strona działa nieco inaczej niż forum. Tutaj odpowiedzi powinny koncentrować się na odpowiedzi na pierwotne pytanie. A jeśli masz inne pytanie lub komentarz - powinien zostać opublikowany jako taki.
Karolis Koncevičius