Was ist eine Vektor-Datenbank? Und warum spielt sie für AI eine so große Rolle?

Wie können Unternehmen und andere Organisationen sicherstellen, dass kein Wissen verloren geht? Intranet, ERP, CRM, DMS oder letztendlich einfach Datenbanken mögen die erste Antwort darauf sein. Doch Datenbanken sind nicht gleich Datenbanken, ganz besonders, da operative IT-Systeme meistens auf relationalen Datenbanken aufsetzen. In diesen geht nur leider dann doch irgendwann das Wissen verloren… Und das auch dann, wenn es nie aus ihnen herausgelöscht wird!

Die meisten Datenbanken sind darauf ausgelegt, Daten zu speichern und wieder abrufbar zu machen. Neben den relationalen Datenbanken (SQL) gibt es auch die NoSQL-Datenbanken wie den Key-Value-Store, Dokumenten- und Graph-Datenbanken mit recht speziellen Anwendungsgebieten. Vektor-Datenbanken sind ein weiterer Typ von Datenbank, die unter Einsatz von AI (Deep Learning, n-grams, …) Wissen in Vektoren übersetzen und damit vergleichbarer und wieder auffindbarer machen. Diese Funktion der Datenbank spielt seinen Vorteil insbesondere bei vielen Dimensionen aus, wie sie Text- und Bild-Daten haben.

Databases Types: Vector Database, Graph Database, Key-Value-Database, Document Database, Relational Database with Row or Column oriented table structures

Datenbank-Typen in grobkörniger Darstellung. Es gibt in der Realität jedoch viele Feinheiten, Übergänge und Überbrückungen zwischen den Datenbanktypen, z. B. zwischen emulierter und nativer Graph-Datenbank. Manche Dokumenten- Vektor-Datenbanken können auch relationale Datenmodellierung. Und eigentlich relationale Datenbanken wie z. B. PostgreSQL können mit Zusatzmodulen auch Vektoren verarbeiten.

Vektor-Datenbanken speichern Daten grundsätzlich nicht relational oder in einer anderen Form menschlich konstruierter Verbindungen. Dennoch sichert die Datenbank gewissermaßen Verbindungen indirekt, die von Menschen jedoch – in einem hochdimensionalen Raum – nicht mehr hergeleitet werden können und sich auf bestimmte Kontexte beziehen, die sich aus den Daten selbst ergeben. Maschinelles Lernen kommt mit der nummerischen Auflösung von Text- und Bild-Daten (und natürlich auch bei ganz anderen Daten, z. B. Sound) am besten zurecht und genau dafür sind Vektor-Datenbanken unschlagbar.

Was ist eine Vektor-Datenbank?

Eine Vektordatenbank speichert Vektoren neben den traditionellen Datenformaten (Annotation) ab. Ein Vektor ist eine mathematische Struktur, ein Element in einem Vektorraum, der eine Reihe von Dimensionen hat (oder zumindest dann interessant wird, genaugenommen starten wir beim Null-Vektor). Jede Dimension in einem Vektor repräsentiert eine Art von Information oder Merkmal. Ein gutes Beispiel ist ein Vektor, der ein Bild repräsentiert: jede Dimension könnte die Intensität eines bestimmten Pixels in dem Bild repräsentieren.
Auf dieseVektor Datenbank Illustration (vereinfacht, symbolisch) Weise kann eine ganze Sammlung von Bildern als eine Sammlung von Vektoren dargestellt werden. Noch gängiger jedoch sind Vektorräume, die Texte z. B. über die Häufigkeit des Auftretens von Textbausteinen (Wörter, Silben, Buchstaben) in sich einbetten (Embeddings). Embeddings sind folglich Vektoren, die durch die Projektion des Textes auf einen Vektorraum entstehen.

Vektor-Datenbanken sind besonders nützlich, wenn man Ähnlichkeiten zwischen Vektoren finden muss, z. B. ähnliche Bilder in einer Sammlung oder die Wörter “Hund” und “Katze”, die zwar in ihren Buchstaben keine Ähnlichkeit haben, jedoch in ihrem Kontext als Haustiere. Mit Vektor-Algorithmen können diese Ähnlichkeiten schnell und effizient aufgespürt werden, was sich mit traditionellen relationalen Datenbanken sehr viel schwieriger und vor allem ineffizienter darstellt.

Vektordatenbanken können auch hochdimensionale Daten effizient verarbeiten, was in vielen modernen Anwendungen, wie zum Beispiel Deep Learning, wichtig ist. Einige Beispiele für Vektordatenbanken sind Elasticsearch / Vector Search, Weaviate, Faiss von Facebook und Annoy von Spotify.

Viele Lernalgorithmen des maschinellen Lernens basieren auf Vektor-basierter Ähnlichkeitsmessung, z. B. der k-Nächste-Nachbarn-Prädiktionsalgorithmus (Regression/Klassifikation) oder K-Means-Clustering. Die Ähnlichkeitsbetrachtung erfolgt mit Distanzmessung im Vektorraum. Die dafür bekannteste Methode, die Euklidische Distanz zwischen zwei Punkten, basiert auf dem Satz des Pythagoras (Hypotenuse ist gleich der Quadratwurzel aus den beiden Dimensions-Katheten im Quadrat, im zwei-dimensionalen Raum). Es kann jedoch sinnvoll sein, aus Gründen der Effizienz oder besserer Konvergenz des maschinellen Lernens andere als die Euklidische Distanz in Betracht zu ziehen.

Vectore-based distance measuring methods: Euclidean Distance L2-Norm, Manhatten Distance L1-Norm, Chebyshev Distance and Cosine Distance

Vectore-based distance measuring methods: Euclidean Distance L2-Norm, Manhatten Distance L1-Norm, Chebyshev Distance and Cosine Distance

Vektor-Datenbanken für Deep Learning

Der Aufbau von künstlichen Neuronalen Netzen im Deep Learning sieht nicht vor, dass ganze Sätze in ihren textlichen Bestandteilen in das jeweilige Netz eingelesen werden, denn sie funktionieren am besten mit rein nummerischen Input. Die Texte müssen in diese transformiert werden, eventuell auch nach diesen in Cluster eingeteilt und für verschiedene Trainingsszenarien separiert werden.

Vektordatenbanken werden für die Datenvorbereitung (Annotation) und als Trainingsdatenbank für Deep Learning zur effizienten Speicherung, Organisation und Manipulation der Texte genutzt. Für Natural Language Processing (NLP) benötigen Modelle des Deep Learnings die zuvor genannten Word Embedding, also hochdimensionale Vektoren, die Informationen über Worte, Sätze oder Dokumente repräsentieren. Nur eine Vektordatenbank macht diese effizient abrufbar.

Vektor-Datenbank und Large Language Modells (LLM)

Ohne Vektor-Datenbanken wären die Erfolge von OpenAI und anderen Anbietern von LLMs nicht möglich geworden. Aber fernab der Entwicklung in San Francisco kann jedes Unternehmen unter Einsatz von Vektor-Datenbanken und den APIs von Google, OpenAI / Microsoft oder mit echten Open Source LLMs (Self-Hosting) ein wahres Orakel über die eigenen Unternehmensdaten herstellen. Dazu werden über APIs die Embedding-Engines z. B. von OpenAI genutzt. Wir von DATANOMIQ nutzen diese Architektur, um Unternehmen und andere Organisationen dazu zu befähigen, dass kein Wissen mehr verloren geht.
Vektor-Datenbank für KI-Applikation (z. B. OpenAI ChatGPT)

Mit der DATANOMIQ Enterprise AI Architektur, die auf jeder Cloud ausrollfähig ist, verfügen Unternehmen über einen intelligenten Unternehmens-Repräsentanten als KI, der für Mitarbeiter relevante Dokumente und Antworten auf Fragen liefert. Sollte irgendein Mitarbeiter im Unternehmen bereits einen bestimmten Vorgang, Vorfall oder z. B. eine technische Konstruktion oder einen rechtlichen Vertrag bearbeitet haben, der einem aktuellen Fall ähnlich ist, wird die AI dies aufspüren und sinnvollen Kontext, Querverweise oder Vorschläge oder lückenauffüllende Daten liefern.

Die AI lernt permanent mit, Unternehmenswissen geht nicht verloren. Das ist Wissensmanagement auf einem neuen Level, dank Vektor-Datenbanken und KI.

Wie Maschinen uns verstehen: Natural Language Understanding

Foto von Sebastian Bill auf Unsplash.

Natural Language Understanding (NLU) ist ein Teilbereich von Computer Science, der sich damit beschäftigt natürliche Sprache, also beispielsweise Texte oder Sprachaufnahmen, verstehen und verarbeiten zu können. Das Ziel ist es, dass eine Maschine in der gleichen Weise mit Menschen kommunizieren kann, wie es Menschen untereinander bereits seit Jahrhunderten tun.

Was sind die Bereiche von NLU?

Eine neue Sprache zu erlernen ist auch für uns Menschen nicht einfach und erfordert viel Zeit und Durchhaltevermögen. Wenn eine Maschine natürliche Sprache erlernen will, ist es nicht anders. Deshalb haben sich einige Teilbereiche innerhalb des Natural Language Understandings herausgebildet, die notwendig sind, damit Sprache komplett verstanden werden kann.

Diese Unterteilungen können auch unabhängig voneinander genutzt werden, um einzelne Aufgaben zu lösen:

  • Speech Recognition versucht aufgezeichnete Sprache zu verstehen und in textuelle Informationen umzuwandeln. Das macht es für nachgeschaltete Algorithmen einfacher die Sprache zu verarbeiten. Speech Recognition kann jedoch auch alleinstehend genutzt werden, beispielsweise um Diktate oder Vorlesungen in Text zu verwandeln.
  • Part of Speech Tagging wird genutzt, um die grammatikalische Zusammensetzung eines Satzes zu erkennen und die einzelnen Satzbestandteile zu markieren.
  • Named Entity Recognition versucht innerhalb eines Textes Wörter und Satzbausteine zu finden, die einer vordefinierten Klasse zugeordnet werden können. So können dann zum Beispiel alle Phrasen in einem Textabschnitt markiert werden, die einen Personennamen enthalten oder eine Zeit ausdrücken.
  • Sentiment Analysis klassifiziert das Sentiment, also die Gefühlslage, eines Textes in verschiedene Stufen. Dadurch kann beispielsweise automatisiert erkannt werden, ob eine Produktbewertung eher positiv oder eher negativ ist.
  • Natural Language Generation ist eine allgemeine Gruppe von Anwendungen mithilfe derer automatisiert neue Texte generiert werden sollen, die möglichst natürlich klingen. Zum Beispiel können mithilfe von kurzen Produkttexten ganze Marketingbeschreibungen dieses Produkts erstellt werden.

Welche Algorithmen nutzt man für NLP?

Die meisten, grundlegenden Anwendungen von NLP können mit den Python Modulen spaCy und NLTK umgesetzt werden. Diese Bibliotheken bieten weitreichende Modelle zur direkten Anwendung auf einen Text, ohne vorheriges Trainieren eines eigenen Algorithmus. Mit diesen Modulen ist ohne weiteres ein Part of Speech Tagging oder Named Entity Recognition in verschiedenen Sprachen möglich.

Der Hauptunterschied zwischen diesen beiden Bibliotheken ist die Ausrichtung. NLTK ist vor allem für Entwickler gedacht, die eine funktionierende Applikation mit Natural Language Processing Modulen erstellen wollen und dabei auf Performance und Interkompatibilität angewiesen sind. SpaCy hingegen versucht immer Funktionen bereitzustellen, die auf dem neuesten Stand der Literatur sind und macht dabei möglicherweise Einbußen bei der Performance.

Für umfangreichere und komplexere Anwendungen reichen jedoch diese Optionen nicht mehr aus, beispielsweise wenn man eine eigene Sentiment Analyse erstellen will. Je nach Anwendungsfall sind dafür noch allgemeine Machine Learning Modelle ausreichend, wie beispielsweise ein Convolutional Neural Network (CNN). Mithilfe von Tokenizern von spaCy oder NLTK können die einzelnen in Wörter in Zahlen umgewandelt werden, mit denen wiederum das CNN als Input arbeiten kann. Auf heutigen Computern sind solche Modelle mit kleinen Neuronalen Netzwerken noch schnell trainierbar und deren Einsatz sollte deshalb immer erst geprüft und möglicherweise auch getestet werden.

Jedoch gibt es auch Fälle in denen sogenannte Transformer Modelle benötigt werden, die im Bereich des Natural Language Processing aktuell state-of-the-art sind. Sie können inhaltliche Zusammenhänge in Texten besonders gut mit in die Aufgabe einbeziehen und liefern daher bessere Ergebnisse beispielsweise bei der Machine Translation oder bei Natural Language Generation. Jedoch sind diese Modelle sehr rechenintensiv und führen zu einer sehr langen Rechenzeit auf normalen Computern.

Was sind Transformer Modelle?

In der heutigen Machine Learning Literatur führt kein Weg mehr an Transformer Modellen aus dem Paper „Attention is all you need“ (Vaswani et al. (2017)) vorbei. Speziell im Bereich des Natural Language Processing sind die darin erstmals beschriebenen Transformer Modelle nicht mehr wegzudenken.

Transformer werden aktuell vor allem für Übersetzungsaufgaben genutzt, wie beispielsweise auch bei www.deepl.com. Darüber hinaus sind diese Modelle auch für weitere Anwendungsfälle innerhalb des Natural Language Understandings geeignet, wie bspw. das Beantworten von Fragen, Textzusammenfassung oder das Klassifizieren von Texten. Das GPT-2 Modell ist eine Implementierung von Transformern, dessen Anwendungen und die Ergebnisse man hier ausprobieren kann.

Was macht den Transformer so viel besser?

Soweit wir wissen, ist der Transformer jedoch das erste Transduktionsmodell, das sich ausschließlich auf die Selbstaufmerksamkeit (im Englischen: Self-Attention) stützt, um Repräsentationen seiner Eingabe und Ausgabe zu berechnen, ohne sequenzorientierte RNNs oder Faltung (im Englischen Convolution) zu verwenden.

Übersetzt aus dem englischen Originaltext: Attention is all you need (Vaswani et al. (2017)).

In verständlichem Deutsch bedeutet dies, dass das Transformer Modell die sogenannte Self-Attention nutzt, um für jedes Wort innerhalb eines Satzes die Beziehung zu den anderen Wörtern im gleichen Satz herauszufinden. Dafür müssen nicht, wie bisher, Recurrent Neural Networks oder Convolutional Neural Networks zum Einsatz kommen.

Was dieser Mechanismus konkret bewirkt und warum er so viel besser ist, als die vorherigen Ansätze wird im folgenden Beispiel deutlich. Dazu soll der folgende deutsche Satz mithilfe von Machine Learning ins Englische übersetzt werden:

„Das Mädchen hat das Auto nicht gesehen, weil es zu müde war.“

Für einen Computer ist diese Aufgabe leider nicht so einfach, wie für uns Menschen. Die Schwierigkeit an diesem Satz ist das kleine Wort „es“, dass theoretisch für das Mädchen oder das Auto stehen könnte. Aus dem Kontext wird jedoch deutlich, dass das Mädchen gemeint ist. Und hier ist der Knackpunkt: der Kontext. Wie programmieren wir einen Algorithmus, der den Kontext einer Sequenz versteht?

Vor Veröffentlichung des Papers „Attention is all you need“ waren sogenannte Recurrent Neural Networks die state-of-the-art Technologie für solche Fragestellungen. Diese Netzwerke verarbeiten Wort für Wort eines Satzes. Bis man also bei dem Wort „es“ angekommen ist, müssen erst alle vorherigen Wörter verarbeitet worden sein. Dies führt dazu, dass nur noch wenig Information des Wortes „Mädchen“ im Netzwerk vorhanden sind bis den Algorithmus überhaupt bei dem Wort „es“ angekommen ist. Die vorhergegangenen Worte „weil“ und „gesehen“ sind zu diesem Zeitpunkt noch deutlich stärker im Bewusstsein des Algorithmus. Es besteht also das Problem, dass Abhängigkeiten innerhalb eines Satzes verloren gehen, wenn sie sehr weit auseinander liegen.

Was machen Transformer Modelle anders? Diese Algorithmen prozessieren den kompletten Satz gleichzeitig und gehen nicht Wort für Wort vor. Sobald der Algorithmus das Wort „es“ in unserem Beispiel übersetzen will, wird zuerst die sogenannte Self-Attention Layer durchlaufen. Diese hilft dem Programm andere Wörter innerhalb des Satzes zu erkennen, die helfen könnten das Wort „es“ zu übersetzen. In unserem Beispiel werden die meisten Wörter innerhalb des Satzes einen niedrigen Wert für die Attention haben und das Wort Mädchen einen hohen Wert. Dadurch ist der Kontext des Satzes bei der Übersetzung erhalten geblieben.

Über die Integration symbolischer Inferenz in tiefe neuronale Netze

Tiefe neuronale Netze waren in den letzten Jahren eine enorme Erfolgsgeschichte. Viele Fortschritte im Bereich der KI, wie das Erkennen von Objekten, die fließende Übersetzung natürlicher Sprache oder das Spielen von GO auf Weltklasseniveau, basieren auf tiefen neuronalen Netzen. Über die Grenzen dieses Ansatzes gab es jedoch nur wenige Berichte. Eine dieser Einschränkungen ist die Unfähigkeit, aus einer kleinen Anzahl von Beispielen zu lernen. Tiefe neuronale Netze erfordern in der Regel eine Vielzahl von Trainingsbeispielen, während der Mensch aus nur einem einzigen Beispiel lernen kann. Wenn Sie eine Katze einem Kind zeigen, das noch nie zuvor eine gesehen hat, kann es eine weitere Katze anhand dieser einzigen Instanz erkennen. Tiefe neuronale Netze hingegen benötigen Hunderttausende von Bildern, um zu erlernen, wie eine Katze aussieht. Eine weitere Einschränkung ist die Unfähigkeit, Rückschlüsse aus bereits erlerntem Allgemeinwissen zu ziehen. Beim Lesen eines Textes neigen Menschen dazu, weitreichende Rückschlüsse auf mögliche Interpretationen des Textes zu ziehen. Der Mensch ist dazu in der Lage, weil er Wissen aus sehr unterschiedlichen Bereichen abrufen und auf den Text anwenden kann.

Diese Einschränkungen deuten darauf hin, dass in tiefen neuronalen Netzen noch etwas Grundsätzliches fehlt. Dieses Etwas ist die Fähigkeit, symbolische Bezüge zu Entitäten in der realen Welt herzustellen und sie in Beziehung zueinander zu setzen. Symbolische Inferenz in Form von formaler Logik ist seit Jahrzehnten der Kern der klassischen KI, hat sich jedoch als spröde und komplex in der Anwendung erwiesen. Gibt es dennoch keine Möglichkeit, tiefe neuronale Netze so zu verbessern, dass sie in der Lage sind, symbolische Informationen zu verarbeiten? Tiefe neuronale Netzwerke wurden von biologischen neuronalen Netzwerken wie dem menschlichen Gehirn inspiriert. Im Wesentlichen sind sie ein vereinfachtes Modell der Neuronen und Synapsen, die die Grundbausteine des Gehirns ausmachen. Eine solche Vereinfachung ist, dass statt mit zeitlich begrenzten Aktionspotenzialen nur mit einem Aktivierungswert gearbeitet wird. Aber was ist, wenn es nicht nur wichtig ist, ob ein Neuron aktiviert wird, sondern auch, wann genau. Was wäre, wenn der Zeitpunkt, zu dem ein Neuron feuert, einen relationalen Kontext herstellt, auf den sich diese Aktivierung bezieht? Nehmen wir zum Beispiel ein Neuron, das für ein bestimmtes Wort steht. Wäre es nicht sinnvoll, wenn dieses Neuron jedes Mal ausgelöst würde, wenn das Wort in einem Text erscheint? In diesem Fall würde das Timing der Aktionspotenziale eine wichtige Rolle spielen. Und nicht nur das Timing einer einzelnen Aktivierung, sondern auch das Timing aller eingehenden Aktionspotenziale eines Neurons relativ zueinander wäre wichtig. Dieses zeitliche Muster kann verwendet werden, um eine Beziehung zwischen diesen Eingangsaktivierungen herzustellen. Wenn beispielsweise ein Neuron, das ein bestimmtes Wort repräsentiert, eine Eingabesynapse für jeden Buchstaben in diesem Wort hat, ist es wichtig, dass das Wort Neuron nur dann ausgelöst wird, wenn die Buchstabenneuronen in der richtigen Reihenfolge zueinander abgefeuert wurden. Konzeptionell könnten diese zeitlichen Unterschiede als Relationen zwischen den Eingangssynapsen eines Neurons modelliert werden. Diese Relationen definieren auch den Zeitpunkt, zu dem das Neuron selbst im Verhältnis zu seinen Eingangsaktivierungen feuert. Aus praktischen Gründen kann es sinnvoll sein, der Aktivierung eines Neurons mehrere Slots zuzuordnen, wie z.B. den Anfang und das Ende eines Wortes. Andernfalls müssten Anfang und Ende eines Wortes als zwei getrennte Neuronen modelliert werden. Diese Relationen sind ein sehr mächtiges Konzept. Sie ermöglichen es, die hierarchische Struktur von Texten einfach zu erfassen oder verschiedene Bereiche innerhalb eines Textes miteinander in Beziehung zu setzen. In diesem Fall kann sich ein Neuron auf eine sehr lokale Information beziehen, wie z.B. einen Buchstaben, oder auf eine sehr weitreichende Information, wie z.B. das Thema eines Textes.

Eine weitere Vereinfachung im Hinblick auf biologische neuronale Netze besteht darin, dass mit Hilfe einer Aktivierungsfunktion die Feuerrate eines einzelnen Neurons angenähert wird. Zu diesem Zweck nutzen klassische neuronale Netze die Sigmoidfunktion. Die Sigmoidfunktion ist jedoch symmetrisch bezüglich großer positiver oder negativer Eingangswerte, was es sehr schwierig macht, ausssagenlogische Operationen mit Neuronen mit der Sigmoidfunktion zu modellieren. Spiking-Netzwerke hingegen haben einen klaren Schwellenwert und ignorieren alle Eingangssignale, die unterhalb dieses Schwellenwerts bleiben. Daher ist die ReLU-Funktion oder eine andere asymmetrische Funktion eine deutlich bessere Annäherung für die Feuerrate. Diese Asymmetrie ist auch für Neuronen unerlässlich, die relationale Informationen verarbeiten. Das Neuron, das ein bestimmtes Wort repräsentiert, muss nämlich für alle Zeitpunkte, an denen das Wort nicht vorkommt, völlig inaktiv bleiben.

Ebenfalls vernachlässigt wird in tiefen neuronalen Netzwerken die Tatsache, dass verschiedene Arten von Neuronen in der Großhirnrinde vorkommen. Zwei wichtige Typen sind die bedornte Pyramidenzelle, die in erster Linie eine exzitatorische Charakteristik aufweist, und die nicht bedornte Sternzelle, die eine hemmende aufweist. Die inhibitorischen Neuronen sind besonders, weil sie es ermöglichen, negative Rückkopplungsschleifen aufzubauen. Solche Rückkopplungsschleifen finden sich normalerweise nicht in einem tiefen neuronalen Netzwerk, da sie einen inneren Zustand in das Netzwerk einbringen. Betrachten wir das folgende Netzwerk mit einem hemmenden Neuron und zwei exzitatorischen Neuronen, die zwei verschiedene Bedeutungen des Wortes “August” darstellen.

Beide Bedeutungen schließen sich gegenseitig aus, so dass das Netzwerk nun zwei stabile Zustände aufweist. Diese Zustände können von weiteren Eingangssynapsen der beiden exzitatorischen Neuronen abhängen. Wenn beispielsweise das nächste Wort nach dem Wort ‘August’ ein potenzieller Nachname ist, könnte eine entsprechende Eingabesynapse für das Entitätsneuron August-(Vorname) das Gewicht dieses Zustands erhöhen. Es ist nun wahrscheinlicher, dass das Wort “August” als Vorname und nicht als Monat eingestuft wird. Aber bedenken Sie, dass beide Zustände evaluiert werden müssen. In größeren Netzwerken können viele Neuronen durch negative oder positive Rückkopplungsschleifen verbunden sein, was zu einer großen Anzahl von stabilen Zuständen im Netzwerk führen kann.

Aus diesem Grund ist ein effizienter Optimierungsprozess erforderlich, der den besten Zustand in Bezug auf eine Zielfunktion ermittelt. Diese Zielfunktion könnte darin bestehen, die Notwendigkeit der Unterdrückung stark aktivierter Neuronen zu minimieren. Diese Zustände haben jedoch den enormen Vorteil, dass sie es erlauben, unterschiedliche Interpretationen eines bestimmten Textes zu berücksichtigen. Es ist eine Art Denkprozess, in dem verschiedene Interpretationen bewertet werden und die jeweils stärkste als Ergebnis geliefert wird. Glücklicherweise lässt sich die Suche nach einem optimalen Lösungszustand recht gut optimieren.

Der Grund, warum wir in diesen Rückkopplungsschleifen hemmende Neuronen benötigen, ist, dass sonst alle gegenseitig unterdrückenden Neuronen vollständig miteinander verbunden sein müssten. Das würde zu einer quadratisch zunehmenden Anzahl von Synapsen führen.

Durch die negativen Rückkopplungsschleifen, d.h. durch einfaches Verbinden einer negativen Synapse mit einem ihrer Vorläuferneuronen, haben wir plötzlich den Bereich der nichtmonotonen Logik betreten. Die nichtmonotone Logik ist ein Teilgebiet der formalen Logik, in dem Implikationen nicht nur zu einem Modell hinzugefügt, sondern auch entfernt werden. Es wird davon ausgegangen, dass eine nichtmonotone Logik erforderlich ist, um Schlussfolgerungen für viele Common Sense Aufgaben ziehen zu können. Eines der Hauptprobleme der nichtmonotonen Logik ist, dass sie oft nicht entscheiden kann, welche Schlussfolgerungen sie ziehen soll und welche eben nicht. Einige skeptische oder leichtgläubige Schlussfolgerungen sollten nur gezogen werden, wenn keine anderen Schlussfolgerungen wahrscheinlicher sind. Hier kommt die gewichtete Natur neuronaler Netze zum Tragen. In neuronalen Netzen können nämlich eher wahrscheinliche Zustände weniger wahrscheinliche Zustände unterdrücken.

Beispielimplementierung innerhalb des Aika-Frameworks

An dieser Stelle möchte ich noch einmal das Beispielneuron für das Wort ‘der’ vom Anfang aufgreifen. Das Wort-Neuron besteht aus drei Eingabesynapsen, die sich jeweils auf die einzelnen Buchstaben des Wortes beziehen. Über die Relationen werden die Eingabesynapsen nun zueinander in eine bestimmte Beziehung gesetzt, so dass das Wort ‘der’ nur erkannt wird, wenn alle Buchstaben in der korrekten Reihenfolge auftreten.
Als Aktivierungsfunktion des Neurons wird hier der im negativen Bereich abgeschnittene (rectified) hyperbolische Tangens verwendet. Dieser hat gerade bei einem UND-verknüpfenden Neuron den Vorteil, dass er selbst bei sehr großen Werten der gewichteten Summe auf den Wert 1 begrenzt ist. Alternativ kann auch die ReLU-Funktion (Rectified Linear Unit) verwendet werden. Diese eignet sich insbesondere für ODER-verknüpfende Neuronen, da sie die Eingabewerte unverzerrt weiterleitet.
Im Gegensatz zu herkömmlichen neuronalen Netzen gibt es hier mehrere Bias Werte, einen für das gesamte Neuron (in diesem Fall auf 5.0 gesetzt) und einen für jede Synapse. Intern werden diese Werte zu einem gemeinsamen Bias aufsummiert. Es ist schon klar, dass dieses Aufteilen des Bias nicht wirklich gut zu Lernregeln wie der Delta-Rule und dem Backpropagation passt, allerdings eignen sich diese Lernverfahren eh nur sehr begrenzt für diese Art von neuronalem Netzwerk. Als Lernverfahren kommen eher von den natürlichen Mechanismen Langzeit-Potenzierung und Langzeit-Depression inspirierte Ansätze in Betracht.

Neuron buchstabeD = m.createNeuron("B-d");
	Neuron buchstabeE = m.createNeuron("B-e");
	Neuron buchstabeR = m.createNeuron("B-r");

	Neuron wortDer = Neuron.init(
                m.createNeuron("W-der"),
                5.0,
                RECTIFIED_HYPERBOLIC_TANGENT,
                EXCITATORY,
                new Synapse.Builder()
                        .setSynapseId(0)
                        .setNeuron(buchstabeD)
                        .setWeight(10.0)
                        .setBias(-10.0)
                        .setRecurrent(false),
                new Synapse.Builder()
                        .setSynapseId(1)
                        .setNeuron(buchstabeE)
                        .setWeight(10.0)
                        .setBias(-10.0)
                        .setRecurrent(false),
                new Synapse.Builder()
                        .setSynapseId(2)
                        .setNeuron(buchstabeR)
                        .setWeight(10.0)
                        .setBias(-10.0)
                        .setRecurrent(false),
                new Relation.Builder()
                        .setFrom(0)
                        .setTo(1)
                        .setRelation(new Equals(END, BEGIN)),
                new Relation.Builder()
                        .setFrom(1)
                        .setTo(2)
                        .setRelation(new Equals(END, BEGIN)),
                new Relation.Builder()
                        .setFrom(0)
                        .setTo(OUTPUT)
                        .setRelation(new Equals(BEGIN, BEGIN)),
                new Relation.Builder()
                        .setFrom(2)
                        .setTo(OUTPUT)
                        .setRelation(new Equals(END, END))
	);

Fazit

Obwohl tiefe neuronale Netze bereits einen langen Weg zurückgelegt haben und mittlerweile beeindruckende Ergebnisse liefern, kann es sich doch lohnen, einen weiteren Blick auf das Original, das menschliche Gehirn und seine Schaltkreise zu werfen. Wenn eine so inhärent komplexe Struktur wie das menschliche Gehirn als Blaupause für ein neuronales Modell verwendet werden soll, müssen vereinfachende Annahmen getroffen werden. Allerdings ist bei diesem Prozess Vorsicht geboten, da sonst wichtige Aspekte des Originals verloren gehen können.

Referenzen

  1. Der Aika-Algorithm
    Lukas Molzberger
  2. Neuroscience: Exploring the Brain
    Mark F. Bear, Barry W. Connors, Michael A. Paradiso
  3. Neural-Symbolic Learning and Reasoning: A Survey and Interpretation
    Tarek R. Besold, Artur d’Avila Garcez, Sebastian Bader; Howard Bowman, Pedro Domingos, Pascal Hitzler, Kai-Uwe Kuehnberger, Luis C. Lamb, ; Daniel Lowd, Priscila Machado Vieira Lima, Leo de Penning, Gadi Pinkas, Hoifung Poon, Gerson Zaverucha
  4. Deep Learning: A Critical Appraisal
    Gary Marcus
  5. Nonmonotonic Reasoning
    Gerhard Brewka, Ilkka Niemela, Mirosław Truszczynski

Einstieg in Natural Language Processing – Teil 2: Preprocessing von Rohtext mit Python

Dies ist der zweite Artikel der Artikelserie Einstieg in Natural Language Processing.

In diesem Artikel wird das so genannte Preprocessing von Texten behandelt, also Schritte die im Bereich des NLP in der Regel vor eigentlichen Textanalyse durchgeführt werden.

Tokenizing

Um eingelesenen Rohtext in ein Format zu überführen, welches in der späteren Analyse einfacher ausgewertet werden kann, sind eine ganze Reihe von Schritten notwendig. Ganz allgemein besteht der erste Schritt darin, den auszuwertenden Text in einzelne kurze Abschnitte – so genannte Tokens – zu zerlegen (außer man bastelt sich völlig eigene Analyseansätze, wie zum Beispiel eine Spracherkennung anhand von Buchstabenhäufigkeiten ect.).

Was genau ein Token ist, hängt vom verwendeten Tokenizer ab. So bringt NLTK bereits standardmäßig unter anderem BlankLine-, Line-, Sentence-, Word-, Wordpunkt- und SpaceTokenizer mit, welche Text entsprechend in Paragraphen, Zeilen, Sätze, Worte usw. aufsplitten. Weiterhin ist mit dem RegexTokenizer ein Tool vorhanden, mit welchem durch Wahl eines entsprechenden Regulären Ausdrucks beliebig komplexe eigene Tokenizer erstellt werden können.

Üblicherweise wird ein Text (evtl. nach vorherigem Aufsplitten in Paragraphen oder Sätze) schließlich in einzelne Worte und Interpunktionen (Satzzeichen) aufgeteilt. Hierfür kann, wie im folgenden Beispiel z. B. der WordTokenizer oder die diesem entsprechende Funktion word_tokenize() verwendet werden.

rawtext = 'This is a short example text that needs to be cleaned.'

tokens = nltk.word_tokenize(rawtext)

tokens
['This', 'is', 'a', 'short', 'example', 'text', 'that', 'needs', 'to',  'be',  'cleaned',  '.']

Stemming & Lemmatizing

Andere häufig durchgeführte Schritte sind Stemming sowie Lemmatizing. Hierbei werden die Suffixe der einzelnen Tokens des Textes mit Hilfe eines Stemmers in eine Form überführt, welche nur den Wortstamm zurücklässt. Dies hat den Zweck verschiedene grammatikalische Formen des selben Wortes (welche sich oft in ihrer Endung unterscheiden (ich gehe, du gehst, er geht, wir gehen, …) ununterscheidbar zu machen. Diese würden sonst als mehrere unabhängige Worte in die darauf folgende Analyse eingehen.

Neben bereits fertigen Stemmern bietet NLTK auch für diesen Schritt die Möglichkeit sich eigene Stemmer zu programmieren. Da verschiedene Stemmer Suffixe nach unterschiedlichen Regeln entfernen, sind nur die Wortstämme miteinander vergleichbar, welche mit dem selben Stemmer generiert wurden!

Im forlgenden Beispiel werden verschiedene vordefinierte Stemmer aus dem Paket NLTK auf den bereits oben verwendeten Beispielsatz angewendet und die Ergebnisse der gestemmten Tokens in einer Art einfachen Tabelle ausgegeben:

# Ready-to-use stemmers in nltk
porter = nltk.PorterStemmer()
lancaster = nltk.LancasterStemmer()
snowball = nltk.SnowballStemmer(language='english')

# Printing a table to compare the different stemmers
header = 'Token\tPorter\tLancas.\tSnowball'
print(header + '\n' + len(header) * '-')
for token in tokens:
    print('\t'.join([token, porter.stem(token), lancaster.stem(token), snowball.stem(token)]))


Token	Porter	Lancas.	Snowball
-----------------------------
This	thi 	thi 	this
is  	is  	is  	is
a    	a    	a    	a
short	short	short	short
example	exampl	exampl	exampl
text	text	text	text
that	that	that	that
needs	need	nee	need
to  	to  	to  	to
be  	be  	be  	be
cleaned	clean	cle 	clean
.   	.   	.   	.

Sehr ähnlich den Stemmern arbeiten Lemmatizer: Auch ihre Aufgabe ist es aus verschiedenen Formen eines Wortes die jeweilige Grundform zu bilden. Im Unterschied zu den Stemmern ist das Lemma eines Wortes jedoch klar als dessen Grundform definiert.

from nltk.stem import WordNetLemmatizer

lemmatizer = WordNetLemmatizer()

lemmas = [lemmatizer.lemmatize(t) for t in tokens()]

Vokabular

Auch das Vokabular, also die Menge aller verschiedenen Worte eines Textes, ist eine informative Kennzahl. Bezieht man die Größe des Vokabulars eines Textes auf seine gesamte Anzahl verwendeter Worte, so lassen sich hiermit Aussagen zu der Diversität des Textes machen.

Außerdem kann das auftreten bestimmter Worte später bei der automatischen Einordnung in Kategorien wichtig werden: Will man beispielsweise Nachrichtenmeldungen nach Themen kategorisieren und in einem Text tritt das Wort „DAX“ auf, so ist es deutlich wahrscheinlicher, dass es sich bei diesem Text um eine Meldung aus dem Finanzbereich handelt, als z. B. um das „Kochrezept des Tages“.

Dies mag auf den ersten Blick trivial erscheinen, allerdings können auch mit einfachen Modellen, wie dem so genannten „Bag-of-Words-Modell“, welches nur die Anzahl des Auftretens von Worten prüft, bereits eine Vielzahl von Informationen aus Texten gewonnen werden.

Das reine Vokabular eines Textes, welcher in der Variable “rawtext” gespeichert ist, kann wie folgt in der Variable “vocab” gespeichert werden. Auf die Ausgabe wurde in diesem Fall verzichtet, da diese im Falle des oben als Beispiel gewählten Satzes den einzelnen Tokens entspricht, da kein Wort öfter als ein Mal vorkommt.

from nltk import wordpunct_tokenizer
from nltk.stem import WordNetLemmatizer

lemma = WordNetLemmatizer()

vocab = set([WordNetLemmatizer().lemmatize(t) for t in wordpunct_tokenize(text.lower())])

Stopwords

Unter Stopwords werden Worte verstanden, welche zwar sehr häufig vorkommen, jedoch nur wenig Information zu einem Text beitragen. Beispiele in der beutschen Sprache sind: der, und, aber, mit, …

Sowohl NLTK als auch cpaCy bringen vorgefertigte Stopwordsets mit. 

from nltk.corpus import stopwords
stoplist = stopwords.words('english')
stopset = set(stopwords.words('english'))

[t for t in tokens if not t in stoplist]
['This', 'short', 'example', 'text', 'needs', 'cleaned', '.']

Vorsicht: NLTK besitzt eine Stopwordliste, welche erst in ein Set umgewandelt werden sollte um die lookup-Zeiten kurz zu halten – schließlich muss jedes einzelne Token des Textes auf das vorhanden sein in der Stopworditerable getestet werden!

%timeit [w for w in tokens if not w in stopset] # 1.11 ms
%timeit [w for w in tokens if not w in stoplist] # 26.6 ms

POS-Tagging

POS-Tagging steht für „Part of Speech Tagging“ und entspricht ungefähr den Aufgaben, die man noch aus dem Deutschunterricht kennt: „Unterstreiche alle Subjekte rot, alle Objekte blau…“. Wichtig ist diese Art von Tagging insbesondere, wenn man später tatsächlich strukturiert Informationen aus dem Text extrahieren möchte, da man hierfür wissen muss wer oder was als Subjekt mit wem oder was als Objekt interagiert.

Obwohl genau die selben Worte vorkommen, bedeutet der Satz „Die Katze frisst die Maus.“ etwas anderes als „Die Maus frisst die Katze.“, da hier Subjekt und Objekt aufgrund ihrer Reihenfolge vertauscht sind (Stichwort: Subjekt – Prädikat – Objekt ).

Weniger wichtig ist dieser Schritt bei der Kategorisierung von Dokumenten. Insbesondere bei dem bereits oben erwähnten Bag-of-Words-Modell, fließen POS-Tags überhaupt nicht mit ein.

Und weil es so schön einfach ist: Die obigen Schritte mit spaCy

Die obigen Methoden und Arbeitsschritte, welche Texte die in natürlicher Sprache geschrieben sind, allgemein computerzugänglicher und einfacher auswertbar machen, können beliebig genau den eigenen Wünschen angepasst, einzeln mit dem Paket NLTK durchgeführt werden. Dies zumindest einmal gemacht zu haben, erweitert das Verständnis für die funktionsweise einzelnen Schritte und insbesondere deren manchmal etwas versteckten Komplexität. (Wie muss beispielsweise ein Tokenizer funktionieren der den Satz “Schwierig ist z. B. dieser Satz.” korrekt in nur einen Satz aufspaltet, anstatt ihn an jedem Punkt welcher an einem Wortende auftritt in insgesamt vier Sätze aufzuspalten, von denen einer nur aus einem Leerzeichen besteht?) Hier soll nun aber, weil es so schön einfach ist, auch das analoge Vorgehen mit dem Paket spaCy beschrieben werden:

import spacy

nlp = spacy.load('en')
doc = nlp(rawtext)

Dieser kurze Codeabschnitt liest den an spaCy übergebenen Rohtext in ein spaCy Doc-Object ein und führt dabei automatisch bereits alle oben beschriebenen sowie noch eine Reihe weitere Operationen aus. So stehen neben dem immer noch vollständig gespeicherten Originaltext, die einzelnen Sätze, Worte, Lemmas, Noun-Chunks, Named Entities, Part-of-Speech-Tags, ect. direkt zur Verfügung und können.über die Methoden des Doc-Objektes erreicht werden. Des weiteren liegen auch verschiedene weitere Objekte wie beispielsweise Vektoren zur Bestimmung von Dokumentenähnlichkeiten bereits fertig vor.

Die Folgende Übersicht soll eine kurze (aber noch lange nicht vollständige) Übersicht über die automatisch von spaCy generierten Objekte und Methoden zur Textanalyse geben:

# Textabschnitte
doc.text                                 # Originaltext
sents = doc.sents                        # Sätze des Dokuments
tokens = [token for token in doc]        # Tokens/Worte des Dokuments
parags = doc.text_with_ws.split('\n\n')  # Absätze des Dokuments

# Eigenschaften einzelner Tokens
[t.lemma_ for t in doc]                  # Lemmata der einzelnen Tokens
[t.tag_ for t in doc]                    # POS-Tags der einzelnen Tokens

# Objekte zur Textanalyse
doc.vocab                                # Vokabular des Dokuments
doc.sentiment                            # Sentiment des Dokuments
doc.noun_chunks                          # NounChunks des Dokuments
entities = [ent for ent in doc.ents]     # Named Entities (Persons, Locations, Countrys)

# Objekte zur Dokumentenklassifikation
doc.vector                               # Vektor
doc.tensor                               # Tensor

Diese „Vollautomatisierung“ der Vorabschritte zur Textanalyse hat jedoch auch seinen Preis: spaCy geht nicht gerade sparsam mit Ressourcen wie Rechenleistung und Arbeitsspeicher um. Will man einen oder einige Texte untersuchen so ist spaCy oft die einfachste und schnellste Lösung für das Preprocessing. Anders sieht es aber beispielsweise aus, wenn eine bestimmte Analyse wie zum Beispiel die Einteilung in verschiedene Textkategorien auf eine sehr große Anzahl von Texten angewendet werden soll. In diesem Fall, sollte man in Erwägung ziehen auf ressourcenschonendere Alternativen wie zum Beispiel gensim auszuweichen.

Wer beim lesen genau aufgepasst hat, wird festgestellt haben, dass ich im Abschnitt POS-Tagging im Gegensatz zu den anderen Abschnitten auf ein kurzes Codebeispiel verzichtet habe. Dies möchte ich an dieser Stelle nachholen und dabei gleich eine Erweiterung des Pakets spaCy vorstellen: displaCy.

Displacy bietet die Möglichkeit, sich Zusammenhänge und Eigenschaften von Texten wie Named Entities oder eben POS-Tagging graphisch im Browser anzeigen zu lassen.

import spacy
from spacy import displacy

rawtext = 'This is a short example sentence that needs to be cleaned.'

nlp = spacy.load('en')
doc = nlp(rawtext)
displacy.serve(doc, style='dep')

Nach ausführen des obigen Codes erhält man eine Ausgabe die wie folgt aussieht:

Serving on port 5000...
Using the 'dep' visualizer

Nun öffnet man einen Browser und ruft die URL ‘http://127.0.0.1:5000’ auf (Achtung: localhost anstatt der IP funktioniert – warum auch immer – mit displacy nicht). Im Browser sollte nun eine Seite mit einem SVG-Bild geladen werden, welches wie folgt aussieht

Die Abbildung macht deutlich was POS-Tagging genau ist und warum es von Nutzen sein kann wenn man Informationen aus einem Text extrahieren will. Jedem Word (Token) ist eine Wortart zugeordnet und die Beziehung der einzelnen Worte durch Pfeile dargestellt. Dies ermöglicht es dem Computer zum Beispiel in dem Satzteil “der grüne Apfel”, das Adjektiv “grün” auf das Nomen “Apfel” zu beziehen und diesem somit als Eigenschaft zuzuordnen.

Nachdem dieser Artikel wichtige Schritte des Preprocessing von Texten beschrieben hat, geht es im nächsten Artikel darum was man an Texten eigentlich analysieren kann und welche Analysemöglichkeiten die verschiedenen für Python vorhandenen Module bieten.

Einstieg in Natural Language Processing – Artikelserie

Unter Natural Language Processing (NLP) versteht man ein Teilgebiet der Informatik bzw. der Datenwissenschaft, welches sich mit der Analyse und Auswertung , aber auch der Synthese natürlicher Sprache befasst. Mit natürlichen Sprachen werden Sprachen wie zum Beispiel Deutsch, Englisch oder Spanisch bezeichnet, welche nicht geplant entworfen wurden, sondern sich über lange Zeit allein durch ihre Benutzung entwickelt haben. Anders ausgedrückt geht es um die Schnittstelle zwischen unserer im Alltag verwendeten und für uns Menschen verständlichen Sprache auf der einen, und um deren computergestützte Auswertung auf der anderen Seite.

Diese Artikelserie soll eine Einführung in die Thematik des Natural Language Processing sein, dessen Methoden, Möglichkeiten, aber auch der Grenzen . Im einzelnen werden folgende Themen näher behandelt:

1. Artikel – Natürliche vs. Formale Sprachen
2. Artikel – Preprocessing von Rohtext mit Python (erscheint demnächst…)
3. Artikel – Möglichkeiten/Methoden der Textanalyse an Beispielen (erscheint demnächst…)
4. Artikel – NLP, was kann es? Und was nicht? (erscheint demnächst…)

Zur Verdeutlichung der beschriebenen Zusammenhänge und Methoden und um Interessierten einige Ideen für mögliche Startpunkte aufzuzeigen, werden im Verlauf der Artikelserie an verschiedenen Stellen Codebeispiele in der Programmiersprache Python vorgestellt.
Von den vielen im Internet zur Verfügung stehenden Python-Paketen zum Thema NLP, werden in diesem Artikel insbesondere die drei Pakete NLTK, Gensim und Spacy verwendet.