Downhill-Simplex-Verfahren

aus Wikipedia, der freien Enzyklopädie
Wechseln zu: Navigation, Suche

Das Downhill-Simplex-Verfahren oder Nelder-Mead-Verfahren[1] ist im Unterschied zum Namensvetter für lineare Probleme (Simplex-Algorithmus) eine Methode zur Optimierung nichtlinearer Funktionen von mehreren Parametern. Er fällt in die Kategorie der Hillclimbing- oder Downhill-Suchverfahren. Angewendet werden kann er z. B. auch beim Kurvenfitten.

Es wurde von John Nelder und Roger Mead 1965 eingeführt.

Grundlagen[Bearbeiten]

Beispiellauf mit Rosenbrock-„Bananenfunktion“
Beispiellauf mit Himmelblau-Funktion

Als Besonderheit kommt dieser Simplex-Algorithmus ohne Ableitungen der Funktion nach den Parametern aus, welche bei manchen Problemstellungen nicht mit vertretbarem Aufwand berechenbar sind. Durch sinnvolle Vergleiche der Funktionswerte mehrerer Punkte im Parameterraum wird ähnlich wie bei einer Regula falsi mit Schrittweitensteuerung die Tendenz der Werte und der Gradient Richtung Optimum angenähert. Das Verfahren konvergiert nicht extrem schnell, ist aber dafür einfach und relativ robust.

Der Algorithmus basiert auf einem Simplex, dem einfachsten Volumen im N-dimensionalen Parameterraum, das aus N+1 Punkten aufgespannt wird. Im Eindimensionalen auf der Zahlengeraden ist das eine Strecke, im Zweidimensionalen ein Dreieck usw. Jeder Punkt entspricht dabei einem (Koordinaten-)Satz Parameter (bzw. Variablen), und zu jedem Punkt kann man einen Funktionswert berechnen, den man z. B. als Fehler- oder Kostenwert ansehen kann. Unter diesen N+1 Punkten ermittelt man dann den „schlechtesten“ und den „besten“. Von einem Iterationsschritt zum nächsten wird normalerweise der „schlechteste“ dieser Punkte durch einen neuen ersetzt, der hoffentlich „besser“ ist. Den „besten“ Punkt behält man als bisher beste Lösung immer bei.

Bei einer Visualisierung im Zweidimensionalen mit Höhenlinien für die zu optimierende Funktion sieht man (in den Bildern rechts) ein Dreieck, das sich zielstrebig dem Optimum nähert, indem es sich um eine seiner Seiten klappt, sich streckt oder in der Nähe des Optimums um dieses zusammenzieht. Siehe auch die Abbildung ganz unten.

Ohne Ableitungen entfallen diverse Fallen wie pathologisches Verhalten dieser Ableitungen bei Unstetigkeiten, sodass das Verfahren zwar im Allgemeinen langsamer konvergiert (nämlich linear) als Optimierungsverfahren mit Ableitungen, aber wesentlich robuster arbeitet. Die möglichen Fallen beim Simplex-Verfahren sind ähnlich wie bei den meisten Optimierungsverfahren unverhoffte lokale Nebenminima (bzw. -optima), auf die konvergiert werden kann, oder dass das Verfahren sich für einen oder mehrere Parameter zu früh auf einen konstanten Wert zusammenzieht.

Der Algorithmus selbst[Bearbeiten]

Flowchart des Downhill-Simplex Verfahrens

Das Downhill-Simplex-Verfahren findet das Optimum einer Funktion mit N Variablen (Parametern). Das Verfahren besteht aus drei Schritten:

  1. Es werden N+1 Anfangspunkte gewählt (Beispiel: Man wählt einen Punkt als Startpunkt und ermittelt N weitere, indem man von diesem aus jeweils einen der Parameter um einen bestimmten Faktor − z. B. 1,1 − erhöht; alternativ: Man gibt für jeden Parameter zwei verschiedene Werte vor, berechnet den ersten Punkt mit allen Erstwerten und die anderen N Punkte auch mit den Erstwerten, nur dass jeweils einer der Parameter durch seinen Zweitwert ersetzt wird).
  2. Die Funktionswerte an diesen Punkten werden berechnet und der beste und der schlechteste unter ihnen bestimmt.
  3. Ist der beste der N+1 Funktionswerte gut genug, wird das Verfahren mit diesem Wert beendet. Ansonsten wird der Punkt mit dem schlechtesten Funktionswert durch einen neuen Punkt ersetzt und das Verfahren bei Schritt 2 weitergeführt.

Der Kern des Verfahrens nach Nelder und Mead besteht in der Strategie, mit der im dritten Schritt der neue Simplex gebildet wird. Nelder und Mead haben dazu folgende Regeln aufgestellt:
Immer den schlechtesten Punkt am (Mittelpunkt des) restlichen Simplex reflektieren (Reflexion, unter Benutzung des Faktors alpha).
a) Wenn dieser Punkt besser ist als alle anderen, mache einen weiteren, noch größeren Schritt (Faktor gamma) in dieselbe Richtung (Expansion) und berechne den zugehörigen Funktionswert.

b1) Wenn auch dieser Punkt besser als alle anderen ist, akzeptiere den Punkt aus der Expansion und beginne von vorne;
b2) wenn nicht, wähle den reflektierten Punkt als neuen Punkt und beginne von vorne. (Reflexion)

c) Wenn der reflektierte Punkt nicht besser als alle anderen ist, überprüfe, ob der reflektierte Punkt besser als der zweitschlechteste Punkt des Simplex ist:

d1) Wenn ja, ersetze den schlechtesten Punkt durch den reflektierten Punkt und starte von vorne. (Reflexion)
d2) Wenn nein, überprüfe ob der reflektierte Punkt besser ist als der alte.
e) Wenn ja, tausche zunächst den alten Punkt durch den reflektierten aus.
f) In beiden Fällen berechne einen neuen Punkt, indem der nun schlechteste Punkt mit dem Faktor beta näher an den Mittelpunkt der anderen Punkte heran gerückt wird. (Kontraktion)
g1) Ist dieser Punkt besser als der vormals schlechteste, starte von vorne.
g2) Wenn dieser neue Punkt eine weitere Verschlechterung bringt, ersetze alle Punkte P_i des Simplex durch  (P_i+P_{best})/2 , rücke also alle Punkte an den besten Punkt heran und beginne den Algorithmus wieder von vorn (Komprimierung).

Die Koordinatenveränderungen der Parameter während dieser Schritte werden mittels der Nelder/Mead-Parameter alpha, beta und gamma berechnet, die normalerweise als 1 („Reflexion“), 1/2 („Kontraktion“ und „Komprimierung“) und 2 („Expansion“) angesetzt werden.

Nach diesen Regeln wird die Iteration so lange weitergeführt, bis ein Konvergenzkriterium erfüllt ist. Der Simplex wird sich dabei in Richtung des Optimums verlagern und sich schließlich um dieses herum zusammenziehen.

Erweiterungen für Notfälle[Bearbeiten]

Wie bei anderen Verfahren besteht immer die Gefahr, dass auf ein Nebenoptimum konvergiert wird. Diese Gefahr kann man durch mehrere Maßnahmen mindern:

  • Nach einer bestimmten Anzahl von Schritten wird neu angesetzt. Dazu wird der bisher beste Punkt beibehalten und um ihn herum ein neuer Start-Simplex aufgebaut, indem bei jedem weiteren Punkt die Koordinate (nur) eines Parameters z. B. um 5 % variiert wird.
  • Genau wie eben, nur werden die zusätzlichen Simplex-Punkte durch Zufallswerte noch mehr flexibilisiert.
  • Statt solche Maßnahmen nach einer festen Anzahl von Schritten zu ergreifen, kann man den Zeitpunkt auch vom aktuellen Stand der Iteration abhängig machen. Dazu muss das Problem natürlich besser bekannt sein, dass man sich dessen auch sicher sein kann.
  • Alternativ kann man sogar den Zeitpunkt der Neuansetzung per Zufallszahl variabel gestalten.
  • Eine zusätzliche Sicherheitsabfrage könnte den Fall abfangen, dass sich nur einer der Parameter vorzeitig festgelaufen hat, dann könnte man ihm eine Spezialbehandlung zukommen lassen.

All diese Varianten erfordern eine intensive Beschäftigung mit dem jeweiligen konkreten Problem, es gibt leider keine allgemein gültigen Kochrezepte dafür.

Erweiterung zum Kurvenfitten[Bearbeiten]

Will man nicht eine einzelne analytische Funktion von N Parametern (Koordinaten) optimieren, sondern eine Theoriefunktion an eine Messkurve anpassen, ist nur ein kleiner zusätzlicher Schritt notwendig: Als zu optimierende Funktion verwendet man oft die Fehlerquadratsumme, also die Summe aus den Quadraten der Differenzen aus den einzelnen Messwerten (Kurvenpunkten) und den Theoriefunktionswerten an denselben Stellen, wobei letztere einerseits von der x-Koordinate der Messkurve abhängen und zusätzlich von den N Parametern. Ob man aus der Fehlerquadratsumme noch die Wurzel zieht, ist für das Simplex-Verfahren irrelevant. Siehe hierzu auch Methode der kleinsten Quadrate.

Jedoch ist das Downhill-Simplex-Verfahren sehr flexibel (anders als etwa der Levenberg-Marquardt-Algorithmus), man kann also je nach Situation auch andere Funktionen zur Optimierung verwenden:

  • Die Daten haben eine starke Streuung: Statt der Summe der quadrierten Abweichung minimiert man die Summe der Beträge der Abweichungen. Ausreißer beeinflussen das Ergebnis weniger.
  • Kurvenanpassung arbeitet normalerweise mit der Annahme, dass der Fehler der abhängigen Variable (Δy = ycalc - yobs) unabhängig von der zugehörigen unabhängigen Variable ist (Homoskedastizität). In manchen Fällen ist jedoch Δy eine Funktion von x. Diese Situation tritt insbesondere dann auf, wenn sich x und y über mehrere Größenordnungen erstrecken. Würde man in solchen Fällen die Fehlerquadratsumme minimieren, bekäme man eine gute Anpassung bei großen x/y-Werten, aber eine sehr schlechte bei kleinen, die zur Fehlerquadratsumme kaum beitragen. In solchen Fällen minimiert man χ2, also die Summe der quadrierten relativen Abweichungen Σ(Δy/y)2.

Beispielimplementierung[Bearbeiten]

Pseudocode (C-ähnlich):

// Deklarationen
      
// Benutzervorgaben, -eingaben
int NP= ... ;        // Anzahl der Parameter, die in FQS() eingehen,
                     // auch "Dimension des Parameterraums"

double ParP[NP]={ ... }; // primärer Parametersatz
double ParS[NP]={ ... }; // sekundärer Parametersatz, gibt Schwankungsbreite vor

double FQS(int pf)   // Zu minimierende Funktion, hängt
  {                  // von den Parametern Par0[pf][...] ab.
    ...              // Muss je nach Problemstellung erstellt werden.
                     // So schreiben, dass sie für den Parametersatz Nr. pf
  }                  // berechnet und am Schluss in Par0[pf][0] (s. u.)
                     // abgespeichert wird.

// Ende Benutzervorgaben

// Initialisierung

// Simplex-Parameter
double NA = 1, NB = .5, NC = 2; // Alpha, Beta, Gamma

// Parametersätze, NP+1 Stück für den Simplex selbst, plus 3 weitere
double Par0[NP+4][NP+1];
// darin 1. Index: 0..NP die (NP+1) Parametersätze des Simplex,
//                       dabei auf 0 immer der schlechteste Punkt des Simplex,
//                             auf 1 immer der beste Punkt des Simplex
//                 NP+1  Punkt P*, s. u.
//                 NP+2  Punkt P**, s. u.
//                 NP+3  Mittelpunkt P-Quer des Simplex, s. u.
// darin 2. Index: 0     Fehlerwert für diesen Satz
//                 1..NP einzelne Parameterwerte dieses Satzes

// Initialisierung

// Simplex aus Primär- und Sekundärwerten
Parametersätze 0 bis NP mit Primärwerten belegen;
In Parametersätzen 1 bis NP (i) jeweils
   Parameter Nr. i mit Sekundärwert belegen;

// Berechnung der Anfangs-Fehlersummen
für Parametersätze 0 bis NP: feh=FQS(i); // speichert Ergebnis in Par0[i][0], s. o.

// Schleife für Iteration, Hauptschleife
bool fertig = false;
while (!fertig)
    NT++; // Simplex-Schritt Nr. NT
    
    // schlechtesten und besten Punkt ermitteln (höchster bzw. niedrigster Fehlerwert)
    Werte fmin und fmax auf Fehlerwert von Satz Nr. 0 vorbelegen;
    für alle Parametersätze von 1 bis NP:
       wenn Fehler für Satz Nr. i größer als fmax:
          noch schlechterer Punkt, fmax auf diesen Wert, Punkt mit Nr. 0 tauschen
       wenn Fehler für Satz Nr. i kleiner als fmin:
          noch besserer Punkt, fmin auf diesen Wert, Punkt mit Nr. 1 tauschen

    // Konvergenzabfrage, sind wir fertig?
    // Dies muss auch je nach Problem individuell erstellt werden,
    // hier als Beispiel die Bedingung, dass alle Parameter nur noch um 1 ‰
    // schwanken.

    fertig = true;
    für alle Parameter in allen Parametersätzen von 0 bis NP:
       wenn nur für einen Parameter die relative Abweichung des Wertes
          zwischen schlechtestem und bestem Punkt größer als 0.001, dann:
             fertig=false; // doch noch nicht fertig

    //Wenn fertig, dann beende mit bestem Punkt Par0[1][1..3] als Ergebnis des Algorithmus

    // Mittelpunkt P-QUER des Simplex nach Index NP+3
    Mittelwerte über Sätze 1 bis NP (also ohne schlechtesten Punkt Nr. 0!) bilden
       und als Satz Nr. NP+3 speichern;

    // Punkt P* durch Reflexion des schlechtesten Punkts 
    // am Mittelpunkt P-QUER (mit Nelder/Mead-Parameter alpha=NA) nach Index NP+1
    innerhalb eines Parametersatzes:
       Par0[NP+1][i]=(1 + NA) * Par0[NP+3][i] - NA * Par0[0][i];
    fs=Par0[NP+1][0]=FQS(NP+1); // und Fehler berechnen
    
    // Fallunterscheidung, ob Verbesserung erreicht
    wenn fs<fmin // neuer Punkt P* im Vergleich zu bisher bestem
    /  // ja, besser!
    |  // weiteren Schritt zu Punkt P** in dieselbe Richtung nach Index NP+2
    |  // (mit Nelder/Mead-Parameter gamma=NC)
    |  innerhalb eines Parametersatzes:
    |     Par0[NP+2][i]=(1 + NC) * Par0[NP+1][i] - NC * Par0[NP+3][i];
    |  fs=Par0[NP+2][0]=FQS(NP+2); // und Fehler berechnen
    |
    |  wenn fs>=fmin // wieder Vergleich mit bisher bestem Punkt
    |  /  // keine weitere Verbesserung: P** verwerfen, P* nach Index 0,
    |  |  // also bisher schlechtesten Wert durch neuen (etwas besseren) ersetzen
    |  \  Parametersatz Nr. NP+1 nach Nr. 0 kopieren;
    |  else // von fs>=fmin
    |  /  // ja, auch eine Verbesserung!
    |  |  // auch besser als P* ?
    |  |  wenn fs>Par0[NP+1][0]
    |  |  /  // nein, P* weiterverwenden und damit bisher schlechtesten auf Index 0 ersetzen
    |  |  \  Parametersatz Nr. NP+1 nach Nr. 0 kopieren;
    |  |  else // von fs>Par0[NP+1][0]
    |  |  /  // ja, P** weiterverwenden und damit bisher schlechtesten auf Index 0 ersetzen
    \  \  \  Parametersatz Nr. NP+2 nach Nr. 0 kopieren;

    else // von fs<fmin
    /  // nicht besser als bisher bester, nun prüfen, ob P* wenigstens überhaupt eine Verbesserung
    |  wenn fs von P* für wenigstens einen der Punkte 0..NP kleiner ist, dann:
    |  /  // ja, besser als mindestens einer, mit P* den bisher schlechtesten Punkt auf Index 0 ersetzen
    |  \  Parametersatz Nr. NP+1 nach Nr. 0 kopieren;
    |  else // von fs von P*
    |  /  // nein, weiterhin schlechter als die anderen Punkte
    |  |  // Zusatzprüfung: sogar schlechter als bisher schlechtester Punkt fmax?
    |  |  wenn fs>fmax // schlechter als bisher schlechtester?
    |  |     // ja, erstmal nichts tun
    |  |  else // von fs>fmax
    |  |  /  // nein, also wenigstens bisher schlechtesten Punkt damit ersetzen
    |  \  \  Parametersatz Nr. NP+1 nach Nr. 0 kopieren;
    |  // neuer Punkt P** durch Kontraktion zwischen bisher schlechtestem Punkt (Index 0)
    |  // und P-QUER (Index NP+3) nach Index NP+2 (mit Nelder/Mead-Parameter beta=NB)
    |  innerhalb eines Parametersatzes:
    |     Par0[NP+2][i]= NB * Par0[0][i] + (1 - NB) * Par0[NP+3][i];
    |  fs=Par0[NP+2][0]=FQS(NP+2); // und Fehler berechnen
    |  // P** besser als bisher schlechtester Punkt?
    |  wenn fs<fmax // besser als bisher schlechtester?
    |  /  // ja, bisher schlechtesten durch P** ersetzen
    |  \  Parametersatz Nr. NP+2 nach Nr. 0 kopieren;
    |  else // von fs<fmax
    |  /  // nein, keinerlei Verbesserung, Notfall
    |  |  // folgt allgemeine Kompression um Faktor 2 um den bisher besten Punkt herum
    |  |  für alle Parametersätze außer Nr. 1 (bisher bester): // also j=0 und 2..NP
    |  |     innerhalb eines Parametersatzes j:
    |  |        Par0[j][i]=(Par0[j][i] + Par0[1][i]) / 2;
    \  \     fs=Par0[j][0]=FQS(j); // und Fehler berechnen
    // Schleifenende


Eine direkt lauffähige Implementierung in QBasic ist unter [2] zu finden.

Grafische Darstellung eines Beispiellaufs[Bearbeiten]

Beispielergebnis

Das Bild zeigt das Ergebnis eines Programmlaufs. Die roten und blauen Linien stellen die Gefällelinien für Täler bzw. Berge dar. Es handelt sich um einen Kreisgraben in einer schrägen Ebene, es interessiert hier das lokale Minimum innerhalb des Kreisgrabens. Die Simplex-Iteration findet das Minimum an der Kreuzungsstelle der roten Linien. In x- und y-Richtung reicht die Grafik von 0 bis 4.

Literatur[Bearbeiten]

Einzelnachweise[Bearbeiten]

  1. Nelder, Mead A simplex method for function minimization, Computer Journal, Band 7, 1965, S. 308–313
  2. Implementierung in QBasic