Wird der Beruf des Programmierers aussterben, weil KI seinen Job übernimmt? Wie weit kommt man heute wirklich beim Programmieren mithilfe von ChatGPT und Co.? Wo es flutscht und wo es klemmt, haben wir in einem kleinen Python-Projekt getestet.
Ganz so einfach wollte ich es der KI nicht machen. Ich wollte ihren Programmierfähigkeiten auf den Zahn fühlen, dabei aber kein Allerweltsbeispiel verwenden, wie es in jedem zweiten Anfängerkurs vorkommt – etwa das Berechnen der Fibonacci-Folge. Denn in diesem Fall hätte die KI die Lösung von vorneherein gekannt. Sie wäre nämlich mit Sicherheit und womöglich mehrfach in ihren Trainingsdaten enthalten gewesen.
Weil es aber beim Programmieren häufig gerade darauf ankommt, zuerst einen brauchbaren Algorithmus zu finden, zog ich es vor, die KI mit einem selteneren und spezielleren Problem zu konfrontieren: die Bestimmung der Akkordbezeichnung aus einer Menge von Akkordtönen, eine Aufgabe aus dem Bereich Musikanalyse. Das Programm soll eine Handvoll Tonnamen wie “C4-E4-G4-B4” übergeben bekommen und darauf mit “C Major 7” antworten.
Wozu wäre so ein Tool gut? Einerseits ließe sich damit ergründen, wie eine nach Gehör gefundene reizvolle Tonkombination als Akkord korrekt zu bezeichnen wäre. Andererseits könnte das allerdings auch der erste Schritt zu einer Analyse der harmonischen Struktur eines Musikstücks sein. Das Problem erscheint womöglich etwas exotisch, steht aber stellvertretend für eine ganze Klasse von Aufgaben, bei denen der Lösungsweg vermutlich selten oder gar nicht in den Trainingsdaten auftaucht. Ein Programmierassistent müsste also über Know-how aus einem speziellen Fachgebiet verfügen, um erfolgreich zu sein. Wie viel Musiktheorie beherrscht ChatGPT, was muss man ihm beibringen?
Wie soll man vorgehen?
Wenn Sie die komplette Aufgabe in einen Prompt verpacken und an ChatGPT übergeben, bekommen Sie eine Lösung, die nicht ganz falsch ist. Sie kann jedoch nur eine minimale Untermenge von Akkorden bestimmen, konkret lediglich die zwölf einfachen Dur-Dreiklänge. Für die sind dann die Namen der beteiligten Töne im Code fest verdrahtet hinterlegt. Das Programm entfernt bei den übergebenen Tönen die Oktavnummern, fasst drei Töne zusammen und vergleicht sie so mit dem hinterlegten Muster (Listing 1).
Listing 1
Erste Lösung
if 'C' in noten_set and 'E' in noten_set and 'G' in noten_set:
return 'C-Dur'
Das ist aus mehreren Gründen nicht zufriedenstellend: ChatGPT klassifiziert Moll-Dreiklänge nicht. Vier- und Mehrklänge bleiben unberücksichtigt und mögliche Zusatztöne werden generell nicht einbezogen. Außerdem können Zusatztöne außerhalb des Tonumfangs einer Oktave (etwa Nonen) nicht erkannt werden. Dreiklangsumkehrungen benennt die KI nicht als solche.
Wer diese Defizite anmahnt, der erhält eine neue Version, bei der zusätzlich die Muster für einfache Moll-Dreiklänge enthalten sind. Fortan bezeichnet die KI jeden vier- und mehrstimmigen Akkord als “erweitert”, ohne jedoch nähere Angaben zur Art der Erweiterung zu machen. Besser wird es auch nach mehreren Versuchen nicht. Das liegt daran, dass der eingeschlagene Weg in ein Dilemma führt: Es bräuchte Unmengen if-Vergleiche mit Elementen von Mustern, wenn Sie jeden möglichen Dur- oder Mollakkord jeder Tonart als Muster-Set aus Tonnamen aufnehmen wollten. Wollten Sie obendrein noch alle Kombinationen mit Sexte, großer oder kleiner Septime, None und so weiter berücksichtigen, stiege die Anzahl nötiger Muster kontinuierlich stark an. Umkehrungen ließen sich gar nicht identifizieren, weil die Reihenfolge der Töne bei einem solchen Vergleich keine Rolle spielt. Die bisherige Strategie endet damit eher in eine Sackgasse.
Tatsächlich existiert ein besserer Lösungsweg, doch ChatGPT weiß davon nichts. Ihn muss mithin ein menschlicher Programmierer beisteuern, wenn er das Projekt voranbringen will. Eine aussichtsreichere Strategie bestünde beispielsweise darin, zunächst den Grundton des Akkords zu bestimmen und danach die Intervallstruktur in Bezug auf den Grundton. Das ergibt zwar wieder eine Reihe von Mustern, aber diesmal besitzen sie den Vorteil, dass nur ein Muster für einen Akkord in allen zwölf Tonarten zuständig ist. Das Muster für einen einfachen Dur-Dreiklang in Grundstellung ist dann “Grundton-große Terz-Quinte”. Das gilt von C-Dur bis B-Dur (deutsch: H-Dur) in jeder Tonart und spart so eine Größenordnung an Vergleichen. Statt einzelne Töne nacheinander abzugleichen, lässt sich zudem das Muster in einem Schritt vergleichen (hier also Grundton-g3-r5). Dementsprechend reduziert sich die Menge an Vergleichen noch einmal auf ein Drittel.
Denkfehler der KI
Obwohl ChatGPT nicht auf diesen Lösungsweg kommt, kann es trotzdem beim Umsetzen helfen. Von nun an gehe ich in kleinen Schritten vor und frage im ersten nach einer Eingaberoutine für die Tonnamen, die den Input verifizieren soll. ChatGPT liefert als Vorschlag den Code aus Listing 2.
Listing 2
Eingaberoutine
import re
def read_names(prompt="Enter tones (1-10 names separated by dashes): "):
# Regular expression to match valid tone patterns
tone_pattern = r'^[A-G](([b#]{1,2})?[1-9]?)?$'
# To capture valid input
valid_tone_pattern = f"({tone_pattern})(-{tone_pattern}){0,9}"
while True:
user_input = input(prompt)
# Check if input matches the pattern for 1 to 10 names
if re.match(valid_tone_pattern, user_input):
tones = user_input.split('-')
return tones
else:
print("Error: Invalid input. Please enter 1 to 10 valid tones separated by dashes.")
# Example usage
if __name__ == "__main__":
tones = read_names()
print(f"Valid tones entered: {tones}")
Der KI-Assistent nutzt dabei reguläre Ausdrücke – im Prinzip keine schlechte Idee. Der Ausdruck »tone_pattern« (Zeile 4) passt perfekt auf einen einzelnen Tonnamen, der aus einem Großbuchstaben zwischen A und G am Anfang besteht, optional gefolgt von einem oder zwei Versetzungszeichen (Kreuz oder Doppelkreuz für eine Erhöhung, b oder Doppel-b für eine Erniedrigung), optional beendet mit einer Oktavnummer zwischen 1 und 9. Der gesamte Input soll aus bis zu zehn solcher Tonnamen bestehen. So viele Töne könnte man maximal mit beiden Händen auf einem Klavier gleichzeitig spielen.
Die Tonnamen habe ich leicht vereinfacht. Grundsätzlich unterscheidet man Groß- und Kleinschreibung und nutzt tief- beziehungsweise hochgestellte Ziffern, was sich jedoch auf einer herkömmlichen Tastatur nicht eingeben ließe. Deshalb verwende ich ausschließlich Majuskel und nummeriere die Oktaven von der Subkontra-Oktave bis zur fünfgestrichenen Oktave (Tonumfang eines Klaviers) einfach durch.
Das Ergebnis sieht auf den ersten Blick plausibel aus, funktioniert aber nicht, da ChatGPT einen Denkfehler eingebaut hat. Für den einzelnen Tonnamen gilt, dass der Buchstabe am Anfang stehen muss (»^«) und die Oktavnummer, falls vorhanden, am Ende (»$«) – bei der Verkettung der Tonnamen mit »-« zu »valid_tone_pattern« (Zeile 6) terminiert allerdings das erste Dollarzeichen im regulären Ausdruck des ersten Tons die gesamte Eingabe. Die folgenden Töne werden nicht mehr berücksichtigt. Der reguläre Ausdruck, der für einen einzelnen Ton passt, lässt sich in dieser Form nicht zu einem Ausdruck verketten, der auf die gesamte Ausgabe passen soll.
Dementsprechend muss man entweder die Zeichen für Anfang und Ende des Musters weglassen, oder man behält sie, splittet dann aber die Eingabesequenz vorher auf und prüft jeden Tonnamen einzeln gegen das Einzelton-Muster, statt die komplette Sequenz auf einmal. Das hätte zusätzlich den Vorteil, dass das Programm den Benutzer darauf hinweisen könnte, welchen Namen es gegebenenfalls beanstandet. Dieser Tonname würde außerdem einfach übergangen und ließe sich in korrekter Schreibweise wiederholen. Den Fehler sowie den möglichen Ausweg muss jedoch der menschliche Programmierer entdecken – ChatGPT gelingt das nicht.
Gute Idee
Nun habe ich also eine regelgerechte Liste von Tonnamen und muss sämtliche Intervalle bestimmen, die sich bei paarweise kombinierten Tönen ergeben. Um solche Paare zu bilden, fällt einem vielleicht auf Anhieb eine verschachtelte Schleife ein: Die äußere Schleife durchläuft alle Elemente der Liste vom ersten bis zum vorletzten Element. Die innere Schleife ergänzt daraufhin die Paare immer mit denjenigen Elementen, die auf das aktuelle Element der äußeren Schleife folgen.
Das Vorgehen entspricht der Lösung 1 in Listing 3. Schildern Sie ChatGPT, was Sie haben möchten, erinnert es sich stattdessen aber an die Bibliothek »itertools«, die eine Funktion »combinations« enthält. Damit erreicht man dasselbe in einer einzigen Zeile, was selbstredend viel eleganter aussieht (Lösung 2 in Listing 3). Eine gute Idee.
Listing 3
Zwei Lösungen
from itertools import combinations
Elementliste=["A","B","C","D","E"]
# Lösung 1: verschachtelte Schleifen
Paare1=[]
i=0
for element1 in Elementliste[:-1]:
i+=1
for element2 in Elementliste[i:]:
Paare1.append((element1, element2))
print(list(Paare1))
# Lösung 2: itertools.combinations
Paare2 = list(combinations(Elementliste, 2))
print(list(Paare2))
Übrigens: Wenn Sie statt mit ChatGPT mit dem Assistenten Github Copilot in Visual Studio Code programmieren, bietet er Ihnen bei Lösung 1 zwar hilfreiche Vervollständigungen angefangener Zeilen an, weist jedoch nicht auf den funktionsgleichen Ersatz durch Lösung 2 hin.
Grundtonsuche
Nun existieren also die Kombinationen aus allen eingegebenen Tönen und ich suche das zugehörige Intervall pro Tonpaar. Das funktioniert relativ einfach: Für den Anfang speichere ich eine chromatische Tonleiter als Dictionary, wobei der Schlüssel der Tonname und der Wert der Abstand vom Grundton in Halbtonschritten ist. Dabei wird es durch Erhöhung beziehungsweise Erniedrigung vorausgehender und nachfolgender Töne immer mehrere Tonnamen pro Halbtonschritt geben. In der Fachsprache heißt das Phänomen enharmonische Verwechslung. Der Anfang des Dictionarys sieht aus wie in Listing 4.
Listing 4
Chromatische Tonleiter
scale = {
'C': 0, 'B#': 0, 'Dbb': 0,
'C#': 1, 'B##': 1, 'Db': 1,
...
'B': 11, 'Cb': 11, 'A##': 11
}
Die Differenz zweier Halbtonwerte – gegebenenfalls unter Einberechnung der Werte für verschiedene Oktaven – entspricht direkt dem gesuchten Intervall: Ist die Differenz 0, handelt es sich um eine Prime, beträgt sie 1, liegt eine kleine Sekunde vor. Zwei Halbtonschritte kommen einer großen Sekunde gleich und so weiter bis zur Oktave nach zwölf Halbtonschritten. Weil ich mich generell für englische Namen und Begriffe entschieden hab, nutze ich sie auch hier: Perfect Unison, Minor Second, Major Second etc.
Nachdem ich alle Intervalle kenne, wäre es jetzt an der Zeit, den Grundton des übergebenen Akkords zu bestimmen. Allerdings fehlt ChatGPT dabei erneut Ahnung davon, wie das zu bewerkstelligen ist. Es gibt den Hinweis, das oft der tiefste Ton der Grundton sei – stimmt, aber eben nicht immer und nie bei Dreiklangsumkehrungen. Darüber hinaus hat die KI den Tipp parat, dass mir Musiksoftware helfen könne. Das bringt mich nicht weiter, denn eine solche möchte ich ja gerade schreiben.
Tatsächlich lässt sich der Grundton mithilfe unterschiedlicher Methoden bestimmen. Ich greife dafür zu einem Verfahren, das auf Paul Hindemith zurückgeht und dem Resonanzen in der Obertonreihe der beteiligten Töne zugrunde liegen. Dazu orientiere ich mich an einer Tabelle, in der sämtliche infrage kommenden Intervalle nach ihrem harmonischen Wert geordnet sind: zum Beispiel reine Quinte vor reiner Quarte vor großer Terz vor kleiner Septime.
In meiner Intervallsammlung suche ich nach dem höchstwertigen Intervall aus der Tabelle, der ich zudem entnehmen kann, ob der höhere oder tiefere Ton des gefundenen Intervalls als Grundton anzusprechen ist. Finden sich eine oder mehrere Quinten, ist deren tieferer Ton ein Akkordgrundton und die Suche lässt sich abbrechen. Anderenfalls setze sie ich mit dem nächsten Intervall fort, der reinen Quarte. Ist auch diese Suche erfolglos, schaue ich nach großen Terzen und so weiter.
Speziell sortiert
Sobald der Grundton auf diese Weise identifiziert ist, geht es an die darauf aufbauende Intervallstruktur. Da ich die Intervalle zwischen allen Tönen bereits berechnet habe, genügt es, diejenigen Intervalle auszusieben, in denen der Grundton auftaucht. Anschließend wartet noch ein kniffliger Schritt: Um die Intervallstruktur besser mit Mustern vergleichen können, sollten Sie die Intervalle sortieren. Die Sortierreihenfolge steht fest, folgt aber keiner alphabetischen oder numerischen Ordnung.
Vielleicht fällt ChatGPT etwas dazu ein? “Given is a list of abbreviations consisting of a character and a number. We are looking for a python function that sorts this list. There is a given sort order (from smallest to largest: P1, m2, M2, m3, M3, P4, P5, m6, M6, m7, M7, P8. We need a function that sorts a list of unordered abbreviations from this set.” In der Tat liefert ChatGPT auf die Anfrage hin eine funktionierende Lösung (Listing 5) – Chapeau!
Listing 5
Custom Sort
def custom_sort(abbreviations):
# Define the custom sort order
sort_order = ['P1', 'm2', 'M2', 'm3', 'M3', 'P4', 'P5', 'm6', 'M6', 'm7', 'M7', 'P8']
# Create a dictionary that maps each abbreviation to its index in the sort order
order_dict = {abbr: index for index, abbr in enumerate(sort_order)}
# Sort the abbreviations using the order_dict
sorted_abbreviations = sorted(abbreviations, key=lambda x: order_dict.get(x, float('inf')))
return sorted_abbreviations
# Example usage
unordered_abbreviations = ['M2', 'P1', 'm6', 'P5', 'M3', 'm3', 'P4', 'm2']
sorted_abbreviations = custom_sort(unordered_abbreviations)
print(sorted_abbreviations)
Die KI erzeugt am Anfang ein Dictionary mit den Abkürzungen für die Intervallnamen als Schlüssel und ihrer Position (bei Null beginnend) in der Liste »sort_order« (Zeile 3) als Wert. Das sieht so aus:
{'P1': 0, 'm2': 1, 'M2': 2, ... 'P8': 11}
Da das Dictionary kurz ist, ließe es sich in der Funktion besser auf diese Weise ausschreiben, statt es bei jedem Akkord neu zu berechnen. Dann können Sie auf die Liste »sort_order« verzichten, die es nur zur Konstruktion des Dictionarys braucht. ChatGPT schlägt also eine unnötig komplizierte und ineffiziente Lösung vor. Danach nutzt es Pythons Funktion »sorted« (Zeile 7), der mit dem Parameter »key« eine anonyme Funktion übergeben wird, die zu jedem Element der zu sortierenden Liste dessen Wert aus dem Intervalle-Dictionary besorgt. Dieser Wert bestimmt die Sortierreihenfolge.
Würde ein Element der zu sortierenden Liste nicht im Dictionary gefunden – was hier praktisch ausgeschlossen ist – erhielte es als Index den Wert »float(‘inf’)«. Er steht für die positive Unendlichkeit, und würde daher immer an letzter Stelle landen. Übrigens sehr nützlich: Wenn Ihnen etwas in der Sortierfunktion nicht ganz klar sein sollte, können Sie ChatGPT jederzeit um eine Erklärung bitten.
Das Finale
Die letzten Schritte lassen sich schnell zusammenfassen. Die sortierte, mit Bindestrichen verbundene Intervallliste dient als Schlüssel in einem weiteren Dictionary, dessen Werte (Listing 6) die zugehörigen Namen der Akkordtypen sind. Zusammen mit dem Namen des Grundtons (ohne Nummer der Oktave) ergibt sich daraus die korrekte Akkordbezeichnung. Die Auflistung in Listing 6 ist aus Platzgründen verkürzt und erfasst selbstverständlich nicht alle denkbaren Akkorde.
Listing 6
Akkordnamen (Auszug)
chordnames = {
'M3-P5': 'Major',
'P4-m6': 'Major (1. inversion)',
'M3-P4': 'Major (2. inversion)',
'm3-P5': 'Minor',
'P4-M6': 'Minor (1. inversion)',
'm3-P4': 'Minor (2. inversion)',
'm3-A5': 'Diminished',
'M3-A5': 'Augmented',
'M3-P5-m7': 'Dominant 7',
'M3-P5-M7': 'Major 7',
'm3-P5-M7': 'Minor 7',
... }
Das Skript lässt sich sicher in vielerlei Hinsicht ausbauen. Beispielsweise müssen Sie in der vorliegenden Fassung oktavierte Akkordtöne bei der Eingabe weglassen. Alternativ ließen sie sich hinterher aussieben (oder das Intervall Oktave). Zur Bestimmung des Akkords tragen sie jedenfalls nichts bei. Überhaupt sind andere Eingabemöglichkeiten vorstellbar, etwa als Darstellung in Notenschrift oder als Griffbild für beispielsweise die Gitarre oder über ein MIDI-fähiges Instrument.
Bisher nicht adressiert ist das Problem unvollständiger Akkorde. Auf der Ausgabeseite könnten Sie neben dem Akkordnamen ein Klangbeispiel erzeugen. All das gibt es nebenbei gesagt schon, zum Beispiel als Android-App [1]. Wenn Sie mehrere Akkorde und eine Tonart vorgeben würden, ließen sich deren Beziehungen im Sinne der sogenannten Funktionsharmonik analysieren.
Fazit
Um Programmierkenntnisse kommen Sie nicht herum, selbst wenn Sie ChatGPT oder den Github Copilot einsetzen. Zwar schlägt die KI an zahlreichen Stellen durchaus brauchbaren Code vor, sie macht dabei aber Fehler, die der menschliche Nutzer korrigieren muss. Außerdem findet sie nicht immer die einfachste Lösung eines Problems. Für spezielle Aufgaben kennt sie unter Umständen den Lösungsweg nicht.
Wer die KI als nützliches Hilfsmittel beim Programmieren einspannt, kommt allerdings schneller voran. Die vorgeschlagenen Funktionen und Ergänzungen angefangener Code-Passagen in der IDE fallen größtenteils korrekt aus, sind brauchbar und sparen Zeit sowie Tipparbeit. Der Mensch vor dem Bildschirm sollte sie jedoch prüfen und einschätzen können. Obendrein muss er das zu lösende Problem verstehen, um es der KI möglichst präzise zu beschreiben. Am besten gehen Sie dabei etappenweise vor und lassen sich bei exakt definierten Teilschritten unter die Arme greifen. Erscheint der Code unklar, kann man von der KI eine Erklärung einfordern und lernt auf diese Weise vielleicht noch dazu. Alles in allem: Die Programmierfähigkeiten großer Sprachmodelle sind eine willkommene Unterstützung, sie zu nutzen macht Spaß. (csi)
Infos
- App smartChord: https://smartchord.de






