이번에 알아볼 것은 시계열 데이터 처리간, 시계열 데이터의 전역적인 특징을 추출하는 라이브러리인
TSFEL (Time Series Feature Extraction Library)에 대해서 알아보겠습니다.
[출처 : https://tsfel.readthedocs.io/en/latest/index.html]
1. TSFEL
- Time Series Feature Extraction Library는 효율적이고 간편하게 시계열 데이터의 전역적인 특징을 추출해주는 라이브러리 입니다.
- 이때 Statistical / Temporal / Spectral / Fractal domain으로 나누어서 추출이 가능하며 각 도메인별 포함된 특징들을 더하면, 즉 전체 추출가능한 특징은 총 65개 입니다.
* 해당 특징들의 목록은 다음 url을 통해서 확인할 수 있습니다.
[ https://tsfel.readthedocs.io/en/latest/descriptions/feature_list.html ]
*아래는 특징들의 목록 중 일부
- 또한, 아래와 같은 파이썬 코드를 통해서도 그 목록을 알아볼 수 있습니다.
* tsfel 설치
* 도메인별 특징 목록 추출코드
import tsfel
# 'statistical', 'temporal', 'frequency', 'fractal' 도메인의 모든 특징 불러오기
cfg = tsfel.get_features_by_domain()
# 도출된 특징 확인
for domain, features in cfg.items():
print(f"\nDomain: {domain}")
for feature_name in features.keys():
print(f"- {feature_name}")
* 아래는 해당 결과 중 일부인 spectral 도메인과 statistical 도메인의 특징 벡터들의 목록입니다.
(Spectral : 25개 / Statistical : 20개 / Temporal : 15개 / Fractal : 6개 )
2. TSFEL을 활용한 시계열 분류
- 이번에는 이러한 전역적인 특징들을 가지고 얼마나 각 시계열을 분류할 수 있는지 알아보겠습니다.
- 먼저 시계열 분류 데이터를 불러오기 위해 tsai 라이브러리와 시계열 특징 도출을 위해 tsfel 라이브러리를 설치해줍니다.
- 이후 데이터 셋을 불러와줍니다. 이번에 활용할 데이터는 UWaveGesturLibrary 입니다
from tsai.all import *
import pandas as pd
import tsfel
# 데이터셋 불러오기
X_train, y_train, X_test, y_test = get_UCR_data('UWaveGestureLibrary')
# 데이터셋 형태 확인
print(f'X_train shape: {X_train.shape}, y_train shape: {y_train.shape}')
print(f'X_test shape: {X_test.shape}, y_test shape: {y_test.shape}')
해당 데이터는 Train 120개 / Test 320개의 샘플로 분류되었으며, 총 3개의 채널로부터 정보가 수집되었으며 315의 time step을 가지고 있습니다.
- 이제 tsfel 을 활용해서 전역적인 특징을 추출해보겠습니다. 이때 별다른 도메인 지정없이 전체를 출력해보겠습니다.
import numpy as np
import tsfel
# Feature extraction using TSFEL
def prompt_generation(ts):
cfg = tsfel.get_features_by_domain()
prompt = tsfel.time_series_features_extractor(cfg, ts)
return prompt
# Extract only features from each dimension and return as a 2D array per sample
def extract_only_features(X):
"""
Extract time series features for each dimension and return a 2D array per sample.
"""
features_list = []
for sample in X:
features_per_variate = []
for variate in range(sample.shape[0]):
ts = sample[variate, :].reshape(-1, 1) # 각 채널의 시계열 데이터를 reshape
features = prompt_generation(ts)
features_per_variate.append(features.values) # 각 채널에 대해 추출한 특징을 리스트에 저장
# 각 채널에 대해 추출된 특징을 2D 배열로 병합 (num_variates, num_features)
sample_features = np.concatenate(features_per_variate, axis=0)
features_list.append(sample_features)
# 모든 샘플에 대해 추출된 특징을 3D 배열로 반환 (num_samples, num_variates, num_features)
return np.stack(features_list)
# Load dataset and split into train and test sets
dsid = 'UWaveGestureLibrary' # 데이터셋 ID
X_train, y_train, X_test, y_test = get_UCR_data(dsid, return_split=True)
print(f"Training set shape: {X_train.shape}, Labels: {y_train.shape}")
print(f"Test set shape: {X_test.shape}, Labels: {y_test.shape}")
# Feature extraction (only features) for train and test sets
X_train_features = extract_only_features(X_train)
X_test_features = extract_only_features(X_test)
# 결과 출력
print(f"Extracted train features shape: {X_train_features.shape}")
print(f"Extracted test features shape: {X_test_features.shape}")
* 각 샘플의 채널별 1번씩 특징들의 추출이 진행되기에 시간이 조금 소요됩니다.
* 결과는 아래와 같이 각 채널별 156개의 특징이 추출되었습니다.
- 이를 시각화 해보겠습니다.
# 시각화를 위해 설명된 대로 각 샘플의 3개 채널에 대한 특징을 그리는 함수
def plot_sample_features(features, sample_idx):
num_channels = features.shape[1] # 채널 수 (3개)
num_features = features.shape[2] # 추출된 특성 수 (156개)
fig, axes = plt.subplots(num_channels, 1, figsize=(10, 4 * num_channels))
for i in range(num_channels):
axes[i].plot(range(num_features), features[sample_idx, i, :])
axes[i].set_title(f"Sample {sample_idx+1} - Channel {i+1}")
axes[i].set_xlabel("Feature Index")
axes[i].set_ylabel("Value")
plt.tight_layout()
plt.show()
# X_train_features의 첫 번째 샘플에 대해 시각화
plot_sample_features(X_train_features, sample_idx=0)
* 결과를 보니 특정 값의 크기가 굉장히 큰 것을 알 수있습니다(방향은 음의 방향 / 약 73~5번째 특징)
- 이렇게 특정값의 스케일만 너무 크게되면 학습에 편향을 줄 수있으므로, 추출된 동일 특징별 동일 채널을 기준으로 전체 데이터에 대한 normalizing을 진행해줍니다.
import numpy as np
# 특징별로 정규화를 적용하는 함수
def normalize_features_across_samples(features):
"""
Normalize each feature across all samples and channels.
Args:
features (np.ndarray): Input features of shape (num_samples, num_channels, num_features).
Returns:
np.ndarray: Normalized features.
"""
# features의 shape: (num_samples, num_channels, num_features)
num_samples, num_channels, num_features = features.shape
# 각 채널의 각 특징에 대해 평균과 표준편차 계산
mean = np.mean(features, axis=0) # (num_channels, num_features)
std = np.std(features, axis=0) # (num_channels, num_features)
# 정규화: (값 - 평균) / 표준편차
normalized_features = (features - mean) / (std + 1e-8) # 표준편차가 0인 경우를 방지하기 위해 1e-8 추가
return normalized_features
# X_train_features와 X_test_features에 대해 정규화 적용
X_train_normalized = normalize_features_across_samples(X_train_features)
X_test_normalized = normalize_features_across_samples(X_test_features)
# 정규화된 결과를 확인하기 위해 시각화
plot_sample_features(X_train_normalized, sample_idx=0)
* 이제 어느정도 값이 안정된 것을 확인할 수 있습니다.
- 이를 바탕으로 분류를 진행해보겠습니다. 해당 데이터들은 시간순서적인 특징들이 사라진 전역적인 정보들이기에 일반적인 분류기인 Random Forest Classifier로 분류해보겠습니다
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report
# Train 데이터를 train/validation으로 나누기
X_train_split, X_val_split, y_train_split, y_val_split = train_test_split(
X_train_normalized.reshape(X_train_normalized.shape[0], -1), # 2D로 reshape (num_samples, num_features)
y_train,
test_size=0.2,
random_state=42
)
# RandomForestClassifier를 사용한 학습 및 검증
clf = RandomForestClassifier(n_estimators=100, random_state=42)
clf.fit(X_train_split, y_train_split)
# 검증 세트에 대한 성능 평가
y_val_pred = clf.predict(X_val_split)
val_accuracy = accuracy_score(y_val_split, y_val_pred)
print(f"Validation Accuracy: {val_accuracy:.4f}")
print("Validation Classification Report:")
print(classification_report(y_val_split, y_val_pred))
# 테스트 데이터로 성능 평가
X_test_reshaped = X_test_normalized.reshape(X_test_normalized.shape[0], -1) # 2D로 reshape
y_test_pred = clf.predict(X_test_reshaped)
test_accuracy = accuracy_score(y_test, y_test_pred)
print(f"Test Accuracy: {test_accuracy:.4f}")
print("Test Classification Report:")
print(classification_report(y_test, y_test_pred))
*Test 분류 결과는 0.7219가 나왔습니다.
- 그렇다면 각 도메인별로 나누어서 분류를 진행하면 어떻게 될지 동일한 코드로 진행하되
cfg = tsfel.get_features_by_domain()
부분에 괄호안에 'temporal' 'spectral' 'statistics' 'fractal' 을 번갈아 넣어가며 결과를 도출해보았습니다.
정리된 결과는 아래와 같습니다.
|
All |
Temporal |
Spectral |
Statistical |
Fractal |
Test Accuracy |
0.7219 |
0.7969 |
0.5687 |
0.4875 |
0.7969 |
전반적으로 보았을때, Temporal 및 Fractal에 해당하는 Feature들을 활용하는 것이 분류에 좋을 것으로 생각되나 더 좋은 조합이 있을 수 있으며 데이터셋마다 다를 수 있기에 최적의 결과를 찾아내기위해서는 여러번의 실험이 추가적으로 필요하겠습니다.
댓글