Package: datasette-ml
Datasette MLBringing Machine Learning models near your data, not the other way around!Datasette ML is aDatasetteplugin providing an MLOps
platform to train, evaluate and make predictions from machine learning models.All the underlying features are provided bysqlite-ml.WARNING: this plugin is still experimental and not ready for production.
Some breaking changes might happen between releases before reaching a stable version.
Use it at your own risks!InstallationInstall this plugin in the same environment as Datasette:$datasetteinstalldatasette-mlUsageDefine configuration withinmetadata.yml/metadata.json:plugins:datasette-ml:db:sqmlA new menu entry is now available, pointing at/-/mlto access the MLOps dashboard.Configuration propertiesPropertyTypeDescriptiondbstringDatabase to store ML models (default issqml)TutorialUsingdatasette-mlyou can start training Machine Learning models directly
along your data, simply by using custom SQL functions! Let's get started by
training a classifier against the famous "Iris Dataset" to predict flower types.Loading the datasetFirst let's load our data. For a real world project, your data may live with its
own table or being accessed through an SQL view. For the purpose of this tutorial,
we can use thesqml_load_datasetfunction to loadstandard Scikit-Learn datasets:SELECTsqml_load_dataset('iris')ASdataset;It will return the following data:dataset{"table": "dataset_iris", "feature_names": ["sepal length (cm)", "sepal width (cm)", "petal length (cm)", "petal width (cm)"], "target_names": ["setosa", "versicolor", "virginica"], "size": 150}The Iris dataset is loaded into a table nammeddataset_iris,
containing 150 examples, 4 features and 3 classes to be predicted.Training a classifierNow that our dataset is ready, let's train a first machine learning model to
perform a classification task using thesqml_trainfunction:SELECTsqml_train('Iris prediction','classification','logistic_regression','dataset_iris','target')AStraining;It will return the following data:training{"experiment_name": "Iris prediction", "prediction_type": "classification", "algorithm": "logistic_regression", "deployed": true, "score": 0.9473684210526315}We have just trained our first machine learning model! The output data informs us
that our model has been trained, yields a score of 0.94 and has been deployed.Performing predictionsNow that we have trained our classifier, let's use it to make predictions!Predict the target label for the first row ofdataset_irisusing thesqml_predictfunction:SELECTdataset_iris.*,sqml_predict('Iris prediction',json_object('sepal length (cm)',[sepallength(cm)],'sepal width (cm)',[sepalwidth(cm)],'petal length (cm)',[petallength(cm)],'petal width (cm)',[petalwidth(cm)]))ASpredictionFROMdataset_irisLIMIT1;This will output the following data:sepal length (cm)sepal width (cm)petal length (cm)petal width (cm)targetprediction5.13.51.40.20.00.0Yay! Our prediction is matching the target label!Let's see if we can find some predictions not matching the target label.
To perform lots of predictions, we will usesqml_predict_batchwhich is more
efficient thansqml_predict:SELECTdataset_iris.*,batch.valueASprediction,dataset_iris.target=batch.valueASmatchFROMdataset_irisJOINjson_each((SELECTsqml_predict_batch('Iris prediction',json_group_array(json_object('sepal length (cm)',[sepallength(cm)],'sepal width (cm)',[sepalwidth(cm)],'petal length (cm)',[petallength(cm)],'petal width (cm)',[petalwidth(cm)])))FROMdataset_iris))batchON(batch.rowid+1)=dataset_iris.rowidWHEREmatch=FALSE;This will yield the following output data:sepal length (cm)sepal width (cm)petal length (cm)petal width (cm)targetpredictionmatch5.93.24.81.81.02.006.73.05.01.71.02.006.02.75.11.61.02.004.92.54.51.72.01.00Oh no! 4 predictions have not predicted the correct target label!Let's see if we can train a better algorithm to enhance the prediction quality.Training a new modelLet's use a Support Vector Machine algorithm, usually yielding better results
compared to the more simplistic Logistic Regression:SELECTsqml_train('Iris prediction','classification','svc','dataset_iris','target')AStraining;This will yield the following data:training{"experiment_name": "Iris prediction", "prediction_type": "classification", "algorithm": "svc", "deployed": true, "score": 0.9736842105263158}We can already see that the score of this new model is higher than the previous one and it has been deployed.Let's try our new classifier on the same dataset:SELECTdataset_iris.*,batch.valueASprediction,dataset_iris.target=batch.valueASmatchFROMdataset_irisJOINjson_each((SELECTsqml_predict_batch('Iris prediction',json_group_array(json_object('sepal length (cm)',[sepallength(cm)],'sepal width (cm)',[sepalwidth(cm)],'petal length (cm)',[petallength(cm)],'petal width (cm)',[petalwidth(cm)])))FROMdataset_iris))batchON(batch.rowid+1)=dataset_iris.rowidWHEREmatch=FALSE;This will lead the following results:sepal length (cm)sepal width (cm)petal length (cm)petal width (cm)targetpredictionmatch5.93.24.81.81.02.006.73.05.01.71.02.006.02.75.11.61.02.00Yay! We manage to predict one more target label with this new model!Also note that we did not have to do anything to switch to the better model:
exactly the same query is used to perform the prediction without having to
specify anything about the new model! This is because new models are deployed
automatically for the current experiment only if their score outperforms the
score of the previously deployed model.SQL functionsThis plugin registers a few SQL functions to perform machine learning model training and predictions:sqml_load_dataset(name, table)name: str: name of the dataset to loadtable: str: (optional) custom table name destination for the datasetsqml_train(experiment_name, prediction_type, algorithm, dataset, target, test_size, split_strategy):experiment_name: str: name of the experiment to train the model withinprediction_type: str: prediction task type to be performed for this experiment (regression,classification)algorithm: str: algorithm type to be traineddataset: str: name of the table or view containing the datasettarget: str: name of the column to be treated as target labeltest_size: float: (optional) dataset test size ratio (default is0.25)split_strategy: str: (optional) dataset train/test split strategy (default isshuffle)sqml_predict(experiment_name, features)experiment_name: str: name of the experiment to train the model withinfeatures: json object: JSON object containing the featuressqml_predict_batch(experiment_name, features)experiment_name: str: name of the experiment to train the model withinfeatures: json list: JSON list containing all feature objectsDevelopmentTo set up this plugin locally, first checkout the code.
Then create a new virtual environment and the required dependencies:poetryshell
poetryinstallTo run the QA suite:black--checkdatasette_mltests
flake8datasette_mltests
mypydatasette_mltests
pytest-v--cov=datasette_ml--cov=tests--cov-branch--cov-report=term-missingtestsDemoWith the developmnent environment setup, you can run the demo locally:pythondemo/generate.py
datasette--metadatademo/metadata.ymldemo/sqml.dbInspirationAll the things on the internet that have been inspiring this project:PostgresMLMLFlowSQLite  Run-Time Loadable ExtensionsAlex Garcia'ssqlite-loadable-rsAlex Garcia's SQLite extensionsAlex Garcia, "Making SQLite extensions pip install-able"Max Halford, "Online gradient descent written in SQL"Ricardo Anderegg, "Extending SQLite with Rust"LicenseLicensed under Apache License, Version 2.0Copyright (c) 2023 - present Romain Clement