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

Cet article est le 18e jour du calendrier de l'Avent Wacul.

Auto-introduction

Je travaille dans l'équipe d'analyse de WACUL Co., Ltd. depuis un an. histoire de python: environ 3 semaines

Choses à faire

Pratiquer python et j'essaierai d'implémenter une machine Boltsman profonde principalement dans le but d'étudier l'apprentissage profond probabiliste. Tous les aspects théoriques sont dans les livres suivants.

«Deep Learning (Machine Learning Professional Series)» Takayuki Okaya (Auteur) ` «Deep Learning» (supervisé par la Society of Artificial Intelligence) »

Dans cet article, nous allons d'abord implémenter une machine Boltsman restreinte comme préparation. La prochaine fois, nous construirons une machine Boltzmann profonde en empilant des machines Boltzmann restreintes.

Une brève description de la machine Boltzmann

Puisque la distribution de probabilité est simplement une «fonction qui associe un ensemble de valeurs à la densité de probabilité (masse de probabilité) à partir de laquelle elle est obtenue», elle peut être considérée comme un «mécanisme derrière la génération d'un ensemble de valeurs» dans son ensemble. Je peux le faire. Cela s'appelle *** modèle généré *** </ font>. Si vous pouvez créer un modèle de génération, vous pouvez comprendre les relations entre les variables et calculer les valeurs attendues, ce qui élargira la gamme des applications. La machine de Boltzmann dont nous parlerons cette fois est un modèle de génération qui calcule la densité de probabilité en utilisant la distribution de Boltzmann. La densité de probabilité spécifique est la valeur normalisée après avoir pris la fonction exponentielle de la fonction d'énergie (Φ) définie ci-dessous multipliée par moins. (Multiplier par une constante pour tenir dans l'intervalle fermé de [0,1]) La fonction d'énergie est une somme linéaire pondérée de chaque biais et lien de nœud.

スライド1.png

Variables visibles et masquées

Les variables visibles sont des variables qui peuvent être observées directement, et les variables cachées sont des variables qui ne peuvent pas être observées. L'introduction de variables cachées améliore l'expressivité de la machine Boltzmann. Dans le cas d'un modèle qui inclut des variables cachées, la distribution des seules variables visibles marginalisées par rapport aux variables cachées </ strong> est utilisée comme fonction objectif au moment de l'estimation la plus probable.

Machine Boltsman limitée

La machine de Boltzmann restreinte est un modèle qui simplifie l'estimation des paramètres en introduisant des variables cachées dans une machine de Boltzmann générale et en ajoutant certaines contraintes. Quelles sont les restrictions? ① Il n'y a pas de lien entre les nœuds visibles ② Il n'y a pas de lien entre les nœuds cachés Il y en a deux. A partir de ces deux conditions, la machine de Boltzmann restreinte s'exprime sous la forme d'un modèle à deux couches, une "couche visible" constituée uniquement de nœuds visibles et une "couche cachée" constituée uniquement de nœuds cachés, et n'a de liens qu'entre différentes couches. Ce sera.

Cela signifie que *** </ font> signifie qu'un calque est conditionnellement indépendant d'un calque *** </ font>. -Obtenir un échantillon de tous les nœuds de la couche cachée à partir des valeurs attendues actuelles de tous les nœuds de la couche visible -Approximer la valeur attendue de tous les nœuds de la couche cachée à partir du groupe d'échantillons de tous les nœuds de la couche cachée ・ Obtenez un exemple de groupe de tous les nœuds de la couche visible à partir des valeurs attendues de tous les nœuds de la couche cachée. ・ Approximer la valeur attendue de tous les nœuds de la couche visible à partir du groupe d'échantillons de tous les nœuds de la couche visible Vous pouvez activer le processus. Ce calcul est relativement simple et présente l'avantage de pouvoir obtenir un échantillon de chaque couche à la fois. Limites Les limites de la machine Boltsman sont censées en bénéficier en échange de l'expressivité du modèle.

スライド2.png

Apprentissage des machines Boltzmann restreintes

Une fois que vous avez décidé de la forme de votre modèle, la prochaine chose à faire est d'apprendre les paramètres. L'apprentissage des paramètres pour les machines de Boltzmann restreintes utilise la méthode du gradient orthodoxe (ascendant) dans le but de maximiser la fonction de vraisemblance logarithmique. La méthode d'augmentation du gradient est une méthode qui vise à maximiser la fonction tout en répétant petit à petit le processus de mise à jour des paramètres dans le sens où la valeur de la fonction objectif augmente. Distribution). Pour maximiser cela, la fonction objectif est différenciée par les paramètres cibles pour obtenir le gradient. Les valeurs attendues des couches visibles et cachées sont nécessaires lors du calcul du gradient, mais comme cela est difficile à calculer directement, il est approximé en générant réellement un échantillon à l'aide de l'échantillonnage de Gibbs et en le moyennant. A ce moment, le calcul peut être facilement exécuté par le bénéfice de "l'indépendance conditionnelle entre les couches" de la machine de Boltzmann restreinte mentionnée ci-dessus.

L'apprentissage passe par le processus suivant lorsqu'il est considéré globalement.

⚫️ Initialiser les paramètres de manière aléatoire


⚫️ Répétez ci-dessous----------------------------------------------

① Échantillonnez alternativement le calque visible et le calque caché en utilisant les valeurs de paramètre actuelles

② Calculez la valeur attendue de chaque nœud dans la couche visible et la couche cachée en utilisant ①

③ Utilisez ② pour calculer le biais de la couche visible, le biais de la couche cachée et le gradient (différenciation) de chaque lien.
    
④ Mettre à jour les paramètres avec ③
  ---------------------------------------------------------

Implémentation Python de la machine Boltsman restreinte

La méthode de divergence contrastive persistante est utilisée lors de l'échantillonnage. Le jugement de convergence est omis car l'efficacité du traitement n'est pas prise en compte.

Implémentation de classe RBM

from typing import List
Input = List[int]

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


class RBM:
    learningRate = 0.005
    sample_num = 100
    
    #Vn est le nombre de nœuds visibles, Hn est le nombre de nœuds cachés
    def __init__(self , Vn : int , Hn : int):
        self.Vn = Vn
        self.Hn = Hn
        self.bias_v = np.array(np.random.rand(Vn)) #Initialisé au hasard
        self.bias_h = np.array(np.random.rand(Hn)) #Initialisé au hasard
        self.link  = np.array(np.random.rand(Vn,Hn)) #Initialisé au hasard
        
        self.hidden_value = []
        
    def train(self, data_for_learning , iteration_times):
        for iteration_times in range(iteration_times):
            
            # samples_vh est v*échantillon h
            samples_h =[]
            samples_v = []
            samples_vh = np.zeros((self.Vn,self.Hn))
            
            messege_in_sampling = 0
            sigmoid_belief = 0
        
            #① Échantillonnez alternativement le calque visible et le calque caché en utilisant les valeurs de paramètre actuelles
            for index in range(RBM.sample_num):
                
                current_v = data_for_learning[0]
                current_h = np.zeros(self.Hn)
                                
                ##Échantillonnage du calque caché
                for j in  range(self.Hn) :  
                    messege_in_sampling = self.bias_h[j] + sum(list(map(lambda i: current_v[i] * self.link[i][j]  , range(self.Vn))))                
                    
                    sigmoid_belief = 1/(1+ np.exp(-1 * messege_in_sampling))
                    
                    r = np.random.rand(1)
                    
                    if sigmoid_belief > r[0]  :
                        current_h[j] = 1
                    else : 
                        current_h[j] = 0
                
                samples_h.append(current_h)
                  
                ##Échantillonnage de la couche visible
                for i in range(self.Vn):
                    messege_in_sampling = self.bias_v[i] + sum(list(map(lambda j: current_h[j] * self.link[i][j]  , range(self.Hn))))                
                    
                    sigmoid_belief = 1/(1+ np.exp(-1 * messege_in_sampling))
                    
                    r = np.random.rand(1)
                    if sigmoid_belief > r[0]  :
                        current_v[i] = 1
                    else : 
                        current_v[i] = 0
                                        
                samples_v.append(current_v)  
                
                ##Couche visible ✖️ Couche cachée échantillonnée
                z = itertools.product(current_v,current_h)
                
                product = []
                for element in z:
                    product.append(element)
                    
                current_vh = (np.array(list(map(lambda x : x[0] * x[1] ,product))) ) .reshape(self.Vn,self.Hn)
                    
                samples_vh += current_vh
            
            #② Calculez la valeur attendue de chaque nœud dans la couche visible et la couche cachée
            E_V  = np.sum(np.array(samples_v),axis=0) / RBM.sample_num
            E_H = np.sum(np.array(samples_h),axis=0) / RBM.sample_num
            E_VH = samples_vh / RBM.sample_num

            #③ Calculez le gradient (différentiel) de chacun des biais de couche visible, biais de couche cachée et lien
            ##Différenciation du biais de couche visible
            gradient_v = sum(np.array(data_for_learning)) / len(data_for_learning) - E_V
                
            ##Différenciation du biais de couche cachée
            gradient_h = []
            for j in range(len(self.bias_h)):
                gradient_h_1 = []
                sigmoid_beliefs_h_1 = []

                for n in range(len(data_for_learning)):
                    messege_h_1 = self.bias_h[j] + sum(list(map(lambda i: data_for_learning[n][i] * self.link[i][j]  , range(self.Vn))))
                    sigmoid_belief_h_1 = 1/(1+ np.exp(-1 * messege_h_1))
                    sigmoid_beliefs_h_1.append(sigmoid_belief_h_1)
                
                gradient_h_1 = sum(sigmoid_beliefs_h_1) / len(data_for_learning)      
                gradient_h_2 = E_H[j]

                gradient_h_j =  gradient_h_1 +  gradient_h_2
                gradient_h.append(gradient_h_j)

            ##Différenciation des liens
            gradient_vh = []
            for i in range(len(self.bias_v)):
                for j in range(len(self.bias_h)):
                    gradient_vh_1 = []
                    sigmoid_beliefs_vh_1 = []

                    for n in range(len(data_for_learning)):
                        messege_vh = self.bias_h[j] + sum(list(map(lambda i: data_for_learning[n][i] * self.link[i][j]  , range(self.Vn))))
                        sigmoid_belief_vh_1 = 1/(1+ np.exp(-1 * messege_vh))
                        sigmoid_beliefs_vh_1.append(sigmoid_belief_vh_1 * data_for_learning[n][i])

                    gradient_vh_1 = sum(sigmoid_beliefs_vh_1) / len(data_for_learning)      
                    gradient_vh_2 = E_VH[i][j]

                    gradient_vh_ij =  gradient_vh_1 +  gradient_vh_2

                    gradient_vh.append(gradient_vh_ij)

            #④ Mettez à jour les paramètres avec ③
            self.bias_v += RBM.learningRate *  np.array(gradient_v)
            self.bias_h += RBM.learningRate *  np.array(gradient_h)
            self.link +=  RBM.learningRate *  np.array(gradient_vh).reshape(self.Vn,self.Hn)            

    def energy(self,input : Input) -> float:
        #Énergie de couche visible
        energy_v = sum(list(map(lambda i : self.bias_v[i] * input[i]  , range(self.Vn))))
        
        #Énergie de la couche cachée
        ##Valeur estimée de la couche masquée
        hidden_layer = self.getHiddenValue(input)
                
        energy_h = sum(list(map(lambda j : self.bias_h[j] * hidden_layer[j]  , range(self.Hn))))
        
        #lier l'énergie
        accumlator = []        
        for i in range(self.Vn):
            for j in range(self.Hn):
                accumlator.append(input[i]*hidden_layer[j] * self.link[i][j])
                
        energy_vh = sum(accumlator)       
                
        energy = -1 * (energy_v + energy_h + energy_vh)
        
        return energy
    
    def estimate(self,input:Input) -> float:            
    
        #Calculer la fonction énergétique
        energy = self.energy(input)
        
        #Calculer la fonction de distribution
        all_patterns = list(itertools.product(range(0,2), repeat=self.Vn))
        partition = sum(list(map(lambda x: np.exp(-1 *self.energy(list(x))) , all_patterns)))
            
        return  np.exp(-1*  energy ) / partition
            
    def getHiddenValue(self,input:Input) :
        
        hidden_layer = np.zeros(self.Hn)
            
        for j in  range(self.Hn) :  
            messege_in_sampling = self.bias_h[j] + sum(list(map(lambda i: input[i] * self.link[i][j]  , range(self.Vn))))                
            sigmoid_belief = 1/(1+ np.exp(-1 * messege_in_sampling))
            
            r = np.random.rand(1)
            if sigmoid_belief > r[0]:
                hidden_layer[j] = 1
            else : 
                hidden_layer[j] = 0
                
        self.hidden_value = hidden_layer
        
        return hidden_layer

Exemple d'exécution

#Construire
visible_nord = 3
hidden_nord = 4
rbm = RBM(visible_nord,hidden_nord)

#Création de données de formation
training_data = np.array(list(map(lambda x : np.random.randint(0,2) , range(0,300)))).reshape(100,3)

#Apprentissage des paramètres
rbm.train(training_data,10)
print(rbm.link)

input  = [1,0,1]

#Obtenez la valeur du calque caché
rbm.getHiddenValue(input)

#Sortie de la fonction énergie
rbm.energy(input)

#Sortie de densité de probabilité
rbm.estimate()

Cela semble fonctionner, mais on ne sait pas si cela convient. .. .. Je vais passer à autre chose pour le moment.

la prochaine fois

La prochaine fois, nous combinerons cette machine Boltzmann restreinte pour configurer une machine Boltzmann profonde. Au fur et à mesure que les couches deviennent plus profondes, l'apprentissage devient plus difficile, donc l'histoire est que vous pouvez obtenir de bonnes valeurs initiales de paramètres en pré-apprenant d'abord à l'aide de la machine Boltzmann restreinte, puis l'utiliser pour affiner l'ensemble du modèle. Sera.

Recommended Posts