안경잡이개발자

728x90
반응형

  기본적으로 외부에 공개되면 안 되는 중요한 코드는 깃허브(GitHub) private repository에 저장하는 것이 일반적이다. 당연히 오픈소스 형태로 외부에 공개된 public repository라면 git clone 명령어를 이용해 전체 소스코드를 가져올 수 있지만, private repository에 저장된 소스코드는 가져오기 어렵다. private repository의 경우 SSH key를 이용해 소스코드를 가져오는 것이 일반적인데, 당연히 깃허브(GitHub) 내에서 SSH 키(key) 설정을 진행해야 한다.

 

  구체적인 과정은 ① SSH 개인키와 공개키를 생성한 뒤에, ② 내 GitHub에 SSH 공개키를 등록하는 것이다. 그러면 나중에 SSH 개인키를 이용해 내 GitHub에 접근할 수 있다. 리눅스에서 SSH 키를 생성하는 방법은 간단하다. ssh-keygen 명령어를 입력하면 된다.

 

 

  이제 리눅스에서 자신의 홈 디렉터리(home directory)에 다음과 같이 개인키와 공개키가 생성된다.

 

 

  여기에서 .pub 확장자가 붙은 것이 공개키이므로, 이 파일의 내용을 복사하여 GitHub에 넣어주면 된다. 이때 개인키가 유출되지 않도록 조심해야 한다.

 

 

  결과적으로 이러한 공개키(public key)의 내용을 그대로 특정한 저장소(repository)의 keys 설정 페이지에 넣어주면 된다. 다음과 같이 [Key] 탭에 붙여넣기 하여 [Add key] 버튼을 누르면 등록된다. 참고로 특정한 private repository의 keys 설정 페이지는 다음의 경로에서 확인할 수 있다.

 

https://github.com/{사용자 이름}/{저장소 이름}/settings/keys

 

 

  그러면 결과적으로 다음과 같이 키가 등록된 것을 알 수 있다.

 

 

  이제 특정 리눅스 컴퓨터에서 GitHub에 접속하여 private repository의 코드를 받아오고자 한다면 환경 설정 목적으로 다음과 같은 명령어를 입력할 수 있다. 다음과 같이 해당 SSH 키를 이용해 접속할 수 있도록 등록하는 것이다.

 

ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
ssh -T git@github.com

 

  이후에 다음과 같이 git clone 명령어를 이용하여 소스코드를 받아오면 된다.

 

git clone git@github.com:{사용자 이름}/{저장소 이름}

 

  필자의 경우 private repository인 Watermarking-Deepfakes라는 이름의 저장소에 있는 코드를 받아왔다.

 

728x90
반응형

Comment +0

728x90
반응형

  믹싱(mixing) 과정에서 굉장히 많이 사용되는 도구로는 컴프레서(compressor)가 있다. 일반적으로 압축기라고 불리기도 하는 이 도구는, 보컬 믹싱이나 강의 영상 믹싱 과정에서 매우 많이 사용된다. 흔히 보컬에서는 컴프레서를 이용해 큰 목소리로 내는 소리와 작은 목소리로 내는 소리의 차이가 덜 나도록 만드는 것이 일반적이다. 당연히 듣는 입장에서 편하게 노래를 들을 수 있을 것이다. 더불어 컴프레서를 사용하여 threshold를 조절하면 체감상 단단한 목소리가 나는 것처럼 느껴진다. 그래서 목소리가 더 명료하게 만들어진다.

 

 

  [효과] 탭으로 이동한 뒤에 멀티밴드 압축기(Multi-band Compressor)를 선택한다.

 

 

  이후에 [효과 컨트롤] 탭으로 이동하여 [편집]을 열어 구체적인 설정을 진행할 수 있다.

 

 

  기본적으로 많이 사용되는 설정은 미리 준비되어 있다. 예를 들어 내가 강의 영상을 제작할 때 많이 사용하는 사전 설정으로는 [거친 음 제거], [저음 향상], [팝 마스터]가 있다. 강의 내용이 너무 졸리다 싶으면 고음을 강조하기 위해 [고음 향상]을 선택하는 경우도 있다. 직접 다양한 설정을 선택해 본 뒤에 가장 마음에 드는 적절한 설정을 선택하면 된다.

 

 

  또한 추가적으로 노이즈 제거(Denoiser)를 적용하는 것도 좋다. 강의를 찍다 보면 다양한 노이즈(noise)가 끼게 되는 경우가 많기 때문이다. 다음과 같이 [효과] 탭에서 [노이즈 제거]를 선택한다.

 

 

  이후에 필자의 경우 5%에서 10% 정도의 강도를 적용하는 경우가 많다. 너무 강도를 높이면 많은 양의 노이즈가 목소리와 함께 제거될 수 있기 때문이다.

 

 

  참고로 이러한 오디오 관련 기능을 적용하는 경우 인코딩(encoding) 속도가 현저하게 느려질 수 있다.

 

728x90
반응형

Comment +0

728x90
반응형

  아래 코드는 PyTorch에서 ImageFolder와 유사한 기능을 직접 구현해야 할 때 사용할 수 있는 코드다. ImageFolder는 기본적으로 (이미지, 레이블) 형태로 특정한 이미지 데이터를 가져올 수 있다. 이때 이미지 대신에 numpy 형태로 가져오거나 원하는 형식의 확장자를 사용하도록 하려면, 이러한 클래스를 직접 작성해 사용할 수 있을 것이다.

 

import torch.utils.data as data

from PIL import Image
import os
import os.path

IMG_EXTENSIONS = [
    '.jpg', '.JPG', '.jpeg', '.JPEG',
    '.png', '.PNG', '.ppm', '.PPM', '.bmp', '.BMP',
]


def is_image_file(filename):
    return any(filename.endswith(extension) for extension in IMG_EXTENSIONS)


def find_classes(dir):
    classes = [d for d in os.listdir(dir) if os.path.isdir(os.path.join(dir, d))]
    classes.sort()
    class_to_idx = {classes[i]: i for i in range(len(classes))}
    return classes, class_to_idx


def make_dataset(dir, class_to_idx):
    images = []
    dir = os.path.expanduser(dir)
    for target in sorted(os.listdir(dir)):
        d = os.path.join(dir, target)
        if not os.path.isdir(d):
            continue

        for root, _, fnames in sorted(os.walk(d)):
            for fname in sorted(fnames):
                if is_image_file(fname):
                    path = os.path.join(root, fname)
                    item = (path, class_to_idx[target])
                    images.append(item)

    return images


def pil_loader(path):
    # open path as file to avoid ResourceWarning (https://github.com/python-pillow/Pillow/issues/835)
    with open(path, 'rb') as f:
        with Image.open(f) as img:
            return img.convert('RGB')


def accimage_loader(path):
    import accimage
    try:
        return accimage.Image(path)
    except IOError:
        # Potentially a decoding problem, fall back to PIL.Image
        return pil_loader(path)


def default_loader(path):
    from torchvision import get_image_backend
    if get_image_backend() == 'accimage':
        return accimage_loader(path)
    else:
        return pil_loader(path)


class ImageFolder(data.Dataset):
    """A generic data loader where the images are arranged in this way: ::

        root/dog/xxx.png
        root/dog/xxy.png
        root/dog/xxz.png

        root/cat/123.png
        root/cat/nsdf3.png
        root/cat/asd932_.png

    Args:
        root (string): Root directory path.
        transform (callable, optional): A function/transform that  takes in an PIL image
            and returns a transformed version. E.g, ``transforms.RandomCrop``
        target_transform (callable, optional): A function/transform that takes in the
            target and transforms it.
        loader (callable, optional): A function to load an image given its path.

     Attributes:
        classes (list): List of the class names.
        class_to_idx (dict): Dict with items (class_name, class_index).
        imgs (list): List of (image path, class_index) tuples
    """

    def __init__(self, root, transform=None, target_transform=None,
                 loader=default_loader):
        classes, class_to_idx = find_classes(root)
        imgs = make_dataset(root, class_to_idx)
        if len(imgs) == 0:
            raise(RuntimeError("Found 0 images in subfolders of: " + root + "\n"
                               "Supported image extensions are: " + ",".join(IMG_EXTENSIONS)))

        self.root = root
        self.imgs = imgs
        self.classes = classes
        self.class_to_idx = class_to_idx
        self.transform = transform
        self.target_transform = target_transform
        self.loader = loader

    def __getitem__(self, index):
        """
        Args:
            index (int): Index

        Returns:
            tuple: (image, target) where target is class_index of the target class.
        """
        path, target = self.imgs[index]
        img = self.loader(path)
        if self.transform is not None:
            img = self.transform(img)
        if self.target_transform is not None:
            target = self.target_transform(target)

        return img, target


    def __len__(self):
        return len(self.imgs)

 

  ▶ 출처: chsasank.github.io/vision/_modules/torchvision/datasets/folder.html

 

728x90
반응형

Comment +0

728x90
반응형

  Clarifai는 이미지 인식 서비스를 제공하는 대표적인 회사입니다. 무료로도 꽤 많은 API를 사용해 볼 수 있습니다. 홈페이지에 방문한 뒤에 바로 모델을 사용해 볼 수 있는데요. 예를 들어 유명인(celebrity) 얼굴 인식 서비스는 다음의 경로에 방문하여 사용해 볼 수 있습니다.

 

  ▶ Clarifai 유명인 얼굴 인식 서비스www.clarifai.com/models/celebrity-image-recognition

 

Analyze Images Using Celebrity Face Recognition | Clarifai

To analyze images and return probability scores, Clarifai Celebrity Face Recognition Model contains 10,000+ famous faces. Who do you look like? Try it out!

www.clarifai.com

 

  이 서비스에 접속해서 다음과 같이 이미지를 업로드하면 인식 결과 레이블(label)이 나옵니다. 저는 한 번 버락 오바마 대통령 사진을 업로드 해보았습니다. 그랬더니 다음과 같은 결과가 나오는 것을 알 수 있었습니다.

 

 

  참고로 유명인(celebrity)이 아닌 일반적인 사람의 얼굴을 넣는 경우, 임의의 유명인으로 레이블링을 진행하되 낮은 확률(probability)로 분류하는 것을 알 수 있습니다.

 

 

  특히 다음과 같이 확률(probability) 자체가 매우 낮은 경우에는 다음과 같이 "No celebrity detected"라는 메시지가 출력되는 것을 알 수 있습니다.

 

 

  ▶ Clarifai 서비스 회원가입(Sign Up)portal.clarifai.com/signup

 

  Clarifai 서비스를 편리하게 사용하기 위해서는 회원가입을 진행하면 됩니다. 일단 저는 무료로 가입하고 무료 서비스를 사용해 보았습니다. Clarifai 서비스 회원가입 방법은 간단합니다. 이메일 인증이 필요하기 때문에 이메일 주소를 정확히 입력할 필요가 있습니다. 예를 들어 저는 다음과 같이 회원가입을 진행했습니다.

 

 

 

  회원가입 이후에는 바로 애플리케이션(application)을 생성하여 사용해 볼 수 있습니다. 이때 API 키를 발급해 주기 때문에 발급받은 API 키를 이용해 다양한 프로그래밍 언어에서 Clarifai 서비스를 사용해 볼 수 있습니다.

 

 

※ Clarifai API 사용 방법 ※

 

  뿐만 아니라 Clarifai는 각종 API를 지원하고 있습니다. 예를 들어 파이썬(Python)을 이용해 얼굴 인식(face recognition) 기능을 사용해 볼 수 있습니다. 자세한 내용은 다음의 깃허브 저장소에 방문하시면 확인 가능합니다.

 

  ▶ 파이썬 clarifai 라이브러리: github.com/Clarifai/clarifai-python

 

Clarifai/clarifai-python

DEPRECATED Clarifai API Python Client, use clarifai-python-grpc instead - Clarifai/clarifai-python

github.com

 

  특정 URL로부터 사진 파일을 받아와 간단한 사용 방법 예시는 다음과 같습니다.

 

from clarifai.rest import ClarifaiApp
from IPython.display import Image, display

image_url = 'https://samples.clarifai.com/celebrity.jpg'
display(Image(image_url))

app = ClarifaiApp(api_key=API_KEY)
model = app.public_models.celebrity_model
response = model.predict_by_url(url=image_url)

regions = response['outputs'][0]['data']['regions']
concepts = regions[0]['data']['concepts']
for concept in concepts:
    print(f'Label: {concept["name"]} (Probability: {concept["value"]})')

 

  위 코드를 실행하면 다음과 같이 사진에서 유명인 얼굴을 추출한 결과가 나오게 됩니다.

 

 

  또한 다음과 같이 파일 경로(file path)로부터 이미지를 읽어와 화면에 출력할 수도 있습니다.

 

from clarifai.rest import ClarifaiApp
from IPython.display import Image, display

image_path = 'aligned_images/barack_obama_01.png'
display(Image(image_path))

app = ClarifaiApp(api_key=API_KEY)
model = app.public_models.celebrity_model
response = model.predict_by_filename(filename=image_path)

regions = response['outputs'][0]['data']['regions']
concepts = regions[0]['data']['concepts']
for concept in concepts:
    print(f'Label: {concept["name"]} (Probability: {concept["value"]})')

 

  실행 결과 예시는 다음과 같습니다.

 

 

  참고로 API를 통해 얻은 결과는 공식 웹 사이트에 넣어서 얻은 결과와 동일한 확률 값을 내보내는 것을 알 수 있습니다.

 

 

  ▶ 참고 소스코드: github.com/ndb796/Clarifai-Python-Celebrity-Recognition

 

ndb796/Clarifai-Python-Celebrity-Recognition

Clarifai Python API: Celebrity Recognition Examples - ndb796/Clarifai-Python-Celebrity-Recognition

github.com

728x90
반응형

Comment +1

728x90
반응형

※ 깃허브 README 파일에 마크다운으로 이미지 올리는 법 ※

 

  깃허브(GitHub)의 README 파일에 이미지(image)를 올리는 것은 간단하다. 먼저 [Issues] 탭으로 이동한 뒤에 하나의 이미지를 업로드한다. 그러면 깃허브 서비스 서버에 해당 이미지가 업로드된다.

 

 

  잠시 기다리면 다음과 같이 이미지 경로가 생성된다. 이 경로를 붙여넣으면 된다.

 

 

이제 해당 경로를 복사한 뒤에 README 파일에 마크다운(markdown) 양식에 맞게 다음과 같이 붙여넣으면 된다.

 

<img width="{해상도 비율}" src="{이미지 경로}"/>

 

  필자의 경우 다음과 같이 붙여넣었다.

 

<img width="80%" src="https://user-images.githubusercontent.com/16822641/109461495-913fc480-7aa5-11eb-9d0e-aff762669f98.gif"/>

 

  결과적으로 다음과 같이 붙여넣을 수 있다. 참고로 필자는 단순한 이미지 파일이 아닌 .gif 파일을 붙여넣었다. 그래도 정상적으로 잘 동작한다.

 

 

※ 참고: 동영상 파일(.mp4)을 .gif로 확장자로 만드는 방법 ※

 

  깃허브(GitHub)에 동영상 파일을 직접적으로 업로드하기는 쉽지 않다. 그래서 일반적으로 MP4 확장자를 GIF 확장자로 변형한 뒤에 업로드한다. 아래 사이트에 방문하면 쉽게 변경할 수 있다.

 

▶ Cloud Convert MP4 to GIF 변환기: cloudconvert.com/mp4-to-gif

 

  방문 이후에 [Select File] 버튼을 눌러 변환할 동영상 파일을 선택한다.

 

 

  변환할 동영상 파일을 선택한 뒤에는 [Convert] 버튼을 눌러 변환을 진행한다.

 

 

  변환이 완료된 이후에 [Download] 버튼을 눌러 완성된 파일을 받을 수 있다.

 

728x90
반응형

Comment +1

728x90
반응형

 이번 시간에는 간단히 어도비 프리미어(Adobe Premiere)를 이용해 동영상 해상도를 변경하는 방법을 소개한다. 예를 들어 인스타그램(Instagram)과 같은 SNS는 기본 동영상 크기가 1:1 비율을 가지기 때문에, 비율이 1:1이 아닌 동영상을 업로드하면 영상이 잘리는 문제가 있다. 그래서 동영상의 해상도를 변경하는 방법이 사용될 수 있다.

 

  실습을 위해 먼저 한 장의 동영상을 어도비 프리미어에 불러온다.

 

 

  해당 영상을 타임라인(timeline)에 끌어다 놓으면, 다음과 같이 성공적으로 시퀀스(sequence)가 생성된다.

 

 

  우클릭 이후에 [시퀀스 설정] 탭으로 들어간다.

 

 

  여기에서 프레임 크기를 조절할 수 있다. 전체 동영상의 크기는 시퀀스의 프레임 크기에 따르는 것이 일반적이므로, 이렇게 편집하고자 하는 시퀀스의 프레임 크기를 조절해서 해상도를 변경할 수 있다.

 

 

  필자는 864 X 600으로 조절해 보았다. 그러면 다음과 같이 해상도가 변경된다.

 

 

  배경색은 기본적으로 검은색이므로, 원하는 경우 [색상 매트]를 추가하여 배경색을 변경할 수 있다.

728x90
반응형

Comment +0

728x90
반응형

  얼굴 성별 분류(face gender classification) 데이터셋은 캐글(Kaggle)에서 다운로드 가능하다.

 

  ▶ 학습 데이터셋 개수: 남성/여성 각각 23,000개씩

  ▶ 평가 데이터셋 개수: 남성/여성 각각 5,500개씩

 

  실제 용량은 약 280MB 정도이며, 전체 데이터셋에 포함된 이미지는 60,000개가 조금 안 된다. 각각 이미지 크기는 실제로 열어 보면 약 100 X 100 정도의 해상도(resolution)로 구성된 것을 알 수 있다.

 

  ※ 얼굴 성별 분류 데이터셋: www.kaggle.com/cashutosh/gender-classification-dataset

 

Gender Classification Dataset

Male Female image dataset

www.kaggle.com

 

  접속 이후에 [Download] 버튼을 눌러서 다운로드를 진행할 수 있다.

 

728x90
반응형

Comment +0

728x90
반응형

  CelebA 데이터셋은 대표적인 얼굴(face) 데이터셋이다. 이때 CelebA 데이터셋은 약 200,000개 정도의 얼굴 이미지로 구성된다. 기본적으로 10,000명가량의 사람이 포함되어 있다. 즉, 한 명당 20장 정도의 이미지가 있다고 보면 된다. 각 이미지는 178 x 218 해상도로 존재한다.

 

  각 얼굴에 대해서는 40개의 이진 레이블(binary label)이 있다. 이때 각 얼굴에 대하여 다음과 같은 레이블이 각각 0 혹은 1의 값으로 붙어있다. 확인해 보면 젊은(young), 남성(male), 대머리(bald) 등의 예시가 붙어 있다.

 

  예를 들어 데이터셋 중에서 000025.jpg 사진은 다음과 같다.

 

 

  이 사진에 붙어 있는 레이블을 간단히 확인해 보면 다음과 같은 것을 알 수 있다.

 

대머리(Bale): -1, 큰 코(Big_Nose): 1, 남자(Male): 1, 웃는(Smiling): -1, 젊은(Young): 1

 

  캐글(Kaggle) 사이트에 방문해서 다운로드할 수 있다.

 

  ▶ CelebA 데이터셋: www.kaggle.com/jessicali9530/celeba-dataset

 

CelebFaces Attributes (CelebA) Dataset

Over 200k images of celebrities with 40 binary attribute annotations

www.kaggle.com

 

  사이트에 방문한 뒤에 [Download] 버튼을 누르면 다운로드할 수 있다.

 

 

  용량은 대략 1.4 GB 정도다.

 

728x90
반응형

Comment +0

728x90
반응형

  ResNet과 같이 배치 정규화(batch normalization)를 포함하고 있는 네트워크를 특징 추출기(feature extractor)로 사용할 때 유의해야 할 점이 있다. 별로 중요하지 않은 것처럼 보여도, 실제로 모델을 만드는 입장에서 제대로 이해하고 있지 않으면 많이 헤맬 수 있는 부분이다.

 

  1. 학습 모드(training mode)와 평가 모드(evaluation mode)일 때 추출되는 특징 맵(feature map)이 다르다는 점

 

  기본적으로 배치 정규화는 학습 시 사용하는 파라미터와 평가 시 사용하는 파라미터가 다르게 적용된다. 따라서 만약 batch normalization을 포함한 네트워크를 feature extractor로 사용하고자 한다면, 같은 이미지에 대해 매번 동일한 특징 맵이 추출될 수 있도록 하기 위해 항상 train() 모드로 사용하거나 항상 eval() 모드로 사용하는 식으로 일관적일 필요가 있다. 일반적인 목적의 특징 추출기로 사용한다면 eval() 모드로만 사용하는 것을 추천한다. (학습할 때 train(), 평가할 때 eval()을 사용하는 것도 일반적인 분류 모델 학습 목적이라면 성능에 문제가 없다.)

 

  전이 학습(transfer learning)을 수행할 때 ResNet 기반의 고정된 특징 추출기가 필요하다면, conv 레이어 부분을 eval() 모드로 사용하면 된다. 만약 이를 지키지 않고 일관적이지 않게 train()과 eval()을 번갈아 호출하게 된다면, 동일한 이미지에 대하여 추출되는 feature maps이 매번 다른 값을 가질 수 있다. 앞서 언급했듯이 일반적인 분류 문제에서는 이게 큰 이슈가 되지 않는다. 다만 동일 이미지에 대한 feature maps가 항상 같아야 되는 경우에는 문제가 된다.

 

  2. 다른 네트워크를 포함한 네트워크에서 train()이나 eval()을 사용하는 경우

 

  필요한 경우 특징 추출기(feature extractor)를 포함한 하나의 네트워크 자체를 새롭게 정의할 수 있다. 이는 학습 코드 구현상의 편리성을 주는 경우가 많기 때문에, 종종 볼 수 있는 코드 유형이다. 예를 들어 ResNet18 모델의 앞부분은 고정한(fixed) 상태로 뒤쪽 FC 레이어만 새롭게 교체하여 학습하는 전이 학습(transfer learning) 방법을 사용할 수 있다. 소스코드 예시는 다음과 같다.

 

class StudentNetwork(nn.Module):
    def __init__(self):
        super(StudentNetwork, self).__init__()

        self.feature_extractor = nn.Sequential(*list(models.resnet18(pretrained=True).children())[:-1]).eval()
        self.fc = nn.Linear(512, 2) # binary classification (num_of_class == 2)

        # fix the pre-trained network
        for param in self.feature_extractor.parameters():
            param.requires_grad = False

    def forward(self, images):
        features = self.feature_extractor(images)
        x = torch.flatten(features, 1)
        outputs = self.fc(x)
        return features, outputs

 

  만약 위와 같이 모델을 정의했다면, 특징 추출기(feature extractor)는 항상 eval() 모드로 수행되는 것이다. 따라서 StudentNetwork 클래스의 인스턴스에 대하여 별도로 train()이나 eval()을 호출할 필요가 없다. 그냥 학습 단계든 평가 단계든 그 단계(phase)에 상관없이 그대로 사용하면 된다. 그러면 동일한 이미지에 대하여 항상 같은 features가 반환된다.

 

  위와 같이 ResNet 기반의 특징 추출기를 내부적으로 포함하고 있는 하나의 모델 인스턴스 model을 초기화한 경우를 생각해보자. 만약에 이때 model.train()을 호출하면 내부에 포함된 ResNet에 대해서도 학습 모드(train mode)가 적용되기 때문에, 의도치 않게 특징 추출기에 영향을 미칠 수 있다. 이 경우 동일한 이미지에 대하여 forward()의 결과인 features 텐서 값이 변경될 수 있는 것이다. 이 부분은 특히나 실수하기 쉬운 부분이므로 유의하자. 

 

  [참고] PyTorch에서 모델을 초기화하면 기본 설정으로 requires_grad 값이 True가 된다. (이는 사전 학습된 네트워크를 불러올 때에도 마찬가지다!) 따라서 별도로 명시하지 않는다면 자동으로 기울기(gradient)를 추적하기 때문에, 학습하지 않고자 하는 레이어에 대해서는 requires_grad 값을 명시적으로 False로 설정할 필요가 있다.

728x90
반응형

Comment +0

728x90
반응형

  파이토치(PyTorch)의 공식 문서에서 전이 학습(transfer learning)에 관해 설명하고 있는 문서는 다음과 같다.

 

  ▶ PyTorch Transfer Learningpytorch.org/tutorials/beginner/transfer_learning_tutorial.html

 

Transfer Learning for Computer Vision Tutorial — PyTorch Tutorials 1.7.1 documentation

Note Click here to download the full example code Transfer Learning for Computer Vision Tutorial Author: Sasank Chilamkurthy In this tutorial, you will learn how to train a convolutional neural network for image classification using transfer learning. You

pytorch.org

  공식 문서에서는 전이 학습(transfer learning)의 대표적인 두 가지 시나리오를 언급한다.

 

  1. 전체 네트워크를 fine-tuning 하는 방식

  2. 사전학습된(pre-trained) 네트워크를 고정된 특징 추출기(fixed feature extractor)로 사용하는 방식

 

  여기에서 1번과 2번의 실제 구현상의 차이점은 사전학습된 네트워크에 대하여 다음의 코드 부분을 넣느냐 마느냐이다. 아래 코드는 사전학습된(pre-trained) 네트워크의 가중치를 고정할 때 사용하는 코드이다.

 

for param in model_conv.parameters():
    param.requires_grad = False

 

  만약 2번 방식(fixed feature extractor)대로 사전학습된 네트워크의 가중치를 특징 추출기로 고정한다면, 뒤쪽에 있는 FC 레이어만 업데이트가 될 것이다. 또한 이 경우에는 앞쪽 레이어에 대한 기울기(gradient)를 계산하지 않아도 되기 때문에 학습 속도가 빨라진다. 참고로 2번의 방식을 사용하는 경우 optimizer에서는 FC 레이어의 파라미터에 대해서만 업데이트한다고 명시해야 한다. (optimizer는 계산된 기울기(gradient)를 이용해 업데이트(update)를 수행한다.)

 

  [참고 1] requires_grad를 False로 설정한 레이어에 대하여 optimizer에서 업데이트를 하겠다고 명시하더라도, 어차피 구해진 gradient 값 자체가 없기 때문에 업데이트가 수행되지 않기는 한다.

 

  [참고 2] PyTorch에서 모델을 초기화하면 기본 설정으로 requires_grad 값이 True가 된다. (이는 사전 학습된 네트워크를 불러올 때에도 마찬가지다!) 따라서 별도로 명시하지 않는다면 자동으로 기울기(gradient)를 추적하기 때문에, 학습하지 않고자 하는 레이어에 대해서는 requires_grad 값을 명시적으로 False로 설정할 필요가 있다.

 

  다만 공식 문서에서는 간단한 이진 분류(binary classification) 예시를 들고 있기 때문에 1번과 2번의 성능 차이가 크게 나지 않으며, 디테일한 하이퍼 파라미터 세팅을 하지 않아도 높은 성능이 나온다. 하지만 실제로 CIFAR-10과 같이 클래스의 개수가 많은 데이터셋을 이용하는 경우에는 성능 차이가 크게 날 수 있다.

 

  필자의 경우 클래스가 3개인 경우, 클래스가 10개인 경우에 대하여 학습을 진행해 보았다. 이때 2번 방법대로 마지막 FC 레이어만 학습하도록 한 경우에는 학습이 정상적으로 수행되지 않았다. 하이퍼 파라미터 세팅에 많은 신경을 써야 하는 것으로 보인다. 따라서 클래스의 개수가 많은 상황에서 빠르게 높은 정확도(high accuracy)를 얻고 싶다면 1번의 방법대로 앞쪽 네트워크를 고정하지 않고 전체 네트워크를 fine-tuning 하는 것이 유리할 수 있다.

 

  예를 들어 필자의 경우 CIFAR-10에 대하여 전이 학습(transfer learning)을 수행한 경험이 있는데, 다른 코드 부분은 완전히 동일하게 유지한 상태로 한 번은 다음과 같은 코드를 사용했다.

 

net = torchvision.models.resnet18(pretrained=True)

# 마지막 레이어의 차원을 10차원으로 조절
num_features = net.fc.in_features
net.fc = nn.Linear(num_features, 10)
net = net.to(device)

 

  그리고 한 번은 다음과 같은 코드를 사용했다.

 

net = torchvision.models.resnet18(pretrained=True)
for param in net.parameters():
    param.requires_grad = False

# 마지막 레이어의 차원을 10차원으로 조절
num_features = net.fc.in_features
net.fc = nn.Linear(num_features, 10)
net = net.to(device)

 

  첫째 경우(fine-tuning)에는 한 번의 epoch만으로 순식간에 94% 정도의 test accuracy를 얻을 수 있었지만, 둘째 경우(fixed feature extractor)에는 여러 번의 epoch을 반복해도 90% 이상의 성능은 얻을 수 없었다. 다시 말해 2번 방법이 학습 속도 측면에서 유리할 수 있으나, 클래스가 많은 상황에서는 성능이 낮게 나오는 문제가 발생할 수 있다.

 

  [참고] 이 문제는 해외 기술 블로그에서도 자주 다루어지고 있는 내용이다. 정리하자면 다음과 같다.

 

  ① Frozen: 앞쪽의 특징 추출기(feature extractor)에 대하여 역전파를 수행하지 않는 방법이다. 일반적으로 목표 작업(target task)의 레이블(label) 수가 적고 오버피팅(overfitting)을 예방하기 위한 목적으로 사용된다.

  ② Fine-tuning: 앞쪽의 특징 추출기(feature extractor)에 대하여 역전파를 수행하는 방법이다. 일반적으로 목표 작업(target task)의 레이블(label) 수가 많을 때 사용한다.

728x90
반응형

Comment +0