티스토리 뷰

반응형

모델링의 목표

적절한 예측력을 얻기 위해서, 적절한 복잡도의 모델을 생성한다(일반화 성능)

모집단 전체를 바라보는 모델을 만들고 싶은 것이다.

모델의 복잡도는 대체로 하이퍼 파라미터 조정에 따라 복잡도가 달라진다.

 

모델이 복잡해지면 노이즈 데이터에 대한 데이터(가짜 패턴)까지 학습하게 된다.

 

따라서, 하이퍼 파라미터 튜닝은 검증 데이터셋에 대해 가장 좋은 일반화 성능을 찾겠다는 뜻이다.

검증 성능을 기반으로 최적의 모델을 선정하는 것 자체가, 과적합을 피하는 방법이다.

 

적절한 모델 만들기

knn의 그래프를 그려보면, 복잡도를 k가 증가할 때마다 validation과 train loss가 cross하는 지점이 있다.

loop, gridsearch,randomsearch등을 찾는다. 

 

성능관리를 위해 딥러닝에서 조절할 수 있는 하이퍼 파라미터

Epoch, learning rate

모델 구조 조정 : hidden layer, node 수

Early Stopping

Regularization(규제) : L1, L2

Dropout

 

ResNet의 등장으로 Dropout은 잘 안쓰게 되었다.

쓰긴 쓰지만, 양자화를 위해 고려하는 수준.

 

Early Stopping(조기 종료)

from keras.callbacks import EarlyStopping
min_val_loss_delta = 0.001 #오차의 최소값에서 변화량이 최소 몇 이상 되어야 하는지를 지정한다(기본값은 0)
enough = 5 #얼마만큼 기다려 줄 것인지

EarlyStopping = EarlyStopping(monitor = 'val_loss', min_delta = min_val_loss_delta, patience = enough)

 

여기서 min_val_loss는 compile에서 주어진 loss function(손실 함수)의 변화값이라는 걸 주의

만약 enough보다 조금 더 많이 변하면, count가 다시 시작된다.

 

.fit(x_train, y_train, epochs = 100, validation_split=0.2,
                  callbacks = [es]) #.fit에 추가해준다.

callback에는 조기 종료 말고도 여러가지 기능이 추가될 수 있다.

 

컬럼이 301개나 되는 데이터는 사실 차원의 저주로 인해 모델링이 잘 되지 않는다.

실제 = 모델 + 오차

 

모델은 오차 안에 있는 패턴도 학습한다.->과적

오차를 줄이는 게 좋으므로 earlystopping을 통해 과적합을 방지하는 것이다.

 

대체로 enough는 5~10~20을 주기도 한다.

Regularization(가중치 규제)

L1 규제 : Lasso

오차함수가 기존의 오차에 규제강도 * 가중치 절대값의 합으로 표현된다.

 

오차함수 = 오차+ alpha * 시그마 w

오차를 최소화하면서, 가중치의 합도 최소화하게 되는 것이다.

가중치 절대값의 합을 최소화하여, 가중치가 작은 값들은 0으로 만드는 경향이 있다.

 

파라미터가 너무 많아서 한 개의 노드에 몰리면, 0에 가까운 파라미터들도 많이 만들어진다.

가중치가 너무 많거나 작은 녀석들을 규제하겠다는 의미이다. 그러면서 모델의 복잡도가 줄어진다.

즉, 너무 많은 파라미터를 정리하는 방법이다.

 

L2 규제 : Ridge

여기는 오차함수 = 오차+ alpha * (시그마 w)^2

규제 강도에 따라 가중치 영향력을 제어한다.

강도(alpha)가 크면 큰 가중치가 더 줄어드는 효과가 있다. 작은 가중치는 거의 0에 수렴한다.

 

과적합 방지를 위해 이렇게 쓰는 것

 

일반적인 값의 범위

L1 규제 : 0.0001 ~ 0.1

L2 규제 : 0.001 ~0.5

강도가 높을수록 일반화된 모델이다. -> 좀 더 하면 단순한 모델로 간다

 

작을수록 기존 모델, 중간이 일반화된 모델, 좀 더 하면 과소적합(단순) 모델이 된다.

# 규제를 위해 필요한 함수
from keras.regularizers import l1, l2

 

[Dense(256, input_shape = (feature,), activation= 'relu',
                            kernel_regularizer = l1(0.01)), #alpha 값을 얼마나 줄 건지 넣는다.
                      Dense(128, activation= 'relu',
                            kernel_regularizer = l1(0.01)

 

l2 규제도 사용법은 같고, l1을 l2로만 바꿔주면 된다.

 

오차를 볼 때는 y축의 스케일을 잘 봐야 한다.

오히려 기존 모델보다 오차가 더 많아 질 수도 있다.

plt.ylim(0~2)

 

일반적으로는 l1을 쓰면 모든 은닉층에 l1을 쓰고, l2를 쓰면 똑같이 모든 은닉층에 l2를 쓰면서 규제를 통일해준다.

 

파라미터가 많은 층에 규제를 주기도 하고, 모든 층에 주기도 하고,

이런 건 모두 해봐야 아는 것들이다.

 

Dropout

과적합을 줄이기 위해 사용되는 규제 기법 중 하나이다.

훈련 과정에서 일부 뉴런을 랜덤하게 drop 시켜버리면서 가중치를 규제한다.

레이어 중간에 Dropout(0.4)

 

중간 체크포인트에 모델 저장

딥러닝 모델은 .h5 (파일 포맷 하둡)으로 많이 저장한다.

pkl로도 저장할 수 있다.

import os

def delete_h5_files(directory):
    for filename in os.listdir(directory):
        if filename.endswith(".h5"):
            file_path = os.path.join(directory, filename)
            try:
                os.remove(file_path)
                print(f"Deleted: {file_path}")
            except Exception as e:
                print(f"Error deleting {file_path}: {e}")

# 삭제할 디렉토리 지정
directory_to_delete_from = "/content/"

# 확장자가 .h5인 파일 삭제
delete_h5_files(directory_to_delete_from)

일단 저장한 .h5 파일들을 제거한다.

그리고 모델을 평범하게 선언, 컴파일 한 후에 실행하면 된다.

from keras.callbacks import ModelCheckpoint

cp_path = '/content/{epoch:03d}.h5'
mcp = ModelCheckpoint(cp_path, monitor='val_loss', verbose = 1, save_best_only=True)
#val accuray 기준으로 성능이 저장된다.
#save_best_only로 best만 저장
#저장할 때마다 표시

# 학습
hist = model1.fit(x_train, y_train, epochs = 50, validation_split=.2, callbacks=[mcp]).history

 

mcp에 경로를 지정

cp_path는 모델을 저장할 경로와 모델 파일이름이다.

epoch 번호를 3자리로 표기하여 저장된다.

verbose는 개선되면 저장

 

만약 개인적 구글 드라이브에 저장하고 싶다면 마운트 후에 해당 결로를 지정해주면 된다.

 

딥러닝의 하이퍼 파라미터 튜닝

epoch는 조절는 model check point와 조기종료로 조절

그러면 HL수, node수를 조절하고, learning rate를 조절한다.

 

값의 범위를 설정

node의 수, HL수 128,64..이런 식으로 줄여나갈 것인가.

 

그 다음엔 찾기이다.

 

찾기 횟수를 줄이면서 찾으려고 하는 것도 있긴 하지만(베이지안 최적화)

랜덤 서치와 그리드 서치 정도면 충분하다고 본다.

 

잘 모르는 HP

1~100을 10번 시도해서 대략의 범위를 찾고 random

그 후에 gridsearch로 찾는다. -> 연구 노가다다.

 

keras-tuner

!pip install keras-tuner

베이스라인 모델을 만든다.

clear_session()
model1 = Sequential([Dense(1, input_shape = (x_train.shape[1],))])

model1.compile(loss='mean_absolute_error', optimizer=Adam(0.1))
model1.summary()

 

후에는 시간 출력

 

%%

y_test,y_test는 실수가 아니다.

y=x를 그리기 위한 것.

이렇게 보면서 성능을 가늠해야 할 필요성이 있다.

pred1 = model1.predict(x_test, verbose = 0)
print('MAE :', mean_absolute_error(y_test, pred1))

plt.scatter(y_test, pred1)
plt.plot(y_test, y_test, color = 'gray', linewidth = .5)
plt.grid()
plt.show()

%%

 

hidden layer 2개 노드 튜닝

def build_model(hp):
    n1 = hp.Choice('node1', [16, 32, 64, 128, 256])
    model = Sequential([ Dense(units=n1,
                               input_shape = (x_train.shape[1],), activation='relu'),
                         Dense(units=n1,
                               activation='relu'),
                         Dense(1)])
    model.compile(loss='mean_absolute_error', optimizer=Adam(learning_rate = 0.001))
    return model
    
 %%time
tuner = kt.RandomSearch(build_model, objective='val_loss', max_trials = 2, project_name='dnn_tune_3')
tuner.search(x_train, y_train, epochs = 30, validation_split = .2, verbose=0)
best_model = tuner.get_best_models(num_models=1)[0]

tuner.results_summary()

node1가 들어가야 하는 자리에 범위로 지정

그 다음 튜너를 쓴다.

 

hidden layer 3개 노드 튜닝

각각 레이어의 노드의 범위를 지정해서 하는 걸 튜닝하는 것.

def build_model(hp):
    model = Sequential([ Dense(units=hp.Choice('node1', [16, 32, 64, 128, 256]), input_shape = (x_train.shape[1],), activation='relu'),
                         Dense(units=hp.Choice('node2', [8, 12, 32, 64, 128]), activation='relu'),
                         Dense(units=hp.Choice('node3', [4, 8, 12, 32, 64]), activation='relu'),
                         Dense(1)])
    model.compile(loss='mean_absolute_error', optimizer=Adam(learning_rate = hp.Choice('learning_rate', [0.0001, 0.001, 0.01])))
    return model

 

 

수동 튜닝

레이어는 리스트이므로, 리스트의 특성을 이용해서 추가해가면서 실험한다.

 

nfeatures= x_train.shape[1]

def modeling_test1(node) :

    # 노드 수를 입력 받아 모델 선언
    clear_session()
    model = Sequential([Dense(node ,input_shape = (nfeatures,)  , activation = 'relu' ),
                        Dense(1) ] )

    model.compile(optimizer=Adam(learning_rate = 0.01), loss = 'mse')
    model.fit(x_train, y_train, epochs = 50, verbose = False)

    pred = model.predict(x_val)
    mae = mean_absolute_error(y_val, pred)

    # mae 결과 return
    return mae

 

from tqdm import tqdm 

진행률 추가

from tqdm import tqdm 
nodes = [2, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150]
result = []
for n in tqdm(nodes) :
    result.append(modeling_test1(n))

 

모집단을 예측하는 모델을 만들려면 가능한 단순한 모델을 만들어야 한다.

노드가 증가하면 어떻게 되는가?

대체로 노드의 수가 증가할수록 모델이 복잡하므로 성능이 그대로거나 오히려 나빠진다.

 

적절한 지점을 찾는 방법

elbow method를 사용한다. 성능이 더 이상 개선되지 않는 elbow 근방에서 답을 찾으라는 것이다.

그래프에서 elbow를 찾고, 그 근방에서 성능을 찾으라는 것이다.

 

개선이 되다가 처음으로 꺾인 지점(자원이 제일 적은 지점)을 고른다.

휴리스틱 법(제한된 정보로 찾기) 중 하나다.

 

gridsearch하면 1이라도 낮은 지점을 찾아주지만, 그게 정답이 아닐 수 있다.

 

노드는 고정하고, 레이어의 수를 추가

def modeling_test2(layer) :

    # 레이어 리스트 만들기
    # 레이어 수 만큼 리스트에 레이어 추가

    clear_session()

    # 첫번째 레이어는 input_shape가 필요.
    layer_list = [Dense(10 ,input_shape = (nfeatures,)  , activation = 'relu' )]

    # 주어진 레이어 수에 맞게 레이어 추가
    for i in range(2, layer) :  # 첫번째 레이어, 아웃풋 레이어는 명시적으로 추가하므로 2부터 시작
        layer_list.append(Dense(10 , activation = 'relu' ))

    # Output Layer 추가하고 모델 선언
    layer_list.append(Dense(1))
    model = Sequential(layer_list)

    # 레이어 잘 추가된 건지 확인하기 위해 summary 출력
    print(model.summary())

    model.compile(optimizer=Adam(learning_rate = 0.01), loss = 'mse')
    model.fit(x_train, y_train, epochs = 50, verbose = False)

    pred = model.predict(x_val)
    mae = mean_absolute_error(y_val, pred)

    return mae

반복 실행

layers = list(range(1,11))
result = []
for l in layers :
    result.append(modeling_test2(l))

 

할 때마다 들쑥 날쑥하다면 데이터 건수가 낮을 가능성이 높다.

편차가 높다는 뜻이다.

 

Functional API

squential vs Functional

squential은 순차적으로 쌓아가면서 모델을 생성한다.

input에서 output으로 순차적으로 연결하는 것.

 

Functional의 경우, 모델을 조금 더 복잡하게 구성한다.

모델을 분리해서 사용도 가능하고, 다중 입력, 다중 출력까지도 가능하다.

 

Input 1과 Input2를 각각 Hidden layer 1,  Hidden layer 2에 넣고 ,  Hidden layer 3에 동시 연결이 가능하다.

 

방법

Input이라는 함수 안에 input shape를 지정.

은닉층의 괄호 안에 열결될 레이어가 무엇인지 지정

그리고 아웃풋까지 이 과정을 반복한다.

clear_session()

il = Input(shape=(nfeatures, ))
hl1 = Dense(18, activation='relu')(il)
hl2 = Dense(4, activation='relu')(hl1)
ol = Dense(1)(hl2)

model = Model(inputs = il, outputs = ol)

model.summary()

 

다중 입력

멀티 모달 모델을 쓸 수 있다.

각 입력에 맞는 특징 도출(feature representation) 가능

다양한 데이터의 데이터를 input 받아서 그에 맞는 출력을 내놓는 것이다.

 

개인정보, 구매 이력, 이미지가 있으면 불만, 만족을 예측할 수 있다.

 

다중 출력은 권장하지는 않는다. 구매 양과 이탈 여부, 분류, 이런 것들을 output으로 내놓을 수 있다.

 

# 모델 구성
input_1 = Input(shape=(nfeatures1,), name='input_1')
input_2 = Input(shape=(nfeatures2,), name='input_2')

# 첫 번째 입력을 위한 레이어
hl1_1 = Dense(10, activation='relu')(input_1)

# 두 번째 입력을 위한 레이어
hl1_2 = Dense(20, activation='relu')(input_2)

# 두 히든레이어 결합
cbl = concatenate([hl1_1, hl1_2])

# 추가 히든레이어
hl2 = Dense(8, activation='relu')(cbl)

# 출력 레이어
output = Dense(1)(hl2)

# 모델 선언
model = Model(inputs = [input_1, input_2], outputs = output)

model.summary()

 

concatenate는 각각의 히든 레이어를 옆으로 붙여서, 여기서는 10+20으로 만든다.

다만, 학습이 되거나 각각의 인풋이 꼬이지는 않난다.

모델 예측시에도 두 가지 입력을 묶어서 줘야 한다.

# 컴파일
model.compile(optimizer=Adam(learning_rate = 0.01), loss = 'mse')

# 학습
hist = model.fit([x_train1, x_train2], y_train, epochs=50, validation_split=.2).history
pred = model.predict([x_val1, x_val2])

print(mean_squared_error(y_val, pred, squared = False))
print(mean_absolute_error(y_val, pred))

 

결과는 2개가 나온다.

하나는 input1에 대한 오차, 하나는 input2에 대한 오차이다.

 

concatenate 을 할 때, 각 input의 행의 수는 같아야 한다.

컬럼의 수는 달라도 된다.

 

다중입력을 할 때, 구매 이력의 0번 인덱스와 개인 정보의 0번 인덱스, 고객 이미지의 0번 인덱스는 무조건 같아야 같은 분석단위로 묶인다.

 

그렇기 떄문에 무조건 같아야 한다.

 

autoencoder에 대해서 알아봐야 한다. 비지도학습에서 굉장히 중요한 방식이다.

학습이 된 다음에 fuctional api를 이용하면 중간에 일부만 사용할 수 있다.

 

transformer과 관련되어 있다.

반응형