Pretrain and Create Model for Classification Based Tasks
from stFormer.classifier.Classifier import Classifier
1.1 Classify From Pretrained Model
We take out Subtype based information to evaluate classification fine-tuning and evaluation
Data Loading & Splitting
Load
train_dsfromdataset_path.If
eval_dataset_pathprovided, loadeval_ds;
otherwise do atrain_test_split(test_size, seed=42).
model_initFunctionLoads base model & config from
model_checkpoint.Overrides
num_labelsto matchself.label_mapping.Optionally freezes the first
self.freeze_layersencoder layers.Adds a classification head onto BERT pretreained model if loading from masked learning objective
Tokenizer & Data Collator
AutoTokenizer.from_pretrained(...)withpadding="max_length"DataCollatorWithPaddingto pad totokenizer.model_max_length.
Classification
Evaluation metricscompute metrics to determine training/test loss and accuracytraining argstakes dictionary of BERT training arguments for hyperparameter selection and model updating
Best Checkpoint Selection and Saving
Saves model checkpoints to output directory based upon ``eval strategy`
Returns final
trainermodel and saves final model tooutput_directory
classifier = Classifier(
metadata_column = 'Tissue',
mode='spot',
classifier_type = 'sequence', #for class predictions
token_dictionary_file='output/spot/token_dictionary.pickle',
rare_threshold=0.1, #remove rare data types (less than 10% of samples)
max_examples_per_class=10000, #option to downsample
nproc=24,
)
ds_path, map_path = classifier.prepare_data(
input_data = 'annotated.dataset/',
output_directory = 'tmp/clasifier',
output_prefix = 'Tissue_Classifier',
)
In this example we utilize the model that was trained with a masked learning objective. While this is definitely possible, we suggest utilizing another Bert model that was trained using a classification task and then fine-tune on specific task
trainer = classifier.train(
model_checkpoint='output/spot/spot_model', # pretrained model path
dataset_path = ds_path, # dataset path from prepare data
output_directory = 'output/models/classification', #output evaluation
test_size=0.2, # splits dataset into test/train splits
)
Using model checkpoint: output/spot/spot_model
Number of labels from data: 3
Label mapping: {'Brain': 0, 'Breast': 1, 'Skin': 2}
Linear(in_features=256, out_features=3, bias=True)
Max label: tensor(2)
Label mapping size: 3
| Step | Training Loss | Validation Loss | Accuracy | Precision | Recall | F1 |
|---|---|---|---|---|---|---|
| 100 | 1.083700 | 1.001198 | 0.539333 | 0.416546 | 0.536904 | 0.438580 |
| 200 | 0.786700 | 0.569715 | 0.796833 | 0.823148 | 0.796179 | 0.780779 |
| 300 | 0.689500 | 0.648199 | 0.687167 | 0.729824 | 0.686503 | 0.641001 |
| 400 | 0.586700 | 0.423288 | 0.868833 | 0.878322 | 0.868952 | 0.868393 |
| 500 | 0.490300 | 0.976297 | 0.581667 | 0.611228 | 0.579323 | 0.547443 |
| 600 | 0.505300 | 0.524849 | 0.801667 | 0.823287 | 0.800729 | 0.798036 |
| 700 | 0.365200 | 0.249833 | 0.940833 | 0.942768 | 0.940911 | 0.940833 |
| 800 | 0.330000 | 0.908484 | 0.667667 | 0.694001 | 0.667420 | 0.620839 |
| 900 | 0.291800 | 0.830796 | 0.706167 | 0.740024 | 0.704866 | 0.680967 |
| 1000 | 0.248000 | 0.150817 | 0.967167 | 0.967391 | 0.967184 | 0.967184 |
| 1100 | 0.185900 | 0.732362 | 0.754667 | 0.783978 | 0.754984 | 0.746594 |
| 1200 | 0.193700 | 0.144109 | 0.968833 | 0.969112 | 0.968875 | 0.968854 |
| 1300 | 0.165200 | 0.136830 | 0.967167 | 0.967243 | 0.967176 | 0.967176 |
| 1400 | 0.138800 | 0.169318 | 0.958500 | 0.959216 | 0.958587 | 0.958527 |
| 1500 | 0.161700 | 0.140191 | 0.969833 | 0.970116 | 0.969880 | 0.969856 |
1.2 Train Gene Classifier
from stFormer.classifier.Classifier import Classifier
import pandas as pd
import pickle
import numpy as np
import os
We are replicating publication analysis by training a classifier to predict responsive genes in TNBC
load genes upregulated in response to neoadjuvent care in TNBC
load list of random shuffled genes as background
load ensembl to gene_name mapping dictionary
create dictionary for respnder and random genes
Run gene classification for predictions of responsive genes in dataset
os.chdir('analyses/models.to.test/Extended.model/')
file1 = "upregulated.top300"
file2 = "gene.shuffled.upregulated"
genes_responder = list(np.loadtxt(file1,dtype=str))
genes_random = list(np.loadtxt(file2, dtype=str))
training_args = {"num_train_epochs": 30.0, "weight_decay": 0.25, "learning_rate": 3e-6, "warmup_steps":1500, "lr_scheduler_type": "polynomial"}
GeneClassifier Token Classification Overview
We take out per-cell Subtype labels and instead classify individual genes (tokens) within each sequence.
Data Loading & Splitting
Load
train_dsfromdataset_path.If
eval_dataset_pathis provided, loadeval_ds.
Otherwise, performtrain_test_split(test_size, seed=42).
Label Mapping (Gene Classes)
Use
classifier_utils.label_classes("gene", ...)to map each input token (gene) to a class label.Generates a per-token
labelsfield matchinginput_idsshape.
Tokenizer & Data Collator
Uses
DataCollatorForGeneClassificationto pad bothinput_idsandlabelsin sync.
Model Initialization
Loads base model & config from
model_checkpoint.Creates a
TokenClassificationhead on the pretrained model.
Classification Training
Computes token-level metrics (e.g., F1 score, accuracy).
Best Checkpoint Selection and Saving
Saves model checkpoints to output directory based on
evaluation_strategy.Final model and tokenizer are saved to
output_directory.Predictions and evaluation metrics are returned for downstream analysis.
ray_config = {"num_train_epochs": [1.0,],
"learning_rate": (1e-3, 1e-2),
"weight_decay": (0.01, 0.05),
"lr_scheduler_type": ["linear", "cosine", "polynomial"],
"warmup_steps": (5, 50),
"seed": (100, 1000),
"per_device_train_batch_size": [10,],
}
gene_class_dict = {'Responder': genes_responder,'Random.genes': genes_random}
# 2) Instantiate for token-classification
gene_classifier = Classifier(
metadata_column=None, # no sequence-level label
mode='extended',
gene_class_dict=gene_class_dict, #specify this dictionary to use Gene Classifier
classifier_type = 'gene',
freeze_layers=4, # freeze first two BERT layers (optional)
forward_batch_size=50,
max_examples=10_000,
nproc=16,
token_dictionary_file='SpatialModel/new_token_dictionary.pickle'
)
# 3) Prepare your dataset (must already contain `input_ids` for each cell)
ds_path, map_path = gene_classifier.prepare_data(
input_data="STFormer_TNBC_neighbor.dataset",
output_directory="tmp/gene_classifier",
output_prefix="gene_classifier"
)
trainer = gene_classifier.train(
model_checkpoint="run-8eb93bdf/checkpoint-1000",
dataset_path=ds_path,
output_directory="tmp/gene_classifier",
)
metrics = gene_classifier.evaluate(
model_directory="models/visium_gene_classifier/final_model",
eval_dataset_path=ds_path,
id_class_dict_file=map_path,
output_directory="output/gene_classifier",
)
1.3 Train and Evaluate Model with Hyperparameter search
In this example we utilize ray configuration to loop through a list of hyperparameters to search for the best configuration of arguments for a classification task.
Performs end-to-end hyperparameter search for a sequence-classification head using Ray Tune and Hugging Face Trainer.
Define Hyperparameter Search Space
Pull ranges/choices from
self.ray_configfor
learning_rate,num_train_epochs,weight_decay, etc.
CLI Reporter
CLIReportershows per-trial metrics (eval_loss,eval_accuracy)
and hyperparameter values in the console.
Trainer & Hyperparameter Search
Instantiate
Trainerwithmodel_init, datasets, collator, andcompute_metrics.Run
trainer.hyperparameter_search(...)with Ray backend andHyperOptSearch.
Best Checkpoint Selection & Saving
Use
ExperimentAnalysisto find best trial/checkpoint byeval_loss.Load that checkpoint into a fresh
BertForSequenceClassification.Save model & tokenizer under
output_directory/best_model.
Load Datasets and Test/Train Split
from stFormer.classifier.Classifier import Classifier
import stFormer.classifier.classifier_utils as cu
from datasets import load_from_disk
import pandas as pd
import numpy as np
import random
random.seed(123)
ds = load_from_disk('annotated.dataset')
#ds_filt = cu.remove_rare(ds,rare_threshold=0.05,nproc=24,state_key='Tissue')
train1 = pd.read_csv('data/train1.csv').dropna()
test1 = pd.read_csv('data/test1.csv').dropna()
train2 = pd.read_csv('data/train2.csv').dropna()
test2 = pd.read_csv('data/test2.csv').dropna()
train_samples = np.unique(train1['Sample'].tolist())
test_samples = np.unique(test1['Sample'].tolist())
ds_train = ds.filter(lambda ex: ex['Sample ID'] in train_samples,num_proc = 24)
ds_test = ds.filter(lambda ex: ex['Sample ID'] in test_samples,num_proc=24)
Set up hyperparameters, classification information, and prepare dataset for classification
For more hyperparameter options, please visit our docs: https://cancerstformer.readthedocs.io/en/latest/
hyperparameters ={
"learning_rate":[1e-5,1e-3],
"weight_decay": [0.0, 0.3],
"warmup_ratio": [0,0.3]
#'lr_scheduler_type': ["linear","cosine","polynomial"],
#'per_device_train_batch_size': [32]
}
classifier = Classifier(
metadata_column = 'Tissue',
mode='spot',
ray_config = hyperparameters,
token_dictionary_file='output/spot/token_dictionary.pickle',
nproc=24,
)
ds_path, map_path = classifier.prepare_data(
input_data = ds_train, #takes Dataset Object or dataset file path
output_directory = 'tmp_eval', #filtered dataset out location
output_prefix = 'train_tissue'
)
# 2) Prepare the data exactly the same way
eval_ds_path, eval_map_path = classifier.prepare_data(
input_data = ds_test,
output_directory = 'tmp_eval',
output_prefix = 'eval_tissue')
Train the model
trainer = classifier.train(
model_checkpoint='output/models/tissue_classification/best_model', # pretrained model path
dataset_path = ds_path, # dataset path from prepare data
output_directory = 'output/eval_tissue_nohyperopt', #output evaluation
eval_dataset = eval_ds_path,
n_trials = 4
)
1.3 Plot Predictions using Evaluation Utils
Utilize seaborn, truth, and predicted values to create a confusion matrix and plot results
from stFormer.classifier.Classifier import Classifier
from datasets import load_from_disk
from sklearn.metrics import confusion_matrix
import pickle
import os
#Produce & save raw predictions
eval_ds = load_from_disk(ds_path).shuffle(seed=42).select(range(1000))
preds = trainer.predict(eval_ds)
y_true = preds.label_ids
y_pred = preds.predictions.argmax(-1)
with open("output/models/classification/predictions.pkl", "wb") as f:
pickle.dump({"y_true": y_true, "y_pred": y_pred}, f)
import numpy as np
with open('output/eval_tissue_nohyperopt/predictions.pkl','rb') as f:
preds = pickle.load(f)
y_true = preds.label_ids
y_pred = preds.predictions.argmax(-1)
map_path = 'tmp_eval/train_tissue_id_class_dict.pkl'
# Load the id→class mapping you dumped in prepare_data()
with open(map_path, "rb") as f:
id_map = pickle.load(f)
# We need a list of class names in label‐index order:
inv_map = {v:k for k,v in id_map.items()}
class_order = [inv_map[i] for i in range(len(inv_map))]
cm = confusion_matrix(y_true, y_pred, labels=list(id_map.values()))
import matplotlib.pyplot as plt
import seaborn as sns
plt.figure(figsize=(8, 8))
sns.heatmap(cm, annot=True, fmt="d", xticklabels=class_order, yticklabels=class_order, cmap="Blues")
plt.xlabel("Predicted")
plt.ylabel("True")
plt.show()
#save heatmap with inbuilt plotting functionality
classifier.plot_predictions(
predictions_file="output/models/classification/predictions.pkl",
id_class_dict_file=map_path,
title="Visium Spot Subtype Predictions",
output_directory="output/models/classification",
output_prefix="visium_spot",
class_order=class_order
)