1. Importieren der notwendigen Bibliotheken
import random
import json
import pickle
import numpy as np
import nltk
from nltk.stem import WordNetLemmatizer
from nltk.stem.isri import ISRIStemmer
from keras.models import Sequential
from tensorflow.keras.optimizers import SGD
from keras.layers import Dense, Dropout
from keras.models import load_model
Beschreibung der importierten Module:
- random: Wird verwendet, um Zufallszahlen zu generieren, was nützlich ist, um die Reihenfolge von Daten zu mischen.
- json: Zum Laden und Speichern von Daten im JSON-Format, was hilfreich ist, um die Struktur der Chatbot-Intents zu definieren.
- pickle: Ermöglicht das Speichern von Python-Objekten in Dateien und das spätere Laden dieser Objekte.
- numpy: Eine Bibliothek für numerische Berechnungen in Python, die insbesondere für die Arbeit mit Arrays nützlich ist.
- nltk: Natural Language Toolkit, eine Bibliothek für die Arbeit mit natürlicher Sprache (Textverarbeitung und -analyse).
- WordNetLemmatizer: Ein Lemmatizer aus NLTK, der Wörter auf ihre Grundform reduziert.
- ISRIStemmer: Ein Stemmer für die arabische Sprache aus NLTK, der Wörter auf ihre Wortstämme reduziert.
- keras.models.Sequential: Ein Modell in Keras, das eine lineare Stapelung von Schichten ermöglicht.
- tensorflow.keras.optimizers.SGD: Stochastic Gradient Descent, ein Optimierungsalgorithmus für das Training von neuronalen Netzen.
- keras.layers.Dense: Eine dicht verbundene Schicht (vollständig verbunden) in einem neuronalen Netzwerk.
- keras.layers.Dropout: Eine Schicht, die Dropout-An regularisierung verwendet, um Überanpassung zu verhindern.
- keras.models.load_model: Ermöglicht das Laden eines zuvor gespeicherten Keras-Modells.
2. Initialisierung des arabischen Stemmers
isstemmer = ISRIStemmer()
3. Herunterladen von NLTK-Resourcen
nltk.download('omw-1.4')
nltk.download("punkt")
nltk.download("wordnet")
- Lädt das Open Multilingual Wordnet (OMW) in der Version 1.4 herunter.
- OMW ist eine Sammlung von Wortnetzen für viele verschiedene Sprachen, die mit dem englischen WordNet verknüpft sind.
- Dies ist nützlich, wenn Sie mehrsprachige Sprachverarbeitung durchführen möchten oder Zugang zu Wortnetzen in verschiedenen Sprachen benötigen.
- Lädt den Punktuation Tokenizer (Punkt) herunter.
- Punkt ist ein vortrainiertes Modell zur Satz- und Worttokenisierung.
- Tokenisierung ist der Prozess der Aufteilung von Text in kleinere Einheiten wie Sätze oder Wörter. Dies ist ein wichtiger Schritt in der Textvorverarbeitung.
- Lädt das WordNet herunter, eine große lexikalische Datenbank der englischen Sprache.
- In WordNet sind Nomen, Verben, Adjektive und Adverbien in Gruppen von kognitiven Synonymen (Synsets) organisiert, die jeweils unterschiedliche Konzepte darstellen.
- WordNet wird häufig für Aufgaben wie Lemmatisierung, Synonym-Erkennung und semantische Textanalyse verwendet.
4. Laden der JSON-Daten (Absichten des Chatbots)
data_file = open("intents_arabic.json", encoding='utf-8', errors='ignore').read()
intents = json.loads(data_file)
Die Datei intents_arabic.json
enthält die Daten wie folgt:
{
"intents": [{
"tag": "greetings",
"patterns": ["مرحبًا هناك", "مرحبًا", "السلام عليكم ", "ياو", "شو فيه", "كيف حالك؟", "مساء الخير", "هولا", "صباح الخير "],
"responses": ["مرحبًا شكرًا للتحقق", "مرحبًا، كيف يمكنني مساعدتك"]
},
{
"tag": "events",
"patterns": ["ما هي الأحداث القادمة", "الأحداث القادمة"],
"responses": ["لا توجد حاليًا أحداث قادمة"]
}
]
}
- Diese Funktion öffnet die Datei
intents_arabic.json
im Lese-Modus ('r'
ist standardmäßig der Lese-Modus). - encoding='utf-8': Die Datei wird mit UTF-8-Kodierung geöffnet, um sicherzustellen, dass alle Zeichen, insbesondere arabische Zeichen, korrekt gelesen werden.
- errors='ignore': Diese Option ignoriert Fehler, die während des Lesens auftreten könnten, z.B. aufgrund von ungültigen Zeichen.
- Diese Methode liest den gesamten Inhalt der geöffneten Datei als einen einzigen String.
- Der Inhalt der Datei wird in der Variable
data_file
als String gespeichert.
- json.loads(data_file):
- Diese Funktion wandelt den JSON-formatierten String
data_file
in ein entsprechendes Python-Objekt um, meistens ein Dictionary. - Der JSON-Inhalt wird in der Variable
intents
gespeichert.
5. Initialisierung von Listen
words = []
classes = []
documents = []
ignore_words = ["?", "!", ".", ","]
words = []
:
- Diese leere Liste wird später verwendet, um alle Wörter zu speichern, die in den Mustern der Chatbot-Intents vorkommen.
- Diese Wörter werden tokenisiert (d.h. in einzelne Wörter zerlegt) und später verarbeitet (z.B. durch Stemming oder Lemmatisierung).
classes = []:
- Diese leere Liste wird verwendet, um die verschiedenen Klassen oder Tags zu speichern, die den unterschiedlichen Absichten (Intents) des Chatbots zugeordnet sind.
- Jede Klasse repräsentiert eine Kategorie von Benutzeranfragen, wie z.B. "Begrüßung", "Hilfe", "Bestellung aufgeben", etc.
documents = []:
- Diese leere Liste wird verwendet, um Tupel zu speichern, die aus tokenisierten Wörtern und den zugehörigen Klassen bestehen.
- Jedes Dokument besteht aus einem Muster (eine Liste von Wörtern) und dem entsprechenden Tag (die Klasse), z.B.
(["Hallo", "wie", "geht", "es", "dir"], "Begrüßung")
.
ignore_words = ["?", "!", ".", ","]:
- Diese Zeichen tragen in der Regel nicht zur Bedeutung des Textes bei und werden daher nicht in die Wortliste aufgenommen.
6. Verarbeitung der Muster und Tags aus den JSON-Daten
for intent in intents["intents"]:
for pattern in intent["patterns"]:
w = nltk.word_tokenize(pattern)
words.extend(w)
documents.append((w, intent["tag"]))
if intent["tag"] not in classes:
classes.append(intent["tag"])
for intent in intents["intents"]:
Diese Schleife geht durch jeden Intent in der intents["intents"]-Liste, die aus dem JSON-Objekt geladen wurde. Jeder Intent ist ein Dictionary, das Informationen wie tag, patterns und responses enthält.
for pattern in intent["patterns"]:
Diese innere Schleife geht durch jedes Muster (pattern) im aktuellen Intent. Muster sind Beispiele für Benutzeranfragen, die dem Intent zugeordnet sind.
w = nltk.word_tokenize(pattern):
Die nltk.word_tokenize(pattern)-Funktion zerlegt das Muster in einzelne Wörter (Tokens). Zum Beispiel wird der Satz "Wie geht es dir?" in die Tokens ["Wie", "geht", "es", "dir", "?"] zerlegt.
words.extend(w):
Die words-Liste wird mit den tokenisierten Wörtern (w) erweitert. Diese Liste enthält schließlich alle Wörter, die in den Mustern aller Intents vorkommen.
words
Ausgabe:
['مرحبًا',
'هناك',
'مرحبًا',
'السلام',
'عليكم',
'ياو',
'شو',
'فيه',
'كيف',
'حالك؟',
'مساء',
'الخير',
'هولا',
'صباح',
'الخير',
'وداعًا',
.
.
.
]
documents.append((w, intent["tag"])):
Jedes Dokument besteht aus einem Tupel von tokenisierten Wörtern (w) und dem zugehörigen Intent-Tag (intent["tag"]). Diese Dokumente werden verwendet, um den Zusammenhang zwischen den Mustern und ihren Intents zu speichern und später zum Training des Modells zu verwenden.
documents
Ausgabe:
[(['مرحبًا', 'هناك'], 'greetings'),
(['مرحبًا'], 'greetings'),
(['السلام', 'عليكم'], 'greetings'),
(['ياو'], 'greetings'),
(['شو', 'فيه'], 'greetings'),
(['كيف', 'حالك؟'], 'greetings'),
(['مساء', 'الخير'], 'greetings'),
(['هولا'], 'greetings'),
(['صباح', 'الخير'], 'greetings'),
(['وداعًا'], 'goodbye'),
(['باي'], 'goodbye'),
(['أراك', 'لاحقًا'], 'goodbye'),
(['شكرا'], 'thanks'),
(['حسنًا'], 'thanks'),
(['شكرًا', 'لك'], 'thanks'),
(['شكرًا', 'جزيلا'], 'thanks')
.
.
.
]
if intent["tag"] not in classes:
Überprüft, ob das Intent-Tag (intent["tag"]) bereits in der classes-Liste vorhanden ist.
classes.append(intent["tag"]):
Wenn das Tag noch nicht in der Liste ist, wird es hinzugefügt. Die classes-Liste enthält alle unterschiedlichen Tags (Intents), die in den Daten vorkommen.
classes
Ausgabe:
['greetings',
'goodbye',
'thanks',
'noanswer',
'name1',
'name',
'date',
'fav',
'need',
'AI',
'sentiment',
'sapient',
'abbr',
'lang',
'sound',
'artificial',
.
.
.
]
7. Stemming der Wörter und Ignorieren von Sonderzeichen
processed_words = []
for w in words:
stemmed_word = isstemmer.stem(w)
if w not in ignore_words:
processed_words.append(stemmed_word)
words = sorted(list(set(processed_words)))
classes = sorted(list(classes))
processed_words = []:
Diese Zeile initialisiert eine leere Liste processed_words, die verwendet wird, um die gestemmten Wörter zu speichern.
for w in words:
Diese Schleife durchläuft jedes Wort in der words-Liste, die zuvor erstellt wurde.
stemmed_word = isstemmer.stem(w):
Das aktuelle Wort w wird mit dem ISRIStemmer gestemmt und das Ergebnis in der Variablen stemmed_word gespeichert. Der ISRIStemmer reduziert das Wort auf seinen Wortstamm.
if w not in ignore_words:
Diese Bedingung überprüft, ob das aktuelle Wort w nicht in der ignore_words-Liste enthalten ist, die Satzzeichen und andere irrelevante Zeichen enthält.
processed_words.append(stemmed_word):
Wenn das Wort w nicht in der ignore_words-Liste ist, wird das gestemmte Wort stemmed_word zur processed_words-Liste hinzugefügt.
words = sorted(list(set(processed_words))):
Die processed_words-Liste wird in ein Set umgewandelt, um Duplikate zu entfernen, dann zurück in eine Liste umgewandelt und schließlich alphabetisch sortiert. Das Ergebnis wird der Variablen words zugewiesen.
classes = sorted(list(classes)):
Die classes-Liste wird alphabetisch sortiert und das Ergebnis wird der Variablen classes zugewiesen.
8. Erstellung des Trainingsdatensatzes
training = []
output_empty = [0] * len(classes)
for doc in documents:
bag = []
pattern_words = doc[0]
pattern_words = [isstemmer.stem(word) for word in pattern_words]
for w in words:
bag.append(1) if w in pattern_words else bag.append(0)
output_row = list(output_empty)
output_row[classes.index(doc[1])] = 1
training.append([bag, output_row])
training = []: Diese Zeile initialisiert eine leere Liste
training
, die später die Trainingsdaten enthalten wird.output_empty = [0] * len(classes): Diese Zeile erstellt eine Liste
output_empty
, die aus Nullen besteht und deren Länge der Anzahl der Klassen entspricht. Diese Liste wird verwendet, um die Ausgabezeilen zu initialisieren.for doc in documents:: Diese Schleife durchläuft jedes Dokument in der
documents
-Liste, die zuvor erstellt wurde.bag = []: Diese Zeile initialisiert eine leere Liste
bag
, die verwendet wird, um die Wortpräsenz in einem Muster zu speichern.pattern_words = doc[0]: Diese Zeile extrahiert die Wörter des aktuellen Musters aus dem Dokument und speichert sie in
pattern_words
.pattern_words = [isstemmer.stem(word) for word in pattern_words]: Diese Zeile stemmt jedes Wort im Muster mit dem ISRIStemmer und speichert die gestemmten Wörter zurück in
pattern_words
.for w in words:: Diese Schleife durchläuft jedes Wort in der
words
-Liste, die alle einzigartigen gestemmten Wörter enthält.bag.append(1) if w in pattern_words else bag.append(0): Diese Zeile fügt
1
zurbag
-Liste hinzu, wenn das Wortw
impattern_words
-Muster vorkommt, andernfalls fügt sie0
hinzu. Dies erstellt einen sogenannten "Bag of Words"-Vektor, der angibt, welche Wörter im Muster vorhanden sind.output_row = list(output_empty): Diese Zeile erstellt eine neue Liste
output_row
, die eine Kopie vonoutput_empty
ist. Diese Liste wird verwendet, um die Zielausgabe für das aktuelle Dokument zu erstellen.output_row[classes.index(doc[1])] = 1: Diese Zeile setzt den Wert der entsprechenden Klasse in
output_row
auf1
.doc[1]
enthält das Tag des aktuellen Dokuments, undclasses.index(doc[1])
gibt den Index dieser Klasse in derclasses
-Liste zurück.training.append([bag, output_row]): Diese Zeile fügt das Paar aus
bag
(dem "Bag of Words"-Vektor) undoutput_row
(der Zielausgabe) zurtraining
-Liste hinzu. Dietraining
-Liste enthält somit die Trainingsdaten für das Modell.
9. Überprüfung und Korrektur von Inkonsistenzen in Längen und Typen
bag_length = len(words)
output_length = len(classes)
fixed_training = []
for item in training:
bag, output_row = item
if len(bag) != bag_length:
if len(bag) < bag_length:
bag = bag + [0] * (bag_length - len(bag))
else:
bag = bag[:bag_length]
if len(output_row) != output_length:
if len(output_row) < output_length:
output_row = output_row + [0] * (output_length - len(output_row))
else:
output_row = output_row[:output_length]
bag = list(bag)
output_row = list(output_row)
fixed_training.append([bag, output_row])
bag_length = len(words): Diese Zeile speichert die Länge der
words
-Liste (die Anzahl der einzigartigen Wörter) in der Variablenbag_length
.output_length = len(classes): Diese Zeile speichert die Länge der
classes
-Liste (die Anzahl der verschiedenen Klassen) in der Variablenoutput_length
.fixed_training = []: Diese Zeile initialisiert eine leere Liste
fixed_training
, die später die korrigierten Trainingsdaten enthalten wird.for item in training:: Diese Schleife durchläuft jedes Element
item
in dertraining
-Liste.bag, output_row = item: Diese Zeile entpackt das
item
in die beiden Variablenbag
undoutput_row
.bag
ist der "Bag of Words"-Vektor undoutput_row
ist die Zielausgabe.if len(bag) != bag_length:: Diese Bedingung überprüft, ob die Länge der
bag
-Liste nicht gleichbag_length
ist.if len(bag) < bag_length:: Diese innere Bedingung überprüft, ob die Länge der
bag
-Liste kleiner alsbag_length
ist.bag = bag + [0] * (bag_length - len(bag)): Wenn die
bag
-Liste zu kurz ist, wird sie durch Hinzufügen der notwendigen Anzahl von Nullen (0
) aufbag_length
verlängert.else:: Diese Bedingung wird ausgeführt, wenn die
bag
-Liste länger alsbag_length
ist.bag = bag[
]: Wenn diebag
-Liste zu lang ist, wird sie aufbag_length
gekürzt.if len(output_row) != output_length:: Diese Bedingung überprüft, ob die Länge der
output_row
-Liste nicht gleichoutput_length
ist.if len(output_row) < output_length:: Diese innere Bedingung überprüft, ob die Länge der
output_row
-Liste kleiner alsoutput_length
ist.output_row = output_row + [0] * (output_length - len(output_row)): Wenn die
output_row
-Liste zu kurz ist, wird sie durch Hinzufügen der notwendigen Anzahl von Nullen (0
) aufoutput_length
verlängert.else:: Diese Bedingung wird ausgeführt, wenn die
output_row
-Liste länger alsoutput_length
ist.output_row = output_row[ ]: Wenn die
output_row
-Liste zu lang ist, wird sie aufoutput_length
gekürzt.bag = list(bag): Diese Zeile stellt sicher, dass
bag
eine Liste ist (falls es versehentlich in ein anderes Format umgewandelt wurde).output_row = list(output_row): Diese Zeile stellt sicher, dass
output_row
eine Liste ist (falls es versehentlich in ein anderes Format umgewandelt wurde).fixed_training.append([bag, output_row]): Diese Zeile fügt das korrigierte Paar aus
bag
undoutput_row
zurfixed_training
-Liste hinzu. Diefixed_training
-Liste enthält somit die korrigierten Trainingsdaten für das Modell.
10. Umwandeln des Trainingsdatensatzes in ein NumPy-Array
random.shuffle(fixed_training)
training_array = np.array(fixed_training, dtype=object)
train_x = list(training_array[:,0])
train_y = list(training_array[:,1])
random.shuffle(fixed_training): Diese Zeile mischt die
fixed_training
-Liste zufällig durch. Dies ist wichtig, um sicherzustellen, dass das Modell nicht in einer bestimmten Reihenfolge der Daten trainiert wird, was zu einer besseren Generalisierung des Modells führt.training_array = np.array(fixed_training, dtype=object): Diese Zeile konvertiert die
fixed_training
-Liste in ein NumPy-Array. Das Argumentdtype=object
stellt sicher, dass das Array Elemente unterschiedlicher Typen aufnehmen kann. In diesem Fall bestehen die Elemente aus Listen (Bag-of-Words-Vektoren und Zielausgaben).train_x = list(training_array[:,0]): Diese Zeile extrahiert die erste Spalte des
training_array
-Arrays (die Bag-of-Words-Vektoren) und konvertiert sie zurück in eine Liste. Diese Liste wird in der Variablentrain_x
gespeichert.train_y = list(training_array[:,1]): Diese Zeile extrahiert die zweite Spalte des
training_array
-Arrays (die Zielausgaben) und konvertiert sie zurück in eine Liste. Diese Liste wird in der Variablentrain_y
gespeichert.
print(train_y)
Ausgabe:
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
.
.
]
11. Erstellen des neuronalen Netzwerks
model = Sequential()
model.add(Dense(128, input_shape=(len(train_x[0]),), activation="relu"))
model.add(Dropout(0.5))
model.add(Dense(64, activation="relu"))
model.add(Dropout(0.5))
model.add(Dense(len(train_y[0]), activation="softmax"))
model.summary()
model = Sequential(): Diese Zeile erstellt ein sequentielles Modell, das bedeutet, dass die Schichten des neuronalen Netzes nacheinander hinzugefügt werden.
model.add(Dense(128, input_shape=(len(train_x[0]),), activation="relu")):
- Diese Zeile fügt dem Modell eine dichte (vollständig verbundene) Schicht mit 128 Neuronen hinzu.
- Dense(128): Gibt die Anzahl der Neuronen in dieser Schicht an.
- input_shape=(len(train_x[0]),): Definiert die Form der Eingabedaten. In diesem Fall entspricht die Eingabe der Länge des ersten Elements in
train_x
, was die Anzahl der Merkmale (Wörter im Bag-of-Words-Vektor) darstellt. - activation="relu": Verwendet die ReLU-Aktivierungsfunktion (Rectified Linear Unit), die für die meisten versteckten Schichten in neuronalen Netzen verwendet wird, um nichtlineare Beziehungen zu modellieren.
model.add(Dropout(0.5)): Diese Zeile fügt eine Dropout-Schicht mit einer Dropout-Rate von 0.5 hinzu. Dropout wird verwendet, um Überanpassung (Overfitting) zu verhindern, indem es zufällig 50% der Neuronen während des Trainings deaktiviert.
model.add(Dense(64, activation="relu")): Diese Zeile fügt eine weitere dichte Schicht mit 64 Neuronen und der ReLU-Aktivierungsfunktion hinzu.
model.add(Dropout(0.5)): Diese Zeile fügt eine weitere Dropout-Schicht mit einer Dropout-Rate von 0.5 hinzu.
model.add(Dense(len(train_y[0]), activation="softmax")):
- Diese Zeile fügt die Ausgabeschicht hinzu, die eine dichte Schicht mit so vielen Neuronen wie es Klassen in
train_y
gibt. - Dense(len(train_y[0])): Die Anzahl der Neuronen entspricht der Anzahl der Klassen (Intents).
- activation="softmax": Verwendet die Softmax-Aktivierungsfunktion, die für die Mehrklassen-Klassifikation geeignet ist, da sie die Wahrscheinlichkeiten für jede Klasse ausgibt.
- Diese Zeile fügt die Ausgabeschicht hinzu, die eine dichte Schicht mit so vielen Neuronen wie es Klassen in
model.summary(): Diese Zeile gibt eine Zusammenfassung des Modells aus, einschließlich der Anzahl der Schichten, der Form der Eingaben und Ausgaben und der Anzahl der zu trainierenden Parameter.
12. Kompilieren und Trainieren des Modells
sgd = SGD(learning_rate=0.01, momentum=0.9, nesterov=True)
model.compile(loss="categorical_crossentropy", optimizer=sgd, metrics=["accuracy"])
save_model = model.fit(np.array(train_x), np.array(train_y), epochs=200, batch_size=5, verbose=1)
sgd = SGD(learning_rate=0.01, momentum=0.9, nesterov=True): Diese Zeile erstellt einen Stochastic Gradient Descent (SGD) Optimierer mit den folgenden Parametern:
- learning_rate=0.01: Legt die Lernrate des Optimierers fest, die bestimmt, wie große Schritte der Optimierer bei der Anpassung der Gewichte macht.
- momentum=0.9: Fügt einen Momentum-Term hinzu, der die Konvergenz beschleunigt und dabei hilft, lokale Minima zu vermeiden.
- nesterov=True: Aktiviert das Nesterov Momentum, eine Variante des Standard-Momentums, die vorsieht, dass die Gradientenschätzung auf den zukünftigen Positionen berechnet wird.
model.compile(loss="categorical_crossentropy", optimizer=sgd, metrics=["accuracy"]): Diese Zeile kompiliert das Modell mit den folgenden Parametern:
- loss="categorical_crossentropy": Setzt die Verlustfunktion auf Kategorische Kreuzentropie, die für Mehrklassenklassifikationsprobleme verwendet wird.
- optimizer=sgd: Verwendet den zuvor definierten SGD-Optimierer.
- metrics=["accuracy"]: Fügt die Metrik "accuracy" hinzu, um die Genauigkeit des Modells während des Trainings zu überwachen.
save_model = model.fit(np.array(train_x), np.array(train_y), epochs=200, batch_size=5, verbose=1): Diese Zeile trainiert das Modell mit den folgenden Parametern:
- np.array(train_x): Konvertiert die Trainingsdaten in ein NumPy-Array.
- np.array(train_y): Konvertiert die Zielwerte in ein NumPy-Array.
- epochs=200: Bestimmt die Anzahl der Epochen (vollständige Durchläufe durch den Trainingsdatensatz), in denen das Modell trainiert wird.
- batch_size=5: Bestimmt die Größe der Mini-Batches, die in jedem Trainingsschritt verwendet werden.
- verbose=1: Setzt die Ausführlichkeit auf 1, was bedeutet, dass während des Trainings Fortschrittsmeldungen angezeigt werden.
Ausgabe:
Model: "sequential"
┃ Layer (type) ┃ Output Shape ┃ Param #
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
│ dense (Dense) │ (None, 128) │ 17,664
├──────────────────────────────────────┼─────
│ dropout (Dropout) │ (None, 128) │ 0
├──────────────────────────────────────┼─────
│ dense_1 (Dense) │ (None, 64) │ 8,256
├──────────────────────────────────────┼─────
│ dropout_1 (Dropout) │ (None, 64) │ 0
├──────────────────────────────────────┼─────
│ dense_2 (Dense) │ (None, 55) │ 3,575
└──────────────────────────────────────┴────
Total params: 29,495 (115.21 KB)
Trainable params: 29,495 (115.21 KB)
Non-trainable params: 0 (0.00 B)
Auch Ausgabe:
Epoch 1/200
21/21 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - accuracy: 0.0395 - loss: 4.0371
Epoch 2/200
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - accuracy: 0.0186 - loss: 3.9789
Epoch 3/200
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - accuracy: 0.0994 - loss: 3.8372
Epoch 4/200
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - accuracy: 0.1152 - loss: 3.7986
Epoch 5/200
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - accuracy: 0.0510 - loss: 3.7852
.
.
.
Epoch 199/200
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - accuracy: 0.9562 - loss: 0.2005
Epoch 200/200
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - accuracy: 0.9315 - loss: 0.2120
13. Speichern des Modells und der Daten
model.save("chatbot_model.h5", save_model)
pickle.dump(words, open("words.pkl", "wb"))
pickle.dump(classes, open("classes.pkl", "wb"))
model.save("chatbot_model.h5", save_model): Diese Zeile speichert das trainierte Modell in einer Datei mit dem Namen
chatbot_model.h5
.- "chatbot_model.h5": Der Name der Datei, in der das Modell gespeichert wird. Das
.h5
-Format ist ein HDF5-Format, das häufig zum Speichern von Modellen in Keras verwendet wird. - save_model: Dies ist die Variable, die das trainierte Modell enthält. Das Argument
save_model
ist optional, da das Modell direkt gespeichert wird, aber es wird häufig verwendet, um sicherzustellen, dass das richtige Modellobjekt gespeichert wird.
- "chatbot_model.h5": Der Name der Datei, in der das Modell gespeichert wird. Das
pickle.dump(words, open("words.pkl", "wb")): Diese Zeile speichert die
words
-Liste in einer Datei mit dem Namenwords.pkl
mithilfe der Pickle-Bibliothek.- words: Die Liste der einzigartigen gestemmten Wörter, die während der Vorverarbeitung erstellt wurde.
- open("words.pkl", "wb"): Öffnet eine Datei mit dem Namen
words.pkl
im Schreibmodus (wb
steht für "write binary"), um die Daten im Binärformat zu speichern. - pickle.dump(words, ...): Speichert die
words
-Liste in der geöffneten Datei.
pickle.dump(classes, open("classes.pkl", "wb")): Diese Zeile speichert die
classes
-Liste in einer Datei mit dem Namenclasses.pkl
mithilfe der Pickle-Bibliothek.- classes: Die Liste der unterschiedlichen Klassen (Intents), die während der Vorverarbeitung erstellt wurde.
- open("classes.pkl", "wb"): Öffnet eine Datei mit dem Namen
classes.pkl
im Schreibmodus (wb
steht für "write binary"), um die Daten im Binärformat zu speichern. - pickle.dump(classes, ...): Speichert die
classes
-Liste in der geöffneten Datei.
14. Laden des gespeicherten Modells und der Daten
words = pickle.load(open('words.pkl', 'rb'))
classes = pickle.load(open('classes.pkl', 'rb'))
model = load_model('chatbot_model.h5')
words = pickle.load(open('words.pkl', 'rb')): Diese Zeile lädt die gespeicherte
words
-Liste aus der Dateiwords.pkl
mithilfe der Pickle-Bibliothek.- open('words.pkl', 'rb'): Öffnet die Datei
words.pkl
im Lesemodus (rb
steht für "read binary"), um die Daten im Binärformat zu lesen. - pickle.load(...): Lädt die Daten aus der geöffneten Datei und weist sie der Variablen
words
zu. Diewords
-Liste enthält die einzigartigen gestemmten Wörter, die während der Vorverarbeitung erstellt wurden.
- open('words.pkl', 'rb'): Öffnet die Datei
classes = pickle.load(open('classes.pkl', 'rb')): Diese Zeile lädt die gespeicherte
classes
-Liste aus der Dateiclasses.pkl
mithilfe der Pickle-Bibliothek.- open('classes.pkl', 'rb'): Öffnet die Datei
classes.pkl
im Lesemodus (rb
steht für "read binary"), um die Daten im Binärformat zu lesen. - pickle.load(...): Lädt die Daten aus der geöffneten Datei und weist sie der Variablen
classes
zu. Dieclasses
-Liste enthält die unterschiedlichen Klassen (Intents), die während der Vorverarbeitung erstellt wurden.
- open('classes.pkl', 'rb'): Öffnet die Datei
model = load_model('chatbot_model.h5'): Diese Zeile lädt das gespeicherte Modell aus der Datei
chatbot_model.h5
mithilfe der Keras-Bibliothek.- load_model('chatbot_model.h5'): Lädt das trainierte Modell aus der Datei
chatbot_model.h5
und weist es der Variablenmodel
zu. Das Modell wurde zuvor trainiert und gespeichert, und durch das Laden kann es nun verwendet werden, um neue Eingaben zu klassifizieren.
- load_model('chatbot_model.h5'): Lädt das trainierte Modell aus der Datei
15. Definieren von Funktionen zur Verarbeitung von Eingabesätzen
def clean_up_sentence(sentence):
sentence_words = nltk.word_tokenize(sentence)
sentence_words = [isstemmer.stem(word) for word in sentence_words]
return sentence_words
def bag_of_words(sentence):
sentence_words = clean_up_sentence(sentence)
bag = [0] * len(words)
for w in sentence_words:
for i, word in enumerate(words):
if word == w:
bag[i] = 1
return np.array(bag)
Funktion clean_up_sentence
def clean_up_sentence(sentence): Definiert eine Funktion
clean_up_sentence
, die einen Satz als Eingabe annimmt und eine Liste von gestemmten Wörtern zurückgibt.sentence_words = nltk.word_tokenize(sentence): Diese Zeile tokenisiert den Eingabesatz in einzelne Wörter (Tokens) mithilfe der
word_tokenize
-Funktion von NLTK. Zum Beispiel wird der Satz "Wie geht es dir?" in die Tokens ["Wie", "geht", "es", "dir", "?"] zerlegt.sentence_words = [isstemmer.stem(word) for word in sentence_words]: Diese Zeile stemmt jedes Wort in der Liste
sentence_words
mithilfe des ISRIStemmer. Der Stemmer reduziert die Wörter auf ihre Grundformen.return sentence_words: Gibt die Liste der gestemmten Wörter zurück.
Funktion bag_of_words
def bag_of_words(sentence): Definiert eine Funktion
bag_of_words
, die einen Satz als Eingabe annimmt und einen Bag-of-Words-Vektor zurückgibt.sentence_words = clean_up_sentence(sentence): Diese Zeile ruft die Funktion
clean_up_sentence
auf, um den Eingabesatz zu tokenisieren und zu stemmen. Das Ergebnis ist eine Liste von gestemmten Wörtern.bag = [0] * len(words): Diese Zeile initialisiert eine Liste
bag
mit Nullen. Die Länge der Liste entspricht der Anzahl der einzigartigen Wörter (words
) im Trainingsdatensatz.for w in sentence_words:: Diese Schleife durchläuft jedes Wort in der Liste
sentence_words
.for i, word in enumerate(words):: Diese verschachtelte Schleife durchläuft jedes Wort in der Liste
words
und gibt dessen Indexi
und das Wortword
zurück.if word == w:: Diese Bedingung überprüft, ob das aktuelle Wort
word
aus derwords
-Liste mit dem aktuellen Wortw
aus dersentence_words
-Liste übereinstimmt.bag[i] = 1: Wenn das Wort
word
im Satz vorkommt, wird der entsprechende Index in derbag
-Liste auf1
gesetzt.return np.array(bag): Konvertiert die
bag
-Liste in ein NumPy-Array und gibt es zurück. Dieser Vektor repräsentiert das Vorkommen der Wörter aus dem Eingabesatz im Trainingsdatensatz.
16. Definieren der Funktion zur Vorhersage der Klasse eines Satzes
def predict_class(sentence):
bow = bag_of_words(sentence)
res = model.predict(np.array([bow]))[0]
ERROR_THRESHOLD = 0.25
results = [[i, r] for i, r in enumerate(res) if r > ERROR_THRESHOLD]
return_list = []
for r in results:
return_list.append({'intent': classes[r[0]], 'probability': str(r[1])})
return return_list
Funktion predict_class
def predict_class(sentence): Definiert eine Funktion
predict_class
, die einen Satz als Eingabe annimmt und eine Liste von vorhergesagten Klassen (Intents) mit deren Wahrscheinlichkeiten zurückgibt.bow = bag_of_words(sentence): Diese Zeile ruft die Funktion
bag_of_words
auf, um den Eingabesatz in einen Bag-of-Words-Vektor (bow
) zu konvertieren.res = model.predict(np.array([bow]))[0]:
- Diese Zeile verwendet das trainierte Modell (
model
), um eine Vorhersage für den Eingabesatz zu machen. - Der Bag-of-Words-Vektor (
bow
) wird in ein NumPy-Array konvertiert und als Eingabe für die Vorhersage verwendet. - Das Modell gibt eine Liste von Wahrscheinlichkeiten für jede Klasse zurück. Das
[0]
am Ende extrahiert das erste (und einzige) Element aus der Liste der Vorhersagen.
- Diese Zeile verwendet das trainierte Modell (
ERROR_THRESHOLD = 0.25: Setzt einen Schwellenwert (
ERROR_THRESHOLD
) auf 0.25. Nur Klassen mit einer Wahrscheinlichkeit über diesem Schwellenwert werden berücksichtigt.results = [[i, r] for i, r in enumerate(res) if r > ERROR_THRESHOLD]:
- Diese Zeile erstellt eine Liste von Indizes und Wahrscheinlichkeiten für die Klassen, deren Wahrscheinlichkeit über dem Schwellenwert liegt.
- enumerate(res): Gibt sowohl den Index (
i
) als auch die Wahrscheinlichkeit (r
) für jede Klasse inres
zurück. - if r > ERROR_THRESHOLD: Filtert die Klassen heraus, deren Wahrscheinlichkeit unter dem Schwellenwert liegt.
return_list = []: Initialisiert eine leere Liste
return_list
, die die vorhergesagten Klassen (Intents) mit deren Wahrscheinlichkeiten speichern wird.for r in results:: Diese Schleife durchläuft jedes Element (
r
) in derresults
-Liste.return_list.append({'intent': classes[r[0]], 'probability': str(r[1])}):
- Diese Zeile fügt ein Dictionary mit den Schlüsseln
intent
undprobability
zurreturn_list
hinzu. - classes[r[0]]: Verwendet den Index (
r[0]
) aus derresults
-Liste, um den entsprechenden Klassennamen aus derclasses
-Liste zu erhalten. - str(r[1]): Konvertiert die Wahrscheinlichkeit (
r[1]
) in einen String.
- Diese Zeile fügt ein Dictionary mit den Schlüsseln
return return_list: Gibt die
return_list
zurück, die die vorhergesagten Klassen und deren Wahrscheinlichkeiten enthält.
17. Definieren der Funktion zur Generierung der Antwort basierend auf der Vorhersage
def get_response(intents_list, intents_json):
tag = intents_list[0]['intent']
list_of_intents = intents_json['intents']
for i in list_of_intents:
if i['tag'] == tag:
result = random.choice(i['responses'])
break
return result
Funktion get_response
def get_response(intents_list, intents_json): Definiert eine Funktion
get_response
, die eine Liste von Intents (intents_list
) und die JSON-Daten der Intents (intents_json
) als Eingabe annimmt und eine entsprechende Antwort zurückgibt.tag = intents_list[0]['intent']: Diese Zeile extrahiert das Intent-Tag aus dem ersten Element der
intents_list
. Dieintents_list
ist eine Liste von vorhergesagten Intents, und hier wird das Intent-Tag des wahrscheinlichsten Intents ausgewählt.list_of_intents = intents_json['intents']: Diese Zeile extrahiert die Liste aller Intents aus den JSON-Daten (
intents_json
). Dies ist eine Liste von Dictionaries, wobei jedes Dictionary einen Intent mit seinen Mustern und Antworten enthält.for i in list_of_intents:: Diese Schleife durchläuft jedes Intent-Dictionary (
i
) in derlist_of_intents
.if i['tag'] == tag:: Diese Bedingung überprüft, ob das Tag des aktuellen Intents (
i['tag']
) mit dem extrahierten Tag (tag
) übereinstimmt.result = random.choice(i['responses']):
- Wenn das Tag übereinstimmt, wird eine zufällige Antwort aus der Liste der Antworten (
i['responses']
) des aktuellen Intents ausgewählt. - random.choice(i['responses']): Wählt zufällig eine Antwort aus der Liste der Antworten des aktuellen Intents.
- Wenn das Tag übereinstimmt, wird eine zufällige Antwort aus der Liste der Antworten (
break: Diese Zeile beendet die Schleife, sobald eine passende Antwort gefunden wurde.
return result: Gibt die ausgewählte Antwort (
result
) zurück.
18. Erstellen einer Endlosschleife für die Chatbot-Interaktion
while True:
message = input("")
ints = predict_class(message)
res = get_response(ints, intents)
print(res)
Unendliche Schleife
- while True:: Diese Zeile startet eine unendliche Schleife, die kontinuierlich ausgeführt wird, bis sie explizit gestoppt wird (z.B. durch eine Unterbrechung des Benutzers).
Benutzer-Eingabe
- message = input(""):
- Diese Zeile wartet auf eine Eingabe des Benutzers.
- input(""): Fordert den Benutzer auf, eine Nachricht einzugeben. Der leere String (
""
) bedeutet, dass keine zusätzliche Aufforderung angezeigt wird. - Die eingegebene Nachricht wird der Variablen
message
zugewiesen.
Klassenvorhersage
- ints = predict_class(message):
- Diese Zeile ruft die Funktion
predict_class
auf, um die Klasse (Intent) der eingegebenen Nachricht vorherzusagen. - predict_class(message): Verarbeitet die eingegebene Nachricht und gibt eine Liste von vorhergesagten Klassen (Intents) mit ihren Wahrscheinlichkeiten zurück.
- Das Ergebnis wird der Variablen
ints
zugewiesen.
- Diese Zeile ruft die Funktion
Antwort generieren
- res = get_response(ints, intents):
- Diese Zeile ruft die Funktion
get_response
auf, um eine passende Antwort basierend auf den vorhergesagten Klassen (Intents) zu generieren. - get_response(ints, intents): Verarbeitet die vorhergesagten Klassen (
ints
) und die JSON-Daten der Intents (intents
), um eine passende Antwort zu finden. - Die generierte Antwort wird der Variablen
res
zugewiesen.
- Diese Zeile ruft die Funktion
Antwort ausgeben
- print(res):
- Diese Zeile gibt die generierte Antwort (
res
) auf der Konsole aus. - print(res): Zeigt die Antwort an, die von der Funktion
get_response
zurückgegeben wurde.
- Diese Zeile gibt die generierte Antwort (
19. Der gesamte Code
import random
import json
import pickle
import numpy as np
import nltk
# english:
from nltk.stem import WordNetLemmatizer
# Arabic
from nltk.stem.isri import ISRIStemmer
from keras.models import Sequential
from tensorflow.keras.optimizers import SGD
from keras.layers import Dense, Dropout
from keras.models import load_model
isstemmer = ISRIStemmer()
nltk.download('om-1.4')
nltk.download("punkt")
nltk.download("wordnet")
data_file = open("intents_arabic.json", encoding='utf-8', errors= 'ignore').read()
intents = json.loads(data_file)
words = []
classes = []
documents = []
ignore_words = ["?","!",".",","]
for intent in intents["intents"]:
for pattern in intent["patterns"]:
w = nltk.word_tokenize(pattern)
words.extend(w)
documents.append((w, intent["tag"]))
if intent["tag"] not in classes:
classes.append(intent["tag"])
processed_words = []
for w in words:
stemmed_word = isstemmer.stem(w)
if w not in ignore_words:
processed_words.append(stemmed_word)
words = sorted(list(set(processed_words)))
classes = sorted(list(classes))
import random
import numpy as np
training = []
output_empty = [0] * len(classes)
for doc in documents:
bag = []
pattern_words = doc[0]
# Stem the pattern words
pattern_words = [isstemmer.stem(word) for word in pattern_words]
for w in words:
bag.append(1) if w in pattern_words else bag.append(0)
output_row = list(output_empty)
output_row[classes.index(doc[1])] = 1
training.append([bag, output_row])
# Check and fix inconsistencies in lengths and types
bag_length = len(words)
output_length = len(classes)
fixed_training = []
for item in training:
bag, output_row = item
# Ensure bag is the correct length
if len(bag) != bag_length:
if len(bag) < bag_length:
bag = bag + [0] * (bag_length - len(bag))
else:
bag = bag[:bag_length]
# Ensure output_row is the correct length
if len(output_row) != output_length:
if len(output_row) < output_length:
output_row = output_row + [0] * (output_length - len(output_row))
else:
output_row = output_row[:output_length]
# Ensure both bag and output_row are lists (not NumPy arrays)
bag = list(bag)
output_row = list(output_row)
fixed_training.append([bag, output_row])
# Convert fixed_training to a NumPy array
random.shuffle(fixed_training)
training_array = np.array(fixed_training, dtype=object)
print("Training data successfully converted to NumPy array after fixing inconsistencies.")
train_x = list(training_array[:,0])
train_y = list(training_array[:,1])
print("train data created")
model = Sequential()
model.add(Dense(128, input_shape=(len(train_x[0]),), activation="relu"))
model.add(Dropout(0.5))
model.add(Dense(64, activation="relu"))
model.add(Dropout(0.5))
model.add(Dense(len(train_y[0]), activation="softmax"))
model.summary()
sgd = SGD(learning_rate=0.01,momentum = 0.9, nesterov=True)
model.compile(loss="categorical_crossentropy", optimizer = sgd, metrics=["accuracy"])
save_model = model.fit(np.array(train_x), np.array(train_y),epochs=200, batch_size = 5, verbose=1)
model.save("chatbot_mode.h5", save_model)
pickle.dump(words, open("words.pkl","wb"))
pickle.dump(classes, open("classes.pkl","wb"))
words= pickle.load(open('words.pkl', 'rb'))
classes= pickle.load(open('classes.pkl', 'rb'))
model = load_model('chatbot_mode.h5')
def clean_up_sentence(sentence):
sentence_words = nltk.word_tokenize(sentence)
sentence_words = [isstemmer.stem(word) for word in sentence_words]
#print("cleaned up sentence =", sentence_words)
return sentence_words
def bag_of_words(sentence):
sentence_words = clean_up_sentence(sentence)
bag = [0]*len(words)
for w in sentence_words:
for i, word in enumerate(words):
if word ==w:
bag[i]=1
return np.array(bag)
def predict_class(sentence):
bow = bag_of_words(sentence)
res = model.predict(np.array([bow]))[0]
#print("row result",res)
ERROR_THRESHOLD = 0.25
results = [[i,r] for i,r in enumerate(res) if r>ERROR_THRESHOLD]
#print("results after THRESHOLDING", results)
return_list = []
for r in results:
return_list.append({'intent':classes[r[0]], 'probability':str(r[1])})
#print("final probabile results list", return_list)
return return_list
def get_response(intents_list, intents_json):
tag= intents_list[0]['intent']
list_of_intents = intents_json['intents']
for i in list_of_intents:
if i['tag'] == tag:
result = random.choice(i['responses'])
break
return result
while True:
message = input("")
ints = predict_class(message)
#print("ints", ints)
res = get_response(ints, intents)
print(res)