Wie lernt ein Programm aus Beispielen, ohne dass jemand ihm die Regel verrät? Du baust ein künstliches Neuron, das an zwei Maßen des Schnabels erkennt, zu welcher Art ein Pinguin gehört, und das diese Regel selbst findet. Danach liest dasselbe Neuron handgeschriebene Ziffern, und du siehst, wo ein einzelnes Neuron nicht mehr weiterkommt.
Auf den Inseln vor der Antarktischen Halbinsel hat die Biologin Kristen Gorman von 2007 bis 2009 Pinguine vermessen: ihre Art, ihr Gewicht, die Länge ihrer Flossen und ihren Schnabel. Drei Arten leben dort: der Zügelpinguin (englisch Chinstrap), der Eselspinguin (Gentoo) und der Adéliepinguin.
Kristen Gorman, Meeresbiologin, heute an der University of Alaska Fairbanks. Für ihre Doktorarbeit an der Simon Fraser University in Kanada vermaß sie auf der Palmer-Station in der Antarktis drei Sommer lang Pinguine aller drei Arten. Sie wollte wissen, wie die Kolonien damit zurechtkommen, dass das Meereis und mit ihm das Futter zurückgeht.
Ihre Messungen hat sie veröffentlicht, frei für alle. Allison Horst und Alison Hill machten 2020 daraus das Paket palmerpenguins, heute einer der beliebtesten Datensätze zum Üben. Deine Datei penguins.csv stammt daraus.

Der Adéliepinguin und der Eselspinguin sehen sich auf den ersten Blick ähnlich. Am Schnabel kann man sie unterscheiden, wenn man weiß, worauf man achten muss. Gemessen wurde er so:

bill_length_mm und bill_depth_mm. Zeichnung: Allison Horst, CC0 (palmerpenguins)Du wirst ein Programm schreiben, das diese Regel selbst findet, nur aus den Messungen und der Art jedes Pinguins. Niemand sagt ihm, worauf es achten soll.
Ein Pinguin hat einen Schnabel, der 48 mm lang und 15 mm hoch ist. Ein anderer hat einen Schnabel, der 38 mm lang und 19 mm hoch ist. Einer ist ein Adéliepinguin, einer ein Eselspinguin. Welcher ist der Eselspinguin?
Eine Nervenzelle im Gehirn bekommt Signale von anderen Zellen, manche stärker, manche schwächer. Kommt insgesamt genug zusammen, feuert sie selbst ein Signal. Ein künstliches Neuron rechnet das nach, in drei Schritten:
Für einen Pinguin sind die Eingaben die Länge und die Höhe seines Schnabels in Millimetern, und 1 heißt Eselspinguin, 0 Adéliepinguin. Mit den Gewichten 2 und −5 und dem Bias 10 rechnet das Neuron für einen Schnabel, der 50 mm lang und 15 mm hoch ist:
$$2 \cdot 50 + (-5) \cdot 15 + 10 = 100 - 75 + 10 = 35$$
35 ist größer als 0: Das Neuron sagt 1, ein Eselspinguin.
Ein Neuron hat die Gewichte 2 und −5 und den Bias 10. Wie groß ist seine Summe für einen Schnabel, der 39 mm lang und 18 mm hoch ist?
2 mal 39, plus −5 mal 18, plus 10.
Das erste solche Neuron beschrieben der Neurologe Warren McCulloch und der Logiker Walter Pitts 1943, auf Papier. Frank Rosenblatt baute 1958 eines, das seine Gewichte selbst einstellte: das Perzeptron.
Frank Rosenblatt (1928–1971), US-amerikanischer Psychologe an der Cornell University. 1958 baute er das Mark I Perceptron: eine Kamera aus 20 × 20 Fotozellen, also 400 Eingaben, deren Gewichte Motoren an Drehwiderständen selbst einstellten. Die Maschine lernte, einfache Formen auseinanderzuhalten.
Deine Funktion train lernt nach seiner Regel: bei jedem Fehler die Gewichte ein Stück in Richtung der richtigen Antwort.
Foto: unbekannt, um 1950, Heinz Nixdorf MuseumsForum, CC BY-SA 4.0, zugeschnitten (Wikimedia Commons)
The Perceptron: A Probabilistic Model for Information Storage and Organization in the Brain, Frank Rosenblatt, Psychological Review, 1958. Der Aufsatz, in dem ein Neuron seine Gewichte aus Beispielen lernt: PDF, auch bei der American Psychological Association.
Lege einen Ordner an und lade die Messungen herunter:
mkdir -p ~/perzeptron cd ~/perzeptron wget https://workspace.hackschule.de/perzeptron/penguins.csv touch neuron.rb learn.rb
In neuron.rb kommen die Funktionen des Neurons, in learn.rb das Programm,
das damit lernt. Ausprobieren kannst du die Funktionen in irb, das
neuron.rb vorher lädt:
irb -r ./neuron.rb
Lege einen Ordner an und lade die Messungen herunter:
mkdir -p ~/perzeptron cd ~/perzeptron wget https://workspace.hackschule.de/perzeptron/penguins.csv touch neuron.py learn.py
In neuron.py kommen die Funktionen des Neurons, in learn.py das Programm,
das damit lernt. Ausprobieren kannst du die Funktionen in Python, das
neuron.py vorher ausführt:
python3 -i neuron.py
penguins.csv hat eine Zeile je Pinguin, 342 Zeilen: die Art, die Insel, die
Länge und die Höhe des Schnabels in Millimetern, die Länge der Flosse und das
Gewicht in Gramm.
species,island,bill_length_mm,bill_depth_mm,flipper_length_mm,body_mass_g Adelie,Torgersen,39.1,18.7,181,3750
predict(weights, bias, features):
Sie multipliziert jedes Merkmal mit seinem Gewicht, addiert die Produkte und den
Bias und gibt 1 zurück, wenn die Summe größer als 0 ist, sonst 0. Später hat
ein Neuron 64 Eingaben; schreib sie so, dass sie mit jeder Anzahl geht.
weights und features sind gleich lange Listen. Geh sie mit einem Index
durch: features.each_index do |i| … end. Die Summe beginnt beim Bias.
# 1 oder 0: ist die gewichtete Summe der Merkmale plus Bias größer als 0? def predict(weights, bias, features) sum = bias features.each_index do |i| sum += weights[i] * features[i] end sum > 0 ? 1 : 0 end
weights und features sind gleich lange Listen. Geh sie mit einem Index
durch: for i in range(len(features)):. Die Summe beginnt beim Bias.
# 1 oder 0: ist die gewichtete Summe der Merkmale plus Bias größer als 0? def predict(weights, bias, features): total = bias for i in range(len(features)): total += weights[i] * features[i] return 1 if total > 0 else 0
Schreib in perzeptron/neuron.rb die Funktion
predict(weights, bias, features). Die Prüfung ruft sie mit zwei und mit
drei Merkmalen auf, auch mit einer Summe von genau 0.
Schreib in perzeptron/neuron.py die Funktion
predict(weights, bias, features). Die Prüfung ruft sie mit zwei und
mit drei Merkmalen auf, auch mit einer Summe von genau 0.
Damit das Neuron die Pinguine sieht, liest diese Funktion die Datei. Sie gibt
für jeden Pinguin der beiden Arten ein Paar zurück: die zwei Maße des Schnabels
und die Art als Zahl, 1 für die zweite der beiden Arten, 0 für die erste. Schreib
sie unter predict in dieselbe Datei:
# Die Pinguine zweier Arten als [[Länge, Höhe], 0 oder 1] def penguins(path, species) File.readlines(path).drop(1).filter_map do |line| name, _island, length, depth = line.split(",") next unless species.include?(name) [[length.to_f, depth.to_f], name == species[1] ? 1 : 0] end end
# Die Pinguine zweier Arten als [[Länge, Höhe], 0 oder 1] def penguins(path, species): examples = [] for line in open(path).readlines()[1:]: name, _island, length, depth = line.split(",")[:4] if name in species: examples.append([[float(length), float(depth)], 1 if name == species[1] else 0]) return examples
Woher kommen die Gewichte? Am Anfang sind alle 0, und das Neuron sagt zu jedem Pinguin 0. Dann geht es die Pinguine einen nach dem anderen durch und vergleicht, was es sagt, mit der richtigen Art. Liegt es richtig, ändert es nichts. Liegt es falsch, verschiebt es seine Gewichte ein Stück in die Richtung der richtigen Antwort:
Kurz: Der Fehler ist die richtige Antwort minus die Vorhersage, also 1, −1 oder 0, und jedes Gewicht bekommt Fehler mal Merkmal dazu, der Bias den Fehler.
$$w_i \leftarrow w_i + (t - y) \cdot x_i \qquad b \leftarrow b + (t - y)$$
Ein Durchgang durch alle Pinguine heißt Epoche. Nach einer Epoche fängt das Neuron wieder vorne an, bis es in einer ganzen Epoche keinen Fehler mehr macht.
train(examples, epochs):
Sie beginnt mit Gewichten 0 und Bias 0, geht höchstens epochs-mal
alle Beispiele durch, lernt aus jedem Fehler nach der Regel oben, gibt nach jeder
Epoche aus, wie viele Fehler es waren, hört nach einer Epoche ohne Fehler auf
und gibt die Gewichte und den Bias zurück.
Die Gewichte sind eine Liste mit so vielen Nullen, wie ein Beispiel Merkmale
hat: Array.new(examples[0][0].size, 0.0). Ein Beispiel ist ein Paar
features, target.
Zwei Schleifen ineinander: außen die Epochen, innen die Beispiele. In der
inneren: vorhersagen, den Fehler target - prediction ausrechnen, und wenn er
nicht 0 ist, die Gewichte und den Bias ändern und den Fehler zählen.
def train(examples, epochs) weights = Array.new(examples[0][0].size, 0.0) bias = 0.0 epochs.times do |epoch| errors = 0 examples.each do |features, target| # hier: vorhersagen, den Fehler ausrechnen, bei einem Fehler # weights und bias ändern und errors zählen end puts "Epoche #{epoch + 1}: #{errors} Fehler" break if errors == 0 end [weights, bias] end
# Lernt Gewichte und Bias aus Beispielen [Merkmale, 0 oder 1], höchstens epochs Epochen lang def train(examples, epochs) weights = Array.new(examples[0][0].size, 0.0) bias = 0.0 epochs.times do |epoch| errors = 0 examples.each do |features, target| error = target - predict(weights, bias, features) next if error == 0 features.each_index do |i| weights[i] += error * features[i] end bias += error errors += 1 end puts "Epoche #{epoch + 1}: #{errors} Fehler" break if errors == 0 end [weights, bias] end
Die Gewichte sind eine Liste mit so vielen Nullen, wie ein Beispiel Merkmale
hat: [0.0] * len(examples[0][0]). Ein Beispiel ist ein Paar
features, target.
Zwei Schleifen ineinander: außen die Epochen, innen die Beispiele. In der
inneren: vorhersagen, den Fehler target - prediction ausrechnen, und wenn er
nicht 0 ist, die Gewichte und den Bias ändern und den Fehler zählen.
def train(examples, epochs): weights = [0.0] * len(examples[0][0]) bias = 0.0 for epoch in range(epochs): errors = 0 for features, target in examples: # hier: vorhersagen, den Fehler ausrechnen, bei einem Fehler # weights und bias ändern und errors zählen ... print(f"Epoche {epoch + 1}: {errors} Fehler") if errors == 0: break return weights, bias
# Lernt Gewichte und Bias aus Beispielen [Merkmale, 0 oder 1], höchstens epochs Epochen lang def train(examples, epochs): weights = [0.0] * len(examples[0][0]) bias = 0.0 for epoch in range(epochs): errors = 0 for features, target in examples: error = target - predict(weights, bias, features) if error == 0: continue for i in range(len(features)): weights[i] += error * features[i] bias += error errors += 1 print(f"Epoche {epoch + 1}: {errors} Fehler") if errors == 0: break return weights, bias
Schreib in perzeptron/neuron.rb die Funktion train(examples, epochs).
Die Prüfung lässt sie »und« lernen (1 nur, wenn beide Eingaben 1 sind) und
»oder« (1, wenn mindestens eine 1 ist), und fragt dann das gelernte
Neuron mit predict.
Schreib in perzeptron/neuron.py die Funktion train(examples, epochs).
Die Prüfung lässt sie »und« lernen (1 nur, wenn beide Eingaben 1 sind)
und »oder« (1, wenn mindestens eine 1 ist), und fragt dann das gelernte
Neuron mit predict.
Gleich lernt dein Neuron aus 274 Adélie- und Eselspinguinen, höchstens 100 Epochen lang. Was passiert?
Schreib das Programm in learn.rb:
require_relative "neuron" examples = penguins("penguins.csv", ["Adelie", "Gentoo"]) weights, bias = train(examples, 100) puts "Gewichte: #{weights.map { |w| w.round(1) }}, Bias: #{bias.round(1)}"
ruby learn.rb
Schreib das Programm in learn.py:
from neuron import * examples = penguins("penguins.csv", ["Adelie", "Gentoo"]) weights, bias = train(examples, 100) print(f"Gewichte: {[round(w, 1) for w in weights]}, Bias: {round(bias, 1)}")
python3 learn.py
Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.
Nach wie vielen Epochen macht dein Neuron bei den Pinguinen keinen Fehler mehr? Gezählt wird die Epoche ohne Fehler mit.
Führe learn.rb (in Python learn.py) aus und lies die letzte Zeile
mit »Epoche«. Ist es eine andere Zahl, schau, ob deine Gewichte und der
Bias bei 0 anfangen.
Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.
Nach zwölf Epochen ordnet das Neuron alle 274 Pinguine richtig ein, mit den Gewichten 29.1 und −74.0 und dem Bias −1. Als Regel gelesen: Ein Pinguin ist ein Eselspinguin, wenn
$$29{,}1 \cdot \text{Länge} - 74 \cdot \text{Höhe} - 1 > 0$$
Die Länge hat ein positives Gewicht, die Höhe ein negatives: Ein langer, flacher Schnabel spricht für den Eselspinguin, ein kurzer, hoher für den Adéliepinguin. Niemand hat dem Neuron das gesagt; es hat die Regel aus 274 Beispielen.
Die Pinguine, für die die Summe genau 0 ist, liegen auf einer Geraden. Auf der
einen Seite sagt das Neuron 0, auf der anderen 1. Diese Funktion zeichnet die
Pinguine und die Gerade in eine Datei plot.svg, die VS Code als Bild öffnet.
Ein SVG-Bild ist Text, Kreise und Linien mit ihren Koordinaten; wie es aufgebaut
ist, steht im Handbuch unter SVG:
# Die Beispiele als Punkte (Länge nach rechts, Höhe nach oben) und die Gerade, # auf der die Summe des Neurons 0 ist, als SVG in path def plot(examples, weights, bias, path) x0, x1, y0, y1 = 30.0, 62.0, 12.0, 23.0 sx = ->(x) { (x - x0) / (x1 - x0) * 600 + 40 } sy = ->(y) { 440 - (y - y0) / (y1 - y0) * 400 } svg = ["<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 680 480'>", "<rect width='680' height='480' fill='white'/>"] examples.each do |(length, depth), target| colour = target == 1 ? "#2b5fb4" : "#d97706" svg << "<circle cx='#{sx.(length)}' cy='#{sy.(depth)}' r='4' fill='#{colour}'/>" end if weights[1] != 0 ya = -(weights[0] * x0 + bias) / weights[1] yb = -(weights[0] * x1 + bias) / weights[1] svg << "<line x1='#{sx.(x0)}' y1='#{sy.(ya)}' x2='#{sx.(x1)}' y2='#{sy.(yb)}' stroke='black' stroke-width='2'/>" end svg << "</svg>" File.write(path, svg.join("\n")) end
Und am Ende von learn.rb:
plot(examples, weights, bias, "plot.svg")
# Die Beispiele als Punkte (Länge nach rechts, Höhe nach oben) und die Gerade, # auf der die Summe des Neurons 0 ist, als SVG in path def plot(examples, weights, bias, path): x0, x1, y0, y1 = 30.0, 62.0, 12.0, 23.0 def sx(x): return (x - x0) / (x1 - x0) * 600 + 40 def sy(y): return 440 - (y - y0) / (y1 - y0) * 400 svg = ["<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 680 480'>", "<rect width='680' height='480' fill='white'/>"] for (length, depth), target in examples: colour = "#2b5fb4" if target == 1 else "#d97706" svg.append(f"<circle cx='{sx(length)}' cy='{sy(depth)}' r='4' fill='{colour}'/>") if weights[1] != 0: ya = -(weights[0] * x0 + bias) / weights[1] yb = -(weights[0] * x1 + bias) / weights[1] svg.append(f"<line x1='{sx(x0)}' y1='{sy(ya)}' x2='{sx(x1)}' y2='{sy(yb)}' stroke='black' stroke-width='2'/>") svg.append("</svg>") with open(path, "w") as f: f.write("\n".join(svg))
Und am Ende von learn.py:
plot(examples, weights, bias, "plot.svg")
train die Zahl der Epochen
auf 1, dann auf 3, und öffne plot.svg jedes Mal neu. Achte darauf,
wie die Gerade sich von Epoche zu Epoche zwischen die beiden Gruppen schiebt.
Dass das Neuron alle Pinguine richtig einordnet, aus denen es gelernt hat, sagt noch nicht viel: Vielleicht hat es sich nur genau diese gemerkt. Ob es die Regel gelernt hat, zeigt sich an Pinguinen, die es nie gesehen hat. Darum lernt es jetzt nur aus jedem zweiten Pinguin und wird an den anderen geprüft.
accuracy(weights, bias, examples),
die den Anteil der Beispiele zurückgibt, die das Neuron richtig einordnet, als
Zahl von 0 bis 1.
Zähl die Beispiele, bei denen predict die richtige Antwort gibt:
examples.count { |features, target| … }. Dann teile durch die Zahl aller
Beispiele, mit to_f vorher, denn in Ruby ergibt 3 / 4 die ganze Zahl 0.
# Der Anteil der Beispiele, die das Neuron richtig einordnet def accuracy(weights, bias, examples) right = examples.count { |features, target| predict(weights, bias, features) == target } right.to_f / examples.size end
Zähl die Beispiele, bei denen predict die richtige Antwort gibt, in einer
Schleife über for features, target in examples:. Dann teile durch
len(examples).
# Der Anteil der Beispiele, die das Neuron richtig einordnet def accuracy(weights, bias, examples): right = sum(1 for features, target in examples if predict(weights, bias, features) == target) return right / len(examples)
Schreib in perzeptron/neuron.rb die Funktion
accuracy(weights, bias, examples). Die Prüfung gibt ihr ein Neuron und
vier Beispiele, von denen es drei richtig einordnet.
Schreib in perzeptron/neuron.py die Funktion
accuracy(weights, bias, examples). Die Prüfung gibt ihr ein Neuron und
vier Beispiele, von denen es drei richtig einordnet.
In learn.rb, statt aus allen Pinguinen zu lernen:
examples = penguins("penguins.csv", ["Adelie", "Gentoo"])training = examples.select.with_index { |_, i| i.even? }test = examples.select.with_index { |_, i| i.odd? }weights, bias = train(training, 100)puts "Richtig bei Pinguinen, die es nie gesehen hat: #{(accuracy(weights, bias, test) * 100).round(1)} %"
In learn.py, statt aus allen Pinguinen zu lernen:
examples = penguins("penguins.csv", ["Adelie", "Gentoo"])training = examples[0::2]test = examples[1::2]weights, bias = train(training, 100)print(f"Richtig bei Pinguinen, die es nie gesehen hat: {round(accuracy(weights, bias, test) * 100, 1)} %")
Wie viel Prozent der Pinguine, aus denen es nicht gelernt hat, ordnet dein Neuron richtig ein?
Auf der Insel Dream lebt eine dritte Art, der Zügelpinguin (englisch Chinstrap). Kann das Neuron ihn vom Adéliepinguin unterscheiden?
Ändere in learn.rb (in Python learn.py) die zweite Art zu
"Chinstrap" und lass das Neuron lernen. Was passiert in 100 Epochen?
Vermute erst, dann probier es aus.
Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.
Das Neuron hört nie auf, Fehler zu machen. Nach jeder Epoche verschiebt es die Gerade wieder, weil es einen Pinguin falsch einordnet, und dabei rutscht ein anderer auf die falsche Seite. Es gibt keine Gerade, die alle Adélie- und alle Zügelpinguine trennt: Ein Zügelpinguin hat einen Schnabel, wie ihn sonst nur Adéliepinguine haben. Ein Neuron kann nur trennen, was sich mit einer Geraden trennen lässt.
Das gilt auch für Aufgaben, bei denen es keinen einzigen Ausreißer gibt. Das bekannteste Beispiel ist XOR, »entweder oder«: Die Antwort ist 1, wenn genau eine von zwei Eingaben 1 ist.
| Eingabe 1 | Eingabe 2 | XOR |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
train([[[0, 0], 0], [[0, 1], 1], [[1, 0], 1], [[1, 1], 0]], 20).
Achte darauf, wie viele Fehler es in den letzten Epochen macht.
Die vier Punkte liegen an den Ecken eines Quadrats, die beiden mit der Antwort 1 schräg gegenüber. Keine Gerade hat die beiden 1 auf der einen und die beiden 0 auf der anderen Seite. Das bewiesen Marvin Minsky und Seymour Papert 1969 in ihrem Buch »Perceptrons«, und für viele Jahre glaubte kaum jemand mehr an lernende Neuronen.
Die Lösung kam später: mehrere Neuronen in Schichten hintereinander. Zwei Neuronen in einer ersten Schicht ziehen zwei Geraden, ein drittes dahinter entscheidet aus ihren Antworten. So geht XOR, und mit vielen Schichten und Millionen von Neuronen gehen auch Bilder, Sprache und Spiele. Wie man die Gewichte vieler Schichten lernt, ist schwieriger als bei einem einzelnen Neuron und kam erst in den 1980er Jahren in Gang.
Dein Neuron kennt nichts von Pinguinen. Es nimmt Zahlen, multipliziert sie mit Gewichten und vergleicht die Summe mit 0. Ob die Zahlen Schnäbel sind oder etwas anderes, ist ihm gleich.
Handgeschriebene Ziffern, auf 8 mal 8 Kästchen verkleinert, sind 64 Zahlen: wie
dunkel jedes Kästchen ist, von 0 (weiß) bis 16 (schwarz). 43 Menschen haben
dafür Ziffern geschrieben, Forschende an der Universität Boğaziçi in Istanbul
haben sie 1998 gesammelt. Zwei Dateien: Aus digits-train.csv lernt das
Neuron, an digits-test.csv wird es geprüft, mit Ziffern von Menschen, deren
Handschrift es nie gesehen hat. Jede Zeile hat 65 Zahlen, die 64 Kästchen
Zeile für Zeile und am Ende die Ziffer.
wget https://workspace.hackschule.de/perzeptron/digits-train.csv https://workspace.hackschule.de/perzeptron/digits-test.csv
# Die Ziffern a und b als [64 Kästchen, 0 für a oder 1 für b] def digits(path, a, b) File.readlines(path).filter_map do |line| values = line.split(",").map(&:to_i) next unless [a, b].include?(values[64]) [values[0, 64], values[64] == b ? 1 : 0] end end
# Die Ziffern a und b als [64 Kästchen, 0 für a oder 1 für b] def digits(path, a, b): examples = [] for line in open(path): values = [int(v) for v in line.split(",")] if values[64] in (a, b): examples.append([values[:64], 1 if values[64] == b else 0]) return examples
Dein Neuron lernt aus digits-train.csv, Nullen von Einsen zu
unterscheiden, und wird an den Ziffern in digits-test.csv geprüft, die
andere Menschen geschrieben haben. Wie viele davon ordnet es richtig ein?
In learn.rb, statt der Pinguine:
training = digits("digits-train.csv", 0, 1) test = digits("digits-test.csv", 0, 1) weights, bias = train(training, 100) puts "Richtig bei fremden Handschriften: #{(accuracy(weights, bias, test) * 100).round(1)} %"
In learn.py, statt der Pinguine:
training = digits("digits-train.csv", 0, 1) test = digits("digits-test.csv", 0, 1) weights, bias = train(training, 100) print(f"Richtig bei fremden Handschriften: {round(accuracy(weights, bias, test) * 100, 1)} %")
Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.
Mit 0 gegen 1 lernt das Neuron in zehn Epochen fehlerfrei, und auch die Ziffern der anderen Menschen ordnet es alle richtig ein. Schwieriger sind Ziffern, die sich ähnlich sehen.
Lass dein Neuron 3 gegen 8 lernen, aus digits-train.csv, und miss mit
accuracy, wie viel Prozent der Ziffern in digits-test.csv es richtig
einordnet. Runde auf eine Stelle nach dem Komma.
Wie bei den Pinguinen: train mit digits("digits-train.csv", 3, 8),
dann accuracy mit digits("digits-test.csv", 3, 8), mal 100.
Die 64 Gewichte gehören zu den 64 Kästchen. Ein großes positives Gewicht heißt: Ist dieses Kästchen dunkel, spricht das für die zweite Ziffer; ein negatives: für die erste. Zeichnest du die Gewichte als 8 mal 8 Kästchen, blau für positiv und orange für negativ, siehst du, worauf das Neuron achtet.
# Die 64 Gewichte als 8 × 8 Kästchen, blau positiv, orange negativ, als SVG in path def picture(weights, path) top = weights.map(&:abs).max svg = ["<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 160'>"] weights.each_with_index do |w, i| colour = w > 0 ? "43,95,180" : "217,119,6" opacity = (w.abs / top).round(2) svg << "<rect x='#{i % 8 * 20}' y='#{i / 8 * 20}' width='20' height='20' fill='rgb(#{colour})' fill-opacity='#{opacity}'/>" end svg << "</svg>" File.write(path, svg.join("\n")) end
# Die 64 Gewichte als 8 × 8 Kästchen, blau positiv, orange negativ, als SVG in path def picture(weights, path): top = max(abs(w) for w in weights) svg = ["<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 160'>"] for i, w in enumerate(weights): colour = "43,95,180" if w > 0 else "217,119,6" opacity = round(abs(w) / top, 2) svg.append(f"<rect x='{i % 8 * 20}' y='{i // 8 * 20}' width='20' height='20' fill='rgb({colour})' fill-opacity='{opacity}'/>") svg.append("</svg>") with open(path, "w") as f: f.write("\n".join(svg))
picture(weights, "weights.svg"). Achte darauf,
wo die blauen Kästchen liegen und wo die orangen, und vergleiche mit einer
geschriebenen 0 und einer 1.
Das Neuron hat aus den Ziffern von 30 Menschen gelernt. Erkennt es auch deine? Dafür bringst du dein Neuron hierher, auf die Seite. Lass dein Programm, nachdem es 0 gegen 1 gelernt hat, die 64 Gewichte und den Bias ausgeben, als eine Zeile mit 65 Zahlen; schreib dazu ans Ende deines Programms:
puts (weights + [bias]).join(" ")
print(" ".join(str(v) for v in weights + [bias]))
Dann:
Wie bei den Ziffern, aus denen das Neuron gelernt hat, wird deine Zeichnung auf 32 mal 32 Punkte gebracht, so dass sie die Fläche füllt, und daraus werden die 64 Werte. Hast du noch kein Neuron, nimm das Beispiel: Es hat 0 gegen 1 gelernt, so wie dein Neuron mit dem Programm oben.