0. Getting Started with TuiML¶
This notebook is the front door. It shows what TuiML is, trains a model in five lines, and lays out the two ways you can drive the library — as a Python developer, and as someone talking to an AI agent. Everything here is revisited in depth later; the goal right now is to give you the shape of the thing.
You will learn:
- what problem TuiML solves, and what it deliberately does not
- how to train and evaluate a model in one call
- the three API levels, and when each one is the right altitude
- how to search a catalog of 189 components instead of memorising class names
- what it means that an agent can drive all of this
Prerequisites: Python, and enough NumPy to know what an array is. No machine learning background is assumed — where a concept matters, this book explains it.
0.1 What TuiML is¶
TuiML is a machine learning library for tabular data — the rows-and-columns kind that comes out of a database export, a CSV, or a spreadsheet. It covers the whole path from a raw file to a served model: loading, cleaning, feature engineering, training, evaluation, statistical comparison, and deployment.
Two things make it different from the libraries you may already know.
Everything is named, not imported. A model, a scaler, a feature selector and a cross-validation splitter are all components registered in a central hub under a string name. You write "RandomForestClassifier", not from tuiml.algorithms.trees import RandomForestClassifier. That sounds like a small ergonomic detail. It is actually the design decision the rest of the library hangs off, and section 0.5 explains why.
Experiments are data. A complete experiment — the model, its hyperparameters, the preprocessing pipeline, the evaluation strategy, the random seed — is one dictionary. Dictionaries can be saved, diffed, version-controlled, sent over a network, and generated by something that is not a human. That last one is the point.
What TuiML is not: a deep learning framework. There is a multilayer perceptron in the catalog, but if you are training transformers, you want PyTorch. TuiML is for the enormous amount of real-world machine learning that is still tables of numbers and categories.
0.2 Installing¶
TuiML needs a C++ compiler, because the performance-critical inner loops — tree splitting, distance computation — are compiled rather than interpreted. There is no pure-Python fallback; this is a deliberate choice, so that the fast path is the only path and cannot silently degrade.
pip install tuiml
Optional extras bring in wrappers around other libraries. They are optional in the real sense: TuiML never depends on them, and everything native works without them.
pip install "tuiml[sklearn]" # scikit-learn estimators, as TuiML components
pip install "tuiml[capymoa]" # CapyMOA streaming learners
Check the install:
import tuiml
print("TuiML", tuiml.__version__)
TuiML 0.2.0
0.3 Your first model¶
Here is a complete experiment: load a dataset, train a random forest, and evaluate it with 5-fold cross-validation.
We use the Pima Indians Diabetes dataset, which ships with the library. It has 768 patient records, 8 clinical measurements each, and a binary outcome: did this person test positive for diabetes. It is small, real, and — as chapter 1 will show — quietly messy, which makes it a much better teacher than a clean synthetic dataset.
model = tuiml.train({
"model": {"name": "RandomForestClassifier", "params": {"n_estimators": 100}},
"data": "diabetes",
"evaluation": {"cv": 5},
"random_seed": 42,
})
model.metrics_
{'cv_accuracy_score_mean': 0.7733978439860792,
'cv_accuracy_score_std': 0.02446911145396197,
'cv_f1_score_mean': 0.6508349491835137,
'cv_f1_score_std': 0.03911759869692709}
That is the whole thing. Read the dictionary back as English: train a random forest of 100 trees, on the diabetes dataset, scored by 5-fold cross-validation, seeded at 42.
The numbers say the model is right about 77% of the time, with a standard deviation of about 2 points across the five folds. Two remarks on that, both of which the book will return to:
Remark — a single number is not a result. The
_stdmatters as much as the_mean. A model scoring 0.77 ± 0.02 and a model scoring 0.77 ± 0.15 are not the same model, and reporting only the mean hides that. Chapter 2 covers why cross-validation gives you the spread, and chapter 8 covers how to decide whether the gap between two models is real or noise.
Remark — accuracy is a bad metric here. 65% of these patients tested negative, so a model that ignores its input and always predicts "negative" scores 65%. Our 77% is better than that, but the margin is smaller than the raw number suggests. This is why
f1_scoreappears alongside it in the output, and why chapter 2 is about evaluation rather than about models.
0.4 The fitted model is a working object¶
train() hands back a fitted pipeline. It behaves like a model — predict with it, score it, save it, serve it over HTTP.
from tuiml.datasets import load_diabetes
data = load_diabetes()
# Predict on the first five patients.
predictions = model.predict(data.X[:5])
print("predicted:", predictions)
print("actual: ", data.y[:5])
predicted: [1 0 1 0 1] actual: [1 0 1 0 1]
# Class probabilities, not just the decision.
probabilities = model.predict_proba(data.X[:3])
print(probabilities.round(3))
[[0.2 0.8 ] [0.99 0.01 ] [0.179 0.821]]
The probabilities are worth a pause. The model does not really answer "diabetic or not" — it answers "how confident am I", and something downstream turns that into a yes or no by cutting at 0.5. That threshold is a choice, not a law, and in a medical screening context it is usually the wrong one: missing a positive case costs far more than a false alarm. Chapter 2 comes back to this.
0.5 Three levels of API¶
TuiML exposes the same machinery at three altitudes. None is more "real" than the others — they compile down to the same objects — and you will move between them constantly.
Level 1 — declarative. One dictionary describes the run. This is what you just used.
Level 2 — pipelines. Workflow takes an ordered list of steps and a model. Use it when you want to build a pipeline out of configured objects and inspect it as you go.
Level 3 — direct objects. Import the class, call fit and predict. Use it when you are writing an algorithm, debugging one, or need control over a single step.
The same job, written all three ways:
from tuiml.workflow import Workflow
from tuiml.preprocessing import StandardScaler
from tuiml.algorithms.bayesian import NaiveBayesClassifier
from tuiml.evaluation import train_test_split, accuracy_score
X_train, X_test, y_train, y_test = train_test_split(
data.X, data.y, test_size=0.2, stratify=data.y, random_state=42
)
# Level 3 — direct objects.
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
clf = NaiveBayesClassifier()
clf.fit(X_train_scaled, y_train)
level3 = accuracy_score(y_test, clf.predict(X_test_scaled))
print(f"level 3 (objects): {level3:.4f}")
level 3 (objects): 0.7961
# Level 2 — the same pipeline as a Workflow.
flow = Workflow([StandardScaler(), NaiveBayesClassifier()])
flow.fit(X_train, y_train)
level2 = accuracy_score(y_test, flow.predict(X_test))
print(f"level 2 (Workflow): {level2:.4f}")
level 2 (Workflow): 0.7961
# Level 1 — the same pipeline as a spec.
spec_model = tuiml.train({
"model": {"name": "NaiveBayesClassifier"},
"data": {"X": data.X, "y": data.y},
"pipeline": [{"name": "StandardScaler"}],
"evaluation": {"test_size": 0.2, "stratify": True},
"random_seed": 42,
})
print(f"level 1 (spec): {spec_model.metrics_['accuracy_score']:.4f}")
level 1 (spec): 0.7961
Three notations, one computation. Level 3 makes the data flow explicit and is the right place to be when something is wrong and you need to see each intermediate array. Level 2 removes the bookkeeping — note that at level 3 you had to remember to call transform on the test set and not fit_transform, which is a mistake with real consequences that chapter 3 devotes itself to. Level 1 removes even the objects.
Remark. The three levels are not a beginner-to-expert ladder. Experienced users live at level 1 for routine work precisely because it is hard to get wrong, and drop to level 3 only when they need to see inside.
0.6 Finding things: the catalog¶
Because components are registered by name, you can ask the library what it has instead of reading its source tree. This is the discovery API, and it is the reason you never need to memorise an import path.
all_components = tuiml.list_algorithms()
print("components in the catalog:", len(all_components))
for kind in ("classifier", "regressor", "clusterer"):
print(f" {kind:11s} {len(tuiml.list_algorithms(kind)):3d}")
components in the catalog: 189 classifier 89 regressor 79 clusterer 21
Searching is usually more useful than listing. search_algorithms matches on names, descriptions and tags:
for hit in tuiml.search_algorithms("gradient boosting", limit=5):
print(f"{hit['name']:32s} {hit['type']}")
sklearn.GradientBoostingClassifier classifier sklearn.GradientBoostingRegressor regressor sklearn.HistGradientBoostingClassifier classifier sklearn.HistGradientBoostingRegressor regressor CatBoostClassifier classifier
And once you have a name, describe_algorithm returns everything the library knows about it — including its full parameter schema with types, defaults and descriptions.
info = tuiml.describe_algorithm("RandomForestClassifier")
print(info["name"], "-", info["type"])
print()
print("parameters:")
for param, meta in list(info["parameters"].items())[:6]:
print(f" {param:20s} default={meta.get('default')!r}")
RandomForestClassifier - ComponentType.CLASSIFIER parameters: n_estimators default=100 max_features default='sqrt' max_depth default=None min_samples_split default=2 min_samples_leaf default=1 bootstrap default=True
Remark. Names with a dot in them —
sklearn.DecisionTreeClassifier,capymoa.HoeffdingTree— are wrappers around an external library, available only if you installed that extra. Undotted names are native TuiML implementations. The prefix is how you tell at a glance whether a component brings a dependency with it.
0.7 The other way to drive it¶
Everything above assumed you are writing Python. You do not have to be.
TuiML ships an MCP server — Model Context Protocol, the standard that lets an AI assistant call real tools. Point Claude Desktop, ChatGPT or Cursor at it and the assistant gets a toolbox covering the same ground: profile a dataset, train a model, tune it, benchmark several, plot the comparison, serve the winner.
from tuiml.agent import get_workflow_tools
tools = get_workflow_tools()
print(f"{len(tools)} tools exposed to an agent\n")
for name in list(tools)[:8]:
summary = tools[name]["description"].split("\n")[0]
print(f" {name:22s} {summary[:52]}")
print(" ...")
30 tools exposed to an agent tuiml_train Train a machine learning model with evaluation. Two tuiml_predict Make predictions using a trained model on new data. tuiml_evaluate Evaluate a trained model on test data and compute me tuiml_benchmark Compare multiple algorithms on one or more datasets tuiml_upload_data Register a dataset for use with other TuiML tools. P tuiml_save_model Copy a trained model to a custom path. Use this when tuiml_serve_model Start a REST API server to serve a trained model for tuiml_stop_server Stop a running model serving API server. ...
The conversation then looks like this:
You: I have a CSV at
~/patients.csv, the outcome column isdiagnosis. Have a look at it and tell me what you'd try.Assistant: (calls
tuiml_profile_data) 768 rows, 8 numeric features, no declared missing values — butplas,pres,skin,insuandmassall contain zeros that are physiologically impossible, so those are missing values in disguise. The target is imbalanced, 65/35. I'd impute the disguised zeros, then compare a random forest against gradient boosting with stratified 5-fold CV, scoring on F1 rather than accuracy.
This is not a demo transcript with the ML hidden behind a chat window. The assistant is calling the same train() you called in section 0.3, with the same spec dictionary — which is exactly why section 0.1 claimed that "experiments are data" was the load-bearing idea. A spec is the interface an agent writes to. Chapter 9 makes that case properly; chapters 10 to 12 wire it up.
Remark — your data stays put. The MCP server runs on your machine. The assistant sends tool names and parameters; your dataset rows never leave the process. What crosses the network is
{"name": "RandomForestClassifier"}, not your patients.
0.8 What is in the rest of this book¶
The book has a spine: you have a table and a question, and you need a model you can defend. It runs in order, and each chapter assumes the ones before it.
| Chapter | What it is really about | |
|---|---|---|
| 1 | Loading and Profiling Data | Getting data in, and looking at it hard enough to find what is wrong |
| 2 | Training and Evaluating | Why the first number you compute is usually a lie |
| 3 | Preprocessing | Cleaning data, and the leakage trap that inflates your score |
| 4 | Pipelines with Workflow | The fix for chapter 3 |
| 5 | Feature Engineering | Making better columns, and proving it helped |
| 6 | Imbalanced Data | When the interesting class is the rare one |
| 7 | Choosing and Tuning | Picking an algorithm and finding its hyperparameters |
| 8 | Benchmarking | Comparing many models, with statistics |
| 9 | Specs | Experiments as data — and the bridge to agents |
| 10 | Connecting Your Agent | MCP setup, end to end |
| 11 | Working with an Agent | Prompt patterns, and verifying what it did |
| 12 | Agents in Your Code | LangChain, Pydantic-AI, raw tool loops |
| 13 | Serving | Getting the model behind an HTTP endpoint |
| 14 | Case Study | The whole book, applied to one problem |
If you only read three, read 2, 3 and 8. Those are the chapters about not fooling yourself, which is most of what applied machine learning consists of.
Recap¶
- TuiML is a tabular ML library covering load → clean → train → evaluate → serve.
- Components are registered by name, so you search a catalog of 189 rather than memorising imports.
- An experiment is one dictionary, which makes it storable, diffable, and writable by an agent.
- Three API levels — spec,
Workflow, direct objects — describe the same computation at different altitudes. tuiml.train(spec)returns a fitted object withmetrics_,predict,predict_proba,saveandserve.- Accuracy on the diabetes data is misleading, because the classes are imbalanced. Chapter 2 deals with that.
Next: chapter 1 loads data from every format TuiML supports, and then profiles the diabetes dataset closely enough to find the impossible zeros.