ProjektBaut auf Katas: Listen auf
image: karte.webp image_alt: "Der Süden einer ausgedachten Stadt mit einem Fluss, darunter der Stadtteil Sandberg. Punkte für Kundinnen und Kunden, orange mit Kaution, blau ohne; am Sandberg sind die meisten orange. In der Mitte ein neuer Kunde mit einem Fragezeichen, von dem Linien zu seinen fünf nächsten Nachbarn gehen, drei davon orange." kind: projekt duration: 2 Doppelstunden languages: [ruby, python] curriculum: "Sek I: Algorithmisches Problemlösen (Implementieren von Algorithmen), Leben in und mit vernetzten Systemen (Datenschutz, rechtliche Grundlagen); Oberstufe: Vertiefung Künstliche Intelligenz (Klassifizieren nach den nächsten Nachbarn), Vertiefung Informatik und Gesellschaft (was ein Programm aus alten Entscheidungen übernimmt, wer verantwortlich bleibt)" topics: [ki, datenschutz, gesellschaft] requires: [kata-lists]

Nächste Nachbarn: Wer entscheidet hier?

Ein Programm kann entscheiden, ohne eine Regel zu kennen: Es sieht nach, wie bei den Beispielen entschieden wurde, die dem neuen Fall am ähnlichsten sind. Du schreibst so ein Programm, erst für Pinguine, dann für einen Fahrradverleih, der wissen will, wer eine Kaution zahlen soll. Dabei siehst du, was das Programm aus alten Entscheidungen übernimmt, auch das, was daran falsch war.

1. Wer ist wie die anderen?

Auf den Inseln vor der Antarktischen Halbinsel leben drei Arten von Pinguinen: der Adéliepinguin, der Zügelpinguin (englisch Chinstrap) und der Eselspinguin (Gentoo). Die Biologin Kristen Gorman hat an 342 von ihnen die Länge und die Höhe des Schnabels gemessen, in Millimetern.

Jemand misst einen weiteren Pinguin, aber schreibt die Art nicht dazu. Welcher Art er ist, lässt sich raten, ohne eine Regel zu kennen: Man sucht unter den 342 bekannten Pinguinen die, deren Schnabel seinem am ähnlichsten ist, und nimmt die Art, die unter ihnen am häufigsten ist. Das sind seine nächsten Nachbarn, und das Verfahren heißt danach: k nächste Nachbarn, wobei k sagt, wie viele Nachbarn gefragt werden.

Mit k = 1 wäre der unbekannte Pinguin ein Eselspinguin, mit k = 5 ein Zügelpinguin. Ein einzelner Nachbar kann ein Ausreißer sein; mehrere Nachbarn stimmen ab.

Beschrieben haben das Verfahren die Statistikerin Evelyn Fix und der Statistiker Joseph Hodges 1951, in einem Bericht für die Luftwaffe der USA. Es ist bis heute eines der einfachsten Verfahren, mit denen ein Programm aus Beispielen lernt, und eines der durchsichtigsten: Zu jeder Entscheidung kann es sagen, welche Beispiele dahinterstehen.

Discriminatory Analysis. Nonparametric Discrimination: Consistency Properties, Evelyn Fix und Joseph L. Hodges, USAF School of Aviation Medicine, 1951. Der Bericht, in dem ein neuer Fall die Antwort seiner nächsten Nachbarn bekommt: als PDF beim Defense Technical Information Center, 1989 neu gedruckt in der International Statistical Review.

2. Einrichten

Lege einen Ordner an und lade die Daten herunter:

mkdir -p ~/nachbarn
cd ~/nachbarn
wget https://workspace.hackschule.de/naechste-nachbarn/penguins.csv https://workspace.hackschule.de/naechste-nachbarn/customers.csv
touch knn.rb penguins.rb

In knn.rb kommen die Funktionen, in penguins.rb das Programm, das sie für die Pinguine benutzt. Ausprobieren kannst du die Funktionen in irb, das knn.rb vorher lädt:

irb -r ./knn.rb

Lege einen Ordner an und lade die Daten herunter:

mkdir -p ~/nachbarn
cd ~/nachbarn
wget https://workspace.hackschule.de/naechste-nachbarn/penguins.csv https://workspace.hackschule.de/naechste-nachbarn/customers.csv
touch knn.py penguins.py

In knn.py kommen die Funktionen, in penguins.py das Programm, das sie für die Pinguine benutzt. Ausprobieren kannst du die Funktionen in Python, das knn.py vorher ausführt:

python3 -i knn.py

penguins.csv hat eine Zeile je Pinguin: die Art, die Insel, die Länge und die Höhe des Schnabels, die Länge der Flosse und das Gewicht.

species,island,bill_length_mm,bill_depth_mm,flipper_length_mm,body_mass_g
Adelie,Torgersen,39.1,18.7,181,3750

3. Wie weit zwei Pinguine auseinanderliegen

Trägst du die zwei Maße eines Schnabels als Punkt in ein Diagramm ein, die Länge nach rechts und die Höhe nach oben, dann ist »ähnlich« dasselbe wie »nah«. Wie weit zwei Punkte auseinanderliegen, rechnest du mit dem Satz des Pythagoras aus: Die Unterschiede in jeder Richtung sind die Katheten, der Abstand ist die Hypotenuse.

$$d = \sqrt{(a_1 - b_1)^2 + (a_2 - b_2)^2}$$

Bei mehr als zwei Merkmalen kommt für jedes ein Quadrat unter die Wurzel dazu. Die Formel bleibt dieselbe, nur zeichnen kann man den Raum dann nicht mehr.

Aufgabe 1Von Hand gemessen

Ein Pinguin hat einen Schnabel, der 39 mm lang und 18 mm hoch ist, ein anderer einen, der 42 mm lang und 14 mm hoch ist. Wie weit liegen die beiden Punkte auseinander?

Tipp

Die Unterschiede sind 3 und 4. Quadriere sie, addiere, zieh die Wurzel.

Jetzt bist du dran: Schreib in knn die Funktion distance(a, b): a und b sind zwei gleich lange Listen von Zahlen, und die Funktion gibt zurück, wie weit die beiden Punkte auseinanderliegen. Später haben die Punkte vier Merkmale; schreib sie so, dass sie mit jeder Anzahl geht.
Tipp

Geh die Listen mit einem Index durch: a.each_index do |i| … end. Für jeden Index kommt das Quadrat des Unterschieds zu einer Summe dazu.

Nächster Tipp

Das Quadrat ist ** 2, die Wurzel Math.sqrt(sum). Beginn die Summe mit 0.0, dann bleibt alles eine Kommazahl.

Lösung anzeigen
# Wie weit zwei Punkte auseinanderliegen, mit beliebig vielen Merkmalen
def distance(a, b)
  sum = 0.0
  a.each_index do |i|
    sum += (a[i] - b[i])**2
  end
  Math.sqrt(sum)
end
Tipp

Geh die Listen mit einem Index durch: for i in range(len(a)):. Für jeden Index kommt das Quadrat des Unterschieds zu einer Summe dazu.

Nächster Tipp

Das Quadrat ist ** 2, die Wurzel math.sqrt(total); dafür steht oben in der Datei import math.

Lösung anzeigen
import math


# Wie weit zwei Punkte auseinanderliegen, mit beliebig vielen Merkmalen
def distance(a, b):
    total = 0.0
    for i in range(len(a)):
        total += (a[i] - b[i]) ** 2
    return math.sqrt(total)
Aufgabe 2Der Abstand

Schreib in nachbarn/knn.rb die Funktion distance(a, b). Die Prüfung ruft sie mit zwei und mit drei Merkmalen auf.

Aufgabe 2Der Abstand

Schreib in nachbarn/knn.py die Funktion distance(a, b). Die Prüfung ruft sie mit zwei und mit drei Merkmalen auf.

4. Die nächsten Nachbarn

Ein Beispiel ist ein Paar: die Merkmale und die Antwort, die dazugehört. Bei einem Pinguin sind das die zwei Maße des Schnabels und seine Art: [[39.1, 18.7], "Adelie"]. Die nächsten Nachbarn eines Punktes findest du, indem du alle Beispiele nach ihrem Abstand zu ihm sortierst und die ersten nimmst.

Jetzt bist du dran: Schreib die Funktion nearest(examples, features, k): Sie gibt die k Beispiele zurück, deren Merkmale am nächsten an features liegen, das nächste zuerst.
Tipp

sort_by sortiert eine Liste nach dem Wert, den der Block für jedes Element ausrechnet. Ein Beispiel kannst du im Block gleich in seine zwei Teile zerlegen: |other, label|.

Nächster Tipp

Die ersten k Elemente einer Liste gibt first(k).

Lösung anzeigen
# Die k Beispiele, deren Merkmale features am nächsten liegen, das nächste zuerst
def nearest(examples, features, k)
  examples.sort_by { |other, _label| distance(other, features) }.first(k)
end
Tipp

sorted(examples, key=…) sortiert eine Liste nach dem Wert, den die Funktion bei key für jedes Element ausrechnet. Eine kurze Funktion schreibst du gleich dorthin: lambda example: …. Die Merkmale eines Beispiels sind example[0].

Nächster Tipp

Die ersten k Elemente einer Liste gibt [:k].

Lösung anzeigen
# Die k Beispiele, deren Merkmale features am nächsten liegen, das nächste zuerst
def nearest(examples, features, k):
    return sorted(examples, key=lambda example: distance(example[0], features))[:k]
Aufgabe 3Die nächsten Nachbarn

Schreib in nachbarn/knn.rb die Funktion nearest(examples, features, k). Die Prüfung gibt ihr vier Beispiele und fragt nach den zwei, drei und einem nächsten.

Aufgabe 3Die nächsten Nachbarn

Schreib in nachbarn/knn.py die Funktion nearest(examples, features, k). Die Prüfung gibt ihr vier Beispiele und fragt nach den zwei, drei und einem nächsten.

5. Die Nachbarn stimmen ab

Jetzt fehlt die Abstimmung: Welche Antwort kommt unter den k nächsten Nachbarn am häufigsten vor? Kommen zwei gleich oft vor, gewinnt die, die unter den Nachbarn zuerst kommt, also zum näheren gehört.

Jetzt bist du dran: Schreib die Funktion classify(examples, features, k): Sie gibt die Antwort zurück, die unter den k nächsten Nachbarn von features am häufigsten ist; bei Gleichstand die des näheren Nachbarn.
Tipp

Mach aus den Nachbarn erst eine Liste ihrer Antworten, mit map, in derselben Reihenfolge, das nächste zuerst.

Nächster Tipp

labels.count(label) zählt, wie oft eine Antwort in der Liste steht. max_by gibt das Element, für das der Block am größten ist, und bei Gleichstand das, das in der Liste zuerst kommt.

Lösung anzeigen
# Was die meisten der k nächsten Nachbarn sind; bei Gleichstand das, was zuerst kommt
def classify(examples, features, k)
  labels = nearest(examples, features, k).map { |_features, label| label }
  labels.max_by { |label| labels.count(label) }
end
Tipp

Mach aus den Nachbarn erst eine Liste ihrer Antworten, in derselben Reihenfolge, das nächste zuerst: [label for _features, label in …].

Nächster Tipp

labels.count(label) zählt, wie oft eine Antwort in der Liste steht. max(labels, key=labels.count) gibt die Antwort, die am häufigsten ist, und bei Gleichstand die, die in der Liste zuerst kommt.

Lösung anzeigen
# Was die meisten der k nächsten Nachbarn sind; bei Gleichstand das, was zuerst kommt
def classify(examples, features, k):
    labels = [label for _features, label in nearest(examples, features, k)]
    return max(labels, key=labels.count)
Aufgabe 4Die Nachbarn stimmen ab

Schreib in nachbarn/knn.rb die Funktion classify(examples, features, k). Die Prüfung gibt ihr fünf Beispiele, zwei »A« nahe bei (0, 0) und drei »B«, und fragt mit verschiedenen k, auch mit einem Gleichstand.

Aufgabe 4Die Nachbarn stimmen ab

Schreib in nachbarn/knn.py die Funktion classify(examples, features, k). Die Prüfung gibt ihr fünf Beispiele, zwei »A« nahe bei (0, 0) und drei »B«, und fragt mit verschiedenen k, auch mit einem Gleichstand.

Damit das Programm die Pinguine kennt, liest diese Funktion die Datei. Sie gibt für jeden Pinguin ein Beispiel zurück: die zwei Maße des Schnabels und die Art. Schreib sie unter die anderen in dieselbe Datei:

# Die Pinguine als [[Länge, Höhe], Art]
def penguins(path)
  File.readlines(path).drop(1).map do |line|
    species, _island, length, depth = line.split(",")
    [[length.to_f, depth.to_f], species]
  end
end

Und in penguins.rb das Programm:

require_relative "knn"

examples = penguins("penguins.csv")
puts classify(examples, [46.0, 18.0], 5)
ruby penguins.rb
# Die Pinguine als [[Länge, Höhe], Art]
def penguins(path):
    examples = []
    for line in open(path).readlines()[1:]:
        species, _island, length, depth = line.split(",")[:4]
        examples.append([[float(length), float(depth)], species])
    return examples

Und in penguins.py das Programm:

from knn import *

examples = penguins("penguins.csv")
print(classify(examples, [46.0, 18.0], 5))
python3 penguins.py
Aufgabe 5Ein unbekannter Pinguin

Welche Art sagt dein Programm für den Pinguin mit einem Schnabel, der 46 mm lang und 18 mm hoch ist, mit den fünf nächsten Nachbarn?

Ein unbekannter Pinguin
Tipp

Führe penguins.rb aus (in Python penguins.py) und lies, was es ausgibt.

6. Wie oft liegt es richtig?

Gelernt hat das Programm eigentlich nichts. Es hebt alle Beispiele auf und sieht bei jeder Frage nach. Ob das reicht, zeigt sich an Pinguinen, deren Art es nicht kennt: Es bekommt nur jeden zweiten Pinguin als Beispiel und muss die anderen einordnen, deren Art man kennt und vergleichen kann.

Jetzt bist du dran: Schreib die Funktion accuracy(training, test, k): Sie ordnet jedes Beispiel aus test mit classify nach den Beispielen in training ein und gibt den Anteil zurück, bei dem die Antwort stimmt, als Zahl von 0 bis 1.
Tipp

Zähl die Testbeispiele, bei denen classify die Antwort des Beispiels gibt: test.count { |features, label| … }.

Nächster Tipp

Teile durch die Zahl aller Testbeispiele, mit to_f vorher, denn in Ruby ergibt 3 / 4 die ganze Zahl 0.

Lösung anzeigen
# Der Anteil der Testbeispiele, die classify richtig einordnet
def accuracy(training, test, k)
  right = test.count { |features, label| classify(training, features, k) == label }
  right.to_f / test.size
end
Tipp

Zähl die Testbeispiele, bei denen classify die Antwort des Beispiels gibt, in einer Schleife über for features, label in test:.

Nächster Tipp

Teile die Zahl durch len(test).

Lösung anzeigen
# Der Anteil der Testbeispiele, die classify richtig einordnet
def accuracy(training, test, k):
    right = sum(1 for features, label in test if classify(training, features, k) == label)
    return right / len(test)
Aufgabe 6Wie oft richtig?

Schreib in nachbarn/knn.rb die Funktion accuracy(training, test, k). Die Prüfung gibt ihr vier Beispiele zum Vergleichen und vier Testbeispiele, von denen zwei richtig eingeordnet werden.

Aufgabe 6Wie oft richtig?

Schreib in nachbarn/knn.py die Funktion accuracy(training, test, k). Die Prüfung gibt ihr vier Beispiele zum Vergleichen und vier Testbeispiele, von denen zwei richtig eingeordnet werden.

In penguins.rb, statt den einen Pinguin einzuordnen:

examples = penguins("penguins.csv")training = examples.select.with_index { |_, i| i.even? }test = examples.select.with_index { |_, i| i.odd? }puts "Richtig: #{(accuracy(training, test, 3) * 100).round(1)} %"

In penguins.py, statt den einen Pinguin einzuordnen:

examples = penguins("penguins.csv")training = examples[0::2]test = examples[1::2]print(f"Richtig: {round(accuracy(training, test, 3) * 100, 1)} %")
Aufgabe 7Pinguine, die es nicht kennt

Wie viel Prozent der Testpinguine ordnet dein Programm mit k = 3 richtig ein? Runde auf eine Stelle nach dem Komma.

%
Tipp

Führe penguins.rb (in Python penguins.py) mit dem geänderten Programm aus. Ist es eine andere Zahl, prüf, ob training die Pinguine mit geradem Index bekommt und test die mit ungeradem.

Ausprobieren: Ändere k auf 1, 5, 15 und 51. Achte darauf, ab welchem k das Programm deutlich schlechter wird.
Vermutung 8Alle Nachbarn

In training sind 171 Pinguine. Was passiert, wenn das Programm mit k = 171 abstimmt? Vermute erst, dann probier es aus.

Alle Nachbarn

Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.

Mit k = 171 fragt das Programm jedes Mal alle Pinguine, die es kennt, und die häufigste Art unter ihnen ist immer dieselbe: der Adéliepinguin. Dann sagt es zu jedem Pinguin »Adelie« und liegt bei 43,9 Prozent richtig, so oft, wie unter den Testpinguinen Adéliepinguine sind. Ein kleines k hört auf einzelne Nachbarn, auch auf Ausreißer; ein großes k sieht nicht mehr hin, wo der Punkt liegt. Dazwischen liegt das k, mit dem das Programm am besten ist, und das muss man ausprobieren.

7. Ein Fahrradverleih entscheidet

Ab hier ist alles ausgedacht. In einer Stadt, durch die ein Fluss fließt, verleiht ein Laden Fahrräder. Wer ein Rad leiht, zahlt manchmal 50 Euro Kaution, die er zurückbekommt, wenn das Rad pünktlich und heil wiederkommt. Ob jemand eine Kaution zahlen muss, haben bisher die Leute im Laden entschieden, und sie haben zu jeder Kundin und jedem Kunden aufgeschrieben:

Spalte Was darin steht
district der Stadtteil, in dem die Person wohnt
x, y wo sie wohnt, in Kilometern östlich und nördlich der Südwestecke der Stadt
distance wie weit sie vom Laden entfernt wohnt, in Kilometern
years seit wie vielen Jahren sie Kundin oder Kunde ist
late wie oft sie im letzten Jahr ein Rad zu spät zurückgebracht hat
deposit die Entscheidung des Ladens: 1 heißt Kaution, 0 keine
on_time ob sie das nächste Rad pünktlich zurückgebracht hat: 1 heißt ja

Jetzt soll ein Programm entscheiden, damit es schneller geht und, wie der Laden sagt, gerechter. Es lernt aus den 300 Entscheidungen der letzten Jahre und entscheidet über die nächsten 300 Kundinnen und Kunden. Die Funktionen hast du schon; dazu kommen zwei, die die Datei lesen. Schreib sie unter die anderen in deine Datei mit den Funktionen:

# Jede Kundin und jeder Kunde als Hash: Spalte => Wert, Zahlen als Zahlen
def customers(path)
  lines = File.readlines(path, chomp: true)
  keys = lines[0].split(",")
  lines.drop(1).map do |line|
    keys.zip(line.split(",")).to_h { |key, value| [key, key == "district" ? value : value.to_f] }
  end
end

# Beispiele aus Kunden: die Spalten in columns als Merkmale, die Kaution als Antwort
def examples(people, columns)
  people.map { |person| [columns.map { |column| person[column] }, person["deposit"].to_i] }
end

Und das Programm in einer neuen Datei deposit.rb:

require_relative "knn"

people = customers("customers.csv")
past = people.select.with_index { |_, i| i.even? }
latest = people.select.with_index { |_, i| i.odd? }
columns = ["years", "late", "x", "y"]
training = examples(past, columns)
test = examples(latest, columns)
puts "Gleich entschieden wie der Laden: #{(accuracy(training, test, 5) * 100).round(1)} %"
ruby deposit.rb
# Jede Kundin und jeder Kunde als Dictionary: Spalte -> Wert, Zahlen als Zahlen
def customers(path):
    lines = open(path).read().splitlines()
    keys = lines[0].split(",")
    people = []
    for line in lines[1:]:
        person = {}
        for key, value in zip(keys, line.split(",")):
            person[key] = value if key == "district" else float(value)
        people.append(person)
    return people


# Beispiele aus Kunden: die Spalten in columns als Merkmale, die Kaution als Antwort
def examples(people, columns):
    return [[[person[column] for column in columns], int(person["deposit"])] for person in people]

Und das Programm in einer neuen Datei deposit.py:

from knn import *

people = customers("customers.csv")
past = people[0::2]
latest = people[1::2]
columns = ["years", "late", "x", "y"]
training = examples(past, columns)
test = examples(latest, columns)
print(f"Gleich entschieden wie der Laden: {round(accuracy(training, test, 5) * 100, 1)} %")
python3 deposit.py

Für die neuen Kundinnen und Kunden weiß man, wie der Laden entschieden hätte; accuracy vergleicht damit, was das Programm sagt. Die Merkmale sind die Jahre, die Verspätungen und der Wohnort, so wie die Leute im Laden ihre Kundschaft kannten.

Aufgabe 9Wie der Laden

Wie viel Prozent der neuen Kundinnen und Kunden ordnet dein Programm genauso ein, wie der Laden es getan hätte? Runde auf eine Stelle nach dem Komma.

%
Tipp

Führe deposit.rb (in Python deposit.py) aus. Ist es eine andere Zahl, prüf, ob die Spalten years, late, x und y sind und k 5 ist.

8. Wen trifft es?

Eine Kaution von jemandem zu verlangen, der jedes Rad pünktlich zurückbringt, ist ein Fehler zu seinen Lasten: Er muss 50 Euro haben, die er für nichts hinterlegt. Ob ein Programm gerecht entscheidet, zeigt sich daran, wen solche Fehler treffen.

Vermutung 10Alle gleich?

Das Programm weiß nicht, in welchem Stadtteil jemand wohnt; es kennt nur die Jahre, die Verspätungen und den Wohnort als zwei Zahlen. Von wem verlangt es eine Kaution, obwohl er sein Rad pünktlich zurückbringt?

Alle gleich?
Jetzt bist du dran: Schreib die Funktion deposit_share(training, test, k): Sie gibt den Anteil der Beispiele in test zurück, für die classify nach training eine Kaution verlangt, also 1 sagt, als Zahl von 0 bis 1. Die Antworten in test zählen dabei nicht.
Tipp

Sie sieht aus wie accuracy. Nur zählt sie nicht, wo classify die Antwort des Beispiels gibt, sondern wo es 1 gibt.

Nächster Tipp
def deposit_share(training, test, k)
  deposits = test.count { |features, _label| … }
  # hier: durch die Zahl der Testbeispiele teilen, als Kommazahl
end
Lösung anzeigen
# Der Anteil der Testbeispiele, für die classify eine Kaution verlangt (1)
def deposit_share(training, test, k)
  deposits = test.count { |features, _label| classify(training, features, k) == 1 }
  deposits.to_f / test.size
end
Tipp

Sie sieht aus wie accuracy. Nur zählt sie nicht, wo classify die Antwort des Beispiels gibt, sondern wo es 1 gibt.

Nächster Tipp
def deposit_share(training, test, k):
    deposits = sum(1 for features, _label in test if ...)
    # hier: durch die Zahl der Testbeispiele teilen
Lösung anzeigen
# Der Anteil der Testbeispiele, für die classify eine Kaution verlangt (1)
def deposit_share(training, test, k):
    deposits = sum(1 for features, _label in test if classify(training, features, k) == 1)
    return deposits / len(test)
Aufgabe 11Wie oft eine Kaution?

Schreib in nachbarn/knn.rb die Funktion deposit_share(training, test, k). Die Prüfung gibt ihr vier Beispiele zum Vergleichen und Testbeispiele, deren Antworten nicht zählen.

Aufgabe 11Wie oft eine Kaution?

Schreib in nachbarn/knn.py die Funktion deposit_share(training, test, k). Die Prüfung gibt ihr vier Beispiele zum Vergleichen und Testbeispiele, deren Antworten nicht zählen.

Am Ende von deposit.rb: für jeden Stadtteil die neuen Kundinnen und Kunden, die ihr Rad pünktlich zurückgebracht haben, und wie viele davon nach dem Programm eine Kaution zahlen sollen:

["Altstadt", "Lindenhof", "Bahnhofsviertel", "Sandberg"].each do |district|
  punctual = latest.select { |person| person["district"] == district && person["on_time"] == 1 }
  share = deposit_share(training, examples(punctual, columns), 5)
  puts "#{district}: #{(share * 100).round(1)} % der Pünktlichen sollen Kaution zahlen"
end

Am Ende von deposit.py: für jeden Stadtteil die neuen Kundinnen und Kunden, die ihr Rad pünktlich zurückgebracht haben, und wie viele davon nach dem Programm eine Kaution zahlen sollen:

for district in ["Altstadt", "Lindenhof", "Bahnhofsviertel", "Sandberg"]:
    punctual = [person for person in latest if person["district"] == district and person["on_time"] == 1]
    share = deposit_share(training, examples(punctual, columns), 5)
    print(f"{district}: {round(share * 100, 1)} % der Pünktlichen sollen Kaution zahlen")
Aufgabe 12Die Pünktlichen am Sandberg

Wie viel Prozent der Pünktlichen am Sandberg sollen nach deinem Programm eine Kaution zahlen? Runde auf eine Stelle nach dem Komma.

%
Tipp

Führe deposit.rb (in Python deposit.py) mit der Schleife über die Stadtteile aus und lies die Zeile »Sandberg«.

Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.

Stadtteil Pünktliche Kaution nach dem Laden Kaution nach dem Programm
Altstadt 43 18,6 % 14,0 %
Lindenhof 63 28,6 % 15,9 %
Bahnhofsviertel 69 24,6 % 18,8 %
Sandberg 85 70,6 % 84,7 %

Wer am Sandberg wohnt und sein Rad pünktlich zurückbringt, soll in mehr als acht von zehn Fällen eine Kaution zahlen; in den anderen Stadtteilen in ein bis zwei von zehn. Das Programm kennt keine Stadtteile, nur Zahlen. Aber zwei der Zahlen sind der Wohnort, und die nächsten Nachbarn eines Menschen am Sandberg wohnen am Sandberg, im wörtlichen Sinn. Der Laden hat dort öfter eine Kaution verlangt, also verlangt sie das Programm dort auch.

Warum der Laden so entschieden hat, steht in keiner Spalte. Die Daten sind ausgedacht, und das Programm, das sie erzeugt hat, sagt es: Wer am Sandberg wohnt, ist genauso zuverlässig wie alle anderen, aber der Laden hat dort von sechs in zehn Leuten, die er sonst ohne Kaution gelassen hätte, trotzdem eine verlangt. Und das Programm ist sogar strenger als der Laden: Haben drei von fünf Nachbarn eine Kaution gezahlt, verlangt es sie immer. Aus »meistens« wird bei einer Abstimmung »immer«.

Vermutung 13Ohne Wohnort

Nimm in deposit.rb (in Python deposit.py) die Spalten x und y aus columns, so dass nur years und late bleiben. Was passiert mit den Pünktlichen am Sandberg? Vermute erst, dann probier es aus.

Ohne Wohnort

Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.

Ohne x und y sollen von den Pünktlichen am Sandberg 28,2 Prozent Kaution zahlen, in der Altstadt 18,6, in Lindenhof 27,0 und im Bahnhofsviertel 27,5. Die Stadtteile werden fast gleich behandelt. Aber nicht, weil das Programm jetzt gerechte Beispiele hätte: Es lernt immer noch aus den alten Entscheidungen, nur kann es den Wohnort nicht mehr sehen. Die Kautionen, die der Laden am Sandberg zu Unrecht verlangt hat, verteilen sich jetzt auf alle: In Lindenhof und im Bahnhofsviertel trifft es mehr Pünktliche als vorher.

Den Wohnort wegzulassen, scheint zu reichen. Aber in der Datei steht noch eine Spalte, die mit dem Wohnort zu tun hat: wie weit jemand vom Laden entfernt wohnt. Der Laden liegt in der Altstadt, nördlich des Flusses.

Aufgabe 14Die Entfernung

Nimm statt x und y die Spalte distance dazu, so dass columns years, late und distance sind. Wie viel Prozent der Pünktlichen am Sandberg sollen jetzt eine Kaution zahlen? Runde auf eine Stelle nach dem Komma.

%

Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.

Mit der Entfernung statt des Wohnorts sollen 56,5 Prozent der Pünktlichen am Sandberg eine Kaution zahlen. Die Entfernung verrät ungefähr den Stadtteil: Wer weit weg wohnt, wohnt oft am Sandberg. Ein Merkmal, das für ein anderes steht, heißt Stellvertreter (englisch proxy). Ein Programm kann so eine Gruppe benachteiligen, ohne dass in seinen Daten steht, wer zu ihr gehört.

9. Was der Laden tun könnte

Das Programm macht genau das, was es soll: entscheiden wie der Laden. Der Fehler steckt in den Beispielen, aus denen es lernt. Daraus folgt einiges, was der Laden tun könnte:

  • Aus etwas anderem lernen. In on_time steht, ob jemand das nächste Rad wirklich pünktlich zurückgebracht hat. Lernt das Programm daraus statt aus den alten Entscheidungen, lernt es, was tatsächlich passiert ist, nicht, was jemand im Laden vermutet hat.
  • Nachmessen, bevor es entscheidet. Wie oft es Pünktliche trifft, je Stadtteil, hast du eben ausgerechnet. Das lässt sich vor dem ersten Einsatz prüfen und danach immer wieder.
  • Merkmale prüfen, auch die, die harmlos aussehen, wie die Entfernung.
  • Fragen, ob ein Programm hier überhaupt entscheiden soll. Eine Kaution von 50 Euro ist für manche Menschen viel Geld. Wer sie verlangt, sollte sagen können, warum.
Das Programm verlangt von einem Kunden am Sandberg eine Kaution, und er fragt, warum. Was kann der Laden ihm sagen? Wer ist verantwortlich: der Laden, die Person, die das Programm geschrieben hat, oder das Programm?

Die Datenschutz-Grundverordnung hat dazu eine Regel: Nach Artikel 22 hat jeder Mensch das Recht, keiner Entscheidung unterworfen zu werden, die nur ein Programm getroffen hat und die ihn erheblich beeinträchtigt. Es gibt Ausnahmen, etwa wenn die Entscheidung für einen Vertrag nötig ist; dann muss ein Mensch sie auf Wunsch überprüfen, und die betroffene Person darf ihren Standpunkt darlegen und die Entscheidung anfechten. Ein Mensch bleibt verantwortlich, auch wenn ein Programm entscheidet.

10. Wenn es wirklich passiert

Was dein Programm mit ausgedachten Kunden macht, passiert mit echten Menschen. Die Informatikerin Joy Buolamwini hat es an Programmen gezeigt, die Gesichter erkennen.

Porträt von Joy Buolamwini vor dunklem Hintergrund, mit lockigem Haar und einer roten Brille, in weißer Jacke über einem lachsfarbenen Oberteil.

Joy Buolamwini, kanadisch-US-amerikanische Informatikerin. Am MIT Media Lab baute sie einen Spiegel, der Gesichter erkennen sollte; ihr Gesicht erkannte die Software erst, als sie eine weiße Maske aufsetzte. Sie gründete die Algorithmic Justice League, die Verzerrungen in Programmen aufdeckt, und schrieb darüber 2023 das Buch »Unmasking AI«.

Wie du am Sandberg hat sie die Fehler eines Programms nach Gruppen getrennt gezählt.

Foto: Niccolò Caranti, 2018, CC BY-SA 4.0, zugeschnitten (Wikimedia Commons)

2018 prüften sie und die Informatikerin Timnit Gebru drei Programme großer Firmen, die auf einem Foto erkennen sollten, ob es eine Frau oder einen Mann zeigt. Bei Männern mit heller Haut lagen sie fast nie falsch, höchstens in 0,8 Prozent der Fälle. Bei Frauen mit dunkler Haut lag eines in 34,7 Prozent der Fälle falsch. Die Sammlungen von Fotos, an denen man solche Programme bis dahin gemessen hatte, zeigten vor allem Menschen mit heller Haut; dass die Programme bei anderen schlechter sind, fiel so nicht auf. Nach der Studie haben IBM und Microsoft ihre Programme überarbeitet.

Gender Shades: Intersectional Accuracy Disparities in Commercial Gender Classification, Joy Buolamwini und Timnit Gebru, Conference on Fairness, Accountability and Transparency, 2018. Die Studie, die die Fehler dreier Programme nach Hautfarbe und Geschlecht getrennt zählt: PDF, auch bei den Proceedings of Machine Learning Research.

Aufgabe 15Wer entscheidet hier?

Der Laden will das Programm einsetzen. Was rätst du ihm? Geh dabei auf deine Zahlen ein und darauf, wer verantwortlich ist, wenn das Programm jemanden zu Unrecht eine Kaution zahlen lässt.

Mindestens 30 Wörter. Nur du und deine Lehrkraft können die Antwort lesen.

11. Zum Nachschlagen