über die Small-World-Experimentdirigiert von Stanley Milgram in den 1960er Jahren. Er erfand ein Experiment, bei dem ein Temporary an einen Freiwilligen in den Vereinigten Staaten geschickt wurde, mit der Anweisung, den Temporary an seinen persönlichen Ansprechpartner weiterzuleiten, der höchstwahrscheinlich eine andere Particular person – die Zielperson – im selben Land kannte. Im Gegenzug würde die Particular person, die den Temporary erhält, aufgefordert, den Temporary erneut weiterzuleiten, bis die Zielperson erreicht ist. Obwohl die meisten Briefe die Zielperson nie erreichten, lag die durchschnittliche Anzahl der Sprünge bei denjenigen, die dies erreichten (Hier ist Überlebensbias im Spiel!), bei etwa 6. Die „sechs Grade der Trennung“ sind zu einem kulturellen Hinweis auf die enge Vernetzung der Gesellschaft geworden.

Ich bin immer noch erstaunt über den Gedanken, dass Menschen mit ~102 Kontakte schaffen es, sich mit einer zufälligen Particular person in einem Netzwerk von ~10 zu verbinden8 Leute, durch ein paar Sprünge.

Wie ist das möglich? Heuristiken.

Nehmen wir an, ich werde gebeten, einen Temporary an eine Zielperson in Finnland zu senden1.

Leider habe ich keine Kontakte in Finnland. Andererseits kenne ich jemanden, der viele Jahre in Schweden gelebt hat. Vielleicht kennt sie Leute in Finnland. Wenn nicht, hat sie wahrscheinlich immer noch Kontakte in Schweden, einem Nachbarland. Sie ist meine beste Wahl, um der Zielperson näher zu kommen. Der Punkt ist: Obwohl ich die Topologie des sozialen Netzwerks über meine persönlichen Kontakte hinaus nicht kenne, kann ich den Temporary mithilfe von Faustregeln in die richtige Richtung weiterleiten.

Hallo, Finnland! Bild von Ilja PanasenkoAn Unsplash.

Wenn wir den Standpunkt der Knoten des Netzwerks (der am Experiment beteiligten Menschen) einnehmen, bestehen ihre verfügbaren Aktionen darin, die Nachricht (den Temporary) an einen ihrer ausgehenden Kanten (persönliche Kontakte) weiterzuleiten. Dieses Drawback der Übermittlung der Nachricht in die richtige Richtung bietet die Möglichkeit, Spaß am maschinellen Lernen zu haben!

Knoten nehmen nicht die gesamte Netzwerktopologie wahr. Wir können eine Umgebung einrichten, die sie dafür belohnt, dass sie die Nachricht auf dem kürzesten bekannten Weg weiterleiten, und sie gleichzeitig dazu ermutigt, suboptimale Kandidatenpfade zu erkunden. Das klingt nach einem großartigen Anwendungsfall für Reinforcement Studying, finden Sie nicht?

Wer daran interessiert ist, den Code auszuführen, kann das Repo erreichen Hier.

Das Drawback

Wir erhalten einen gerichteten Graphen, in dem die Kanten zwischen den Knoten dünn besetzt sind, was bedeutet, dass die durchschnittliche Anzahl der von einem Knoten ausgehenden Kanten deutlich kleiner ist als die Anzahl der Knoten. Darüber hinaus sind mit den Kanten Kosten verbunden. Diese zusätzliche Funktion verallgemeinert den Fall des Small-World-Experiments, bei dem jeder Sprung des Buchstabens mit Kosten von 1 gezählt wurde.

Das Drawback, das wir betrachten werden, besteht darin, einen Reinforcement-Studying-Algorithmus zu entwerfen, der einen Pfad von einem beliebigen Startknoten zu einem beliebigen Zielknoten in einem spärlich besetzten gerichteten Graphen findet, und zwar mit möglichst geringen Kosten, sofern ein solcher Pfad existiert. Für dieses Drawback gibt es deterministische Lösungen. Zum Beispiel, Dijkstras Algorithmus findet den kürzesten Weg von einem Startknoten zu allen anderen Knoten in einem gerichteten Graphen. Dies wird nützlich sein, um die Ergebnisse unseres Reinforcement-Studying-Algorithmus auszuwerten, der nicht garantiert die optimale Lösung findet.

Q-Studying

Q-Studying ist eine Technik des Verstärkungslernens, bei der ein Agent eine Tabelle mit Zustands-Aktionspaaren verwaltet, die mit der erwarteten abgezinsten kumulativen Belohnung verknüpft sind – der Qualitätdaher die Q-Lernen. Durch iterative Experimente wird die Tabelle aktualisiert, bis ein Stoppkriterium erfüllt ist. Nach dem Coaching kann der Agent die Aktion (die Spalte der Q-Matrix) auswählen, die der maximalen Qualität für einen bestimmten Zustand (die Zeile der Q-Matrix) entspricht.

Die Aktualisierungsregel, vorausgesetzt eine Testaktion aJwas zum Übergang vom Zustand führt sich angeben sokayeine Belohnung rund eine beste Schätzung der Qualität des nächsten Zustands sokayIst:

( Q(i, j) leftarrow (1 – alpha) Q(i, j) + alpha left( r + gamma max_{l} Q(okay, l) proper) )

Gleichung 1: Die Aktualisierungsregel für Q-Studying.

In Gleichung 1:

  • α ist die Lernrate, die steuert, wie schnell neue Ergebnisse alte Qualitätsschätzungen löschen.
  • γ ist der Abzinsungsfaktor, der steuert, wie hoch zukünftige Belohnungen im Vergleich zu unmittelbaren Belohnungen ausfallen.
  • Q ist die Qualitätsmatrix. Der Zeilenindex i ist der Index des Ursprungszustands und der Spaltenindex j ist der Index der ausgewählten Aktion.

Kurz gesagt besagt Gleichung 1, dass die Qualität von (Zustand, Aktion) teilweise mit einem neuen Qualitätswert aktualisiert werden sollte, der sich aus der Summe der unmittelbaren Belohnung und der diskontierten Schätzung der maximalen Qualität des nächsten Zustands über mögliche Aktionen ergibt.

Für unsere Problemstellung wäre eine mögliche Formulierung für den Zustand das Paar (aktueller Knoten, Zielknoten) und die Menge der Aktionen wäre die Menge der Knoten. Der Zustandssatz würde dann enthalten N2 Werte, und der Aktionssatz würde enthalten N Werte, wo N ist die Anzahl der Knoten. Da der Graph jedoch dünn besetzt ist, weist ein gegebener Ursprungsknoten nur eine kleine Teilmenge von Knoten als ausgehende Kanten auf. Diese Formulierung würde zu a führen Q-Matrix, in der die große Mehrheit der N3 Einträge werden nie besucht, was unnötig Speicher verbraucht.

Verteilte Agenten

Eine bessere Ressourcennutzung besteht darin, die Agenten zu verteilen. Jeder Knoten kann als Agent betrachtet werden. Der Standing des Agenten ist der Zielknoten, daher auch sein Q-Matrix hat N Reihen und Naus Spalten (die Anzahl der ausgehenden Kanten für diesen bestimmten Knoten). Mit N Agenten beträgt die Gesamtzahl der Matrixeinträge N2Nauswas niedriger ist als N3.

Zusammenfassend:

  • Wir werden trainieren N Agenten, einen für jeden Knoten im Diagramm.
  • Jeder Agent lernt a Q-Dimensionsmatrix (N x Naus). Die Anzahl der ausgehenden Kanten (Naus) kann von Knoten zu Knoten unterschiedlich sein. Für einen dünn zusammenhängenden Graphen gilt: Naus << N.
  • Die Zeilenindizes der Q-Matrix entspricht dem Zustand des Agenten, additionally des Zielknotens.
  • Die Spaltenindizes der Q-Matrix entsprechen der ausgehenden Kante, die von einem Agenten ausgewählt wird, um eine Nachricht an den Zielknoten weiterzuleiten.
  • Q(i, j) stellt die Einschätzung eines Knotens hinsichtlich der Qualität der Weiterleitung der Nachricht an seinen Knoten dar jTh ausgehende Kante, vorausgesetzt, der Zielknoten ist i.
  • Wenn ein Knoten eine Nachricht empfängt, schließt sie den Zielknoten ein. Da der Absender der vorherigen Nachricht nicht erforderlich ist, um die Weiterleitung der nächsten Nachricht zu bestimmen, wird er in letzterer nicht berücksichtigt.

Code

Die Kernklasse, der Knoten, wird benannt QNode.

class QNode:
    def __init__(self, number_of_nodes=0, connectivity_average=0, connectivity_std_dev=0, Q_arr=None, neighbor_nodes=None,
                 state_dict=None):
        if state_dict shouldn't be None:
            self.Q = state_dict('Q')
            self.number_of_nodes = state_dict('number_of_nodes')
            self.neighbor_nodes = state_dict('neighbor_nodes')
        else:  # state_dict is None
            if Q_arr is None:
                self.number_of_nodes = number_of_nodes
                number_of_neighbors = connectivity_average + connectivity_std_dev * np.random.randn()
                number_of_neighbors = spherical(number_of_neighbors)
                number_of_neighbors = max(number_of_neighbors, 2)  # A minimum of two out-connections
                number_of_neighbors = min(number_of_neighbors, self.number_of_nodes)  # No more than N connections
                self.neighbor_nodes = random.pattern(vary(self.number_of_nodes), number_of_neighbors)  # (1, 4, 5, ...)
                self.Q = np.zeros((self.number_of_nodes, number_of_neighbors))  # Optimistic initialization: all rewards can be destructive
                # q = self.Q(state, motion): state = goal node; motion = chosen neighbor node (transformed to column index) to route the message to

            else:  # state_dict is None and Q_arr shouldn't be None
                self.Q = Q_arr
                self.number_of_nodes = self.Q.form(0)
                self.neighbor_nodes = neighbor_nodes

Die Klasse QNode hat drei Felder: die Anzahl der Knoten im Diagramm, die Liste der ausgehenden Kanten und die Q-Matrix. Der Q-matrix wird mit Nullen initialisiert. Die von der Umwelt erhaltenen Belohnungen sind das Detrimental der Randkosten. Daher sind alle Qualitätswerte negativ. Aus diesem Grund ist die Initialisierung mit Nullen eine optimistische Initialisierung.

Wenn eine Nachricht eine erreicht QNode Objekt wählt es eine seiner ausgehenden Kanten durch das aus Epsilon-gierig Algorithmus. Wenn ε klein ist, wählt der Epsilon-Grasping-Algorithmus meistens die ausgehende Kante mit der höchsten aus Q-Wert. Hin und wieder wird eine ausgehende Kante zufällig ausgewählt:

def epsilon_greedy(self, target_node, epsilon):
        rdm_nbr = random.random()
        if rdm_nbr < epsilon:  # Random alternative
            random_choice = random.alternative(self.neighbor_nodes)
            return random_choice
        else:  # Grasping alternative
            neighbor_columns = np.the place(self.Q(target_node, :) == self.Q(target_node, :).max())(0)  # (1, 4, 5)
            neighbor_column = random.alternative(neighbor_columns)
            neighbor_node = self.neighbor_node(neighbor_column)
            return neighbor_node

Die andere Klasse ist der Graph, genannt QGraph.

class QGraph:
    def __init__(self, number_of_nodes=10, connectivity_average=3, connectivity_std_dev=0, cost_range=(0.0, 1.0),
                 maximum_hops=100, maximum_hops_penalty=1.0):
        self.number_of_nodes = number_of_nodes
        self.connectivity_average = connectivity_average
        self.connectivity_std_dev = connectivity_std_dev
        self.cost_range = cost_range
        self.maximum_hops = maximum_hops
        self.maximum_hops_penalty = maximum_hops_penalty
        self.QNodes = ()
        for node in vary(self.number_of_nodes):
            self.QNodes.append(QNode(self.number_of_nodes, self.connectivity_average, self.connectivity_std_dev))

        self.cost_arr = cost_range(0) + (cost_range(1) - cost_range(0)) * np.random.random((self.number_of_nodes, self.number_of_nodes))

Seine Hauptfelder sind die Liste der Knoten und das Array der Kantenkosten. Beachten Sie, dass die tatsächlichen Kanten Teil davon sind QNode Klasse, als Liste ausgehender Knoten.

Wenn wir einen Pfad von einem Startknoten zu einem Zielknoten generieren möchten, rufen wir auf QGraph.trajectory() Methode, die die aufruft QNode.epsilon_greedy() Verfahren:

    def trajectory(self, start_node, target_node, epsilon):
        visited_nodes = (start_node)
        prices = ()
        if start_node == target_node:
            return visited_nodes, prices
        current_node = start_node
        whereas len(visited_nodes) < self.maximum_hops + 1:
            next_node = self.QNodes(current_node).epsilon_greedy(target_node, epsilon)
            price = float(self.cost_arr(current_node, next_node))
            visited_nodes.append(next_node)
            prices.append(price)
            current_node = next_node
            if current_node == target_node:
                return visited_nodes, prices
        # We reached the utmost variety of hops
        return visited_nodes, prices

Der trajectory() Die Methode gibt die Liste der besuchten Knoten entlang des Pfads und die Liste der Kosten zurück, die den verwendeten Kanten zugeordnet sind.

Das letzte fehlende Stück ist die Aktualisierungsregel von Gleichung 1, die von implementiert wird QGraph.update_Q() Verfahren:

def update_Q(self, start_node, neighbor_node, alpha, gamma, target_node):
   price = self.cost_arr(start_node, neighbor_node)
   reward = -cost
   # Q_orig(goal, dest) <- (1 - alpha) Q_orig(goal, dest) + alpha * ( r + gamma * max_neigh' Q_dest(goal, neigh') )
   Q_orig_target_dest = self.QNodes(start_node).Q(target_node, self.QNodes(start_node).neighbor_column(neighbor_node))
   max_neigh_Q_dest_target_neigh = np.max(self.QNodes(neighbor_node).Q(target_node, :))
   updated_Q = (1 - alpha) * Q_orig_target_dest + alpha * (reward + gamma * max_neigh_Q_dest_target_neigh)
   self.QNodes(start_node).Q(target_node, self.QNodes(start_node).neighbor_column(neighbor_node)) = updated_Q

Um unsere Agenten zu schulen, durchlaufen wir iterativ die Paare von (start_node, target_node) mit einer inneren Schleife über die Nachbarknoten von start_nodeund wir rufen an update_Q().

Experimente und Ergebnisse

Beginnen wir mit einem einfachen Diagramm mit 12 Knoten und gerichteten gewichteten Kanten.

Abbildung 1: Ein Diagramm mit 12 Knoten. Bild vom Autor.

Aus Abbildung 1 können wir ersehen, dass der einzige eingehende Knoten für Knoten-1 Knoten-7 ist und der einzige eingehende Knoten für Knoten-7 Knoten-1 ist. Daher kann kein anderer Knoten außer diesen beiden Knoten-1 und Knoten-7 erreichen. Wenn ein anderer Knoten damit beauftragt wird, eine Nachricht an Knoten-1 oder Knoten-7 zu senden, springt die Nachricht im Diagramm herum, bis die maximale Anzahl von Hops erreicht ist. Wir gehen davon aus, dass es große destructive Auswirkungen geben wird Q-Werte in diesen Fällen.

Wenn wir unseren Graphen trainierenerhalten wir Statistiken über die Kosten und die Anzahl der Sprünge als Funktion der Epoche, wie in Abbildung 2:

Abbildung 2: Typische Entwicklung der Kosten und der Pfadlängen (Anzahl der Hops) als Funktion der Epoche. Bild vom Autor.

Nach dem Coaching ist dies die Q-Matrix für Knoten-4:

Tabelle 1: Q-Matrix für Knoten-4. Bild vom Autor.

Die Flugbahn von Knoten 4 zu beispielsweise Knoten 11 kann durch Aufrufen von ermittelt werden trajectory() Methode, Einstellung epsilon=0 für die gierige deterministische Lösung: (4, 3, 5, 11) für undiskontierte Gesamtkosten von 0,9 + 0,9 + 0,3 = 2,1. Der Dijkstra-Algorithmus gibt denselben Pfad zurück.

In einigen seltenen Fällen wurde der optimale Pfad nicht gefunden. Um beispielsweise von Knoten 6 zu Knoten 9 zu gelangen, gab eine bestimmte Instanz des trainierten Q-Graphen (6, 11, 0, 4, 10, 2, 9) für nicht diskontierte Gesamtkosten von 3,5 zurück, während der Dijkstra-Algorithmus (6, 0, 4, 10, 2, 9) für nicht diskontierte Gesamtkosten von 3,4 zurückgab. Wie bereits erwähnt, wird dies von einem iterativen Algorithmus erwartet

Abschluss

Wir haben das Small-World-Experiment als Drawback formuliert, den Pfad mit minimalen Kosten zwischen einem beliebigen Knotenpaar in einem dünn besetzten gerichteten Graphen mit gewichteten Kanten zu finden. Wir haben die Knoten als Q-Studying-Agenten implementiert, die durch die Aktualisierungsregel lernen, bei gegebenem Zielknoten die kostengünstigste Aktion durchzuführen.

Mit einem einfachen Diagramm haben wir gezeigt, dass das Coaching zu einer Lösung nahe der optimalen Lösung führte.

Vielen Dank für Ihre Zeit und zögern Sie nicht experimentieren Sie mit dem Code. Wenn Sie Ideen für unterhaltsame Anwendungen für Q-Studying haben, lassen Sie es mich bitte wissen!


1 OK, ich gehe über das ursprüngliche Small-World-Experiment hinaus, das Small-Nation-Experiment heißen sollte.

Referenzen

Reinforcement Studying, Richard S. Sutton, Andrew G. Barto, MIT Press, 1998

Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert