Hintergrund
Das Erkennen der Primalität scheint für (künstliche) neuronale Netze schlecht geeignet zu sein. Der universelle Approximationssatz besagt jedoch, dass neuronale Netze jede kontinuierliche Funktion approximieren können, so dass es insbesondere möglich sein sollte, jede beliebige endlich unterstützte Funktion darzustellen. Versuchen wir also, alle Primzahlen unter den ersten Millionen Zahlen zu erkennen.
Genauer gesagt, da dies eine Programmierwebsite ist, steigen wir auf 2 ^ 20 = 1.048.576. Die Anzahl der Primzahlen unterhalb dieser Schwelle beträgt 82.025 oder ungefähr 8%.
Herausforderung
Wie klein von einem neuronalen Netzwerk können Sie feststellen, dass alle 20-Bit-Ganzzahlen korrekt als Primzahl oder Nicht-Primzahl klassifiziert werden?
Für die Zwecke dieser Herausforderung ist die Größe eines neuronalen Netzwerks die Gesamtzahl der Gewichte und Vorspannungen, die erforderlich sind, um es darzustellen.
Einzelheiten
Ziel ist es , die Größe eines einzelnen, expliziten neuronalen Netzwerks zu minimieren .
Die Eingabe in Ihr Netzwerk ist ein Vektor der Länge 20, der die einzelnen Bits einer Ganzzahl enthält, die entweder mit 0 und 1 oder alternativ mit -1 und + 1 dargestellt werden. Die Reihenfolge dieser kann das höchstwertige Bit zuerst oder das niedrigstwertige Bit zuerst sein.
Die Ausgabe Ihres Netzwerks sollte eine einzelne Zahl sein, sodass die Eingabe oberhalb eines bestimmten Grenzwerts als Primzahl und unterhalb desselben Grenzwerts als Nichtprimzahl erkannt wird. Zum Beispiel könnte Positiv Primzahl bedeuten (und Negativ nicht Primzahl), oder alternativ könnte Größer als 0,5 Primzahl bedeuten (und weniger als 0,5 nicht Primzahl).
Das Netzwerk muss für alle 2 ^ 20 = 1.048.576 möglichen Eingaben 100% genau sein. Wie oben erwähnt, gibt es in diesem Bereich 82.025 Primzahlen. (Daraus folgt, dass die Ausgabe von "nicht prim" immer 92% genau wäre.)
In Bezug auf die Standardterminologie für neuronale Netze würde dies wahrscheinlich als Überanpassung bezeichnet . Mit anderen Worten, Ihr Ziel ist es, die Primzahlen perfekt zu überziehen. Andere Wörter, die man verwenden könnte, sind, dass der "Trainingssatz" und der "Testsatz" gleich sind.
Diese Herausforderung berücksichtigt nicht die Anzahl der "trainierbaren" oder "lernbaren" Parameter. In der Tat enthält Ihr Netzwerk wahrscheinlich fest codierte Wertigkeiten, und das folgende Beispiel ist vollständig fest codiert. Stattdessen werden alle Gewichte und Verzerrungen als Parameter betrachtet und gezählt.
Die Länge des Codes, die zum Trainieren oder Generieren Ihres neuronalen Netzwerks erforderlich ist, ist für Ihre Punktzahl nicht relevant, aber das Posten des entsprechenden Codes ist sicherlich erwünscht.
Grundlinie
Grundsätzlich ist es möglich, sich alle 82.025 Primzahlen mit 1.804.551 Gesamtgewichten und Vorspannungen zu "merken" .
Beachten Sie, dass dieser folgende Code viele Dinge beinhaltet: ein funktionierendes Beispiel, einen funktionierenden Testcode, eine funktionierende Definition eines neuronalen Netzwerks unter Verwendung einer bekannten Bibliothek für neuronale Netzwerke, ein "hartcodiertes" (oder zumindest nicht "trainiertes") neuronales Netzwerk, und eine funktionierende Messung der Punktzahl.
import numpy as np
bits = 20
from keras.models import Sequential
from keras.layers import Dense
from sympy import isprime
# Hardcode some weights
weights = []
biases = []
for n in xrange(1<<bits):
if not isprime(n):
continue
bit_list = [(n / (1 << i))%2 for i in xrange(bits)]
weight = [2*bit - 1 for bit in bit_list]
bias = - (sum(bit_list) - 1)
weights.append(weight)
biases .append(bias)
nprimes = len(biases)
weights1 = np.transpose(np.array(weights))
biases1 = np.array(biases )
weights2 = np.full( (nprimes,1), 1 )
biases2 = np.array( [0] )
model = Sequential()
model.add(Dense(units=nprimes, activation='relu', input_dim=bits, weights=[weights1, biases1]))
model.add(Dense(units=1, activation='relu', weights=[weights2, biases2]))
print "Total weights and biases: {}".format( np.size(weights1) + np.size(weights2) + np.size(biases1) + np.size(biases2) )
# Evaluate performance
x = []
y = []
for n in xrange(1<<bits):
row = [(n / (1 << i))%2 for i in xrange(bits)]
x.append( row )
col = 0
if isprime(n):
col = 1
y.append( col )
x = np.array(x)
y = np.array(y)
model.compile(loss='binary_crossentropy', optimizer='sgd', metrics=['accuracy'])
loss, accuracy = model.evaluate(x, y, batch_size=256)
if accuracy == 1.0:
print "Perfect fit."
else:
print "Made at least one mistake."
Was ist ein neuronales Netzwerk?
Für diese Herausforderung können wir eine enge, aber genaue Definition eines (künstlichen) neuronalen Netzwerks aufschreiben. Für eine externe Lektüre empfehle ich Wikipedia zu künstlichem neuronalen Netzwerk , vorwärts gerichtetem neuronalen Netzwerk , mehrschichtigem Perzeptron und Aktivierungsfunktion .
Ein Feedforward-neuronales Netzwerk ist eine Sammlung von Schichten von Neuronen. Die Anzahl der Neuronen pro Schicht variiert, wobei sich 20 Neuronen in der Eingabeschicht, einige Neuronen in einer oder mehreren verborgenen Schichten und 1 Neuron in der Ausgabeschicht befinden. (Es muss mindestens eine ausgeblendete Ebene vorhanden sein, da Primzahlen und Nicht-Primzahlen gemäß ihren Bitmustern nicht linear trennbar sind.) Im obigen Baseline-Beispiel betragen die Größen der Ebenen [20, 82025, 1].
Die Werte der Eingangsneuronen werden durch die Eingabe bestimmt. Wie oben beschrieben, sind dies entweder 0s und 1s, die den Bits einer Zahl zwischen 0 und 2 ^ 20 entsprechen, oder -1s und + 1s in ähnlicher Weise.
Die Werte der Neuronen jeder folgenden Schicht, einschließlich der Ausgangsschicht, werden vorher aus der Schicht bestimmt. Zunächst wird eine lineare Funktion in vollständig verbundener oder dichter Weise angewendet . Eine Methode zur Darstellung einer solchen Funktion ist die Verwendung einer Gewichtungsmatrix . Beispielsweise können die Übergänge zwischen den ersten beiden Ebenen der Grundlinie mit einer 82025 x 20-Matrix dargestellt werden. Die Anzahl der Gewichtungen ist die Anzahl der Einträge in dieser Matrix, z. B. 1640500. Dann wird jedem Eintrag ein (separater) Bias-Term hinzugefügt. Dies kann durch einen Vektor dargestellt werden, z. B. eine 82025 x 1-Matrix in unserem Fall. Die Anzahl der Verzerrungen entspricht der Anzahl der Einträge, z. B. 82025. (Beachten Sie, dass die Gewichte und Verzerrungen zusammen eine affine lineare Funktion beschreiben .)
Eine Gewichtung oder Verzerrung wird gezählt, auch wenn sie Null ist. Für die Zwecke dieser engen Definition gelten Vorspannungen als Gewichte, auch wenn sie alle Null sind. Beachten Sie, dass im Baseline-Beispiel nur zwei unterschiedliche Gewichtungen (+1 und -1) verwendet werden (und nur geringfügig stärker ausgeprägte Verzerrungen). Trotzdem ist die Größe mehr als eine Million, denn die Wiederholung hilft in keiner Weise bei der Partitur.
Schließlich wird eine nichtlineare Funktion, die Aktivierungsfunktion genannt wird, eingangsweise auf das Ergebnis dieser affinen linearen Funktion angewendet. Für die Zwecke dieser engen Definition sind die zulässigen Aktivierungsfunktionen ReLU , tanh und sigmoid . Die gesamte Ebene muss dieselbe Aktivierungsfunktion verwenden.
Im Baseline-Beispiel beträgt die Anzahl der Gewichte 20 * 82025 + 82025 * 1 = 1722525 und die Anzahl der Verzerrungen beträgt 82025 + 1 = 82026, was einer Gesamtpunktzahl von 1722525 + 82026 = 1804551 entspricht eine weitere Schicht und die Schichtgrößen waren stattdessen [20, a, b, 1], dann wäre die Anzahl der Gewichte 20 * a + a * b + b * 1 und die Anzahl der Verzerrungen wäre a + b + 1.
Diese Definition des neuronalen Netzwerks wird von vielen Frameworks gut unterstützt, einschließlich Keras , Scikit-Learn und Tensorflow . Keras wird im obigen Baseline-Beispiel verwendet, wobei der Code im Wesentlichen wie folgt lautet:
from keras.models import Sequential
model = Sequential()
from keras.layers import Dense
model.add(Dense(units=82025, activation='relu', input_dim=20, weights=[weights1, biases1]))
model.add(Dense(units=1, activation='relu', weights=[weights2, biases2]))
score = numpy.size(weights1) + numpy.size(biases1) + numpy.size(weights2) + numpy.size(biases2)
Wenn die Gewichte und Bias - Matrizen sind numpy Arrays, dann numpy.size gelangen Sie direkt die Anzahl der Einträge erzählen.
Gibt es andere Arten von neuronalen Netzen?
Wenn Sie eine einzige, genaue Definition des neuronalen Netzwerks und der Bewertung für diese Herausforderung wünschen, verwenden Sie bitte die Definition im vorherigen Abschnitt. Wenn Sie der Meinung sind, dass "jede Funktion" ein neuronales Netzwerk ohne Parameter ist , verwenden Sie bitte die Definition im vorherigen Abschnitt.
Wenn Sie ein freier Geist sind, dann ermutige ich Sie, weiter zu erforschen. Vielleicht zählt Ihre Antwort nicht für die knappe Herausforderung, aber vielleicht werden Sie mehr Spaß haben. Einige andere Ideen, die Sie möglicherweise ausprobieren, umfassen exotischere Aktivierungsfunktionen, wiederkehrende neuronale Netze (jeweils ein Bit lesend), faltungsmäßige neuronale Netze, exotischere Architekturen, Softmax und LSTMs (!). Sie können jede Standardaktivierungsfunktion und jede Standardarchitektur verwenden. Eine liberale Definition von "Standard" -Features für neuronale Netze könnte alles beinhalten, was vor dem Versenden dieser Frage auf arxiv veröffentlicht wurde.
Antworten:
Versuchsaufteilung: 59407 Punkte, 6243 Schichten, insgesamt 16478 Neuronen
Gegeben als Python-Programm, das das Netz generiert und validiert. In den Kommentaren finden Sie
trial_division
eine Erklärung zur Funktionsweise. Die Validierung ist ziemlich langsam (wie in, Laufzeit in Stunden): Ich empfehle die Verwendung von PyPy oder Cython.Die Schwelle ist 1: alles, was über der Primzahl liegt, alles, was darunter liegt, ist zusammengesetzt oder Null, und die einzige Eingabe, die eine Ausgabe von 1 ergibt, ist 1 selbst.
Nebenbei, re
Es ist leicht zu zeigen, dass ein neuronales Netzwerk, das ReLU verwendet, vollständig ist. Das am einfachsten robust zu implementierende Logikgatter ist NOR: Ein NOR-Gatter mit n Eingängen ist . Ich sage robust, weil dieses Gatter Eingaben größer als 1 akzeptiert, aber (vorausgesetzt, die Eingaben liegen nicht zwischen 0 und 1) immer nur 0 oder 1 ausgibt. Ein einschichtiges UND-Gatter ist funktioniert aber nur dann richtig, wenn die Eingabe garantiert 0 oder 1 ist und möglicherweise größere Ganzzahlen ausgibt. In einer Schicht sind verschiedene andere Gatter möglich, aber NOR ist für sich genommen Turing-vollständig, sodass es nicht erforderlich ist, auf Details einzugehen.max ( 0 , 1 - ∑ aich) max ( 0 , 1 + ∑ ( a i - 1 ) )max ( 0 , 1 + ∑ ( aich- 1 ) )
quelle
Kerbe 984314, 82027 Schichten, 246076 Neuronen insgesamt
Wenn wir die Aktivierungsfunktion ReLU verwenden, die die Analyse vereinfacht, können wir die Dinge vollständig in den ganzen Zahlen belassen.
Bei einer Eingabe von die als Ganzzahl bekannt ist, können wir testen, ob mit zwei Schichten und drei Neuronen ist:x x = a
Schicht 4: Ausgängeakkumulieren3= ( 221akkumulieren2- ge3- le3+ 1 )+ ge5= ( ge3- ( 5 - 3 ) )+ le5= ( - ge3+ ( 5 - 3 ) )+
Schicht 5: Ausgängeakkumulieren5= ( 221akkumulieren3- ge5- le5+ 1 )+ ge7= ( ge5- ( 7 - 5 ) )+ le7= ( - ge5+ ( 7 - 5 ) )+
...
Schicht 82026: gibtakkumulieren1048571= ( 221akkumulieren1048559- ge1048571- le1048571+ 1 )+ ge1048573= ( ge1048571- ( 1048573 - 1048571 ) )+ le1048573= ( - ge1048571+ ( 1048573 - 1048571 ) )+
Die Bewertung lautet (82026-3) * 12 + 21 + 4 + 9 + 4.
quelle