Das Wissen, das Du jetzt brauchst!
Die neue Version 18 bietet völlig neue Möglichkeiten für Schachtraining und Analyse: Stilanalyse von Spielern, Suche nach strategischen Themen, Zugriff auf 6 Mrd. LiChess-Partien, Download von chess.com mit eingebauter API, Spielervorbereitung durch Abgleich mit LiChess-Partien, eingebaute Cloud-Engine u.v.m..
Wenn man als Schachspieler Informatik studiert, hat man irgendwann die Idee, selbst eine Schachengine zu programmieren. Das ging mir auch so in den 80er Jahren, aber ich bin zu Unizeiten nicht dazu gekommen, das umzusetzen. Gründe gab es viele, zu wenig Zeit, keine vernünftige Literatur verfügbar und so weiter. Aber wahrscheinlich liegt der wahre Grund in meiner Faulheit. So habe ich nicht mal angefangen mit der Programmierung.
Vor einiger Zeit bin ich auf einen interessanten Artikel gestoßen [Stöckel22], in dem der Autor Andreas Stöckel beschreibt, wie er ein einfaches Schachprogramm innerhalb einer Stunde programmiert hat – mit Hilfe des AI Chat Programms ChatGPT. Der Artikel hat mich motiviert, mich nochmal mit dem Thema zu beschäftigen.
Eine reines Nachprogrammieren des Artikels wäre kein spannendes Projekt gewesen. Ich habe mir daher folgende Ziele gesetzt für die Programmierung einer Schachengine:
Wie bereits erwähnt, hatte ich mir als Ziel gesetzt, die Engine an einem Wochenende zu erstellen. Als dann in diesem Herbst das Wetter schlechter wurde, habe ich dann an einem Samstagmorgen losgelegt.
Eine nicht so fleißige Person wie ich delegiert gerne Aufgaben an andere. Leider stand für diese Arbeiten niemand zur Verfügung, an den ich hätte delegieren können. Und einen programmierenden Roboter wie in der nächsten Abbildung hatte ich auch nicht zur Verfügung.
Abbildung 1: Programmierender Roboter (Abbildung wurde mit Automatic1111 and Stable Diffusion erzeugt)
Daher habe ich es genauso gemacht wie Andreas Stöckl’s und habe ChatGPT (Version 3.5, kostenlos) als meinen virtuellen Programmierroboter genutzt. ChatGPT kann Programmcode erzeugen, wenn man seine Spezifikation mitteilt. Man sollte dabei beachten, dass ChatGPT dabei durchaus menschliches Verhalten zeigt. Es empfiehlt sich, ChatGPT am Anfang den Kontext eines Projektes zu erklären, bevor man in die Details geht. Außerdem macht ChatGPT schonmal Fehler wie ein Mensch. Das kann zu unangenehmen Überraschungen führen, wie wir später noch sehen werden.
Mit diesem Ansatz habe ich mich dann an die Arbeit gemacht. In diesem Artikel werde ich beschreiben, wie es mir dabei an dem Wochenende ergangen ist.
Vorbemerkungen:
Wenn man einen Artikel über Schachprogrammierung schreibt, dann besteht die Gefahr, dass es schnell sehr technisch wird und man sich in Details verliert. Daher habe ich folgenden Ansatz gewählt:
Wie bereits erwähnt fängt man ein neues Projekt mit ChatGPT damit an, den Kontext zur erklären. Die Kommunikation mit ChatGPT ist denkbar einfach. Man ruft die Webseite auf, logt sich ein (oder erstellt kostenlos ein neues Konto) und kann dann schon loslegen.
Abbildung 2: Starten eines neuen Projektes mit ChatGPT
Kurz nach Eingabe einer Anfrage erhält man schon die Antwort. ChatGPT ist sehr erfreut für mich zu arbeiten. Dann nehmen wir ihn mal beim Wort.
Abbildung 3: Die Anforderungen spezifizieren.
Bevor wir uns die Antwort anschauen, würde ich gerne meine Eingabe erklären:
Nun schauen wir uns die Antwort von ChatGPT an.
Zuerst muss die python chess Bibliothek installiert werden (pip install). Der zweite Codeblock implementiert ein Programm, das zunächst die Ausgangsstellung erzeugt und dann den Zug e2-e4 ausführt. Man beachte, dass die Engine schon darauf achtet, dass nur legale Züge (if move in self.board.legal_moves:)ausgeführt werden. Zusätzlich stellt die Engine eine sehr rudimentäre Funktion zur Darstellung der aktuellen Position bereit. Noch keine wirkliche Engine, aber etwas Geduld ist nötig, denn wir haben gerade erst begonnen und es ist noch früh am Samstagmorgen.
Wie versprochen kann jeder den Programmcode selbst ausführen. Um das zu ermöglichen habe ich ein sogenanntes Colab Notebook erstellt und den Programmcode hineinkopiert. Ein Colab Notebook kann man sich als virtuellen Computer vorstellen, der im Browser läuft und bedient wird. Die Version v01 kann man über diesen Link erreichen. Dann wählt man Laufzeit -> alles ausführen. Warnungen, dass das Notebook nicht von Google erstellt wurde, können ignoriert werden.
Nach dem ersten Codeblock erhält man die Info, dass die Bibliothek python chess erfolgreich installiert wurde. Der Output nach dem zweiten Codeblock sollte wie folgt aussehen (Großbuchstaben stellen weiße Figuren da, es werden englische Abkürzungen wie Q für Queen/Dame verwendet):
r n b q k b n r
p p p p p p p p
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
P P P P P P P P
R N B Q K B N R
r n b q k b n r
p p p p p p p p
. . . . . . . .
. . . . . . . .
. . . . P . . .
. . . . . . . .
P P P P . P P P
R N B Q K B N R
Der Output zeigt zwei Stellungen, die Ausgangsposition und die Position nach dem Zug e2-e4.
Für diese Version implementierte ChatGPT folgende Verbesserungen:
Die Eingaben, um ChatGPT zu instruieren und den von ChatGPT generierten Code findet man hier.
Nun haben wir also eine Schachengine, die 3 Halbzüge tief rechnet. Der MiniMax Algorithmus stellt sicher, dass alle Positionen bis zu dieser Tiefe analysiert werden. Die Engine sollte also in der Lage sein, alle Matts in zwei Zügen zu finden. Probieren wir das mal mit einer Studie von Paul Morphy aus (Weiß am Zug).
<DIV class=cbdiagram data-title=" Figure 5: Study from Paul Morphy" data-squares="" data-solution="" data-size="400" data-legend="" data-hint="" data-fen=" kbK5/pp6/1P6/8/8/8/8/R7 w - - 0 1" data-buttons="" data-arrows="a1a6"></DIV>
Abbildung 5: Studie von Paul Morphy
Zum Testen bauen wir die Position mittels eines FEN Strings auf (bitte im Glossar nachschlagen, wenn der Begriff nicht bekannt ist). Ich verwende dieses Tool, um für eine Stellung einen FEN String zu erzeugen. Aber es gibt viele andere Tools, die das auch können. Der Programmcode zum Testen dieser Position sieht dann wie folgt aus:
if __name__ == "__main__":
fen_position = "kbK5/pp6/1P6/8/8/8/8/R7 w - - 0 1"
chess_engine = ChessEngine_v02(fen=fen_position, search_depth=3)
# Display the board
chess_engine.display_board()
# Find the best move
eval, best_move = chess_engine.get_best_move()
print(f"Best move: {best_move}, Evaluation: {eval}")
Wie erwartet findet die Engine den Gewinnzug Ta1-a6 und bewertet ihn mit inf (inf ist in Python die höchste Zahl und wird daher für den Mattwert verwendet).
Nun machen wir den gleichen Test mit einer spiegelverkehrten Position, wo Schwarz den Zug Th8-h3 spielen sollte.
Abbildung 6: Spiegelverkehrte Position der Studie von Paul Morphy.
Diesen Test habe ich manuell zu der Main Routine hinzugefügt:
if __name__ == "__main__":
…
fen_position = "7r/8/8/8/8/6p1/6PP/5kBK b - - 0 1"
chess_engine = ChessEngine_v02(fen=fen_position, search_depth=3)
eval, best_move = chess_engine.get_best_move()
print(f"Best move: {best_move}, Evaluation: {eval}")
…
Überraschung! Die Engine gibt Th8-g8 als besten Zug zurück und bewertet die Stellung als ausgeglichen (Wert 0).
Jeder, der schon einmal programmiert hat, kennt solche Situationen. Da muss man dann in die Fehlersuche – in Fachkreisen Debugging genannt – einsteigen. Nach einigen Analysen fand ich dann den Fehler (Bug) in der Funktion get_best_move(). Diese Funktion arbeitet nur korrekt, wenn Weiß am Zug ist.
def get_best_move(self):
…
best_move = None
best_eval = float('-inf')
for move in legal_moves:
…
eval = self.minimax(self.search_depth - 1, False)
…
if eval > best_eval:
best_eval = eval
best_move = move
return best_eval, best_move
Der Zweck dieser Funktion ist es, für die Seite, die am Zug ist, den besten Zug zu finden. Um das zu erreichen, startet die Funktion zunächst mit einer Worst Case Annahme. Bei Weiß ist der Worst Case, dass er Matt gesetzt wird. Das ist der Wert -inf. Findet dann die Funktion einen Zug, der aus Sicht von Weiß besser ist, dann wird dieser gewählt. Klappt perfekt, wenn Weiß am Zug ist. Aber der Worst Case für Schwarz ist +inf (Schwarz wird mattgesetzt). Jeder Zug mit einer schlechteren Bewertung ist besser für Schwarz. Aus diesem Grund liefert die Funktion für Schwarz am Zug nicht den richtigen Zug.
Diesen Fehler zu finden, kostete einiges an Zeit. Daher sind wir jetzt am Samstagnachmittag angekommen.
Für die nächste Version gab ich ChatGPT folgenden Input:
Die Eingaben und der von ChatGPT generierten Code können hier gefunden werden.
Anmerkung: ChatGPT hat den Fehler nicht komplett korrigiert. Ich musste daher folgende Codezeile manuell ändern (nach weiterem zeitaufwendigem Debugging):
ChatGPT:
eval = self.minimax(self.search_depth - 1, false)
Manuell geändert:
eval = self.minimax(self.search_depth - 1, self.board.turn == chess.WHITE)
Nachdem ich ChatGPT auf den Fehler hingewiesen habe, bekam ich eine Entschuldigung:
Thank you for pointing out the error, and I appreciate your understanding. This correction ensures that the minimax function correctly considers whether it's the turn of the white or black player.
Wenn man nun nochmals die Stellung in Abbildung 6 testet, bekommt man das richtige Ergebnis (Th8-h3 mit einer Bewertung von -inf).
Nun wird es Zeit, eine erste Partie gegen die Engine zu spielen. Hier ist die Notation (ich spiele Weiß).
e2 – e4 Sg8 - h6 (das ist der erste Zug in der Liste der legalen Züge)
d2 – d4 Th8 – g8
Lc1 x h6 Tg8 – h8 (Die Engine zählt kein Material; daher ist der Zug aus Sicht der Engine nicht schlechter als g7xh6)
Lf1 – c4 Th8 – g8
Dd1 – h5 g7 - g6 (Die Engine sieht das drohende Matt auf f7 und verhindert es)
Dh5 – g5
Der Rest der Partie ist nicht interessant. Die Engine spielt sehr schlecht. Für die Evaluierung einer Stellung wird nur auf Schachmatt geprüft. Material oder andere Faktoren werden nicht berücksichtigt. Daher haben im dritten Zug von Schwarz Th8 und gxh6 die gleiche Bewertung. In dieser Situation wählt die Engine einfach den Zug, der in der Liste der legalen Züge als erster steht. Im 5. Zug erkennt die Engine aber korrekt das drohende Matt und wehrt es ab. Mehr konnte von dieser Version nicht erwartet werden.
Den zweiten Bug von ChatGPT zu finden hat wieder viel Zeit gekostet und hat sich bis Sonntagmittag hingezogen. Nach dem Mittagessen ging es dann mit der nächsten Version weiter.
Nun wird es Zeit, an der Stellungsevaluierung zu arbeiten. Die klassischen Ansätze hierzu wären das Zählen von Material, die Bewertung der Aktivität von Figuren oder der Königssicherheit usw. Aber ich habe mich für eine Abkürzung entschieden.
Diese besteht darin, für die Evaluierung ein bestehendes neuronales Netz zu verwenden. Da für Stockfish eine Reihe solcher Netze über diesen Link herunterladbar sind, habe ich mich für ein Stockfish Netz (auch NNUE genannt) entschieden.
Nach etwas Suchen fand ich die nnue_probe Bibliothek, die eine Schnittstelle zu NNUE-Netzen bereitstellt. Allerdings ist diese Bibliothek in C++ implementiert, so dass sie nicht direkt in Colab mittels des pip Kommandos installiert werden kann. Man muss den Source Code herunterladen und dann kompilieren. Erst danach kann man die Bibliothek verwenden. Das hat die ganze Sache schwieriger gemacht. Aber am Ende habe ich es hinbekommen. Details sind in diesem Artikel [Lorenz23] dokumentiert. Bei dieser Aufgabe war ChatGPT keine Hilfe.
Nachdem die neue Bibliothek bereitstand, mussten noch zwei Änderungen in dem Programmcode der Engine gemacht werden. Zunächst muss die neu kompilierte Bibliothek sowie ein NNUE Netz (in diesem Fall: nn-04cf2b4ed1da.nnue) geladen werden. Der Code dafür sieht dann so aus (Änderungen in fett):
def __init__(self, initial_position_fen, max_depth=3):
self.board = chess.Board(initial_position_fen)
self.max_depth = max_depth
self.nnue = cdll.LoadLibrary("libnnueprobe.so")
self.nnue.nnue_init(b"nn-04cf2b4ed1da")
Nun kann man das NNUE Netz für die Evaluierung verwenden. Man muss nur für die aktuelle Position den FEN String ermitteln (das erledigt python chess). Und dann kann man schon die Bewertung abfragen. Dafür musste nur eine Codezeile (fett markiert) geändert warden.
def evaluate_board(self):
if self.board.is_checkmate():
return float('-inf') if self.board.turn else float('inf')
elif self.board.is_stalemate() or
self.board.is_insufficient_material():
return 0
else:
# old version: return 0
return self.nnue.nnue_evaluate_fen(bytes(self.board.fen(),
encoding='utf-8'))/-210
Diese neue Version findet man hier. Mein alter Freund Marcus Oechtering hatte die Ehre, die erste Partie zu spielen. Hier ist die Partie.
Die ersten 13 Züge hat die Engine gut gespielt und folgende Position war nun auf dem Brett.
Abbildung 7: Stellung nach dem 13. Zug
Wenn man hier einfach die Damen tauscht, ist die Stellung ausgeglichen. Leider entschied sich die Engine zu dem Zug 14.Sg5xf7, danach ist die Stellung leider verloren.
Aber was für eine Verbesserung gegenüber der Vorgängerversion. Viele der Enginezüge sehen vernünftig aus. Das gilt auch für die Eröffnung, wobei man beachten muss, dass kein Eröffnungsbuch verwendet wurde. Darauf lässt sich aufbauen.
Nach etwas Recherche bin ich der Ansicht, dass der Grund für den schlechten Enginezug Sxf7 darin liegt, dass Bewertungen für Positionen, die innerhalb einer Abtausch Sequenz erreicht werden, nicht gut funktioniert. Eine einfache Verbesserung wäre hier, die Suchtiefe dynamisch zu erweitern, wenn der letzte Zug ein Schlagen oder ein Schach war. Das wird dann eine Verbesserung für die nächste Version sein, wenn ich mal wieder ein freies Wochenende habe.
Man kann schon gegen diese Version spielen. Ich würde aber empfehlen damit bis zur nächsten Version zu warten, die ein besseres User Interface hat.
Ich war nicht besonders zufrieden mit der Qualität des von ChatGPT erzeugten Codes. Daher habe ich manuell eine finale Version erstellt, in der ich den Code etwas anders strukturiert habe. Funktionalität habe ich dabei aber nicht geändert. Außerdem habe ich die Gelegenheit genutzt, das User Interface etwas zu verbessern. Natürlich noch keine Fritzoberfläche, aber schon besser als in der Vorgängerversion.
Da Marcus Oechtering, der erste Gewinner gegen die Engine, immer ein großer Fan von Victor Kortschnoi war, habe ich diese finale Version Kortschnoi_Engine genannt (ich hoffe, dass der gute Victor sich deswegen nicht im Grab umdreht). Mittels diesen Links kann man gegen diese Version spielen.
Wenn man sich den Code anschaut, erkennt man, dass die eigentliche Engine aus weniger als 50 Zeilen Python Code besteht. Das zeigt, wie mächtig die eingesetzten Bibliotheken python-chess und nnue_probe sind.
Ihr dürft so viele Partien gegen die Engine spielen, wie ihr wollt. Solltet ihr eine interessante Partie spielen, dann last es mich bitte wissen. Beachtet aber bitte folgende Hinweise:
Wenn jemand eine eigene Python Umgebung auf seinem eigenen Computer hat, dann besteht auch die Möglichkeit, den Code herunterzuladen und lokal auszuführen. Der Source Code ist Open Source und steht unter der Lizenz GNU General Public License (GPL). Das bedeutet, dass man den Code nutzen, modifizieren und verteilen kann. GPL bedeutet aber auch, dass es keine Haftung gibt.
Wenn ich gewusst hätte, wie einfach die Programmierung einer KI Schachengine ist, dann hätte schon während meines Studiums in den 80ern damit angefangen. Nein, nur ein Scherz. Damals waren die Computer zu langsam, die Bibliotheken und ChatGPT noch nicht verfügbar und neuronale Netze ein brandneues Thema. Aber mit den heute zur Verfügung stehenden Technologien und dem ChatGPT Support hat es richtig Spaß gemacht, eine Engine zu programmieren.
Ich hatte mir für das Vorhaben den Zeitrahmen ein Wochenende gesetzt. Mit der nnue_probe Bibliothek habe ich dabei geschummelt. Die Portierung auf Colab war mich eine Herausforderung, bei der auch ChatGPT keine Hilfe war. Das lag aber auch daran, dass meine technischen Skills ziemlich eingerostet waren und ich mich in das Themen, wie man eine C++ Bibliothek kompiliert, nochmal einarbeiten musste. In Summe hat mich diese Bibliothek ein weiteres Wochenende gekostet. Aber die im Artikel beschriebenen Schritte wurden während eines Wochenendes abgeschlossen. Und wenn ChatGPT nicht so viele Bugs erzeugt hätte, dann hätte das auch nur einen Tag gedauert.
Ich will mich jetzt nicht zu sehr über ChatGPT beschweren, denn es war schon eine große Hilfe. In meinem nächsten Projekt mit ChatGPT werde ich aber den generierten Code intensiver testen.
Ich habe vor, die Engine weiter zu entwickeln und habe schon eine Menge Verbesserungsideen wie z.B.:
Wenn jemand Interesse an dem Fortschritt der Engine hat, dann einfach meine Homepage besuchen. Ich werde dort regelmäßig über Updates berichten.
Sollte jemand an einer aktiven Mitarbeit interessiert sein, dann mich bitte über meine Homepage oder über aichess.project(at)gmail.com kontaktieren. Ich würde mich über Mitstreiter freuen.
Glossar
Begriff | Definition |
AlphaZero | AlphaZero ist ein von DeepMind (Tochterunternehmen von Google) entwickeltes Computer Programm. Es ist bekannt für seine Erfolge in Brettspielen wie Schach, Shogi und Go. Das besondere an AlphaZero war, dass nur die Spielregeln implementiert wurde. Alles darüber hinaus hat es sich selbst mittels maschinellen Lernens beigebracht. |
ChatGPT | ChatGPT ist ein von OpenAI entwickeltes KI Chatprogramm. Das Hauptmerkmal von ChatGPT besteht darin, menschenähnlichen Text zu verstehen und darauf zu reagieren. Es kann auf natürliche Weise mit Benutzern in Textkonversationen interagieren. |
Colab | Colab, Kurzform für Colaboratory, ist ein freier Cloudservice von Google, der eine Python Programmierumgebung mit Bibliotheken für das maschinelle Lernen bereitstellt. Colab ist in der AI Community weitverbreitet, insbesondere bei denjenigen, die keinen Zugriff auf eigene leistungsfähige Hardware haben. |
FEN | Die Forsyth-Edwards-Notation (FEN) ist eine Standartnotation für Schachstellungen. Sie ermöglicht, eine Stellung in einer Textzeile – dem sogenannten FEN String – zu beschreiben. |
Künstliche Intelligenz (KI) | Künstliche Intelligenz bezieht sich auf die Fähigkeit von Maschinen oder Computern, Aufgaben auszuführen, die normalerweise menschliche Intelligenz erfordern. Dieses schließt das Lernen, Problemlösen, Sprachverstehen, visuelle Wahrnehmung und sogar Kreativität mit ein. Der Begriff umfasst ein breites Spektrum von Technologien und Anwendungen, von einfachen Regelbasierten Systemen bis hin zu fortschrittlichen maschinellen Lern- und Deep-Learning-Modellen. |
MiniMax | Minimax ist ein Algorithmus, der in der Spieltheorie und in der künstlichen Intelligenz (KI) verwendet wird, um den besten Zug in einem Zwei-Spieler-Spiel zu bestimmen, bei dem jeder Spieler versucht, das beste Ergebnis für sich selbst zu erzielen (Max) und das schlechteste für den Gegner (Min). |
Neuronales Netz | Ein neuronales Netz ist ein Modell, das von der Funktionsweise des menschlichen Gehirns inspiriert wurde. Es besteht aus verbundenen Knoten (Neuronen), die in Schichten organisiert sind. Es wird verwendet, um komplexe Muster und Abhängigkeiten in Daten zu lernen, und findet Anwendung in verschiedenen Aufgaben wie Mustererkennung, Klassifikation, Regression und mehr. |
Python | Python ist eine Programmiersprache, die bekannt ist für die gute Lesbarkeit und die Einfachheit des Codes. Sie wurde von Guido van Rossum entwickelt und zuerst 1991 herausgegeben. Seitdem hat sich Python zu einer der weltweit populärsten Programmiersprachen entwickelt. Python wird in vielen Bereichen eingesetzt wie Webanwendungen, künstliche Intelligenz, maschinelles Lernen und Automatisierung. |
Python Chess | Ist eine populäre Python Bibliothek, die Funktionen für die Speicherung von Schachpositionen und der Generierung von legalen Zügen in einer Stellung bereitstellt. Des weiteren unterstützt sie Standartformate wie z.B. FEN (Forsyth–Edwards Notation). |
Stockfish | Stockfish ist eine der stärksten Open Source Schachengines. Die Engine wird von Schachspielern und Entwicklern für die Analyse und als Benchmark für andere Schachengines benutzt. |
Stockfish NNUE | Stockfish Efficiently Updated Neural Network Evaluation (NNUE) ist eine Verbesserung der Stockfish Schachengine, die auf der Verwendung von neuronalen Netzen für die Bewertung von Schachstellungen beruht. |
Referenzen
[Lorenz23] Roger Lorenz: Compiling external C++ Libraries on Colab and bringing the nnue-probe library to Colab https://medium.com/p/9ce57c35679c
[Stöckel22] Andreas Stöckel: Writing a chess Program in one hour with ChatGPT https://medium.com/datadriveninvestor/writing-a-chess-program-in-one-hour-with-chatgpt-67e7ec56ba5d
Bibliotheken und Werkzeuge
Automatc1111 (verwendet für Abbildung 1.): https://github.com/AUTOMATIC1111
ChatGPT: https://chat.openai.com/
Colab: https://colab.research.google.com/
Daily Chess FEN Viewer: https://www.dailychess.com/chess/chess-fen-viewer.php
nnue-probe library: https://github.com/dshawul/nnue-probe
python: https://www.python.org/
python-chess library: https://github.com/niklasf/python-chess
stockfish: https://stockfishchess.org/
stockfish nnue: https://tests.stockfishchess.org/nns (bitte beachten, dass nur Netze funktionieren, die zwischen 20 und 21 MB groß sind)
Über den Autor
Roger Lorenz studierte Informatik in Bonn in den 1980ern und arbeitete später viele Jahre als Projektmanager und Berater. Im Ruhestand hat er nun mehr Zeit für seine Hobbies wie Schachspielen, Schachgeschichte und Schachengines. Er ist Mitglied des Schachclubs Bonn/Beuel und der Chess History and Literature Society. Kontaktieren kann man ihn über seine Homepage.
Anzeige |