Photo by DeepMind on Unsplash
Die Matrixmultiplikation ist eine grundlegende Operation, die in vielen Systemen verwendet wird, von neuronalen Netzen bis hin zu wissenschaftlichen Rechenroutinen. Das Finden effizienter und nachweislich korrekter Algorithmen für die Matrixmultiplikation kann einen großen Einfluss darauf haben, die Berechnung schneller und effizienter zu machen, ist jedoch eine sehr herausfordernde Aufgabe. Der Raum möglicher Algorithmen ist enorm, und herkömmliche Methoden zur Entdeckung von Algorithmen, wie z. B. von Menschen entworfene Heuristiken oder kombinatorische Suche, sind oft suboptimal.
DeepMindDie kürzlich vorgeschlagene KI-basierte Lösung für die automatisierte Suche geht weit über die menschliche Intuition hinaus. Die Lösung besteht aus einem Deep-Reinforcement-Learning-Agenten namens AlphaTensor, der darauf aufbaut AlphaZero. Dieser Agent ist darauf trainiert, ein Einzelspieler-Spiel, TensorGame, zu spielen, bei dem das Ziel darin besteht, recheneffiziente Algorithmen für die Matrixmultiplikation zu entdecken.
AlphaTensor ist besonders gut im Umgang mit großen Matrizen, indem große Matrixmultiplikationen in kleinere Multiplikationen zerlegt werden. Darüber hinaus kann AlphaTensor verwendet werden, um eine hochmoderne Leistung für die Matrixmultiplikation zu erreichen, sobald sie auf einem bestimmten Hardwaregerät feinabgestimmt wurde.
AlphaTensor hat ein großes Potenzial zur Beschleunigung des Deep-Learning-Computing. Beim Deep Learning lassen sich viele zeitaufwändige Operationen auf Matrixmultiplikationen abbilden. Durch die Verwendung von AlphaTensor zur Optimierung dieser Operationen kann die Gesamtleistung von Deep-Learning-Modellen erheblich verbessert werden.
Kürzlich hat OpenAlphaTensor, die erste Open-Source-Implementierung von AlphaTensor, veröffentlicht, das die Rechenleistung von Deep-Learning-Modellen revolutionieren könnte.
Matrixmultiplikationstensor
Für Nicht-Experten in der Matrixmultiplikationsoptimierung ist es möglicherweise nicht einfach zu verstehen, wie eine Operation wie die Matrixmultiplikation in einem dreidimensionalen Tensor abgebildet werden kann. Ich werde versuchen, es in einfachen Worten und mit Beispielen zu erklären.
Betrachten wir das Produkt C = A*B, wobei der Einfachheit halber sowohl A als auch B quadratische Matrizen der Größe N sind. Die Multiplikationsoperation kann in einem 3D-Tensor der Form (N^2, N^2, N^2) abgebildet werden. Die erste Tensordimension repräsentiert die abgeflachte Matrix A, die zweite Dimension die abgeflachte Matrix B und die dritte Dimension die abgeflachte Matrix C.
Der Tensor hat nur binäre Werte (entweder 1 oder 0) für jeden Eintrag. Beachten Sie, dass der Tensor die Multiplikationsoperation darstellt und daher unabhängig von den Werten der Matrizen A und B ist.
Jeder Eintrag des Tensors entspricht dem Koeffizienten der Operation. Um beispielsweise C[1,1] zu berechnen, ist es notwendig, sowohl A[1,1] als auch B[1,1] zu multiplizieren. Daher hat der Tensor-Eintrag [0,0,0], der A[1,1], B[1,1] und C[1,1] entspricht, den Wert 1. Um dagegen C[1,1 ,2,1], A[1] wird nicht benötigt. Somit enthält die Tensorzeile T[N+0, :, XNUMX] nur Nullen.
Das Bild unten zeigt ein Beispiel eines Tensors für N=2.
Bild von DeepMind Krepppapier veröffentlicht Natur
Wie in (b) und (c) in der obigen Abbildung gezeigt, ist es möglich, einen Algorithmus zur Berechnung des Produkts unter Verwendung einer Zerlegung des 3D-Tensors zu implementieren. Genauer gesagt kann der nachstehende Algorithmus verwendet werden, um eine Tensorzerlegung (die Matrizen U, V, W) in einen Matrixmultiplikationsalgorithmus umzuwandeln.
Parametrisierter Metaalgorithmus zur Berechnung des in DeepMind eingeführten Matrixprodukts C=AB Krepppapier
Das TensorGame
Das Problem, effiziente Algorithmen für die Matrixmultiplikation zu finden, ist äußerst herausfordernd, da die Anzahl der zu berücksichtigenden möglichen Algorithmen viel größer ist als die Anzahl der Atome im Universum, selbst für kleine Fälle der Matrixmultiplikation.
DeepMind hat dieses Problem in ein Einzelspieler-Spiel umgewandelt und es TensorGame genannt. In diesem Spiel wählt der Spieler, wie er verschiedene Einträge von Matrizen kombiniert, um sie zu multiplizieren. Eine Punktzahl wird basierend auf der Anzahl der Operationen zugewiesen, die erforderlich sind, um das richtige Multiplikationsergebnis zu erzielen. Das Spiel endet, wenn der Nulltensor erreicht ist oder wenn die maximale Anzahl an Zügen gemacht wurde. Die endgültige Faktorisierung wird basierend auf einer Schätzung des Restrangs und bestimmten Optimierungskriterien wie asymptotischer Zeitkomplexität oder praktischer Laufzeit bewertet.
Die Anfangsposition im TensorGame entspricht dem Matrixmultiplikationstensor, der auf einer zufälligen Basis ausgedrückt wird.
In jedem Schritt t des Spiels schreibt der Spieler drei Vektoren auf
, die die Rang-1-Tensoren angibt . Der Spielstand wird aktualisiert, indem die vom Spieler ausgewählten Vektoren subtrahiert werden:
woher
ist der Matrixmultiplikationstensor.Wenn das Spiel in p Schritten endet, bedeutet dies, dass der Matrixmultiplikationstensor
kann in p Rang-1-Tensoren zerlegt werden , dh es hat mindestens Rang p.Das TensorGame kann dann als Rangzerlegungsalgorithmus interpretiert werden und AlphaTensor kann als Algorithmus zur Schätzung des Rangs des Tensors angesehen werden.
AlphaTensor-Architektur
Bisher haben wir das TensorGame kennengelernt und geklärt, wie seine Lösung als Matrixmultiplikationsalgorithmus angesehen werden kann. Lassen Sie uns nun die Hauptkonzepte von AlphaTensor untersuchen, dem Algorithmus, der für das Spiel verwendet wird.
Die AlphaTensor-Architektur ist im Grunde eine Encoder-Decoder-Transformer-Architektur, bei der:
- Der Encoder nimmt den Spielstatus als Eingabe , die n vorherigen Aktionen des Modells (normalerweise n = 7) und den Zeitindex t der aktuellen Aktion. Informationen werden in einem Tensor mit Form (n+1, N^2, N^2, N^2) gestapelt. Dieser Tensor wird dann umgeformt und (unter Verwendung von drei linearen Schichten) in einen Formtensor (N^2, N^2, c) transformiert, wobei c die innere Dimension des Modells ist.
- Der Decoder generiert die n_steps-Aktionen aus dem eingebetteten Vektor, der vom Encoder auf autoregressive Weise angegeben wird. Jede Aktion entspricht einem Token der Tripletts stellt eines der Tripletts dar, die den Spieltensor zerlegen (dh seinen Rang verringern)
Das Modell wird durch abwechselnde Backpropagation und Model Acting trainiert. Model Acting wird verwendet, um Daten zu generieren, die dann zum Trainieren des Modells verwendet werden. In der Praxis wird das Modell mit einer Mischung aus synthetisch generierten Daten und Daten trainiert, die das Modell während des Handelns generiert. Der Handlungsschritt wird durchgeführt, indem ein 3D-Tensor, der einer Matrixoperation entspricht, genommen und n_actors-Spiele darauf gespielt werden. Jeder Akteur spielt ein Spiel entweder auf der Standardbasis oder auf einer alternativen Basis (der Basiswechsel wird mit einer bestimmten Wahrscheinlichkeit angewendet). Die Ergebnisse werden dann gesammelt und können im Trainingsschritt mit den synthetischen Daten verwendet werden.
Der Handlungsschritt basiert auf der Monte-Carlo-Baumsuche (MCTS) von AlphaZero, die zur Unterstützung großer Aktionsräume modifiziert wurde. Kurz gesagt, vor der Auswahl der Aktion werden n_sims-Pfade anhand der Modellausgabe mit einer maximalen zukünftigen Erkundung von 5 Schritten untersucht. Die vom Modell generierten Wahrscheinlichkeiten werden dann unter Berücksichtigung der generierten Pfade angepasst. Dann wird die Aktion mit den vielversprechendsten Zukunftspfaden ausgewählt, um das Spiel fortzusetzen.
Beim Trainieren des Modells ist die Belohnung eigentlich eine negative Belohnung (Strafe). Sein absoluter Wert steigt mit jedem zusätzlichen Schritt, der zur Lösung des Spiels erforderlich ist. Wenn das Modell m Schritte macht, um ein TensorGame zu lösen, ist die mit dem Spiel verbundene Belohnung r=-m. Wenn das Modell das TensorGame nicht in max_rank-Schritten lösen kann, wird die Belohnung berechnet, indem der Rang des verbleibenden Tensors geschätzt wird. Der Rang wird als Summe der Ränge der Matrizen geschätzt, die den Tensor bilden. Die Schätzung ist eine Obergrenze für den wahren Rang des Tensors.
Bei der Feinabstimmung des Modells sollte die Strafbelohnung im Endzustand auch die Latenz des vom Modell erzeugten Algorithmus berücksichtigen. Die Belohnungsformel lautet rt'=rt+λbt, wobei rt das zuvor beschriebene Belohnungsschema ist, bt die Benchmark-Belohnung ist (nur im Endzustand ungleich Null) und λ ist ein benutzerdefinierter Koeffizient.
Beschleunigungen (%) der von AlphaTensor entdeckten Algorithmen, die auf eine GPU und eine TPU zugeschnitten sind, extrahiert aus dem Paper von DeepMind. Beschleunigungen werden relativ zur standardmäßigen (z. B. cuBLAS für die GPU) Matrixmultiplikation auf derselben Hardware gemessen und mit der verglichen Strassen-Quadrat-Algorithmus. Quelle: DeepMind.
Ich habe vor kurzem veröffentlicht OpenAlphaTensor, die erste Open-Source-Implementierung von AlphaTensor. In diesem Abschnitt gehe ich durch die Implementierung. Wie wir bereits besprochen haben, ist die AlphaTensor-Architektur ziemlich unkompliziert und basiert auf einem Standardtransformator mit einer Encoder-Decoder-Architektur. Die interessantesten Komponenten von AlphaTensor sind die erste Ebene im Encoder-Teil und die Art und Weise, wie die Aktionen abgetastet werden.
Beginnen wir mit der ersten Codierungsschicht.
# x.size = (N, T, S, S, S)
# scalars.size = (N, s)
batch_size = x.shape[0]
S = x.shape[-1]
T = x.shape[1]
x1 = x.permute(0, 2, 3, 4, 1).reshape(batch_size, S, S, S * T)
x2 = x.permute(0, 4, 2, 3, 1).reshape(batch_size, S, S, S * T)
x3 = x.permute(0, 3, 4, 2, 1).reshape(batch_size, S, S, S * T)
input_list = [x1, x2, x3]
for i in range(3): temp = self.linears_1[i](scalars).reshape(batch_size, S, S, 1) input_list[i] = torch.cat([input_list[i], temp], dim=-1) input_list[i] = self.linears_2[i](input_list[i])
x1, x2, x3 = input_list
Im obigen Ausschnitt zeigen wir, wie der Eingabetensor in drei Tensoren zerlegt wird, die dann als Abfrage-, Schlüssel- und Werteingaben der Transformatorschicht verwendet werden.
- Über die drei Tensordimensionen hinweg, die die abgeflachten Matrizen (A, B, C) darstellen, wird der Eingabetensor entlang jeder Dimension zusammen mit der Dimension, die die vorherigen Aktionen darstellt, abgeflacht. Auf diese Weise ist die ausgewählte Dimension in jeder abgeflachten Kopie des Eingabetensors eine Aggregation der letzten T-1-Werte und des tatsächlichen Werts für alle S-Werte der ausgewählten Dimension, wobei S=N^2. Philosophisch ist es so, als ob wir uns für jede Dimension auf das konzentrieren, was in den vorherigen Aktionen in dieser Dimension passiert ist.
- Die Skalare werden in drei verschiedenen Räumen der Dimension S ^ 2 abgebildet und dann umgeformt, um mit den am vorherigen Punkt erhaltenen Tensoren verkettet zu werden. Konzeptionell werden die Skalare auf einen Einbettungsraum der Dimension S^2 abgebildet, und dann werden die eingebetteten Informationen in S-Vektoren zerlegt und zusammengestapelt, ähnlich wie es mit Text geschieht, wenn er in Tokens umgewandelt wird.
- Skalar-Token werden mit dem umstrukturierten Eingabetensor verkettet und dann als Eingabe an eine lineare Schicht gegeben, um die Fokusinformationen aus Skalaren + Kanalverlauf in der internen Dimension des Modells abzubilden.
Diese drei Schritte können als eine Möglichkeit interpretiert werden, dem Modell sowohl Informationen über die Skalare (wie im TensorGame-Zeitschritt) als auch den Fokus auf die vorherigen Aktionen für jeden Kanal zu geben.
In Bezug auf die Art und Weise, wie die Aktionen erzeugt werden, ist es interessant festzustellen, dass AlphaTensor als Ausgabe das Triplett u, v, w generiert, das darauf abzielt, den Tensorrang zu reduzieren. Die drei Vektoren haben die Größe S und da sie verkettet sind, muss das Modell einen Vektor der Größe 3*S erzeugen. AlphaTensor wird mit einem RL-Algorithmus trainiert, daher müssen alle möglichen Aktionen in Form von Wahrscheinlichkeiten in einem aufgezählten Raum ausgedrückt werden, dh das Modell erzeugt eine Wahrscheinlichkeit über die verschiedenen Aktionen. Das bedeutet, dass jeder Vektor im 3S-Raum einer anderen Aktion zugeordnet werden sollte. Daraus ergibt sich ein Aktionsraum der Größe |F|^(3S), wobei |F| ist die Anzahl der verschiedenen Werte, die das Element von u, v, w annehmen kann. Normalerweise sind die Werte auf (-2, -1, 0, 1, 2) beschränkt, was zu einer Kardinalität von 5 Elementen führt.
Hier kommt eine große Herausforderung: Um die Aktionswahrscheinlichkeiten für ein Matrixprodukt von Matrizen der Größe 5 zu generieren, bräuchten wir einen Speicher von 5^75 * 4 Bytes, was ~10^44 GB Speicher bedeuten würde. Natürlich können wir einen so großen Aktionsraum nicht verwalten.
Wie lösen wir das Problem? Um den Speicherbedarf der Aktionswahrscheinlichkeiten zu reduzieren, können wir die Tripletts in kleinere Stücke aufteilen, sie „tokenisieren“ und die Stücke als generierte Token in der Transformer-Architektur behandeln, dh die Token werden als Eingabe an den Decoder in einer Autoregression gegeben Weg. Im obigen Beispiel können wir die Triplets in 15 Chunks aufteilen, wodurch der Speicherverbrauch auf 15 * 5^(75/15) * 4, dh 187.5 KB, reduziert wird.
def _eval_forward(self, e: torch.Tensor): bs = e.shape[0] future_g = ( torch.zeros((bs, self.n_samples, self.n_steps)).long().to(e.device) ) ps = torch.ones((bs, self.n_samples)).to(e.device) e = e.unsqueeze(1).repeat(1, self.n_samples, 1, 1) future_g = future_g.view(-1, self.n_steps) ps = ps.view(-1) e = e.view(-1, e.shape[-2], e.shape[-1]) for i in range(self.n_steps): o_s, z_s = self.core(future_g[:, : i + 1], e) future_g[:, i], p_i = sample_from_logits(o_s[:, i]) ps *= p_i future_g = future_g.view(bs, self.n_samples, self.n_steps) ps = ps.view(bs, self.n_samples) return ( future_g, ps, z_s[:, 0].view(bs, self.n_samples, *z_s.shape[2:]).mean(1), )
Oben zeigen wir das Code-Snippet zum Generieren der vollständigen Aktion. Im Code enthält self.core die Decoderschicht und der Tensor e repräsentiert die Ausgabe der Encoderschicht. Null kann als betrachtet werden Token in NLP-Modellen und die n_steps-Aktionen, die die n_steps-Blöcke darstellen, werden auf progressive Weise generiert.
Das Modell gibt drei Größen zurück:
- Die generierten Aktionen
- Die der vollständigen Aktion zugeordnete Wahrscheinlichkeit
- Die zum Generieren der ersten Aktion (des ersten Blocks) erzeugten Logits, die zum Berechnen des Modellwerts verwendet werden.
Es lohnt sich, ein paar Worte zum Parameter n_samples zu verlieren. Der Parameter wird für den Schritt des Handelns verwendet und ermöglicht es dem Modell, verschiedene Versionen der Tripletts zu generieren, die dann zum Erkunden des Aktionsraums im Monte-Carlo-Baumsuchalgorithmus verwendet werden, der im Prozess des Handelns verwendet wird. Die n_samples unterschiedlichen Aktionen werden gemäß der vom Modell generierten Richtlinie abgetastet.
Handelnder Schritt
Der schwierigste Teil des gesamten Algorithmus ist wahrscheinlich der Handlungsschritt, der zum Lösen des TensorGames verwendet wird. Der Algorithmus wird im AlphaTensor-Artikel nicht ausführlich erklärt, da er auf mehreren früheren Artikeln von DeepMind basiert, die nur zitiert und als bekannt angegeben werden. Hier rekonstruiere ich alle fehlenden Teile und erkläre Schritt für Schritt unsere Implementierung.
Wir können die Handlungsschritte in drei verschiedene Komponenten gliedern:
- Die Monte-Carlo-Baumsuche
- Die Spielsimulation
- Die verbesserte Policy-Berechnung
Lassen Sie uns sie einzeln analysieren.
Monte-Carlo-Baumsuche (MCTS)
Monte Carlo Tree Search (MCTS) ist eine weit verbreitete Technik der künstlichen Intelligenz zum Spielen, insbesondere in Brettspielen und Videospielen. Der Algorithmus erstellt einen Spielbaum, der potenzielle Züge und Ergebnisse simuliert und zufällige Stichproben verwendet, um die erwartete Belohnung für jeden Zug zu bewerten. Der Algorithmus wählt dann iterativ den Zug mit der höchsten erwarteten Belohnung aus und simuliert die Ergebnisse, bis er einen Endzustand oder eine bestimmte Stoppbedingung erreicht. Die Simulationen werden verwendet, um die Gewinnwahrscheinlichkeit für jeden Zug abzuschätzen und den Entscheidungsprozess zu leiten. MCTS hat sich in komplexen Spielen als effektiv erwiesen, in denen die Anzahl möglicher Züge und Ergebnisse groß ist, und es wurde in erfolgreichen KI-Spielsystemen wie AlphaGo verwendet.
In AlphaTensor wird eine modifizierte Version des ursprünglichen MCTS verwendet. Anstatt die Aktion zufällig aus dem gesamten Aktionsraum auszuwählen, wird die Aktion insbesondere aus einer Teilmenge ausgewählt, die direkt durch das Modell (durch die zuvor präsentierten n_samples) erzeugt wird. Die Korrektur des Richtlinien-Upgrades wird dann im Berechnungsschritt Verbesserte Richtlinie angewendet.
In unserer Implementierung haben wir uns entschieden, alle Informationen über den Monte-Carlo-Baum in einem Wörterbuch zu speichern, das als Schlüssel die Hash-Version des TensorGame-Zustands und als Werte die mit dem Zustand selbst verbundenen Informationen enthält. Jeder Monte-Carlo-Schritt beginnt an einem Knoten und simuliert n_sim-Minispiele, die die Zukunft mit einem Horizont von 5 Zügen erkunden. Wenn der Knoten bereits in früheren Simulationen erkundet wurde, wird n_sim unter Berücksichtigung der Anzahl früherer Erforschungen angepasst. Für jeden Knoten wird die Anzahl der Besuche im Tensor N_s_a gespeichert, da dieser Tensor die Anzahl der Besuche pro untergeordneter Aktion des Knotens enthält (unter den vom Modell abgetasteten).
def monte_carlo_tree_search( model: torch.nn.Module, state: torch.Tensor, n_sim: int, t_time: int, n_steps: int, game_tree: Dict, state_dict: Dict,
): """Runs the monte carlo tree search algorithm. Args: model (torch.nn.Module): The model to use for the simulation. state (torch.Tensor): The initial state. n_sim (int): The number of simulations to run. t_time (int): The current time step. n_steps (int): The maximum number of steps to simulate. game_tree (Dict): The game tree. state_dict (Dict): The dictionary containing the states. """ state_hash = to_hash(extract_present_state(state)) if state_hash in state_dict: with torch.no_grad(): N_s_a = state_dict[state_hash][3] n_sim -= int(N_s_a.sum()) n_sim = max(n_sim, 0) for _ in range(n_sim): simulate_game(model, state, t_time, n_steps, game_tree, state_dict) # return next state possible_states_dict, _, repetitions, N_s_a, q_values, _ = state_dict[ state_hash ] possible_states = _recompose_possible_states(possible_states_dict) next_state_idx = select_future_state( possible_states, q_values, N_s_a, repetitions, return_idx=True ) next_state = possible_states[next_state_idx] return next_state
Der obige Code zeigt unsere Implementierung des Algorithmus. Aus Gründen der Code-Einfachheit wird die Richtlinienkorrektur in der simulation_game-Funktion durchgeführt.
Simulations Spiele
Die Funktion „simulate_game“ ist für die Untersuchung des Baums verantwortlich, der aus Knoten besteht, die einen bestimmten Zustand des TensorGames darstellen. Es führt das Modell auch immer dann aus, wenn ein Blattknoten angetroffen wird, und speichert alle Knoteninformationen im Wörterbuch state_dict. Werfen wir einen tiefen Blick auf die Implementierung:
@torch.no_grad()
def simulate_game( model, state: torch.Tensor, t_time: int, max_steps: int, game_tree: Dict, states_dict: Dict, horizon: int = 5,
): """Simulates a game from a given state. Args: model: The model to use for the simulation. state (torch.Tensor): The initial state. t_time (int): The current time step. max_steps (int): The maximum number of steps to simulate. game_tree (Dict): The game tree. states_dict (Dict): The states dictionary. horizon (int): The horizon to use for the simulation. """ idx = t_time max_steps = min(max_steps, t_time + horizon) state_hash = to_hash(extract_present_state(state)) trajectory = [] # selection while state_hash in game_tree: ( possible_states_dict, old_idx_to_new_idx, repetition_map, N_s_a, q_values, actions, ) = states_dict[state_hash] possible_states = _recompose_possible_states(possible_states_dict) state_idx = select_future_state( possible_states, q_values, N_s_a, repetition_map, return_idx=True ) trajectory.append((state_hash, state_idx)) # state_hash, action_idx future_state = extract_present_state(possible_states[state_idx]) state = possible_states[state_idx] state_hash = to_hash(future_state) idx += 1 # expansion if idx = max_steps: trajectory.append((state_hash, None)) if not game_is_finished(extract_present_state(state)): state = state.to(model.device) scalars = get_scalars(state, idx).to(state.device) actions, probs, q_values = model(state, scalars) ( possible_states, cloned_idx_to_idx, repetitions, not_dupl_indexes, ) = extract_children_states_from_actions( state, actions, ) not_dupl_actions = actions[:, not_dupl_indexes].to("cpu") not_dupl_q_values = torch.zeros(not_dupl_actions.shape[:-1]).to( "cpu" ) N_s_a = torch.zeros_like(not_dupl_q_values).to("cpu") present_state = extract_present_state(state) states_dict[to_hash(present_state)] = ( _reduce_memory_consumption_before_storing(possible_states), cloned_idx_to_idx, repetitions, N_s_a, not_dupl_q_values, not_dupl_actions, ) game_tree[to_hash(present_state)] = [ to_hash(extract_present_state(fut_state)) for fut_state in possible_states ] leaf_q_value = q_values else: leaf_q_value = -int(torch.linalg.matrix_rank(state).sum()) # backup backward_pass(trajectory, states_dict, leaf_q_value=leaf_q_value)
Jede Simulation ist in drei Teile unterteilt:
- Auswahl
- Erweiterung
- Sicherungskopie
Im Auswahlteil wird die Simulation auf den bereits generierten Baumknoten ausgeführt, und der folgende Knoten wird mit der folgenden Funktion ausgewählt:
def select_future_state( possible_states: List[torch.Tensor], q_values: torch.Tensor, N_s_a: torch.Tensor, repetitions: Dict[int, list], c_1: float = 1.25, c_2: float = 19652, return_idx: bool = False,
) -> torch.Tensor: """Select the future state maximizing the upper confidence bound."""
# q_values (1, K, 1) pi = torch.tensor( [ len(repetitions[i]) for i in range(len(possible_states)) if i in repetitions ] ).to(q_values.device) ucb = q_values.reshape(-1) + pi * torch.sqrt( torch.sum(N_s_a) / (1 + N_s_a) ) * (c_1 + torch.log((torch.sum(N_s_a) + c_2 + 1) / c_2)) if return_idx: return ucb.argmax() return possible_states[ucb.argmax()]
In der Praxis ist die Aktion zur Maximierung der ucb-Funktion:
für den gegebenen Zustand ausgewählt wird. Hier stellt Q die vom Modell generierten Q-Werte dar, und π stellt die Zufallsverteilung über die Aktionen dar, die unter Verwendung der Modellrichtlinie abgetastet wurden. N(s, a) repräsentiert die Anzahl der Besuche des Knotens zur Aktion a von Knoten s.
Sobald die Auswahlphase einen Blattknoten erreicht, wenn die Simulation keine Endbedingung erreicht hat (entweder in Bezug auf maximale Exploration, d. h. Zukunftshorizont oder Spielende), wird das Modell dann zum Auswählen von n_Beispielen alternativer Knoten verwendet (sie werden Blatt sein Knoten in der nachfolgenden Iteration). Dies wird als Expansionsphase bezeichnet, da dem Baum neue Knoten hinzugefügt werden. Dann wird in der aktuellen Simulation kein weiterer Knoten untersucht, sondern das Blatt q_value wird an den folgenden Simulationsschritt gesendet: das Backup.
Backup ist die letzte Phase jeder Simulation. Wenn der Blattknoten während der Sicherung ein Endzustand war, wird die endgültige Belohnung berechnet; andernfalls wird der Blatt-q-Wert als geschätzte Belohnung verwendet. Dann wird die Belohnung auf der Simulationstrajektorie zurück übertragen, wobei sowohl die Zustände q_values als auch der Besuchszähler N(s, a) aktualisiert werden. Im folgenden Snippet zeigen wir den Code für die Reward-Back-Propagation.
def backward_pass(trajectory, states_dict, leaf_q_value: torch.Tensor): """Backward pass of the montecarlo algorithm"""
reward = 0 for idx, (state, action_idx) in enumerate(reversed(trajectory)): if action_idx is None: # leaf node reward += leaf_q_value else: ( _, old_idx_to_new_idx, _, N_s_a, q_values, _, ) = states_dict[state] if isinstance(reward, torch.Tensor): reward = reward.to(q_values.device) action_idx = int(action_idx) if action_idx in old_idx_to_new_idx: not_dupl_index = old_idx_to_new_idx[int(action_idx)] else: not_dupl_index = action_idx reward -= 1 q_values[:, not_dupl_index] = ( N_s_a[:, not_dupl_index] * q_values[:, not_dupl_index] + reward ) / (N_s_a[:, not_dupl_index] + 1) N_s_a[:, not_dupl_index] += 1
Verbesserte Richtlinienberechnung
Sobald alle Simulationen ausgeführt wurden und das MCTS eine interessante Momentaufnahme der nahen Zukunft bietet, ist es an der Zeit, die mit den vorhergesagten Knoten verknüpfte Richtlinie zu aktualisieren und sie zurückzugeben, damit sie während des Trainings verwendet werden können. Die verbesserte Richtlinie nach der in beschriebenen Methode Hubertet al, wird zur Verwaltung großer Aktionsräume verwendet. Tatsächlich ist es für einen kleinen Suchraum während MCTS möglich, eine Aktion zufällig aus dem Aktionsraum abzutasten und ihre Auswirkung zu bewerten. Ein ähnlicher Ansatz in einem viel größeren Aktionsraum würde dazu führen, dass alle Trajektorien in verschiedene Richtungen auseinanderlaufen, und es wären unendlich viele Trajektorien erforderlich, um aussagekräftige Statistiken zu erhalten und dann die Politik zu aktualisieren. Da wir hier Sample-MCTS verwenden, um die Streuung zu vermeiden, dh n_samples-Aktionen werden entsprechend der Modellrichtlinie abgetastet und MCTS dann nur eine der abgetasteten Aktionen auswählt, während der Baum untersucht wird, müssen wir die Sample-Korrektur bei der Berechnung berücksichtigen die letzte aktualisierte Richtlinie, die beim Trainieren des Modells verwendet wird.
In der Praxis wird die verbesserte Politik als berechnet
woher
def compute_improved_policy( state_dict: Dict, states: List[str], model_n_steps: int, model_n_logits: int, N_bar: int,
): """Compute the improved policy given the state_dict, the list of states. The improved policy is computed as (N_s_a / N_s_a.sum())^(1/tau) where tau is (log(N_s_a.sum()) / log(N_bar)) if N_s_a.sum() > N_bar else 1. """ policies = torch.zeros(len(states), model_n_steps, model_n_logits) N_bar = torch.tensor(N_bar) for idx, state in enumerate(states): N_s_a = state_dict[state][3] actions = state_dict[state][5] if N_s_a.sum() > N_bar: tau = (torch.log(N_s_a.sum()) / torch.log(N_bar)).item() else: tau = 1 N_s_a = N_s_a ** (1 / tau) improved_policy = N_s_a / N_s_a.sum() for sample_id in range(actions.shape[1]): action_ids = actions[0, sample_id] for step_id, action_id in enumerate(action_ids): policies[idx, step_id, action_id] += improved_policy[ 0, sample_id ] return policies
Beachten Sie, dass wir in unserer Implementierung, nachdem wir die Richtlinie aus dem N_s_a-Tensor berechnet haben, sie wieder auf den ursprünglichen Aktionstensor abbilden müssen. Tatsächlich berücksichtigt N_s_a nur die vom Modell abgetasteten Aktionen, während die endgültige Richtlinie auch Wahrscheinlichkeiten für die nicht untersuchten Aktionen enthalten muss.
Unterschiede in Bezug auf den ChatGPT-Trainingsalgorithmus
AlphaTensor ist das neueste Mitglied der AlphaGo/AlphaZero-Familie künstlicher Intelligenzmethoden von DeepMind. Diese Methoden basieren auf dem Monte-Carlo-Tree-Search-Algorithmus (MCTS), der von DeepMind verfeinert und erweitert wurde, um immer komplexere Aufgaben zu bewältigen. Ein anderes KI-System, ChatGPT von OpenAI, das wegen seiner bemerkenswerten Leistung viel Aufsehen erregt hat, wurde mit einem anderen Ansatz trainiert, dem Reinforcement Learning with Human Feedback (RLHF).
RLHF ist eine Feinabstimmungstechnik, die verwendet wird, um Sprachmodelle so abzustimmen, dass sie einer Reihe schriftlicher Anweisungen folgen. Es verwendet menschliche Vorlieben als Belohnungssignal, um das Modell zu verfeinern, wodurch das Verhalten des Sprachmodells an den angegebenen Vorlieben einer bestimmten Gruppe von Menschen ausgerichtet wird und nicht an einer breiteren Vorstellung von „menschlichen Werten“.
Im Gegensatz dazu ist MCTS ein baumbasierter Suchalgorithmus, der verwendet wird, um die optimalen Züge in Spielen zu bestimmen. Es simuliert potenzielle Bewegungen und aktualisiert die Werte jeder Bewegung basierend auf ihren Ergebnissen, um die Auswahl der besten Bewegung zu leiten.
RLHF sammelt Daten aus von Menschen geschriebenen Demonstrationen und von Menschen gekennzeichneten Vergleichen zwischen KI-Modellen und trainiert ein Belohnungsmodell, um die Präferenzen einer bestimmten Gruppe von Menschen vorherzusagen. Das Belohnungsmodell wird dann zur Feinabstimmung der KI-Modelle verwendet. MCTS hingegen nutzt Simulationen und Auswertungen, um die beste Entscheidung zu ermitteln.
Obwohl es sich um unterschiedliche Ansätze handelt, weisen RLHF und MCTS auch Gemeinsamkeiten auf. Beide Techniken der künstlichen Intelligenz verwenden Entscheidungsfindungs- und Problemlösungsmethoden, und beide verwenden einen Trial-and-Error-Ansatz, um verschiedene Optionen zu erkunden und Entscheidungen auf der Grundlage verfügbarer Informationen zu treffen. Beides sind auch iterative Prozesse, die sich im Laufe der Zeit verbessern, wenn mehr Informationen und Erfahrungen gesammelt werden.
Die Wahl zwischen RLHF und MCTS hängt von der jeweiligen Aufgabe ab. RLHF ist ideal, wenn es keine klare Metrik zur Bewertung der Modellleistung gibt, während sich MCTS bei spielähnlichen Aufgaben bewährt hat, bei denen Wissen und Erforschung der Zukunft dem Modell einen erheblichen Vorteil verschaffen.
Codeoptimierung für das AlphaTensor-Training
Die Implementierung des AlphaTensor-Trainingsalgorithmus erfordert das Finden des perfekten Kompromisses zwischen Trainingsgeschwindigkeit und Speicherverbrauch. Wie im Abschnitt „Modell“ zu sehen ist, kann die bloße Berücksichtigung der Aktionstokenisierung viel Speicher sparen, aber eine übermäßig aggressive Reduzierung des Aktionsraums kann sowohl zu einem Rückgang der Genauigkeit als auch zu einer langsameren Leistung führen. Letzteres geschieht, weil alle Token sequentiell auf autoregressive Weise vom Modelldecoder generiert werden. Daher wächst die Inferenzzeit linear mit der Anzahl der Tokens pro Aktion, sobald der Softmax auf dem Aktionsfeld nicht mehr der Flaschenhals ist.
Beim Aufbau des AlphaTensor-Trainings lagen die Hauptschwierigkeiten im Umgang mit dem Schauspielprozess. Wenn die Tensoren nicht im richtigen Format gespeichert werden, kann das MCTS leicht zu einem unkontrollierten Wachstum der Speichernutzung führen. Wenn andererseits die Anzahl der während jeder Simulation gespeicherten Tensoren zu stark reduziert wird, kann das MCTS unendlich viel Zeit damit verbringen, die erforderlichen Zustände neu zu berechnen.
Nehmen wir ein Beispiel für den Spielsimulationsschritt, bei dem das Spiel anhand möglicher Zukunftsszenarien untersucht wird. Wenn wir für jeden Zustand die vom Modell generierten Aktionen nicht speichern und uns dafür entscheiden, nur den zufälligen Startwert zu speichern, der zum Abtasten der Aktionen aus der Richtlinie verwendet wird, müssten wir jedes Mal, wenn wir einen Baumknoten erkunden, die Richtlinie neu berechnen und Probieren Sie dann die Aktionen aus. Wir haben uns eindeutig dafür entschieden, die erfassten Aktionen zu speichern, um Zeit zu sparen und zu vermeiden, dass bei der MCTS-Explorationsparallelisierung die Modellfreigabe zwischen verschiedenen Prozessen verwaltet werden muss. Das bloße Speichern der Aktionen reichte jedoch nicht aus, um einen ausreichend effizienten Handlungsschritt zu erhalten. Tatsächlich würde die Zeit für die Konvertierung der n_steps-Aktionen in das (u, v, w)-Triplett, die Reduzierung des Spieltensorzustands und die Erstellung der neuen 3D-Tensoren aus den n_samples-Aktionen leicht einen Engpass für das gesamte Training darstellen. Zweitens wollten wir nicht alle möglichen zukünftigen Zustände für jede abgetastete Aktion speichern, da dies große Auswirkungen auf den vom Algorithmus verwendeten Speicher haben würde. Angenommen, wir setzen n_samples=32, n=7 und N=5 und erinnern uns, dass N die Größe des quadratischen Matrixprodukts ist, das wir reduzieren möchten, und n die Anzahl der vorherigen Aktionen, an die sich das Modell erinnert. In dieser Situation hätte jeder Zustandstensor die Form (8, 25, 25, 25), was multipliziert mit 32 32 ergeben würde82525254 Bytes für jeden Knoten im Diagramm. Wenn man nun bedenkt, dass jede Simulation in der Erweiterungsphase einen neuen Knoten generiert (und n_sim = 200), hätten wir einen endgültigen Speicherverbrauch von 200328252525*4 = 3.2 GB allein für den ersten MCTS-Knoten. Im schlimmsten Fall würde dies beim Erkunden von aktiven max_rank-Knoten (wobei max_rank = 150) zu einem Gesamtspeicherverbrauch von 150 * 3.2 GB = 480 GB im RAM-Speicher (oder GPU-Speicher, wenn alle Tensoren auf der GPU gespeichert wären) führen. . Wir haben das Training auf unserer Workstation mit 128 GB RAM und 48 GB GPU-Speicher durchgeführt, daher mussten wir den Speicherverbrauch reduzieren.
Da wir die Ausführungszeit nicht verlängern wollten, haben wir eine Optimierung eingeführt, die die Redundanz in den erzeugten Zustandstensoren ausnutzt. Tatsächlich haben die Tensoren n-1 vorherige Aktionen gemeinsam, die dann einmal gespeichert werden können und nicht für jeden gespeicherten Tensor wiederholt werden können. Dies führt zu einer Speicherreduzierung von 2/7~28 %, sodass im schlimmsten Fall 137 GB gespeichert werden können. Zu diesem Zeitpunkt konnten wir durch einfaches Beschneiden des nicht verwendeten Teils des Baums (z. B. die nicht ausgewählten Trajektorien) und Speichern der Tensoren im CPU-Speicher Speicherfehler während des Trainings vermeiden.
Da OpenAlphaTensor jetzt Open Source ist, eröffnen sich mehrere spannende Wege für die Weiterentwicklung.
Ein natürlicher Fortschritt ist die Feinabstimmung von OpenAlphaTensor auf Zielhardwaregeräten. Dies wird voraussichtlich zu einer sehr konkurrenzfähigen Rechenleistung führen. Ich werde mehr über die Leistung von OpenAlphaTensor auf verschiedener Hardware veröffentlichen GitHub. Zum Zeitpunkt der Erstellung dieses Artikels befand sich OpenAlphaTensor in der Schulung.
Ein weiterer wichtiger Fortschritt wäre die Unterstützung der Remote-Kompilierung, die es Benutzern ermöglicht, Algorithmen zu erstellen, die für Edge-Geräte optimiert sind. Dies kann erreicht werden, indem das OpenAlphaTensor-Modell auf einem Server gespeichert wird, während der Matrixmultiplikationsalgorithmus auf unterschiedlicher Hardware evaluiert wird.
Es könnte auch wichtig sein, die Unterstützung für verschiedene Compiler zu erweitern, um die latenzbasierte Belohnungskorrektur zu berechnen. Unterschiedliche Compiler können auf einer gegebenen Hardware zu unterschiedlich optimierten Algorithmen führen. Beispielsweise zeigte das DeepMind-Papier vielversprechende Ergebnisse mit JAX und dem XLA-Compiler auf TPU- und Nvidia-GPUs. Es wäre interessant, dies mit NCCL auf Nvidia oder LLVM auf CPUs zu evaluieren.
Schließlich bleibt die Erweiterung des Modells und des Trainingsalgorithmus zur Unterstützung größerer Matrixgrößen eine große offene Herausforderung. Derzeit unterstützt OpenAlphaTensor eine maximale Matrixgröße von 5, aber es kann angewendet werden, indem größere Matrixmultiplikationen in Gruppen von winzigen MMs mit einer Größe kleiner als 5 aufgeteilt werden. Dieser Ansatz ist suboptimal und führt die Reduktion direkt auf dem großen Tensor durch, der dem entspricht vollständiges MM könnte theoretisch zu besseren Ergebnissen führen.
Diego Fiori ist CTO von Nebuly AI, einem Unternehmen, das sich dafür einsetzt, die KI-Optimierung zum Bestandteil des Toolkits jedes Entwicklers zu machen.
- SEO-gestützte Content- und PR-Distribution. Holen Sie sich noch heute Verstärkung.
- Platoblockkette. Web3-Metaverse-Intelligenz. Wissen verstärkt. Hier zugreifen.
- Quelle: https://www.kdnuggets.com/2023/03/first-open-source-implementation-deepmind-alphatensor.html?utm_source=rss&utm_medium=rss&utm_campaign=first-open-source-implementation-of-deepminds-alphatensor
- :Ist
- ][P
- $UP
- 1
- 3d
- 8
- a
- Fähig
- Über uns
- oben
- Absolute
- beschleunigend
- Nach
- entsprechend
- Konto
- Genauigkeit
- Erreichen
- erreicht
- Action
- Aktionen
- berührt das Schneidwerkzeug
- hinzugefügt
- Zusätzliche
- Bereinigt
- angenommen
- vorantreiben
- Vorteil
- Nach der
- Makler
- Anhäufung
- aggressiv
- AI
- KI-Systeme
- Ziel
- Algorithmus
- Algorithmen
- Alle
- Zulassen
- erlaubt
- allein
- bereits
- Alternative
- unter
- Betrag
- analysieren
- und
- Ein anderer
- angewandt
- Ansatz
- Ansätze
- Architektur
- SIND
- Artikel
- künstlich
- künstliche Intelligenz
- AS
- zugewiesen
- damit verbundenen
- At
- Automatisiert
- verfügbar
- Vermeidung von
- Zurück
- Sicherungskopie
- basierend
- Grundsätzlich gilt
- Grundlage
- BE
- weil
- wird
- Bevor
- Sein
- unten
- Benchmark
- BESTE
- Besser
- zwischen
- Beyond
- Tafel
- Brettspiele
- gebunden
- breiteres
- BT
- bauen
- erbaut
- by
- namens
- CAN
- kann keine
- Häuser
- Verursachen
- verursacht
- sicher
- challenges
- herausfordernd
- Übernehmen
- Kanal
- ChatGPT
- der
- Wahl
- Auswahl
- gewählt
- zitiert
- klar
- Code
- sammelt
- kombinieren
- begangen
- gemeinsam
- Unternehmen
- verglichen
- wettbewerbsfähig
- Komplex
- Komplexität
- Komponenten
- zusammengesetzt
- Kompromiss
- Berechnung
- Rechenleistung
- Berechnen
- Computing
- Konzepte
- Konzeptionell
- Zustand
- Vertrauen
- Geht davon
- betrachtet
- Berücksichtigung
- überlegt
- Verbrauch
- enthält
- fortsetzen
- Kontrast
- umgewandelt
- Kernbereich
- Dazugehörigen
- entspricht
- könnte
- Counter
- CPU
- schafft
- Erstellen
- Kriterien
- CTO
- Strom
- Zur Zeit
- technische Daten
- Behandlung
- entscheidet
- entschieden
- Entscheidung
- Decision Making
- Entscheidungen
- tief
- tiefe Lernen
- DeepMind
- hängt
- beschrieben
- Bestimmen
- Entwickler:in / Unternehmen
- Entwicklung
- Gerät
- Geräte
- DICT
- anders
- Schwierigkeiten
- Abmessungen
- Größe
- Direkt
- entdeckt,
- entdecken
- diskutiert
- Verteilung
- geteilt
- nach unten
- Drop
- im
- e
- jeder
- Früher
- leicht
- Edge
- Effektiv
- effizient
- entweder
- Element
- Elemente
- eingebettet
- endet
- verbesserte
- enorm
- genug
- Eintrag
- Fehler
- schätzen
- geschätzt
- Äther (ETH)
- bewerten
- Bewerten
- Auswerten
- Auswertungen
- Sogar
- Jedes
- Beispiel
- Beispiele
- unterhaltsame Programmpunkte
- Ausführung
- Expansion
- erwartet
- ERFAHRUNGEN
- Erklären
- erklärt
- Abenteuer
- Exploration
- ERKUNDEN
- Erkundet
- Möglichkeiten sondieren
- zum Ausdruck gebracht
- erweitern
- Verlängerung
- äußerst
- ziemlich
- Familie
- beschleunigt
- Feedback
- wenige
- Abbildung
- Finale
- Suche nach
- Vorname
- Schwimmer
- Setzen Sie mit Achtsamkeit
- folgen
- Folgende
- Fußabdruck
- Aussichten für
- unten stehende Formular
- Format
- Formel
- gefunden
- für
- voller
- Funktion
- fundamental
- weiter
- weitere Entwicklung
- Zukunft
- Spiel
- Games
- erzeugen
- erzeugt
- erzeugt
- Erzeugung
- bekommen
- bekommen
- ABSICHT
- gegeben
- Unterstützung
- Kundenziele
- Goes
- gut
- GPU
- GPUs
- Graph
- groß
- Gruppe an
- Gruppen
- Wächst
- Wachstum
- Guide
- Pflege
- Handling
- passiert
- das passiert
- Hardware
- Hardwaregerät
- Hardwaregeräte
- Haben
- mit
- hier
- höchste
- Horizont
- Ultraschall
- Hilfe
- aber
- HTTPS
- riesig
- human
- i
- KRANK
- ideal
- IDX
- Image
- Impact der HXNUMXO Observatorien
- implementieren
- Implementierung
- wichtig
- zu unterstützen,
- verbessert
- in
- Erhöhung
- Steigert
- zunehmend
- unabhängig
- Index
- Information
- Anfangs-
- Eingabe
- beantragen müssen
- Anleitung
- Intelligenz
- interessant
- intern
- eingeführt
- Intuition
- IT
- Iteration
- SEINE
- selbst
- jpg
- KDnuggets
- Behalten
- Wesentliche
- Wissen
- bekannt
- Sprache
- grosse
- größer
- Nachname
- Latency
- neueste
- Schicht
- Lagen
- führen
- gelernt
- lernen
- Liste
- aussehen
- suchen
- Los
- gemacht
- Main
- Dur
- um
- Making
- verwalten
- flächendeckende Gesundheitsprogramme
- viele
- Karte
- Mapping
- Matrix
- Materie
- maximal
- Bedeutung
- sinnvoll
- Mittel
- Mitglied
- Memory
- Methode
- Methoden
- Metrisch
- Kommt demnächst...
- Mischung
- Modell
- für
- geändert
- Modulen
- mehr
- effizienter
- Zudem zeigt
- vor allem warme
- schlauer bewegen
- bewegt sich
- multipliziert
- Natürliche
- Natur
- In der Nähe von
- notwendig,
- Need
- erforderlich
- Negativ
- Netzwerke
- Neural
- Neuronale Netze
- Neu
- weiter
- Nlp
- Knoten
- Fiber Node
- Nicht-Experten
- Notion
- Anzahl
- Nvidia
- erhalten
- of
- Angebote
- on
- EINEM
- XNUMXh geöffnet
- Open-Source-
- OpenAI
- Betrieb
- Einkauf & Prozesse
- optimal
- Optimierung
- Optimieren
- optimiert
- Optionen
- Original
- Andere
- Andernfalls
- Ausgabe
- Gesamt-
- Papier
- Papiere
- Parameter
- Teil
- besondere
- besonders
- Teile
- Personen
- perfekt
- Leistung
- Durchführung
- Phase
- Stücke
- Plato
- Datenintelligenz von Plato
- PlatoData
- Play
- Spieler
- spielend
- Points
- Politik durchzulesen
- Datenschutzrichtlinien
- Position
- möglich
- Potenzial
- Werkzeuge
- Praktisch
- Praxis
- vorhersagen
- vorhergesagt
- Vorlieben
- vorgeführt
- früher
- Wahrscheinlichkeit
- wahrscheinlich
- Aufgabenstellung:
- Prozessdefinierung
- anpassen
- produziert
- Produziert
- Produkt
- Progression
- fortschrittlich
- aussichtsreich
- vorgeschlage
- nachweislich
- zuverlässig
- veröffentlichen
- veröffentlicht
- RAM
- zufällig
- Rangstufen
- lieber
- erreicht
- Erreicht
- kürzlich
- Veteran
- Reduziert
- Reduzierung
- raffiniert
- Verstärkung lernen
- freigegeben
- verbleibenden
- bleibt bestehen
- bemerkenswert
- merken
- entfernt
- wiederholt
- Darstellen
- representiert
- falls angefordert
- erfordert
- für ihren Verlust verantwortlich.
- eingeschränkt
- Folge
- was zu
- Die Ergebnisse
- Rückkehr
- Rückgabe
- revolutionieren
- Belohnen
- REIHE
- rt
- Führen Sie
- s
- gleich
- Speichern
- Einsparung
- Szenario
- Szenarien
- Schema
- Suche
- Zweite
- Abschnitt
- Samen
- ausgewählt
- Auswahl
- Auswahl
- SELF
- kompensieren
- Einstellung
- mehrere
- Form
- ,,teilen"
- Short
- sollte
- erklären
- gezeigt
- Konzerte
- Signal
- signifikant
- bedeutend
- ähnlich
- Ähnlichkeiten
- Einfacher
- Einfachheit
- einfach
- Simulation
- da
- Situation
- Größe
- Größen
- klein
- kleinere
- Schnappschuss
- So
- Lösung
- LÖSEN
- Auflösung
- einige
- Quelle
- Raumfahrt
- Räume
- spezifisch
- speziell
- angegeben
- Geschwindigkeit
- verbringen
- Ausgabe
- gespalten
- quadratisch
- gestapelt
- Stufe
- Standard
- Anfang
- beginnt
- Bundesstaat
- State-of-the-art
- angegeben
- Staaten
- Statistiken
- Schritt
- Shritte
- Einstellung
- speichern
- gelagert
- Läden
- einfach
- erfolgreich
- so
- Support
- Unterstützt
- synthetisch
- synthetische Daten
- synthetisch
- System
- Systeme und Techniken
- zugeschnitten
- Nehmen
- nimmt
- Einnahme
- Target
- Aufgabe
- und Aufgaben
- Techniken
- Terminal
- AGB
- zur Verbesserung der Gesundheitsgerechtigkeit
- Das
- Die Zukunft
- Der Graph
- die Informationen
- Der Staat
- ihr
- Sie
- damit
- deswegen
- Diese
- Dritte
- nach drei
- dreidimensional
- Durch
- Zeit
- Zeitaufwendig
- zu
- gemeinsam
- Zeichen
- Tokenisierung
- Tokenisiert
- Tokens
- auch
- Toolkit
- Top
- Fackel
- Gesamt
- traditionell
- Training
- trainiert
- Ausbildung
- schult Ehrenamtliche
- Flugbahn
- verwandelt
- behandeln
- was immer dies auch sein sollte.
- verstehen
- Universum
- ungenutzt
- Aktualisierung
- aktualisiert
- Updates
- Aktualisierung
- mehr Stunden
- us
- Anwendungsbereich
- -
- Nutzer
- gewöhnlich
- Wert
- Werte
- verschiedene
- Version
- Video
- Videospiele
- Besuchen Sie
- Besuche
- W
- Weg..
- Was
- welche
- während
- weit
- Wikipedia
- werden wir
- gewinnt
- mit
- Worte
- Arbeitsplatz
- wert
- würde
- Schreiben
- geschrieben
- X
- Zephyrnet
- Null