Emulando las capas comunes en otros Frameworks

Los frameworks de deeplearning como Keras y Pytorch normalmente tienen una abstraccion llamada Capa o Layer que consiste un conjunto de tecnicas para el forward propagation enpaquetadas en una API simple con un metodo forward() para llamarlos. Un ejemplo de esto sería una capa Lineal:

import os
import sys
module_path = os.path.abspath(os.path.join('..'))
if module_path not in sys.path:
    sys.path.append(module_path)
import numpy as np
from lightdlf_old.cpu.core import Tensor

class Layer(object):
    
    def __init__(self):
        self.parameters = list()
        
    def get_parameters(self):
        return self.parameters
    
class Linear(Layer):
    
    def __init__(self, n_inputs, n_outputs):
        super().__init__()
        W = np.random.randn(n_inputs, n_outputs)*np.sqrt(2.0/(n_inputs))
        self.weight = Tensor(W, autograd=True)
        self.bias = Tensor(np.zeros(n_outputs), autograd=True)
        
        self.parameters.append(self.weight)
        self.parameters.append(self.bias)
        
    def forward(self, input):
        return Tensor.mm(input, self.weight) + self.bias.expand(0, len(input.data))

Notese que la clase Linear inicializa automáticamente los pesos de la capa y tiene un nuevo Tensor, llamado bias.

La clase Linear hereda de la clase Layer la cual solo contienen el método get_parameters. Esta clase permite definir capaz más complejas (como capas conteniendo otras capas). Solo se necesita sobreescribir el metodo get_parameterspara controlar que tensores son pasados al optimizador (como SGD)

Stackeando capas

class Sequential(Layer):
    
    def __init__(self, layers=list()):
        super().__init__()
        self.layers = layers
        
    def add(self, layer):
        self.layers.append(layer)
        
    def forward(self, input):
        for layer in self.layers:
            input = layer.forward(input)
        return input
    
    def get_parameters(self):
        params = list()
        for l in self.layers:
            params += l.get_parameters()
        return params

Creando el primer modelo secuencial lineal o también llamado Perceptron Multicapa

from lightdlf_old.cpu.optimizers import SGD
np.random.seed(0)

data = Tensor(np.array([[0,0],[0,1],[1,0],[1,1]]), autograd=True)   # (4,2)
target = Tensor(np.array([[0],[1],[0],[1]]), autograd=True)         # (4,1)

model = Sequential([Linear(2,3), Linear(3,1)])
optim = SGD(model.get_parameters(), alpha=0.05)

for i in range(10):
    
    # Predecir
    pred = model.forward(data)
    
    # Comparar
    loss = ((pred - target) * (pred - target)).sum(0)
    
    # Aprender
    loss.backward(Tensor(np.ones_like(loss.data)))
    optim.step()
    
    print(loss)
[2.33428272]
[0.06743796]
[0.0521849]
[0.04079507]
[0.03184365]
[0.02479336]
[0.01925443]
[0.01491699]
[0.01153118]
[0.00889602]

Capa Mean Squared Error

Se pueden crear capas que son funciones sobre las entradas, como la funcion de perdida Mean Squared Error

class MSELoss(Layer):
    
    def __init__(self):
        super().__init__()
        
    def forward(self, pred, target):
        return ((pred - target) * (pred - target)).sum(0)
from lightdlf_old.cpu.optimizers import SGD
np.random.seed(0)

data = Tensor(np.array([[0,0],[0,1],[1,0],[1,1]]), autograd=True)   # (4,2)
target = Tensor(np.array([[0],[1],[0],[1]]), autograd=True)         # (4,1)

model = Sequential([Linear(2,3), Linear(3,1)])
criterion = MSELoss()
optim = SGD(model.get_parameters(), alpha=0.05)

for i in range(10):
    # Predecir
    pred = model.forward(data)
    
    # Comparar
    loss = criterion.forward(pred, target)
    
    # Aprender
    loss.backward(Tensor(np.ones_like(loss.data)))
    optim.step()
    
    print(loss)
[2.33428272]
[0.06743796]
[0.0521849]
[0.04079507]
[0.03184365]
[0.02479336]
[0.01925443]
[0.01491699]
[0.01153118]
[0.00889602]