ONNX, Interoperabilidade entre Frameworks de Deep Learning

Handrey Emanuel Galon
gb.tech
Published in
10 min readMar 4, 2022
Setup de uma pessoa que trabalha com tecnologia com duas telas
Setup de uma pessoa que trabalha com tecnologia com duas telas | Foto de Farzad na Unsplash

Tensorflow, Pytorch, MXNet ou outro? Qual framework devo usar para desenvolver meu projeto de Machine Learning ou Deep Learning? Qual arquitetura de hardware se encaixa melhor para o que estou desenvolvendo, CPU, GPU, FPGA, ou outra?

A maior parte das pessoas que trabalha com ciência de dados deve ter feito essas e outras perguntas ao iniciar um projeto, pois atualmente existem muitas ferramentas para desenvolver uma aplicação. Tendo isso em mente, está na hora de conhecermos o Open Neural Network Exchange (ONNX), um padrão que visa garantir portabilidade e interoperabilidade entre os diversos frameworks sem perder velocidade na etapa de inferência.

Nesta publicação, irei abordar os conceitos referente ao padrão ONNX e na análise de velocidade na etapa de inferência de um modelo em Tensorflow e no padrão ONNX. A publicação está dividida nas seguintes seções:

  • Introdução
  • O que é exatamente o ONNX?
  • ONNX Runtime
  • Mãos na massa
  • Comparando três abordagens de inferência
  • Conclusão

Vamos nessa…

Introdução

Está cada vez mais complexo construir, implantar e gerenciar modelos de Machine Learning e Deep Learning. Isso se deve ao fato do surgimento de vários frameworks e arquiteturas de hardware personalizadas para essa área.

Tensorflow, Pytorch e MXNet são alguns dos frameworks mais populares para criação de modelos de Deep Learning. Cada framework tem suas características, alguns são mais orientados a pesquisa, como o Pytorch, já outros são mais utilizados para o deploy da aplicação, como é o caso do Tensorflow. No entanto, um modelo treinado pelo Tensorflow, não pode ser usado com PyTorch e vice-versa.

Além disso, para acelerar o treinamento e a inferência, aceleradores de IA em hardware são utilizados, tais como Graphics Processing Units (GPU), Field Programmable Gate Arrays (FPGA), Tensor Processing Units (TPU), etc. Cada um desses aceleradores acompanha drivers e bibliotecas personalizados para interagir com os programas de Deep Learning. NVIDIA CUDA/cuDNN é apenas um exemplo de software, que atua como interface para os aceleradores de hardware.

Então, há alguma forma de combinarmos as vantagens de cada framework, visando otimizar o desenvolvimento de modelos de Deep Learning? A resposta é SIM. Isso é possível por meio do padrão ONNX e pelo ONNX Runtime.

O que exatamente é o ONNX?

Em setembro de 2017, as empresas Meta (Facebook) e Microsoft introduziram o ONNX com o objetivo de aumentar a portabilidade e interoperabilidade entre diferentes frameworks de Deep Learning. Em outras palavras, ONNX é um padrão que representa modelos de Deep Learning, permitindo que esses modelos sejam transferidos entre frameworks.

“O ONNX é o primeiro passo em direção a um ecossistema aberto, onde os desenvolvedores de IA podem alternar facilmente entre ferramentas de última geração e escolher a combinação que é melhor para eles” [1].

Uma grande vantagem desse padrão, é a possibilidade de treinar um modelo no seu framework preferido sem se preocupar com as restrições de tempo de inferência, pois após finalizar o treinamento, basta converter o modelo para o formato ONNX e executar a inferência através do ONNX Runtime ou até mesmo, por outro framework, conforme é exibido na Figura 1.

Figura 1. Interoperabilidade oferecida pelo padrão ONNX. Fonte: Adaptado de [3].

Antes de existir o padrão ONNX, decidir qual framework utilizar em um projeto não era uma tarefa simples para cientistas de dados. Muitas vezes, era optado por utilizar o framework no qual a pessoa desenvolvedora possuía mais familiaridade (fato que ainda ocorre nos dias de hoje). No entanto, desenvolvedores podem preferir utilizar um determinado framework no início do projeto, durante a fase de pesquisa e desenvolvimento, porém as características e recursos desejados para a fase de deploy da aplicação podem ser completamente diferentes. Sem uma solução concreta para esse problema, desenvolvedores se viam na obrigação de optar por soluções criativas, incluindo até mesmo traduzir manualmente os modelos para cada etapa do projeto.

Após a etapa de treinamento de um modelo, a inferência poderá ser executada em diferentes plataformas e dispositivos, e para tirar o máximo proveito da combinação do software de IA com o hardware no qual a aplicação está rodando, é necessário que o desenvolvedor faça otimizações no modelo.

A falta de portabilidade e interoperabilidade entre modelos de Deep Learning é um problema complexo devido aos diversos aceleradores de hardware e software que existem hoje. Este problema é sanado pelo ONNX, devido ao fato do mesmo permitir que desenvolvedores alternem entre os diferentes frameworks com base no estágio atual do projeto.

Além da flexibilidade de escolher o framework na criação dos modelos oferecida pelo padrão ONNX, ele também permite que desenvolvedores de hardware otimizem o hardware focado em Deep Learning com base em uma especificação padrão compatível com diferentes frameworks. O ONNX Runtime se encaixa nesse cenário, já que é um mecanismo focado em melhorar o desempenho para modelos ONNX em várias plataformas (Windows, distribuições Linux e Mac e em CPU e GPU) e hardwares.

ONNX Runtime

Conforme a documentação do ONNX Runtime, ele é um acelerador multiplataforma focado em treinamento e inferências de modelos compatíveis com os frameworks mais comuns de Machine Learning e Deep Learning e também flexível para integração com bibliotecas específicas de hardware. É escrito em C++ e fornece APIs para Python, C, C++, C# e Java, permitindo integrar a inferência em aplicativos escritos em várias linguagens. Pode ser utilizado em várias plataformas funcionando igualmente bem em distribuições Linux, Windows e Mac.

Devido ao fato de cada plataforma de hardware ter seus recursos e características, otimizar diferentes combinações de frameworks e hardwares (CPU, GPU, FPGA, etc.) é uma tarefa extremamente custosa. Acelerar este processo é mais uma vantagem do ONNX, porque, uma vez que um modelo seja convertido para o padrão ONNX, ele pode ser executado em diferentes plataformas e dispositivos com as mesmas configurações de otimização. Um modelo ONNX é uma representação intermediária padrão (IR), que é bem definida e bem documentada.

O ONNX Runtime abstrai o hardware subjacente, expondo uma interface consistente para inferência. A Figura 2 demonstra a problemática abordada pelo ONNX e pelo ONNX Runtime. Nela, podemos imaginar o seguinte cenário: desenvolvedor(a) treinou e fez a otimização de um modelo utilizando Tensorflow com GPU, mas irá fazer o deploy em um dispositivo de borda e não quer perder a performance. Como isso poderia ser solucionado? Simples, basta exportar o modelo do Tensorflow para o padrão ONNX e, com o ONNX Runtime, poderá fazer o deploy de sua aplicação em qualquer arquitetura de hardware.

Figura 2. Problemática abordada pelo ONNX e ONNX Runtime. Fonte: ONNX Runtime Execution Providers.

Conforme podemos ver na Figura 3, a representação intermediária do ONNX tem como alvo o mesmo runtime, independente do framework em que é treinado. O ONNX Runtime possui provedores de execução que cuidam da otimização do modelo para o ambiente de hardware alvo.

Figura 3. Arquitetura de alto nível de execução de um modelo ONNX. Fonte: Adaptado de [5].

Para finalizar, o ONNX Runtime surgiu devido à necessidade de uma interface que acelere a inferência em diferentes arquiteturas de hardware. Em outras palavras, havia uma dependência entre o framework e a arquitetura de hardware para a qual o modelo era otimizado. É com o padrão ONNX e o acelerador ONNX Runtime que as portas se abrem para um amplo espectro de interoperabilidade entre frameworks e arquiteturas de hardware.

Mãos na massa

Agora que já estamos contextualizados com a teoria, vamos desenvolver um classificador utilizando o Tensorflow e a base de dados pública Fashion-MNIST. Após treinar o modelo, iremos executar a inferência no Tensorflow, no ONNX Runtime e no Tensorflow Backend para ONNX. Finalmente iremos fazer uma comparação entre os três métodos de inferência utilizados.

O TensorFlow Backend para ONNX possibilita o uso de modelos ONNX como entrada para o TensorFlow. O modelo ONNX é primeiro convertido em um modelo TensorFlow e depois delegado para execução no TensorFlow para produzir a saída. [4]

Caso seja de seu interesse, acesse o notebook que contém o código completo da aplicação.

Conforme a Listagem 1, iniciamos importando as bibliotecas necessárias para treinarmos um modelo no Tensorflow (linhas 1 e 2) e para realizarmos a exportação do modelo treinado para o padrão ONNX (linha 3). Além disso, importamos também as bibliotecas necessárias para executarmos a inferência pelo ONNX Runtime e pelo Tensorflow Backend para ONNX (linhas 4 e 5 respectivamente). Nas linhas 6 e 7 importamos as bibliotecas numpy e matplotlib, que irão nos auxiliar no processamento e visualização dos dados.

Listagem 1. Importação das bibliotecas necessárias.

Com os pacotes importados, na Listagem 2, vamos carregar a base de dados Fashion-MNIST na linha 1, a qual contém 70.000 imagens de peças de roupas com baixa resolução (28 x 28 pixels) em tons de cinza separadas em 10 classes.

Após isso, a base de dados é dividida em conjunto de treinamento (train_imagese train_labels) e conjunto de teste (test_imagese test_labels). Cada imagem é mapeada com um só label. Já que o nome das classes não são incluídas na base de dados, é criado uma lista na linha 4 que armazena essas informações.

Listagem 2. Carregar base de dados dividindo em conjunto de treino e teste.

Criando e avaliando o modelo no Tensorflow

A próxima etapa é construir a rede neural, isso requer configurar as camadas do modelo, e depois, compilar o mesmo, conforme demostrado na listagem abaixo das linhas 1 à 9.

Na linha 11 vamos executa o treinamento do modelo. Neste caso, vamos treinar o modelo com 10 épocas. Levando menos de 1 minuto em um ambiente com 1 GPU para concluir o treinamento. A GPU fornecida gratuitamente pelo Kaggle (onde o notebook foi criado) é uma NVIDIA TESLA P100. Lembrando que você pode ajustar o número de épocas conforme a sua necessidade.

Na linha 13, salvamos o modelo no diretório informado como parâmetro.

Listagem 3. Construindo, treinando e salvando o modelo.

Executando o trecho de código da Listagem 4, temos como resultado a acurácia do modelo, que foi de 88,17%.

Listagem 4. Verificação da performance do modelo em um conjunto de dados novos.

Convertendo o modelo do Tensorflow para o ONNX

O Tensorflow usa vários formatos de arquivo para representar um modelo, como arquivos de checkpoint, grafos ponderados e o próprio arquivo saved_model.

Irei abordar como converter o modelo do Tensorflow para o ONNX a partir do arquivo saved_model. Para isso, executamos o comando t2onnx.convert, fornecendo:

  • O caminho para o modelo do Tensorflow (neste caso o diretório é a pasta tf-fashion-mnist-model).
  • Um nome para o arquivo de saída ONNX (tf2onnx.onnx).
Listagem 5. Comando para converter o modelo do Tensorflow para o padrão ONNX.

Agora é possível analisar a estrutura do modelo ONNX pela ferramenta Netron. Podemos ver na Figura 4, que, conforme a listagem 3 (linhas 1 à 5), o modelo contém somente duas camadas densas, nas quais a segunda delas já é a camada de saída.

Figura 4. Arquitetura do modelo ONNX.

Executando a inferência no modelo ONNX pelo ONNX Runtime

Fazemos a inferência utilizando o ONNX Runtime conforme o código apresentado na Listagem 6. Na linha 1, criamos uma sessão no ONNX Runtime passando como parâmetro o modelo no padrão ONNX. Essa sessão será utilizada para realizar a inferência na linha 9.

Para executar a inferência, precisamos usar o mesmo nome da camada de entrada, juntamente com as imagens do conjunto de testes, e da camada de saída da rede neural como parâmetro do método run. Podemos buscar essa informação por meio dos métodos session.get_inputs(linha 2) e session.get_outputs(linha 3) para recuperar o nome da camada de entrada e saída respectivamente. Perceba que a saída destes comandos são as mesmas mostradas pelo Netron, na Figura 4.

Por padrão, o formato dos elementos dos arrays numpy, que representam as imagens, são no formato float64. Nós precisamos convertê-los para float32 na linha 7, que é formato correto para realizarmos a inferência pelo ONNX Runtime.

Listagem 6. Inferência utilizando ONNX Runtime.

Após executarmos a inferência, podemos executar o código da Listagem 7 para verificar a acurácia do modelo, que resultou em 88,17%.

acurácia = predições corretas/total de predições

Listagem 7. Verificando a acurácia do modelo ONNX.

Executando a inferência do modelo ONNX no Tensorflow Backend

A Listagem 8 apresenta como fazer a inferência utilizando o Tensorflow Backend para ONNX. Na linha 1, carregamos o arquivo do modelo ONNX e na linha 2 importamos para o Tensorflow.

Os arrays numpy são convertidos para float32 na linha 4 da mesma forma como foi feito para o ONNX Runtime.

Por fim, na linha 6, executamos a inferência passando como parâmetro o conjunto de imagens de testes.

Listagem 8. Inferência utilizando Tensorflow Backend para ONNX.

Podemos verificar a acurácia do modelo da mesma forma como no ONNX Runtime, executando o código da Listagem 7, que resultou também em 88,17%.

Comparando as três abordagens na etapa de inferência

O objetivo deste artigo não é encontrar o melhor modelo, apesar de que veremos que os resultados ficaram muito bom, como reflexo de uma boa preparação na base de dados.

Foram extraídos os relatórios de classificação das três abordagens utilizadas para fazer a inferência. O relatório mostra as principais métricas de classificação: precisão, recall e f1-score por classe. Talvez, se você tentar reproduzir em sua máquina local, o resultado não seja o mesmo, mas não será muito diferente do apresentado.

Listagem 9. Obtenção do relatório de classificação (classification_report).

Conforme já apresentado, a acurácia do modelo para os três casos foi de 88,17% e o relatório de classificação foi igual para os três casos, conforme apresentado na Figura 5, mostrando, dessa forma, que não há perdas no processo de conversão do modelo para o formato ONNX.

Figura 5. Relatório de classificação das três abordagens utilizadas para realizar a inferência.

Podemos ver na Figura 6 que os tempos de inferência utilizando o ONNX Runtime e o Tensorflow Backend para ONNX foram significativamente mais rápidos, tanto utilizando CPU quanto GPU.

Figura 6. Comparação dos tempos de inferência nas três abordagens utilizadas.

Devido a essa melhora na velocidade de inferência, o padrão ONNX pode ser uma ótima escolha para ser utilizado em dispositivos de borda, aonde o poder computacional geralmente é limitado.

Conclusão

Nesta publicação, conseguimos visualizar a importância de existir um padrão para modelos de Deep Learning e como é simples converter um modelo treinando no Tensorflow para o padrão ONNX.

A velocidade alta na execução da inferência utilizando o ONNX Runtime e o Tensorflow Backend para ONNX é um ótimo incentivo para começar a utilizar o padrão ONNX em suas aplicações, quando comparada a velocidade de inferência do modelo no Tensorflow.

Por fim, o exemplo prático abordado nesta publicação é apenas a ponta do iceberg para as aplicações e para o valor do ONNX. Para mais exemplos, confira Tutoriais ONNX.

Dúvidas ou sugestões, deixem nos comentários.

Referências

[1] Facebook and Microsoft introduce new open ecosystem for interchangeable AI frameworks. (Joaquin Quiñonero Candela).

[2] Github: microsoft/onnxruntime: ONNX Runtime: cross-platform, high performance ML inferencing and training accelerator.

[3] ONNX: Preventing Framework Lock in. (Fernando López).

[4] Github: onnx/onnx-tensorflow

[5] Open Neural Network Exchange Brings Interoperability to Machine Learning Frameworks. (Janakiram MSV).

[6] ONNX Runtime Execution Providers.

--

--