This tutorial for the development and the provable deployment of the binary liquidation prediction model for Aave v2 & v3 protocols are meant to showcase the many capabilities of the Giza Stack (including Datasets, CLI and Actions).
Before delving deeper into the tutorial itself, we recommend you to check the documentations for Giza Datasets, Giza CLI and Giza Actions to be more familiar with the topics discussed as well as the codebase.
The project is structured along two alternatives paths. You can either use the follow the Jupyter Notebooks, first aave_liquidations_model_notebook.ipynb then aave_liquidations_actions_notebook.ipynb to build, deploy and run verifiable inferences iteratively, or use the python scripts directly. They both run identically, however python scripts assumes that you already have login to your giza user and giza workspace successfully.
Installation
The project uses the Poetry Dependency Manager, which makes installing the required packages a breeze. To install the required packages, simply execute the following command from the root of the project.
poetryinstall
For more information on how to install the Giza Datasets, Giza CLI and Giza Actions, check out their respective installation guides.
Data loading
For the seamless querying of curated DeFi datasets relevant for our task, we are leveraging the full capabilities of Giza Datasets SDK to query and load datasets.
For more information about the individual datasets being used, as well as additional datasets that you might find useful, check out the Giza Datasets Hub.
The general purpose of the data preprocessing step is to transform the loaded models into processed datasets ready for model training. Although we wont be explaining each individual preproccesing step here, it is interesting to look at one code snippet in detail.
As you can see, the given preprocessing step is decorated with the @task decorator (to learn more about the use of @task and other useful decorators, see the following documentation):
With the datasets split into the test and training, we are ready to create and train the models!
Model Development
classFeedForwardNN(nn.Module):def__init__(self,input_size,hidden_size1,hidden_size2,output_size):super(FeedForwardNN, self).__init__() self.fc1 = nn.Linear(input_size, hidden_size1) self.relu = nn.ReLU() self.fc2 = nn.Linear(hidden_size1, hidden_size2) self.relu = nn.ReLU() self.fc3 = nn.Linear(hidden_size2, output_size) self.sigmoid = nn.Sigmoid()defforward(self,x): x = self.relu(self.fc1(x)) x = self.relu(self.fc2(x)) x = self.sigmoid(self.fc3(x))return x
As you can see, the model is a very simple 3 Layer Feedforward Neural Network. We are going to test prediction on two different models, one with 7 day lagged features and one with 3 day lagged features.
input_size =10hidden_size1 =32hidden_size2 =16output_size =1# Create an instance of the feedforward neural networkmodel_3day =FeedForwardNN(input_size, hidden_size1, hidden_size2, output_size)model_7day =FeedForwardNN(input_size, hidden_size1, hidden_size2, output_size)
Training
The two models are trained with a k-fold validation scheme to monitor potential overfitting. Since we use the @task decorator here, we will be able to measure relevant metrics for our training session, such as time spent, training error etc.
Since our models are binary classifiers, we are interested in accuracy, recall, precision and f1, in addition to the validation performances.
@task(name=f'Model Training and Evaluation')deftrain_and_evaluate(model,X_train,Y_train,X_test,Y_test,num_epochs=30,batch_size=32):# Train the model with X_train cv =train_model(model, X_train, Y_train, num_epochs, batch_size, num_folds=5)# Set the model to evaluation mode model.eval() X_test_tensor = torch.tensor(X_test.values, dtype=torch.float32) pred =model(X_test_tensor)# Convert the predictions to binary values pred_binary = (pred >=0.5).float()# Convert the actual values to binary values Y_test_binary = torch.tensor(Y_test.values, dtype=torch.float32)# Calculate the metrics for X_test accuracy =accuracy_score(Y_test_binary, pred_binary) precision =precision_score(Y_test_binary, pred_binary) recall =recall_score(Y_test_binary, pred_binary) f1 =f1_score(Y_test_binary, pred_binary) cv_avg = np.mean(cv) cv_std = np.std(cv)return{'accuracy': accuracy,'precision': precision,'recall': recall,'f1': f1,'cv': cv,'cv_avg': cv_avg,'cv_std': cv_std}
Metrics for X7_test:
Accuracy: 0.7348242811501597
Precision: 0.7
Recall: 0.15384615384615385
F1-score: 0.25225225225225223
The performance of the models is clearly not good for production! Some comments on the reasons behind this, as well as potential solutions:
The overall f1 score is significantly bad, mostly because of the low recall value. This implies that there is a significant number of liquidations in the test set that the model fails to predict accurately.
There are clear signals of market momentum having a very high impact on the occurance rate of predictions, that we dont represent with our current selection of features.
Additionally, having a model that is able to process temporal data rather than tabular data would significantly increase the performance of the model.
Since the number of days with liquidations is relatively low compared to those without liquidations, oversampling the data with liquidations might also improve the end result.
Execution
We've already seen the model results and some preprocessing steps. However, let's see what the final model development method would look like:
If you have followed the "Build a Verifiable Neural Network with Giza Actions" tutorial that we recommended in the introduction, you will already be familiar with Giza CLI, ONNX, and how to transpile and deploy our model.
To proceed with these steps quickly, the first thing we need to do is execute our train_action.py. This script will train the model based on the dataset and preprocessing steps we've discussed:
pythonaave_liquidations_model.py
Now, we will transpile it:
giza transpile model_7day.onnx
Deploy it:
giza deployments deploy --model-id <MODEL_ID> --version-id <VERSION_ID>