pip install -U scikit-learn # 사이킷런 최신버전 다운
sklearn.__version__ # 사이킷런 버전 확인
import sklearn.model_selection # 데이터세트 분류, 평가
import sklearn.datasets # 사이킷런이 제공하는 데이터세트
import sklearn.tree # tree기반 ML 알고리즘
import sklearn.preprocessing # 데이터 전처리
머신러닝,딥러닝에서는 데이터를 학습데이터와 테스트데이터로 나눠야합니다.
데이터를 학습만 진행해서는 데이터의 성능을 알기 어렵습니다.
데이터 수가 만약 적다면 학습데이터와 테스트데이터 2가지로 나눠도 상관없습니다.
하지만 데이터 수가 많다면 학습데이터 검증데이터 테스트데이터 3가지로 나눠야하는것이 일반적입니다. 왜냐하면, 과적합(overfitting)이 일어날 수 있기 때문입니다.
train_test_split함수는 데이터를 나누어주는 역할을 하는 함수입니다.
train_test_split(arrays, test_size, train_size, random_state, shuffle, stratify)로 인자가 있습니다.
train_test_split의 반환값은 tuple형태이며, 순차적으로 학습 데이터, 테스트 데이터, 학습 레이블, 테스트 레이블을 반환합니다.
밑의 코드는 train_test_split 함수를 사용하는 예시입니다.
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer
import pandas as pd
breast_cancer_data = load_breast_cancer()
cancer_data = breast_cancer_data.data # 학습데이터
cancer_target = breast_cancer_data.target # 레이블데이터
# breast_cancer 데이터프레임으로 데이터 확인하기
breast_cancer_df = pd.DataFrame(data = cancer_data,columns = breast_cancer_data.feature_names)
breast_cancer_df['label'] = cancer_target
# train, test 데이터 분리하기(test_size = 0.2이므로 train에는 0.8, test에는 0.2의 비율로 데이터를 할당한다)
train_data, test_data, train_label, test_label = train_test_split(cancer_data, cancer_target, test_size = 0.2, random_state = 100)
fit은 지도학습과 비지도학습에서의 역할이 다릅니다.
지도학습에서는 입력데이터와 레이블 간의 관계를 학습하여 predict를 통해 예측합니다.
비지도학습은 모델의 구조나 패턴을 이해하기 때문에 스스로 답을 찾습니다. 즉, 입력 데이터의 형태에 맞춰 에티러르 변호나하기 위한 사전 구조를 맞추는 작업이고 사전 구조를 맞춘다면 이후 실제작업은 transform 메서드를 통해서 수행합니다.
밑의 코드는 fit과 predict로 정확도를 측정한 코드입니다.
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer
from sklearn.tree import DecisionTreeClassifier # 의사결정 트리 알고리즘
from sklearn.metrics import accuracy_score # 정확도 측정위함
breast_cancer_data = load_breast_cancer()
cancer_data = breast_cancer_data.data
cancer_target = breast_cancer_data.target
train_data, test_data, train_label, test_label = train_test_split(cancer_data, cancer_target, test_size = 0.2, random_state = 100)
dt_clf = DecisionTreeClassifier() # 의사결정 트리 객체 반환
dt_clf.fit(train_data, train_label) # 학습 진행
pred = dt_clf.predict(test_data) # 예측진행
accuracy = accuracy_score(test_label, pred)
accuracy
predict는 학습된 모델의 예측을 위해 사용
Estimator는 Classifier(분류 알고리즘), Regressor(회귀 알고리즘)을 통칭하는 말이다. Estimator는 fit과 predict의 메서드를 가지고 있습니다.
또한 하이퍼 파라미터 튜닝을 지원하는 클래스 경우 Estimator를 인자로 받습니다.
앞서, 설명했지만 과적합은 모델이 해당 학습데이터에만 과도하게 최적화 되어서, 다른 테스트 데이터에는 성능이 떨어지는것을 말한다고 했습니다.
이러한 문제점을 개선하기 위해서 교차검증을 해야합니다.
교차검증은 학습 중간에 검증 데이터를 통해서 모델의 성능을 평가합니다.
우리는 교차검증을 하는 이유
- 과적합 방지
- 하이퍼파라미티 조정
- 일반화 능력향상
처음부터 학습데이터/레이블 데이터가 있다면, 학습데이터에서 검증데이터를 추출하게 됩니다.
학습데이터로 학습하면서 중간에 검증데이터로 성능을 평가하고, 모델이 최적의 성능이 나온다 싶으면 테스트 세트로 모델을 평가하게됩니다.
하지만, 데이터세트가 많다면 학습/레이블 데이터로만 나누는 것이 일반적이라고 말씀드렸습니다.
왜냐하면 학습을 하는 과정에서 시간이 굉장히 오래걸릴수 있기 때문입니다.
데이터세트가 작다면 검증 데이터세트를 이용하는 것이 일반적입니다.
KFold 교차검증은 가장 보편적으로 사용되는 기법으로 먼저 K개의 데이터 폴드 세트를 만들어서 k번만큼 각 폴드 세트에 학습과 검증 평가를 반복적으로 수행하는 방법입니다.
위 사진은 iteration이 증가하면서 학습/검증 데이터가 바뀌는 것을 볼 수 있습니다.
최종적인 정확도는 사진에서 5번의 iteration이 일어났으니, 전체의 iteartion에서의 평균을 구한 값으로 예측 성능을 평가하게됩니다.
밑의 코드는 KFold를 사용한 코드입니다.
from sklearn.model_selection import train_test_split, KFold
from sklearn.datasets import load_breast_cancer
from sklearn.tree import DecisionTreeClassifier # 의사결정 트리 알고리즘
from sklearn.metrics import accuracy_score # 정확도 측정위함
import numpy as np
breast_cancer_data = load_breast_cancer()
cancer_data = breast_cancer_data.data
cancer_target = breast_cancer_data.target
dt_clf = DecisionTreeClassifier(random_state = 100) # 의사결정 트리 객체 반환
accuracy = [] # 각 iteration마다 정확도 측정
for iter, (train_index, test_index) in enumerate(KFold(n_splits = 5).split(cancer_data)):
train_data, train_label = cancer_data[train_index], cancer_target[train_index]
test_data, test_label = cancer_data[test_index], cancer_target[test_index]
dt_clf.fit(train_data, train_label) # 학습 수행
pred = dt_clf.predict(test_data) # 예측 수행
score = np.round(accuracy_score(test_label, pred), 4) # 정확도 측정
accuracy.append(score)
print(f'[교차검증{iter + 1}]\n정확도 : {score}\n')
print('최종 정확도 : ', np.round(np.mean(accuracy),4))
KFold는 데이터셋을 일정한 간격으로 사용하기 때문에 target의 비율이 일정하지 않게 들어갈 수 있습니다.(데이터가 shuffle이 되어있다고 생각합니다.)
예를들어서, 데이터셋이 총 100개인데 A가 90개, B가 10개라고 합시다.
A와B중 A가 압도적으로 많기 때문에 폴드에 B가 들어가지 않을 수 있습니다.
그렇기 때문에 데이터의 불균형으로 인해서 학습이 원활하게 진행이 되지 않을 수 있어서 데이터의 균형도 매우 중요하게 작용합니다.
Stratified KFold 교차검증은 KFold의 불균형한 데이터 분배를 균형있게 분배해줍니다.
원본 데이터의 레이블 분포를 먼저 고려한 뒤 이 분포와 동일하게 데이터세트를 분배해주게 됩니다.
밑에 코드는 Stratified KFold를 이용하고, 데이터가 일정하게 분포가 되어있는지 확인하고 예측을 수행하는 코드입니다.
from sklearn.datasets import load_iris
from sklearn.model_selection import StratifiedKFold
from sklearn.tree import DecisionTreeClassifier # 의사결정 트리 알고리즘
from sklearn.metrics import accuracy_score # 정확도 측정위함
import numpy as np
iris = load_iris()
iris_data = iris.data
iris_target = iris.target
accuracy = []
dt_clf = DecisionTreeClassifier(random_state = 100)
iris_df = pd.DataFrame(data = iris_data, columns = iris.feature_names)
iris_df['label'] = iris_target
print('각 label마다 개수\n',iris_df['label'].value_counts()) # 0,1,2 각각 50개씩 있다.
# Stratified를 이용해서 제대로 분배가 되었는지 확인/학습 시행
for iter, (train_index, test_index) in enumerate(StratifiedKFold(n_splits = 3).split(iris_df, iris_df['label'])): # 레이블보고 비율을 판단하기 때문에 label값도 넣어줘야함
label_train = iris_df['label'].iloc[train_index]
label_test = iris_df['label'].iloc[test_index]
print(f'\n\n######교차검증 {iter}#######')
print(f'train 분포\n{label_train.value_counts()}')
print(f'test 분포\n{label_test.value_counts()}')
train_data, train_label = iris_data[train_index], iris_target[train_index]
test_data, test_label = iris_data[test_index], iris_target[test_index]
dt_clf.fit(train_data, train_label)
pred = dt_clf.predict(test_data)
score = accuracy_score(test_label, pred)
accuracy.append(score)
print('정확도 : ', score)
print('\n\n전체 정확도 : ',np.mean(accuracy))
회귀에서는 Stratified KFold가 지원되지 않습니다.
회귀는 discrete한 값이 아닌 continuous한 값을 예측하기 때문에 결정값별로 분포를 정하는 의미가 없습니다.
cross_val_score는 위 코드 for문안의 일련의 과정들을 한꺼번에 수행해주는 API입니다.
cross_val_score(estimator, x, y, scoring, cv)의 인자가 있습니다.
밑의 코드는 cross_val_score를 이용해서 앞의 코드들과는 더 단순한 형태를 보여줍니다.
scoring파라미터로 지정된 성능 지표를 배열 형태로 반환하는것이 개인적으로 중요하다 생각합니다.
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
iris = load_iris()
iris_data = iris.data
iris_target = iris.target
dt_clf = DecisionTreeClassifier(random_state = 100)
scores = cross_val_score(dt_clf, iris_data, iris_target, scoring = 'accuracy', cv = 3)
for i in range(len(scores)):
print(f'교차검증별 정확도{i + 1} : {scores[i]}')
print('전체 정확도 : ',np.mean(scores))
cross_val_score는 학습, 예측, 평가를 시켜줌으로써 간단하게 교차검증을 수행할 수 있습니다.
ML알고리즘은 데이터에 기반하기 때문에 어떤 데이터를 입력으로 가지느냐에 따라 결과가 크게 달라질 수 있습니다. 그러기 대문에 데이터 전처리과정이 매우 중요합니다.
데이터프레임에서 NaN,Null 등의 결손값들은 허용되지 않기 때문에 다른 값으로 변환해야 합니다.
만약, 결손값이 적다면 해당 열의 평균값으로 채워주는 것이 일반적이지만 결손값이 많다면 결손값을 drop해주는 것이 좋습니다.
그렇지만, 결손값들의 변환으로 인해 모델의 예측 왜곡이 심할 수 있기 때문에 업무로직 등을 상세히 검토해 더 정밀한 대체 값을 선정해야 합니다.
사이킷런의 ML알고리즘은 문자열 값을 입력값으로 허용하지 않기 때문에 모든 문자열 값은 인코딩돼서 숫자 형으로 변환해야 합니다.
데이터 인코딩은 문자열 값을 숫자 형으로 변환하는 것이고, 레이블 인코딩과 원-핫 인코딩이 있습니다.
레이블 인코딩은 Category Feature를 코드형 숫자 값으로 변환하는 것입니다.
다음 코드는 LabelEncoder 클래스를 이용해서 인코딩과 디코딩을 한 코드입니다.
from sklearn.preprocessing import LabelEncoder
clothes = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
encoder = LabelEncoder()
encoder.fit(clothes)
labels = encoder.transform(clothes)
print('인코딩 속성값 : ', encoder.classes_) # LabelEncoder.classes_로 속성 값을 확인할 수 있다.
print('디코딩 값 : ',encoder.inverse_transform([0,1,2,3,4,5,6,7,8,9])) # 디코딩으로 인코딩 결과가 제대로 일어날 것을 볼수있다.
레이블 인코딩은 문자열 값을 단순히 숫자형 카테고리로 변환할 수 있어서 편리합니다.
그러나, 일부 ML알고리즘에서는 숫자형으로 변환했기 때문에 가중치가 더 부여되거나 더 중요하게 생각할 수 있기 때문에 예측성능이 떨어질 수 있습니다.
그렇기 때문에 선형회귀와 같은 알고리즘에서는 적용하면 안되고 트리 알고리즘은 적용해도 문제가 없습니다.
이러한 문제점을 보안하기위해 나온 것이 원-핫 인코딩입니다.
원-핫 인코딩은 피처 값의 유형에 따라 새로운 피처를 추가해 고유 값에 해당하는 칼럼에만 1을 표시하고 나머지는 0을 표시하는 방법입니다.
그리고 원-핫 인코딩은 문자열에서 바로 변환은 되지 않기 때문에 레이블 인코딩을 통해 숫자로 변환 후 사용이 가능합니다.(사이킷런에서는 OneHotEncoder객체에서 모든 과정이 포함되어 있다.)
밑의 코드는 OneHotEncoder클래스를 이용한 코드입니다.
주의해야할 점은 희소행렬 개념입니다.
from sklearn.preprocessing import OneHotEncoder
import pandas as pd
import numpy as np
clothes = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
clothes = np.array(clothes).reshape(-1,1) # OneHotEncoder객체는 입력값으로 2D 배열이 필요합니다.
encoder = OneHotEncoder()
encoder.fit(clothes)
labels = encoder.transform(clothes)
print('원-핫 인코딩 결과\n', labels.toarray()) # labels값이 희소행렬이기 때문에 toarray()를 통해서 밀집행렬로 변환한다.
희소행렬(Sparse Matrix) / 밀집행렬(Dense Matrix)
희소행렬은 대부분의 항이 0으로 채워져 있는 행렬을 의미합니다.
연산이 간단하다는 장점이 있지만 메모리가 낭비가 되는 단점이 있습니다.
메모리 낭비가 되는것을 방지하기 위해 다음과 같이 표현합니다.
밀집행렬은 차원/전체 공간에 비해 데이터가 있는 공간이 빽빽하게 차 있는 데이터를 의미합니다.메모리 낭비가 적어지지만, 각종 행렬 연산이 복잡해지는 단점이 생깁니다. toarray()는 희소행렬에서 밀집행렬로 변환해줍니다. 즉, 그림과 같은 행,열,값을 참조하여 원래의 행렬(밀집행렬)로 변환해주는 역할을 해줍니다.
get_dummies 원-핫 인코딩을 해주는 pandas 메서드입니다.
입력값으로는 데이터프레임과 시리즈 자료형을 받습니다.
아래는 get_dummies를 이용한 코드입니다.
import pandas as pd
df = pd.DataFrame({'clothes' : ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']})
pd.get_dummies(df)
피처스케일링이란 서로 다른 변수의 값 범위를 일정한 수준으로 맞추눈 작업이라고 합니다.
대표적으로 표준화와 정규화가 있습니다.
표준화는 데이터의 피처가 평균이 0이고 분산이 1인 가우시안 정규 분포를 따릅니다.
이러한 표준화 식은 이상치에 영향을 적게 받을 수 있습니다.
위의 식은 Z-Score 이라고 부르기도합니다.
정규화는 서로 다른 피처의 크기를 통일하기 위해 크기를 변환해주는 개념입니다.
이 정규화 식은 최소값 : 0, 최대값 : 1이 나오는 피처스케일링입니다.
정규화를 통해 모든 특성이 동일한 스케일로 비교되며, 특히 신경망과 같은 알고리즘에서 유용합니다.
사이킷런 정규화 모듈의 식은 다음과 같습니다.
한개의 벡터가 있을때 1개의 벡터에서 개별 element값을 정규화 시켜준다고 생각하시면 됩니다.
단위벡터를 만들어준다고 생각하시면 됩니다.
StandardScaler는 표준화를 시켜주는 클래스입니다.
서포트 벡터 머신, 선형 회귀, 로지스틱 회귀들은 가우시안 분포를 가지고 있다고 가정하고 구현되어서 사전에 표준화를 적용하는것은 예측 성능 향상에 도움을 준다고 합니다.
다음 아래 코드는 StandardScaler를 이용한 코드입니다.
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_iris
iris = load_iris()
iris_data = iris.data
iris_target = iris.target
df = pd.DataFrame(data = iris_data, columns = iris.feature_names)
scaler = StandardScaler()
scaler.fit(df)
iris_scaled = scaler.transform(df)
scaledDf = pd.DataFrame(data = iris_scaled, columns = iris.feature_names)
print('평균값 : ',scaledDf.mean())
print('분산값 : ',scaledDf.var())
평균값과 분산값이 정확히 0과 1이 나오지 않는 이유는 데이터양이 적기 때문입니다.
데이터양이 더 많아진다면 0과 1에 가까워집니다.
MinMaxScaler는 정규화를 시켜주는 클래스입니다.
데이터값이 0~1으로 변환시켜주는데, 만약 음수값이 존재한다면 -1~1값으로 변환합니다.
밑의 코드는 MinMaxScaler를 이용한 코드입니다.
from sklearn.preprocessing import MinMaxScaler
from sklearn.datasets import load_iris
iris = load_iris()
iris_data = iris.data
scaler = MinMaxScaler()
scaler.fit(iris_data)
scaled_data = scaler.transform(iris_data)
df = pd.DataFrame(data = scaled_data, columns = iris.feature_names)
print('최대값\n',df.max())
print('\n최소값\n',df.min())
학습/테스트 데이터가 있을때 fit()메서드는 학습 데이터로 한번만 사용합니다.
테스트 데이터까지 fit()메서드를 이용하면 테스트 데이터 기준으로 transform을 사용하기 때문에 학습/데스트 데이터의 전처리 기준이 달라질 수 있습니다.
그렇기 때문에, 데이터를 학습/테스트 데이터로 분리하기전에 데이터의 전처리를 수행한 후에 학습/테스트 데이터로 나누는것이 가장 적합한 방법입니다.
읽어주셔서 감사함다 :>