Capítulo 4. Adquirir un conjunto de datos inicial

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

Una vez que tienes un plan para resolver las necesidades de tu producto y has construido un prototipo inicial para validar que el flujo de trabajo y el modelo propuestos son sólidos, es hora de profundizar en tu conjunto de datos. Utilizaremos lo que encontremos para fundamentar nuestras decisiones de modelado. A menudo, comprender bien tus datos conduce a las mayores mejoras de rendimiento.

En este capítulo, empezaremos estudiando formas de juzgar eficazmente la calidad de un conjunto de datos. A continuación, veremos cómo vectorizar los datos y cómo utilizar dicha representación vectorizada para etiquetar e inspeccionar un conjunto de datos de forma más eficaz. Por último, veremos cómo esta inspección debe guiar las estrategias de generación de características.

Empecemos por descubrir un conjunto de datos y juzgar su calidad.

Iterar en conjuntos de datos

La forma más rápida de construir un producto de ML es construir, evaluar e iterar rápidamente sobre los modelos. Los propios conjuntos de datos son una parte esencial de ese éxito de los modelos. Por eso la recopilación, preparación y etiquetado de datos debe considerarse un proceso iterativo, igual que el modelado. Empieza con un conjunto de datos sencillo que puedas reunir inmediatamente, y estate abierto a mejorarlo en función de lo que aprendas.

Este enfoque iterativo de los datos de puede parecer confuso al principio. En la investigación del ML, a menudo se informa del rendimiento en conjuntos de datos estándar que la comunidad utiliza como puntos de referencia y que, por tanto, son inmutables. En la ingeniería de software tradicional, escribimos reglas deterministas para nuestros programas, por lo que tratamos los datos como algo que recibir, procesar y almacenar.

La ingeniería ML combina ingeniería y ML para construir productos. Nuestro conjunto de datos es, por tanto, una herramienta más que nos permite construir productos. En la ingeniería de ML, elegir un conjunto de datos inicial, actualizarlo periódicamente y aumentarlo suele ser la mayor parte del trabajo. Esta diferencia en el flujo de trabajo entre la investigación y la industria se ilustra en la Figura 4-1.

Datasets are fixed in research, but part of a product in industry
Figura 4-1. Los conjuntos de datos son fijos en la investigación, pero forman parte del producto en la industria

Tratar los datos como parte de tu producto sobre el que puedes (y debes) iterar, cambiar y mejorar suele ser un gran cambio de paradigma para los recién llegados al sector. Sin embargo, una vez que te acostumbres, los datos se convertirán en tu mejor fuente de inspiración para desarrollar nuevos modelos y en el primer lugar donde buscar respuestas cuando las cosas vayan mal.

Haz Ciencia de Datos

He visto en que el proceso de curar un conjunto de datos es el principal obstáculo para crear productos de ML más veces de las que puedo contar. Esto se debe en parte a la relativa falta de formación sobre el tema (la mayoría de los cursos en línea proporcionan el conjunto de datos y se centran en los modelos), lo que hace que muchos profesionales teman esta parte del trabajo.

Es fácil pensar que trabajar con datos es una tarea que hay que abordar antes de jugar con modelos divertidos, pero los modelos sólo sirven para extraer tendencias y patrones de los datos existentes. Por tanto, asegurarse de que los datos que utilizamos presentan patrones lo suficientemente predictivos como para que un modelo pueda aprovecharlos (y comprobar si contienen sesgos claros) es una parte fundamental del trabajo de un científico de datos (de hecho, te habrás dado cuenta de que el nombre de la función no es científico de modelos).

Este capítulo se centrará en este proceso, desde la recopilación de un conjunto de datos inicial hasta la inspección y validación de su aplicabilidad para el ML. Empecemos por explorar eficazmente un conjunto de datos para juzgar su calidad.

Explora tu primer conjunto de datos

Así que ¿cómo hacemos para explorar un conjunto de datos inicial? El primer paso es, por supuesto, reunir un conjunto de datos. Aquí es donde veo que los profesionales se atascan con más frecuencia al buscar un conjunto de datos perfecto. Recuerda que nuestro objetivo es conseguir un conjunto de datos sencillo del que extraer resultados preliminares. Al igual que con otras cosas en ML, empieza por lo sencillo y construye a partir de ahí.

Sé eficiente, empieza poco a poco

Para la mayoría de los problemas de ML, más datos pueden conducir a un modelo mejor, pero esto no significa que debas empezar con el mayor conjunto de datos posible. Al empezar un proyecto, un conjunto de datos pequeño te permite inspeccionar y comprender fácilmente tus datos y cómo modelarlos mejor. Debes aspirar a un conjunto de datos inicial con el que sea fácil trabajar. Sólo cuando hayas establecido una estrategia, tendrá sentido ampliarla a un tamaño mayor.

Si trabajas en una empresa con terabytes de datos almacenados en un clúster, puedes empezar extrayendo un subconjunto muestreado uniformemente que quepa en la memoria de tu máquina local. Si quieres empezar a trabajar en un proyecto paralelo que intente identificar las marcas de los coches que pasan por delante de tu casa, por ejemplo, empieza con unas decenas de imágenes de coches en las calles.

Una vez que hayas visto cómo funciona tu modelo inicial y dónde tiene dificultades, ¡podrás iterar sobre tu conjunto de datos con conocimiento de causa!

puedes encontrar muchos conjuntos de datos existentes en línea en plataformas como Kaggle o Reddit o reunir tú mismo algunos ejemplos, ya sea raspando la web, aprovechando grandes conjuntos de datos abiertos como los que se encuentran en el sitio Common Crawl, ¡o generando datos! Para más información, consulta "Datos abiertos".

Recopilar y analizar datos no sólo es necesario, sino que te acelerará, sobre todo al principio del desarrollo de un proyecto. Observar tu conjunto de datos y conocer sus características es la forma más fácil de idear una buena canalización de modelado y generación de características.

La mayoría de los profesionales sobrestiman el impacto de trabajar sobre el modelo e infravaloran el valor de trabajar sobre los datos, por lo que recomiendo hacer siempre un esfuerzo por corregir esta tendencia y predisponerse a mirar los datos.

Al examinar los datos, es bueno identificar las tendencias de forma exploratoria, pero no debes detenerte ahí. Si tu objetivo es crear productos ML, deberías preguntarte cuál es la mejor forma de aprovechar estas tendencias de forma automatizada. ¿Cómo pueden ayudarte estas tendencias a potenciar un producto automatizado?

Perspectivas frente a productos

Una vez que tienes un conjunto de datos, es hora de sumergirse en él y explorar su contenido. Mientras lo hacemos, tengamos presente la distinción entre la exploración de datos con fines de análisis y la exploración de datos con fines de creación de productos. Aunque ambas tienen como objetivo extraer y comprender las tendencias de los datos, la primera se ocupa de crear ideas a partir de las tendencias (aprender que la mayoría de los inicios de sesión fraudulentos en un sitio web se producen los jueves y son de la zona de Seattle, por ejemplo), mientras que la segunda trata de utilizar las tendencias para crear funciones (utilizar la hora de un intento de inicio de sesión y su dirección IP para crear un servicio que evite los inicios de sesión fraudulentos en las cuentas).

Aunque la diferencia pueda parecer sutil, conlleva una capa adicional de complejidad en el caso de la construcción de productos. Necesitamos tener confianza en que los patrones que vemos se aplicarán a los datos que recibamos en el futuro y cuantificar las diferencias entre los datos con los que estamos entrenando y los datos que esperamos recibir en producción.

Para la predicción del fraude en, observar un aspecto estacional en los inicios de sesión fraudulentos es el primer paso. A continuación, debemos utilizar esta tendencia estacional observada para estimar la frecuencia con la que debemos entrenar nuestros modelos con los datos recopilados recientemente. Nos sumergiremos en más ejemplos a medida que exploremos nuestros datos más profundamente más adelante en este capítulo.

Antes de notar tendencias predictivas, deberíamos empezar por examinar la calidad. Si el conjunto de datos que hemos elegido no cumple las normas de calidad, deberíamos mejorarlo antes de pasar al modelado.

Una rúbrica de calidad de datos

En esta sección, trataremos algunos aspectos que debes examinar cuando trabajes por primera vez con un nuevo conjunto de datos. Cada conjunto de datos viene con sus propios sesgos y rarezas, que requieren diferentes herramientas para ser comprendidos, por lo que escribir una rúbrica exhaustiva que cubra todo lo que puedas querer buscar en un conjunto de datos va más allá del alcance de este libro. Sin embargo, hay algunas categorías a las que conviene prestar atención cuando te acerques por primera vez a un conjunto de datos. Empecemos por el formato.

Formato de los datos

¿El conjunto de datos ya está formateado de tal manera que tienes claras las entradas y salidas, o requiere un preprocesamiento y etiquetado adicionales?

Cuando se construye un modelo que intenta predecir si un usuario hará clic en un anuncio, por ejemplo, un conjunto de datos habitual consistirá en un registro histórico de todos los clics de un periodo de tiempo determinado. Tendrías que transformar este conjunto de datos para que contenga múltiples instancias de un anuncio presentado a un usuario y si el usuario hizo clic. También querrías incluir cualquier característica del usuario o del anuncio que creas que tu modelo podría aprovechar.

Si te dan un conjunto de datos que ya ha sido procesado o agregado para ti, debes validar que comprendes la forma en que se procesaron los datos. Si una de las columnas que te han dado contiene una tasa media de conversión, por ejemplo, ¿puedes calcular tú mismo esta tasa y verificar que coincide con el valor proporcionado?

En algunos casos, no tendrás acceso a la información necesaria para reproducir y validar los pasos del preprocesamiento. En esos casos, observar la calidad de los datos te ayudará a determinar en qué características de los mismos confías y cuáles sería mejor ignorar.

Calidad de los datos

Examinar la calidad de un conjunto de datos es crucial antes de empezar a modelarlo. Si sabes que falta la mitad de los valores de una característica crucial, no pasarás horas depurando un modelo para intentar comprender por qué no funciona bien.

Los datos pueden ser de mala calidad de muchas maneras. Pueden faltar, ser imprecisos o incluso estar corruptos. Obtener una imagen precisa de su calidad no sólo te permitirá estimar qué nivel de rendimiento es razonable, sino que te facilitará la selección de posibles características y modelos a utilizar.

Si estás trabajando con registros de la actividad de los usuarios para predecir el uso de un producto online, ¿puedes estimar cuántos eventos registrados faltan? De los eventos que sí tienes, ¿cuántos contienen sólo un subconjunto de información sobre el usuario?

Si estás trabajando con un texto en lenguaje natural, ¿cómo calificarías la calidad del texto? Por ejemplo, ¿hay muchos caracteres incomprensibles? ¿La ortografía es muy errónea o incoherente?

Si trabajas con imágenes, ¿son lo suficientemente claras como para que puedas realizar la tarea tú mismo? Si te resulta difícil detectar un objeto en una imagen, ¿crees que a tu modelo le costará hacerlo?

En general, ¿qué proporción de tus datos parece ruidosa o incorrecta? ¿Cuántos datos te resultan difíciles de interpretar o comprender? Si los datos tienen etiquetas, ¿tiendes a estar de acuerdo con ellas, o te encuentras a menudo cuestionando su exactitud?

He trabajado en algunos proyectos destinados a extraer información de imágenes de satélite, por ejemplo. En el mejor de los casos, estos proyectos tienen acceso a un conjunto de datos de imágenes con las correspondientes anotaciones que denotan objetos de interés, como campos o planos. En algunos casos, sin embargo, estas anotaciones pueden ser inexactas o incluso faltar. Tales errores tienen un impacto significativo en cualquier enfoque de modelado, por lo que es vital descubrirlos pronto. Podemos trabajar con las etiquetas que faltan etiquetando nosotros mismos un conjunto de datos inicial o encontrando una etiqueta débil que podamos utilizar, pero sólo podremos hacerlo si nos damos cuenta de la calidad con antelación.

Después de verificar el formato y la calidad de los datos, un paso adicional puede ayudar a aflorar problemas de forma proactiva: examinar la cantidad de datos y la distribución de las características.

Cantidad y distribución de los datos

Estimemos si tenemos suficientes datos y si los valores de las características parecen estar dentro de un margen razonable.

¿Cuántos datos tenemos? Si tenemos un conjunto de datos grande, deberíamos seleccionar un subconjunto con el que empezar nuestro análisis. Por otra parte, si nuestro conjunto de datos es demasiado pequeño o algunas clases están infrarrepresentadas, los modelos que entrenemos correrían el riesgo de estar tan sesgados como nuestros datos. La mejor forma de evitar ese sesgo es aumentar la diversidad de nuestros datos mediante la recopilación y el aumento de datos. Las formas de medir la calidad de tus datos dependen de tu conjunto de datos, pero la Tabla 4-1 recoge algunas preguntas para empezar.

Tabla 4-1. Una rúbrica de calidad de datos
Calidad Formato Cantidad y distribución

¿Hay algún campo relevante que esté vacío?

¿Cuántos pasos de preprocesamiento requieren tus datos?

¿Cuántos ejemplos tienes?

¿Existen posibles errores de medición?

¿Podrás preprocesarlo del mismo modo en producción?

¿Cuántos ejemplos por clase? ¿Falta alguno?

Como ejemplo práctico, al crear un modelo para clasificar automáticamente los correos electrónicos de atención al cliente en distintas áreas de especialización, un científico de datos con el que trabajaba, Alex Wahl, recibió nueve categorías distintas, con un solo ejemplo por categoría. Un conjunto de datos así es demasiado pequeño para que un modelo aprenda de él, así que centró la mayor parte de su esfuerzo en una estrategia de generación de datos. Utilizó plantillas de formulaciones comunes para cada una de las nueve categorías con el fin de producir miles de ejemplos más de los que pudiera aprender un modelo. Utilizando esta estrategia, consiguió que una tubería alcanzara un nivel de precisión mucho mayor que el que habría tenido intentando construir un modelo lo suficientemente complejo como para aprender de sólo nueve ejemplos.

Apliquemos este proceso de exploración al conjunto de datos que elegimos para nuestro editor ML y estimemos su calidad.

Inspección de datos del editor ML

Para nuestro editor ML, inicialmente nos decidimos por utilizar el volcado de datos anonimizado de Stack Exchange como conjunto de datos. Stack Exchange es una red de sitios web de preguntas y respuestas, cada uno centrado en un tema como la filosofía o los juegos. El volcado de datos contiene muchos archivos, uno por cada uno de los sitios web de la red Stack Exchange.

Para nuestro conjunto de datos inicial, elegiremos un sitio web que parezca contener preguntas lo suficientemente amplias como para construir una heurística útil a partir de ellas. A primera vista, la comunidad de Escritores parece una buena opción.

Cada archivo web se proporciona como un archivo XML. Tenemos que construir un proceso para ingerir esos archivos y transformarlos en texto del que podamos extraer características. El siguiente ejemplo muestra el archivo Posts.xml para datascience.stackexchange.com:

<?xml version="1.0" encoding="utf-8"?>
<posts>
  <row Id="5" PostTypeId="1" CreationDate="2014-05-13T23:58:30.457"
Score="9" ViewCount="516" Body="&lt;p&gt; &quot;Hello World&quot; example? "
OwnerUserId="5" LastActivityDate="2014-05-14T00:36:31.077"
Title="How can I do simple machine learning without hard-coding behavior?"
Tags="&lt;machine-learning&gt;" AnswerCount="1" CommentCount="1" />
  <row Id="7" PostTypeId="1" AcceptedAnswerId="10" ... />

Para poder aprovechar estos datos, necesitaremos poder cargar el archivo XML, descodificar las etiquetas HTML del texto y representar las preguntas y los datos asociados en un formato que sea más fácil de analizar, como un DataFrame de pandas. La siguiente función hace precisamente esto. Como recordatorio, el código de esta función, y el resto del código a lo largo de este libro, se puede encontrar en el repositorio GitHub de este libro.

import xml.etree.ElementTree as ElT


def parse_xml_to_csv(path, save_path=None):
    """
    Open .xml posts dump and convert the text to a csv, tokenizing it in the
         process
    :param path: path to the xml document containing posts
    :return: a dataframe of processed text
    """

    # Use python's standard library to parse XML file
    doc = ElT.parse(path)
    root = doc.getroot()

    # Each row is a question
    all_rows = [row.attrib for row in root.findall("row")]

    # Using tdqm to display progress since preprocessing takes time
    for item in tqdm(all_rows):
        # Decode text from HTML
        soup = BeautifulSoup(item["Body"], features="html.parser")
        item["body_text"] = soup.get_text()

    # Create dataframe from our list of dictionaries
    df = pd.DataFrame.from_dict(all_rows)
    if save_path:
        df.to_csv(save_path)
    return df

Incluso para un conjunto de datos relativamente pequeño que sólo contiene 30.000 preguntas, este proceso tarda más de un minuto, por lo que serializamos el archivo procesado de vuelta al disco para tener que procesarlo sólo una vez. Para ello, podemos utilizar simplemente la función to_csv de panda, como se muestra en la última línea del fragmento.

En general, ésta es una práctica recomendada para cualquier preprocesamiento necesario para entrenar un modelo. El código de preprocesamiento que se ejecuta justo antes del proceso de optimización del modelo puede ralentizar considerablemente la experimentación. En la medida de lo posible, preprocesa siempre los datos con antelación y envíalos al disco.

Una vez que tenemos nuestros datos en este formato, podemos examinar los aspectos que hemos descrito antes. Todo el proceso de exploración que detallamos a continuación se puede encontrar en el cuaderno de exploración del conjunto de datos en el repositorio GitHub de este libro.

Para empezar, utilizamos df.info() para mostrar información resumida sobre nuestro DataFrame, así como cualquier valor vacío. Esto es lo que devuelve

>>>> df.info()

AcceptedAnswerId         4124 non-null float64
AnswerCount              33650 non-null int64
Body                     33650 non-null object
ClosedDate               969 non-null object
CommentCount             33650 non-null int64
CommunityOwnedDate       186 non-null object
CreationDate             33650 non-null object
FavoriteCount            3307 non-null float64
Id                       33650 non-null int64
LastActivityDate         33650 non-null object
LastEditDate             10521 non-null object
LastEditorDisplayName    606 non-null object
LastEditorUserId         9975 non-null float64
OwnerDisplayName         1971 non-null object
OwnerUserId              32117 non-null float64
ParentId                 25679 non-null float64
PostTypeId               33650 non-null int64
Score                    33650 non-null int64
Tags                     7971 non-null object
Title                    7971 non-null object
ViewCount                7971 non-null float64
body_text                33650 non-null object
full_text                33650 non-null object
text_len                 33650 non-null int64
is_question              33650 non-null bool

Podemos ver que tenemos algo más de 31.000 entradas, de las que sólo unas 4.000 tienen una respuesta aceptada. Además, podemos observar que algunos de los valores de Body, que representa el contenido de una entrada, son nulos, lo que parece sospechoso. Cabría esperar que todas las entradas contuvieran texto. Si miramos las filas con un Body nulo, rápidamente descubrimos que pertenecen a un tipo de entrada que no tiene ninguna referencia en la documentación proporcionada con el conjunto de datos, así que las eliminamos.

Sumerjámonos rápidamente en el formato y veamos si lo entendemos. Cada entrada tiene un PostTypeId valor de 1 para una pregunta, o de 2 para una respuesta. Nos gustaría ver qué tipo de preguntas reciben puntuaciones altas, ya que nos gustaría utilizar la puntuación de una pregunta como una etiqueta débil para nuestra verdadera etiqueta, la calidad de una pregunta.

Primero, unamos las preguntas con las respuestas asociadas. El código siguiente selecciona todas las preguntas que tienen una respuesta aceptada y las une con el texto de dicha respuesta. Luego podemos mirar las primeras filas y validar que las respuestas coinciden con las preguntas. Esto también nos permitirá examinar rápidamente el texto y juzgar su calidad.

questions_with_accepted_answers = df[
    df["is_question"] & ~(df["AcceptedAnswerId"].isna())
]
q_and_a = questions_with_accepted_answers.join(
    df[["Text"]], on="AcceptedAnswerId", how="left", rsuffix="_answer"
)

pd.options.display.max_colwidth = 500
q_and_a[["Text", "Text_answer"]][:5]

En la Tabla 4-2, podemos ver que las preguntas y las respuestas parecen coincidir y que el texto parece correcto en su mayor parte. Ahora confiamos en poder emparejar las preguntas con sus respuestas asociadas.

Tabla 4-2. Preguntas con sus respuestas asociadas
Id cuerpo_texto cuerpo_texto_respuesta

1

Siempre he querido empezar a escribir (de forma totalmente amateur), pero siempre que quiero empezar algo me bloqueo al instante teniendo un montón de preguntas y dudas.\n¿Existen algunos recursos sobre cómo empezar a ser escritor?\nEstoy pensando en algo con consejos y ejercicios fáciles para empezar a escribir.\n

Cuando pienso en dónde aprendí más a escribir, creo que la lectura fue para mí la guía más importante. Puede parecer una tontería, pero leyendo artículos de periódico bien escritos (hechos, opiniones, artículos científicos y, sobre todo, críticas de películas y música), aprendí cómo hacían el trabajo los demás, qué funciona y qué no. En mi propia escritura, intento imitar los estilos de otras personas que me han gustado. Además, aprendo cosas nuevas leyendo, lo que me da un bagaje más amplio que necesito cuando re...

2

¿Qué tipo de historia se adapta mejor a cada punto de vista? ¿Hay ventajas o desventajas inherentes a ellos?\nPor ejemplo, escribiendo en primera persona siempre estás siguiendo a un personaje, mientras que en tercera persona puedes "saltar" entre líneas argumentales.\n

Con una historia en primera persona, pretendes que el lector se apegue mucho más al personaje principal. Puesto que el lector ve lo que ese personaje ve y siente lo que ese personaje siente, el lector tendrá una inversión emocional en ese personaje. La tercera persona no tiene este vínculo tan estrecho; el lector puede implicarse emocionalmente, pero no será tan fuerte como en primera persona.

3

He terminado mi novela y todo el mundo con el que he hablado me dice que necesito un agente. ¿Cómo encuentro uno?

Intenta encontrar una lista de agentes que escriban en tu género y consulta sus sitios web. Si no lo hacen, busca otro agente. Pero si lo hacen, intenta enviarles algunos capítulos de tu historia, un resumen y una breve carta de presentación pidiéndoles que te representen.\nEn la carta de presentación menciona tus créditos de publicaciones anteriores. Si la envías por correo, te sugiero que les des un medio de respuesta, ya sea un correo electrónico o un sobre franqueado con tu dirección.\nAgentes...

Como última comprobación de cordura, veamos cuántas preguntas no recibieron respuesta, cuántas recibieron al menos una y cuántas tuvieron una respuesta que fue aceptada.

has_accepted_answer = df[df["is_question"] & ~(df["AcceptedAnswerId"].isna())]
no_accepted_answers = df[
    df["is_question"]
    & (df["AcceptedAnswerId"].isna())
    & (df["AnswerCount"] != 0)
]
no_answers = df[
    df["is_question"]
    & (df["AcceptedAnswerId"].isna())
    & (df["AnswerCount"] == 0)
]

print(
    "%s questions with no answers, %s with answers, %s with an accepted answer"
    % (len(no_answers), len(no_accepted_answers), len(has_accepted_answer))
)

3584 questions with no answers, 5933 with answers, 4964 with an accepted answer.

Tenemos un reparto relativamente equitativo entre preguntas contestadas, parcialmente contestadas y sin contestar. Esto parece razonable, por lo que podemos sentirnos lo suficientemente seguros como para continuar con nuestra exploración.

Conocemos el formato de nuestros datos y tenemos suficientes para empezar. Si estás trabajando en un proyecto y tu conjunto de datos actual es demasiado pequeño o contiene una mayoría de características demasiado difíciles de interpretar, deberías reunir más datos o probar con un conjunto de datos totalmente distinto.

Nuestro conjunto de datos tiene calidad suficiente para seguir adelante. Ahora es el momento de explorarlo más a fondo, con el objetivo de informar nuestra estrategia de modelado.

Etiqueta para encontrar tendencias de datos

Identificar las tendencias de en nuestro conjunto de datos es algo más que calidad. Esta parte del trabajo consiste en ponernos en la piel de nuestro modelo e intentar predecir qué tipo de estructura captará. Lo haremos separando los datos en diferentes grupos (explicaré la agrupación en "Agrupación") e intentando extraer puntos en común en cada grupo.

Lo que sigue es una lista paso a paso para hacerlo en la práctica. Empezaremos generando estadísticas resumidas de nuestro conjunto de datos y luego veremos cómo explorarlo rápidamente aprovechando las técnicas de vectorización. Con ayuda de la vectorización y la agrupación, exploraremos eficazmente nuestro conjunto de datos.

Resumen estadístico

Cuando empiezas a examinar un conjunto de datos, suele ser una buena idea mirar algunas estadísticas de resumen para cada una de las características que tienes. Esto te ayuda tanto a hacerte una idea general de las características de tu conjunto de datos como a identificar cualquier forma fácil de separar tus clases.

Identificar con antelación las diferencias en las distribuciones entre clases de datos es útil en ML, porque nos facilitará la tarea de modelado o nos impedirá sobrestimar el rendimiento de un modelo que quizá sólo aproveche una característica especialmente informativa.

Por ejemplo, si estás intentando predecir si los tweets expresan una opinión positiva o negativa, podrías empezar contando el número medio de palabras de cada tweet. Luego podrías trazar un histograma de esta característica para conocer su distribución.

Un histograma te permitiría darte cuenta de si todos los tweets positivos eran más cortos que los negativos. Esto podría llevarte a añadir la longitud de las palabras como predictor para facilitar tu tarea o, por el contrario, reunir datos adicionales para asegurarte de que tu modelo puede aprender sobre el contenido de los tweets y no sólo sobre su longitud.

Vamos a trazar unas cuantas estadísticas resumidas de nuestro editor ML para ilustrar este punto.

Estadísticas resumidas del editor ML

Para nuestro ejemplo, podemos trazar un histograma de la longitud de las preguntas en nuestro conjunto de datos, resaltando las diferentes tendencias entre las preguntas con puntuaciones altas y bajas. He aquí cómo hacerlo utilizando pandas:

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle

"""
df contains questions and their answer counts from writers.stackexchange.com
We draw two histograms:
one for questions with scores under the median score
one for questions with scores over
For both, we remove outliers to make our visualization simpler
"""

high_score = df["Score"] > df["Score"].median()
# We filter out really long questions
normal_length = df["text_len"] < 2000

ax = df[df["is_question"] & high_score & normal_length]["text_len"].hist(
    bins=60,
    density=True,
    histtype="step",
    color="orange",
    linewidth=3,
    grid=False,
    figsize=(16, 10),
)

df[df["is_question"] & ~high_score & normal_length]["text_len"].hist(
    bins=60,
    density=True,
    histtype="step",
    color="purple",
    linewidth=3,
    grid=False,
)

handles = [
    Rectangle((0, 0), 1, 1, color=c, ec="k") for c in ["orange", "purple"]
]
labels = ["High score", "Low score"]
plt.legend(handles, labels)
ax.set_xlabel("Sentence length (characters)")
ax.set_ylabel("Percentage of sentences")

Podemos ver en la Figura 4-2 que las distribuciones son similares en su mayor parte, y que las preguntas con puntuaciones altas tienden a ser ligeramente más largas (esta tendencia es especialmente notable en torno a la marca de 800 caracteres). Esto indica que la longitud de la pregunta puede ser una característica útil para que un modelo prediga la puntuación de una pregunta.

Podemos trazar otras variables de forma similar para identificar más características potenciales. Una vez que hayamos identificado unas cuantas características, vamos a examinar nuestro conjunto de datos un poco más de cerca para poder identificar tendencias más granulares.

Text length for questions with high or low scores
Figura 4-2. Histograma de la longitud del texto de las preguntas con puntuaciones altas y bajas

Explorar y etiquetar eficazmente

sólo puedes llegar hasta cierto punto mirando estadísticas descriptivas como las medias y gráficos como los histogramas. Para desarrollar una intuición de tus datos, deberías dedicar algún tiempo a observar puntos de datos individuales. Sin embargo, recorrer puntos de un conjunto de datos al azar es bastante ineficaz. En esta sección, explicaré cómo maximizar tu eficacia al visualizar puntos de datos individuales.

La agrupación es un método útil que se puede utilizar aquí. La agrupación es la tarea de agrupar un conjunto de objetos de forma que los objetos de un mismo grupo (llamado cluster) sean más similares (en cierto sentido) entre sí que los de otros grupos (clusters). Utilizaremos la agrupación tanto para explorar nuestros datos como para las predicciones de nuestro modelo más adelante (véase "Reducción de la dimensionalidad").

Muchos algoritmos de agrupación de agrupan puntos de datos midiendo la distancia entre puntos y asignando al mismo clúster los que están próximos entre sí. La Figura 4-3 muestra un ejemplo de un algoritmo de agrupación que separa un conjunto de datos en tres grupos diferentes. La agrupación es un método no supervisado, y a menudo no hay una única forma correcta de agrupar un conjunto de datos. En este libro, utilizaremos la agrupación como una forma de generar cierta estructura para guiar nuestra exploración.

Dado que la agrupación se basa en el cálculo de la distancia entre puntos de datos, la forma que elijamos para representar numéricamente nuestros puntos de datos tiene un gran impacto en los clusters que se generen. Nos sumergiremos en esto en la siguiente sección, "Vectorización".

Generating three clusters from a dataset
Figura 4-3. Generar tres conglomerados a partir de un conjunto de datos

La gran mayoría de los conjuntos de datos pueden separarse en clusters en función de sus características, etiquetas o una combinación de ambas. Examinar cada conglomerado individualmente y las similitudes y diferencias entre conglomerados es una forma excelente de identificar la estructura de un conjunto de datos.

Aquí hay que tener en cuenta varias cosas:

  • ¿Cuántas agrupaciones identificas en tu conjunto de datos?

  • ¿Cada uno de estos grupos te parece diferente? ¿En qué sentido?

  • ¿Hay agrupaciones mucho más densas que otras? Si es así, es probable que tu modelo tenga dificultades para funcionar en las zonas más dispersas. Añadir características y datos puede ayudar a aliviar este problema.

  • ¿Todos los conglomerados representan datos que parecen "difíciles" de modelar? Si algunos conglomerados parecen representar puntos de datos más complejos, anótalos para poder volver a ellos cuando evaluemos el rendimiento de nuestro modelo.

Como ya hemos dicho, los algoritmos de agrupación trabajan con vectores, por lo que no podemos simplemente pasar un conjunto de frases a un algoritmo de agrupación. Para que nuestros datos estén listos para ser agrupados, primero tendremos que vectorizarlos.

Vectorizar

Vectorizar un conjunto de datos es el proceso de pasar de los datos brutos a un vector que los represente. La Figura 4-4 muestra un ejemplo de representaciones vectorizadas para datos textuales y tabulares.

Examples of vectorized representations
Figura 4-4. Ejemplos de representaciones vectorizadas

Hay muchas formas de vectorizar datos, así que nos centraremos en algunos métodos sencillos que funcionan para algunos de los tipos de datos más comunes, como los datos tabulares, el texto y las imágenes.

Datos tabulares

Para los datos tabulares de que constan de rasgos categóricos y continuos, una posible representación vectorial es simplemente la concatenación de las representaciones vectoriales de cada rasgo.

Las características continuas deben normalizarse a una escala común para que las características con mayor escala no hagan que las características más pequeñas sean completamente ignoradas por los modelos. Hay varias formas de normalizar los datos, pero empezar transformando cada característica de modo que su media sea cero y su varianza uno suele ser un buen primer paso. Este suele denominarse puntuación estándar.

Las características categóricas, como los colores, pueden convertirse en una codificación de un solo punto: una lista tan larga como el número de valores distintos de la característica, formada sólo por ceros y un solo uno, cuyo índice representa el valor actual (por ejemplo, en un conjunto de datos que contenga cuatro colores distintos, podríamos codificar el rojo como [1, 0, 0, 0] y el azul como [0, 0, 1, 0]). Quizá tengas curiosidad por saber por qué no asignamos simplemente a cada valor potencial un número, como 1 para el rojo y 3 para el azul. Es porque un esquema de codificación así implicaría un ordenamiento entre valores (el azul es mayor que el rojo), lo que suele ser incorrecto para las variables categóricas.

Una propiedad de la codificación de un punto es que la distancia entre dos valores cualesquiera de una característica es siempre uno. Esto suele proporcionar una buena representación para un modelo, pero en algunos casos, como los días de la semana, algunos valores pueden ser más parecidos que otros (el sábado y el domingo están ambos en fin de semana, por lo que lo ideal sería que sus vectores estuvieran más juntos que el miércoles y el domingo, por ejemplo). Las redes neuronales han empezado a demostrar su utilidad en el aprendizaje de tales representaciones (véase el artículo "Entity Embeddings of Categorical Variables", de C. Guo y F. Berkhahn). Se ha demostrado que estas representaciones mejoran el rendimiento de los modelos que las utilizan en lugar de otros esquemas de codificación.

Por último, los rasgos más complejos, como las fechas, deben transformarse en unos pocos rasgos numéricos que capten sus características más destacadas.

Veamos un ejemplo práctico de vectorización de datos tabulares. Puedes encontrar el código del ejemplo en el cuaderno de vectorización de datos tabulares del repositorio GitHub de este libro.

Supongamos que, en lugar de fijarnos en el contenido de las preguntas, queremos predecir la puntuación que obtendrá una pregunta a partir de sus etiquetas, el número de comentarios y la fecha de creación. En la Tabla 4-3, puedes ver un ejemplo de cómo sería este conjunto de datos para el conjunto de datos escritores.stackexchange.com.

Tabla 4-3. Entradas tabulares sin procesar
Id Etiquetas CuentaComentarios FechaCreación Puntuación

1

<recursos><primer-autor>

7

2010-11-18T20:40:32.857

32

2

<ficción><persona-gramatical><tercera-persona>

0

2010-11-18T20:42:31.513

20

3

<publicación><novela><agente>

1

2010-11-18T20:43:28.903

34

5

<trama> <relato corto> <planificación> <tormenta cerebral>

0

2010-11-18T20:43:59.693

28

7

<ficción><género><categorías>

1

2010-11-18T20:45:44.067

21

Cada pregunta tiene varias etiquetas, así como una fecha y varios comentarios. Vamos a preprocesar cada uno de ellos. En primer lugar, normalizamos los campos numéricos:

def get_norm(df, col):
    return (df[col] - df[col].mean()) / df[col].std()

tabular_df["NormComment"]= get_norm(tabular_df, "CommentCount")
tabular_df["NormScore"]= get_norm(tabular_df, "Score")

A continuación, extraemos la información relevante de la fecha. Podríamos, por ejemplo, elegir el año, el mes, el día y la hora de publicación. Cada uno de ellos es un valor numérico que nuestro modelo puede utilizar.

# Convert our date to a pandas datetime
tabular_df["date"] = pd.to_datetime(tabular_df["CreationDate"])

# Extract meaningful features from the datetime object
tabular_df["year"] = tabular_df["date"].dt.year
tabular_df["month"] = tabular_df["date"].dt.month
tabular_df["day"] = tabular_df["date"].dt.day
tabular_df["hour"] = tabular_df["date"].dt.hour

Nuestras etiquetas son características categóricas, y cada pregunta puede tener cualquier número de etiquetas. Como hemos visto antes, la forma más fácil de representar entradas categóricas es codificarlas en un solo paso, transformando cada etiqueta en su propia columna, y que cada pregunta tenga un valor de 1 para una característica de etiqueta determinada sólo si esa etiqueta está asociada a esa pregunta.

Como tenemos más de trescientas etiquetas en nuestro conjunto de datos, aquí optamos por crear sólo una columna para las cinco más populares que se utilizan en más de quinientas preguntas. Podríamos añadir todas y cada una de las etiquetas, pero como la mayoría de ellas sólo aparecen una vez, esto no sería útil para identificar patrones.

# Select our tags, represented as strings, and transform them into arrays of tags
tags = tabular_df["Tags"]
clean_tags = tags.str.split("><").apply(
    lambda x: [a.strip("<").strip(">") for a in x])

# Use pandas' get_dummies to get dummy values
# select only tags that appear over 500 times
tag_columns = pd.get_dummies(clean_tags.apply(pd.Series).stack()).sum(level=0)
all_tags = tag_columns.astype(bool).sum(axis=0).sort_values(ascending=False)
top_tags = all_tags[all_tags > 500]
top_tag_columns = tag_columns[top_tags.index]

# Add our tags back into our initial DataFrame
final = pd.concat([tabular_df, top_tag_columns], axis=1)

# Keeping only the vectorized features
col_to_keep = ["year", "month", "day", "hour", "NormComment",
               "NormScore"] + list(top_tags.index)
final_features = final[col_to_keep]

En la Tabla 4-4, puedes ver que nuestros datos están ahora totalmente vectorizados, con cada fila formada sólo por valores numéricos. Podemos introducir estos datos en un algoritmo de agrupación o en un modelo ML supervisado.

Tabla 4-4. Entradas tabulares vectorizadas
Id Año Mes Día Hora Norma-Comentario Puntuación normal Escritura creativa Ficción Estilo Personajes Tech-nique Novedad Pub-lishing

1

2010

11

18

20

0.165706

0.140501

0

0

0

0

0

0

0

2

2010

11

18

20

-0.103524

0.077674

0

1

0

0

0

0

0

3

2010

11

18

20

-0.065063

0.150972

0

0

0

0

0

1

1

5

2010

11

18

20

-0.103524

0.119558

0

0

0

0

0

0

0

7

2010

11

18

20

-0.065063

0.082909

0

1

0

0

0

0

0

Los distintos tipos de datos exigen métodos de vectorización diferentes. En particular, los datos de texto suelen requerir enfoques más creativos.

Datos de texto

La forma más sencilla de vectorizar el texto es utilizar un vector de recuento, que es el equivalente en palabras de la codificación de un solo punto. Empieza construyendo un vocabulario formado por la lista de palabras únicas de tu conjunto de datos. Asocia cada palabra de nuestro vocabulario a un índice (de 0 al tamaño de nuestro vocabulario). A continuación, puedes representar cada frase o párrafo mediante una lista tan larga como nuestro vocabulario. Para cada frase, el número de cada índice representa el recuento de apariciones de la palabra asociada en la frase dada.

Este método ignora el orden de las palabras en una frase, por lo que se denomina bolsa de palabras. La Figura 4-5 muestra dos frases y sus representaciones en bolsa de palabras. Ambas frases se transforman en vectores que contienen información sobre el número de veces que una palabra aparece en una frase, pero no sobre el orden en que las palabras están presentes en la frase.

Getting bag of words vectors from sentences
Figura 4-5. Obtención de vectores bolsa de palabras a partir de frases

Utilizar una representación de bolsa de palabras o su versión normalizada TF-IDF (abreviatura de Term Frequency-Inverse Document Frequency) es sencillo utilizando scikit-learn, como puedes ver aquí:

# Create an instance of a tfidf vectorizer,
# We could use CountVectorizer for a non normalized version
vectorizer = TfidfVectorizer()

# Fit our vectorizer to questions in our dataset
# Returns an array of vectorized text
bag_of_words = vectorizer.fit_transform(df[df["is_question"]]["Text"])

A lo largo de los años se han desarrollado múltiples métodos novedosos de vectorización de textos, empezando en 2013 con Word2Vec (véase el artículo "Efficient Estimation of Word Representations in Vector Space", de Mikolov et al .) y enfoques más recientes como fastText (véase el artículo "Bag of Tricks for Efficient Text Classification", de Joulin et al.). Estas técnicas de vectorización producen vectores de palabras que intentan aprender una representación que capte las similitudes entre conceptos mejor que una codificación TF-IDF. Lo hacen aprendiendo qué palabras tienden a aparecer en contextos similares en grandes cuerpos de texto como Wikipedia. Este enfoque se basa en la hipótesis distribucional, que afirma que los elementos lingüísticos con distribuciones similares tienen significados similares.

Concretamente, este se hace aprendiendo un vector para cada palabra y entrenando un modelo para predecir una palabra que falta en una frase utilizando los vectores de las palabras que la rodean. El número de palabras vecinas que hay que tener en cuenta se denomina tamaño de la ventana. En la Figura 4-6, puedes ver una representación de esta tarea para un tamaño de ventana de dos. A la izquierda, los vectores de palabras de las dos palabras anteriores y posteriores al objetivo se introducen en un modelo simple. A continuación, este modelo simple y los valores de los vectores de palabras se optimizan para que la salida coincida con el vector de palabras de la palabra que falta.

Existen muchos modelos de vectorización de palabras preentrenados de código abierto. Utilizar vectores producidos por un modelo que ha sido preentrenado en un corpus grande (a menudo Wikipedia o un archivo de noticias) puede ayudar a nuestros modelos a aprovechar mejor el significado semántico de las palabras comunes.

Por ejemplo, los vectores de palabras mencionados en el artículo fastText de Joulin et al. están disponibles en línea en una herramienta independiente. Para un enfoque más personalizado de, spaCy es un conjunto de herramientas de PNL que proporciona modelos preentrenados para diversas tareas, así como formas sencillas de construir los tuyos propios.

He aquí un ejemplo de uso de spaCy para cargar vectores de palabras preentrenados y utilizarlos para obtener un vector de frases semánticamente significativas. Bajo el capó, spaCy recupera el valor preentrenado de cada palabra de nuestro conjunto de datos (o lo ignora si no formaba parte de su tarea de preentrenamiento) y promedia todos los vectores de una pregunta para obtener una representación de la misma.

import spacy

# We load a large model, and disable pipeline unnecessary parts for our task
# This speeds up the vectorization process significantly
# See https://spacy.io/models/en#en_core_web_lg for details about the model
nlp = spacy.load('en_core_web_lg', disable=["parser", "tagger", "ner",
      "textcat"])

# We then simply get the vector for each of our questions
# By default, the vector returned is the average of all vectors in the sentence
# See https://spacy.io/usage/vectors-similarity for more
spacy_emb = df[df["is_question"]]["Text"].apply(lambda x: nlp(x).vector)

Para ver una comparación de un modelo TF-IDF con incrustaciones de palabras preentrenadas para nuestro conjunto de datos, consulta el cuaderno de vectorización de texto en el repositorio GitHub del libro.

Desde 2018, la vectorización de palabras mediante grandes modelos lingüísticos en conjuntos de datos aún mayores ha empezado a producir los resultados más precisos (véanse los artículos "Universal Language Model Fine-Tuning for Text Classification", de J. Howard y S. Ruder, y "BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding", de J. Devlin et al.). Sin embargo, estos grandes modelos tienen el inconveniente de ser más lentos y complejos que las simples incrustaciones de palabras.

Por último, examinemos la vectorización para otro tipo de datos de uso común, las imágenes.

Datos de la imagen

Los datos de las imágenes ya están vectorizados, ya que una imagen no es más que una matriz multidimensional de números, a menudo denominados tensores en la comunidad de ML. La mayoría de las imágenes RGB estándar de tres canales de, por ejemplo, se almacenan simplemente como una lista de números de longitud igual a la altura de la imagen en píxeles, multiplicada por su anchura, multiplicada por tres (para los canales rojo, verde y azul). En la Figura 4-7, puedes ver cómo podemos representar una imagen como un tensor de números, que representan la intensidad de cada uno de los tres colores primarios.

Aunque podemos utilizar esta representación tal cual, nos gustaría que nuestros tensores captaran algo más sobre el significado semántico de nuestras imágenes. Para ello, podemos utilizar un enfoque similar al del texto y aprovechar grandes redes neuronales preentrenadas.

Modelos que se han entrenado en conjuntos de datos de clasificación masiva como VGG (véase el artículo de A. Simonyan y A. Zimmerman, "Very Deep Convolutional Networks for Large-Scale Image Recognition") o Inception (véase el artículo de C. Szegedy et al., "Going Deeper with Convolutions") en el conjunto de datos ImageNet acaban aprendiendo representaciones muy expresivas para clasificar bien. Estos modelos siguen en su mayoría una estructura de alto nivel similar. La entrada es una imagen que pasa por muchas capas sucesivas de cálculo, cada una de las cuales genera una representación diferente de dicha imagen.

Por último, la penúltima capa se pasa a una función que genera probabilidades de clasificación para cada clase. Esta penúltima capa contiene, pues, una representación de la imagen suficiente para clasificar qué objeto contiene, lo que la convierte en una representación útil para otras tareas.

An image is simply a tensor
Figura 4-7. Representación de un 3 como una matriz de valores de 0 a 1 (mostrando sólo el canal rojo)

Extraer esta capa de representación demuestra que funciona muy bien a la hora de generar vectores significativos para las imágenes. Esto no requiere más trabajo personalizado que cargar el modelo preentrenado. En la Figura 4-8, cada rectángulo representa una capa diferente de uno de esos modelos preentrenados. La representación más útil aparece resaltada. Suele estar situada justo antes de la capa de clasificación, ya que es la representación que mejor debe resumir la imagen para que el clasificador funcione bien.

Using a pre-trained model to vectorize images
Figura 4-8. Utilizar un modelo preentrenado para vectorizar imágenes

Utilizar bibliotecas modernas como Keras facilita mucho esta tarea. He aquí una función que carga imágenes de una carpeta y las transforma en vectores semánticamente significativos para su análisis posterior, utilizando una red preentrenada disponible en Keras:

import numpy as np

from keras.preprocessing import image
from keras.models import Model
from keras.applications.vgg16 import VGG16
from keras.applications.vgg16 import preprocess_input


def generate_features(image_paths):
    """
    Takes in an array of image paths
    Returns pretrained features for each image
    :param image_paths: array of image paths
    :return: array of last-layer activations,
    and mapping from array_index to file_path
    """

    images = np.zeros(shape=(len(image_paths), 224, 224, 3))

    # loading a  pretrained model
    pretrained_vgg16 = VGG16(weights='imagenet', include_top=True)

    # Using only the penultimate layer, to leverage learned features
    model = Model(inputs=pretrained_vgg16.input,
                  outputs=pretrained_vgg16.get_layer('fc2').output)

    # We load all our dataset in memory (works for small datasets)
    for i, f in enumerate(image_paths):
        img = image.load_img(f, target_size=(224, 224))
        x_raw = image.img_to_array(img)
        x_expand = np.expand_dims(x_raw, axis=0)
        images[i, :, :, :] = x_expand

    # Once we've loaded all our images, we pass them to our model
    inputs = preprocess_input(images)
    images_features = model.predict(inputs)
    return images_features

Una vez que tengas una representación vectorizada, puedes agruparla o pasar tus datos a un modelo, pero también puedes utilizarla para inspeccionar más eficazmente tu conjunto de datos. Agrupando puntos de datos con representaciones similares, puedes observar más rápidamente las tendencias de tu conjunto de datos. A continuación veremos cómo hacerlo.

Reducción de la dimensionalidad

Disponer de representaciones vectoriales en es necesario para los algoritmos, ¡pero también podemos aprovechar esas representaciones para visualizar los datos directamente! Esto puede parecer un reto, porque los vectores que hemos descrito suelen tener más de dos dimensiones, lo que dificulta su visualización en un gráfico. ¿Cómo podríamos visualizar un vector de 14 dimensiones?

Geoffrey Hinton, que ganó un Premio Turing por su trabajo en aprendizaje profundo, reconoce este problema en su conferencia con el siguiente consejo: "Para tratar con hiperplanos en un espacio de 14 dimensiones, visualiza un espacio 3D y dite catorce a ti mismo muy alto. Todo el mundo lo hace". (Consulta aquí la diapositiva 16 de la conferencia de G. Hinton et al., "Una visión general de los principales tipos de arquitectura de redes neuronales"). Si esto te parece difícil, te entusiasmará oír hablar de la reducción de la dimensionalidad, que es la técnica de representar vectores en menos dimensiones conservando todo lo posible sobre su estructura.

Dimensionalidad técnicas de reducción como t-SNE (véase el artículo de L. van der Maaten y G. Hinton, PCA, "Visualizing Data Using t-SNE"), y UMAP (véase el artículo de L. McInnes et al, "UMAP: Uniform Manifold Approximation and Projection for Dimension Reduction") te permiten proyectar datos de alta dimensión, como vectores que representan frases, imágenes u otras características, en un plano 2D.

Estas proyecciones son útiles para advertir patrones en los datos que luego puedes investigar. Sin embargo, son representaciones aproximadas de los datos reales, por lo que debes validar cualquier hipótesis que hagas a partir de un gráfico de este tipo utilizando otros métodos. Si ves grupos de puntos pertenecientes todos a una clase que parecen tener una característica en común, comprueba que tu modelo está aprovechando realmente esa característica, por ejemplo.

Para empezar, traza tus datos utilizando una técnica de reducción de la dimensionalidad y colorea cada punto según un atributo que quieras inspeccionar. Para tareas de clasificación, empieza coloreando cada punto en función de su etiqueta. Para tareas no supervisadas, puedes colorear los puntos basándote en los valores de determinadas características que estés examinando, por ejemplo. Esto te permite ver si alguna región parece que será fácil de separar para tu modelo, o más difícil.

He aquí cómo hacerlo fácilmente utilizando UMAP, pasándole las incrustaciones que generamos en "Vectorizar":

import umap

# Fit UMAP to our data, and return the transformed data
umap_emb = umap.UMAP().fit_transform(embeddings)

fig = plt.figure(figsize=(16, 10))
color_map = {
    True: '#ff7f0e',
    False:'#1f77b4'
}
plt.scatter(umap_emb[:, 0], umap_emb[:, 1],
            c=[color_map[x] for x in sent_labels],
            s=40, alpha=0.4)

Como recordatorio, decidimos empezar utilizando sólo datos de la comunidad de escritores de Stack Exchange. El resultado de este conjunto de datos se muestra en la Figura 4-9. A primera vista, podemos ver algunas regiones que deberíamos explorar, como la densa región de preguntas sin respuesta de la parte superior izquierda. Si podemos identificar qué características tienen en común, quizá descubramos una característica de clasificación útil.

Una vez vectorizados y trazados los datos, suele ser buena idea empezar a identificar sistemáticamente grupos de puntos de datos similares y explorarlos. Podríamos hacerlo simplemente observando los gráficos UMAP, pero también podemos aprovechar la agrupación.

UMAP plot colored by whether a given question was successfully answered or not
Figura 4-9. Gráfico UMAP coloreado en función de si se ha respondido correctamente a una pregunta determinada

Agrupación

Antes hemos mencionado la agrupación en como método para extraer la estructura de los datos. Tanto si agrupas datos para inspeccionar un conjunto de datos como si lo utilizas para analizar el rendimiento de un modelo, como haremos en el Capítulo 5, la agrupación es una herramienta fundamental que debes tener en tu arsenal. Yo utilizo la agrupación de forma similar a la reducción de la dimensionalidad, como una forma adicional de sacar a la luz problemas y puntos de datos interesantes.

Un método sencillo de para agrupar datos en la práctica es empezar probando unos cuantos algoritmos sencillos, como k-means, y ajustar sus hiperparámetros, como el número de conglomerados, hasta que alcances un rendimiento satisfactorio.

El rendimiento de la agrupación es difícil de cuantificar. En la práctica, utilizar una combinación de visualización de datos y métodos como el método del codo o un gráfico de siluetas es suficiente para nuestro caso de uso, que no consiste en separar perfectamente nuestros datos, sino en identificar las regiones en las que nuestro modelo puede tener problemas.

A continuación se muestra un fragmento de código de ejemplo para agrupar nuestro conjunto de datos, así como para visualizar nuestros conglomerados utilizando una técnica de dimensionalidad que hemos descrito antes, UMAP.

from sklearn.cluster import KMeans
import matplotlib.cm as cm

# Choose number of clusters and colormap
n_clusters=3
cmap = plt.get_cmap("Set2")

# Fit clustering algorithm to our vectorized features
clus = KMeans(n_clusters=n_clusters, random_state=10)
clusters = clus.fit_predict(vectorized_features)

# Plot the dimentionality reduced features on a 2D plane
plt.scatter(umap_features[:, 0], umap_features[:, 1],
            c=[cmap(x/n_clusters) for x in clusters], s=40, alpha=.4)
plt.title('UMAP projection of questions, colored by clusters', fontsize=14)

Como puedes ver en la Figura 4-10, la forma en que agruparíamos instintivamente la representación 2D no siempre coincide con los agrupamientos que nuestro algoritmo encuentra en los datos vectorizados. Esto puede deberse a artefactos en nuestro algoritmo de reducción de la dimensionalidad o a una topología compleja de los datos. De hecho, añadir el conglomerado asignado a un punto como característica puede mejorar a veces el rendimiento de un modelo al permitirle aprovechar dicha topología.

Una vez que tengas los clusters, examina cada uno de ellos e intenta identificar tendencias en tus datos en cada uno de ellos. Para ello, debes seleccionar unos cuantos puntos por conglomerado y actuar como si fueras el modelo, etiquetando así esos puntos con lo que crees que debería ser la respuesta correcta. En la siguiente sección, describiré cómo realizar este trabajo de etiquetado.

Visualizing our questions, colored by cluster
Figura 4-10. Visualización de nuestras preguntas, coloreadas por cluster

Sé el Algoritmo

Una vez que hayas mirado las métricas agregadas y la información de los clusters, te animo a que sigas el consejo de "Monica Rogati: Cómo elegir y priorizar proyectos de ML" e intentes hacer el trabajo de tu modelo etiquetando unos cuantos puntos de datos de cada cluster con los resultados que te gustaría que produjera el modelo.

Si nunca has intentado hacer el trabajo de tu algoritmo, será difícil juzgar la calidad de sus resultados. Por otro lado, si pasas algún tiempo etiquetando datos tú mismo, a menudo notarás tendencias que harán tu tarea de modelado mucho más fácil.

Puede que reconozcas este consejo de nuestra sección anterior sobre heurística, y no debería sorprenderte. Elegir un enfoque de modelización implica hacer casi tantas suposiciones sobre nuestros datos como construir una heurística, así que tiene sentido que estas suposiciones se basen en los datos.

Debes etiquetar los datos aunque tu conjunto de datos contenga etiquetas. Esto te permite validar que tus etiquetas captan la información correcta y que son correctas. En nuestro estudio de caso, utilizamos la puntuación de una pregunta como medida de su calidad, que es una etiqueta débil. Etiquetar nosotros mismos algunos ejemplos nos permitirá validar la suposición de que esta etiqueta es adecuada.

Una vez que hayas etiquetado unos cuantos ejemplos, no dudes en actualizar tu estrategia de vectorización añadiendo cualquier característica que descubras para ayudar a que la representación de tus datos sea lo más informativa posible, y vuelve a etiquetar. Se trata de un proceso iterativo, como se ilustra en la Figura 4-11.

The process of labeling data.
Figura 4-11. El proceso de etiquetado de datos

Para acelerar tu etiquetado, asegúrate de aprovechar tu análisis previo etiquetando unos cuantos puntos de datos en cada conglomerado que hayas identificado y para cada valor común en tu distribución de características.

Una forma de hacerlo es aprovechar las bibliotecas de visualización de para explorar interactivamente tus datos. Bokeh ofrece la posibilidad de hacer gráficos interactivos. Una forma rápida de etiquetar los datos es recorrer un gráfico de nuestros ejemplos vectorizados, etiquetando unos cuantos ejemplos para cada grupo.

La Figura 4-12 muestra un ejemplo individual representativo de un grupo de preguntas en su mayoría sin respuesta. Las preguntas de este grupo tendían a ser bastante vagas y difíciles de responder objetivamente, y no recibieron respuesta. Éstas se etiquetan con precisión como preguntas deficientes. Para ver el código fuente de este gráfico y un ejemplo de su uso para el Editor ML, navega hasta el cuaderno Explorar datos para generar características en el repositorio GitHub de este libro.

Using Bokeh to inspect and label data
Figura 4-12. Utilizar Bokeh para inspeccionar y etiquetar datos

Al etiquetar datos, puedes elegir almacenar las etiquetas con los propios datos (como una columna adicional en un Marco de datos, por ejemplo) o por separado, utilizando un mapeo de archivo o identificador a etiqueta. Esto es puramente una cuestión de preferencia.

A medida que vayas etiquetando ejemplos, intenta fijarte en qué proceso estás utilizando para tomar tus decisiones. Esto te ayudará a identificar tendencias y a generar características que ayuden a tus modelos.

Tendencias de los datos

Después de tener datos etiquetados durante un tiempo, normalmente identificarás tendencias. Algunas pueden ser informativas (los tweets cortos tienden a ser más sencillos de clasificar como positivos o negativos) y guiarte para generar características útiles para tus modelos. Otras pueden ser correlaciones irrelevantes debido a la forma en que se recopilaron los datos.

Puede que todos los tweets que recopilamos que están en francés resulten ser negativos, lo que probablemente llevaría a un modelo a clasificar automáticamente los tweets en francés como negativos. Dejaré que tú decidas lo inexacto que podría ser eso en una muestra más amplia y representativa.

Si observas algo parecido, ¡no desesperes! Es crucial identificar este tipo de tendencias antes de empezar a construir modelos, ya que inflarían artificialmente la precisión en los datos de entrenamiento y podrían llevarte a poner en producción un modelo que no funcione bien.

La mejor forma de tratar estos ejemplos sesgados es recopilar datos adicionales para que tu conjunto de entrenamiento sea más representativo. También podrías intentar eliminar esas características de tus datos de entrenamiento para evitar sesgar tu modelo, pero esto puede no ser eficaz en la práctica, ya que los modelos suelen captar el sesgo aprovechando las correlaciones con otras características (véase el Capítulo 8).

Una vez que hayas identificado algunas tendencias, es hora de utilizarlas. La mayoría de las veces, puedes hacerlo de dos formas: creando un elemento que caracterice esa tendencia o utilizando un modelo que la aproveche fácilmente.

Deja que los datos informen las características y los modelos

Nos gustaría utilizar las tendencias que descubrimos en los datos para informar nuestro procesamiento de datos, la generación de características y la estrategia de modelado. Para empezar, veamos cómo podemos generar características que nos ayuden a captar esas tendencias.

Construye funciones a partir de patrones

ML consiste en utilizar algoritmos de aprendizaje estadístico para aprovechar los patrones de los datos, pero algunos patrones son más fáciles de captar para los modelos que otros. Imagina el ejemplo trivial de predecir un valor numérico utilizando el propio valor dividido por 2 como característica. El modelo simplemente tendría que aprender a multiplicar por 2 para predecir perfectamente el objetivo. En cambio, predecir el mercado bursátil a partir de datos históricos es un problema que requiere aprovechar patrones mucho más complejos.

Por eso, muchos de los beneficios prácticos del ML provienen de generar características adicionales que ayuden a nuestros modelos a identificar patrones útiles. La facilidad con la que un modelo identifica patrones depende de la forma en que representemos los datos y de la cantidad de ellos que tengamos. Cuantos más datos tengas y menos ruidosos sean tus datos, menos trabajo de ingeniería de características tendrás que hacer normalmente.

Sin embargo, a menudo es valioso empezar generando características; en primer lugar, porque normalmente empezaremos con un conjunto de datos pequeño y, en segundo lugar, porque ayuda a codificar nuestras creencias sobre los datos y a depurar nuestros modelos.

Estacionalidad es una tendencia común que se beneficia de la generación de funciones específicas. Supongamos que un minorista online se ha dado cuenta de que la mayoría de sus ventas se producen los dos últimos fines de semana del mes. Al construir un modelo para predecir las ventas futuras, quieren asegurarse de que tiene potencial para captar este patrón.

Como verás, dependiendo de cómo representen las fechas, la tarea puede resultar bastante difícil para sus modelos. La mayoría de los modelos sólo son capaces de tomar entradas numéricas (véase "Vectorización" para conocer los métodos para transformar texto e imágenes en entradas numéricas), así que examinemos algunas formas de representar fechas.

Fecha y hora en bruto

La forma más sencilla de representar el tiempo es en tiempo Unix, que representa "el número de segundos transcurridos desde las 00:00:00 del jueves 1 de enero de 1970".

Aunque esta representación es sencilla, nuestro modelo necesitaría aprender algunos patrones bastante complejos para identificar los dos últimos fines de semana del mes. El último fin de semana de 2018, por ejemplo (desde las 00:00:00 del día 29 hasta las 23:59:59 del día 30 de diciembre), se representa en tiempo Unix como el intervalo comprendido entre 1546041600 y 1546214399 (puedes comprobarlo si tomas la diferencia entre ambos números, que representa un intervalo de 23 horas, 59 minutos y 59 segundos medidos en segundos).

Nada en este intervalo lo hace especialmente fácil de relacionar con otros fines de semana de otros meses, por lo que será bastante difícil para un modelo separar los fines de semana relevantes de los demás cuando utilice la hora Unix como entrada. Podemos facilitar la tarea a un modelo generando características.

Extraer el día de la semana y el día del mes

Una forma de hacer más clara nuestra representación de las fechas sería extraer el día de la semana y el día del mes en dos atributos separados.

La forma en que representaríamos las 23:59:59 del 30 de diciembre de 2018, por ejemplo, sería con el mismo número que antes, y dos valores adicionales que representarían el día de la semana (0 para el domingo, por ejemplo) y el día del mes (30).

Esta representación facilitará que nuestro modelo aprenda que los valores relacionados con los fines de semana (0 y 6 para el domingo y el sábado) y con las fechas más tardías del mes corresponden a una mayor actividad.

También es importante señalar que las representaciones a menudo introducirán sesgos en nuestro modelo. Por ejemplo, al codificar el día de la semana como un número, la codificación para el viernes (igual a cinco) será cinco veces mayor que la del lunes (igual a uno). Esta escala numérica es un artefacto de nuestra representación y no representa algo que deseemos que aprenda nuestro modelo.

Cruces de características

Aunque la representación anterior facilita la tarea a nuestros modelos, aún tendrían que aprender una compleja relación entre el día de la semana y el día del mes: el tráfico elevado no se produce los fines de semana de principios de mes ni los días laborables de finales de mes.

Algunos modelos, como las redes neuronales profundas, aprovechan las combinaciones no lineales de características y pueden así captar estas relaciones, pero a menudo necesitan una cantidad significativa de datos. Una forma habitual de abordar este problema es facilitar aún más la tarea e introducir cruces de características.

Una característica cruzada es una característica generada simplemente multiplicando (cruzando) dos o más características entre sí. Esta introducción de una combinación no lineal de rasgos permite a nuestro modelo discriminar más fácilmente basándose en una combinación de valores de múltiples rasgos.

En la Tabla 4-5, puedes ver cómo quedaría cada una de las representaciones que hemos descrito para unos cuantos puntos de datos de ejemplo.

Tabla 4-5. Representar tus datos de forma más clara facilitará que tus algoritmos funcionen bien
Representación humana Datos sin procesar (fecha y hora Unix) Día de la semana (DdS) Día del mes (DdM) Cruz (DoW / DoM)

Sábado, 29 de diciembre de 2018, 00:00:00

1,546,041,600

7

29

174

sábado, 29 de diciembre de 2018, 01:00:00

1,546,045,200

7

29

174

...

...

...

...

...

domingo, 30 de diciembre de 2018, 23:59:59

1,546,214,399

1

30

210

En la Figura 4-13, puedes ver cómo cambian los valores de estas características con el tiempo y cuáles hacen que sea más sencillo para un modelo separar unos puntos de datos concretos de otros.

Last weekends of the month are easiest to separate using feature crosses and extracted features
Figura 4-13. Los últimos fines de semana del mes son más fáciles de separar utilizando cruces de rasgos y rasgos extraídos

Hay una última forma de representar nuestros datos que facilitará aún más que nuestro modelo aprenda el valor predictivo de los dos últimos fines de semana del mes.

Dar la respuesta a tu modelo

Puede parecer trampa, pero si sabes a ciencia cierta que una determinada combinación de valores de características es especialmente predictiva, puedes crear una nueva característica binaria que tome un valor distinto de cero sólo cuando estas características tomen la combinación de valores pertinente. En nuestro caso, esto significaría añadir una característica llamada "es_últimos_dos_finales_de_semana", por ejemplo, que se pondrá a uno sólo durante los dos últimos fines de semana del mes.

Si los dos últimos fines de semana son tan predictivos como suponíamos, el modelo simplemente aprenderá a aprovechar esta característica y será mucho más preciso. Cuando construyas productos de ML, nunca dudes en facilitar la tarea a tu modelo. Es mejor tener un modelo que funcione en una tarea más sencilla que uno que tenga dificultades en una compleja.

La generación de características es un campo muy amplio, y existen métodos para la mayoría de los tipos de datos. Discutir cada característica que es útil generar para distintos tipos de datos está fuera del alcance de este libro. Si quieres ver más ejemplos prácticos y métodos, te recomiendo que eches un vistazo a Feature Engineering for Machine Learning (O'Reilly), de Alice Zheng y Amanda Casari.

En general, la mejor forma de generar características útiles es observar tus datos utilizando los métodos que hemos descrito y preguntarte cuál es la forma más sencilla de representarlos de modo que tu modelo aprenda sus patrones. En la siguiente sección, describiré algunos ejemplos de características que generé utilizando este proceso para el Editor ML.

Funciones del Editor ML

Para nuestro Editor ML, utilizando las técnicas descritas anteriormente para inspeccionar nuestro conjunto de datos (ver detalles de la exploración en el cuaderno Explorar datos para generar características, en el repositorio GitHub de este libro), generamos las siguientes características:

  • Los verbos de acción como puede y debe son predictivos de la respuesta a una pregunta, por lo que añadimos un valor binario que comprueba si están presentes en cada pregunta.

  • Los signos de interrogación también son buenos predictores, así que hemos generado una función has_question.

  • Las preguntas sobre el uso correcto de la lengua inglesa no solían obtener respuesta, así que añadimos una función is_language_question.

  • La longitud del texto de la pregunta es otro factor, ya que las preguntas muy cortas tienden a quedar sin respuesta. Esto llevó a añadir una característica de longitud de pregunta normalizada.

  • En nuestro conjunto de datos, el título de la pregunta también contiene información crucial, y fijarse en los títulos al etiquetar facilitó mucho la tarea. Esto nos llevó a incluir el texto del título en todos los cálculos de características anteriores.

Una vez que tenemos un conjunto inicial de características, podemos empezar a construir un modelo. La construcción de este primer modelo es el tema del siguiente capítulo, el Capítulo 5.

Antes de pasar a los modelos, quería profundizar en el tema de cómo recopilar y actualizar un conjunto de datos. Para ello, me senté con Robert Munro, un experto en la materia. Espero que disfrutes aquí del resumen de nuestra conversación, y que te deje entusiasmado para pasar a la siguiente parte: ¡construir nuestro primer modelo!

Robert Munro: ¿Cómo encontrar, etiquetar y aprovechar los datos?

Robert Munro ha fundado varias empresas de IA, creando algunos de los mejores equipos de inteligencia artificial. Fue director de tecnología de Figure Eight, una empresa líder en etiquetado de datos durante su mayor periodo de crecimiento. Antes de eso, Robert dirigió el producto de los primeros servicios nativos de procesamiento del lenguaje natural y traducción automática de AWS. En nuestra conversación, Robert comparte algunas lecciones que aprendió construyendo conjuntos de datos para ML.

P: ¿Cómo se empieza un proyecto de ML?

R: Lo mejor es empezar por el problema empresarial, ya que te dará unos límites con los que trabajar. En tu ejemplo de estudio de caso del editor ML, ¿estás editando texto que otra persona ha escrito después de enviarlo, o estás sugiriendo ediciones en directo mientras alguien escribe? Lo primero te permitiría procesar las solicitudes por lotes con un modelo más lento, mientras que lo segundo requeriría algo más rápido.

En cuanto a los modelos, el segundo enfoque invalidaría los modelos secuencia a secuencia, ya que serían demasiado lentos. Además, los modelos secuencia a secuencia actuales no funcionan más allá de las recomendaciones a nivel de frase y requieren mucho texto paralelo para ser entrenados. Una solución más rápida sería aprovechar un clasificador y utilizar las características importantes que extrae como sugerencias. Lo que quieres de este modelo inicial es una implementación sencilla y unos resultados en los que puedas confiar, empezando con Bayes ingenuo sobre características de bolsa de palabras, por ejemplo.

Por último, tienes que dedicar algún tiempo a observar algunos datos y etiquetarlos tú mismo. Esto te dará una intuición de lo difícil que es el problema y de qué soluciones podrían encajar bien.

P: ¿Cuántos datos necesitas para empezar?

R: Al recopilar datos, lo que buscas es garantizar que tienes un conjunto de datos representativo y diverso. Empieza por examinar los datos que tienes y ver si hay algún tipo que no esté representado, para poder reunir más. Agrupar tu conjunto de datos y buscar valores atípicos puede ser útil para acelerar este proceso.

Para etiquetar datos, en el caso común de la clasificación, hemos visto que etiquetar del orden de 1.000 ejemplos de tu categoría más rara funciona bien en la práctica. Al menos obtendrás una señal suficiente para saber si debes seguir con tu enfoque de modelado actual. En torno a los 10.000 ejemplos, puedes empezar a confiar en la seguridad de los modelos que estás construyendo.

A medida que obtengas más datos, la precisión de tu modelo aumentará lentamente, dándote una curva de cómo su rendimiento escala con los datos. En cualquier punto sólo te importa la última parte de la curva, que debería darte una estimación del valor actual que te darán más datos. En la inmensa mayoría de los casos, la mejora que obtendrás al etiquetar más datos será más significativa que si iteraras sobre el modelo.

P: ¿Qué proceso utilizas para recopilar y etiquetar los datos?

R: Puedes examinar tu mejor modelo actual y ver con qué tropieza. El muestreo de incertidumbre es un enfoque habitual: identifica los ejemplos sobre los que tu modelo tiene más incertidumbre (los más cercanos a su límite de decisión), y encuentra ejemplos similares para añadirlos al conjunto de entrenamiento.

También puedes entrenar un "modelo de errores" para encontrar más datos en los que tu modelo actual tenga problemas. Utiliza los errores que comete tu modelo como etiquetas (etiquetando cada punto de datos como "predicho correctamente" o "predicho incorrectamente"). Una vez que hayas entrenado un "modelo de error" con estos ejemplos, puedes utilizarlo con tus datos sin etiquetar y etiquetar los ejemplos en los que predice que tu modelo fallará.

Alternativamente, puedes entrenar un "modelo de etiquetado" para encontrar los mejores ejemplos para etiquetar a continuación. Supongamos que tienes un millón de ejemplos, de los que sólo has etiquetado 1.000. Puedes crear un conjunto de entrenamiento de 1.000 imágenes etiquetadas muestreadas aleatoriamente y 1.000 sin etiquetar, y entrenar un clasificador binario para predecir qué imágenes has etiquetado. A continuación, puedes utilizar este modelo de etiquetado para identificar los puntos de datos que son más diferentes de los que ya has etiquetado y etiquetarlos.

P: ¿Cómo validas que tus modelos están aprendiendo algo útil?

R: Un escollo habitual es acabar centrando los esfuerzos de etiquetado en una pequeña parte del conjunto de datos relevante. Puede que tu modelo tenga problemas con los artículos sobre baloncesto. Si sigues anotando más artículos de baloncesto, tu modelo puede llegar a ser excelente en baloncesto, pero malo en todo lo demás. Por eso, aunque debes utilizar estrategias para recopilar datos, siempre debes tomar muestras aleatorias de tu conjunto de pruebas para validar tu modelo.

Por último, la mejor forma de hacerlo es hacer un seguimiento de cuándo se desvía el rendimiento de tu modelo implementado. Podrías hacer un seguimiento de la incertidumbre del modelo o, idealmente, llevarlo a las métricas empresariales: ¿están bajando gradualmente tus métricas de uso? Esto podría deberse a otros factores, pero es un buen desencadenante para investigar y potencialmente actualizar tu conjunto de entrenamiento.

Conclusión

En este capítulo, hemos tratado consejos importantes para examinar de forma eficiente y eficaz un conjunto de datos.

Empezamos examinando la calidad de los datos y cómo decidir si son suficientes para nuestras necesidades. A continuación, tratamos la mejor manera de familiarizarte con el tipo de datos que tienes: empezando por las estadísticas resumidas y pasando a los grupos de puntos similares para identificar tendencias generales.

A continuación, tratamos por qué es valioso dedicar un tiempo considerable a etiquetar los datos para identificar tendencias que luego podamos aprovechar para diseñar características valiosas. Por último, pudimos aprender de la experiencia de Robert Munro ayudando a varios equipos a crear conjuntos de datos de última generación para ML.

Ahora que hemos examinado un conjunto de datos y generado características que esperamos que sean predictivas, estamos listos para construir nuestro primer modelo, lo que haremos en el Capítulo 5.

Get Creación de aplicaciones basadas en el aprendizaje automático now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.