no image
MMSeg에서 새로운 증강 기법 만들기 | Custom Transforms
MMSegmentation에 추가하기1. 클래스 생성 MMCV에서 정의된 여러 변형 기법들이 있지만[1], 사용하기에 적합하지 않거나 새로운 기법을 적용하고 싶을 수 있습니다. 이럴 경우 새로운 사용자 정의 변형(transformation) 기법을 만들어 등록하는 과정을 거칩니다. 아래 예시는 프로젝트에서 utils라는 하위 디렉토리를 만들고 그 안에 function.py를 만든 후 RandomSharpen이라는 transformation 클래스를 만들었습니다.  from mmseg.registry import DATASETSfrom .basesegdataset import BaseSegDatasetfrom PIL import ImageEnhance, Image@TRANSFORMS.register_mod..
2024.11.27
no image
MMSeg에서 새로운 데이터셋 정의하기 | Custom Dataset
MMSegmentation에 추가하기1. 데이터셋 추가 새로운 사용자 정의 데이터셋을 mmseg/datasets/example.py로 만듭니다. 새로운 모듈을 추가하기 위해서는 registry에 정의되어 있는 클래스의 register_module을 통해 등록합니다. 여기서는 MMSegmentation에서 제공하고 있는 BaseSegDataset 클래스를 상속받아 작성하는 것을 가정했습니다.  from mmseg.registry import DATASETSfrom .basesegdataset import BaseSegDataset@DATASETS.register_module()class ExampleDataset(BaseSegDataset): METAINFO = dict( classes=..
2024.11.26
no image
MMSeg에서 .py로 정의된 사용자 정의 지표 가져오기 | Custom Metric
적용 방법1. 모듈 추가새로운 지표 클래스(CustomMetric)를 만들어 해당 클래스를 mmseg/evaluation/metrics/custom_metric.py에 두었다고 가정하겠습니다. 새로운 모듈을 추가하기 위해서는 registry에 정의되어 있는 클래스의 register_module을 통해 등하게 됩니다. (일반적으로 데코레이터를 통해 등록을 진행합니다) from typing import List, Sequencefrom mmengine.evaluator import BaseMetricfrom mmseg.registry import METRICS@METRICS.register_module()class CustomMetric(BaseMetric): def __init__(self, arg1..
2024.11.25
앙상블 기법을 딥러닝에서 활용할 수 있게 pytorch로 구현하기 | Voting, Bagging, Stacking
앙상블 기법앙상블(Ensemble)이란 일련의 예측 모델들로부터 예측 결과를 수집하여 더 좋은 예측을 도출하기 위한 방법입니다. 예를 들면, 훈련 데이터셋에서 무작위 다른 부분 데이터셋을 만들어 일련의 Decision Tree를 만들고 개별 트리의 예측을 구하여 종합해 가장 많은 선택을 받은 클래스를 최종 예측을 삼는 방법이 있을 수 있습니다. 여기선 기본이  되는 여러 앙상블 기법들을 살펴보고, 딥러닝에서 어떻게 활용할 수 있을지 간단히 코드를 통해 알아보려고 합니다. 1. 투표 기반여러 분류기들의 예측을 모아서 가장 많이 선택된(투표 결과 높은) 클래스를 예측하는 방법을 직접 투표(hard voting)라고 합니다. 즉, 다수결 투표로 정해지는 것입니다. 머신러닝에서 많이 활용하는 사이킷런에서는 sk..
2024.11.11
no image
PIL을 활용한 이미지 특징(Image attributes) 추출하기 | 이미지 데이터 EDA
PIL(Python Imaging Library)는 파이썬 환경에서 이미지를 처리할 수 있도록 도와주는 대표적인 라이브러리 중 하나입니다. 이미지 읽기, 쓰기, 변환, 조작 등이 가능하며 다양한 이미지 파일 형식을 지원하기 때문에 다양한 곳에서 사용됩니다. 이번 글에서는 기본적으로 PIL이 설치되어 있음을 가정하고, 이미지 데이터에 대한 EDA를 수행하기에 앞서 필요한 이미지 속성들을 추출하는데 집중합니다. PIL에서 지원하고 있는 이미지 특성 추출 기능은 아래와 같습니다. filename : 파일명format : 파일의 형식(JPEG, PNG 등)mode : 이미지의 색상 모드로 픽셀이 표현 방법 (L-그레이, RGB-색상, CMYK-인쇄용 등) size : 픽셀 기준의 이미지 사이즈 (너비, 높이)로..
2024.09.25
no image
인공신경망에서 그래디언트 손실 및 폭주 문제 해결 | 활성화 함수, 가중치 초기화
1. 도입머신러닝에서 학습을 위해 필요한 과정 중 하나로 역전파를 통해 손실함수의 그래디언트를 계산합니다. 이렇게 계산한 값을 활용해 경사 하강법 단계에서 파라미터를 수정하게 되죠. [1] 그런데 여러개의 연산을 쌓아서 만들어진 인공신경망(네트워크)에서 그래디언트가 작아지는 경우가 있습니다. 이렇게 값이 작아진다면 수렴이 되지 않아 원하는 결과를 얻지 못하게 되는데, 이러한 경우를 '그래디언트 소실(vanishing gradient)'이라 합니다. 그와 반대되는 현상으로 그래디언트가 커지면서 파라미터가 수렴하지 않고 발산하게 되는 '그래디언트 폭주(exploding gradient)'도 있습니다. [2] 이렇게 그래디언트가 소멸하거나 폭주하게 되면, 신경망 훈련이 어려워지게 됩니다. 이를 해결하기 위해 ..
2024.08.31
no image
PyTorch로 Binary Classifier 구현하기 | 로지스틱 회귀, 이진 분류
1. 도입선형 분류(Linear Classifier)는 선형 회귀에 있던 것처럼 Wx+b의 선형 방정식 형태로 데이터를 분류하는 것을 말합니다. W를 바꾸게 되면 결정할 수 있는 기준(결정 경계)가 회전하게 되고, b를 조정하게 되면 결정 경계가 위/아래로 이동하게 됩니다. 비교적 단순하게 가중치 W와 편향 b만으로 데이터를 구분할 수 있습니다. 선형 기준(2차원에서는 직선, 3차원에서는 평면)에 따라 나뉘기 때문에 2가지 유형으로 분류가 가능합니다. (Binary Classifier) 하지만, 이 방법의 한계는 결정 경계를 기준으로 구분만 할 뿐 추가적으로 해석할 내용이 없습니다. 2. 로지스틱 회귀 로지스틱 회귀(Logistic Regression 또는 Logit Regression)는 데이터가 어떤..
2024.08.19
no image
PyTorch를 통해 Linear Regression 구현하기
1. 선형 회귀 모델선형 회귀(Linear Regression)는 종속 변수(y)와 하나 이상의 독립 변수(X, 설명 변수)의 상관관계를 설명하는 모델링을 하는 것으로 상관관계를 시각화하였을 때 선형으로 나타나기 때문에 '선형'이라 표현합니다. 선형회귀는 선형성, 독립성, 등분산성, 정규성이라는 4가지 가정을 두고 수행합니다. [1] 선형성 : 종속 변수와 독립 변수의 관계가 선형적 (y = wx + b)독립성 : 데이터들이 무작위로 분포(독립적)되어 있어야 함. 만약 시간의 흐름에 따라 패턴이 있으면 독립성 x등분산성 : 오차(잔차)의 분포가 일정정규성 : 오차가 정규 분포를 따름1.1. 모델 구성선형 회귀는 출력(y)을 계산하기 위해 입력(x)에 가중치(A, 회귀 계수)를 곱하고 편향(bias)을 더..
2024.08.19
no image
머신러닝 과정에서 손실함수와 최적화가 필요한 이유
1. 머신러닝 개요와 모델 기반 학습  머신러닝은 데이터로부터 학습할 수 있도록 컴퓨터를 프로그래밍하는 것을 말합니다. 잘 설계된 머신러닝의 프로세스는 다음과 같이 정의할 수 있습니다. [1] 목표 설정 (Business Goal)머신러닝 문제 정의 (ML Problem Framing)데이터 처리 (Data Processing) : 수집, 전처리, 피처 엔지니어링 등모델 개발 (Model Development) : 훈련, 평가, 튜닝모델 적용 (Deployment) 모델 모니터링 (Monitoring)이중에서 모델 개발에 좀 더 집중해서 살펴보려고 합니다. 모델 개발을 위한 여러가지 접근 방식이 있지만, 그 중에서 아래와 같은 방식으로 진행하는 것을 '모델 기반 학습(model-based learning..
2024.08.17
반응형

MMSegmentation에 추가하기

1. 클래스 생성 

MMCV에서 정의된 여러 변형 기법들이 있지만[1], 사용하기에 적합하지 않거나 새로운 기법을 적용하고 싶을 수 있습니다. 이럴 경우 새로운 사용자 정의 변형(transformation) 기법을 만들어 등록하는 과정을 거칩니다. 아래 예시는 프로젝트에서 utils라는 하위 디렉토리를 만들고 그 안에 function.py를 만든 후 RandomSharpen이라는 transformation 클래스를 만들었습니다. 

 

from mmseg.registry import DATASETS
from .basesegdataset import BaseSegDataset
from PIL import ImageEnhance, Image


@TRANSFORMS.register_module()
class RandomSharpen(BaseTransform):
    def __init__(self, prob=0.5, sharpness_factor=(0.8, 1.2)):
        self.prob = prob
        self.sharpness_factor = sharpness_factor

    def _apply_sharpen(self, img):
        enhancer = ImageEnhance.Sharpness(img)
        factor = np.random.uniform(self.sharpness_factor[0], self.sharpness_factor[1])
        return enhancer.enhance(factor)

    def transform(self, results):
        if np.random.rand() < self.prob:
            img = results['img']
            img_pil = Image.fromarray(img)  # numpy -> PIL
            img_sharpened = self._apply_sharpen(img_pil)
            results['img'] = np.array(img_sharpened)  # PIL -> numpy
        return results

 

새로운 transformation 클래스를 만들 때는 BaseTransform 을 상속받아야만 합니다. 그리고 transform 메서드를 만들어야 하는데, 자세한 내용은 참고 문헌을 참고해주시기 바랍니다. [2]

 

2. 불러오기

새롭게 정의된 클래스를 아래와 같이 가져올 수 있습니다. 

 

from .utils.function import RandomSharpen

 

또는 custom_imports를 통해 불러올 수 있습니다.

 

custom_imports = dict(
    imports=[
        'utils.function'
    ],  
    allow_failed_imports=False 
)

3. 적용하기

import 로 가져온 클래스는 아래 처럼 사용할 수 있습니다.

 

transform = RandomSharpen()
data_dict = {'img': np.random.rand(224, 224, 3)}
data_dict = transform(data_dict)
processed_img = data_dict['img']

 

또는 config를 통해서도 아래와 같이 사용할 수 있습니다.

 

pipeline = [
    ...
    dict(type='RandomSharpen'),
    ...
]

참고자료

[1] https://mmcv.readthedocs.io/en/latest/understand_mmcv/data_transform.html

[2] https://mmcv.readthedocs.io/en/latest/understand_mmcv/data_transform.html#design-of-data-transformation

[3] https://mmsegmentation.readthedocs.io/en/latest/advanced_guides/add_datasets.html

반응형
반응형

MMSegmentation에 추가하기

1. 데이터셋 추가 

새로운 사용자 정의 데이터셋을 mmseg/datasets/example.py로 만듭니다. 새로운 모듈을 추가하기 위해서는 registry에 정의되어 있는 클래스의 register_module을 통해 등록합니다. 여기서는 MMSegmentation에서 제공하고 있는 BaseSegDataset 클래스를 상속받아 작성하는 것을 가정했습니다. 

 

from mmseg.registry import DATASETS
from .basesegdataset import BaseSegDataset


@DATASETS.register_module()
class ExampleDataset(BaseSegDataset):

    METAINFO = dict(
        classes=('xxx', 'xxx', ...),
        palette=[[x, x, x], [x, x, x], ...])

    def __init__(self, arg1, arg2):
        pass

2. 불러오기

새롭게 정의된 데이터셋을 mmseg/datasets/__init__.py에 아래와 같이 추가해줍니다.

 

from .example import ExampleDataset

3. config 파일에 추가

새롭게 만들어진 데이터셋에 대한 config를 configs/_base_/datasets/example_dataset.py에 아래와 같이 작성해서 추가해줍니다. 

 

dataset_type = 'ExampleDataset'
data_root = 'data/example/'

4.정보 추가

메타 데이터(클래스, 팔레트 등)를 mmseg/utils/class_names.py를 통해 추가해줍니다. 

 

def example_classes():
    return [
        'xxx', 'xxx',
        ...
    ]

def example_palette():
    return [
        [x, x, x], [x, x, x],
        ...
    ]
dataset_aliases ={
    'example': ['example', ...],
    ...
}

참고자료

[1] https://mmsegmentation.readthedocs.io/en/latest/advanced_guides/add_datasets.html

반응형
반응형

적용 방법

1. 모듈 추가

새로운 지표 클래스(CustomMetric)를 만들어 해당 클래스를 mmseg/evaluation/metrics/custom_metric.py에 두었다고 가정하겠습니다. 새로운 모듈을 추가하기 위해서는 registry에 정의되어 있는 클래스의 register_module을 통해 등하게 됩니다. (일반적으로 데코레이터를 통해 등록을 진행합니다)

 

from typing import List, Sequence
from mmengine.evaluator import BaseMetric
from mmseg.registry import METRICS


@METRICS.register_module()
class CustomMetric(BaseMetric):

    def __init__(self, arg1, arg2):
    	pass

    def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None:
        pass

    def compute_metrics(self, results: list) -> dict:
        pass

    def evaluate(self, size: int) -> dict:
        pass

 

위 클래스는 BaseMetric이란 것을 상속받아서 만들어지게 되는데, 3가지 메서드(process, compute_metrics, evaluate)를 포함해야 하는데, 여기서는 이에 대한 설명은 생략하도록 하겠습니다. 자세히 알고 싶은 경우 참고문헌의 [1]에서 확인하시기 바랍니다. 

2. 사용자 지표 가져오기

새롭게 정의된 지표를 mmseg/evaluation/metrics/__init__.py에 아래와 같이 추가해줍니다.

 

from .custom_metric import CustomMetric
__all__ = ['CustomMetric', ...]

3. config 파일에 추가

OpenMMLab에서 만든 라이브러리를 사용하면 .py로 쓰여진 Configuration 파일을 만들어야 합니다. (예를 들어, config.py 처럼 말이죠) 해당 파일에 새롭게 정의된 사용자 지표를 아래와 같이 작성해서 config.py에 추가해줍니다. 

 

val_evaluator = dict(type='CustomMetric', arg1=xxx, arg2=xxx)
test_evaluator = dict(type='CustomMetric', arg1=xxx, arg2=xxx)

 

또 다른 방법

위 접근방식은 MMSegmentation에서 제공하고 있는 BaseMetric 등 코드를 활용하는 방법입니다. 만약 다른 방식으로 지표를 정의하고 싶다면 아래와 같은 방식으로 만들 수도 있습니다. 먼저 custom_metric.py를 만듭니다. 여기서 경로는 임의로 'path/custom_metric.py'라고 하겠습니다.

 

from typing import List, Sequence
from mmseg.registry import METRICS


@METRICS.register_module()
class CustomMetric:

    def __init__(self, arg1, arg2):
    	pass

    def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None:
        pass

    def compute_metrics(self, results: list) -> dict:
        pass

    def evaluate(self, size: int) -> dict:
        pass

 

정의된 custom_metric.py를 코드나 config 파일에 불러옵니다. 만약 코드에 직접불러온다면, 아래와 같이 import하면 됩니다.

 

from path import CustomMetric

 

또는 config 파일에 쓰려면 아래와 같이 써주면 됩니다.

 

custom_imports = dict(imports=['/Path/to/metrics'], allow_failed_imports=False)

val_evaluator = dict(type='CustomMetric', arg1=xxx, arg2=xxx)
test_evaluator = dict(type='CustomMetric', arg1=xxx, arg2=xxx)

참고자료

[1] https://mmsegmentation.readthedocs.io/en/latest/advanced_guides/add_metrics.html

 

반응형
반응형

앙상블 기법

앙상블(Ensemble)이란 일련의 예측 모델들로부터 예측 결과를 수집하여 더 좋은 예측을 도출하기 위한 방법입니다. 예를 들면, 훈련 데이터셋에서 무작위 다른 부분 데이터셋을 만들어 일련의 Decision Tree를 만들고 개별 트리의 예측을 구하여 종합해 가장 많은 선택을 받은 클래스를 최종 예측을 삼는 방법이 있을 수 있습니다.

 

여기선 기본이  되는 여러 앙상블 기법들을 살펴보고, 딥러닝에서 어떻게 활용할 수 있을지 간단히 코드를 통해 알아보려고 합니다. 

1. 투표 기반

여러 분류기들의 예측을 모아서 가장 많이 선택된(투표 결과 높은) 클래스를 예측하는 방법을 직접 투표(hard voting)라고 합니다. 즉, 다수결 투표로 정해지는 것입니다. 머신러닝에서 많이 활용하는 사이킷런에서는 sklearn.ensemble에서 VotingClassifer 클래스를 불러오고 voting 매개변수를 'hard'로 불러올 수 있습니다.

from sklearn.ensemble import VotingClassifier

clf1 = LogisticRegression(random_state=1)
clf2 = RandomForestClassifier(n_estimators=50, random_state=1)
clf3 = GaussianNB()

eclf = VotingClassifier(
    estimators=[('lr', clf1), ('rf', clf2), ('gnb', clf3)],
    voting='hard'
)

 

 

사이킷런을 활용하지 않을 경우 hard voting의 원리에 따라 각 결과들을 저장하고 예측 결과(클래스)들에 대해 하나씩 세주는 Counter를 활용해 세주는 방식으로 구현할 수 있습니다. 

import numpy as np
from collections import Counter

def hard_voting_ensemble(classifiers, X):
    predictions = []
    for classifier in classifiers:
        predictions.append(classifier.predict(X))
    
    final_predictions = []
    for sample_predictions in zip(*predictions):
        vote_count = Counter(sample_predictions)
        final_predictions.append(vote_count.most_common(1)[0][0])
    
    return np.array(final_predictions)

 

하지만, 이런 투표방식은 각 클래스의 예측 확률을 반영하지 못해 비교적 낮은 확률이라도 개수가 조금이라도 많으면 채택될 수 있습니다. 확률을 결과값으로 가져올 수 있다면 이들을 평균 내어서 확률이 가장 높은 클래스를 선택할 경우 조금 더 세밀한 앙상블 기법이 될 수 있습니다.

 

이를 간접 투표(soft voting)이라고 하며 위에서 사이킷런을 통해서 구현할 때는 voting만 'soft'로만 바꾸면 됩니다. 하지만, 사이킷런을 사용하지 않을 경우 아래 코드와 같이 사용할 수 있습니다. 

import torch

def soft_voting_ensemble(models, inputs):
    predictions = []
    for model in models:
        model.eval()
        with torch.no_grad():
            output = model(inputs) # 여기서 모델은 확률을 반환
            predictions.append(output)
    
    ensemble_pred = torch.mean(torch.stack(predictions), dim=0)
    return torch.argmax(ensemble_pred, dim=1)

# Ensemble 예측
ensemble_models = [model1, model2, model3]
final_prediction = soft_voting_ensemble(ensemble_models, test_inputs)

2. 배깅과 페이스팅

앞서 다른 알고리즘으로 학습된 결과를 합치는 방법도 있지만, 같은 알고리즘을 통해 훈련할 때 훈련 데이터셋을 무작위로 서브셋을 구성해 모델을 각기 다르게 학습시키고 합치는 방법도 있습니다. 배깅(Bootstrap aggregating; Bagging)은 훈련 데이터셋을 중복 허용해 샘플링하는 방식이고, 중복을 허용하지 않고 샘플링하는 것을 페이스팅(pasting)이라고 합니다. 만약에 한 모델을 위해 적용한다면 배깅이 적합한 방식이라고 합니다. [2]

 

사이킷런으로는 아래와 같이 구현할 수 있습니다. 

from sklearn.ensemble import BaggingClassifier
from sklearn.neighbors import KNeighborsClassifier
bagging = BaggingClassifier(KNeighborsClassifier(),
                            max_samples=0.5, max_features=0.5)

 

또는 딥러닝에서 활용할 수 있도록 pytorch로 구현할 수 있습니다. 

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset, SubsetRandomSampler
import numpy as np

class BaggingEnsemble(nn.Module):
    def __init__(self, base_model, n_estimators, input_size, hidden_size, output_size):
        super(BaggingEnsemble, self).__init__()
        self.base_model = base_model
        self.n_estimators = n_estimators
        self.models = nn.ModuleList([base_model(input_size, hidden_size, output_size) for _ in range(n_estimators)])
    
    def forward(self, x):
        outputs = [model(x) for model in self.models]
        return torch.stack(outputs).mean(dim=0)

def train_bagging(model, train_loader, criterion, optimizer, n_epochs):
    model.train()
    for epoch in range(n_epochs):
        for batch_idx, (data, target) in enumerate(train_loader):
            optimizer.zero_grad()
            output = model(data)
            loss = criterion(output, target)
            loss.backward()
            optimizer.step()

def predict_bagging(model, test_loader):
    model.eval()
    predictions = []
    with torch.no_grad():
        for data, _ in test_loader:
            output = model(data)
            pred = output.argmax(dim=1, keepdim=True)
            predictions.extend(pred.numpy())
    return np.array(predictions)

# 사용 예시
if __name__ == "__main__":
    X = torch.randn(1000, 10)
    y = torch.randint(0, 2, (1000,))
    
    dataset = TensorDataset(X, y)
    train_loader = DataLoader(dataset, batch_size=32, shuffle=True)
    test_loader = DataLoader(dataset, batch_size=32, shuffle=False)
    
    input_size, hidden_size, output_size = 10, 50, 2
    bagging_model = BaggingEnsemble(BaseModel, n_estimators=10, input_size=input_size, hidden_size=hidden_size, output_size=output_size)
    
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(bagging_model.parameters())
    train_bagging(bagging_model, train_loader, criterion, optimizer, n_epochs=5)
    
    predictions = predict_bagging(bagging_model, test_loader)
    print("Predictions shape:", predictions.shape)

 

 

랜덤 포레스트는 배깅이나 페이스팅을 이용해 하나의 베이스모델인 Decision Tree를 적용한 것들의 앙상블입니다. [3] 다만, 딥러닝 보다는 전통적인 머신러닝 기법이니만큼 굳이 pytorch의 필요성이 없기 때문에 참고자료의 내용으로 대신합니다.

3. 스태킹

모든 모델들의 예측을 모으는 과정을 간단함 함수(예를 들어, 투표 방식) 대신에 모델을 통해 앙상블을 수행하는 방식입니다. 개별 모델의 예측은 함께 모여서 마지막 모델에 예측을 위한 입력으로 사용되며, 마지막 모델은 교차 검증(cross-validation)을 통해 학습됩니다. 여기서 마지막 모델은 블렌더(Blender) 또는 메타 학습기(Meta learner)라고 불립니다.

 

사이킷런을 사용한다면 StackingClassifier 또는 StackingRegressor를 사용하면 되지만, 딥러닝은 조금 더 구현이 필요합니다. 메타 학습기(메타 모델)을 구현하고, 기존 모델 예측값을 합친 후에 다시 메타 모델에 넣어서 최종 예측값을 도출하는 과정이 추가되면 됩니다. 다소 길긴 하지만, 아래처럼 구현할 수 있습니다. 

import torch
import torch.nn as nn
import torch.optim as optim

class BaseModel(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(BaseModel, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_size)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(hidden_size, output_size)
    
    def forward(self, x):
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)
        return x

class MetaModel(nn.Module):
    def __init__(self, input_size, output_size):
        super(MetaModel, self).__init__()
        self.fc = nn.Linear(input_size, output_size)
    
    def forward(self, x):
        return self.fc(x)

def train_model(model, X, y, epochs=100):
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters())
    
    for epoch in range(epochs):
        optimizer.zero_grad()
        outputs = model(X)
        loss = criterion(outputs, y)
        loss.backward()
        optimizer.step()

def stacking_ensemble(X_train, y_train, X_test):
    model1 = BaseModel(20, 64, 2)
    model2 = BaseModel(20, 32, 2)
    model3 = BaseModel(20, 16, 2)
    
    train_model(model1, X_train, y_train)
    train_model(model2, X_train, y_train)
    train_model(model3, X_train, y_train)
    
    with torch.no_grad():
        pred1 = model1(X_train)
        pred2 = model2(X_train)
        pred3 = model3(X_train)
        
        test_pred1 = model1(X_test)
        test_pred2 = model2(X_test)
        test_pred3 = model3(X_test)
    
    meta_input = torch.cat((pred1, pred2, pred3), dim=1)
    meta_test_input = torch.cat((test_pred1, test_pred2, test_pred3), dim=1)
    
    meta_model = MetaModel(6, 2)  # 6 = 2 (클래스 수) * 3 (기본 모델 수)
    train_model(meta_model, meta_input, y_train)
    
    with torch.no_grad():
        final_pred = meta_model(meta_test_input)
    
    return final_pred

ensemble_pred = stacking_ensemble(X_train, y_train, X_test)
ensemble_pred = torch.argmax(ensemble_pred, dim=1).numpy()

 

참고자료

[1] 오렐리앙 제롱. "핸즈온 머신러닝 2판"

[2] https://scikit-learn.org/stable/modules/ensemble.html#bagging

[3] https://scikit-learn.org/stable/modules/ensemble.html#forest

[4] https://scikit-learn.org/stable/modules/ensemble.html#stacked-generalization

 

 

 

 

반응형
반응형

PIL(Python Imaging Library)는 파이썬 환경에서 이미지를 처리할 수 있도록 도와주는 대표적인 라이브러리 중 하나입니다. 이미지 읽기, 쓰기, 변환, 조작 등이 가능하며 다양한 이미지 파일 형식을 지원하기 때문에 다양한 곳에서 사용됩니다. 이번 글에서는 기본적으로 PIL이 설치되어 있음을 가정하고, 이미지 데이터에 대한 EDA를 수행하기에 앞서 필요한 이미지 속성들을 추출하는데 집중합니다.

 

PIL에서 지원하고 있는 이미지 특성 추출 기능은 아래와 같습니다. 

  • filename : 파일명
  • format : 파일의 형식(JPEG, PNG 등)
  • mode : 이미지의 색상 모드로 픽셀이 표현 방법 (L-그레이, RGB-색상, CMYK-인쇄용 등) 
  • size : 픽셀 기준의 이미지 사이즈 (너비, 높이)로 반환 
  • info : 파일에 포함하고 있는 메타 데이터
  • is_animated : 애니매이션 여부를 True / False 로 반
  • n_frames : 애니메이션 이미지(GIF 등)에서 총 프레임 수, 기본 이미지는 1

사진 이미지 데이터를 가져온다고 가정하고 간단하게 아래와 같이 구현할 수 있습니다.

 

from PIL import Image

# path는 이미지의 경로 예를 들어 ./class1/image1.jpg 형태
img = Image.open(path)
width, height = img.size
mode = img.mode
format = img.format

 

관련해서 자세한 내용의 원문은 아래 링크를 참고바랍니다.

https://pillow.readthedocs.io/en/stable/reference/Image.html#image-attributes

 

또한, 이미지의 주요 특징 중 하나인 색상은 파일에서 3 채널(RGB 기준)로 구현됩니다. 저는 여기서 두가지 방법을 소개하고자 합니다. 첫번째는 .split()으로 나누는 방법, 두번째는 슬라이싱으로 구현하는 방법입니다. split() 메서드는 이미지를 각 색상 채널로 분리한 후, 각 채널을 개별 이미지 객체로 반환합니다. 여기서 각 채널은 별도의 그레이 스케일 이미지로 반환됩니다. 아래는 두 방법의 코드입니다. 

 

from PIL import Image

img = Image.open('/content/example.jpg')
r, g, b = img.split()
red1 = np.array(r)
red2 = np.array(img)[:,:,0]
print(red1)
print(red2)

 

 

이러한 여러 이미지 속성들과 이들을 조합한 데이터로 EDA에 쓸 수 있는 코드를 아래와 같이 모았습니다. 필요할 경우 함수로 구현해서 사용할 수 있습니다. 

 

from PIL import Image

with Image.open(image_path) as img:
    img = img.convert('RGB') # 이미지를 RGB 모드로 변환
    width, height = img.size
    img_array = np.array(img)
    mean_red = np.mean(img_array[:, :, 0])
    mean_green = np.mean(img_array[:, :, 1])
    mean_blue = np.mean(img_array[:, :, 2])
    format = image_path.split('.')[-1].upper()
    image_aspect_ratio = img.width / img.height

 

 

반응형
반응형

1. 도입

머신러닝에서 학습을 위해 필요한 과정 중 하나로 역전파를 통해 손실함수의 그래디언트를 계산합니다. 이렇게 계산한 값을 활용해 경사 하강법 단계에서 파라미터를 수정하게 되죠. [1]

 

그런데 여러개의 연산을 쌓아서 만들어진 인공신경망(네트워크)에서 그래디언트가 작아지는 경우가 있습니다. 이렇게 값이 작아진다면 수렴이 되지 않아 원하는 결과를 얻지 못하게 되는데, 이러한 경우를 '그래디언트 소실(vanishing gradient)'이라 합니다. 그와 반대되는 현상으로 그래디언트가 커지면서 파라미터가 수렴하지 않고 발산하게 되는 '그래디언트 폭주(exploding gradient)'도 있습니다. [2]

 

이렇게 그래디언트가 소멸하거나 폭주하게 되면, 신경망 훈련이 어려워지게 됩니다. 이를 해결하기 위해 다양한 방법들이 연구되었는데, 여기서는 두가지만 우선 소개합니다.

 

2. 활성화 함수

활성화 함수는 인공 신경망의 각 요소들에서 함수를 언제 활성화할 것인지에 대해 결정하는 함수를 말합니다. 활성화 함수 사용가능한 것들은 시그모이드 함수(로지스틱), 하이퍼볼릭 탄젠트 함수, ReLU (Rectified Linear Unit) 함수가 있습니다. 종류는 다양하지만, 활성화 함수를 잘못 선택할 경우 그래디언트 소실이나 폭주 현상이 일어날 수 있어 주의가 필요합니다.

 

여러 연구를 거치면서 ReLU 함수가 특정 양수로 수렴하지 않는 것이 밝혀졌고, 지수함수를 사용하는 다른 함수에 비해 1차 함수이기 때문에 연산도 빠릅니다. 여러 장점이 있지만, 음수 영역에서 0을 출력하는 Dead ReLU 문제를 발생하기 때문에 음수영역에 약간의 기울기(예 : 0.01)를 주는 Leaky ReLU, 음수 영역에서 지수함수를 포함한 ELU(Exponential Linear Unit)을 통해 해결이 가능합니다. [1]

 

파이토치에선 nn 모듈에서 다양한 활성화함수를 활용할 수 있습니다.

 

import torch.nn as nn

relu = nn.ReLU()
leaky_relu = nn.LeakyReLU(negative_slope = 0.01)
elu = nn.ELU(alpha = 1.1)

 

3. 가중치 초기화

이전 머신러닝 사이클에서 모델 기반 학습의 두번째 단계로 '무작위로 파라미터 설정'(여기서 부터는 '초기화')을 얘기한 바 있습니다. [1] 다른 단계들은 한번씩 논의가 됐지만, 이 단계에 대해서는 따로 다룬 적이 없지만 올바른 학습을 위해서는 더 나은 초기화가 이뤄져야 합니다.

 

만약 초기화 값을 np.random.randn 처럼 정규분포에 맞춰서 데이터를 생성하게 되면 어떻게 될까요? 아래와 같이 값들이 0과 1에 치우쳐서 분포하게 됩니다. 이렇게 될 경우 활성화 함수(예를 들어, 시그모이드 함수)의 값이 0이 되기 때문에 기울기가 없어지는 문제가 생깁니다. 이를 그래디언트 소실(vanishing gradient) 문제라고 합니다.  

 

 

활성화 값 분포 [3]

 

그렇다면, 그래디언트 소실 문제를 해결하려면 어떻게 해야 할까요? 대표적으로 많이 사용하는 방법은 Xavier 초기화, He 초기화(또는 Kaming 초기화. Kaming He의 이름.)를 활용하는 것 입니다. 파이토치에서는 torch.nn.init을 통해 지원하고 있습니다. 

 

import torch

# Xavier initialization
torch.nn.init.xavier_normal(tensor)
torch.nn.init.xavier_uniform_(tensor)

# He initialization
torch.nn.init.kaming_normal(tensor)
torch.nn.init.kaming_uniform(tensor)

 

여기서 normal과 uniform의 차이는 가중치가 초기화될 때 값들의 분포를 결정하는 차이입니다. normal은 정규 분포에서 샘플링된 값으로 초기화되며, 대부분의 값이 평균 근처에 모이게 되고, uniform은 특정 범위에서 고르게 분포하게끔 만듭니다.

 

Xavier 초기화와 He 분포의 선택 기준은 다음과 같습니다. [2]  

초기화 방법 활성화 함수
Xavier 대칭적 활성화 함수 (tanh, Sigmoid, Softmax)
He 비대칭적 함수 (ReLU, Leaky ReLU 등 변종 함수)

 

 

4. 참고자료

[1] https://seanpark11.tistory.com/109

[2] 오렐리앙 제롱. 『핸즈온 머신러닝 (제2판)』

[3] Fei-Fei, Krishna, Xu. CS231n Lecture. https://cs231n.stanford.edu/slides/2023/lecture_7.pdf

[4] https://pytorch.org/docs/stable/nn.init.html

반응형
반응형

1. 도입

선형 분류(Linear Classifier)는 선형 회귀에 있던 것처럼 Wx+b의 선형 방정식 형태로 데이터를 분류하는 것을 말합니다. W를 바꾸게 되면 결정할 수 있는 기준(결정 경계)가 회전하게 되고, b를 조정하게 되면 결정 경계가 위/아래로 이동하게 됩니다. 비교적 단순하게 가중치 W와 편향 b만으로 데이터를 구분할 수 있습니다. 선형 기준(2차원에서는 직선, 3차원에서는 평면)에 따라 나뉘기 때문에 2가지 유형으로 분류가 가능합니다. (Binary Classifier) 하지만, 이 방법의 한계는 결정 경계를 기준으로 구분만 할 뿐 추가적으로 해석할 내용이 없습니다. 

2. 로지스틱 회귀 

로지스틱 회귀(Logistic Regression 또는 Logit Regression)는 데이터가 어떤 클래스에 속할지에 대한 확률을 계산해줍니다. 그리고 추정 확률이 50%가 넘을 경우 해당 데이터가 클래스에 속한다고 예측하는 모델입니다. 방법은 아래와 같이 진행합니다. [1]

 

1) 선형식에 대한 계산

2) 시그모이드 함수를 통해 계산값을 0~1 사이의 확률로 변환

3) 변환된 확률이 0.5를 기준으로 이상이면 1, 아니면 0으로 정의 

 

위 방법에서 시그모이드 함수부터 살펴보겠습니다.

2.1. 시그모이드 함수

시그모이드 함수는 S자형 곡선(시그모이드 곡선)을 갖는 함수로, 실수 전체를 정의역으로 갖고 함수값은 단조증가(감소)하는 형태를 갖습니다. 대표적인 시그모이드 함수는 로지스틱 함수, 쌍곡탄젠트 함수, 아크탄젠트 함수 등이 있습니다. [2]

 

로지스틱 함수
쌍곡탄젠트 함수
아크탄젠트 함수

 

로지스틱 함수에 대해 조금 더 살펴보자면, 소프트맥스 함수의 특수한 상황이기도 합니다. 즉, 0/1로 이진 분류하는 경우에는 로지스틱 함수를 쓸 수 있지만 클래스가 더 많은 경우로 일반화하면 소프트맥스 함수를 생각해볼 수 있을 것 입니다.

 

소프트맥스 함수 [3]

2.2. 로지스틱 함수의 손실 함수

여러 시그모이드 함수 중 로지스틱 회귀에서 사용할 로지스틱 함수의 손실 함수에 대해 살펴보겠습니다. (머신러닝을 활용하려면 손실 함수가 필요함을 얘기한 적 있습니다 [4])

 

 

머신러닝 과정에서 손실함수와 최적화가 필요한 이유

1. 머신러닝 개요와 모델 기반 학습  머신러닝은 데이터로부터 학습할 수 있도록 컴퓨터를 프로그래밍하는 것을 말합니다. 잘 설계된 머신러닝의 프로세스는 다음과 같이 정의할 수 있습니다.

seanpark11.tistory.com

 

로지스틱 함수의 손실함수로 사용될 수 있는 Binary Cross Entropy (BCE) 함수는 아래와 같이 유도될 수 있다. (좀 더 자세한 설명은 그 아래 링크의 블로그 글을 참고)

 

로그 손실 [5]

 

[손실함수] Binary Cross Entropy

확률, 정보이론 관점에서 살펴보는 Binary Cross Entropy 함수

curt-park.github.io

3. 파이토치로 모델 구축

이전에 만들었던 선형회귀 모델[4]에서 손실함수와 모델에 시그모이드가 추가된 사실을 제외하면 거의 유사합니다. 이중에서 조금 다른 부분인 Sigmoid에 대해서만 살펴보고자 합니다.

3.1. Sigmoid in PyTorch  

파이토치에서 sigmoid는 요소마다 로지스틱 함수를 적용하는 형태로 제공하고 있습니다. torch.nn.Sigmoid 클래스, torch.nn.functional.sigmoid 함수로 사용 가능합니다.

 

import torch
import torch.nn as nn

sigmoid = nn.Sigmoid()
input = torch.randn(2)
output = sigmoid(input)

3.2.  최종 모델

입력 데이터(x)와 레이블(y)을 제외하고는 아래와 같이 구현할 수 있습니다. 

 

import torch.nn as nn
import torch.optim as optim

# 선형 회귀 모델 클래스 
class BinaryClassification(nn.Module):
    def __init__(self, input_dim, output_dim):
        super(BinaryClassification, self).__init__()
        self.layer_1 = nn.Linear(input_dim, output_dim)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        y = self.layer_1(x)
        z = self.sigmoid(y)
        return z

model = BinaryClassificationRegression(1, 1)

# 손실함수 
criterion = nn.BCELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 모델 학습
# x,y 는 실제값으로 입력되어 있다고 가정 
epochs = 100
for epoch in range(epochs):
    y_hat = model(x)
    loss = criterion(y_hat, y)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

4. 참고자료

[1] 오렐리앙 제롱, 『핸즈온 머신러닝 (2판)』

[2] Wikipedia. (2024). "시그모이드 함수".  https://ko.wikipedia.org/wiki/%EC%8B%9C%EA%B7%B8%EB%AA%A8%EC%9D%B4%EB%93%9C_%ED%95%A8%EC%88%98

[3] Wikipedia. (2024). "Softmax function"  https://en.wikipedia.org/wiki/Softmax_function

[4] https://seanpark11.tistory.com/109

[5] https://developers.google.com/machine-learning/crash-course/logistic-regression/loss-regularization?hl=ko

 

반응형
반응형

1. 선형 회귀 모델

선형 회귀(Linear Regression)는 종속 변수(y)와 하나 이상의 독립 변수(X, 설명 변수)의 상관관계를 설명하는 모델링을 하는 것으로 상관관계를 시각화하였을 때 선형으로 나타나기 때문에 '선형'이라 표현합니다. 선형회귀는 선형성, 독립성, 등분산성, 정규성이라는 4가지 가정을 두고 수행합니다. [1] 

  • 선형성 : 종속 변수와 독립 변수의 관계가 선형적 (y = wx + b)
  • 독립성 : 데이터들이 무작위로 분포(독립적)되어 있어야 함. 만약 시간의 흐름에 따라 패턴이 있으면 독립성 x
  • 등분산성 : 오차(잔차)의 분포가 일정
  • 정규성 : 오차가 정규 분포를 따름

1.1. 모델 구성

선형 회귀는 출력(y)을 계산하기 위해 입력(x)에 가중치(A, 회귀 계수)를 곱하고 편향(bias)을 더해서 조정하는 방식으로 계산되며, 수식로 표현하면 다음과 같습니다

 

 

파이토치에서는 torch.nn.Module 상속을 통해 파이토치에서 이미 구현된 기능을 사용할 수 있습니다. 아래 코드는 선형 회귀 클래스(LinearRegression)를 만들어서 순전파까지 구현한 것입니다. 

 

import torch.nn as nn
import torch.optim as optim

# 선형 회귀 모델 클래스 
class LinearRegression(nn.Module):
    def __init__(self, input_size, output_size):
        super(LinearRegression, self).__init__() # 상속받은 nn.Module 초기화 
        self.linear = nn.Linear(input_size, output_size) # linear 속성 정의 : 선형 변환

    def forward(self, x): # 순전파 연산 정의 : x를 입력 받아 linear 속성에 y를 계산
        y = self.linear(x) 
        return y

model = LinearRegression(1, 1) # 모델 인스턴스 생성. (1, 1)

 

1. 2. 손실함수와 최적화

머신러닝에서 학습은 손실값을 줄이는 방향으로 파라미터를 업데이트 합니다. 선형 회귀 모델에서는 손실함수가 MSE가 되고, 파이토치에서는 torch.nn.MSELoss()로 구현할 수 있습니다. [2]

 

MSE에 대한 이전 글 : https://seanpark11.tistory.com/109#3.1.-mse

 

머신러닝 과정에서 손실함수와 최적화가 필요한 이유

1. 머신러닝 개요와 모델 기반 학습  머신러닝은 데이터로부터 학습할 수 있도록 컴퓨터를 프로그래밍하는 것을 말합니다. 잘 설계된 머신러닝의 프로세스는 다음과 같이 정의할 수 있습니다.

seanpark11.tistory.com

 

또한, 선형 회귀 모델에서 학습은 경사하강법을 사용하게 되는데, 전체 데이터셋에 대해서 적용하기 보다는 확률적 경사하강법으로 조금 더 효율적으로 이용이 가능합니다. 확률적 경사하강법은 아래와 같은 절차로 이뤄집니다.

 

1) 이전 단계에서 계산된 그래디언트 초기화

2) 손실 함수의 그래디언트 계산

3) 경사하강법 공식에 따라 파라미터 업데이트 

 

또한, 확률적 경사하강법을 수행하는데 있어 같은 데이터셋을 여러 번 학습하여 모델 파라미터를 업데이트할 수 있습니다. 이렇게 모델이 전체 데이터셋을 학습하는 과정을 '에폭'이라고 하며, 여러 번의 에폭을 통해 모델의 성능이 향상될 수 있습니다.  

 

criterion = nn.MSELoss() # 손실 함수 정의 
optimizer = optim.SGD(model.parameters(), lr=0.01) # 최적화 정의

# 모델 학습
# x,y 는 실제값으로 입력되어 있다고 가정 
epochs = 100
for epoch in range(epochs):
    y_hat = model(x)
    loss = criterion(y_hat, y) # 손실값 계산
    optimizer.zero_grad() # 그래디언트 초기화
    loss.backward() # 손실함수 그래디언트 계산 (역전파)
    optimizer.step() # 계산된 그래디언트로 가중치 업데이트 : 위에서 SGD 정의로 최적화 진행

 

1.3. 최종 코드

이렇게 작성된 전체 코드는 아래와 같습니다. 입력값(x)과 목표값(y)은 별도로 정의해서 진행하면 됩니다. 

 

import torch.nn as nn
import torch.optim as optim

# 선형 회귀 모델 클래스 
class LinearRegression(nn.Module):
    def __init__(self, input_dim, output_dim):
        super(LinearRegression, self).__init__()
        self.linear = nn.Linear(input_dim, output_dim)

    def forward(self, x):
        y = self.linear(x_tensor)
        return y

model = LinearRegression(1, 1)

# 손실함수 
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 모델 학습
# x,y 는 실제값으로 입력되어 있다고 가정 
epochs = 100

for epoch in range(epochs):
    y_hat = model(x)
    loss = criterion(y_hat, y)
    
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

 

 

참고자료

[1] https://ko.wikipedia.org/wiki/%EC%84%A0%ED%98%95_%ED%9A%8C%EA%B7%80

[2] https://seanpark11.tistory.com/109

 

 

반응형
반응형

1. 머신러닝 개요와 모델 기반 학습 

 

머신러닝은 데이터로부터 학습할 수 있도록 컴퓨터를 프로그래밍하는 것을 말합니다. 잘 설계된 머신러닝의 프로세스는 다음과 같이 정의할 수 있습니다. [1]

 

  • 목표 설정 (Business Goal)
  • 머신러닝 문제 정의 (ML Problem Framing)
  • 데이터 처리 (Data Processing) : 수집, 전처리, 피처 엔지니어링 등
  • 모델 개발 (Model Development) : 훈련, 평가, 튜닝
  • 모델 적용 (Deployment) 
  • 모델 모니터링 (Monitoring)

이중에서 모델 개발에 좀 더 집중해서 살펴보려고 합니다. 모델 개발을 위한 여러가지 접근 방식이 있지만, 그 중에서 아래와 같은 방식으로 진행하는 것을 '모델 기반 학습(model-based learning)'이라고 합니다. [2]

 

1) 모델 설정

2) 무작위로 파라미터 설정

3) 설정된 파라미터에 따라 예측값 계산

4) 예측값과 실제값의 차이 계산 - 손실함수

5) 손실 값이 작아지도록 파라미터 업데이트 - 최적화

6) 충분히 작아질 때까지 3~5 반복 

 

위 과정에서 손실함수와 최적화에 대해 조금 더 자세히 살펴보려고 합니다. 

2. torch.nn

PyTorch에서는 이러한 모델 기반 학습을 지원할 수 있도록 계산 그래프를 쌓을 수 있는 torch.nn을 제공합니다. 아래 사진에서 보이는만큼 머신러닝에 필요한 여러가지 기능을 가진 계산 그래프를 제공하고 있기 때문에 실제로 모든 것을 구현할 필요는 없습니다. 

 

torch.nn에서 제공하고 있는 다양한 블럭들 ('24.8.16 캡처)

 

특히, 손실함수는 torch.nn에 자체 클래스 또는 torch.nn.functional 함수로 제공하고 있고, 최적화는 torch.optim에 함수 형태로 이용 가능합니다.

3. 손실함수

손실함수는 머신러닝 모델이 얼마나 좋은지, 나쁜지를 정량화하는 함수로 모델을 통해 도출된 추정(예측)값과 기준(실제)값이 얼마나 다른지에 따라 모델에 페널티를 주는 정도를 다르게 합니다. 대표적인 손실함수는 MSE, Cross Entropy, 쿨백-라이블러 발산 등이 있습니다. 

3.1. MSE

평균제곱오차(Mean Squared Error, MSE)는 추정값과 기준값의 차이의 제곱의 평균입니다. 일반적인 수식은 쉽게 찾아볼 수 있으니, 익숙해져야 하는 행렬식으로 표현하면 아래와 같습니다. 여기서 e는 오차들로 이뤄진 벡터입니다. [3]

 

 

PyTorch에서 MSE를 계산하는 방법은 nn모듈에서 MSELoss 클래스를 사용하거나, nn.functional의 함수를 사용하면 됩니다.

 

nn.MSELoss()
nn.functional.mseloss()

 

 

3.2. Cross Entropy

크로스엔트로피(Cross Entropy)는 정보이론에서 두 확률분포를 구분하는데 필요한 평균비트 수를 측정하는 것을 말합니다. 특별한 경우로 0/1과 같이 이진 문제에 대해서는 Binary Cross Entropy(BCE)가 존재합니다. BCE에 대한 정의는 아래와 같습니다. [4]

 

 

PyTorch에서 Cross Entropy와 BCE를 계산하는 방법은 아래와 같습니다.

 

# Cross Entropy
nn.functional.cross_entropy()

# BCE
nn.BCELoss()
nn.functional.binary_cross_entropy()

 

4. 최적화

손실값이 가장 최소가 되는 순간은 미분적분 과목을 돌이켜볼 때 손실함수의 미분값, 즉 손실함수의 그래디언트가 0인 순간입니다. 최적화는 손실함수의 그래디언트가 0이 될 수 있도록 만드는 일련의 알고리즘으로 다양한 방식이 개발되어 왔습니다. 이 글에서는 경사하강법과 역전파를 우선 살펴보고자 합니다. 

 

파이토치에서는 torch.optim 패키지를 통해 최적화 알고리즘을 지원하고 있습니다. torch.optim에 각 알고리즘 클래스를 해 적용이 가능합니다. 최적화는 아래 코드와 같이 일련의 과정이 필요한데, 각 코드의 세부 의미는 '4.2. 역전파'의 과정입니다. [5]

 

# Simple verison
for input, target in dataset:
    optimizer.zero_grad() # 이전 단계에 계산된 그래디언트 초기화
    output = model(input) 
    loss = loss_fn(output, target)
    loss.backward() # 자동 미분 수행
    optimizer.step() # 그래디언트 값을 통해 업데이트
    
# Clousre
for input, target in dataset:
    def closure():
        optimizer.zero_grad()
        output = model(input)
        loss = loss_fn(output, target)
        loss.backward()
        return loss
    optimizer.step(closure)

 

4.1. 경사하강법

경사하강법(Gradient Descent)은 손실함수의 그래디언트(쉽게 말해 미분값)를 0이 되는 방향으로 계산하는 최적화 알고리즘입니다. 그림의 식은 경사하강법의 계산식이며,다음과 같은 방법으로 계산됩니다. 

 

경사하강법의 계산식 [6]

 

1) 임의의 값(첫번째)의 그래디언트 계산

2) 그래디언트에 계수(학습률, learning rate)를 곱한 것을 그 값에서 빼기

3) 뺀 값을 다음 값으로 1~2과정을 그래디언트가 0이 될 때까지 계산

 

하지만, 일반적으로 전체 훈련 세트를 대상으로 위의 알고리즘을 수행하는 것은 (데이터가 많다면) 매우 시간이 많이 걸릴 수 있습니다. 이를 해결하기 위해 제안된 확률적 경사하강법(Stochastic Gradient Descent, SGD)은 무작위로 샘플을 선택하고 그 샘플에 대해 그래디언트를 계산하는 것입니다. 파이토치 코드는 아래같이 구현됩니다. (세부 설명 링크)

 

torch.optim.SGD(params, lr, ...)

 

4.2. 역전파

오늘날의 신경망 모델들은 여러 연산(선형 방정식, 비선형 활성화 함수 등)들을 겹겹이 쌓은 계산 그래프를 네트워크 형태로 구축하고 있습니다. (다층 퍼셉트론이라고도 합니다.) 경사하강법은 하나의 손실함수에 대해서는 최적화가 가능하지만, 이렇게 쌓여있는 다층 퍼셉트론을 훈련하기에는 연산이 너무 많아질 수 있기 때문에 최적화 알고리즘으로는 적절하지 않습니다. 

 

이러한 문제를 해결하기 위해 제안된 역전파(Backpropagation)는 업그레이드된 경사하강법으로 출력값을 계산하는 정방향(forward), 오차 그래디언트를 계산하는 역방향(backward) 두가지 방향의 연산을 수행해 최적화합니다.   

 

1) 임의의 값에 대한 예측값을 계산 (정방향)

2) 계산 그래프의 역으로 그래디언트를 곱하고 값을 대입하여 각 단계의 그래디언트 계산

3) 2)를 반복하면서 최종 단계인 손실함수의 그래디언트까지 계산 (역방향)

4) 계산된 그래디언트 값으로 경사하강법을 수행해 값 업데이트 

 

(사실 이 과정을 글로 이해하는 것보다는 계산 그래프를 보면서 이해하는 것이 훨씬 나은데, 스탠포드 CS231n 강의자료가 제일 잘 이해되는 것 같아서 링크를 참고하시기 바랍니다.[7])

 

역전파는 각 노드별로 다양한 연산을 만들어야 하기 때문에 꽤 구조화된 프로그래밍이 필요합니다. 하지만, 파이토치에서는 아래와 같이 자동미분 연산을 지원하고 있습니다.

 

pred = model(data) # 정의된 model에 따른 예측값 (Forward pass 수행)
loss = (pred - label).sum() # 별도 정의한 손실함수 MSE, BCE 등 다른 것을 사용해도 됨
loss.backward() # Backward pass 수행
torch.optim.SGD(param, lr,...).step() # 경사하강법 수행. 결과값은 .grad에 저장

 

 

 

5. 참고자료

[1] https://docs.aws.amazon.com/wellarchitected/latest/machine-learning-lens/well-architected-machine-learning-lifecycle.html

[2] 오렐리앙 제롱, 『핸즈온 머신러닝 (2판)』

[3] https://en.wikipedia.org/wiki/Mean_squared_error

[4] https://en.wikipedia.org/wiki/Cross-entropy

[5] https://pytorch.org/docs/stable/optim.html

[6] https://ko.wikipedia.org/wiki/%EA%B2%BD%EC%82%AC_%ED%95%98%EA%B0%95%EB%B2%95

[7] https://cs231n.stanford.edu/slides/2018/cs231n_2018_ds02.pdf

 

 

반응형