Archive for Marzec, 2009

Szkoła cz. 2 – Winner Takes All

Prowadzący zajęcia ze Sztucznej Inteligencji nie pozwala odpocząć moim szarym komórkom serwując kolejne zadanie z sieci neuronowych. Tym razem jednak nie musiałem rozwiązywać konkretnego problemu, a jedynie przedstawić działanie algorytmu.

Zadanie:

Napisać program prezentujący w sposób graficzny naukę neuronów stosując proces nauki bez nauczyciela. Uczenie przeprowadzić z wykorzystaniem jednego z algorytmów WTA (Winner Takes All) lub WTM (Winner Takes Most).

Krótkie postawy teoretyczne:

Nauka bez nauczyciela (sieć samoorganizująca się) – W tym procesie nauki pożądana odpowiedź sieci nie jest znana. Sieć nie posiadając informacji o poprawności danych powstałych na wyjściu, uczy się poprzez analizę pobudzenia, w trakcie tej analizy parametry sieci podlegają zmianom.

Nauka z algorytmem WTA:

  1. inicjalizacja wag sieci
    • neuron1 (w1, w2) = (1, 3)
    • neuron2 (w1, w2) = (2, 4)
  2. wzór na obliczenie odległości wektora wejściowego do wag każdego z neuronów:
    • d = sqrt((w1 – x1)^2 + (w2 – x2)^2)
  3. wybranie neuronu zwycięzcy (wygrywającego) dla którego odległość wag od wektora wejściowego jest najmniejsza (na podstawie Euklidesowej miary odległości).
    • Zwyciężył neuron2 ponieważ ma odległość mniejszą od neuron1.
  4. zmiana wartości poszczególnych wag tego neuronu przyjmując, że szybkość nauki wynosi n = 0,6
  5. powtórzenie kroków 2-5 dla wszystkich przykładów uczących.
  • Obliczenia (d):

    dn1 = sqrt((1 – 5)^2 + (3 – 8)^2) = sqrt(41) = 6,40

    dn2 = sqrt((2 – 5)^2 + (4 – 8)^ 2) = sqrt(25) = 5

  • Obliczenia (w):

    w1’ = w1 + n * (x1 – w1) = 2 + 0,6 * (5 – 2) = 3,8

    w2’ = w2 + n * (x2 – w2) = 4 + 0,6 * (8 – 4) = 6,4

Implementacja:

Cała samoorganizująca się sieć miała składać się jakby z dwóch części:

  1. Rodzin losowo wybranych punktów
  2. Losowo lub „ręcznie” dodanych neuronów

Postanowiłem więc utworzyć takie oto klasy reprezentujące powyższą sytuację:
Klasa „Characteristics”:

Klasa abstrakcyjna zawierająca właściwość zwracającą rodziny punktów, bądź utworzone neurony.
Zawiera także metodę Prepare() przygotowująca rodziny punktów, bądź neurony.

public abstract class Characteristics
{
        protected List<DataTable> _listOfAttribute;
        public abstract void Prepare();
}

Klasa „FamiliesOfPoints”:

Klasa dziedzicząca po „Characteristics”. Losuje rodziny punktów z wyznaczonego przedziału, oraz udostępnia je w postaci listy typu DataTable.

class FamiliesOfPoints : Characteristics
{
/// <summary>
/// Ilosc rodzin
/// </summary>
private int _amount;
/// <summary>
/// Obiekt pozwalajacy na losowanie liczb
/// </summary>
private Random _random;

/// <summary>
/// Wlasciwosc udostepniajaca obiekty
/// DataTable zawierajace rodziny punktow
/// </summary>
public List<DataTable> FamiliesPoints
{
  get
  {
    return base._listOfAttribute;
  }
}
/// <summary>
/// Konstruktor klasy
/// </summary>
/// <param name="amount">Ilosc rodzin punktow</param>
public FamiliesOfPoints(int amount)
{
base._listOfAttribute = new List<DataTable>();
_amount = amount;
_random = new Random(DateTime.Now.Millisecond);
}

/// <summary>
/// Metoda przygotowujaca rodziny punktow
/// Uruchamia losowanie
/// </summary>
public override void Prepare()
{
//Wylosowane liczby sa typu double
string switchOperand = "x1";
for (int i = 0; i < _amount; i++)
{
  //Wylosowanie czesci ulamkowej
  double doubleValue = RandomDoubleNumber();

  //Wylosowanie punktu i dodanie
  //czesci ulamkowej do niego
  double x2 = RandomIntNumber() + doubleValue;
  double x1 = RandomIntNumber() + doubleValue;

  //Utworzenie DataTable przechowujacego dana rodzine punktow
  DataTable dataTable = new DataTable("Family "
                       + i.ToString());
  dataTable.Columns.Add(new DataColumn("X1"));
  dataTable.Columns.Add(new DataColumn("X2"));

//Utworzenie punktow z danej rodziny na
//podstawie pierwszego wylosowanego punktu
//Przesuwam o wyznaczona wartosc punkt x1
//dodajac nowo wylosowana czesc ulamkowa lub x2
  switch (switchOperand)
  {
    case "x1":
     dataTable.Rows.Add(new object[] {
            Math.Round(i + RandomDoubleNumber(), Math.Round(x2, 2) });
     dataTable.Rows.Add(new object[] {
          Math.Round(i + RandomDoubleNumber() + 2, 2), Math.Round(x2 - 2, 2) });
     dataTable.Rows.Add(new object[] {
         Math.Round(i + RandomDoubleNumber() + 2, 1), Math.Round(x2 + 2, 1) });
     dataTable.Rows.Add(new object[] {
         Math.Round(i + RandomDoubleNumber() + 2, 2), Math.Round(x2, 2) });
     dataTable.Rows.Add(new object[] {
         Math.Round(i + RandomDoubleNumber() + 2, 1), Math.Round(x2, 1) });
     switchOperand = "x2";
    break;
	case "x2":
     dataTable.Rows.Add(new object[] { Math.Round(x1, 2),
				   Math.Round(x2, 2) });
	 dataTable.Rows.Add(new object[] { x1, Math.Round(x2 - 2, 2) });
     dataTable.Rows.Add(new object[] { x1, Math.Round(x2 + 2, 1) });
     dataTable.Rows.Add(new object[] { Math.Round(x1 + 2, 2),
	               Math.Round(x2 + 2, 2) });
     dataTable.Rows.Add(new object[] { Math.Round(x1 - 2, 1),
	               Math.Round(x2 - 2, 1) });
     switchOperand = "x1";
    break;
}
  _listOfAttribute.Add(dataTable);
}
}

/// <summary>
/// Metoda losujaca liczbe calkowita
/// </summary>
/// <returns>Zwraca wylosowana liczbe
/// w postaci typu double</returns>
private double RandomIntNumber()
{
  return _random.Next(3, 98);
}

/// <summary>
/// Losuje liczbe z przedzialu (0, 1)
/// </summary>
/// <returns>Zwraca wartosc liczby</returns>
private double RandomDoubleNumber()
{
  return Math.Round(_random.NextDouble(), 1);
}
}
}

Klasa „Neurons”:

Klasa dziedzicząca po „Characteristics”. Wagi neuronów pobierane są z pliku .xml lub losowane.

class Neurons : Characteristics
{
/// <summary>
/// Zmienna identyfikujaca sposob
/// tworzenia neuronow
/// odczyt z pliku/losowanie
/// </summary>
private object _neuronMode;

/// <summary>
/// Wlasciwosc zwracajaca obiekt
/// DataTable zawierajacy neurony
/// </summary>
public DataTable FamilyNeurons
{
  get
  {
    return base._listOfAttribute[0];
  }
}
/// <summary>
/// Szybkosc uczenia
/// </summary>
public double N
{
  get;
  private set;
}

/// <summary>
/// Konstruktor klasy
/// </summary>
/// <param name="neuronMode">Sposob tworzenia neuronow</param>
/// <param name="n">Szybkosc nauki</param>
public Neurons(object neuronMode, double n)
{
  base._listOfAttribute = new List<DataTable>();
  _neuronMode = neuronMode;
  N = n;
}
/// <summary>
/// Metoda przygotowujaca neurony
/// Kontroluje jakiego typu
//jest zmienna _neuronMode
/// Jesli jest to zmienna typu
///string przyjmuje to za sciezke do
/// pliku i uruchamia metode ReadFromFile()
/// Jesli jest to liczba typu int uruchamia metode
/// RandomNeurons()
/// </summary>
public override void Prepare()
{
  if (_neuronMode.GetType().Equals(typeof(string)))
  {
    ReadFromFile((string)_neuronMode);
  }

  if (_neuronMode.GetType().Equals(typeof(int)))
  {
    RandomNeurons();
  }
}
/// <summary>
/// Metoda odczytujaca z pliku,
// wagi poszczegolnych neuronow
/// </summary>
/// <param name="path">Sciezka do pliku</param>
private void ReadFromFile(string path)
{
  //Utworzenie obiektu DataTable
  //przechowujacego wagi
  //oraz identyfikator danego neuronu
  DataTable neuronsTable =
	new DataTable("Neurons");
  neuronsTable.Columns.Add(new DataColumn("Id"));
  neuronsTable.Columns.Add(new DataColumn("W1"));
  neuronsTable.Columns.Add(new DataColumn("W2"));

  //Utworzenie dokumentu XPathDocument pozwalajacego odczytac
  //plik xml z neuronami
  XPathDocument xPathDocument = new XPathDocument(path);
  int idNeuron = 0;
  XPathNavigator nav = xPathDocument.CreateNavigator();
  XPathNodeIterator iter = nav.Select("/Neurons/Neuron/*");

  object[] weights = new object[3];

  while (iter.MoveNext())
  {
    idNeuron++;
    weights[0] = idNeuron;
   //Sprawdzam czy aktualny node ma nazwe W1
    if (iter.Current.Name.Equals("W1"))
    {
      weights[1] = iter.Current.ValueAsDouble;
    }
    iter.MoveNext();
   //Sprawdzam czy aktualny node ma nazwe W2
    if (iter.Current.Name.Equals("W2"))
    {
      weights[2] = iter.Current.ValueAsDouble;
    }

   neuronsTable.Rows.Add(weights);
  }

  _listOfAttribute.Add(neuronsTable);
}

/// <summary>
/// Metoda losujaca wagi neuronow
/// </summary>
private void RandomNeurons()
{
  DataTable neuronsTable =
	new DataTable("Neurons");
  neuronsTable.Columns.Add(new DataColumn("Id"));
  neuronsTable.Columns.Add(new DataColumn("W1"));
  neuronsTable.Columns.Add(new DataColumn("W2"));

  //Utworzenie obiektu umozliwiajacego losowanie
  Random random =
	new Random(DateTime.Now.Millisecond);
 for (int i = 0; i < (int)_neuronMode; i++)
 {
  int w1 = random.Next(4, 97);
  int w2 = random.Next(4, 97);

  neuronsTable.Rows.Add(new object[] {
        i + 1,
         w1,
         w2 });
 }

  _listOfAttribute.Add(neuronsTable);
}
}

Plik xml zawierający wagi neuronów:

<?xml version="1.0" encoding="utf-8" ?>
<Neurons>
  <Neuron>
    <W1>1</W1>
    <W2>2</W2>
  </Neuron>
  <Neuron>
    <W1>2</W1>
    <W2>5</W2>
  </Neuron>
  <Neuron>
    <W1>4</W1>
    <W2>1</W2>
  </Neuron>
  <Neuron>
    <W1>7</W1>
    <W2>1</W2>
  </Neuron>
  <Neuron>
    <W1>4</W1>
    <W2>6</W2>
  </Neuron>
</Neurons>

Implementując dalej algorytm WTA postanowiłem poskładać sieć w całość wykorzystując wzorzec projektowy o nazwie „Builder” Wzorzec ten pozwolił mi oddzielić sposób tworzenia składników sieci i udostępnić obiekt reprezentujący algorytm WTA – złożony z tychże składników.

Interfejs „INetworkBuilder”:

W pierwszym kroku utworzyłem interfejs zlecający budowę konkretnych części danego algorytmu i zwracający utworzony obiekt z tych części.

interface INetworkBuilder
    {
        void BuildPointsClass(int amount);
        void BuildNeurons(object neuronMode, double n);
        Algorithm GetAlgorithm();
    }

Klasa „AlgorithmBuilderWTA”:

Następnie stworzyłem klasę implementująca ten interfejs – budującą zgodnie z wytycznymi interfejsu wybrany algorytm. (W tym przypadku algorytm WTA)

class AlgorithmBuilderWTA : INetworkBuilder
{
   private Algorithm _algorithmWTA = new AlgorithmWTA();

#region IBuilder Members
/// <summary>
/// Metoda uruchamiajac proces losowania punktow
/// </summary>
/// <param name="amount">Ilosc rodzin punktow</param>
public void BuildPointsClass(int amount)
{
  Characteristics familiesOfPoints =
  	new FamiliesOfPoints(amount);
  familiesOfPoints.Prepare();
  (_algorithmWTA as AlgorithmWTA).AddPartAlgorithm(familiesOfPoints);
}

/// <summary>
/// Metoda uruchamiajaca tworzenie neuronow
/// </summary>
/// <param name="neuronMode">Sposob tworzenie: odczyt z pliku/losowanie</param>
/// <param name="n">Szybkosc nauki</param>
public void BuildNeurons(object neuronMode, double n)
{
  Characteristics neurons =
		new Neurons(neuronMode, n);
  neurons.Prepare();
  (_algorithmWTA as AlgorithmWTA).AddPartAlgorithm(neurons);
}

/// <summary>
/// Metoda zwracajaca obiekt utworzonego algorytmu WTA
/// </summary>
/// <returns>Algorithm obiekt</returns>
public Algorithm GetAlgorithm()
{
	return _algorithmWTA;
}

#endregion
}
}

Kolejnym krokiem było stworzenie dwóch klas. Algorithm – niejako „spina” w jedną rodzinę klasy reprezentujące algorytmy nauki sieci (w moim przypadku mam tylko jeden algorytm, klasa nie zawiera żadnych składowych), oraz klasę AlgorithmWTA – oto ich kody.

Klasa „Algorithm”:

public abstract class Algorithm
{
}

Klasa „AlgorithmWTA”:

class AlgorithmWTA : Algorithm
{
#region Fields &amp;amp; Properties
/// <summary>
/// Obiekt rodziny punktow
/// </summary>
private FamiliesOfPoints _familiesPoints;
/// <summary>
/// Obiekt neuronow
/// </summary>
private Neurons _neurons;
/// <summary>
/// Wlasciwosc zwracajaca Neurony
/// </summary>
public DataTable FamilyNeurons
{
  get
  {
    return _neurons.FamilyNeurons;
  }
}
/// <summary>
/// Wlasciwosc zwracajaca rodziny punktow
/// </summary>
public List<DataTable> FamilyPoints
{
  get
  {
    return _familiesPoints.FamiliesPoints;
  }
}
/// <summary>
/// Wlasciwosc zwracajaca neurony nauczone
/// </summary>
public DataTable ChangedNeurons
{
  get;
  private set;
}
#endregion

#region Public Methods
/// <summary>
/// Metoda dodajaca obiekty: Kolejne skladowe
/// niezbedne do uruchomienia
/// procesu nauki algorytmem WTA
/// </summary>
/// <param name="part">Skladowe</param>
public void AddPartAlgorithm(Characteristics part)
{
  if (part.GetType().Equals(
           typeof(FamiliesOfPoints)))
  {
    _familiesPoints = (FamiliesOfPoints)part;
  }

  if (part.GetType().Equals(
           typeof(Neurons)))
  {
    _neurons = (Neurons)part;
  }
}
/// <summary>
/// Metoda uruchamiajaca proces nauki
/// </summary>
public void TeachNeurons()
{
if (_familiesPoints != null
		&amp;amp;&amp;amp; _neurons != null)
{
  int max = _familiesPoints.FamiliesPoints[0].Rows.Count;

  ChangedNeurons = GetNewNeuronsDataTable();

 foreach (DataRow row in _neurons.FamilyNeurons.Rows)
 {
   ChangedNeurons.Rows.Add(new object[] {
		row["Id"],
		row["W1"],
		row["W2"]});
 }

   DataRow winner = null;
 //Rozpoczecie procesu obliczania kolejnych
 //zwyciezcow dla danych wektorow uczacych
 //Petla zewnetrzna jest wykonywana tyle razy
 //ile wynosi ilosc najwiekszej rodziny punktow
 //W tym wypadku rodziny maja stala wielkosc 5 punktow
 for (int i = 0; i < max; i++)
 {
  //Petla wewnetrzna wybiera po jednym
  // punkcie z kazdej rodziny
  //Kolejno wyliczani sa zwyciezkie
  //neurony dla danego punktu
   for (int j = 0; j < _familiesPoints.FamiliesPoints.Count; j++)
   {
     DataTable dataTable = _familiesPoints.FamiliesPoints[j];

     if (max >= dataTable.Rows.Count)
     {
       max = dataTable.Rows.Count;
     }
   //Metoda ElementAtOrDefault(i) wyciaga po
   //jednym punkcie z kazdej rodziny
     DataRow point =
	    dataTable.Rows.OfType<DataRow>().ElementAtOrDefault(i);

     if (point != null)
     {
      //Obliczany jest zwyciezca
       winner = CalculateWinner(point);
     }

     if (winner != null)
     {
       CalculateWeights(point,
	         winner["Id"].ToString());
     }
   }
 }
}
}
#endregion

#region Help Methods
/// <summary>
/// Metoda obliczajaca zwyciezce dla
/// danego punku v i neuronow
/// </summary>
/// <param name="v">Punkt</param>
/// <param name="neurons">Neurony z aktualnymi wagami</param>
/// <returns>Zwyciezki neuron</returns>
private DataRow CalculateWinner(DataRow v)
{
  DataRow winner = null;
 if (v != null)
 {
    List<double> neuronsList =
		new List<double>();

    double valueOne;
    double valueTwo;

  for (int i = 0; i < ChangedNeurons.Rows.Count; i++)
  {
    valueOne = double.Parse(ChangedNeurons.Rows[i]["W1"].ToString())
	- double.Parse(v["X1"].ToString());
    valueTwo = double.Parse(ChangedNeurons.Rows[i]["W2"].ToString())
	- double.Parse(v["X2"].ToString());
    neuronsList.Add(Math.Round(Math.Sqrt(Math.Pow(valueOne, 2)
	+ Math.Pow(valueTwo, 2)), 2));
  }
  winner = ChangedNeurons.Rows.OfType<DataRow>()
     .ElementAtOrDefault(
		neuronsList.IndexOf(neuronsList.Min())
	);
 }

  return winner;
}
/// <summary>
/// Oblieczenie nowych wag dla danego neuronu
/// </summary>
/// <param name="neurons">Aktualne neurony</param>
/// <param name="v">Punkt</param>
/// <param name="idNeuron">Identyfikator neuronu
/// zapisany w DataTable</param>
private void CalculateWeights(DataRow v, string idNeuron)
{
  //Wyszukanie pierwszego neuronu zgodnego
  // z podanym Id w DataTable neurons
  DataRow neuron = ChangedNeurons.Rows.OfType<DataRow>()
	.FirstOrDefault(dr =>
  {
    return dr["Id"].Equals(idNeuron);
  });

 //Obliczenie nowych wag dla neuronow
 if (neuron != null)
 {
   neuron["W1"] = double.Parse(neuron["W1"].ToString())
   + _neurons.N * (double.Parse(v["X1"].ToString())
   - double.Parse(neuron["W1"].ToString()));

   neuron["W2"] = double.Parse(neuron["W2"].ToString())
   + _neurons.N *(double.Parse(v["X2"].ToString())
   - double.Parse(neuron["W2"].ToString()));
 }
}
/// <summary>
/// Utworzenie nowej tabeli dla neuronow
/// </summary>
/// <returns>Tabela</returns>
private DataTable GetNewNeuronsDataTable()
{
  var columnCollection = from colName
    in _neurons.FamilyNeurons.Columns.OfType<DataColumn>().AsEnumerable()
    select new DataColumn(colName.ColumnName);

  DataTable neurons = new DataTable();
  foreach (DataColumn column in columnCollection)
  {
    neurons.Columns.Add(column);
  }
    return neurons;
}
#endregion
}

Ostatnim etapem było utworzenie klasy uruchamiającej cały proces tworzenia.

Klasa „AlgorithmManager”:

class AlgorithmManager
{
/// <summary>
/// Metoda tworzaca algorytm WTA,
///przyjmujaca klase budujaca algorytm
/// oraz poszczegolne skladowe niezbedne
/// do utworznie algorytmu
/// </summary>
/// <param name="algorithmBuilder">Klasa budujaca algorytm</param>
/// <param name="amountOfPoints">Ilosc rodzin punktow</param>
/// <param name="neuronMode">Sposob tworzenia neuronow</param>
/// <param name="n">Szybkosc nauki</param>
public void BuildAlgorithm(INetworkBuilder algorithmBuilder, int amountOfPoints,
		object neuronMode, double n)
{
  algorithmBuilder.BuildPointsClass(amountOfPoints);
  algorithmBuilder.BuildNeurons(neuronMode, n);
}
}

Dodanie kolejnego algorytmu nauki sieci jest dość proste dzięki zastosowanemu wzorcowi – sprowadza się tylko do implementacji jednej klasy reprezentującej dany algorytm.

Przykładowe uruchomienie procesu nauki:
Ilość rodzin punktów: 5;
Neurony (Losowane) – ilość: 6;
Szybkość nauki n: 0,6.

INetworkBuilder algorithmBuilder = new AlgorithmBuilderWTA();

AlgorithmManager manager = new AlgorithmManager();
manager.BuildAlgorithm(algorithmBuilder, 5, 6, 0,6);
Algorithm algorithm = algorithmBuilder.GetAlgorithm();

AlgorithmWTA wta = (AlgorithmWTA)algorithm;
wta.TeachNeurons();

Na koniec zamieszczam screeny obrazujące wyniki działania algorytmu WTA.

Neurony pobrane z wpisanych wag w pliku xml:

xmlneurons.jpg

Neurony wylosowane:

losneurons.jpg

Wzorce projektowe w C# – Intro.

W ostatnim czasie zgłębiam tajniki wzorców projektowych wspomagając się tą oto pozycją książkową:

designpatternsbook.jpg

Książka w przemawiający do mnie sposób opisuję implementację dość dużej liczby wzorców projektowych opierając się na nowej wersji języka C#. Rozdziały kończą się ćwiczeniami sprawdzającymi poziom zrozumienia kolejnych wzorców. Niebawem spróbuje zaprezentować własną implementację niektórych ćwiczeń – wcześniej jednak na dobry początek przykład wzorca projektowego „Singleton” w połączeniu z wzorcem projektowym „Facade”.

Utworzyłem dwa Systemy których funkcjonalność jest udostępniania przez klasę Facade.

Klasa SystemA:

internal class SystemA
    {
        internal void OperationA()
        {
            Console.WriteLine("Operacja SystemA.");
        }
    }

Klasa SystemB:

internal class SystemB
    {
        internal void OperationB()
        {
            Console.WriteLine("Operacja SystemB.");
        }
    }

Klasa Facade:

/// <summary>
    /// Wzorzec projektowy Singleton
    /// Klasa bedaca jednoczesnie reprezentacja
    /// wzorca projektowego Facade
    /// udostepnia operacja poszczegolnych systemow
    /// </summary>
    public sealed class Facade
    {
        /// <summary>
        /// Obiekt pierwszego systemu
        /// </summary>
        private SystemA _systemA;
        /// <summary>
        /// Obiekt drugiego systemu
        /// </summary>
        private SystemB _systemB;

        /// <summary>
        /// Prywatny konstruktor
        /// </summary>
        private Facade()
        {
            //Utworzenie obiektow
            _systemA = new SystemA();
            _systemB = new SystemB();
        }

        /// <summary>
        /// Zmienna statczyna klasy Facade
        /// Jest utworzona w pierwszej kolejnosci
        /// </summary>
        private static readonly Facade _uniqueInstance = new Facade();

        /// <summary>
        /// Wlasciwosc zwracajaca unikalna instancje klasy
        /// </summary>
        public static Facade UniqueInstance
        {
            get
            {
                return _uniqueInstance;
            }
        }

        /// <summary>
        /// Operacja wykonywana przez jeden z Systemow
        /// SystemA
        /// </summary>
        public void Operation1()
        {
            _systemA.OperationA();
        }

        /// <summary>
        /// Operacja wykonywana przez jeden z Systemow
        /// SystemB
        /// </summary>
        public void Operation2()
        {
            _systemB.OperationB();
        }
    }

Tak oto wygląda wykorzystanie powyższej funkcjonalności:

//Utworzenie fasady systemow
Facade facade = Facade.UniqueInstance;
//Uruchomienie funkcjonalnosci
facade.Operation1();
facade.Operation2();

I to było by na tyle, kończę małą dawką humoru: „Każdego eksperta da się zastąpić skończoną liczbą studentów.” ;)

Szkoła cz.1

Kolejny semestr nauki przyniósł ze sobą dość interesujący przedmiot „Sztuczna Inteligencja”. Na pierwszym laboratorium otrzymałem proste zadanie rozgrzewkowe którego treść i rozwiązanie chciałbym zaprezentować. Pominę tutaj podstawy teoretyczne tego zadania, skupię się na implementacji – opisie klas które utworzyłem, pokaże także w jaki sposób narysowałem wymagany przez prowadzącego prosty wykres rozwiązania.

Na dobry początek treść zadania:

W ćwiczeniu tym będzie wykorzystany neuron do klasyfikacji samochodów. Neuron ”potrafi” ocenić dwie cechy samochodu: czy jest on „nowy” i czy jest „ładny”. Taki neuron będzie miał dwa wejścia
i jedno wyjście.

Sygnały wejściowe neuronu (podawane przez użytkownika programu, charakteryzują samochód):

  • wejście x1 – wiek samochodu
  • wejście x2 – wygląd samochodu

Informacje podawane na wejście neuronu (sygnały wejściowe) będą „mówiły” neuronowi o tym jaki samochód ”właśnie przejeżdża obok” – neuron wyrazi w odpowiedzi swoją o nim opinię. Wartości na wejściach będą oznaczały, że samochód jest: brzydki i stary (-4, -5), taki sobie (-1, 1), lub nowy i ładny (4, 5).

Wagi neuronu: w1, w2 (podawane przez użytkownika programu, należy przyjąć że ich wartość charakteryzuje komis).

Wagi neuronów to preferencje komisów wynikające m.in. stąd, że klienci danego komisu szukają w nim specyficznych aut. Np. komis sprzedawał do tej pory głównie samochody bardzo stare, przy czym analiza sprzedaży nie wykazała wpływu wyglądu na popyt auta w tym komisie. Komis ten jest reprezentowany przez neuron o wagach w1 =3, w2 = 0.

Schemat neuronu:

obliczenia.jpg

Zakładamy że wejścia oraz wagi są w przedziale: <-5; 5>
Odpowiedź neuronu jest poziomem zadowolenia komisu
z dostarczonego auta. Im wartość jest większa tym poziom zadowolenia jest wyższy.

Polecenia:

  1. Napisz program który generuje określoną przez użytkownika liczbe komisów od 2-5 (z możliwością określenia preferencji komisów „wag” ) oraz automatycznie przydziela auta do komisu
    po wpisaniu x1 i x2.
  2. Wygeneruj samochody o wszystkich możliwych x1 i x2
    z przedziału <-5,5> (jest ich łącznie 121) i przydziel wygenerowane samochody do komisów.
    (narysuj wykres prezentujący przydział samochodów do poszczególnych komisów)

Moje rozwiązanie

Przykład:
Charakterystyka samochodu:

  • x1 = -4 (wiek samochodu)
  • x2 = -5 (wygląd samochodu)

Generuje dwa komisy o wagach:

Komis nr 1:

  • w1 = 3
  • w2 = 0

Komis nr 2:

  • w1 = – 3
  • w2 = 1

Obliczenie poziomu zadowolenia jednego z komisów (Komis nr 1):

neuron.jpg

Poziom zadowolenia Komisu nr 1 wynosi: -12, analogicznie możemy obliczyć poziom zadowolenia z dostarczonego auta Komisu nr 2 który wynosi: 7. Komis o wyższym poziomie zadowolenia przyjmuje auto w tym wypadku jest to Komis nr 2.

Teraz to co Tygryski lubią najbardziej ;) – Implementacja:

Ograniczę się tutaj do pokazania kodu najważniejszych klas. W swoim programie wykorzystałem wzorzec projektowy „Decorator”. Samochód jaki i komis charakteryzują po dwie właściwości, więc w pierwszej kolejności utworzyłem klasę abstrakcyjną uogólniająca ten fakt:

public abstract class Characteristic
    {
        #region Abstract
        /// <summary>
        /// Preferencja pierwsza
        /// </summary>
        public abstract int PreferenceOne { get; protected set; }
        /// <summary>
        /// Preferencja druga
        /// </summary>
        public abstract int PreferenceTwo { get; protected set; }
        #endregion
    }

Następnie utworzyłem klasy: Car oraz CarCommission dziedziczące po klasie abstrakcyjnej Characteristic. Klasa Car jest w tym momencie komponentem który będzie „dekorowany” przez klase CarCommission. Dodatkowo klasa Car dziedziczy po interfejsie IEnumerable<Car> co umożliwia mi utworzenie kolekcji obiektów klasy Car i iteracji po niej. Wewnątrz klasy znajduje się lista List&ltCar&gt która przechowuję wszystkie kombinację aut typu wiek/wygląd. (Polecenie II). Natomiast klasa CarCommission jest odzwierciedleniem komisu samochodowego i określa poziom zadowolenia z dostarczonego auta dzięki metodzie Satisfaction().

Klasa Car:

///Komponent
public class Car : Characteristic, IEnumerable<Car>
    {
        private List<Car> _cars;
        /// <summary>
        /// Konstruktor klasy okreslajacy wiek i wyglad samochodu
        /// </summary>
        /// <param name="x1">Wiek samochodu</param>
        /// <param name="x2">Wyglad samochodu</param>
        public Car(int x1, int x2)
        {
            PreferenceOne = x1;
            PreferenceTwo = x2;
        }

        /// <summary>
        /// Konstruktor tworzacy liste ze wszystkimi
        /// kombinacjami opisujacymi samochod wyglad/wiek
        /// </summary>
        public Car()
        {
            _cars = new List<Car>();
        }

        #region Override region
        /// <summary>
        /// Wlasciwosc zwracajaca wiek samochodu
        /// </summary>
        public override int CharacterOne
        {
            get;
            protected set;
        }

        /// <summary>
        /// Wlasciwosc zwracajaca wyglad samochodu
        /// </summary>
        public override int PreferenceTwo
        {
            get;
            protected set;
        }

        public override string ToString()
        {
            return "(" + PreferenceOne.ToString() + ", "
                     + PreferenceTwo.ToString() + ")";
        }
        #endregion

        #region IEnumerable<Car> Members

        public IEnumerator<Car> GetEnumerator()
        {
            return _cars.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator
                System.Collections.IEnumerable.GetEnumerator()
        {
            return _cars.GetEnumerator();
        }

        #endregion

        /// <summary>
        /// Dodaje samochod do listy
        /// </summary>
        /// <param name="car">Obiekt samochodu</param>
        public void AddCar(Car car)
        {
            if (_cars != null)
            {
                _cars.Add(car);
            }
        }
    }

Klasa CarCommission

 public class CarCommission : Characteristic
    {
        private Characteristic _car;
        private string _commissionName;

        /// <summary>
        /// Konstruktor klasy przyjmujacy samochod do komisu
        /// Okreslajacy wagi
        /// Nazwe komisu
        /// </summary>
        /// <param name="car">Samochod</param>
        /// <param name="w1">Waga pierwsza</param>
        /// <param name="w2">Waga druga</param>
        /// <param name="commissonName">Nazwa komisu</param>
        public CarCommission(Characteristic car, int w1,
                     int w2, string commissionName)
        {
            _car = car;
            PreferenceOne = w1;
            PreferenceTwo = w2;
            _commissionName = commissionName;
        }

        #region Override region
        /// <summary>
        /// Waga pierwsza W1
        /// </summary>
        public override int CharacterOne
        {
            get;
            protected set;
        }

        /// <summary>
        /// Waga druga W2
        /// </summary>
        public override int PreferenceTwo
        {
            get;
            protected set;
        }

        public override string ToString()
        {
            return _commissionName + ": " + Satisfaction().ToString();
        }
        #endregion

        /// <summary>
        /// Poziom zadowolenia z pozyskanego samochodu
        /// </summary>
        /// <returns>Zadowolenie komisu z samochodu</returns>
        public int Satisfaction()
        {
            return _car.PreferenceOne * PreferenceOne +
                     _car.PreferenceTwo * PreferenceTwo;
        }
    }

Przykład oblczenia poziomu zadowolenia jednego z komisow z dostarczonego auta (pseudo kod):

int x1 = -4;
int x2 = -5;
///Tworze obiekt samochodu i charakteryzuje
///go wykorzystujac odpowiedni konstruktor klasy Car
Car car = new Car(x1, x2);
int w1 = 3;
int w2 = 0;
///Dostarczam samochod do komisu,
///charakteryzuje komis podajac jego wagi i nazwe
CarCommission carCommission =
          new CarCommission(car, w1, w2, "Komis 1");
//Komis okresla swoj poziom zadowolenia z dostarczonego auta
int satisfaction = carCommission.Satisfaction();

Samochód trafia do komisu o najwyższym poziomie zadowolenia. Druga część zadania opiera się na tych kilku wyżej napisanych linijkach, krótki opis:
Dostarczam do obiektu klasy Car wszystkie auta z przedziału <-5; 5> (np. (-5, 4), (-5, 4) itd.) jest ich dokładnie 121. Następnie iteruję po obiekcie tej klasy (umożliwia mi to implementacja interfejsu IEnumerable<Car> (patrz kod klasy Car) dostaczając kolejne auta do wygenerowanych komisów. Spradzam który z komisów jest najbardziej zadowolony i przypisuję mu auto.

Ostatnim etapem zadania było narysowanie wykresu. Wykorzystałem tutaj darmową bibliotekę ZedGraph dostępną tutaj. Ściągnięty plik .dll najlepiej dodać do Toolbox-a wybierając menu Tools —> Choose Toolbox Items —-> Browse (wskazać ZedGraph.dll)
dodaną kontrolke przenieść na formularz. Kod rysujący wykres do powyższego zadania przedstawia się następująco:

            PointPairList pointPairList = new PointPairList();
            //samochod (-1, 5)
            pointPairList.Add(-1, 5);
            //samochod (-1, 4)
            pointPairList.Add(-1, 4);
            //samochod (-2, 4)
            pointPairList.Add(-2, 4);

            //pobieramy obiekt GraphPane na ktorym
            //zostanie narysowany wykres z kontrolki ZedGraph
            GraphPane graphPane = zedGraphControl.GraphPane;
            //Ustawiamy tytul wykresu
            zedGraphControl.GraphPane.Title.Text = "Wykres";
            //Opis osi x
            graphPane.XAxis.Title.Text = "Wygląd samochodu.";
            //Opis osi y
            graphPane.YAxis.Title.Text = "Wiek samochodu.";

            //Rysujemy wykres, dostarczajac do metody kolejno:
            //Nazwe komisu, pary punktow(samochody), kolor linii
            //laczacej punkty, ksztalt punktow
            graphPane.AddCurve("Komis 1", pointPairList,
                  Color.Red, SymbolType.Circle);

            //Wywolujemy metode wykonujaca rysowanie
            zedGraphControl.AxisChange();

Zrzut ekranu prezentujący otrzymane wykresy dla dwóch komisów o wyżej wymienionych wagach:

wykres.jpg

Moje zmagania z programem zakończyły się pełnym sukcesem. ;)

Hello .Net World. ;)

Witam.

Postanowiłem przełamać swoją wrodzoną nieśmiałość i od dziś dawać upust wielu kłębiącym się myślom w mojej początkującej programistycznej głowie. Tytuł tego pierwszego wpisu mówi w sporej części czym postaram się dzielić z każdym kto odwiedzi moje skromne blogowe progi. Zapewniam jednak że od czasu do czasu będę odbiegał od technologii związanych z platformą .NET. Dołożę wszelkich starań, aby to czym uraczę wszystkich gości bloga – krótko mówiąc nie było jakąś programistyczną herezją, gdybym jednak popełnił takową – konstruktywna krytyka jest czymś co motywuję mnie do poprawy błędów i nauki.

Chyba każdy od tego zaczynał :)

namespace HellowNetWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hellow .NET World!");
            Console.ReadLine();
        }
    }
}

Pozdrawiam wszystkich odwiedzających i życzę miłej lektury.
Piotr Zarzycki / Hellix