LightGBM을 활용한 이상탐지(Anomaly detection)
LightGBM을 활용한 지도학습 기반의 이상 데이터 탐지 모델 개발¶
- 활용 데이터:
차량 해킹 공격 데이터셋 (https://ieee-dataport.org/open-access/car-hacking-attack-defense-challenge-2020-dataset),
1. 분석 배경 및 목적 설명¶
1) 배경¶
차량 해킹 공격 데이터 셋은 차량 내 네트워크에서 널리 사용되는 표준인 CAN(Controller Area Network)의 공격 및 탐지 기술 개발을 목표로 고려대학교 해킹대응기술연구실에서 수집한 데이터 셋입니다. 본 자료에 활용된 데이터 셋은 현대 아반떼 CN7을 탑승하여 실제 수집한 데이터입니다.
따라서, 데이터셋은 일반 메시지와 공격 메시지를 포함하는 아반떼 CN7 CAN 네트워크 트래픽입니다.
CAN(Controller Area Network)?
- 위키백과의 정의에 따르면, 차량 내에서 호스트 컴퓨터 없이 마이크로 컨트롤러나 장치들이 서로 통신하기 위해 설계된 표준 통신 규격입니다.
본 데이터는 정상 주행중인 차량의 CAN 트래픽과 Flooding, Fuzzing, Replay, Spoofing 공격에 대한 CAN 트래픽 자료를 담고 있습니다.
이는 전 세계적으로 자동차 보안 연구가 초기 단계이고, 차량용 공격 패턴과 공격 시 CAN 트래픽이 공개된 사례가 최근까지 없었다는 점에서 희소성이 무척 높은 데이터셋이라고 할 수 있습니다.
국내 차량 보안 연구자들이 차량용 침입탐지시스템을 개발하고 싶어도 벤치마킹할 수 있는 대상 시스템이 없고, 실제 차량에서 추출된 데이터는 최근까지도 전무하였으므로 해당 데이터셋을 이용하여 차량보안 연구를 활성화 하는데 기여할 수 있을 것으로 기대됩니다.
해당 데이터를 활용하여 본 자료에서는 차량 해킹 시도를 탐지할 수 있는 지도학습 기반의 이상 데이터 탐지를 개발해보고자 합니다.
2) 이상 탐지¶
이상탐지는 일반적으로 예상되는 정상적인 패턴과는 다른 비정상적인 패턴을 식별하는데 사용하는 기술입니다.
이상탐지를 수행하는 가장 간단한 방법은 평균, 중앙값, 최빈수 및 분위수를 비롯한 분포의 일반적인 통계량을 통하여, 일반적인 통계 속성을 벗어나는 데이터 요소를 지정하는 것으로, 대표적으로 IQR 방법의 이상탐지가 있습니다.
그러나, 정상 데이터와 이상 데이터 사이의 경계가 명확하지 않은 경우가 많고, 정상 데이터에도 비정상적인 행동과 유사할 수 있는 노이즈가 포함되어 있는 경우가 많습니다.
또한 정상과 이상의 정의 자체가 변경되는 경우도 있으며 기타 다양한 원인들로 인해 명확하게 구분하기 힘든 경우가 많습니다.
이에 기계학습, 나아가 딥러닝 기반의 이상 탐지 기법에 대한 연구가 활발히 진행되고 있습니다.
다음은 주로 활용되는 기계학습 기반 이상탐지 기법들입니다.
- 밀도 기반(Density based) 이상 탐지: 정상적인 데이터는 밀집된 이웃 주변에서 발생하고 이상 데이터는 상대적으로 거리가 멀리 떨어져 있다는 것을 가정한 것으로, 유클리드 거리 또는 이와 유사한 측정 기준으로 데이터 사이의 가까운 지표를 계산합니다. 대표적으로 크게 두 가지 알고리즘으로 분류할 수 있습니다.
- KNN(K-nearest neighbors): 주로 유클리드와 멘하탄 등의 거리 계산방법을 기준으로 데이터를 분류하는 간단한 비모수 학습 테크닉입니다.
- LOF(Local Outlier factor): 데이터의 상대적인 밀도를 고려하여 데이터를 분류하는 기법입니다.
- 클러스터링 기반(Clustering based) 이상 탐지: 클러스터링 기반 이상 탐지 기법은 지도 학습 영역에서 가장 널리 활용되는 방법으로, 한 그룹의 중심에 가깝게 위치한 데이터는 서로 유사한 클러스터에 속한다는 것을 가정한 것으로, 주로 K-Means, DBSCAN 등이 있습니다.
- 이 외에도 서포트 벡터 머신을 활용한 One class SVM, 차원축소의 아이디어롤 활용한 PCA 기반의 이상탐지와 딥러닝 기반의 차원축소를 활용한 Auto Encoder, 트리 모형 기반의 Isolation Forest 등 다양한 이상 탐지 알고리즘이 존재합니다.
3) 데이터셋 구성¶
본 데이터셋은 실제 주행 중인 자동차에 해킹 공격 등이 발생할 때의 차량 내부 통신 트래픽을 수집한 데이터 셋입니다.
변수 구성은 아래와 같습니다.
- timestamp: UNIX Time으로 기록된 로깅 시점
- CAN ID: CAN 식별자 넘버
- Data: CAN 데이터
- DLC: DataLength 코드
- Class
- Normal: 정상상태
- Attack: 공격상태
- Subclass (본 자료에서는 사용하지 않습니다)
- Normal: 정상상태
- Flooding: 플러딩 공격은 대량의 메시지를 전송하여 CAN 대역폭을 소비하는 것을 목표로 하는 공격입니다.
- Fuzzing: 퍼징 공격은 임의의 메시지를 주입하여 차량의 의도치 않은 동작을 유발하는 공격입니다.
- Replay: 리플레이 공격은 특정 시간의 정상적인 트래픽을 추출하여, 다시 주입하는 공격입니다.
- Spoofing: 스푸핑 공격은 원하는 특정 기능을 제어하기 위한 공격입니다.
본 자료에 활용되는 데이터는 원활한 자료 작성을 위하여 원본 데이터 중 일부만 무작위 샘플링 방식으로 추출하였음을 알려드립니다.
2. 데이터 수집 및 전처리¶
1) 패키지 로드 및 데이터 확인¶
import numpy as np
import pandas as pd
from tqdm import tqdm
import seaborn as sns
import matplotlib.pyplot as plt
from imblearn.combine import SMOTETomek
from imblearn.under_sampling import TomekLinks
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import classification_report
from lightgbm import LGBMClassifier
tqdm.pandas()
본 데이터는 원본 데이터 중 일부만 무작위 샘플링한 데이터를 github에 따로 업로드하여 불러온 것입니다.
실제 더 많은 데이터를 활용하고자 하신다면 https://ieee-dataport.org/open-access/car-hacking-attack-defense-challenge-2020-dataset 해당 사이트를 통해 데이터를 활용해보시는 것을 권장드립니다.
또한, 본 데이터는 원본 데이터의 디렉토리 중 0_Preliminary/0_Training/Pre_train_D_1.csv 파일을 활용하여 샘플링 하였습니다.
# 데이터 로드
df = pd.read_csv('https://raw.githubusercontent.com/hyeonkeemin/dip_test/main/CarPlatform/sample_car_hacking.csv')
# 데이터 확인
print(f"Row: {df.shape[0]}, Columns: {df.shape[1]}")
df.head()
Row: 80639, Columns: 7
Unnamed: 0 | Timestamp | Arbitration_ID | DLC | Data | Class | SubClass | |
---|---|---|---|---|---|---|---|
0 | 9 | 1.597760e+09 | 453 | 5 | 00 88 8B 00 C1 | Normal | Normal |
1 | 17 | 1.597760e+09 | 251 | 8 | F4 03 27 17 00 F7 07 80 | Normal | Normal |
2 | 21 | 1.597760e+09 | 47F | 8 | 04 F5 FF FF 00 7B 00 00 | Normal | Normal |
3 | 36 | 1.597760e+09 | 394 | 8 | 00 40 08 00 FF D3 9D FC | Normal | Normal |
4 | 42 | 1.597760e+09 | 260 | 8 | 05 34 02 30 00 BC 59 28 | Normal | Normal |
본 지도학습 기반의 이상탐지에 있어 가장 핵심적인 정보를 제공하는 것은 CAN 네트워크 정보를 제공하는 Data 변수 입니다. Data 변수의 경우 상태에 따라 8개 이하 값이 입력되어 있습니다.
CAN 메시지의 길이와 해킹 시도 공격 간의 상관관계가 있는지 확인하기 위해, 메시지의 길이와 공격 시도의 차이를 시각화해서 확인해보도록 하겠습니다.
# 클래스 범주별 데이터 수 시각화
plt.subplots(figsize=(8, 6))
sns.countplot(x='DLC', hue='Class', data=df)
plt.show()
단순한 빈도 그래프의 표현으로는 정확한 정보를 확인할 수 없으므로, 이를 비율로 나타낸 뒤 다시 한 번 확인해본 결과, 차이가 크진 않지만 8번의 메시지까지 도달한 경우 공격 시도가 근소하게 많았던 것을 확인할 수 있습니다.
# 클래스 범주별 데이터 비율 시각화
plt.subplots(figsize=(8, 6))
sns.histplot(data=df, x='DLC', hue='Class', multiple='fill', stat='probability')
plt.show()
2) Train/Test Split¶
본 자료의 목적은 지도학습 기반의 데이터 이상탐지를 수행하는 것으로, 활용되는 데이터는 실제 원본 데이터의 학습용 데이터에 해당하는 부분입니다.
이에 Train/Test 용 데이터 셋을 8:2 비율로 먼저 분리하여 Test 데이터 셋을 원본 형태로 유지하도록 하겠습니다.
이 때, 본 데이터는 시간의 흐름에 따라 기록된 데이터이므로 데이터가 섞이는 것을 방지하기 위해 shuffle
을 False로 설정하여 분리된 데이터 셋의 정보가 섞이는 것을 방지하였습니다.
# 8:2로 학습용 데이터, 테스트용 데이터 분리
df, test = train_test_split(df, test_size=0.2, shuffle=False)
로드된 데이터프레임의 경우 Unnamed: 0
컬럼이 추가 되었습니다.
해당 컬럼의 경우 원본 데이터의 index 번호를 나타낸 것으로, 원본 데이터엔 없는 부분이므로 제거하도록 하겠습니다.
그리고 Timestamp
의 경우 본 자료에서는 시계열 요소를 고려하지 않기에 삭제하고, 본 자료의 목적이 지도학습 기반의 이상 데이터의 여부를 판단하는 것이므로 Subclass
도 함께 제거하도록 하되,
Class
정보는 별도로 저장하도록 하겠습니다.
# 학습 데이터 예측값 분리
train_y = df['Class']
# 불필요한 칼럼 제거
df = df.drop(['Timestamp', 'Class', 'Unnamed: 0', 'SubClass'], axis=1)
# 테스트 데이터 예측값 분리
test_y = test['Class']
# 불필요한 칼럼 제거
test = test.drop(['Timestamp', 'Class', 'Unnamed: 0', 'SubClass'], axis=1)
# 학습데이터 예측값 범주별 데이터 수 집계
train_y.value_counts()
Normal 58726 Attack 5785 Name: Class, dtype: int64
학습 데이터의 클래스 분포를 미리 확인해보면, 약 9:1의 비율로 정상 데이터와 해킹 시도 데이터가 포함되어 있는 것을 확인할 수 있습니다.
해당 데이터의 분포로부터 이상 탐지 문제가 단순한 분류 문제와 조금 차이가 있다는 것을 확인할 수 있는데요, 단순 분류문제의 경우 분류 대상이 되는 데이터의 분포가 고르게 분포되어 있는 경우가 많은데 비해, 이상 탐지에 활용되는 데이터는 분류 대상에 필요한 데이터의 분포가 위 경우처럼 비정상적인 경우가 많이 있기 때문에 단순한 분류 문제로 접근하기가 힘든 것입니다.
예를 들어, 본 자료에 활용되는 데이터도 별도의 학습 없이 모든 데이터를 정상 데이터로 결론짓기만 하더라도 90%의 정확도를 담보할 수 있기 때문에(물론 이를 보정하기 위한 F1-score 등을 활용할 수도 있으나) 잘못된 판단에 빠질 가능성이 있어, 필요에 따라 이에 대한 보정처리도 진행하여야 합니다.
본 자료에서는 위 경우 수행하여야 하는 전처리 과정을 조금 더 중점적으로 다루어 볼 예정입니다.
3) 데이터 전처리 및 파생변수 생성¶
# DataLength 코드별 CAN 메세지 수 집계
df['DLC'].value_counts()
8 52940 4 4971 6 2641 7 2597 5 1269 2 93 Name: DLC, dtype: int64
약 64,500건의 데이터 중 약 53,000건의 경우 8번의 CAN 메시지가 존재한 것을 확인할 수 있습니다.
# 결측치 확인
df.isna().sum()
Arbitration_ID 0 DLC 0 Data 0 dtype: int64
CAN 데이터의 경우 한 번의 메시지 값에 16진수로 표현된 0부터 255까지의 256개의 값이 입력되어 있습니다.
추후 모델링 과정에서 본 데이터를 합성한 값을
위 정보를 최대로 활용하기 위해 Data를 8개의 상태로 다시 분리하여 파생변수를 각각 생성하도록 하겠습니다.
def Data_col_split(df):
split = df['Data'].str.split(' ').progress_apply(lambda x: pd.Series(x))
df = pd.concat([df, split], axis=1)
df = df.drop('Data', axis=1)
df = df.rename(columns={
0: 'Data_1',
1: 'Data_2',
2: 'Data_3',
3: 'Data_4',
4: 'Data_5',
5: 'Data_6',
6: 'Data_7',
7: 'Data_8',
})
return df
추후 테스트 데이터 셋에도 활용하기 위하여 함수 형태로 작성하였습니다.
# 생성한 함수를 통해 데이터 전처리 진행
df = Data_col_split(df)
# 데이터 확인
df.head()
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 64511/64511 [00:07<00:00, 8251.52it/s]
Arbitration_ID | DLC | Data_1 | Data_2 | Data_3 | Data_4 | Data_5 | Data_6 | Data_7 | Data_8 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 453 | 5 | 00 | 88 | 8B | 00 | C1 | NaN | NaN | NaN |
1 | 251 | 8 | F4 | 03 | 27 | 17 | 00 | F7 | 07 | 80 |
2 | 47F | 8 | 04 | F5 | FF | FF | 00 | 7B | 00 | 00 |
3 | 394 | 8 | 00 | 40 | 08 | 00 | FF | D3 | 9D | FC |
4 | 260 | 8 | 05 | 34 | 02 | 30 | 00 | BC | 59 | 28 |
레이블 인코딩
기계는 숫자를 이해할 뿐 텍스트는 이해하지 못합니다. 따라서 categorical(text)데이터를 numerical 데이터로 바꿔야하는데 이 과정 중 한가지 방법이 Label Encoding입니다.
레이블 인코딩은 n개의 범주형 데이터를 0부터 n-1까지의 연속적 수치 데이터로 표현을 합니다. 주의할 점은 인코딩의 결과가 수치적인 차이를 의미하진 않는다는 것입니다.
scikit-learn의 분류용 추정기는 클래스 레이블을 순서가 없는 범주형 데이터로 다루기 때문에, LabelEncoder
를 사용하여 간편하게 문자열 데이터를 정수로 인코딩할 수 있습니다.
# 라벨 인코더 인스턴스 생성 및 데이터 학습
le = LabelEncoder().fit(df['Arbitration_ID'])
# 학습된 잌코더를 바탕으로 데이터 변환
df['Arbitration_ID'] = le.transform(df['Arbitration_ID'])
# 데이터 확인
df.head()
Arbitration_ID | DLC | Data_1 | Data_2 | Data_3 | Data_4 | Data_5 | Data_6 | Data_7 | Data_8 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 31 | 5 | 00 | 88 | 8B | 00 | C1 | NaN | NaN | NaN |
1 | 8 | 8 | F4 | 03 | 27 | 17 | 00 | F7 | 07 | 80 |
2 | 34 | 8 | 04 | F5 | FF | FF | 00 | 7B | 00 | 00 |
3 | 23 | 8 | 00 | 40 | 08 | 00 | FF | D3 | 9D | FC |
4 | 9 | 8 | 05 | 34 | 02 | 30 | 00 | BC | 59 | 28 |
16진수 변환
Data 컬럼은 16진수로 입력된 문자변수입니다. 이를 숫자로 변환하도록 한 뒤, 결측값을 -1로 대체 처리하였습니다.
col = ['Data_1', 'Data_2', 'Data_3', 'Data_4', 'Data_5', 'Data_6', 'Data_7', 'Data_8']
for i in col:
df[i] = df[i].apply(lambda x: int(x, 16) if type(x)==str else x)
df[col] = df[col].fillna(-1)
지금까지의 전처리 과정을 함수로 재작성하도록 하겠습니다.
def data_preprocessing(df):
# CAN 데이터 분할
split = df['Data'].str.split(' ').progress_apply(lambda x: pd.Series(x))
df = pd.concat([df, split], axis=1)
df.drop('Data', axis=1, inplace=True)
df.rename(columns={
0: 'Data_1',
1: 'Data_2',
2: 'Data_3',
3: 'Data_4',
4: 'Data_5',
5: 'Data_6',
6: 'Data_7',
7: 'Data_8',
}, inplace=True)
# 레이블 인코딩
df['Arbitration_ID'] = le.transform(df['Arbitration_ID'])
# CAN 데이터의 16진수 변환
for i in col:
df[i] = df[i].apply(lambda x: int(x, 16) if type(x)==str else x)
# 결측값 처리
df[col] = df[col].fillna(-1)
return df
df.head()
Arbitration_ID | DLC | Data_1 | Data_2 | Data_3 | Data_4 | Data_5 | Data_6 | Data_7 | Data_8 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 31 | 5 | 0 | 136 | 139.0 | 0.0 | 193.0 | -1.0 | -1.0 | -1.0 |
1 | 8 | 8 | 244 | 3 | 39.0 | 23.0 | 0.0 | 247.0 | 7.0 | 128.0 |
2 | 34 | 8 | 4 | 245 | 255.0 | 255.0 | 0.0 | 123.0 | 0.0 | 0.0 |
3 | 23 | 8 | 0 | 64 | 8.0 | 0.0 | 255.0 | 211.0 | 157.0 | 252.0 |
4 | 9 | 8 | 5 | 52 | 2.0 | 48.0 | 0.0 | 188.0 | 89.0 | 40.0 |
4) 불균형 데이터의 샘플링¶
앞서 진행한 자료를 통해 확인할 수 있는 것처럼, 본 데이터의 경우 정상 데이터와 해킹 공격이 시도된 이상 데이터의 비율이 9:1 정도로 나뉘어진 불균형 데이터 셋입니다.
위와 같은 불균형 데이터가 문제가 되는 이유는, 이상 탐지를 목적으로 하는 분석의 경우 일반적으로 정상적인 상태를 정확히 잡아내는 것 보다 이상 데이터를 정확히 분류하는 것이 더 중요하나 target이 되는 이상 데이터가 정상 데이터에 비해 그 수가 많지 않아 모델 학습시 이상 데이터의 특징을 정확히 잡아내기 힘들기 때문입니다.
이를 보정하기 위한 몇가지 샘플링 기법이 있는데, 대표적인 기법이 언더샘플링(UnderSampling)과 오버샘플링(OverSampling) 입니다.
언더샘플링은 다수 범주(정상)의 데이터를 소수 범주(이상) 데이터와 그 비율이 맞게 줄이는 방식의 샘플링을 수행하는 방법이며,
오버샘플링은 소수 범주(이상)의 데이터를 다수 범주(정상) 데이터와 그 비율이 맞게 수를 눌리는 방식의 샘플링입니다.
위 두 샘플링 방법에 착안한 여러 방법론들이 개발되고 현재까지도 활발히 연구되고 있습니다.
대표적인 불균형 데이터의 처리를 위한 언더샘플링 방법으로는 Tomek's link
가 있으며, 오버샘플링 방법으로는 SMOTE
, ADASYN
등이 존재합니다.
Tomek's link¶
토멕링크(Tomek's link)란 서로 다른 클래스에 속하는 한 쌍의 데이터로 서로에게 더 가까운 다른 데이터가 존재하지 않는 상태를 의미합니다. 즉, 클래스가 다른 두 데이터가 아주 가까이 붙어 있으면 토멕링크가 되고, 이러한 토멕링크를 찾은 다음 그 중에서 다수 클래스에 속하는 데이터를 제외하는 방법으로 경계선을 다수 클래스쪽으로 밀어붙이는 효과가 생깁니다.
SMOTE(Synthetic Minority Over-sampling Technique)¶
SMOTE는 데이터 개수가 적은 클래스의 표본을 가져 온 뒤, 임의로 선택한 K에 대해 KNN(K-nearestr neighbors)을 활용하여 선택한 데이터와 가장 가까운 K개의 데이터 중 하나를 무작위로 선정 후 Synthetic 공식을 통해 가상의 데이터를 생성하는 방법으로 위 과정을 소수 범주에 속하는 모든 데이터에 대해 수행하여 가상 데이터를 생성하는 오버샘플링 방법입니다.
ADASYN(Adaptive Synthetic Sampling Approach)¶
ADASYN은 SMOTE에서 일종의 weight를 통해 샘플 수를 결정하고 더 훈련시키기 어려운 관측치에 집중하여 근방의 synthetic 샘플을 더 많이 생성할 수 있도록 개선한 방법입니다.
SMOTE-Tomek¶
SMOTE-tomek은 SMOTE 방법과 토멕링크 방법을 섞은 것으로, 소수 클래스와 가까운 다수 클래스를 제외하면서 다소 모호할 수 있는 경계가 명확히 구분될 수 있도록 합니다.
본 자료에서는 SMOTE-tomek 방법을 활용하여 불균형 이상 데이터의 클래스를 늘리고 지도학습을 수행해보도록 하겠습니다.
# 데이터 확인
print(f"Row: {df.shape[0]}, Columns: {df.shape[1]}")
df.head()
Row: 64511, Columns: 10
Arbitration_ID | DLC | Data_1 | Data_2 | Data_3 | Data_4 | Data_5 | Data_6 | Data_7 | Data_8 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 31 | 5 | 0 | 136 | 139.0 | 0.0 | 193.0 | -1.0 | -1.0 | -1.0 |
1 | 8 | 8 | 244 | 3 | 39.0 | 23.0 | 0.0 | 247.0 | 7.0 | 128.0 |
2 | 34 | 8 | 4 | 245 | 255.0 | 255.0 | 0.0 | 123.0 | 0.0 | 0.0 |
3 | 23 | 8 | 0 | 64 | 8.0 | 0.0 | 255.0 | 211.0 | 157.0 | 252.0 |
4 | 9 | 8 | 5 | 52 | 2.0 | 48.0 | 0.0 | 188.0 | 89.0 | 40.0 |
# 데이터 증폭기 생성
smote_tomek = SMOTETomek(tomek=TomekLinks(sampling_strategy='majority'))
# 데이터 증폭기를 활용한 데이터 증폭
df_smt, y_smt = smote_tomek.fit_resample(df, train_y)
# 데이터 확인
print(f"Row: {df_smt.shape[0]}, Columns: {df_smt.shape[1]}")
df_smt.head()
Row: 117355, Columns: 10
Arbitration_ID | DLC | Data_1 | Data_2 | Data_3 | Data_4 | Data_5 | Data_6 | Data_7 | Data_8 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 31 | 5 | 0 | 136 | 139.0 | 0.0 | 193.0 | -1.0 | -1.0 | -1.0 |
1 | 8 | 8 | 244 | 3 | 39.0 | 23.0 | 0.0 | 247.0 | 7.0 | 128.0 |
2 | 34 | 8 | 4 | 245 | 255.0 | 255.0 | 0.0 | 123.0 | 0.0 | 0.0 |
3 | 23 | 8 | 0 | 64 | 8.0 | 0.0 | 255.0 | 211.0 | 157.0 | 252.0 |
4 | 9 | 8 | 5 | 52 | 2.0 | 48.0 | 0.0 | 188.0 | 89.0 | 40.0 |
데이터 증폭전 64511행의 데이터가 증폭 후 117355개의 행으로 증가한 것을 확인할 수 있다.
train_y.value_counts()
Normal 58726 Attack 5785 Name: Class, dtype: int64
y_smt.value_counts()
Normal 58726 Attack 58629 Name: Class, dtype: int64
위 코드를 통해 기존 9:1의 불균형 상태에서 5:5의 비율로 맞춰진 것을 확인할 수 있습니다.
이상으로 간단한 파생변수 생성 및 시각화, 불균형 데이터의 샘플링 작업까지 수행하였습니다.
3. 모델링¶
본 교육자료에서는 이 중 의사결정나무(Tree) 기반 모형인 Light GBM
를 활용하여 지도학습 기반 이상 데이터 분류를 시도해보고자 합니다.
light GBM(Light Gradient Bossting Machine)은 잘못 분류된 부분에 가중치를 더하면서 진행하는 알고리즘으로, 기존의 다른 Tree 기반 모델과의 차이점은 기존 Tree 구조가 수평적으로 확장하는 것과는 달리 수직적으로 Tree 구조를 확장하는데 있습니다.
LGBM은 이름에서처럼, 속도가 빠르다는 것이 가장 큰 장점입니다. 큰 사이즈의 데이터를 다룰 수 있고 실행시킬 때 적은 메모리를 차지하나, 수직적인 Tree 구조의 확장으로 인해 데이터의 양이 부족한 경우, 상대적으로 Overfitting에 취약한 단점이 존재합니다.
LGBM은 scikit-learn에서 제공하지 않아 별도의 패키지 설치가 필요하지만, 모델의 API은 scikit-learn 방식을 그대로 따릅니다.
LGBM에서 주로 활용되는 파라미터는 다음과 같습니다.
- 'boosting_type': 부스팅 방식에 대한 파라미터입니다.
- 'n_estimators': 반복 수행하는 tree의 개수를 나타냅니다.
- 'num_leaves': 하나의 트리가 가질 수 있는 최대 리프의 개수를 나타냅니다.
- 'learning_rate': 학습률입니다.
- 'colsample_bytree': 컬럼에 대한 샘플링 수행 비율을 나타냅니다.
- 'min_child_samples': 과적합 제어용 파라미터, num_leaves와 함게 활용합니다.
- 'max_bins': feature 값의 최대 bin 수를 나타냅니다.
- 'reg_alpha': L1, L2 정규화 수행에 관한 내용을 설정합니다.
# 분류기 생성
clf = LGBMClassifier(boosting_type='dart', random_state=0)
# 분류기 학습
clf.fit(df_smt, y_smt)
[LightGBM] [Info] Number of positive: 58726, number of negative: 58629 [LightGBM] [Warning] Auto-choosing row-wise multi-threading, the overhead of testing was 0.000888 seconds. You can set `force_row_wise=true` to remove the overhead. And if memory is not enough, you can set `force_col_wise=true`. [LightGBM] [Info] Total Bins 2063 [LightGBM] [Info] Number of data points in the train set: 117355, number of used features: 10 [LightGBM] [Info] [binary:BoostFromScore]: pavg=0.500413 -> initscore=0.001653 [LightGBM] [Info] Start training from score 0.001653
LGBMClassifier(boosting_type='dart', random_state=0)
지금까지 생성한 데이터프레임을 활용하여 LGBM 모델을 학습시켰습니다.
이제, 별도로 분리한 테스트 데이터를 함수로 정의한 전처리 과정을 활용하여 전처리를 수행하고, 이에 대한 예측을 수행해보도록 하겠습니다.
# 사전에 정의한 함수를 통한 테스트 데이터 전처리
test = data_preprocessing(test)
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 16128/16128 [00:01<00:00, 8183.39it/s]
# 학습된 모델을 화룡하여 예측
pred = clf.predict(test)
4. 모델 평가¶
분류 예측 결과를 살펴보면, 약 10% 정도의 데이터를 이상 데이터로 탐지한 것으로 확인됩니다.
# 라벨별 데이터 수 집계
pd.DataFrame(pred).value_counts()
Normal 14905 Attack 1223 dtype: int64
이에 대한 자세한 정보를 확인하기 위해, Classfication_report
함수를 활용하여 자세한 정보를 살펴보겠습니다.
weighted average score가 0.90로, 약 98% 정도의 정확도를 가진 모델이 완성되었습니다. 다만 이는 어느정도 보정이 되었다곤 하나 위에서 언급한 것 처럼 실제로 98%의 성능으로 잘 만들어 졌다고 이야기하긴 힘듭니다.
좀 더 자세히 살펴보면, 이상 데이터(Attack
)의 Recall 성능이 0.83으로, 실제 이상 데이터를 이상 데이터로 약 83%의 정확도로 탐지한 것으로 확인됩니다. 따라서 해당 정확도를 중점적으로 보완할 수 있어야 해당 분석에 주목적에 맞게 모델이 잘 만들어졌다고 판단할 수 있을 것입니다.
# 분류 결과 평가
print(classification_report(test_y, pred))
precision recall f1-score support Attack 0.95 0.83 0.88 1403 Normal 0.98 1.00 0.99 14725 accuracy 0.98 16128 macro avg 0.97 0.91 0.94 16128 weighted avg 0.98 0.98 0.98 16128
모델 성능 향상을 위해 할 수 있는 추가적인 방법은 다음과 같습니다.
- 도메인 지식을 기반으로 한 추가 활용 가능 데이터 탐색(외부 데이터, 파생변수 등)
- 데이터의 지속적 추가 확보
- 파라미터 튜닝
이 중, 파라미터 튜닝의 경우 Isolation forest 모델 선언 시 주로 활용되는 파라미터에 대한 딕셔너리를 선언 한 후,
scikit-learn에서 제공하는 GridSearchCV
함수를 통해 파라미터 튜닝을 수행할 수 있습니다.
이상으로 지도학습 기반의 이상 데이터 탐지 모델 개발을 마무리하도록 하겠습니다.
감사합니다.
참고문헌¶
- Kim, H. K. (2021, March 20). Car hacking: Attack & Defense Challenge 2020 Dataset. IEEE DataPort. Retrieved September 18, 2022, from https://ieee-dataport.org/open-access/car-hacking-attack-defense-challenge-2020-dataset
- 정보보호산업진흥포털. ksecurity.or.kr. (n.d.). Retrieved September 18, 2022, from https://www.ksecurity.or.kr/kisis/subIndex/384.do
- NAVEEN. (2019, June 19). Anomaly detection - credit card fraud analysis. Kaggle. Retrieved September 18, 2022, from https://www.kaggle.com/code/naveengowda16/anomaly-detection-credit-card-fraud-analysis
- 데이터 사이언스 스쿨. 비대칭 데이터 문제. (n.d.). Retrieved September 18, 2022, from https://datascienceschool.net/03%20machine%20learning/14.02%20%EB%B9%84%EB%8C%80%EC%B9%AD%20%EB%8D%B0%EC%9D%B4%ED%84%B0%20%EB%AC%B8%EC%A0%9C.html
- Welcome to LIGHTGBM's documentation! Welcome to LightGBM's documentation! - LightGBM 3.3.2 documentation. (n.d.). Retrieved September 18, 2022, from https://lightgbm.readthedocs.io/en/v3.3.2/