YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

ChronosBERT-Bolt

ChronosBERT-Bolt is a pretrained BERT-style bidirectional encoder-only model for time series representation learning.

Model Description

  • Architecture: BERT-Small (encoder-only)
  • Input representation: Chronos-Bolt patch-based representation
  • Pretraining objective: Masked Language Modeling
  • Number of parameters: Approximately 29M

ChronosBERT-Bolt combines the patch-based time series representation of Chronos-Bolt with a BERT architecture and a masked language modeling (MLM) pretraining objective.

Time series are divided into patches using the Chronos-Bolt representation. These patches are then processed by a bidirectional BERT encoder, which is trained to reconstruct randomly masked patches of the input. This allows the model to learn general time series representations that can be transferred to downstream tasks.

ChronosBERT-Bolt can be fine-tuned for individual downstream tasks.

GitHub

Fine-tuning for Time Series Classification

The following example demonstrates how to fine-tune ChronosBERT-Bolt on a time series classification dataset.

Load the Dataset

Download the UCR Time Series Classification Archive and extract the ArrowHead dataset.

import numpy as np
import pandas as pd

# Load training and test data
train = pd.read_csv("ArrowHead_TRAIN.tsv", sep="\t", header=None).values
test = pd.read_csv("ArrowHead_TEST.tsv", sep="\t", header=None).values

X_train = train[:, 1:].astype(np.float32)
y_train = train[:, 0].astype(int)

X_test = test[:, 1:].astype(np.float32)
y_test = test[:, 0].astype(int)

# Map class labels to consecutive integers
classes = np.unique(y_train)
label_map = {v: i for i, v in enumerate(classes)}

y_train = np.array([label_map[y] for y in y_train])
y_test = np.array([label_map[y] for y in y_test])

num_labels = len(classes)

Load ChronosBERT-Bolt

ChronosBERT-Bolt uses the pretrained Chronos-Bolt forecasting model as its backbone. A classification model is constructed using the pretrained configuration, and the pretrained weights are transferred to it. The classification head is initialized separately.

import torch
from chronos_pkg.src.chronos.chronos_bolt import (
    ChronosBoltModelForForecasting,
)
from chronos_pkg.src.chronos.chronos_bolt_classification import (
    ChronosBoltModelForClassification,
)

device = torch.device(
    "cuda" if torch.cuda.is_available() else "cpu"
)

# Load pretrained model
pretrained_model = ChronosBoltModelForForecasting.from_pretrained(
    "ddrg/ChronosBERT-Bolt-Default"
)

# Disable random patch augmentation
config = pretrained_model.chronos_config

for attribute in [
    "masking_prob",
    "patch_masking_prob",
    "perturbation_prob",
    "patch_perturbation_prob",
]:
    if hasattr(config, attribute):
        setattr(config, attribute, 0.0)

# Get context length
context_length = config.context_length

config = pretrained_model.config


# Initialize classification model
model = ChronosBoltModelForClassification(
    config=config,
    num_labels=num_labels,
    dropout_head=0.1,
    pooling="mean",
)

# Transfer pretrained backbone weights
model.load_state_dict(
    pretrained_model.state_dict(),
    strict=False,
)

del pretrained_model

# Disable random patch augmentation on classification model
config = model.chronos_config

for attribute in [
    "masking_prob",
    "patch_masking_prob",
    "perturbation_prob",
    "patch_perturbation_prob",
]:
    if hasattr(config, attribute):
        setattr(config, attribute, 0.0)

# Fine-tune the entire model
for parameter in model.parameters():
    parameter.requires_grad = True

model.to(device)

Prepare the Dataset

ChronosBERT-Bolt processes numerical time series directly. The input consists of the time series context and a mask indicating which values are observed.

from torch.utils.data import Dataset, DataLoader

class ClassificationDataset(Dataset):

    def __init__(self, X, y, context_length):
        self.X = X
        self.y = np.asarray(y, dtype=np.int64)
        self.context_length = context_length

    def __len__(self):
        return len(self.X)

    def __getitem__(self, idx):

        series = torch.tensor(
            self.X[idx],
            dtype=torch.float32,
        )

        mask = torch.isfinite(series)
        series[~mask] = float("nan")
        padding = self.context_length - len(series)

        if padding > 0:
            series = torch.cat([
                torch.full(
                    (padding,),
                    float("nan"),
                    dtype=series.dtype,
                ),
                series,
            ])

            mask = torch.cat([
                torch.zeros(padding, dtype=torch.bool),
                mask,
            ])

        return {
            "context": series,
            "mask": mask,
            "labels": torch.tensor(
                self.y[idx],
                dtype=torch.long,
            ),
        }


train_dataset = ClassificationDataset(
    X_train, y_train, context_length
)

test_dataset = ClassificationDataset(
    X_test, y_test, context_length
)

train_loader = DataLoader(
    train_dataset,
    batch_size=16,
    shuffle=True,
)

test_loader = DataLoader(
    test_dataset,
    batch_size=32,
)

4. Fine-tune the Model

The model is fine-tuned using cross-entropy loss and the AdamW optimizer. A linear learning rate scheduler with warmup is used during training.

import math
from torch.optim import AdamW
from transformers import get_linear_schedule_with_warmup

num_epochs = 10

optimizer = AdamW(
  model.parameters(),
  lr=1e-4
)

for epoch in range(10):
    model.train()

    for batch in train_loader:
        context = batch["context"].to(device)
        mask = batch["mask"].to(device)
        labels = batch["labels"].to(device)

        outputs = model(
            context=context,
            mask=mask,
            labels=labels,
        )

        loss = outputs["loss"]
        loss.backward()

        optimizer.step()
        optimizer.zero_grad()

    print(f"Epoch {epoch + 1}, Loss: {loss.item():.4f}")

Evaluate the Model


model.eval()
correct = 0
total = 0

with torch.no_grad():

    for batch in test_loader:

        input_ids = batch["input_ids"].to(device)
        attention_mask = batch["attention_mask"].to(device)
        labels = batch["labels"].to(device)

        outputs = model(
            input_ids=input_ids,
            attention_mask=attention_mask
        )

        predictions = outputs["logits"].argmax(dim=-1)

        correct += (predictions == labels).sum().item()
        total += labels.size(0)

accuracy = correct / total

print(f"Accuracy: {accuracy:.4f}")
Downloads last month
20
Safetensors
Model size
15.3M params
Tensor type
F32
ยท
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support