Principais conceitos por trás do Machine Learning

A versão em inglês deste artigo está disponível em Main concepts behind Machine Learning.

Machine Learning é um conceito que está em alta atualmente. Ele é uma subárea da inteligência artificial e consiste no fato de uma máquina conseguir aprender sozinha a realizar determinada tarefa sem ter sido programada explicitamente. Como exemplos de aplicações temos: caracterização de um tumor, predição financeira e até mesmo reconhecimento facial.

Machine learning aplicado em caracterização de tumor e reconhecimento facial

Existem diversos frameworks como TensorFlow, Keras, Caffe2 e PyTorch que facilitam o desenvolvimento de aplicações que utilizam machine learning, inclusive para aqueles menos experientes.

Por outro lado, tais frameworks possuem um alto grau de abstração, fazendo com que os algoritmos de machine learning sejam vistos como uma caixa preta, ou seja, seus detalhes de implementação são "escondidos", fazendo com que muitas pessoas não entendam o que acontece neles.

Alguns dos principais frameworks utilizados para machine learning

O objetivo deste artigo é mostrar um pouco do que está dentro desta caixa preta. Focaremos em um tipo específico de machine learning, a aprendizagem supervisionada, citando e explicando alguns dos principais conceitos relacionados à ela.


Aprendizagem Supervisionada

Imagine que você está ensinando uma criança a diferenciar cachorros e gatos: primeiro você mostra várias imagens de ambos, identificando cada uma delas. Com esses exemplos, ela consegue associar cada animal com o seu respectivo nome e, assim, classificar corretamente novas imagens.

A aprendizagem supervisionada utiliza exatamente a mesma ideia, a partir de um grande conjunto de dados rotulados, o algoritmo "aprende" a relação entre o dado e o rótulo e, com isso, ele consegue prever o resultado para qualquer nova entrada.

Para os mais técnicos, em uma linguagem matemática, estamos tentando encontrar uma expressão Y = f(X) + b que consiga prever os resultados. Onde X é a entrada, Y é a previsão do algoritmo e f(X) + b é o modelo aprendido pelo mesmo.

As duas principais tarefas que utilizam a aprendizagem supervisionada são: classificação e regressão.

As duas principais aplicações da aprendizagem supervisionada

A primeira, como o próprio nome diz, está relacionada em separar os dados em categorias predeterminadas, como, por exemplo, classificar imagens em cachorro, gato ou pássaro. Já a segunda, tem como objetivo prever um valor contínuo dadas determinadas condições, por exemplo, estimar o preço de uma casa de acordo com o seu tamanho, sua localização e o número de cômodos.

Para falarmos dos conceitos relacionados à aprendizagem supervisionada, utilizaremos como exemplo um tipo específico de classificação: classificação linear. Apesar de simples, os mesmos conceitos apresentados também valem para outros modelos mais complexos como redes neurais, CNNs (Redes Neurais Convolucional) e deep learning.


Classificação linear

Na classificação linear, a expressão Y = f(X) + b também é chamada de função de pontuação. Neste caso, a função f(X) = X⋅W é uma simples multiplicação de matrizes, onde W são os pesos que serão aprendidos pelo algoritmo e Y representa a pontuação para cada categoria (quanto maior a pontuação maior a chance de ser a categoria correta). Basicamente, o que estamos tentando fazer é desenhar retas que consigam separar as categorias da melhor maneira possível.

Exemplo mostrando o cálculo da pontuação

Uma possível interpretação para o peso W é que cada linha representa um modelo para a respectiva categoria, isto é, se visualizarmos a imagem que cada linha forma, veremos algo que se aproxima da categoria que ele representa (veremos isso ocorrendo mais para frente).

Na imagem acima, observando o resultado Y conseguimos ter uma ideia se os valores de W e b estão consistentes ou não. Porém, como podemos medir e dizer para o algoritmo o quão bom (ou quão ruim) estão os nossos parâmetros?

Para isso, utilizamos a função de perda.


Função de perda

Também conhecido como função de custo, ela mede o nível de infelicidade com o trabalho sendo feito, ou seja, se o algoritmo estiver muito ruim, seu valor será alto.

Basicamente, o que ela faz é comparar as pontuações da categoria certa com as outras e com isso determinar o quão satisfeita ela está com o resultado. Os dois tipos mais populares de função de perda são hinge-loss e cross-entropy.

O primeiro é utilizado em classificadores SVM (Supported Vector Machine) e ele está preocupado em deixar a pontuação da categoria correta acima das demais por uma margem Δ.

Fórmula para o hinge-loss. sᵢ é a pontuação da categoria correta

O segundo é utilizado com o Softmax que interpreta as pontuações como probabilidades, sempre tentando deixar a probabilidade da categoria correta próximo de 1.

Fórmula para o cross-entropy. sᵢ é a pontuação da categoria correta
Exemplo de cálculo do hinge loss e cross-entropy

É importante observar que as fórmulas apresentadas acima são para um dado. Para calcular sobre todo o conjunto de treinamento, fazemos a média de todas as perdas:

Perda total sobre todo o conjunto de treinamento

Overfitting

Um cuidado que sempre devemos tomar ao treinar um conjunto de dados é com o overfitting. Isso ocorre quando o algoritmo tenta encaixar perfeitamente todos os dados de treinamento, criando um modelo complexo e com diversos "ruídos". Por ruído, eu digo as características das imagens que não representa o objeto em geral, mas sim uma particularidade daquele exemplo. E isso é ruim pois, apesar de obtermos um bom resultado nos dados de treinamento, quando utilizarmos novos dados, o resultado pode não ser o desejado.

O ponto vermelho é corretamente classificado sem overfitting, mas não com overfitting

Há duas técnicas para evitar o overfitting: a primeira é aumentar o conjunto de treinamento, e a segunda é o que chamamos de regularização.

Regularização

“Se você tem duas soluções equivalentes, escolha a mais simples Navalha de Occam.

Essa técnica adiciona uma penalização em modelos que são muito complexos ou que dão muito destaque para uma característica específica. Ela prefere modelos mais simples uma vez que são melhores para generalizar.

Assim, com a adição da regularização, a perda total fica:

Perda total com regularização R(W)

Onde λ representa a "força" da regularização, e é um hiperparâmetro que devemos definir.

Pronto! Vimos como a máquina sabe se ela está fazendo um bom trabalho ou não. Mas como é que ela sabe o que fazer com essa informação, ou seja, como é que ela aprende sozinha?


Otimização

Como visto na seção anterior, quanto menor a perda, melhor é o nosso modelo. Assim, o objetivo da otimização é encontrar os parâmetros que minimizam a nossa função de perda.

Pensando em Cálculo, sempre que falamos em máximos e mínimo de uma função, lembramos de derivadas e, no caso de dimensões maiores, gradiente. O gradiente nos diz a direção em que a curva está mais inclinada, assim, basta seguir a direção oposta do gradiente para encontrarmos o mínimo da função. Essa técnica é chamada de gradiente descendente.

Para entender um pouco melhor, podemos fazer uma analogia com uma pessoa de olhos vendados em uma montanha tentando encontrar um vale. Um possível método seria sentir a inclinação do terreno com seus pés e dar um pequeno passo para uma direção mais baixa. Esse processo se repete até que ela chegue num lugar plano, indicando que chegou em um vale.

Figura representando o gradiente descendente

O tamanho do passo (conhecido como taxa de aprendizagem) que devemos tomar a cada iteração é um dos hiperparâmetros mais importantes a ser decidido quando se está treinando um modelo, pois, se o seu valor for muito pequeno, o progresso pode ser muito devagar, e caso seja muito grande, podemos até mesmo ter uma piora na perda.

Agora que vimos alguns dos principais conceitos envolvendo Machine Learning, vamos ver um exemplo prático bem simples.


Exemplo prático — Classificador linear com SVM

Todo o código pode ser acessado neste repositório.

Foi utilizado o dataset CIFAR-10 que possui 60000 imagens 32x32 distribuídas igualmente em 10 categorias, nas quais 50000 são para treinamento e 10000 são para teste.

Primeiramente é feito o download das imagens e um pré-processamento das mesmas. Isso é sempre uma boa prática antes de começar a treinar um modelo.

Algumas amotras de cada categoria

Em seguida implementamos a função que calcula a perda e o gradiente:

def svm_loss_naive(W, X, y, reg):
  dW = np.zeros(W.shape) # inicializa o gradiente com 0's
num_classes = W.shape[1]
num_train = X.shape[0]
loss = 0.0
  for i in range(num_train):
scores = X[i].dot(W)
correct_class_score = scores[y[i]]
for j in range(num_classes):
if j == y[i]:
continue
margin = scores[j] - correct_class_score + 1 # note delta = 1
if margin > 0:
loss += margin
dW.T[j] += X[i]
dW.T[y[i]] -= X[i]
  # Média
loss /= num_train
dW /= num_train
  # Regularização
loss += reg * np.sum(W*W)
dW += 2 * reg * W
  return loss, dWmesmo código:

Como trabalhamos com um número muito grande de imagens, é sempre bom evitar loops na implementação, a seguir temos uma versão vetorizada do mesmo código:

def svm_loss_vectorized(W, X, y, reg):
  dW = np.zeros(W.shape) # inicializa o gradiente com 0's
num_classes = W.shape[1]
num_train = X.shape[0]
loss = 0.0
  scores = X.dot(W)
margins = np.maximum(0, scores - scores[np.arange(num_train), y].reshape(num_train, 1) + 1)
margins[np.arange(num_train), y] = 0
loss += np.sum(margins)
  # Média
loss /= num_train
  # Regularização
loss += reg * np.sum(W * W)
  # Gradiente
binary = margins
binary[margins > 0] = 1
row_sum = np.sum(binary, axis=1)
binary[np.arange(num_train), y] = -row_sum.T
dW = np.dot(X.T, binary)
  # Média
dW /= num_train
  # Regularização
dW += 2 * reg * W
  return loss, dW

Executando ambas funções e calculando o tempo de execução de cada uma, obtemos que o primeiro executou em 0.196567s e o segundo em 0.006055s, um aumento de velocidade bastante significativo.

Com os cálculos da perda e do gradiente feitos, podemos treinar o nosso modelo, que é quando aplicamos o gradiente descendente:

def train(self, X, y, learning_rate=1e-3, reg=1e-5, num_iters=100,
batch_size=200, verbose=False):
num_train, dim = X.shape
num_classes = np.max(y) + 1
if W is None:
# initialize W
W = 0.001 * np.random.randn(dim, num_classes)
  # Executa o gradiente descendente para otimizar W
for it in range(num_iters):
X_batch = None
y_batch = None
idxs = np.random.choice(range(num_train), size=batch_size, replace=True)
X_batch = X[idxs, :]
y_batch = y[idxs]
    # calcula a perda e o gradiente
loss, grad = loss_grad(X_batch, y_batch, reg)
    # faz a atualização do parâmetro
W += - grad * learning_rate

Vale ressaltar que, nessa etapa, é preciso tomar cuidado com os valores escolhidos dos hiperparâmetros para não ocorrer overfitting. O resultado desse modelo foi uma precisão de 35,8% no conjunto de teste. Apesar de a precisão não ser muito alta, é um valor razoável pela simplicidade do modelo.

Agora que já treinamos nosso modelo, podemos observar como ficou o parâmetro W:

Cada figura representa uma linha de W

Apesar das imagens serem bastante confusas, é possível notar uma leve semelhança entre a imagem e a categoria que ela representa. No cavalo, por exemplo, vemos algo que se parece com um cavalo de duas cabeças, isso se deve ao fato de no conjunto de treinamento existirem tanto cavalos virados para a direita quanto virados para a esquerda, e o nosso algoritmo tenta aprender tudo que está no conjunto.

E isso é justamente uma das desvantagens de usar classificação linear para imagens, pois aprendemos apenas um modelo para cada categoria sendo que há diversas variações de cada uma.


Conclusão

Vimos alguns dos principais conceitos por trás do Machine Learning: função de perda, regularização e otimização. Além disso, vimos um exemplo prático de classificação linear. Apesar desse método não ser recomendado para reconhecimento de imagens, os conceitos são aproveitados em outros algoritmos mais apropriados para tal tarefa, como CNNs (para mais informações sobre CNNs, leia este artigo).


Referências:

- Stanford University CS231n: Convolutional Neural Networks for Visual Recognition: http://cs231n.stanford.edu/2017/
- Maini, Vishal. Machine Learning for Humans, Part 2.1: Supervised Learning: https://medium.com/machine-learning-for-humans/supervised-learning-740383a2feab
- Maini, Vishal. Machine Learning for Humans, Part 2.2: Supervised Learning II: https://medium.com/machine-learning-for-humans/supervised-learning-2-5c1c23f3560d