Menu iconMenu icon
Procesamiento de Lenguaje Natural con Python Edición Actualizada

Capítulo 11: Proyecto de Chatbot: Chatbot Asistente Personal

11.2 Recolección y Preprocesamiento de Datos

La recolección y el preprocesamiento de datos son pasos críticos para construir un chatbot efectivo. La calidad y relevancia de los datos utilizados para entrenar el modelo impactan directamente en el rendimiento del chatbot. En esta sección, discutiremos cómo recolectar y preprocesar datos para nuestro chatbot asistente personal.

11.2.1 Recolección de Datos

Para nuestro chatbot asistente personal, necesitamos datos que cubran una amplia gama de intenciones y entidades de los usuarios. Podemos comenzar con las intenciones y patrones definidos en nuestro archivo intents.json y expandirlo con fuentes de datos adicionales:

  1. Recolección Manual de Datos: Crear manualmente una lista de consultas y respuestas comunes de los usuarios.
  2. Conjuntos de Datos Públicos: Utilizar conjuntos de datos disponibles públicamente que contengan datos conversacionales, como el Corpus de Diálogos de Películas de Cornell o el conjunto de datos de ChatterBot.
  3. Documentación de API: Para tareas específicas como actualizaciones meteorológicas o establecimiento de recordatorios, consultar la documentación de la API para comprender el formato de los datos y las consultas de muestra.

Vamos a mejorar nuestro archivo intents.json con más patrones y respuestas para hacer que el chatbot sea más robusto.

{
    "intents": [
        {
            "tag": "greeting",
            "patterns": ["Hi", "Hello", "Hey"],
            "responses": ["Hello! How can I assist you today?", "Hi there! What can I do for you?", "Hey! How can I help?"]
        },
        {
            "tag": "goodbye",
            "patterns": ["Bye", "Goodbye", "See you later"],
            "responses": ["Goodbye! Have a great day!", "See you later! Take care!"]
        },
        {
            "tag": "weather",
            "patterns": ["What's the weather like?", "Tell me the weather", "How's the weather today?"],
            "responses": ["Let me check the weather for you.", "Fetching the weather details..."]
        },
        {
            "tag": "reminder",
            "patterns": ["Set a reminder", "Remind me to", "Add a reminder"],
            "responses": ["Sure, what would you like to be reminded about?", "When would you like the reminder to be set?"]
        }
    ]
}

Este archivo define algunas intenciones básicas: saludo, despedida, clima y recordatorio. Cada intención tiene patrones (posibles entradas del usuario) y respuestas (respuestas predefinidas del chatbot).

Si deseas una comprensión más profunda sobre el manejo de archivos JSON, te recomendamos leer este artículo del blog: https://www.cuantum.tech/post/mastering-json-creating-handling-and-working-with-json-files

11.2.2 Construyendo el Motor NLP

A continuación, construiremos el motor NLP para procesar las entradas de los usuarios, reconocer intenciones y extraer entidades. Usaremos TensorFlow para entrenar un modelo simple de reconocimiento de intenciones.

nlp_engine.py:

import json
import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from sklearn.preprocessing import LabelEncoder
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split

# Load the intents file
with open('data/intents.json') as file:
    intents = json.load(file)

# Extract patterns and corresponding tags
patterns = []
tags = []
for intent in intents['intents']:
    for pattern in intent['patterns']:
        patterns.append(pattern)
        tags.append(intent['tag'])

# Encode the tags
label_encoder = LabelEncoder()
labels = label_encoder.fit_transform(tags)

# Vectorize the patterns
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(patterns).toarray()
y = np.array(labels)

# Split the data into training and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Build the model
model = Sequential()
model.add(Dense(128, input_shape=(X_train.shape[1],), activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(64, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(len(label_encoder.classes_), activation='softmax'))

# Compile the model
model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

# Train the model
model.fit(X_train, y_train, epochs=100, batch_size=8, verbose=1, validation_data=(X_test, y_test))

# Save the model and tokenizer
model.save('models/nlp_model.h5')
with open('models/tokenizer.pickle', 'wb') as file:
    pickle.dump(vectorizer, file)
with open('models/label_encoder.pickle', 'wb') as file:
    pickle.dump(label_encoder, file)

Aquí tienes una explicación detallada de cada parte del script:

  1. Importación de Bibliotecas:
    import json
    import numpy as np
    import tensorflow as tf
    from tensorflow.keras.models import Sequential
    from tensorflow.keras.layers import Dense, Dropout
    from sklearn.preprocessing import LabelEncoder
    from sklearn.feature_extraction.text import TfidfVectorizer
    from sklearn.model_selection import train_test_split

    Esta sección importa las bibliotecas esenciales:

    • json: Para manejar archivos JSON.
    • numpy: Para operaciones numéricas.
    • tensorflow y keras: Para construir y entrenar la red neuronal.
    • LabelEncoder y TfidfVectorizer de scikit-learn: Para codificar etiquetas y vectorizar datos de texto.
    • train_test_split de scikit-learn: Para dividir el conjunto de datos en conjuntos de entrenamiento y prueba.
  2. Cargando el Archivo de Intenciones:
    with open('data/intents.json') as file:
        intents = json.load(file)

    Este fragmento de código carga el archivo JSON de intenciones, que contiene varias intenciones de usuario y sus correspondientes patrones y respuestas.

  3. Extracción de Patrones y Etiquetas:
    patterns = []
    tags = []
    for intent in intents['intents']:
        for pattern in intent['patterns']:
            patterns.append(pattern)
            tags.append(intent['tag'])

    Aquí, el script itera a través de las intenciones y extrae los patrones (entradas del usuario) y sus etiquetas correspondientes (etiquetas de intención). Estos se almacenan en las listas patterns y tags, respectivamente.

  4. Codificación de las Etiquetas:
    label_encoder = LabelEncoder()
    labels = label_encoder.fit_transform(tags)

    Las etiquetas se codifican en valores numéricos utilizando LabelEncoder, lo cual es necesario para entrenar la red neuronal.

  5. Vectorización de los Patrones:
    vectorizer = TfidfVectorizer()
    X = vectorizer.fit_transform(patterns).toarray()
    y = np.array(labels)

    El TfidfVectorizer convierte los patrones de texto en vectores numéricos basados en el esquema de Frecuencia de Término-Inversa Frecuencia de Documento (TF-IDF). Esta transformación es crucial para alimentar los datos de texto en la red neuronal.

  6. División de los Datos:
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

    El conjunto de datos se divide en conjuntos de entrenamiento y prueba utilizando una proporción del 80-20. El parámetro random_state asegura la reproducibilidad.

  7. Construcción del Modelo de Red Neuronal:
    model = Sequential()
    model.add(Dense(128, input_shape=(X_train.shape[1],), activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(64, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(len(label_encoder.classes_), activation='softmax'))

    Se construye un modelo de red neuronal secuencial utilizando Keras. Consiste en:

    • Una capa de entrada con 128 neuronas y activación ReLU.
    • Una capa de dropout con una tasa de abandono del 50% para prevenir el sobreajuste.
    • Una capa oculta con 64 neuronas y activación ReLU.
    • Otra capa de dropout con una tasa de abandono del 50%.
    • Una capa de salida con el número de neuronas igual al número de intenciones únicas, utilizando activación softmax para la clasificación multiclase.
  8. Compilación del Modelo:
    model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

    El modelo se compila con:

    • La función de pérdida sparse_categorical_crossentropy, adecuada para la clasificación multiclase con etiquetas enteras.
    • El optimizador adam, una elección popular por su eficiencia.
    • accuracy como la métrica de evaluación.
  9. Entrenamiento del Modelo:
    model.fit(X_train, y_train, epochs=100, batch_size=8, verbose=1, validation_data=(X_test, y_test))

    El modelo se entrena durante 100 épocas con un tamaño de lote de 8. El proceso de entrenamiento utiliza los datos de entrenamiento y evalúa el rendimiento en los datos de prueba después de cada época.

  10. Guardando el Modelo y el Tokenizador:
    model.save('models/nlp_model.h5')
    with open('models/tokenizer.pickle', 'wb') as file:
        pickle.dump(vectorizer, file)
    with open('models/label_encoder.pickle', 'wb') as file:
        pickle.dump(label_encoder, file)

    Una vez entrenado, el modelo se guarda en un archivo HDF5 (nlp_model.h5). Además, los objetos TfidfVectorizer y LabelEncoder se guardan utilizando el módulo pickle. Estos objetos guardados son esenciales para preprocesar nuevos datos durante la inferencia.

En resumen, este script procesa los datos de entrenamiento del chatbot, construye una red neuronal para el reconocimiento de intenciones, entrena el modelo y guarda los componentes necesarios para su uso futuro.

En esta sección, presentamos el proyecto del chatbot asistente personal, esbozamos las consideraciones de diseño y configuramos la estructura inicial del proyecto. También definimos las intenciones y entidades y construimos el motor NLP para el reconocimiento de intenciones. Esto sienta las bases para desarrollar un chatbot asistente personal completamente funcional que puede manejar varias tareas y mejorar la productividad del usuario.

11.2.3 Manejo de Datos Faltantes o Desequilibrados

En aplicaciones del mundo real, los datos pueden estar incompletos o desequilibrados. Es importante abordar estos problemas durante el preprocesamiento para asegurar que el modelo funcione bien.

  1. Manejo de Datos Faltantes: Al lidiar con datos faltantes, es esencial reemplazar los valores faltantes con un marcador de posición, como la media o mediana de la columna, o eliminar las instancias que contienen datos faltantes. Esto asegura que el conjunto de datos se mantenga limpio y utilizable para el análisis o entrenamiento del modelo.
  2. Abordar Datos Desequilibrados: Para abordar el problema de los datos desequilibrados, que pueden afectar negativamente el rendimiento del modelo, se pueden emplear varias técnicas. Estas incluyen el sobremuestreo de la clase minoritaria, el submuestreo de la clase mayoritaria o la generación de muestras sintéticas utilizando métodos como SMOTE (Synthetic Minority Over-sampling Technique). Balancear el conjunto de datos de esta manera ayuda a lograr resultados más confiables y precisos.

Ejemplo: Manejo de Datos Faltantes y Desequilibrados:

from imblearn.over_sampling import SMOTE

# Check for missing data
print(f"Missing values: {np.isnan(X).sum()}")

# Handle missing data (if any)
X = np.nan_to_num(X)

# Balance the dataset using SMOTE (Synthetic Minority Over-sampling Technique)
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X, y)

# Split the resampled data into training and test sets
X_train, X_test, y_train, y_test = train_test_split(X_resampled, y_resampled, test_size=0.2, random_state=42)

# Train the model with the balanced dataset
model.fit(X_train, y_train, epochs=100, batch_size=8, verbose=1, validation_data=(X_test, y_test))

Este fragmento de código de ejemplo demuestra un flujo de trabajo de aprendizaje automático para manejar conjuntos de datos desequilibrados utilizando SMOTE (Synthetic Minority Over-sampling Technique). Primero, verifica y maneja cualquier valor faltante en el conjunto de características X.

Luego, aplica SMOTE para equilibrar el conjunto de datos generando muestras sintéticas para la clase minoritaria. Después de equilibrar, divide los datos en conjuntos de entrenamiento y prueba. Finalmente, entrena un modelo de aprendizaje automático utilizando el conjunto de datos equilibrado.

11.2 Recolección y Preprocesamiento de Datos

La recolección y el preprocesamiento de datos son pasos críticos para construir un chatbot efectivo. La calidad y relevancia de los datos utilizados para entrenar el modelo impactan directamente en el rendimiento del chatbot. En esta sección, discutiremos cómo recolectar y preprocesar datos para nuestro chatbot asistente personal.

11.2.1 Recolección de Datos

Para nuestro chatbot asistente personal, necesitamos datos que cubran una amplia gama de intenciones y entidades de los usuarios. Podemos comenzar con las intenciones y patrones definidos en nuestro archivo intents.json y expandirlo con fuentes de datos adicionales:

  1. Recolección Manual de Datos: Crear manualmente una lista de consultas y respuestas comunes de los usuarios.
  2. Conjuntos de Datos Públicos: Utilizar conjuntos de datos disponibles públicamente que contengan datos conversacionales, como el Corpus de Diálogos de Películas de Cornell o el conjunto de datos de ChatterBot.
  3. Documentación de API: Para tareas específicas como actualizaciones meteorológicas o establecimiento de recordatorios, consultar la documentación de la API para comprender el formato de los datos y las consultas de muestra.

Vamos a mejorar nuestro archivo intents.json con más patrones y respuestas para hacer que el chatbot sea más robusto.

{
    "intents": [
        {
            "tag": "greeting",
            "patterns": ["Hi", "Hello", "Hey"],
            "responses": ["Hello! How can I assist you today?", "Hi there! What can I do for you?", "Hey! How can I help?"]
        },
        {
            "tag": "goodbye",
            "patterns": ["Bye", "Goodbye", "See you later"],
            "responses": ["Goodbye! Have a great day!", "See you later! Take care!"]
        },
        {
            "tag": "weather",
            "patterns": ["What's the weather like?", "Tell me the weather", "How's the weather today?"],
            "responses": ["Let me check the weather for you.", "Fetching the weather details..."]
        },
        {
            "tag": "reminder",
            "patterns": ["Set a reminder", "Remind me to", "Add a reminder"],
            "responses": ["Sure, what would you like to be reminded about?", "When would you like the reminder to be set?"]
        }
    ]
}

Este archivo define algunas intenciones básicas: saludo, despedida, clima y recordatorio. Cada intención tiene patrones (posibles entradas del usuario) y respuestas (respuestas predefinidas del chatbot).

Si deseas una comprensión más profunda sobre el manejo de archivos JSON, te recomendamos leer este artículo del blog: https://www.cuantum.tech/post/mastering-json-creating-handling-and-working-with-json-files

11.2.2 Construyendo el Motor NLP

A continuación, construiremos el motor NLP para procesar las entradas de los usuarios, reconocer intenciones y extraer entidades. Usaremos TensorFlow para entrenar un modelo simple de reconocimiento de intenciones.

nlp_engine.py:

import json
import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from sklearn.preprocessing import LabelEncoder
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split

# Load the intents file
with open('data/intents.json') as file:
    intents = json.load(file)

# Extract patterns and corresponding tags
patterns = []
tags = []
for intent in intents['intents']:
    for pattern in intent['patterns']:
        patterns.append(pattern)
        tags.append(intent['tag'])

# Encode the tags
label_encoder = LabelEncoder()
labels = label_encoder.fit_transform(tags)

# Vectorize the patterns
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(patterns).toarray()
y = np.array(labels)

# Split the data into training and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Build the model
model = Sequential()
model.add(Dense(128, input_shape=(X_train.shape[1],), activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(64, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(len(label_encoder.classes_), activation='softmax'))

# Compile the model
model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

# Train the model
model.fit(X_train, y_train, epochs=100, batch_size=8, verbose=1, validation_data=(X_test, y_test))

# Save the model and tokenizer
model.save('models/nlp_model.h5')
with open('models/tokenizer.pickle', 'wb') as file:
    pickle.dump(vectorizer, file)
with open('models/label_encoder.pickle', 'wb') as file:
    pickle.dump(label_encoder, file)

Aquí tienes una explicación detallada de cada parte del script:

  1. Importación de Bibliotecas:
    import json
    import numpy as np
    import tensorflow as tf
    from tensorflow.keras.models import Sequential
    from tensorflow.keras.layers import Dense, Dropout
    from sklearn.preprocessing import LabelEncoder
    from sklearn.feature_extraction.text import TfidfVectorizer
    from sklearn.model_selection import train_test_split

    Esta sección importa las bibliotecas esenciales:

    • json: Para manejar archivos JSON.
    • numpy: Para operaciones numéricas.
    • tensorflow y keras: Para construir y entrenar la red neuronal.
    • LabelEncoder y TfidfVectorizer de scikit-learn: Para codificar etiquetas y vectorizar datos de texto.
    • train_test_split de scikit-learn: Para dividir el conjunto de datos en conjuntos de entrenamiento y prueba.
  2. Cargando el Archivo de Intenciones:
    with open('data/intents.json') as file:
        intents = json.load(file)

    Este fragmento de código carga el archivo JSON de intenciones, que contiene varias intenciones de usuario y sus correspondientes patrones y respuestas.

  3. Extracción de Patrones y Etiquetas:
    patterns = []
    tags = []
    for intent in intents['intents']:
        for pattern in intent['patterns']:
            patterns.append(pattern)
            tags.append(intent['tag'])

    Aquí, el script itera a través de las intenciones y extrae los patrones (entradas del usuario) y sus etiquetas correspondientes (etiquetas de intención). Estos se almacenan en las listas patterns y tags, respectivamente.

  4. Codificación de las Etiquetas:
    label_encoder = LabelEncoder()
    labels = label_encoder.fit_transform(tags)

    Las etiquetas se codifican en valores numéricos utilizando LabelEncoder, lo cual es necesario para entrenar la red neuronal.

  5. Vectorización de los Patrones:
    vectorizer = TfidfVectorizer()
    X = vectorizer.fit_transform(patterns).toarray()
    y = np.array(labels)

    El TfidfVectorizer convierte los patrones de texto en vectores numéricos basados en el esquema de Frecuencia de Término-Inversa Frecuencia de Documento (TF-IDF). Esta transformación es crucial para alimentar los datos de texto en la red neuronal.

  6. División de los Datos:
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

    El conjunto de datos se divide en conjuntos de entrenamiento y prueba utilizando una proporción del 80-20. El parámetro random_state asegura la reproducibilidad.

  7. Construcción del Modelo de Red Neuronal:
    model = Sequential()
    model.add(Dense(128, input_shape=(X_train.shape[1],), activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(64, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(len(label_encoder.classes_), activation='softmax'))

    Se construye un modelo de red neuronal secuencial utilizando Keras. Consiste en:

    • Una capa de entrada con 128 neuronas y activación ReLU.
    • Una capa de dropout con una tasa de abandono del 50% para prevenir el sobreajuste.
    • Una capa oculta con 64 neuronas y activación ReLU.
    • Otra capa de dropout con una tasa de abandono del 50%.
    • Una capa de salida con el número de neuronas igual al número de intenciones únicas, utilizando activación softmax para la clasificación multiclase.
  8. Compilación del Modelo:
    model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

    El modelo se compila con:

    • La función de pérdida sparse_categorical_crossentropy, adecuada para la clasificación multiclase con etiquetas enteras.
    • El optimizador adam, una elección popular por su eficiencia.
    • accuracy como la métrica de evaluación.
  9. Entrenamiento del Modelo:
    model.fit(X_train, y_train, epochs=100, batch_size=8, verbose=1, validation_data=(X_test, y_test))

    El modelo se entrena durante 100 épocas con un tamaño de lote de 8. El proceso de entrenamiento utiliza los datos de entrenamiento y evalúa el rendimiento en los datos de prueba después de cada época.

  10. Guardando el Modelo y el Tokenizador:
    model.save('models/nlp_model.h5')
    with open('models/tokenizer.pickle', 'wb') as file:
        pickle.dump(vectorizer, file)
    with open('models/label_encoder.pickle', 'wb') as file:
        pickle.dump(label_encoder, file)

    Una vez entrenado, el modelo se guarda en un archivo HDF5 (nlp_model.h5). Además, los objetos TfidfVectorizer y LabelEncoder se guardan utilizando el módulo pickle. Estos objetos guardados son esenciales para preprocesar nuevos datos durante la inferencia.

En resumen, este script procesa los datos de entrenamiento del chatbot, construye una red neuronal para el reconocimiento de intenciones, entrena el modelo y guarda los componentes necesarios para su uso futuro.

En esta sección, presentamos el proyecto del chatbot asistente personal, esbozamos las consideraciones de diseño y configuramos la estructura inicial del proyecto. También definimos las intenciones y entidades y construimos el motor NLP para el reconocimiento de intenciones. Esto sienta las bases para desarrollar un chatbot asistente personal completamente funcional que puede manejar varias tareas y mejorar la productividad del usuario.

11.2.3 Manejo de Datos Faltantes o Desequilibrados

En aplicaciones del mundo real, los datos pueden estar incompletos o desequilibrados. Es importante abordar estos problemas durante el preprocesamiento para asegurar que el modelo funcione bien.

  1. Manejo de Datos Faltantes: Al lidiar con datos faltantes, es esencial reemplazar los valores faltantes con un marcador de posición, como la media o mediana de la columna, o eliminar las instancias que contienen datos faltantes. Esto asegura que el conjunto de datos se mantenga limpio y utilizable para el análisis o entrenamiento del modelo.
  2. Abordar Datos Desequilibrados: Para abordar el problema de los datos desequilibrados, que pueden afectar negativamente el rendimiento del modelo, se pueden emplear varias técnicas. Estas incluyen el sobremuestreo de la clase minoritaria, el submuestreo de la clase mayoritaria o la generación de muestras sintéticas utilizando métodos como SMOTE (Synthetic Minority Over-sampling Technique). Balancear el conjunto de datos de esta manera ayuda a lograr resultados más confiables y precisos.

Ejemplo: Manejo de Datos Faltantes y Desequilibrados:

from imblearn.over_sampling import SMOTE

# Check for missing data
print(f"Missing values: {np.isnan(X).sum()}")

# Handle missing data (if any)
X = np.nan_to_num(X)

# Balance the dataset using SMOTE (Synthetic Minority Over-sampling Technique)
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X, y)

# Split the resampled data into training and test sets
X_train, X_test, y_train, y_test = train_test_split(X_resampled, y_resampled, test_size=0.2, random_state=42)

# Train the model with the balanced dataset
model.fit(X_train, y_train, epochs=100, batch_size=8, verbose=1, validation_data=(X_test, y_test))

Este fragmento de código de ejemplo demuestra un flujo de trabajo de aprendizaje automático para manejar conjuntos de datos desequilibrados utilizando SMOTE (Synthetic Minority Over-sampling Technique). Primero, verifica y maneja cualquier valor faltante en el conjunto de características X.

Luego, aplica SMOTE para equilibrar el conjunto de datos generando muestras sintéticas para la clase minoritaria. Después de equilibrar, divide los datos en conjuntos de entrenamiento y prueba. Finalmente, entrena un modelo de aprendizaje automático utilizando el conjunto de datos equilibrado.

11.2 Recolección y Preprocesamiento de Datos

La recolección y el preprocesamiento de datos son pasos críticos para construir un chatbot efectivo. La calidad y relevancia de los datos utilizados para entrenar el modelo impactan directamente en el rendimiento del chatbot. En esta sección, discutiremos cómo recolectar y preprocesar datos para nuestro chatbot asistente personal.

11.2.1 Recolección de Datos

Para nuestro chatbot asistente personal, necesitamos datos que cubran una amplia gama de intenciones y entidades de los usuarios. Podemos comenzar con las intenciones y patrones definidos en nuestro archivo intents.json y expandirlo con fuentes de datos adicionales:

  1. Recolección Manual de Datos: Crear manualmente una lista de consultas y respuestas comunes de los usuarios.
  2. Conjuntos de Datos Públicos: Utilizar conjuntos de datos disponibles públicamente que contengan datos conversacionales, como el Corpus de Diálogos de Películas de Cornell o el conjunto de datos de ChatterBot.
  3. Documentación de API: Para tareas específicas como actualizaciones meteorológicas o establecimiento de recordatorios, consultar la documentación de la API para comprender el formato de los datos y las consultas de muestra.

Vamos a mejorar nuestro archivo intents.json con más patrones y respuestas para hacer que el chatbot sea más robusto.

{
    "intents": [
        {
            "tag": "greeting",
            "patterns": ["Hi", "Hello", "Hey"],
            "responses": ["Hello! How can I assist you today?", "Hi there! What can I do for you?", "Hey! How can I help?"]
        },
        {
            "tag": "goodbye",
            "patterns": ["Bye", "Goodbye", "See you later"],
            "responses": ["Goodbye! Have a great day!", "See you later! Take care!"]
        },
        {
            "tag": "weather",
            "patterns": ["What's the weather like?", "Tell me the weather", "How's the weather today?"],
            "responses": ["Let me check the weather for you.", "Fetching the weather details..."]
        },
        {
            "tag": "reminder",
            "patterns": ["Set a reminder", "Remind me to", "Add a reminder"],
            "responses": ["Sure, what would you like to be reminded about?", "When would you like the reminder to be set?"]
        }
    ]
}

Este archivo define algunas intenciones básicas: saludo, despedida, clima y recordatorio. Cada intención tiene patrones (posibles entradas del usuario) y respuestas (respuestas predefinidas del chatbot).

Si deseas una comprensión más profunda sobre el manejo de archivos JSON, te recomendamos leer este artículo del blog: https://www.cuantum.tech/post/mastering-json-creating-handling-and-working-with-json-files

11.2.2 Construyendo el Motor NLP

A continuación, construiremos el motor NLP para procesar las entradas de los usuarios, reconocer intenciones y extraer entidades. Usaremos TensorFlow para entrenar un modelo simple de reconocimiento de intenciones.

nlp_engine.py:

import json
import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from sklearn.preprocessing import LabelEncoder
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split

# Load the intents file
with open('data/intents.json') as file:
    intents = json.load(file)

# Extract patterns and corresponding tags
patterns = []
tags = []
for intent in intents['intents']:
    for pattern in intent['patterns']:
        patterns.append(pattern)
        tags.append(intent['tag'])

# Encode the tags
label_encoder = LabelEncoder()
labels = label_encoder.fit_transform(tags)

# Vectorize the patterns
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(patterns).toarray()
y = np.array(labels)

# Split the data into training and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Build the model
model = Sequential()
model.add(Dense(128, input_shape=(X_train.shape[1],), activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(64, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(len(label_encoder.classes_), activation='softmax'))

# Compile the model
model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

# Train the model
model.fit(X_train, y_train, epochs=100, batch_size=8, verbose=1, validation_data=(X_test, y_test))

# Save the model and tokenizer
model.save('models/nlp_model.h5')
with open('models/tokenizer.pickle', 'wb') as file:
    pickle.dump(vectorizer, file)
with open('models/label_encoder.pickle', 'wb') as file:
    pickle.dump(label_encoder, file)

Aquí tienes una explicación detallada de cada parte del script:

  1. Importación de Bibliotecas:
    import json
    import numpy as np
    import tensorflow as tf
    from tensorflow.keras.models import Sequential
    from tensorflow.keras.layers import Dense, Dropout
    from sklearn.preprocessing import LabelEncoder
    from sklearn.feature_extraction.text import TfidfVectorizer
    from sklearn.model_selection import train_test_split

    Esta sección importa las bibliotecas esenciales:

    • json: Para manejar archivos JSON.
    • numpy: Para operaciones numéricas.
    • tensorflow y keras: Para construir y entrenar la red neuronal.
    • LabelEncoder y TfidfVectorizer de scikit-learn: Para codificar etiquetas y vectorizar datos de texto.
    • train_test_split de scikit-learn: Para dividir el conjunto de datos en conjuntos de entrenamiento y prueba.
  2. Cargando el Archivo de Intenciones:
    with open('data/intents.json') as file:
        intents = json.load(file)

    Este fragmento de código carga el archivo JSON de intenciones, que contiene varias intenciones de usuario y sus correspondientes patrones y respuestas.

  3. Extracción de Patrones y Etiquetas:
    patterns = []
    tags = []
    for intent in intents['intents']:
        for pattern in intent['patterns']:
            patterns.append(pattern)
            tags.append(intent['tag'])

    Aquí, el script itera a través de las intenciones y extrae los patrones (entradas del usuario) y sus etiquetas correspondientes (etiquetas de intención). Estos se almacenan en las listas patterns y tags, respectivamente.

  4. Codificación de las Etiquetas:
    label_encoder = LabelEncoder()
    labels = label_encoder.fit_transform(tags)

    Las etiquetas se codifican en valores numéricos utilizando LabelEncoder, lo cual es necesario para entrenar la red neuronal.

  5. Vectorización de los Patrones:
    vectorizer = TfidfVectorizer()
    X = vectorizer.fit_transform(patterns).toarray()
    y = np.array(labels)

    El TfidfVectorizer convierte los patrones de texto en vectores numéricos basados en el esquema de Frecuencia de Término-Inversa Frecuencia de Documento (TF-IDF). Esta transformación es crucial para alimentar los datos de texto en la red neuronal.

  6. División de los Datos:
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

    El conjunto de datos se divide en conjuntos de entrenamiento y prueba utilizando una proporción del 80-20. El parámetro random_state asegura la reproducibilidad.

  7. Construcción del Modelo de Red Neuronal:
    model = Sequential()
    model.add(Dense(128, input_shape=(X_train.shape[1],), activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(64, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(len(label_encoder.classes_), activation='softmax'))

    Se construye un modelo de red neuronal secuencial utilizando Keras. Consiste en:

    • Una capa de entrada con 128 neuronas y activación ReLU.
    • Una capa de dropout con una tasa de abandono del 50% para prevenir el sobreajuste.
    • Una capa oculta con 64 neuronas y activación ReLU.
    • Otra capa de dropout con una tasa de abandono del 50%.
    • Una capa de salida con el número de neuronas igual al número de intenciones únicas, utilizando activación softmax para la clasificación multiclase.
  8. Compilación del Modelo:
    model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

    El modelo se compila con:

    • La función de pérdida sparse_categorical_crossentropy, adecuada para la clasificación multiclase con etiquetas enteras.
    • El optimizador adam, una elección popular por su eficiencia.
    • accuracy como la métrica de evaluación.
  9. Entrenamiento del Modelo:
    model.fit(X_train, y_train, epochs=100, batch_size=8, verbose=1, validation_data=(X_test, y_test))

    El modelo se entrena durante 100 épocas con un tamaño de lote de 8. El proceso de entrenamiento utiliza los datos de entrenamiento y evalúa el rendimiento en los datos de prueba después de cada época.

  10. Guardando el Modelo y el Tokenizador:
    model.save('models/nlp_model.h5')
    with open('models/tokenizer.pickle', 'wb') as file:
        pickle.dump(vectorizer, file)
    with open('models/label_encoder.pickle', 'wb') as file:
        pickle.dump(label_encoder, file)

    Una vez entrenado, el modelo se guarda en un archivo HDF5 (nlp_model.h5). Además, los objetos TfidfVectorizer y LabelEncoder se guardan utilizando el módulo pickle. Estos objetos guardados son esenciales para preprocesar nuevos datos durante la inferencia.

En resumen, este script procesa los datos de entrenamiento del chatbot, construye una red neuronal para el reconocimiento de intenciones, entrena el modelo y guarda los componentes necesarios para su uso futuro.

En esta sección, presentamos el proyecto del chatbot asistente personal, esbozamos las consideraciones de diseño y configuramos la estructura inicial del proyecto. También definimos las intenciones y entidades y construimos el motor NLP para el reconocimiento de intenciones. Esto sienta las bases para desarrollar un chatbot asistente personal completamente funcional que puede manejar varias tareas y mejorar la productividad del usuario.

11.2.3 Manejo de Datos Faltantes o Desequilibrados

En aplicaciones del mundo real, los datos pueden estar incompletos o desequilibrados. Es importante abordar estos problemas durante el preprocesamiento para asegurar que el modelo funcione bien.

  1. Manejo de Datos Faltantes: Al lidiar con datos faltantes, es esencial reemplazar los valores faltantes con un marcador de posición, como la media o mediana de la columna, o eliminar las instancias que contienen datos faltantes. Esto asegura que el conjunto de datos se mantenga limpio y utilizable para el análisis o entrenamiento del modelo.
  2. Abordar Datos Desequilibrados: Para abordar el problema de los datos desequilibrados, que pueden afectar negativamente el rendimiento del modelo, se pueden emplear varias técnicas. Estas incluyen el sobremuestreo de la clase minoritaria, el submuestreo de la clase mayoritaria o la generación de muestras sintéticas utilizando métodos como SMOTE (Synthetic Minority Over-sampling Technique). Balancear el conjunto de datos de esta manera ayuda a lograr resultados más confiables y precisos.

Ejemplo: Manejo de Datos Faltantes y Desequilibrados:

from imblearn.over_sampling import SMOTE

# Check for missing data
print(f"Missing values: {np.isnan(X).sum()}")

# Handle missing data (if any)
X = np.nan_to_num(X)

# Balance the dataset using SMOTE (Synthetic Minority Over-sampling Technique)
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X, y)

# Split the resampled data into training and test sets
X_train, X_test, y_train, y_test = train_test_split(X_resampled, y_resampled, test_size=0.2, random_state=42)

# Train the model with the balanced dataset
model.fit(X_train, y_train, epochs=100, batch_size=8, verbose=1, validation_data=(X_test, y_test))

Este fragmento de código de ejemplo demuestra un flujo de trabajo de aprendizaje automático para manejar conjuntos de datos desequilibrados utilizando SMOTE (Synthetic Minority Over-sampling Technique). Primero, verifica y maneja cualquier valor faltante en el conjunto de características X.

Luego, aplica SMOTE para equilibrar el conjunto de datos generando muestras sintéticas para la clase minoritaria. Después de equilibrar, divide los datos en conjuntos de entrenamiento y prueba. Finalmente, entrena un modelo de aprendizaje automático utilizando el conjunto de datos equilibrado.

11.2 Recolección y Preprocesamiento de Datos

La recolección y el preprocesamiento de datos son pasos críticos para construir un chatbot efectivo. La calidad y relevancia de los datos utilizados para entrenar el modelo impactan directamente en el rendimiento del chatbot. En esta sección, discutiremos cómo recolectar y preprocesar datos para nuestro chatbot asistente personal.

11.2.1 Recolección de Datos

Para nuestro chatbot asistente personal, necesitamos datos que cubran una amplia gama de intenciones y entidades de los usuarios. Podemos comenzar con las intenciones y patrones definidos en nuestro archivo intents.json y expandirlo con fuentes de datos adicionales:

  1. Recolección Manual de Datos: Crear manualmente una lista de consultas y respuestas comunes de los usuarios.
  2. Conjuntos de Datos Públicos: Utilizar conjuntos de datos disponibles públicamente que contengan datos conversacionales, como el Corpus de Diálogos de Películas de Cornell o el conjunto de datos de ChatterBot.
  3. Documentación de API: Para tareas específicas como actualizaciones meteorológicas o establecimiento de recordatorios, consultar la documentación de la API para comprender el formato de los datos y las consultas de muestra.

Vamos a mejorar nuestro archivo intents.json con más patrones y respuestas para hacer que el chatbot sea más robusto.

{
    "intents": [
        {
            "tag": "greeting",
            "patterns": ["Hi", "Hello", "Hey"],
            "responses": ["Hello! How can I assist you today?", "Hi there! What can I do for you?", "Hey! How can I help?"]
        },
        {
            "tag": "goodbye",
            "patterns": ["Bye", "Goodbye", "See you later"],
            "responses": ["Goodbye! Have a great day!", "See you later! Take care!"]
        },
        {
            "tag": "weather",
            "patterns": ["What's the weather like?", "Tell me the weather", "How's the weather today?"],
            "responses": ["Let me check the weather for you.", "Fetching the weather details..."]
        },
        {
            "tag": "reminder",
            "patterns": ["Set a reminder", "Remind me to", "Add a reminder"],
            "responses": ["Sure, what would you like to be reminded about?", "When would you like the reminder to be set?"]
        }
    ]
}

Este archivo define algunas intenciones básicas: saludo, despedida, clima y recordatorio. Cada intención tiene patrones (posibles entradas del usuario) y respuestas (respuestas predefinidas del chatbot).

Si deseas una comprensión más profunda sobre el manejo de archivos JSON, te recomendamos leer este artículo del blog: https://www.cuantum.tech/post/mastering-json-creating-handling-and-working-with-json-files

11.2.2 Construyendo el Motor NLP

A continuación, construiremos el motor NLP para procesar las entradas de los usuarios, reconocer intenciones y extraer entidades. Usaremos TensorFlow para entrenar un modelo simple de reconocimiento de intenciones.

nlp_engine.py:

import json
import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from sklearn.preprocessing import LabelEncoder
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split

# Load the intents file
with open('data/intents.json') as file:
    intents = json.load(file)

# Extract patterns and corresponding tags
patterns = []
tags = []
for intent in intents['intents']:
    for pattern in intent['patterns']:
        patterns.append(pattern)
        tags.append(intent['tag'])

# Encode the tags
label_encoder = LabelEncoder()
labels = label_encoder.fit_transform(tags)

# Vectorize the patterns
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(patterns).toarray()
y = np.array(labels)

# Split the data into training and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Build the model
model = Sequential()
model.add(Dense(128, input_shape=(X_train.shape[1],), activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(64, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(len(label_encoder.classes_), activation='softmax'))

# Compile the model
model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

# Train the model
model.fit(X_train, y_train, epochs=100, batch_size=8, verbose=1, validation_data=(X_test, y_test))

# Save the model and tokenizer
model.save('models/nlp_model.h5')
with open('models/tokenizer.pickle', 'wb') as file:
    pickle.dump(vectorizer, file)
with open('models/label_encoder.pickle', 'wb') as file:
    pickle.dump(label_encoder, file)

Aquí tienes una explicación detallada de cada parte del script:

  1. Importación de Bibliotecas:
    import json
    import numpy as np
    import tensorflow as tf
    from tensorflow.keras.models import Sequential
    from tensorflow.keras.layers import Dense, Dropout
    from sklearn.preprocessing import LabelEncoder
    from sklearn.feature_extraction.text import TfidfVectorizer
    from sklearn.model_selection import train_test_split

    Esta sección importa las bibliotecas esenciales:

    • json: Para manejar archivos JSON.
    • numpy: Para operaciones numéricas.
    • tensorflow y keras: Para construir y entrenar la red neuronal.
    • LabelEncoder y TfidfVectorizer de scikit-learn: Para codificar etiquetas y vectorizar datos de texto.
    • train_test_split de scikit-learn: Para dividir el conjunto de datos en conjuntos de entrenamiento y prueba.
  2. Cargando el Archivo de Intenciones:
    with open('data/intents.json') as file:
        intents = json.load(file)

    Este fragmento de código carga el archivo JSON de intenciones, que contiene varias intenciones de usuario y sus correspondientes patrones y respuestas.

  3. Extracción de Patrones y Etiquetas:
    patterns = []
    tags = []
    for intent in intents['intents']:
        for pattern in intent['patterns']:
            patterns.append(pattern)
            tags.append(intent['tag'])

    Aquí, el script itera a través de las intenciones y extrae los patrones (entradas del usuario) y sus etiquetas correspondientes (etiquetas de intención). Estos se almacenan en las listas patterns y tags, respectivamente.

  4. Codificación de las Etiquetas:
    label_encoder = LabelEncoder()
    labels = label_encoder.fit_transform(tags)

    Las etiquetas se codifican en valores numéricos utilizando LabelEncoder, lo cual es necesario para entrenar la red neuronal.

  5. Vectorización de los Patrones:
    vectorizer = TfidfVectorizer()
    X = vectorizer.fit_transform(patterns).toarray()
    y = np.array(labels)

    El TfidfVectorizer convierte los patrones de texto en vectores numéricos basados en el esquema de Frecuencia de Término-Inversa Frecuencia de Documento (TF-IDF). Esta transformación es crucial para alimentar los datos de texto en la red neuronal.

  6. División de los Datos:
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

    El conjunto de datos se divide en conjuntos de entrenamiento y prueba utilizando una proporción del 80-20. El parámetro random_state asegura la reproducibilidad.

  7. Construcción del Modelo de Red Neuronal:
    model = Sequential()
    model.add(Dense(128, input_shape=(X_train.shape[1],), activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(64, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(len(label_encoder.classes_), activation='softmax'))

    Se construye un modelo de red neuronal secuencial utilizando Keras. Consiste en:

    • Una capa de entrada con 128 neuronas y activación ReLU.
    • Una capa de dropout con una tasa de abandono del 50% para prevenir el sobreajuste.
    • Una capa oculta con 64 neuronas y activación ReLU.
    • Otra capa de dropout con una tasa de abandono del 50%.
    • Una capa de salida con el número de neuronas igual al número de intenciones únicas, utilizando activación softmax para la clasificación multiclase.
  8. Compilación del Modelo:
    model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

    El modelo se compila con:

    • La función de pérdida sparse_categorical_crossentropy, adecuada para la clasificación multiclase con etiquetas enteras.
    • El optimizador adam, una elección popular por su eficiencia.
    • accuracy como la métrica de evaluación.
  9. Entrenamiento del Modelo:
    model.fit(X_train, y_train, epochs=100, batch_size=8, verbose=1, validation_data=(X_test, y_test))

    El modelo se entrena durante 100 épocas con un tamaño de lote de 8. El proceso de entrenamiento utiliza los datos de entrenamiento y evalúa el rendimiento en los datos de prueba después de cada época.

  10. Guardando el Modelo y el Tokenizador:
    model.save('models/nlp_model.h5')
    with open('models/tokenizer.pickle', 'wb') as file:
        pickle.dump(vectorizer, file)
    with open('models/label_encoder.pickle', 'wb') as file:
        pickle.dump(label_encoder, file)

    Una vez entrenado, el modelo se guarda en un archivo HDF5 (nlp_model.h5). Además, los objetos TfidfVectorizer y LabelEncoder se guardan utilizando el módulo pickle. Estos objetos guardados son esenciales para preprocesar nuevos datos durante la inferencia.

En resumen, este script procesa los datos de entrenamiento del chatbot, construye una red neuronal para el reconocimiento de intenciones, entrena el modelo y guarda los componentes necesarios para su uso futuro.

En esta sección, presentamos el proyecto del chatbot asistente personal, esbozamos las consideraciones de diseño y configuramos la estructura inicial del proyecto. También definimos las intenciones y entidades y construimos el motor NLP para el reconocimiento de intenciones. Esto sienta las bases para desarrollar un chatbot asistente personal completamente funcional que puede manejar varias tareas y mejorar la productividad del usuario.

11.2.3 Manejo de Datos Faltantes o Desequilibrados

En aplicaciones del mundo real, los datos pueden estar incompletos o desequilibrados. Es importante abordar estos problemas durante el preprocesamiento para asegurar que el modelo funcione bien.

  1. Manejo de Datos Faltantes: Al lidiar con datos faltantes, es esencial reemplazar los valores faltantes con un marcador de posición, como la media o mediana de la columna, o eliminar las instancias que contienen datos faltantes. Esto asegura que el conjunto de datos se mantenga limpio y utilizable para el análisis o entrenamiento del modelo.
  2. Abordar Datos Desequilibrados: Para abordar el problema de los datos desequilibrados, que pueden afectar negativamente el rendimiento del modelo, se pueden emplear varias técnicas. Estas incluyen el sobremuestreo de la clase minoritaria, el submuestreo de la clase mayoritaria o la generación de muestras sintéticas utilizando métodos como SMOTE (Synthetic Minority Over-sampling Technique). Balancear el conjunto de datos de esta manera ayuda a lograr resultados más confiables y precisos.

Ejemplo: Manejo de Datos Faltantes y Desequilibrados:

from imblearn.over_sampling import SMOTE

# Check for missing data
print(f"Missing values: {np.isnan(X).sum()}")

# Handle missing data (if any)
X = np.nan_to_num(X)

# Balance the dataset using SMOTE (Synthetic Minority Over-sampling Technique)
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X, y)

# Split the resampled data into training and test sets
X_train, X_test, y_train, y_test = train_test_split(X_resampled, y_resampled, test_size=0.2, random_state=42)

# Train the model with the balanced dataset
model.fit(X_train, y_train, epochs=100, batch_size=8, verbose=1, validation_data=(X_test, y_test))

Este fragmento de código de ejemplo demuestra un flujo de trabajo de aprendizaje automático para manejar conjuntos de datos desequilibrados utilizando SMOTE (Synthetic Minority Over-sampling Technique). Primero, verifica y maneja cualquier valor faltante en el conjunto de características X.

Luego, aplica SMOTE para equilibrar el conjunto de datos generando muestras sintéticas para la clase minoritaria. Después de equilibrar, divide los datos en conjuntos de entrenamiento y prueba. Finalmente, entrena un modelo de aprendizaje automático utilizando el conjunto de datos equilibrado.