Skip to content

Commit 98c7b07

Browse files
committed
feat: 🎸 support time series classification task
1 parent 2adc116 commit 98c7b07

18 files changed

Lines changed: 1378 additions & 28 deletions

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040

4141
$\text{BasicTS}^{+}$ (**Basic** **T**ime **S**eries) is a benchmark library and toolkit designed for time series forecasting. It now supports a wide range of tasks and datasets, including spatial-temporal forecasting and long-term time series forecasting. It covers various types of algorithms such as statistical models, machine learning models, and deep learning models, making it an ideal tool for developing and evaluating time series forecasting models. You can find detailed tutorials in [Getting Started](./tutorial/getting_started.md).
4242

43+
🎉 **Update (Aug 2025):** BasicTS now supports **time series classification tasks and the UEA dataset!** Check out [how to use BasicTS for classification tasks](./tutorial/time_series_classification_cn.md).
44+
4345
🎉 **Update (June 2025):** Adds six LTSF baselines: CARD, TimeXer, Bi-Mamba, ModernTCN, S-D-Mamba, and S4.
4446

4547
🎉 **Update (May 2025):** BasicTS now supports training universal forecasting models—such as **TimeMoE** and **ChronosBolt**—with the [BLAST](https://arxiv.org/abs/2505.17871) corpus. BLAST enables **faster convergence**, **notable reductions in computational cost**, and superior performance even with limited resources. See [here](./tutorial/training_with_BLAST.md).

README_CN.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141

4242
$\text{BasicTS}^{+}$ (**Basic** **T**ime **S**eries) 是一个面向时间序列预测的基准库和工具箱,现已支持时空预测、长序列预测等多种任务与数据集,涵盖统计模型、机器学习模型、深度学习模型等多类算法,为开发和评估时间序列预测模型提供了理想的工具。你可以在[快速上手](./tutorial/getting_started_cn.md)找到详细的教程。
4343

44+
🎉 **更新(2025年8月):BasicTS现已支持时间序列分类任务和UEA数据集!** 在这里了解[使用BasicTS进行分类任务](./tutorial/time_series_classification_cn.md)
45+
4446
🎉 **更新(2025年6月):** 添加了6个长序列预测基线:CARD、TimeXer、Bi-Mamba、ModernTCN、S-D-Mamba、S4。
4547

4648
🎉 **更新(2025年5月):** BasicTS 现已支持使用 [BLAST](https://arxiv.org/abs/2505.17871) 语料库训练通用预测模型(例如 **TimeMoE****ChronosBolt**)。BLAST 能够实现 **更快的收敛速度****显著降低计算成本**,并且即使在资源有限的情况下也能获得卓越性能。[查看](./tutorial/training_with_BLAST_cn.md)
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import os
2+
import sys
3+
4+
from easydict import EasyDict
5+
6+
from basicts.data import UEADataset
7+
from basicts.metrics import accuracy
8+
from basicts.runners import SimpleTimeSeriesClassificationRunner
9+
from basicts.utils import load_dataset_desc
10+
11+
from .arch import iTransformer
12+
13+
sys.path.append(os.path.abspath(__file__ + "/../../.."))
14+
15+
############################## Hot Parameters ##############################
16+
# Dataset & Metrics configuration
17+
DATA_NAME = "JapaneseVowels" # Dataset name
18+
desc = load_dataset_desc(os.path.join("UEA", DATA_NAME))
19+
INPUT_LEN = desc["seq_len"]
20+
NUM_CLASSES = desc["num_classes"]
21+
NULL_VAL = 0.0
22+
# Model architecture and parameters
23+
MODEL_ARCH = iTransformer
24+
NUM_NODES = desc["num_nodes"]
25+
MODEL_PARAM = {
26+
"task_name": "classification",
27+
"num_classes": NUM_CLASSES,
28+
"enc_in": NUM_NODES, # num nodes
29+
"dec_in": NUM_NODES,
30+
"c_out": NUM_NODES,
31+
"seq_len": INPUT_LEN,
32+
"label_len": INPUT_LEN/2, # start token length used in decoder
33+
"pred_len": 0, # prediction sequence length
34+
"factor": 3, # attn factor
35+
"d_model": 128,
36+
"moving_avg": 25, # window size of moving average. This is a CRUCIAL hyper-parameter.
37+
"n_heads": 8,
38+
"e_layers": 3, # num of encoder layers
39+
"d_ff": 256,
40+
"distil": True,
41+
"sigma" : 0.2,
42+
"dropout": 0.1,
43+
"freq": "h",
44+
"use_norm": True,
45+
"output_attention": False,
46+
"embed": "timeF", # [timeF, fixed, learned]
47+
"activation": "gelu",
48+
}
49+
NUM_EPOCHS = 20
50+
51+
############################## General Configuration ##############################
52+
CFG = EasyDict()
53+
# General settings
54+
CFG.DESCRIPTION = "An Example Config"
55+
CFG.GPU_NUM = 1 # Number of GPUs to use (0 for CPU mode)
56+
# Runner
57+
CFG.RUNNER = SimpleTimeSeriesClassificationRunner
58+
59+
############################## Dataset Configuration ##############################
60+
CFG.DATASET = EasyDict()
61+
# Dataset settings
62+
CFG.DATASET.NAME = DATA_NAME
63+
CFG.DATASET.TYPE = UEADataset
64+
CFG.DATASET.NUM_CLASSES = NUM_CLASSES
65+
CFG.DATASET.PARAM = EasyDict({
66+
"dataset_name": DATA_NAME,
67+
"train_val_test_ratio": None, # None for UEA datasets
68+
# "mode" is automatically set by the runner
69+
})
70+
71+
############################## Model Configuration ##############################
72+
CFG.MODEL = EasyDict()
73+
# Model settings
74+
CFG.MODEL.NAME = MODEL_ARCH.__name__
75+
CFG.MODEL.ARCH = MODEL_ARCH
76+
CFG.MODEL.PARAM = MODEL_PARAM
77+
78+
############################## Metrics Configuration ##############################
79+
80+
CFG.METRICS = EasyDict()
81+
# Metrics settings
82+
CFG.METRICS.FUNCS = EasyDict({
83+
"Accuracy": accuracy
84+
})
85+
CFG.METRICS.TARGET = "Accuracy"
86+
CFG.METRICS.NULL_VAL = NULL_VAL
87+
88+
############################## Training Configuration ##############################
89+
CFG.TRAIN = EasyDict()
90+
CFG.TRAIN.NUM_EPOCHS = NUM_EPOCHS
91+
CFG.TRAIN.CKPT_SAVE_DIR = os.path.join(
92+
"checkpoints",
93+
MODEL_ARCH.__name__,
94+
"_".join([DATA_NAME, str(CFG.TRAIN.NUM_EPOCHS)])
95+
)
96+
# CFG.TRAIN.LOSS = nn.CrossEntropyLoss()
97+
# Optimizer settings
98+
CFG.TRAIN.OPTIM = EasyDict()
99+
CFG.TRAIN.OPTIM.TYPE = "Adam"
100+
CFG.TRAIN.OPTIM.PARAM = {
101+
"lr": 0.001,
102+
}
103+
# Learning rate scheduler settings
104+
CFG.TRAIN.LR_SCHEDULER = EasyDict()
105+
CFG.TRAIN.LR_SCHEDULER.TYPE = "MultiStepLR"
106+
CFG.TRAIN.LR_SCHEDULER.PARAM = {
107+
"milestones": [1, 25, 50],
108+
"gamma": 0.5
109+
}
110+
CFG.TRAIN.CLIP_GRAD_PARAM = {
111+
"max_norm": 5.0
112+
}
113+
# Train data loader settings
114+
CFG.TRAIN.DATA = EasyDict()
115+
CFG.TRAIN.DATA.BATCH_SIZE = 16
116+
CFG.TRAIN.DATA.SHUFFLE = True
117+
118+
############################## Validation Configuration ##############################
119+
CFG.VAL = EasyDict()
120+
CFG.VAL.INTERVAL = 1
121+
CFG.VAL.DATA = EasyDict()
122+
CFG.VAL.DATA.BATCH_SIZE = 16
123+
124+
############################## Test Configuration ##############################
125+
CFG.TEST = EasyDict()
126+
CFG.TEST.INTERVAL = 1
127+
CFG.TEST.DATA = EasyDict()
128+
CFG.TEST.DATA.BATCH_SIZE = 16
129+
130+
############################## Evaluation Configuration ##############################
131+
132+
CFG.EVAL = EasyDict()
133+
134+
# Evaluation parameters
135+
CFG.EVAL.USE_GPU = True # Whether to use GPU for evaluation. Default: True

baselines/iTransformer/arch/itransformer_arch.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from .Transformer_EncDec import Encoder, EncoderLayer
55
from .SelfAttention_Family import FullAttention, AttentionLayer
66
from .Embed import DataEmbedding_inverted
7-
import numpy as np
87
from basicts.utils import data_transformation_4_xformer
98

109
class iTransformer(nn.Module):
@@ -13,28 +12,28 @@ class iTransformer(nn.Module):
1312
Official Code: https://github.com/thuml/iTransformer
1413
Link: https://arxiv.org/abs/2310.06625
1514
Venue: ICLR 2024
16-
Task: Long-term Time Series Forecasting
15+
Task: Long-term Time Series Forecasting, Time Series Classification
1716
"""
1817
def __init__(self, **model_args):
19-
super(iTransformer, self).__init__()
18+
super().__init__()
2019
self.pred_len = model_args['pred_len']
2120
self.seq_len = model_args['seq_len']
2221
self.output_attention = model_args['output_attention']
2322
self.enc_in = model_args['enc_in']
2423
self.dec_in = model_args['dec_in']
2524
self.c_out = model_args['c_out']
26-
self.factor = model_args["factor"]
25+
self.factor = model_args['factor']
2726
self.d_model = model_args['d_model']
2827
self.n_heads = model_args['n_heads']
2928
self.d_ff = model_args['d_ff']
3029
self.embed = model_args['embed']
31-
self.freq = model_args["freq"]
32-
self.dropout = model_args["dropout"]
30+
self.freq = model_args['freq']
31+
self.dropout = model_args['dropout']
3332
self.activation = model_args['activation']
3433
self.e_layers = model_args['e_layers']
35-
self.d_layers = model_args['d_layers']
34+
self.use_norm = model_args['use_norm']
35+
self.task_name = model_args['task_name']
3636

37-
self.use_norm =model_args['use_norm']
3837
# Embedding
3938
self.enc_embedding = DataEmbedding_inverted(self.seq_len, self.d_model, self.embed, self.freq,
4039
self.dropout)
@@ -54,7 +53,16 @@ def __init__(self, **model_args):
5453
],
5554
norm_layer=torch.nn.LayerNorm(self.d_model)
5655
)
57-
self.projector = nn.Linear(self.d_model, self.pred_len, bias=True)
56+
if self.task_name == 'forecast':
57+
self.projector = nn.Linear(self.d_model, self.pred_len, bias=True)
58+
59+
elif self.task_name == 'classification':
60+
self.num_classes = model_args['num_classes']
61+
self.act = F.gelu
62+
self.dropout = nn.Dropout(self.dropout)
63+
self.projector = nn.Linear(self.d_model * self.enc_in, self.num_classes)
64+
else:
65+
raise ValueError(f"Task name {self.task_name} is not supported.")
5866

5967
def forward_xformer(self, x_enc: torch.Tensor, x_mark_enc: torch.Tensor, x_dec: torch.Tensor,
6068
x_mark_dec: torch.Tensor,
@@ -103,9 +111,23 @@ def forward(self, history_data: torch.Tensor, future_data: torch.Tensor, batch_s
103111
torch.Tensor: outputs with shape [B, L2, N, 1]
104112
"""
105113

106-
x_enc, x_mark_enc, x_dec, x_mark_dec = data_transformation_4_xformer(history_data=history_data,
107-
future_data=future_data,
108-
start_token_len=0)
109-
#print(x_mark_enc.shape, x_mark_dec.shape)
110-
prediction = self.forward_xformer(x_enc=x_enc, x_mark_enc=x_mark_enc, x_dec=x_dec, x_mark_dec=x_mark_dec)
111-
return prediction.unsqueeze(-1)
114+
if self.task_name == 'forecast':
115+
x_enc, x_mark_enc, x_dec, x_mark_dec = data_transformation_4_xformer(history_data=history_data,
116+
future_data=future_data,
117+
start_token_len=0)
118+
119+
prediction = self.forward_xformer(x_enc=x_enc, x_mark_enc=x_mark_enc, x_dec=x_dec, x_mark_dec=x_mark_dec)
120+
121+
elif self.task_name == 'classification':
122+
enc_out = self.enc_embedding(history_data[..., 0], None)
123+
enc_out, attns = self.encoder(enc_out, attn_mask=None)
124+
# Output
125+
output = self.act(enc_out) # the output transformer encoder/decoder embeddings don't include non-linearity
126+
output = self.dropout(output)
127+
output = output.reshape(output.shape[0], -1) # (batch_size, c_in * d_model)
128+
prediction = self.projector(output) # (batch_size, num_classes)
129+
130+
else:
131+
raise ValueError(f"Task name {self.task_name} is not supported.")
132+
133+
return prediction

basicts/data/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
from .base_dataset import BaseDataset
2+
from .simple_tsc_dataset import TimeSeriesClassificationDataset
23
from .simple_tsf_dataset import TimeSeriesForecastingDataset
4+
from .uea_dataset import UEADataset
35

4-
__all__ = ['BaseDataset', 'TimeSeriesForecastingDataset']
6+
__all__ = ['BaseDataset', 'TimeSeriesForecastingDataset',
7+
'TimeSeriesClassificationDataset', 'UEADataset']

basicts/data/base_dataset.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
@dataclass
99
class BaseDataset(Dataset):
1010
"""
11-
An abstract base class for creating datasets for time series forecasting in PyTorch.
11+
An abstract base class for creating datasets for time series analysis in PyTorch.
1212
1313
This class provides a structured template for defining custom datasets by specifying methods
1414
to load data and descriptions, and to access individual samples. It is designed to be subclassed
@@ -19,18 +19,11 @@ class BaseDataset(Dataset):
1919
train_val_test_ratio (List[float]): Ratios for splitting the dataset into training, validation,
2020
and testing sets respectively. Each value in the list should sum to 1.0.
2121
mode (str): Operational mode of the dataset. Valid values are "train", "valid", or "test".
22-
input_len (int): The length of the input sequence, i.e., the number of historical data points used.
23-
output_len (int): The length of the output sequence, i.e., the number of future data points predicted.
24-
overlap (bool): Flag to indicate whether the splits between training, validation, and testing can overlap.
25-
Defaults to False to enforce non-overlapping data in different sets, but can be set to True to allow overlap.
2622
"""
2723

2824
dataset_name: str
2925
train_val_test_ratio: List[float]
3026
mode: str
31-
input_len: int
32-
output_len: int
33-
overlap: bool = False
3427

3528
def _load_description(self) -> dict:
3629
"""

basicts/data/simple_inference_dataset.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ def __init__(self, dataset_name:str, dataset: Union[str, list], input_len: int,
3636
"""
3737
train_val_test_ratio: List[float] = []
3838
mode: str = 'inference'
39-
overlap = False
40-
super().__init__(dataset_name, train_val_test_ratio, mode, input_len, output_len, overlap)
39+
super().__init__(dataset_name, train_val_test_ratio, mode)
40+
self.input_len = input_len
41+
self.output_len = output_len
4142
self.logger = logger
4243

4344
self.description = {}

0 commit comments

Comments
 (0)