4주차 | 머신러닝(Machine learning)(1)
KT AIVLE SCHOOL 5기 4주차에 진행한 머신러닝(Machine learning) 강의 내용 정리 글입니다.
전처리(리뷰)
def preprocess():
# NaN 열이 포함된 모든 변수(axis=1) 제거
titanic.dropna(axis=1, inplace=True)
# NaN을 평균값으로 채우기
titanic['Age'].fillna(mean_age, inplace=True)
# NaN 값을 가장 빈도가 높은 값으로 채우기
titanic['Embarked'].fillna(titanic['Embarked'].mode()[0], inplace=True)
# Ozone 변수 NaN 값을 바로 앞의 값으로 채우기
air['Ozone'].fillna(method='ffill', inplace=True)
# Solar.R 변수 NaN 값을 바로 뒤의 값으로 채우기
air['Solar.R'].fillna(method='bfill', inplace=True)
# 선형 보간법으로 채우기
air['Ozone'].interpolate(method='linear', inplace=True)
# 가변수화 - drop_first = True 로 다중공선성 문제 제거 가능
titanic = pd.get_dummies(titanic, columns=dumm_cols, drop_first=True, dtype = int)
머신러닝 소개
(1) 머신러닝에 대한 이해
인간의 경험 = 머신의 데이터
- 적당량의 학습 데이터를 주는 것이 중요
- 학습 방법에 따른 분류
- 지도 학습 : 데이터의 패턴을 배우게 하는 학습 방법
- 비지도 학습 : 정답이 없는 데이터 만으로 배우게 하는 학습 방법
- 강화 학습 : 선택한 결과에 대해 보상을 받아 행동을 개선하면서 배우게 하는 학습 방법
- 과제에 따른 분류
- 분류 문제 : 이미 적절히 분류된 데이터를 통해 규칙을 찾아 그 규칙을 기반으로 새롭게 주어진 데이터를 적절히 분류하는 것이 목적 (지도 학습)
- 회귀 문제 : 이미 결과 값이 있는 데이터를 통해 입력값과 출력값의 연관성을 찾아 그 연관성을 기반으로 새롭게 주어진 데이터에 대한 값을 예측하는 것이 목적 (지도 학습)
- 클러스터링 : 주어진 데이터를 학습하여 적절한 분류 규칙을 찾아 데이터를 분류함을 목적으로 함, 정답이 없으니 성능을 평가하기 어려움 (비지도 학습)
(2) 분류와 회귀
- 모델링을 하기 전에 원하는 결과가 분류인지 회귀인지를 명확히 이해해야 함
- 회귀는 연속적인 숫자를 예측하는 것이고, 분류는 범줏 값을 예측하는 것
- 연속적인 숫자 확인 방법
- 두 값 사이에 중간값이 의미가 있는 숫자인지
- 또는 두 값에 대한 연산 결과가 의미가 있는 숫자인지 등
- 예측해야 할 값에 연속성이 있는지 확인하면 분류와 회귀를 쉽게 구분할 수 있음
- 분류: A일까? B일까?, 회귀: 얼마나 많이?
- 분류와 회귀는 서로 다른 함수를 사용해 모델링을 하게 됨 (중요)
- 문제 유형을 정확히 파악 → 알고리즘과 평가 방법을 선택 → 관련된 함수를 사용해 모델링
(3) 미리 알아둘 용어
- 모델, 모델링
- 모델 : 데이터로부터 패턴을 찾아 수식으로 정리해 놓은 것
- 모델링 : 오차가 적고 성능이 좋은 모델을 만드는 과정
- 모델의 목적
- 샘플을 가지고 전체를 추정
- 행, 열
- 행 : 개체, 관측치, 기록, 사례, 경우
- 열 : 특성, 속성, 변수, 필드
- 독립변수, 종속변수
- 원인과 결과 (x, y)
- 오차
- 평균과 오차
- 통계학에서 사용되는 가장 단순한 모델 중 하나 : 평균
- 관측값(=실젯값)과 모델 예측값의 차이: 이탈도(Deviance) → 오차
- 평균과 오차
- 데이터 분리
- 실전 : 학습용, 검증용, 평가용 데이터로 분리
- 평가용 : 별도 제공 데이터일 경우 많음
- 검증용 : 평가 전에 모델 성능 검증 가능 (튜닝 시 사용)
- 수업 : 편의상 모델 생성 후 평가용으로 바로 평가
- 실전 : 학습용, 검증용, 평가용 데이터로 분리
- 과대적합 vs 과소적합
- 과대적합
- 학습 데이터에 대해서는 성능이 매우 좋은데, 평가 데이터에서 대해서는 성능이 매우 좋지 않은 경우
- 학습 데이터에 대해서 만 잘 맞는 모델 → 실전에서 예측 성능이 좋지 않음
- 과소적합
- 학습 데이터보다 평가 데이터에 대한 성능이 매우 좋거나, 모든 데이터에 대한 성능이 매우 안 좋은 경우
- 모델이 너무 단순하여 학습 데이터에 대해 적절히 훈련되지 않은 경우
- 과대적합
(4) 데이터 준비 과정
- 머신러닝은 데이터에서 규칙을 찾고 그 규칙에 기반해 예측 하는 것 → 데이터 준비 과정이 필요!!!
- 충분히 학습하고 평가할 수 있는 좋은 데이터가 준비 되어야 좋은 모델을 기대할 수 있음
- 대상 데이터를 불러와 충분히 탐색하고 적절히 전처리 한 후 데이터 분리 과정을 수행
(5) 실습
# 상관관계 시각화
sns.heatmap(data.corr(), annot = True, cmap = 'Blues', cbar = False,
square = True, fmt = '.2f', annot_kws = {'size' : 9})
plt.show()
# 예측값과 실젯값 시각화 비교
medv_mean = y_train.mean()
print(f'평균 : {medv_mean}')
plt.plot(y_test.values, label = 'Actual')
plt.plot(y_pred, label = 'Predicted')
plt.legend()
plt.axhline(medv_mean, color = 'r')
plt.show()
# GPA --> ADMIT (수치형 변수에 따른 범주형 변수(target)의 분포 확인)
sns.histplot(x = data['GPA'], hue = data['ADMIT'], bins = 30)
plt.show()
# 7:3으로 분리
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3,
random_state = 1, shuffle = True, stratify = y)
# 시계열 데이터에서는 shuffle 금지(default: True), stratify : 라벨 별로 균등하게 분포되도록 해주는 옵션
성능 평가
회귀 모델 성능 평가
예측 값이 실제 값에 가까울 수록 좋은 모델 → 오차로 모델 성능 평가
(1) 용어 정리
\(\Large y\) : 실제값 → 실제 예측하고자 하는 값, 오차 : 이 값과 예측값의 차이
\(\Large \bar{y}\) : 평균값 → 이미 존해하는 평균으로 예측한 값
\(\Large \hat{y}\) : 예측값 → 새롭게 모델로 예측한 값, 평균값보다 얼마나 잘 예측했는지 확인
(2) 회귀 평가 지표 정리
\(\Large MSE = \frac{\sum_{i=1}^{n} (y_i - \hat{y}_i)^2}{n}\)
\(\Large RMSE = \sqrt{MSE} = \sqrt{\frac{\sum_{i=1}^{n} (y_i - \hat{y}_i)^2}{n}}\)
\(\Large MAE = \frac{\sum_{i=1}^{n} |y_i - \hat{y}_i|}{n}\)
\(\Large MAPE = \frac{\sum_{i=1}^{n} \left|\frac{y_i - \hat{y}_i}{y_i}\right|}{n}\)위 값 모두 작을 수록 모델 성능이 좋다
(3) 오차를 보는 다양한 관점
\(\Large SST = \sum_{i=1}^{n} (y_i - \bar{y})^2\) : 전체 오차
\(\Large SSE = \sum_{i=1}^{n} (y_i - \hat{y}_i)^2\) : 회귀식이 잡아내지 못한 오차
\(\Large SSR = \sum_{i=1}^{n} (\hat{y}_i - \bar{y})^2\) : 회귀식이 잡아낸 오차
\(\Large SST = SSR + SSE\)
(4) 결정 계수 (R-Squared)
- 전체 오차 중에서 회귀식이 잡아낸 오차 비율
- 모델의 설명력이라고도 부름 (값이 클수록 좋음)
\[\Large R^{2} = \frac{SSR}{SST} = 1 - \frac{SSE}{SST} = 1-\frac{\sum_{i=1}^{n}(y_{i}-\hat{y}_{i})^2}{\sum_{i=1}^{n}(y_{i}-\bar{y}_{i})^2}\]
(5) 실습
# 1. 데이터 준비
target = 'medv' ## target 확인
X = data.drop(target, axis = 1) ## 데이터 분리
y = data.loc[:, target]
## 7:3으로 분리
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 1)
# 2. 모델링
model = LinearRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
# 3. 회귀 성능 평가
print(f'MAE : {mean_absolute_error(y_test, y_pred)}')
print(f'MSE : {mean_squared_error(y_test, y_pred)}')
print(f'RMSE : {mean_squared_error(y_test, y_pred, squared = False)}')
print(f'MAPE : {mean_absolute_percentage_error(y_test, y_pred)}')
print(f'R2 : {r2_score(y_test, y_pred)}')
분류 모델 성능 평가
- 분류 모델은 0인지 1인지 예측하는 것
- 예측 값이 실제 값과 일치하는 값이 많을수록 좋은 모델 → 정확히 예측한 비율로 모델 성능 평가
(1) Confusion Matrix(혼동 행렬)
예측Negative(0) | 예측Positive(1) | |
---|---|---|
실제Negative(0) | \(TN\) | \(FP\) |
실제Positive(1) | \(FN\) | \(TP\) |
- 용어 : 결과(T / F) + 예측 (P / N), 행렬의 역슬래시 값은 항상 정답
- 정확도(Accuracy) = \(\Large \frac{TN + TP}{TN + FP + FN + TP}\)
- 정분류율
- 가장 직관적으로 모델 성능 확인 가능한 평가지표
- 정밀도(Precision) = \(\Large \frac{TP}{FP + TP}\)
- 예측 관점
- 정밀도가 낮을 경우 발생하는 문제
- 암이 아닌데 암이라 하여 불필요한 치료 발생
- 재현율(Recall) = \(\Large \frac{TP}{FN + TP}\)
- 실제 관점
- 민감도(Sensitivity)라고도 부름
- 재현율이 낮을 경우 발생하는 문제
- 암인 사람에게 암이 아니라고 하는 경우
- 정밀도와 재현율은 기본적으로 Positive에 대해서 이야기
- Negative에 대한 정밀도와 재현율도 의미를 가짐
- 특이도(Specificity) = \(\Large \frac{TN}{FP + TN}\)
- 실제 Negative 중에서 Negative로 예측한 비율
- 특이도가 낮을 경우 발생하는 문제
- 암이 아닌데 암이라 했으니 불필요한 치료가 발생
(2) F1-Score
- 정밀도와 재현율의 조화평균
- 관점이 다른 경우 조화평균이 큰 의미를 가짐
- 정밀도와 재현율이 적절하게 요구될 때 사용
- F1-Score = \(\Large \frac{2 \times Precision \times Recall}{Precision + Recall}\)
(3) 실습
# 1. 데이터 준비
target = 'ADMIT' # target 확인
x = data.drop(target, axis=1) # 데이터 분리
y = data.loc[:, target]
# 2. 모델링
model = KNeighborsClassifier()
model.fit(x_train, y_train)
y_pred = model.predict(x_test)
# 3.분류 성능 평가
print('Confusion Matrix\n', confusion_matrix(y_test, y_pred), '\n') # 성능 평가
## 혼동 행렬 시각화
plt.figure(figsize = (5, 3))
sns.heatmap(confusion_matrix(y_test, y_pred), annot = True, cmap = 'Blues', cbar = False)
plt.show()
print(f'Accuracy : {accuracy_score(y_test, y_pred)}')
## 참고 : 두 수의 차이로 과대적합, 과소적합을 알 수 있음 (약간 과대적합)
print(f'평가 성능(정확도) : {model.score(x_test, y_test)}')
print(f'학습 성능(정확도) : {model.score(x_train, y_train)}\n')
print('Precision : ', precision_score(y_test, y_pred)) # default : 1에 대한 정밀도
print('Precision : ', precision_score(y_test, y_pred, average = 'binary')) # default
print('Precision : ', precision_score(y_test, y_pred, average = None)) # 둘다 출력
print('Precision : ', precision_score(y_test, y_pred, average = 'macro')) # 평균
print('Precision : ', precision_score(y_test, y_pred, average = 'weighted'), '\n') # 가중치
print('Recall : ', recall_score(y_test, y_pred))
print('Recall : ', recall_score(y_test, y_pred, average = None), '\n') # 둘다 출력 기억하기
print('F1-Score : ', f1_score(y_test, y_pred))
print('F1-Score : ', f1_score(y_test, y_pred, average = None), '\n') # 둘다 출력 기억하기
print('Classification_report \n\n', classification_report(y_test, y_pred))
기본 알고리즘
Linear Regression
(1) 정의 및 특징
- 최선의 회귀 모델 : 오차 합이 최소가 되는 모델(오차 합이 최소가 되는 가중치, 편향을 찾기)
단순 회귀 : 독립 변수 하나가 종속 변수에 영향을 미치는 선형 회귀
# 회귀계수 확인 print(model.coef_) print(model.intercept_)
다중 회귀 : 여러 독립 변수가 종속 변수에 영향을 미치는 선형 회귀
# 회귀계수 확인 print(list(x_train)) print(model.coef_) print(model.intercept_)
(2) 실습
# 1. 데이터 준비
target = 'dist' # target 확인
x = data.drop(target, axis=1) # 데이터 분리
y = data.loc[:, target]
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=1)
# 2. 모델링
model = LinearRegression()
model.fit(x_train, y_train)
y_pred = model.predict(x_test)
print('MAE : ', mean_absolute_error(y_test, y_pred))
print('R2 : ', r2_score(y_test, y_pred))
# 회귀계수 확인
print(model.coef_)
print(model.intercept_)
# 3. 기타
# 회귀식 만들기
a = model.coef_
b = model.intercept_
speed = np.linspace(x_test.min(), x_test.max(), 10)
dist = a * speed + b
# 회귀선 표시
dist_mean = y_train.mean()
plt.scatter(x_test, y_test) # 평가 데이터
plt.scatter(x_test, y_pred) # 학습 데이터
plt.plot(speed, dist, color = 'r') # 학습 데이터에 대한 예측값
plt.axhline(dist_mean, color = 'r', linestyle = '--')
plt.title('Speed & Distance', size = 20, pad = 10)
plt.xlabel('Speed(mph)')
plt.ylabel('Dist(ft)')
plt.show()
# 시각화
plt.plot(y_test.values, label='Actual')
plt.plot(y_pred, label='Predicted')
plt.legend()
plt.ylabel('Dist(ft)')
plt.show()
K-Nearest Neighbor
(1) 정의 및 특징
- k개의 최근접 이웃의 값을 찾아 그 값들로 새로운 값을 예측하는 알고리즘
- 회귀와 분류에 사용되는 매우 간단한 지도 학습 알고리즘 → 연산 속도는 느림
- k개 값의 평균으로 예측, 가장 많이 포함된 유형으로 분류
- k값의 중요성
- 적절한 k값을 찾는 것이 중요(기본값 = 5)
- 일반적으로 1이 아닌 홀수로 설정
- 거리 구하기
- 맨하튼 거리(두 지점의 각 좌표의 차의 절대값) ≥ 유클리드 거리(두 지점의 거리)
- Scaling 필요성
- 스케일링 여부에 따라 KNN 모델 성능이 달라질 수 있음
- 대표적인 스케일링: 정규화(Normalization), 표준화(Standardization)
평가용 데이터에도 학습용 데이터 기준으로 스케일링 수행
[참고] 학습 데이터를 기준으로 정규화
(2) 실습
# 1. 데이터 준비
data.interpolate(method='linear', inplace=True) # 결측치 채우기
# 변수 제거
drop_cols = ['Month', 'Day']
data.drop(drop_cols, axis=1, inplace=True)
target = 'Ozone' # target 확인
# 데이터 분리
x = data.drop(target, axis=1)
y = data.loc[:, target]
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=1)
# 정규화
scaler = MinMaxScaler()
x_train = scaler.fit_transform(x_train)
x_test = scaler.transform(x_test)
# 2. 모델링
model = KNeighborsRegressor() # n_neighbors를 데이터의 수로 하면 모델이 평균값이 된다
model.fit(x_train, y_train)
y_pred = model.predict(x_test)
print('MAE : ', mean_absolute_error(y_test, y_pred))
print('R2 : ', r2_score(y_test, y_pred))
# 3. 기타
# 예측값, 실젯값 시각화
plt.plot(y_test.values, label='Actual')
plt.plot(y_pred, label='Predicted')
plt.legend()
plt.ylabel('Ozone')
plt.show()
Decision Tree
(1) 정의 및 특징
- 특정 변수에 대한 의사결정 규칙을 나무 가지가 뻗는 형태로 분류해 나감
- 분류와 회귀 모두에 사용되는 지도학습 알고리즘
- 분석 과정을 실제로 확인 가능 → 화이트 박스 모델
- 의미 있는 질문을 먼저 하는 것이 중요
- 과적합 발생하기 쉬움
- 트리 깊이를 제한하는 튜닝이 필요
- Root Node(뿌리 마디), Terminal Node(끝 마디, Leaf Node), Depth(깊이)
- 분류와 회귀
- 비용함수: 분류 → 불순도, 회귀 → MSE
- 분류: 마지막 노드에 있는 샘플들의 최빈값을 예측값으로 반환
- 회귀: 마지막 노드에 있는 샘플들의 평균을 예측값으로 반환
(2) 불순도(Impurity)
- 불순도가 낮을 수록 분류가 잘 된 것
- 불순도 수치화 가능한 지표
- 지니 불순도(Gini Impurity)
\[- (양성 클래스 비율^2 + 음성 클래스 비율^2)\]
- 분류 후 얼마나 잘 분류했는지 평가하는 지표
- 특징
- 지니 불순도가 낮을수록 순도가 높음
- 0 ~ 0.5 사이의 값(이진 분류의 경우) → 순수하게 분류: 0, 완벽하게 섞이면: 0.5
- 지니 불순도가 낮은 속성으로 의사결정 트리 노드 결정
- 엔트로피(Entropy)
\[- 음성클래스비율 \times log_2(음성 클래스 비율) - 양성클래스비율 \times log_2(양성 클래스 비율)\]
- \(p_i\) : 집합 안에서 속성 i의 확률
- 0 ~ 1사이의 값 → 순수하게 분류되면: 0, 완벽하게 섞이면: 1
- 정보 이득(Information Gain)
\[𝐺𝑎𝑖𝑛 (𝑇, 𝑋) = 𝐸𝑛𝑡𝑟𝑜𝑝𝑦 (𝑇) − 𝐸𝑛𝑡𝑟𝑜𝑝𝑦(𝑇, 𝑋)\]
- 정보 이득이 크다 = 어떤 속성으로 분할할 때 불순도가 줄어든다
- 정보 이득이 가장 큰 속성부터 분할
- 지니 불순도(Gini Impurity)
(3) 가지치기
- 가지치기를 하지 않으면 → 과대적합, 일반화되지 못함
- 여러 하이퍼파라미터 값을 조정해 가지치기 할 수 있음
- max_depth(트리의 최대 깊이(기본값: None))
- min_samples_leaf(노드를 분할하기 위한 최소한의 샘플 개수(기본값: 2))
- min_samples_split(리프 노드가 되기 위한 최소한의 샘플 수(기본값: 1))
- 가장 적절한 하이퍼파라미터 값 찾도록 노력해야 함
- Decision Tree 분류 모델도 결국 확률에 근거해 예측을 한다
(4) 실습
# 1. 데이터 준비
# 제거 대상: PassengerId, Name, Ticket, Cabin
drop_cols = ['PassengerId', 'Name', 'Ticket', 'Cabin']
# 변수 제거
data.drop(drop_cols, axis=1, inplace=True)
# Age 결측치를 중앙값으로 채우기
age_median = data['Age'].median()
data['Age'].fillna(age_median, inplace=True)
# Embarked 최빈값으로 채우기
emb_freq = data['Embarked'].mode()[0]
data['Embarked'].fillna(emb_freq, inplace=True)
# target 확인
target = 'Survived'
# 데이터 분리
x = data.drop(target, axis=1)
y = data.loc[:, target]
# 가변수화 대상: Pclass, Sex, Embarked
dumm_cols = ['Pclass', 'Sex', 'Embarked']
# 가변수화
x = pd.get_dummies(x, columns=dumm_cols, drop_first=True, dtype=int)
# 7:3으로 분리
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=1)
# 2. 모델링
model = DecisionTreeClassifier(max_depth = 5, random_state=1)
model.fit(x_train, y_train)
y_pred = model.predict(x_test)
print(confusion_matrix(y_test, y_pred), '\n')
print(classification_report(y_test, y_pred))
# 3. 기타
# 이미지 파일 만들기
export_graphviz(model, # 모델 이름
out_file='tree.dot', # 파일 이름
feature_names=x.columns, # Feature 이름
class_names=['die', 'survived'], # Target Class 이름
rounded=True, # 둥근 테두리
precision=2, # 불순도 소숫점 자리수
# max_depth = 3, # 표시할 트리 깊이
filled=True) # 박스 내부 채우기
# 파일 변환
!dot tree.dot -Tpng -otree.png -Gdpi=300
# 이미지 파일 표시
Image(filename='tree.png')
# 변수 중요도 데이터프레임 만들기
df = pd.DataFrame()
df['feature'], df['importance'] = list(x), model.feature_importances_
df.sort_values(by='importance', ascending=True, inplace=True)
# 시각화
plt.figure(figsize=(5, 5))
plt.barh(df['feature'], df['importance']) # 오름차순으로 정렬해야 내림차순으로 보여진다
plt.show()
Logistic Regression
(1) 정의와 특징
- 로지스틱 회귀: 확률 문제를 선형회귀로 모델링
- 로지스틱 함수
- 시그모이드 함수 : \(\Large p = \frac {1} {1 + e^{-f(x)}}\)
- (-∞, ∞) 범위를 갖는 선형 판별식 결과로 (0, 1) 범위의 확률 값을 얻게 됨
- 학습 데이터를 잘 설명하는 선형 판별식의 기울기(𝑎)와 절편(𝑏)을 찾는 문제
(2) 실습
# 1.데이터 준비
target = 'Outcome' # target 확인
# 데이터 분리
x = data.drop(target, axis=1)
y = data.loc[:, target]
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=1)
# 2. 모델링
model = LogisticRegression()
model.fit(x_train, y_train)
y_pred = model.predict(x_test)
print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred))
# 3. 기타
# 예측값 확인
print(y_test.values[10:30])
print(y_pred[10:30])
# 확률값 확인
p = model.predict_proba(x_test)
print(p[10:30])
# 1의 확률값 얻기
p1 = p[:, [1]]
# 임계값 0.5
y_pred2 = np.array([1 if x > 0.5 else 0 for x in p1])
print(y_pred2[:20])
print(classification_report(y_test, y_pred2))
# 임계값 0.45
y_pred2 = np.array([1 if x > 0.45 else 0 for x in p1])
print(y_pred2[:20])
print(classification_report(y_test, y_pred2))