By Lothar Jung
Date 2023-11-30 10:20
Edited 2023-11-30 10:23
Upvotes 1
Ich habe mir die statistische Bewertung, auch für Schach, genauer erklären lassen:
Die statistische Bewertung in einem MCTS (Monte Carlo Tree Search) Algorithmus wird in den Knoten des Entscheidungsbaums gespeichert. Jeder Knoten im Baum repräsentiert einen bestimmten Zustand oder eine bestimmte Konfiguration des Spiels, und die damit verbundenen statistischen Daten geben Aufschluss über die bisherigen Erfahrungen des Algorithmus mit diesem Zustand. Hier sind die Schlüsseldetails zur Speicherung dieser statistischen Bewertungen:
1. **Struktur eines Knotens:** Jeder Knoten im Baum enthält typischerweise die folgenden statistischen Informationen:
- **Anzahl der Besuche (Visits):** Diese Zahl gibt an, wie oft der Knoten während der MCTS-Durchläufe besucht wurde. Sie dient als Maß für die Erfahrung des Algorithmus mit diesem spezifischen Spielzustand.
- **Gewinn-Statistik (Wins):** Hier wird aufgezeichnet, wie oft die von diesem Knoten ausgehenden Entscheidungen zu einem erfolgreichen Ergebnis (z.B. einem Gewinn) geführt haben.
2. **Speicherung der Daten:** Die Daten werden direkt in den Knoten des Baumes gespeichert. Wenn der Algorithmus einen Knoten während des Auswahl- oder Simulationsprozesses besucht, werden die entsprechenden Daten des Knotens aktualisiert.
3. **Aktualisierung während der Rückmeldung (Backpropagation):** Nachdem eine Simulation abgeschlossen ist, werden die Ergebnisse (Gewinn, Verlust oder Unentschieden) entlang des Pfades von den Blattknoten (Endpunkte der Simulation) zurück zur Wurzel des Baumes weitergegeben. Dabei werden die Besuchs- und Gewinn-Statistiken jedes Knotens entlang dieses Pfades aktualisiert.
4. **Datenstruktur:** In der Praxis wird diese Information oft in einer Datenstruktur wie einem Objekt oder einem Record in der Programmiersprache gespeichert, in der der MCTS-Algorithmus implementiert ist. Jeder Knoten hat seine eigenen Attribute für Besuche und Gewinne, die bei jedem Durchlauf des Algorithmus angepasst werden.
5. **Zugriff und Analyse:** Diese gespeicherten Daten werden dann verwendet, um Entscheidungen über zukünftige Züge zu treffen. Beispielsweise verwendet der Algorithmus die UCB1-Formel, um zu entscheiden, welcher Knoten als nächstes besucht werden soll, basierend auf den gespeicherten statistischen Daten.
Durch diese Methode der Speicherung und Aktualisierung von statistischen Daten kann der MCTS-Algorithmus "lernen" und sich im Laufe der Zeit verbessern, indem er die Ergebnisse früherer Simulationen und Entscheidungen berücksichtigt.
Bei der Anwendung des Monte Carlo Tree Search (MCTS) Algorithmus im Schachspiel wird die Datenstruktur für jeden Knoten im Entscheidungsbaum so gestaltet, dass sie alle relevanten Informationen über den jeweiligen Spielzustand und die statistischen Bewertungen der Züge enthält. Hier ist ein Überblick über die typischen Elemente einer solchen Datenstruktur:
1. **Spielzustand:** Jeder Knoten repräsentiert einen bestimmten Zustand des Schachspiels. Dies umfasst die Position aller Figuren auf dem Brett, den Spieler am Zug, den Status von Sonderregeln wie Rochade oder En-passant, sowie Informationen über Schach, Schachmatt oder Patt.
2. **Zuginformationen:** Jeder Knoten speichert Informationen über den Zug, der zu diesem Zustand geführt hat. Dies kann in Form einer Notation wie der Standard Algebraic Notation (SAN) oder der Universal Chess Interface (UCI) Notation geschehen.
3. **Statistische Bewertungen:**
- **Anzahl der Besuche (Visits):** Diese Zahl gibt an, wie oft der Knoten während der MCTS-Durchläufe besucht wurde.
- **Gewinn-Statistik (Wins):** Hier wird aufgezeichnet, wie oft die von diesem Knoten ausgehenden Entscheidungen zu einem erfolgreichen Ergebnis (z.B. einem Gewinn) geführt haben.
4. **Kinder (Children):** Jeder Knoten hat eine Liste oder ein Array von Kindknoten, die alle möglichen Züge vom aktuellen Spielzustand aus repräsentieren.
5. **Weitere Informationen:** Je nach Implementierung können zusätzliche Informationen wie die Bewertung des Spielzustands durch eine Heuristik, die Historie der Züge bis zu diesem Punkt oder spezifische Spielstrategien gespeichert werden.
### Beispiel in Python
Hier ist ein einfaches Beispiel, wie eine solche Datenstruktur in Python aussehen könnte:
```python
class ChessNode:
def __init__(self, game_state, move=None):
self.game_state = game_state # Der aktuelle Zustand des Schachspiels
self.move = move # Der Zug, der zu diesem Zustand geführt hat
self.visits = 0 # Anzahl der Besuche
self.wins = 0 # Anzahl der Gewinne
self.children = [] # Liste der Kindknoten
# Weitere Methoden zur Verwaltung des Knotens können hier hinzugefügt werden
```
In dieser Struktur ist `game_state` ein Objekt, das den aktuellen Zustand des Schachspiels repräsentiert. Es könnte Informationen über die Position der Figuren, den Spieler am Zug und andere relevante Spielzustände enthalten. `move` repräsentiert den Zug, der zu diesem Knoten geführt hat, und könnte in einer Schachnotation wie SAN oder UCI ausgedrückt werden.
Diese Datenstruktur ermöglicht es dem MCTS-Algorithmus, den Entscheidungsbaum effizient zu durchsuchen und zu bewerten, um die besten Züge im Schachspiel zu finden.
Kommen wir also nun zur ersten Phase, der Auswahl bzw. Selektion.
Hier bestimmen wir, welcher Ast des Suchbaums als nächstes un einen Halbzug tiefer untersucht wird.
Wie entscheiden wir das aber?
Denkbar wäre eine zufällige Auswahl, was aber nicht wirklich zielführend wäre.
Würden wir immer nur den Zügen mit der besten Bewertung folgen, würden andere Züge,
die (bisher) schlechter bewertet sind, vernachlässigt.
Würden wir darauf schauen, dass möglichst alle Züge gleich tief untersucht werden,
kämen wir nicht sehr tief und wären damit nicht wettbewerbsfähig.
Dies bringt uns zur UCB1-Formel.
Diese sorgt für eine ausgewogene Balance zwischen Exploration, also einer breiteren Suche,
und Exploitation, also einer tieferen Suche. Dies lässt sich mit der sog. Explorations-Konstante C
sogar in gewissem Maße steuern.
Ein wesentlicher Bestandteil der UCB1-Formel ist die Bewertung einer Position.
Diese ist der Durchschnitt aller gesammelten Bewertungen! (Also die Summe der Bewertungen durch die Anzahl der Visits.)
Dies ist einer der wesentlichen Unterschiede zu Alpha-Beta, wo ja immer der beste Wert genommen wird.
Die UCB1-Formel ist mehr oder weniger der Standard. Man spricht dann von einer UCT.
Bei Lc0 werden dann noch zusätzlich die Gewinnwahrscheinlichkeiten für die einzelnen Züge in Betracht gezogen.
Dann wird aus der UCT eine pUCT.
Wer hier tiefer in die Materie eintauchen will, sollte in den von Lothar zusammengestellten Links fündig werden.
By Lothar Jung
Date 2023-12-28 13:04
Edited 2023-12-28 13:08
Upvotes 1
Jetzt folgt die Phase der Expansion.
Hier machen wir nichts anderes, als dass wir an der vorher ausgesuchten Endposition eine oder mehrere oder auch alle Kind-Positionen hinzufügen.
Fügen wir für alle legalen Züge die entsprechenden Folgepositionen hinzu, dann bezeichnet man diese Position auch als "fully expanded".
Bei der ProofNumberSearch z. B. macht man genau das.
Der Nachteil ist klar, der Speicherbedarf steigt drastisch an.
Deshalb fügt man meistens nur eine Kindposition pro Durchgang hinzu.
Es ist dann aber auch immens wichtig, für welchen Zug man die entsprechende Position hinzufügt.
Natürlich wird man versuchen, immer den besten Zug als erstes zu wählen.
Denn wie wir ja im vorhergehenden Schritt gelernt haben, spielt die Bewertung einer Position
eine wichtige Rolle bei der Auswahl der Endposition, die wir tiefer untersuchen wollen.
Würde man nun also zuerst einen (oder mehrere) der schlechten Züge hinzufügen, würde diese Position
eventuell nicht mehr so schnell zur weiteren Untersuchung ausgesucht werden. Obwohl genau hier noch ein sehr guter Zug verfügbar ist!
Die spannende Frage ist nur, wie und woher vorher wissen?
Und da kommt bei Lc0 wieder der policy head ins Spiel, bei dessen Neuronalem Netz ja nicht nur die Bewertung (value head)
einer Position verfügbar ist, sondern eben auch die Gewinnwahrscheinlichkeiten für alle legalen Züge einer Position.
Sprich, wie wichtig oder erfolgversprechend die einzelnen Züge sind!
Kommen wir nun zur nächsten Phase, der Simulation.
Die gerade neu hinzugefügte(n) Position(en) müssen in irgendeiner Form bewertet werden.
Die eigentliche Grundidee einer MCTS ist nun, dass ausgehend von der neuen Position randomisierte Playouts oder Simulationen, also Spiele, ausgeführt werden. Das kann nur 1 Playout sein, oder aber auch mehrere Hunderte oder noch mehr.
Der große Vorteil besteht darin, dass kein domain-spezifisches Wissen vorhanden sein muss, außer natürlich dem grundlegenden Wissen über Gewinn, Unentschieden (falls möglich), und Verlust.
Diese Vorgehensweise ist relativ erfolgreich in vielen unterschiedlichen Einsatzgebieten von MCTS.
Nicht jedoch im Schach, zumindest soweit ich weiß.
Im Grunde könnte man dies ja auch in AB-Engines an Stelle einer Bewertungsfunktion hernehmen,
aber mir ist kein solcher Ansatz, noch dazu erfolgreich, bekannt.
Generell würde ich hier wie für AB-Engines eine Ruhesuche setzen.
Dies erscheint mir am sinnvollsten.
Vorstellbar wäre aber auch eine rein statische Bewertung (HCE oder NNUE), alternativ plus einer Static Exchange Evaluation (SEE),
um mögliche Schlagabtäusche besser einschätzen zu können.
Lc0 verlässt sich hier ganz auf die vom NN gelieferte Bewertung (value head).