Uczenie maszynowe od podstaw · Lekcja 01 · Część 2
Twój pierwszy neuron — w PyTorch
Zamień ręczne obliczenia z części 1 w program i pozwól, by gradient policzył za Ciebie PyTorch.
Zanim zaczniesz
Najpierw ukończ część 1: ta część korzysta z jej danych, pierwszej aktualizacji i wyników. Potrzebujesz podstaw Pythona (zmienne, listy, pętle, wcięcia) i zwykłego komputera; GPU nie jest potrzebne.
Zaplanuj około jednej–dwóch godzin w jednej lub dwóch sesjach: do pół godziny na instalację Pythona i PyTorch, resztę na laboratoria i ćwiczenia. To szacunek; poświęć laboratoriom tyle czasu, ile potrzebują.
Po tej części będziesz umieć wyjaśnić każdą linię pętli treningowej, porównać gradienty z PyTorch z własnymi obliczeniami i rozpoznać przebieg, który się rozbiega.
Powtórka — co już wiesz z części 1
W części 1 stratę, gradienty i aktualizacje parametrów liczyliśmy samodzielnie, ręcznie. Już wiesz, jak ten model się uczy. Część 2 wykonuje dokładnie te same operacje; matematyka się nie zmienia. PyTorch przechowuje liczby jako tensory i automatycznie liczy gradient. Model, funkcję straty i współczynnik uczenia nadal wybierasz Ty i nadal Ty piszesz aktualizację.
| Część 1 — ręcznie | Część 2 — PyTorch |
|---|---|
| Oblicz predykcje | prediction = x * w + b |
| Oblicz błąd średniokwadratowy: | loss = torch.mean(...) |
| Wyprowadź pochodne cząstkowe: , | loss.backward() |
| Zaktualizuj parametry ręcznie: , | w -= lr * w.grad |
| Powtórz obliczenia | for epoch in range(...) |
Miej te liczby pod ręką. W laboratorium A PyTorch ma odtworzyć każdą z nich; jeśli wynik się nie zgadza, błąd jest w kodzie, nie w matematyce.
1. Tensory — liczby, na których działa PyTorch
Tensor to tablica liczb o określonym kształcie i typie danych. Pojedyncza liczba to tensor zerowymiarowy (skalar); szereg pięciu liczb to tensor jednowymiarowy o kształcie (5,); siatka jest dwuwymiarowa. „Wymiar” oznacza tu liczbę osi, a nie liczbę wartości.
Wejścia i cele mają kształt (5,), a waga i bias są skalarami o kształcie (). Mnożenie tensora pięcioelementowego przez skalar stosuje skalar do każdego elementu. To broadcasting. Predykcje i błędy zachowują kształt (5,), a ich średnia jest skalarem. Używamy liczb zmiennoprzecinkowych, bo aktualizacje i gradienty są ułamkami.
Kształty mają znaczenie. Gdyby cele miały kształt (5, 1), a predykcje (5,), broadcasting po cichu porównałby każdą predykcję z każdym celem — siatkę (5, 5) — i policzył złą stratę.
2. Autograd — PyTorch liczy gradient
W części 1 liczyliśmy predykcje, potem błędy, ich kwadraty i średnią. To przejście w przód (forward pass). PyTorch zapisuje te kroki jako graf obliczeń. Wywołanie backward() przechodzi graf w odwrotnej kolejności i na każdym kroku stosuje regułę łańcuchową; to propagacja wsteczna (backpropagation). Mechanizm, który robi to automatycznie, nazywa się automatycznym różniczkowaniem, a jego wersja w PyTorch to autograd.
- Parametry + wejścia → predykcje
- Predykcje − cele → błędy
- Kwadraty błędów → średnia → strata
- Przejście wstecz → gradient
- Optymalizator używa gradientu → nowe parametry → powtórzenie
Autograd liczy dokładnie te wzory, które wyprowadzono ręcznie w części 1:
Nie aktualizuje parametrów. Kod, który je zmienia, to optymalizator. PyTorch ma gotowe optymalizatory, ale w tej lekcji napiszesz własny w dwóch liniach.
3. Laboratorium z instrukcjami
Przygotuj środowisko CPU
Użyj Pythona 3.10–3.14. PyTorch 2.14 nie ma jeszcze pakietów dla nowszych wersji, więc na Pythonie 3.15 pip install torch kończy się błędem „No matching distribution found”. Na Macu z Homebrew uruchom brew install python@3.13 i utwórz środowisko poleceniem python3.13 -m venv .venv zamiast pierwszej linii poniżej. Jeśli instalacja nie działa z innego powodu, skorzystaj z konfiguratora instalacji PyTorch. W Windows aktywuj środowisko przez .venv\Scripts\Activate.ps1 w PowerShell.
python3 -m venv .venv
source .venv/bin/activate
python -m pip install torch
python -c "import sys, torch; print(sys.version); print(torch.__version__)"
python train.py
PyTorch to jedyny potrzebny pakiet; nie trzeba pobierać żadnych danych. Zanotuj wersje Pythona i PyTorch wypisane przez polecenie kontrolne i zachowaj je razem z wynikami. Program nie używa losowości, więc nie potrzebuje ziarna losowości.
Sprawdzono na CPU z Pythonem 3.12.14 i PyTorch 2.14.1.
Zacznij od małego kroku: odtwórz jeden stan
Zanim napiszesz całą pętlę, sprawdź, czy PyTorch zgadza się z Twoimi obliczeniami. Ten fragment ustawia w i b na wartości po pierwszej aktualizacji z części 1 i tylko mierzy model; niczego nie uczy.
Pokaż przejście w przód
import torch
x = torch.tensor([1., 2., 3., 4., 5.])
y = torch.tensor([20., 40., 60., 80., 100.])
w = torch.tensor(0.44)
b = torch.tensor(0.12)
prediction = x * w + b
print(prediction)
print(torch.mean((prediction - y) ** 2).item())x i y przechowują pięć próbek, a w i b stan po jednej aktualizacji. Nie ma tu requires_grad ani backward, bo niczego nie trenujemy. Oczekuj [0.56, 1.00, 1.44, 1.88, 2.32] i MSE około 4194,4608 — tak jak w przykładzie obliczeniowym z części 1.
Kompletny program
Pokaż kompletny program
import torch
x = torch.tensor([1., 2., 3., 4., 5.])
y = torch.tensor([20., 40., 60., 80., 100.])
w = torch.tensor(0.0, requires_grad=True)
b = torch.tensor(0.0, requires_grad=True)
lr = 0.001
for epoch in range(1000):
prediction = x * w + b
loss = torch.mean((prediction - y) ** 2)
w.grad = None
b.grad = None
loss.backward()
if epoch % 100 == 0:
print(f"epoch={epoch:4d} loss={loss.item():.3f} w={w.item():.3f} b={b.item():.3f}")
with torch.no_grad():
w -= lr * w.grad
b -= lr * b.grad
with torch.no_grad():
print("Final MSE:", torch.mean((x * w + b - y) ** 2).item(), "w:", w.item(), "b:", b.item())
print("Prediction for 7:", (7 * w + b).item())Czytanie programu linia po linii
import torch- Wczytuje tensory i autograd z PyTorch.
x = torch.tensor(...)iy = torch.tensor(...)- Pięć wejść i odpowiadające im cele, w tej samej kolejności. Kropki dziesiętne tworzą liczby zmiennoprzecinkowe. Oba tensory mają kształt (5,) i żaden nie jest trenowany.
w = torch.tensor(0.0, requires_grad=True)i analogiczna liniab- Dwa trenowane parametry jako skalary.
requires_grad=Truekaże autogradowi śledzić ich wpływ na stratę. Start od zera wystarcza dla pojedynczego neuronu; w sieciach z wieloma warstwami już nie, a jedna z kolejnych lekcji wyjaśnia dlaczego. lr = 0.001- Współczynnik uczenia . Wybierasz go Ty; program nigdy go nie zmienia.
for epoch in range(1000):- 1000 aktualizacji na pełnym batchu, numerowanych od 0 do 999. Każdy nowy gradient liczony jest dla parametrów z poprzedniej aktualizacji.
prediction = x * w + b- Przejście w przód dla pięciu próbek naraz. Broadcasting współdzieli jedno w i jedno b między nimi.
loss = torch.mean((prediction - y) ** 2)- Błędy, kwadraty, średnia: dokładnie MSE z części 1.
w.grad = Noneib.grad = None- PyTorch domyślnie dodaje nowe gradienty do starych. Wyczyszczenie zostawia tylko bieżący gradient. Czyści to gradient, nie parametr.
loss.backward()- Wykonuje propagację wsteczną i zapisuje oraz w
w.gradib.grad. Nie zmienia w ani b. if epoch % 100 == 0:iprint(...)- Raportuje co setną epokę, przed aktualizacją, więc strata, w i b w jednej linii dotyczą tego samego stanu; epoka 0 to model przed treningiem.
.item()zamienia tensor skalarny na liczbę Pythona, a:.3fzaokrągla tylko wydruk. with torch.no_grad():- Wyłącza zapisywanie operacji przez autograd. Aktualizacja nie jest częścią obliczania straty, a PyTorch pozwala zmieniać te tensory w miejscu tylko poza grafem.
w -= lr * w.gradib -= lr * b.grad- Optymalizator: spadek gradientowy, jak w części 1. Oba gradienty pochodzą z tego samego przejścia w przód, więc aktualizacja jest jednoczesna, choć zajmuje dwie linie.
- Końcowe
with torch.no_grad():i dwa wywołaniaprint - Mierzą wytrenowany model i prognozują wynik dla x = 7 bez zapisywania grafu. Użycie modelu bez trenowania go to inferencja. Końcowa strata nadal jest liczona na próbkach treningowych, więc nic nie mówi o nowych danych.
Laboratorium A — zatrzymaj się po jednej aktualizacji
- Przed uruchomieniem zapisz początkową stratę, oba gradienty i nowe parametry z przykładu obliczeniowego z części 1.
- W kopii pliku zmień
range(1000)narange(1). Zaraz poloss.backward()dodajprint(w.grad.item(), b.grad.item()). - Uruchom kopię. Oczekuj −440 i −120, a potem w ≈ 0,44, b ≈ 0,12 i końcowego MSE ≈ 4194,4608. Drobne różnice w ostatnich cyfrach wynikają z arytmetyki zmiennoprzecinkowej.
- Jeśli po aktualizacji ponownie wypiszesz pierwotne
loss, nadal zobaczysz 4400. Dlaczego? To wartość obliczona wcześniej, a nie formuła, która sama się przelicza; aby zmierzyć nowy stan, trzeba ponownie wykonać przejście w przód.
Laboratorium B — trenuj, obserwuj, wyjaśniaj
- Uruchom oryginalny program z 1000 aktualizacjami. Pierwszy raport to
epoch= 0 loss=4400.000 w=0.000 b=0.000. - Końcowy wynik powinien być bliski MSE 2,45, w 18,98, b 3,67 i prognozy 136,56 dla x = 7. Sprawdź dla parametrów wypisanych przez Twoje uruchomienie.
- Powtórz dla współczynników 0, 0,001, 0,05 i 0,1, za każdym razem od zera. Zapisz współczynnik, początkową i końcową stratę oraz to, czy strata pozostała skończona. Przy 0,1 strata przekracza zakres liczb i staje się
inf, a potemnan(„not a number”): przebieg się nie udał, a jego prognoza nic nie znaczy. - Wyjaśnij, dlaczego malejąca strata treningowa nie dowodzi trafności na nowych danych. Wszystkie pięć punktów posłużyło do treningu; nic nie zostało odłożone do testu.
Reguła, według której wymyślono dane, daje 140 dla siedmiu godzin, ale siedem leży poza zakresem treningowym [1, 5]. Prognozowanie poza zakresem, na którym trenowano model, to ekstrapolacja. Wynik przekracza też typowe maksimum egzaminu wynoszące 100 punktów, bo prosta nie ma górnej granicy.
Częste nieporozumienia
- „Backward trenuje wagi”. Wypisz w przed
backward()i po nim, a potem po aktualizacji. Który krok naprawdę je zmienia? - „Ujemny gradient to ujemny błąd”. Błąd dotyczy jednej predykcji; gradient łączy wszystkie pięć próbek. Jaką rolę gra x w gradiencie względem wagi?
- „Większy współczynnik zawsze przyspiesza naukę”. Porównaj 0,05 i 0,1.
- „Zerowy gradient oznacza idealny model”. Oznacza płaski punkt funkcji straty. Przy zaszumionych danych albo zbyt prostym modelu najniższa strata nadal może być większa od zera.
- „Czyszczenie gradientu to zerowanie parametru”. Czyść
.grad, nie w ani b. Jaki postęp by przetrwał, gdyby każda epoka zerowała wagi? - „Niskie MSE to trafność na nowych danych”. MSE mierzy dopasowanie do próbek treningowych. Jakich dowodów nadal brakuje?
4. Ćwiczenia
Każde ćwiczenie zacznij od nowej kopii train.py. Najpierw zapisz przewidywanie, potem napisz kod.
- Zacznij od , i wykonaj jedną aktualizację. Porównaj gradienty z autograd i nowe parametry ze swoją odpowiedzią do ćwiczenia 1 z części 1.
- Zamroź b na zerze, czyli go nie aktualizuj, i trenuj tylko w. Dlaczego ten zbiór nadal da się dopasować dokładnie? Teraz użyj celów [25, 45, 65, 85, 105]: co tracisz przez zamrożony bias?
- Zapisz stratę dla aktualizacji 0–100 i narysuj wykres na papierze lub w wybranym narzędziu, z opisanymi osiami (aktualizacje; MSE w punktach²). Porównaj dwa współczynniki z tego samego punktu startowego.
- Użyj wytrenowanego modelu dla x = 3,5 i x = 7. Która prognoza to interpolacja, a która ekstrapolacja? Dlaczego żadna z nich nie mówi, jak dobrze model uogólnia?
Wskazówki
Zamrożenie parametru oznacza pominięcie linii z jego aktualizacją. Aby uczciwie porównać przebiegi, za każdym razem twórz tensory startowe od nowa. Interpolacja pozostaje w zakresie wejść, na których trenowano model.
5. Sprawdzenie rozumienia — wyjaśnij pętlę treningową
Odpowiedz bez przewijania w górę. Poprzyj każdą odpowiedź uruchomionym kodem lub obserwowanym wynikiem.
- Co stanie się z predykcjami, jeśli wielokrotnie wywołasz
backward(), ale usuniesz blok aktualizacji? A jeśli zostawisz aktualizacje, ale przestaniesz czyścić gradienty? - Przebieg ze współczynnikiem 0,1 wypisuje
nan. Co poszło źle i dlaczego to nie jest błąd PyTorch? - Przebieg zwraca 140 dla x = 7. Co to mówi o implementacji, a czego nie mówi o prawdziwych wynikach egzaminów?
Omówienie rozumowania — otwórz po zapisaniu odpowiedzi
Samo backward() wypełnia tylko bufory gradientów; predykcje się nie zmieniają. Bez czyszczenia każde przejście wstecz dodaje się do poprzednich gradientów, więc każdy krok używa sumy starych gradientów: to inny algorytm, który tutaj szybko traci stabilność. Przy 0,1 każdy krok przeskakuje dalej niż poprzedni, aż liczby float32 przekroczą zakres i staną się nieskończonością, a potem NaN — to ta sama rozbieżność, którą widać było w symulacji z części 1. Prognoza 140 zgadza się z regułą, według której wymyślono dane, więc kod działa. Ale dane są syntetyczne, a x = 7 leży poza zakresem treningowym, więc nie mówi to nic o prawdziwych osobach zdających egzamin.
Lista kontrolna
Zachowaj kod, wypisane wersje, wykres straty i tabelę współczynników. Pola wyboru to przypomnienia dla Ciebie, nie test.
Zapisywane tylko w tej przeglądarce.
Źródła i dalsza nauka
Samouczek tensorów PyTorch pomoże zgłębić kształty, a samouczek autograd PyTorch — różniczkowanie. Lekcja 02 wprowadzi bogatsze operacje tensorowe i sieci nieliniowe.