[Mit einfacher Erklärung] Scratch-Implementierung einer tiefen Boltzmann-Maschine mit Python ①

Dieser Artikel ist der 18. Tag des Wacul Adventskalenders.

Vorstellen

Ich arbeite seit einem Jahr im Analyseteam von WACUL Co., Ltd. Python-Geschichte: ca. 3 Wochen

Dinge die zu tun sind

Das Üben von Python und ich werden versuchen, eine Deep Boltsman-Maschine zu implementieren, hauptsächlich um probabilistisches Deep Learning zu studieren. Alle theoretischen Aspekte sind in den folgenden Büchern enthalten.

"Deep Learning (Machine Learning Professional Series)" Takayuki Okaya (Autor) " "Deep Learning" (betreut von der Society of Artificial Intelligence)

In diesem Artikel werden wir zunächst eine eingeschränkte Boltsman-Maschine als Vorbereitung implementieren. Nächstes Mal werden wir eine tiefe Boltsman-Maschine bauen, indem wir eingeschränkte Boltsman-Maschinen stapeln.

Eine kurze Beschreibung der Boltzmann-Maschine

Da die Wahrscheinlichkeitsverteilung einfach eine "Funktion ist, die einen Satz von Werten mit der Wahrscheinlichkeitsdichte (Wahrscheinlichkeitsmasse) verknüpft, aus der sie erhalten wird", kann sie als "Mechanismus hinter der Erzeugung eines Satzes von Werten" als Ganzes angesehen werden. Ich kann es schaffen Dies wird als *** generiertes Modell *** </ font> bezeichnet. Wenn Sie ein Generierungsmodell erstellen können, können Sie die Beziehungen zwischen Variablen verstehen und erwartete Werte berechnen, wodurch der Anwendungsbereich erweitert wird. Die Boltzmann-Maschine, die wir dieses Mal diskutieren werden, ist ein Generationsmodell, das die Wahrscheinlichkeitsdichte unter Verwendung der Boltzmann-Verteilung berechnet. Die spezifische Wahrscheinlichkeitsdichte ist der standardisierte Wert nach der Exponentialfunktion der unten definierten Energiefunktion (Φ) multipliziert mit minus. (Mit einer Konstanten multiplizieren, um in das geschlossene Intervall von [0,1] zu passen) Die Energiefunktion ist eine gewichtete lineare Summe jeder Knotenvorspannung und -verbindung.

スライド1.png

Sichtbare und versteckte Variablen

Sichtbare Variablen sind Variablen, die direkt beobachtet werden können, und versteckte Variablen sind Variablen, die nicht beobachtet werden können. Die Einführung versteckter Variablen verbessert die Ausdruckskraft der Boltzmann-Maschine. Im Fall eines Modells, das versteckte Variablen enthält, wird zum Zeitpunkt der wahrscheinlichsten Schätzung die Verteilung nur sichtbarer Variablen verwendet, die in Bezug auf versteckte Variablen </ strong> marginalisiert sind.

Begrenzte Boltsman-Maschine

Die eingeschränkte Boltzmann-Maschine ist ein Modell, das die Parameterschätzung vereinfacht, indem versteckte Variablen in eine allgemeine Boltzmann-Maschine eingeführt und bestimmte Einschränkungen hinzugefügt werden. Was sind die Einschränkungen? ① Es gibt keine Verbindung zwischen sichtbaren Knoten ② Es gibt keine Verbindung zwischen versteckten Knoten Es gibt zwei. Unter diesen beiden Bedingungen wird die eingeschränkte Boltsman-Maschine als Modell ausgedrückt, das zwei Ebenen aufweist, eine "sichtbare Ebene", die nur aus sichtbaren Knoten besteht, und eine "verborgene Ebene", die nur aus verborgenen Knoten besteht, und nur Verknüpfungen zwischen verschiedenen Ebenen aufweist. Es wird sein.

Dies bedeutet, dass *** </ font> bedeutet, dass eine Ebene bedingt unabhängig von einer Ebene *** </ font> ist.

  • Holen Sie sich eine Beispielgruppe aller Knoten in der verborgenen Ebene aus den aktuell erwarteten Werten aller Knoten in der sichtbaren Ebene
  • Schätzen Sie den erwarteten Wert aller Knoten in der verborgenen Ebene aus der Stichprobengruppe aller Knoten in der verborgenen Ebene ・ Ermitteln Sie eine Beispielgruppe aller Knoten in der sichtbaren Ebene aus den erwarteten Werten aller Knoten in der verborgenen Ebene. ・ Schätzen Sie den erwarteten Wert aller Knoten in der sichtbaren Ebene aus der Stichprobengruppe aller Knoten in der sichtbaren Ebene Sie können den Prozess drehen. Diese Berechnung ist relativ einfach und hat den Vorteil, dass Sie von jeder Schicht gleichzeitig eine Probe erhalten können. Grenzen Die Grenzen der Boltzmann-Maschine sollen im Austausch für die Ausdruckskraft des Modells davon profitieren.

スライド2.png

Eingeschränkte Boltzmann-Maschinen lernen

Sobald Sie sich für die Form Ihres Modells entschieden haben, müssen Sie als Nächstes die Parameter lernen. Das Parameterlernen für eingeschränkte Boltzmann-Maschinen verwendet die Methode des orthodoxen Gradienten (aufsteigend), um die logarithmische Wahrscheinlichkeitsfunktion zu maximieren. Das Gradientenerhöhungsverfahren ist ein Verfahren, das darauf abzielt, die Funktion zu maximieren, während der Vorgang des Aktualisierens der Parameter nach und nach in der Richtung wiederholt wird, in der der Wert der Zielfunktion zunimmt. Verteilung). Um dies zu maximieren, wird die Zielfunktion durch die Zielparameter differenziert, um den Gradienten zu erhalten. Erwartete Werte für sichtbare und verborgene Schichten sind bei der Berechnung des Gradienten erforderlich. Da dies jedoch schwierig direkt zu berechnen ist, wird es angenähert, indem tatsächlich eine Probe unter Verwendung der Gibbs-Abtastung erzeugt und gemittelt wird. Zu diesem Zeitpunkt kann die Berechnung leicht durch den Vorteil der "bedingten Unabhängigkeit zwischen Schichten" der oben erwähnten eingeschränkten Boltzmann-Maschine ausgeführt werden.

Das Lernen durchläuft den folgenden Prozess, wenn man es allgemein betrachtet.

⚫️ Initialisieren Sie die Parameter nach dem Zufallsprinzip


⚫️ Wiederholen Sie unten----------------------------------------------

① Probieren Sie abwechselnd die sichtbare und die verborgene Ebene mit den aktuellen Parameterwerten ab

② Berechnen Sie den erwarteten Wert jedes Knotens in der sichtbaren und der verborgenen Ebene mit ①

③ Berechnen Sie den Gradienten (Differential) der Vorspannung der sichtbaren Schicht, der Vorspannung der verborgenen Schicht und der Verknüpfung mit ②
    
④ Aktualisieren Sie die Parameter mit ③
  ---------------------------------------------------------

Eingeschränkte Boltsman-Maschine Python-Implementierung

Ich verwende beim Abtasten die Methode der persistenten kontrastiven Divergenz. Die Konvergenzbeurteilung entfällt, da die Verarbeitungseffizienz nicht berücksichtigt wird.

Implementierung der RBM-Klasse

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 ist die Anzahl der sichtbaren Knoten, Hn ist die Anzahl der versteckten Knoten
    def __init__(self , Vn : int , Hn : int):
        self.Vn = Vn
        self.Hn = Hn
        self.bias_v = np.array(np.random.rand(Vn)) #Zufällig initialisiert
        self.bias_h = np.array(np.random.rand(Hn)) #Zufällig initialisiert
        self.link  = np.array(np.random.rand(Vn,Hn)) #Zufällig initialisiert
        
        self.hidden_value = []
        
    def train(self, data_for_learning , iteration_times):
        for iteration_times in range(iteration_times):
            
            # samples_vh ist v*h Probe
            samples_h =[]
            samples_v = []
            samples_vh = np.zeros((self.Vn,self.Hn))
            
            messege_in_sampling = 0
            sigmoid_belief = 0
        
            #① Probieren Sie abwechselnd die sichtbare und die verborgene Ebene mit den aktuellen Parameterwerten ab
            for index in range(RBM.sample_num):
                
                current_v = data_for_learning[0]
                current_h = np.zeros(self.Hn)
                                
                ##Abtasten der verborgenen Ebene
                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)
                  
                ##Abtasten der sichtbaren Schicht
                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)  
                
                ##Sichtbare Schicht ✖️ Versteckte Schicht abgetastet
                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
            
            #② Berechnen Sie den erwarteten Wert jedes Knotens in der sichtbaren und der verborgenen Ebene
            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

            #③ Berechnen Sie den Gradienten (Differential) jeder sichtbaren Schichtvorspannung, verborgenen Schichtvorspannung und Verknüpfung
            ##Differenzierung der sichtbaren Schichtvorspannung
            gradient_v = sum(np.array(data_for_learning)) / len(data_for_learning) - E_V
                
            ##Differenzierung der versteckten Schichtvorspannung
            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)

            ##Linkdifferenzierung
            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)

            #④ Aktualisieren Sie die Parameter mit ③
            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:
        #Sichtbare Schichtenergie
        energy_v = sum(list(map(lambda i : self.bias_v[i] * input[i]  , range(self.Vn))))
        
        #Versteckte Schichtenergie
        ##Geschätzter Wert der verborgenen Ebene
        hidden_layer = self.getHiddenValue(input)
                
        energy_h = sum(list(map(lambda j : self.bias_h[j] * hidden_layer[j]  , range(self.Hn))))
        
        #Energie verbinden
        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:            
    
        #Energiefunktion berechnen
        energy = self.energy(input)
        
        #Verteilungsfunktion berechnen
        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

Ausführungsbeispiel

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

#Erstellung von Trainingsdaten
training_data = np.array(list(map(lambda x : np.random.randint(0,2) , range(0,300)))).reshape(100,3)

#Parameter lernen
rbm.train(training_data,10)
print(rbm.link)

input  = [1,0,1]

#Ermitteln Sie den Wert der ausgeblendeten Ebene
rbm.getHiddenValue(input)

#Energiefunktionsausgabe
rbm.energy(input)

#Ausgabe der Wahrscheinlichkeitsdichte
rbm.estimate()

Es scheint zu funktionieren, aber es ist unklar, ob es passt. .. .. Ich werde vorerst weitermachen.

nächstes Mal

Nächstes Mal werden wir diese eingeschränkte Boltzmann-Maschine kombinieren, um eine tiefe Boltzmann-Maschine zu konfigurieren. Je tiefer die Ebenen werden, desto schwieriger wird das Lernen. Daher können Sie gute Anfangswerte von Parametern erhalten, indem Sie zuerst mit der eingeschränkten Boltzmann-Maschine vorlernen und dann das gesamte Modell optimieren. Wird sein.

Recommended Posts