Bc. Vojtěch Hordějčuk

„Čas je způsob, jakým příroda zajišťuje, aby se vše neodehrálo najednou.” - J. A. Wheeler

Domů » Wiki » Umělá inteligence » Neuronové sítě

Neuronové sítě

Lidský mozek se skládá z miliard neuronů – buněk, které zpracovávají informace. Každá tato buňka funguje jako jednoduchý procesor a jen masivní interakce mezi nimi dává mozku všechny jeho úžasné schopnosti. Umělá neuronová síť je distribuovaný výpočetní systém sestávající z dílčích podsystémů (tzv. neuronů), který je inspirován neurofyziologickými poznatky o struktuře a činnosti neuronů a nervových systémů živých organizmů, a který je ve větší či menší míře realizuje.

Historie

Prvním matematickým modelem neuronu byl perceptron (1958), navržený F. Rosenblattem (1928–1969). Tento jednoduchý prvek pracoval pouze s binárními hodnotami a jejich sítě byly schopny řešit jednoduché logické funkce, jako je AND nebo OR. O jedenáct let později představili M. Minski a S. Papert vícevrstvý perceptron (1969). V roce 1982 vzniklo hned několik nových modelů najednou. J. J. Hopfield sestavil tzv. Hopfieldův model a T. Kohonen svou samoorganizující se mapu. V té se neurony samy organizují a simulují tak lépe vývoj mozku. Také se poprvé objevuje zpětná vazba. Dnes velmi rozšířenou neuronovou síť se zpětnou propagací chyby představili v roce 1986 badatelé G. E. Hinton, E. Rumelhart a R. J. Williams.

Neuron

Neuron je základní mozkovou buňkou. Bez zbytečného zabíhání do biologických detailů jej lze rozložit na dendrity (vstupy), tělo a axon (výstup). Jeho model se analogicky skládá z několika vstupů (X), které mají přiřazenou číselnou váhu (w). Ta udává „důležitost“ či „sílu“ daného vstupu. Vstupy jsou v modelu složeny (nejčastěji prostým součtem vážených hodnot vstupů) a výsledek je jako argument předán nelineární prahové funkci. Proces učení zpravidla spočívá ve vhodném nastavení všech vah.

schéma biologického neuronu

Matematický model

Nejjednodušší model neuronu sečte vstupy (X) vážené odpovídajícími vahami (w) a výsledek vloží do prahové funkce (f). Jako prahové funkce se například používají různé parametrizovatelné sigmoidy a funkce signum. Pro funkci neuronové sítě je významná i platnost Kolmogorovovy věty.

matematický model neuronu (perceptron)

y = f(\sum_{i=1}^n w_i \cdot x_i )
Prahová funkce

Prahová funkce by měla být nelineární a alespoň velmi přibližně modelovat reálné chování neuronu.

Log-Sigmoida (logistická funkce):

f(x) = \frac{1}{1 + e^{-\alpha \cdot x}}

Tan-Sigmoida:

f(x) = \tanh (\alpha \cdot x)

Signum:

f(x) = \begin{cases} 1 & \text{ pokud } x > 0 \\ 0 & \text{ pokud } x = 0 \\ -1 & \text{ pokud } x < 0 \end{cases}

Využití

Neuronová síť má asi jako každý přírodou inspirovaný přístup velmi široké možnosti uplatnění, především při řešení problémů z reálného světa. V této oblasti jsou totiž počítače nejslabší. Dále se přímo nabízí využití v umělé inteligenci. Konkrétní typ a architektura neuronové sítě se většinou silně upravuje na míru danému konkrétnímu problému.

Nejzajímavější vlastností neuronových sítí je jejich odolnost vůči chybám. Jakmile se neuronová síť jednou dobře naučí, je schopná řešit i příbuzné problémy, se kterými se dosud nesetkala. Na druhou stranu, není však garantovaná kvalita ani správnost řešení. Neoddělitelnou součástí výsledku je vždy určitá chyba.

V těchto oblastech se neuronové sítě používají nejčastěji:

  • klasifikace
  • detekce pravidelnosti
  • zpracování řeči
  • zpracování obrazu
  • optimizační problémy
  • ovládání robotů
  • predikce časových řad
  • simulace

Vícevrstvý perceptron

Vícevrstvý perceptron (multilayer perceptron) je typ neuronové sítě, která se chová podobně jako jeden perceptron, ale skládá se z většího počtu umělých neuronů. Tyto neurony jsou rozděleny do vrstev. První vrstva, tzv. vstupní vrstva je vstupem neuronové sítě. Počet neuronů ve vstupní vrstvě odpovídá velikosti vstupního vektoru. Za ní následují tzv. skryté vrstvy. Těch může být i více, přičemž jejich počet i velikost je libovolná. Poslední skrytá vrstva je napojena na poslední, tzv. výstupní vrstvu. Počet neuronů ve výstupní vrstvě odpovídá velikosti výstupního vektoru. Všechny neurony jsou spojené pouze dopředně – směrem ze vstupu na výstup.

vícevrstvý perceptron

Při učení se nejprve nastaví vstupní vektor jako výstup vstupní vrstvy. Poté je spuštěn výpočet výstupů všech neuronů, a to postupně od první skryté vrstvy po výstupní vrstvu. Následuje nastavení správného výstupu na výstupní vrstvu. Ta vypočítá chybu a zpětně ji rozšíří až do první skryté vrstvy. Poté jsou podle chyby upraveny jednotlivé váhy všech neuronů.

Implementace (Java)

Neuron
package network;

import java.util.HashSet;
import java.util.Set;

/**
 * Třída představující jednoduchý neuron.
 * @author Vojtěch Hordějčuk
 */

public class Neuron
{
  /**
   * počítadlo instancí neuronů
   */

  private static int counter = 1;
  /**
   * unikátní ID neuronu
   */

  final protected int id;
  /**
   * množina synapsí - vstupní neurony
   */

  final private Set<Synapsis> inputs;
  /**
   * výstupní hodnota
   */

  protected double output;
  /**
   * velikost chyby
   */

  protected double error;

  /**
   * Vytvoří nový neuron.
   */

  public Neuron ()
  {
    this.id = Neuron.counter ++;
    this.inputs = new HashSet<Synapsis> ();
    this.output = 0;
    this.error = 0;
  }

  /**
   * Vrátí unikátní ID neuronu.
   * @return ID neuronu
   */

  public int getId ()
  {
    return this.id;
  }

  /**
   * Vrátí výstup neuronu.
   * @return výstup neuronu
   */

  public double getOutput ()
  {
    return this.output;
  }

  /**
   * Nastaví výstup neuronu.
   * @param value hodnota výstupu neuronu
   */

  public void setOutput (final double value)
  {
    this.output = value;
  }

  /**
   * Vynuluje výstup a chybu neuronu, nastaví náhodné váhy synapsím.
   */

  public void reset ()
  {
    this.output = 0;
    this.error = 0;

    for (final Synapsis temp: this.inputs)
    {
      temp.reset ();
    }
  }

  /**
   * Spojí neuron s výstupem jiného neuronu.
   * @param neuron vstupní neuron
   */

  public void addInput (final Neuron neuron)
  {
    this.inputs.add (new Synapsis (neuron));
  }

  /**
   * Přidá k neuronu speciální vstup, tzv. bias.
   */

  public void addBiasInput ()
  {
    this.inputs.add (new Synapsis (new Bias ()));
  }

  /**
   * Vypočítá výstup neuronu na základě jeho vstupů a odpovídajících vah.
   * 1) Sečtou se vážené vstupy neuronu.
   * 2) Výsledná hodnota je předána jako parametr nelineární prahové funkci.
   */

  public void calculateOutput ()
  {
    this.output = 0;

    for (final Synapsis temp: this.inputs)
    {
      this.output += temp.getWeight () * temp.getNeuron ().getOutput ();
    }

    this.output = Neuron.getThreshold (this.output);
  }

  /**
   * Vyresetuje chybu neuronu.
   */

  public void resetError ()
  {
    this.error = 0;
  }

  /**
   * Vypočítá chybu a zpropaguje ji dál do všech vstupních neuronů.
   * Tato metoda je určena pro výstupní vrstvu.
   * @param ideal správný výstup neuronu
   */

  public void propagateError (final double ideal)
  {
    this.error = ideal - this.output;

    this.propagateError ();
  }

  /**
   * Zpropaguje chybu dál do všech vstupních neuronů.
   * Tato metoda je určena pro skrytou vrstvu.
   */

  public void propagateError ()
  {
    for (final Synapsis temp: this.inputs)
    {
      temp.getNeuron ().error += this.error * temp.getWeight ();
    }
  }

  /**
   * Opraví váhy synapsí podle velikosti chyby a rychlosti učení.
   * @param rate rychlost učení (nenulová hodnota).
   */

  public void fixWeights (final double rate)
  {
    if (rate <= 0)
    {
      throw new UnsupportedOperationException ("Invalid learning rate!");
    }

    for (final Synapsis temp: this.inputs)
    {
      temp.increaseWeight (rate * this.error * Neuron.getThresholdDerivated (this.output) * temp.getNeuron ().output);
    }
  }

  /**
   * Vypočítá hodnotu prahové funkce. Zde je použit hyperbolický tangens.
   * F(x) = tanh (x)
   * @param input vstupní hodnota
   * @return hodnota prahové funkce
   */

  private static double getThreshold (final double input)
  {
    return Math.tanh (input);
  }

  /**
   * Vypočítá hodnotu derivace prahové funkce. Zde je použita derivace hyperbolického tangens.
   * F'(x) = 1 - tanh^2 (x)
   * @param input vstupní hodnota
   * @return hodnota derivace prahové funkce
   */

  private static double getThresholdDerivated (final double input)
  {
    return 1.0 - (Math.tanh (input) * Math.tanh (input));
  }

  @Override
  public String toString ()
  {
    return String.format ("N%d (out = %f, err = %f, in = %s)", this.id, this.output, this.error, this.inputs.toString ());
  }
}

zdrojový kód (Java) - zobrazit (4060 znaků)

Bias (speciální neuron)
package network;

/**
 * Speciální neuron, který má na výstupu neustále 1.
 * Používá se pro zvýšení kvality neuronové sítě.
 * @author Vojtěch Hordějčuk
 */

public class Bias extends Neuron
{
  @Override
  public double getOutput ()
  {
    return 1;
  }
}

zdrojový kód (Java) - zobrazit (257 znaků)

Synapse
package network;

/**
 * Třída představující synapsi - vážený spoj mezi dvěma neurony.
 * @author Vojtěch Hordějčuk
 */

public class Synapsis
{
  /**
   * neuron napojený na synapsi
   */

  final private Neuron neuron;
  /**
   * váha synapse
   */

  private double weight;

  /**
   * Vytvoří novou synapsi.
   * @param neuron napojený neuron
   */

  public Synapsis (final Neuron neuron)
  {
    this.neuron = neuron;
    this.weight = 1;
  }

  /**
   * Vrátí napojený neuron.
   * @return napojený neuron
   */

  public Neuron getNeuron ()
  {
    return this.neuron;
  }

  /**
   * Vrátí váhu synapse.
   * @return váha synapse
   */

  public double getWeight ()
  {
    return this.weight;
  }

  /**
   * Nastaví váhu synapse na náhodnou hodnotu.
   */

  public void reset ()
  {
    this.weight =  - 1.0 + (Math.random () * 2.0);
  }

  /**
   * Změní váhu synapse.
   * @param delta velikost změny
   */

  public void increaseWeight (final double delta)
  {
    this.weight += delta;
  }

  @Override
  public String toString ()
  {
    return String.format ("N%d (w = %f)", this.neuron.getId (), this.weight);
  }
}

zdrojový kód (Java) - zobrazit (1126 znaků)

Vrstva neuronů
package network;

import java.util.HashSet;
import java.util.Set;

/**
 * Třída představující vrstvu neuronové sítě.
 * @author Vojtěch Hordějčuk
 */

public class Layer
{
  /**
   * množina neuronů ve vrstvě
   */

  final private Set<Neuron> neurons;

  /**
   * Vytvoří novou vrstvu a její neurony.
   * @param size počet neuronů ve vrstvě
   */

  public Layer (final int size)
  {
    this.neurons = new HashSet<Neuron> ();

    for (int i = 0; i < size; i ++)
    {
      this.neurons.add (new Neuron ());
    }

    // každému neuronu přidat jeden speciální vstup (bias)
    // (toto jejen optimalizace, k funkci to není nutné)

    for (final Neuron temp: this.neurons)
    {
      temp.addBiasInput ();
    }
  }

  /**
   * Vrátí výstupní vektor vrstvy.
   * @return výstupní vektor
   */

  public double[] getOutputs ()
  {
    final double[] outputs = new double[this.neurons.size ()];

    int i = 0;

    for (final Neuron temp: this.neurons)
    {
      outputs[i ++] = temp.getOutput ();
    }

    return outputs;
  }

  /**
   * Nastaví neuronům výstup podle vektoru.
   * @param values vektor hodnot
   */

  public void setOutputs (final double[] values)
  {
    // zkontrolovat parametr

    if (values.length != this.neurons.size ())
    {
      throw new UnsupportedOperationException ("Invalid output vector size!");
    }

    // přiřadit jednotlivé hodnoty neuronům

    int i = 0;

    for (final Neuron temp: this.neurons)
    {
      temp.setOutput (values[i ++]);
    }
  }

  /**
   * Přidá každý neuron této vrstvy jako vstup neuronu další vrstvy.
   * @param target další cílová vrstva
   */

  public void connectTo (final Layer target)
  {
    for (final Neuron tNext: target.neurons)
    {
      for (final Neuron tPrev: this.neurons)
      {
        tNext.addInput (tPrev);
      }
    }
  }

  /**
   * Vyresetuje všechny neurony ve vrstvě.
   */

  public void reset ()
  {
    for (final Neuron temp: this.neurons)
    {
      temp.reset ();
    }
  }

  /**
   * Vypočítá výstupní hodnotu všech neuronů.
   */

  public void calculateOutput ()
  {
    for (final Neuron temp: this.neurons)
    {
      temp.calculateOutput ();
    }
  }

  /**
   * Vyresetuje chybu všech neuronů.
   */

  public void resetError ()
  {
    for (final Neuron temp: this.neurons)
    {
      temp.resetError ();
    }
  }

  /**
   * Zpětně zpropagovat chybu.
   * Tato metoda je určena pro výstupní vrstvu.
   * @param output výstupní vektor
   */

  public void propagateError (final double[] output)
  {
    int i = 0;

    for (final Neuron temp: this.neurons)
    {
      temp.propagateError (output[i ++]);
    }
  }

  /**
   * Zpětně zpropagovat chybu.
   * Tato metoda je určena pro skrytou vrstvu.
   */

  public void propagateError ()
  {
    for (final Neuron temp: this.neurons)
    {
      temp.propagateError ();
    }
  }

  /**
   * Opravit váhy synapsí podle chyby.
   * @param rate rychlost učení
   */

  public void fixWeights (final double rate)
  {
    for (final Neuron temp: this.neurons)
    {
      temp.fixWeights (rate);
    }
  }

  @Override
  public String toString ()
  {
    final StringBuffer buffer = new StringBuffer ();

    for (final Neuron temp: this.neurons)
    {
      buffer.append (temp.toString () + "\n");
    }

    return buffer.toString ().trim ();
  }
}

zdrojový kód (Java) - zobrazit (3318 znaků)

Neuronová síť
package network;

/**
 * Třída představující neuronovou síť.
 * Jedná se o jednoduchý vícevrstvý perceptron.
 * @author Vojtěch Hordějčuk
 */

public class Network
{
  /**
   * vstupní vrstva neuronů
   */

  final private Layer input;
  /**
   * skryté vrstvy neuronů
   */

  final private Layer hidden[];
  /**
   * výstupní vrstva neuronů
   */

  final private Layer output;
  /**
   * rychlost učení
   */

  private double rate;

  public Network (final int inputSize, final int outputSize, final int hiddenSize, final int hiddenCount, final double rate)
  {
    // zkontrolovat parametry

    if (inputSize < 1 || outputSize < 1 || hiddenSize < 1 || hiddenCount < 1 || rate <= 0.0)
    {
      throw new UnsupportedOperationException ("Invalid parameters of neural network!");
    }

    // inicializovat

    this.rate = rate;

    // vytvořit vstupní a výstupní vrstvy

    this.input = new Layer (inputSize);
    this.output = new Layer (outputSize);

    // vytvořit skryté vrstvy

    this.hidden = new Layer[hiddenCount];

    for (int i = 0; i < this.hidden.length; i ++)
    {
      this.hidden[i] = new Layer (hiddenSize);
    }

    // spojit vrstvy (IN-H...H-OUT)

    this.input.connectTo (this.hidden[0]);

    for (int i = 0; i < hiddenCount - 1; i ++)
    {
      this.hidden[i].connectTo (this.hidden[i + 1]);
    }

    this.hidden[this.hidden.length - 1].connectTo (this.output);

    // vyresetovat síť

    this.reset ();
  }

  /**
   * Vyresetuje všechny vrstvy neuronové sítě.
   */

  public void reset ()
  {
    this.input.reset ();

    for (int i = 0; i < this.hidden.length; i ++)
    {
      this.hidden[i].reset ();
    }

    this.output.reset ();
  }

  /**
   * Provede proces učení neuronové sítě pro daný vstupní a výstupní vektor.
   * @param inputs vstupní vektor hodnot
   * @param ideal správný výstupní vektor
   */

  public void learn (final double[] inputs, final double[] ideal)
  {
    // spočítat výstupy všech neuronů

    this.guess (inputs);

    // vynulovat chybu

    this.input.resetError ();

    for (int i = 0; i < this.hidden.length; i ++)
    {
      this.hidden[i].resetError ();
    }

    this.output.resetError ();

    // zpětně zpropagovat chybu (OUT-H...H)

    this.output.propagateError (ideal);

    for (int i = this.hidden.length - 1; i >= 0; i --)
    {
      this.hidden[i].propagateError ();
    }

    this.input.propagateError ();

    // opravit váhy synapsí podle aktuální chyby (OUT-H...H)

    this.input.fixWeights (this.rate);

    for (int i = 0; i < this.hidden.length; i ++)
    {
      this.hidden[i].fixWeights (this.rate);
    }

    this.output.fixWeights (this.rate);
  }

  /**
   * Provede odhad výstupu pro daný vstup.
   * @param inputs vstupní vektor
   * @return odhadovaný výstupní vektor
   */

  public double[] guess (final double[] inputs)
  {
    // nastavit vstup

    this.input.setOutputs (inputs);

    // spočítat výstupní hodnoty všech neuronů od skrytých vrstev dál

    for (int i = 0; i < this.hidden.length; i ++)
    {
      this.hidden[i].calculateOutput ();
    }

    this.output.calculateOutput ();

    // vrátit výstupní vektor z výstupní vrstvy

    return this.output.getOutputs ();
  }

  @Override
  public String toString ()
  {
    final StringBuffer temp = new StringBuffer ();

    temp.append ("Neural network\n--------------\n");
    temp.append ("*** INPUT LAYER ***\n");
    temp.append (this.input.toString () + "\n");

    for (int i = 0; i < this.hidden.length; i ++)
    {
      temp.append ("*** HIDDEN LAYER " + i + " ***\n");
      temp.append (this.hidden[i].toString () + "\n");
    }

    temp.append ("*** OUTPUT LAYER ***\n");
    temp.append (this.output.toString () + "\n");

    return temp.toString ().trim ();
  }
}

zdrojový kód (Java) - zobrazit (3763 znaků)

Příklad použití

Tuto neuronovou síť lze například vyzkoušet na funkci XOR.

public static void main (String[] args)
{
  // vytvořit neuronovou síť

  final Network network = new Network (2, 1, 5, 1, 0.2);

  // vytvořit vstupní a správné datové vektory
  // (toto je funkce XOR)

  double[][] in = {{0,0},{0,1},{1,0},{1,1}};
  double[][] out = {{0},{1},{1},{0}};

  // naučit neuronovou síť

  System.out.println ("Learning...");

  for (int j = 0; j < 10000; j ++)
  {
    for (int i = 0; i < in.length; i ++)
    {
      network.learn (in[i], out[i]);
    }
  }

  System.out.println (network.toString ());

  // otestovat neuronovou síť

  System.out.println ("Processing...");

  System.out.println (network.guess (in[0])[0]);
  System.out.println (network.guess (in[1])[0]);
  System.out.println (network.guess (in[2])[0]);
  System.out.println (network.guess (in[3])[0]);
}

zdrojový kód (Java) - zobrazit (803 znaků)

Kohonenova síť

Kohonenova síť je speciální typ samoorganizované neuronové sítě a někdy se označuje také jako Kohonenova samoorganizující se mapa. Poprvé ji představil finský vědec Teuvo Kohonen v roce 1982. Je vhodná tam, kde je třeba stabilní klasifikátor dat, který při provozu své vlastnosti již nemění. Výsledek je však závislý na pořadí vstupů, proto je nutné jejímu učení věnovat zvýšenou pozornost.

Topologie základní varianty Kohonenovy sítě je založena na obdélníkové mřížce. V každém vrcholu mřížky je umístěn jeden neuron. Jednotlivé příčky mřížky vedoucí z neuronu určují jeho sousedy. Každý neuron i vstupní vektor si lze představit jako jeden bod v n-dimenzionálním prostoru S, kde n je délka vstupního vektoru. Každá klasifikační třída odpovídá nějakému podprostoru tohoto prostoru S.

Kohonenova síť se učí sama, bez učitele. Před učením se nejprve inicializují váhy neuronů, například na náhodná malá čísla. Při učení dostává síť na vstup množinu podnětů, které si sama utřídí a svou konfigurací začne vystihovat jejich vlastnosti. Proces učení probíhá v iteracích. V každé iteraci je na vstup každého neuronu přiveden vstupní vektor a neurony vyhodnotí svůj výstup, který je roven druhé mocnině Eukleidovské vzdálenosti vektoru vah od vektoru vstupního:

d = (\bar{x} - \bar{w})^2 = \sum_{i=1}^n (\bar{x}_i - \bar{w}_i)^2
  • x – vstupní vektor
  • w – vektor vah
  • xi – jedna složka vstupního vektoru
  • wi – jedna složka vektoru vah

Neuron, pro který je tato vzdálenost nejmenší, je nazván vítězným neuronem. Vstupní vektor je poté klasifikován třídou, ve které se vítězný neuron nachází. Poté jsou upraveny váhy všech neuronů a podle následujícího vztahu:

\bar{w}_a (t + 1) = \bar{w}_a (t) + \alpha \cdot g (a, k) \cdot [x(t) - \bar{w}_a(t)]
  • a – obecný neuron
  • k – vítězný neuron
  • wa – vektor vah neuronu a
  • alfa – koeficient adaptační funkce
  • g(a,k) – funkce vzdálenosti mezi neurony a a k (např. gaussián)
  • t – číslo aktuální iterace

Reference