티스토리 뷰

반응형

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등급이라고 예측하면 심각한 것이다.

 

반응형