Deep Learning/Anomaly Detection

[Python] Deep SVDD DataLoader on Time Series Data

sdbeans 2022. 1. 23. 17:02

Paper Review: https://emptydb.tistory.com/3

 

새로운 시계열 데이터셋 다섯개를 Deep SVDD 모델에 돌리기 위해 data loader 파일을 새로 만들고, network layer들의 파라미터도 변경했다. DataLoader는 아예 start from scratch이고, autoencoder와 encoder 부분은 데이터의 dimension과 size에 따라 바꿔주었다.

 

아쉬운 점:

하루만에 100줄 조금 넘는 코드를 짰다... 사실 처음 보는 arff 형 파일 때문에 이미 지칠 때로 지쳤던 상태라 모듈을 사용할 생각은 하지 못했다. 다른 dataset은 그냥 복사 붙여 넣기로 코드를 수정해서 새로운 data loader를 만들었다. 다음에 이런 식으로 data loader를 만들 때는 조금 더 체계적으로 짜고 싶다. 중복되는 함수는 하나의 파일로 묶어서 사용하게 하고 싶다. 다만 각 데이터셋마다 dimension이 달라서 함수의 역할은 같아도 구조가 달랐다. 예를 들어 data label을 그냥 int로 변환시키는 함수를 만들었는데, 어떤 data는 byte형의 label을 달고 있었고, 또 다른 data는 float형의 label을 달고 있었다. 함수의 역할은 모두 다 int형 label로 바꾸는 것이었지만, 바뀌기 전의 데이터가 다 달랐기 때문에 같은 함수를 복붙 하는 것으로는 해결되지 않았다.

 

잘했다고 생각하는 점:

모든 함수에 docstring을 달았고 class 코드 사이사이 주석으로 설명도 달았다. 원본 코드에 있는 주석이 코드 해석하는 나에게 꽤나 도움이 되었기에 나도 다른 사람이 구조를 이해하기 쉬운 코드를 짜고 싶어서 코드의 역할을 군데군데 적어두었다.

 


My Code for Deep SVDD on Time-Series Data:

DataLoader 중 하나

import pandas as pd
import numpy as np
import torch

from scipy.io import arff
from base.torchvision_dataset import TorchvisionDataset
from torch.utils.data import TensorDataset

class EP_Dataset(TorchvisionDataset):

    def __init__(self, root: str, normal_class):

        super().__init__(root)
        self.n_classes = 2
        self.normal_class = normal_class

        # train set
        #load data file path
        url1_train = '../data/ep/EpilepsyDimension1_TRAIN.arff'
        url2_train = '../data/ep/EpilepsyDimension2_TRAIN.arff'
        url3_train = '../data/ep/EpilepsyDimension3_TRAIN.arff'

        # get x and y as dataframe
        x_dim1_train, target_train = get_data(url1_train)
        x_dim2_train, __ = get_data(url2_train)
        x_dim3_train, __ = get_data(url3_train)

        # combine 3 dimensions of x
        x_train = np.dstack([x_dim1_train, x_dim2_train, x_dim3_train])
        # process output y and produce index
        y_train, index_train = get_target(target_train, normal_class)

        # train only on normal data, extracting normal data
        x_final_train, y_final_train, index_final_train = get_training_set(x_train, y_train, index_train)

        # print("size: ", x_final_train.shape)
        train_set = TensorDataset(torch.Tensor(x_final_train), torch.Tensor(y_final_train), torch.Tensor(index_final_train))
        self.train_set = train_set

        # set up testing set
        url1_test = '../data/ep/EpilepsyDimension1_TEST.arff'
        url2_test = '../data/ep/EpilepsyDimension2_TEST.arff'
        url3_test = '../data/ep/EpilepsyDimension3_TEST.arff'

        x_dim1_test, target_test = get_data(url1_test)
        x_dim2_test, __ = get_data(url2_test)
        x_dim3_test, __ = get_data(url3_test)

        x_final_test = np.dstack([x_dim1_test, x_dim2_test, x_dim3_test])
        y_final_test, index_test = get_target(target_test, normal_class)

        test_set = TensorDataset(torch.Tensor(x_final_test), torch.Tensor(y_final_test), torch.Tensor(index_test))
        self.test_set = test_set


def get_data(url):
    """
    input: path to arff data file
    This function loads the arff file, then converts into dataframe.
    The dataframe is then split into x and y.
    output: x is dataframe object without the last column. y is series.
    """
    loaded = arff.loadarff(url)
    df = pd.DataFrame(loaded[0])
    
    # dropping the last column of dataframe
    # it is still a dataframe object
    x = df.iloc[:, :-1].to_numpy()

    # getting last column as series, not dataframe object
    # as dataframe object is using iloc[:, -1:]
    y = df.iloc[:, -1]

    return x, y


def get_target(y, normal_class):
    """
    input: pandas series. last column of dataframe.
    This function converts the byte string of series and compare to each classification group
    Each class is represented as a number.
    output: returns numpy array of numbers and index array
    """
    y_new = []
    y_temp = []
    idx = []
    length = len(y)

    for i in range(0, length):
        if y[i].decode('UTF-8') == 'EPILEPSY':
            y_temp.append(0)
        elif y[i].decode('UTF-8') == 'SAWING':
            y_temp.append(1)
        elif y[i].decode('UTF-8') == 'RUNNING':
            y_temp.append(2)
        elif y[i].decode('UTF-8') == 'WALKING':
            y_temp.append(3)
        idx.append(i)

    for i in range(0, length):
        if y_temp[i] == normal_class:
            y_new.append(0) # normal
        else:
            y_new.append(1) # anomaly

    return np.array(y_new), np.array(idx)

def get_training_set(x, y, idx):
    """
    Input: x, y, index of training set from data file
    This function only collects the normal data from train set.
    The model only trains on normal data of the train set.
    Output: x, y, index of normal data only in train set.
    """
    x_final = []
    y_final = []
    idx_final = []

    for i in range(0, len(x)):
        if y[i] == 0:
            x_final.append(x[i])
            y_final.append(y[i])
    
    for i in range(0, len(x_final)):
        idx_final.append(i)
    
    return np.array(x_final), np.array(y_final), np.array(idx_final)