#
사용자 정의 모델 공유하기

🤗 Transformers 라이브러리는 쉽게 확장할 수 있도록 설계되었습니다. 모든 모델은 추상화 없이 저장소의 지정된 하위 폴더에 완전히 코딩되어 있으므로, 손쉽게 모델링 파일을 복사하고 필요에 따라 조정할 수 있습니다.

완전히 새로운 모델을 만드는 경우에는 처음부터 시작하는 것이 더 쉬울 수 있습니다. 이 튜토리얼에서는 Transformers 내에서 사용할 수 있도록 사용자 정의 모델과 구성을 작성하는 방법과 🤗 Transformers 라이브러리에 없는 경우에도 누구나 사용할 수 있도록 (의존성과 함께) 커뮤니티에 공유하는 방법을 배울 수 있습니다.

timm 라이브러리의 ResNet 클래스를 `PreTrainedModel`로 래핑한 ResNet 모델을 예로 모든 것을 설명합니다.

##
사용자 정의 구성 작성하기

모델에 들어가기 전에 먼저 구성을 작성해보도록 하겠습니다. 모델의 `configuration`은 모델을 만들기 위해 필요한 모든 중요한 것들을 포함하고 있는 객체입니다. 다음 섹션에서 볼 수 있듯이, 모델은 `config`를 사용해서만 초기화할 수 있기 때문에 완벽한 구성이 필요합니다.

아래 예시에서는 ResNet 클래스의 인수(argument)를 조정해보겠습니다. 다른 구성은 가능한 ResNet 중 다른 유형을 제공합니다. 그런 다음 몇 가지 유효성을 확인한 후 해당 인수를 저장합니다.

    from transformers import PretrainedConfig
    from typing import List

    class ResnetConfig(PretrainedConfig):
        model_type = "resnet"

        def __init__(
            self,
            block_type="bottleneck",
            layers: List[int] = [3, 4, 6, 3],
            num_classes: int = 1000,
            input_channels: int = 3,
            cardinality: int = 1,
            base_width: int = 64,
            stem_width: int = 64,
            stem_type: str = "",
            avg_down: bool = False,
            **kwargs,
        ):
            if block_type not in ["basic", "bottleneck"]:
                raise ValueError(f"`block_type` must be 'basic' or bottleneck', got {block_type}.")
            if stem_type not in ["", "deep", "deep-tiered"]:
                raise ValueError(f"`stem_type` must be '', 'deep' or 'deep-tiered', got {stem_type}.")

            self.block_type = block_type
            self.layers = layers
            self.num_classes = num_classes
            self.input_channels = input_channels
            self.cardinality = cardinality
            self.base_width = base_width
            self.stem_width = stem_width
            self.stem_type = stem_type
            self.avg_down = avg_down
            super().__init__(**kwargs)

사용자 정의 `configuration`을 작성할 때 기억해야 할 세 가지 중요한 사항은 다음과 같습니다:

- `PretrainedConfig`을 상속해야 합니다.
- `PretrainedConfig`의 `__init__`은 모든 kwargs를 허용해야 하고,
- 이러한 `kwargs`는 상위 클래스 `__init__`에 전달되어야 합니다.

상속은 🤗 Transformers 라이브러리에서 모든 기능을 가져오는 것입니다. 이러한 점으로부터 비롯되는 두 가지 제약 조건은 `PretrainedConfig`에 설정하는 것보다 더 많은 필드가 있습니다. `from_pretrained` 메서드로 구성을 다시 로드할 때 해당 필드는 구성에서 수락한 후 상위 클래스로 보내야 합니다.

모델을 auto 클래스에 등록하지 않는 한, `configuration`에서 `model_type`을 정의(여기서 `model_type="resnet"`)하는 것은 필수 사항이 아닙니다 (마지막 섹션 참조).

이렇게 하면 라이브러리의 다른 모델 구성과 마찬가지로 구성을 쉽게 만들고 저장할 수 있습니다. 다음은 resnet50d 구성을 생성하고 저장하는 방법입니다:

    resnet50d_config = ResnetConfig(block_type="bottleneck", stem_width=32, stem_type="deep", avg_down=True)
    resnet50d_config.save_pretrained("custom-resnet")

이렇게 하면 `custom-resnet` 폴더 안에 `config.json`이라는 파일이 저장됩니다. 그런 다음 `from_pretrained` 메서드를 사용하여 구성을 다시 로드할 수 있습니다.

    resnet50d_config = ResnetConfig.from_pretrained("custom-resnet")

구성을 Hub에 직접 업로드하기 위해 `PretrainedConfig` 클래스의 `push_to_hub()`와 같은 다른 메서드를 사용할 수 있습니다.

##
사용자 정의 모델 작성하기

이제 ResNet 구성이 있으므로 모델을 작성할 수 있습니다. 실제로는 두 개를 작성할 것입니다. 하나는 이미지 배치에서 hidden features를 추출하는 것(`BertModel`과 같이), 다른 하나는 이미지 분류에 적합한 것입니다(`BertForSequenceClassification`과 같이).

이전에 언급했듯이 이 예제에서는 단순하게 하기 위해 모델의 느슨한 래퍼(loose wrapper)만 작성할 것입니다. 이 클래스를 작성하기 전에 블록 유형과 실제 블록 클래스 간의 매핑 작업만 하면 됩니다. 그런 다음 `ResNet` 클래스로 전달되어 `configuration`을 통해 모델이 선언됩니다:

    from transformers import PreTrainedModel
    from timm.models.resnet import BasicBlock, Bottleneck, ResNet
    from .configuration_resnet import ResnetConfig

    BLOCK_MAPPING = {"basic": BasicBlock, "bottleneck": Bottleneck}

    class ResnetModel(PreTrainedModel):
        config_class = ResnetConfig

        def __init__(self, config):
            super().__init__(config)
            block_layer = BLOCK_MAPPING[config.block_type]
            self.model = ResNet(
                block_layer,
                config.layers,
                num_classes=config.num_classes,
                in_chans=config.input_channels,
                cardinality=config.cardinality,
                base_width=config.base_width,
                stem_width=config.stem_width,
                stem_type=config.stem_type,
                avg_down=config.avg_down,
            )

        def forward(self, tensor):
            return self.model.forward_features(tensor)

이미지 분류 모델을 만들기 위해서는 forward 메소드만 변경하면 됩니다:

    import torch

    class ResnetModelForImageClassification(PreTrainedModel):
        config_class = ResnetConfig

        def __init__(self, config):
            super().__init__(config)
            block_layer = BLOCK_MAPPING[config.block_type]
            self.model = ResNet(
                block_layer,
                config.layers,
                num_classes=config.num_classes,
                in_chans=config.input_channels,
                cardinality=config.cardinality,
                base_width=config.base_width,
                stem_width=config.stem_width,
                stem_type=config.stem_type,
                avg_down=config.avg_down,
            )

        def forward(self, tensor, labels=None):
            logits = self.model(tensor)
            if labels is not None:
                loss = torch.nn.cross_entropy(logits, labels)
                return {"loss": loss, "logits": logits}
            return {"logits": logits}

두 경우 모두 `PreTrainedModel`를 상속받고, `config`를 통해 상위 클래스 초기화를 호출하다는 점을 기억하세요 (일반적인 `torch.nn.Module`을 작성할 때와 비슷함). 모델을 auto 클래스에 등록하고 싶은 경우에는 `config_class`를 설정하는 부분이 필수입니다 (마지막 섹션 참조).

라이브러리에 존재하는 모델과 굉장히 유사하다면, 모델을 생성할 때 구성을 참조해 재사용할 수 있습니다.

원하는 것을 모델이 반환하도록 할 수 있지만, `ResnetModelForImageClassification`에서 했던 것 처럼 레이블을 통과시켰을 때 손실과 함께 사전 형태로 반환하는 것이 `Trainer` 클래스 내에서 직접 모델을 사용하기에 유용합니다. 자신만의 학습 루프 또는 다른 학습 라이브러리를 사용할 계획이라면 다른 출력 형식을 사용해도 좋습니다.

이제 모델 클래스가 있으므로 하나 생성해 보겠습니다:

    resnet50d = ResnetModelForImageClassification(resnet50d_config)

다시 말하지만, `save_pretrained()`또는 `push_to_hub()`처럼 `PreTrainedModel`에 속하는 모든 메소드를 사용할 수 있습니다. 다음 섹션에서 두 번째 메소드를 사용해 모델 코드와 모델 가중치를 업로드하는 방법을 살펴보겠습니다. 먼저, 모델 내부에 사전 훈련된 가중치를 로드해 보겠습니다.

이 예제를 활용할 때는, 사용자 정의 모델을 자신만의 데이터로 학습시킬 것입니다. 이 튜토리얼에서는 빠르게 진행하기 위해 사전 훈련된 resnet50d를 사용하겠습니다. 아래 모델은 resnet50d의 래퍼이기 때문에, 가중치를 쉽게 로드할 수 있습니다.

    import timm

    pretrained_model = timm.create_model("resnet50d", pretrained=True)
    resnet50d.model.load_state_dict(pretrained_model.state_dict())

이제 `save_pretrained()` 또는 `push_to_hub()`를 사용할 때 모델 코드가 저장되는지 확인해봅시다.

##
Hub로 코드 업로드하기

이 API는 실험적이며 다음 릴리스에서 약간의 변경 사항이 있을 수 있습니다.

먼저 모델이 `.py` 파일에 완전히 정의되어 있는지 확인하세요. 모든 파일이 동일한 작업 경로에 있기 때문에 상대경로 임포트(relative import)에 의존할 수 있습니다 (transformers에서는 이 기능에 대한 하위 모듈을 지원하지 않습니다). 이 예시에서는 작업 경로 안의 `resnet_model`에서 `modeling_resnet.py` 파일과 `configuration_resnet.py` 파일을 정의합니다. 구성 파일에는 `ResnetConfig`에 대한 코드가 있고 모델링 파일에는 `ResnetModel` 및 `ResnetModelForImageClassification`에 대한 코드가 있습니다.

    .
    └── resnet_model
        ├── __init__.py
        ├── configuration_resnet.py
        └── modeling_resnet.py

Python이 `resnet_model`을 모듈로 사용할 수 있도록 감지하는 목적이기 때문에 `__init__.py`는 비어 있을 수 있습니다.

라이브러리에서 모델링 파일을 복사하는 경우, 모든 파일 상단에 있는 상대 경로 임포트(relative import) 부분을 `transformers` 패키지에서 임포트 하도록 변경해야 합니다.

기존 구성이나 모델을 재사용(또는 서브 클래스화)할 수 있습니다.

커뮤니티에 모델을 공유하기 위해서는 다음 단계를 따라야 합니다: 먼저, 새로 만든 파일에 ResNet 모델과 구성을 임포트합니다:

    from resnet_model.configuration_resnet import ResnetConfig
    from resnet_model.modeling_resnet import ResnetModel, ResnetModelForImageClassification

다음으로 `save_pretrained` 메소드를 사용해 해당 객체의 코드 파일을 복사하고, 복사한 파일을 Auto 클래스로 등록하고(모델인 경우) 실행합니다:

    ResnetConfig.register_for_auto_class()
    ResnetModel.register_for_auto_class("AutoModel")
    ResnetModelForImageClassification.register_for_auto_class("AutoModelForImageClassification")

`configuration`에 대한 auto 클래스를 지정할 필요는 없지만(`configuration` 관련 auto 클래스는 AutoConfig 클래스 하나만 있음), 모델의 경우에는 지정해야 합니다. 사용자 지정 모델은 다양한 작업에 적합할 수 있으므로, 모델에 맞는 auto 클래스를 지정해야 합니다.

다음으로, 이전에 작업했던 것과 마찬가지로 구성과 모델을 작성합니다:

    resnet50d_config = ResnetConfig(block_type="bottleneck", stem_width=32, stem_type="deep", avg_down=True)
    resnet50d = ResnetModelForImageClassification(resnet50d_config)

    pretrained_model = timm.create_model("resnet50d", pretrained=True)
    resnet50d.model.load_state_dict(pretrained_model.state_dict())

이제 모델을 Hub로 업로드하기 위해 로그인 상태인지 확인하세요. 터미널에서 다음 코드를 실행해 확인할 수 있습니다:

    huggingface-cli login

주피터 노트북의 경우에는 다음과 같습니다:

    from huggingface_hub import notebook_login

    notebook_login()

그런 다음 이렇게 자신의 네임스페이스(또는 자신이 속한 조직)에 업로드할 수 있습니다:

    resnet50d.push_to_hub("custom-resnet50d")

On top of the modeling weights and the configuration in json format, this also copied the modeling and configuration `.py` files in the folder `custom-resnet50d` and uploaded the result to the Hub. You can check the result in this model repo. json 형식의 모델링 가중치와 구성 외에도 `custom-resnet50d` 폴더 안의 모델링과 구성 `.py` 파일을 복사하해 Hub에 업로드합니다. 모델 저장소에서 결과를 확인할 수 있습니다.

sharing tutorial 문서의 `push_to_hub` 메소드에서 자세한 내용을 확인할 수 있습니다.

##
사용자 정의 코드로 모델 사용하기

auto 클래스와 `from_pretrained` 메소드를 사용하여 사용자 지정 코드 파일과 함께 모든 구성, 모델, 토크나이저를 사용할 수 있습니다. Hub에 업로드된 모든 파일 및 코드는 멜웨어가 있는지 검사되지만 (자세한 내용은 Hub 보안 설명 참조), 자신의 컴퓨터에서 모델 코드와 작성자가 악성 코드를 실행하지 않는지 확인해야 합니다. 사용자 정의 코드로 모델을 사용하려면 `trust_remote_code=True`로 설정하세요:

    from transformers import AutoModelForImageClassification

    model = AutoModelForImageClassification.from_pretrained("sgugger/custom-resnet50d", trust_remote_code=True)

모델 작성자가 악의적으로 코드를 업데이트하지 않았다는 점을 확인하기 위해, 커밋 해시(commit hash)를 `revision`으로 전달하는 것도 강력히 권장됩니다 (모델 작성자를 완전히 신뢰하지 않는 경우).

    commit_hash = "ed94a7c6247d8aedce4647f00f20de6875b5b292"
    model = AutoModelForImageClassification.from_pretrained(
        "sgugger/custom-resnet50d", trust_remote_code=True, revision=commit_hash
    )

Hub에서 모델 저장소의 커밋 기록을 찾아볼 때, 모든 커밋의 커밋 해시를 쉽게 복사할 수 있는 버튼이 있습니다.

##
사용자 정의 코드로 만든 모델을 auto 클래스로 등록하기

🤗 Transformers를 상속하는 라이브러리를 작성하는 경우 사용자 정의 모델을 auto 클래스에 추가할 수 있습니다. 사용자 정의 모델을 사용하기 위해 해당 라이브러리를 임포트해야 하기 때문에, 이는 Hub로 코드를 업로드하는 것과 다릅니다 (Hub에서 자동적으로 모델 코드를 다운로드 하는 것과 반대).

구성에 기존 모델 유형과 다른 `model_type` 속성이 있고 모델 클래스에 올바른 `config_class` 속성이 있는 한, 다음과 같이 auto 클래스에 추가할 수 있습니다:

    from transformers import AutoConfig, AutoModel, AutoModelForImageClassification

    AutoConfig.register("resnet", ResnetConfig)
    AutoModel.register(ResnetConfig, ResnetModel)
    AutoModelForImageClassification.register(ResnetConfig, ResnetModelForImageClassification)

사용자 정의 구성을 `AutoConfig`에 등록할 때 사용되는 첫 번째 인수는 사용자 정의 구성의 `model_type`과 일치해야 합니다. 또한, 사용자 정의 모델을 auto 클래스에 등록할 때 사용되는 첫 번째 인수는 해당 모델의 `config_class`와 일치해야 합니다.