Instructions to use thu-sail-lab/Time-RCD with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use thu-sail-lab/Time-RCD with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("feature-extraction", model="thu-sail-lab/Time-RCD", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("thu-sail-lab/Time-RCD", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
| """User-facing zero-shot inference API for Time-RCD.""" | |
| from __future__ import annotations | |
| from copy import deepcopy | |
| from pathlib import Path | |
| from typing import Literal, Optional, Tuple, Union | |
| import numpy as np | |
| HF_REPO_ID = "thu-sail-lab/Time-RCD" | |
| CHECKPOINT_FILES = { | |
| "uni": "best_model/pretrain_checkpoint_best_uni.pth", | |
| "multi": "best_model/pretrain_checkpoint_best_multi.pth", | |
| } | |
| DEFAULT_WIN_SIZE = 5000 | |
| DEFAULT_BATCH_SIZE = {"uni": 64, "multi": 1} | |
| class TimeRCDDetector: | |
| """Zero-shot time series anomaly detector powered by Time-RCD. | |
| Parameters | |
| ---------- | |
| checkpoint_path: | |
| Path to a local ``.pth`` checkpoint. | |
| variant: | |
| ``"uni"`` for univariate series, ``"multi"`` for multivariate series. | |
| win_size: | |
| Sliding window length. Sequences shorter than this value use the full | |
| sequence length instead. | |
| batch_size: | |
| Inference batch size. Defaults to 64 (uni) or 1 (multi). | |
| device: | |
| PyTorch device string, e.g. ``"cuda"`` or ``"cpu"``. Auto-detected when | |
| omitted. | |
| Notes | |
| ----- | |
| The model is initialized on the first call to :meth:`predict`, when the | |
| number of input features is known. | |
| """ | |
| def __init__( | |
| self, | |
| checkpoint_path: str, | |
| variant: Literal["uni", "multi"] = "uni", | |
| win_size: int = DEFAULT_WIN_SIZE, | |
| batch_size: Optional[int] = None, | |
| device: Optional[str] = None, | |
| ) -> None: | |
| if variant not in CHECKPOINT_FILES: | |
| raise ValueError(f"variant must be one of {list(CHECKPOINT_FILES)}, got {variant!r}") | |
| self.variant = variant | |
| self.win_size = win_size | |
| self.batch_size = batch_size if batch_size is not None else DEFAULT_BATCH_SIZE[variant] | |
| self.checkpoint_path = str(checkpoint_path) | |
| self.device = device | |
| self._tester = None | |
| self._num_features: Optional[int] = None | |
| def _ensure_tester(self, num_features: int) -> None: | |
| """Initialize a model compatible with ``num_features`` when needed.""" | |
| if self._tester is not None and self._num_features == num_features: | |
| return | |
| from ._core.time_rcd_config import default_config | |
| from ._inference import TimeRCDPretrainTester | |
| # ``default_config`` is a module-level template. Each detector must own | |
| # its configuration so its runtime options cannot affect other instances. | |
| config = deepcopy(default_config) | |
| config.ts_config.patch_size = 16 | |
| config.win_size = self.win_size | |
| config.batch_size = self.batch_size | |
| config.ts_config.num_features = num_features | |
| self._tester = TimeRCDPretrainTester(self.checkpoint_path, config) | |
| self._num_features = num_features | |
| if self.device is not None: | |
| import torch | |
| self._tester.device = torch.device(self.device) | |
| self._tester.model.to(self._tester.device) | |
| def from_pretrained( | |
| cls, | |
| repo_id: str = HF_REPO_ID, | |
| variant: Literal["uni", "multi"] = "uni", | |
| cache_dir: Optional[str] = None, | |
| local_files_only: bool = False, | |
| **kwargs, | |
| ) -> "TimeRCDDetector": | |
| """Load a checkpoint from Hugging Face Hub (cached locally after first use).""" | |
| from huggingface_hub import hf_hub_download | |
| checkpoint_path = hf_hub_download( | |
| repo_id=repo_id, | |
| filename=CHECKPOINT_FILES[variant], | |
| cache_dir=cache_dir, | |
| local_files_only=local_files_only, | |
| ) | |
| return cls(checkpoint_path=checkpoint_path, variant=variant, **kwargs) | |
| def from_local( | |
| cls, | |
| checkpoint_path: Union[str, Path], | |
| variant: Literal["uni", "multi"] = "uni", | |
| **kwargs, | |
| ) -> "TimeRCDDetector": | |
| """Load a checkpoint from a local path.""" | |
| path = Path(checkpoint_path) | |
| if not path.is_file(): | |
| raise FileNotFoundError(f"Checkpoint not found: {path}") | |
| return cls(checkpoint_path=str(path), variant=variant, **kwargs) | |
| def predict( | |
| self, | |
| data: Union[np.ndarray, list], | |
| return_logits: bool = False, | |
| ) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]: | |
| """Run zero-shot anomaly scoring on a single time series. | |
| Parameters | |
| ---------- | |
| data: | |
| Array of shape ``(T,)`` or ``(T, C)`` where ``T`` is time steps and | |
| ``C`` is the number of channels. | |
| return_logits: | |
| When ``True``, also return raw anomaly logits. | |
| Returns | |
| ------- | |
| scores: | |
| Anomaly scores in ``[0, 1]``, one value per time step. | |
| logits: | |
| Returned only when ``return_logits=True``. | |
| """ | |
| data = np.asarray(data, dtype=np.float64) | |
| if data.ndim == 1: | |
| data = data.reshape(-1, 1) | |
| elif data.ndim != 2: | |
| raise ValueError(f"data must be 1D or 2D, got shape {data.shape}") | |
| if self.variant == "uni" and data.shape[1] != 1: | |
| raise ValueError( | |
| "variant='uni' expects a univariate series with shape (T,) or (T, 1). " | |
| f"Got {data.shape[1]} channels; use variant='multi' instead." | |
| ) | |
| if self.variant == "multi" and data.shape[1] < 2: | |
| raise ValueError( | |
| "variant='multi' expects at least two channels with shape (T, C), where C > 1. " | |
| "Use variant='uni' for a univariate series." | |
| ) | |
| original_length = data.shape[0] | |
| self._ensure_tester(data.shape[1]) | |
| assert self._tester is not None | |
| score_chunks, logit_chunks = self._tester.zero_shot(data) | |
| scores = np.concatenate([np.asarray(chunk).reshape(-1) for chunk in score_chunks], axis=0) | |
| logits = np.concatenate([np.asarray(chunk).reshape(-1) for chunk in logit_chunks], axis=0) | |
| scores = scores[:original_length] | |
| logits = logits[:original_length] | |
| if return_logits: | |
| return scores, logits | |
| return scores | |