# Docker для Data Scientist'a

## Введение
Часто у начинающих Data Scientists возникает вопрос, как демонстрировать работу своих моделей другим людям. Банальный пример - прикрепить ссылку на гитхаб репозиторий в отклике на вакансию или показать свое "детище" знакомым со словами "смотрите, что умею".
Проще говоря, мы хотим задеплоить нашу модель, превратить ее в демо нашего исследования.
Проблема в том, что для этого нужно скачивать репозиторий, установливать нужную версию python и всех необходимых библиотек, а также разбираться, как запускать приложение. Слишком много сложностей для человека, который хочет использовать или просто посмотреть вашу работу. То есть вопрос в том, как передать продукт клиенту.
В статье я расскажу простыми словами, что такое Docker и как его можно использовать для реализации своих решений в Machine Learning.
## Что такое контейнеризация?

Одна из болей Data Scientist'a — клиенты, которые "запутались в установке", у которых "непонятно написано" или "все сломалось". Контейнеризация позволяет решить эту проблему.
Контейнер — отдельная операционная система, настроенная заранее. Она выполняет указания, которые даются при сборке образа этой системы. Контейнер использует процессорные ядра и оперативную память хост-машины.
Другими словами, внутри нашей операционки мы запускаем другую с заранее подготовленной последовательностью действий.
## Docker

Прежде чем начинать работу, необходимо установить [docker](https://hub.docker.com/) на компьютер или сервер.
#### Создание образа
Сначала нужно создать образ сервера, который называется `docker image`. Это можно сделать несколькими способами, но самый простой и понятный - создать файл с названием `Dockerfile`. Это будет скрипт, содержащий последовательность действий для сборки образа.
В Docker образы могут наследоваться друг от друга, поэтому обычно в первой строчке за основу берут готовый образ:
```
FROM python:3.8-slim-buster
```
Готовые образы вы можете найти на [docker hub](https://hub.docker.com/search?q=&type=image)
Теперь следует описать логику инициализации контейнера.
Для этого существует много директив, например `RUN` - для запуска какой-либо команды на сервере при сборке образа.
Ниже я опишу директивы, которые сам использовал для создания своего первого Docker-образа:
```
RUN mkdir /app
```
Команда, которая создает папку `app` внутри контейнера.
```
COPY . /app/
WORKDIR /app
```
Следующей командой мы копируем все файлы из локального диска (директории, где находится Dockerfile) в папку `app`. Затем мы объявляем `app` рабочей директорией.
```
RUN pip install -r requirements.txt
```
Я снова запускаю команду `RUN` для того, чтобы установить все зависимости внутри контейнера.
Еще раз уточню, что все пакеты с зависимостями находятся внутри контейнера и никак не пересекаются с версиями библиотек на локальном компьютере. То есть внутри контейнера своя виртуальная среда.
```
ENTRYPOINT ["python"]
CMD ["demo.py"]
```
Команда `ENTRYPOINT` позволяет объявить точку входа для сервера. Таким образом, при развертке сервера я запускаю Python в контейнере.
Директива `CMD` говорит, что в командной строке/терминале я выполняю код внутри скобок.
Мы можем объединить две эти команды в одну:
```
CMD ["python", "demo.py"]
```
#### Создание контейнера
После того, как вы сохранили данный файл, можно собирать образ. Делается это очень просто: достаточно прописать в командной строке
```
docker build . -t simp_server
```
Точка означает, что мы хотим собрать образ из Dockerfile, который находится в данной директории.
* `-t` - тег для обозначения имени сервера
* `simp_server` - любое имя сервера.
Итак, образ собран.
Для того, чтобы увидеть все образы на хост-машине, пропишем
```
docker images
```
Запускаем сервер следующей командой:
```
docker run --rm -it -p 8888:8888 simp_server
```
* `run` создает контейнер.
* `--rm` удаляет контейнер после завершения его работы.
* `-p 8888:8888` пробрасывает порты между хост-машиной и контейнером (порт на хост соответствует порту в контейнере).
* `simp_server` определяет образ запускаемого сервера.
## Пример деплоя модели c использованием Docker и flask
[github репозиторий проекта](https://github.com/pavelkochkin1/simpsons-classification)
Суть проекта в том, что пользователь загружает картинку с персонажем из "Симпсонов" и получает предположение сети о том, кто на ней изображен.

Сначала я описываю логику приложения, используя библиотеку `flask` в файле `demo.py`. В нем находится один метод, который запрашивает у пользователя картинку и возвращает предсказание.
Это тот код, который я прошу запустить docker сервер на старте.
```
import os
from flask import Flask
from flask import request
from flask import render_template
from model.model import MobNetSimpsons
app = Flask(__name__)
UPLOAD_FOLDER = "static/"
@app.route('/', methods=['GET', 'POST'])
def upload_predict():
if request.method == "POST":
image_file = request.files["image"]
image_location = os.path.join(
UPLOAD_FOLDER,
image_file.filename
)
if image_file:
image_file.save(image_location)
pred = model.predict(image_location)
return render_template(
"index.html",
prediction=pred[0],
proba=round(pred[1], 2),
image_loc=image_file.filename
)
return render_template("index.html", prediction=0, image_loc=None)
if __name__ == "__main__":
model = MobNetSimpsons()
app.run(port=8888, debug=True, host='0.0.0.0')
```
Также, мне понадобится класс, в котором инициализируется модель и объявляется метод для получения предсказания:
```
import pickle
import numpy as np
from torchvision import models
from torch import nn
from model.utils import *
DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
class MobNetSimpsons():
def __init__(self):
print("Loading model...")
self.model = models.mobilenet_v3_large(pretrained=True)
num_features = 960
n_classes = 42
self.model.classifier = nn.Sequential(
nn.Linear(num_features, 1280, bias=True),
nn.Hardswish(),
nn.Dropout(p=0.2, inplace=True),
nn.Linear(1280, n_classes, bias=True)
)
print("Seting parameters...")
self.model.load_state_dict(torch.load('model/mobNetLarge.pth', map_location=DEVICE))
print("Seting on evaluation mode...")
self.model.eval()
self.label_encoder = pickle.loads(open('model/label_encoder.pkl', 'rb').read())
print("Model is ready!")
def predict(self, image_path):
img = prepare_img(image_path)
proba = predict_one_sample(self.model, img, device=DEVICE)
predicted_proba = np.max(proba)*100
y_pred = np.argmax(proba)
label = self.label_encoder.inverse_transform([y_pred])[0].split('_')
label = label_to_string(label)
return [label, predicted_proba]
```
Затем создаю `Dockerfile` и собираю образ:
```
docker build . -t simp_server
```
Далее создаю контейнер:
```
docker run --rm -it -p 8888:8888 simp_server
```
После этого перехожу по адресу `http://localhost:8888/`.
Если контейнер и приложение работают на вашем компьютере, то они будут работать и на другом компьютере, например, у вашего клиента.
Остается дать порядок команд для Docker'a.
Для моего приложения порядок установки выглядит так:
1. Install and run Docker
2. Build Docker image using `docker build . -t simp_server`
3. Run Docker container using `docker run --rm -it -p 8888:8888 simp_server`
4. Go to `http://localhost:8888/`
## Магия
Если вашей моделью захотят воспользоваться, для этого нужно будет:
1. Установить Docker
1. Загрузить репозиторий
2. Собрать образ (docker image)
3. Создать и запустить контейнер
## Что дальше?
В статье я расскрыл лишь одну проблему, которую может решить Docker, - доставка приложения до клиента.
Вы можете попробовать задеплоить свою модель, даже если она просто классифицирует ирис.

Таким образом, вы лучше поймете, как работать с Docker, и в дальнейшем сможете применять его в своих проектах.
## Полезные ссылки
— [Docker Documentation](https://docs.docker.com/)
— [Flask Documentation](https://flask.palletsprojects.com/en/2.0.x/)
— [Подробнее про Docker](https://habr.com/ru/post/253877/)
— [FastAPI Documentation](https://fastapi.tiangolo.com/) - бэкенд. Преимущество в том, что фрондэндер может легко получить все доступные методы через `/docs`
Ключевые слова: machinelearning docker deep learning машинное обучение контейнеризация продукт
Хабы: Машинное обучение Искусственный интеллект Big Data