본 포스팅은 elice의 2021 NIPA AI 온라인 교육을 듣고 개인 공부를 위해 정리한 것입니다.
머신러닝 과정은 크게 네 가지로 나눌 수 있다.
만약 평가에서 잘 안나오면 다시 학습하거나 다시 분석하거나 아니면 또 다른 데이터를 수집한다.
대부분의 머신러닝 모델은 숫자 데이터를 입력 받는다.
그러나 실제 데이터는 다양한 형태로 존재한다! 이미지, 자연어, 범주형, 시계열 등등
이렇게 실제 데이터는 머신러닝 모델이 이해할 수 없는 형태로 되어있다.
그.래.서!👻 전처리를 통해서 머신러닝 모델이 이해할 수 있는 수치형 자료로 변환할 필요가 있다!!!
데이터 정제란 전처리를 통하여 데이터의 결측값 및 이상치를 처리하는 것이다.
위 표를 보면 Age에 35.3이 있는데 실제 나이는 소수점이 될 수 없다! 이 35.3은 이상치가 된다. 그리고 Cabin열에 NaN(결측값)도 확인할 수 있다.
결측값이나 이상치가 있는 상태로 머신러닝을 진행하게 되면 성능이 떨어진다.
그리고 결측값이 있는 경우 모델이 입력을 아예 하지 못하는 경우도 있다.
그렇기 때문에 데이터 정제가 필요한 것이다!
전처리를 통해서 학습용과 평가용 데이터로 분리하는 것을 의미한다.
학습과 평가를 따로따로 하기때문에 그에 맞는 데이터가 필요하다.
원본 데이터가 150개가 있다면 학습용으로 100개, 평가용으로 50개 이런식으로 분리가 필요하다.
🙄 왜 원본 데이터로 한꺼번에 학습하고 평가하지 않을까??
학습 데이터로 원본 데이터를 모두 사용하게 되면 평가할 때 객관성이 떨어지게 된다.
타이타닉 생존자 데이터를 활용해서 범주형 자료 전처리를 해보자😊
변수 명 | 변수 설명 |
---|---|
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, 나머지는 명목형 자료에 속한다.
쉽게 말하자면 범주를 숫자로 변환하는 것이다!
Sex_female
의 데이터를 보자. 0과 1로 나뉜 것을 볼 수 있는데 여기서 0은 해당되는 변수가 아니라는 의미다. 즉 첫번째 사람은 female이 아니라는 것이다. Sex_male
을 보면 1이니 이 사람은 male, 남성임을 알 수 있다.🧑 Sex_female
과 Sex_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
수치형 자료는 크기를 갖는 수치형 값으로 이루어진 데이터이다.
아래 타이타닉 생존자 데이터에서는 Age, SibSo, Parch, Fare
가 수치형 자료에 속한다.
수치형 자료는 머신러닝에 바로 입력할 수 있으나 모델의 성능을 높이기 위해서 데이터 변환이 필요하다.
수치형 자료 변환은 아래의 두 방식이 대표적이다.
스케일링이란 변수 값의 범위 및 크기를 변환하는 방식이다.
변수(feature) 간의 범위가 차이가 난다면 사용하는 것이 좋다.
스케일링 방식은 대표적으로 두 가지가 있는데 하나는 정규화, 다른 하나는 표준화이다.
정규화(Normalization)는 변수 를 정규화한 값 로 표현했을 때
= 이다.
변수의 값들이 원래는 0 ~100가지 숫자를 갖고 있던 데이터라면 정규화를 거치면 0 ~1까지의 값으로 변환이 된다.
feature3
이 다른 feature
에 비해 값이 크기 때문에 feature3
의 영향을 크게 받을 수 있다.표준화(Standardization)는 변수 를 표준화한 값 로 표현했을 때
= 이다. 어떤 데이터든 이런 형태로 평균과 표준편차를 이용하여 변환해주면 표준 정규 분포와는 다르지만 비슷하게 된다.
어떠한 분포가 정규 분포를 따르고 있을 때, 평균은 0, 표준편차는 1인 형태의 표준 정규 분포로 변환해 주는 것이 표준화이다.
범주화는 변수의 값보다 범주가 중요한 경우에 사용한다.
titanic 데이터에서 수치형 자료인 Fare
데이터를 정규화 해 보도록 하자
정규화 공식 :
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
데이터를 표준화 해보자
표준화 공식 :
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
일반적으로 머신러닝 모델은 결측값을 입력할 수 없다!
따라서 Null, None, NaN 등의 결측값을 처리해야한다 → 데이터 정제
이상치가 있으면 모델의 성능을 저하할 수 있다. 그래서 이상치는 보통 전처리 과정에서 제거하며, 어떤 값이 이상치인지 판단하는 기준이 중요하다!
머신러닝 모델을 평가하기 위해서는 학습에 사용하지 않은 평가용 데이터가 필요하다
보통 약 7:3 ~ 8:2 비율로 학습용/평가용 데이터를 분리한다.
지도학습의 경우 feature 데이터와 label 데이터를 분리하여 저장해야 한다.
예를 들어 공부시간 대비 시험점수를 예측한다고 생각해 보자
공부시간 | 시험점수 |
---|---|
... | ... |
여기서 Feature 데이터는 공부시간이 되고 Label 데이터는 시험점수가 된다.
만약 타이타닉 데이터를 바탕으로 생존자를 예측한다면?
👉 Survived
데이터가 바로 Label 데이터가 되고 나머지가 Feautre 데이터가 된다.
titanic 데이터에서 과반수 이상의 데이터가 결측값으로 존재하는 Cabin
변수를 삭제하자.
이 후, 나머지 변수에 존재하는 결측값을 처리하기 위하여 결측값이 존재하는 샘플들을 제거하자
pandas의 DataFrame에서 특정 변수(columns)를 삭제하기 위해서는
drop
을 사용한다.
DataFrame.drop(columns=[변수명])
DataFrame에서 결측값이 있는 샘플을 제거하기 위해서는dropna
를 사용한다.
DataFrame.dropna()
drop
을 사용하여 Cabin
변수를 삭제하고 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)가 아닌 자연수가 오게 된다면 비율이 아닌 데이터의 개수를 뜻하게 된다.
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을 학습시킨 적이 있어서 조금 수월했다😎