경진대회 개요
이번 경진대회는 AD 경진대회로 이상 탐지하는 경진대회였다.
24시간 내내 운영되는 화학 공정은 이상이 발생하면 막대한 금전적 피해를 입을 수 있다. 공정 상태를 예측하고 대비책을 마련하는 것이 중요한 과제인데, 이를 위해서는 공정 데이터를 이해하고 이상 징후를 파악하는 것이 필수적이다.
이번 대회는 화학 공정 데이터를 이용한 이상 탐지(anomaly detection)를 수행하여, 공정 데이터에서 비정상적인 동작을 탐지하는 것을 목표로 한다. 이를 통해 공정에서 발생할 수 있는 문제를 예측하고 대비할 수 있다.
이번 대회에서 사용되는 입력 데이터와 출력 데이터는 모두 CSV 파일 형태로 제공된다. 입력 데이터로는 약 25만 개의 화학 공정 데이터가 제공되며, 이에 대응하는 약 7만 2천 개의 출력 데이터가 제공된다.
이상 탐지를 위한 알고리즘 개발은 화학 공정 분야에서 매우 중요한 과제이며, 이를 통해 공정의 안정성을 높이고 예기치 않은 문제를 예방할 수 있다는 점에서 큰 의미가 있다.
경진대회 기간은 24.12.23 ~ 24.01.02이다.
평가 기준은 F1-score로 모델의 성능을 평가한다. 이상인 경우에 1을, 정상인 경우에는 0으로 계산한다.
그리고 Upstage AI Lab 4기에서 AD 경진대회를 진행하는 팀은 우리밖에 없었다.
데이터 특징 (EDA)
현재 데이터는 정상 데이터로만 되어있는 Train Set으로 모델을 학습해서, Test Set의 정상/이상 데이터를 정확하게 식별해야 한다.
현재 Train 데이터에는 정상 데이터(0)만 존재하고 Feature는 무엇인지 알아볼 수 없는 이름으로 되어있다. 그나마 고유 식별정보로 사용될 수 있는 것은 simulationRun, Sample이었다.
- Train: 500 Simulation Run x 500 Sample
- Test set: 960 Simulation Run x 700 Sample
Correlation Matrix
현재 학습 데이터셋의 상관관계들을 살펴보자.
현재 0.95~1의 높은 상관관계를 보이는 Feature들이 다수 존재하기 때문에, 차원축소나 Feature Selection을 검토해 볼 수 있다.
SimulationRun, Sample의 관계
아까 위에서 살펴본 피쳐 중에서 SimulationRun, Sample의 관계에 대해서 살펴보자
현재 데이터 시각화를 해보면 각 Simulation Run마다 sample이 1~500까지 1씩 증가하는 형태로 존재한다.
즉, simulation run은 데이터를 묶는 상위 단위이고 sample은 그 안에서 데이터를 세분화하는 하위 단위로 볼 수 있다.
여기서 faultNumber를 보면 하나의 시뮬레이션 런 안에서는 모두 동일한(정상/이상 레이블) 값을 갖고 있다. 예를 들어, 특정 simulation run이 정상 데이터라면 그 안에 모든 sample은 0으로 들어가 있고 특정 simulation run이 이상 데이터라면 그 안에 있는 모든 sample은 1로 들어가 있다.
이 데이터는 일단 실제 공장에서 수집된 데이터가 아니라 시뮬레이션을 통해 생성된 데이터이기 때문에 위 현상이 나타난 것 같다. 실제로는 한 시뮬레이션 런 안에 정상과 이상 데이터가 섞여 있을 수도 있겠지만 지금 데이터에는 그런 경우는 없다.
데이터 시각화
이제 학습데이터와 테스트 데이터를 3차원으로 시각화해 보자.
왼쪽 파란 분포가 학습 데이터셋이고 빨간 분포가 테스트 데이터셋이다.
Train 데이터는 좁은 공간 안에 안정적으로 분포되어 있다. 이 이유 중에 하나는 학습 데이터셋의 각 Feature들도 정규분포의 형태를 보이기 때문이 아닐까 추측해 본다.
그리고 위 그림에서 오른쪽에 있는 빨간 분포가 Test 데이터인데 눈에 띄게 넓은 범위에 걸쳐 무작위로 분포하고 있다.
이게 두 그래프를 위에서 쳐다본 형태이다. 학습 데이터는 테스트 데이터에 비해 굉장히 좁게 분포되어 있는 걸 확인할 수 있다.
지금까지는 simulaion run과 sample의 관계에 대해서 생각하지 않고 그냥 데이터를 기반으로 보여줬었는데 이제는 데이터 포인트를 simulation run 단위로 그룹화해 보자.
데이터 포인트를 Simulation run 단위로 그룹화(mean)하면 분포의 차이가 명확하게 보이고, 따라서 분포의 지역적 특성을 이용한 분석 방법이 유의미할 수 도있겠다.
여기서 중요한 건 아까 말했다시피 Simulation Run의 sample은 모두 0 또는 1로 통일되어 있다는 것이다.
사실 이 그림만 봐서는 '이상 데이터 탐지하는 거 너무 쉬워 보이는데?'라는 생각이 든다. 실제로 학습데이터와 눈에 띄게 멀리 떨어져 있는 데이터는 쉽게 이상으로 판단하는데 파란색과 빨간색이 겹쳐져있는 부분에 대해서 이상을 탐지하기가 어려웠다 ㅠㅠ
모델링 프로세스
우리가 진행한 모델링 프로세스는 다음과 같다.
모델 학습
우리는 분포의 지역적 특성 기반의 모델, 데이터 밀도 기반의 모델, 트리 기반의 모델을 다양하게 활용했다.
지도 학습으로는 KNN, 비지도 학습으로는 K-Means Clustering, LOF, Isolation Forest(베이스라인 코드 적용)를 적용했다.
KNN
KNN의 특성은 다음과 같다.
- 데이터를 K개의 클러스터로 군집화
- 각 클러스터 중심점으로부터의 거리 계산
- 클러스터 중심에서 멀리 떨어진 점을 이상치로 판단
장점은 계산 효율이 좋고, 클러스터의 전역적 특성을 잘 포착한다. 구현과 해석이 비교적 간단하다. 단점으로는 클러스터 수(K) 선정이 중요하며 비구형 클러스터에 취약하다. 그리고 클러스터 크기가 불균형한 경우에는 성능이 저하된다.
우리가 KNN을 적용한 방법은 다음과 같다.
- Train(정상) 데이터를 K개(2~5)의 클러스터로 군집화
- 각 클러스터의 중심점 계산 및 정상 범위 설정
- 테스트 데이터의 각 포인트에 대해 가장 가까운 클러스터 중심 찾기
- 해당 클러스터의 정상 범위를 벗어나면 이상치로 판별
- SimulationRun 별로 하나라도 이상치면 전체 Sample을 이상치로 처리
여기서 말하는 정상 범위는 16% 16.5% 가 f1 score가 가장 높게 나왔다.
이런 식으로 k값을 조정해 주고 각 클러스터의 중심점을 계산하는 방법과 정상 범위를 변경해 주면서 실험을 진행했다.
K-means
K-means의 특성은 다음과 같다.
- 데이터를 K개의 클러스터로 군집화
- 각 클러스터의 중심점으로부터의 거리 계산
- 클러스터 중심에서 멀리 떨어진 점을 이상치로 판단
장점으로는 계산 효율이 좋고, 클러스터의 전역적 특성을 잘 포착한다. 그리고 구현과 해석이 비교적 간단하다. 단점으로는 KNN과 마찬가지로 클러스터 수(K) 선정이 중요하다. 비구형 클러스터에 취약하며 클러스터 크기가 불균형한 경우 성능 저하를 유발할 수 있다.
우리가 K-Means를 적용한 방법은 다음과 같다.
- Train(정상) 데이터를 K개의 클러스터로 군집화
- 각 클러스터의 중심점 계산 및 정상 범위 설정
- 테스트 데이터의 각 포인트에 대해 가장 가까운 클러스터 중심 찾기
- 해당 클러스터의 정상 범위를 벗어나면 이상치로 판별
- simulation run별로 하나라도 이상치면 전체를 이상치로 처리
LOF
LOF의 특성은 다음과 같다.
- 데이터의 지역적 밀도 고려해서 이상치 탐지
- 주변 데이터의 밀도와 비교하여 상대적 밀도 계산
- 밀도가 주변보다 현저히 낮으면 이상치로 판단
장점으로는 다양한 밀도를 가진 데이터에서도 효과적이고 지역적 이상치 탐지에 강점이 있다. 그리고 클러스터 크기가 다양한 경우에도 잘 작동한다. 단점으로는 KNN보다 계산 복잡도가 높고, 파라미터 설정이 더 까다롭다. 그리고 대용량 데이터에서 속도가 느리다.
우리 프로젝트에 적용한 방법은 다음과 같다.
- Train(정상) 데이터로 LOF 모델 학습 (local density 계산)
- Train(정상) 데이터의 LOF 점수 계산하여 정상 분포 파악
- LOF 점수 하위 5%를 임계값으로 설정
- 테스트 데이터의 LOF 점수 계산하여 임계값과 비교
- 시뮬레이션 런별로 통계량 계산하여 최종 이상치 판별 (10%~11% 이상의 sample이 이상치면 전체를 이상치로 결정할 때 가장 성능이 좋았음)
이렇게 각 모델을 사용했을 때 결국 모델의 베스트 f1 score가 0.9292이고 accuracy는 0.9568로 전부 동일하게 나왔다.
모델의 특징이 전부 다른데 전부 동일한 성능이 나왔다는 게 신기했다.
이상치 최종 판별
이제 기존에 테스트 데이터셋에서 이상이라고 나온 데이터가 있다면 해당 simulation run 기반으로 이상치를 최종적으로 판별해 주는 과정이 필요했다.
- simulation run안에서 한 개라도 이상치가 탐지된 경우 simulation run 전체를 이상치로 판별
- 일정 수치 이상의 이상치가 탐지되었을 때 simulation run 전체를 이상치로 판별
일단 이렇게 두 가지로 최종 이상치를 판별하는 과정을 적용해 봤다.
한 개라도 이상치 판별된 경우 전체 이상치로 판별한 코드고,
다음은 이상치 판별 기준을 비율로 저장해서
- anomaly_ratio가 일정값 (0.11) 이상인 경우
- 특정 run의 평균 점수가 하위 11번째 백분위수에 속하는 경우를 추가적으로 이상치로 판단
이렇게 Simulation run을 이상치로 간주하는 코드다.
최종 사용된 모델
최종적으로 K-Means, KNN, LOF 전부를 사용했다.
이때 앙상블을 진행했는데 Hard Voting, Weighted Hard Voding, Merge, Meta Model Ensemble로 진행했다.
Hard Voting
하드보팅인 경우 K-Means + KNN+ LOF 세 개를 조합했다. 각각의 최고성능이 나온 결과에서 다수결에 의해서 정상/이상을 판단했다.
이때 f1-score 0.9339 accuracy 0.9595를 달성했다.
Weight Hard Voting
이 경우 K-Means + KNN+ LOF 조합을 사용한 건 동일한데 각 모델의 F1 score를 기반으로 가중치를 부여했다.
이때도 f1-score 0.9339 accuracy 0.9595가 나왔다.
Meta Model Ensemble
이 경우도 K-Means + KNN+ LOF 조합을 사용한 건 동일한데 이번에는 각 모델의 예측값을 입력으로 사용해서 Random Forest로 학습을 진행했다.
이때도 f1-score 0.9339 accuracy 0.9595가 나왔다.
Merge
이 뒤에 이제 뭐를 더 해볼까, 하다가 앙상블은 아니고 Merge를 해보자고 이야기가 나왔다.
사실 이전에 데이터를 전부 1로 넣어서 제출해서 실제로 테스트 데이터셋에 몇 개의 이상데이터가 있는지 확인해 보는 과정을 거쳤다.
이렇게 나온 결과로 우리는 다음과 같은 결론을 얻었다.
이렇게 실제로 테스트 데이터셋에 이상이라고 나와야 하는 데이터의 개수는 약 23만 개라는 걸 알 수 있었다.
그리고 세 개의 모델들이 전부 이상(1)으로 판단한 데이터 말고 몇 개의 모델은 0으로 판단하고 몇 개의 모델은 1로 판단한 데이터에 대해서는 또 전부 이상으로 설정해 주는 과정을 한 게 바로 이 Merge 작업이었다. (엄밀히 말하면 정상적인 앙상블 기법은 아니긴 하다,,ㅎㅎ)
실제로 우리가 제출한 모델 중에서 가장 좋은 모델의 output과 새로 나온 결과를 비교해서 총 몇 개가 겹치고 몇 개가 다른지 파악하는 코드를 돌려봤었다.
import pandas as pd
import numpy as np
# CSV 파일 불러오기
df1 = pd.read_csv('submission_lof_v4.csv')
df2 = pd.read_csv('submission_knn_16.csv')
# 순서대로 일치하는 faultNumber 개수 계산
matches_in_order = (df1['faultNumber'].values == df2['faultNumber'].values).sum()
total_rows = len(df1)
match_percentage_in_order = (matches_in_order / total_rows) * 100
# 결과 출력
print(f"전체 데이터 수: {total_rows}개")
print(f"순서대로 일치하는 faultNumber 수: {matches_in_order}개")
print(f"순서대로 일치 비율: {match_percentage_in_order:.2f}%")
# 추가 분석: 불일치하는 부분 상세 확인
mismatches = df1[df1['faultNumber'] != df2['faultNumber']]
print("\n불일치하는 데이터:")
print(f"불일치 데이터 수: {len(mismatches)}개")
# 각 faultNumber의 분포 비교
print("\nfaultNumber 분포 비교:")
print("1.csv:")
print(df1['faultNumber'].value_counts())
print("\n2.csv:")
print(df2['faultNumber'].value_counts())
이렇게 해서 각 데이터들이 총 몇 개를 1로 판단하고 그중에서 겹치는 건 몇 개고 다른 건 몇 개인지 판단하는 코드를 작성했다.
여기서 서로 다르게 판단한 데이터만 다시 가져와서 일단 하나의 모델이라도 이상(1)이라고 판단했으면 전부 이상이라고 설정해 줬다. 이게 Merge라고 생각하면 된다.
각 모델의 최종 점수는 동일하게 나왔는데 신기하게 몇천 개씩 1로 판단한 데이터들의 차이가 있었다. 그래서 전부 1로 판단한 데이터는 진짜 이상이라고 생각하고 서로 다르게 예측(전부 0으로 정상으로 판단한 경우는 제외)한 데이터에 대해서 다시 이상치를 설정해 주는 작업을 했다.
이렇게 했을 때 f1 score 0.9345로 성능개선이 있었다!
이외에도 여러 가지 방법들(PCA, t-SNE, AutoEncoder, LSTM 등등)을 시도해 봤는데 그렇게 유의미한 성능향상은 없었다 ㅠㅠ
최종 결과
최종적으로 우리가 제출한 모델은 다음과 같다.
이렇게 해서 우리의 최종 리더보드 점수는 0.9091이 되었다.
실제로 Public 기준 가장 높은 점수는 위에서 Merge 한 결과였다.
그런데 Private 기준으로 실제로 가장 높은 점수는 여러 모델을 앙상블 한 게 아니라 단일 모델 KNN에서 K를 5로 줬던 모델이었다.
이번 AD 경진대회는 우리 팀만 참여했기 때문에 이전 기수들의 리더보드를 보면 다음과 같다.
![[Pasted image 20250106153004.png]]
아쉽게도 다른 기수들보다 더 좋게 나오진 않았지만 시도해보지 못한 방법들로 해보면 더 나아졌을까?라는 아쉬움이 든다.
- 각 모델별로 f1 score는 동일한데 1로 판단한 데이터들이 조금씩 달라진 걸 확인해 봤으니 이 데이터들 중에서 해답이 있을 것 같은데 이 해답을 찾지 못했다.
- 모든 모델이 이상(1)이라고 판단한 데이터를 다시 학습데이터에 넣어서 아예 이상을 학습시키는 건 어떨까?
이런저런 방법들을 생각해 봤지만 시간이 부족해서 진행하지는 못했다 ㅠㅠ
'Upstage AI Lab 4기' 카테고리의 다른 글
Upstage AI Lab 4기 IR 경진대회 회고 (1) | 2024.12.23 |
---|---|
Large Language Model 개요 (등장 배경 및 제작 프로세스) (1) | 2024.12.06 |
Dialogue Summarization (일상 대화 요약) 경진대회 2주차 + 자체 평가 및 회고 (2) | 2024.12.01 |
Dialogue Summarization (일상 대화 요약) 경진대회 1주차 (1) | 2024.11.28 |
[CV 경진대회] 최종 제출, 자체 평가 및 회고 (1) | 2024.11.08 |