| | <!--Copyright 2020 The HuggingFace Team. All rights reserved. |
| |
|
| | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
| | the License. You may obtain a copy of the License at |
| |
|
| | http: |
| |
|
| | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
| | an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
| | specific language governing permissions and limitations under the License. |
| | --> |
| |
|
| | # Compartilhando modelos customizados |
| |
|
| | A biblioteca 🤗 Transformers foi projetada para ser facilmente extensível. Cada modelo é totalmente codificado em uma determinada subpasta |
| | do repositório sem abstração, para que você possa copiar facilmente um arquivo de modelagem e ajustá-lo às suas necessidades. |
| |
|
| | Se você estiver escrevendo um modelo totalmente novo, pode ser mais fácil começar do zero. Neste tutorial, mostraremos |
| | como escrever um modelo customizado e sua configuração para que possa ser usado com Transformers, e como você pode compartilhá-lo |
| | com a comunidade (com o código em que se baseia) para que qualquer pessoa possa usá-lo, mesmo se não estiver presente na biblioteca 🤗 Transformers. |
| |
|
| | Ilustraremos tudo isso em um modelo ResNet, envolvendo a classe ResNet do |
| | [biblioteca timm](https: |
| |
|
| | ## Escrevendo uma configuração customizada |
| |
|
| | Antes de mergulharmos no modelo, vamos primeiro escrever sua configuração. A configuração de um modelo é um objeto que |
| | terá todas as informações necessárias para construir o modelo. Como veremos na próxima seção, o modelo só pode |
| | ter um `config` para ser inicializado, então realmente precisamos que esse objeto seja o mais completo possível. |
| |
|
| | Em nosso exemplo, pegaremos alguns argumentos da classe ResNet que podemos querer ajustar. Diferentes |
| | configurações nos dará os diferentes tipos de ResNets que são possíveis. Em seguida, apenas armazenamos esses argumentos, |
| | após verificar a validade de alguns deles. |
| |
|
| | ```python |
| | 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, |
| | |
| | ): |
| | 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) |
| | ``` |
| |
|
| | As três coisas importantes a serem lembradas ao escrever sua própria configuração são: |
| | - você tem que herdar de `PretrainedConfig`, |
| | - o `__init__` do seu `PretrainedConfig` deve aceitar quaisquer kwargs, |
| | - esses `kwargs` precisam ser passados para a superclasse `__init__`. |
| |
|
| | A herança é para garantir que você obtenha todas as funcionalidades da biblioteca 🤗 Transformers, enquanto as outras duas |
| | restrições vêm do fato de um `PretrainedConfig` ter mais campos do que os que você está configurando. Ao recarregar um |
| | config com o método `from_pretrained`, esses campos precisam ser aceitos pelo seu config e então enviados para a |
| | superclasse. |
| |
|
| | Definir um `model_type` para sua configuração (aqui `model_type="resnet"`) não é obrigatório, a menos que você queira |
| | registrar seu modelo com as classes automáticas (veja a última seção). |
| |
|
| | Com isso feito, você pode facilmente criar e salvar sua configuração como faria com qualquer outra configuração de modelo da |
| | biblioteca. Aqui está como podemos criar uma configuração resnet50d e salvá-la: |
| |
|
| | ```py |
| | resnet50d_config = ResnetConfig(block_type="bottleneck", stem_width=32, stem_type="deep", avg_down=True) |
| | resnet50d_config.save_pretrained("custom-resnet") |
| | ``` |
| |
|
| | Isso salvará um arquivo chamado `config.json` dentro da pasta `custom-resnet`. Você pode então recarregar sua configuração com o |
| | método `from_pretrained`: |
| |
|
| | ```py |
| | resnet50d_config = ResnetConfig.from_pretrained("custom-resnet") |
| | ``` |
| |
|
| | Você também pode usar qualquer outro método da classe [`PretrainedConfig`], como [`~PretrainedConfig.push_to_hub`] para |
| | carregar diretamente sua configuração para o Hub. |
| |
|
| | ## Escrevendo um modelo customizado |
| |
|
| | Agora que temos nossa configuração ResNet, podemos continuar escrevendo o modelo. Na verdade, escreveremos dois: um que |
| | extrai os recursos ocultos de um lote de imagens (como [`BertModel`]) e um que é adequado para classificação de imagem |
| | (como [`BertForSequenceClassification`]). |
| |
|
| | Como mencionamos antes, escreveremos apenas um wrapper solto do modelo para mantê-lo simples para este exemplo. A única |
| | coisa que precisamos fazer antes de escrever esta classe é um mapa entre os tipos de bloco e as classes de bloco reais. Então o |
| | modelo é definido a partir da configuração passando tudo para a classe `ResNet`: |
| |
|
| | ```py |
| | 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) |
| | ``` |
| |
|
| | Para o modelo que irá classificar as imagens, vamos apenas alterar o método forward: |
| |
|
| | ```py |
| | 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} |
| | ``` |
| |
|
| | Em ambos os casos, observe como herdamos de `PreTrainedModel` e chamamos a inicialização da superclasse com o `config` |
| | (um pouco parecido quando você escreve um `torch.nn.Module`). A linha que define o `config_class` não é obrigatória, a menos que |
| | você deseje registrar seu modelo com as classes automáticas (consulte a última seção). |
| |
|
| | <Tip> |
| |
|
| | Se o seu modelo for muito semelhante a um modelo dentro da biblioteca, você poderá reutilizar a mesma configuração desse modelo. |
| |
|
| | </Tip> |
| |
|
| | Você pode fazer com que seu modelo retorne o que você quiser,porém retornando um dicionário como fizemos para |
| | `ResnetModelForImageClassification`, com a função de perda incluída quando os rótulos são passados, vai tornar seu modelo diretamente |
| | utilizável dentro da classe [`Trainer`]. Você pode usar outro formato de saída, desde que esteja planejando usar seu próprio |
| | laço de treinamento ou outra biblioteca para treinamento. |
| |
|
| | Agora que temos nossa classe do modelo, vamos criar uma: |
| |
|
| | ```py |
| | resnet50d = ResnetModelForImageClassification(resnet50d_config) |
| | ``` |
| |
|
| | Novamente, você pode usar qualquer um dos métodos do [`PreTrainedModel`], como [`~PreTrainedModel.save_pretrained`] ou |
| | [`~PreTrainedModel.push_to_hub`]. Usaremos o segundo na próxima seção e veremos como enviar os pesos e |
| | o código do nosso modelo. Mas primeiro, vamos carregar alguns pesos pré-treinados dentro do nosso modelo. |
| |
|
| | Em seu próprio caso de uso, você provavelmente estará treinando seu modelo customizado em seus próprios dados. Para este tutorial ser rápido, |
| | usaremos a versão pré-treinada do resnet50d. Como nosso modelo é apenas um wrapper em torno dele, será |
| | fácil de transferir esses pesos: |
| |
|
| | ```py |
| | import timm |
| |
|
| | pretrained_model = timm.create_model("resnet50d", pretrained=True) |
| | resnet50d.model.load_state_dict(pretrained_model.state_dict()) |
| | ``` |
| |
|
| | Agora vamos ver como ter certeza de que quando fazemos [`~PreTrainedModel.save_pretrained`] ou [`~PreTrainedModel.push_to_hub`], o |
| | código do modelo é salvo. |
| |
|
| | ## Enviando o código para o Hub |
| |
|
| | <Tip warning={true}> |
| |
|
| | Esta API é experimental e pode ter algumas pequenas alterações nas próximas versões. |
| |
|
| | </Tip> |
| |
|
| | Primeiro, certifique-se de que seu modelo esteja totalmente definido em um arquivo `.py`. Ele pode contar com importações relativas para alguns outros arquivos |
| | desde que todos os arquivos estejam no mesmo diretório (ainda não suportamos submódulos para este recurso). Para o nosso exemplo, |
| | vamos definir um arquivo `modeling_resnet.py` e um arquivo `configuration_resnet.py` em uma pasta no |
| | diretório de trabalho atual chamado `resnet_model`. O arquivo de configuração contém o código para `ResnetConfig` e o arquivo de modelagem |
| | contém o código do `ResnetModel` e `ResnetModelForImageClassification`. |
| |
|
| | ``` |
| | . |
| | └── resnet_model |
| | ├── __init__.py |
| | ├── configuration_resnet.py |
| | └── modeling_resnet.py |
| | ``` |
| |
|
| | O `__init__.py` pode estar vazio, apenas está lá para que o Python detecte que o `resnet_model` possa ser usado como um módulo. |
| |
|
| | <Tip warning={true}> |
| |
|
| | Se estiver copiando arquivos de modelagem da biblioteca, você precisará substituir todas as importações relativas na parte superior do arquivo |
| | para importar do pacote `transformers`. |
| |
|
| | </Tip> |
| |
|
| | Observe que você pode reutilizar (ou subclasse) uma configuração/modelo existente. |
| |
|
| | Para compartilhar seu modelo com a comunidade, siga estas etapas: primeiro importe o modelo ResNet e a configuração do |
| | arquivos criados: |
| |
|
| | ```py |
| | from resnet_model.configuration_resnet import ResnetConfig |
| | from resnet_model.modeling_resnet import ResnetModel, ResnetModelForImageClassification |
| | ``` |
| |
|
| | Então você tem que dizer à biblioteca que deseja copiar os arquivos de código desses objetos ao usar o `save_pretrained` |
| | e registrá-los corretamente com uma determinada classe automáticas (especialmente para modelos), basta executar: |
| |
|
| | ```py |
| | ResnetConfig.register_for_auto_class() |
| | ResnetModel.register_for_auto_class("AutoModel") |
| | ResnetModelForImageClassification.register_for_auto_class("AutoModelForImageClassification") |
| | ``` |
| |
|
| | Observe que não há necessidade de especificar uma classe automática para a configuração (há apenas uma classe automática, |
| | [`AutoConfig`]), mas é diferente para os modelos. Seu modelo customizado pode ser adequado para muitas tarefas diferentes, então você |
| | tem que especificar qual das classes automáticas é a correta para o seu modelo. |
| |
|
| | Em seguida, vamos criar a configuração e os modelos como fizemos antes: |
| |
|
| | ```py |
| | 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()) |
| | ``` |
| |
|
| | Agora para enviar o modelo para o Hub, certifique-se de estar logado. Ou execute no seu terminal: |
| |
|
| | ```bash |
| | huggingface-cli login |
| | ``` |
| |
|
| | ou a partir do notebook: |
| |
|
| | ```py |
| | from huggingface_hub import notebook_login |
| |
|
| | notebook_login() |
| | ``` |
| |
|
| | Você pode então enviar para seu próprio namespace (ou uma organização da qual você é membro) assim: |
| |
|
| |
|
| | ```py |
| | resnet50d.push_to_hub("custom-resnet50d") |
| | ``` |
| |
|
| | Além dos pesos do modelo e da configuração no formato json, isso também copiou o modelo e |
| | configuração `.py` na pasta `custom-resnet50d` e carregou o resultado para o Hub. Você pode conferir o resultado |
| | neste [repositório de modelos](https: |
| |
|
| | Consulte o [tutorial de compartilhamento](model_sharing) para obter mais informações sobre o método push_to_hub. |
| |
|
| | ## Usando um modelo com código customizado |
| |
|
| | Você pode usar qualquer configuração, modelo ou tokenizador com arquivos de código customizados em seu repositório com as classes automáticas e |
| | o método `from_pretrained`. Todos os arquivos e códigos carregados no Hub são verificados quanto a malware (consulte a documentação de [Segurança do Hub](https: |
| | revisar o código do modelo e o autor para evitar a execução de código malicioso em sua máquina. Defina `trust_remote_code=True` para usar |
| | um modelo com código customizado: |
| |
|
| | ```py |
| | from transformers import AutoModelForImageClassification |
| |
|
| | model = AutoModelForImageClassification.from_pretrained("sgugger/custom-resnet50d", trust_remote_code=True) |
| | ``` |
| |
|
| | Também é fortemente recomendado passar um hash de confirmação como uma `revisão` para garantir que o autor dos modelos não |
| | atualize o código com novas linhas maliciosas (a menos que você confie totalmente nos autores dos modelos). |
| |
|
| |
|
| | ```py |
| | commit_hash = "ed94a7c6247d8aedce4647f00f20de6875b5b292" |
| | model = AutoModelForImageClassification.from_pretrained( |
| | "sgugger/custom-resnet50d", trust_remote_code=True, revision=commit_hash |
| | ) |
| | ``` |
| |
|
| | Observe que ao navegar no histórico de commits do repositório do modelo no Hub, há um botão para copiar facilmente o commit |
| | hash de qualquer commit. |
| |
|
| | ## Registrando um modelo com código customizado para as classes automáticas |
| |
|
| | Se você estiver escrevendo uma biblioteca que estende 🤗 Transformers, talvez queira estender as classes automáticas para incluir seus próprios |
| | modelos. Isso é diferente de enviar o código para o Hub no sentido de que os usuários precisarão importar sua biblioteca para |
| | obter os modelos customizados (ao contrário de baixar automaticamente o código do modelo do Hub). |
| |
|
| | Desde que sua configuração tenha um atributo `model_type` diferente dos tipos de modelo existentes e que as classes do seu modelo |
| | tenha os atributos `config_class` corretos, você pode simplesmente adicioná-los às classes automáticas assim: |
| |
|
| | ```py |
| | from transformers import AutoConfig, AutoModel, AutoModelForImageClassification |
| |
|
| | AutoConfig.register("resnet", ResnetConfig) |
| | AutoModel.register(ResnetConfig, ResnetModel) |
| | AutoModelForImageClassification.register(ResnetConfig, ResnetModelForImageClassification) |
| | ``` |
| |
|
| | Observe que o primeiro argumento usado ao registrar sua configuração customizada para [`AutoConfig`] precisa corresponder ao `model_type` |
| | de sua configuração customizada. E o primeiro argumento usado ao registrar seus modelos customizados, para qualquer necessidade de classe de modelo automático |
| | deve corresponder ao `config_class` desses modelos. |
| |
|
| |
|