# Introduction

SurrealML stores and runs trained ML models in SurrealDB - train in Python, then load and infer with sklearn or PyTorch.

SurrealML is an engine that seeks to do one thing, and one thing well: store and execute trained ML models. SurrealML does not intrude on the training frameworks that are already out there, instead works with them to ease the storage, loading, and execution of models. Someone using SurrealML will be able to train their model in a chosen framework in Python, save their model, and load and execute the model in either Python or Rust. The inference engine is composed of python bindings interacting with a core written in Rust. This means that the exact same code that the Python client calls will be running on a production node by itself, or in the database. While the SurrealML engine runs in the database, it is developed in a completely isolated GitHub repository, giving the user 100% freedom on how they deploy and interact with the SurrealML engine.

While this is all exciting, let us check to see if SurrealML works on your machine. There is nothing worse than reading about a package only to find that it does not work. The first move is to install the package.

## Installation

To install SurrealML, make sure you have Python installed. Then, install the `SurrealML` library and either `PyTorch` or `sklearn`, based on your model choice. You can install the package with both `PyTorch` and `SKLearn` with the command below:

```bash
pip install "git+https://github.com/surrealdb/surrealml#egg=surrealml[sklearn,torch]"
```

If you want to use `SurrealML` with `sklearn` you will need the following installation:

```bash
pip install "git+https://github.com/surrealdb/surrealml#egg=surrealml[sklearn]"
```

For `PyTorch`:

```bash
pip install "git+https://github.com/surrealdb/surrealml#egg=surrealml[torch]"
```

Once the package is installed, you can then train and save your first model using sklearn.

## Quick start with Sklearn

Sklearn models can also be converted and stored in SurrealML’s `.surml` format enabling developers to load them in any Python version as we are not relying on `pickle`. Metadata in the file also enables other users of the model to use them out of the box without having to worry about the normalisation of the data or getting the right inputs in order. We will cover `.surml` files in more depth in the storage section. You will also be able to load your Sklearn models in Rust and run them, meaning you can use them in your SurrealDB server.

Before we start writing any training code we need to import the following:

```python
from sklearn.linear_model import LinearRegression
from surrealml import SurMlFile, Engine
from surrealml.model_templates.datasets.house_linear import HOUSE_LINEAR
```

Here we can see that we have imported the standard `sklearn` `LinearRegression` model. We then import the `SurMlFile` object which will facilitate the saving, loading, and execution of the trained model. We will use the `Engine` enum to tell our `SurMlFile` object if we are using `sklearn` or `torch`. We then finally import a small example dataset called `HOUSE_LINEAR`. This example dataset is a simple linear correlation between house prices, the square foot of the house, and the number of floors. This dataset is also used in the CI testing pipeline when we push updates.

Now that we have imported everything that we need, we can train our model with the code below:

```python
model = LinearRegression()
model.fit(HOUSE_LINEAR["inputs"], HOUSE_LINEAR["outputs"])
```

This will give us a trained model. Now we need to save it and this is where `SurrealML` comes in. First we declare a `SurMlFile` object instance with the inputs, name, model object, and engine with the following code:

```python
file = SurMlFile(
	model=model,
	name="house-price-prediction",
	inputs=HOUSE_LINEAR["inputs"],
	engine=Engine.SKLEARN
)

file.add_version(version="0.0.1")
```

The next step is optional, but it would be nice to map our inputs to some keys. We must be careful with the order that we declare our columns as they need to be mapped with the order or inputs from the vector that our model was trained on. If you click on `HOUSE_LINEAR` you will see the following declaration:

```python
HOUSE_LINEAR = {
    "inputs": inputs,
    "outputs": house_price,

    "squarefoot": squarefoot,
    "num_floors": num_floors,
    "input order": ["squarefoot", "num_floors"],
    "raw_inputs": {
        "squarefoot": raw_squarefoot,
        "num_floors": raw_num_floors,
    },
    "normalised_inputs": {
        "squarefoot": squarefoot,
        "num_floors": num_floors,
    },
    "normalisers": {
        "squarefoot": {
            "type": "z_score",
            "mean": squarefoot.mean(),
            "std": squarefoot.std()
        },
        "num_floors": {
            "type": "z_score",
            "mean": num_floors.mean(),
            "std": num_floors.std()
        }
    },
}
```

Here we can see that there are some normalisers involved. We can also see that the input order for the model training was `["squarefoot", "num_floors"]` . Therefore, we can add the column names to our `SurMlFile` object instance with the code below:

```python
file.add_column("squarefoot")
file.add_column("num_floors")
```

The `add_column` was the only order that we have to be careful about. We need to add our normalisers to the `SurMlFile` object instance with the code below but we do not have to worry about the order as the normalisers by default will be mapped to the columns:

```python
file.add_normaliser(
	"squarefoot",
	"z_score",
	HOUSE_LINEAR["squarefoot"].mean(),
	HOUSE_LINEAR["squarefoot"].std()
)

file.add_normaliser(
	"num_floors",
	"z_score",
	HOUSE_LINEAR["num_floors"].mean(),
	HOUSE_LINEAR["num_floors"].std()
)
```

We are nearly done with adding metadata, with just one item left to add: the output that the model is trying to predict. This can be achieved by the following code:

```python
file.add_output(
	"house_price",
	"z_score",
	HOUSE_LINEAR["outputs"].mean(),
	HOUSE_LINEAR["outputs"].std()
)
```

And now our file is ready to be saved which is done with the code below:

```python
file.save(path="./linear.surml")
```

The file is stored in the `.surml` format meaning that there is a header with the data that we defined, and the weights are stored in the `ONNX` format. This means that there is zero language dependent dependencies. We are now ready to load and perform calculations on our model. We can load our model with the following code:

```python
new_file = SurMlFile.load(path="./linear.surml", engine=Engine.SKLEARN)
```

If you are confident in what you are doing at this point, you can choose to perform calculations through `surrealML` using a raw compute in which the raw vector of inputs is directly passed into the model with the code below:

```python
print(new_file.raw_compute(input_vector=[5, 6]))
```

However, if you want the normalisation to be automatically applied, and inputs mapped via keys, we can use a buffered compute with the following code:

```python
print(
	new_file.buffered_compute(
		value_map={
			"squarefoot": 5,
			"num_floors": 6
		}
	)
)
```

Both types of executions are executing the ML model using the Rust engine under the hood. As a result, the exact same code will be running your model in SurrealDB or your inference server regardless of whether you choose to build a server in Python or Rust. This `.surml` file can be also be loaded by either Rust or any Python version that has surrealML and execute inference installed.

Now that we are able to load and execute our model locally, how do we deploy our model onto SurrealDB and run it? In the next section, we cover uploading.

## Model deployment

Before we try and upload our model, we need to have a node running. We can do with the `docker-compose.yml` file below:

```yaml
version: '3'
services:
  surrealdb:
    image: surrealdb/surrealdb
    command: start
    environment:
      - SURREAL_USER=root
      - SURREAL_PASS=root
      - SURREAL_LOG=trace
    ports:
      - 8000:8000
```

Once our node is running via docker, we can then upload our trained model with the following code:

```python
url = "http://0.0.0.0:8000/ml/import"
SurMlFile.upload(
    path="./linear.surml",
    url=url,
    chunk_size=36864,
    namespace="main",
    database="main",
    username="root",
    password="secret"
)

```

The `upload` function will chunk the model and stream it up to a SurrealDB node. We can then perform an execution of the model with the following SurrealQL function:

```python
ml::house-price-prediction<0.0.1>({
	squarefoot: 500.0,
	num_floors: 2.0
})
```

Here, `house-price-prediction` is the name of the model. The `<0.0.1>` is the version of the model. The SurrealQL function above will give us a model output from the inputs that we passed in.

We can now explore how our model can interact with other data with the SurrealQL script below:

```sql
CREATE house_listing SET squarefoot_col = 500.0, num_floors_col = 1.0;
CREATE house_listing SET squarefoot_col = 1000.0, num_floors_col = 2.0;
CREATE house_listing SET squarefoot_col = 1500.0, num_floors_col = 3.0;

SELECT * FROM (
	SELECT *,
	ml::house_price_prediction<0.0.1>({
		squarefoot: squarefoot_col,
		num_floors: num_floors_col
	}) AS price_prediction
	FROM house_listing
)
WHERE price_prediction > 177206.21875;
```

What is happening here is that we are feeding the columns from the table `house_listing` into a model we uploaded called `house-price-prediction` with a version of `0.0.1`. We then get the results of that trained ML model as the column `price_prediction`. We then use the calculated predictions to filter the rows giving us the following result:

```json
[
  {
    "id": "house_listing:7bo0f35tl4hpx5bymq5d",
    "num_floors_col": 3,
    "price_prediction": 406534.75,
    "squarefoot_col": 1500
  },
  {
    "id": "house_listing:8k2ttvhp2vh8v7skwyie",
    "num_floors_col": 2,
    "price_prediction": 291870.5,
    "squarefoot_col": 1000
  }
]
```

Having covered everything that we need to get up and running with SurrealML, we should explore some other concepts in more depth to get the most out of SurrealML and be able to troubleshoot problems.
