hexabot/docs/developer-guide/hexabot-nlu-engine.md

164 lines
5.6 KiB
Markdown
Raw Normal View History

2024-09-13 15:48:51 +00:00
# Hexabot NLU Engine
The [Hexabot](https://hexabot.ai/) NLU (Natural Language Understanding) engine is a Python-based project that provides tools for building, training, and evaluating machine learning models for natural language tasks such as intent detection and language recognition. It also includes a REST API for inference, built using FastAPI.
### Directory Structure
* **/run.py:** The CLI tool that provides commands for training, evaluating, and managing models.
* **/models:** Contains the different model definitions and logic for training, testing, and evaluation.
* **/data:** Placeholder for datasets used during training and evaluation.
* **/experiments:** Placeholder for stored models generated during training.
* **/data\_loaders:** Classes that define the way to load datasets to be used by the different models.
* **/main.py:** The FastAPI-based REST API used for inference, exposing endpoints for real-time predictions.
### Setup
**No dependencies needed besides Python 3.11.6, virtualenv, and TensorFlow.** Start developing your new model on top of this workflow by cloning this repository:
```bash
# Set up a virtualenv
pip install virtualenv
python3.11 -m venv venv
source env.sh
pip install -r requirements.txt
```
### Directory structure
* `data`: gitignore'd, place datasets here.
* `experiments`: gitignore'd, trained models written here.
* `data_loaders`: write your data loaders here.
* `models`: write your models here.
### Usage
**Check `models/mlp.py` and `data_loaders/mnist.py` for fully working examples.**
You should run `source env.sh` on each new shell session. This activates the virtualenv and creates a nice alias for `run.py`:
```bash
$ cat env.sh
source env/bin/activate
alias run='python run.py'
```
Most routines involve running a command like this:
```bash
# Usage: run [method] [save_dir] [model] [data_loader] [hparams...]
run fit myexperiment1 mlp mnist --batch_size=32 --learning_rate=0.1
```
Examples :
```bash
# Intent classification
run fit intent-classifier-en-30072024 jisf --intent_num_labels=88 --slot_num_labels=17 --language=en
run predict intent-classifier-fr-30072024 --intent_num_labels=7 --slot_num_labels=2 --language=fr
# Language classification
run fit language-classifier-26082023 tflc
run predict language-classifier-26082023
run evaluate language-classifier-26082023
```
where the `model` and `data_loader` args are the module names (i.e., the file names without the `.py`). The command above would run the Keras model's `fit` method, but it could be any custom as long as it accepts a data loader instance as argument.
**If `save_dir` already has a model**:
* Only the first two arguments are required and the data loader may be changed, but respecifying the model is not allowed-- the existing model will always be used.
* Specified hyperparameter values in the command line WILL override previously used ones (for this run only, not on disk).
#### `tfbp.Model`
Models pretty much follow the same rules as Keras models with very slight differences: the constructor's arguments should not be overriden (since the boilerplate code handles instantiation), and the `save` and `restore` methods don't need any arguments.
```python
import tensorflow as tf
import boilerplate as tfbp
@tfbp.default_export
class MyModel(tfbp.Model):
default_hparams = {
"batch_size": 32,
"hidden_size": 512,
"learning_rate": 0.01,
}
# Don't mess with the args and keyword args, `run.py` handles that.
def __init__(self, *a, **kw):
super().__init__(*a, **kw)
self.dense1 = tf.keras.layers.Dense(self.hparams.hidden_size)
...
def call(self, x):
z = self.dense1(x)
...
```
You can also write your own training loops à la pytorch by overriding the `fit` method or writing a custom method that you can invoke via `run.py` simply by adding the `@tfbp.runnable` decorator. Examples of both are available in `models/mlp.py`.
#### `tfbp.DataLoader`
Since model methods invoked by `run.py` receive a data loader instance, you may name your data loader methods whatever you wish and call them in your model code. A good practice is to make the data loader handle anything that is specific to a particular dataset, which allows the model to be as general as possible.
```python
import tensorflow as tf
import boilerplate as tfbp
@tfbp.default_export
class MyDataLoader(tfbp.DataLoader):
default_hparams = {
"batch_size": 32,
}
def __call__(self):
if self.method == "fit":
train_data = tf.data.TextLineDataset("data/train.txt").shuffle(10000)
valid_data = tf.data.TextLineDataset("data/valid.txt").shuffle(10000)
return self.prep_dataset(train_data), self.prep_dataset(valid_data)
elif self.method == "eval":
test_data = tf.data.TextLineDataset("data/test.txt")
return self.prep_dataset(test_data)
def prep_dataset(self, ds):
return ds.batch(self.hparams.batch_size).prefetch(1)
```
#### API
API is built using FastAPI : https://fastapi.tiangolo.com/
Run the dev server in standalone with:
```sh
ENVIRONMENT=dev uvicorn main:app --host 0.0.0.0 --port 5000 --reload
```
Run the project with Docker :
```sh
docker compose -f "docker-compose.yml" up -d --build
```
### Pushing models to HuggingFace
Please refer to official HF documentation on how to host models : https://huggingface.co/docs/hub/en/repositories-getting-started
What is important to note is that big files should be tracked with git-lfs, which you can initialize with:
```
git lfs install
```
and if your files are larger than 5GB youll also need to run:
```
huggingface-cli lfs-enable-largefiles .
```