본 포스팅은 elice2021 NIPA AI 온라인 교육을 듣고 개인 공부를 위해 정리한 것입니다.

데이터 전 처리하기

1. 머신러닝을 위한 데이터 전 처리 이해하기

머신러닝 과정은 크게 네 가지로 나눌 수 있다.

  • 데이터 수집 : 크롤링 또는 DB데이터를 통하여 데이터 수집
  • 데이터 분석 및 전처리 : 수집한 데이터를 분석하고 머신러닝에 사용할 형태로 전처리
  • 머신러닝 학습 : 머신러닝 모델을 사용하여 데이터를 학습
  • 머신러닝 평가 : 학습된 머신러닝 모델을 평가용 데이터를 사용하여 평가

만약 평가에서 잘 안나오면 다시 학습하거나 다시 분석하거나 아니면 또 다른 데이터를 수집한다.

데이터 전 처리의 역할

  1. 머신러닝의 입력 형태로 데이터 변환 (특성 엔지니어링)
    : 머신러닝을 사용할 수 있도록 형태에 맞게 데이터를 변환해줘야 한다!
  2. 결측값 및 이상치를 처리하여 데이터 정제
  3. 학습용 및 평가용 데이터 분리

왜 데이터 전 처리가 필요할까?

데이터 변환

대부분의 머신러닝 모델은 숫자 데이터를 입력 받는다.

그러나 실제 데이터는 다양한 형태로 존재한다! 이미지, 자연어, 범주형, 시계열 등등

이렇게 실제 데이터는 머신러닝 모델이 이해할 수 없는 형태로 되어있다.
그.래.서!👻 전처리를 통해서 머신러닝 모델이 이해할 수 있는 수치형 자료로 변환할 필요가 있다!!!

데이터 정제

데이터 정제란 전처리를 통하여 데이터의 결측값 및 이상치를 처리하는 것이다.

위 표를 보면 Age에 35.3이 있는데 실제 나이는 소수점이 될 수 없다! 이 35.3은 이상치가 된다. 그리고 Cabin열에 NaN(결측값)도 확인할 수 있다.

결측값이나 이상치가 있는 상태로 머신러닝을 진행하게 되면 성능이 떨어진다.
그리고 결측값이 있는 경우 모델이 입력을 아예 하지 못하는 경우도 있다.
그렇기 때문에 데이터 정제가 필요한 것이다!

데이터 분리

전처리를 통해서 학습용과 평가용 데이터로 분리하는 것을 의미한다.
학습과 평가를 따로따로 하기때문에 그에 맞는 데이터가 필요하다.

원본 데이터가 150개가 있다면 학습용으로 100개, 평가용으로 50개 이런식으로 분리가 필요하다.

🙄 왜 원본 데이터로 한꺼번에 학습하고 평가하지 않을까??
학습 데이터로 원본 데이터를 모두 사용하게 되면 평가할 때 객관성이 떨어지게 된다.

2. 범주형 자료 전 처리

타이타닉 생존자 데이터를 활용해서 범주형 자료 전처리를 해보자😊

  • 타이타닉 생존자 데이터 변수 확인>
변수 명변수 설명
PassengerId각 승객의 고유 번호
Survived생존 여부 (0: 사망, 1: 생존)
Pclass객실 등급 (1st: Upper, 2nd: Middle, 3rd: Lower)
Name이름
Sex성별
Age나이
SibSp동반한 형제자매와 배우자의 수
Parch동반한 부모, 자식의 수
Ticket티켓의 고유 번호
Fare티켓의 요금
Cabin객실 번호
Embarked승선한 항(C: Cherbourg, Q: Queenstown, S: Southampton)

범주형 자료

범주형 데이터는 몇 개의 범주로 나누어진 자료를 의미한다.
아래 타이타닉 생존자 데이터에서는 PassengerId, Survived, Pclass, Name, Sex, Ticket, Cabin, Emabarked가 범주형 자료에 속한다.

범주형 자료는 범주의 순서에 의미가 있는가에 따라 순서형 자료(순위형 자료)와 명목형 자료로 나뉜다.
범주의 크기에 의미가 없다면 명목형 자료, 범주의 크기에 의미가 있다면 순서형 자료이다.
여기서 순서형 자료는 Pclass, 나머지는 명목형 자료에 속한다.

범주형 자료 변환 방식

명목형 자료

  • 수치 맵핑 방식
  • 더미(Dummy) 기법

순서형 자료

  • 수치 맵핑 방식

명목형 자료 변환하기

수치 맵핑 변환

쉽게 말하자면 범주를 숫자로 변환하는 것이다!

  • 일반적으로 범주를 0, 1로 맵핑
  • (-1, 1), (0, 100) 등 다양한 케이스가 있지만 모델에 따라 성능이 달라질 수 있다

  • 3개 이상인 경우, 수치의 크기 간격을 같게 하여 수치 맵핑 ex) (0, 1, 2, 3, ...)
    아래의 Emabarked 데이터 변환 예를 보면 S는 0, Q는 1, C는 2로 변환된 것을 확인할 수 있다.

더미(Dummy) 기법

  • 더미 기법을 사용하여 각 범주를 0 or 1로 변환
    👉 수치 맵핑과 뭐가 다른걸까? 아래는 더미 변환의 예다.


Sex_female 의 데이터를 보자. 0과 1로 나뉜 것을 볼 수 있는데 여기서 0은 해당되는 변수가 아니라는 의미다. 즉 첫번째 사람은 female이 아니라는 것이다. Sex_male을 보면 1이니 이 사람은 male, 남성임을 알 수 있다.🧑 Sex_femaleSex_male이 둘 다 1이거나 0인 경우는 없다

Embarked 의 데이터를 보면 범주가 세 가지로 나뉘기 때문에 변수도 세 개로 나뉘게 된다.

👉 수치 맵핑은 하나의 범주에 대해서 숫자로 맵핑한 것이고 더미는 변수를 만들어서 그 안에서 맵핑했다고 생각하면 된다.

순서형 자료 변환하기

수치 맵핑 변환

  • 수치에 맵핑하여 번환하지만 수치 간 크기 차이는 커스텀 가능
  • 크기 차이가 머신러닝 결과에 영향을 끼칠 수 있음

실습✍ 명목형 자료 변환하기 - 수치 맵핑


titanic 데이터에서 범주형 자료인 성별(Sex) 데이터는 male, female 값을 가지고 있다. 이를 0, 1 인 수치형 자료로 변환해 보자

pandas의 DataFrame에서 이를 수행하기 위하여 replace를 사용한다.
DataFrame.replace({A:B, C:D,...}) : A → B, C → D,… 로 변환하는 코드

성별(‘Sex’) 변수에서 male 데이터는 0, female 데이터는 1로 변환하여 titanic 데이터프레임에 저장해 보자

  • 답안 작성하기
import pandas as pd
from elice_utils import EliceUtils

elice_utils = EliceUtils()


# 데이터를 읽어옵니다.
titanic = pd.read_csv('./data/titanic.csv')
print('변환 전: \n',titanic['Sex'].head())

"""
1. replace를 사용하여 male -> 0, female -> 1로 변환합니다.
"""
titanic = titanic.replace({"male":0, "female":1 })

# 변환한 성별 데이터를 출력합니다.
print('\n변환 후: \n',titanic['Sex'].head())
    
  • 결과
변환 전: 
 0      male
1    female
2    female
3    female
4      male
Name: Sex, dtype: object

변환 후: 
 0    0
1    1
2    1
3    1
4    0
Name: Sex, dtype: int64

실습✍ 명목형 자료 변환하기 - 더미 방식

titanic 데이터에서 범주형 자료인 Embarked 데이터는 S, Q, C 3가지 값을 가지고 있다. 이를 더미 방식을 사용하여 변환해 보자

pandas의 DataFrame에서 이를 수행하기 위하여 get_dummies를 사용한다.
pd.get_dummies(DataFrame[[변수명]])

Embarked의 S, Q, C데이터를 더미를 사용하여 변환하고 dummies에 저장해 보자

  • 답안 작성하기
import pandas as pd
from elice_utils import EliceUtils

elice_utils = EliceUtils()
   
# 데이터를 읽어옵니다.
titanic = pd.read_csv('./data/titanic.csv')
print('변환 전: \n',titanic['Embarked'].head())

"""
1. get_dummies를 사용하여 변환합니다.
"""
dummies = pd.get_dummies(titanic[["Embarked"]])

# 변환한 Embarked 데이터를 출력합니다.
print('\n변환 후: \n',dummies.head())
  • 결과
변환 전: 
 0    S
1    C
2    S
3    S
4    S
Name: Embarked, dtype: object

변환 후: 
    Embarked_C  Embarked_Q  Embarked_S
0           0           0           1
1           1           0           0
2           0           0           1
3           0           0           1
4           0           0           1

3. 수치형 자료 전 처리

수치형 자료는 크기를 갖는 수치형 값으로 이루어진 데이터이다.
아래 타이타닉 생존자 데이터에서는 Age, SibSo, Parch, Fare가 수치형 자료에 속한다.

수치형 자료는 머신러닝에 바로 입력할 수 있으나 모델의 성능을 높이기 위해서 데이터 변환이 필요하다.

수치형 자료 변환은 아래의 두 방식이 대표적이다.

  • 스케일링(Scaling) - 정규화(Normalization), 표준화(Standardization)
  • 범주화

수치형 자료 변환 하기

스케일링 - 정규화

스케일링이란 변수 값의 범위 및 크기를 변환하는 방식이다.
변수(feature) 간의 범위가 차이가 난다면 사용하는 것이 좋다.
스케일링 방식은 대표적으로 두 가지가 있는데 하나는 정규화, 다른 하나는 표준화이다.

정규화(Normalization)는 변수 XX를 정규화한 값 XX'로 표현했을 때

XX' = XXminXmaxXmin{X-X_{min}}\over{X_{max}-X_{min}} 이다.

변수의 값들이 원래는 0 ~100가지 숫자를 갖고 있던 데이터라면 정규화를 거치면 0 ~1까지의 값으로 변환이 된다.

  • 정규화 예시

    만약 정규화로 변환 전의 값을 머신러닝 입력을 한다면 feature3 이 다른 feature에 비해 값이 크기 때문에 feature3의 영향을 크게 받을 수 있다.

스케일링 - 표준화

표준화(Standardization)는 변수 XX를 표준화한 값 XX'로 표현했을 때
XX' = Xμσ{X-\mu}\over{\sigma} 이다. 어떤 데이터든 이런 형태로 평균과 표준편차를 이용하여 변환해주면 표준 정규 분포와는 다르지만 비슷하게 된다.

어떠한 분포가 정규 분포를 따르고 있을 때, 평균은 0, 표준편차는 1인 형태의 표준 정규 분포로 변환해 주는 것이 표준화이다.

  • 표준화 예시

    표준화는 평균이 0, 표준편차가 1로 되어있기 때문에 값들이 0에서 ±(1~2) 사이에 있는 분포로 비슷하게 바뀌게 된다.

범주화

범주화는 변수의 값보다 범주가 중요한 경우에 사용한다.

  • 범주화 예시

    위와 같이 점수를 예측하는 것이 아니고 점수가 평균보다 높은지 낮은지 판별하고 싶을 때, 즉 범주를 예측하고 싶을 때! 범주화로 변환하게 된다.

실습✍ 수치형 자료 변환하기 - 정규화


titanic 데이터에서 수치형 자료인 Fare 데이터를 정규화 해 보도록 하자

정규화 공식 : XXminXmaxXmin{X-X_{min}}\over{X_{max}-X_{min}}

normal 함수를 완성하고 Fare 데이터를 정규화하여 Fare에 저장하기

  • 답안 작성하기 : min(), max() 메서드 이용
import pandas as pd
from elice_utils import EliceUtils

elice_utils = EliceUtils()

"""
1. 정규화를 수행하는 함수를 구현합니다.
"""
def normal(data):
    
    data = (data - data.min())/(data.max() - data.min())
    
    return data

# 데이터를 읽어옵니다.
titanic = pd.read_csv('./data/titanic.csv')
print('변환 전: \n',titanic['Fare'].head())

# normal 함수를 사용하여 정규화합니다.
Fare = normal(titanic['Fare'])

# 변환한 Fare 데이터를 출력합니다.
print('\n변환 후: \n',Fare.head())
  • 결과
변환 전: 
 0     7.2500
1    71.2833
2     7.9250
3    53.1000
4     8.0500
Name: Fare, dtype: float64

변환 후: 
 0    0.014151
1    0.139136
2    0.015469
3    0.103644
4    0.015713
Name: Fare, dtype: float64

실습✍ 수치형 자료 변환하기 - 표준화

titanic 데이터에서 수치형 자료인 Fare 데이터를 표준화 해보자

표준화 공식 : Xμσ{X-\mu}\over{\sigma}

standard 함수를 완성하고 Fare 데이터를 표준화하여 Fare에 저장하기

  • 답안 작성하기 : mean(), std() 메서드 이용
import pandas as pd
from elice_utils import EliceUtils

elice_utils = EliceUtils()

"""
1. 표준화를 수행하는 함수를 구현합니다.
"""
def standard(data):
    
    data = (data - data.mean())/data.std()
    
    return data
    
# 데이터를 읽어옵니다.
titanic = pd.read_csv('./data/titanic.csv')
print('변환 전: \n',titanic['Fare'].head())

# standard 함수를 사용하여 표준화합니다.
Fare = standard(titanic['Fare'])

# 변환한 Fare 데이터를 출력합니다.
print('\n변환 후: \n',Fare.head())
  • 결과
변환 전: 
 0     7.2500
1    71.2833
2     7.9250
3    53.1000
4     8.0500
Name: Fare, dtype: float64

변환 후: 
 0   -0.502163
1    0.786404
2   -0.488580
3    0.420494
4   -0.486064
Name: Fare, dtype: float64

4. 데이터 정제 및 분리하기

데이터 정제 - 결측값(Missing data) 처리하기

일반적으로 머신러닝 모델은 결측값을 입력할 수 없다!
따라서 Null, None, NaN 등의 결측값을 처리해야한다 → 데이터 정제

대표적인 결측값 처리 방식

  • 결측값이 존재하는 샘플 삭제
  • 결측값이 많이 존재하는 변수 삭제
  • 결측값을 다른 값으로 대체 → 평균값, 중앙값, 머신러닝으로 예측한 값 등

데이터 정제 - 이상치(Outlier) 처리하기

이상치가 있으면 모델의 성능을 저하할 수 있다. 그래서 이상치는 보통 전처리 과정에서 제거하며, 어떤 값이 이상치인지 판단하는 기준이 중요하다!

이상치 판단 기준 방법

  • 통계 지표(카이제곱 검정, IQR 지표 등)를 사용하여 판단
  • 데이터 분포를 보고 직접 판단
  • 머신러닝 기법을 사용하여 이상치 분류 → 이상치 분류를 위한 머신러닝 종류가 있음

데이터 분리

머신러닝 모델을 평가하기 위해서는 학습에 사용하지 않은 평가용 데이터가 필요하다
보통 약 7:3 ~ 8:2 비율로 학습용/평가용 데이터를 분리한다.

지도학습 데이터 분리

지도학습의 경우 feature 데이터와 label 데이터를 분리하여 저장해야 한다.

  • Feature 데이터 : label을 예측하기 위한 입력 값
  • Label 데이터 : 예측해야 할 대상이 되는 데이터

예를 들어 공부시간 대비 시험점수를 예측한다고 생각해 보자

공부시간시험점수
......

여기서 Feature 데이터는 공부시간이 되고 Label 데이터는 시험점수가 된다.


만약 타이타닉 데이터를 바탕으로 생존자를 예측한다면?

👉 Survived데이터가 바로 Label 데이터가 되고 나머지가 Feautre 데이터가 된다.

실습✍ 결측값 처리하기

titanic 데이터에서 과반수 이상의 데이터가 결측값으로 존재하는 Cabin 변수를 삭제하자.
이 후, 나머지 변수에 존재하는 결측값을 처리하기 위하여 결측값이 존재하는 샘플들을 제거하자

pandas의 DataFrame에서 특정 변수(columns)를 삭제하기 위해서는 drop을 사용한다.
DataFrame.drop(columns=[변수명])
DataFrame에서 결측값이 있는 샘플을 제거하기 위해서는 dropna를 사용한다.
DataFrame.dropna()

  1. drop 을 사용하여 Cabin 변수를 삭제하고 titanic_1에 저장한다.
  2. titanic_1에서 dropna 를 사용하여 결측값이 존재하는 샘플을 삭제하고 titanic_2에 저장한다.
  • 답안 작성하기
import pandas as pd
from elice_utils import EliceUtils

elice_utils = EliceUtils()

    
# 데이터를 읽어옵니다.
titanic = pd.read_csv('./data/titanic.csv')
# 변수 별 데이터 수를 확인하여 결측 값이 어디에 많은지 확인합니다.
print(titanic.info(),'\n')

"""
1. Cabin 변수를 제거합니다.
"""
titanic_1 = titanic.drop(columns=["Cabin"])
# Cabin 변수를 제거 후 결측값이 어디에 남아 있는지 확인합니다.
print('Cabin 변수 제거')
print(titanic_1.info(),'\n')

"""
2. 결측값이 존재하는 샘플 제거합니다.
"""
titanic_2 = titanic_1.dropna()
# 결측값이 존재하는지 확인합니다.
print('결측값이 존재하는 샘플 제거')
print(titanic_2.info())
  • 결과
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId    891 non-null int64
Survived       891 non-null int64
Pclass         891 non-null int64
Name           891 non-null object
Sex            891 non-null object
Age            714 non-null float64
SibSp          891 non-null int64
Parch          891 non-null int64
Ticket         891 non-null object
Fare           891 non-null float64
Cabin          204 non-null object
Embarked       889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB
None 

Cabin 변수 제거
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 11 columns):
PassengerId    891 non-null int64
Survived       891 non-null int64
Pclass         891 non-null int64
Name           891 non-null object
Sex            891 non-null object
Age            714 non-null float64
SibSp          891 non-null int64
Parch          891 non-null int64
Ticket         891 non-null object
Fare           891 non-null float64
Embarked       889 non-null object
dtypes: float64(2), int64(5), object(4)
memory usage: 76.6+ KB
None 

결측값이 존재하는 샘플 제거
<class 'pandas.core.frame.DataFrame'>
Int64Index: 712 entries, 0 to 890
Data columns (total 11 columns):
PassengerId    712 non-null int64
Survived       712 non-null int64
Pclass         712 non-null int64
Name           712 non-null object
Sex            712 non-null object
Age            712 non-null float64
SibSp          712 non-null int64
Parch          712 non-null int64
Ticket         712 non-null object
Fare           712 non-null float64
Embarked       712 non-null object
dtypes: float64(2), int64(5), object(4)
memory usage: 66.8+ KB
None

👀 가장 데이터의 개수가 적은 Age에 맞춰서 714가 나올 줄 알았는데 712가 나왔네? 무슨 이유일까?

실습✍ 이상치 처리하기 🤢

titanic 데이터에서 Age 변수에 존재하는 이상치를 제거한다. 아래 그림과 같이 Age 변수 안에는 소수점으로 표현되는 데이터가 존재한다. 나이는 자연수로 표현되어야 하기에 이러한 소수점 데이터는 이상치로 판단하고 삭제하도록 하자

Age 변수에서 outlier 에 있는 이상치를 제외한 데이터를 titanic_3에 저장한다.

np.floor()는 소수점 내림을 하는 함수이다.

  • 답안 작성하기
import pandas as pd
import numpy as np
from elice_utils import EliceUtils

elice_utils = EliceUtils()



# 데이터를 읽어옵니다.
titanic = pd.read_csv('./data/titanic.csv')

# Cabin 변수를 제거합니다.
titanic_1 = titanic.drop(columns=['Cabin'])

# 결측값이 존재하는 샘플 제거합니다.
titanic_2 = titanic_1.dropna()

# (Age 값 - 내림 Age 값) 0 보다 크다면 소수점을 갖는 데이터로 분류합니다.
outlier = titanic_2[titanic_2['Age']-np.floor(titanic_2['Age']) > 0 ]['Age']

print('소수점을 갖는 Age 변수 이상치')
print(outlier)
print('이상치 처리 전 샘플 개수: %d' %(len(titanic_2)))
print('이상치 개수: %d' %(len(outlier)))

"""
1. 이상치를 처리합니다.
"""
titanic_3 = titanic_2[titanic_2['Age']-np.floor(titanic_2['Age']) == 0 ]['Age']
print('이상치 처리 후 샘플 개수: %d' %(len(titanic_3)))
  • 결과
소수점을 갖는 Age 변수 이상치
57     28.50
78      0.83
111    14.50
116    70.50
122    32.50
123    32.50
148    36.50
152    55.50
153    40.50
203    45.50
227    20.50
296    23.50
305     0.92
331    45.50
469     0.75
525    40.50
644     0.75
676    24.50
735    28.50
755     0.67
767    30.50
803     0.42
814    30.50
831     0.83
843    34.50
Name: Age, dtype: float64
이상치 처리 전 샘플 개수: 712
이상치 개수: 25
이상치 처리 후 샘플 개수: 687

👉 나는 titanic_3 에 titanic_2 에서 outlier를 제거해주는 쪽으로 접근했는데 정답에서는 아주 간단하게 outlier 조건과 반대로 넣어줄 뿐이었다. 너무 복잡하게 생각하지 말도록 하자!

실습✍ 데이터 처리하기

titanic 데이터에서 생존 여부인 Survived 을 예측하는 머신러닝을 수행한다고 했을 때 데이터를 분리해보자
이번 실습에서는 앞에서 이상치를 처리한 데이터를 바탕으로 feature 데이터와 label 데이터를 분리하고 이 후 학습용, 평가용 데이터로 분리한다.
학습용, 평가용 데이터 분리는 sklearn 라이브러리의 train_test_split을 사용하여 분리할 수 있다.

train_test_split()

X_train, X_test, y_train, y_test
= train_test_split(feature 데이터, label 데이터, test_size= 0~1, random_state=랜덤시드값)

test size는 테스트 셋 구성의 비율을 나타낸다. 만약 0.2라면 체 데이터 셋의 20%를 test (validation) 셋으로 지정하겠다는 의미이다.
만약 여기에 소수(decimal)가 아닌 자연수가 오게 된다면 비율이 아닌 데이터의 개수를 뜻하게 된다.

  1. titanic_3 에서 Survived 변수를 제거하여 X에 저장하고 Survived 변수의 데이터는 pandas의 Series 형태로 y에 저장한다.
  2. train_test_split 를 사용하여 데이터를 분리한다. test_size는 0.3, random_state는 42로 설정한다.
  • 답안 작성하기
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from elice_utils import EliceUtils

elice_utils = EliceUtils()

# 데이터를 읽어옵니다.
titanic = pd.read_csv('./data/titanic.csv')

# Cabin 변수를 제거합니다.
titanic_1 = titanic.drop(columns=['Cabin'])

# 결측값이 존재하는 샘플 제거합니다.
titanic_2 = titanic_1.dropna()

# 이상치를 처리합니다.
titanic_3 = titanic_2[titanic_2['Age']-np.floor(titanic_2['Age']) == 0 ]
print('전체 샘플 데이터 개수: %d' %(len(titanic_3)))

"""
1. feature 데이터와 label 데이터를 분리합니다.
"""
X = titanic_3.drop(columns=["Survived"])
y = titanic_3["Survived"]
print('X 데이터 개수: %d' %(len(X)))
print('y 데이터 개수: %d' %(len(y)))

"""
2. 학습용, 평가용 데이터로 분리합니다.
"""
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 분리한 데이터의 개수를 출력합니다.
print('학습용 데이터 개수: %d' %(len(X_train)))
print('평가용 데이터 개수: %d' %(len(X_test)))
  • 결과
전체 샘플 데이터 개수: 687
X 데이터 개수: 687
y 데이터 개수: 687
학습용 데이터 개수: 480
평가용 데이터 개수: 207

👉 마지막 실습은 내가 어제 오늘 colab을 이용하여 yolov5로 object detection을 학습시킨 적이 있어서 조금 수월했다😎

profile
한 발짝 두 발짝 개발자의 길로

0개의 댓글