Набор инструментов данных ученого Анализ

Набор инструментов для анализа данных ученого

Разбор сложных документов может быть легким, если у вас есть правильные инструменты

Исходный код нового парсера и преобразователя rd2md на основе Python, обсуждаемого в этой статье. Изображение автора.

Для многих специалистов по анализу данных преобразование сложных документов в удобные данные является распространенной проблемой. Давайте рассмотрим сложный документ и исследуем различные методы преобразования данных.

Краткое содержание;

Мы рассмотрим эти правила по мере разработки сложного парсера:

Правило 1: Будьте ленивыми; делайте только то, что необходимоПравило 2: Начните с простых частей проблемы.Правило 3: Не бойтесь выбрасывать код и начинать сначала!Правило 4: Используйте наиболее простой метод для выполнения работы.

Проблема

В качестве руководителя исследований в компании по машинному обучению я часто сталкиваюсь с различными проблемами, которые нужно исследовать и для которых нужно разработать решения. На прошлой неделе возникла интересная маленькая проблема: нам понадобился способ генерации документации в формате Markdown для нашего открытого R-SDK, который позволяет вести журнал важных деталей экспериментов в области машинного обучения. И нам понадобилось быстрое решение, чтобы не тратить на это много времени.

Эта проблема может быть немного сложнее, чем то, с чем сталкивается специалист по анализу данных ежедневно, но она послужит хорошим примером использования различных методов разбора. И в качестве бонуса мы получим новый проект с открытым исходным кодом, который займет свое место в определенной нише. Начнем!

Когда мне стали известны проблемы, сработало мое первое правило исследований и разработки (R&D):

Правило 1: Будьте ленивыми; делайте только то, что необходимо (Лень была определена Ларри Уоллом как одно из Трех великих достоинств программиста).

Так что я начал искать, является ли преобразование кода R в markdown решенной проблемой. Кажется, что да! Однако, попробовав все доступные программы, которые я мог найти (такие как устаревший Rd2md в R), они просто не работали, и репозитории git больше не обслуживались. Хорошо, так что я остался один на один с проблемой. Если бы я был лучшим программистом на R, я, наверное, попробовал бы исправить существующие решения. Но я больше люблю Python и подумал, что это будет прекрасный пример разбора. Итак, да, мы будем разбирать документацию на R с помощью Python.

Итак, я начал писать код. И это напомнило мне мое следующее правило для исследований и разработки:

Правило 2: Начните с простых частей проблемы.

Правило 2, вероятно, просто является моим способом удовлетворить свою потребность в мгновенной обратной связи. Но оно также решает более важную роль: если вы начнете с простых частей, возможно, сложные части окажутся не такими сложными. Оно также служит разминкой для начала работы над проблемой. Обычно у меня бывает один или два ложных старта при написании решения. Что приводит к моему следующему правилу:

Правило 3: Не бойтесь выбрасывать код и начинать сначала!

Наконец, когда вы на правильном пути, последнее правило гласит:

Правило 4: Используйте наиболее простой метод для выполнения работы.

Итак, какой самый простой метод преобразования файлов документации на R в markdown? Сначала, что такое файл документации на R? Документация на R преобразуется прямо из кода R в что-то, что очень похоже на LaTeX. Вот пример (файлы заканчиваются на .Rd):

% Generated by roxygen2: do not edit by hand% Please edit documentation in R/experiment.R\name{Experiment}\alias{Experiment}\title{A Comet Experiment object}\description{A comet experiment object can be used to modify or get information about an activeexperiment. All methods documented here are the different ways to interact with anexperiment. Use \code{\link[=create_experiment]{create_experiment()}} to create or \code{\link[=get_experiment]{get_experiment()}} toretrieve a Comet experiment object.}

Цель – преобразовать LaTeX в разметку Markdown, которая выглядит примерно так:

## Description
Объект эксперимента космического кометы может быть использован для изменения или получения информации об активном эксперименте. Все здесь задокументированные методы представляют различные способы взаимодействия с экспериментом. Используйте [`create_experiment()`](../create_experiment), чтобы создать или [`get_experiment()`](../get_experiment), чтобы получить объект эксперимента кометы.

которая выводится следующим образом:

Example markdown output. Image by the author.

Хорошо, давайте начнем с чего-то очень простого. Мы пройдемся по файлу Rd построчно, используя код наподобие такого:

doc = Documentation()...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)    ...

В этом коде мы смотрим, начинается ли строка с символа «%», и если да, то пропускаем ее (это просто комментарий в файле Rd). Аналогично, если она начинается с символа «\name», то мы устанавливаем текущее имя документа. Обратите внимание, что нам нужно экранировать обратный слеш, если мы не используем Python-строки в “сыром” виде. Код re.search(“{{(.*)}}”, line) предполагает, что на строке будет закрывающая фигурная скобка. Это предположение верно для всех примеров в нашей среде разработки, поэтому этот код не стану делать более сложным, в соответствии с правилом 3.

Обратите внимание, что мы создаем экземпляр Documentation() перед обработкой строк в файле. Мы делаем это, чтобы собрать все части и затем вызвать doc.generate() в конце. Мы делаем это (вместо генерации markdown на лету), потому что некоторые элементы, которые мы анализируем, будут находиться в другом порядке в markdown.

Мы можем обрабатывать некоторый R-код именно таким образом: искать шаблон в строке из файла Rd и сразу же его обрабатывать. Однако посмотрим на следующую часть, которую нельзя обработать таким способом:

\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)}

Раздел использования всегда начинается с строки \usage{ и заканчивается строкой, содержащей одну фигурную скобку }. Исходя из этого, мы можем создать слегка более сложный парсер:

...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)

Этот код будет читать строку за строкой, собирая весь текст в секции \usage{}.

Когда мы переходим к следующей самой сложной части, нам приходится начать использовать немного хитрости и впервые использовать понятие “состояние”.

Рассмотрим этот код LaTeX:

\item{log_error}{If \code{TRUE}, all output from 'stderr' (which includes errors,warnings, and messages) will be redirected to the Comet servers to display as messagelogs for the experiment. Note that unlike \code{auto_log_output}, if this option is on thenthese messages will not be shown in the console and instead they will only be loggedto the Comet experiment. This option is set to \code{FALSE} by default because of thisbehavior.}

Это сложно. Верхнеуровневый формат такой:

\item{NAME}{DESCRIPTION}

Однако само ОПИСАНИЕ может содержать фигурные скобки внутри него. Если у вас есть этот отрывок кода в строке (даже с переносами строки), вы можете использовать модуль регулярных выражений Python (re) следующим образом:

text = """\item{log_error}{If \code{TRUE}, all output from 'stderr' (which includes errors,warnings, and messages) will be redirected to the Comet servers to display as messagelogs for the experiment. Note that unlike \code{auto_log_output}, if this option is on thenthese messages will not be shown in the console and instead they will only be loggedto the Comet experiment. This option is set to \code{FALSE} by default because of thisbehavior.}"""
matches = re.search("{{(.*)}}{(.*)}", text, re.DOTALL)

Вы можете получить NAME и DESCRIPTION как matches.groups(). Скобки в выражении re “{(.*)}{(.*)}” указывают на сопоставление двух групп: первая группа между первыми фигурными скобками, а вторая группа между следующей парой. Это работает отлично, предполагая, что текст является только этой секцией. Чтобы парсить его без разбиения секции, мы фактически будем разбираться с текстом, символ за символом. Но это несложно.

Вот небольшая функция, которая вернет несколько секций, заключенных в фигурные скобки, при наличии указателя файла (также известного как “подобный файлу” в современной терминологии Python):

def get_curly_contents(number, 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("malformed?", current)                current = ""        elif char == "{":            count += 1        if len(retval) == number:            return retval        current += char

В функцию get_curly_contents() вы передаете количество секций в фигурных скобках и указатель файла. Так что, чтобы получить 2 секции, заключенные в фигурные скобки, из файла, вы можете сделать так:

fp = open(FILENAME)name, description = get_curly_contents(2, fp)

get_curly_contents() почти такая же сложная, как она может быть в этом проекте. В ней есть три переменные состояния: retval, count и current. retval – это список разобранных секций. count – это глубина текущих элементов в фигурных скобках. current – то, что обрабатывается в данный момент. Эта функция фактически полезна в нескольких местах, как мы увидим.

Наконец, есть еще один уровень сложности. Проблемная область – подраздел Method определения класса R. Вот простой пример:

\if{html}{\out{<hr>}}\if{html}{\out{<a id="method-Experiment-new"></a>}}\if{latex}{\out{\hypertarget{method-Experiment-new}{}}}\subsection{Method \code{new()}}{Не вызывайте эту функцию напрямую. Вместо этого используйте \code{create_experiment()} или \code{get_experiment()}.\subsection{Usage}{\if{html}{\out{<div class="r">}}\preformatted{Experiment$new(  experiment_key,  project_name = NULL)}\if{html}{\out{</div>}}}\subsection{Arguments}{\if{html}{\out{<div class="arguments">}}\describe{\item{\code{experiment_key}}{Ключ \code{Experiment}.}\item{\code{project_name}}{Имя проекта (можно также указать с помощью параметра \code{COMET_PROJECT_NAME} как переменной среды или в файле конфигурации кометы).}}\if{html}{\out{</div>}}}}

Это сложно, потому что у нас есть вложенные секции: Usage и Arguments находятся внутри Method. Мы собираемся использовать полный арсенал парсинга для этого.

Чтобы сделать это немного проще, первое, что мы сделаем, – это “токенизировать” подраздел Method. Это модное слово для разделения текста на соответствующие строки. Например, рассмотрим этот текст LaTeX:

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

Его можно разбить на список строк:

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

Список токенизированных строк дает вам возможность легко обработать его на подстроки. Кроме того, вы можете легко «подглядывать» за одним или несколькими токенами, чтобы увидеть, что будет следующим. Это может быть сложно сделать с помощью регулярных выражений или если вы работаете с отдельными символами, а не с токенами. Вот пример разбора токенизированного раздела:

doc = Documentation ()...method = Method ()position = 0preamble = ""tokens = tokenize (text)while position < len (tokens): token = tokens [position] if token == "\\": если tokens [position + 1] == "подраздел": в предисловие входит = False если tokens [position + 3] == "применение": position, использование = get_tokenized_section (position + 5, tokens) format метода (использование) elif tokens [position + 3] == "аргументы": # пропустим, возьмем с помощью describe position + = 5 elif tokens [position + 3] == "Примеры": position, примеры = get_tokenized_section (position + 5, tokens) метод set_examples (примеры) elif tokens [position + 3] == "Возвращает": position, возвращает = get_tokenized_section (position + 5, tokens) format метода (возвращает) else: raise Exception ("неизвестный подраздел:", tokens [позиция + 3]) elif tokens [position + 1] == "описать": position, описать = get_tokenized_section (position + 2, tokens) # noqa метод set_describe (описать) else: # \ html position + = 1 else: если в предисловии: preamble + = token позиция + = 1format метода (прекамбула)doc.add_method (method)  

Вот и все! Чтобы увидеть готовый проект, проверьте новую практику, основанную на Python rd2md . Это устанавливаемый с помощью pip , открытая исходная библиотека Python для создания разметки из файлов Rd R. Мы использовали его в нашей собственной документации R здесь:

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

Это немного некогда. Это состоит не меньше, чем из 4 разных методов разбора. Но он выполняет свою работу и является единственным работающим конвертером Rd в разметку markdown, который я знаю. Если бы я переделывал это, я бы, вероятно, токенизировал весь файл сначала, а затем обработал его, используя последний показанный выше метод. Помните правило № 3: Не бойтесь выкидывать код и начать заново!

Если вы хотите внести вклад в репозиторий github, пожалуйста, делайте это. Если у вас есть вопросы, пожалуйста, дайте нам знать в вопросы.

Интересуетесь искусственным интеллектом, машинным обучением и наукой о данных? Подумайте о том, чтобы похлопать и подписаться. Даг является руководителем исследований в comet.com, компанией по отслеживанию экспериментов и мониторингу моделей МЛ.