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. ;)

  1. Wygląda porządnie, tylko jeśli już implementujesz klase, która może stanowić kolekcje to staraj się przeciążyć metode equals i hashcode (dobrze zastosowane przyniesie piękne efekty). Nie zapominaj o komentarzach również w polach i metodach. Metodom prywatnym może być smutno, że są takie… pominięte :D

    Pieknie… :p

  2. szkoda, że nie można edytować, ale taka prośba. Jeśli kod się załamuje to mogłbyś go załamać przed wklejeniem w te okienka ?? :) szybciej by się to przeglądało (pytanie retoryczne)

  3. Dziękuje za rady Si3Ma :) – następnym razem z pewnością będzie już lepiej. Przy kolejnym wpisie postaram się dopracować także estetykę.

    • adanos112
    • 4 marca 2009

    zapomniałeś dodać że bez przestrzeni nazw
    „using zetGraph;”
    sugerując się przedstawionym pseudokodem nie ruszysz :D

  4. Co za wyczerpujacy post, niezle. Tak jak mowilem, zaraz caly rok bede odwiedzal ten blog zeby programy z AI kopiowac :)

  5. Akurat to zadanie umieściłem zbyt późno, ale kolejne kto wie może zdąże… Wcześniej jednak będzie coś całkowicie innego.. ;)

  1. Na razie brak trackbacków