- 로지스틱 회귀로 모델 훈련하고 평가하기
- 훈련 데이터 세트와 테스트 세트로 분류
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
cancer = load_breast_cancer()
x = cancer.data
y = cancer.target
x_train_all, x_test, y_train_all, y_test = train_test_split(x, y, startify=y, test_size=0.2, random_state=43)
- SDGClassifier 클래스를 이용해 모델 훈련
from sklearn.linear_model import SGDClassifier
sgd = SGDClassifier(loss='log', random_state=42)
sgd.fit(x_train_all, y_train_all)
sgd.score(x_test, y_test)
- 약 88%의 정확도를 가지고 있다
- 정확도를 올리기 위해 다른 손실 함수를 이용하여도 된다.
- loss와 같은 매개변수 값은 사용자가 직접 결정하는 것
- loss와 같은 매개변수를 '하이퍼 파라미터' 라 함
- 서포트 벡터 머신으로 모델 훈련하고 평가하기
- SGDClassifier 클래스의 loss 매개변수를 log에서 hinge로 변형 시 선형 서포트 벡터 머신 문제를 푸는 모델 생성
from sklearn.linear_model import SGDClassifier
sgd = SGDClassifier(loss='hinge', random_state=42)
sgd.fit(x_train_all, y_train_all)
sgd.score(x_test, y_test)
- 약 90%의 정확도로 측정되고 있는데 기본 로지스틱 회귀모델보다 성능이 좋아졌다.
- 정확도 같은경우 각자 환경에 따라 변동이 있을 수 있다.
- 지금처럼 'log'를 'hinge'로 변경하는 것처럼 매개변수를 변경하는 행위를 '모델 튜닝' 이라한다.
테스트 세트로 모델 튜닝 시 실전에서 좋은 성능을 기대할 수 없다.
- 테스트 세트 모델 튜닝시 실전에서는 원하는 성능을 기대할 수 없다
- 이유는 테스트 세트 모델을 튜닝하여 정확도를 올린 경우 테스트 세트에 대해서 정확도가 올라간 행위이기 때문이다.
- 이런 행위를 '테스트 세트 정보가 모델에서 새어나갔다'라고 한다.
- 모델의 일반화 성능(genetalization performance)이 왜곡된다.
검증 세트 준비
- 모델을 튜닝하는 행위를 하는 곳은 테스트 세트를 사용하지 않도록 한다.
- 그러나 튜닝에는 성능 점수가 필요하다.
- 테스트 세트는 실전 투입 전 단 한 번만 사용하며, 튜닝용 세트를 따로 준비한다.
- 이를 '검증 세트'라고 한다.
- 검증 세트(vaildation set)는 훈련 세트의 일부를 이용
- 데이터 세트 준비
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
cancer = load_breast_cancer()
x = cancer.data
y = cancer.target
x_train_all, x_test, y_train_all, y_test = train_test_split(x, y, stratify=y, test_size=0.2, random_state=42)
- 검증 세트 분할
- 훈련, 검증, 테스트의 비율이 6 : 2 : 2로 사용하지만 실제로는 훈련, 테스트 8 : 2로 제작 후 훈련을 8 : 2로 재분할
- 훈련 세트를 8 : 2로 나누기(세트 준비에서 이미 훈련과 테스트의 분리가 끝남)
x_train, x_val, y_train, y_val = train_test_split(x_train_all, y_train_all, stratify=y_train_all, test_size=0.2, random_state=42)
print(len(x_train), len(x_val))
- 455개의 훈련 세트가 8 : 2의 비율로 나눠져 훈련 세트(x_train) 364개, 검증 세트(y_train) 91개로 분류 완료
- 검증 세트 사용해 모델 평가
from sklearn.linear_model import SGDClassifier
sgd = SGDClassifier(loss='log', random_state=42)
sgd.fit(x_train_all, y_train_all)
sgd.score(x_test, y_test)
- 이전에 테스트한 것보다 정확도가 감소
- 이유는 훈련 세트의 크기가 줄어듦 -> 검증 세트 생성
데이터 전처리와 특성의 스케일
- 사이킷런과 같은 머신러닝 패키지에 준비되어 있는 데이터는 대부분 실습을 위한 것으로 잘 가공됨
- 실전 데이터는 가공은커녕 누락이나 데이터 형태의 불균형 등의 문제가 발생할 수 있음
- 이를 보완하기 위한 데이터 전처리(data preprocessing) 과정이 필요하다
- 특성의 스케일은 알고리즘에 영향을 준다
- 특성의 스케일(scale)은 어떤 특성이 가지고 있는 값의 범위를 말한다.
당도 | 무게 | |
사과 1 | 4 | 540 |
사과 2 | 8 | 700 |
사과 3 | 2 | 480 |
- 위의 표처럼 형태가 균일하고 누락도 없지만 특성의 스케일(값의 범위)이 다르다
- 당도의 범위가 1 ~ 10이고 무게 범위는 400 ~ 1000
- 위 같은 범위를 '두 특성의 스케일 차이가 크다' 라 한다
- 계속 배우고 있는 경사 하강법은 스케일에 민감한 알고리즘으로 특성의 스케일을 맞추는 전처리 같은 행위를 해야 한다.
- 이를 '스케일의 조정' 이라 한다
- 훈련 데이터를 준비하고 스케일 비교
- 유방암 데이터와 4에서 만든 단일층 신경망 모델을 이용해 스케일을 비교
import matplotlib.pyplot as plt
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
cancer = load_breast_cancer()
x = cancer.data
y = cancer.target
x_train, x_test, y_train, y_test = train_test_split(x, y, stratify=y, test_size=0.2, random_state=42)
print(cancer.feature_names[[2,3]])
plt.boxplot(x_train[:, 2:4])
plt.xlabel('feature')
plt.ylabel('value')
plt.show()
- mean perimeter는 주로 100~200 사이에 값이 있으며
- mean area는 주로 200~2000 사이에 값들이 집중되어 있음을 알 수 있다.
- 가중치를 기록할 변수와 학습률 파라미터 추가 하기
- SingleLayer 클래스에 인스턴스 변수를 추가하여 에포크마다 가중치의 값을 저장하여 가중치의 변화를 관찰
- init() 메서드에서 인스턴스 변수 w_history를 만들고 학습률 파라미터 learning_rate추가
def __init__(self, leaning_rate=0.1) :
self.w = None
self.b = None
self.losses = []
self.w_history = []
self.lr = learning_rate
- learning_rate는 하이퍼 파라미터이며 이 값으로 가중치의 업데이트 양을 조절
- 손실 함수는 복잡한 굴곡을 가진 다차원 공간의 초평면(hyperplane)이다
- 가중치가 큰 폭으로 업데이트될 경우 손실 함수의 최소 지점을 지나칠 경우 최적의 해를 구할 수 없다
- 전역 최솟값을 놓치지 않도록 가중치의 업데이트 양을 조절할 필요가 있다
- 그림과 같이 높은 학습률을 적용할 경우 전역 최솟값을 지나치는 것을 알 수 있다.
- 적절한 학습률을 적용하여 천천히 전역 최솟값을 찾는다
- 가중치 기록 후 업데이트 양 조절
- fit() 메서드에서 가중치가 바뀔 때마다 w_history 리스트에 가중치를 기록
- 넘 파이 배열을 리스트에 추가하면 실제 값이 복사되는 것이 아니라 배열을 참조
- 가중치 변수 self.w의 값이 바뀔 때마다 그 값을 복사하여 w_history 리스트에 추가
- w_grad에 학습률 self.lr을 곱하는 연산 추가
class SingleLayer :
def __init__(self, learning_rate=0.1) :
self.w = None
self.b = None
self.losses = []
self.w_history = []
self.lr = learning_rate
def forpass(self, x) :
z = np.sum(x * self.w) + self.b # 직선 방정식 계산
return z
def backprop(self, x, err) :
w_grad = x * err # 가중치에 대한 그레디언트 계산
b_grad = 1 * err # 절편에 대한 그레디언트 계산
return w_grad, b_grad
def fit(self, x, y, epochs=100) :
self.w = np.ones(x.shape[1]) # 가중치 초기화
self.b = 0 # 절편 포기화
self.w_history.append(self.w.copy()) # 가중치 기록
np.random.seed(42) # 무작위 시드 지정
for i in range(epochs) : # epochs 만큼 반복
loss = 0
# 인덱스 섞기
indexes = np.random.permutation(np.arange(len(x)))
for i in indexes : # 모든 샘플에 대해 반복
z = self.forpass(x[i]) # 정방향 계산
a = self.activation(z) # 활성화 함수 적용
err = -(y[i] - a) # 오차 계산
w_grad, b_grad = self.backprop(x[i], err) # 역방향 계산
self.w -= self.lr * w_grad # 가중치 업데이트(학습률 적용)
self.b -= b_grad # 절편 업데이트
# 가중치를 기록
self.w_history.append(self.w.copy())
# 안전한 로그 계산을 위해 클리핑한 후 손실을 누적
a = np.clip(a, 1e-10, 1-1e-10)
loss += -(y[i]*np.log(a)+(1-y[i])*np.log(1-a))
# 에포크마다 평균 손실 저장
self.losses.append(loss/len(y))
def activation(self, z) :
a = 1 / (1+np.exp(-z)) # 시그모이드 계산
return a
def predict(self, x) :
z = [self.forpass(x_i) for x_i in x] # 정방향 계산
return np.array(z) > 0 # 계단 함수 적용
def score(self, x, y) :
return np.mean(self.predict(x) == y)
- 모델 훈련, 평가
layer1 = SingleLayer()
layer1.fit(x_train, y_train)
layer1.score(x_val, y_val)
- 약 91%의 정확도
- layer1 객체의 인스턴스 변수 w_history에는 100번의 에포크 동안 변경된 가중치가 모두 기록
- 세 번째, 네 번째 요소 (w [2], w [3])는 각각 mean perimeter, mean area특성에 대한 가중치
w2 = []
w3 = []
for w in layer1.w_history :
w2.append(w[2])
w3.append(w[3])
plt.plot(w2, w3)
plt.plot(w2[-1], w3[-1], 'ro')
plt.xlabel('w[2]')
plt.ylabel('w[3]')
plt.show()
- mean perimeter에 비해 mean area의 스케일이 크므로 w3 값이 학습 과정에서 큰 폭으로 흔들며 변화
- w2의 값이 0부터 시작하여 조금씩 최적 값에 가까워짐
- w3에 대한 그레이더 언트가 크기 때문에 w3 축을 따라 가중치가 크게 요동치는 중
- 빨간 점은 최종 가중치를 의미하고 w3가 최종 가중치에 도달하는 동안 위아래로 크게 요동친다.
- 모델이 불안정 함을 알고 스케일을 조정한다.
스케일 조정을 통한 모델 훈련
- 신경망에서 자주 사용하는 스케일 조정 방법 : 표준화(standardization)
- 표준화는 특성 값에서 평균을 빼고 표준 편차로 나누면 완성
- 표준화 시 평균이 0이고 분산이 1인 특성이 만들어진다.
- 넘 파이로 표준화 구현
- 넘 파이의 mean(), std() 함수를 통해 평균과 표준 편차 계산 시 표준화를 쉽게 할 수 있다.
- mean(), std()의 axis 매개변수를 0으로 지정 시 2차원 배열의 열을 기준으로 통계치를 계산 하나의 행 백터로 변환
train_maan = np.mean(x_train, axis=0)
train_std = np.std(x_train, axis=0)
x_train_scaled = (x_train - train_mean) / train_std
- 모델 훈련
layer2 = SingleLayer()
layer2.fit(x_train_scaled, y_train)
w2 = []
w3 = []
for w in layer2.w_history :
w2.append(w[2])
w3.append(w[3])
plt.plot(w2, w3)
plt.plot(w2[-1], w3[-1], 'ro')
plt.xlabel('w[2]')
plt.ylabel('w[3]')
plt.show()
- 표준화 특징
- w2와 w3의 변화율이 비슷하기에 대각선 방향으로 가중치의 이동
- 특성의 스케일이 비슷함으로 최적 값에 빠르게 근접
- 모델 성능 평가
layer2.score(x_val, y_val)
- 성능이 떨어지는 이유는 훈련 세트와 검증 세트의 스케일이 비슷하지 않기 때문
- 튜닝이 완료된 훈련 세트와 다르게 검증 세트는 스케일의 표준화가 이루어지지 않았기 때문이다.
val_mean = np.mean(x_val, axis=0)
val_std = np.std(x_val, axis=0)
x_val_scaled = (x_val - val_mean) / val_std
layer2.score(x_val_scaled, y_val)
- 정확도가 확 올라와 필자의 모델에서는 100%의 정확도를 보유한 것을 알 수 있다.
스케일 조정 후 함정에 대하여
- 스케일 조정 후 나타날 수 있는 함정은 훈련 세트와 검증 세트가 다른 비율로 스케일이 조정된 경우
- 원본 훈련 세트와 검증 세트로 산점도 그리기
- 파란 점은 훈련 세트, 빨간 점은 검증 세트
plt.plot(x_train[:50, 0], x_train[:50, 1], 'bo')
plt.plot(x_val[:50, 0], x_val[:50, 1], 'ro')
plt.xlabel('fature1')
plt.ylabel('fature2')
plt.legend(['train set', 'val set'])
plt.show()
- 이어서 전 처리한 훈련 세트와 검증 세트로 산점도 그리기
plt.plot(x_train_scaled[:50, 0], x_train_scaled[:50,1], 'bo')
plt.plot(x_val_scaled[:50, 0], x_val_scaled[:50, 1], 'ro')
plt.xlabel('fature1')
plt.ylabel('fature2')
plt.legend(['train set', 'val set'])
plt.show()
- 너무나도 비슷해 보이는 환경으로 구성되어있으나 실제 산점도를 보면 비율의 변화가 존재한다.
- 이유는 훈련 세트와 검증 세트를 각각 다른 비율로 전 처리하였기 때문
- 올바르게 검증 세트 전처리 하기
- 검증 세트의 스케일이 훈련 세트의 스케일과 다른비율로 조정되면 모델에 적용된 알고리즘이 검증 세트의 샘플데이터를 잘못 인식
- 훈련세트의 편균, 표준 편차를 사용해 검증 세트를 변환
x_val_scaled = (x_val - train_mean) / train_std
plt.plot(x_train_scaled[:50, 0], x_train_scaled[:50, 1], 'bo')
plt.plot(x_val_scaled[:50, 0], x_val_scaled[:50, 1], 'ro')
plt.xlabel('fature1')
plt.ylabel('fature2')
plt.legend(['train set', 'val set'])
plt.show()
- 위 세 가지 그림의 차이점이 보이는가
- 그림 1과 그림 3은 산점도의 분포나 모양이 동일
- 그러나 그림 2의 산점도는 1이나 3과는 미묘한 변화가 존재
'AI' 카테고리의 다른 글
5-2 과대적합, 과소 적합 (0) | 2020.11.25 |
---|---|
4-7 사이킷 런으로 로지스틱 회귀 수행 (0) | 2020.11.18 |
4-6 로지스틱 회귀 뉴런으로 단일층 신경망 제작 (0) | 2020.11.18 |
4-5 로지스틱 회귀를 위한 뉴런 만들기 (0) | 2020.11.18 |
4-4 분류용 데이터 세트 (0) | 2020.11.18 |