[PYTHON] Introduction à l'apprentissage en profondeur ~ Approximation des fonctions ~

Personne cible

L'article précédent était ici Dans cet article, nous allons essayer d'approcher la fonction sin en utilisant le code introduit dans la [Série Introduction à l'apprentissage profond](#Série d'apprentissage profond). Au fait, j'ai trouvé beaucoup de bugs et de correctifs dans ce processus, donc je les reflète dans chaque article.

environnement

Le code s'exécute sur un notebook jupyter. L'introduction du notebook jupyter est présentée sur ici. Forfaits requis

  1. numpy
  2. matplotlib
  3. tqdm

Il est devenu. tqdm ne montre la progression que d'une manière facile à comprendre, donc si cela pose problème, vous pouvez supprimer la partie appropriée du code de test et cela fonctionnera.

Le code expérimental (test.ipynb) de cet article est téléchargé sur github tel quel.

table des matières

Codes utilisés

Veuillez vous référer aux [Articles précédents](#Deep Learning Series) pour le processus de production détaillé.

`_layererror.py`

_layererror.py


class LayerManagerError(Exception):
    """Classe de base pour les erreurs définies par l'utilisateur dans les modules de couche"""
    pass


class AssignError(LayerManagerError):
    def __init__(self, value=None):
        if not value is None:
            self.value = value
            self.message = (str(value)
                         + ": Assigning that value is prohibited.")
        else:
            self.value = None
            self.message = "Assigning that value is prohibited."


    def __str__(self):
        return self.message


class UnmatchUnitError(LayerManagerError):
    def __init__(self, prev, n):
        self.prev = prev
        self.n = n

        self.message = "Unmatch units: {} and {}.".format(prev, n)


    def __str__(self):
        return self.message


class UndefinedLayerError(LayerManagerError):
    def __init__(self, type_name):
        self.type = type_name
        self.message = str(type_name) + ": Undefined layer type."


    def __str__(self):
        return self.message
`baselayer.py`

baselayer.py


import numpy as np


class BaseLayer():
    """
Toutes les classes de couches sous-jacentes
Décrivez le traitement commun à la couche intermédiaire et à la couche de sortie.
    """

    def __init__(self, *, prev=1, n=1,
                 name="", wb_width=5e-2,
                 act="ReLU", opt="Adam",
                 act_dic={}, opt_dic={}, **kwds):
        self.prev = prev  #Nombre de sorties de la couche précédente=Nombre d'entrées dans cette couche
        self.n = n        #Nombre de sorties dans cette couche=Nombre d'entrées vers la couche suivante
        self.name = name  #Le nom de cette couche

        #Définir le poids et le biais
        self.w = wb_width*np.random.randn(prev, n)
        self.b = wb_width*np.random.randn(n)

        #Fonction d'activation(classe)Avoir
        self.act = get_act(act, **act_dic)

        #Optimiseur(classe)Avoir
        self.opt = get_opt(opt, **opt_dic)


    def forward(self, x):
        """
Mise en œuvre de la propagation directe
        """
        #Souvenez-vous de votre entrée
        self.x = x.copy()

        #Propagation vers l'avant
        self.u = [email protected] + self.b
        self.y = self.act.forward(self.u)
        
        return self.y


    def backward(self, grad):
        """
Mise en œuvre de la rétropropagation
        """
        dact = grad*self.act.backward(self.u, self.y)
        self.grad_w = self.x.T@dact
        self.grad_b = np.sum(dact, axis=0)
        self.grad_x = [email protected]

        return self.grad_x


    def update(self, **kwds):
        """
Mise en œuvre de l'apprentissage des paramètres
        """
        dw, db = self.opt.update(self.grad_w, self.grad_b, **kwds)

        self.w += dw
        self.b += db
`middlelayer.py`

middlelayer.py


import numpy as np


class MiddleLayer(BaseLayer):
    """
Classe moyenne
La couche d'entrée est également traitée comme l'une des couches intermédiaires en termes de montage.
    """
    pass
`outputlayer.py`

outputlayer.py


import numpy as np


class OutputLayer(BaseLayer):
    """
Classe de couche de sortie
    """
    def __init__(self, *, err_func="Square", **kwds):
        #Fonction de perte(classe)Avoir
        self.errfunc = get_err(err_func)

        super().__init__(**kwds)
    

    def backward(self, t):
        """
Mise en œuvre de la rétropropagation
        """
        #Lorsque la fonction d'activation de la couche de sortie est la fonction softmax et la fonction de perte est l'erreur d'entropie croisée
        #Cas séparés de propagation d'erreur
        if isinstance(self.act, type(get_act("softmax"))) \
        and isinstance(self.errfunc, type(get_err("Cross"))):
            dact = self.y - t
            self.grad_w = self.x.T@dact
            self.grad_b = np.sum(dact, axis=0)
            self.grad_x = [email protected]

            return self.grad_x
        elif isinstance(self.act, type(get_act("sigmoid"))) \
         and isinstance(self.errfunc, type(get_err("Binary"))):
            dact = self.y - t
            self.grad_w = self.x.T@dact
            self.grad_b = np.sum(dact, axis=0)
            self.grad_x = [email protected]

            return self.grad_x
        else:
            grad = self.errfunc.backward(self.y, t)
            return super().backward(grad)


    def get_error(self, t):
        self.error = self.errfunc.forward(self.y, t)
        return self.errfunc.total_error()
`layermanager.py`

layermanager.py


import numpy as np


class _TypeManager():
    """
Classe de gestionnaire pour les types de couches
    """
    N_TYPE = 2  #Nombre de types de couches

    MIDDLE = 0  #Numérotation des couches intermédiaires
    OUTPUT = 1  #Numérotation des couches de sortie


class LayerManager(_TypeManager):
    """
Classe Manager pour la gestion des couches
    """
    def __init__(self):
        self.__layer_list = []  #Liste des couches
        self.__name_list = []   #Liste de noms pour chaque couche
        self.__ntype = np.zeros(self.N_TYPE, dtype=int)  #Nombre de couches par type


    def __repr__(self):
        layerRepr= "layer_list: " + repr(self.__layer_list)
        nameRepr = "name_list: " + repr(self.__name_list)
        ntypeRepr = "ntype: " + repr(self.__ntype)
        return (layerRepr + "\n"
                + nameRepr + "\n"
                + ntypeRepr)


    def __str__(self):
        layerStr = "layer_list: " + str(self.__layer_list)
        nameStr = "name_list: " + str(self.__name_list)
        ntypeStr = "ntype: " + str(self.__ntype)
        return (layerStr + "\n"
                + nameStr + "\n"
                + ntypeStr)


    def __len__(self):
        """
Fonctions intégrées Python`len`Décrit l'opération lorsqu'elle est appelée depuis.
Renvoie la somme du nombre de couches par type.
        """
        return int(np.sum(self.__ntype))


    def __getitem__(self, key):
        """
Par exemple
        lm = LayerManager()

        +----------------+
        | (Ajouter un élément à lm) |
        +----------------+

        x = lm[3].~~
Est appelé lors de l'accès à un élément d'une liste ou d'un tableau, comme
Décrivez l'opération à ce moment-là.
tranche et str,Autoriser uniquement l'accès int.
        """
        if isinstance(key, slice):
            #Si la clé est une tranche, reportez-vous à la liste des calques avec tranche.
            #Valeur inhabituelle(Index hors de portée, etc.)Quand est entré
            #Python me donne une erreur.
            return self.__layer_list[key]
        elif isinstance(key, str):
            #Si key est une chaîne, récupérez l'index dans la liste des noms de chaque couche et
            #Renvoie les éléments de la liste des couches applicables.
            if key in self.__name_list:
                index = self.__name_list.index(key)
                return self.__layer_list[index]
            else:
                #Si la clé n'existe pas, une KeyError est émise.
                raise KeyError("{}: No such item".format(key))
        elif isinstance(key, int):
            #Si key est un entier, renvoie l'élément correspondant dans la liste des couches.
            #Valeur inhabituelle(Index hors de portée, etc.)Quand est entré
            #Python me donne une erreur.
            return self.__layer_list[key]
        else:
            raise KeyError(key, ": Undefined such key type.")


    def __setitem__(self, key, value):
        """
Par exemple
        lm = LayerManager()

        +----------------+
        | (Ajouter un élément à lm) |
        +----------------+

        lm[1] = x
Est appelé lors de l'accès à un élément d'une liste ou d'un tableau, comme
Décrivez l'opération à ce moment-là.
Seul l'écrasement des éléments est autorisé et l'ajout de nouveaux éléments est interdit.
        """
        value_type = ""
        if isinstance(value, list):
            #Spécifié sur le côté droit'value'Mais'list'Si
            #Tous les éléments'BaseLayer'Erreur si classe ou n'en hérite pas.
            if not np.all(
                np.where(isinstance(value, BaseLayer), True, False)):
                self.AssignError()
            value_type = "list"
        elif isinstance(value, BaseLayer):
            #Spécifié sur le côté droit'value'Mais'BaseLayer'Est-ce une classe?
            #Erreur s'il n'est pas hérité.
            self.AssignError(type(value))
        if value_type == "":
            value_type = "BaseLayer"

        if isinstance(key, slice):
            #Si la clé est une tranche, écrasez l'élément dans la liste des calques.
            #pourtant'value_type'Mais'list'Sinon, une erreur.
            #Valeur inhabituelle(Index hors de portée, etc.)Quand est entré
            #Python me donne une erreur.
            if value_type != "list":
                self.AssignError(value_type)
            self.__layer_list[key] = value
        elif isinstance(key, str):
            #Si key est une chaîne, récupérez l'index dans la liste des noms de chaque couche et
            #Remplacez les éléments dans la liste des couches applicables.
            #pourtant'value_type'Mais'BaseLayer'Sinon, une erreur.
            if value_type != "BaseLayer":
                raise AssignError(value_type)
            if key in self.__name_list:
                index = self.__name_list.index(key)
                self.__layer_list[index] = value
            else:
                #Si la clé n'existe pas, une KeyError est émise.
                raise KeyError("{}: No such item".format(key))
        elif isinstance(key, int):
            #Si la clé est un entier, écrasez l'élément correspondant dans la liste des couches.
            #pourtant'value_type'Mais'BaseLayer'Sinon, une erreur.
            #Aussi, une valeur anormale(Index hors de portée, etc.)Quand est entré
            #Python me donne une erreur.
            if value_type != "BaseLayer":
                raise AssignError(value_type)
            self.__layer_list[key] = value
        else:
            raise KeyError(key, ": Undefined such key type.")


    def __delitem__(self, key):
        """
Par exemple
        lm = LayerManager()

        +----------------+
        | (Ajouter un élément à lm) |
        +----------------+

        del lm[2]
Parce qu'il est appelé lorsque l'élément de la liste ou du tableau est accédé par l'instruction del comme
Décrivez l'opération à ce moment-là.
Si l'élément spécifié existe, il sera supprimé et renommé.
        """
        if isinstance(key, slice):
            #Si la clé est une tranche, supprimez l'élément spécifié tel quel
            #Valeur inhabituelle(Index hors de portée, etc.)Quand est entré
            #Python me donne une erreur.
            del self.__layer_list[slice]
            del self.__name_list[slice]
        elif isinstance(key, str):
            #Si key est une chaîne, récupérez l'index dans la liste des noms de chaque couche et
            #Supprimez l'élément concerné.
            if key in self.__name_list:
                del self.__layer_list[index]
                del self.__name_list[index]
            else:
                #Si la clé n'existe pas, une KeyError est émise.
                raise KeyError("{}: No such item".format(key))
        elif isinstance(key, int):
            #Si la clé est un entier, supprimez l'élément correspondant dans la liste des couches.
            #Valeur inhabituelle(Index hors de portée, etc.)Quand est entré
            #Python me donne une erreur.
            del self.__layer_list[key]
        else:
            raise KeyError(key, ": Undefined such key type.")

        #Renommer
        self._rename()


    def _rename(self):
        """
Lorsque la dénomination de la liste de noms enfreint les règles en raison de l'opération de liste
Renommez la liste de dénomination et chaque couche pour respecter à nouveau les règles.

La règle de dénomination est[Type de calque][Quel nombre]ça ira.
Si le type de calque est Couche intermédiaire, Milieu
Sortie pour la couche de sortie
Il est abrégé en.
Le nombre est compté par type.

Aussi, ici encore__Compte ntypes.
        """
        #Initialiser le nombre de couches par type
        self.__ntype = np.zeros(self.N_TYPE)

        #Recompter et renommer chaque couche
        for i in range(len(self)):
            if "Middle" in self.__name_list[i]:
                self.__ntype[self.MIDDLE] += 1
                self.__name_list[i] = "Middle{}".format(
                        self.__ntype[self.MIDDLE])
                self.__layer_list[i].name = "Middle{}".format(
                        self.__ntype[self.MIDDLE])
            elif "Output" in self.__name_list[i]:
                self.__ntype[self.OUTPUT] += 1
                self.__name_list[i] = "Output{}".format(
                        self.__ntype[self.OUTPUT])
                self.__layer_list[i].name = "Output{}".format(
                        self.__ntype[self.OUTPUT])
            else:
                raise UndefinedLayerType(self.__name_list[i])

    def append(self, *, name="Middle", **kwds):
        """
Implémentation de la méthode append familière, qui est une méthode pour ajouter des éléments à une liste.
        """
        if "prev" in kwds:
            # 'prev'Est inclus dans le mot-clé
            #Cela signifie que le nombre d'éléments de la couche précédente est spécifié.
            #Fondamentalement, il est censé être le moment d'insérer la première couche, donc
            #En dehors de cela, il est essentiellement déterminé automatiquement et n'est pas spécifié.
            if len(self) != 0:
                if kwds["prev"] != self.__layer_list[-1].n:
                    #Erreur s'il ne correspond pas au nombre d'unités à la fin.
                    raise UnmatchUnitError(self.__layer_list[-1].n,
                                           kwds["prev"])
        else:
            if len(self) == 0:
                #La première couche doit toujours spécifier le nombre d'unités d'entrée.
                raise UnmatchUnitError("Input units", "Unspecified")
            else:
                #Le nombre d'unités dans la dernière couche'kwds'Ajouter à
                kwds["prev"] = self.__layer_list[-1].n

        #Lisez le type de couche et modifiez le nom selon la règle de dénomination
        if name == "Middle" or name == "mid" or name == "m":
            name = "Middle"
        elif name == "Output" or name == "out" or name == "o":
            name = "Output"
        else:
            raise UndefinedLayerError(name)

        #Ajoutez un calque.
        if name == "Middle":
            #Incrémenter la couche par type
            self.__ntype[self.MIDDLE] += 1
            #Ajouter au nom
            name += str(self.__ntype[self.MIDDLE])
            #Ajouter à la liste de noms
            self.__name_list.append(name)
            #Enfin, créez un calque et ajoutez-le à la liste.
            self.__layer_list.append(
                    MiddleLayer(name=name, **kwds))
        elif name == "Output":
            #C'est également la même chose.
            self.__ntype[self.OUTPUT] += 1
            name += str(self.__ntype[self.OUTPUT])
            self.__name_list.append(name)
            self.__layer_list.append(
                    OutputLayer(name=name, **kwds))
        #Si vous ne dessinez pas d'instruction else ici, modifiez le nom selon la règle de dénomination
        #Déjà anormal au stade'name'Est omis.


    def extend(self, lm):
        """
Un autre gestionnaire de couches qui existe déjà dans la méthode d'extension'lm'Des éléments de
Tout ajouter.
        """
        if not isinstance(lm, LayerManager):
            # 'lm'Erreur si l'instance de n'est pas LayerManager.
            raise TypeError(type(lm), ": Unexpected type.")
        if len(self) != 0:
            if self.__layer_list[-1].n != lm[0].prev:
                #Avec le nombre d'unités dans votre dernière couche
                # 'lm'Erreur si le nombre d'entrées dans la première couche de n'est pas le même.
                raise UnmatchUnitError(self.__layer_list[-1].n,
                                       lm[0].prev)

        #Chaque'extend'Ajouter par méthode
        self.__layer_list.extend(lm.layer_list)
        self.__name_list.extend(lm.name_list)

        #Renommer
        self._rename()


    def insert(self, prev_name, name="Middle", **kwds):
        """
Dans la méthode d'insertion, spécifiez le nom du calque précédent et combinez-le avec ce calque.
Ajoutez un élément.
        """
        # 'prev_name'Erreur si n'existe pas.
        if not prev_name in self.__name_list:
            raise KeyError(prev_name, ": No such key.")
        # 'prev'Est inclus dans le mot-clé
        # 'prev_name'Erreur s'il ne correspond pas au nombre d'unités du calque spécifié dans.
        if "prev" in kwds:
            if kwds["prev"] \
                != self.__layer_list[self.index(prev_name)].n:
                raise UnmatchUnitError(
                    kwds["prev"],
                    self.__layer_list[self.index(prev_name)].n)
        # 'n'Est inclus dans le mot-clé
        if "n" in kwds:
            # 'prev_name'Si ce n'est pas le dernier
            if prev_name != self.__name_list[-1]:
                #Erreur s'il ne correspond pas au nombre d'unités dans la couche suivante.
                if kwds["n"] != self.__layer_list[
                        self.index(prev_name)+1].prev:
                    raise UnmatchUnitError(
                        kwds["n"],
                        self.__layer_list[self.index(prev_name)].prev)
        #S'il n'y a pas encore d'éléments'append'Donnez une erreur pour utiliser la méthode.
        if len(self) == 0:
            raise RuntimeError(
                "You have to use 'append' method instead.")

        #Obtenir l'index de l'emplacement d'insertion
        index = self.index(prev_name) + 1

        #Lisez le type de couche et modifiez le nom selon la règle de dénomination
        if name == "Middle" or name == "mid" or name == "m":
            name = "Middle"
        elif name == "Output" or name == "out" or name == "o":
            name = "Output"
        else:
            raise UndefinedLayerError(name)

        #Insérer un élément
        #En ce moment,'name'Ne respecte pas encore les règles de dénomination,
        #Je le renommerai plus tard alors ne vous inquiétez pas.
        if "Middle" in name:
            self.__layer_list.insert(index,
                                     MiddleLayer(name=name, **kwds))
            self.__name_list.insert(index, name)
        elif "Output" in name:
            self.__layer_list.insert(index,
                                     OutputLayer(name=name, **kwds))
            self.__name_list.insert(index, name)

        #Renommer
        self._rename()


    def extend_insert(self, prev_name, lm):
        """
C'est la fonction d'origine.
Il se comporte comme une combinaison de la méthode extend et de la méthode insert.
En termes simples, c'est comme insérer un autre gestionnaire de calques.
        """
        if not isinstance(lm, LayerManager):
            # 'lm'Erreur si l'instance de n'est pas LayerManager.
            raise TypeError(type(lm), ": Unexpected type.")
        # 'prev_name'Erreur si n'existe pas.
        if not prev_name in self.__name_list:
            raise KeyError(prev_name, ": No such key.")
        #Le nombre d'unités des couches avant et après l'emplacement spécifié et les première et dernière couches de lm
        #S'ils ne correspondent pas, une erreur se produit.
        if len(self) != 0:
            if self.__layer_list[self.index(prev_name)].n \
                    != lm.layer_list[0].prev:
                #Avec le nombre d'unités dans votre emplacement désigné'lm'Le premier nombre d'unités dans
                #S'ils ne correspondent pas, une erreur se produit.
                raise UnmatchUnitError(
                    self.__layer_list[self.index(prev_name)].n,
                    lm.layer_list[0].prev)
            if prev_name != self.__name_list[-1]:
                # 'prev_name'N'est-ce pas ma dernière couche
                if lm.layer_list[-1].n \
                    != self.__layer_list[self.index(prev_name)+1].prev:
                    # 'lm'Le nombre d'unités à la fin et au niveau suivant de votre emplacement désigné
                    # 'prev'Erreur s'il ne correspond pas au nombre d'unités.
                    raise UnmatchUnitError(
                        lm.layer_list[-1].n,
                        self.__layer_list[self.index(prev_name)+1].prev)
        else:
            #Si vous n'avez aucun élément'extend'J'obtiens une erreur lors de l'utilisation de la méthode.
            raise RuntimeError(
                "You have to use 'extend' method instead.")

        #Obtenir l'index de l'emplacement d'insertion
        index = self.index(prev_name) + 1

        #Éléments après l'emplacement d'insertion'buf'Après avoir évacué vers, retirez-le une fois
        #Ajouter un élément à l'aide de la méthode extend
        layer_buf = self.__layer_list[index:]
        name_buf = self.__name_list[index:]
        del self.__layer_list[index:]
        del self.__name_list[index:]
        self.extend(lm)

        #Ajouter l'élément qui a été évacué
        self.__layer_list.extend(layer_buf)
        self.__name_list.extend(name_buf)

        #Renommer
        self._rename()


    def remove(self, key):
        """
La méthode remove supprime l'élément avec le nom spécifié.
Il est également autorisé à être spécifié par index.
        """
        #Déjà implémenté'del'La phrase est OK.
        del self[key]


    def index(self, target):
        return self.__name_list.index(target)


    def name(self, indices):
        return self.__name_list[indices]


    @property
    def layer_list(self):
        return self.__layer_list


    @property
    def name_list(self):
        return self.__name_list


    @property
    def ntype(self):
        return self.__ntype
`errors.py`

errors.py


import numpy as np


class Error():
    def __init__(self, *args,**kwds):
        self.error = 0
    
    
    def forward(self, *args,**kwds):
        pass
    
    
    def backward(self, *args,**kwds):
        pass
    
    
    def total_error(self, *args,**kwds):
        return np.sum(self.error)/self.error.size


class SquareError(Error):
    def forward(self, y, t, *args,**kwds):
        self.error = 0.5 * (y - t)**2
        return self.error
    
    
    def backward(self, y, t, *args,**kwds):
        return y - t


class BinaryCrossEntropy(Error):
    def forward(self, y, t, *args,**kwds):
        self.error = - t*np.log(y) - (1 - t)*np.log(1 - y)
        return self.error
    
    
    def backward(self, y, t, *args,**kwds):
        return (y - t) / (y*(1 - y))
    

class CrossEntropy(Error):
    def forward(self, y, t, *args,**kwds):
        self.error = - t*np.log(y)
        return self.error
    
    
    def backward(self, y, t, *args,**kwds):
        return - t/y
`get_err.py`

get_err.py


_err_dic = {"Square": SquareError,
            "Binary": BinaryCrossEntropy,
            "Cross": CrossEntropy,
           }


def get_err(name, *args,**kwds):
    if name in _err_dic.keys():
        errfunc = _err_dic[name](*args,**kwds)
    else:
        raise ValueError(name + ": Unknown error function")

    return errfunc
`activations.py`

activations.py


import numpy as np


class Activator():
    def __init__(self, *args,**kwds):
        pass


    def forward(self, *args,**kwds):
        raise Exception("Not Implemented")


    def backward(self, *args,**kwds):
        raise Exception("Not Implemented")


    def update(self, *args,**kwds):
        pass


class step(Activator):
    def forward(self, x, *args,**kwds):
        return np.where(x > 0, 1, 0)


    def backward(self, x, *args,**kwds):
        return np.zeros_like(x)


class identity(Activator):
    def forward(self, x, *args,**kwds):
        return x


    def backward(self, x, *args,**kwds):
        return np.ones_like(x)


class bentIdentity(Activator):
    def forward(self, x, *args,**kwds):
        return 0.5*(np.sqrt(x**2 + 1) - 1) + x


    def backward(self, x, *args,**kwds):
        return 0.5*x/np.sqrt(x**2 + 1) + 1


class hardShrink(Activator):
    def __init__(self, lambda_=0.5, *args,**kwds):
        self.lambda_ = lambda_
        super().__init__(*args,**kwds)


    def forward(self, x, *args,**kwds):
        return np.where((-self.lambda_ <= x) & (x <= self.lambda_),
                        0, x)


    def backward(self, x, *args,**kwds):
        return np.where((-self.lambda_ <= x) & (x <= self.lambda_),
                        0, 1)


class softShrink(Activator):
    def __init__(self, lambda_=0.5, *args,**kwds):
        self.lambda_ = lambda_
        super().__init__(*args,**kwds)


    def forward(self, x, *args,**kwds):
        return np.where(x < -self.lambda_, x + self.lambda_,
                        np.where(x > self.lambda_, x - self.lambda_, 0))


    def backward(self, x, *args,**kwds):
        return np.where((-self.lambda_ <= x) & (x <= self.lambda_),
                        0, 1)


class threshold(Activator):
    def __init__(self, threshold, value, *args,**kwds):
        self.threshold = threshold
        self.value = value
        super().__init__(*args,**kwds)


    def forward(self, x, *args,**kwds):
        return np.where(x > self.threshold, x, self.value)


    def backward(self, x, *args,**kwds):
        return np.where(x > self.threshold, 1, 0)


class sigmoid(Activator):
    def forward(self, x, *args,**kwds):
        return 1/(1 + np.exp(-x))


    def backward(self, x, y, *args,**kwds):
        return y*(1 - y)


class hardSigmoid(Activator):
    def forward(self, x, *args,**kwds):
        return np.clip(0.2*x + 0.5, 0, 1)


    def backward(self, x, *args,**kwds):
        return np.where((x > 2.5) | (x < -2.5), 0, 0.2)


class logSigmoid(Activator):
    def forward(self, x, *args,**kwds):
        return -np.log(1 + np.exp(-x))


    def backward(self, x, *args,**kwds):
        return 1/(1 + np.exp(x))


class act_tanh(Activator):
    def forward(self, x, *args,**kwds):
        return np.tanh(x)


    def backward(self, x, *args,**kwds):
        return 1 - np.tanh(x)**2


class hardtanh(Activator):
    def forward(self, x, *args,**kwds):
        return np.clip(x, -1, 1)


    def backward(self, x, *args,**kwds):
        return np.where((-1 <= x) & (x <= 1), 1, 0)


class tanhShrink(Activator):
    def forward(self, x, *args,**kwds):
        return x - np.tanh(x)


    def backward(self, x, *args,**kwds):
        return np.tanh(x)**2


class ReLU(Activator):
    def forward(self, x, *args,**kwds):
        return np.maximum(0, x)


    def backward(self, x, *args,**kwds):
        return np.where(x > 0, 1, 0)


class ReLU6(Activator):
    def forward(self, x, *args,**kwds):
        return np.clip(x, 0, 6)


    def backward(self, x, *args,**kwds):
        return np.where((0 < x) & (x < 6), 1, 0)


class leakyReLU(Activator):
    def __init__(self, alpha=1e-2, *args,**kwds):
        self.alpha = alpha
        super().__init__(*args,**kwds)


    def forward(self, x, *args,**kwds):
        return np.maximum(self.alpha * x, x)


    def backward(self, x, *args,**kwds):
        return np.where(x < 0, self.alpha, 1)


class ELU(Activator):
    def __init__(self, alpha=1., *args,**kwds):
        self.alpha = alpha
        super().__init__(*args,**kwds)


    def forward(self, x, *args,**kwds):
        return np.where(x >= 0, x, self.alpha*(np.exp(x) - 1))


    def backward(self, x, *args,**kwds):
        return np.where(x >= 0, 1, self.alpha*np.exp(x))


class SELU(Activator):
    def __init__(self, lambda_=1.0507, alpha=1.67326, *args,**kwds):
        self.lambda_ = lambda_
        self.alpha = alpha
        super().__init__(*args,**kwds)


    def forward(self, x, *args,**kwds):
        return np.where(x >= 0,
                        self.lambda_*x,
                        self.lambda_*self.alpha*(np.exp(x) - 1))


    def backward(self, x, *args,**kwds):
        return np.where(x >= 0,
                        self.lambda_,
                        self.lambda_*self.alpha*np.exp(x))


class CELU(Activator):
    def __init__(self, alpha=1., *args,**kwds):
        self.alpha = alpha
        super().__init__(*args,**kwds)


    def forward(self, x, *args,**kwds):
        return np.where(x >= 0,
                        x,
                        self.alpha*(np.exp(x/self.alpha) - 1))


    def backward(self, x, *args,**kwds):
        return np.where(x >= 0, 1, np.exp(x/self.alpha))


class softmax(Activator):
    def forward(self, x, *args,**kwds):
        return np.exp(x)/np.sum(np.exp(x))


    def backward(self, x, *args,**kwds):
        return np.exp(x)*(np.sum(np.exp(x))
                          - np.exp(x))/np.sum(np.exp(x))**2


class softmin(Activator):
    def forward(self, x, *args,**kwds):
        return np.exp(-x)/np.sum(np.exp(-x))


    def backward(self, x, *args,**kwds):
        return -(np.exp(x)*(np.sum(np.exp(-x)) - np.exp(x))
                 /np.sum(np.exp(-x))**2)


class logSoftmax(Activator):
    def forward(self, x, *args,**kwds):
        return np.log(np.exp(x)/np.sum(np.exp(x)))


    def backward(self, x, *args,**kwds):
        y = np.sum(np.exp(x))
        return (y - np.exp(x))/y


class softplus(Activator):
    def forward(self, x, *args,**kwds):
        return np.logaddexp(x, 0)


    def backward(self, x, *args,**kwds):
        return 1/(1 + np.exp(-x))


class softsign(Activator):
    def forward(self, x, *args,**kwds):
        return x/(1 + np.abs(x))


    def backward(self, x, *args,**kwds):
        return 1/(1 + np.abs(x)) ** 2


class Swish(Activator):
    def __init__(self, beta=1, *args,**kwds):
        self.beta = beta
        super().__init__(*args,**kwds)


    def forward(self, x, *args,**kwds):
        return x/(1 + np.exp(-self.beta*x))


    def backward(self, x, y, *args,**kwds):
        return self.beta*y + (1 - self.beta*y)/(1 + np.exp(-self.beta*x))


    def d2y(self, x, *args,**kwds):
        return (-0.25*self.beta*(self.beta*x*np.tanh(0.5*self.beta*x) - 2)
                               *(1 - np.tanh(0.5*self.beta*x)**2))


class Mish(Activator):
    def forward(self, x, *args,**kwds):
        return x*np.tanh(np.logaddexp(x, 0))


    def backward(self, x, *args,**kwds):
        omega = (4*(x + 1) + 4*np.exp(2*x)
                 + np.exp(3*x) + (4*x + 6)*np.exp(x))
        delta = 2*np.exp(x) + np.exp(2*x) + 2
        return np.exp(x)*omega/delta**2


    def d2y(self, x, *args,**kwds):
        omega = (2*(x + 2)
                 + np.exp(x)*(np.exp(x)*(-2*np.exp(x)*(x - 1) - 3*x + 6)
                              + 2*(x + 4)))
        delta = np.exp(x)*(np.exp(x) + 2) + 2
        return 4*np.exp(x)*omega/delta**3


class tanhExp(Activator):
    def forward(self, x, *args,**kwds):
        return x*np.tanh(np.exp(x))


    def backward(self, x, *args,**kwds):
        tanh_exp = np.tanh(np.exp(x))
        return tanh_exp - x*np.exp(x)*(tanh_exp**2 - 1)


    def d2y(self, x, *args,**kwds):
        tanh_exp = np.tanh(np.exp(x))
        return (np.exp(x)*(-x + 2*np.exp(x)*x*tanh_exp - 2)
                         *(tanh_exp**2 - 1))


class maxout(Activator):
    def __init__(self, n_prev, n, k, wb_width=5e-2, *args,**kwds):
        self.n_prev = n_prev
        self.n = n
        self.k = k
        self.w = wb_width*np.random.rand((n_prev, n*k))
        self.b = wb_width*np.random.rand(n*k)

        super().__init__(*args,**kwds)


    def forward(self, x, *args,**kwds):
        self.x = x.copy()
        self.z = np.dot(self.w.T, x) + self.b
        self.z = self.z.reshape(self.n, self.k)
        self.y = np.max(self.z, axis=1)
        return self.y

    def backward(self, g, *args,**kwds):
        self.dw = np.sum(np.dot(self.w, self.x))
`get_act.py`

get_act.py


_act_dic = {"step": step,
            "identity": identity,
            "bent-identity": bentIdentity,
            "hard-shrink": hardShrink,
            "soft-shrink": softShrink,
            "threshold": threshold,
            "sigmoid": sigmoid,
            "hard-sigmoid": hardSigmoid,
            "log-sigmoid": logSigmoid,
            "tanh": act_tanh,
            "tanh-shrink": tanhShrink,
            "hard-tanh":hardtanh,
            "ReLU": ReLU,
            "ReLU6": ReLU6,
            "leaky-ReLU": leakyReLU,
            "ELU": ELU,
            "SELU": SELU,
            "CELU": CELU,
            "softmax": softmax,
            "softmin": softmin,
            "log-softmax": logSoftmax,
            "softplus": softplus,
            "softsign": softsign,
            "Swish": Swish,
            "Mish": Mish,
            "tanhExp": tanhExp,
           }


def get_act(name, *args,**kwds):
    if name in _act_dic.keys():
        activator = _act_dic[name](*args,**kwds)
    else:
        raise ValueError(name + ": Unknown activator")

    return activator
`optimizers.py`

optimizers.py


import numpy as np


class Optimizer():
    """
Une super classe héritée de la méthode d'optimisation.
    """
    def __init__(self, *args,**kwds):
        pass


    def update(self, *args,**kwds):
        pass


class SGD(Optimizer):
    def __init__(self, eta=1e-2, *args,**kwds):
        super().__init__(*args,**kwds)

        self.eta = eta


    def update(self, grad_w, grad_b, *args,**kwds):
        dw = -self.eta*grad_w
        db = -self.eta*grad_b
        return dw, db


class MSGD(Optimizer):
    def __init__(self, eta=1e-2, mu=0.9, *args,**kwds):
        super().__init__(*args,**kwds)

        self.eta = eta
        self.mu = mu

        #Maintenez la valeur de l'étape précédente
        self.dw = 0
        self.db = 0


    def update(self, grad_w, grad_b, *args,**kwds):
        dw = self.mu*self.dw - (1-self.mu)*self.eta*grad_w
        db = self.mu*self.db - (1-self.mu)*self.eta*grad_b

        #L'affectation dans la vue au lieu de la copie est due au fait que ces valeurs peuvent être utilisées
        #C'est parce qu'il ne sera pas changé.
        self.dw = dw
        self.db = db

        return dw, db


class NAG(Optimizer):
    def __init__(self, eta=1e-2, mu=0.9, *args,**kwds):
        super().__init__(*args,**kwds)

        self.eta = eta
        self.mu = mu

        #Contient la valeur de l'étape précédente
        self.dw = 0
        self.db = 0


    def update(self, grad_w, grad_b, w=0, b=0, dfw=None, dfb=None,
               nargs=2, *args,**kwds):
        if nargs == 1:
            grad_w = dfw(w + self.mu*self.dw)
            grad_b = 0
        elif nargs == 2:
            grad_w = dfw(w + self.mu*self.dw, b + self.mu*self.db)
            grad_b = dfb(w + self.mu*self.dw, b + self.mu*self.db)

        dw = self.mu*self.dw - (1-self.mu)*self.eta*grad_w
        db = self.mu*self.db - (1-self.mu)*self.eta*grad_b

        #L'affectation dans la vue au lieu de la copie est due au fait que ces valeurs peuvent être utilisées
        #C'est parce qu'il ne sera pas changé.
        self.dw = dw
        self.db = db

        return dw, db


class AdaGrad(Optimizer):
    def __init__(self, eta=1e-3, *args,**kwds):
        super().__init__(*args,**kwds)

        self.eta = eta

        #Maintenez la valeur de l'étape précédente
        self.gw = 0
        self.gb = 0


    def update(self, grad_w, grad_b, *args,**kwds):
        self.gw += grad_w*grad_w
        self.gb += grad_b*grad_b

        dw = -self.eta*grad_w/np.sqrt(self.gw)
        db = -self.eta*grad_b/np.sqrt(self.gb)

        return dw, db


class RMSprop(Optimizer):
    def __init__(self, eta=1e-2, rho=0.99, eps=1e-8, *args,**kwds):
        super().__init__(*args,**kwds)

        self.eta = eta
        self.rho = rho
        self.eps = eps

        #Maintenez la valeur de l'étape précédente
        self.vw = 0
        self.vb = 0


    def update(self, grad_w, grad_b, *args,**kwds):
        self.vw += (1-self.rho)*(grad_w**2 - self.vw)
        self.vb += (1-self.rho)*(grad_b**2 - self.vb)

        dw = -self.eta*grad_w/np.sqrt(self.vw+self.eps)
        db = -self.eta*grad_b/np.sqrt(self.vb+self.eps)

        return dw, db


class AdaDelta(Optimizer):
    def __init__(self, rho=0.95, eps=1e-6, *args,**kwds):
        super().__init__(*args,**kwds)

        self.rho = rho
        self.eps = eps

        #Maintenez la valeur de l'étape précédente
        self.vw = 0
        self.vb = 0
        self.uw = 0
        self.ub = 0


    def update(self, grad_w, grad_b, *args,**kwds):
        self.vw += (1-self.rho)*(grad_w**2 - self.vw)
        self.vb += (1-self.rho)*(grad_b**2 - self.vb)

        dw = -grad_w*np.sqrt(self.uw+self.eps)/np.sqrt(self.vw+self.eps)
        db = -grad_b*np.sqrt(self.ub+self.eps)/np.sqrt(self.vb+self.eps)

        self.uw += (1-self.rho)*(dw**2 - self.uw)
        self.ub += (1-self.rho)*(db**2 - self.ub)

        return dw, db


class Adam(Optimizer):
    def __init__(self, alpha=1e-3, beta1=0.9, beta2=0.999, eps=1e-8,
                 *args,**kwds):
        super().__init__(*args,**kwds)

        self.alpha = alpha
        self.beta1 = beta1
        self.beta2 = beta2
        self.eps = eps

        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0


    def update(self, grad_w, grad_b, t=1, *args,**kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        self.vw += (1-self.beta2)*(grad_w**2 - self.vw)
        self.vb += (1-self.beta2)*(grad_b**2 - self.vb)

        alpha_t = self.alpha*np.sqrt(1-self.beta2**t)/(1-self.beta1**t)

        dw = -alpha_t*self.mw/(np.sqrt(self.vw+self.eps))
        db = -alpha_t*self.mb/(np.sqrt(self.vb+self.eps))

        return dw, db


class RMSpropGraves(Optimizer):
    def __init__(self, eta=1e-4, rho=0.95, eps=1e-4, *args,**kwds):
        super().__init__(*args,**kwds)

        self.eta = eta
        self.rho = rho
        self.eps = eps

        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0


    def update(self,grad_w, grad_b, *args,**kwds):
        self.mw += (1-self.rho)*(grad_w - self.mw)
        self.mb += (1-self.rho)*(grad_b - self.mb)
        self.vw += (1-self.rho)*(grad_w**2 - self.vw)
        self.vb += (1-self.rho)*(grad_b**2 - self.vb)

        dw = -self.eta*grad_w/np.sqrt(self.vw - self.mw**2 + self.eps)
        db = -self.eta*grad_b/np.sqrt(self.vb - self.mb**2 + self.eps)

        return dw, db


class SMORMS3(Optimizer):
    def __init__(self, eta=1e-3, eps=1e-8, *args,**kwds):
        super().__init__(*args,**kwds)

        self.eta = eta
        self.eps = eps

        #Maintenez la valeur de l'étape précédente
        self.zetaw = 0
        self.zetab = 0
        self.sw = 1
        self.sb = 1
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0


    def update(self, grad_w, grad_b, *args,**kwds):
        rhow = 1/(1+self.sw)
        rhob = 1/(1+self.sb)

        self.mw += (1-rhow)*(grad_w - self.mw)
        self.mb += (1-rhob)*(grad_b - self.mb)
        self.vw += (1-rhow)*(grad_w**2 - self.vw)
        self.vb += (1-rhob)*(grad_b**2 - self.vb)

        self.zetaw = self.mw**2 / (self.vw + self.eps)
        self.zetaw = self.mb**2 / (self.vb + self.eps)

        dw = -grad_w*(np.minimum(self.eta, self.zetaw)
                      /np.sqrt(self.vw + self.eps))
        db = -grad_b*(np.minimum(self.eta, self.zetab)
                      /np.sqrt(self.vb + self.eps))

        self.sw = 1 + (1 - self.zetaw)*self.sw
        self.sb = 1 + (1 - self.zetab)*self.sb

        return dw, db


class AdaMax(Optimizer):
    def __init__(self, alpha=2e-3, beta1=0.9, beta2=0.999,
                 *args,**kwds):
        super().__init__(*args,**kwds)

        self.alpha = alpha
        self.beta1 = beta1
        self.beta2 = beta2

        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.uw = 0
        self.ub = 0


    def update(self, grad_w, grad_b, t=1, *args,**kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        self.uw = np.maximum(self.beta2*self.uw, np.abs(grad_w))
        self.ub = np.maximum(self.beta2*self.ub, np.abs(grad_b))

        alpha_t = self.alpha/(1 - self.beta1**t)

        dw = -alpha_t*self.mw/self.uw
        db = -alpha_t*self.mb/self.ub

        return dw, db


class Nadam(Optimizer):
    def __init__(self, alpha=2e-3, mu=0.975, nu=0.999, eps=1e-8,
                 *args,**kwds):
        super().__init__(*args,**kwds)

        self.alpha = alpha
        self.mu = mu
        self.nu = nu
        self.eps = eps

        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0


    def update(self, grad_w, grad_b, t=1, *args,**kwds):
        self.mw += (1-self.mu)*(grad_w - self.mw)
        self.mb += (1-self.mu)*(grad_b - self.mb)
        self.vw += (1-self.nu)*(grad_w**2 - self.vw)
        self.vb += (1-self.nu)*(grad_b**2 - self.vb)

        mhatw = (self.mu*self.mw/(1-self.mu**(t+1))
                 + (1-self.mu)*grad_w/(1-self.mu**t))
        mhatb = (self.mu*self.mb/(1-self.mu**(t+1))
                 + (1-self.mu)*grad_b/(1-self.mu**t))
        vhatw = self.nu*self.vw/(1-self.nu**t)
        vhatb = self.nu*self.vb/(1-self.nu**t)

        dw = -self.alpha*mhatw/np.sqrt(vhatw + self.eps)
        db = -self.alpha*mhatb/np.sqrt(vhatb + self.eps)

        return dw, db


class Eve(Optimizer):
    def __init__(self, alpha=1e-3, beta1=0.9, beta2=0.999, beta3=0.999,
                 c=10, eps=1e-8, fstar=0, *args,**kwds):
        super().__init__(*args,**kwds)

        self.alpha = alpha
        self.beta1 = beta1
        self.beta2 = beta2
        self.beta3 = beta3
        self.c = c
        self.eps = eps

        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
        self.f = 0
        self.fstar = fstar
        self.dtilde_w = 0
        self.dtilde_b = 0


    def update(self, grad_w, grad_b, t=1, f=1, *args,**kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        self.vw += (1-self.beta2)*(grad_w**2 - self.vw)
        self.vb += (1-self.beta2)*(grad_b**2 - self.vb)

        mhatw = self.mw/(1 - self.beta1**t)
        mhatb = self.mb/(1 - self.beta1**t)
        vhatw = self.vw/(1 - self.beta2**t)
        vhatb = self.vb/(1 - self.beta2**t)

        if t > 1:
            d_w = (np.abs(f-self.fstar)
                    /(np.minimum(f, self.f) - self.fstar))
            d_b = (np.abs(f-self.fstar)
                    /(np.minimum(f, self.f) - self.fstar))
            dhat_w = np.clip(d_w, 1/self.c, self.c)
            dhat_b = np.clip(d_b, 1/self.c, self.c)
            self.dtilde_w += (1 - self.beta3)*(dhat_w - self.dtilde_w)
            self.dtilde_b += (1 - self.beta3)*(dhat_b - self.dtilde_b)
        else:
            self.dtilde_w = 1
            self.dtilde_b = 1

        self.f = f

        dw = -(self.alpha*mhatw
               /(self.dtilde_w*(np.sqrt(vhatw) + self.eps)))
        db = -(self.alpha*mhatb
               /(self.dtilde_b*(np.sqrt(vhatb) + self.eps)))

        return dw, db


class SantaE(Optimizer):
    def __init__(self, eta=1e-2, sigma=0.95, lambda_=1e-8,
                 anne_func=lambda t, n: t**n, anne_rate=0.5,
                 burnin=100, C=5, N=16,
                 *args,**kwds):
        """
        Args:
            eta: Learning rate
            sigma: Maybe in other cases;
                    'rho' in RMSprop, AdaDelta, RMSpropGraves.
                    'rhow' or 'rhob' in SMORMS3.
                    'beta2' in Adam, Eve.
                    'nu' in Nadam.
                   To use calculation 'v'.
            lambda_: Named 'eps'(ε) in other cases.
            anne_func: Annealing function.
                       To use calculation 'beta' at each timestep.
                       Default is 'timestep'**'annealing rate'.
                       The calculated value should be towards infinity
                       as 't' increases.
            anne_rate: Annealing rate.
                       To use calculation 'beta' at each timestep.
                       The second Argument of 'anne_func'.
            burnin: Swith exploration and refinement.
                    This should be specified by users.
            C: To calculate first 'alpha'.
            N: Number of minibatch.
        """
        super().__init__(*args,**kwds)

        self.eta = eta
        self.sigma = sigma
        self.lambda_ = lambda_
        self.anne_func = anne_func
        self.anne_rate = anne_rate
        self.burnin = burnin
        self.N = N

        # Keep one step before and Initialize.
        self.alpha_w = np.sqrt(eta)*C
        self.alpha_b = np.sqrt(eta)*C
        self.vw = 0
        self.vb = 0
        self.gw = 0
        self.gb = 0


    def update(self, grad_w, grad_b, t=1, *args,**kwds):
        try:
            shape_w = grad_w.shape
        except:
            shape_w = (1, )
        try:
            shape_b = grad_b.shape
        except:
            shape_b = (1, )

        if t == 1:
            # Initialize uw, ub.
            self.uw = np.sqrt(self.eta)*np.random.randn(*shape_w)
            self.ub = np.sqrt(self.eta)*np.random.randn(*shape_b)

        self.vw = (self.sigma*self.vw
                   + grad_w*grad_w * (1 - self.sigma) / self.N**2)
        self.vb = (self.sigma*self.vb
                   + grad_b*grad_b * (1 - self.sigma) / self.N**2)

        gw = 1/np.sqrt(self.lambda_ + np.sqrt(self.vw))
        gb = 1/np.sqrt(self.lambda_ + np.sqrt(self.vb))

        beta = self.anne_func(t, self.anne_rate)
        if t < self.burnin:
            # Exploration.
            self.alpha_w += self.uw*self.uw - self.eta/beta
            self.alpha_b += self.ub*self.ub - self.eta/beta

            uw = (self.eta/beta * (1 - self.gw/gw)/self.uw
                  + np.sqrt(2*self.eta/beta * self.gw)
                  * np.random.randn(*shape_w))
            ub = (self.eta/beta * (1 - self.gb/gb)/self.ub
                  + np.sqrt(2*self.eta/beta * self.gb)
                  * np.random.randn(*shape_b))
        else:
            # Refinement.
            uw = 0
            ub = 0

        uw += (1 - self.alpha_w)*self.uw - self.eta*gw*grad_w
        ub += (1 - self.alpha_b)*self.ub - self.eta*gb*grad_b

        # Update values.
        self.uw = uw
        self.ub = ub
        self.gw = gw
        self.gb = gb

        dw = gw*uw
        db = gb*ub

        return dw, db


class SantaSSS(Optimizer):
    def __init__(self, eta=1e-2, sigma=0.95, lambda_=1e-8,
                 anne_func=lambda t, n: t**n, anne_rate=0.5,
                 burnin=100, C=5, N=16,
                 *args,**kwds):
        """
        Args:
            eta: Learning rate
            sigma: Maybe in other cases;
                    'rho' in RMSprop, AdaDelta, RMSpropGraves.
                    'rhow' or 'rhob' in SMORMS3.
                    'beta2' in Adam, Eve.
                    'nu' in Nadam.
                   To use calculation 'v'.
            lambda_: Named 'eps'(ε) in other cases.
            anne_func: Annealing function.
                       To use calculation 'beta' at each timestep.
                       Default is 'timestep'**'annealing rate'.
                       The calculated value should be towards infinity
                       as 't' increases.
            anne_rate: Annealing rate.
                       To use calculation 'beta' at each timestep.
                       The second Argument of 'anne_func'.
            burnin: Swith exploration and refinement.
                    This should be specified by users.
            C: To calculate first 'alpha'.
            N: Number of minibatch.
        """
        super().__init__(*args,**kwds)

        self.eta = eta
        self.sigma = sigma
        self.lambda_ = lambda_
        self.anne_func = anne_func
        self.anne_rate = anne_rate
        self.burnin = burnin
        self.N = N

        # Keep one step before and Initialize.
        self.alpha_w = np.sqrt(eta)*C
        self.alpha_b = np.sqrt(eta)*C
        self.vw = 0
        self.vb = 0
        self.gw = 0
        self.gb = 0


    def update(self, grad_w, grad_b, t=1, *args,**kwds):
        try:
            shape_w = grad_w.shape
        except:
            shape_w = (1, )
        try:
            shape_b = grad_b.shape
        except:
            shape_b = (1, )

        if t == 1:
            # Initialize uw, ub.
            self.uw = np.sqrt(self.eta)*np.random.randn(*shape_w)
            self.ub = np.sqrt(self.eta)*np.random.randn(*shape_b)

        self.vw = (self.sigma*self.vw
                   + grad_w*grad_w * (1 - self.sigma) / self.N**2)
        self.vb = (self.sigma*self.vb
                   + grad_b*grad_b * (1 - self.sigma) / self.N**2)

        gw = 1/np.sqrt(self.lambda_ + np.sqrt(self.vw))
        gb = 1/np.sqrt(self.lambda_ + np.sqrt(self.vb))

        dw = 0.5*gw*self.uw
        db = 0.5*gb*self.ub

        beta = self.anne_func(t, self.anne_rate)
        if t < self.burnin:
            # Exploration.
            self.alpha_w += (self.uw*self.uw - self.eta/beta)*0.5
            self.alpha_b += (self.ub*self.ub - self.eta/beta)*0.5

            uw = np.exp(-0.5*self.alpha_w)*self.uw
            ub = np.exp(-0.5*self.alpha_b)*self.ub
            uw += (-gw*grad_w*self.eta
                        + np.sqrt(2*self.gw*self.eta/beta)
                        * np.random.randn(*shape_w)
                        + self.eta/beta*(1-self.gw/gw)/self.uw)
            ub += (-gb*grad_b*self.eta
                        + np.sqrt(2*self.gb*self.eta/beta)
                        * np.random.randn(*shape_b)
                        + self.eta/beta*(1-self.gb/gb)/self.ub)
            uw *= np.exp(-0.5*self.alpha_w)
            ub *= np.exp(-0.5*self.alpha_b)

            self.alpha_w += (uw*uw - self.eta/beta)*0.5
            self.alpha_b += (ub*ub - self.eta/beta)*0.5
        else:
            # Refinement.
            uw = np.exp(-0.5*self.alpha_w)*self.uw
            ub = np.exp(-0.5*self.alpha_b)*self.ub

            uw -= gw*grad_w*self.eta
            ub -= gb*grad_b*self.eta

            uw *= np.exp(-0.5*self.alpha_w)
            ub *= np.exp(-0.5*self.alpha_b)

        # Update values.
        self.uw = uw
        self.ub = ub
        self.gw = gw
        self.gb = gb

        dw = gw*uw*0.5
        db = gb*ub*0.5

        return dw, db


class AMSGrad(Optimizer):
    def __init__(self, alpha=1e-3, beta1=0.9, beta2=0.999, eps=1e-8,
                 *args,**kwds):
        super().__init__(*args,**kwds)

        self.alpha = alpha
        self.beta1 = beta1
        self.beta2 = beta2
        self.eps = eps

        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
        self.vhatw = 0
        self.vhatb = 0


    def update(self, grad_w, grad_b, t=1, *args,**kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)

        self.vw += (1-self.beta2)*(grad_w**2 - self.vw)
        self.vb += (1-self.beta2)*(grad_b**2 - self.vb)

        self.vhatw = np.maximum(self.vhatw, self.vw)
        self.vhatb = np.maximum(self.vhatb, self.vb)

        alpha_t = self.alpha / np.sqrt(t)

        dw = - alpha_t * self.mw/np.sqrt(self.vhatw + self.eps)
        db = - alpha_t * self.mb/np.sqrt(self.vhatb + self.eps)

        return dw, db


class AdaBound(Optimizer):
    def __init__(self, alpha=1e-3, eta=1e-1, beta1=0.9, beta2=0.999,
                 eps=1e-8, *args,**kwds):
        super().__init__(*args,**kwds)

        self.alpha = alpha
        self.eta = eta
        self.beta1 = beta1
        self.beta2 = beta2
        self.eps = eps

        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0


    def update(self, grad_w, grad_b, t=1, *args,**kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        self.vw += (1-self.beta2)*(grad_w**2 - self.vw)
        self.vb += (1-self.beta2)*(grad_b**2 - self.vb)

        etal = self.eta*(1 - 1/((1-self.beta2)*t + 1))
        etau = self.eta*(1 + 1/((1-self.beta2)*t + self.eps))

        etahatw_t = np.clip(self.alpha/np.sqrt(self.vw), etal, etau)
        etahatb_t = np.clip(self.alpha/np.sqrt(self.vb), etal, etau)

        etaw_t = etahatw_t/np.sqrt(t)
        etab_t = etahatb_t/np.sqrt(t)

        dw = - etaw_t*self.mw
        db = - etab_t*self.mb

        return dw, db


class AMSBound(Optimizer):
    def __init__(self, alpha=1e-3, eta=1e-1, beta1=0.9, beta2=0.999,
                 eps=1e-8, *args,**kwds):
        super().__init__(*args,**kwds)

        self.alpha = alpha
        self.eta = eta
        self.beta1 = beta1
        self.beta2 = beta2
        self.eps = eps

        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
        self.vhatw = 0
        self.vhatb = 0


    def update(self, grad_w, grad_b, t=1, *args,**kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        self.vw += (1-self.beta2)*(grad_w**2 - self.vw)
        self.vb += (1-self.beta2)*(grad_b**2 - self.vb)
        self.vhatw = np.maximum(self.vhatw, self.vw)
        self.vhatb = np.maximum(self.vhatb, self.vb)

        etal = self.eta*(1 - 1/((1-self.beta2)*t + 1))
        etau = self.eta*(1 + 1/((1-self.beta2)*t + self.eps))

        etahatw_t = np.clip(self.alpha/np.sqrt(self.vhatw), etal, etau)
        etahatb_t = np.clip(self.alpha/np.sqrt(self.vhatb), etal, etau)

        etaw_t = etahatw_t/np.sqrt(t)
        etab_t = etahatb_t/np.sqrt(t)

        dw = - etaw_t*self.mw
        db = - etab_t*self.mb

        return dw, db
`get_opt.py`

get_opt.py


_opt_dic = {
    "SGD": SGD,
    "MSGD": MSGD,
    "NAG": NAG,
    "AdaGrad": AdaGrad,
    "RMSprop": RMSprop,
    "AdaDelta": AdaDelta,
    "Adam": Adam,
    "RMSpropGraves": RMSpropGraves,
    "SMORMS3": SMORMS3,
    "AdaMax": AdaMax,
    "Nadam": Nadam,
    "Eve": Eve,
    "SantaE": SantaE,
    "SantaSSS": SantaSSS,
    "AMSGrad": AMSGrad,
    "AdaBound": AdaBound,
    "AMSBound": AMSBound,
}


def get_opt(name, *args,**kwds):
    if name in _opt_dic.keys():
        optimizer = _opt_dic[name](*args,**kwds)
    else:
        raise ValueError(name + ": Unknown optimizer")

    return optimizer
Collez ces codes dans chaque cellule d'un fichier notebook jupyter (extension `.ipynb`), puis collez et exécutez le code de test suivant et cela fonctionnera.

Code de test

Voici le code expérimental.

`test.py`

test.py


%matplotlib nbagg
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import tqdm


#Définition d'objectifs d'apprentissage
def split_test(target, train_indices):
    return target[train_indices], target[~ train_indices]
x = np.arange(0, 4, 5e-2)
y = np.sin(x)
x_left = 1
x_right = 3
y_top = np.max(y) + 1
y_bottom = np.min(y) - 1
indices = (x_left <= x) & (x <= x_right)
x_train, x_test = split_test(x, indices)
y_train, y_test = split_test(y, indices)

#Réglage initial
epoch = 10000
error_prev = 0
error = 0
error_list = []
threshold = 1e-8
n_batch = 4
n_train = x_train.size//n_batch
n_test = x_test.size

#Construction de réseau
n_in = 1
n_out = 1
lm = LayerManager()
lm.append(prev=n_in, n=30, act="sigmoid", wb_width=1)
lm.append(n=30, act="sigmoid", wb_width=1)
lm.append(n=n_out, name="o", act="identity", wb_width=1)

#Création d'une base pour les tracés d'animation
n_image = 100
interval = 50
images = []
fig, ax = plt.subplots(1)
fig.suptitle("fitting animation")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_xlim(np.min(x), np.max(x))
ax.set_ylim(y_bottom, y_top)
ax.grid()
ax.plot(x, y, color="r")
ax.plot(np.full_like(np.arange(y_bottom, y_top+1), x_left),
             np.arange(y_bottom, y_top+1),
             color="g")
ax.plot(np.full_like(np.arange(y_bottom, y_top+1), x_right),
             np.arange(y_bottom, y_top+1),
             color="g")

#Commencer à apprendre
rand_index = np.arange(x_train.size)
for t in tqdm.tqdm(range(1, epoch+1)):
    #Création de scène
    if t % (epoch/n_image) == 1:
        x_in = x.reshape(-1, 1)
        for ll in lm.layer_list:
            x_in = ll.forward(x_in)
        im, = ax.plot(x, ll.y, color="b")
        images.append([im])
    
    #Calcul d'erreur
    x_in = x_test.reshape(n_test, n_in)
    for ll in lm.layer_list:
        x_in = ll.forward(x_in)
    error = lm[-1].get_error(y_test.reshape(n_test, n_out))
    error_list.append(error)

    #Jugement de convergence
    if abs(error - error_prev) < threshold:
        print("end learning...")
        break
    else:
        error_prev = error

    #print("t", t)
    np.random.shuffle(rand_index)
    for i in range(n_train):
        rand = rand_index[i*n_in : (i+n_batch)*n_in]

        x_in = x_train[rand].reshape(-1, n_in)
        #print("x_in", x_in)
        for ll in lm.layer_list:
            x_in = ll.forward(x_in)

        y_in = y_train[rand].reshape(-1, n_out)
        #print("y_in", y_in)
        for ll in lm.layer_list[::-1]:
            y_in = ll.backward(y_in)

        for ll in lm.layer_list:
            ll.update()

#Création d'une animation d'adaptation
anim = animation.ArtistAnimation(fig, images, interval=interval, repeat_delay=3000)

#Affichage de transition d'erreur
fig2, ax2 = plt.subplots(1)
fig2.suptitle("error transition")
ax2.set_yscale("log")
ax2.set_xlabel("epoch")
ax2.set_ylabel("error")
ax2.grid()
ax2.plot(error_list)
fig2.show()
fig2.savefig("error_transition.png ")

Je vais expliquer chacun d'eux.

Définition d'objectifs d'apprentissage

<détails>

Paramètres de cible d'apprentissage </ summary>

test.py


#Définition d'objectifs d'apprentissage
def split_test(target, train_indices):
    return target[train_indices], target[~ train_indices]
x = np.arange(0, 4, 5e-2)
y = np.sin(x)
x_left = 1
x_right = 3
y_top = np.max(y) + 1
y_bottom = np.min(y) - 1
indices = (x_left <= x) & (x <= x_right)
x_train, x_test = split_test(x, indices)
y_train, y_test = split_test(y, indices)
Ici, les données d'entraînement «x_train» et les données de test «x_test» sont générées en fonction de la fonction «sin» qui est la cible d'apprentissage. La fonction `split_test` est une fonction pour diviser les données en deux. «x_left» et «x_right» sont les limites inférieure et supérieure des données d'entraînement. «y_top» et «y_bottom» sont le haut et le bas pour le traçage.

Réglage de la valeur initiale

<détails>

Réglage de la valeur initiale </ summary>

test.py


#Réglage initial
epoch = 10000
error_prev = 0
error = 0
error_list = []
threshold = 1e-8
n_batch = 4
n_train = x_train.size//n_batch
n_test = x_test.size
Le nom de chaque valeur initiale est assez approprié. Eh bien, il sera transmis à la dernière minute, alors acceptons-le.

Construction de réseau

<détails>

Construction du réseau </ summary>

test.py


#Construction de réseau
n_in = 1
n_out = 1
lm = LayerManager()
lm.append(prev=n_in, n=30, act="sigmoid", wb_width=1)
lm.append(n=30, act="sigmoid", wb_width=1)
lm.append(n=n_out, name="o", act="identity", wb_width=1)
Nous construisons un réseau neuronal ici. Le nombre d'entrées «n_in» et le nombre de sorties «n_out» sont tous deux 1 cette fois. Ici, le gestionnaire de couches «lm» est utilisé pour construire un réseau à trois couches. ![test_layer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/2c1818a6-07ab-9f5a-7ab3-5a97f2edd55e.png)

Créer une base pour l'animation

Création d'une base pour l'animation

test.py


#Création d'une base pour les tracés d'animation
n_image = 100
interval = 50
images = []
fig, ax = plt.subplots(1)
fig.suptitle("fitting animation")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_xlim(np.min(x), np.max(x))
ax.set_ylim(y_bottom, y_top)
ax.grid()
ax.plot(x, y, color="r")
ax.plot(np.full_like(np.arange(y_bottom, y_top+1), x_left),
             np.arange(y_bottom, y_top+1),
             color="g")
ax.plot(np.full_like(np.arange(y_bottom, y_top+1), x_right),
             np.arange(y_bottom, y_top+1),
             color="g")
Je crée une base pour coller des animations. Le titre, les paramètres de l'axe vertical / horizontal, les lignes de la grille, le graphique de réponse correcte et la division entre les données d'entraînement et les données de test sont tracés sur la base de l'animation.

Apprentissage

<détails>

Apprentissage </ summary>

test.py


#Commencer à apprendre
rand_index = np.arange(x_train.size)
for t in tqdm.tqdm(range(1, epoch+1)):
    #Création de scène
    if t % (epoch/n_image) == 1:
        x_in = x.reshape(-1, 1)
        for ll in lm.layer_list:
            x_in = ll.forward(x_in)
        im, = ax.plot(x, ll.y, color="b")
        images.append([im])

    #Calcul d'erreur
    x_in = x_test.reshape(n_test, n_in)
    for ll in lm.layer_list:
        x_in = ll.forward(x_in)
    error = lm[-1].get_error(y_test.reshape(n_test, n_out))
    error_list.append(error)

    #Jugement de convergence
    if abs(error - error_prev) < threshold:
        print("end learning...")
        break
    else:
        error_prev = error

    #print("t", t)
    np.random.shuffle(rand_index)
    for i in range(n_train):
        rand = rand_index[i*n_in : (i+n_batch)*n_in]

        x_in = x_train[rand].reshape(-1, n_in)
        #print("x_in", x_in)
        for ll in lm.layer_list:
            x_in = ll.forward(x_in)

        y_in = y_train[rand].reshape(-1, n_out)
        #print("y_in", y_in)
        for ll in lm.layer_list[::-1]:
            y_in = ll.backward(y_in)

        for ll in lm.layer_list:
            ll.update()
C'est une mise en œuvre du corps d'apprentissage. "Création de scène" crée une scène pour afficher la progression de l'apprentissage avec une animation. Dans "Calcul d'erreur", les données de test sont envoyées et le total des erreurs est obtenu. Dans le "jugement de convergence", le jugement de convergence est effectué en comparant avec l'erreur précédente. Lorsque la différence est inférieure à "seuil", on estime qu'elle a convergé. Après cela, la propagation vers l'avant → la propagation arrière → la mise à jour des paramètres (apprentissage) est répétée pour les données et une époque est apprise.

Affichage d'animation et de transition d'erreur

<détails>

Animation et affichage des transitions d'erreur </ summary>

test.py


#Création d'une animation d'adaptation
anim = animation.ArtistAnimation(fig, images, interval=interval, repeat_delay=3000)

#Affichage de transition d'erreur
fig2, ax2 = plt.subplots(1)
fig2.suptitle("error transition")
ax2.set_yscale("log")
ax2.set_xlabel("epoch")
ax2.set_ylabel("error")
ax2.grid()
ax2.plot(error_list)
fig2.show()
fig2.savefig("error_transition.png ")
La création d'animation utilise la fonction «Animation d'artiste». `repeat_delay` définit le temps de pause au niveau du délimiteur de répétition. L'affichage de transition d'erreur montre l'erreur qui était surveillée pendant le processus d'apprentissage.

Résultat expérimental

Voici un exemple de l'animation créée et de la transition d'erreur. Ceci n'est qu'un exemple car les paramètres initiaux sont générés avec des nombres aléatoires et des résultats différents sont donnés à chaque fois qu'ils sont exécutés. fitting_sin.gif error_transition.png En ce qui concerne les données d'entraînement, l'ajustement est terminé dès que possible, et les données d'essai sont également bien ajustées afin de pouvoir être suivies. Bien sûr, puisque les données de test ne sont pas entraînées, elles ** gagnent en polyvalence pour des données inconnues **.

Fonctionnalité portée vers LayerManager

Maintenant, portons certaines des fonctions écrites directement dans le code de test vers LayerManager. Tout d'abord, laissez le gestionnaire de couches contenir diverses choses.

Modifiez les paramètres initiaux de `test.py`

test.py


#Réglage initial
epoch = 10000
#error_prev = 0
#error = 0
#error_list = []
threshold = 1e-8
n_batch = 4
#n_train = x_train.size//n_batch
#n_test = x_test.size

#Construction de réseau
n_in = 1
n_out = 1
lm = LayerManager((x_train, x_test), (y_train, y_test))
lm.append(prev=n_in, n=30, act="sigmoid", wb_width=1)
lm.append(n=30, act="sigmoid", wb_width=1)
lm.append(n=n_out, name="o", act="identity", wb_width=1)
Portez les paramètres par défaut sur `__init__` dans` layermanager.py`

layermanager.py


    def __init__(self, x, y):
        self.x_train, self.x_test = x
        self.y_train, self.y_test = y
        
        self.__layer_list = []  #Liste des couches
        self.__name_list = []   #Liste de noms pour chaque couche
        self.__ntype = np.zeros(self.N_TYPE, dtype=int)  #Nombre de couches par type

Ensuite, nous porterons le corps d'apprentissage et l'affichage de la transition d'erreur.

Changer le code d'apprentissage de `test.py`

test.py


#Commencer à apprendre
lm.training(epoch, threshold=threshold, n_batch=n_batch)
Portez le code d'apprentissage vers `layermanager.py`

layermanager.py


    def training(self, epoch, n_batch=16, threshold=1e-8, show_error=True):
        if show_error:
            self.error_list = []
        
        n_in = self.__layer_list[0].prev
        n_out = self.__layer_list[-1].n
        n_train = self.x_train.size//n_batch
        n_test = self.x_test.size
        
        #Commencer à apprendre
        error = 0
        error_prev = 0
        rand_index = np.arange(self.x_train.size)
        for t in tqdm.tqdm(range(1, epoch+1)):
            #Calcul d'erreur
            x_in = self.x_test.reshape(n_test, n_in)
            for ll in self.__layer_list:
                x_in = ll.forward(x_in)
            error = lm[-1].get_error(self.y_test.reshape(n_test, n_out))
            if show_error:
                error_list.append(error)

            #Jugement de convergence
            if abs(error - error_prev) < threshold:
                print("end learning...")
                break
            else:
                error_prev = error

            #print("t", t)
            np.random.shuffle(rand_index)
            for i in range(n_train):
                rand = rand_index[i*n_in : (i+n_batch)*n_in]

                x_in = self.x_train[rand].reshape(-1, n_in)
                #print("x_in", x_in)
                for ll in self.__layer_list:
                    x_in = ll.forward(x_in)

                y_in = self.y_train[rand].reshape(-1, n_out)
                #print("y_in", y_in)
                for ll in self.__layer_list[::-1]:
                    y_in = ll.backward(y_in)

                for ll in self.__layer_list:
                    ll.update()

        if show_error:
            #Affichage de transition d'erreur
            self.show_error(**kwds)
    
    
    def show_errors(self, title="error transition",
                                  xlabel="epoch", ylabel="error", fname="error_transition.png "):
        fig, ax = plt.subplots(1)
        fig.suptitle(title)
        ax.set_yscale("log")
        ax.set_xlabel(xlabel)
        ax.set_ylabel(ylabel)
        ax.grid()
        ax.plot(error_list)
        fig.show()
        if len(fname) != 0:
            fig.savefig(fname)

Enfin, c'est une animation. J'y ai pensé, mais je ne pouvais pas penser à un moyen d'insérer une animation avec une grande polyvalence, donc c'est approprié ... Si vous trouvez quelque chose, changez-le.

Changer le code d'animation de `test.py`

test.py


#Création d'une base pour les tracés d'animation
n_image = 100
interval = 100
fig, ax = lm.ready_anim(n_image, x, y, title="fitting animation")
#images = []
#fig, ax = plt.subplots(1)
#fig.suptitle("fitting animation")
#ax.set_xlabel("x")
#ax.set_ylabel("y")
#ax.set_xlim(np.min(x), np.max(x))
#ax.set_ylim(y_bottom, y_top)
#ax.grid()
#ax.plot(x, y, color="r")
ax.plot(np.full_like(np.arange(y_bottom, y_top+1), x_left),
             np.arange(y_bottom, y_top+1),
             color="g")
ax.plot(np.full_like(np.arange(y_bottom, y_top+1), x_right),
             np.arange(y_bottom, y_top+1),
             color="g")

#Commencer à apprendre
lm.training(epoch, threshold=threshold, n_batch=n_batch)

#Création d'une animation d'adaptation
anim = animation.ArtistAnimation(lm.anim_fig, lm.images,
                                                            interval=interval, repeat_delay=3000)
Portez le code d'animation vers `layermanager.py`

layermanager.py


    def training(self, epoch, n_batch=16, threshold=1e-8,
                         show_error=True, **kwds):
        if show_error:
            self.error_list = []
        if self.make_anim:
            self.images = []
        
        n_in = self.__layer_list[0].prev
        n_out = self.__layer_list[-1].n
        n_train = self.x_train.size//n_batch
        n_test = self.x_test.size
        
        #Commencer à apprendre
        error = 0
        error_prev = 0
        rand_index = np.arange(self.x_train.size)
        for t in tqdm.tqdm(range(1, epoch+1)):
            #Création de scène
            if self.make_anim:
                self.make_scene(t, epoch)
            
            #Calcul d'erreur
Ce qui suit est omis


    def show_errors(self, title="error transition",
                                  xlabel="epoch", ylabel="error", fname="error_transition.png ",
                                  **kwds):
Ce qui suit est omis


    def ready_anim(self, n_image, x, y, title="animation",
                                xlabel="x", ylabel="y", ex_color="r", color="b",
                                x_left=0, x_right=0, y_down = 1, y_up = 1):
        self.n_image = n_image
        self.x = x
        self.color = color
        self.make_anim = True
        
        self.anim_fig, self.anim_ax = plt.subplots(1)
        self.anim_fig.suptitle(title)
        self.anim_ax.set_xlabel(xlabel)
        self.anim_ax.set_ylabel(ylabel)
        self.anim_ax.set_xlim(np.min(x) - x_left, np.max(x) + x_right)
        self.anim_ax.set_ylim(np.min(y) - y_down, np.max(y) + y_up)
        self.anim_ax.grid()
        self.anim_ax.plot(x, y, color=ex_color)
        
        return self.anim_fig, self.anim_ax
    
    
    def make_scene(self, t, epoch):
        #Création de scène
        if t % (epoch/self.n_image) == 1:
            x_in = self.x.reshape(-1, 1)
            for ll in self.__layer_list:
                x_in = ll.forward(x_in)
            im, = self.anim_ax.plot(self.x, ll.y, color=self.color)
            self.images.append([im])
Ceci termine le portage. L'ensemble du code modifié ressemble à ceci: `test.py` entier

test.py


%matplotlib nbagg
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import tqdm


#Définition d'objectifs d'apprentissage
def split_test(target, train_indices):
    return (target[train_indices], target[~ train_indices])
x = np.arange(0, 4, 5e-2)
y = np.sin(x)
x_left = 1
x_right = 3
y_top = np.max(y) + 1
y_bottom = np.min(y) - 1
indices = (x_left <= x) & (x <= x_right)
x_train, x_test = split_test(x, indices)
y_train, y_test = split_test(y, indices)

#Réglage initial
epoch = 10000
threshold = 1e-5
n_batch = 4

#Construction de réseau
n_in = 1
n_out = 1
lm = LayerManager((x_train, x_test), (y_train, y_test))
lm.append(prev=n_in, n=30, act="sigmoid", wb_width=1)
lm.append(n=30, act="sigmoid", wb_width=1)
lm.append(n=n_out, name="o", act="identity", wb_width=1)

#Création d'une base pour les tracés d'animation
n_image = 100
interval = 100
fig, ax = lm.ready_anim(n_image, x, y, title="fitting animation")
ax.plot(np.full_like(np.arange(y_bottom, y_top+1), x_left),
             np.arange(y_bottom, y_top+1),
             color="g")
ax.plot(np.full_like(np.arange(y_bottom, y_top+1), x_right),
             np.arange(y_bottom, y_top+1),
             color="g")

#Commencer à apprendre
lm.training(epoch, threshold=threshold, n_batch=n_batch)

#Création d'une animation d'adaptation
anim = animation.ArtistAnimation(lm.anim_fig, lm.images,
                                                            interval=interval, repeat_delay=3000)
`layermanager.py` entier

layermanager.py


import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import tqdm


class _TypeManager():
    """
Classe de gestionnaire pour les types de couches
    """
    N_TYPE = 2  #Nombre de types de couches

    MIDDLE = 0  #Numérotation des couches intermédiaires
    OUTPUT = 1  #Numérotation des couches de sortie


class LayerManager(_TypeManager):
    """
Classe Manager pour la gestion des couches
    """
    def __init__(self, x, y):
        self.x_train, self.x_test = x
        self.y_train, self.y_test = y
        
        self.__layer_list = []  #Liste des couches
        self.__name_list = []   #Liste de noms pour chaque couche
        self.__ntype = np.zeros(self.N_TYPE, dtype=int)  #Nombre de couches par type


    def __repr__(self):
        layerRepr= "layer_list: " + repr(self.__layer_list)
        nameRepr = "name_list: " + repr(self.__name_list)
        ntypeRepr = "ntype: " + repr(self.__ntype)
        return (layerRepr + "\n"
                + nameRepr + "\n"
                + ntypeRepr)


    def __str__(self):
        layerStr = "layer_list: " + str(self.__layer_list)
        nameStr = "name_list: " + str(self.__name_list)
        ntypeStr = "ntype: " + str(self.__ntype)
        return (layerStr + "\n"
                + nameStr + "\n"
                + ntypeStr)


    def __len__(self):
        """
Fonctions intégrées Python`len`Décrit l'opération lorsqu'elle est appelée depuis.
Renvoie la somme du nombre de couches par type.
        """
        return int(np.sum(self.__ntype))


    def __getitem__(self, key):
        """
Par exemple
        lm = LayerManager()

        +----------------+
        | (Ajouter un élément à lm) |
        +----------------+

        x = lm[3].~~
Est appelé lors de l'accès à un élément d'une liste ou d'un tableau, comme
Décrivez l'opération à ce moment-là.
tranche et str,Autoriser uniquement l'accès via int.
        """
        if isinstance(key, slice):
            #Si la clé est une tranche, reportez-vous à la liste des calques avec tranche.
            #Valeur inhabituelle(Index hors de portée, etc.)Quand est entré
            #Python me donne une erreur.
            return self.__layer_list[key]
        elif isinstance(key, str):
            #Si key est une chaîne, récupérez l'index dans la liste des noms de chaque couche et
            #Renvoie les éléments de la liste des couches applicables.
            if key in self.__name_list:
                index = self.__name_list.index(key)
                return self.__layer_list[index]
            else:
                #Si la clé n'existe pas, une KeyError est émise.
                raise KeyError("{}: No such item".format(key))
        elif isinstance(key, int):
            #Si key est un entier, renvoie l'élément correspondant dans la liste des couches.
            #Valeur inhabituelle(Index hors de portée, etc.)Quand est entré
            #Python me donne une erreur.
            return self.__layer_list[key]
        else:
            raise KeyError(key, ": Undefined such key type.")


    def __setitem__(self, key, value):
        """
Par exemple
        lm = LayerManager()

        +----------------+
        | (Ajouter un élément à lm) |
        +----------------+

        lm[1] = x
Est appelé lors de l'accès à un élément d'une liste ou d'un tableau, comme
Décrivez l'opération à ce moment-là.
Seul l'écrasement des éléments est autorisé et l'ajout de nouveaux éléments est interdit.
        """
        value_type = ""
        if isinstance(value, list):
            #Spécifié sur le côté droit'value'Mais'list'Si
            #Tous les éléments'BaseLayer'Erreur si classe ou ne l'hérite pas.
            if not np.all(
                np.where(isinstance(value, BaseLayer), True, False)):
                self.AssignError()
            value_type = "list"
        elif isinstance(value, BaseLayer):
            #Spécifié sur le côté droit'value'Mais'BaseLayer'Est-ce une classe?
            #Erreur s'il n'est pas hérité.
            self.AssignError(type(value))
        if value_type == "":
            value_type = "BaseLayer"

        if isinstance(key, slice):
            #Si la clé est une tranche, écrasez l'élément dans la liste des calques.
            #pourtant'value_type'Mais'list'Sinon, une erreur.
            #Valeur inhabituelle(Index hors de portée, etc.)Quand est entré
            #Python me donne une erreur.
            if value_type != "list":
                self.AssignError(value_type)
            self.__layer_list[key] = value
        elif isinstance(key, str):
            #Si key est une chaîne, récupérez l'index dans la liste des noms de chaque couche et
            #Remplacez les éléments dans la liste des calques applicables.
            #pourtant'value_type'Mais'BaseLayer'Sinon, une erreur.
            if value_type != "BaseLayer":
                raise AssignError(value_type)
            if key in self.__name_list:
                index = self.__name_list.index(key)
                self.__layer_list[index] = value
            else:
                #Si la clé n'existe pas, une KeyError est émise.
                raise KeyError("{}: No such item".format(key))
        elif isinstance(key, int):
            #Si la clé est un entier, écrasez l'élément correspondant dans la liste des couches.
            #pourtant'value_type'Mais'BaseLayer'Sinon, une erreur.
            #Aussi, une valeur anormale(Index hors de portée, etc.)Quand est entré
            #Python me donne une erreur.
            if value_type != "BaseLayer":
                raise AssignError(value_type)
            self.__layer_list[key] = value
        else:
            raise KeyError(key, ": Undefined such key type.")


    def __delitem__(self, key):
        """
Par exemple
        lm = LayerManager()

        +----------------+
        | (Ajouter un élément à lm) |
        +----------------+

        del lm[2]
Parce qu'il est appelé lorsque l'élément de la liste ou du tableau est accédé par l'instruction del comme
Décrivez l'opération à ce moment-là.
Si l'élément spécifié existe, il sera supprimé et renommé.
        """
        if isinstance(key, slice):
            #Si la clé est une tranche, supprimez l'élément spécifié tel quel
            #Valeur inhabituelle(Index hors de portée, etc.)Quand est entré
            #Python me donne une erreur.
            del self.__layer_list[slice]
            del self.__name_list[slice]
        elif isinstance(key, str):
            #Si key est une chaîne, récupérez l'index dans la liste des noms de chaque couche et
            #Supprimez l'élément concerné.
            if key in self.__name_list:
                del self.__layer_list[index]
                del self.__name_list[index]
            else:
                #Si la clé n'existe pas, une KeyError est émise.
                raise KeyError("{}: No such item".format(key))
        elif isinstance(key, int):
            #Si la clé est un entier, supprimez l'élément correspondant dans la liste des couches.
            #Valeur inhabituelle(Index hors de portée, etc.)Quand est entré
            #Python me donne une erreur.
            del self.__layer_list[key]
        else:
            raise KeyError(key, ": Undefined such key type.")

        #Renommer
        self._rename()


    def _rename(self):
        """
Lorsque la dénomination de la liste de noms enfreint les règles en raison de l'opération de liste
Renommez la liste de dénomination et chaque couche pour respecter à nouveau les règles.

La règle de dénomination est[Type de calque][Quel nombre]ça ira.
Si le type de calque est Couche intermédiaire, Milieu
Sortie pour la couche de sortie
Il est abrégé en.
Le nombre est compté par type.

Aussi, ici encore__Compte ntypes.
        """
        #Initialiser le nombre de couches par type
        self.__ntype = np.zeros(self.N_TYPE)

        #Recompter et renommer chaque couche
        for i in range(len(self)):
            if "Middle" in self.__name_list[i]:
                self.__ntype[self.MIDDLE] += 1
                self.__name_list[i] = "Middle{}".format(
                        self.__ntype[self.MIDDLE])
                self.__layer_list[i].name = "Middle{}".format(
                        self.__ntype[self.MIDDLE])
            elif "Output" in self.__name_list[i]:
                self.__ntype[self.OUTPUT] += 1
                self.__name_list[i] = "Output{}".format(
                        self.__ntype[self.OUTPUT])
                self.__layer_list[i].name = "Output{}".format(
                        self.__ntype[self.OUTPUT])
            else:
                raise UndefinedLayerType(self.__name_list[i])

    def append(self, *, name="Middle", **kwds):
        """
Implémentation de la méthode append familière, qui est une méthode pour ajouter des éléments à une liste.
        """
        if "prev" in kwds:
            # 'prev'Est inclus dans le mot-clé
            #Cela signifie que le nombre d'éléments de la couche précédente est spécifié.
            #Fondamentalement, il est censé être le moment d'insérer la première couche, donc
            #En dehors de cela, il est essentiellement déterminé automatiquement et n'est pas spécifié.
            if len(self) != 0:
                if kwds["prev"] != self.__layer_list[-1].n:
                    #Erreur s'il ne correspond pas au nombre d'unités à la fin.
                    raise UnmatchUnitError(self.__layer_list[-1].n,
                                           kwds["prev"])
        else:
            if len(self) == 0:
                #La première couche doit toujours spécifier le nombre d'unités d'entrée.
                raise UnmatchUnitError("Input units", "Unspecified")
            else:
                #Le nombre d'unités dans la dernière couche'kwds'Ajouter à
                kwds["prev"] = self.__layer_list[-1].n

        #Lisez le type de couche et modifiez le nom selon la règle de dénomination
        if name == "Middle" or name == "mid" or name == "m":
            name = "Middle"
        elif name == "Output" or name == "out" or name == "o":
            name = "Output"
        else:
            raise UndefinedLayerError(name)

        #Ajoutez un calque.
        if name == "Middle":
            #Incrémenter la couche par type
            self.__ntype[self.MIDDLE] += 1
            #Ajouter au nom
            name += str(self.__ntype[self.MIDDLE])
            #Ajouter à la liste de noms
            self.__name_list.append(name)
            #Enfin, créez un calque et ajoutez-le à la liste.
            self.__layer_list.append(
                    MiddleLayer(name=name, **kwds))
        elif name == "Output":
            #C'est également la même chose.
            self.__ntype[self.OUTPUT] += 1
            name += str(self.__ntype[self.OUTPUT])
            self.__name_list.append(name)
            self.__layer_list.append(
                    OutputLayer(name=name, **kwds))
        #Si vous ne dessinez pas d'instruction else ici, modifiez le nom selon la règle de dénomination
        #Déjà anormal au stade'name'Est omis.


    def extend(self, lm):
        """
Un autre gestionnaire de couches qui existe déjà dans la méthode d'extension'lm'Des éléments de
Tout ajouter.
        """
        if not isinstance(lm, LayerManager):
            # 'lm'Erreur si l'instance de n'est pas LayerManager.
            raise TypeError(type(lm), ": Unexpected type.")
        if len(self) != 0:
            if self.__layer_list[-1].n != lm[0].prev:
                #Avec le nombre d'unités dans votre dernière couche
                # 'lm'Erreur si le nombre d'entrées dans la première couche de n'est pas le même.
                raise UnmatchUnitError(self.__layer_list[-1].n,
                                       lm[0].prev)

        #Chaque'extend'Ajouter par méthode
        self.__layer_list.extend(lm.layer_list)
        self.__name_list.extend(lm.name_list)

        #Renommer
        self._rename()


    def insert(self, prev_name, name="Middle", **kwds):
        """
Dans la méthode d'insertion, spécifiez le nom du calque précédent et combinez-le avec ce calque.
Ajoutez un élément.
        """
        # 'prev_name'Erreur si n'existe pas.
        if not prev_name in self.__name_list:
            raise KeyError(prev_name, ": No such key.")
        # 'prev'Est inclus dans le mot-clé
        # 'prev_name'Erreur s'il ne correspond pas au nombre d'unités du calque spécifié dans.
        if "prev" in kwds:
            if kwds["prev"] \
                != self.__layer_list[self.index(prev_name)].n:
                raise UnmatchUnitError(
                    kwds["prev"],
                    self.__layer_list[self.index(prev_name)].n)
        # 'n'Est inclus dans le mot-clé
        if "n" in kwds:
            # 'prev_name'Si ce n'est pas le dernier
            if prev_name != self.__name_list[-1]:
                #Erreur s'il ne correspond pas au nombre d'unités dans la couche suivante.
                if kwds["n"] != self.__layer_list[
                        self.index(prev_name)+1].prev:
                    raise UnmatchUnitError(
                        kwds["n"],
                        self.__layer_list[self.index(prev_name)].prev)
        #S'il n'y a pas encore d'éléments'append'Donnez une erreur pour utiliser la méthode.
        if len(self) == 0:
            raise RuntimeError(
                "You have to use 'append' method instead.")

        #Obtenir l'index de l'emplacement d'insertion
        index = self.index(prev_name) + 1

        #Lisez le type de couche et modifiez le nom selon la règle de dénomination
        if name == "Middle" or name == "mid" or name == "m":
            name = "Middle"
        elif name == "Output" or name == "out" or name == "o":
            name = "Output"
        else:
            raise UndefinedLayerError(name)

        #Insérer un élément
        #En ce moment,'name'Ne respecte pas encore les règles de dénomination,
        #Je le renommerai plus tard alors ne vous inquiétez pas.
        if "Middle" in name:
            self.__layer_list.insert(index,
                                     MiddleLayer(name=name, **kwds))
            self.__name_list.insert(index, name)
        elif "Output" in name:
            self.__layer_list.insert(index,
                                     OutputLayer(name=name, **kwds))
            self.__name_list.insert(index, name)

        #Renommer
        self._rename()


    def extend_insert(self, prev_name, lm):
        """
C'est la fonction d'origine.
Il se comporte comme une combinaison de la méthode extend et de la méthode insert.
En termes simples, c'est comme insérer un autre gestionnaire de calques.
        """
        if not isinstance(lm, LayerManager):
            # 'lm'Erreur si l'instance de n'est pas LayerManager.
            raise TypeError(type(lm), ": Unexpected type.")
        # 'prev_name'Erreur si n'existe pas.
        if not prev_name in self.__name_list:
            raise KeyError(prev_name, ": No such key.")
        #Le nombre d'unités des couches avant et après l'emplacement spécifié et les première et dernière couches de lm
        #S'ils ne correspondent pas, une erreur se produit.
        if len(self) != 0:
            if self.__layer_list[self.index(prev_name)].n \
                    != lm.layer_list[0].prev:
                #Avec le nombre d'unités dans votre emplacement désigné'lm'Le premier nombre d'unités dans
                #S'ils ne correspondent pas, une erreur se produit.
                raise UnmatchUnitError(
                    self.__layer_list[self.index(prev_name)].n,
                    lm.layer_list[0].prev)
            if prev_name != self.__name_list[-1]:
                # 'prev_name'N'est-ce pas ma dernière couche
                if lm.layer_list[-1].n \
                    != self.__layer_list[self.index(prev_name)+1].prev:
                    # 'lm'Le nombre d'unités à la fin et au niveau suivant de votre emplacement désigné
                    # 'prev'Erreur s'il ne correspond pas au nombre d'unités.
                    raise UnmatchUnitError(
                        lm.layer_list[-1].n,
                        self.__layer_list[self.index(prev_name)+1].prev)
        else:
            #Si vous n'avez aucun élément'extend'J'obtiens une erreur lors de l'utilisation de la méthode.
            raise RuntimeError(
                "You have to use 'extend' method instead.")

        #Obtenir l'index de l'emplacement d'insertion
        index = self.index(prev_name) + 1

        #Éléments après l'emplacement d'insertion'buf'Après avoir évacué vers, retirez-le une fois
        #Ajouter un élément à l'aide de la méthode extend
        layer_buf = self.__layer_list[index:]
        name_buf = self.__name_list[index:]
        del self.__layer_list[index:]
        del self.__name_list[index:]
        self.extend(lm)

        #Ajouter l'élément qui a été évacué
        self.__layer_list.extend(layer_buf)
        self.__name_list.extend(name_buf)

        #Renommer
        self._rename()


    def remove(self, key):
        """
La méthode remove supprime l'élément avec le nom spécifié.
Il est également autorisé à être spécifié par index.
        """
        #Déjà implémenté'del'La phrase est OK.
        del self[key]


    def index(self, target):
        return self.__name_list.index(target)


    def name(self, indices):
        return self.__name_list[indices]


    @property
    def layer_list(self):
        return self.__layer_list


    @property
    def name_list(self):
        return self.__name_list


    @property
    def ntype(self):
        return self.__ntype
    
    
    def training(self, epoch, n_batch=16, threshold=1e-8,
                         show_error=True, **kwds):
        if show_error:
            self.error_list = []
        if self.make_anim:
            self.images = []
        
        n_in = self.__layer_list[0].prev
        n_out = self.__layer_list[-1].n
        n_train = self.x_train.size//n_batch
        n_test = self.x_test.size
        
        #Commencer à apprendre
        error = 0
        error_prev = 0
        rand_index = np.arange(self.x_train.size)
        for t in tqdm.tqdm(range(1, epoch+1)):
            #Création de scène
            if self.make_anim:
                self.make_scene(t, epoch)
            
            #Calcul d'erreur
            x_in = self.x_test.reshape(n_test, n_in)
            for ll in self.__layer_list:
                x_in = ll.forward(x_in)
            error = lm[-1].get_error(self.y_test.reshape(n_test, n_out))
            if show_error:
                self.error_list.append(error)

            #Jugement de convergence
            if abs(error - error_prev) < threshold:
                print("end learning...")
                break
            else:
                error_prev = error

            np.random.shuffle(rand_index)
            for i in range(n_train):
                rand = rand_index[i*n_in : (i+n_batch)*n_in]

                x_in = self.x_train[rand].reshape(-1, n_in)
                for ll in self.__layer_list:
                    x_in = ll.forward(x_in)

                y_in = self.y_train[rand].reshape(-1, n_out)
                for ll in self.__layer_list[::-1]:
                    y_in = ll.backward(y_in)

                for ll in self.__layer_list:
                    ll.update()
        
        if show_error:
            #Affichage de transition d'erreur
            self.show_errors(**kwds)
    
    
    def show_errors(self, title="error transition",
                                  xlabel="epoch", ylabel="error", fname="error_transition.png ",
                                  **kwds):
        fig, ax = plt.subplots(1)
        fig.suptitle(title)
        ax.set_yscale("log")
        ax.set_xlabel(xlabel)
        ax.set_ylabel(ylabel)
        ax.grid()
        ax.plot(self.error_list)
        fig.show()
        if len(fname) != 0:
            fig.savefig(fname)
    
    
    def ready_anim(self, n_image, x, y, title="animation",
                                xlabel="x", ylabel="y", ex_color="r", color="b",
                                x_left=0, x_right=0, y_down = 1, y_up = 1):
        self.n_image = n_image
        self.x = x
        self.color = color
        self.make_anim = True
        
        self.anim_fig, self.anim_ax = plt.subplots(1)
        self.anim_fig.suptitle(title)
        self.anim_ax.set_xlabel(xlabel)
        self.anim_ax.set_ylabel(ylabel)
        self.anim_ax.set_xlim(np.min(x) - x_left, np.max(x) + x_right)
        self.anim_ax.set_ylim(np.min(y) - y_down, np.max(y) + y_up)
        self.anim_ax.grid()
        self.anim_ax.plot(x, y, color=ex_color)
        
        return self.anim_fig, self.anim_ax
    
    
    def make_scene(self, t, epoch):
        #Création de scène
        if t % (epoch/self.n_image) == 1:
            x_in = self.x.reshape(-1, 1)
            for ll in self.__layer_list:
                x_in = ll.forward(x_in)
            im, = self.anim_ax.plot(self.x, ll.y, color=self.color)
            self.images.append([im])

en conclusion

Ceci conclut l'expérience DNN (Deep Neural Network). Essayez de jouer avec d'autres fonctions diverses.

Série d'apprentissage en profondeur

Recommended Posts