티스토리 뷰
ML 모델을 고를 때 중요한 것
알고리즘의 개념
알고리즘의 전체 조건
성능에 영향을 미치는 요소
svm : 마진을 최대화 하는 초평면을 찾는다.
가장 큰 도로 만들기
데이터를 커널변환 한다.
svm은 데이터가 적을 때 성능 발휘. 요즘엔 응용 알고리즘들이 많다.
나이브 베이즈 : 특징들이 서로 독립적이라는 "나이브(Naive)"한 가정
딥러닝 dense
input_shape = (nfeatures,)
input_shape에 들어가는 건 분석 단위여야 한다.
r2 score의 마이너스 값이 나오는 건 실제값과 평균 추세선, 그리고 모델 오차가 더 커진 곳이 많아서 그렇다.
평균보다 못한 모델이라고 해석은 아지만, 꼭 그렇지만도 않다. 모델에 따라 다르다.
히든 레이어의 의미
실제 hidden layer의 각 노드가 어떤 비즈니스 의미인지 파악하는 건 어렵다.
그렇기에 여기서 설명하는 건 특별한 모델 구조를 구성했을 때이다.
만약에 input이 5개인 모델이 있다고 하자.
이때는 모든 노드를 은닉층의 하나의 노드에 할당할 수 있기도 하지만,
2,3개로 나눠서 2개의 노드에 할당할 수도 있다.(locally connected)
- squential( [ Dense( 2, input_shape(5,) ,activation='relu']) fully
or
- functional( [ Dense( 1, input_shape(3,) ,activation='relu'])
- functional( [ Dense( 1, input_shape(2,) ,activation='relu'])
그러면 locally conneted에서, 각각에 대한 가중치 함수가 나온다.
- z1 = w1 *x1 + w2 *x2+ w3*x3
- z2 = w4 * x4 + w5 * x5
이때 각각은 input을 표현한 또 하나의 feature가 된다.
집값 예측을 한다고 하면, 일부러 input을 넣을 때 이런 식으로 카테고리로 나눌 수 있다.
- z1 = 내부요인 점수
- z2 = 외부요인 점수
그래서 만약에 , z1의 가중치와 z2의 가중치를 줄 수 있다면, 각각의 feature에 대해서 가중치를 줘서 최종 집값이라는 예측 지표를 구할 수 있는 것이다.
z1 * 0.8 + z2 * 0.2 = output
즉, 은닉층이라는 건 새로운 비즈니스 feature를 만들어내는 과정이라고 볼 수 있는 것(Representation)이다.
feature engineering이 자동으로 진행되는 것이다.
해당 특징이 분명 오차 예측 방면에서 유익할 것이라는 믿음이 필요하다.
한 단계 추상화를 진행하는 것이다.
Q. 히든 레이어가 피처 엔지니어링을 하는 과정이 매 에포크마다 랜덤하게 일어나는가?
dropout이 아니라면 처음에만 랜덤이고, 전체 피처를 과정으로 수행한다.
추상적으로 되었을 뿐이지, 실제로는 기저 벡터 변환을 수행하기 때문에 일단 처음에만 은닉층이 전부 하나의 중요하다고 생각하는 feature를 구상한다.
그리고 그 feature에 대한 가중치를 역전파로 업데이트 해주는 것.
dropout을 하지 않으면, 사실상 전체 input shape에 대해 feature를 만드는 게 된다.
특징 공간
쉽게 설명했지만, 실제로는 특징 공간을 변환해서 만드는 기저 벡터 변환을 수행한다.
각각의 특징을 담은 값들이 있고, XOR 문제를 풀 수 있는 결정 경계를 만들기 위해서 값들을 하나의 직선으로 가를 수 있는 해당 평면으로 이동시킨다.
그렇게 변환된 평면에서 결정 경계를 그어서 활성화 함수로 판단하게 된다.
딥러닝 - 이진분류
분류에서는 node에서 out의 결과를 변환해주는 함수가 필요하다.
분류는 반드시 끝에 활성 함수가 필요하고, 이진분류는 시그모이드 함수, 다중분류는 softmax 함수를 쓴다.
%%
분할 한 후 스케일링하는 이유
스케일링이 학습이기 때문에 data leakage 발생
정확히는 미래에 있는 데이터가 과거에 섞일 수 있기 때문이다.
%%
nfeatures = x_train.shape[1]
clear_session()
model = Sequential( Dense( 1 , input_shape = (nfeatures ,), activation= 'sigmoid') )
model.summary()
model.compile(optimizer = Adam(learning_rate=0.01), loss = 'binary_crossentropy')
history = model.fit(x_train, y_train, epochs = 50, validation_split=0.2).history
여기서는 binary_crossentropy를 쓴다.
log 함수는 곱셈을 덧셈으로 바꿔줄 수 있다.
그래프와 만나는 지점이 무조건 1이다.
x가 증가하면 log x는 무조건 증가한다(단조증가함수)
만약에 예측값이 1에 가까울수록 오차는 0에 가까워지도록 하고,
예측값이 0에 가까울수록 오차가 무한대에 가까워지도록 해야 한다고 하자.
-log 함수를 사용하면 이게 가능하게 된다.(log 함수를 뒤집는다 생각하면 쉽다)
그런데 문제는 log 함수에는 - 값을 넣을 수 앖다.
그러므로 0,1과의 분류 문제라면 y가 0이면, 1-예측값으로 만든다.
그래서 하나의 수식으로 정리하면,
-(1/n)시그마 y*log(y^) + (1-y)*log(1-y^)가 된다.
각각의 앞에 곱해주는 이유는 y=1이면 좌항으로, y=0이면 우항으로 들어가게 만들기 위함이다.
궁금하다면 정보 이론을 공부하면 된다.
평가
pred = model.predict(x_val)
pred[:5]
# activation이 sigmoid --> 0 ~ 1 사이의 확률값.
# 그러므로 cut-off value(보통 0.5)를 기준으로 잘라서 0과 1로 만들어 준다.
# pred = np.where(pred >= .5, 1, 0)
9/9 [==============================] - 0s 1ms/step
array([[0.70583427],
[0.17244492],
[0.22722076],
[0.19907498],
[0.7296619 ]], dtype=float32)
print(classification_report(y_val, pred))
처음에 predict는 확률 값으로 나온다.
그래서 1과 0으로 바꿔야 한다. 여기서는 np.where로 수행한다.
f1-score
재현율과 정밀도의 조화 평균
갈 때 60km/h, 올때 80km/h의 평균 속력 =
이진 분류에서의 은닉층
clear_session()
model3 = Sequential([ Dense( 16, input_shape = (n ,), activation = 'relu'),
Dense(8, activation = 'relu'),
Dense( 1, activation = 'sigmoid')])
model3.summary()
#혹은
clear_session()
model3 = Sequential()
model3.add(Dense( 16, input_shape = (n ,), activation = 'relu'))
model3.add(BatchNormalization(axis=-1, momentum=0.99, epsilon=0.001, center=True, scale=True))
model3.add(Dense(8, activation = 'relu'))
model3.add(Dense( 1, activation = 'sigmoid'))
model3.summary()
오차는 0에 수렴하지만, val_loss가 올라가면서 과적합
오히려 모델이 복잡하다는 의미이다.
그런데 이는 타겟 불균형으로 인해서 일어날 수도 있다.
0.84 , 0.16같이 나왔을 때. 더더욱 그렇다.
클래스 불균형(y에 대한 불균형)
resampling하여 불균형을 해결
이걸 안하면 어떤 문제가 발생하는 지를 이해야하는 게 더 중요하다.
일반적인 알고리즘은 전부 데이터가 클래스 내에서 고르게 분포되어 있다고 가정한다.
그래서 다수 클래스를 더 많이 예측하는 쪽으로 모델이 편향되는 경향이 있다.
그러므로 소수의 클래스에서 오분류 비율이 높아진다.
모델링의 방향은 전체 오차를 줄어드는 선택을 한다. 그래서 더 많은 녀석에게 편향.
accuracy는 높지만 적은 class쪽의 recall은 형편없이 낮다.
어떻게 보면 비즈니스 상황에서 당연한 현상이기 때문에, 다룰줄 알아야 한다.
우리의 관심사는 보통 더 적은 쪽이다. (관심사가 어디에 있느냐가 Positive)
해결 방법
respling - oversampling,undersampling,smote 기법 등
class weight 조정 - 사이킷 런에서 하이퍼 파라미터 조정
둘 다 효과는 똑같은데, resampling은 물리적으로 데이터를 균형맞추는 것이고,
class weight은 논리적으로 오차를 균형을 준다.(큰 쪽 오차가 발생하면 패널티를 준다.
from imblearn.over_sampling import RandomOverSampler
ros = RandomOverSampler()
x_train_ros, y_train_ros = ros.fit_resample(x_train, y_train)
print(y_train_ros.value_counts(normalize=True))
train에 대해서만 리샘플링을 해준다.
그 이외의 방법
1. 다른 데이터 augmentation 기법 적용
- 회전, 이동, 잘라내기 등 다양한 augmentation 기법을 조합하여 적용
- 색상 변환, 노이즈 추가, 합성 등 더 다양한 기법으로 데이터 다양성 증가
2. 클래스 가중치 조정
- 소수 클래스 샘플에 더 높은 가중치를 부여하여 모델이 중요하게 학습하도록 유도
3. 앙상블 기법
- 서로 다른 모델들의 예측을 결합하는 베이징, 부스팅, 스태킹 등의 앙상블 활용
4. 모델 아키텍처 변경
- 클래스 불균형 문제에 적합한 모델 아키텍처 적용 (예: Focal Loss 사용)
5. 데이터 클리닝
- 잡음 데이터나 이상치 등을 제거하여 데이터 품질 향상
6. 소수 클래스만 오버샘플링 혹은 언더샘플링 추가
- 오버샘플링한 데이터에 추가로 샘플링 적용
7. 하이퍼파라미터 튜닝
- 학습율, 정규화 등 하이퍼파라미터 값을 변경하며 최적의 성능 모델 탐색
배치 사이즈
하나의 에포크 안에, 몇 배치 사이즈를 하느냐도 중요.
배치를 나누게 되면 병렬 연산 처리로 연산 속도가 빨라진다.
배치 사이즈가 데이터의 크기가 6만, 배치 사이즈가 32라고 하면, 32행을 추출(이미지는 32개)해서 1875번 iteration을 반복한다.
각 iteration 동안 모델은 순전파와 역전파를 반복.
즉, 순전파 역전파를 1875번 반복하게 되고, 이때는 1875번째 이터레이션이 가장 좋은 가중치로서 결과값으로 도출된다.
단, 배치 사이즈로 나누면 이상치에 민감하기 때문에, 손실 함수 사용 시 이상치에 강건하도록 설계해야 한다.
ex : huber loss
무작위로 샘플링하여 이상치를 분산시키는 작업과, 배치 정규화로 기본적으로 이상치에 영향 받지 않게 해줄 필요가 있다.
%%
비즈니스에서의 타겟층 노후화
신규 유입이 아닌, 잔존률에 문제가 있다.
%%
다중 분류에서의 전처리
다중 분류는 y가 범주이고, 범주가 3개 이상인 목표이다.
output layer의 노드의 수 = 분류의 수
여기서 soft max를 쓴다.(e^4, e^0.8, e^2) 각각을 총 합으로 나눠준다.
softmax = e^x / 시그마 e^x
3가지 결과를 하나의 확률 값으로 변환해서, 각 확률의 합을 1로 만든다.
다중 분류에서는 y에 대한 전처리가 필요하다.
방법 1
정수 인코딩(0부터 시작) - 라벨 인코딩
예를 들어서, 타겟1은 0, 타겟 2는 1, 타겟 3은 2로 하는 것이다.
이때의 loss function은 sparse_categorical_crossentropy
data['Species'] = data['Species'].map({'setosa':0, 'versicolor':1, 'virginica':2})
data.head()
얼마 없다면 map 함수로
많다면 라벨 인코더를 쓴다.
방법 2
원 핫 인코딩(가변수화)
categorical_crossentropy를 사용한다.
두 개의 loss function은 수학적으로 완전 동일하다.
원핫 인코딩은 정수 인코딩이 선행되어야 한다.
sparse_categorical_crossentropy
[0.1, 0.8, 0.3]의 확률 값이 있다고 해보자.
y는 인덱스로 사용되어, 해당 인덱스의 예측확률로 계산된다.
1(리스트 중 1번 인덱스) | [0.1, 0.6, 0.3] | -log(0.6) |
0 | [0.2, 0.6, 0.2] | -log(0.2) |
2 | [0.1, 0.6, 0.3] | -log(0.3) |
모델 설계시 주의사항
dense의 output이 클래스 수로 나와야 한다는 것에 주의
activation도 softmax로 바꿔주어야 한다.
sparse_categorical_crossentropy를 쓴다.
clear_session()
model = Sequential( Dense( 3 , input_shape = (nfeatures,), activation = 'softmax') )
model.summary()
model.compile(optimizer=Adam(learning_rate=0.1), loss= 'sparse_categorical_crossentropy')
history = model.fit(x_train, y_train, epochs = 50, validation_split=0.2).history
confusion matrix로 만들기 전에, argmax를 통해 각 인덱스를 추출해줘야 한다.
np.argmax(pred[:5], axis = 1)
pred_1 = pred.argmax(axis=1) #열에 대해서, 가장 큰 값에 대한 인덱스를 저장한다.
pred_1
오탐의 면도날
그 문제만 해결할 수 있는 적당한 것이 오히려 좋다.
불필요한 가정은 빼는 것이 좋다.
confusion matrix로 보면서 의미를 파악하는 게 중요하다.
0,1,2,3,4가 있으면, 4등급을 2등급이라고 하거나 3등급이라고 하는 경우에는 괜찮지만,
0,1등급이라고 예측하면 심각한 것이다.