Open GabrielBG0 opened 10 months ago
Ao criar uma API para transformações, é essencial considerar a utilização de padrões estabelecidos e bem-documentados. Neste caso, fiz uma busca sobre como implemetar transformações em alguns projetos do campo de visão computational, incluindo: MONAI, lightly, ClassyVision, MMCV (alicerce utilizdo pelo MMSegmentation e MMDetection) e nas recomendações do Pytorch.
Um ponto importante que vale ressaltar, visto em todos os projetos, é que as transformações são aplicadas a nível de amostra e não a nível de batch. Isso significa que a transformação é aplicada a cada amostra individualmente, e não a um batch de amostras ou no conjunto de dados como um todo.
Interoperabilidade: Ao aderir aos padrões sugeridos por projetos populares, como PyTorch, MONAI, lightly, ClassyVision e MMCV, garantimos que as transformações possam ser facilmente integradas em diferentes ecossistemas e frameworks.
Documentação Abundante: Projetos amplamente utilizados tendem a oferecer documentação abrangente, o que simplifica o entendimento e a utilização das transformações por parte dos desenvolvedores.
Comunidade Ativa: Ao alinhar-se com APIs conhecidas, beneficiamo-nos de uma comunidade ativa e engajada, facilitando a obtenção de suporte e contribuições externas.
A estrutura básica da API de transformações pode seguir o padrão estabelecido pelo projeto PyTorch. Nela utiliza-se uma classe base abstrata que implementa o método __call__
. Essa é mostrada abaixo:
class BaseTransform:
def __call__(self, sample):
raise NotImplementedError
Aqui, sample
representa a entrada da transformação.
Entretanto, o pytorch não especifica o tipo de entrada e saída, e isso pode levar a ambiguidades e dificuldades na utilização.
sample
pode ser um simples tensor, uma imagem, um dicionário ou qualquer outra estrutura de dados.
De fato, implementações em diferentes projetos podem variar em relação à estrutura e funcionalidade:
np.ndarray
, enquanto outros podem ser dicionários, ou até possuir mais de um único argumento. Desta forma, os subpacotes de transformações definem uma estrutura sobre o tipo da entrada e saída.__call__
, onde a entrada e saída é um dicionário.Assim, vale ressaltar que a estrutura da API de transformações pode variar de acordo com o projeto, mas é importante que ela seja clara e consistente em seu uso e na definição de seus subpacotes, para que os desenvolvedores possam utilizá-la de forma intuitiva, mas também para que seja fácil de integrar em diferentes ecossistemas e frameworks.
Desta forma, sugiro que a estrutura da API de transformações siga o padrão estabelecido pelo PyTorch, mas que os tipos de entrada e saída sejam especificados e que transformações as sejam aplicadas a nível de amostra e não a nível de batch. Desta forma, as transformações devem ser implementadas como classes, e não como funções, para que possam ser facilmente integradas em pipelines de transformações. Além disso, as transformações podem ser agrupaadas em subpacotes, de acordo com o tipo de entrada e saída/nicho, como, por exemplo, transforms.signal_1d
, .transforms.spatial
.
Para abordar esse problema, é fundamental fornecer uma definição clara dos tipos esperados na entrada e saída da transformação. O uso de anotações de tipo (Type Hints
) na assinatura do método __call__
torna explícito o que a transformação espera e retorna.
from PIL import Image
def __call__(self, sample: Dict[str, Union[np.ndarray, Image.Image]]) -> Dict[str, Union[np.ndarray, np.ndarray]]:
# ...
Aqui, Dict[str, Union[np.ndarray, np.ndarray]]
indica que a entrada sample
é um dicionário com chaves de string, contendo valores que podem ser np.ndarray
ou qualquer subtipo compatível.
Em casos em que tipos mistos são comuns, como dicionários contendo diferentes tipos de dados, é essencial documentar claramente a estrutura esperada. Além disso, posteriormente, a API pode incluir verificações ou tratamentos condicionais para lidar com diferentes tipos de entrada.
Nossa achei super legal, por mim é isso aí. A menos que o @fernandoGubiMarques tenha algo a complementar acho que a gente pode fechar esse estilo mesmo. Acho importante a gente ter uma definição de tipos pra todo o código, tipagem dinâmica é a receita pra problema mais pra frente.
Responsible for creating data augmentations
Features to be implemented