Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen der Seite angezeigt.
Beide Seiten, vorherige Überarbeitung Vorherige Überarbeitung Nächste Überarbeitung | Vorherige Überarbeitung | ||
talit:tutorial_oop3 [2025-05-11 14:25] – [Score berechnen] hof | talit:tutorial_oop3 [2025-05-26 14:11] (aktuell) – [Aufgabe F] hof | ||
---|---|---|---|
Zeile 132: | Zeile 132: | ||
< | < | ||
+ | |||
### Vererbung | ### Vererbung | ||
Zeile 200: | Zeile 201: | ||
* `0` bedeutet, dass das Wort unbekannt ist oder immer falsch war. | * `0` bedeutet, dass das Wort unbekannt ist oder immer falsch war. | ||
* `1` bedeutet, dass das Wort unendlich mal richtig getestet wurde. | * `1` bedeutet, dass das Wort unendlich mal richtig getestet wurde. | ||
- | * Der neueste | + | * Der neueste |
Es bietet sich an, mit einem Decay zu arbeiten: jedes Mal, wenn ein neuer Wert dazukommt, wird der alte Score mit einem Faktor <1 multipliziert. Mit einem Faktor von 0.5 setzt sich der Score zur Hälfte aus dem neuesten Test, zur anderen Hälfte aus dem bisherigen Score zusammen: | Es bietet sich an, mit einem Decay zu arbeiten: jedes Mal, wenn ein neuer Wert dazukommt, wird der alte Score mit einem Faktor <1 multipliziert. Mit einem Faktor von 0.5 setzt sich der Score zur Hälfte aus dem neuesten Test, zur anderen Hälfte aus dem bisherigen Score zusammen: | ||
$$\begin{aligned} score_{new} &= 0.5 \cdot (test_0 + score_{old}) \\ | $$\begin{aligned} score_{new} &= 0.5 \cdot (test_0 + score_{old}) \\ | ||
&= 0.5 \cdot (test_0 + 0.5 \cdot (test_1 + 0.5 \cdot (test_2 + \ldots))) \\ | &= 0.5 \cdot (test_0 + 0.5 \cdot (test_1 + 0.5 \cdot (test_2 + \ldots))) \\ | ||
- | &= \frac{test_0}{2} + \frac{test_1}{4} + \frac{test_2}{8} + \frac{test_3}{16} + \ldots \end{aligned}$ | + | &= \frac{test_0}{2} + \frac{test_1}{4} + \frac{test_2}{8} + \frac{test_3}{16} + \ldots \end{aligned}$$ |
- | $ | + | |
#### Ausgabe | #### Ausgabe | ||
Nach einem Learning Run möchten wir alle `WordPairs` mit ihren Statistiken ausgeben. Füge eine Methode `print_stats()` zu `VocabularyUnit` hinzu und verwende darin die `__str__` Funktion von `WordPair`. | Nach einem Learning Run möchten wir alle `WordPairs` mit ihren Statistiken ausgeben. Füge eine Methode `print_stats()` zu `VocabularyUnit` hinzu und verwende darin die `__str__` Funktion von `WordPair`. | ||
Zeile 224: | Zeile 226: | ||
100% (32/46) Blume -> flower | 100% (32/46) Blume -> flower | ||
</ | </ | ||
+ | |||
+ | ## Refactoring | ||
+ | |||
+ | Dein Code in `ConsoleLearner.learn()` könnte irgendwie so aussehen: | ||
+ | |||
+ | <code python> | ||
+ | def learn(self, unit): | ||
+ | for pair in unit.pairs: | ||
+ | guess = input(f' | ||
+ | correct = guess == pair.word2 | ||
+ | pair.stats.record(correct) | ||
+ | if correct: | ||
+ | print(' | ||
+ | else: | ||
+ | print(f' | ||
+ | </ | ||
+ | |||
+ | Wir möchten einige Teile dieses Verhaltens anpassen: | ||
+ | ### Auswahl eines Wortes | ||
+ | Statt immer alle Paare durchzugehen, | ||
+ | * zufällige Wahl eines Wortpaars | ||
+ | * gewichtete Auswahl der Wortpaare: je schlechter der Score eines Paars, desto wahrscheinlicher dessen Wahl. | ||
+ | |||
+ | Statt unseren Code oben mit all diesen Varianten vollzukleistern, | ||
+ | |||
+ | <code python> | ||
+ | class LearningStrategy: | ||
+ | def select(self, | ||
+ | pass # has to be implemented by subclasses. | ||
+ | </ | ||
+ | |||
+ | Hier ein Beispiel für das lineare Durchgehen der Paare, wie im ursprünglichen Code: | ||
+ | |||
+ | <code python> | ||
+ | class LinearStrategy(LearningStrategy): | ||
+ | """ | ||
+ | def __init__(self): | ||
+ | self.index = 0 | ||
+ | |||
+ | def select(self, | ||
+ | pair = unit.pairs[self.index % len(unit.pairs)] | ||
+ | self.index += 1 | ||
+ | return pair | ||
+ | </ | ||
+ | |||
+ | Wir passen `ConsoleLearner.learn()` so an, dass eine Strategie mitgeliefert werden kann, aber ein sinnvoller Default-Wert ausgewählt wird: | ||
+ | |||
+ | <code python> | ||
+ | def learn(self, unit, learning_strategy=LinearStrategy()): | ||
+ | for _ in range(len(unit.pairs)): | ||
+ | pair = learning_strategy.select(unit) | ||
+ | guess = input(f' | ||
+ | correct = guess == pair.word2 | ||
+ | pair.stats.record(correct) | ||
+ | if correct: | ||
+ | print(' | ||
+ | else: | ||
+ | print(f' | ||
+ | </ | ||
+ | |||
+ | ### Aufgabe C: Bessere Auswahlstrategien | ||
+ | Schreibe zwei Auswahl-Strategien mit folgenden Eigenschaften, | ||
+ | |||
+ | * `RandomStrategy`: | ||
+ | * `ScoreStrategy`: | ||
+ | * s. [[https:// | ||
+ | |||
+ | Du hast damit das [[wp> | ||
+ | ### Aufgabe D: Wie lange lernen? | ||
+ | Ähnlich wie die Entscheidung über das nächste Wortpaar möchten wir auch die Entscheidung, | ||
+ | |||
+ | Schreibe eine Klasse `StopCriterion` | ||
+ | |||
+ | * `ScoreCriterion`: | ||
+ | * `TimeCriterion`: | ||
+ | * `CountingCriterion`: | ||
+ | * `OrCriterion`: | ||
+ | |||
+ | {{: | ||
+ | |||
+ | Wie sieht die `ConsoleLearner.learn` Methode jetzt aus? | ||
+ | ## Speichern & Lesen | ||
+ | Wir möchten `VocabularyUnits` in eine Datei speichern und von dort wieder lesen können. Es bietet sich an, eine Unit als JSON-Objekt zu speichern. JSON (JavaScript Object Notation) sind Dictionaries, | ||
+ | |||
+ | <code python> | ||
+ | {" | ||
+ | </ | ||
+ | |||
+ | Eine ganze Unit wäre dann eine Liste solcher Objekte: | ||
+ | |||
+ | <code python> | ||
+ | [ | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | ] | ||
+ | </ | ||
+ | ### Aufgabe E: JSON-Serialisierung | ||
+ | |||
+ | * Füge eine Methode `to_dict(self)` zu `WordPair` hinzu, die ein Wort-Paar und seine Stats in ein Dictionary verwandelt und zurückgibt. | ||
+ | * Füge eine Methode `save_to(self, | ||
+ | |||
+ | <code python> | ||
+ | def save_to(self, | ||
+ | json_list = [pair.to_dict() for pair in self.pairs] | ||
+ | import json | ||
+ | with open(filename, | ||
+ | json.dump(json_list, | ||
+ | </ | ||
+ | |||
+ | |||
+ | ### Statische Methoden | ||
+ | Fürs Einlesen kommt die umgekehrte `json.load` Funktion zum Einsatz. Allerdings haben wir noch ein kleines Problem: Eine VocabularyUnit existiert ja noch gar nicht, wenn wir sie einlesen wollen aus der Datei. Wir benötigen also eine Funktion, die nicht an eine bestimmte Unit gebunden ist. Diese werden mit `@staticmethod` annotiert und haben keinen `self` Parameter. Statische Funktionen werden direkt über den Klassennamen aufgerufen. | ||
+ | |||
+ | <code python> | ||
+ | class WordPair: | ||
+ | ... | ||
+ | | ||
+ | @staticmethod | ||
+ | def from_dict(data): | ||
+ | """ | ||
+ | pair = WordPair(data[' | ||
+ | # TODO also read stats if available | ||
+ | |||
+ | |||
+ | class VocabularyUnit: | ||
+ | ... | ||
+ | | ||
+ | @staticmethod | ||
+ | def read_from(filename): | ||
+ | """ | ||
+ | import json | ||
+ | pairs = [] | ||
+ | with open(filename, | ||
+ | json_pairs = json.load(infile) | ||
+ | pairs = [WordPair.from_dict(p) for p in json_pairs] | ||
+ | return VocabularyUnit(pairs) | ||
+ | </ | ||
+ | ### Aufgabe F | ||
+ | Füge statische Methoden zu `VocabularyUnit` und `WordPair` hinzu, um die gespeicherten Daten wieder einlesen zu können. | ||
+ | |||
+ | Ein Beispielprogramm für unseren Code könnte nun so aussehen: | ||
+ | |||
+ | <code python> | ||
+ | from voci import * | ||
+ | |||
+ | filename = ' | ||
+ | unit = VocabularyUnit.read_from(filename) | ||
+ | |||
+ | learner = ConsoleLearner() | ||
+ | try: | ||
+ | learner.learn(unit) | ||
+ | unit.print_stats() | ||
+ | finally: | ||
+ | unit.save_to(filename) | ||
+ | </ | ||
+ | |||
+ | |||
+ | ### Aufgabe G - Webapp | ||
+ | S. auch [[talit: |