Article image
Ubiratan Tavares
Ubiratan Tavares20/10/2023 17:32
Compartilhe

Visão Geral de Algumas Bibliotecas de Aprendizagem Profundo

    1 - INTRODUÇÃO

    O aprendizado de máquina é um tópico amplo. O aprendizado profundo, em particular, é uma forma de usar redes neurais para aprendizado de máquina. Uma rede neural é provavelmente um conceito mais antigo que o aprendizado de máquina, que remonta à década de 1950. Não é novidade que muitas bibliotecas foram criadas para isso.

    O objetivo a seguir é dar uma visão geral de algumas das famosas bibliotecas para redes neurais e aprendizado profundo.

    2 - BIBLIOTECAS C++

    O aprendizado profundo ganhou atenção na última década. Antes disso, havia pouca confiança em como treinar uma rede neural com muitas camadas. No entanto, a compreensão de como construir um perceptron multicamadas já existia há muitos anos.

    Antes de termos aprendizado profundo, provavelmente a biblioteca de redes neurais mais famosa era a biblioteca libann (consulte [1]). É uma biblioteca para C++ e a funcionalidade é limitada devido à sua idade. Esta biblioteca parou de ser desenvolvida desde 2004.

    Uma biblioteca mais recente para C++ é OpenNN (consulte [2]), que permite sintaxe C++ moderna.

    Mas isso é tudo para C++. A sintaxe rígida do C++ pode ser o motivo pelo qual não temos muitas bibliotecas para aprendizado profundo. A fase de treinamento de um projeto de aprendizagem profunda é sobre experimentos. Queremos algumas ferramentas que nos permitam iterar mais rapidamente. Portanto, uma linguagem de programação dinâmica poderia ser uma opção melhor. Portanto, você vê o Python entrar em cena.

    3 - BIBLIOTECAS PYTHON

    Uma das primeiras bibliotecas para aprendizagem profunda é a Caffe (Consulte [3]). Foi desenvolvido na UC Berkeley especificamente para problemas de visão computacional. Embora seja desenvolvido em C++, serve como uma biblioteca com interface Python. Portanto, podemos construir nosso projeto em Python com a rede definida em uma sintaxe semelhante a JSON.

    Chainer (Consulte [4]) é outra biblioteca em Python. É influente porque a sintaxe faz muito sentido. Embora seja menos comum hoje em dia, a API em Keras (Consulte [5]) e PyTorch (Consulte [6]) tem uma semelhança com o Chainer.

    A outra biblioteca obsoleta é Theano (Consulte [7]). Ele cessou o desenvolvimento, mas já foi uma importante biblioteca para aprendizado profundo. A versão anterior da biblioteca Keras permite escolher entre um back-end Theano ou TensorFlow (consulte [8]). Na verdade, nem Theano nem TensorFlow são exatamente bibliotecas de aprendizado profundo. Em vez disso, são bibliotecas de tensores que tornam as operações de matriz e a diferenciação úteis, sobre as quais as operações de aprendizagem profunda podem ser construídas. Conseqüentemente, esses dois são considerados substitutos um do outro na perspectiva Keras.

    CNTK (Consulte [9]) da Microsoft e Apache MXNet (Consulte [10]) são duas outras bibliotecas que valem a pena mencionar. Eles são grandes, com interfaces para vários idiomas. Python, claro, é um deles. CNTK possui interfaces C# e C++, enquanto MXNet fornece interfaces para Java, Scala, R, Julia, C++, Clojure e Perl. Mas recentemente, a Microsoft decidiu parar de desenvolver o CNTK. MXNet tem algum impulso e é provavelmente a biblioteca mais popular depois do TensorFlow e do PyTorch.

    4 - BIBLIOTECAS PyTorch E TensorFlow

    PyTorch e TensorFlow são as duas principais bibliotecas da atualidade. No passado, quando o TensorFlow estava na versão 1.x, eles eram muito diferentes. Mas como o TensorFlow absorveu Keras como parte de sua biblioteca, essas duas bibliotecas funcionam principalmente de maneira semelhante.

    PyTorch é apoiado pelo Facebook e sua sintaxe tem se mantido estável ao longo dos anos. Existem também muitos modelos existentes que podemos emprestar. A maneira comum de definir um modelo de aprendizado profundo no PyTorch é criar uma classe:

    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    
    class Model(nn.Module):
    def __init__(self):
      super().__init__()
      self.conv1 = nn.Conv2d(1, 6, kernel_size=(5,5), stride=1, padding=2)
      self.pool1 = nn.AvgPool2d(kernel_size=2, stride=2)
      self.conv2 = nn.Conv2d(6, 16, kernel_size=5, stride=1, padding=0)
      self.pool2 = nn.AvgPool2d(kernel_size=2, stride=2)
      self.conv3 = nn.Conv2d(16, 120, kernel_size=5, stride=1, padding=0)
      self.flatten = nn.Flatten()
      self.linear4 = nn.Linear(120, 84)
      self.linear5 = nn.Linear(84, 10)
      self.softmax = nn.LogSoftMax(dim=1)
    
    def forward(self, x):
      x = F.tanh(self.conv1(x))
      x = self.pool1(x)
      x = F.tanh(self.conv2(x))
      x = self.pool2(x)
      x = F.tanh(self.conv3(x))
      x = self.flatten(x)
      x = F.tanh(self.linear4(x))
      x = self.linear5(x)
      return self.softmax(x)
    
    model = Model()
    

    Mas também existe uma sintaxe sequencial para tornar o código mais conciso:

    import torch
    import torch.nn as nn
    
    model = nn.Sequential(
    # assume input 1x28x28
    nn.Conv2d(1, 6, kernel_size=(5,5), stride=1, padding=2),
    nn.Tanh(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5, stride=1, padding=0),
    nn.Tanh(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(16, 120, kernel_size=5, stride=1, padding=0),
    nn.Tanh(),
    nn.Flatten(),
    nn.Linear(120, 84),
    nn.Tanh(),
    nn.Linear(84, 10),
    nn.LogSoftmax(dim=1)
    )
    

    O TensorFlow na versão 2.x adotou Keras como parte de suas bibliotecas. No passado, esses dois eram projetos separados. No TensorFlow 1.x, precisamos construir um gráfico de computação, configurar uma sessão e derivar gradientes de uma sessão para o modelo de aprendizado profundo. Portanto, é um pouco detalhado. Keras foi projetado como uma biblioteca para ocultar todos esses detalhes de baixo nível.

    A mesma rede acima pode ser produzida pela sintaxe Keras do TensorFlow da seguinte forma:

    from tensorflow.keras.models import Sequential
    from tensorflow.keras.layers import Conv2D, Dense, AveragePooling2D, Flatten
    
    model = Sequential([
    Conv2D(6, (5,5), input_shape=(28,28,1), padding="same", activation="tanh"),
    AveragePooling2D((2,2), strides=2),
    Conv2D(16, (5,5), activation="tanh"),
    AveragePooling2D((2,2), strides=2),
    Conv2D(120, (5,5), activation="tanh"),
    Flatten(),
    Dense(84, activation="tanh"),
    Dense(10, activation="softmax")
    ])
    

    Uma grande diferença entre a sintaxe PyTorch e Keras está no loop de treinamento. No Keras, precisamos apenas atribuir a função de perda, o algoritmo de otimização, o conjunto de dados e alguns outros parâmetros ao modelo. Então temos uma função fit() para fazer todo o trabalho de treinamento, como segue:

    from tensorflow.keras.models import Sequential
    from tensorflow.keras.layers import Conv2D, Dense, AveragePooling2D, Flatten
    from tensorflow.keras.datasets import mnist
    from tensorflow.keras.utils import to_categorical
    
    (X_train, y_train), (X_test, y_test) = mnist.load_data()
    y_train = to_categorical(y_train)
    y_test = to_categorical(y_test)
    
    model = Sequential([
    Conv2D(6, (5,5), input_shape=(28,28,1), padding="same", activation="tanh"),
    AveragePooling2D((2,2), strides=2),
    Conv2D(16, (5,5), activation="tanh"),
    AveragePooling2D((2,2), strides=2),
    Conv2D(120, (5,5), activation="tanh"),
    Flatten(),
    Dense(84, activation="tanh"),
    Dense(10, activation="softmax")
    ])
    
    model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])
    model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=100, batch_size=32)
    

    Mas no PyTorch, precisamos escrever nosso próprio código de loop de treinamento, como segue:

    import numpy as np
    import torch
    import torch.nn as nn
    import torch.optim as optim
    import torchvision
    
    # Load MNIST data
    transform = torchvision.transforms.Compose([
    torchvision.transforms.ToTensor(), # required, otherwise MNIST are in PIL format
    #torchvision.transforms.Normalize((0.5,), (0.5,)),
    ])
    train = torchvision.datasets.MNIST('./datafiles/', train=True, download=True, transform=transform)
    test = torchvision.datasets.MNIST('./datafiles/', train=True, download=True, transform=transform)
    
    # For manual feed into the model
    X_train = train.data.reshape(-1,1,28,28)
    y_train = train.targets
    X_test = test.data.reshape(-1,1,28,28)
    y_test = test.targets
    
    # As iterator for data and target
    train_loader = torch.utils.data.DataLoader(train, batch_size=32, shuffle=True)
    test_loader = torch.utils.data.DataLoader(test, batch_size=32, shuffle=True)
    
    # Neural network model
    model = nn.Sequential(
    # assume input 1x28x28
    nn.Conv2d(1, 6, kernel_size=(5,5), stride=1, padding=2),
    nn.Tanh(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5, stride=1, padding=0),
    nn.Tanh(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(16, 120, kernel_size=5, stride=1, padding=0),
    nn.Tanh(),
    nn.Flatten(),
    nn.Linear(120, 84),
    nn.Tanh(),
    nn.Linear(84, 10),
    nn.LogSoftmax(dim=1)
    )
    
    # self-defined training loop function
    def training_loop(model, optimizer, loss_fn, train_loader, val_loader=None, n_epochs=100):
    best_loss, best_epoch = np.inf, -1
    best_state = model.state_dict()
    
    for epoch in range(n_epochs):
      # Training
      model.train()
      train_loss = 0
      for data, target in train_loader:
        output = model(data)
        loss = loss_fn(output, target)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        train_loss += loss.item()
      # Validation
      model.eval()
      status = (f"{str(datetime.datetime.now())} End of epoch {epoch}, "
           f"training loss={train_loss/len(train_loader)}")
      if val_loader:
        val_loss = 0
        for data, target in val_loader:
          output = model(data)
          loss = loss_fn(output, target)
          val_loss += loss.item()
        status += f", validation loss={val_loss/len(val_loader)}"
      print(status)
    
    optimizer = optim.Adam(model.parameters())
    criterion = nn.NLLLoss()
    training_loop(model, optimizer, criterion, train_loader, test_loader, n_epochs=100)
    

    Isso pode não ser um problema se você estiver experimentando um novo design de rede no qual deseja ter mais controle sobre como a perda é calculada e como o otimizador atualiza os pesos do modelo. Caso contrário, você apreciará a sintaxe mais simples do Keras.

    Observe que PyTorch e TensorFlow são bibliotecas com interface Python. Portanto, é possível ter interface para outras linguagens também. Por exemplo, existem Torch for R (Consulte [11]) e TensorFlow for R (Consulte [12]).

    Além disso, observe que as bibliotecas mencionadas acima são bibliotecas completas que incluem treinamento e previsão. Se você considerar um ambiente de produção onde você faz uso de um modelo treinado, poderá haver uma escolha mais ampla. O TensorFlow possui uma contraparte denominada “TensorFlow Lite” que permite que um modelo treinado seja executado em um dispositivo móvel ou na web.

    5 - REFERÊNCIAS

    1. libann
    2. OpenNN
    3. Caffe
    4. Chainer
    5. Keras
    6. PyTorch
    7. Theano
    8. TensorFlow
    9. CNTK
    10. MXNet
    11. Torch for R
    12. TensorFlow for R
    Compartilhe
    Comentários (1)
    Carlos Lima
    Carlos Lima - 07/04/2024 01:25

    Ubiratan Tavares, ótimo post! Gosto da forma como teve a preocupação de mostrar a implementação do PyTorch, por exemplo. Inclusive, uma ferramenta que considero indispensável nos dias de hoje é o fast.ai, especialmente porque foi desenvolvida no topo do PyTorch, tornando a implementação e o uso de redes neurais muito mais simples do que começar do zero (para algumas situações, não tem jeito haha).