La caja de herramientas del científico de datos Análisis sintáctico

El kit de herramientas del científico de datos Análisis sintáctico

Analizar documentos complejos puede ser fácil si tienes las herramientas adecuadas

Código fuente del nuevo parser y transformador rd2md basado en Python discutido en este artículo. Imagen del autor.

Para muchos científicos de datos, convertir documentos complejos en datos utilizables es un problema común. Veamos un documento complejo y exploremos diferentes métodos para transformar los datos.

TLDR;

Exploraremos estas reglas mientras desarrollamos un parser complejo:

Regla 1: Sé perezoso; no hagas más de lo necesarioRegla 2: Comienza por las partes fáciles del problema.Regla 3: No tengas miedo de desechar el código y empezar de nuevo.Regla 4: Usa el método más simple posible para hacer el trabajo.

El problema

Como jefe de investigación en una empresa de aprendizaje automático (ML), a menudo me enfrento a una variedad de problemas que necesitan ser explorados y diseñar soluciones. La semana pasada surgió un problema interesante: necesitábamos una forma de generar documentación en formato markdown para nuestro SDK R de código abierto que permite a los experimentos de ML registrar detalles importantes. Y necesitábamos una solución rápida sin invertir mucho tiempo en ella.

Este problema puede ser un poco más complejo de lo que un científico de datos encontraría a diario, pero servirá como un buen ejemplo de cómo utilizar diferentes métodos de análisis. Y como bonificación, obtendremos un nuevo proyecto de código abierto que cubre una necesidad específica. ¡Empecemos!

Al conocer el problema, se activó mi primera regla de investigación y diseño (R&D):

Regla 1: Sé perezoso; no hagas más de lo necesario (La pereza fue identificada por Larry Wall como una de las Tres Grandes Virtudes de un Programador).

Así que comencé a buscar si la conversión de código R a markdown era un problema resuelto. ¡Parece que sí lo era! Sin embargo, después de probar todos los programas disponibles que pude encontrar (como el antiguo Rd2md de R), simplemente no funcionaban y los repositorios de Git ya no estaban activos. Está bien, así que estaba solo. Si fuera un programador R mejor, probablemente habría intentado solucionar las soluciones existentes. Pero disfruto más de Python y pensé que sería un buen ejemplo de análisis. Así que sí, analizaremos la documentación de R en Python.

Entonces, empecé a escribir algo de código. Y eso me recordó mi siguiente regla de R&D:

Regla 2: Comienza por las partes fáciles del problema.

La regla 2 probablemente sea mi manera de satisfacer mi necesidad de tener una retroalimentación instantánea. Pero también cumple un papel más importante: si comienzas por las partes fáciles, tal vez las partes difíciles no resulten ser tan difíciles. También sirve para calentar y empezar a trabajar en un problema. Normalmente tengo uno o dos intentos fallidos al codificar una solución. Lo que me lleva a mi siguiente regla:

Regla 3: No tengas miedo de desechar el código y empezar de nuevo.

Finalmente, cuando estás en el camino correcto, la última regla es:

Regla 4: Usa el método más simple posible para hacer el trabajo.

Ok, ¿entonces cuál es el método más fácil para convertir archivos de documentación de R a markdown? Primero, ¿qué es un archivo de documentación de R? La documentación de R se convierte directamente del código R a algo que se ve muy parecido a LaTeX. Aquí tienes un ejemplo (los archivos terminan en .Rd):

% Generado por roxygen2: no editar manualmente% Por favor, edite la documentación en R/experiment.R\nombre{Experimento}\alias{Experimento}\title{Un objeto de experimento Comet}\description{Un objeto de experimento Comet se puede usar para modificar o obtener información acerca de un experimento activo. Todos los métodos documentados aquí son las diferentes formas de interactuar con un experimento. Use \code{\link[=create_experiment]{create_experiment()}} para crear o \code{\link[=get_experiment]{get_experiment()}} para obtener un objeto de experimento Comet.}

El objetivo es convertir el LaTeX en markdown que se vea algo así:

## DescripciónUn objeto de experimento de cometa se puede usar para modificar o obtener información sobre un experimento activo. Todos los métodos documentados aquí son las diferentes formas de interactuar con un experimento. Use [`create_experiment()`](../create_experiment) para crear o [`get_experiment()`](../get_experiment) para recuperar un objeto de experimento de cometa.

que se vea así:

Ejemplo de resultado de markdown. Imagen del autor.

De acuerdo, comencemos con algo muy simple. Pasaremos línea por línea del archivo Rd con algo como esto:

doc = Documentación()...for line in lines:    if line.startswith("%"):        pass    elif line.startswith("\\name{"):        matches = re.search("{(.*)}", line)        groups = groups()        name = groups[0]        doc.set_name(name)    ...

En este código, buscamos si una línea comienza con “%” y si es así, la omitimos (es solo un comentario en el archivo Rd). Del mismo modo, si comienza con “\name” establecemos el nombre actual de la documentación. Ten en cuenta que debemos escapar la barra invertida si no usamos cadenas de Python “en bruto”. El código re.search(“{(.*)}”, line) asume que la línea contendrá el corchete de cierre. Esta suposición se cumple en todos los ejemplos de nuestro SDK, por lo que no complicaré más este código de lo necesario, según la Regla 3.

Ten en cuenta que creamos una instancia de Documentación() antes de procesar las líneas del archivo. Hacemos esto para recopilar todas las partes y luego llamar a doc.generate() al final. Hacemos eso (en lugar de generar el markdown de forma continua) porque algunos de los elementos que analizamos estarán en un orden diferente en el markdown.

Podemos manejar parte del código R exactamente de esta manera: buscar un patrón en una línea del archivo Rd y procesarlo de inmediato. Sin embargo, veamos la siguiente parte más fácil que no se puede manejar de esta manera:

\usage{create_experiment(  experiment_name = NULL,  project_name = NULL,  workspace_name = NULL,  api_key = NULL,  keep_active = TRUE,  log_output = TRUE,  log_error = FALSE,  log_code = TRUE,  log_system_details = TRUE,  log_git_info = FALSE)}

La sección de uso siempre comienza con una línea que es \usage{ y termina con una línea que es un solo }. Dado que este es el caso, podemos utilizar estos hechos para crear un analizador ligeramente más complicado:

...for line in lines:    ....    elif line.startswith("\\usage{"):        usage = ""        line = fp_in.readline().rstrip()        while line != "}":            usage += line + "\n"            line = fp_in.readline().rstrip()        doc.set_usage(usage)

Esto leerá línea por línea, recopilando todo el texto en la sección \usage{}.

A medida que pasamos a la siguiente parte más complicada, tenemos que empezar a ser un poco ingeniosos y, por primera vez, usar la idea de “estado”.

Considera este código LaTeX:

\item{log_error}{Si \code{TRUE}, toda la salida de 'stderr' (que incluye errores, advertencias y mensajes) se redirigirá a los servidores de Comet para mostrar como registros de mensajes para el experimento. Ten en cuenta que, a diferencia de \code{auto_log_output}, si esta opción está activada, estos mensajes no se mostrarán en la consola y, en cambio, solo se registrarán en el experimento de Comet. Esta opción está establecida en \code{FALSE} de forma predeterminada debido a este comportamiento.}

Esto es complicado. El formato de nivel superior es:

\item{NOMBRE}{DESCRIPCIÓN}

Sin embargo, DESCRIPCIÓN puede tener elementos entre corchetes dentro de ella. Si tuvieras esta sección de código como una cadena (incluso con saltos de línea), podrías usar el módulo de Expresiones Regulares de Python, así:

text = """\item{log_error}{Si \code{TRUE}, toda la salida de 'stderr' (que incluye errores, advertencias y mensajes) se redirigirá a los servidores de Comet para mostrar como registros de mensajes para el experimento. Ten en cuenta que, a diferencia de \code{auto_log_output}, si esta opción está activada, estos mensajes no se mostrarán en la consola y, en cambio, solo se registrarán en el experimento de Comet. Esta opción está establecida en \code{FALSE} de forma predeterminada debido a este comportamiento.}"""matches = re.search("{(.*)}{(.*)}", text, re.DOTALL)

Puedes obtener NAME y DESCRIPTION como matches.groups(). Los paréntesis en el patrón re “{(.*)}{(.*)}” indican que se deben coincidir dos grupos: el primer grupo entre el primer par de llaves y el segundo grupo entre el siguiente par. Esto funciona bien, asumiendo que el texto es solo esa sección. Para poder analizar esto sin tener que romper primero esta sección, en realidad tendremos que analizar el texto, carácter por carácter. Pero esto no es difícil.

Aquí hay una pequeña función que obtendrá un número de secciones entre llaves dada una referencia de archivo (también conocida como “similar a un archivo” en la jerga moderna de Python):

def get_curly_contents(numero, fp):
    retval = []
    count = 0
    current = ""
    while True:
        char = fp.read(1)
        if char == "}":
            count -= 1
            if count == 0:
                if current.startswith("{"):
                    retval.append(current[1:])
                elif current.startswith("}{"):
                    retval.append(current[2:])
                else:
                    raise Exception("malformado?", current)
                current = ""
        elif char == "{":
            count += 1
        if len(retval) == numero:
            return retval
        current += char

En la función get_curly_contents() pasarías el número de secciones entre llaves y una referencia de archivo. Entonces, para obtener 2 secciones entre llaves de un archivo, puedes hacer:

fp = open(NOMBRE_DE_ARCHIVO)
nombre, descripcion = get_curly_contents(2, fp)

get_curly_contents() es casi lo más complicado que se obtiene en este proyecto. Tiene tres variables de estado: retval, count y current. retval es una lista de secciones analizadas. count es la profundidad de los elementos entre llaves actual. current es lo que se está procesando actualmente. Esta función es realmente útil en algunos lugares, como veremos.

Finalmente, hay un nivel más de complejidad. El área problemática es la subsección Método de una definición de clase R. Aquí hay un ejemplo simplificado:

\if{html}{\out{<hr>}}\if{html}{\out{<a id="method-Experiment-new"></a>}}\if{latex}{\out{\hypertarget{method-Experiment-new}{}}}\subsection{Método \code{new()}}{No llame a esta función directamente. Use \code{create_experiment()} o \code{get_experiment()} en su lugar.}\subsection{Uso}{\if{html}{\out{<div class="r">}}\preformatted{Experiment$new(  experiment_key,  project_name = NULL)}\if{html}{\out{</div>}}}\subsection{Argumentos}{\if{html}{\out{<div class="arguments">}}\describe{\item{\code{experiment_key}}{La clave del \code{Experimento}.}\item{\code{project_name}}{El nombre del proyecto (también se puede especificar utilizando el parámetro \code{COMET_PROJECT_NAME} como una variable de entorno o en un archivo de configuración de comet).}}\if{html}{\out{</div>}}}}

Esto es complicado porque tenemos secciones anidadas: Uso y Argumentos están dentro de Método. Vamos a sacar todo el arsenal de análisis para esto.

Para facilitarnos un poco las cosas, lo primero que haremos será “tokenizar” la subsección Método. Es una palabra elegante para dividir el texto en cadenas relevantes. Por ejemplo, considera este texto en LaTeX:

\subsection{Uso}{\if{html}{\out{<div class="r">}}\preformatted{Experiment$new(  experiment_key,  project_name = NULL)}\if{html}{\out{</div>}}}

Podría ser tokenizado en una lista de cadenas, así:

[ "\\", "subsection", "{", "Uso", "}", "\\", "if", "{", "html", "}", "{", "\\", "out", "{", "<", "div",  " ",  "class", "=", "\"r\"", ">", "}", "}", "\\",  "preformatted", "{", "Experiment$new", "(", "experiment_key", "project_name", "=", "NULL", ")", "}", "\\", "if", "{", "html", "}", "{", "\\", "out", "{", "<", "/", "div", ">", "}", "}", "}"]

Una lista de cadenas tokenizadas le brinda la capacidad de procesar fácilmente sus subpartes. Además, puede “anticipar” uno o más tokens para ver qué viene después. Esto puede ser difícil de hacer con Expresiones Regulares o si está tratando con caracteres individuales en lugar de tokens. Aquí hay un ejemplo de cómo analizar una sección tokenizada:

doc = Documentation()...method = Method()position = 0preamble = ""tokens = tokenize(text)while position < len(tokens):    token = tokens[position]    if token == "\\":        if tokens[position + 1] == "subsection":            in_preamble = False            if tokens[position + 3] == "Usage":                position, usage = get_tokenized_section(                    position + 5, tokens                )                 method.set_usage(usage)            elif tokens[position + 3] == "Arguments":                # omitir esto, lo haremos con describe                position += 5            elif tokens[position + 3] == "Examples":                position, examples = get_tokenized_section(                    position + 5, tokens                )                method.set_examples(examples)            elif tokens[position + 3] == "Returns":                position, returns = get_tokenized_section(                    position + 5, tokens                )                 method.set_returns(returns)            else:                raise Exception("subsection desconocida:", tokens[position + 3])        elif tokens[position + 1] == "describe":            position, describe = get_tokenized_section(position + 2, tokens)  # noqa            method.set_describe(describe)        else:            # \html            position += 1    else:        if in_preamble:            preamble += token        position += 1method.set_preamble(preamble)doc.add_method(method)

¡Eso es todo! Para ver el proyecto terminado, revisa la nueva herramienta basada en Python llamada rd2md. Es una biblioteca de Python de código abierto y instalable con pip para generar markdown desde los archivos Rd de R. Ya lo hemos utilizado en nuestra propia documentación de R aquí:

https://www.comet.com/docs/v2/api-and-sdk/r-sdk/overview/

¿Es esto un código que resultó de una tarde de hackeo? Sí. Está compuesto por no menos de 4 métodos diferentes de análisis. Pero hace el trabajo y es el único conversor de Rd a markdown que conozco y que funciona. Si tuviera que refactorizarlo, probablemente tokenizaría todo el archivo primero y luego lo procesaría utilizando el último método mostrado anteriormente. Recuerde la Regla 3: ¡No tengas miedo de desechar el código y comenzar de nuevo!

Si desea contribuir al repositorio de GitHub, por favor hágalo. Si tiene preguntas, por favor avísenos en los Issues.

¿Está interesado/a en Inteligencia Artificial, Aprendizaje Automático y Ciencia de Datos? Considere aplaudir y seguir a Doug. Él es el Jefe de Investigación en comet.com, una empresa de seguimiento de experimentos de IA y de monitoreo de modelos.

We will continue to update Zepes; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

Inteligencia Artificial

Este artículo de IA de NTU Singapur presenta MeVIS un banco de pruebas a gran escala para la segmentación de video con expresiones de movimiento

La segmentación de video guiada por lenguaje es un campo en desarrollo que se centra en segmentar y rastrear objetos ...

Inteligencia Artificial

Investigadores de UC Berkeley presentan Gorilla un modelo basado en LLaMA afinado que supera a GPT-4 en la escritura de llamadas a la API.

Un avance reciente en el campo de la Inteligencia Artificial es la introducción de los Modelos de Lenguaje Grandes (L...

Inteligencia Artificial

Hacia la IA General el papel de LLMs y Modelos Fundamentales en la Revolución del Aprendizaje de por Vida

En la última década y especialmente con el éxito del aprendizaje profundo, se ha formado una discusión continua en to...

Inteligencia Artificial

Amazon está probando la entrega de medicamentos con drones

Amazon está probando un servicio de entrega con drones para medicamentos recetados en College Station, Texas, con pla...

Inteligencia Artificial

Una revisión exhaustiva de los modelos de difusión de video en el Contenido Generado por Inteligencia Artificial (CGIA)

La Inteligencia Artificial está en auge, al igual que su subcampo, es decir, el dominio de la Visión por Computadora....