Jak budujemy pierwszy prawdziwy algorytm n+1 do nauki języka
To jest tekst dla ludzi, których interesuje inżynieria pod spodem, a nie wycieczka po funkcjach produktu. Jeśli chcesz wiedzieć, co dzieje się między „zapisuję się na język" a „widzę kolejne zdanie", czytaj dalej. I jak sprawdzamy, czy to zdanie w ogóle było warte pokazania.
Piotr Woźniak zbudował SuperMemo pod koniec lat 80. i przy okazji zamienił powtórki w odstępach z wyniku z literatury psychologicznej w coś, co uczący się może odpalać co rano. Sama zasada była znana od stu lat. Nie istniał algorytm, który stosowałby ją do twojej pamięci i twojego materiału. Ta luka między uzgodnioną zasadą a działającą implementacją to dokładnie miejsce, w którym stoimy teraz. Tylko z inną zasadą.
Comprehensible input siedzi w tej samej sytuacji od lat 80. Prawie każdy, kto uczy języka, zgadza się, że materiał ma być o krok przed tym, co uczący się już umie. Prawie nikt nie potrafi powiedzieć, czy konkretne zdanie faktycznie takie jest. Nie znamy produktu, który próbowałby to udowodnić zdanie po zdaniu. To jest nasze podejście do tego algorytmu, a pomiary niżej to uczciwy raport z tego, ile jest na razie warte. Historię SuperMemo opisaliśmy tutaj.
Przebudowujemy rdzeń Taalhammera wokół jednej reguły, a potem większość wysiłku wkładamy w udowodnienie, że ta reguła jest przestrzegana. Ten tekst opisuje obie połowy: jak silnik jest zbudowany i jak testujemy, czy uczy.
Jedna reguła
Celem projektowym jest comprehensible input Stephena Krashena, zapisywany też jako i+1 (Krashen, Principles and Practice in Second Language Acquisition). Język przyswajasz wtedy, gdy wejście jest o mały krok przed twoim poziomem. Jest zrozumiałe, bo prawie wszystko w nim już znasz, plus dokładnie jeden nowy element.
Zamieniamy to w twarde ograniczenie nałożone na każde zdanie, które silnik produkuje:
Każde zdanie wprowadza dokładnie jeden nowy element. Jeśli nowym elementem jest struktura gramatyczna, każde słowo i każda inna struktura w zdaniu muszą być już znane. Jeśli nowym elementem jest słowo, każda struktura i każde inne słowo muszą być już znane.
Ciekawa część jest taka, że nie ufamy modelowi językowemu, że tej reguły dotrzyma. Sprawdzamy go.
Co silnik śledzi: cechy, nie lekcje
Klasyczny kurs dowozi stałą listę lekcji w stałej kolejności. Nasz silnik nie ma lekcji. Śledzi cechy, a każda cecha to niezależnie planowalna jednostka wiedzy:
- Słowa, uszeregowane po frekwencji i wybrane pod CEFR. Lista dla początkujących zaczyna się od rzeczowników i czasowników, które naprawdę są potrzebne, a nie od surowego zrzutu frekwencyjnego.
- Struktury gramatyczne, inwentarz ponad 200 na język, ważony od A1 do C2, każda z przykładowymi zdaniami.
- Zwroty i chunki, jednostki wielowyrazowe uczone jako jeden element (
avere fame, a nie „mieć głód"). Płynny język składa się z kolokacji, nie z pojedynczych słów. - Formy czasownikowe, trzymane jako
lemat@czas(na przykładsein@present). Dzięki temu „znam tryb przypuszczający essere" to inny fakt niż „znam jego coniunctivo", a każdy śledzimy osobno. - Liczby, daty i godziny, mały system generatywny ćwiczony osobno od listy słów. „Wpół do ósmej" i „tysiąc dziewięćset sześćdziesiąty ósmy" idą z reguł, a nie z frekwencji.
Stan jest podzielony tak jak w reszcie systemu. Twój stan nauki siedzi w Postgresie (konta, harmonogram, pewność per cecha), a sama treść, czyli zdania, kolekcje i referencje do audio, w Elasticsearchu.
Jak poznajemy twój poziom, nie pytając o niego
Nigdy nam nie mówisz, na jakim jesteś poziomie. Silnik odkrywa to bayesowskim testem adaptacyjnym na dwóch osiach naraz: poziom gramatyki i granica słownictwa. To ta sama maszyneria statystyczna co w egzaminie komputerowo-adaptacyjnym, czyli item response theory nad dwuwymiarową oceną zdolności. Każda sonda aktualizuje rozkład a posteriori po obu osiach, a kolejną wybieramy tak, żeby najszybciej zbić jego niepewność. W symulacji zbiega do około ±0,4 CEFR w mniej więcej 11 sondach. Odbija też, jeśli akurat na starcie miałeś wyjątkowo dobry albo wyjątkowo słaby dzień.
Jak powstaje zdanie, które naprawdę rozumiesz
Gdy poziom jest znany, kolejne zdanie powstaje w małym potoku:
- Wybór celu: jeden nowy element do wprowadzenia (struktura, słowo, zwrot, forma czasownika albo liczba) plus paleta słów, które już znasz, z których zbudujemy resztę zdania.
- Generacja: duży model językowy klasy GPT pisze zdanie, które ćwiczy cel, używając wyłącznie znanej palety.
- Dowód: ekstraktor cech dla danego języka czyta wygenerowane zdanie od nowa. Wypisuje każdą strukturę i każdy lemat, jaki w nim siedzi. Zbudowaliśmy go na analizie zależnościowej i morfologicznej spaCy plus ręcznie pisanych regułach. Jeśli przecieknie cokolwiek nieznanego poza jednym celem, zdanie leci do kosza i powstaje od nowa.
To jest krok, który zamienia „miękkie i+1" w sprawdzone. Warto być precyzyjnym co do tego, czego ten test dowodzi, a czego nie. Jest jednokierunkowy: weryfikuje, że nic nieznanego nie przeciekło, a nie że cel został wykryty. Mniej więcej ćwierci struktur gramatycznych ekstraktor w ogóle nie umie wykryć, a jego pokrycie inwentarza struktur idzie od około 39% do 76% zależnie od języka. Czyli „brak przecieku" znaczy „brak przecieku wykrywalnego podzbioru". To realne ograniczenie, które łapie realne naruszenia. To nie jest dowód. 4. Bramka gramatyczna: drugi model, celowo inny niż generator, poświadcza, że zdanie jest poprawne gramatycznie, a tłumaczenie wierne. Inaczej je naprawia albo odrzuca. Inny model bierze się stąd, że model sprawdzający własne wyjście jest ślepy na własne błędy.
Około 70% zdań przechodzi ekstraktor za pierwszym razem, przy średnio dwóch podejściach. Reszta powstaje ponownie, a te około 30%, których nadal nie da się zbudować czysto, są serwowane z flagą, a nie po cichu. Te oflagowane zdania to prawdziwe naruszenia i+1, które weryfikator złapał: albo model dołożył strukturę powyżej granicy, albo ekstraktor dał fałszywy alarm. Serwujemy je z flagą, bo uczciwa flaga bije cichy przeciek. Ale są serwowane i liczą się na naszą niekorzyść.
Jest też limit długości. Sens zdania i+1 polega na tym, że testowany jest jeden element. Więc to cel dostaje budżet złożoności, a wszystko dookoła zostaje krótkie i proste. Trudna struktura nie ląduje zakopana pod stosem przymiotników, a nowe słowo nie ginie w przypadkowej składni.
Serwowanie: N, potem N+1
W czasie działania silnik pracuje w dwóch trybach i sam się między nimi przełącza:
- N, powtórka. Serwuje to, co według powtórek w odstępach jest na dziś: słowa i zwroty pokazywane wprost, czyli fiszki, oraz struktury sprawdzane ponownie w świeżych zdaniach. Jedno zdanie powtórkowe potrafi połączyć kilka zaległych elementów zbudowanych wyłącznie ze znanego materiału, więc jedno zdanie zbija kilka powtórek naraz.
- N+1, wprowadzenie. Kiedy nic nie czeka, silnik wprowadza dokładnie jeden nowy element. To jest prawdziwe zdanie i+1.
Pierwszy dzień to same N+1, bo nie ma jeszcze czego powtarzać. Potem każda sesja miesza oba tryby. Uczącego się nikt nie pyta „powtarzasz czy się uczysz?". Silnik decyduje z tego, co jest na dziś.
Powtórki w odstępach i model pamięci
Harmonogram to miejsce, w którym zapada decyzja „kiedy pokazać to znowu". Silnik używa FSRS-6, aktualnej wersji Free Spaced Repetition Scheduler. To ten sam otwartoźródłowy, uczony maszynowo model pamięci, który Anki wozi dziś jako swój scheduler. FSRS opisuje każdy element trzema wielkościami, czyli modelem DSR. Odtwarzalność to prawdopodobieństwo, że przypomnisz sobie coś teraz. Stabilność to liczba dni, w których to prawdopodobieństwo spada ze 100% do 90%. Trudność idzie w skali 1–10. Krzywa zapominania jest funkcją potęgową, R(t) = (1 + FACTOR · t/S)^decay, bo prawdziwe zapominanie ma długi ogon, którego zwykła wykładnicza nie łapie.
Modelem uczonym maszynowo, a nie zwykłym wzorem, czyni go sposób ustawiania jego 21 parametrów. Trenuje się je tak jak małą sieć rekurencyjną, na logu powtórek uczącego się. Metodą jest propagacja wsteczna w czasie z estymacją największej wiarygodności. Stabilność karty jest przy tym przenoszona między powtórkami jak stan ukryty. Parametry zostają interpretowalne, bo one są odtwarzalnością, stabilnością i trudnością. To odróżnia FSRS od sieci-czarnej-skrzynki i od sztywnych wzorów z lat 80. Publiczne wagi FSRS-6 dopasowano na jakichś 700 milionach powtórek od około 20 tysięcy ochotników. My puszczamy własne py-fsrs 6.3.1 z zestawem 21 wag. Do harmonogramu bierzemy docelowe odtworzenie 0,9, a FSRS rozwiązuje swoją krzywą zapominania dla momentu, w którym odtworzenie spadłoby do tego poziomu. To jest kolejny interwał. Powtórka robi najwięcej roboty, gdy trafia tuż przed tym, jak byś zapomniał. To efekt pożądanej trudności.
Na schedulerze siedzi jeszcze pewność per cecha, wartość między 0 a 1, korygowana przy każdej obserwacji wykładniczą średnią kroczącą. Bezpośrednio sprawdzona poprawna odpowiedź rusza ją mocno. Słowo użyte jako rusztowanie rusza ją ledwo. Odpowiedź „dobrze, ale wolno" albo „prawie" obniża pewność, bo to sygnał sprzeczny. Gdy pewność spadnie poniżej progu, cecha dostaje status lapsed: wypada ze zbioru, z którego budujemy zdania, i staje się celem do ponownego nauczenia.
Ten ostatni fragment miał błąd, o którym warto wspomnieć, bo złapały go nasze własne testy. Oblany element jest przeplanowany tak, żeby wrócił prawie natychmiast. Ale ta sama porażka przerzuca też jego stan na lapsed, a zapytanie powtórkowe serwowało wyłącznie elementy w stanie known. Czyli trudne elementy były planowane do ponownego nauczenia i potem nigdy nie serwowane. Znaleźliśmy to, bo świeżo napisany test pokazał, że wraca tylko 19% obalonych elementów. Poprawka: kolejka powtórek serwuje teraz każdy zaległy element, od najbardziej chwiejnych. Czyli to, co ci nie wyszło, jest dokładnie tym, co wraca.
Jak sprawdzamy, czy to w ogóle uczy
Tu jest część, na której zależy nam najbardziej, bo to część, którą większość systemów do nauki języków pomija.
Warstwa pierwsza, rubryki. Trzymamy wykonywalną specyfikację: dziesiątki kryteriów akceptacji podzielonych na twarde niezmienniki i miękkie cele. Twardy niezmiennik to taki, przy którym jedno naruszenie jest błędem, na przykład uczący się nigdy nie widzi dwa razy tego samego wygenerowanego zdania. Miękki cel to na przykład udział zwrotów, który ma trzymać się miksu Core dla danego poziomu. Każda zmiana jest przez to przepuszczana.
Warstwa druga, symulacje czystego lotu. Przepuszczamy sześciu symulowanych uczących się, po jednym na poziom CEFR od A1 do C2, przez żywy serwis z pustej bazy. Dziesięć dni po jakieś pięćdziesiąt odpowiedzi, a potem ocena każdego zaserwowanego elementu względem każdej rubryki. Gdy metryka nie wychodzi, przebieg atrybucyjny sortuje elementy na przyczyny: prawdziwy przeciek od generatora, fałszywy alarm ekstraktora albo artefakt pomiaru. Czerwona liczba staje się posortowaną listą roboty zamiast zagadką.
Warstwa trzecia, uczeń z zewnątrz. Każda rubryka jest proxy. Zaliczenie ich nie dowodzi, że człowiek się uczy. Więc zbudowaliśmy zewnętrzny test, który nie używa żadnego wewnętrznego sygnału silnika.
Uczeń z zewnątrz, w szczegółach
Pomysł: wytrenować ucznia od losowej inicjalizacji na samym serwowanym strumieniu i zmierzyć, czy przyswaja język. „Od zera" jest tu warunkiem nośnym. Celowo nie używamy pretrenowanego dużego modelu językowego. Sieć taka jak GPT-2 czy niemiecki BERT przeczytała już miliardy niemieckich tokenów, więc kilka tysięcy więcej przesuwa jej predykcje o szum. Raportowałaby swój prior, a nie nasz program nauczania. To jest premisa akademickiego BabyLM challenge: trenuj od zera na maleńkim korpusie, tutaj około 2300 zdań, bez pretrenowanych wag. Czysta karta, która efektywnie uczy się niemieckiego z serwowanego strumienia, jest dowodem o programie, niezależnym od tego, co silnik sądzi sam o sobie.
Uczeń jest modelem pamięci per cecha, a nie siecią neuronową. Każda cecha, którą spotka, czyli słowo, struktura gramatyczna, forma czasownika, liczebnik albo zwrot, niesie jedną zmienną stanu: okres półtrwania pamięci h. Prawdopodobieństwo przypomnienia sobie tej cechy spada z upływem czasu t jak p = 2^(−t / h). To Half-Life Regression, model, który Duolingo opublikowało dokładnie do tego problemu (Settles i Meeder, A Trainable Spaced Repetition Model for Language Learning, ACL 2016). Napędzają go dwie reguły aktualizacji, po jednej na każdą połowę N/N+1:
- Comprehensible input, czyli bramka N+1. Uczeń wchłania zdanie tylko wtedy, gdy najwyżej jedna albo dwie jego cechy są jeszcze nieznane. To operacyjna forma i+1 Krashena. Stoi za tym też twardszy wynik: żeby zrozumieć tekst, trzeba znać jakieś 95–98% słów. Podali to Hu i Nation w „Unknown vocabulary density and reading comprehension", Reading in a Foreign Language 13(1), 2000. Zdanie ponad ten budżet to zmarnowana ekspozycja, tak samo jak zdanie C2 zmarnowane na początkującym.
- Zapominanie i odstępy, czyli połowa N. Okres półtrwania wchłoniętej cechy spada z czasem i wydłuża się przy powtórce. Wydłuża się najbardziej wtedy, gdy powtórka trafia przy umiarkowanym odtworzeniu. To ten sam efekt pożądanej trudności, wokół którego zbudowany jest FSRS. Cecha zobaczona raz i nigdy nieodwiedzona jest zapominana. Powtórzona w dobrych momentach zostaje. Model, który nigdy nie zapomina, nie pokazałby nic z tego, więc powtórkowa połowa algorytmu byłaby dla niego niewidzialna.
Trening jest online: jedno zdanie naraz, w kolejności serwowania, więc krzywą uczenia da się czytać wprost. Cechy, które uczeń śledzi, pochodzą z tego samego ekstraktora, którym silnik dowodzi i+1, więc uczeń jest oceniany dokładnie z tego, czego silnik twierdzi, że uczy. Jako kontrolę, że wynik jest własnością programu, a nie jednego modelu, przepuściliśmy te same strumienie przez niezależny model uczony od zera. Był to znakowy model n-gramowy z wygładzaniem Wittena-Bella, które radzi sobie z niemiecką morfologią bez problemu słów spoza słownika. Przy tej samej bramce zrozumiałości pokazuje w tę samą stronę.
Potem robimy A/B z treścią trzymaną na sztywno i zmienianą wyłącznie kolejnością. Dokładnie ten sam zestaw zdań podany na trzy sposoby: adaptacyjna kolejność N/N+1, którą serwuje nasz silnik, sztywna drabina CEFR, czyli „kolejność Core", oraz losowa. Losowa to ten sam zestaw zdań przetasowany w żadną konkretną kolejność, a nie losowy albo bezsensowny tekst. To samo dzienne obciążenie, ten sam odłożony test, ten sam uczeń. Różni się tylko sekwencja i moment powtórek.
Wynik
Mierzone na pełnej trajektorii od A1 do C2. Jeden symulowany uczący się wspina się po całej drabinie, około 2300 unikalnych zdań. Do tego 12 przepróbkowanych przebiegów i dwutygodniowa przerwa na zapominanie. Uczeń przyswaja tu każdą cechę, której silnik uczy: słowa, struktury gramatyczne, liczebniki, formy czasownikowe i zwroty, każdą jako osobny ślad pamięciowy.
| Program | Cechy pamiętane po 14 dniach | Zmarnowana ekspozycja (niezrozumiała) |
|---|---|---|
| Adaptacyjny N/N+1 (nasz silnik) | 174,1 ± 3,2 | 23,8% |
| Sztywna drabina CEFR | 170,6 ± 4,8 | 25,3% |
| Losowy | 94,4 ± 12,0 | 59,6% |
Dominującym wynikiem jest comprehensible input. I adaptacyjny silnik, i sztywna drabina CEFR zostawiają uczącego się z około 80% większą liczbą pamiętanych cech niż kolejność losowa i marnują mniej niż połowę ekspozycji. Ta różnica jest duża i statystycznie czysta: adaptacyjny nad losowym to +79,7 ± 10,3 zapamiętanych cech i 35,8 ± 4,2 punktu procentowego mniej zmarnowanych. Gdy struktury gramatyczne zaczynają się liczyć do tego, czy zdanie jest zrozumiałe, strumień nieuporządkowany pod zrozumiałość robi się niezrozumiały prawie w 60% przypadków. Uczący się bierze z niego niewiele.
Między dwoma programami, które są uporządkowane pod zrozumiałość, różnica jest mała. Adaptacyjny zatrzymuje +3,5 ± 5,0 cechy więcej niż sztywna drabina, co na tym teście nie jest istotne statystycznie. Dobrze zbudowany kurs CEFR sam w sobie jest uporządkowaniem pod comprehensible input. Więc na czystym sekwencjonowaniu ustalonego zestawu zdań adaptacyjny silnik i dobra drabina wychodzą mniej więcej na remis.
Ten pierwszy wynik trzyma treść na sztywno i zmienia tylko kolejność, co izoluje sekwencjonowanie. Ale wyróżnikiem adaptacyjnego silnika nie jest układanie ustalonej talii. Jest nim generowanie innej treści dla każdego uczącego się, o krok przed tym, co ta konkretna osoba już zna. Więc zrobiliśmy drugi eksperyment, który zmienia treść: strumień silnika per uczący się przeciwko prawdziwemu kursowi Core 1–4 Taalhammera. To sztywna sekwencja, którą uczący się szedłby inaczej, około 6900 elementów od A1 do B2. Oba serwowane przez ten sam scheduler powtórek, więc jedyną różnicą jest sama treść.
Decyzją projektową, która to umożliwia, jest to, że nie testujemy jednego uczącego się. Symulujemy szereg typów, bo uczciwa odpowiedź na „czy adaptacyjny jest lepszy?" zależy od tego, kto pyta. Nowe cechy, które każdy typ pamięta po dwóch tygodniach:
| Uczący się | Adaptacyjny | Core | Wygrywa |
|---|---|---|---|
| A1 (absolutnie początkujący) | 20 | 34 | Core |
| A2 | 21 | 14 | Adaptacyjny |
| B1 | 15 | 12 | Adaptacyjny |
| B2 | 9 | 7 | Adaptacyjny |
| C1 (powyżej sufitu B2) | 12 | 7 | Adaptacyjny |
| C2 (powyżej sufitu B2) | 14 | 7 | Adaptacyjny |
| B1, nierówny (mocna gramatyka, słabe słownictwo) | 24 | 15 | Adaptacyjny |
| B2, nierówny | 21 | 13 | Adaptacyjny |
(Wczesne liczby z pojedynczych przebiegów symulacji. Rzędy wielkości dopinamy powtarzanymi przebiegami i przedziałami ufności, więc czytaj wzór, a nie miejsca po przecinku.)
To czysty crossover i uczciwsza historia niż „adaptacyjny zawsze wygrywa". Kurs zbudowany pod początkujących bije adaptacyjny silnik u prawdziwego A1, bo kurs dla początkujących jest u początkującego trudny do pobicia. Od A2 w górę silnik prowadzi, i tym mocniej, im dalej uczący się siedzi od tego przeciętnego początkującego. Skromnie na własnych poziomach kursu, mocno tam, gdzie kurs jednego rozmiaru strukturalnie nie może pasować do osoby. Uczący się powyżej sufitu B2 nie ma już nowego materiału w kursie, który kończy się na B2. Nierówny uczący się potrzebuje słownictwa, którego kurs ułożony po gramatyce nie potrafi wyciągnąć osobno. Wersja skrócona: dla przeciętnego uczącego się na ścieżce kursu dobry kurs jest mocnym punktem odniesienia. Adaptacyjność jest warta tyle, ile sięga tam, gdzie sztywny kurs nie sięga.
Czego NIE twierdzimy
- Liczby o personalizacji są wczesne: pojedyncze przebiegi symulacji na typ uczącego się, jeszcze bez przedziałów ufności. Czytaj wzór, czyli kto wygrywa i mniej więcej o ile, a nie dokładne liczby, dopóki powtarzane przebiegi nie postawią przy nich słupków błędu.
- Przedziały ufności przy sekwencjonowaniu pokrywają zmienność podziału testowego i parametrów modelu, ale pochodzą z jednego wygenerowanego strumienia. Ostateczne liczby wymagają kilku niezależnie wygenerowanych strumieni.
- Krzywa zapominania jest rozsądnym ustawieniem domyślnym, a nie kalibracją na prawdziwej historii powtórek w Taalhammerze. Więc porównanie między programami się trzyma, ale bezwzględne liczby retencji są względne wobec modelu.
- To jest model, a nie człowiek. Ostatnie słowo należy do badania retencji na ludziach.
Co dalej
Kierunek, w który pchamy najmocniej, to sama przestrzeń uczących się: mapowanie, które typy zyskują na adaptacyjności najwięcej. Wynik wyżej już pokazuje, dlaczego to jest właściwe pytanie. „Czy adaptacyjny jest lepszy?" nie ma jednej odpowiedzi. Zależy od tego, kim jest uczący się. Uczciwym wyjściem z tych badań nie jest jedna nagłówkowa liczba, tylko mapa tego, kto zyskuje i ile. Przeszliśmy absolutnie początkującego, uczącego się na ścieżce na każdym poziomie, uczącego się powyżej sufitu i nierównego. Następni są uczący się poza tempem, czyli szybsi albo wolniejsi, niż zakłada sztywny kurs, oraz drobniejsze profile pomiędzy. Każdy z powtarzanymi przebiegami, żeby marginesy niosły przedziały ufności, a nie punktowe oszacowania z jednego przebiegu. Wolimy raportować tę mapę niż średnią, która ją chowa.
Więcej o tym, co robi warstwa audio i fonetyki, jest w technologii, a o badaniach, na których się opieramy, w nauce.
Jeśli budujesz systemy do nauki języków, chętnie pogadamy. O ekstraktorze cech, o pętli „zweryfikuj i wygeneruj od nowa", o metodologii ucznia z zewnątrz. Odezwij się.