0. Source Code 위치 (Orion)
앞써 포스팅에서
AER 관련된 논문리뷰를 하였고
소스코드는 아래 링크에 자세히 올라와져 있다
https://github.com/sintel-dev/Orion/tree/master
GitHub - sintel-dev/Orion: Unsupervised time series anomaly detection library
Unsupervised time series anomaly detection library - sintel-dev/Orion
github.com
aer관련된 튜토리얼은 아래 두 경로에 있는데
Orion/tutorials/Orion_on_Custom_Data.ipynb
Orion/tutorials/pipelines/aer.ipynb
이 경로가
Orion/tutorials/pipelines/aer.ipynb
aer에 대한 자세한 Pipeline 설명과 단계 별 설명이 step by step으로 나와있어서 좋다
하지만 여기에 custom Data를 넣으려면 약간의 작업이 필요해서 이 과정을 정리하고자 한다
1. 데이터 준비 (Prepare Data)
row data는 너무 중구 남방이고 이를 time stamp로 가져가기엔 무리가 있어
아래와 같이 전처리를 하였다
unit_set = 'min'
df_resampled = sorted_df.set_index('time_stamp').resample(unit_set).agg({
'is_defect': lambda x: (x == 1).sum(),
'is_checked': lambda x: (x == 1).sum()
}).reset_index()
df_resampled["ppm"] = (df_resampled['is_defect']/df_resampled['is_checked'])*1000000
df_resampled["persent"] =(df_resampled['is_defect']/df_resampled['is_checked'])*100
여기서 주목할 것은
Datafram resample 매소드 인데 위에 코드와 같이 parameter를 unit_set로 던진 다음
아래 옵션들 중 선택하면 된다
'D': 일별 'M': 월별 'W': 주별
'H': 시간별 'T' or 'min': 분별 'S': 초별 'L':
밀리초별 'ms': 밀리초별 'U': 마이크로초별 'N': 나노초별
resample을 언급한 이유는 아래
Pre-processing에 hyperparameter를 결정할 때 참고하기 위해서 이다
이건 내 데이터에 대한 전처리 이고 사실 상 data라는 변수에 아래 포멧으로 들어가 있으면 된다
주의 해야할 것은 timestamp 가 datatime이 아닌 int 로 들어 간것
공식은 다음과 같다
'timestamp': timestamps.values.astype(np.int64) // 10 ** 9,
자세한 건 해당 링크(클릭) 에 잘 설명되어 있다 (흐름 상 생략)
2. pipeline 정의
다음으로는 아래의 코드가 나와 있다
from mlblocks import MLPipeline
pipeline_name = 'aer'
pipeline = MLPipeline(pipeline_name)
이건 MLPipeline에 있는 aer pipeline을 따르겠다는 뜻이다
이렇게 pipeline을 불러 왔으면 본 게임이 시작된다
먼저 아래의 코드를 실행 시켜보자 (코드내에 있음)
얘네는 primitives라는 것을 쓰는데
설명에 다음과 같이 적혀있다
MLPipelines are compose of a squence of primitive, these primitives apply tranformation and calculation operations to the data and updates the variables within the pipeline. To view the primitives used by the pipeline, we access its `primtivies` attribute.
그래서 보려면 primitives 실행하라 하므로 실행해보면
pipeline.primitives
이렇게 각 단계가 나오고적혀 있는 순서 대로 진행이 된다는 뜻이다
그리고 튜토리얼은
The `lstm_dynamic_threshold` contains 7 primitives. we will observe how the `context` (which are the variables held within the pipeline) are updated after the execution of each primitive.
이 7개가 어떻게 변하는지 보여 준다고 한다
라이브러리 까지 적혀있어 헷갈리지만 요약하면 다음과 같다
Pipeline 요약
time_segments_aggregate
SimpleImputer
MinMaxScaler
rolling_window_sequences
slice_array_by_dims
AER
score_anomalies
find_anomalies
여기에서 우리는 각 요소마다 Hyperparameter를 지정해줘야 한다
근데 내가 이 녀석들의 Hyperparameter 명도 모르고 기본 default 가 어떤 값인지도 모르는데
어떻게 설정할까?
각 Step 별 Hyperparameter확인
pipeline.get_hyperparameters()
위에 코드를 실행해 주면 현재 설정되어 있는 parameter들이 나온다
우리는 각 step 별 내가 필요한 Hyperparameter를 가져와 수정해주면 된다
(전체 복붙한 코드는 맨 아래에 첨부)
<Default 값 참고용>
{'mlstars.custom.timeseries_preprocessing.time_segments_aggregate#1': {'interval': 21600, 'time_column': 'timestamp', 'method': 'mean'}, 'sklearn.impute.SimpleImputer#1': {'missing_values': nan, 'fill_value': None, 'copy': True, 'strategy': 'mean'}, 'sklearn.preprocessing.MinMaxScaler#1': {'feature_range': [-1, 1], 'copy': True}, 'mlstars.custom.timeseries_preprocessing.rolling_window_sequences#1': {'window_size': 100, 'target_size': 1, 'step_size': 1, 'target_column': 0, 'offset': 0, 'drop_windows': False}, 'orion.primitives.timeseries_preprocessing.slice_array_by_dims#1': {'axis': 2}, 'orion.primitives.aer.AER#1': {'optimizer': 'tensorflow.keras.optimizers.Adam', 'learning_rate': 0.001, 'reg_ratio': 0.5, 'epochs': 35, 'batch_size': 64, 'lstm_units': 30, 'callbacks': [{'class': 'tensorflow.keras.callbacks.EarlyStopping', 'args': {'monitor': 'val_loss', 'patience': 10, 'min_delta': 0.0003}}], 'validation_split': 0.2, 'shuffle': True, 'verbose': True, 'layers_encoder': [{'class': 'tensorflow.keras.layers.Bidirectional', 'parameters': {'layer': {'class': 'tensorflow.keras.layers.LSTM', 'parameters': {'units': 'lstm_units', 'return_sequences': False}}, 'merge_mode': 'concat'}}], 'layers_decoder': [{'class': 'tensorflow.keras.layers.RepeatVector', 'parameters': {'n': 'repeat_vector_n'}}, {'class': 'tensorflow.keras.layers.Bidirectional', 'parameters': {'layer': {'class': 'tensorflow.keras.layers.LSTM', 'parameters': {'units': 'lstm_units', 'return_sequences': True}}, 'merge_mode': 'concat'}}, {'class': 'tensorflow.keras.layers.TimeDistributed', 'parameters': {'layer': {'class': 'tensorflow.keras.layers.Dense', 'parameters': {'units': 1}}}}]}, 'orion.primitives.aer.score_anomalies#1': {'comb': 'mult', 'lambda_rec': 0.5, 'mask': True, 'rec_error_type': 'dtw'}, 'orion.primitives.timeseries_anomalies.find_anomalies#1': {'z_range': [0, 12], 'window_size': 2000, 'window_step_size': 200, 'lower_threshold': False, 'fixed_threshold': True, 'inverse': False, 'window_size_portion': 0.33, 'window_step_size_portion': 0.1, 'min_percent': 0.13, 'anomaly_padding': 50}}
Hyperparameter 수정
hyperparameters = {
'mlstars.custom.timeseries_preprocessing.time_segments_aggregate#1': {
'interval' : 60*1 #sec
},
'orion.primitives.aer.AER#1': {
'epochs': 5, #5
'verbose': True,
# 'window_size' : 7, #test Data랑 나누어 떨어져야 함
# 'batch_size' : 32
}
}
pipeline.set_hyperparameters(hyperparameters)
위에 처럼 때려 박으면 된다는 뜻이고 각 단계 마다 적어 보도록 하겠다
3. time_segments_aggregate
time (시간)_ segments(조각,부분)_aggregate(집계)
즉, Time series Data를 나눈고 집계하는 부분이다.
이것 때문에 신기한 현상이 많이 나와 hyperparameter를 찾아 보았다
time segments aggregatethis primitive creates an equi-spaced time series by aggregating values over fixed specified interval.
input : `X` which is an n-dimensional sequence of values.
output:
- `X` sequence of aggregated values, one column for each aggregation method.
- `index` sequence of index values (first index of each aggregated segment).
튜토리얼에는 이렇게 써 있다
- 기본요소는 지정된 고정 간격에 걸쳐 값을 집계하여 동일한 간격의 시계열 데이터를 생성한다.
그냥 "동일한 간격"을 만들어 주는 부분이라고 생각하면 된다
Default Parameter 종류는 다음과 같다
'mlstars.custom.timeseries_preprocessing.time_segments_aggregate#1':{
'interval': 21600,
'time_column': 'timestamp',
'method': 'mean'}
'mlstars.custom.timeseries_preprocessing.time_segments_aggregate#1': {
'interval': 21600, # 데이터를 6시간(21600초) 단위로 그룹핑하여 집계
'time_column': 'timestamp', # 시간 기준이 될 컬럼명
'method': 'mean' # 그룹 내 값들의 평균을 계산
},
일반적으로 Row Data를 뽑으면 시간 간격이 지 마음대로여서 DTW를 사용한다고 해도 Noise가 많다
그걸 좀 완화 시키기 위해 interval hyperparameter를 사용한다
hyperparameters = {
'mlstars.custom.timeseries_preprocessing.time_segments_aggregate#1': {
'interval' : 60*1 #sec
}
}
sec 단위 이고 본인이 60초를 사용한 이유는
나중에 그래프에 score 값들도 같이 띄워서 확인하고 싶고
위에 전처리 과정에서 unit_set을 "min"으로 했기 때문이다.
(한마디로 단위를 맞춰주는 것이 정신건강에 좋다)
근데 Default 값은 21600 이다.
21600 = 6*60*60 이고 6시간 기준으로 집계하겠다는 것이다
그래서
해당 step의 pipeline에 input과 output 개수를 찍어보면
step = 0
context = pipeline.fit(data, output_=step)
print(len(data),len(context['index']))
context.keys()
13만개 데이터가 갑자기 377개로 줄어들어서 당황할 것이다.
(적당히 설정해서 사용하도록 하자)
그 외에 time_column에는 내 Data의 timestamp가 담겨있는 Column 명을 적는 거다
'time_column': 'timestamp'
'method': 'mean'
따로 이거 설정하기 귀찮아서 나는 timestamp로 바꿧지만
다른 이름을 쓰고 있다면 설정해 주자.
Method의 경우 위에서 internal로 묶을 때 그 값을 Max로 할지 평균으로 할지 등을 결정한다
(Mean이 가장 일반적인 것 같다)
4. SimpleImputer
이게 뭔지 설명하기 전에 Hyperparameter를 보면 감이 온다
'sklearn.impute.SimpleImputer#1': {
'missing_values': nan, # 누락된 값으로 처리할 값 (NaN)
'fill_value': None, # 특정 값으로 채울 때 사용할 값 (strategy='constant'일 때 사용)
'copy': True, # 입력 데이터를 복사할지 여부
'strategy': 'mean' # 결측치를 채울 방식: 평균값으로 대체
},
딱봐도 비어있는 값들을 채워주는 부분이다
step = 1
context = pipeline.fit(**context, output_=step, start_=step)
context.keys()
튜토리얼 에도 다음과 같이 적혀있다
this primitive is an imputation transformer for filling missing values.
input: `X` which is an n-dimensional sequence of values.
output : `X` which is a transformed version of X.
먼저 missing_values의 경우 어떤 값을 누락된 값으로 볼지를 결정해 준다 (default nan) - np.nan그리고 이 결측치를 어떻게 채울지에 대해서 strategy를 통해 결정해준다(mean, median, most_frequent, constant)fill_value의 경우는 strategy = 'constant' 일때 어떤 고정 값을 사용할 지를 결정하는 것이다copy의 경우는 지금 data -> data 꼴인데 이걸 inplace 할지 말지 결정하는 것이다 - 메모리 효율 (False면 inplace)
5. MinMaxScaler
디폴트로 들어가있는 값은
'sklearn.preprocessing.MinMaxScaler#1': {
'feature_range': [-1, 1], # 정규화 범위: -1에서 1 사이로
'copy': True # 원본 데이터를 복사해서 변환 (True면 inplace 아님)
},
Scaling을 해주는 부분이다
이에대한 설명은 다음과 같고
this primitive transforms features by scaling each feature to a given range.
input: `X` the data used to compute the per-feature minimum and maximum used for later scaling along the features axis.
output: `X` which is a transformed version of X.
관련코드는
step = 2
context = pipeline.fit(**context, output_=step, start_=step)
context.keys()
여기까지도
context.keys()의 출력은 dict_keys(['index', 'X']) 이다
아래와 같이 Standard Scaler 로 바꿀 수 있다
(바꿀시 MinMaxscaler hyperparameter부분은 삭제해 주자)
- StandardScaler로 교체 방법
만약 Minmax가 아닌 standard로 바꾸고 싶으면 아래 코드로 바꿔주면 된다
from mlblocks import MLPipeline
pipeline = MLPipeline('aer')
# MinMaxScaler 제거 (필요시)
pipeline.remove_step('sklearn.preprocessing.MinMaxScaler#1')
# StandardScaler로 교체
pipeline.set_hyperparameters({
'sklearn.preprocessing.StandardScaler#1': {
'copy': True,
'with_mean': True,
'with_std': True
}
})
어떤 걸 쓰지는 아래를 참고하자
6. rolling_window_sequences
하나의 시계열 데이터를
고정길이 (window_size)의 시퀀스와 Target을 쌍으로 만들어 주는 역할이다
target_size가 1이면 단일 값, >1이면 시퀀스 이다
offset의 경우 입력과 타깃 사이의 거리이고 0일 때 input 바로 다음이 target이다
(마지막 행같이 조건을 맞지 않는 항목을 Drop하고 싶으면 drop_windows를 True로 설정하면 된다)
'mlstars.custom.timeseries_preprocessing.rolling_window_sequences#1': {
'window_size': 100, # 입력 시계열을 몇 개 timestep 단위로 자를지 (슬라이딩 윈도우 크기)
'target_size': 1, # 예측할 future step 수 (보통 1)
'step_size': 1, # 윈도우 슬라이딩 간격 (1이면 전체 윈도우 생성)
'target_column': 0, # 예측할 타깃 컬럼 (0번째 컬럼)
'offset': 0, # 타깃이 위치한 시점까지의 거리 (0이면 현재)
'drop_windows': False # True면 일부 조건에 맞는 윈도우를 버림 (보통 False)
},
맨 앞에서 부터 100개씩(default) 묶기 때문에 실제 데이터의 개수는 window size만큼 줄어들게 된다
(순방향의 경우 앞에가 없어짐)
step_size는 윈도우가 슬라이딩하는 간격이다
그리고 target_column일 경우에는 예측할 다음 sequence의 index를 설정하는 것이다
이에 대한 설명은 다음과 같다
this primitive generates many sub-sequences of the original sequence.
it uses a rolling window approach to create the sub-sequences out of time series data.
input:
- `X` n-dimensional sequence to iterate over.
- `index` array containing the index values of X.
output:
- `X` input sequences.
- `y` target sequences.
- `index` first index value of each input sequence.
- `target_index` first index value of each target sequence.
실행은 아래의 코드로 실행 시킨다
step = 3
context = pipeline.fit(**context, output_=step, start_=step)
context.keys()
7. slice_array_by_dims
소스 코드에 첨부되어 있는 Markdown 부터 봐보자
this primitive selects a particular channel from `X` to try and reconstruct / predict it.
input :
`X` n-dimensional array containing the input sequences.
target_index which index from `X` to slice out.
output:
`y` 1-dimenstional array containing the target sequences.
AER은 기본적으로 shape이 (num_samples, window_size) 와 같은 (100000,100) 이런 거를 기대한다.
하지만 input으로 들어가는 녀석의 shape 가 (num_samples, window_size, num_features) 이러면 ValueError 가 나온다
실제로 찍어보면 feature가 1개인 Sequence Data의 경우 (1001234,100,1) 이런 식으로 되어있어 마지막 ( , ,1)은 사실상 아무 필요 없는 숫자 이기 때문에 제거를 해주는 것이 좋다
그래서 hyperparameter를 보면
'orion.primitives.timeseries_preprocessing.slice_array_by_dims#1': {
'axis': 2 # 입력 배열에서 2번째 축을 기준으로 자름 (채널 축 분리)
},
가 들어 간다. (일종의 Squeeze 효과)
그래서 row code를 적으면 다음과 같지 않을까 싶다
import numpy as np
X = np.random.rand(500,100,1) #(num_samples, window_size, num_features)
#slice_array_ny_dims(axis=2)
X_sliced = np.suqeeze(X, axis=2)
print(X_sliced.shape) #(500,100)
하지만 AER Orion 은 mlblock의 MLPipeline을 쓰기 때문에
다음과 같이 구현 되어 있다
step = 4
context = pipeline.fit(**context, output_=step, start_=step)
context.keys()
그러면 num_feature > 1 인 다변량 데이터는 어떡할까?
안해봐서 모르겠지만 그대로 냅두고 해당 block을 없앤 다음에
AER 모델 부분을 수정해야지 않을까 싶다
8. AER
딱봐도 모델이 구현된 부분인데 실행되고 있는 코드는 위에랑 똑같이 매우 간결하다
step = 5
context = pipeline.fit(**context, output_=step, start_=step)
context.keys()
이게 간편하다고 생각할 수도 있지만 뭔가 많이 감춰져 있다고 생각할 수도 있다
여기서 불러와 지는 mlblock 은 (애초에 pipeline 변수 자체가 from mlblock import MLPipeline을 통해서 선언됨)
orion.primitives.aer.AER#1
이다.
이에 대해 설명은
this is a hybrid prediction and reconstruction model using LSTM layers.
you can read more about it in the related paper(https://arxiv.org/pdf/2212.13558).
input:
- `X` n-dimensional array containing the input sequences for the model.
- `y` n-dimensional array containing the target sequences for the model.
output:
- `y_hat` predicted values.
라고 나와 있다
요약하자면 LSTM을 사용한 AER 모델이 여기 담겨 있고
X : input Data
y : label (predict나 reconstruct의 결과와 비교할 Ground Truth)
y_hat : 예측값(predict나 reconstruct의 결과) 이다
이 step에서 아래와 같은 학습이 이루어 진다
Hyperparmeter가 좀 많아서 3 part로 구분을 해보면 (call back은 생략)
먼저 학습에 사용되는 부분이다
'orion.primitives.aer.AER#1': {
'optimizer': 'tensorflow.keras.optimizers.Adam', # 최적화 알고리즘
'learning_rate': 0.001, # 학습률
'reg_ratio': 0.5, # autoencoder와 regressor의 loss 가중치 비율
'epochs': 5, # 전체 학습 반복 횟수
'batch_size': 64, # 배치 크기
'lstm_units': 30, # LSTM 셀의 유닛 수
'validation_split': 0.2, # 전체 데이터 중 20%를 검증용으로 분리
'shuffle': True, # 학습 시 배치 샘플을 섞을지 여부
'verbose': True, # 학습 과정 로그 출력 여부
'window_size': 7 # 모델 내부에서의 시계열 윈도우 크기 (슬라이싱이나 repeat에 사용)
사실 상 여기에서 정말 중요하다고 생각하는 것은
'orion.primitives.aer.AER#1': {
'epochs': 5, # 전체 학습 반복 횟수
'batch_size': 64, # 배치 크기
'verbose': True, # 학습 과정 로그 출력 여부
'window_size': 7 # 모델 내부에서의 시계열 윈도우 크기 (슬라이싱이나 repeat에 사용)
}
이렇게 4개의 parameter는 꼭 직접 결정을 해주는 것이 좋은 것 같다
optimizer는 Adam을 사용하고 기본적인 Epoch는 5로 설정 되어 있다 batchsize는 64로 되어 있는데
데이터의 양에 따라 적절히 조절해주는 것이 좋다
그 다음은 Encoder 부분이다
# 인코더 구조: 양방향 LSTM
'layers_encoder': [
{
'class': 'tensorflow.keras.layers.Bidirectional',
'parameters': {
'layer': {
'class': 'tensorflow.keras.layers.LSTM',
'parameters': {
'units': 'lstm_units', # 위에 지정한 lstm_units 사용
'return_sequences': False
}
},
'merge_mode': 'concat' # 양방향 LSTM의 출력 결합 방식
}
}
],
기본적으로 논문에서 말한 Bidirection을 사용하고
LSTM을 사용하는 Encoder 이다 (사실 상 수정할 건 없음)
마지막은 Decoder 이다
# 디코더 구조: RepeatVector + Bidirectional LSTM + TimeDistributed Dense
'layers_decoder': [
{
'class': 'tensorflow.keras.layers.RepeatVector',
'parameters': {
'n': 'repeat_vector_n' # 인코딩된 벡터를 다시 시계열로 복제
}
},
{
'class': 'tensorflow.keras.layers.Bidirectional',
'parameters': {
'layer': {
'class': 'tensorflow.keras.layers.LSTM',
'parameters': {
'units': 'lstm_units',
'return_sequences': True
}
},
'merge_mode': 'concat'
}
},
{
'class': 'tensorflow.keras.layers.TimeDistributed',
'parameters': {
'layer': {
'class': 'tensorflow.keras.layers.Dense',
'parameters': {
'units': 1 # 타깃 출력 크기
}
}
}
}
],
encoder랑 거의 동일하고 Multivariate의 경우만 units 을 건드려주면 될 것 같다
구현코드는 마찬가지로
step = 5
context = pipeline.fit(**context, output_=step, start_=step)
context.keys()
이다.
딱 여기까지가 모델을 학습 시킨 부분이다
9. score_anomalies
모델을 학습했으면 Anomaly Score를 계산해야 한다
논문에서 언급했 듯 Anomaly Score는 Reconstruction error 와 Prediction error를 이용하여 계산 한다
첨부된 설명을 보면
this primitive computes an array of errors comparing predictions and expected output.
input:
- `y` ground truth.
- `y_hat` reconstructed values.
- `fy_hat` forward prediction values.
- `ry_hat` reverse prediction values.
output: `errors` array of errors.
Prediction 과 expected output를 비교하여 Error를 계산하는 부분이라고 한다
y 가 ground truth 이고
y_hat은 reconstruct로 만든 값
fy_hat 과 ry_hat은 각각 순방향과 역방향으로 예측한 값이다
그리고 output으로 error 점수들을 담은 배열을 출력한다
그래서 Hyperparameter도 다음과 같고
'orion.primitives.aer.score_anomalies#1': {
'comb': 'mult', # reconstruction과 regression score를 곱셈으로 결합
'lambda_rec': 0.5, # reconstruction error 비중
'mask': True, # window 중심만 score에 반영할지 여부
'rec_error_type': 'dtw' # reconstruction error 계산 방식: DTW (Dynamic Time Warping)
},
논문 피셜로 dtw와 mult 조합이 가장 좋다고 하니 안 건드리는 것이 좋아 보이나
reconstruction 과 prediction의 비율을 결정하는 lambda_rec 는 조금 바꿔보는 것도 좋은 시도인 것같다
(mask도 쓰는 게 더 효과적이라고 한다)
10. find_anomalies
this primitive extracts anomalies from sequences of errors following the approach
explained in the [related paper](https://arxiv.org/pdf/1802.04431.pdf).
input:
- `errors` array of errors.
- `target_index` array of indices of errors.
output: `y` array containing start-index, end-index, score for each anomalous sequence that was found.
이 부분이 위에서 산출한 errors 와 각 error가 산출된 timestamp 즉 target_index를 가지고
최종 출력 y를 만들어 내는 것이다
최종 출력은 다음과 같다
Hyperparameter는
'orion.primitives.timeseries_anomalies.find_anomalies#1': {
'z_range': [0, 12], # z-score 임계값 탐색 범위
'window_size': 2000, # 이상 탐지 시 사용하는 window 크기
'window_step_size': 200, # 슬라이딩 윈도우의 간격
'lower_threshold': False, # 하한값 기준으로도 anomaly 검출할지 여부
'fixed_threshold': True, # z-score 임계값을 고정할지 여부
'inverse': False, # score가 작을수록 이상인가? (False면 클수록 이상)
'window_size_portion': 0.33, # anomaly padding 적용 시 한 윈도우에서 이상 비율 기준
'window_step_size_portion': 0.1, # 윈도우 슬라이딩 비율 (전체 길이의 10%)
'min_percent': 0.13, # 윈도우 안에서 이상으로 판단될 최소 비율
'anomaly_padding': 50 # 이상 감지 구간에 패딩을 주는 길이 (경계 부드럽게)
}
이거 이고 이 Anomaly 인지 아닌지는 error를 z-score 기반으로 통계 분석을 하여서 산출하는데
z_range라는 것은 z-score를 0~12까지 바꿔가면서 구한 다음 F1 Score 가 제일 높은 값을 채택한다는 것이다
물론 여기까지만 해도 되지만
우리는 Dataframe으로 관리하는 것이 더 편하기 때문에
train_anomalies =pd.DataFrame(context['y'], columns=['start', 'end', 'severity'])
train_result = pd.DataFrame({
'start': pd.to_datetime(train_anomalies['start'], unit='s'),
'end': pd.to_datetime(train_anomalies['end'], unit='s'),
'serverity' : train_anomalies['severity']
})
train_result
이걸로 Dataframe으로 바꿔주면 된다
- 시각화
이제 여기에 시각화를 곁들이면
error_delete_num = len(train_data_df['time_stamp']) - len(train_error['time_stamp'])
plt.figure(figsize=(15,6))# 원본 데이터 라인 플롯
plt.plot(train_data_df['time_stamp'], train_data_df[value_key], label='Value', color='blue')# anomaly 구간 그리기
plt.plot(train_error['time_stamp'], train_error['errors'], label='errors', color='red', alpha=0.7)# anomaly 구간 그리기
for _, row in train_result.iterrows():
plt.axvspan(row['start'],
row['end'],
color='green', alpha=0.3)
# plt.ylim(0,30)
plt.xlabel('Timestamp')
plt.ylabel('Value')
plt.title('Anomaly Intervals')
plt.legend()
plt.show()
이런 그래프를 얻을 수 있다
근데 우리는 그리다 보면
여러 개의 그래프를 한 Block에 다 그리고 싶을 때가 있다 그럴 땐 이 코드를 쓰면 된다
- 여러 개의 그래프 그리기
plt.figure(figsize=(20,20))
plt_row = 3
plt_column = 2
plt_key = ['cum_defect_rate','cum_defect_rate','defect_rate','defect_rate','ewma','ewma']
for idx in range (0,plt_row*plt_column):
plt_index = int(str(plt_row)+str(plt_column)+str(idx+1))
plt.subplot(plt_index)
plt.plot(df_resampled['time_stamp'],df_resampled[plt_key[idx]])#,linestyle='-')#,marker='o')
plt.title(plt_key[idx])
plt.xlabel('time')
plt.ylabel('error')
#plt.ylim(0,100)
if idx %2 == 1: #특수한 조건들이 필요할 때
plt.xlim(df_resampled['time_stamp'][11000],df_resampled['time_stamp'][15000])
plt.grid(True)
plt.legend()
#plt.tight_layout()
plt.show()
plt_key는 Title에 다른 글자들을 적기 위해 작성한 코드 이다
- 모델 저장하기/불러오기
위에 코드를 다시하면 모델을 다시 돌려볼 수 없다
근데 나 같은 경우는 학습한 모델을 계속 Predict를 하는 경우이다
(이 모델을 가지고 또 재학습하는 fine tuning 같은 건 안할 예정이다)
이럴 때는 joblib을 사용하면 된다
(주의! 모델을 불러와서 Predict만 가능)
그래서 아래와 같은 Warning 창이 나온다
WARNING:tensorflow:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.-> 이거 컴파일 안되어서 predict 만 가능하고 train이나 evaluate는 안된다는 뜻'optimizer': 'tensorflow.keras.optimizers.Adam'
# !pip install joblib
import joblib
# 모델 저장
joblib.dump(pipeline,save_model_path)
# 모델 불러오기
my_model = joblib.load(save_model_path)
유첨. Hyperparameter
필요하다면 아래를 붙여넣기 하자
{
'mlstars.custom.timeseries_preprocessing.time_segments_aggregate#1': {
'interval': 21600, # 데이터를 6시간(21600초) 단위로 그룹핑하여 집계
'time_column': 'timestamp', # 시간 기준이 될 컬럼명
'method': 'mean' # 그룹 내 값들의 평균을 계산
},
'sklearn.impute.SimpleImputer#1': {
'missing_values': nan, # 누락된 값으로 처리할 값 (NaN)
'fill_value': None, # 특정 값으로 채울 때 사용할 값 (strategy='constant'일 때 사용)
'copy': True, # 입력 데이터를 복사할지 여부
'strategy': 'mean' # 결측치를 채울 방식: 평균값으로 대체
},
'sklearn.preprocessing.MinMaxScaler#1': {
'feature_range': [-1, 1], # 정규화 범위: -1에서 1 사이로
'copy': True # 원본 데이터를 복사해서 변환 (True면 inplace 아님)
},
'mlstars.custom.timeseries_preprocessing.rolling_window_sequences#1': {
'window_size': 100, # 입력 시계열을 몇 개 timestep 단위로 자를지 (슬라이딩 윈도우 크기)
'target_size': 1, # 예측할 future step 수 (보통 1)
'step_size': 1, # 윈도우 슬라이딩 간격 (1이면 전체 윈도우 생성)
'target_column': 0, # 예측할 타깃 컬럼 (0번째 컬럼)
'offset': 0, # 타깃이 위치한 시점까지의 거리 (0이면 현재)
'drop_windows': False # True면 일부 조건에 맞는 윈도우를 버림 (보통 False)
},
'orion.primitives.timeseries_preprocessing.slice_array_by_dims#1': {
'axis': 2 # 입력 배열에서 2번째 축을 기준으로 자름 (채널 축 분리)
},
'orion.primitives.aer.AER#1': {
'optimizer': 'tensorflow.keras.optimizers.Adam', # 최적화 알고리즘
'learning_rate': 0.001, # 학습률
'reg_ratio': 0.5, # autoencoder와 regressor의 loss 가중치 비율
'epochs': 5, # 전체 학습 반복 횟수
'batch_size': 64, # 배치 크기
'lstm_units': 30, # LSTM 셀의 유닛 수
'callbacks': [ # 학습 중단 조건 설정
{
'class': 'tensorflow.keras.callbacks.EarlyStopping',
'args': {
'monitor': 'val_loss',
'patience': 10,
'min_delta': 0.0003
}
}
],
'validation_split': 0.2, # 전체 데이터 중 20%를 검증용으로 분리
'shuffle': True, # 학습 시 배치 샘플을 섞을지 여부
'verbose': True, # 학습 과정 로그 출력 여부
# 인코더 구조: 양방향 LSTM
'layers_encoder': [
{
'class': 'tensorflow.keras.layers.Bidirectional',
'parameters': {
'layer': {
'class': 'tensorflow.keras.layers.LSTM',
'parameters': {
'units': 'lstm_units', # 위에 지정한 lstm_units 사용
'return_sequences': False
}
},
'merge_mode': 'concat' # 양방향 LSTM의 출력 결합 방식
}
}
],
# 디코더 구조: RepeatVector + Bidirectional LSTM + TimeDistributed Dense
'layers_decoder': [
{
'class': 'tensorflow.keras.layers.RepeatVector',
'parameters': {
'n': 'repeat_vector_n' # 인코딩된 벡터를 다시 시계열로 복제
}
},
{
'class': 'tensorflow.keras.layers.Bidirectional',
'parameters': {
'layer': {
'class': 'tensorflow.keras.layers.LSTM',
'parameters': {
'units': 'lstm_units',
'return_sequences': True
}
},
'merge_mode': 'concat'
}
},
{
'class': 'tensorflow.keras.layers.TimeDistributed',
'parameters': {
'layer': {
'class': 'tensorflow.keras.layers.Dense',
'parameters': {
'units': 1 # 타깃 출력 크기
}
}
}
}
],
'window_size': 7 # 모델 내부에서의 시계열 윈도우 크기 (슬라이싱이나 repeat에 사용)
},
'orion.primitives.aer.score_anomalies#1': {
'comb': 'mult', # reconstruction과 regression score를 곱셈으로 결합
'lambda_rec': 0.5, # reconstruction error 비중
'mask': True, # window 중심만 score에 반영할지 여부
'rec_error_type': 'dtw' # reconstruction error 계산 방식: DTW (Dynamic Time Warping)
},
'orion.primitives.timeseries_anomalies.find_anomalies#1': {
'z_range': [0, 12], # z-score 임계값 탐색 범위
'window_size': 2000, # 이상 탐지 시 사용하는 window 크기
'window_step_size': 200, # 슬라이딩 윈도우의 간격
'lower_threshold': False, # 하한값 기준으로도 anomaly 검출할지 여부
'fixed_threshold': True, # z-score 임계값을 고정할지 여부
'inverse': False, # score가 작을수록 이상인가? (False면 클수록 이상)
'window_size_portion': 0.33, # anomaly padding 적용 시 한 윈도우에서 이상 비율 기준
'window_step_size_portion': 0.1, # 윈도우 슬라이딩 비율 (전체 길이의 10%)
'min_percent': 0.13, # 윈도우 안에서 이상으로 판단될 최소 비율
'anomaly_padding': 50 # 이상 감지 구간에 패딩을 주는 길이 (경계 부드럽게)
}
}