[Mit einfacher Erklärung] Scratch-Implementierung einer Deep Boltsman-Maschine mit Python ②

Dieser Artikel ist der letzte Tag des Wacul Adventskalenders 2016.

Letztes Mal

In Vorbereitung haben wir eine eingeschränkte Boltsman-Maschine implementiert. Dieses Mal werden wir diese eingeschränkte Boltzmann-Maschine "stapeln", um eine tiefe Boltzmann-Maschine zu bilden.

[Mit einfacher Erklärung] Scratch-Implementierung einer tiefen Boltzmann-Maschine mit Python ① http://qiita.com/yutaitatsu/items/a9478841357b10789514

Was ist eine Deep Boltsman-Maschine?

Es ist eine Art Boltzmann-Maschine (mit versteckter Schicht), die ich letztes Mal erklärt habe. Es repräsentiert ein Modell einer Generation als Ganzes. Es hat eine sichtbare Ebene unten und eine versteckte Ebene ist darüber gestapelt.   Die tiefe Boltzmann-Maschine ist eine Art Boltzmann-Maschine mit einer allgemein verborgenen Schicht, so dass Sie durch die Gradientenmethode so wie sie ist lernen können. Je tiefer die Schicht ist, desto mehr Parameter müssen geschätzt werden und desto größer ist das Risiko, in eine lokale Lösung zu geraten.

Einfallsreichtum des Lernens

Daher ist *** jede Schicht bedingt unabhängig (es gibt keine Verbindung zwischen Knoten in derselben Schicht) ***. Hier ist der größte Trick des tiefen maschinellen Lernens von Boltzmann, der dieses Mal eingeführt wurde. Der Grund dafür ist, dass *** in der Reihenfolge von unten jeweils zwei Ebenen als eingeschränkte Boltsman-Maschine *** </ font> betrachtet. Das Paar von zwei Schichten von unten wird als eingeschränkte Boltzmann-Maschine angesehen, und das Ziel besteht darin, durch Vorlernen jedes Paares einen guten Anfangswert der Parameter zu erhalten. Führen Sie insbesondere die folgenden Schritte aus.

[Schritte vor dem Lernen]

① Lernen Sie zunächst die eingeschränkte Boltzmann-Maschine (unten) einschließlich der sichtbaren Schicht
② In der in ① erlernten eingeschränkten Boltzmann-Maschine wird der Wert der verborgenen Schicht anhand der "bedingten Verteilung von der sichtbaren Schicht zur verborgenen Schicht" abgetastet.
③ Lernen Sie, indem Sie die in ② erhaltene Probe als Eingabe der sichtbaren Schicht der nächsten (zweiten von unten) eingeschränkten Boltzmann-Maschine betrachten.
(Wiederholen Sie unten)

Nachdem Sie einen guten Anfangswert für die Parameter erhalten haben, lernen Sie orthodox mit der Gradientenmethode neu. Wenn die Schicht jedoch tief ist, steigen die Berechnungskosten des erwarteten Werts explosionsartig an, sodass eine Näherungsmethode erforderlich ist, um eine Lösung in einer realistischen Zeit zu erhalten.

スライド3.png

Ungefähre Methode

Bei der Berechnung des Gradienten ist eine Annäherung erforderlich, um den erwarteten Wert unter den aktuellen Parametern zu berechnen. Dieser Bereich ist der gleiche wie bei der vorherigen eingeschränkten Boltzmann-Maschine.   Es gibt zwei Hauptmethoden zur Annäherung des erwarteten Werts der Verteilung. Eine ist eine *** Abtastmethode wie "Gyps Sampling", die während des vorherigen begrenzten maschinellen Lernens von Boltzmann verwendet wurde, und die andere ist eine *** Variationsmethode wie "Mean Field Approximation", die dieses Mal verwendet wird *. ** ist. Dieses Mal werden wir beide Methoden anwenden.

Der Unterschied zwischen der Stichprobenmethode und der Variationsmethode besteht darin, dass die erstere "tatsächlich eine große Anzahl von Stichproben aus der Zielverteilung selbst erzeugt und den Durchschnitt zur Annäherung an den erwarteten Wert verwendet", während die letztere "den erwarteten Wert". Verwenden Sie stattdessen die Verteilung, die der Zielverteilung am nächsten kommt, unter den Verteilungen, die analytisch (einfach) berechnet werden können. " Es ist zu beachten, dass der erstere garantiert, dass der Wert gegen den tatsächlich erwarteten Wert konvergiert, wenn eine unendliche Anzahl von Proben erhalten wird, während der letztere dies nicht tut.

Insbesondere verwendet das Stichprobenverfahren das Markov-Ketten-Monte-Carlo-Verfahren (MCMC), und das Variationsverfahren schätzt die Parameter, die die KL-Divergenz mit der Zielverteilung für eine Verteilungsgruppe wie eine gemischte Gauß-Verteilung minimieren, und verwendet diese. Machen.

Verwendung der Deep Boltsman-Maschine

Neben der Verwendung der Deep Botulman-Maschine als Generationsmodell gibt es auch eine Möglichkeit, sie als "Auto-Encoder" zu verwenden. Dies bedeutet, dass jede verborgene Ebene als *** Reduzierung der sichtbaren Ebenenabmessungen *** </ font> betrachtet werden kann. Mit anderen Worten, der Wert der verborgenen Schicht kann als Merkmalsgröße mit einer reduzierten Dimension betrachtet werden, während die Essenz der Informationen, die jeder Knoten der sichtbaren Schicht besitzt, erhalten bleibt. Daher kann die Deep Boltsman-Maschine als Datenvorverarbeitung zum Anwenden deterministischer neuronaler Netze und anderer orthodoxer Techniken des maschinellen Lernens verwendet werden.

Implementierungsbeispiel

  • Alle Parameter der tiefen Boltzmann-Maschine bleiben als Parameter der eingeschränkten Boltzmann-Maschine erhalten, aus der sie bestehen.
  • Gibbs-Ergänzung und Erwartungswertberechnung der durchschnittlichen Feldnäherung werden verwendet.
  • Während des Nachlernens wird nur der Verbindungswert für die Energiefunktion verwendet, und die Vorspannung wird weggelassen.

Implementierung einer tiefen Boltsman-Maschine

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):
        #Generieren Sie eine Liste von RBM, indem Sie die Reihenfolge in der sichtbaren Ebene festlegen
        
        ##Schichtstruktur
        self.layer_structure = layer_structure
        self.layer_num = len(layer_structure)
        
        ##Bilden Sie ein Paar
        self.pairs = []
        for i in range(len(layer_structure) -1):
            self.pairs.append((layer_structure[i],layer_structure[i+1]))
        
        ##Generieren Sie RBM für jedes Paar
        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 = []

        ##Berechnen Sie den Anfangswert jedes RBM
        for rbm_index in range(len(self.rbms)):            
            #Wenn die Ebene die unterste Ebene ist (sichtbar oder versteckt)
            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)
            
            #Wenn die Ebene nicht die unterste Ebene ist (Versteckt vs. Versteckt)
            else:
                #Lernen Sie mit Eingabedaten
                self.rbms[rbm_index].train(current_learning_data,100)
                
                #Erstellen Sie Daten für das nächste 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
               

    #Aktualisieren Sie die im Vorlernen erhaltenen Parameter mit der Gradientenanstiegsmethode auf die Anfangswerte
    #Finden Sie den ersten Differenzierungsterm durch den Verknüpfungsparameter der logarithmischen Wahrscheinlichkeitsfunktion durch mittlere Feldnäherung und den zweiten Term durch kontrastive Divergenz.
    def train(self, data_for_learning:Input, iteration_times:int):
        samples = []
        for iteration_times in range(iteration_times):            
            #Berechnen Sie den erwarteten Wert jeder verborgenen Schicht / jedes Knotens für eine Probeneingabe durch Mittelung der Feldnäherung [Hinweis]:Aktualisiere nur den Link, nicht den Bias]
            ##Zwischendaten der erwarteten Werte(mean_field)Wird als dreidimensionales Array (etw. Probe) aufbewahrt/r Schicht/Erwarteter Wert der j-ten Variablen)
            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))))

            #Berechnen Sie den ersten Term der Steigung der Verbindung
            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)
            
            #Berechnen Sie den zweiten Term der Steigung der Verbindung
            
            ##Zum Probieren
            new_samples = []    
            for s in range(DeepBolzmannMachine.sample_num):
                sample = []
                ##Initialisieren Sie den Wert der Probe
                for layer in self.layer_structure:
                    accumlator = []
                    for i in range(layer):
                        accumlator.append(np.empty)
                    sample.append(accumlator)
            
                ##Stellen Sie den Anfangswert für die Abtastung ein (zu diesem Zeitpunkt wird die vorherige Abtastung für die zweite und nachfolgende Zeit der Gradientenanstiegsschleife verwendet).
                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]

                ##Schätzen Sie eine Stichprobe (Wert jedes Knotens)
                sigmoid_belief = []
                for layer_index in range(len(self.layer_structure)):
                    for node_index in range(self.layer_structure[layer_index]):
                        ####Berechnen Sie den Sigmoid-Glauben anhand von Nachrichten von allen an diesen Knoten angeschlossenen Knoten
                        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)
            
                        ####Zufallszahlen generieren
                        r = np.random.rand(1)[0]
                    
                        ####Setzen Sie 1, wenn der Sigmoid-Glaube größer als die Zufallszahl ist, 0, wenn er kleiner als die Zufallszahl ist
                        if sigmoid_belief > r :
                            sample[layer_index][node_index] = 1
                        else: 
                            sample[layer_index][node_index] = 0
            
                #Probe hinzufügen
                new_samples.append(sample)
                
            #Verwerfen Sie das Beispiel aus der vorherigen Verlaufsaktualisierung und ersetzen Sie es durch dieses Beispiel
            samples = new_samples
        
            #Annähern Sie den zweiten Gradiententerm aus der Stichprobe
            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)
            
            
            #Berechnen Sie den Gradienten
            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)
            
            #Parameter mit Farbverlauf aktualisieren
            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))

Ausführungsbeispiel

#4-Schicht-Netzwerk mit 5, 4, 3, 2 Knoten in der Reihenfolge von der sichtbaren Schicht
layer_structure = [5,4,3,2]

#Anzahl der Proben, die bei der Berechnung des erwarteten Werts abgetastet werden sollen
sample_num = 100

#Boltzmann-Maschineninstanziierung
dbm = DeepBolzmannMachine(layer_structure)

#Erstellen Sie entsprechende Trainingsdaten
data_for_learning = np.random.rand(100,layer_structure[0])

#Vorlernen
dbm.preTrain(data_for_learning,sample_num)

#Nach dem Lernen
dbm.train(data_for_learning,sample_num)

Ende

Deshalb habe ich es implementiert.

Probabilistisches Deep Learning scheint nicht so allgegenwärtig zu sein wie Deep Learning in deterministischen neuronalen Netzen, sondern persönlich *** "Verstehen, was passiert, einschließlich seiner Unsicherheiten" *** Bayes Ich denke, dass das Verdienst dieser Denkweise groß ist, also hoffe ich, dass dies der De-facto-Standard wird.

Dieses Jahr steht vor der Tür. Gutes Jahresende, alle ...

Recommended Posts