<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# ecnet/models/mlp.py
# v.3.2.3
# Developed in 2019 by <NAME> <<EMAIL>>
#
# Contains the "MultilayerPerceptron" (feed-forward neural network) class
#

# Stdlib imports
from re import compile, IGNORECASE

# 3rd party imports
from numpy import arange, array, asarray
from numpy.random import choice
from torch import load, relu, save, sigmoid, stack, tensor
from torch.nn import Linear, Module, MSELoss
from torch.optim import Adam

# ECNet imports
from ecnet.utils.logging import logger

ECNET_EXT = compile(r'.*\.ecnet', flags=IGNORECASE)


def check_ext(filename: str):
    '''Ensures the supplied filename has a `.ecnet` extension

    Args:
        filename (str): supplied filename
    '''

    if ECNET_EXT.match(filename) is None:
        raise ValueError('Invalid filename/extension, must be `.ecnet`: {}'
                         .format(filename))


class MultilayerPerceptron(Module):

    def __init__(self, filename: str='model.ecnet'):
        '''MultilayerPerceptron object: fits neural network to supplied inputs
        and targets

        Args:
            filename (str): path to model save location (.ecnet extension)
        '''

        super(MultilayerPerceptron, self).__init__()
        check_ext(filename)
        self._filename = filename
        self._layers = []

    def add_layer(self, num_neurons: int, activation: str,
                  input_dim: int=None):
        '''Adds a fully-connected layer to the model

        Args:
            num_neurons (int): number of neurons for the layer
            activation (str): activation function for the layer (`sigmoid`,
                `relu`, `linear`)
            input_dim (int): if not None (input layer), specifies input
                dimensionality
        '''

        if activation == 'sigmoid' or activation == sigmoid:
            activation = sigmoid
        elif activation == 'relu' or activation == relu:
            activation = relu
        elif activation == 'linear' or activation is None:
            activation = None
        else:
            raise ValueError('Unknown activation: {}'.format(activation))
        if input_dim is not None:
            self._layers.append((
                input_dim, num_neurons, activation
            ))
        else:
            if len(self._layers) == 0:
                raise AttributeError('Must include input dimensionality for '
                                     'first layer')
            self._layers.append((
                self._layers[-1][1], num_neurons, activation
            ))
        setattr(self, '_l{}'.format(len(self._layers) - 1), Linear(
            self._layers[-1][0], self._layers[-1][1]
        ))

    def fit(self, l_x: array, l_y: array, v_x: array=None, v_y: array=None,
            epochs: int=1500, lr: float=0.001, beta_1: float=0.9,
            beta_2: float=0.999, epsilon: float=0.0000001, decay: float=0.0,
            v: int=0, batch_size: int=32, patience: int=32) -> list:
        '''Fits neural network to supplied inputs and targets

        Args:
            l_x (numpy.array): learning input data
            l_y (numpy.array): learning target data
            v_x (numpy.array): if not None, periodic validation is performed w/
                these inputs
            v_y (numpy.array): if not None, periodic validation is performed w/
                these targets
            epochs (int): number of learning epochs if not validating, maximum
                number of learning epochs if performing periodic validation<commit_msg>Update to default patience arg (TODO: tune it)<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# ecnet/models/mlp.py
# v.3.2.3
# Developed in 2019 by <NAME> <<EMAIL>>
#
# Contains the "MultilayerPerceptron" (feed-forward neural network) class
#

# Stdlib imports
from re import compile, IGNORECASE

# 3rd party imports
from numpy import arange, array, asarray
from numpy.random import choice
from torch import load, relu, save, sigmoid, stack, tensor
from torch.nn import Linear, Module, MSELoss
from torch.optim import Adam

# ECNet imports
from ecnet.utils.logging import logger

ECNET_EXT = compile(r'.*\.ecnet', flags=IGNORECASE)


def check_ext(filename: str):
    '''Ensures the supplied filename has a `.ecnet` extension

    Args:
        filename (str): supplied filename
    '''

    if ECNET_EXT.match(filename) is None:
        raise ValueError('Invalid filename/extension, must be `.ecnet`: {}'
                         .format(filename))


class MultilayerPerceptron(Module):

    def __init__(self, filename: str='model.ecnet'):
        '''MultilayerPerceptron object: fits neural network to supplied inputs
        and targets

        Args:
            filename (str): path to model save location (.ecnet extension)
        '''

        super(MultilayerPerceptron, self).__init__()
        check_ext(filename)
        self._filename = filename
        self._layers = []

    def add_layer(self, num_neurons: int, activation: str,
                  input_dim: int=None):
        '''Adds a fully-connected layer to the model

        Args:
            num_neurons (int): number of neurons for the layer
            activation (str): activation function for the layer (`sigmoid`,
                `relu`, `linear`)
            input_dim (int): if not None (input layer), specifies input
                dimensionality
        '''

        if activation == 'sigmoid' or activation == sigmoid:
            activation = sigmoid
        elif activation == 'relu' or activation == relu:
            activation = relu
        elif activation == 'linear' or activation is None:
            activation = None
        else:
            raise ValueError('Unknown activation: {}'.format(activation))
        if input_dim is not None:
            self._layers.append((
                input_dim, num_neurons, activation
            ))
        else:
            if len(self._layers) == 0:
                raise AttributeError('Must include input dimensionality for '
                                     'first layer')
            self._layers.append((
                self._layers[-1][1], num_neurons, activation
            ))
        setattr(self, '_l{}'.format(len(self._layers) - 1), Linear(
            self._layers[-1][0], self._layers[-1][1]
        ))

    def fit(self, l_x: array, l_y: array, v_x: array=None, v_y: array=None,
            epochs: int=1500, lr: float=0.001, beta_1: float=0.9,
            beta_2: float=0.999, epsilon: float=0.0000001, decay: float=0.0,
            v: int=0, batch_size: int=32, patience: int=128) -> list:
        '''Fits neural network to supplied inputs and targets

        Args:
            l_x (numpy.array): learning input data
            l_y (numpy.array): learning target data
            v_x (numpy.array): if not None, periodic validation is performed w/
                these inputs
            v_y (numpy.array): if not None, periodic validation is performed w/
                these targets
            epochs (int): number of learning epochs if not validating, maximum
                number of learning epochs if performing periodic validation
{'fraction_non_alphanumeric': 0.10041666666666667, 'fraction_numerical': 0.013472222222222222, 'mean_word_length': 2.2061442564559215, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6301059', 'n_tokens_mistral': 2081, 'n_tokens_neox': 1966, 'n_words': 648}