Обслуживание моделей-трансформеров в Shiny Apps

Пошаговое руководство по объединению лучшего из R и Python для создания потрясающих продуктов НЛП.

В течение долгого времени R был моим основным инструментом для решения большинства задач, связанных с наукой о данных. Мне особенно нравится, как легко я могу делать вещи быстро и свободно. В частности, появление tidyverse действительно изменило правила обработки данных, исследовательского анализа и визуализации данных. Более того, Shiny — фреймворк для создания универсальных и красивых веб-приложений — становится все более популярным.

Однако, когда дело доходит до машинного и глубокого обучения, Python, кажется, на несколько шагов впереди благодаря таким платформам ML/DL, как Sci-kit Learn, PyTorch и Tensorflow/Keras. Следовательно, я все больше и больше использую (и люблю) Python.

Меня разочаровывало то, что я часто хотел развернуть и представить компьютерное зрение и модели НЛП в приложениях Shiny. Несмотря на то, что Shiny недавно стал доступен для Python, Shiny для Python в настоящее время находится на очень ранней стадии разработки. Подобные инструменты действительно доступны для пользователей Python, например. Streamlit и Gradio отлично подходят для демонстрации моделей машинного обучения, но я считаю, что они несколько ограничены по сравнению с Shiny, особенно когда речь идет о создании пользовательских интерфейсов. Конечно, для Python есть еще варианты, которые мне нужно изучить, но мне очень нравится разрабатывать приложения Shiny.

Поэтому я решил научиться делать следующее:

  1. Используйте пакет reticulate R, чтобы использовать код Python в приложении Shiny.
  2. Реализуйте предварительно обученную модель преобразователя для обработки пользовательского ввода.
  3. Контейнеризируйте приложения, содержащие функциональные возможности R и Python, а также обслуживающие модель преобразования.

В этой статье мы пройдем все три шага, чтобы увидеть, как это делается.

Часть 1. Работа с reticulate: минимальный пример

Я должен признать, что начать работу с reticulate и преодолеть разрыв между R и Python было довольно сложно и потребовало проб, ошибок и упорства. Хорошей новостью является то, что теперь вам, возможно, не придется. Здесь я покажу один из способов использования кода Python в R и Shiny.

Шаг 1. Настройка

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

Далее нам нужно настроить среду Python. Существуют разные способы сделать это, например. с помощью virtualenv согласно сетчатой ​​документации. Здесь мы будем использовать conda для настройки нашей среды из YAML-файла, в котором мы указываем все необходимые зависимости. Создайте новый файл с именем environment.yml в домашнем каталоге вашего проекта со следующим содержимым:

name: my_env
channels:
  - conda-forge
  - defaults
dependencies:
  - python=3.8
  - pip
  - pip:
    - numpy

Как видите, мы указали, что среда должна называться my_env, в которой будет установлен Python v. 3.8 вместе с pip — установщиком, который, в свою очередь, получает пакет numpy, который нам понадобится для простой функции, которую мы создам.

Затем мы можем создать среду, открыв терминал в нашем сеансе RStudio и выполнив следующую команду:

conda env create -f environment.yml

Вы можете проверить, что среда была создана с помощью команды conda env list. Чтобы активировать среду, выполните следующее:

conda activate my_env

Теперь, чтобы reticulate нашел версию Python, которую мы только что установили, скопируйте и сохраните вывод commandwhich python на потом. Он должен заканчиваться чем-то вроде «../miniconda3/envs/my_env/bin/python».

Шаг 2: Соединение Shiny с reticulate и Python

Если вы инициализировали свой проект как веб-приложение Shiny, у вас уже должен быть файл с именем app.r. Для этого минимального примера мы упростим его и изменим приложение, которое уже сделано для нас. В верхней части этого скрипта вставьте следующие строки:

library(shiny)
library(reticulate)
Sys.setenv(RETICULATE_PYTHON="python-path-in-my_env")
reticulate::use_condaenv("my_env")

Вместо этого вы также можете установить RETICULATE_PYTHON в файле с именем .Rprofile.

Шаг 3: Создание и реализация функций Python

Теперь мы можем создать простую функцию Python, которую будем использовать в серверном коде app.r. Начните с создания скрипта, который вы можете вызвать python_functions.py со следующими строками кода:

import numpy as np
def make_bins(x, length):
  
  low = min(x)
  high = max(x)
  length = int(length)
  return np.linspace(low,high,length).tolist()

Эта функция находит самые низкие и самые высокие значения вектора и использует функцию Numpy linspace() для возврата списка равноотстоящих чисел в диапазоне от самого низкого до самого высокого. Длина списка равна length и будет устанавливаться интерактивно пользователями приложения. Определив функцию, мы можем перейти к app.r и изменить скрипт, чтобы использовать нашу функцию python.

Прямо под двумя строками, которые мы вставили в app.r на предыдущем шаге, мы добавляем следующую строку:

reticulate::py_run_file("python_functions.py")

Эта строка делает нашу функцию make_bins() доступной всякий раз, когда мы запускаем сеанс Shiny. Теперь удалите или закомментируйте следующую строку в коде сервера:

bins <- seq(min(x), max(x), length.out = input$bins + 1)

Затем мы заменяем эту строку на следующую:

bins <- py$make_bins(x, input$bins + 1)

Обратите внимание на часть py$, которая сигнализирует о том, что функция является функцией Python. Наконец-то мы можем запустить приложение и, надеюсь, увидим, что оно работает!

Часть 2: Использование трансформаторов!

Итак, теперь мы попробуем реализовать модель преобразователя, которая определяет, является ли заданный входной текст положительным или отрицательным. Модель, которую мы будем использовать, называется distilbert-base-uncased-emotion, о которой вы можете узнать больше на сайте Huggingface. Если вы еще этого не сделали, я рекомендую вам изучить сайт, доступные модели и поддерживаемые задачи НЛП и компьютерного зрения.

Шаг 1: Обновление среды conda

Сначала нам нужно добавить пакеты факел и трансформаторы в наш файл environment.yml следующим образом:

name: my_env
channels:
  - conda-forge
  - defaults
dependencies:
  - python=3.8
  - pip
  - pip:
    - torch
    - transformers

Затем мы можем обновить среду с помощью следующих команд:

conda deactivate;
conda env update -f environment.yml --prune

—-pruneflag гарантирует удаление ненужных пакетов при обновлении среды.

Шаг 2: Обновление python_functions.py

Установив факел и трансформаторы, мы готовы написать новые функции Python, которые позволят нам использовать модель.

import torch
from transformers import pipeline
import numpy as np
def get_model():
  model = pipeline("text-classification", model='bhadresh-savani/distilbert-base-uncased-emotion', top_k=-1)
  return model
def get_predictions(input_text, classifier):
  predictions = classifier(input_text)
  return predictions

При первом запуске get_model() модель загружается, что может занять минуту или две. Это хорошая идея — запустить get_predictions() вне сеанса Shiny, чтобы получить представление о том, как выглядит результат.

Шаг 3. Создание базового приложения, предоставляющего модель

Теперь мы, наконец, можем создать приложение, использующее эту модель. Ниже я предоставил простой рабочий скрипт, который вы можете попробовать, если выполнили предыдущие шаги.

Вы заметите, что мы загружаем модель классификации эмоций в строке 10 с помощью model <- py$get_model().

Затем в строках 31–33 мы применяем модель к некоторому входному тексту, предоставленному пользователями, и преобразуем выходные данные во фрейм данных, что значительно упрощает построение графиков.

predictions <- py$get_predictions(input$text)
df <- map_df(predictions[[1]], unlist)

Иногда бывает сложно преобразовать вывод функции Python в тип данных, с которым может работать R. Если у вас возникнут проблемы в собственном проекте, вам может пригодиться сетчатая документация (см. Преобразование типов).

Ниже вы можете увидеть, как будет выглядеть приложение.

Часть 3. Контейнеризация приложения с помощью Docker

Технология Docker и контейнеров предлагает отличный способ запуска кода и приложений с полным контролем над средами. Сначала нам нужно создать Dockerfile, что часто может быть довольно сложно и требует много времени. здесь я покажу одно решение для объединения моделей Python, R, Shiny и трансформера в одном образе Docker. Возможно, он не самый эффективный, а некоторые зависимости и команды могут оказаться лишними. Таким образом, вы можете сократить как время, необходимое для создания образа, так и его размер, поработав с файлом Dockerfile, указав, как создается образ.

Шаг 1: Написание Dockerfile

Первая строка Dockerfile указывает базовый образ. По умолчанию используется последняя версия. Обычно при запуске приложения в производство рекомендуется выбирать конкретную версию. Следующие несколько строк устанавливают несколько зависимостей, включая R.

FROM continuumio/miniconda3
RUN apt-get update -y; apt-get upgrade -y; \
    apt-get install -y vim-tiny vim-athena ssh r-base-core \
    build-essential gcc gfortran g++

Обычно я предпочитаю использовать образы Docker, созданные Rocker Project, что упрощает написание файлов Dockerfile для контейнеризации приложений Shiny и других приложений на основе R. Однако при добавлении Python я столкнулся с некоторыми проблемами и решил попробовать другой способ.

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

COPY environment.yml environment.yml
RUN conda env create -f environment.yml
RUN echo "conda activate my_env" >> ~/.bashrc
ENV CONDA_EXE /opt/conda/bin/conda
ENV CONDA_PREFIX /opt/conda/envs/my_env
ENV CONDA_PYTHON_EXE /opt/conda/bin/python
ENV CONDA_PROMPT_MODIFIER (my_env)
ENV CONDA_DEFAULT_ENV my_env
ENV PATH /opt/conda/envs/my_env/bin:/opt/conda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Затем мы загружаем нашу модель следующей строкой:

RUN python -c "from transformers import pipeline; pipeline('text-classification', model='bhadresh-savani/distilbert-base-uncased-emotion')"

Важно сделать это во время сборки, а не во время выполнения. Если бы мы делали это во время выполнения, каждый сеанс начинался бы с загрузки модели!

Существуют разные способы установки пакетов R. Этот способ довольно прост.

RUN R -e "install.packages(c('dplyr','purrr','ggplot2','shiny','reticulate'), repos = 'http://cran.us.r-project.org')"

Обратите внимание, что на самом деле это одна строка. Вы также можете видеть, что мы устанавливаем dplyr, purrr и ggplot2 — пакеты tidyverse, которые нам действительно нужны. Следовательно, нам нужно загрузить эти конкретные пакеты и удалить library(tidyverse) из app.r.

По какой-то причине я не смог установить всю tidyverse в это время. Кроме того, это займет значительно больше времени и приведет к тому, что изображение будет больше, чем необходимо.

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

COPY . ./
RUN chmod ugo+rwx ./
EXPOSE 3838
CMD ["R", "-e", "shiny::runApp('/', host = '0.0.0.0', port = 3838)"]

Ниже вы можете увидеть весь Dockerfile.

Шаг 2: Создание образа

Если мы просто назовем наш файл Dockerfile «Dockerfile», Docker по умолчанию будет искать этот файл, когда мы выполним следующую команду:

docker build -t mysimpleapp .

-t для «тега», и мы пометим это изображение «mysimpleapp». Точка в конце указывает, что контекстом сборки является текущий каталог.

Если у вас возникли проблемы из-за нехватки места на диске, вы можете увеличить разрешенное место на диске в настройках Docker или, если у вас есть большие висящие образы, которые вам не нужны, вы можете запустить docker system prune или docker system prune -a. Имейте в виду, что последняя команда удалит все неиспользуемые изображения!

Наконец, скрестив пальцы, мы можем попробовать запустить наше приложение!

docker run -it -p 3838:3838 mysimpleapp

Флаг -it означает, что мы хотим работать в интерактивном режиме, чтобы мы могли видеть, что происходит «внутри» нашего контейнера при его запуске. Это может быть полезно, если что-то неожиданно пойдет не так.

В вашей консоли вы должны увидеть запуск R, а затем Прослушивание «http://0.0.0.0:3838». Укажите в браузере этот адрес и убедитесь, что приложение работает.

Я развернул чуть более продвинутое приложение здесь. Это приложение, которое я назвал Wine Finder, использует модель семантического поиска под названием all-MiniLM-L6-v2, которая позволяет пользователям искать вина, которые им могут понравиться, вводя запросы, описывающие качества, которые они ищут в вине. Например, запрос может быть сформулирован как "насыщенный с нотками красных ягод". Приложение содержит описания примерно 130 000 вин, которые затем ранжируются по релевантности запросу. Набор данных доступен здесь. Ниже вы можете увидеть, как выглядит приложение.

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

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

Мы увидели, как реализовать функции Python и модели преобразования в приложениях Shiny и как упаковать все это в образ докера, готовый к развертыванию в качестве веб-службы. Относительно легко развернуть образы Docker в качестве веб-приложений с помощью облачных провайдеров, таких как AWS и Microsoft Azure. Однако для личных проектов я думаю, что Google Cloud — самый дешевый вариант на данный момент. Если вы хотите развернуть, например. блестящее приложение в Google Cloud, обязательно ознакомьтесь с моим пошаговым руководством по использованию Google Cloud Run для развертывания блестящих приложений. Независимо от того, каким из этих провайдеров вы пользуетесь, процесс примерно одинаков. Вам нужно будет иметь учетную запись, отправить образ докера в реестр контейнеров, а затем настроить веб-службу, используя образ.

Мы не рассмотрели развертывание приложений Shiny, которые запускают код Python непосредственно на Shinyapp.io. Процедура немного отличается от описанной в этом уроке. Имейте в виду, что если вы планируете отображать модели-трансформеры в своем приложении, Shinyapps.io может оказаться неподходящим вариантом, по крайней мере, если вы находитесь на бесплатном уровне. Однако, если вам не нужно, чтобы приложение фактически содержало большую модель преобразователя и/или много данных, вы можете рассмотреть возможность простого вызова Huggingface Inference API для данной модели.