본문 바로가기
딥러닝 with Python

[딥러닝 with Python] TSFEL(Time Series Feature Extraction Library) / 시계열 특징 추출 라이브러리

by CodeCrafter 2024. 10. 18.
반응형

 

이번에 알아볼 것은 시계열 데이터 처리간, 시계열 데이터의 전역적인 특징을 추출하는 라이브러리인

 

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 설치

!pip install 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 라이브러리를 설치해줍니다.

!pip install 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들을 활용하는 것이 분류에 좋을 것으로 생각되나 더 좋은 조합이 있을 수 있으며 데이터셋마다 다를 수 있기에 최적의 결과를 찾아내기위해서는 여러번의 실험이 추가적으로 필요하겠습니다.

반응형

댓글