Inhalt:
Anhang:
Aufgabenstellung - Knapsackproblem
Das Knapsack-, oder auf ganz deutsch Rucksackproblem ist eines der bekanntesten Optimierungsprobleme überhaupt. Es existiert in einer Vielzahl von Varianten und ist sogar von erheblicher praktischer Bedeutung.
Das Grundproblem sieht folgendermaßen aus: Sie sind Einbrecher und haben sich gerade Zugang in ein Haus verschaffen. Dort finden Sie eine Menge mehr oder weniger wertvoller Gegenstände verschiedenen Gewichts, die zu rauben Sie beabsichtigen. Zum Transport haben Sie allerdings nur einen Rucksack mit begrenztem Tragevermögen bei sich, so daß Sie nur eine Auswahl an Gegenständen mit sich nehmen können. Es gilt nun, die Auswahl der zu stehlenden Gegenstände so zu wählen, daß deren Gesamtwert möglichst hoch ist.
Das Rucksackproblem zählt zu den NP-vollständigen Problemen, es ist bei der Bestimmung des optimalen Ergebnisses durch Berechnung aller Indexmengen mit einer Laufzeit der Ordnung 2^n zu rechnen. Die Existenz eines Algorithmus zur Bestimmung des optimalen Ergebnisses bei angemessener Laufzeit ist fraglich, deshalb werden bei so gearteten Problemen oft heuristische Verfahren gewählt, von denen man sich in annehmbarer Rechenzeit gute Näherungslösungen verspricht. Zu diesen heuristischen Verfahren zählen auch die genetischen Algorithmen, deren Arbeitsweise wir in unserem Projekt einmal visualisieren sollten.
Ein ähnliches Problem kann man sich so vorstellen, daß eine Menge von Kisten (im folgenden: Blöcke) mit verschiedenen Ausmaßen gegeben ist, diese sollen möglichst platzsparend in gleichgroße Container verpackt werden, so daß die Anzahl der dazu benötigten Container möglichst gering ist. Dieser Variante des Rucksackproblems liegt unser Programm zugrunde.
Da hier die Aufgabe auch darin besteht, die Effektivität von genetischen Algorithmen zu testen, sind wir von einem "dummen" Algorithmus ausgegangen, der die Blöcke einfach in einer gegebenen Reihenfolge nimmt und sie von der linken, oberen Ecke ausgehend anfängt anzuordnen, indem einfach ein Karton unter den anderen gesetzt wird und - wenn der untere Rand des Containers dabei überschritten wird - wieder oben anfängt, diesmal allerdings dort am rechten Rand des obersten Blocks.
Als Güte des erhaltenen Ergebnisses (Fitness) wird der Abstand des rechten Randes des äußersten Blockes zum rechten Containerrand genommen (je größer der Abstand, desto besser die Raumausnutzung).
Demnach gibt es bei n Blöcken n! Möglichkeiten der Anordnung, folglich sind schon bei relativ geringer Anzahl an Blöcken nicht mehr alle Möglichkeiten in einer vernünftigen Zeit durchzurechnen.
Mit einem genetischen Algorithmus soll
hier in einer kurzen akzeptablen Zeit eine gute (wenn auch nicht
unbedingt die beste) Lösung gefunden werden.
Codierung einer Lösung in einem Chromosom
Die zur Einführung in GAs auch von Goldberg gewählte Methode ist die Codierung des Chromosoms in einem Bitstring:
Die Gene enthalten nur 0-en und 1-en. Dies ist sehr einleuchtend bei der Verschlüsselung von numerischen Parametern, die der GA optimieren soll, da diese leicht als Binärzahl darstellbar sind. Auch die Kreuzung derartiger Chromosomen gestaltet sich einfach, da bei der Kreuzung Teile des einen Chromosoms jederzeit mit Teilen des anderen Chromosoms ausgetauscht werden können. Es entsteht wieder ein gültiges Chromosom (von Überschreitungen eines eventuell begrenzten Wertebereiches abgesehen).
Beim gegebenen Knapsackproblem ist
nun allerdings nicht die Modifikation von Werten Aufgabe
des GAs, sondern die Modifikation einer Reihenfolge. Deshalb
haben wir uns entschlossen, in den Genen des Chromosoms die Nummer
des zu plazierenden Blockes abzuspeichern. Ein Chromosom 4-1-5-2-3
steht also dafür, daß zuerst der vierte, dann der erste,
dann der fünfte, dann der zweite und schließlich der
dritte Block im Container plaziert werden.
Erstellung der Datenstrukturen mit der VDM-Klassenbibliothek
Wir hatten folgende Bestandteile unseres GA für das Knapsackproblem analysiert (im folgenden zuerst eine verbale Beschreibung des Objektes und dann die Codierung in VDM):
Ein Gen soll natürliche Zahlen ab 1 enthalten.
=> Gen = Nat1
Ein Chromosom ist eine Liste von Genen.
Ein Individuum besteht aus einem Chromosom und dem dazugehörigen Fitness-Wert.
=> Individual = Chromosom x Fitness
Die Fitness ist eine reelle Zahl (könnte aber auch als natürliche Zahl dargestellt werden).
=> Fitness = Real
Die Individuen werden in einem Array zusammengefaßt.
Die Population besteht im einfachsten Fall aus einer Liste von Individuen. Wir haben noch weitere Angaben hinzugenommen: Populationsgröße, Länge der Chromosomen, Schrittweite bei der Selektion, Nr. des besten und schlechtesten Chromosoms, Werte des besten und schlechtesten Chromosoms, Gesamtfitness der Population. Diese Angaben erhöhen die Effizienz oder erleichtern auch die Erstellung der Funktionen.
Ein zu belegender Container hat eine Breite und eine Höhe (in einer best. Einheit z.B. 10cm).
Ein Block im Container hat eine Breite, eine Höhe, eine x- und eine y- Koordinate im Container.
=> Block = X x Y x Width x Height
Mehrere Blöcke werden als Liste zusammengefaßt.
Zur Messung der Belegung des Containers wird eine Datenstruktur benötigt, die je Containerhöhen-Einheit angibt, bis zu welcher Breite der Container auf dieser Höhe schon belegt ist.
[Um die dazugehörige Source für die VDM-Classlibrary
anzusehen, bitte HIER anklicken].
(Um den Quellcode der im folgenden erläuterten Funktionen
anzusehen, bitte HIER anklicken)
Aufgabe: Initialisierung des GAs.
Parameter: Container, in denen die Blöcke angeordnet werden sollen
Blöcke, die angeordnet werden sollen
Populationsgröße, Chromosomenlänge, Kreuzungswahrscheinlichkeit, Mutationswahrscheinlichkeit, Maximaler Anteil zu tauschender Gene beim PMX-Verfahren
Rückgabe: Referenz auf GA
Ruft auf: GA::Makechrom, GA::GetFitness,
POPULATION::MakeSumFitness,
POPULATION(), INDIVIDUALS(),
INDIVIDUAL()
Aufgabe: Erzeugen einer neuen Population aus der bisherigen
Parameter: keine
Rückgabe: Referenz auf GA
Ruft auf: INDIVIDUAL(), POPULATION(), INDIVIDUALS(), GA::Scaling,
POPULATION::Select, GA::Crossover,
POPULATION::MakeSumFitness
Aufgabe: Erzeugen eines neuen Chromosoms mit Genen, die Zahlen von 1-<Chromosomenlänge> in zufälliger Reihenfolge enthalten
Parameter: Chromosomenlänge
Rückgabe: Erstelltes Chromosom
Ruft auf: CHROMOSOME()
Aufgabe: Berechnung des Fitness-Wertes für ein Chromosom. Dieser ermittelt sich als Abstand des am weitesten rechts plazierten Blockes zum rechten Rand des letzten Containers + der Anzahl der noch freien Container verglichen mit dem schlechtestmöglichen Fall. Im schlechtesten Fall belegt jeder Block einen Container.
Bsp.: Container-Breite=15, 10 Blöcke, Belegte Einheiten im schlimmsten Fall: 10*15=150, Tatsächliche Belegung (angenommen): 1 voller Container + 5 Einheiten im zweiten Container => Fitness=150-15-10=125.
[Anmerkung: Wir haben auch versucht, diese Fitness-Berechnung noch zu verbessern, indem auch der Belegungsgrad des letzten Containers noch in die Berechnung einfließt. Dies führte allerdings zu schlechteren Ergebnissen.]
Parameter: Chromosom
Rückgabe: Fitness-Wert des Chromosoms
Ruft auf: CONFILL()
Aufgabe: Kreuzung zweier Chromosomen mit PMX-Operator und Erstellung zweier neuer Individuen
Parameter: Erstes und zweites Eltern-Individuum, Referenz Speicherbereich für erstes Kind-Individuum
Rückgabe: Zweites Kind (Speicher für erstes Kind wird mit erstem Kind belegt)
Ruft auf: INDIVIDUAL(), GA::Mutation,
CHROMOSOME::Position, CHROMOSOME::
Exchange, GA::GetFitness
Aufgabe: Mutation eines Chromosoms. Abhängig von der Mutationswahrscheinlichkeit wird das Chromosom entweder gar nicht verändert oder Chromosomen vertauscht.
Parameter: Zu mutierendes Chromosom
Rückgabe: (Mutiertes) Chromosom
Ruft auf: CHROMOSOME(), CHROMOSOME::Exchange
Aufgabe: Ermittlung der Summe der Fitness-Werte, des besten Chromosoms und dessen Fitness, des schlechtesten Chromosoms und dessen Fitness
Parameter: keine
Rückgabe: Referenz auf die Population
Ruft auf: -
Aufgabe: Auswahl eines Individuums für die Kreuzung. Es wird mit Roulette-Wheel-Selection ausgewählt d.h. ein Individuum wird proportional zu seiner Fitness oft gewählt.
Parameter: keine
Rückgabe: Nummer des gewählten Individuums
Ruft auf: -
Aufgabe: Skalierung der Fitness-Werte der Population. Alle Werte werden modifiziert mit neueFitness=a*(alteFitness-b)+b, wobei b der Durchschnitt der Fitness-Werte ist und a so gewählt wird, daß der beste Wert 2*b ist.
Danach werden die Fitness-Werte noch summiert, so daß für alle Fitness-Werte gilt: neueFitness=Summe der altenFitness-Werte bis zu diesem Individuum. Dies ist Voraussetzung für das korrekte Arbeiten der Select-Funktion.
Parameter: keine
Rückgabe: Referenz auf geänderte Population
Ruft auf: -
Aufgabe: Vertauschen zweier Gene im Chromosom
Parameter: Nr. der beiden zu vertauschenden Gene
Rückgabe: Referenz auf geändertes Chromosom
Ruft auf: -
Aufgabe: Ermittlung der Position eines Gens im Chromosom
Parameter: gesuchtes Gen
Rückgabe: Position, beginnend mit 1, oder 0 wenn das Gen nicht vorkommt
Ruft auf: -
Steuerung und Visualisierung durch Tcl und Tk
Die Steuerung des Programmablaufes wurde implementiert in der Skriptsprache Tcl (sprich: tickle), einem von John K. Ousterhood entwickelten Interpreter, welcher mit seinem eingebauten grafischen Benutzeroberflächen-Toolkit Tk komfortabel ereignisorientierte Programmierung sowie die Einbringung eigener C-Funktionen unterstützt. Tcl ist zunächst eine recht primitive Sprache, deren einziger Datentyp der String ist und deren Variablenhandling größtenteils auf Textersetzung beruht. Gerade in der Zeichenkettenverarbeitung ist Tcl jedoch ein mächtiges Werkzeug, ebenso stellt es einen ausreichenden Befehlssatz für Dateiverarbeitung, Prozeßsteuerung und Fehlerbehandlung zur Verfügung.
Tk ist ein unabhängiges Toolkit für das X Window System, welches den Tcl-Befehlssatz um eine Menge von Befehlen zur grafischen Gestaltung erweitert. Es ermöglicht die Erstellung von Benutzeroberflächen wie auch die Erzeugung grafischer Objekte. Die Tk-Befehle werden durch dessen Windowing-Shell "wish" interpretiert und die erzeugten Objekte dargestellt.
Die grafischen Objekte in Tk sind hierarchisch in Baumstrukturen gegliedert. So werden im Hauptfenster mit der Bezeichnung "." andere Fenster kreiert, die wiederum eigene Fenster beinhalten können usw. So entstehen eindeutige Objektbezeichnungen; beispielsweise könnte der Pfad ".eingabefenster.buttons.weiter" einen Clickbutton "Weiter" in einem Fenster von mehreren Buttons bezeichnen, welches wiederum in einem Eingabefenster liegt, das schließlich sich im Haupt- oder "toplevel"-Fenster befindet. Objekte (in Tk genannt: Widgets) können sein: einfache Rahmen (frames), Anzeigezeilen (labels), Ein/Ausgabezeilen (entries), Druckknöpfe (buttons), Ankreuzfelder (checkbuttons), Auswahlfelder (radiobuttons), Schieberegler (scales), Menüleisten (menues), Scrollbars, Grafikbildschirme (canvas) und Textfenster (text), die jeweils durch eine Vielzahl von Attributen angepaßt werden können. Der Programmablauf wird über einen sehr umfangreichen Ereignis-Befehlssatz gesteuert.
Dieses Programm ist stabil lauffähig unter Tcl Version 7.5 und Tk Version 4.1.
Als Beispiel für ein Tcl/Tk-Programm können Sie HIER klicken, um einen Blick auf das Skript für das Rucksackproblem zu werfen.
Um zur Programmdokumentation zu gelangen, klicken Sie bitte HIER.
Verbindung von Tcl/Tk mit dem C++-Programm
Die Befehle im Tk-Skript werden ausgewertet von einem Interpreter, der 'wish' (windowing-shell). Diese ruft entsprechend den Befehlen Funktionen von X-Windows auf oder wertet Ausdrücke usw.. Will man nicht alle Teile eines Software-Projektes in Form eines solchen Skripts schreiben (z.B. aus Geschwindigkeitsgründen), kann man Funktionen auch in C(++) schreiben und dann von Tcl/Tk aus aufrufen lassen.
Dies funktioniert allerdings nur, wenn der 'wish' diese Befehle bekannt sind. Zu diesem Zweck muß eine neue 'wish' erstellt werden, die die bisherigen Tk-Befehle und zusätzlich die neuen Funktionen enthält.
Um dies zu ermöglichen, werden mit der Installationsversion von Tcl/Tk die Dateien 'tclAppInit' und 'tkAppInit' mitgeliefert. Diese kann man dann erweitern um wenige Zeilen Code, in denen die neuen Befehle Tcl bzw. Tk bekannt gegeben werden.
Da Parameterübergaben von Tcl/Tk als String kommen, müssen
unter Umständen noch Hilfsfunktionen codiert werden, die
diese Parameter in das von den Funktionen erwartete Format konvertieren.
[HIER anklicken, um den Source-Code der für das Knapsackproblem geänderten tkAppInit zu sehen].
[Alternativ besteht auch die Möglichkeit, daß beide
Programme über Pipes kommunizieren.]
Probleme bei einzelnen Operationen für den GA
Wir haben in einem ersten Anlauf versucht, einen GA nur mit den drei grundlegensten Operationen und der Selektion (proportional zum Fitness-Wert) zu implementieren.
Dabei ergaben sich folgendes Problem: Der GA tendierte entweder sehr wenig zu einer guten Lösung oder nur bei einer sehr günstigen Auswahl der Populationsgröße und der Kreuzungswahrscheinlichkeit.
Zu einer Verbesserung haben wir zuerst versucht, nur Individuen in die neue Population einzufügen, die besser sind als die bisherigen. Dies führte schneller zu einer guten Lösung, allerdings leider auch zu lokalen Maxima (nach einer bestimmten Anzahl von Durchläufen waren schließlich alle Chromosomen der Population gleich).
Dieser Weg wurde deshalb verworfen, statt dessen wurden die Fitness-Werte skaliert. Speziell bei der gegebenen Problemstellung liegen die Fitness-Werte nah beieinander, so daß alle Individuen in etwa gleich häufig selektiert werden. Nach der Skalierung liegen bei unserer Lösung die schlechtesten Werte nahe bei Null und der beste liegt beim doppelten des vorherigen Durchschnitts der Fitness-Werte. Dies führt schneller zu einer guten Lösung und hat auch nicht (zumindest nicht binnen ca. 200 Generationen) zu lokalen Extrema geführt.
Ein weiteres Problem lag darin, daß die Berechnung einer
Generation speziell bei großen Populationen viel Zeit kostet.
Ein Profiling hat hierbei recht schnell ergeben, daß ein
Großteil der Zeit in den ersten Anläufen auf die Selektionsfunktion
verwendet wurde. Diese wurde deshalb optimiert.
Nach Start des Programms erscheint eine dreigeteilte Oberfläche; in der oberen befinden sich die Eingabewidgets, in der Mitte die grafische Darstellung der Ergebnisse und im unteren Drittel die Anzeige von Rückgabewerten und statistischen Grafiken.
Über den Button Attribute ändern gelangen Sie in ein Fenster, in dem Sie die für den Genetischen Algorithmus nötigen Parameter einstellen können.
Beenden Sie dort die Eingabe mit Weiter, so werden die Änderungen durchgeführt und das Programm zurückgesetzt. Achtung: Ihre bisherige Population geht verloren! Mit Abbruch verwerfen Sie die Änderungen.
Ferner können Sie auf dem Hauptbildschirm die Maße der Container in x- und y-Richtung festlegen. Bei einer Änderung wird ebenfalls die Evolution zurückgesetzt.
Durch den Schieberegler Zoom können Sie die grafische Darstellung skalieren. In Verbindung mit den Scrollbars neben und unter dem Grafikbildschirm erreichen Sie so die Darstellung des gewünschten Bildschirmausschnitts.
Nach Wahl der GA-Attribute drücken Sie den Button Evolution starten. Sie haben nun zwei Möglichkeiten, die Initialisierung der Anfangspopulation durchzuführen, also anzugeben, wie die Blöcke aussehen, welche in den Containern untergebracht werden. Entweder können Sie die Initialisierung aus einer Datei heraus durchführen oder durch Zufall.
Wählen Sie Datei, so erscheint ein Dateiauswahlfenster. Befindet sich die Datei nicht im aktuellen Verzeichnis, so können Sie im Feld Pfad das Verzeichnis wählen.
Wenn Sie Zufall auswählen, so können Sie im folgenden Fenster
Es werden Ihnen Blöcke erstellt, deren Größe sich zwischen 1x1 und den von Ihnen gewählten Ausmaßen bewegen. Defaultmäßig ist die halbe Containerbreite bzw. -höhe vorgeschlagen. Sind die von Ihnen eingegebenen Werte größer als die des Containers, so wird dessen Größe automatisch angepaßt.
Es wird nun die Initialisierung vorgenommen und die Ausgangspopulation berechnet, welche je nach Populationsgröße Ihnen mehr oder weniger schnell angezeigt wird.
Aus dem Button Evolution starten sind nun drei Knöpfe geworden. Mit Neustart können Sie erneut eine Initialisierung durchführen. Nächste Gen berechnet für Sie die folgende Generation, in der der Genetische Algorithmus versuchen wird, die vorherige Lösung zu optimieren. Das Ergebnis einer Optimierung wird jeweils im Grafikbildschirm dargestellt. Die wichtigsten Daten über die Generation finden Sie im unteren Drittel des Bildschirms. Hier ist dargestellt
und daneben
Die Bestwerte und die Gesamtfitnesswerte sind zudem in den Grafiken daneben in ihrem zeitlichen Verlauf dargestellt.
Wenn Sie sich den lahmen Zeigefinger ersparen wollen, drücken Sie 20 Gen und es werden gleich 20 Generationen auf einmal berechnet.
Zum Beenden des Programmes drücken Sie Ende.
Sie können nun HIER klicken, um das Programm auszuführen.
Bei den Initialisierungsdateien handelt es sich um einfache Textdateien mit Endung ".gad", deren Aufbau folgendermaßen aussieht.
Sie besteht aus einer Folge von Zeilen des Aufbaus: Anzahl_Blöcke <blank> Länge <blank> Höhe <Enter>
Eine Datei "Beispiel.gad" mit dem Inhalt
3 2 4
5 1 2
beschreibt beispielsweise die Initialisierung mit 3 Blöcken der Ausmaße 2 x 4 Einheiten und 5 Blöcken der Ausmaße 1 x 2 Einheiten. Informationen über Containerformate oder Populationsgröße sind in der Datei nicht enthalten, diese werden im Programm selber bestimmt.
Klicken Sie HIER, um sich eine Beispieldatei anzusehen, deren Blöcke im Idealfall genau in zwei Container hineinpassen.
Hier werden die einzelnen Prozeduren des Tcl/Tk-Programms in ihrer
Funktionsweise näher erläutert. Bezüglich Variablenzugriffs
wird sich hier auf Variablenübergaben beschränkt; Informationen
über den Zugriff auf globale Variablen entnehmen Sie bitte
den Modulköpfen des Programmlistings.
Prozedur Fehler
wird für die EIngabefehler-Kommunikation mit dem Anwender benötigt. Gibt eine Fehlermeldung in einem Fenster aus, wartet auf Bestätigung (Button Weiter) und kehrt zurück.
IN: FText = String mit auszugebendem Text
OUT: -
Prozedur LinealeZeichnen
zeichnet horizontales und vertikales Lineal in den Grafikbildschirm. Der Zoom wird der aktuellen Einstellung des Zoom-Schiebereglers entnommen, daher nicht übergeben.
IN: -
OUT: -
Prozedur ContainerZeichnen
zeichnet die in dieser Generation benötigte Anzahl von Containern nebeneinander auf den Grafikbildschirm (Farbe: blau).
IN: AnzContainer = Anzahl der benötigten Container
OUT: -
stellt die aktuelle Population als rote Blöcke in den Containern dar. Damit die Wände der Container nicht durch Blöcke überdeckt werden, werden die an die Containerwände angrenzenden Blöcke entwas kürzer gezeichnet.
IN: DLIST = Liste der zu zeichnenden Blöcke. Neben einigen nicht für diese Funktion benötigten Daten enthält die Liste die Anfangskoordinaten des Blockes sowie seine Breite und Höhe.
OUT: -
Prozedur Dateiwahl
übernimmt die Auswahl der Initialisierungspopulation aus einer Datei. Defaultpfad ist das aktuelle Verzeichnis pwd. In einem Auswahlfenster kann der Benutzer sich durch die Verzeichnisse bewegen und sich eine Datei mit der Endung .gad und einem vorgeschriebenen Dateiformat auswählen, aus der heraus initialisiert werden soll. Der Inhalt der Datei wird gelesen und in eine gültige Initialisierungspopulation umgewandelt. Wird dabei die aktuelle Containergröße zwangsläufig gesprengt, so wird diese angepaßt an die neuen Formate. Ein String mit der gebildeten Population wird zurückgegeben.
IN: -
OUT: String mit den Blockformaten der ersten Generation
Prozedur Zufallsliste
übernimmt die Bildung der Initialisierungspopulation aus Zufallszahlen. In einem Fenster kann der Benutzer einige Parameter einstellen, aus denen dann eine zufällige Population erstellt wird. Auch hier gilt, daß bei Überschreiten der aktuellen Containergröße diese in begrenztem Rahmen angepaßt wird.
IN: -
OUT: String mit den zufälligen Blockformaten der ersten Generation
Prozedur Achsenbeschriftungen
berechnet bei Erstaufruf die Skalierung der Statistik-Grafiken abhängig vom Bestwert der ersten Generation und zeichnet sodann die Achsen des Diagramms. Der Bestwert wird mit 2 multipliziert und die Anfangsziffer des Ergebnisses auf 5, 10 oder 15 aufgerundet, der Rest der Ziffern auf 0 gesetzt. Dies ist der Maximalwert der y-Achse. Dieser wird in 10 gleichgroße Abschnitte eingeteilt, welche ebenfalls an der Achse angetragen werden. So ensteht beispielsweise aus einem Bestwert von 3288 eine Achsenskalierung von 0 bis 10000 in 1000er-Schritten (3288*2=6576, wird aufgerundet zu 10000; 10000/10 =1000). Die Skala wird in das Statistik-Grafikfenster eingepaßt; die x-Achse umfaßt konstant ca. 140 Generationen.
Auf diese Weise werden die Achsen für die Bestwert- und die Gesamtfitness-Darstellung gezeichnet.
IN: THISBEST = Bestwert der Generation, THISFITNESS = Fitnesswert der Generation
OUT: -
Prozedur INITIALIZE
erstellt nach Wunsch des Anwenders entweder aus einer Datei oder aus Zufallszahlen eine Anfangsgeneration (Aufruf des C-Programms GAInit), veranlaßt deren grafische Darstellung und übernimmt Veränderungen in der Benutzeroberfläche (neue Buttons).
IN: DLIST = Name der zu initialisierenden Liste
OUT: String mit der berechneten Initialpopulation
berechnet aus einer übergebenen Population statistische Werte, zeichnet diese in die zugehörigen Graphen und stellt die Blöcke einer Population im Grafikfenster dar.
IN: PopList = Liste mit Daten über die darzustellende Population
OUT: -
Prozedur EVOLUTE
berechnet die Blockanordnung der nächsten Generation und veranlaßt deren grafische Darstellung.
IN: DLIST = Liste mit bisheriger Population
OUT: Liste der neu erstellten Population
Prozedur KillEmAll
Nach Änderung von Evolutionsparametern oder der Containergröße ist eine Rücksetzung der bisherigen Daten erforderlich. Die Benutzeroberfläche wird wieder in den Anfangszustand überführt (Buttons gelöscht), die statistischen Werte zurückgesetzt und deren Graphen gelöscht.
IN: DLIST = -
OUT: -
Prozedur ChangeAttr
Durch Anklicken des Attribute ändern-Buttons wird ein Fenster erzeugt, in dem die Evolutionsparameter eingestellt werden können. Es erfolgt eine Plausibilitätsprüfung der Neueinstellungen. Wenn die Änderung beendet ist, wird die Rücksetzung der Evolution veranlaßt und zurückgekehrt.
IN: DLIST = -
OUT: -
Prozedur CHANGESIZE
Wird die Containergröße im Hauptfenster geändert, so muß die Evolution zurückgesetzt werden. Hier erfolgt ebenfalls eine Plausibilitäts- und Bereichsprüfung der Eingabedaten.
IN: DLIST = -
OUT: -
Prozedur CHANGESCALE
Wird über den Zoom-Schieberegler der Skalierungsfaktor für die Grafikdarstellung verändert, so wird hier die eine Neudarstellung der Grafik durchgeführt.
IN: DLIST = -
OUT: -
Hauptprogramm
Hier erfolgt der Aufbau des Bildschirms inklusive der Ereignisbindung des Programms.
Parameter: -
Rückgabe: -
Genauere Informationen über die Module können Sie dem
Programmlisting entnehmen.
Vergleich des GA's mit reinen Zufallszahlen
Da im Verlauf des GA's immer neue Populationen mit starkem Einfluß des Zufalls entstehen, ist die Frage berechtigt, ob denn dieselben Ergebnisse (oder gar bessere) auch erzielt werden können, wenn einfach ebensoviele Individuen wie der GA sie auch produziert per Zufall generiert werden.
Um dies zu überprüfen, haben wir eine Beispieldatei mit 24 Blöcken erstellt, die der GA mit einer Population von 100 Individuen 50 Generationen lang verbesserte, sowie eine Population von 100*50 = 5000 Individuen per Zufall.
Es zeigte sich, daß der GA eindeutig das bessere Ergebnis
liefert, er konnte (i.d.R. bereits nach einer Generation) die
Blöcke besser anordnen als das beste Individuum der Zufallspopulation
dies ergeben hätte.
Eine Lösung wird hierbei als eine Folge von Chromosomen definiert. Diese enthalten Teilinformationen in mehreren in ihnen enthaltenen Genen. Die Lösung wird mit einem Fitness-Wert bewertet (Fitness und Chromosom bilden ein Individuum).
Die Gesamtheit der Chromosomen einer Generation
bildet eine Population. In jeder Generation
ändert sich mittels Vererbungsmechanismen die Population,
damit auch die in ihr enthaltenen Chromosomen und somit entstehen
i.d.R. neue Lösungen, die im günstigen Fall besser sind
als bisherige.
Grundlegende Vererbungsmechanismen
für den GA: Die grundlegenden Mechanismen eines GA sind Reproduktion,
Kreuzung und Mutation.
[weitere Mechanismen]
Reproduktion: Ein Chromosom wird
unverändert in die nächste Population vererbt.
Kreuzung: Aus zwei Chromosomen wird mit einer Kreuzungsstrategie ein/mehrere Chromosom(en) erstellt.
Beispiel: Gene bestehen aus Bits,
Chromosomen: a=110101000 b=010011110, Kreuzung
nach dem fünften Gen => a'=110101110 b'=010011000.
Die Teile nach dem Kreuzungspunkt wurden vertauscht.
Mutation: Ein Chromosom wird
zufällig abgeändert.
Weitere Mechanismen: Von
uns genutzt wurden außer den grundlegenden Vererbungsmechanismen
auch noch die Selektion und das Scaling der
Fitness-Werte. [grundlegende Mechanismen].
Selektion: Bei der Auswahl für
Kreuzung und Reproduktion werden
die Chromosomen mit einer besonders
hohen Fitness bevorzugt.
Scaling: Der genetische Algorithmus arbeitet
besonders effektiv, wenn die Fitness-Werte
in etwa gleich über einen bestimmten Wertebereich verteilt
sind. Einzelne besonders gute Individuen würden
dafür sorgen, daß alle anderen Individuen fast gar
nicht mehr selektiert werden, was leicht
zu lokalen Maxima führt. Ebenso kann nicht günstig selektiert
werden, wenn z.B. die Fitness-Werte alle einen sehr hohen Wert
haben und sehr nah beieinander liegen. Dies hätte zur Folge,
daß alle etwa gleich oft ausgewählt werden, wobei hier
eine stärkere Differenzierung wünschenswert wäre.
Mittels einer Skalierung aller Fitness-Werte kann dem abgeholfen
werden.
PMX-Operator: Bei der Codierung der Gene als Nummer eines Blockes ist ein einfaches Vertauschen von Teilen der beiden zu kreuzenden Chromosomen (siehe Beispiel oben) nicht mehr möglich, da sich oft ungültige Lösungen ergeben.
Beispiel: Gene sind ganze Zahlen ab 1, Chromosomen: a=12345 b=42531, Kreuzung nach dem dritten Gen => a^=12331 b^=42545.
Problem: In a^ tauchen die Gene 1 und 3 zweimal auf. Sie dürfen aber nur einmal vorkommen (Blöcke 1 und 3 zweimal anordnen und Blöcke 4 und 5 dafür rauswerfen ist keine gültige Lösung). Gleiches gilt für b^.
Beim PMX-Verfahren werden zwei Kreuzungspunkte bestimmt. Die Gene zwischen diesen beiden Punkten werden ausgewechselt. Vorher werden aber in den einzelnen Chromosomen die Gene, die ausgewechselt werden sollen, mit dem Gen ausgetauscht, welches dem neu hereinkommenden entspricht.
Beispiel: Chromosomen: a=12345 b=42531, Kreuzung nach dem ersten und vor dem vierten Gen =>
Austausch von 3 und 5 in a, Austausch von 3 und 5 in b (der Tausch
2 gegen 2 in beiden bewirkt keine Änderung) => a'=12543
b'=42351. In beiden Chromosomen steht jetzt zwischen den Kreuzungspunkten
der Teil, der vor der Kreuzung zum anderen Chromosom gehörte.