transformers/docs/source/ja/custom_models.md

17 KiB
Raw Permalink Blame History

Sharing custom models

🤗 Transformersライブラリは、簡単に拡張できるように設計されています。すべてのモデルはリポジトリの特定のサブフォルダに完全にコード化されており、抽象化はありません。したがって、モデリングファイルをコピーして調整することが簡単です。

新しいモデルを書いている場合、ゼロから始める方が簡単かもしれません。このチュートリアルでは、カスタムモデルとその設定をどのように書き、Transformers内で使用できるようにし、コードに依存する共同体と共有する方法を説明します。ライブラリに存在しない場合でも、誰でも使用できるようにします。

これを実証するために、timmライブラリのResNetクラスを[PreTrainedModel]にラップすることによって、ResNetモデルを使用します。

Writing a custom configuration

モデルに取り組む前に、まずその設定を書きましょう。モデルの設定は、モデルを構築するために必要なすべての情報を含むオブジェクトです。次のセクションで見るように、モデルは初期化するためにconfigしか受け取ることができないため、そのオブジェクトができるだけ完全である必要があります。

この例では、ResNetクラスのいくつかの引数を取得し、調整したいかもしれないとします。異なる設定は、異なるタイプの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)

重要なことを3つ覚えておくべきポイントは次のとおりです

  • PretrainedConfig を継承する必要があります。
  • あなたの PretrainedConfig__init__ は任意の kwargs を受け入れる必要があります。
  • これらの kwargs は親クラスの __init__ に渡す必要があります。

継承は、🤗 Transformers ライブラリのすべての機能を取得できるようにするためです。他の2つの制約は、 PretrainedConfig が設定しているフィールド以外にも多くのフィールドを持っていることから来ています。 from_pretrained メソッドで設定を再ロードする場合、これらのフィールドはあなたの設定に受け入れられ、 その後、親クラスに送信される必要があります。

設定の 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")

また、[PretrainedConfig] クラスの他のメソッドを使用することもできます。たとえば、[~PretrainedConfig.push_to_hub] を使用して、設定を直接 Hub にアップロードできます。

Writing a custom model

ResNet の設定ができたので、モデルを書き始めることができます。実際には2つのモデルを書きます。1つはバッチの画像から隠れた特徴を抽出するモデル[BertModel] のようなもので、もう1つは画像分類に適したモデル[BertForSequenceClassification] のようなもの)です。

前述したように、この例をシンプルに保つために、モデルの緩いラッパーのみを書きます。このクラスを書く前に行う必要がある唯一のことは、ブロックタイプと実際のブロッククラスの間のマップです。その後、すべてを ResNet クラスに渡して設定からモデルを定義します:

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を書くときのような感じです)。 config_classを設定する行は必須ではありませんが、(最後のセクションを参照)、モデルを自動クラスに登録したい場合に使用できます。

モデルがライブラリ内のモデルと非常に似ている場合、このモデルと同じ構成を再利用できます。

モデルが返す内容は何でも構いませんが、ラベルが渡されるときに損失を含む辞書を返す(ResnetModelForImageClassificationのように行ったもの)と、 モデルを[Trainer]クラス内で直接使用できるようになります。独自のトレーニングループまたは他のライブラリを使用する予定である限り、 別の出力形式を使用することも問題ありません。

さて、モデルクラスができたので、1つ作成しましょう

resnet50d = ResnetModelForImageClassification(resnet50d_config)

再度、[PreTrainedModel]のいずれかのメソッド、例えば[~PreTrainedModel.save_pretrained]や [~PreTrainedModel.push_to_hub]などを使用できます。次のセクションでは、モデルの重みをコードと一緒に Hugging Face Hub にプッシュする方法を見てみます。 しかし、まずはモデル内に事前学習済みの重みをロードしましょう。

独自のユースケースでは、おそらく独自のデータでカスタムモデルをトレーニングすることになるでしょう。 このチュートリアルではスピードアップのために、resnet50dの事前学習済みバージョンを使用します。 私たちのモデルはそれをラップするだけなので、これらの重みを転送するのは簡単です:

import timm

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

さて、[~PreTrainedModel.save_pretrained]または[~PreTrainedModel.push_to_hub]を実行したときに、 モデルのコードが保存されるようにする方法を見てみましょう。

Sending the code to the Hub

このAPIは実験的であり、次のリリースでわずかな変更があるかもしれません。

まず、モデルが.pyファイルに完全に定義されていることを確認してください。 ファイルは相対インポートを他のファイルに依存できますが、すべてのファイルが同じディレクトリにある限り(まだこの機能ではサブモジュールはサポートしていません)、問題ありません。 この例では、現在の作業ディレクトリ内に名前が「resnet_model」のフォルダを作成し、その中にmodeling_resnet.pyファイルとconfiguration_resnet.pyファイルを定義します。 構成ファイルにはResnetConfigのコードが含まれ、モデリングファイルにはResnetModelResnetModelForImageClassificationのコードが含まれています。

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

__init__.pyは空であっても問題ありません。Pythonがresnet_modelをモジュールとして検出できるようにするために存在します。

ライブラリからモデリングファイルをコピーする場合、ファイルの先頭にあるすべての相対インポートを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")

注意: 設定については自動クラスを指定する必要はありません設定用の自動クラスは1つしかなく、[AutoConfig]です)が、 モデルについては異なります。カスタムモデルは多くの異なるタスクに適している可能性があるため、 モデルが正確な自動クラスのうちどれに適しているかを指定する必要があります。

次に、前述のように設定とモデルを作成しましょう:

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")

モデリングの重みとJSON形式の構成に加えて、このフォルダー「custom-resnet50d」内のモデリングおよび構成「.py」ファイルもコピーされ、結果はHubにアップロードされました。結果はこのmodel repoで確認できます。

詳細については、Hubへのプッシュ方法を参照してください。

Using a model with custom code

自動クラスと from_pretrained メソッドを使用して、リポジトリ内のカスタムコードファイルと共に任意の構成、モデル、またはトークナイザを使用できます。 Hubにアップロードされるすべてのファイルとコードはマルウェアのスキャンが実施されます詳細はHubセキュリティドキュメンテーションを参照してください)、しかし、依然として悪意のあるコードを実行しないために、モデルコードと作者を確認する必要があります。 trust_remote_code=True を設定してカスタムコードを持つモデルを使用できます:

from transformers import AutoModelForImageClassification

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

コミットハッシュを「revision」として渡すことも強く推奨されています。これにより、モデルの作者がコードを悪意のある新しい行で更新しなかったことを確認できますモデルの作者を完全に信頼している場合を除きます

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

モデルリポジトリのコミット履歴をブラウジングする際には、任意のコミットのコミットハッシュを簡単にコピーできるボタンがあります。

Registering a model with custom code to the auto classes

🤗 Transformersを拡張するライブラリを作成している場合、独自のモデルを含めるために自動クラスを拡張したい場合があります。 これはコードをHubにプッシュすることとは異なり、ユーザーはカスタムモデルを取得するためにあなたのライブラリをインポートする必要があります Hubからモデルコードを自動的にダウンロードするのとは対照的です

構成に既存のモデルタイプと異なる model_type 属性がある限り、またあなたのモデルクラスが適切な config_class 属性を持っている限り、 次のようにそれらを自動クラスに追加できます:

from transformers import AutoConfig, AutoModel, AutoModelForImageClassification

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

注意: AutoConfig にカスタム設定を登録する際の最初の引数は、カスタム設定の model_type と一致する必要があります。 また、任意の自動モデルクラスにカスタムモデルを登録する際の最初の引数は、それらのモデルの config_class と一致する必要があります。