construire un chatbot en python

Comment construire un chatbot en Python ? Ou comment se construire notre propre ami qui peut répondre à toutes nos questions ? 🙂

L’Intelligence Artificielle (IA) s’intègre très rapidement dans le workflow de nombreuses entreprises dans divers secteurs et fonctions. Grâce aux progrès réalisés dans le domaine du traitement du langage naturel (NLP pour Natural Language Processing), de la compréhension du langage naturel (NLU pour Natural Language Understanding) et de l’apprentissage automatique (ML pour Machine Learning), les humains sont désormais en mesure de développer des technologies capables d’imiter les interactions humaines, notamment la reconnaissance de la parole et du texte.

Dans cet article, nous allons construire un Chatbot en utilisant le NLP et les réseaux de neurones de l’apprentissage profond (ou Deep Learning) avec le langage Python.

Les Données

Avant de commencer à penser à coder quelques lignes en Python, nous devons mettre en place un fichier JSON intents qui définit certaines intentions qui pourraient se produire pendant les interactions avec notre chatbot. Pour ce faire, nous devons d’abord créer un ensemble de balises (ou tags) dans lesquelles les requêtes des utilisateurs peuvent s’inscrire.
Par exemple :

  • Un utilisateur peut souhaiter connaître le prénom de notre chatbot, nous créons donc une intention étiquetée avec un tag appelé name.
  • Un utilisateur peut souhaiter connaître l’âge de notre chatbot, nous créons donc une intention étiquetée avec le tag age.
  • et ainsi de suite…

Tags et Patterns

Pour chacun des tags que nous créons, nous devons spécifier des patterns (ou motifs ou modèles). Ainsi, cela définit les différentes façons dont un utilisateur peut poser une question à notre chatbot. Par exemple, sous le tag name, un utilisateur peut demander le prénom d’une personne de différentes manières : « Quel est ton prénom ? », « Qui es-tu ? », « Comment t’appelles-tu ? ».

Le chatbot prend ensuite ces patterns et les utilise comme données d’apprentissage pour déterminer à quoi ressemblerait une personne demandant le prénom de notre chatbot. Le but c’est qu’il puisse s’adapter aux différentes façons dont une personne peut demander le prénom à notre chatbot. Par conséquent, les utilisateurs n’ont pas besoin d’utiliser les requêtes exactes que notre chatbot a apprises. Il pourrait poser la question « Comment t’appelles-tu ? » et notre chatbot serait capable de déduire que l’utilisateur veut connaître son prénom et en réponse il lui fournirait alors son prénom.

Remarque : Notre robot ne sera pas super intelligent et donc il ne reconnaîtra pas toujours ce qu’on lui dit ou demande. Mais avec suffisamment d’exemples, il pourra faire un travail de décryptage plus qu’intéressant. Gardez en tête que notre objectif c’est surtout la mise en place de techniques de NLP et de Deep Learning. Et ainsi construire notre chatbot avec ces 2 éléments sous la capot.

Réponses associées aux Patterns

Dans ce fichier JSON intents contenant nos diverses intentions, à côté de chaque tag et pattern d’intentions, il y aura des réponses pré-enregistrées. En effet, pour notre chatbot (qui je lme répète sera très simple et naïf), ces réponses ne seront pas générées. Cela signifie que nos patterns ne seront pas aussi fluides que les patterns que les utilisateurs peuvent demander (c’est à dire qu’ils ne s’adapteront pas à la situation et au contexte).
Qu’est-ce que cela veut dire ? Hé bien, tout simplement que les réponses seront des réponses statiques que le chatbot renverra lorsqu’on lui posera une question.

# utilisation d'un dictionnaire pour représenter un fichier JSON d'intentions
data = {"intents": [
             {"tag": "greeting",
              "patterns": ["Hello", "La forme?", "yo", "Salut", "ça roule?"],
              "responses": ["Salut à toi!", "Hello", "Comment vas tu?", "Salutations!", "Enchanté"],
             },
             {"tag": "age",
              "patterns": ["Quel âge as-tu?", "C'est quand ton anniversaire?", "Quand es-tu né?"],
              "responses": ["J'ai 25 ans", "Je suis né en 1996", "Ma date d'anniversaire est le 3 juillet et je suis né en 1996", "03/07/1996"]
             },
             {"tag": "date",
              "patterns": ["Que fais-tu ce week-end?",
"Tu veux qu'on fasse un truc ensemble?", "Quels sont tes plans pour cette semaine"],
              "responses": ["Je suis libre toute la semaine", "Je n'ai rien de prévu", "Je ne suis pas occupé"]
             },
             {"tag": "name",
              "patterns": ["Quel est ton prénom?", "Comment tu t'appelles?", "Qui es-tu?"],
              "responses": ["Mon prénom est Miki", "Je suis Miki", "Miki"]
             },
             {"tag": "goodbye",
              "patterns": [ "bye", "Salut", "see ya", "adios", "cya"],
              "responses": ["C'était sympa de te parler", "à plus tard", "On se reparle très vite!"]
             }
]}

Le code pour construire un chatbot en Python

Les modules et bibliothèques Python

Pour coder notre robot, nous allons avoir besoin de quelques modules intégrés à Python, ainsi que certaines bibliothèques populaires pour le NLP et le Deep Learning, ainsi que la bibliothèque de facto NumPy, qui est idéale pour traiter les tableaux.

import json
import string
import random 
import nltk
import numpy as np
from nltk.stem import WordNetLemmatizer 
import tensorflow as tf 
from tensorflow.keras import Sequential 
from tensorflow.keras.layers import Dense, Dropout
nltk.download("punkt")
nltk.download("wordnet")

Séparation des données

Afin de créer nos données d’entraînement, nous devons d’abord effectuer certaines opérations sur nos données telles que :

  • Créer un vocabulaire de tous les mots utilisés dans les patterns (rappelons que les patterns sont les requêtes/questions posées par l’utilisateur).
  • Créer une liste des classes – Il s’agit simplement des tags de chaque intention.
  • Créer une liste de tous les patterns dans le fichier des intentions.
  • Créer une liste de touts les tags associés à chaque pattern dans le fichier intents.

Voyons voir cela en Python…

# initialisation de lemmatizer pour obtenir la racine des mots
lemmatizer = WordNetLemmatizer()

# création des listes
words = []
classes = []
doc_X = []
doc_y = []

# parcourir avec une boucle For toutes les intentions
# tokéniser chaque pattern et ajouter les tokens à la liste words, les patterns et
# le tag associé à l'intention sont ajoutés aux listes correspondantes
for intent in data["intents"]:
    for pattern in intent["patterns"]:
        tokens = nltk.word_tokenize(pattern)
        words.extend(tokens)
        doc_X.append(pattern)
        doc_y.append(intent["tag"])
    
    # ajouter le tag aux classes s'il n'est pas déjà là 
    if intent["tag"] not in classes:
        classes.append(intent["tag"])

# lemmatiser tous les mots du vocabulaire et les convertir en minuscule
# si les mots n'apparaissent pas dans la ponctuation
words = [lemmatizer.lemmatize(word.lower()) for word in words if word not in string.punctuation]

# trier le vocabulaire et les classes par ordre alphabétique et prendre le
# set pour s'assurer qu'il n'y a pas de doublons
words = sorted(set(words))
classes = sorted(set(classes))

Voici à quoi ressemble chaque liste :

print(words)
['adios', 'anniversaire', 'as-tu', 'bye', "c'est", 'ce', 'cette', 'comment', 'cya', 'ensemble', 'es-tu', 'est', 'fais-tu', 'fasse', 'forme', 'hello', 'la', 'né', 'plan', 'pour', 'prénom', "qu'on", 'quand', 'que', 'quel', 'quels', 'qui', 'roule', 'salut', 'see', 'semaine', 'sont', "t'appelles", 'te', 'ton', 'truc', 'tu', 'un', 'veux', 'week-end', 'ya', 'yo', 'âge', 'ça']
print(classes)
['age', 'date', 'goodbye', 'greeting', 'name']
print(doc_X)
['Hello', 'La forme?', 'yo', 'Salut', 'ça roule?', 'Quel âge as-tu?', "C'est quand ton anniversaire?", 'Quand es-tu né?', 'Que fais-tu ce week-end?', "Tu veux qu'on fasse un truc ensemble?", 'Quels sont tes plans pour cette semaine', 'Quel est ton prénom?', "Comment tu t'appelles?", 'Qui es-tu?', 'bye', 'Salut', 'see ya', 'adios', 'cya']
print(doc_y)
['greeting', 'greeting', 'greeting', 'greeting', 'greeting', 'age', 'age', 'age', 'date', 'date', 'date', 'name', 'name', 'name', 'goodbye', 'goodbye', 'goodbye', 'goodbye', 'goodbye']

Traitement des données

Maintenant que nous avons séparé nos données, nous sommes prêts à entraîner notre algorithme. Cependant, les réseaux de neurones s’attendent à recevoir des valeurs numériques, et non des mots. Nous devons donc d’abord traiter nos données pour qu’un réseau de neurones puisse lire ce que nous faisons.

Afin de convertir nos données en valeurs numériques, nous allons utiliser une technique de « sac de mots ».

# liste pour les données d'entraînement
training = []
out_empty = [0] * len(classes)

# création du modèle d'ensemble de mots
for idx, doc in enumerate(doc_X):
    bow = []
    text = lemmatizer.lemmatize(doc.lower())
    for word in words:
        bow.append(1) if word in text else bow.append(0)

    # marque l'index de la classe à laquelle le pattern atguel est associé à
    output_row = list(out_empty)
    output_row[classes.index(doc_y[idx])] = 1

    # ajoute le one hot encoded BoW et les classes associées à la liste training
    training.append([bow, output_row])

# mélanger les données et les convertir en array
random.shuffle(training)
training = np.array(training, dtype=object)

# séparer les features et les labels target
train_X = np.array(list(training[:, 0]))
train_y = np.array(list(training[:, 1]))

Construction du réseau de neurones de Deep Learning

Après avoir converti nos données au format numérique, nous pouvons maintenant construire un modèle de réseau de neurones dans lequel nous allons introduire nos données d’entraînement. L’idée c’est que le modèle examine les features et prédise l’étiquette associée à ces features, puis sélectionne une réponse appropriée à partir de cette étiquette. Si besoin, n’hésitez pas à consulter ma formation complète de Deep Learning.

# définition de quelques paramètres
input_shape = (len(train_X[0]),)
output_shape = len(train_y[0])
epochs = 200
# modèle Deep Learning
model = Sequential()
model.add(Dense(128, input_shape=input_shape, activation="relu"))
model.add(Dropout(0.5))
model.add(Dense(64, activation="relu"))
model.add(Dropout(0.3))
model.add(Dense(output_shape, activation = "softmax"))

adam = tf.keras.optimizers.Adam(learning_rate=0.01, decay=1e-6)
model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=["accuracy"])
print(model.summary())
Construire un chatbot en Python - modèle Deep Learning

Dans notre modèle séquentiel, nous avons utilisé des couches d’exclusion (ou DropOut) qui sont très efficaces pour empêcher les modèles de Deep Learning de s’adapter de manière excessive aux données.

# entraînement du modèle
model.fit(x=train_X, y=train_y, epochs=200, verbose=1)

Création de l’application de chatbot

Super ! Nous avons entraîné notre modèle de Deep Learning, mais nous devons maintenant créer les fonctions réelles qui nous permettraient d’utiliser notre modèle dans une application de chatbot. Pour cette prochaine tâche, j’ai créé un ensemble de fonctions utilitaires qui nous permettront d’effectuer facilement cette tâche.

def clean_text(text): 
  tokens = nltk.word_tokenize(text)
  tokens = [lemmatizer.lemmatize(word) for word in tokens]
  return tokens

def bag_of_words(text, vocab): 
  tokens = clean_text(text)
  bow = [0] * len(vocab)
  for w in tokens: 
    for idx, word in enumerate(vocab):
      if word == w: 
        bow[idx] = 1
  return np.array(bow)

def pred_class(text, vocab, labels): 
  bow = bag_of_words(text, vocab)
  result = model.predict(np.array([bow]))[0]
  thresh = 0.2
  y_pred = [[idx, res] for idx, res in enumerate(result) if res > thresh]

  y_pred.sort(key=lambda x: x[1], reverse=True)
  return_list = []
  for r in y_pred:
    return_list.append(labels[r[0]])
  return return_list

def get_response(intents_list, intents_json): 
  tag = intents_list[0]
  list_of_intents = intents_json["intents"]
  for i in list_of_intents: 
    if i["tag"] == tag:
      result = random.choice(i["responses"])
      break
  return result

La partie suivante est simple. Nous devons créer une boucle while qui permet à l’utilisateur de saisir une requête qui est ensuite nettoyée, c’est-à-dire que nous prenons les tokens et lemmatisons chaque mot. Après cela, nous convertissons notre texte en valeurs numériques en utilisant notre modèle de « sac de mots » et nous faisons une prédiction du tag que les features représentent le mieux à partir de nos intentions.
À partir de là, nous prenons une réponse aléatoire parmi nos réponses dans cette étiquette d’intentions et l’utilisons pour répondre à la requête/question de l’utilisateur.

# lancement du chatbot
while True:
    message = input("")
    intents = pred_class(message, words, classes)
    result = get_response(intents, data)
    print(result)

Voyons notre chatbot en action… Je vous laisse copier et exécuter le notebook suivant sur Google Colab : Notebook Construire un chatbot en Python avec du Deep Learning.

Construire un chatbot en Python - notre discussion

Voici un exemple de discussion que j’ai eu avec notre nouvel ami Miki :

Salut
Enchanté
Quel est ton prénom?
Miki
Quel âge as-tu?
Ma date d'anniversaire est le 3 juillet et je suis né en 1996
Que fais-tu ce weekend?
Je n'ai rien de prévu
bye
On se reparle très vite!

C’est terminé pour la construction pas à pas de notre chatbot (très simple certes) en Python en appliquant des concepts de NLP et de Deep Learning. J’espère que cela vous a plu, n’hésitez pas à partager et commenter ci-dessous 🙂

A lire également

    • Tokenizer ici consiste à remplacer le texte en tokens en fonction de l’intention (intents)