Siec neuronowa, usuwanie polaczen

0

Witam
Napisałem sobie sieć neuronową, i teraz stoję przed problemem dodania do niej kolejnej funkcjonalności. Nie wiem które z rozwiązań było by lepsze. Chodzi o to aby można były usuwać połączenia miedzy warstwami oraz wybrane neurony.

Obliczanie wyjść oraz błędów polega na mnożeniu wektor razy wektor.
Mam dwa pomysły żeby to rozwiązać:

  1. Dodać do mnożenia kolejny wektor który będzie zawierał 0 lub 1, 0 by usuwało połączenie 1 pozostawało by dalej aktywne.
  2. Zrobić tablice dla każdego neuron zawierającą które połączenie nie istnieje.

Tak wygląda pętla obliczająca wyjścia

private void ComputeOutputs(double[] inputs)
        {
            int biasIndex;
            for (int k = 0; k < network.Length; k++)
            {
                for (int i = 0; i < network[k].Length; i++)
                {
                    biasIndex = network[k][i].Length - 1;
                    activationResults[k][i] = 0.0;
                    for (int j = 0; j < network[k][i].Length; j++)
                    {
                        if (j == biasIndex)
                        {
                            activationResults[k][i] += network[k][i][j] * bias;
                        }
                        else
                        {
                            if (k == 0)
                            {
                                activationResults[k][i] += network[k][i][j] * inputs[j];
                            }
                            else
                            {
                                activationResults[k][i] += network[k][i][j] * activationResults[k - 1][j];
                            }
                        }
                    }
                    activationResults[k][i] = network[k][i].Activate(activationResults[k][i]);   
                }
            }
        }

Klasa neuron

class Neuron
    {
        private IActivatable activator;
        private double[] weights;

        public Neuron(IActivatable activator, int weightCount)
        {
            this.activator = activator;
            weights = new double[weightCount];
        }

        public double Activate(double net)
        {
            return activator.Activate(net);
        }

        public double ComputeDerivative(double value)
        {
            return activator.ComputeDerivative(value);
        }

        public double this[int i]
        {
            get { return weights[i]; }
            set { weights[i] = value; }
        }

        public int Length
        {
            get { return weights.Length; }
        }
    }
 

Tutaj modyfikacja o której pisze w punkcje 1

 
class Neuron
    {
        private IActivatable activator;
        private double[] weights;
        private double[] connectionExists; //tutaj modyfikacja

        public Neuron(IActivatable activator, int weightCount)
        {
            this.activator = activator;
            weights = new double[weightCount];
        }

        public double Activate(double net)
        {
            return activator.Activate(net);
        }

        public double ComputeDerivative(double value)
        {
            return activator.ComputeDerivative(value);
        }

        public double this[int i]
        {
            get { return weights[i] * connectionExists[i]; } // tutaj modyfikacja
            set { weights[i] = value; }
        }

        public int Length
        {
            get { return weights.Length; }
        }
    }
0

A nie możesz po prostu ustawić wagi przy neuronie, który chcesz "usunąć" na 0? - równoznaczne z usunięciem ;-)
Gdybyś chciał później powrócić do skasowanej wagi to zawsze możesz ją gdzieś wcześniej zapisać.

0

Dzięki, to jest to czego potrzebowałem.
Jednak nie jest to dobre rozwiązanie, ponieważ w pętli korektującej wagi, dostaną one nowa wartość rożną od zera, i w tym momencie połączenie znowu się aktywuje. Czy masz może jakiś inny pomysł?

public void Train(double[] inputs, double[] targets)
        {
            ComputeOutputs(inputs);
            ComputeErrors(inputs, targets);
            int biasIndex;
            for (int k = 0; k < network.Count; k++)
            {
                for (int i = 0; i < network[k].Count; i++)
                {
                    biasIndex = network[k][i].Count - 1;
                    for (int j = 0; j < network[k][i].Count; j++)
                    {
                        if (j == biasIndex)
                        {
                            network[k][i][j] += 2.0 * learningCoefficient * errors[k][i] * bias;
                        }
                        else
                        {
                            if (k == 0)
                            {
                                network[k][i][j] += 2.0 * learningCoefficient * errors[k][i] * inputs[j];
                            }
                            else
                            {
                                network[k][i][j] += 2.0 * learningCoefficient * errors[k][i] *
                                    activationResults[k - 1][j];
                            }
                        }
                    }               
                }
            }
        } 
0

Myślałem, że chcesz usuwać połączenia już po nauczeniu sieci, w celu poprawienia generalizacji.
Natomiast jeśli chcesz robić to w trakcie nauki, to możesz dać dodatkową zmienną np. w klasie neuron typu bool i dla neuronów, które mają podlegać uczeniu wartość tej zmiennej będzie true a w innym wypadku false. Natomiast w trakcie nauki danego neuronu sprawdzać jedynie czy jest true czy false... -> jest to chyba rozwiązanie nieco bardziej optymalne od mnożenia każdej wagi przez 0/1.

Drugie rozwiązanie to ponowne wyzerowanie wagi -> chyba łatwiej jest zmienić jedną wagę przy wybranych neuronach na 0.0 niż mnożyć wszystkie wagi przez dodatkową liczbę lub sprawdzać warunek (j.w.).

Możesz również wyzerować pochodną - wówczas nie zmieni ona wartości wyzerowanej wagi.
A jeśli znasz numer neuronu, który chcesz odłączyć to możesz go po prostu pominąć w trakcie nauki i/lub obliczania wyników SSN.

By the way. Kodu nie analizowałem, ale na pierwszy rzut oka jest jakiś taki... dziwny ;-).
A tak w ogóle to dlaczego mnożysz wyniki przez 2.0?
Po drugie - czy nie da się tego zrobić bez tych if-ów?

0

Ogólnie chce napisać uniwersalną sieć, to od osoby korzystającej będzie zależało kiedy i co chce usunąć (neuron czy połączenie).

Sama odłączenie neuronu chyba nie jest wystarczające, wszędzie czytałem o odłączaniu konkretnych połączeń.

Tutaj nie jestem pewien, teoretycznie powinno być tak jak mówisz ale dodatkowo dochodzi tutaj parę sprawdzeń i tak dla każdej wagi np.

 
List<int> disableConnection;
for(int i = 0; i < disableConnection.Count; i++)
{
    if(disableConnection[i] == weightIndex)
    {

    }
}

A w przypadku mnożenie wykonujemy chyba najszybszą operacje.

Mnożę przez 2.0 bo taki dostałem wzór na uczelni, ale będę musiał jeszcze poszukać czy on rzeczywiście jest konieczny.

Co do ifów, to trochę nad tym siedziałem i chyba to jest optymalne rozwiązanie, ale kiedy będę próbował to wszystko rozbić na wątki to może uda mi coś lepszego uzyskać.

0
Bumcykowy napisał(a):

Ogólnie chce napisać uniwersalną sieć, to od osoby korzystającej będzie zależało kiedy i co chce usunąć (neuron czy połączenie).
Sama odłączenie neuronu chyba nie jest wystarczające, wszędzie czytałem o odłączaniu konkretnych połączeń.

Nie bardzo rozumiem. Jedno jest tożsame drugiemu. Jak odłączysz połączenie (wyzerujesz je) to tak jakby tego neuronu w ogóle nie było - nie wpływa on w żaden sposób na wynik SSN.

Bumcykowy napisał(a):

Tutaj nie jestem pewien, teoretycznie powinno być tak jak mówisz ale dodatkowo dochodzi tutaj parę sprawdzeń i tak dla każdej wagi np.

 
List<int> disableConnection;
for(int i = 0; i < disableConnection.Count; i++)
{
    if(disableConnection[i] == weightIndex)
    {

    }
}

Sorry, ale nie bardzo rozumiem w czym rzecz.

Bumcykowy napisał(a):

Mnożę przez 2.0 bo taki dostałem wzór na uczelni, ale będę musiał jeszcze poszukać czy on rzeczywiście jest konieczny.

Te 2.0 na pewno nie jest konieczne, bo jest tu zależność linowa. Zwróć uwagę, że wszędzie mnożysz wynik przez 2.0 a później jeszcze przez współczynnik uczenia. Równie dobrze możesz zastosować 2 razy większy współczynnik uczenia i wyjdzie na to samo.
Zresztą dlatego wzór na błąd często podaje się jako E = 0.5*(y-d)2 zamiast E = (y-d)2 -> by wywalić tą dwójkę ;-).

Bumcykowy napisał(a):

Co do ifów, to trochę nad tym siedziałem i chyba to jest optymalne rozwiązanie, ale kiedy będę próbował to wszystko rozbić na wątki to może uda mi coś lepszego uzyskać.

Od biedy może zostać, ale moim zdaniem przynajmniej ten if dot. biasu można by nieco ładniej rozwiązać ;-). W końcu bias to zwykła zmienna...
Co do tego drugiego ifa to też inaczej bym to rozwiązał, ale to tylko czepianie się szczegółów ;p.

0

No własnie tak nie jest, wyzerowanie jednego wejścia ma tylko delikatny wpływ na wyjście neuronu ponieważ używam tutaj +=
activationResults[k][i] += network[k][i][j] * inputs[j];

Chodzi mi o to ze w neuronie będziemy musieli przechowywać tablice która zawiera indeksy tych wejść które maja być nie aktywne, i że przeszukiwanie takiej tablicy dla każdego wejścia będzie dłużej trwało aniżeli mnożenie przez 1 lub 0.

Usunąłem 2.0 i siec działa bez zarzutu, czyli rzeczywiscie zbędne to było.

Nie obraził bym się jak byś podzielił się tym rozwiązaniem :)
Bias wydaje się prosto rozwiązać, poprzez dorzucenie go na koniec inputs oraz activationResults(dla każdej warstwy).

0
Bumcykowy napisał(a):

No własnie tak nie jest, wyzerowanie jednego wejścia ma tylko delikatny wpływ na wyjście neuronu ponieważ używam tutaj +=
activationResults[k][i] += network[k][i][j] * inputs[j];

Ale ja nie pisałem, żeby zerować wagi w neuronie tylko wagi stojące przy neuronie (czyli w następnej warstwie).
Oczywiście możesz wyzerować wszystkie wagi w neuronie i efekt będzie ten sam, ale to trochę więcej roboty ;].

Bumcykowy napisał(a):

Chodzi mi o to ze w neuronie będziemy musieli przechowywać tablice która zawiera indeksy tych wejść które maja być nie aktywne, i że przeszukiwanie takiej tablicy dla każdego wejścia będzie dłużej trwało aniżeli mnożenie przez 1 lub 0.

To chyba lepiej zrobić tak, jak pisałem wcześniej. Zrób sobie w clasie neuron zmienną bool let_learn; a następnie w trakcie nauki sprawdzaj warunek if(let_learn){//licz pochodną} - wydaje mi się, że te rozwiązanie będzie łatwiejsze do ogarnięcia, ale sam zdecyduj.
Sprawdzenie warunku trwa chyba nieco krócej niż mnożenie. Poza tym zwróć uwagę, że mnożyć będziesz musiał każdą wagę a warunek możesz sprawdzać tylko dla całego neuronu (czyli jednorazowo dla wszystkich wag, które się w nim znajdują).

Bumcykowy napisał(a):

Nie obraził bym się jak byś podzielił się tym rozwiązaniem :)
Bias wydaje się prosto rozwiązać, poprzez dorzucenie go na koniec inputs oraz activationResults(dla każdej warstwy).

No właśnie dokładnie o tym mówię ;-). Wystarczy, że wartość biasu (1.0 lub 0.0 - bo można zrobić sieć bez biasu) wrzucisz na sam koniec tablicy ze zmiennymi. Zmienne z poprzedniej warstwy (poza warstwą wejściową) będą ulegać zmianie pod wpływem uczenia, natomiast bias zawsze będzie równy tyle samo.

Jeśli chodzi o drugiego if-a to sprawa jest troszkę trudniejsza i to może lepiej u siebie zostaw. Ja programuję w C++ i ten problem rozwiązuję z użyciem wskaźników (mam nieco inaczej to zorganizowane), natomiast w C# nie wiem czy będzie to takie proste.

0

Tutaj nie za bardzo rozumiem, czy chodzi ci o to aby dodać możliwość całkowitego odłączenia neuronu, zamiast to robić na piechotę wyłączając wszystkie wejścia?

No tak ale w ten sposób mogę odłączać tylko całego neuronu anie kilka jego wejść. Ja chce uzyskać możliwość taką aby można było wyłączyć wybrane wejścia neuronu, czyli jeżeli w połączeniu każdy z każdym ma ich 3 to ja sobie wyłączam 2 i pozostaje 1, a resztę neuronów w warstwie pozostawiam bez zmian.
Mniej więcej coś takiego:
user image

Na szczęście w C# tez są wskaźniki :) wiec możesz choć trochę mnie nasunąć na eliminacje tego kolejnego ifa.

1

Ups, sorry, ale chyba trochę namieszałem ;-). Źle to przeanalizowałem w głowie :P.
Rzeczywiście można to rozpatrywać jako odłączenie pojedynczego połączenia i całego neuronu z tym, że to drugie jest równoznaczne z odłączeniem (wyzerowaniem) wszystkich wag w danym neuronie lub wszystkich wag w następnej warstwie, które dot. wyjścia tego neuronu lub wyzerowaniem wyjścia (a nie wagi) tego neuronu ;-).

Jeśli chodzi o wersję bez if-a to obawiam się, że mimo wszystko może być ciężko zrobić to w C# tak ładnie jak w C++ ;-) ale sam oceń ;P.
Strukturalnie (bo obiektowo jest trochę bardziej namieszane i trudniej zobrazować o co kaman) można to zrobić np. tak (z komentarzami):

double** inputs; /*tablica tablic o wymiarach: num_series i liczby wejść do sieci */
double** outputs;	/*tablica tablic o wymiarach: liczby warstw i liczby wejść w każdej warstwie, do której zapisuje się wyjścia z każdej warstwy -> outputs[0][i] to warstwa wejściowa*/
//...
for(s=0; s<num_series; s++)
	{
	outputs[0] = inputs[s]; /*s-seria - na początku zapisujemy do zmiennej outputs wejścia do sieci - takie przypisanie 
							robimy dla każdej s-serii z danych - ustawiamy tylko wskaźnik na odpowiedniej tablicy z serią danych.
Takie przypisanie jest zamiast tego if-a, który Ty wrzuciłeś w pętli i który się wykonuje za każdym razem*/
			
	for(l=1; l<Layers; l++)	/*dla każdej warstwy ukrytej + warstwy wyjściowej (tj. tam, gdzie są neurony)*/
		{
		for(n=0; n<Neurons[l]; n++) /*dla każdego n-neurona w l-warstwie (liczba neuronów w l-warstwie więc tutaj bez biasu!)*/
			{
			sum_inputs = 0.0;
			for(i=0; i<Neurons[l-1]+1; i++) /*dla każdego i-inputa dla n-neuronu w l-warstwie (czyli output z l-1 warstwy + bias)*/
				sum_inputs += weights[l-1][n][i]*outputs[l-1][i];	/*inputs[l] = outputs[l-1]; wagi dot. warstwy poprzedniej (są przed daną warstwą)*/
						
			outputs[l][n] = fun_act(sum_inputs); /*zapisujemy nowe dane wejściowe dla kolejnej warstwy ;-)*/
			}
		}
//... pozostałe obliczenia
	}

i analogicznie dla kolejnych działań - w tym obliczanie delty etc.

0

Dzięki, postaram się to zaimplementować.

A co do szybkości if i mnożenia to w trybie release mnożenie okazało się delikatnie szybsze.

1 użytkowników online, w tym zalogowanych: 0, gości: 1