Este artigo será o primeiro de uma série que irá abordar diferentes tipos e aplicações do framework PyTorch na indústria.
Aqui, apresentarei conceitos introdutórios e extremamente importantes sobre o framework e seu ecossistema. A ideia é desenvolver uma trilha de aprendizado, indo do básico (abordado nesta parte 1) até os conceitos mais avançados, como métodos para lidar com big data, treinamento em GPU etc.
Por ora, falaremos de conceitos como tensores, camadas e funções custo, aplicando-os no desenvolvimento de um modelo muito difundido em aprendizado de máquina: regressão linear.
Todos os códigos desenvolvidos durante nossa jornada ficarão no seguinte repositório: https://github.com/felipesassi/pytorch-course.
O PyTorch foi disponibilizado para o público geral em determinado período de janeiro de 2017. Seu desenvolvimento foi realizado nos laboratórios de IA do Facebook.
Primeiramente, é importante deixar claro que o PyTorch é um framework para deep learning (ou aprendizado profundo). Um framework nada mais é do que um conjunto de ferramentas que auxiliam no desenvolvimento de projetos.
A preferência por um determinado framework ou outro é pessoal, mas vou elencar alguns pontos que me levaram a optar pelo PyTorch:
No início, o PyTorch era muito utilizado dentro das academias (principalmente devido à facilidade de desenvolver arquiteturas mais complexas nele), mas com o passar dos anos, diferentes empresas (como a Tesla) o adotaram para desenvolver seus produtos.
Regressão linear — Fundamentos
Antes de começar a trabalhar, mais alguns conceitos devem ser vistos. A ideia aqui não é rever tudo o que existe sobre regressões lineares, mas sim apresentar alguns recursos que serão úteis para seu desenvolvimento com PyTorch.
Uma regressão linear é usada para prever dados contínuos (ou seja: uma variável que queremos prever pode assumir infinitos valores). Vamos supor que precisamos prever o preço de venda de uma casa em função de diferentes características do imóvel, como área total, quantidade de quartos, presença de piscina etc.
Suponha que temos um conjunto de dados x, que pode ser uma matriz 1000x5 (onde 1000 se refere ao número de dados e 5 aos atributos medidos em cada observação), e um conjunto de dados observados y que, para fazer sentido com o exemplo, deve ser um vetor de 1000 observações.
Com isso, podemos treinar uma regressão linear para obter um modelo que represente a relação entre os conjuntos de dados x e y, ou seja: conseguimos obter uma função aproximada que modele o nosso problema. Nosso intuito é obter o modelo que gere a menor diferença possível entre nossas observações reais (y) e nossas predições (ŷ).
Utilizando um pouco de álgebra linear, podemos ver uma regressão linear da seguinte forma:
No nosso caso, y é a variável a se prever e w são os pesos que devem ser informados na entrada x.
Para se obter a matriz w, duas técnicas podem ser utilizadas:
A utilização do gradiente descendente é a mais comum, visto que as operações apresentadas na equação 1 (principalmente a inversão matricial) são extremamente custosas.
Para avaliar a qualidade da nossa predição, podemos usar uma métrica conhecida como erro quadrático médio, apresentada na equação 3:
Quanto mais próximo de zero esse erro estiver, melhor.
Conceitos básicos sobre PyTorch
Grande parte do que será apresentado a seguir foi publicado na documentação oficial do PyTorch, que pode ser uma excelente fonte de pesquisa e esclarecimento de dúvidas para esse tipo de implementação.
Tensores:
Tensores são uma estrutura de dados usada pelo PyTorch, semelhante a uma estrutura de arrays do numpy (valendo mencionar que uma conversão entre tensores e arrays do numpy é muito simples).
A seguir, mostrarei como os tensores funcionam com alguns exemplos bem simples.
Primeiro, apresento como converter um numpy array para um tensor e vice-versa.
A conversão é realizada por meio do método .numpy(). Um jeito simples de iniciar determinado projeto é realizar todas as operações com o numpy e convertê-las para tensor apenas no momento necessário. Um ponto interessante é que essa compatibilidade faz com que todo o ecossistema de aprendizado de máquina que utiliza o numpy (como pandas, matplotlib, scikit-learn) possa ser usado com facilidade nos projetos envolvendo o PyTorch.
Os tensores também podem ser criados a partir de listas. Uma sintaxe para isso fica assim:
O próximo passo é apresentar alguns atributos interessantes dos tensores, como seu shape e seu tipo. Para isso, devemos fazer o seguinte:
Para conversão de tipos (o que muitas vezes é necessário), o PyTorch traz alguns métodos nativos:
Com esses conhecimentos em mente, podemos prosseguir para uma série de operações envolvendo tensores. O código abaixo mostra algumas das possibilidades.
A última operação que devemos ver é a diferenciação. O PyTorch traz um sistema de diferenciação automática chamado autograd. Com ele, podemos computar facilmente derivadas (taxas de variação) de uma função em relação às suas variáveis. Vamos supor que precisamos calcular as derivadas parciais da função apresentada na figura 6.
O sistema autograd lida com isso diretamente. No exemplo abaixo, podemos ver que uma simples chamada ao método .backward() resolve o nosso problema. Note que passamos o argumento requires_grad=True pela função torch.tensor(). Esse argumento avisa ao PyTorch que queremos computar a derivada em relação a essa variável.
Agora, vamos abordar alguns conceitos mais aprofundados do PyTorch e do mundo de deep learning. Não se preocupe se algumas passagens ficarem obscuras: à medida que avançarmos, o entendimento será mais natural.
Layers
Layers, ou camadas, são operações que podem ser aplicadas a tensores. Algumas dessas operações serão abordadas no futuro, mas no momento podemos citar:
Loss functions
Loss functions são funções que nos ajudam a medir como está o desempenho do nosso modelo. Além disso, elas servem como objetivo de minimização durante o treinamento, ou seja: durante o treino, os pesos do nosso modelo vão sendo atualizados para fazer com que nossa perda seja reduzida.
Um exemplo de loss function é a equação 3, muito utilizada em problemas de regressão.
Optimizers
Quem tem algum tipo de familiaridade com redes neurais sabe que seu treinamento é realizado por meio de uma técnica conhecida como gradiente descendente. Essa técnica é o modo mais simples de atualização de pesos em problemas de otimização numérica. O PyTorch implementa diferentes refinamentos desse método, de forma a auxiliar no treinamento das redes neurais. O otimizador mais utilizado é o Adam (artigo sobre ele e outros otimizadores nas referências).
Com esses conhecimentos, já é possível ter uma ideia das principais etapas necessárias para desenvolver um projeto com o PyTorch. Para não me alongar, deixarei alguns artigos nas referências sobre alguns temas interessantes (e mais complexos também), como automatic diferentiation.
Prevendo preços de imóveis com regressão linear
Para aplicarmos nosso modelo, vamos utilizar um conjunto de dados bem conhecido em problemas de regressão, o boston housing dataset (que inclusive está disponível para uso dentro do módulo sklearn.datasets).
A ideia desse conjunto de dados é prever o preço de um imóvel com base em suas características, como número de quartos, acessibilidade do imóvel etc.
Para prosseguirmos, você deve assumir algumas coisas:
Dito isso, podemos desenvolver nossa regressão linear. Uma das principais formas para desenvolver modelos no PyTorch é utilizando classes. O trecho de código abaixo apresenta a estrutura básica que seu modelo deve ter:
O método forward() é efetivamente chamado para realizar a predição. Utilizando o esqueleto apresentado, nossa regressão linear fica da seguinte forma:
A classe nn.LinearModel() implementa a equação 1. O usuário só precisa informar a quantidade de features de entrada (que no nosso caso é 13, o número de características do imóvel) e a quantidade de features de saída (que no nosso caso é o preço do imóvel, ou seja: 1). No método forward(), a equação 1 é devidamente aplicada aos dados de entrada.
Abaixo, apresento uma função bem simples para treino:
Essa função treina e valida o modelo por determinado número de épocas (variável epochs), sendo um esqueleto útil para os seus projetos futuros.
A figura 12 mostra o trecho de código responsável por instanciar nossa classe LinearModel e a chamada da função train_validate_model (não se esqueça de definir a métrica mean_squared_error).
Os resultados foram os seguintes:
Podemos ver que a cada época, nosso custo decai até atingir um valor estável de 19,34 para o treinamento e 33,28 para a validação.
Tratamos de vários assuntos nesse primeiro momento, muitos deles para criar uma ponte entre conceitos que você já deve conhecer, como erro quadrático médio, e como isso se encaixa no framework PyTorch.
Nos próximos artigos, daremos continuidade aos nossos estudos, desenvolvendo redes neurais para os mais diversos fins.
Espero que tenham gostado!
Qualquer dúvida, é só me chamar no linkedin/e-mail.
(Não necessariamente na ordem de ocorrência)