데이터를 사용한 성능 최적화
- 최대한 많은 데이터 수집
- 데이터 생성
- 데이터 범위 조정: 활성화함수로 시그모이드를 사용한다면 데이터셋 범위를 0~1의 값을 갖도록 하고, 하이퍼볼릭 탄젠트를 사용한다면 -1~1의 값을 갖도록 조정(정규화, 규제화, 표준화도 성능 향상에 도움이 됨)
알고리즘 튜닝을 위한 성능 최적화
- 진단
- 훈련 성능이 검증보다 눈에 띄게 좋으면 과적합 의심 -> 규제화
- 훈련과 검증 결과가 성능이 좋지 않으면 과소적합 의심 -> 네트워크 구조 변경, 에포크 수 조정
- 훈련 성능이 검증을 넘어서면 조기 종료 고려
- 가중치: 가중치에 대한 초깃값은 작은 난수를 사용하는데 애매하면 오토인코더 같은 비지도 학습을 이용하여 사전 훈련
- 학습률: 네트워크의 계층이 많다면 학습률을 높여야 하며, 네트워크의 계층이 몇 개 되지 않는다면 학습률은 작게 설정
- 활성화 함수: 활성화 함수를 변경하면 손실 함수도 함께 변경(일반적으로 활성화 함수로 시그모이드나 하이퍼볼릭 탄젠트를 사용했다면 출력층에서는 소프트맥스나 시그모이드 함수 많이 선택)
-> 예를 들어, 이진 분류 작업에서 시그모이드 활성화 함수를 사용했다면, 보통은 교차 엔트로피 손실 (Cross Entropy Loss) 함수를 함께 사용, 다중 클래스 분류 작업에서는 소프트맥스 활성화 함수와 크로스 엔트로피 손실 함수를 함께 사용
- 배치와 에포크: 큰 에포크와 작은 배치를 사용하는 것이 최근 딥러닝의 트렌드
- 옵티마이저와 손실 함수: 옵티마이저는 확률적 경사 하강법 많이 사용(다양한 옵티마이저와 손실 함수를 적용해 보고 성능이 최고인 것을 선택해야 함)
- 네트워크 구성: 네트워크 구성을 변경해 가면서 성능을 테스트(하나의 은닉층에 뉴런을 여러 개 포함시키거나 네트워크 계층을 늘리되 뉴런 개수를 줄여보기)
앙상블을 이용한 성능 최적화
- 앙상블은 모델을 두 개 이상 섞어서 사용하는 것으로 성능 향상에 도움이 됨
하이퍼파라미터를 이용한 성능 최적화
- 배치 정규화
- 정규화: 데이터 범위를 사용자가 원하는 범위로 제한하는 것을 의미
- 규제화: 모델 복잡독를 줄이기 위해 제약(데이터가 네트워크에 들어가기 전에 필터를 적용한 것)을 두는 방법 - 드롭아웃, 조기 종료
- 표준화: 기존 데이터를 평균 0, 표준편차 1인 형태의 데이터로 만드는 방법
- 배치 정규화: 표준화와 유사한 방식을 미니배치에 적용하여 평균은 0, 표준편차는 1로 유지하도록 하는 방법
- 기울기 소멸 또는 폭발의 원인은 내부 공변량 변화(활성화 함수가 적용되면서 입력 값들의 분포가 계속 바뀌는 현상) 때문인데 이와 같은 문제점을 해결
- 단, 배치의 크기가 작아 분산이 0인 경우 정규화 불가, RNN은 네트워크 계층별로 미니 정규화를 적용해야 하기 때문에 모델이 더 복잡해지면서 비효율적일 수 있음 -> 아래의 그림에서 초록색 계층마다 미니 정규화를 적용해야 한다는 것을 의미
- 드롭아웃을 이용한 성능 최적화
- 드롭아웃: 훈련할 때 일정 비율의 뉴런만 사용 -> 과적합 방지
- 어떤 노드를 비활성화할지는 학습할 때마다 무작위로 선정하며, 테스트 데이터로 평가할 때는 노드를 모두 사용하여 출력하되 노드 삭제 비율(드롭아웃 비율)을 곱해서 성능 평가
실습
- 책에 있는 코드 중 데이터셋을 분리하는 과정에서 에러가 발생하였는데 아래와 같이 코드를 실행하면 에러 해결
# 책에 있는 코드
dataiter = iter(trainloader)
images, labels = dataiter.next()
# 코드 변경
images, labels = next(iter(trainloader))
print(images.shape)
print(images[0].shape)
print(labels[0].item())
- 배치 정규화
- 배치 정규화가 적용된 모델은 에포크가 진행될수록 오차도 줄어들면서 안정적인 학습을 하고 있는 것을 확인할 수 있었음
class BNNet(nn.Module): # BNNet 정의(nn.Module 클래스 상속)
def __init__(self): # BNNet 클래스의 초기화
super(BNNet, self).__init__() # nn.Module의 초기화 메서드를 호출하여 BNNet 클래스를 초기화
self.classifier = nn.Sequential(
nn.Linear(784, 48), # 28 x 28 = 784
nn.BatchNorm1d(48), # 배치 정규화 - 완전연결층과 합성곱층 뒤, 활성화 함수 앞에 위치
nn.ReLU(),
nn.Linear(48, 24),
nn.BatchNorm1d(24),
nn.ReLU(),
nn.Linear(24, 10)
)
def forward(self, x):
x = x.view(x.size(0), -1)
x = self.classifier(x) # 위에서 정의한 계층 호출
return x
- 데이터셋 만들기
x_train = torch.unsqueeze(torch.linspace(-1, 1, N), 1)
# torch.linspace(-1, 1, N) 의미는 -1~1의 범위에서 N개의 균등한 값을 갖는 텐서를 생성
# torch.unsqueeze는 torch.linspace(-1, 1, N) 텐서의 첫 번째 자리에 차원을 증가시키겠다는 것
y_train = x_train + noise * torch.normal(torch.zeros(N, 1), torch.ones(N, 1))
x_test = torch.unsqueeze(torch.linspace(-1, 1, N), 1)
y_test = x_test + noise * torch.normal(torch.zeros(N, 1), torch.ones(N, 1))
- 드롭아웃
- 드롭아웃을 사용할 경우 과적합 현상이 발생하지 않은 것을 확인할 수 있었음
N_h = 100
model = torch.nn.Sequential(
torch.nn.Linear(1, N_h),
torch.nn.ReLU(),
torch.nn.Linear(N_h, N_h),
torch.nn.ReLU(),
torch.nn.Linear(N_h, 1),
)
model_dropout = torch.nn.Sequential(
torch.nn.Linear(1, N_h),
torch.nn.Dropout(0.2), # 드롭아웃
torch.nn.ReLU(),
torch.nn.Linear(N_h, N_h),
torch.nn.Dropout(0.2),
torch.nn.ReLU(),
torch.nn.Linear(N_h, 1),
)
조기 종료를 이용한 성능 최적화
- 조기 종료는 뉴럴 네트워크가 과적합을 회피하는 규제 기법임
- 검증 데이터셋에 대한 오차가 증가하는 시점에서 학습을 멈추도록 함(조기 종료는 학습을 언제 종료시킬지 결정할 뿐이며, 최고의 성능을 갖는 모델을 보장하지는 않는다는 점에 주의해야 함)
실습
- 데이터셋 가져오기
- 책에 있는 코드 중 데이터셋을 가져오는 과정에서 에러가 발생하였는데 google drive에 데이터를 다운받아 이용하면 에러 해결
# 책에 있는 코드
train_dataset = datasets.ImageFolder(
root=r'../chap08/data/archive/train',
transform=train_transform
)
train_dataloader = torch.utils.data.DataLoader(
train_dataset, batch_size=32, shuffle=True,
)
val_dataset = datasets.ImageFolder(
root=r'../chap08/data/archive/test',
transform=val_transform
)
val_dataloader = torch.utils.data.DataLoader(
val_dataset, batch_size=32, shuffle=False,
)
# google drive에 데이터 다운받아 가져오기
from google.colab import drive
drive.mount('/content/drive')
train_dataset = datasets.ImageFolder(
root='/content/drive/MyDrive/train',
transform=train_transform
)
train_dataloader = torch.utils.data.DataLoader(
train_dataset, batch_size=32, shuffle=True,
)
val_dataset = datasets.ImageFolder(
root='/content/drive/MyDrive/test',
transform=val_transform
)
val_dataloader = torch.utils.data.DataLoader(
val_dataset, batch_size=32, shuffle=False,
)
- 파이토치에서 조기 종료화 함께 자주 사용되는 것은 학습률 감소(학습이 진행되는 과정에서 학습률을 조금씩 낮추어 주는 성능 튜닝 기법 중 하나)
- 학습률 감소는 학습률 스케줄러라는 것을 이용하는데 주어진 'patience' 횟수만큰 검증 데이터셋에 대한 오차 감소가 없으면 주어진 'factor'만큼 학습률을 감소시켜서 모델 학습의 최적화가 가능하도록 도움
- 학습률 감소
- 정확도의 변동이 크고, 오차가 우상향한다면 모델 과적합이 시작했다는 것을 의미하며 학습률 값을 줄여야 함을 의미함
class LRScheduler():
def __init__(
self, optimizer, patience=5, min_lr=1e-6, factor=0.5
):
self.optimizer = optimizer
self.patience = patience
self.min_lr = min_lr
self.factor = factor
self.lr_scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
self.optimizer,
mode='min', # 언제 학습률을 조정할지에 대한 기준이 되는 값(학습률 조정의 기준이 되는 값을 모델의 정확도로 사용하면 값이 클수록 좋고, 모델의 오차를 사용할 경우 작을수록 좋음)
patience=self.patience, # 학습률을 업데이트하기 전에 몇 번의 에포크를 기다려야 하는지 결정
factor=self.factor, # 학습률을 얼마나 감소시킬지 지정
min_lr=self.min_lr, # 학습률의 하한선
verbose=True # 조기 종료의 시작과 끝을 출력하기 위해 사용(1로 설정할 경우 조기 종료가 적용되면 적용되었다고 화면에 출력, 0으로 설정할 경우 아무런 출력 없이 학습 종료)
)
def __call__(self, val_loss):
self.lr_scheduler.step(val_loss)
- 조기 종료
- 검증 데이터셋에 대한 오차가 정체되기 시작한다면 조기 종료를 고려할 수 있음(조기 종료의 효과를 판단하기 위해서는 정확도뿐만 아니라 오차도 고려해야 함)
class EarlyStopping():
def __init__(self, patience=5, verbose=False, delta=0, path='../chap08/data/checkpoint.pt'):
self.patience = patience # 오차 개선이 없다고 바로 종료하지 않고 개선이 없는 에포크를 얼마나 기다려 줄지 지정
self.verbose = verbose # 조기 종료 동작을 출력할지
self.counter = 0 # 조기 종료 동작이 실행될떄마다 이 변수의 값을 증가시킴
self.best_score = None
self.early_stop = False # 초깃값은 false로 설정
self.delta = delta # 오차가 개선되고 있다고 판단하기 위한 최소 변화량
self.path = path # 모델이 저장될 경로
def __call__(self, val_loss, model):
score = -val_loss
if self.best_score is None: # best_score에 값이 존재하지 않으면 실행
self.best_score = score
self.save_checkpoint(val_loss, model)
elif score < self.best_score + self.delta:
self.counter += 1
print(f'EarlyStopping counter: {self.counter} out of {self.patience}')
if self.counter >= self.patience:
self.early_stop = True
else:
self.best_score = score
self.save_checkpoint(val_loss, model)
self.counter = 0
def save_checkpoint(self, val_loss, model): # 검증 데이터셋에 대한 오차가 감소하면 모델을 저장
if self.verbose:
print(f'Validation loss decreased ({self.val_loss_min:.6f} --> {val_loss:.6f}). Saving model ...')
torch.save(model.state_dict(), self.path)
self.val_loss_min = val_loss
- 아나콘다 프롬프트에서 'python es-python_8장.py --early-stopping'을 실행하면 조기 종료됨
parser = argparse.ArgumentParser() # 인수 값을 받을 수 있는 인스턴스 생성
parser.add_argument('--lr-scheduler', dest='lr_scheduler', action='store_true')
# --lr-scheduler은 옵션 문자열의 이름으로 명령을 실행할 때 사용
# dest은 입력 값이 저장되는 변수이며, 이 예시에서는 lr_scheduler 변수에 입력 값이 저장됨
# action='store_true'은 입력값을 dset 파라미터에 의해 생성된 변수에 저장
parser.add_argument('--early-stopping', dest='early_stopping', action='store_true')
args = vars(parser.parse_args()) # 입력받은 인수값이 실제로 args 변수에 저장
출처: 서지영(2022). 딥러닝 파이토치 교과서. p441-p504.
'파이토치' 카테고리의 다른 글
[딥러닝 파이토치 교과서] 자연어 처리를 위한 임베딩 (0) | 2023.06.18 |
---|---|
[딥러닝 파이토치 교과서] 자연어 전처리 (0) | 2023.06.18 |
[딥러닝 파이토치 교과서] 합성곱 신경망2 (0) | 2023.05.28 |
[파이토치] 신경망 모델 구성/Autograd/최적화 (1) | 2023.05.14 |
[딥러닝 파이토치 교과서] 합성곱 신경망 이해하기 (1) | 2023.05.13 |