Sparta/Theory

[250929] 머신러닝 02

junecho 2025. 9. 29. 20:50

✅ 데이터 전처리                                                     

원시(raw) 데이터에서 불필요하거나 손실(노이즈)이 있는 부분을 처리하고, 분석 목적에 맞는 형태로 만드는 과정

 

ex) 스팸 메일 필터링, 이미지 분류, 음성 인식 등

🔸 결측치 처리                                                                                                             

  • 삭제 (Removal)
    • : 결측치가 있는 행(row) 또는 열(column)을 제거
    • 간단하지만 데이터 손실이 발생
    • 결측치가 전체 데이터에서 매우 소수일 때 적합
  • 대체 (Imputation)
    • 평균or중앙값으로 대체 → 수치형 데이터에서 많이 사용, 데이터 분포 왜곡이 비교적 적음
    • 최빈값으로 대체 → 범주형 데이터에서 사용
    • 예측 모델로 대체 → 회귀/분류 모델을 이용해 결측값을 예측

🔎 결측치 처리 코드                                                                                                                       

import numpy as np
import pandas as pd

# 1) 가상 데이터 생성
#   - 일부 값들을 np.nan으로 지정해 결측값을 만듭니다.
data = {
    'A': [1, 2, np.nan, 4, 5, np.nan, 7],
    'B': [5, 4, 2, np.nan, np.nan, 3, 1],
    'C': [2, np.nan, np.nan, 6, 7, 8, 9]
}
df = pd.DataFrame(data)
df

 

📌 .dropna() : 결측이 하나라도 있으면 해당 행 제거

# 2) 결측치 제거 (결측이 하나라도 있으면 해당 행을 제거)
df_drop = df.dropna()
df_drop

 

📌 .fillna() : NULL값을 지정된 값으로 바꿈

📌 numeric_only=True : 숫자형 데이터에만 계산을 해주고 싶을 때 사용

# 3) 평균값으로 대치
df_mean = df.copy()
df_mean = df_mean.fillna(df_mean.mean(numeric_only=True))
df_mean

 

# 4) 중앙값으로 대치
df_median = df.copy()
df_median = df_median.fillna(df_median.median(numeric_only=True))
df_median

# 5) 최빈값으로 대치
#   - DataFrame의 mode()는 각 열별로 최빈값을 반환합니다.
#   - mode() 결과가 여러 개(동률)일 경우 첫 번째 행의 값을 취합니다.
df_mode = df.copy()
print(df_mode.mode()) # 확인용
mode_values = df_mode.mode().iloc[0]  # 첫 번째 행(가장 상위 mode)만 취함
df_mode = df_mode.fillna(mode_values)
df_mode

 

 

🔸 이상치 탐지 & 제거                                                                                              

이상치 (Outlier) : 정상 범주에서 크게 벗어난 값

🔎 이상치 탐지                                                                                                                              

1️⃣ 통계적 기법 (3σ Rule)

  • : 데이터가 정규분포를 따른다고 가정, 평균에서 ±3σ(표준편차) 범위를 벗어나는 값을
  • 이상치로 간주
  • 직관적이고 간단하나 정규성 가정이 틀릴 수 있음

2️⃣ 박스플롯(Boxplot) 기준

  • : 사분위수 (IQR = Q3 - Q1) 를 이용해 (Q1 - 1.5 * IQR) + (Q3 + 1.5 * IQR) 벗어나는 데이터를 이상치로 간주
  • 분포 특성에 영향을 적게 받는 장점

3️⃣ 머신러닝 기반

  • : 이상치 탐지 알고리즘 (Isolation Forest, DBSCAN 등)
  • 복합적 패턴을 고려할 수 있음

 

🔎 이상치 제거 코드                                                                                                                       

import pandas as pd
import numpy as np

# 예시 데이터프레임 생성
np.random.seed(42)  # 재현성을 위해 시드 설정
normal_values = np.random.normal(loc=50, scale=5, size=30)   # 평균 50, 표준편차 5인 정규분포에서 30개 값 생성
outliers = [150, 180, 200, 300]  # 눈으로 봐도 이상치로 판단될 수 있는 큰 값들

# normal_values와 outliers를 합쳐서 하나의 리스트로 구성
all_values = np.concatenate([normal_values, outliers])
# 예시로 0~39 범위의 임의 날짜/시간 데이터를 간단히 만들기
dates = pd.date_range('2021-01-01', periods=len(all_values), freq='D')

df = pd.DataFrame({
    'date': dates,
    'sensor_value': all_values
})
df

# 이상치 제거 (간단하게 박스플롯 기준 적용 예시)
Q1 = df['sensor_value'].quantile(0.25)
Q3 = df['sensor_value'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
df = df[(df['sensor_value'] >= lower_bound) & (df['sensor_value'] <= upper_bound)]
df

 

 

🔸 정규화/표준화 = 스케일링                                                                                      

❓ WHY 필요

  • 모델(특히 거리 기반 알고리즘, 딥러닝 등)에 따라 특정 변수의 스케일이 크게 영향을 미칠 수 ⭕
  • ex) 센서 A는 값 범위가 0~1000, 센서 B는 값 범위가 0~1이라면, A가 모델에 더 큰 영향을 줌

🔎 정규화 (MinMaxScaler)                                                                                                    

  • : 모든 값을 0과 1 사이로 매핑
  • 값의 스케일이 달라도 공통 범위로 맞출 수 있음
  • 딥러닝(신경망), 이미지 처리 등에서 입력값을 0~1로 제한해야 하거나, 각 특성이 동일한 범위 내 있어야 하는 경우 자주 사용
  • 거리 기반 알고리즘(유클리디안 거리 사용)이나, 각 특성의 범위를 동일하게 맞춤으로써 계산 안정성을 높이고 싶을 때
  • 최소값·최대값이 극단값(Outlier)에 민감. 만약 극단치가 있으면 대부분의 데이터가 [0, 1] 구간 내부 한쪽에 치우침
  • 새로운 데이터가 기존 최대값보다 커지거나, 최소값보다 작아지는 경우, 스케일링 범위를 벗어날 수 있어 재학습하거나 다른 처리가 필요

🔎 표준화 (StandardScaler)                                                                                                    

  • : 평균을 0, 표준편차를 1로 만듦
  • 분포가 정규분포에 가깝게 변형됨
  • 평균이 0, 표준편차가 1로 맞춰지므로, 정규분포 가정을 사용하는 알고리즘 (선형회귀, 로지스틱회귀, SVM 등)에 자주 쓰임
  • 변환된 값들이 이론적으로 -∞ ~ +∞ 범위를 가질 수 있음
  • 데이터가 특정 구간([0, 1] 등)에 고정되지 않음
  • 데이터 분포가 심하게 치우쳐 있으면, 평균과 표준편차만으로는 충분한 스케일링이 되지 않을 수 있음 (로그 변환, RobustScaler 등 추가 고려)

 

🔎 정규화/표준화 코드                                                                                                                 

import pandas as pd
import numpy as np

# 난수를 재현하기 위해 시드 설정
np.random.seed(42)

# 예시 마케팅 지표 데이터 생성
data_size = 10
df = pd.DataFrame({
    'impressions': np.random.randint(1000, 10000, size=data_size), # 광고 노출 횟수
    'clicks': np.random.randint(0, 300, size=data_size), # 광고 클릭 횟수
    'conversions': np.random.randint(0, 50, size=data_size), # 광고를 통해 구매한 횟수
    'cost': np.random.randint(100, 5000, size=data_size), # 광고비 지출액
    'revenue': np.random.randint(100, 10000, size=data_size) # 광고를 통해 발생한 매출
})
df

 

📌 MinMaxScaler() : 정규화. 일반적으로 0과 1 사이의 지정된 범위로 특성을 조정하는데 사용

from sklearn.preprocessing import MinMaxScaler

# 스케일링을 적용할 컬럼만 선정
cols_to_scale = ['impressions', 'clicks', 'conversions', 'cost', 'revenue']

# MinMaxScaler 객체 생성(기본 스케일: [0,1])
minmax_scaler = MinMaxScaler()

# fit_transform을 통해 스케일링된 결과를 데이터프레임으로 변환
df_minmax_scaled = pd.DataFrame(minmax_scaler.fit_transform(df[cols_to_scale]), 
                                columns=cols_to_scale)                            
print(df_minmax_scaled.max())
print(df_minmax_scaled.min())
                     
df_minmax_scaled

  • MinMaxScaler() 에서의 fit_transform()
    • : 밑의 두 단계를 한 번에 호출
    • fit : 열을 살펴 최솟값과 최댓값을 찾음
    • transform : 찾은 최솟값/최댓값으로 각 데이터를 0~1 범위로 바꿈

 

📌 StandardScaler() : 표준화. 평균을 제거하고 단위 분산으로 스케일링하여 특성을 표준화.

from sklearn.preprocessing import StandardScaler

# StandardScaler 객체 생성
standard_scaler = StandardScaler()

# fit_transform을 통해 스케일링된 결과를 데이터프레임으로 변환
df_standard_scaled = pd.DataFrame(standard_scaler.fit_transform(df[cols_to_scale]), 
                                  columns=cols_to_scale)

print(df_standard_scaled.mean())
print(df_standard_scaled.std())
df_standard_scaled

  • StandardScaler() 에서의 fit_transform()
    • : 밑의 두 단계를 한 번에 호출
    • fit : 열의 평균과 표준편차를 구함
    • transform : 각 값을 “(x - 평균) / 표준편차” 로 변환하여 평균 0, 표준편차 1인 분포를 만듦

 

🔸 불균형 데이터 처리                                                                                               

정상 99%, 불량 1%처럼 한 클래스가 극도로 적은 경우

  • 문제점 : 모델이 극도로 적은 클래스를 거의 예측하지 못할 가능성이 큼 (편향 발생)

🔎 해결 기법                                                                                                                                   

1️⃣ Oversampling

  • Random Oversampling
    • : 소수 클래스의 데이터를 단순 복제하여 개수를 늘림
  • SMOTE(Synthetic Minority Over-sampling Technique)
    • : 소수 클래스를 "무작정 복사"만 하는 게 아니라, “비슷한” 데이터들을 서로 섞어서(Interpolation) 새로운 데이터 생성
    • 즉, 소수 클래스(ex: 스팸) 안에서 가까운 데이터 둘(혹은 몇 개)을 고르고, 그 사이에 새 데이터 포인트를 만들어내어, 소수 클래스의 다양한 예시를 가상으로 늘리는 기법
    • ex) “모양이나 맛이 비슷한 `두 오렌지를 고른 다음, 그 중간 정도 되는 새로운 오렌지를 상상해서 만들어낸다” 같은 느낌

2️⃣ Undersampling

  • : 다수의 클래스 데이터를 줄이는 방식
  • 데이터 손실 위험이 있지만, 전체 데이터 균형을 맞출 수 있음

3️⃣ 혼합 기법

  • : SMOTE와 언더샘플링을 적절히 섞어서 사용

🔎 불균형 데이터 처리 코드                                                                                                          

import numpy as np
import pandas as pd

# 난수 고정 (재현성)
np.random.seed(42)

# 불균형 데이터 크기 설정
# 예: 총 100개 중 defect=1(불량)인 샘플 10개, defect=0(정상)인 샘플 90개
size_1 = 10
size_0 = 90

# 정상 클래스 (defect=0) 데이터 생성
feature1_0 = np.random.normal(loc=10, scale=2, size=size_0)
feature2_0 = np.random.normal(loc=5, scale=1, size=size_0)

# 불량 클래스 (defect=1) 데이터 생성
feature1_1 = np.random.normal(loc=20, scale=5, size=size_1)
feature2_1 = np.random.normal(loc=10, scale=2, size=size_1)

# 배열 병합
feature1 = np.concatenate([feature1_0, feature1_1])
feature2 = np.concatenate([feature2_0, feature2_1])
defect = np.array([0]*size_0 + [1]*size_1)

# 데이터프레임 생성
df = pd.DataFrame({
    'feature1': feature1,
    'feature2': feature2,
    'defect': defect
})

df

 

📌 SMOTE()

from imblearn.over_sampling import SMOTE
# 불균형 데이터 처리 (SMOTE)
X = df.drop('defect', axis=1)   # 결측치 처리, 이상치 제거, 인코딩 등 사전 처리 후
y = df['defect']
smote = SMOTE(random_state=42)
X_res, y_res = smote.fit_resample(X, y)
  • fit_resample
    • : SMOTE 알고리즘이 x, y를 바탕으로 소수 클래스 데이터를 자동 생성
    • 오버샘플링된 X_res, y_res에는 클래스 불균형이 개선된(1:1에 가깝거나 원하는 비율이 된) 상태가 됨
X_res

y_res

y_res.hist()

 

 

🔸 범주형 데이터 변환                                                                                                 

import pandas as pd
import numpy as np

# 예시 데이터프레임 생성
data_size = 10
np.random.seed(42)

labels = ['apple', 'banana', 'cherry']
random_labels = np.random.choice(labels, data_size)

df = pd.DataFrame({
    'id': range(1, data_size + 1),
    'label': random_labels,
    'value': np.random.randint(1, 100, data_size),
    'another_feature': np.random.choice(['A', 'B'], data_size)  # 또 다른 범주형 변수
})

df

🔎 원-핫 인코딩 (One-Hot Encoding)                                                                                           

  • 범주형 변수를 각각의 범주별로 새로운 열로 표현, 해당 범주에 해당하면 1, 아니면 0
  • ex) 색상(‘Red’, ‘Blue’, ‘Green’) → ‘Red=1,Blue=0,Green=0’ / ‘Red=0,Blue=1,Green=0’ / …
  • 장점 : 범주 간 서열 관계가 없을 때 사용하기 좋음
  • 단점 : 범주가 매우 많으면 차원이 커짐

📌 pd.get_dummies()

# 범주형 변수 변환 (원-핫 인코딩 예시)
df = pd.get_dummies(df, columns=['label'])

  • pd.get_dummies(df, columns=["칼럼이름"]) : 열의 범주들을 각각 별도 열로 만들어, 해당하는 행에는 1, 그렇지 않은 행에는 0 매핑

🔎 레이블 인코딩 (Label Encoding)                                                                                          

  • 범주를 숫자로 직접 맵핑(‘M’=0, ‘L’=1, ‘XL’=2 등)
  • 단순하지만, 모델이 숫자의 크기를 서열 정보로 잘못 해석할 수 있음

📌 LabelEncoder()

# 범주형 변수 변환 (레이블 인코딩 예시)
from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()
df["label"] = encoder.fit_transform(df["label"])
df

 

🔸 피처 엔지니어링                                                                          

모델 성능 향상을 위해 기존 데이터를 변형, 조합해 새로운 특성(피처)를 만드는 작업

  • 복잡한 데이터 구조 안에 존재하는 패턴을 효과적으로 추출해 모델이 쉽게 학습하게 함

🔎 실습 예시

1️⃣ 파생 변수 생성

  • 날짜 파생 변수
    • ex) 측정 시간이 ‘2025-02-24 10:35:00’이라면, ‘월(2)’, ‘요일(월=1)’, ‘시(10)’, ‘주말여부(0/1)’ 등으로 분해
  • 수치형 변수 조합
    • ex) ‘온도’와 ‘습도’가 있을 때, 새로운 피처 ‘온도×습도(TEMP×HUMID)’를 추가 → 두 변수의 상호작용이 불량 발생에 영향을 줄 수 있음
  • 로그 변환, 제곱근 변환 등
    • : 분포가 매우 치우친 변수(오른쪽 꼬리가 긴 경우)에 로그 변환을 적용하여 정규성에 가까워지도록 조정

2️⃣ 파생 변수 생성 코드 예시

import pandas as pd
import numpy as np

np.random.seed(42)  # 재현성을 위한 시드 고정

# 10개 데이터 샘플 생성
data_size = 10

# 날짜/시간 컬럼(예시)
dates = pd.date_range(start="2023-01-01", periods=data_size, freq='D')

# 온도(°C) : 15 ~ 35 사이 정수
temperature = np.random.randint(15, 36, size=data_size)

# 습도(%) : 30 ~ 90 사이 정수
humidity = np.random.randint(30, 91, size=data_size)

df = pd.DataFrame({
    'date': dates,
    'temperature': temperature,
    'humidity': humidity
})

df

# 피처 엔지니어링 (온도와 습도 간 상호작용)
df['temp_humid_interaction'] = df['temperature'] * df['humidity']
df

 

3️⃣ 변수 선택 (Feature Selection)

  • 상관관계
    • 두 변수 간 상관도가 높은 상황인 경우 다중공선성 의심.
    • 중복 정보가 클 수 있으므로, 하나만 남기거나 둘 다 제거 고려
  • VIF
    • : 어떤 변수 하나가, 다른 변수들과 얼마나 겹치는지(상관이 큰지) 수치로 보여주는 지표
    • 회귀분석에서 다중공선성 문제를 파악할 때 사용
    • VIF가 일정 기준(예: 10 이상)을 넘으면 해당 변수를 제거하거나 비슷한 변수들을 합치는(변환) 등의 방법으로 문제를 해결
  • 모델 기반 중요도 (Feature Importance)
    • 트리 기반 모델(랜덤 포레스트, XGBoost 등)을 훈련 후 중요도가 낮은 변수를 제거

4️⃣ 변수 간 상호작용 추가

  • 다항식 / 교차항 생성
    • ex) 2차 다항식(Quadratic Features)
    • 제조 공정에서 온도, 압력, 속도 등이 곱해져야 비로소 의미가 생기는 경우가 많음

다중공선성(multicollinearity)

  • 회귀분석(집값 예측, 매출 예측 등)을 할 때, 여러 설명 변수(독립 변수)를 사용
  • 그런데 이 변수들이 서로 너무 비슷한 정보를 담고 있어 (즉, 서로 강하게 상관이 있어) 모델이 헷갈리는 문제가 생김
  • 이런 다중공선성 문제는 회귀계수(모델 파라미터)의 의미 해석과 모델 안정성을 해침
    • ex) 집 크기(㎡)와 방 개수가 거의 정비례한다면, 둘 다 넣었을 때 겹치는 정보가 많아짐
    • "방 개수"와 "평수(㎡)"라는 두 변수가 존재
    • 방이 5개면 평수도 대체로 넓고, 1개면 대체로 좁을거임 (둘은 서로 높은 상관 관계).
    • 둘 다 회귀분석에 넣으면 모델 입장에서 "비슷한 정보가 두 번 들어온 셈"이라, 어떤 변수가 집값에 얼마나 영향을 주는지(독립적 기여도)를 구분하기 어려워짐
    • 이런 경우, VIF가 높게 나타난다.