Сравнить коммиты
25 Коммитов
105e06f7b4
...
00e0ce4b78
| Автор | SHA1 | Дата | |
|---|---|---|---|
|
00e0ce4b78
|
|||
|
25444818cc
|
|||
|
4665a66473
|
|||
|
59897fbe61
|
|||
|
bb1796e081
|
|||
|
d39d8f98d6
|
|||
|
a7a0780f1a
|
|||
|
c16caf2e6a
|
|||
|
22ef4d303c
|
|||
|
543a4c6571
|
|||
|
ce02a6966b
|
|||
|
8c3e3c1588
|
|||
|
2831ff4e81
|
|||
|
6038a1c566
|
|||
|
070688dc68
|
|||
|
2b2241b2ab
|
|||
|
3ee22c3f0e
|
|||
|
af9340eda2
|
|||
|
a3e8ebc030
|
|||
|
2f1b884d4b
|
|||
|
b66aed2636
|
|||
|
462ab85b18
|
|||
|
41497aa039
|
|||
|
f6714c0918
|
|||
|
0d66f73f7f
|
10
README.md
10
README.md
@@ -51,16 +51,20 @@
|
|||||||
|
|
||||||
#### Общие зависимости
|
#### Общие зависимости
|
||||||
|
|
||||||
Зависимости — пакеты Python — записаны в файле `requirements.txt` (см. **Пакеты Python**).
|
Зависимости — пакеты Python — записаны в файле `requirements/requirements.txt` (см. **Пакеты Python**).
|
||||||
|
|
||||||
#### Пакеты Python
|
#### Пакеты Python
|
||||||
|
|
||||||
Установка/обновление пакетов Python в активное окружение из файла `requirements.txt`:
|
Установка/обновление пакетов Python в активное окружение из файла `requirements/requirements.txt`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pip install -U -r requirements.txt
|
pip install -U -r requirements/requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
## Разведочный анализ данных (EDA)
|
## Разведочный анализ данных (EDA)
|
||||||
|
|
||||||
См. `eda/README.md`.
|
См. `eda/README.md`.
|
||||||
|
|
||||||
|
## Исследование и настройка предсказательной модели
|
||||||
|
|
||||||
|
См. `research/README.md`.
|
||||||
|
|||||||
2
_mlflow_config_common.ps1
Обычный файл
2
_mlflow_config_common.ps1
Обычный файл
@@ -0,0 +1,2 @@
|
|||||||
|
$BACKEND_STORE_DB_PATH = "./mlflow/mlruns.sqlite"
|
||||||
|
$BACKEND_URI = "sqlite:///$BACKEND_STORE_DB_PATH"
|
||||||
4
_mlflow_config_common.sh
Обычный файл
4
_mlflow_config_common.sh
Обычный файл
@@ -0,0 +1,4 @@
|
|||||||
|
set -eu
|
||||||
|
|
||||||
|
BACKEND_STORE_DB_PATH="${BACKEND_STORE_DB_PATH:-./mlflow/mlruns.sqlite}"
|
||||||
|
BACKEND_URI="sqlite:///$BACKEND_STORE_DB_PATH"
|
||||||
143
docs/jupyter.md
Обычный файл
143
docs/jupyter.md
Обычный файл
@@ -0,0 +1,143 @@
|
|||||||
|
# Использование среды Jupyter
|
||||||
|
|
||||||
|
Для исследовательских задач в проекте используется среда [Jupyter](https://jupyter.org/). Т.к. блокноты хранятся в текстовом формате под контролем версий, нужно также дополнение [Jupytext](https://jupytext.readthedocs.io/en/latest/) (как минимум для ручной конвертации блокнотов; см. ниже).
|
||||||
|
|
||||||
|
Опционально можно использовать дополнение [papermill](https://papermill.readthedocs.io/en/latest/) для простого параметризованного исполнения блокнотов.
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
### Общий порядок
|
||||||
|
|
||||||
|
**Внимание**: Оптимальный порядок установки и конфигурации Jupyter для работы с проектом неоднозначен. См. обоснование выбранного здесь порядка работы с блокнотами Jupyter и возможные альтернативные варианты в статье [Использование Jupyter с виртуальными окружениями Python](https://asrelo.hashnode.dev/using-jupyter-with-python-virtual-environments-ru).
|
||||||
|
|
||||||
|
1. Jupyter и дополнения должны быть установлены в систему, а **не** в виртуальное окружение. При необходимости деактивируйте виртуальное окружение.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
deactivate
|
||||||
|
```
|
||||||
|
|
||||||
|
2. [Установите Jupyter](https://jupyter.org/install) и Jupytext в систему (**не** в виртуальное окружение).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pip install -U notebook jupytext
|
||||||
|
```
|
||||||
|
|
||||||
|
Полная инструкция по установке Jupytext: [Installation — Jupytext documentation](https://jupytext.readthedocs.io/en/latest/install.html).
|
||||||
|
|
||||||
|
3. **Опционально**, установите papermill в систему (**не** в виртуальное окружение).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pip install -U papermill
|
||||||
|
```
|
||||||
|
|
||||||
|
Полная инструкция по установке: [Installation - papermill 2.4.0 documentation](https://papermill.readthedocs.io/en/stable/installation.html).
|
||||||
|
|
||||||
|
4. Активируйте **виртуальное окружение** повторно.
|
||||||
|
|
||||||
|
5. Установите ядро Jupyter, связанное с данным виртуальным окружением, в директорию этого виртуального окружения. Укажите следующее имя ядра: `python3_venv`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python -m ipykernel --sys-prefix --name python3_venv
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Опционально**, **заранее** сохраните в переменную окружения `JUPYTER_PATH` путь к данным Jupyter в виртуальном окружении `<path>` — см. п. 1 в инструкции по использованию.
|
||||||
|
|
||||||
|
* Windows (PowerShell):
|
||||||
|
|
||||||
|
```ps
|
||||||
|
[System.Environment]::SetEnvironmentVariable('JUPYTER_PATH', "<path>;$env:JUPYTER_PATH", 'User')
|
||||||
|
```
|
||||||
|
|
||||||
|
* Windows (cmd):
|
||||||
|
|
||||||
|
```bat
|
||||||
|
setx JUPYTER_PATH "<path>;%PATH%;JUPYTER_PATH"
|
||||||
|
```
|
||||||
|
|
||||||
|
* UNIX (sh):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
echo 'export JUPYTER_PATH="<path>:$JUPYTER_PATH"' >> ~/.profile
|
||||||
|
```
|
||||||
|
|
||||||
|
**Внимание**: На данном этапе могут отсутствовать пригодные для прямого использования блокноты `.ipynb` (например, если проект развёртывается с нуля). Об использовании спаренных блокнотов и конвертации форматов см. [Использование Jupytext](#использование-jupytext).
|
||||||
|
|
||||||
|
### Зависимости
|
||||||
|
|
||||||
|
*Используемые при работе с Jupyter зависимости — пакеты Python — на данный момент включены в общие зависимости (см. выше), дополнительных действий не требуется.*
|
||||||
|
|
||||||
|
## Работа с блокнотами Jupyter
|
||||||
|
|
||||||
|
### Jupyter
|
||||||
|
|
||||||
|
1. **Если** при выполнении инструкции по установке Вы **не** сохранили в переменную окружения JUPYTER_PATH путь к данным Jupyter в виртуальном окружении, этот путь нужно добавить в переменную окружения сейчас.
|
||||||
|
|
||||||
|
Добавьте в переменную окружения `JUPYTER_PATH` абсолютный путь (далее обозначаемый `<path>`) `$VIRTUAL_ENV/share/jupyter`, где следует заменить `$VIRTUAL_ENV` на путь к директории, где развёрнуто виртуальное окружение. Для инструментов [`venv`](https://docs.python.org/3/library/venv.html), [`virtualenv`](https://virtualenv.pypa.io/en/stable/) можно просто в активном виртуальном окружении использовать подстановку переменной окружения `VIRTUAL_ENV` (активное виртуальное окружение не повлияет на дальнейшие шаги).
|
||||||
|
|
||||||
|
* Windows (PowerShell):
|
||||||
|
|
||||||
|
```ps
|
||||||
|
$env:JUPYTER_PATH = "<path>;$env:JUPYTER_PATH"
|
||||||
|
```
|
||||||
|
|
||||||
|
* Windows (cmd):
|
||||||
|
|
||||||
|
```bat
|
||||||
|
set "JUPYTER_PATH=<path>;%JUPYTER_PATH%"
|
||||||
|
```
|
||||||
|
|
||||||
|
* UNIX (sh):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export JUPYTER_PATH="<path>:$JUPYTER_PATH"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Запустите глобальный установленное приложение Jupyter (**не** из виртуального окружения).
|
||||||
|
|
||||||
|
* Например, запустите Jupyter Notebook:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
jupyter notebook
|
||||||
|
```
|
||||||
|
|
||||||
|
Веб-приложение Notebook должно открыться в веб-браузере автоматически. Если этого не произошло, найдите в сообщениях сервера Jupyter строку примерно следующего содержания:
|
||||||
|
|
||||||
|
[I 08:58:24.417 NotebookApp] The Jupyter Notebook is running at: http://localhost:8888/
|
||||||
|
|
||||||
|
Откройте веб-браузер и перейдите по ссылке, выведенной в конце указанного сообщения.
|
||||||
|
|
||||||
|
См. также [документацию Jupyter](https://docs.jupyter.org/en/stable/running.html).
|
||||||
|
|
||||||
|
2. Используйте приложение для навигации по файловой системе (в частности, по каталогу `eda/`), редактирования и исполнения кода в блокнотах.
|
||||||
|
|
||||||
|
3. Если приложение Jupyter запрашивает **выбор ядра** Jupyter (**kernel**) или Вы сталкиваетесь с необъяснимыми **ошибками импортов**, выберите для текущего блокнота ядро с именем `python3_venv`.
|
||||||
|
|
||||||
|
* **Jupyter Notebook**: Может понадобиться выбор вручную; кнопка для выбора ядра для открытого блокнота находится в верхнем правом углу веб-страницы.
|
||||||
|
|
||||||
|
### Расширение Jupyter для Visual Studio Code
|
||||||
|
|
||||||
|
1. Запустите Visual Studio Code.
|
||||||
|
|
||||||
|
2. Откройте корневую директорию проекта в VS Code (*File* -> *Open Folder...*).
|
||||||
|
|
||||||
|
3. Если Вы открыли директорию проекта и VS Code запрашивает выбор автоматически обнаруженного виртуального окружения, согласитесь.
|
||||||
|
|
||||||
|
3. **Если** VS Code запрашивает выбор автоматически обнаруженного виртуального окружения, согласитесь.
|
||||||
|
|
||||||
|
**Иначе** [укажите](https://code.visualstudio.com/docs/python/environments#_working-with-python-interpreters) своё виртуальное окружение самостоятельно.
|
||||||
|
|
||||||
|
4. Используйте VS Code с расширением Jupyter для навигации по файловой системе (в частности, по каталогу `eda/`), редактирования и исполнения кода в блокнотах. **Не забывайте** при открытии любого блокнота проверять, что выбрано корректное ядро Jupyter (принадлежащее корректному виртуальному окружению). (Кнопка для выбора ядра для открытого блокнота находится в верхнем правом углу области содержимого вкладки; по умолчанию Вы увидите название выбранного виртуального окружения; если ядро не выбрано, на кнопке написано *Select Kernel*.)
|
||||||
|
|
||||||
|
### Использование Jupytext
|
||||||
|
|
||||||
|
Описанные ниже команды `jupytext` используют глобальной установленный экземпляр Jupytext (однако его можно запускать и изнутри виртуального окружения).
|
||||||
|
|
||||||
|
Для автоматической синхронизации связанных блокнотов (включая создание блокнотов отсутствующих, но ожидаемых форматов):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
jupytext --sync eda/cars_eda.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Jupytext довольно удобно работает в оригинальной среде Jupyter, синхронизируя изменения связанных файлов на лету при работе в Jupyter, **ориентируясь на метки времени на файлах**. См. документацию [Jupytext](https://jupytext.readthedocs.io/en/latest/index.html).
|
||||||
|
|
||||||
|
**Внимание**: С расширением Jupyter для Visual Studio Code Jupytext **не работает напрямую**. Для использования блокнотов `.ipynb` с расширением Jupyter для VS Code нужно синхронизировать текстовый файл под контролем версий и файл `.ipynb` вручную указанными выше командами. Однако заметьте, что это же расширение может исполнять блокнот в текстовом формате самостоятельно, посредством автоматизированного ведения временного блокнота; и оно даже автоматически создаёт/подхватывает локальное ядро Jupyter в виртуальном окружении.
|
||||||
138
eda/README.md
138
eda/README.md
@@ -32,144 +32,12 @@
|
|||||||
|
|
||||||
Для EDA необходимы общие зависимости, см. [Общие зависимости](../README.md#общие-зависимости) в `README.md`.
|
Для EDA необходимы общие зависимости, см. [Общие зависимости](../README.md#общие-зависимости) в `README.md`.
|
||||||
|
|
||||||
Для EDA используется среда [Jupyter](https://jupyter.org/). Т.к. блокноты хранятся в текстовом формате под контролем версий, нужно также дополнение [Jupytext](https://jupytext.readthedocs.io/en/latest/) (как минимум для ручной конвертации блокнотов; см. ниже).
|
Для EDA используется среда [Jupyter](https://jupyter.org/). См. об установке и использовании Jupyter в проекте в `docs/jupyter.md`.
|
||||||
|
|
||||||
Опционально можно использовать дополнение [papermill](https://papermill.readthedocs.io/en/latest/) для простого параметризованного исполнения блокнотов.
|
|
||||||
|
|
||||||
### Общий порядок
|
|
||||||
|
|
||||||
**Внимание**: Оптимальный порядок установки и конфигурации Jupyter для работы с проектом неоднозначен. См. обоснование выбранного здесь порядка работы с блокнотами Jupyter и возможные альтернативные варианты в статье [Использование Jupyter с виртуальными окружениями Python](https://asrelo.hashnode.dev/using-jupyter-with-python-virtual-environments-ru).
|
|
||||||
|
|
||||||
1. Выполните установку общих зависимостей, если это ещё не выполнено, см. **Общие зависимости** в `README.md`.
|
|
||||||
|
|
||||||
2. Jupyter и дополнения должны быть установлены в систему, а **не** в виртуальное окружение. При необходимости деактивируйте виртуальное окружение.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
deactivate
|
|
||||||
```
|
|
||||||
|
|
||||||
3. [Установите Jupyter](https://jupyter.org/install) и Jupytext в систему (**не** в виртуальное окружение).
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pip install -U notebook jupytext
|
|
||||||
```
|
|
||||||
|
|
||||||
Полная инструкция по установке Jupytext: [Installation — Jupytext documentation](https://jupytext.readthedocs.io/en/latest/install.html).
|
|
||||||
|
|
||||||
4. **Опционально**, установите papermill в систему (**не** в виртуальное окружение).
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pip install -U papermill
|
|
||||||
```
|
|
||||||
|
|
||||||
Полная инструкция по установке: [Installation - papermill 2.4.0 documentation](https://papermill.readthedocs.io/en/stable/installation.html).
|
|
||||||
|
|
||||||
5. Активируйте **виртуальное окружение** повторно.
|
|
||||||
|
|
||||||
6. Установите ядро Jupyter, связанное с данным виртуальным окружением, в директорию этого виртуального окружения. Укажите следующее имя ядра: `python3_venv`.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
python -m ipykernel --sys-prefix --name python3_venv
|
|
||||||
```
|
|
||||||
|
|
||||||
7. **Опционально**, **заранее** сохраните в переменную окружения `JUPYTER_PATH` путь к данным Jupyter в виртуальном окружении `<path>` — см. п. 1 в инструкции по использованию.
|
|
||||||
|
|
||||||
* Windows (PowerShell):
|
|
||||||
|
|
||||||
```ps
|
|
||||||
[System.Environment]::SetEnvironmentVariable('JUPYTER_PATH', "<path>;$env:JUPYTER_PATH", 'User')
|
|
||||||
```
|
|
||||||
|
|
||||||
* Windows (cmd):
|
|
||||||
|
|
||||||
```bat
|
|
||||||
setx JUPYTER_PATH "<path>;%PATH%;JUPYTER_PATH"
|
|
||||||
```
|
|
||||||
|
|
||||||
* UNIX (sh):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
echo 'export JUPYTER_PATH="<path>:$JUPYTER_PATH"' >> ~/.profile
|
|
||||||
```
|
|
||||||
|
|
||||||
**Внимание**: На данном этапе могут отсутствовать пригодные для прямого использования блокноты `.ipynb` (например, если проект развёртывается с нуля). Об использовании спаренных блокнотов и конвертации форматов см. [Использование Jupytext](#использование-jupytext).
|
|
||||||
|
|
||||||
### Зависимости
|
### Зависимости
|
||||||
|
|
||||||
Используемые непосредственно кодом проекта зависимости для разведочного анализа данных (EDA) (директория `eda/`) — пакеты Python — на данный момент включены в общие зависимости (см. выше).
|
Дополнительные зависимости, необходимые для EDA, — пакеты Python — записаны в файле `requirements/requirements-eda.txt` (см. **Пакеты Python**). См. об установке пакетов Python в **Пакеты Python** в `README.md`.
|
||||||
|
|
||||||
## Работа с блокнотами Jupyter
|
## Работа с блокнотами Jupyter
|
||||||
|
|
||||||
### Jupyter
|
См. об установке и использовании Jupyter в проекте в `docs/jupyter.md`.
|
||||||
|
|
||||||
1. **Если** при выполнении инструкции по установке Вы **не** сохранили в переменную окружения JUPYTER_PATH путь к данным Jupyter в виртуальном окружении, этот путь нужно добавить в переменную окружения сейчас.
|
|
||||||
|
|
||||||
Добавьте в переменную окружения `JUPYTER_PATH` абсолютный путь (далее обозначаемый `<path>`) `$VIRTUAL_ENV/share/jupyter`, где следует заменить `$VIRTUAL_ENV` на путь к директории, где развёрнуто виртуальное окружение. Для инструментов [`venv`](https://docs.python.org/3/library/venv.html), [`virtualenv`](https://virtualenv.pypa.io/en/stable/) можно просто в активном виртуальном окружении использовать подстановку переменной окружения `VIRTUAL_ENV` (активное виртуальное окружение не повлияет на дальнейшие шаги).
|
|
||||||
|
|
||||||
* Windows (PowerShell):
|
|
||||||
|
|
||||||
```ps
|
|
||||||
$env:JUPYTER_PATH = "<path>;$env:JUPYTER_PATH"
|
|
||||||
```
|
|
||||||
|
|
||||||
* Windows (cmd):
|
|
||||||
|
|
||||||
```bat
|
|
||||||
set "JUPYTER_PATH=<path>;%JUPYTER_PATH%"
|
|
||||||
```
|
|
||||||
|
|
||||||
* UNIX (sh):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
export JUPYTER_PATH="<path>:$JUPYTER_PATH"
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Запустите глобальный установленное приложение Jupyter (**не** из виртуального окружения).
|
|
||||||
|
|
||||||
* Например, запустите Jupyter Notebook:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
jupyter notebook
|
|
||||||
```
|
|
||||||
|
|
||||||
Веб-приложение Notebook должно открыться в веб-браузере автоматически. Если этого не произошло, найдите в сообщениях сервера Jupyter строку примерно следующего содержания:
|
|
||||||
|
|
||||||
[I 08:58:24.417 NotebookApp] The Jupyter Notebook is running at: http://localhost:8888/
|
|
||||||
|
|
||||||
Откройте веб-браузер и перейдите по ссылке, выведенной в конце указанного сообщения.
|
|
||||||
|
|
||||||
См. также [документацию Jupyter](https://docs.jupyter.org/en/stable/running.html).
|
|
||||||
|
|
||||||
2. Используйте приложение для навигации по файловой системе (в частности, по каталогу `eda/`), редактирования и исполнения кода в блокнотах.
|
|
||||||
|
|
||||||
3. Если приложение Jupyter запрашивает **выбор ядра** Jupyter (**kernel**) или Вы сталкиваетесь с необъяснимыми **ошибками импортов**, выберите для текущего блокнота ядро с именем `python3_venv`.
|
|
||||||
|
|
||||||
* **Jupyter Notebook**: Может понадобиться выбор вручную; кнопка для выбора ядра для открытого блокнота находится в верхнем правом углу веб-страницы.
|
|
||||||
|
|
||||||
### Расширение Jupyter для Visual Studio Code
|
|
||||||
|
|
||||||
1. Запустите Visual Studio Code.
|
|
||||||
|
|
||||||
2. Откройте корневую директорию проекта в VS Code (*File* -> *Open Folder...*).
|
|
||||||
|
|
||||||
3. Если Вы открыли директорию проекта и VS Code запрашивает выбор автоматически обнаруженного виртуального окружения, согласитесь.
|
|
||||||
|
|
||||||
3. **Если** VS Code запрашивает выбор автоматически обнаруженного виртуального окружения, согласитесь.
|
|
||||||
|
|
||||||
**Иначе** [укажите](https://code.visualstudio.com/docs/python/environments#_working-with-python-interpreters) своё виртуальное окружение самостоятельно.
|
|
||||||
|
|
||||||
4. Используйте VS Code с расширением Jupyter для навигации по файловой системе (в частности, по каталогу `eda/`), редактирования и исполнения кода в блокнотах. **Не забывайте** при открытии любого блокнота проверять, что выбрано корректное ядро Jupyter (принадлежащее корректному виртуальному окружению). (Кнопка для выбора ядра для открытого блокнота находится в верхнем правом углу области содержимого вкладки; по умолчанию Вы увидите название выбранного виртуального окружения; если ядро не выбрано, на кнопке написано *Select Kernel*.)
|
|
||||||
|
|
||||||
### Использование Jupytext
|
|
||||||
|
|
||||||
Описанные ниже команды `jupytext` используют глобальной установленный экземпляр Jupytext (однако его можно запускать и изнутри виртуального окружения).
|
|
||||||
|
|
||||||
Для автоматической синхронизации связанных блокнотов (включая создание блокнотов отсутствующих, но ожидаемых форматов):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
jupytext --sync eda/cars_eda.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Jupytext довольно удобно работает в оригинальной среде Jupyter, синхронизируя изменения связанных файлов на лету при работе в Jupyter, **ориентируясь на метки времени на файлах**. См. документацию [Jupytext](https://jupytext.readthedocs.io/en/latest/index.html).
|
|
||||||
|
|
||||||
**Внимание**: С расширением Jupyter для Visual Studio Code Jupytext **не работает напрямую**. Для использования блокнотов `.ipynb` с расширением Jupyter для VS Code нужно синхронизировать текстовый файл под контролем версий и файл `.ipynb` вручную указанными выше командами. Однако заметьте, что это же расширение может исполнять блокнот в текстовом формате самостоятельно, посредством автоматизированного ведения временного блокнота; и оно даже автоматически создаёт/подхватывает локальное ядро Jupyter в виртуальном окружении.
|
|
||||||
|
|||||||
5
gc_mlflow.ps1
Обычный файл
5
gc_mlflow.ps1
Обычный файл
@@ -0,0 +1,5 @@
|
|||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
. $PSScriptRoot\_mlflow_config_common.ps1
|
||||||
|
|
||||||
|
& mlflow gc --backend-store-uri="$BACKEND_URI" @args
|
||||||
7
gc_mlflow.sh
Обычный файл
7
gc_mlflow.sh
Обычный файл
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
. _mlflow_config_common.sh
|
||||||
|
|
||||||
|
& mlflow gc --backend-store-uri="$BACKEND_URI" "$@"
|
||||||
0
iis_project/mlxtend_utils/__init__.py
Обычный файл
0
iis_project/mlxtend_utils/__init__.py
Обычный файл
3
iis_project/mlxtend_utils/feature_selection.py
Обычный файл
3
iis_project/mlxtend_utils/feature_selection.py
Обычный файл
@@ -0,0 +1,3 @@
|
|||||||
|
SEQUENTIAL_FEATURE_SELECTOR_PARAMS_COMMON_INCLUDE = [
|
||||||
|
'k_features', 'forward', 'floating', 'scoring', 'cv', 'fixed_features', 'feature_groups',
|
||||||
|
]
|
||||||
60
iis_project/sklearn_utils/__init__.py
Обычный файл
60
iis_project/sklearn_utils/__init__.py
Обычный файл
@@ -0,0 +1,60 @@
|
|||||||
|
from collections.abc import Container, Sequence, Mapping
|
||||||
|
from typing import TypeAlias, TypeVar
|
||||||
|
|
||||||
|
|
||||||
|
ParamsFilterSpec: TypeAlias = (
|
||||||
|
bool
|
||||||
|
| Container[str]
|
||||||
|
| tuple[bool, Container[str]]
|
||||||
|
| Mapping[str, 'ParamsFilterSpec']
|
||||||
|
| tuple[bool, 'ParamsFilterSpec']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
V = TypeVar('V')
|
||||||
|
|
||||||
|
|
||||||
|
def _split_param_key(key: str) -> tuple[str, ...]:
|
||||||
|
return tuple(key.split('__'))
|
||||||
|
|
||||||
|
|
||||||
|
def _match_key_to_filter_spec(
|
||||||
|
key: Sequence[str], spec: ParamsFilterSpec, empty_default: bool,
|
||||||
|
) -> bool:
|
||||||
|
if isinstance(spec, Sequence) and (len(spec) == 2) and isinstance(spec[0], bool):
|
||||||
|
if (len(key) == 0) and (not spec[0]):
|
||||||
|
return empty_default
|
||||||
|
spec = spec[1]
|
||||||
|
if isinstance(spec, Mapping):
|
||||||
|
if len(key) == 0:
|
||||||
|
return empty_default
|
||||||
|
spec_nested = spec.get(key[0])
|
||||||
|
if spec_nested is None:
|
||||||
|
return False
|
||||||
|
return _whether_to_include_param(key[1:], spec_nested)
|
||||||
|
elif isinstance(spec, Container):
|
||||||
|
if len(key) == 0:
|
||||||
|
return True
|
||||||
|
return (key[0] in spec)
|
||||||
|
return bool(spec)
|
||||||
|
|
||||||
|
|
||||||
|
def _whether_to_include_param(
|
||||||
|
key: Sequence[str], include: ParamsFilterSpec = True, exclude: ParamsFilterSpec = False,
|
||||||
|
) -> bool:
|
||||||
|
return (
|
||||||
|
(not _match_key_to_filter_spec(key, exclude, empty_default=False))
|
||||||
|
and _match_key_to_filter_spec(key, include, empty_default=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def filter_params(
|
||||||
|
params: Mapping[str, V],
|
||||||
|
include: ParamsFilterSpec = True,
|
||||||
|
exclude: ParamsFilterSpec = False,
|
||||||
|
) -> Mapping[str, V]:
|
||||||
|
return {
|
||||||
|
k: v
|
||||||
|
for k, v in params.items()
|
||||||
|
if _whether_to_include_param(_split_param_key(k), include, exclude)
|
||||||
|
}
|
||||||
3
iis_project/sklearn_utils/compose.py
Обычный файл
3
iis_project/sklearn_utils/compose.py
Обычный файл
@@ -0,0 +1,3 @@
|
|||||||
|
COLUMN_TRANSFORMER_PARAMS_COMMON_INCLUDE = [
|
||||||
|
'remainder', 'sparse_threshold', 'transformer_weights',
|
||||||
|
]
|
||||||
1
iis_project/sklearn_utils/ensemble.py
Обычный файл
1
iis_project/sklearn_utils/ensemble.py
Обычный файл
@@ -0,0 +1 @@
|
|||||||
|
RANDOM_FOREST_REGRESSOR_PARAMS_COMMON_EXCLUDE = ['n_jobs', 'verbose', 'warm_start']
|
||||||
5
iis_project/sklearn_utils/pandas.py
Обычный файл
5
iis_project/sklearn_utils/pandas.py
Обычный файл
@@ -0,0 +1,5 @@
|
|||||||
|
from pandas import DataFrame
|
||||||
|
|
||||||
|
|
||||||
|
def pandas_dataframe_from_transformed_artifacts(matrix, transformer) -> DataFrame:
|
||||||
|
return DataFrame(matrix, columns=transformer.get_feature_names_out())
|
||||||
1
iis_project/sklearn_utils/preprocessing.py
Обычный файл
1
iis_project/sklearn_utils/preprocessing.py
Обычный файл
@@ -0,0 +1 @@
|
|||||||
|
STANDARD_SCALER_PARAMS_COMMON_EXCLUDE = ['copy']
|
||||||
2
mlflow/.gitignore
поставляемый
Обычный файл
2
mlflow/.gitignore
поставляемый
Обычный файл
@@ -0,0 +1,2 @@
|
|||||||
|
mlruns.sqlite
|
||||||
|
mlartifacts/
|
||||||
1
requirements/requirements-eda.txt
Обычный файл
1
requirements/requirements-eda.txt
Обычный файл
@@ -0,0 +1 @@
|
|||||||
|
bokeh >=3.7.2,<4
|
||||||
2
requirements/requirements-isolated-research-model.txt
Обычный файл
2
requirements/requirements-isolated-research-model.txt
Обычный файл
@@ -0,0 +1,2 @@
|
|||||||
|
mlxtend ~=0.23.4
|
||||||
|
scikit-learn >=1.7.2,<2
|
||||||
4
requirements/requirements-research.txt
Обычный файл
4
requirements/requirements-research.txt
Обычный файл
@@ -0,0 +1,4 @@
|
|||||||
|
mlflow >=2.16,<2.22
|
||||||
|
mlxtend ~=0.23.4
|
||||||
|
optuna ~=4.5
|
||||||
|
scikit-learn >=1.7.2,<2
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
bokeh >=3.7.2,<4
|
|
||||||
ipykernel >=6.30.1,<7
|
ipykernel >=6.30.1,<7
|
||||||
ipympl ~=0.9.6
|
ipympl ~=0.9.6
|
||||||
matplotlib >=3.10.1,<4
|
matplotlib >=3.10.1,<4
|
||||||
numpy >=2.3.1,<3
|
numpy >=2.2.6,<3
|
||||||
pandas >=2.3.1,<3
|
pandas >=2.3.1,<3
|
||||||
scipy >=1.16.1,<2
|
scipy >=1.15.3,<2
|
||||||
seaborn ~=0.13.2
|
seaborn ~=0.13.2
|
||||||
59
research/README.md
Обычный файл
59
research/README.md
Обычный файл
@@ -0,0 +1,59 @@
|
|||||||
|
# Исследование и настройка предсказательной модели
|
||||||
|
|
||||||
|
## Блокноты Jupyter
|
||||||
|
|
||||||
|
* `research` — Создание множества разных моделей, с использованием разных создаваемых признаков и оптимизацией гиперпараметров.
|
||||||
|
|
||||||
|
Использует файл аугментированных данных датасета о подержанных автомобилях, создаваемый блокнотом `eda/cars_eda.py`. См. `eda/README.md`.
|
||||||
|
|
||||||
|
Если параметр блокнота `mlflow_do_log` установлен в `True`, блокнот логирует в MLFlow создаваемые модели в отдельные вложенные (nested) прогоны под одним (новым) общим прогоном с именем, определяемым параметром `mlflow_experiment_name`.
|
||||||
|
|
||||||
|
Точность предсказания текущей цены автомобиля оценивается в первую очередь по показателю MAPE (из-за наличия в выборке значений цены разных порядков), во вторую очередь учитывается MSE (ради отслеживания систематических ошибок на подвыборках). Исследованные модели:
|
||||||
|
|
||||||
|
1. baseline (MAPE = 0.35, MSE = 1.18);
|
||||||
|
2. с использованием добавленных признаков (feature engineering с помощью scikit-learn) — точность неоднозначна по сравнению с baseline (MAPE = 0.31, MSE = 1.50);
|
||||||
|
3. с использованием добавленных и выбранных (SFS) признаков — точность существенно лучше baseline (MAPE = 0.20, MSE = 1.02);
|
||||||
|
4. с использованием добавленных и выбранных признаков и оптимизированными гиперпараметрами (optuna) — точность немного лучше модели 3 по MAPE (MAPE = 0.20, MSE = 0.94).
|
||||||
|
|
||||||
|
Модель 4 выбрана как финальная модель для последующего развёртывания. Она использует следующие признаки (такие же, как и модель 3):
|
||||||
|
* `extend_features_as_polynomial__selling_price` (исходная цена продажи, нормализована `StandardScaler`),
|
||||||
|
* `extend_features_as_polynomial__selling_price^2`,
|
||||||
|
* `extend_features_as_spline__age_sp_1` (значение базисной функции 2/5 однородного сплайна, нормализованного к крайним значениям возраста автомобилей),
|
||||||
|
* `extend_features_as_spline__age_sp_2` (то же, но базисная функция 3/5),
|
||||||
|
* `scale_to_standard__age` (исходный возраст автомобиля, нормализован `StandardScaler`).
|
||||||
|
|
||||||
|
По указанию преподавателя, скриншоты пользовательского интерфейса MLFlow сохранены в директории `./mlflow_ui_figures`.
|
||||||
|
|
||||||
|
По указанию преподавателя, ID финального прогона: `4c7f04ad9ee94237b44f60b6eb14b41e` (вложен в прогон `4e4a9094cb3c4eed9d4a056a27cadcd9`).
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
Для исследования и настройки предсказательной модели необходимы общие зависимости, см. [Общие зависимости](../README.md#общие-зависимости) в `README.md`.
|
||||||
|
|
||||||
|
Для исследования и настройки предсказательной модели используется среда [Jupyter](https://jupyter.org/). См. об установке и использовании Jupyter в проекте в `docs/jupyter.md`.
|
||||||
|
|
||||||
|
### Зависимости
|
||||||
|
|
||||||
|
Дополнительные зависимости, необходимые для исследования и настройки предсказательной модели, — пакеты Python — записаны в файле `requirements/requirements-research.txt` (см. **Пакеты Python**). См. об установке пакетов Python в **Пакеты Python** в `README.md`.
|
||||||
|
|
||||||
|
## Работа с блокнотами Jupyter
|
||||||
|
|
||||||
|
См. об установке и использовании Jupyter в проекте в `docs/jupyter.md`.
|
||||||
|
|
||||||
|
## Работа с MLFlow
|
||||||
|
|
||||||
|
Для управления жизненным циклом моделей машинного обучения используется платформа [MLFlow](https://mlflow.org/).
|
||||||
|
|
||||||
|
Запуск локального сервера MLFlow (**выполнять в корневой директории проекта**):
|
||||||
|
|
||||||
|
run_mlflow_server
|
||||||
|
|
||||||
|
Для остановки сервера MLFlow пошлите ему сигнал `SIGINT` (`Ctrl+C` в терминале).
|
||||||
|
|
||||||
|
Очистка локальной tracking БД MLFlow от удалённых прогонов (**выполнять в корневой директории проекта**):
|
||||||
|
|
||||||
|
gc_mlflow
|
||||||
|
|
||||||
|
Очистка локальной tracking БД MLFlow от конкретных удалённых экспериментов по списку их ID, разделённым запятыми, `<ids>` (**выполнять в корневой директории проекта**):
|
||||||
|
|
||||||
|
gc_mlflow --experiment-ids=<ids>
|
||||||
Двоичные данные
research/mlflow_ui_figures/registered_model_experimental.png
Обычный файл
Двоичные данные
research/mlflow_ui_figures/registered_model_experimental.png
Обычный файл
Двоичный файл не отображается.
|
После Ширина: | Высота: | Размер: 43 KiB |
Двоичные данные
research/mlflow_ui_figures/registered_model_final.png
Обычный файл
Двоичные данные
research/mlflow_ui_figures/registered_model_final.png
Обычный файл
Двоичный файл не отображается.
|
После Ширина: | Высота: | Размер: 37 KiB |
Двоичные данные
research/mlflow_ui_figures/run_final_model_artifacts_mlmodel.png
Обычный файл
Двоичные данные
research/mlflow_ui_figures/run_final_model_artifacts_mlmodel.png
Обычный файл
Двоичный файл не отображается.
|
После Ширина: | Высота: | Размер: 93 KiB |
Двоичные данные
research/mlflow_ui_figures/runs_with_metrics_display.png
Обычный файл
Двоичные данные
research/mlflow_ui_figures/runs_with_metrics_display.png
Обычный файл
Двоичный файл не отображается.
|
После Ширина: | Высота: | Размер: 62 KiB |
13796
research/research.ipynb
Обычный файл
13796
research/research.ipynb
Обычный файл
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
905
research/research.py
Обычный файл
905
research/research.py
Обычный файл
@@ -0,0 +1,905 @@
|
|||||||
|
# ---
|
||||||
|
# jupyter:
|
||||||
|
# jupytext:
|
||||||
|
# formats: py:percent,ipynb
|
||||||
|
# text_representation:
|
||||||
|
# extension: .py
|
||||||
|
# format_name: percent
|
||||||
|
# format_version: '1.3'
|
||||||
|
# jupytext_version: 1.17.3
|
||||||
|
# kernelspec:
|
||||||
|
# display_name: python3_venv
|
||||||
|
# language: python
|
||||||
|
# name: python3_venv
|
||||||
|
# ---
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# # Исследование и настройка предсказательной модели для цен подержанных автомобилях
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Блокнот использует файл аугментированных данных датасета о подержанных автомобилях, создаваемый блокнотом `eda/cars_eda.py`. См. ниже параметры блокнота для papermill.
|
||||||
|
|
||||||
|
# %%
|
||||||
|
#XXX: разделить блокнот штук на 5
|
||||||
|
|
||||||
|
# %%
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
# %% tags=["parameters"]
|
||||||
|
data_aug_pickle_path: Optional[str] = None
|
||||||
|
# Полный путь к файлу (pickle) для сохранения очищенного датасета. Если не установлен, используется `data/<data_aug_pickle_relpath>`.
|
||||||
|
data_aug_pickle_relpath: str = 'cars.aug.pickle'
|
||||||
|
# Путь к файлу (pickle) для сохранения очищенного датасета относительно директории данных `data`. Игнорируется, если установлен data_aug_pickle_path.
|
||||||
|
|
||||||
|
#model_global_comment_path: Optional[str] = None
|
||||||
|
## Полный путь к текстовому файлу с произвольным комментарием для сохранения в MLFlow как артефакт вместе с моделью. Если не установлен, используется `research/<comment_relpath>`.
|
||||||
|
#model_comment_relpath: str = 'comment.txt'
|
||||||
|
## Путь к текстовому файлу с произвольным комментарием для сохранения в MLFlow как артефакт вместе с моделью относительно директории `research`. Игнорируется, если установлен comment_path.
|
||||||
|
|
||||||
|
mlflow_tracking_server_uri: str = 'http://localhost:5000'
|
||||||
|
# URL tracking-сервера MLFlow.
|
||||||
|
mlflow_registry_uri: Optional[str] = None
|
||||||
|
# URL сервера registry MLFlow (если не указан, используется `mlflow_tracking_server_uri`).
|
||||||
|
|
||||||
|
mlflow_do_log: bool = True
|
||||||
|
# Записывать ли прогоны (runs) в MLFlow.
|
||||||
|
mlflow_experiment_id: Optional[str] = None
|
||||||
|
# ID эксперимента MLFlow, имеет приоритет над `mlflow_experiment_name`.
|
||||||
|
mlflow_experiment_name: Optional[str] = 'Current price predicion for used cars'
|
||||||
|
# Имя эксперимента MLFlow (ниже приоритетом, чем `mlflow_experiment_id`).
|
||||||
|
mlflow_root_run_name: str = 'Models'
|
||||||
|
# Имя корневого прогона MLFlow (остальные прогоны будут созданы блокнотом внутри этого, как nested)
|
||||||
|
|
||||||
|
# %%
|
||||||
|
from collections.abc import Collection, Sequence
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import pickle
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# %%
|
||||||
|
import matplotlib
|
||||||
|
import mlflow
|
||||||
|
import mlflow.models
|
||||||
|
import mlflow.sklearn
|
||||||
|
import mlxtend.feature_selection
|
||||||
|
import mlxtend.plotting
|
||||||
|
import optuna
|
||||||
|
import optuna.samplers
|
||||||
|
import sklearn.compose
|
||||||
|
import sklearn.ensemble
|
||||||
|
import sklearn.metrics
|
||||||
|
import sklearn.model_selection
|
||||||
|
import sklearn.pipeline
|
||||||
|
import sklearn.preprocessing
|
||||||
|
|
||||||
|
# %%
|
||||||
|
BASE_PATH = pathlib.Path('..')
|
||||||
|
|
||||||
|
# %%
|
||||||
|
CODE_PATH = BASE_PATH
|
||||||
|
sys.path.insert(0, str(CODE_PATH.resolve()))
|
||||||
|
|
||||||
|
# %%
|
||||||
|
from iis_project.mlxtend_utils.feature_selection import SEQUENTIAL_FEATURE_SELECTOR_PARAMS_COMMON_INCLUDE
|
||||||
|
from iis_project.sklearn_utils import filter_params
|
||||||
|
from iis_project.sklearn_utils.compose import COLUMN_TRANSFORMER_PARAMS_COMMON_INCLUDE
|
||||||
|
from iis_project.sklearn_utils.ensemble import RANDOM_FOREST_REGRESSOR_PARAMS_COMMON_EXCLUDE
|
||||||
|
from iis_project.sklearn_utils.pandas import pandas_dataframe_from_transformed_artifacts
|
||||||
|
from iis_project.sklearn_utils.preprocessing import STANDARD_SCALER_PARAMS_COMMON_EXCLUDE
|
||||||
|
|
||||||
|
# %%
|
||||||
|
MODEL_INOUT_EXAMPLE_SIZE = 0x10
|
||||||
|
|
||||||
|
# %%
|
||||||
|
mlflow.set_tracking_uri(mlflow_tracking_server_uri)
|
||||||
|
if mlflow_registry_uri is not None:
|
||||||
|
mlflow.set_registry_uri(mlflow_registry_uri)
|
||||||
|
|
||||||
|
# %%
|
||||||
|
if mlflow_do_log:
|
||||||
|
mlflow_experiment = mlflow.set_experiment(experiment_name=mlflow_experiment_name, experiment_id=mlflow_experiment_id)
|
||||||
|
mlflow_root_run_id = None # изменяется позже
|
||||||
|
|
||||||
|
# %%
|
||||||
|
DATA_PATH = (
|
||||||
|
pathlib.Path(os.path.dirname(data_aug_pickle_path))
|
||||||
|
if data_aug_pickle_path is not None
|
||||||
|
else (BASE_PATH / 'data')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# %%
|
||||||
|
def build_sequential_feature_selector(*args, **kwargs):
|
||||||
|
return mlxtend.feature_selection.SequentialFeatureSelector(*args, **kwargs)
|
||||||
|
|
||||||
|
def plot_sequential_feature_selection(feature_selector, *args_rest, **kwargs):
|
||||||
|
metric_dict = feature_selector.get_metric_dict()
|
||||||
|
return mlxtend.plotting.plot_sequential_feature_selection(metric_dict, *args_rest, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# ## Загрузка и обзор данных
|
||||||
|
|
||||||
|
# %%
|
||||||
|
with open(
|
||||||
|
(
|
||||||
|
data_aug_pickle_path
|
||||||
|
if data_aug_pickle_path is not None
|
||||||
|
else (DATA_PATH / data_aug_pickle_relpath)
|
||||||
|
),
|
||||||
|
'rb',
|
||||||
|
) as input_file:
|
||||||
|
df_orig = pickle.load(input_file)
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Обзор датасета:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
len(df_orig)
|
||||||
|
|
||||||
|
# %%
|
||||||
|
df_orig.info()
|
||||||
|
|
||||||
|
# %%
|
||||||
|
df_orig.head(0x10)
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# ## Разделение датасета на выборки
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Выделение признаков и целевых переменных:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
feature_columns = (
|
||||||
|
'selling_price',
|
||||||
|
'driven_kms',
|
||||||
|
'fuel_type',
|
||||||
|
'selling_type',
|
||||||
|
'transmission',
|
||||||
|
#'owner',
|
||||||
|
'age',
|
||||||
|
)
|
||||||
|
|
||||||
|
target_columns = (
|
||||||
|
'present_price',
|
||||||
|
)
|
||||||
|
|
||||||
|
# %%
|
||||||
|
features_to_scale_to_standard_columns = (
|
||||||
|
'selling_price',
|
||||||
|
'driven_kms',
|
||||||
|
'age',
|
||||||
|
)
|
||||||
|
assert all(
|
||||||
|
(col in df_orig.select_dtypes(('number',)).columns)
|
||||||
|
for col in features_to_scale_to_standard_columns
|
||||||
|
)
|
||||||
|
|
||||||
|
features_to_encode_wrt_target_columns = (
|
||||||
|
'fuel_type',
|
||||||
|
'selling_type',
|
||||||
|
'transmission',
|
||||||
|
#'owner',
|
||||||
|
)
|
||||||
|
assert all(
|
||||||
|
(col in df_orig.select_dtypes(('category', 'object')).columns)
|
||||||
|
for col in features_to_encode_wrt_target_columns
|
||||||
|
)
|
||||||
|
|
||||||
|
# %%
|
||||||
|
df_orig_features = df_orig[list(feature_columns)]
|
||||||
|
df_target = df_orig[list(target_columns)]
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Разделение на обучающую и тестовую выборки:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
DF_TEST_PORTION = 0.25
|
||||||
|
|
||||||
|
# %%
|
||||||
|
df_orig_features_train, df_orig_features_test, df_target_train, df_target_test = (
|
||||||
|
sklearn.model_selection.train_test_split(
|
||||||
|
df_orig_features, df_target, test_size=DF_TEST_PORTION, random_state=0x7AE6,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Размеры обучающей и тестовой выборки соответственно:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
tuple(map(len, (df_target_train, df_target_test)))
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# ## Модели
|
||||||
|
|
||||||
|
# %%
|
||||||
|
# XXX: один файл requirements для всех моделей
|
||||||
|
MODEL_PIP_REQUIREMENTS_PATH = BASE_PATH / 'requirements' / 'requirements-isolated-research-model.txt'
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Сигнатура модели для MLFlow:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
mlflow_model_signature = mlflow.models.infer_signature(model_input=df_orig_features, model_output=df_target)
|
||||||
|
mlflow_model_signature
|
||||||
|
|
||||||
|
|
||||||
|
# %% [raw] vscode={"languageId": "raw"}
|
||||||
|
# input_schema = mlflow.types.schema.Schema([
|
||||||
|
# mlflow.types.schema.ColSpec("double", "selling_price"),
|
||||||
|
# mlflow.types.schema.ColSpec("double", "driven_kms"),
|
||||||
|
# mlflow.types.schema.ColSpec("string", "fuel_type"),
|
||||||
|
# mlflow.types.schema.ColSpec("string", "selling_type"),
|
||||||
|
# mlflow.types.schema.ColSpec("string", "transmission"),
|
||||||
|
# mlflow.types.schema.ColSpec("double", "age"),
|
||||||
|
# ])
|
||||||
|
#
|
||||||
|
# output_schema = mlflow.types.schema.Schema([
|
||||||
|
# mlflow.types.schema.ColSpec("double", "present_price"),
|
||||||
|
# ])
|
||||||
|
#
|
||||||
|
# mlflow_model_signature = mlflow.models.ModelSignature(inputs=input_schema, outputs=output_schema)
|
||||||
|
|
||||||
|
# %%
|
||||||
|
def build_features_scaler_standard():
|
||||||
|
return sklearn.preprocessing.StandardScaler()
|
||||||
|
|
||||||
|
|
||||||
|
# %%
|
||||||
|
#def build_categorical_features_encoder_onehot():
|
||||||
|
# return sklearn.preprocessing.OneHotEncoder()
|
||||||
|
|
||||||
|
def build_categorical_features_encoder_target(*, random_state=None):
|
||||||
|
return sklearn.preprocessing.TargetEncoder(
|
||||||
|
target_type='continuous', smooth='auto', shuffle=True, random_state=random_state,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Регрессор — небольшой случайный лес, цель — минимизация квадрата ошибки предсказания:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
def build_regressor(n_estimators, *, max_depth=None, max_features='sqrt', random_state=None):
|
||||||
|
return sklearn.ensemble.RandomForestRegressor(
|
||||||
|
n_estimators, criterion='squared_error',
|
||||||
|
max_depth=max_depth, max_features=max_features,
|
||||||
|
random_state=random_state,
|
||||||
|
)
|
||||||
|
|
||||||
|
def build_regressor_baseline(*, random_state=None):
|
||||||
|
return build_regressor(16, max_depth=8, max_features='sqrt')
|
||||||
|
|
||||||
|
|
||||||
|
# %%
|
||||||
|
def score_predictions(target_test, target_test_predicted):
|
||||||
|
return {
|
||||||
|
'mse': sklearn.metrics.mean_squared_error(target_test, target_test_predicted),
|
||||||
|
'mae': sklearn.metrics.mean_absolute_error(target_test, target_test_predicted),
|
||||||
|
'mape': sklearn.metrics.mean_absolute_percentage_error(target_test, target_test_predicted),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# %%
|
||||||
|
# использует глобальные переменные mlflow_do_log, mlflow_experiment, mlflow_root_run_name
|
||||||
|
def mlflow_log_model(
|
||||||
|
model,
|
||||||
|
model_params,
|
||||||
|
metrics,
|
||||||
|
*,
|
||||||
|
nested_run_name,
|
||||||
|
model_signature=None,
|
||||||
|
input_example=None,
|
||||||
|
pip_requirements=None,
|
||||||
|
#global_comment_file_path=None,
|
||||||
|
extra_logs_handler=None,
|
||||||
|
):
|
||||||
|
global mlflow_root_run_id
|
||||||
|
if not mlflow_do_log:
|
||||||
|
return
|
||||||
|
experiment_id = mlflow_experiment.experiment_id
|
||||||
|
start_run_root_kwargs_extra = {}
|
||||||
|
if mlflow_root_run_id is not None:
|
||||||
|
start_run_root_kwargs_extra['run_id'] = mlflow_root_run_id
|
||||||
|
else:
|
||||||
|
start_run_root_kwargs_extra['run_name'] = mlflow_root_run_name
|
||||||
|
with mlflow.start_run(experiment_id=experiment_id, **start_run_root_kwargs_extra) as root_run:
|
||||||
|
if root_run.info.status not in ('RUNNING',):
|
||||||
|
raise RuntimeError('Cannot get the root run to run')
|
||||||
|
if mlflow_root_run_id is None:
|
||||||
|
mlflow_root_run_id = root_run.info.run_id
|
||||||
|
# важно одновременно использовать nested=True и parent_run_id=...:
|
||||||
|
with mlflow.start_run(experiment_id=experiment_id, run_name=nested_run_name, nested=True, parent_run_id=mlflow_root_run_id):
|
||||||
|
if isinstance(pip_requirements, pathlib.PurePath):
|
||||||
|
pip_requirements = str(pip_requirements)
|
||||||
|
_ = mlflow.sklearn.log_model(
|
||||||
|
model,
|
||||||
|
'model',
|
||||||
|
signature=model_signature,
|
||||||
|
input_example=input_example,
|
||||||
|
pip_requirements=pip_requirements,
|
||||||
|
)
|
||||||
|
if model_params is not None:
|
||||||
|
_ = mlflow.log_params(model_params)
|
||||||
|
if metrics is not None:
|
||||||
|
_ = mlflow.log_metrics(metrics)
|
||||||
|
#if (global_comment_file_path is not None) and global_comment_file_path.exists():
|
||||||
|
# mlflow.log_artifact(str(global_comment_file_path))
|
||||||
|
if extra_logs_handler is not None:
|
||||||
|
if callable(extra_logs_handler) and (not isinstance(extra_logs_handler, Collection)):
|
||||||
|
extra_logs_handler = (extra_logs_handler,)
|
||||||
|
for extr_logs_handler_fn in extra_logs_handler:
|
||||||
|
extr_logs_handler_fn(mlflow)
|
||||||
|
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# ### Baseline модель
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Пайплайн предобработки признаков:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
preprocess_transformer = sklearn.compose.ColumnTransformer(
|
||||||
|
[
|
||||||
|
('scale_to_standard', build_features_scaler_standard(), features_to_scale_to_standard_columns),
|
||||||
|
(
|
||||||
|
#'encode_categoricals_one_hot',
|
||||||
|
'encode_categoricals_wrt_target',
|
||||||
|
#build_categorical_features_encoder_onehot(),
|
||||||
|
build_categorical_features_encoder_target(random_state=0x2ED6),
|
||||||
|
features_to_encode_wrt_target_columns,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
remainder='drop',
|
||||||
|
)
|
||||||
|
|
||||||
|
# %%
|
||||||
|
regressor = build_regressor_baseline(random_state=0x016B)
|
||||||
|
regressor
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Составной пайплайн:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
pipeline = sklearn.pipeline.Pipeline([
|
||||||
|
('preprocess', preprocess_transformer),
|
||||||
|
('regress', regressor),
|
||||||
|
])
|
||||||
|
pipeline
|
||||||
|
|
||||||
|
# %%
|
||||||
|
model_params = filter_params(
|
||||||
|
pipeline.get_params(),
|
||||||
|
include={
|
||||||
|
'preprocess': (
|
||||||
|
False,
|
||||||
|
{
|
||||||
|
**{k: True for k in COLUMN_TRANSFORMER_PARAMS_COMMON_INCLUDE},
|
||||||
|
'scale_to_standard': True,
|
||||||
|
'encode_categorical_wrt_target': True,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
'regress': (False, True),
|
||||||
|
},
|
||||||
|
exclude={
|
||||||
|
'preprocess': {'scale_to_standard': STANDARD_SCALER_PARAMS_COMMON_EXCLUDE},
|
||||||
|
'regress': RANDOM_FOREST_REGRESSOR_PARAMS_COMMON_EXCLUDE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
model_params
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Обучение модели:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
_ = pipeline.fit(df_orig_features_train, df_target_train.iloc[:, 0])
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Оценка качества:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
target_test_predicted = pipeline.predict(df_orig_features_test)
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Метрики качества (MAPE, а также MSE, MAE):
|
||||||
|
|
||||||
|
# %%
|
||||||
|
metrics = score_predictions(df_target_test, target_test_predicted)
|
||||||
|
metrics
|
||||||
|
|
||||||
|
# %%
|
||||||
|
mlflow_log_model(
|
||||||
|
pipeline,
|
||||||
|
model_params=model_params,
|
||||||
|
metrics={k: float(v) for k, v in metrics.items()},
|
||||||
|
nested_run_name='Baseline model',
|
||||||
|
model_signature=mlflow_model_signature,
|
||||||
|
input_example=df_orig_features.head(MODEL_INOUT_EXAMPLE_SIZE),
|
||||||
|
pip_requirements=MODEL_PIP_REQUIREMENTS_PATH,
|
||||||
|
#global_comment_file_path=(
|
||||||
|
# model_comment_path
|
||||||
|
# if model_comment_path is not None
|
||||||
|
# else (BASE_PATH / 'research' / model_comment_relpath)
|
||||||
|
#),
|
||||||
|
)
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# ### Модель с дополнительными признаками
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Пайплайн предобработки признаков:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
features_to_extend_as_polynomial = ('selling_price', 'driven_kms')
|
||||||
|
features_to_extend_as_spline = ('age',)
|
||||||
|
|
||||||
|
|
||||||
|
# %%
|
||||||
|
def build_preprocess_augmenting_transformer():
|
||||||
|
assert set(features_to_extend_as_polynomial) <= {*features_to_scale_to_standard_columns}
|
||||||
|
assert set(features_to_extend_as_spline) <= {*features_to_scale_to_standard_columns}
|
||||||
|
return sklearn.compose.ColumnTransformer(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
'extend_features_as_polynomial',
|
||||||
|
sklearn.pipeline.Pipeline([
|
||||||
|
(
|
||||||
|
'extend_features',
|
||||||
|
sklearn.preprocessing.PolynomialFeatures(2, include_bias=False),
|
||||||
|
),
|
||||||
|
('scale_to_standard', build_features_scaler_standard()),
|
||||||
|
]),
|
||||||
|
features_to_extend_as_polynomial,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'extend_features_as_spline',
|
||||||
|
sklearn.preprocessing.SplineTransformer(
|
||||||
|
4, knots='quantile', extrapolation='constant', include_bias=False,
|
||||||
|
),
|
||||||
|
features_to_extend_as_spline,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scale_to_standard',
|
||||||
|
build_features_scaler_standard(),
|
||||||
|
tuple(filter(lambda f: f not in features_to_extend_as_polynomial, features_to_scale_to_standard_columns)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'encode_categoricals_wrt_target',
|
||||||
|
build_categorical_features_encoder_target(random_state=0x2ED6),
|
||||||
|
features_to_encode_wrt_target_columns,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
remainder='drop',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# %%
|
||||||
|
PREPROCESS_AUGMENTING_TRANSFORMER_PARAMS_COMMON_INCLUDE = {
|
||||||
|
**{k: True for k in COLUMN_TRANSFORMER_PARAMS_COMMON_INCLUDE},
|
||||||
|
'extend_features_as_polynomial': {
|
||||||
|
'extend_features': True,
|
||||||
|
'scale_to_standard': True,
|
||||||
|
},
|
||||||
|
'extend_features_as_spline': True,
|
||||||
|
'scale_to_standard': True,
|
||||||
|
'encode_categorical_wrt_target': True,
|
||||||
|
}
|
||||||
|
PREPROCESS_AUGMENTING_TRANSFORMER_PARAMS_COMMON_EXCLUDE = {
|
||||||
|
'extend_features_as_polynomial': {
|
||||||
|
'scale_to_standard': STANDARD_SCALER_PARAMS_COMMON_EXCLUDE,
|
||||||
|
},
|
||||||
|
'scale_to_standard': STANDARD_SCALER_PARAMS_COMMON_EXCLUDE,
|
||||||
|
}
|
||||||
|
|
||||||
|
# %%
|
||||||
|
preprocess_transformer = build_preprocess_augmenting_transformer()
|
||||||
|
preprocess_transformer
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Демонстрация предобработки данных:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
preprocess_transformer_tmp = build_preprocess_augmenting_transformer()
|
||||||
|
df_augd_features_matrix_train = preprocess_transformer_tmp.fit_transform(df_orig_features_train, df_target_train.iloc[:, 0])
|
||||||
|
df_augd_features_train = pandas_dataframe_from_transformed_artifacts(df_augd_features_matrix_train, preprocess_transformer_tmp)
|
||||||
|
del preprocess_transformer_tmp
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Обзор предобработанного датасета:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
df_augd_features_train.info()
|
||||||
|
|
||||||
|
# %%
|
||||||
|
df_augd_features_train.head(0x8)
|
||||||
|
|
||||||
|
# %%
|
||||||
|
regressor = build_regressor_baseline(random_state=0x3AEF)
|
||||||
|
regressor
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Составной пайплайн:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
pipeline = sklearn.pipeline.Pipeline([
|
||||||
|
('preprocess', preprocess_transformer),
|
||||||
|
('regress', regressor),
|
||||||
|
])
|
||||||
|
pipeline
|
||||||
|
|
||||||
|
# %%
|
||||||
|
model_params = filter_params(
|
||||||
|
pipeline.get_params(),
|
||||||
|
include={
|
||||||
|
'preprocess': (False, PREPROCESS_AUGMENTING_TRANSFORMER_PARAMS_COMMON_INCLUDE.copy()),
|
||||||
|
'regress': (False, True),
|
||||||
|
},
|
||||||
|
exclude={
|
||||||
|
'preprocess': PREPROCESS_AUGMENTING_TRANSFORMER_PARAMS_COMMON_EXCLUDE.copy(),
|
||||||
|
'regress': RANDOM_FOREST_REGRESSOR_PARAMS_COMMON_EXCLUDE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
model_params
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Обучение модели:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
_ = pipeline.fit(df_orig_features_train, df_target_train.iloc[:, 0])
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Оценка качества:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
target_test_predicted = pipeline.predict(df_orig_features_test)
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Метрики качества (MAPE, а также MSE, MAE):
|
||||||
|
|
||||||
|
# %%
|
||||||
|
metrics = score_predictions(df_target_test, target_test_predicted)
|
||||||
|
metrics
|
||||||
|
|
||||||
|
# %%
|
||||||
|
mlflow_log_model(
|
||||||
|
pipeline,
|
||||||
|
model_params=model_params,
|
||||||
|
metrics={k: float(v) for k, v in metrics.items()},
|
||||||
|
nested_run_name='Model with engineered features',
|
||||||
|
model_signature=mlflow_model_signature,
|
||||||
|
input_example=df_orig_features.head(MODEL_INOUT_EXAMPLE_SIZE),
|
||||||
|
pip_requirements=MODEL_PIP_REQUIREMENTS_PATH,
|
||||||
|
#global_comment_file_path=(
|
||||||
|
# model_comment_path
|
||||||
|
# if model_comment_path is not None
|
||||||
|
# else (BASE_PATH / 'research' / model_comment_relpath)
|
||||||
|
#),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# ### Модель с дополнительными и отфильтрованными признаками
|
||||||
|
|
||||||
|
# %%
|
||||||
|
def build_selected_columns_info_for_mlflow(names=None, indices=None):
|
||||||
|
info = {}
|
||||||
|
if names is not None:
|
||||||
|
info['names'] = names
|
||||||
|
if indices is not None:
|
||||||
|
info['indices'] = indices
|
||||||
|
return info
|
||||||
|
|
||||||
|
def build_extra_logs_handler_selected_columns(names=None, indices=None):
|
||||||
|
def extra_log(mlf):
|
||||||
|
if any((v is not None) for v in (names, indices)):
|
||||||
|
info = build_selected_columns_info_for_mlflow(names=names, indices=indices)
|
||||||
|
mlf.log_dict(info, 'selected_columns_info.json')
|
||||||
|
return extra_log
|
||||||
|
|
||||||
|
|
||||||
|
# %%
|
||||||
|
def build_selected_columns_info_for_mlflow_from_sequential_feature_selector(feature_selector, *, take_names=True, take_indices=True):
|
||||||
|
return build_selected_columns_info_for_mlflow(
|
||||||
|
names=(feature_selector.k_feature_names_ if take_names else None),
|
||||||
|
indices=(tuple(feature_selector.k_feature_idx_) if take_indices else None),
|
||||||
|
)
|
||||||
|
|
||||||
|
def build_extra_logs_handler_selected_columns_from_sequential_feature_selector(feature_selector):
|
||||||
|
def extra_log(mlf):
|
||||||
|
info = build_selected_columns_info_for_mlflow_from_sequential_feature_selector(feature_selector)
|
||||||
|
mlf.log_dict(info, 'selected_columns_info.json')
|
||||||
|
return extra_log
|
||||||
|
|
||||||
|
|
||||||
|
# %%
|
||||||
|
regressor = build_regressor_baseline(random_state=0x8EDD)
|
||||||
|
regressor
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Выбор признаков среди дополненного набора по минимизации MAPE:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
len(df_augd_features_train.columns)
|
||||||
|
|
||||||
|
# %%
|
||||||
|
FILTERED_FEATURES_NUM = (4, 8)
|
||||||
|
|
||||||
|
|
||||||
|
# %%
|
||||||
|
def build_feature_selector(*, verbose=0):
|
||||||
|
return build_sequential_feature_selector(
|
||||||
|
regressor, k_features=FILTERED_FEATURES_NUM, forward=True, floating=True, cv=4, scoring='neg_mean_absolute_percentage_error',
|
||||||
|
verbose=verbose,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# %%
|
||||||
|
FEATURE_SELECTOR_PARAMS_COMMON_INCLUDE = {
|
||||||
|
**{k: True for k in SEQUENTIAL_FEATURE_SELECTOR_PARAMS_COMMON_INCLUDE},
|
||||||
|
'estimator': False,
|
||||||
|
}
|
||||||
|
FEATURE_SELECTOR_PARAMS_COMMON_EXCLUDE = () # TODO: ай-яй-яй
|
||||||
|
|
||||||
|
# %%
|
||||||
|
feature_selector = build_feature_selector(verbose=1)
|
||||||
|
feature_selector
|
||||||
|
|
||||||
|
# %%
|
||||||
|
_ = feature_selector.fit(df_augd_features_train, df_target_train.iloc[:, 0])
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Выбранные признаки (имена и индексы):
|
||||||
|
|
||||||
|
# %%
|
||||||
|
build_selected_columns_info_for_mlflow_from_sequential_feature_selector(feature_selector)
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# MAPE в зависимости от количества выбранных признаков (указан регион выбора, ограниченный `FILTERED_FEATURES_NUM`):
|
||||||
|
|
||||||
|
# %%
|
||||||
|
fig, ax = plot_sequential_feature_selection(feature_selector, kind='std_dev')
|
||||||
|
ax.grid(True)
|
||||||
|
if isinstance(FILTERED_FEATURES_NUM, Sequence):
|
||||||
|
_ = ax.axvspan(min(FILTERED_FEATURES_NUM), max(FILTERED_FEATURES_NUM), color=matplotlib.colormaps.get_cmap('tab10')(6), alpha=0.15)
|
||||||
|
# хотелось бы поставить верхнюю границу `len(df_augd_features_train.columns)`, но SequentialFeatureSelector до неё не досчитывает-то
|
||||||
|
_ = ax.set_xlim((1, (max(FILTERED_FEATURES_NUM) if isinstance(FILTERED_FEATURES_NUM, Sequence) else FILTERED_FEATURES_NUM)))
|
||||||
|
_ = ax.set_ylim((None, 0.))
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Составной пайплайн:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
pipeline = sklearn.pipeline.Pipeline([
|
||||||
|
('preprocess', build_preprocess_augmenting_transformer()),
|
||||||
|
('select_features', feature_selector),
|
||||||
|
('regress', regressor),
|
||||||
|
])
|
||||||
|
pipeline
|
||||||
|
|
||||||
|
# %%
|
||||||
|
model_params = filter_params(
|
||||||
|
pipeline.get_params(),
|
||||||
|
include={
|
||||||
|
'preprocess': (False, PREPROCESS_AUGMENTING_TRANSFORMER_PARAMS_COMMON_INCLUDE.copy()),
|
||||||
|
'select_features': (False, FEATURE_SELECTOR_PARAMS_COMMON_INCLUDE.copy()),
|
||||||
|
'regress': (False, True),
|
||||||
|
},
|
||||||
|
exclude={
|
||||||
|
'preprocess': PREPROCESS_AUGMENTING_TRANSFORMER_PARAMS_COMMON_EXCLUDE.copy(),
|
||||||
|
'select_features': FEATURE_SELECTOR_PARAMS_COMMON_EXCLUDE,
|
||||||
|
'regress': RANDOM_FOREST_REGRESSOR_PARAMS_COMMON_EXCLUDE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
model_params
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Обучение модели:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
# XXX: SequentialFeatureSelector обучается опять!?
|
||||||
|
_ = pipeline.fit(df_orig_features_train, df_target_train.iloc[:, 0])
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Оценка качества:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
target_test_predicted = pipeline.predict(df_orig_features_test)
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Метрики качества (MAPE, а также MSE, MAE):
|
||||||
|
|
||||||
|
# %%
|
||||||
|
metrics = score_predictions(df_target_test, target_test_predicted)
|
||||||
|
metrics
|
||||||
|
|
||||||
|
# %%
|
||||||
|
mlflow_log_model(
|
||||||
|
pipeline,
|
||||||
|
model_params=model_params,
|
||||||
|
metrics={k: float(v) for k, v in metrics.items()},
|
||||||
|
nested_run_name='Model with filtered engineered features',
|
||||||
|
model_signature=mlflow_model_signature,
|
||||||
|
input_example=df_orig_features.head(MODEL_INOUT_EXAMPLE_SIZE),
|
||||||
|
pip_requirements=MODEL_PIP_REQUIREMENTS_PATH,
|
||||||
|
#global_comment_file_path=(
|
||||||
|
# model_comment_path
|
||||||
|
# if model_comment_path is not None
|
||||||
|
# else (BASE_PATH / 'research' / model_comment_relpath)
|
||||||
|
#),
|
||||||
|
extra_logs_handler=(build_extra_logs_handler_selected_columns_from_sequential_feature_selector(pipeline.named_steps['select_features']),),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# ### Автоматический подбор гиперпараметров модели
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Составной пайплайн:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
def build_pipeline(regressor_n_estimators, regressor_max_depth=None, regressor_max_features='sqrt'):
|
||||||
|
return sklearn.pipeline.Pipeline([
|
||||||
|
('preprocess', build_preprocess_augmenting_transformer()),
|
||||||
|
('select_features', build_feature_selector()),
|
||||||
|
('regress', build_regressor(regressor_n_estimators, max_depth=regressor_max_depth, max_features=regressor_max_features)),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Целевая функция для оптимизатора гиперпараметров (подбирает параметры `RandomForestRegressor`: `n_estimators`, `max_depth`, `max_features`):
|
||||||
|
|
||||||
|
# %%
|
||||||
|
def regressor_hyperparams_objective(trial):
|
||||||
|
n_estimators = trial.suggest_int('n_estimators', 1, 256, log=True)
|
||||||
|
max_depth = trial.suggest_int('max_depth', 1, 16, log=True)
|
||||||
|
max_features = trial.suggest_float('max_features', 0.1, 1.)
|
||||||
|
# составной пайплайн:
|
||||||
|
pipeline = build_pipeline(n_estimators, regressor_max_depth=max_depth, regressor_max_features=max_features)
|
||||||
|
# обучение модели:
|
||||||
|
_ = pipeline.fit(df_orig_features_train, df_target_train.iloc[:, 0])
|
||||||
|
# оценка качества:
|
||||||
|
target_test_predicted = pipeline.predict(df_orig_features_test)
|
||||||
|
# метрика качества (MAPE):
|
||||||
|
mape = sklearn.metrics.mean_absolute_percentage_error(df_target_test, target_test_predicted)
|
||||||
|
return mape
|
||||||
|
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# optuna study:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
optuna_sampler = optuna.samplers.TPESampler(seed=0x0A1C)
|
||||||
|
optuna_study = optuna.create_study(sampler=optuna_sampler, direction='minimize')
|
||||||
|
optuna_study.optimize(regressor_hyperparams_objective, n_trials=24)
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Количество выполненных trials:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
len(optuna_study.trials)
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Лучшие найдённые гиперпараметры:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
repr(optuna_study.best_params)
|
||||||
|
|
||||||
|
# %%
|
||||||
|
regressor_best_params = dict(optuna_study.best_params.items())
|
||||||
|
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Составной пайплайн:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
def build_pipeline_optimized_best():
|
||||||
|
return build_pipeline(
|
||||||
|
regressor_best_params['n_estimators'],
|
||||||
|
regressor_max_depth=regressor_best_params['max_depth'],
|
||||||
|
regressor_max_features=regressor_best_params['max_features'],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# %%
|
||||||
|
pipeline = build_pipeline_optimized_best()
|
||||||
|
pipeline
|
||||||
|
|
||||||
|
# %%
|
||||||
|
model_params = filter_params(
|
||||||
|
pipeline.get_params(),
|
||||||
|
include={
|
||||||
|
'preprocess': (False, PREPROCESS_AUGMENTING_TRANSFORMER_PARAMS_COMMON_INCLUDE.copy()),
|
||||||
|
'select_features': (False, FEATURE_SELECTOR_PARAMS_COMMON_INCLUDE.copy()),
|
||||||
|
'regress': (False, True),
|
||||||
|
},
|
||||||
|
exclude={
|
||||||
|
'preprocess': PREPROCESS_AUGMENTING_TRANSFORMER_PARAMS_COMMON_EXCLUDE.copy(),
|
||||||
|
'select_features': FEATURE_SELECTOR_PARAMS_COMMON_EXCLUDE,
|
||||||
|
'regress': RANDOM_FOREST_REGRESSOR_PARAMS_COMMON_EXCLUDE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
model_params
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Обучение модели:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
_ = pipeline.fit(df_orig_features_train, df_target_train.iloc[:, 0])
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Оценка качества:
|
||||||
|
|
||||||
|
# %%
|
||||||
|
target_test_predicted = pipeline.predict(df_orig_features_test)
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Метрики качества (MAPE, а также MSE, MAE):
|
||||||
|
|
||||||
|
# %%
|
||||||
|
metrics = score_predictions(df_target_test, target_test_predicted)
|
||||||
|
metrics
|
||||||
|
|
||||||
|
# %%
|
||||||
|
mlflow_log_model(
|
||||||
|
pipeline,
|
||||||
|
model_params=model_params,
|
||||||
|
metrics={k: float(v) for k, v in metrics.items()},
|
||||||
|
nested_run_name='Optimized model with filtered engineered features',
|
||||||
|
model_signature=mlflow_model_signature,
|
||||||
|
input_example=df_orig_features.head(MODEL_INOUT_EXAMPLE_SIZE),
|
||||||
|
pip_requirements=MODEL_PIP_REQUIREMENTS_PATH,
|
||||||
|
#global_comment_file_path=(
|
||||||
|
# model_comment_path
|
||||||
|
# if model_comment_path is not None
|
||||||
|
# else (BASE_PATH / 'research' / model_comment_relpath)
|
||||||
|
#),
|
||||||
|
extra_logs_handler=(build_extra_logs_handler_selected_columns_from_sequential_feature_selector(pipeline.named_steps['select_features']),),
|
||||||
|
)
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# ### И в продакшн
|
||||||
|
|
||||||
|
# %% [markdown]
|
||||||
|
# Лучшая выбранная модель — с автоматически подобранными гиперпараметрами.
|
||||||
|
|
||||||
|
# %%
|
||||||
|
pipeline = build_pipeline_optimized_best()
|
||||||
|
pipeline
|
||||||
|
|
||||||
|
# %%
|
||||||
|
model_params = filter_params(
|
||||||
|
pipeline.get_params(),
|
||||||
|
include={
|
||||||
|
'preprocess': (False, PREPROCESS_AUGMENTING_TRANSFORMER_PARAMS_COMMON_INCLUDE.copy()),
|
||||||
|
'select_features': (False, FEATURE_SELECTOR_PARAMS_COMMON_INCLUDE.copy()),
|
||||||
|
'regress': (False, True),
|
||||||
|
},
|
||||||
|
exclude={
|
||||||
|
'preprocess': PREPROCESS_AUGMENTING_TRANSFORMER_PARAMS_COMMON_EXCLUDE.copy(),
|
||||||
|
'select_features': FEATURE_SELECTOR_PARAMS_COMMON_EXCLUDE,
|
||||||
|
'regress': RANDOM_FOREST_REGRESSOR_PARAMS_COMMON_EXCLUDE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
model_params
|
||||||
|
|
||||||
|
# %%
|
||||||
|
_ = pipeline.fit(df_orig_features, df_target.iloc[:, 0])
|
||||||
|
|
||||||
|
# %%
|
||||||
|
mlflow_log_model(
|
||||||
|
pipeline,
|
||||||
|
model_params=model_params,
|
||||||
|
metrics=None,
|
||||||
|
nested_run_name='Final model',
|
||||||
|
model_signature=mlflow_model_signature,
|
||||||
|
input_example=df_orig_features.head(MODEL_INOUT_EXAMPLE_SIZE),
|
||||||
|
pip_requirements=MODEL_PIP_REQUIREMENTS_PATH,
|
||||||
|
#global_comment_file_path=(
|
||||||
|
# model_comment_path
|
||||||
|
# if model_comment_path is not None
|
||||||
|
# else (BASE_PATH / 'research' / model_comment_relpath)
|
||||||
|
#),
|
||||||
|
extra_logs_handler=(build_extra_logs_handler_selected_columns_from_sequential_feature_selector(pipeline.named_steps['select_features']),),
|
||||||
|
)
|
||||||
|
|
||||||
|
# %%
|
||||||
14
run_mlflow_server.ps1
Обычный файл
14
run_mlflow_server.ps1
Обычный файл
@@ -0,0 +1,14 @@
|
|||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
. $PSScriptRoot\_mlflow_config_common.ps1
|
||||||
|
|
||||||
|
$DEFAULT_ARTIFACT_ROOT = "./mlflow/mlartifacts/"
|
||||||
|
|
||||||
|
$MLFLOW_PORT = if ($env:MLFLOW_PORT) { $env:MLFLOW_PORT } else { 5000 }
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Force -Path $DEFAULT_ARTIFACTS_ROOT | Out-Null
|
||||||
|
|
||||||
|
& mlflow server `
|
||||||
|
--backend-store-uri="$BACKEND_URI" `
|
||||||
|
--default-artifact-root="$DEFAULT_ARTIFACT_ROOT" `
|
||||||
|
-p $MLFLOW_PORT
|
||||||
16
run_mlflow_server.sh
Исполняемый файл
16
run_mlflow_server.sh
Исполняемый файл
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
. _mlflow_config_common.sh
|
||||||
|
|
||||||
|
DEFAULT_ARTIFACT_ROOT="./mlflow/mlartifacts/"
|
||||||
|
|
||||||
|
: "${MLFLOW_PORT:=5000}"
|
||||||
|
|
||||||
|
mkdir -p "${DEFAULT_ARTIFACT_ROOT}"
|
||||||
|
|
||||||
|
exec mlflow server \
|
||||||
|
--backend-store-uri="$BACKEND_URI" \
|
||||||
|
--default-artifact-root="$DEFAULT_ARTIFACT_ROOT" \
|
||||||
|
-p "$MLFLOW_PORT"
|
||||||
Ссылка в новой задаче
Block a user