[Avec une explication simple] Implémentation Scratch d'une machine Boltsman profonde avec Python ②

Cet article est le dernier jour du calendrier de l'Avent Wacul 2016.

Dernière fois

En préparation, nous avons implémenté une machine Boltsman restreinte. Cette fois, nous allons "empiler" cette machine Boltzmann restreinte pour former une machine Boltzmann profonde.

[Avec une explication simple] Implémentation Scratch d'une machine Boltzmann profonde avec Python ① http://qiita.com/yutaitatsu/items/a9478841357b10789514

Qu'est-ce qu'une machine Deep Boltsman?

C'est une sorte de machine Boltzmann (avec couche cachée) que j'ai expliqué la dernière fois. Il représente un modèle de génération dans son ensemble. Il a un calque visible en bas et un calque caché est empilé au-dessus.   La machine Boltzmann profonde est un type de machine Boltzmann avec une couche cachée générale, vous pouvez donc apprendre par la méthode du gradient telle qu'elle est. Cependant, plus la couche est profonde, plus il y a de paramètres à estimer et plus le risque de tomber dans une solution locale est grand.

Ingéniosité d'apprentissage

Par conséquent, *** chaque couche est conditionnellement indépendante (il n'y a pas de lien entre les nœuds de la même couche) ***. Voici la plus grande astuce de l'apprentissage automatique profond de Boltzmann introduit cette fois. La raison en est que ***, dans l'ordre du bas, considère deux couches chacune comme une machine Boltzmann restreinte *** </ font>. La paire de deux couches du bas est considérée comme une machine de Boltzmann restreinte, et le but est d'obtenir une bonne valeur initiale des paramètres en pré-apprenant chaque paire. Plus précisément, suivez les étapes suivantes.

[Étapes de pré-apprentissage]

① Tout d'abord, apprenez la machine Boltzmann restreinte (en bas), y compris la couche visible
② Dans la machine Boltzmann restreinte apprise dans ①, échantillonnez la valeur de la couche cachée en utilisant la «distribution conditionnelle de la couche visible à la couche cachée».
③ Apprenez en considérant l'échantillon obtenu dans ② comme l'entrée de la couche visible de la prochaine machine Boltsman restreinte (la seconde à partir du bas).
(Répétez ci-dessous)

Après avoir obtenu une bonne valeur initiale des paramètres, réapprendre l'orthodoxe avec la méthode du gradient. Cependant, si la couche est profonde, le coût de calcul de la valeur attendue augmentera de manière explosive, donc une méthode d'approximation est nécessaire pour obtenir une solution dans un temps réaliste.

スライド3.png

Méthode approximative

Une approximation est nécessaire lors du calcul du gradient pour calculer la valeur attendue sous les paramètres actuels. Cette zone est la même que la précédente machine Boltzmann restreinte.   Il existe deux méthodes principales pour estimer la valeur attendue de la distribution. L'une est une méthode d'échantillonnage *** telle que "l'échantillonnage Gyps" utilisée lors du précédent apprentissage automatique limité de Boltzmann, et l'autre est une méthode de *** variation telle que "l'approximation du champ moyen" utilisée cette fois *. **est. Cette fois, nous utiliserons les deux méthodes.

La différence entre la méthode d'échantillonnage et la méthode de variation est que la première "génère en fait un grand nombre d'échantillons à partir de la distribution cible elle-même et prend la moyenne pour se rapprocher de la valeur attendue", tandis que la seconde "valeur attendue". Utilise celle qui est la plus proche de la distribution cible parmi les distributions qui peuvent être calculées analytiquement (facilement) à la place. " Il convient de noter que le premier garantit que la valeur convergera vers la vraie valeur attendue si un nombre infini d'échantillons est obtenu, tandis que le second ne le fait pas.

Plus précisément, la méthode d'échantillonnage utilise la méthode de Monte Carlo en chaîne de Markov (MCMC), et la méthode de variation estime les paramètres qui minimisent la KLdivergence avec la distribution cible pour les groupes de distribution tels que la distribution gaussienne mixte, et l'utilise. Faire.

Comment utiliser la machine Boltsman profonde

La machine Botulman à couche profonde peut être utilisée comme "auto-encodeur" en plus d'être utilisée comme modèle de génération tel quel. Cela signifie que chaque calque masqué peut être considéré comme une *** réduction de la dimension du calque visible *** </ font>. En d'autres termes, la valeur de la couche cachée peut être considérée comme une quantité de caractéristiques de dimension réduite tout en préservant l'essence des informations possédées par chaque nœud de la couche visible. Par conséquent, la machine Deep Boltsman peut être appliquée en tant que prétraitement de données pour appliquer des réseaux neuronaux déterministes et d'autres techniques d'apprentissage automatique orthodoxes.

Exemple d'implémentation

  • Tous les paramètres de la machine Boltzmann profonde sont conservés comme paramètres de la machine Boltzmann restreinte qui les compose.
  • Le complément de Gibbs et le calcul de la valeur attendue de l'approximation moyenne du champ sont utilisés.
  • Seule la valeur de lien est utilisée pour la fonction d'énergie pendant le post-apprentissage et le biais est omis.

Mise en œuvre de la machine Boltsman profonde

from typing import List
from itertools import chain
Layer = List[int]
Input = List[int]

from pprint import pprint

def sigmoid(z):
    return 1/(1+np.exp(-z))

def get_array_from_structure(base_array, structure):
    result = []
    current_index = 0 
    for index in range(len(structure)):
        accumlator = []
        if index == 0:
            accumlator = base_array[0:structure[current_index]]
        else:
            accumlator = base_array[current_index: current_index + structure[index]]

        current_index += structure[index]
        result.append(accumlator)
    
    return result

class DeepBolzmannMachine:
    learningRate = 0.05
    sample_num = 100
    
    def __init__(self,layer_structure:Layer):
        #Générez une liste de RBM en les associant à partir de la couche visible dans l'ordre
        
        ##Structure des couches
        self.layer_structure = layer_structure
        self.layer_num = len(layer_structure)
        
        ##Faire une paire
        self.pairs = []
        for i in range(len(layer_structure) -1):
            self.pairs.append((layer_structure[i],layer_structure[i+1]))
        
        ##Générer RBM pour chaque paire
        self.rbms = []
        for pair in self.pairs:
            rbm = RBM(pair[0],pair[1])
            self.rbms.append(rbm)
            
        self.link_structure = list(map(lambda x :x[0] *x[1], self.pairs))
 
    def preTrain(self,initaial_data_for_learning:Input, sample_num : int):

        current_learning_data = []

        ##Calculez la valeur initiale de chaque RBM
        for rbm_index in range(len(self.rbms)):            
            #Lorsque le calque est le calque inférieur (visible ou masqué)
            if rbm_index == 0:
                self.rbms[rbm_index].train(initaial_data_for_learning,100)
                
                for i in range(sample_num):
                    hidden_value = self.rbms[rbm_index].getHiddenValue(initaial_data_for_learning[i])
                    current_learning_data.append(hidden_value)
            
            #Lorsque le calque n'est pas le calque du bas (Caché ou Caché)
            else:
                #Apprenez avec les données d'entrée
                self.rbms[rbm_index].train(current_learning_data,100)
                
                #Créer des données pour le prochain RBM
                data_for_learning = []
                for i in range(sample_num):
                    hidden_value = self.rbms[rbm_index].getHiddenValue(current_learning_data[i])
                    data_for_learning.append(hidden_value)
                    
                current_learning_data = data_for_learning
               

    #Mettre à jour les paramètres obtenus dans le pré-apprentissage aux valeurs initiales par la méthode ascendante de gradient
    #Trouvez le premier terme de différenciation par le paramètre de lien de la fonction de vraisemblance logarithmique par approximation de champ moyen et le deuxième terme par divergence contrastive
    def train(self, data_for_learning:Input, iteration_times:int):
        samples = []
        for iteration_times in range(iteration_times):            
            #Calculez la valeur attendue de chaque couche / nœud masqué pour une entrée d'échantillon en calculant la moyenne de l'approximation des champs [Note]:Mettez à jour uniquement le lien, pas le biais]
            ##Données intermédiaires des valeurs attendues(mean_field)Est conservé sous forme de tableau tridimensionnel (qch échantillon/couche r/Valeur attendue de la jème variable)
            sample_length = len(data_for_learning)
            max_node_num = max(self.layer_structure)
            
            mean_field = np.zeros((sample_length, self.layer_num, max_node_num))
            for s in range(sample_length):
                for r in range(self.layer_num):
                    if r == 0:
                        pass
                    elif r == 1:
                        for j in range(self.rbms[0].Hn):
                            first_paragraph = sum(map(lambda i : self.rbms[0].link[i][j] * data_for_learning[s][i] , range(self.rbms[0].Vn)))
                            second_paragraph = sum(map(lambda k : self.rbms[1].link[j][k] * mean_field[s][1][k] , range(self.rbms[1].Hn)))
                            
                            mean_field[s][1][j]  =  sigmoid(first_paragraph + second_paragraph)
                            
                    elif r == self.layer_num - 1:
                        for j in range(self.rbms[r -1].Hn):
                            mean_field[s][r][j]  =  sigmoid(sum(map(lambda k : self.rbms[r-1].link[k][j] * mean_field[s][r-1][k]  , range(self.rbms[r-2].Hn))))
                
                    else :
                        for j in range(self.rbms[r -1].Hn):
                            mean_field[s][r][j]  =  sigmoid(sum(map(lambda k : self.rbms[r -1].link[k][j] * mean_field[s][r-2][k]  , range(self.rbms[r - 2].Hn))) + sum(map(lambda l : self.rbms[r].link[j][l] * mean_field[s][r+1][l] , range(self.rbms[r].Hn))))

            #Calculer le premier terme de la pente du lien
            gradient_1 = []
            
            for r in range(len(self.rbms)):
                gradient_1_element = []
                if r == 0:
                    for i in range(self.rbms[0].Vn):
                        for j in range(self.rbms[0].Hn):
                            gradient_1_element.append((sum(map(lambda s : data_for_learning[s][i]  * mean_field[s][1][j]  , range(sample_length)))) / sample_length)
                        
                else:
                    for i in range(self.rbms[r].Vn):
                        for j in range(self.rbms[r].Hn):
                            gradient_1_element.append((sum(map(lambda s : mean_field[s][r][i]  * mean_field[s][r+1][j]  , range(sample_length)))) / sample_length)
                
                gradient_1.append(gradient_1_element)
            
            #Calculer le deuxième terme de la pente du lien
            
            ##Échantillonner
            new_samples = []    
            for s in range(DeepBolzmannMachine.sample_num):
                sample = []
                ##Initialiser la valeur de sample
                for layer in self.layer_structure:
                    accumlator = []
                    for i in range(layer):
                        accumlator.append(np.empty)
                    sample.append(accumlator)
            
                ##Définissez la valeur initiale de l'échantillonnage (à ce stade, l'échantillon précédent est utilisé pour la deuxième fois et les instants suivants de la boucle ascendante de gradient)
                initial_randoms = []
                if iteration_times == 0:
                    for layer in self.layer_structure:
                        accumlator = []
                        for i in range(layer):
                            accumlator.append(np.random.rand(1)[0])
                        initial_randoms.append(accumlator)
                else:
                    initial_randoms = samples[s]

                ##Estimer un échantillon (valeur de chaque nœud)
                sigmoid_belief = []
                for layer_index in range(len(self.layer_structure)):
                    for node_index in range(self.layer_structure[layer_index]):
                        ####Calculer la croyance sigmoïde en utilisant les messages de tous les nœuds attachés à ce nœud
                        if layer_index == 0:
                            sigmoid_belief = sigmoid(sum(map(lambda j : initial_randoms[1][j] * self.rbms[0].link[node_index][j]  , range(self.layer_structure[1]))))
            
                        elif layer_index == self.layer_num -1:
                            sigmoid_belief = sigmoid(sum(map(lambda j : initial_randoms[self.layer_num - 2][j] * self.rbms[self.layer_num -2].link[j][node_index]  , range(self.layer_structure[self.layer_num -2]))))
            
                        else :
                            belief_downstair = sum(map(lambda j : initial_randoms[layer_index - 1][j] * self.rbms[layer_index -1].link[j][node_index]  , range(self.layer_structure[layer_index -1])))
                            belief_upstair = sum(map(lambda j : initial_randoms[layer_index + 1][j] * self.rbms[layer_index].link[node_index][j]  , range(self.layer_structure[layer_index + 1])))
                            sigmoid_belief = sigmoid(belief_upstair  + belief_downstair)
            
                        ####Générer des nombres aléatoires
                        r = np.random.rand(1)[0]
                    
                        ####Définir 1 si la croyance sigmoïde est plus grande que le nombre aléatoire, 0 si elle est plus petite que le nombre aléatoire
                        if sigmoid_belief > r :
                            sample[layer_index][node_index] = 1
                        else: 
                            sample[layer_index][node_index] = 0
            
                #Ajouter un échantillon
                new_samples.append(sample)
                
            #Supprimez l'échantillon de la mise à jour du dégradé précédente et remplacez-le par cet exemple
            samples = new_samples
        
            #Approximer le deuxième terme de gradient de l'échantillon
            gradient_2 = []
            for r in range(len(self.rbms)):
                gradient_2_element = []
                if r == 0:
                    for i in range(self.rbms[0].Vn):
                        for j in range(self.rbms[0].Hn):
                            gradient_2_element.append(sum(map(lambda m : samples[m][0][i] * samples[m][1][j], range(DeepBolzmannMachine.sample_num))) / DeepBolzmannMachine.sample_num)
                else :
                    for i in range(self.rbms[r].Vn):
                        for j in range(self.rbms[r].Hn):
                            gradient_2_element.append(sum(map(lambda m : samples[m][r][i] * samples[m][r+1][j], range(DeepBolzmannMachine.sample_num))) / DeepBolzmannMachine.sample_num)
                
                gradient_2.append(gradient_2_element)
            
            
            #Calculer le gradient
            gradient_1_flatten = np.array(list(chain.from_iterable(gradient_1)))
            gradient_2_flatten = np.array(list(chain.from_iterable(gradient_2)))
            
            gradient_flatten = gradient_1_flatten + gradient_2_flatten            
            gradient = get_array_from_structure(gradient_flatten, self.link_structure)
            
            #Mettre à jour les paramètres avec un dégradé
            for rbm_index in range(len(self.rbms)):
                self.rbms[rbm_index].link +=  DeepBolzmannMachine.learningRate *  gradient[rbm_index].reshape((self.rbms[rbm_index].Vn,self.rbms[rbm_index].Hn))

Exemple d'exécution

#Réseau à 4 couches avec 5, 4, 3, 2 nœuds dans l'ordre à partir de la couche visible
layer_structure = [5,4,3,2]

#Nombre d'échantillons à échantillonner lors du calcul de la valeur attendue
sample_num = 100

#Instanciation de la machine Boltzmann
dbm = DeepBolzmannMachine(layer_structure)

#Créez des données d'entraînement appropriées
data_for_learning = np.random.rand(100,layer_structure[0])

#Pré-apprentissage
dbm.preTrain(data_for_learning,sample_num)

#Post-apprentissage
dbm.train(data_for_learning,sample_num)

fin

C'est pourquoi je l'ai implémenté.

L'apprentissage probabiliste profond ne semble pas être aussi répandu que l'apprentissage profond dans les réseaux neuronaux déterministes, mais personnellement *** "Comprendre ce qui se passe, y compris ses incertitudes" *** Bayes Je pense que le mérite de cette façon de penser est grand, alors j'espère que cela deviendra la norme de facto.

Cette année approche à grands pas. Bonne fin d'année, tout le monde ...

Recommended Posts