3. 탐색한 데이터로 모델성능 개선1
3.1.1 연속 수치 데이터를 범주형 변수로 변경하기
- Overfitting: Train set에 대해선 Good, Test set에 대해선 bad
- Underfitting: Train/Test 둘 다 bad -> 그냥 학습 자체가 제대로 안됨
- 둘 다 일반화 성능 자체는 Bad. 결국 일반화가 중요한 것이므로 Under와 Over의 경계에서 optimize된 지점을 찾아야 함.
- sample의 수가 적거나, 모델이 복잡하거나, 학습에 Target이 포함되었거나 등의 이유로 Overfitting이 발생.
- 모델이 너무 간단하거나, 데이터의 수가 적거나 등의 이유로 Underfitting 발생
datasets
df = pd.read_csv('/content/drive/MyDrive/edwith/프로젝트로 배우는 데이터 사이언스/diabetes.csv')
df['Pregnancies_high'] = df['Pregnancies'] > 6
df[['Pregnancies', 'Pregnancies_high']].head()
train = df[:int(df.shape[0] * 0.8)]
test = df[int(df.shape[0] * 0.8):]
- 이전의 분석에서 임신 횟수가 7회가 넘어가면 발병이 더 높다는 사실을 발견했었고 이를 범주화
feature_names = train.columns.tolist()
feature_names.remove('Pregnancies')
feature_names.remove('Outcome')
feature_names
- remove 매서드는 리스트에 적용되는 매서드인데 입력한 원소에 대해 있으면 삭제를 해줌.
- 존재하지 않는다면 에러 발생. 또한 중복된 값이 있다면 순서상으로 가장 빠른 원소를 제거
label_name = 'Outcome'
X_train = train[feature_names]
y_train = train[label_name]
print(X_train.shape)
X_train.head()
- X_train은 8개의 컬럼을 가지고 614개의 관측치를 가짐. 원래 데이터 셋의 80%만큼만 할당했기 때문
모델 생성, 학습, 변수 중요도
model = DecisionTreeClassifier()
model.fit(X_train, y_train)
plt.figure(figsize = (10, 8))
sns.barplot(x = model.feature_importances_, y = feature_names)
plt.show()
- 이전과는 다르게 Pregnancies의 중요도가 높지 않음
### 실제값 - 예측값을 빼주면 제대로 예측했다면 0, 틀리다면 -1
### 절대값을 취해 틀린 개수를 계산 후 Accuracy 계산
X_test = test[feature_names]
y_test = test[label_name]
y_predict = model.predict(X_test)
diff_count = abs(y_test - y_predict).sum()
print('틀린 개수는:', diff_count)
print('Accuracy:', (len(y_test) - diff_count) /len(y_test))
3.1.2 범주형 변수를 수치형 변수로 변환하기
원핫인코딩(One-hot-encoding)
- 범주형 변수의 범주들을 컬럼으로 변환하고 해당되면 1, 해당되지 않으면 0으로. dummy화 한다고 생각
정수인코딩(Label-encoding)
- 범주가 문자열로 되어있는 변수를 단순 명목형 수치로 변환해 줌
- ex) ['남', '여', '여', '여'] -> [0, 1, 1, 1]
Age < 25, 25 <= Age < 60, Age > 60 총 세개의 구간으로 나눠줌
train['Age_low'] = train['Age'] < 25
train['Age_middle'] = (train['Age'] >= 25) & (df['Age'] <= 60)
train['Age_high'] = train['Age'] > 60
train[['Age', 'Age_low', 'Age_middle', 'Age_high']].head()
- 총 세개의 파생변수가 생성되었고 One-hot 완성. 어차피 True는 1, False는 0
- test에도 똑같이 원핫 인코딩
test['Age_low'] = test['Age'] < 25
test['Age_middle'] = (test['Age'] >= 25) & (test['Age'] <= 60)
test['Age_high'] = test['Age'] > 60
feature_names += ['Age_low', 'Age_middle', 'Age_high']
feature_names.remove('Age')
model.fit(train[feature_names], y_train)
y_predict = model.predict(X_test)
diff_count = abs(y_test - y_predict).sum()
print('틀린 개수는:', diff_count)
print('Accuracy:', (len(y_test) - diff_count) /len(y_test))
plt.figure(figsize = (10, 8))
sns.barplot(y = feature_names, x = model.feature_importances_)
plt.show()
- 글루코스가 피쳐 중 가장 큰 역할을 하는건 변함이 없고, 파생된 변수들도 별다른 영향은 딱히..
- Acc는 68%.
범주의 구간을 조금 다르게 수정
## train one-hot
train['Age_low'] = train['Age'] < 30
train['Age_middle'] = (train['Age'] >= 30) & (df['Age'] <= 60)
train['Age_high'] = train['Age'] > 60
## test one-hot
test['Age_low'] = test['Age'] < 30
test['Age_middle'] = (test['Age'] >= 30) & (test['Age'] <= 60)
test['Age_high'] = test['Age'] > 60
## model fit
model.fit(train[feature_names], y_train)
y_predict = model.predict(test[feature_names])
diff_count = abs(y_test - y_predict).sum()
print('틀린 개수는:', diff_count)
print('Accuracy:', (len(y_test) - diff_count) /len(y_test))
plt.figure(figsize = (10, 8))
sns.barplot(y = feature_names, x = model.feature_importances_)
plt.show()
- 정확도가 조금 줄어들었다.
- 수치형 변수를 세 개의 구간으로 나누고 one-hot을 해줬는데 수치형 구간을 잡아주는 기준에 따라 성능이 좀 달라질 수도 있음 => 그 자체의 영향력을 전체로 묶어주기 때문에
- 카테고리의 수가 적다면 원핫이 별 상관 없을 수 있지만, 만약 10개라고 치면 1인 변수와 10인 변수의 차이는 10배 만큼 차이가 나게 되는 상황. 명목형 변수들의 수치 비교는 불가능함. 따라서 원핫 인코딩을 해주는 것.
3.1.3 결측치 평균으로 대체하기
df.isnull().sum()
- 인슐린의 값이 0인 null로 대체해서 새로운 컬럼을 생성
df['Insulin_nan'] = df['Insulin'].replace(0, np.nan)
df[['Insulin', 'Insulin_nan']].head()
df.isna()['Insulin_nan'].sum()
- 총 374개의 관측치가 null에 해당
df.groupby('Outcome')['Insulin', 'Insulin_nan'].agg(['mean', 'median', 'count'])
- Outcome을 기준으로 그룹바이 하고 인슐린의 0값을 nan으로 처리한 것과 안한 것의 비교
- 평균과 median 값의 차이가 많이 난다.
- 결측치로 처리하니 count가 1/2 가량 줄어들었다. 결측치를 없애고 분석을 한다면 train set이 반으로 줄어들었기 때문에 under or over fitting이 발생할 수 있음
- 데이터 셋이 충분히 많다면 결측치를 그냥 제거해도 되겠지만, 이처럼 부족한 상황에선 무작정 제거할 수는 없음.
- 결측치를 채워주거나 해야함. 제일 좋은건 실제 관측치에 해당되는 값으로 채워주면 좋겠지만 이는 쉽지 않기 때문에 평균 혹은 median 등등 분석자의 판단에 따라 채워줌.
- 해당 도메인에서 사용하는 값들이나.. 아무튼 합리적인 값으로 보통 채워서 학습.
### 결측치 대체(발병 여부별 평균값으로)
df.loc[(df.Outcome == 0) & (df['Insulin_nan'].isnull()), 'Insulin_nan'] = 68.7
df.loc[(df.Outcome == 1) & (df['Insulin_nan'].isnull()), 'Insulin_nan'] = 100.3
### 새롭게 features 만들어 주고 학습
features = df.columns.tolist()
features.remove('Insulin')
features.remove('Age_low')
features.remove('Age_middle')
features.remove('Age_high')
features.remove('Outcome')
train = df[:int(df.shape[0]*0.8)]
test = df[int(df.shape[0]*0.8):]
model.fit(train[features], y_train)
plt.figure(figsize = (10, 8))
sns.barplot(x = model.feature_importances_, y = features)
plt.show()
- Insulin의 null을 평균값으로 대체해준 값이 가장 큰 영향으로 바뀜
y_predict = model.predict(test[features])
diff_count = abs(y_test - y_predict).sum()
print('틀린 개수는:', diff_count)
print('Accuracy:', (len(y_test) - diff_count) /len(y_test))
- 정확도가 70% 아래에 머물다가 결측치를 처리해준 것 만으로 88%까지 상승
- 학습에 포함되지 않았던 Test set으로 성능 평가를 한 것이므로 일반화의 가능성이 상승함
- 결측치를 어떻게 처리할 지, 수치형 변수들을 구간으로 나눌지 등 여러가지를 고려해보며 성능을 향상
- 너무 범주가 많은 변수의 one-hot을 하게되면 features의 수가 확 늘어나기 때문에 모델이 복잡해짐
- 이는 오버피팅을 유발하게 될 수도 있고, 학습하는데 시간이 더 소요되고 무조건 장점은 아님
- 그러므로 항상 Trade-off를 잘 생각해서 모델링을 해야함.
3.1.4 결측치 중앙값으로 대체하기
결측치 대체의 중요성을 느꼈고, 이번엔 median으로 대체
### fill the missing values
df['Insulin_nan'] = df['Insulin'].replace(0, np.nan)
df.loc[(df.Outcome == 0) & (df['Insulin_nan'].isnull()), 'Insulin_nan'] = 102.5
df.loc[(df.Outcome == 1) & (df['Insulin_nan'].isnull()), 'Insulin_nan'] = 169.5
### train / test split
train = df[:int(df.shape[0]*0.8)]
test = df[int(df.shape[0]*0.8):]
model.fit(train[features], y_train)
from sklearn.tree import plot_tree
plt.figure(figsize = (20, 20))
tree = plot_tree(model, feature_names = features, filled = True, fontsize = 10)
plt.figure(figsize = (10, 8))
sns.barplot(x = model.feature_importances_, y = features)
plt.show()
- 인슐린 결측치 대체해준 값이 중요도가 가장 높음
y_predict = model.predict(test[features])
diff_count = abs(y_test - y_predict).sum()
print('틀린 개수는:', diff_count)
print('Accuracy:', (len(y_test) - diff_count) /len(y_test))
- mean보다 median으로 대체해준 경우가 좀 더 성능이 좋아졌다.
- 이처럼 결측치를 채워주는 부분이 정말 중요하고 보통 보수적으로 채워주기 위해 median이나 mean을 많이 넣어준다.
- mean의 장점은 전체 값이 다 영향을 주는 대신, 이상치가 있다면 영향을 크게 받는다.
- median은 순서대로 정렬했을 때 가운데 값을 넣어주므로 이상치에 별 영향을 안 받는다. 하지만 모든 값을 다 활용하는 것이 아니기 때문에 이게 단점이 됨.
Author And Source
이 문제에 관하여(3. 탐색한 데이터로 모델성능 개선1), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@chaong309/3.-탐색한-데이터로-모델성능-개선1저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)